kitty/glfw/dbus_glfw.c
Luflosi f3b9ff5f9f
Use datatype monotonic_t instead of double to keep track of time
The time is stored in a signed 64 bit integer with nanosecond accuracy. This eliminates the possibility of floating-point inaccuracies.
`monotonic_t` can currently hold values large enough to work correctly for more than 200 years into the future.
Using a typedef instead of directly using `int64_t` everywhere will also allow easily changing the datatype in the future should the need arise for more precise or bigger time values.
2019-09-25 17:43:11 +02:00

351 lines
12 KiB
C
Vendored

//========================================================================
// GLFW 3.4 XKB - www.glfw.org
//------------------------------------------------------------------------
// Copyright (c) 2018 Kovid Goyal <kovid@kovidgoyal.net>
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would
// be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
// be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
// distribution.
//
//========================================================================
#include "internal.h"
#include "dbus_glfw.h"
#include "../kitty/monotonic.h"
#include <stdlib.h>
#include <string.h>
static inline void
report_error(DBusError *err, const char *fmt, ...) {
static char buf[1024];
va_list args;
va_start(args, fmt);
int n = vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
snprintf(buf + n, sizeof(buf), ". DBUS error: %s", err->message);
_glfwInputError(GLFW_PLATFORM_ERROR, "%s", buf);
dbus_error_free(err);
}
static _GLFWDBUSData *dbus_data = NULL;
static DBusConnection *session_bus = NULL;
bool
glfw_dbus_init(_GLFWDBUSData *dbus, EventLoopData *eld) {
dbus->eld = eld;
dbus_data = dbus;
return true;
}
static void
on_dbus_watch_ready(int fd UNUSED, int events, void *data) {
DBusWatch *watch = (DBusWatch*)data;
unsigned int flags = 0;
if (events & POLLERR) flags |= DBUS_WATCH_ERROR;
if (events & POLLHUP) flags |= DBUS_WATCH_HANGUP;
if (events & POLLIN) flags |= DBUS_WATCH_READABLE;
if (events & POLLOUT) flags |= DBUS_WATCH_WRITABLE;
dbus_watch_handle(watch, flags);
}
static inline int
events_for_watch(DBusWatch *watch) {
int events = 0;
unsigned int flags = dbus_watch_get_flags(watch);
if (flags & DBUS_WATCH_READABLE) events |= POLLIN;
if (flags & DBUS_WATCH_WRITABLE) events |= POLLOUT;
return events;
}
static dbus_bool_t
add_dbus_watch(DBusWatch *watch, void *data) {
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);
if (!watch_id) return FALSE;
id_type *idp = malloc(sizeof(id_type));
if (!idp) return FALSE;
*idp = watch_id;
dbus_watch_set_data(watch, idp, free);
return TRUE;
}
static void
remove_dbus_watch(DBusWatch *watch, void *data UNUSED) {
id_type *idp = dbus_watch_get_data(watch);
if (idp) removeWatch(dbus_data->eld, *idp);
}
static void
toggle_dbus_watch(DBusWatch *watch, void *data UNUSED) {
id_type *idp = dbus_watch_get_data(watch);
if (idp) toggleWatch(dbus_data->eld, *idp, dbus_watch_get_enabled(watch));
}
static void
on_dbus_timer_ready(id_type timer_id UNUSED, void *data) {
if (data) {
DBusTimeout *t = (DBusTimeout*)data;
dbus_timeout_handle(t);
}
}
static dbus_bool_t
add_dbus_timeout(DBusTimeout *timeout, void *data) {
int enabled = dbus_timeout_get_enabled(timeout) ? 1 : 0;
monotonic_t interval = ms_to_monotonic_t(dbus_timeout_get_interval(timeout));
if (interval < 0) return FALSE;
id_type timer_id = addTimer(dbus_data->eld, data, interval, enabled, true, on_dbus_timer_ready, timeout, NULL);
if (!timer_id) return FALSE;
id_type *idp = malloc(sizeof(id_type));
if (!idp) {
removeTimer(dbus_data->eld, timer_id);
return FALSE;
}
*idp = timer_id;
dbus_timeout_set_data(timeout, idp, free);
return TRUE;
}
static void
remove_dbus_timeout(DBusTimeout *timeout, void *data UNUSED) {
id_type *idp = dbus_timeout_get_data(timeout);
if (idp) removeTimer(dbus_data->eld, *idp);
}
static void
toggle_dbus_timeout(DBusTimeout *timeout, void *data UNUSED) {
id_type *idp = dbus_timeout_get_data(timeout);
if (idp) toggleTimer(dbus_data->eld, *idp, dbus_timeout_get_enabled(timeout));
}
DBusConnection*
glfw_dbus_connect_to(const char *path, const char* err_msg, const char *name, bool register_on_bus) {
DBusError err;
dbus_error_init(&err);
DBusConnection *ans = dbus_connection_open_private(path, &err);
if (!ans) {
report_error(&err, err_msg);
return NULL;
}
dbus_connection_set_exit_on_disconnect(ans, FALSE);
dbus_error_free(&err);
if (register_on_bus) {
if (!dbus_bus_register(ans, &err)) {
report_error(&err, err_msg);
return NULL;
}
}
if (!dbus_connection_set_watch_functions(ans, add_dbus_watch, remove_dbus_watch, toggle_dbus_watch, (void*)name, NULL)) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to set DBUS watches on connection to: %s", path);
dbus_connection_close(ans);
dbus_connection_unref(ans);
return NULL;
}
if (!dbus_connection_set_timeout_functions(ans, add_dbus_timeout, remove_dbus_timeout, toggle_dbus_timeout, (void*)name, NULL)) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to set DBUS timeout functions on connection to: %s", path);
dbus_connection_close(ans);
dbus_connection_unref(ans);
return NULL;
}
return ans;
}
void
glfw_dbus_dispatch(DBusConnection *conn) {
while(dbus_connection_dispatch(conn) == DBUS_DISPATCH_DATA_REMAINS);
}
void
glfw_dbus_session_bus_dispatch() {
if (session_bus) glfw_dbus_dispatch(session_bus);
}
void
glfw_dbus_terminate(_GLFWDBUSData *dbus UNUSED) {
if (dbus_data) {
dbus_data->eld = NULL;
dbus_data = NULL;
}
if (session_bus) {
dbus_connection_unref(session_bus);
session_bus = NULL;
}
}
void
glfw_dbus_close_connection(DBusConnection *conn) {
dbus_connection_close(conn);
dbus_connection_unref(conn);
}
bool
glfw_dbus_get_args(DBusMessage *msg, const char *failmsg, ...) {
DBusError err;
dbus_error_init(&err);
va_list ap;
va_start(ap, failmsg);
int firstarg = va_arg(ap, int);
bool ret = dbus_message_get_args_valist(msg, &err, firstarg, ap) ? true : false;
va_end(ap);
if (!ret) report_error(&err, failmsg);
return ret;
}
typedef struct {
dbus_pending_callback callback;
void *user_data;
} MethodResponse;
static const char*
format_message_error(DBusError *err) {
static char buf[1024];
snprintf(buf, sizeof(buf), "[%s] %s", err->name ? err->name : "", err->message);
return buf;
}
static void
method_reply_received(DBusPendingCall *pending, void *user_data) {
MethodResponse *res = (MethodResponse*)user_data;
DBusMessage *msg = dbus_pending_call_steal_reply(pending);
if (msg) {
DBusError err;
dbus_error_init(&err);
if (dbus_set_error_from_message(&err, msg)) res->callback(NULL, format_message_error(&err), res->user_data);
else res->callback(msg, NULL, res->user_data);
dbus_message_unref(msg);
}
}
bool
call_method_with_msg(DBusConnection *conn, DBusMessage *msg, int timeout, dbus_pending_callback callback, void *user_data) {
bool retval = false;
#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)
if (callback) {
DBusPendingCall *pending = NULL;
if (dbus_connection_send_with_reply(conn, msg, &pending, timeout)) {
MethodResponse *res = malloc(sizeof(MethodResponse));
if (!res) return false;
res->callback = callback;
res->user_data = user_data;
dbus_pending_call_set_notify(pending, method_reply_received, res, free);
retval = true;
} else {
REPORT("out of memory");
}
} else {
if (dbus_connection_send(conn, msg, NULL)) {
retval = true;
} else {
REPORT("out of memory");
}
}
return retval;
#undef REPORT
}
static bool
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, va_list ap) {
if (!conn) return false;
DBusMessage *msg = dbus_message_new_method_call(node, path, interface, method);
if (!msg) return false;
bool retval = false;
int firstarg = va_arg(ap, int);
if ((firstarg == DBUS_TYPE_INVALID) || dbus_message_append_args_valist(msg, firstarg, ap)) {
retval = call_method_with_msg(conn, msg, timeout, callback, user_data);
} else {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: %s on node: %s and interface: %s could not add arguments", method, node, interface);
}
dbus_message_unref(msg);
return retval;
}
bool
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, ...) {
bool retval;
va_list ap;
va_start(ap, user_data);
retval = call_method(conn, node, path, interface, method, timeout, callback, user_data, ap);
va_end(ap);
return retval;
}
bool
glfw_dbus_call_method_no_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...) {
bool retval;
va_list ap;
va_start(ap, method);
retval = call_method(conn, node, path, interface, method, DBUS_TIMEOUT_USE_DEFAULT, NULL, NULL, ap);
va_end(ap);
return retval;
}
int
glfw_dbus_match_signal(DBusMessage *msg, const char *interface, ...) {
va_list ap;
va_start(ap, interface);
int ans = -1, num = -1;
while(1) {
num++;
const char *name = va_arg(ap, const char*);
if (!name) break;
if (dbus_message_is_signal(msg, interface, name)) { ans = num; break; }
}
va_end(ap);
return ans;
}
static void
glfw_dbus_connect_to_session_bus(void) {
DBusError error;
dbus_error_init(&error);
if (session_bus) {
dbus_connection_unref(session_bus);
}
session_bus = dbus_bus_get(DBUS_BUS_SESSION, &error);
if (dbus_error_is_set(&error)) {
report_error(&error, "Failed to connect to DBUS session bus");
session_bus = NULL;
return;
}
static const char *name = "session-bus";
if (!dbus_connection_set_watch_functions(session_bus, add_dbus_watch, remove_dbus_watch, toggle_dbus_watch, (void*)name, NULL)) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to set DBUS watches on connection to: %s", name);
dbus_connection_close(session_bus);
dbus_connection_unref(session_bus);
return;
}
if (!dbus_connection_set_timeout_functions(session_bus, add_dbus_timeout, remove_dbus_timeout, toggle_dbus_timeout, (void*)name, NULL)) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to set DBUS timeout functions on connection to: %s", name);
dbus_connection_close(session_bus);
dbus_connection_unref(session_bus);
return;
}
}
DBusConnection *
glfw_dbus_session_bus() {
if (!session_bus) glfw_dbus_connect_to_session_bus();
return session_bus;
}