2wmw

Tiling Windowmanager for Wayland

Files | Log | Commits | Refs | README


dbus.c

Size: 9229 bytes

#define SNOT_DBUS_INTERFACE "org.freedesktop.Notifications"
#define SNOT_DBUS_PATH "/org/freedesktop/Notifications"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dbus/dbus.h>
#include "dbus.h"
#include "snot.h"
#include "config.h"


static DBusConnection *connection;
static uint32_t next_notification_id = 1;

/* https://dbus.freedesktop.org/doc/dbus-api-design.html */
static const char introspection_xml[] =
    "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n"
    "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
    "<node>\n"
    "  <interface name=\"org.freedesktop.Notifications\">\n"
    "    <method name=\"Notify\">\n"
    "      <arg name=\"app_name\" type=\"s\" direction=\"in\"/>\n"
    "      <arg name=\"replaces_id\" type=\"u\" direction=\"in\"/>\n"
    "      <arg name=\"app_icon\" type=\"s\" direction=\"in\"/>\n"
    "      <arg name=\"summary\" type=\"s\" direction=\"in\"/>\n"
    "      <arg name=\"body\" type=\"s\" direction=\"in\"/>\n"
    "      <arg name=\"actions\" type=\"as\" direction=\"in\"/>\n"
    "      <arg name=\"hints\" type=\"a{sv}\" direction=\"in\"/>\n"
    "      <arg name=\"expire_timeout\" type=\"i\" direction=\"in\"/>\n"
    "      <arg name=\"id\" type=\"u\" direction=\"out\"/>\n"
    "    </method>\n"
    "    <method name=\"GetServerInformation\">\n"
    "      <arg name=\"name\" type=\"s\" direction=\"out\"/>\n"
    "      <arg name=\"vendor\" type=\"s\" direction=\"out\"/>\n"
    "      <arg name=\"version\" type=\"s\" direction=\"out\"/>\n"
    "      <arg name=\"spec_version\" type=\"s\" direction=\"out\"/>\n"
    "    </method>\n"
    "    <method name=\"GetCapabilities\">\n"
    "      <arg name=\"capabilities\" type=\"as\" direction=\"out\"/>\n"
    "    </method>\n"
    "  </interface>\n"
    "</node>\n";

int
dbus_get_fd(void) {
    unsigned long pid;
    dbus_connection_get_unix_process_id(connection, &pid);
    return (int)pid;
}

static DBusHandlerResult
method_get_server_information(DBusConnection *conn, DBusMessage *msg) {
    DBusMessage *reply = dbus_message_new_method_return(msg);
    if (!reply)
        return DBUS_HANDLER_RESULT_NEED_MEMORY;

    const char *name = "snot";
    const char *vendor = "snot";
    const char *version = "1.0";
    const char *spec_version = "1.2";

    dbus_message_append_args(reply,
                           DBUS_TYPE_STRING, &name,
                           DBUS_TYPE_STRING, &vendor,
                           DBUS_TYPE_STRING, &version,
                           DBUS_TYPE_STRING, &spec_version,
                           DBUS_TYPE_INVALID);

    dbus_connection_send(conn, reply, NULL);
    dbus_message_unref(reply);

    return DBUS_HANDLER_RESULT_HANDLED;
}

static DBusHandlerResult
method_get_capabilities(DBusConnection *conn, DBusMessage *msg) {
    DBusMessage *reply = dbus_message_new_method_return(msg);
    if (!reply)
        return DBUS_HANDLER_RESULT_NEED_MEMORY;

    DBusMessageIter iter, array;
    dbus_message_iter_init_append(reply, &iter);
    dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &array);

    const char *capabilities[] = {
        "body",
        "body-markup",
        "actions",
        NULL
    };

    for (const char **cap = capabilities; *cap; cap++) {
        dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, cap);
    }

    dbus_message_iter_close_container(&iter, &array);
    dbus_connection_send(conn, reply, NULL);
    dbus_message_unref(reply);

    return DBUS_HANDLER_RESULT_HANDLED;
}

