summaryrefslogtreecommitdiff
path: root/src/platform/freedesktop/dbus.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/platform/freedesktop/dbus.c')
-rw-r--r--src/platform/freedesktop/dbus.c187
1 files changed, 187 insertions, 0 deletions
diff --git a/src/platform/freedesktop/dbus.c b/src/platform/freedesktop/dbus.c
new file mode 100644
index 0000000..abca900
--- /dev/null
+++ b/src/platform/freedesktop/dbus.c
@@ -0,0 +1,187 @@
+#define _GNU_SOURCE
+#include "platform/freedesktop/dbus.h"
+
+#if YASL_HAS_DBUS
+
+#include <dbus/dbus.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+/* The portal may need activation on first use; bound how long a query blocks. */
+#define PORTAL_TIMEOUT_MS 1000
+
+static DBusConnection *bus;
+
+/* The session bus address, without libdbus's X11 autolaunch fallback. */
+static char *session_address(void)
+{
+ const char *env = getenv("DBUS_SESSION_BUS_ADDRESS");
+ if (env && *env) return strdup(env);
+ const char *run = getenv("XDG_RUNTIME_DIR");
+ if (!run || !*run) return NULL;
+ char path[4096];
+ struct stat st;
+ if (snprintf(path, sizeof(path), "%s/bus", run) >= (int)sizeof(path) ||
+ stat(path, &st) != 0 || !S_ISSOCK(st.st_mode))
+ return NULL;
+ char *escaped = dbus_address_escape_value(path);
+ if (!escaped) return NULL;
+ char *address = NULL;
+ if (asprintf(&address, "unix:path=%s", escaped) < 0) address = NULL;
+ dbus_free(escaped);
+ return address;
+}
+
+static DBusConnection *session_bus(void)
+{
+ if (bus && dbus_connection_get_is_connected(bus)) return bus;
+ if (bus) {
+ dbus_connection_close(bus);
+ dbus_connection_unref(bus);
+ bus = NULL;
+ }
+ char *address = session_address();
+ if (!address) return NULL;
+ DBusError err;
+ dbus_error_init(&err);
+ DBusConnection *c = dbus_connection_open_private(address, &err);
+ free(address);
+ if (!c) {
+ dbus_error_free(&err);
+ return NULL;
+ }
+ dbus_connection_set_exit_on_disconnect(c, FALSE);
+ if (!dbus_bus_register(c, &err)) {
+ dbus_error_free(&err);
+ dbus_connection_close(c);
+ dbus_connection_unref(c);
+ return NULL;
+ }
+ bus = c;
+ return bus;
+}
+
+bool bus_notify(const char *app, const char *title, const char *body)
+{
+ DBusConnection *c = session_bus();
+ if (!c) return false;
+ DBusMessage *msg = dbus_message_new_method_call("org.freedesktop.Notifications",
+ "/org/freedesktop/Notifications", "org.freedesktop.Notifications", "Notify");
+ if (!msg) return false;
+
+ const char *icon = "";
+ dbus_uint32_t replaces = 0;
+ dbus_int32_t expire = -1;
+ DBusMessageIter it, sub, entry, value;
+ dbus_message_iter_init_append(msg, &it);
+ bool ok = dbus_message_iter_append_basic(&it, DBUS_TYPE_STRING, &app) &&
+ dbus_message_iter_append_basic(&it, DBUS_TYPE_UINT32, &replaces) &&
+ dbus_message_iter_append_basic(&it, DBUS_TYPE_STRING, &icon) &&
+ dbus_message_iter_append_basic(&it, DBUS_TYPE_STRING, &title) &&
+ dbus_message_iter_append_basic(&it, DBUS_TYPE_STRING, &body);
+ /* No actions. */
+ ok = ok && dbus_message_iter_open_container(&it, DBUS_TYPE_ARRAY, "s", &sub) &&
+ dbus_message_iter_close_container(&it, &sub);
+ /* Hints: desktop-entry names the application's .desktop file. */
+ const char *hint = "desktop-entry";
+ ok = ok && dbus_message_iter_open_container(&it, DBUS_TYPE_ARRAY, "{sv}", &sub) &&
+ dbus_message_iter_open_container(&sub, DBUS_TYPE_DICT_ENTRY, NULL, &entry) &&
+ dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &hint) &&
+ dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "s", &value) &&
+ dbus_message_iter_append_basic(&value, DBUS_TYPE_STRING, &app) &&
+ dbus_message_iter_close_container(&entry, &value) &&
+ dbus_message_iter_close_container(&sub, &entry) &&
+ dbus_message_iter_close_container(&it, &sub);
+ ok = ok && dbus_message_iter_append_basic(&it, DBUS_TYPE_INT32, &expire);
+
+ if (ok) {
+ dbus_message_set_no_reply(msg, TRUE);
+ ok = dbus_connection_send(c, msg, NULL);
+ if (ok) dbus_connection_flush(c);
+ }
+ dbus_message_unref(msg);
+ return ok;
+}
+
+/* Read an integer from it, unwrapping variants: Settings.Read nests one more. */
+static bool iter_int(DBusMessageIter *it, int64_t *out)
+{
+ DBusMessageIter inner[4];
+ int depth = 0;
+ while (dbus_message_iter_get_arg_type(it) == DBUS_TYPE_VARIANT && depth < 4) {
+ dbus_message_iter_recurse(it, &inner[depth]);
+ it = &inner[depth++];
+ }
+ DBusBasicValue v;
+ switch (dbus_message_iter_get_arg_type(it)) {
+ case DBUS_TYPE_BYTE: dbus_message_iter_get_basic(it, &v); *out = v.byt; return true;
+ case DBUS_TYPE_BOOLEAN: dbus_message_iter_get_basic(it, &v); *out = v.bool_val; return true;
+ case DBUS_TYPE_INT16: dbus_message_iter_get_basic(it, &v); *out = v.i16; return true;
+ case DBUS_TYPE_UINT16: dbus_message_iter_get_basic(it, &v); *out = v.u16; return true;
+ case DBUS_TYPE_INT32: dbus_message_iter_get_basic(it, &v); *out = v.i32; return true;
+ case DBUS_TYPE_UINT32: dbus_message_iter_get_basic(it, &v); *out = v.u32; return true;
+ case DBUS_TYPE_INT64: dbus_message_iter_get_basic(it, &v); *out = v.i64; return true;
+ case DBUS_TYPE_UINT64:
+ dbus_message_iter_get_basic(it, &v);
+ if (v.u64 > INT64_MAX) return false;
+ *out = (int64_t)v.u64;
+ return true;
+ default: return false;
+ }
+}
+
+bool portal_read_int(const char *ns, const char *key, int64_t *out)
+{
+ DBusConnection *c = session_bus();
+ if (!c) return false;
+ /* ReadOne is portal version 2; older portals only have Read. */
+ static const char *const methods[] = {"ReadOne", "Read"};
+ for (int i = 0; i < 2; i++) {
+ DBusMessage *msg = dbus_message_new_method_call("org.freedesktop.portal.Desktop",
+ "/org/freedesktop/portal/desktop", "org.freedesktop.portal.Settings", methods[i]);
+ if (!msg) return false;
+ if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &ns, DBUS_TYPE_STRING, &key,
+ DBUS_TYPE_INVALID)) {
+ dbus_message_unref(msg);
+ return false;
+ }
+ DBusError err;
+ dbus_error_init(&err);
+ DBusMessage *reply = dbus_connection_send_with_reply_and_block(c, msg,
+ PORTAL_TIMEOUT_MS, &err);
+ dbus_message_unref(msg);
+ if (!reply) {
+ bool retry = dbus_error_has_name(&err, DBUS_ERROR_UNKNOWN_METHOD);
+ dbus_error_free(&err);
+ if (retry) continue;
+ return false;
+ }
+ DBusMessageIter it;
+ bool ok = dbus_message_iter_init(reply, &it) && iter_int(&it, out);
+ dbus_message_unref(reply);
+ return ok;
+ }
+ return false;
+}
+
+#else
+
+bool bus_notify(const char *app, const char *title, const char *body)
+{
+ (void)app;
+ (void)title;
+ (void)body;
+ return false;
+}
+
+bool portal_read_int(const char *ns, const char *key, int64_t *out)
+{
+ (void)ns;
+ (void)key;
+ (void)out;
+ return false;
+}
+
+#endif