dbus_glfw.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. //========================================================================
  2. // GLFW 3.4 XKB - www.glfw.org
  3. //------------------------------------------------------------------------
  4. // Copyright (c) 2018 Kovid Goyal <kovid@kovidgoyal.net>
  5. //
  6. // This software is provided 'as-is', without any express or implied
  7. // warranty. In no event will the authors be held liable for any damages
  8. // arising from the use of this software.
  9. //
  10. // Permission is granted to anyone to use this software for any purpose,
  11. // including commercial applications, and to alter it and redistribute it
  12. // freely, subject to the following restrictions:
  13. //
  14. // 1. The origin of this software must not be misrepresented; you must not
  15. // claim that you wrote the original software. If you use this software
  16. // in a product, an acknowledgment in the product documentation would
  17. // be appreciated but is not required.
  18. //
  19. // 2. Altered source versions must be plainly marked as such, and must not
  20. // be misrepresented as being the original software.
  21. //
  22. // 3. This notice may not be removed or altered from any source
  23. // distribution.
  24. //
  25. //========================================================================
  26. #include "internal.h"
  27. #include "dbus_glfw.h"
  28. #include "../kitty/monotonic.h"
  29. #include <stdlib.h>
  30. #include <string.h>
  31. static void
  32. report_error(DBusError *err, const char *fmt, ...) {
  33. static char buf[4096];
  34. va_list args;
  35. va_start(args, fmt);
  36. int n = vsnprintf(buf, sizeof(buf), fmt, args);
  37. va_end(args);
  38. if (n >= 0 && (size_t)n < (sizeof(buf) - 256)) snprintf(buf + n, sizeof(buf) - n, ". DBUS error: %s", err->message ? err->message : "(null)");
  39. _glfwInputError(GLFW_PLATFORM_ERROR, "%s", buf);
  40. dbus_error_free(err);
  41. }
  42. static _GLFWDBUSData *dbus_data = NULL;
  43. static DBusConnection *session_bus = NULL;
  44. bool
  45. glfw_dbus_init(_GLFWDBUSData *dbus, EventLoopData *eld) {
  46. dbus->eld = eld;
  47. dbus_data = dbus;
  48. return true;
  49. }
  50. static void
  51. on_dbus_watch_ready(int fd UNUSED, int events, void *data) {
  52. DBusWatch *watch = (DBusWatch*)data;
  53. unsigned int flags = 0;
  54. if (events & POLLERR) flags |= DBUS_WATCH_ERROR;
  55. if (events & POLLHUP) flags |= DBUS_WATCH_HANGUP;
  56. if (events & POLLIN) flags |= DBUS_WATCH_READABLE;
  57. if (events & POLLOUT) flags |= DBUS_WATCH_WRITABLE;
  58. dbus_watch_handle(watch, flags);
  59. }
  60. static int
  61. events_for_watch(DBusWatch *watch) {
  62. int events = 0;
  63. unsigned int flags = dbus_watch_get_flags(watch);
  64. if (flags & DBUS_WATCH_READABLE) events |= POLLIN;
  65. if (flags & DBUS_WATCH_WRITABLE) events |= POLLOUT;
  66. return events;
  67. }
  68. static dbus_bool_t
  69. add_dbus_watch(DBusWatch *watch, void *data) {
  70. id_type watch_id = addWatch(dbus_data->eld, data, dbus_watch_get_unix_fd(watch), events_for_watch(watch), dbus_watch_get_enabled(watch), on_dbus_watch_ready, watch);
  71. if (!watch_id) return FALSE;
  72. id_type *idp = malloc(sizeof(id_type));
  73. if (!idp) return FALSE;
  74. *idp = watch_id;
  75. dbus_watch_set_data(watch, idp, free);
  76. return TRUE;
  77. }
  78. static void
  79. remove_dbus_watch(DBusWatch *watch, void *data UNUSED) {
  80. id_type *idp = dbus_watch_get_data(watch);
  81. if (idp) removeWatch(dbus_data->eld, *idp);
  82. }
  83. static void
  84. toggle_dbus_watch(DBusWatch *watch, void *data UNUSED) {
  85. id_type *idp = dbus_watch_get_data(watch);
  86. if (idp) toggleWatch(dbus_data->eld, *idp, dbus_watch_get_enabled(watch));
  87. }
  88. static void
  89. on_dbus_timer_ready(id_type timer_id UNUSED, void *data) {
  90. if (data) {
  91. DBusTimeout *t = (DBusTimeout*)data;
  92. dbus_timeout_handle(t);
  93. }
  94. }
  95. static dbus_bool_t
  96. add_dbus_timeout(DBusTimeout *timeout, void *data) {
  97. int enabled = dbus_timeout_get_enabled(timeout) ? 1 : 0;
  98. monotonic_t interval = ms_to_monotonic_t(dbus_timeout_get_interval(timeout));
  99. if (interval < 0) return FALSE;
  100. id_type timer_id = addTimer(dbus_data->eld, data, interval, enabled, true, on_dbus_timer_ready, timeout, NULL);
  101. if (!timer_id) return FALSE;
  102. id_type *idp = malloc(sizeof(id_type));
  103. if (!idp) {
  104. removeTimer(dbus_data->eld, timer_id);
  105. return FALSE;
  106. }
  107. *idp = timer_id;
  108. dbus_timeout_set_data(timeout, idp, free);
  109. return TRUE;
  110. }
  111. static void
  112. remove_dbus_timeout(DBusTimeout *timeout, void *data UNUSED) {
  113. id_type *idp = dbus_timeout_get_data(timeout);
  114. if (idp) removeTimer(dbus_data->eld, *idp);
  115. }
  116. static void
  117. toggle_dbus_timeout(DBusTimeout *timeout, void *data UNUSED) {
  118. id_type *idp = dbus_timeout_get_data(timeout);
  119. if (idp) toggleTimer(dbus_data->eld, *idp, dbus_timeout_get_enabled(timeout));
  120. }
  121. DBusConnection*
  122. glfw_dbus_connect_to(const char *path, const char* err_msg, const char *name, bool register_on_bus) {
  123. DBusError err;
  124. dbus_error_init(&err);
  125. DBusConnection *ans = dbus_connection_open_private(path, &err);
  126. if (!ans) {
  127. report_error(&err, err_msg);
  128. return NULL;
  129. }
  130. dbus_connection_set_exit_on_disconnect(ans, FALSE);
  131. dbus_error_free(&err);
  132. if (register_on_bus) {
  133. if (!dbus_bus_register(ans, &err)) {
  134. report_error(&err, err_msg);
  135. return NULL;
  136. }
  137. }
  138. if (!dbus_connection_set_watch_functions(ans, add_dbus_watch, remove_dbus_watch, toggle_dbus_watch, (void*)name, NULL)) {
  139. _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to set DBUS watches on connection to: %s", path);
  140. dbus_connection_close(ans);
  141. dbus_connection_unref(ans);
  142. return NULL;
  143. }
  144. if (!dbus_connection_set_timeout_functions(ans, add_dbus_timeout, remove_dbus_timeout, toggle_dbus_timeout, (void*)name, NULL)) {
  145. _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to set DBUS timeout functions on connection to: %s", path);
  146. dbus_connection_close(ans);
  147. dbus_connection_unref(ans);
  148. return NULL;
  149. }
  150. return ans;
  151. }
  152. void
  153. glfw_dbus_dispatch(DBusConnection *conn) {
  154. while(dbus_connection_dispatch(conn) == DBUS_DISPATCH_DATA_REMAINS);
  155. }
  156. void
  157. glfw_dbus_session_bus_dispatch(void) {
  158. if (session_bus) glfw_dbus_dispatch(session_bus);
  159. }
  160. void
  161. glfw_dbus_terminate(_GLFWDBUSData *dbus UNUSED) {
  162. if (dbus_data) {
  163. dbus_data->eld = NULL;
  164. dbus_data = NULL;
  165. }
  166. if (session_bus) {
  167. dbus_connection_unref(session_bus);
  168. session_bus = NULL;
  169. }
  170. }
  171. void
  172. glfw_dbus_close_connection(DBusConnection *conn) {
  173. dbus_connection_close(conn);
  174. dbus_connection_unref(conn);
  175. }
  176. bool
  177. glfw_dbus_get_args(DBusMessage *msg, const char *failmsg, ...) {
  178. DBusError err;
  179. dbus_error_init(&err);
  180. va_list ap;
  181. va_start(ap, failmsg);
  182. int firstarg = va_arg(ap, int);
  183. bool ret = dbus_message_get_args_valist(msg, &err, firstarg, ap) ? true : false;
  184. va_end(ap);
  185. if (!ret) report_error(&err, failmsg);
  186. return ret;
  187. }
  188. typedef struct {
  189. dbus_pending_callback callback;
  190. void *user_data;
  191. } MethodResponse;
  192. static const char*
  193. format_message_error(DBusError *err) {
  194. static char buf[1024];
  195. snprintf(buf, sizeof(buf), "[%s] %s", err->name ? err->name : "", err->message);
  196. return buf;
  197. }
  198. static void
  199. method_reply_received(DBusPendingCall *pending, void *user_data) {
  200. MethodResponse *res = (MethodResponse*)user_data;
  201. RAII_MSG(msg, dbus_pending_call_steal_reply(pending));
  202. if (msg) {
  203. DBusError err;
  204. dbus_error_init(&err);
  205. if (dbus_set_error_from_message(&err, msg)) res->callback(NULL, format_message_error(&err), res->user_data);
  206. else res->callback(msg, NULL, res->user_data);
  207. }
  208. }
  209. bool
  210. call_method_with_msg(DBusConnection *conn, DBusMessage *msg, int timeout, dbus_pending_callback callback, void *user_data, bool block) {
  211. bool retval = false;
  212. #define REPORT(errs) _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: node=%s path=%s interface=%s method=%s, with error: %s", dbus_message_get_destination(msg), dbus_message_get_path(msg), dbus_message_get_interface(msg), dbus_message_get_member(msg), errs)
  213. if (callback) {
  214. DBusPendingCall *pending = NULL;
  215. if (block) {
  216. DBusError error; dbus_error_init(&error);
  217. RAII_MSG(reply, dbus_connection_send_with_reply_and_block(session_bus, msg, timeout, &error));
  218. if (dbus_error_is_set(&error)) {
  219. callback(reply, error.message, user_data);
  220. return false;
  221. } else if (reply) {
  222. callback(reply, NULL, user_data);
  223. } else return false;
  224. } else if (dbus_connection_send_with_reply(conn, msg, &pending, timeout)) {
  225. MethodResponse *res = malloc(sizeof(MethodResponse));
  226. if (!res) return false;
  227. res->callback = callback;
  228. res->user_data = user_data;
  229. dbus_pending_call_set_notify(pending, method_reply_received, res, free);
  230. retval = true;
  231. } else {
  232. REPORT("out of memory");
  233. }
  234. } else {
  235. if (dbus_connection_send(conn, msg, NULL)) {
  236. retval = true;
  237. } else {
  238. REPORT("out of memory");
  239. }
  240. }
  241. return retval;
  242. #undef REPORT
  243. }
  244. static bool
  245. call_method(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout, dbus_pending_callback callback, void *user_data, bool blocking, va_list ap) {
  246. if (!conn || !path) return false;
  247. RAII_MSG(msg, dbus_message_new_method_call(node, path, interface, method));
  248. if (!msg) return false;
  249. bool retval = false;
  250. int firstarg = va_arg(ap, int);
  251. if ((firstarg == DBUS_TYPE_INVALID) || dbus_message_append_args_valist(msg, firstarg, ap)) {
  252. retval = call_method_with_msg(conn, msg, timeout, callback, user_data, blocking);
  253. } else {
  254. _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: %s on node: %s and interface: %s could not add arguments", method, node, interface);
  255. }
  256. return retval;
  257. }
  258. bool
  259. glfw_dbus_call_method_with_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout, dbus_pending_callback callback, void* user_data, ...) {
  260. bool retval;
  261. va_list ap;
  262. va_start(ap, user_data);
  263. retval = call_method(conn, node, path, interface, method, timeout, callback, user_data, false, ap);
  264. va_end(ap);
  265. return retval;
  266. }
  267. bool
  268. glfw_dbus_call_blocking_method(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout, dbus_pending_callback callback, void* user_data, ...) {
  269. bool retval;
  270. va_list ap;
  271. va_start(ap, user_data);
  272. retval = call_method(conn, node, path, interface, method, timeout, callback, user_data, true, ap);
  273. va_end(ap);
  274. return retval;
  275. }
  276. bool
  277. glfw_dbus_call_method_no_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...) {
  278. bool retval;
  279. va_list ap;
  280. va_start(ap, method);
  281. retval = call_method(conn, node, path, interface, method, DBUS_TIMEOUT_USE_DEFAULT, NULL, NULL, false, ap);
  282. va_end(ap);
  283. return retval;
  284. }
  285. int
  286. glfw_dbus_match_signal(DBusMessage *msg, const char *interface, ...) {
  287. va_list ap;
  288. va_start(ap, interface);
  289. int ans = -1, num = -1;
  290. while(1) {
  291. num++;
  292. const char *name = va_arg(ap, const char*);
  293. if (!name) break;
  294. if (dbus_message_is_signal(msg, interface, name)) { ans = num; break; }
  295. }
  296. va_end(ap);
  297. return ans;
  298. }
  299. static void
  300. glfw_dbus_connect_to_session_bus(void) {
  301. DBusError error;
  302. dbus_error_init(&error);
  303. if (session_bus) {
  304. dbus_connection_unref(session_bus);
  305. }
  306. session_bus = dbus_bus_get(DBUS_BUS_SESSION, &error);
  307. if (dbus_error_is_set(&error)) {
  308. report_error(&error, "Failed to connect to DBUS session bus");
  309. session_bus = NULL;
  310. return;
  311. }
  312. static const char *name = "session-bus";
  313. if (!dbus_connection_set_watch_functions(session_bus, add_dbus_watch, remove_dbus_watch, toggle_dbus_watch, (void*)name, NULL)) {
  314. _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to set DBUS watches on connection to: %s", name);
  315. dbus_connection_close(session_bus);
  316. dbus_connection_unref(session_bus);
  317. return;
  318. }
  319. if (!dbus_connection_set_timeout_functions(session_bus, add_dbus_timeout, remove_dbus_timeout, toggle_dbus_timeout, (void*)name, NULL)) {
  320. _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to set DBUS timeout functions on connection to: %s", name);
  321. dbus_connection_close(session_bus);
  322. dbus_connection_unref(session_bus);
  323. return;
  324. }
  325. }
  326. DBusConnection *
  327. glfw_dbus_session_bus(void) {
  328. if (!session_bus) glfw_dbus_connect_to_session_bus();
  329. return session_bus;
  330. }