static DBusHandlerResult
handle_notification_method(DBusConnection *conn, DBusMessage *msg) {
    DBusMessageIter iter;
    char *app_name = NULL, *app_icon = NULL, *summary = NULL, *body = NULL;
    uint32_t replaces_id = 0;
    int32_t expire_timeout = -1;

    printf("Received notification request\n");

    if (!dbus_message_iter_init(msg, &iter)) {
        fprintf(stderr, "Message has no arguments\n");
        goto error;
    }

    if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
        fprintf(stderr, "First argument is not a string\n");
        goto error;
    }
    dbus_message_iter_get_basic(&iter, &app_name);
    printf("App name: %s\n", app_name);
    if (!dbus_message_iter_next(&iter)) goto error;

    if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) goto error;
    dbus_message_iter_get_basic(&iter, &replaces_id);
    printf("Replaces ID: %u\n", replaces_id);
    if (!dbus_message_iter_next(&iter)) goto error;

    if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) goto error;
    dbus_message_iter_get_basic(&iter, &app_icon);
    printf("App icon: %s\n", app_icon);
    if (!dbus_message_iter_next(&iter)) goto error;

    if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) goto error;
    dbus_message_iter_get_basic(&iter, &summary);
    printf("Summary: %s\n", summary);
    if (!dbus_message_iter_next(&iter)) goto error;

    if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) goto error;
    dbus_message_iter_get_basic(&iter, &body);
    printf("Body: %s\n", body);

    if (!dbus_message_iter_next(&iter)) goto error;
    if (!dbus_message_iter_next(&iter)) goto error;

    if (!dbus_message_iter_next(&iter)) goto error;
    if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32) goto error;
    dbus_message_iter_get_basic(&iter, &expire_timeout);

    printf("Creating notification...\n");
    add_notification(summary, body, app_name, replaces_id, expire_timeout);
    printf("Notification created\n");

    DBusMessage *reply = dbus_message_new_method_return(msg);
    if (reply) {
        uint32_t id = next_notification_id++;
        dbus_message_append_args(reply,
                               DBUS_TYPE_UINT32, &id,
                               DBUS_TYPE_INVALID);
        dbus_connection_send(conn, reply, NULL);
        dbus_message_unref(reply);
        printf("Reply sent, ID: %u\n", id);
    }

    return DBUS_HANDLER_RESULT_HANDLED;

error:
    fprintf(stderr, "Failed to parse notification message\n");
    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

static DBusHandlerResult
handle_message(DBusConnection *conn, DBusMessage *msg, void *user_data) {
    printf("Received D-Bus message\n");  // Debug print

    if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Introspectable", "Introspect")) {
        printf("Handling Introspect request\n");  // Debug print
        DBusMessage *reply = dbus_message_new_method_return(msg);
        if (!reply) {
            return DBUS_HANDLER_RESULT_NEED_MEMORY;
        }

        dbus_message_append_args(reply,
                               DBUS_TYPE_STRING, &introspection_xml,
                               DBUS_TYPE_INVALID);

        dbus_connection_send(conn, reply, NULL);
        dbus_connection_flush(conn);
        dbus_message_unref(reply);
        return DBUS_HANDLER_RESULT_HANDLED;
    }

    if (dbus_message_is_method_call(msg, SNOT_DBUS_INTERFACE, "GetServerInformation")) {
        printf("Handling GetServerInformation request\n");  // Debug print
        return method_get_server_information(conn, msg);
    }

    if (dbus_message_is_method_call(msg, SNOT_DBUS_INTERFACE, "GetCapabilities")) {
        printf("Handling GetCapabilities request\n");  // Debug print
        return method_get_capabilities(conn, msg);
    }

    if (dbus_message_is_method_call(msg, SNOT_DBUS_INTERFACE, "Notify")) {
        printf("Handling Notify request\n");  // Debug print
        DBusHandlerResult result = handle_notification_method(conn, msg);
        dbus_connection_flush(conn);
        return result;
    }

    printf("Unhandled D-Bus message\n");  // Debug print
    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

static const DBusObjectPathVTable vtable = {
    .message_function = handle_message,
    .unregister_function = NULL,
};

int
dbus_init(void) {
    DBusError err;
    dbus_error_init(&err);

    connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
    if (dbus_error_is_set(&err)) {
        fprintf(stderr, "Failed to connect to bus: %s\n", err.message);
        dbus_error_free(&err);
        return -1;
    }

    dbus_connection_set_exit_on_disconnect(connection, FALSE);

    int ret = dbus_bus_request_name(connection, SNOT_DBUS_INTERFACE,
                                  DBUS_NAME_FLAG_REPLACE_EXISTING,
                                  &err);
    if (dbus_error_is_set(&err)) {
        fprintf(stderr, "Failed to request name: %s\n", err.message);
        dbus_error_free(&err);
        return -1;
    }
    if (ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
        fprintf(stderr, "Not primary owner of interface\n");
        return -1;
    }

    if (!dbus_connection_register_object_path(connection, SNOT_DBUS_PATH,
                                            &vtable, NULL)) {
        fprintf(stderr, "Failed to register object path\n");
        return -1;
    }

    printf("D-Bus initialized successfully\n");
    return 0;
}

void
dbus_destroy(void) {
    if (connection) {
        dbus_connection_unref(connection);
    }
}

int
dbus_dispatch(void) {
    dbus_connection_read_write_dispatch(connection, 0);
    return 0;
}