| /* |
| * |
| * D-Bus++ - C++ bindings for D-Bus |
| * |
| * Copyright (C) 2005-2007 Paolo Durante <[email protected]> |
| * |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <dbus-c++/debug.h> |
| #include <dbus-c++/connection.h> |
| |
| #include <dbus/dbus.h> |
| #include <string> |
| |
| #include "internalerror.h" |
| |
| #include "connection_p.h" |
| #include "dispatcher_p.h" |
| #include "server_p.h" |
| #include "message_p.h" |
| #include "pendingcall_p.h" |
| |
| using namespace DBus; |
| |
| Connection::Private::Private(DBusConnection *c, Server::Private *s) |
| : conn(c) , dispatcher(0), server(s) |
| { |
| init(); |
| } |
| |
| Connection::Private::Private(DBusBusType type) |
| { |
| InternalError e; |
| |
| conn = dbus_bus_get_private(type, e); |
| |
| if (e) throw Error(e); |
| |
| init(); |
| } |
| |
| Connection::Private::~Private() |
| { |
| debug_log("terminating connection 0x%08x", conn); |
| |
| detach_server(); |
| |
| if (dbus_connection_get_is_connected(conn)) |
| { |
| std::vector<std::string>::iterator i = names.begin(); |
| |
| while (i != names.end()) |
| { |
| debug_log("%s: releasing bus name %s", dbus_bus_get_unique_name(conn), i->c_str()); |
| dbus_bus_release_name(conn, i->c_str(), NULL); |
| ++i; |
| } |
| dbus_connection_close(conn); |
| } |
| dbus_connection_unref(conn); |
| } |
| |
| void Connection::Private::init() |
| { |
| dbus_connection_ref(conn); |
| dbus_connection_ref(conn); //todo: the library has to own another reference |
| |
| disconn_filter = new Callback<Connection::Private, bool, const Message &>( |
| this, &Connection::Private::disconn_filter_function |
| ); |
| |
| dbus_connection_add_filter(conn, message_filter_stub, &disconn_filter, NULL); // TODO: some assert at least |
| |
| dbus_connection_set_dispatch_status_function(conn, dispatch_status_stub, this, 0); |
| dbus_connection_set_exit_on_disconnect(conn, false); //why was this set to true?? |
| } |
| |
| void Connection::Private::detach_server() |
| { |
| /* Server::Private *tmp = server; |
| |
| server = NULL; |
| |
| if (tmp) |
| { |
| ConnectionList::iterator i; |
| |
| for (i = tmp->connections.begin(); i != tmp->connections.end(); ++i) |
| { |
| if (i->_pvt.get() == this) |
| { |
| tmp->connections.erase(i); |
| break; |
| } |
| } |
| }*/ |
| } |
| |
| bool Connection::Private::do_dispatch() |
| { |
| debug_log("dispatching on %p", conn); |
| |
| if (!dbus_connection_get_is_connected(conn)) |
| { |
| debug_log("connection terminated"); |
| |
| detach_server(); |
| |
| return true; |
| } |
| |
| return dbus_connection_dispatch(conn) != DBUS_DISPATCH_DATA_REMAINS; |
| } |
| |
| void Connection::Private::dispatch_status_stub(DBusConnection *dc, DBusDispatchStatus status, void *data) |
| { |
| Private *p = static_cast<Private *>(data); |
| |
| switch (status) |
| { |
| case DBUS_DISPATCH_DATA_REMAINS: |
| debug_log("some dispatching to do on %p", dc); |
| p->dispatcher->queue_connection(p); |
| break; |
| |
| case DBUS_DISPATCH_COMPLETE: |
| debug_log("all dispatching done on %p", dc); |
| break; |
| |
| case DBUS_DISPATCH_NEED_MEMORY: //uh oh... |
| debug_log("connection %p needs memory", dc); |
| break; |
| } |
| } |
| |
| DBusHandlerResult Connection::Private::message_filter_stub(DBusConnection *conn, DBusMessage *dmsg, void *data) |
| { |
| MessageSlot *slot = static_cast<MessageSlot *>(data); |
| |
| Message msg = Message(new Message::Private(dmsg)); |
| |
| return slot && !slot->empty() && slot->call(msg) |
| ? DBUS_HANDLER_RESULT_HANDLED |
| : DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| bool Connection::Private::disconn_filter_function(const Message &msg) |
| { |
| if (msg.is_signal(DBUS_INTERFACE_LOCAL,"Disconnected")) |
| { |
| debug_log("%p disconnected by local bus", conn); |
| dbus_connection_close(conn); |
| |
| return true; |
| } |
| return false; |
| } |
| |
| DBusDispatchStatus Connection::Private::dispatch_status() |
| { |
| return dbus_connection_get_dispatch_status(conn); |
| } |
| |
| bool Connection::Private::has_something_to_dispatch() |
| { |
| return dispatch_status() == DBUS_DISPATCH_DATA_REMAINS; |
| } |
| |
| |
| Connection Connection::SystemBus() |
| { |
| return Connection(new Private(DBUS_BUS_SYSTEM)); |
| } |
| |
| Connection Connection::SessionBus() |
| { |
| return Connection(new Private(DBUS_BUS_SESSION)); |
| } |
| |
| Connection Connection::ActivationBus() |
| { |
| return Connection(new Private(DBUS_BUS_STARTER)); |
| } |
| |
| Connection::Connection(const char *address, bool priv) |
| : _timeout(-1) |
| { |
| InternalError e; |
| DBusConnection *conn = priv |
| ? dbus_connection_open_private(address, e) |
| : dbus_connection_open(address, e); |
| |
| if (e) throw Error(e); |
| |
| _pvt = new Private(conn); |
| |
| setup(default_dispatcher); |
| |
| debug_log("connected to %s", address); |
| } |
| |
| Connection::Connection(Connection::Private *p) |
| : _pvt(p), _timeout(-1) |
| { |
| setup(default_dispatcher); |
| } |
| |
| Connection::Connection(const Connection &c) |
| : _pvt(c._pvt),_timeout(c._timeout) |
| { |
| dbus_connection_ref(_pvt->conn); |
| } |
| |
| Connection::~Connection() |
| { |
| dbus_connection_unref(_pvt->conn); |
| } |
| |
| Dispatcher *Connection::setup(Dispatcher *dispatcher) |
| { |
| debug_log("registering stubs for connection %p", _pvt->conn); |
| |
| if (!dispatcher) dispatcher = default_dispatcher; |
| |
| if (!dispatcher) throw ErrorFailed("no default dispatcher set for new connection"); |
| |
| Dispatcher *prev = _pvt->dispatcher; |
| |
| _pvt->dispatcher = dispatcher; |
| |
| dispatcher->queue_connection(_pvt.get()); |
| |
| dbus_connection_set_watch_functions( |
| _pvt->conn, |
| Dispatcher::Private::on_add_watch, |
| Dispatcher::Private::on_rem_watch, |
| Dispatcher::Private::on_toggle_watch, |
| dispatcher, |
| 0 |
| ); |
| |
| dbus_connection_set_timeout_functions( |
| _pvt->conn, |
| Dispatcher::Private::on_add_timeout, |
| Dispatcher::Private::on_rem_timeout, |
| Dispatcher::Private::on_toggle_timeout, |
| dispatcher, |
| 0 |
| ); |
| |
| return prev; |
| } |
| |
| bool Connection::operator == (const Connection &c) const |
| { |
| return _pvt->conn == c._pvt->conn; |
| } |
| |
| bool Connection::register_bus() |
| { |
| InternalError e; |
| |
| bool r = dbus_bus_register(_pvt->conn, e); |
| |
| if (e) throw (e); |
| |
| return r; |
| } |
| |
| bool Connection::connected() const |
| { |
| return dbus_connection_get_is_connected(_pvt->conn); |
| } |
| |
| void Connection::disconnect() |
| { |
| // dbus_connection_disconnect(_pvt->conn); // disappeared in 0.9x |
| dbus_connection_close(_pvt->conn); |
| } |
| |
| void Connection::exit_on_disconnect(bool exit) |
| { |
| dbus_connection_set_exit_on_disconnect(_pvt->conn, exit); |
| } |
| |
| bool Connection::unique_name(const char *n) |
| { |
| return dbus_bus_set_unique_name(_pvt->conn, n); |
| } |
| |
| const char *Connection::unique_name() const |
| { |
| return dbus_bus_get_unique_name(_pvt->conn); |
| } |
| |
| void Connection::flush() |
| { |
| dbus_connection_flush(_pvt->conn); |
| } |
| |
| void Connection::add_match(const char *rule) |
| { |
| InternalError e; |
| |
| dbus_bus_add_match(_pvt->conn, rule, e); |
| |
| debug_log("%s: added match rule %s", unique_name(), rule); |
| |
| if (e) throw Error(e); |
| } |
| |
| void Connection::remove_match(const char *rule) |
| { |
| InternalError e; |
| |
| dbus_bus_remove_match(_pvt->conn, rule, e); |
| |
| debug_log("%s: removed match rule %s", unique_name(), rule); |
| |
| if (e) throw Error(e); |
| } |
| |
| bool Connection::add_filter(MessageSlot &s) |
| { |
| debug_log("%s: adding filter", unique_name()); |
| return dbus_connection_add_filter(_pvt->conn, Private::message_filter_stub, &s, NULL); |
| } |
| |
| void Connection::remove_filter(MessageSlot &s) |
| { |
| debug_log("%s: removing filter", unique_name()); |
| dbus_connection_remove_filter(_pvt->conn, Private::message_filter_stub, &s); |
| } |
| |
| bool Connection::send(const Message &msg, unsigned int *serial) |
| { |
| return dbus_connection_send(_pvt->conn, msg._pvt->msg, serial); |
| } |
| |
| Message Connection::send_blocking(Message &msg, int timeout) |
| { |
| DBusMessage *reply; |
| InternalError e; |
| |
| if (this->_timeout != -1) |
| { |
| reply = dbus_connection_send_with_reply_and_block(_pvt->conn, msg._pvt->msg, this->_timeout, e); |
| } |
| else |
| { |
| reply = dbus_connection_send_with_reply_and_block(_pvt->conn, msg._pvt->msg, timeout, e); |
| } |
| |
| if (e) throw Error(e); |
| |
| return Message(new Message::Private(reply), false); |
| } |
| |
| PendingCall *Connection::send_async(Message &msg, int timeout) |
| { |
| DBusPendingCall *pending; |
| |
| // TODO(ers) At the moment using a timeout other than -1 |
| // results in a deadlock if the timeout expires. |
| if (!dbus_connection_send_with_reply(_pvt->conn, msg._pvt->msg, &pending, timeout)) |
| { |
| throw ErrorNoMemory("Unable to start asynchronous call"); |
| } |
| return new PendingCall(new PendingCall::Private(pending)); |
| } |
| |
| int Connection::_request_name(const char *name, int flags) |
| { |
| InternalError e; |
| |
| debug_log("%s: registering bus name %s", unique_name(), name); |
| |
| int ret = dbus_bus_request_name(_pvt->conn, name, flags, e); |
| |
| if (ret == -1) |
| { |
| if (e) throw Error(e); |
| } |
| |
| // this->remove_match("destination"); |
| |
| if (name) |
| { |
| _pvt->names.push_back(name); |
| std::string match = "destination='" + _pvt->names.back() + "'"; |
| add_match(match.c_str()); |
| } |
| |
| return ret; |
| } |
| |
| void Connection::request_name(const char *name, int flags) |
| { |
| /* |
| * TODO: |
| * Think about giving back the 'ret' value. Some people on the list |
| * requested about this... |
| */ |
| _request_name(name, flags); |
| } |
| |
| bool Connection::acquire_name(const char *name) |
| { |
| /* |
| * Request the desired name, allowing the request to be queued. |
| */ |
| int ret = _request_name(name, 0); |
| switch (ret) { |
| case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER: |
| case DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER: |
| return true; |
| case DBUS_REQUEST_NAME_REPLY_IN_QUEUE: |
| break; |
| case DBUS_REQUEST_NAME_REPLY_EXISTS: |
| default: |
| // Unexpected, but try to keep going. |
| debug_log("%s: unexpected reply %d for RequestName", |
| dbus_bus_get_unique_name(_pvt->conn), ret); |
| break; |
| } |
| |
| /* |
| * We didn't get the name. Perhaps the name owner terminated |
| * abruptly, and dbus-daemon doesn't know that yet. Ask |
| * dbus-daemon to check if the name owner is still up. |
| */ |
| CallMessage ping_request(name, "/", "org.freedesktop.DBus.Peer", "Ping"); |
| try { |
| send_blocking(ping_request, DBUS_TIMEOUT_USE_DEFAULT); |
| } catch (const DBus::Error &) { |
| /* |
| * We don't care if the ping request times out or generates |
| * some other error. We only care about its side-effect. |
| */ |
| } |
| |
| /* |
| * Now that we've nudged dbus-daemon, try requesting the name |
| * one more time. Note that we may already be the owner at |
| * this point, because our first attempt allowed queuing the |
| * request. |
| */ |
| ret = _request_name(name, DBUS_NAME_FLAG_DO_NOT_QUEUE); |
| switch (ret) { |
| case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER: |
| case DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER: |
| return true; |
| case DBUS_REQUEST_NAME_REPLY_EXISTS: |
| return false; |
| case DBUS_REQUEST_NAME_REPLY_IN_QUEUE: |
| default: |
| // Log unexpected response. |
| debug_log("%s: unexpected reply %d for RequestName", |
| dbus_bus_get_unique_name(_pvt->conn), ret); |
| return false; |
| } |
| } |
| |
| unsigned long Connection::sender_unix_uid(const char *sender) |
| { |
| InternalError e; |
| |
| unsigned long ul = dbus_bus_get_unix_user(_pvt->conn, sender, e); |
| |
| if (e) throw Error(e); |
| |
| return ul; |
| } |
| |
| bool Connection::has_name(const char *name) |
| { |
| InternalError e; |
| |
| bool b = dbus_bus_name_has_owner(_pvt->conn, name, e); |
| |
| if (e) throw Error(e); |
| |
| return b; |
| } |
| |
| const std::vector<std::string>& Connection::names() |
| { |
| return _pvt->names; |
| } |
| |
| bool Connection::start_service(const char *name, unsigned long flags) |
| { |
| InternalError e; |
| |
| bool b = dbus_bus_start_service_by_name(_pvt->conn, name, flags, NULL, e); |
| |
| if (e) throw Error(e); |
| |
| return b; |
| } |
| |
| void Connection::set_timeout(int timeout) |
| { |
| _timeout=timeout; |
| } |
| |
| int Connection::get_timeout() |
| { |
| return _timeout; |
| } |