1 // Copyright (c) Benjamin Kietzman (github.com/bkietz)
2 //
3 // Distributed under the Boost Software License, Version 1.0. (See accompanying
4 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5 
6 #ifndef DBUS_WATCH_TIMEOUT_HPP
7 #define DBUS_WATCH_TIMEOUT_HPP
8 
9 #include <dbus/dbus.h>
10 #include <boost/asio/generic/stream_protocol.hpp>
11 #include <boost/asio/steady_timer.hpp>
12 
13 #include <chrono>
14 
15 namespace dbus {
16 namespace detail {
17 
18 static void watch_toggled(DBusWatch *dbus_watch, void *data);
19 struct watch_handler {
20   DBusWatchFlags flags;
21   DBusWatch *dbus_watch;
22   watch_handler(DBusWatchFlags f, DBusWatch *w) : flags(f), dbus_watch(w) {}
23   void operator()(boost::system::error_code ec, size_t) {
24     if (ec) return;
25     dbus_watch_handle(dbus_watch, flags);
26 
27     boost::asio::generic::stream_protocol::socket &socket = *static_cast<boost::asio::generic::stream_protocol::socket *>(
28         dbus_watch_get_data(dbus_watch));
29 
30     watch_toggled(dbus_watch, &socket.get_io_service());
31   }
32 };
33 static void watch_toggled(DBusWatch *dbus_watch, void *data) {
34   boost::asio::generic::stream_protocol::socket &socket =
35       *static_cast<boost::asio::generic::stream_protocol::socket *>(dbus_watch_get_data(dbus_watch));
36 
37   if (dbus_watch_get_enabled(dbus_watch)) {
38     if (dbus_watch_get_flags(dbus_watch) & DBUS_WATCH_READABLE)
39       socket.async_read_some(boost::asio::null_buffers(),
40                              watch_handler(DBUS_WATCH_READABLE, dbus_watch));
41 
42     if (dbus_watch_get_flags(dbus_watch) & DBUS_WATCH_WRITABLE)
43       socket.async_write_some(boost::asio::null_buffers(),
44                               watch_handler(DBUS_WATCH_WRITABLE, dbus_watch));
45 
46   } else {
47     socket.cancel();
48   }
49 }
50 
51 static dbus_bool_t add_watch(DBusWatch *dbus_watch, void *data) {
52   if (!dbus_watch_get_enabled(dbus_watch)) return TRUE;
53 
54   boost::asio::io_service &io = *static_cast<boost::asio::io_service *>(data);
55 
56   int fd = dbus_watch_get_unix_fd(dbus_watch);
57 
58   if (fd == -1)
59     // socket based watches
60     fd = dbus_watch_get_socket(dbus_watch);
61 
62   boost::asio::generic::stream_protocol::socket &socket = *new boost::asio::generic::stream_protocol::socket(io);
63 
64   socket.assign(boost::asio::generic::stream_protocol(0, 0), fd);
65 
66   dbus_watch_set_data(dbus_watch, &socket, NULL);
67 
68   watch_toggled(dbus_watch, &io);
69   return TRUE;
70 }
71 
72 static void remove_watch(DBusWatch *dbus_watch, void *data) {
73   delete static_cast<boost::asio::generic::stream_protocol::socket *>(
74       dbus_watch_get_data(dbus_watch));
75 }
76 
77 struct timeout_handler {
78   DBusTimeout *dbus_timeout;
79   timeout_handler(DBusTimeout *t) : dbus_timeout(t) {}
80   void operator()(boost::system::error_code ec) {
81     if (ec) return;
82     dbus_timeout_handle(dbus_timeout);
83   }
84 };
85 
86 static void timeout_toggled(DBusTimeout *dbus_timeout, void *data) {
87   boost::asio::steady_timer &timer =
88       *static_cast<boost::asio::steady_timer *>(dbus_timeout_get_data(dbus_timeout));
89 
90   if (dbus_timeout_get_enabled(dbus_timeout)) {
91     boost::asio::steady_timer::duration interval =
92         std::chrono::milliseconds(dbus_timeout_get_interval(dbus_timeout));
93     timer.expires_from_now(interval);
94     timer.cancel();
95     timer.async_wait(timeout_handler(dbus_timeout));
96   } else {
97     timer.cancel();
98   }
99 }
100 
101 static dbus_bool_t add_timeout(DBusTimeout *dbus_timeout, void *data) {
102   if (!dbus_timeout_get_enabled(dbus_timeout)) return TRUE;
103 
104   boost::asio::io_service &io = *static_cast<boost::asio::io_service *>(data);
105 
106   boost::asio::steady_timer &timer = *new boost::asio::steady_timer(io);
107 
108   dbus_timeout_set_data(dbus_timeout, &timer, NULL);
109 
110   timeout_toggled(dbus_timeout, &io);
111   return TRUE;
112 }
113 
114 static void remove_timeout(DBusTimeout *dbus_timeout, void *data) {
115   delete static_cast<boost::asio::steady_timer *>(dbus_timeout_get_data(dbus_timeout));
116 }
117 
118 struct dispatch_handler {
119   boost::asio::io_service &io;
120   DBusConnection *conn;
121   dispatch_handler(boost::asio::io_service &i, DBusConnection *c)
122       : io(i), conn(c) {}
123   void operator()() {
124     if (dbus_connection_dispatch(conn) == DBUS_DISPATCH_DATA_REMAINS)
125       io.post(dispatch_handler(io, conn));
126   }
127 };
128 
129 static void dispatch_status(DBusConnection *conn, DBusDispatchStatus new_status,
130                             void *data) {
131   boost::asio::io_service &io = *static_cast<boost::asio::io_service *>(data);
132   if (new_status == DBUS_DISPATCH_DATA_REMAINS)
133     io.post(dispatch_handler(io, conn));
134 }
135 
136 static void set_watch_timeout_dispatch_functions(DBusConnection *conn,
137                                                  boost::asio::io_service &io) {
138   dbus_connection_set_watch_functions(conn, &add_watch, &remove_watch,
139                                       &watch_toggled, &io, NULL);
140 
141   dbus_connection_set_timeout_functions(conn, &add_timeout, &remove_timeout,
142                                         &timeout_toggled, &io, NULL);
143 
144   dbus_connection_set_dispatch_status_function(conn, &dispatch_status, &io,
145                                                NULL);
146 }
147 
148 }  // namespace detail
149 }  // namespace dbus
150 
151 #endif  // DBUS_WATCH_TIMEOUT_HPP
152