dwlb

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 86806a43fbe42ba86d540d22bbdca84f18ba711b
parent 992439120383933021e5a4d2db7d26f7931a1e03
Author: Janne Veteläinen <janne.vetelainen@elisanet.fi>
Date:   Wed,  3 Jul 2024 08:06:50 +0300

Multi-monitor support

Diffstat:
Msystray/Makefile | 4++--
Msystray/dwlbtray.c | 130++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msystray/sndbusmenu.c | 46++++++++++++++++++++++------------------------
Msystray/snhost.c | 503++++++++++++++++++++++++-------------------------------------------------------
Msystray/snhost.h | 32+++++---------------------------
Msystray/snitem.c | 33+++++++++++++++++----------------
Asystray/snwatcher.c | 409+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asystray/snwatcher.h | 40++++++++++++++++++++++++++++++++++++++++
8 files changed, 714 insertions(+), 483 deletions(-)

diff --git a/systray/Makefile b/systray/Makefile @@ -1,6 +1,6 @@ BINS = dwlbtray -OBJS = dwlbtray.o snhost.o snitem.o sndbusmenu.o -DEPS = snhost.h snitem.h sndbusmenu.h +OBJS = dwlbtray.o snwatcher.o snhost.o snitem.o sndbusmenu.o +DEPS = snwatcher.h snhost.h snitem.h sndbusmenu.h PREFIX ?= /usr/local CFLAGS += -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -g diff --git a/systray/dwlbtray.c b/systray/dwlbtray.c @@ -12,13 +12,17 @@ typedef struct args_parsed { int barheight; - char traymon[1024]; - char cssdata[1024]; + char cssdata[64]; int position; } args_parsed; -static int margin = 4; -static int spacing = 4; +static const int margin = 4; +static const int spacing = 4; + +enum { + DWLB_POSITION_TOP, + DWLB_POSITION_BOTTOM +}; static void activate(GtkApplication* app, void *data) @@ -33,69 +37,78 @@ activate(GtkApplication* app, void *data) win_default_width = args->barheight; win_default_height = args->barheight; - GtkWindow *window = GTK_WINDOW(gtk_window_new()); - gtk_window_set_decorated(window, FALSE); - gtk_window_set_default_size(window, win_default_width, win_default_height); - gtk_window_set_application(window, app); - GtkCssProvider *css = gtk_css_provider_new(); gtk_css_provider_load_from_string(css, args->cssdata); gtk_style_context_add_provider_for_display(display, GTK_STYLE_PROVIDER(css), GTK_STYLE_PROVIDER_PRIORITY_USER); - gtk_widget_add_css_class(GTK_WIDGET(window), "dwlbtray"); g_object_unref(css); - gtk_layer_init_for_window(window); - gtk_layer_set_layer(window, GTK_LAYER_SHELL_LAYER_BOTTOM); - gtk_layer_set_exclusive_zone(window, -1); - static gboolean anchors[] = {FALSE, TRUE, TRUE, FALSE}; - if (args->position == 1) { - anchors[0] = FALSE; // left - anchors[1] = TRUE; // right - anchors[2] = FALSE; // top - anchors[3] = TRUE; // bottom - } - for (int i = 0; i < GTK_LAYER_SHELL_EDGE_ENTRY_NUMBER; i++) { - gtk_layer_set_anchor(window, i, anchors[i]); - } - - const char *traymon = NULL; - - if (strcmp(args->traymon, "") != 0) { - traymon = args->traymon; + gboolean anchors[4]; + + switch (args->position) { + case DWLB_POSITION_TOP: + anchors[0] = FALSE; // left + anchors[1] = TRUE; // right + anchors[2] = TRUE; // top + anchors[3] = FALSE; // bottom + break; + case DWLB_POSITION_BOTTOM: + anchors[0] = FALSE; // left + anchors[1] = TRUE; // right + anchors[2] = FALSE; // top + anchors[3] = TRUE; // bottom + break; + default: + g_assert_not_reached(); + break; } GListModel *mons = gdk_display_get_monitors(display); - if (traymon) { - for (uint i = 0; i < g_list_model_get_n_items(mons); i++) { - GdkMonitor *mon = g_list_model_get_item(mons, i); - const char *conn = gdk_monitor_get_connector(mon); - if (strcmp(conn, traymon) == 0) { - gtk_layer_set_monitor(window, mon); - } - } - } else { - GdkMonitor *mon = g_list_model_get_item(mons, 0); + + // Create tray for each monitor + for (uint i = 0; i < g_list_model_get_n_items(mons); i++) { + GdkMonitor *mon = g_list_model_get_item(mons, i); const char *conn = gdk_monitor_get_connector(mon); - traymon = conn; - gtk_layer_set_monitor(window, mon); - } + SnHost *host = sn_host_new(win_default_width, + win_default_height, + iconsize, + margin, + spacing, + conn); + + GtkWindow *window = GTK_WINDOW(host); + + gtk_window_set_application(window, app); - SnHost *host = sn_host_new(traymon, iconsize, margin, spacing); - GtkWidget *widget = GTK_WIDGET(host); - gtk_window_set_child(window, widget); + gtk_layer_init_for_window(window); + gtk_layer_set_layer(window, GTK_LAYER_SHELL_LAYER_BOTTOM); + gtk_layer_set_exclusive_zone(window, -1); - gtk_window_present(window); + gtk_layer_set_monitor(window, mon); + + for (int j = 0; j < GTK_LAYER_SHELL_EDGE_ENTRY_NUMBER; j++) { + gtk_layer_set_anchor(window, j, anchors[j]); + } + + gtk_window_present(window); + } } +static void +terminate_app_helper(void *data, void *udata) +{ + GtkWindow *window = GTK_WINDOW(data); + + gtk_window_close(window); +} static gboolean terminate_app(GtkApplication *app) { - GtkWindow *win = gtk_application_get_active_window(app); - gtk_window_close(win); + GList *windows = gtk_application_get_windows(app); + g_list_foreach(windows, terminate_app_helper, NULL); return G_SOURCE_REMOVE; } @@ -105,35 +118,30 @@ main(int argc, char *argv[]) { args_parsed args; args.barheight = 22; - args.position = 0; - *args.traymon = '\0'; - *args.cssdata = '\0'; + args.position = DWLB_POSITION_TOP; char *bgcolor = NULL; - int position = 0; int i = 1; for (; i < argc; i++) { char **strings = g_strsplit(argv[i], "=", 0); if (strcmp(strings[0], "--height") == 0) { - args.barheight = atoi(strings[1]); - } else if (strcmp(strings[0], "--traymon") == 0) { - strncpy(args.traymon, strings[1], sizeof(args.traymon)); + args.barheight = strtol(strings[1], NULL, 10); } else if (strcmp(strings[0], "--bg-color") == 0) { bgcolor = strdup(strings[1]); } else if (strcmp(strings[0], "--position") == 0) { if (strcmp(strings[1], "bottom") == 0) - position = 1; + args.position = DWLB_POSITION_BOTTOM; } g_strfreev(strings); } - args.position = position; - - if (bgcolor) - snprintf(args.cssdata, sizeof(args.cssdata), "window.dwlbtray{background-color:%s;}", bgcolor); - else - snprintf(args.cssdata, sizeof(args.cssdata), "window.dwlbtray{background-color:%s;}", "#222222"); + if (bgcolor) { + snprintf(args.cssdata, sizeof(args.cssdata), "window{background-color:%s;}", bgcolor); + g_free(bgcolor); + } else { + snprintf(args.cssdata, sizeof(args.cssdata), "window{background-color:%s;}", "#222222"); + } GtkApplication *app = gtk_application_new("org.dwlb.dwlbtray", G_APPLICATION_DEFAULT_FLAGS); diff --git a/systray/sndbusmenu.c b/systray/sndbusmenu.c @@ -59,20 +59,20 @@ static void sn_dbusmenu_dispose (GObject *object); static void sn_dbusmenu_finalize (GObject *object); static void sn_dbusmenu_get_property (GObject *object, - uint property_id, - GValue *value, - GParamSpec *pspec); + uint property_id, + GValue *value, + GParamSpec *pspec); static void sn_dbusmenu_set_property (GObject *object, - uint property_id, - const GValue *value, - GParamSpec *pspec); + uint property_id, + const GValue *value, + GParamSpec *pspec); static GMenu* create_menumodel (GVariant *data, SnDbusmenu *self); static GMenuItem* create_menuitem (int32_t id, GVariant *menu_data, - GVariant *submenu_data, - SnDbusmenu *self); + GVariant *submenu_data, + SnDbusmenu *self); static void @@ -137,7 +137,7 @@ create_menuitem(int32_t id, GVariant *menuitem_data, GVariant *submenuitem_data, gboolean isvisible = TRUE; gboolean has_submenu = FALSE; - /* + /* * gboolean ischeckmark = FALSE; * gboolean isradio = FALSE; * int32_t toggle_state = 99; @@ -153,7 +153,7 @@ create_menuitem(int32_t id, GVariant *menuitem_data, GVariant *submenuitem_data, g_variant_dict_lookup(&dict, "visible", "b", &isvisible); g_variant_dict_lookup(&dict, "children-display", "&s", &has_submenu_s); - /* + /* * g_variant_dict_lookup(&dict, "toggle-type", "&s", &toggle_type); * g_variant_dict_lookup(&dict, "toggle-state", "i", &toggle_state); */ @@ -163,7 +163,7 @@ create_menuitem(int32_t id, GVariant *menuitem_data, GVariant *submenuitem_data, if (has_submenu_s && strcmp(has_submenu_s, "submenu") == 0) has_submenu = TRUE; - /* + /* * if (toggle_type && strcmp(toggle_type, "checkmark") == 0) * ischeckmark = TRUE; * else if (toggle_type && strcmp(toggle_type, "radio") == 0) @@ -196,9 +196,6 @@ create_menuitem(int32_t id, GVariant *menuitem_data, GVariant *submenuitem_data, g_object_unref(submenu); } - if (menuitem) - g_menu_item_set_attribute(menuitem, "itemid", "i", id); - return menuitem; } @@ -236,7 +233,7 @@ layout_update_finish(GObject *obj, GAsyncResult *res, void *udata) { SnDbusmenu *self = SN_DBUSMENU(udata); GError *err = NULL; - + GVariant *data = g_dbus_proxy_call_finish(self->proxy, res, &err); if (err) { @@ -475,19 +472,19 @@ proxy_ready_handler(GObject *obj, GAsyncResult *res, void *data) } g_debug("Created gdbusproxy for menu %s %s", - g_dbus_proxy_get_name(proxy), - g_dbus_proxy_get_object_path(proxy)); + g_dbus_proxy_get_name(proxy), + g_dbus_proxy_get_object_path(proxy)); g_object_set(self, "proxy", proxy, NULL); g_dbus_proxy_call(self->proxy, - "GetLayout", - g_variant_new ("(iias)", 0, -1, NULL), - G_DBUS_CALL_FLAGS_NONE, - -1, - NULL, - menulayout_ready_handler, - g_object_ref(self)); + "GetLayout", + g_variant_new ("(iias)", 0, -1, NULL), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + menulayout_ready_handler, + g_object_ref(self)); g_signal_connect(self->proxy, "g-signal", G_CALLBACK(proxy_signal_handler), self); g_object_unref(self); @@ -519,6 +516,7 @@ sn_dbusmenu_set_property(GObject *object, uint property_id, const GValue *value, break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; } } diff --git a/systray/snhost.c b/systray/snhost.c @@ -11,6 +11,7 @@ #include <gio/gio.h> #include <gtk/gtk.h> +#include "snwatcher.h" #include "snitem.h" @@ -18,28 +19,32 @@ struct _SnHost { GtkWidget parent_instance; - GDBusConnection* conn; - char* traymon; - GSList* trayitems; + GtkWidget* box; + SnWatcher* watcher; + GHashTable* snitems; + char* mon; + ulong reg_sub_id; + ulong unreg_sub_id; + + int defaultwidth; + int defaultheight; int iconsize; int margins; int spacing; - int owner_id; - int obj_reg_id; - int sig_sub_id; - int nitems; int curwidth; gboolean exiting; }; -G_DEFINE_FINAL_TYPE(SnHost, sn_host, GTK_TYPE_BOX) +G_DEFINE_FINAL_TYPE(SnHost, sn_host, GTK_TYPE_WINDOW) enum { - PROP_TRAYMON = 1, + PROP_MON = 1, + PROP_DEFAULTWIDTH, + PROP_DEFAULTHEIGHT, PROP_ICONSIZE, PROP_MARGINS, PROP_SPACING, @@ -49,341 +54,88 @@ enum static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, }; -static void sn_host_constructed (GObject *obj); -static void sn_host_dispose (GObject *obj); -static void sn_host_finalize (GObject *obj); - -static void sn_host_bus_call_method_handler (GDBusConnection *conn, - const char *sender, - const char *object_path, - const char *interface_name, - const char *method_name, - GVariant *parameters, - GDBusMethodInvocation *invocation, - void *data); - -static GVariant* sn_host_bus_prop_get_handler (GDBusConnection* conn, - const char* sender, - const char* object_path, - const char* interface_name, - const char* property_name, - GError** err, - void *data); - -static GDBusInterfaceVTable interface_vtable = { - sn_host_bus_call_method_handler, - sn_host_bus_prop_get_handler, - NULL -}; +static void sn_host_constructed (GObject *obj); +static void sn_host_dispose (GObject *obj); +static void sn_host_finalize (GObject *obj); static void dwlb_request_resize(SnHost *self) { - if (self->exiting) + if (self->exiting) { + // Restore original size on exit. self->curwidth = 0; - else if (self->nitems <= 1) + } else if (self->nitems <= 1) { + // Width of 1 icon even when there are none. self->curwidth = self->iconsize + 2 * self->margins; - else - self->curwidth = self->nitems * self->iconsize + - (self->nitems - 1) * self->spacing + - 2 * self->margins; + } else { + self->curwidth = + // Icons themselves. + self->nitems * self->iconsize + + + // Spacing between icons. + (self->nitems - 1) * self->spacing + + + // Margins before first icon and after last icon. + 2 * self->margins; + } struct sockaddr_un sockaddr; + sockaddr.sun_family = AF_UNIX; - char *socketpath = g_strdup_printf("%s/dwlb/dwlb-0", g_get_user_runtime_dir()); - snprintf(sockaddr.sun_path, sizeof(sockaddr.sun_path), "%s", socketpath); - char *sockbuf = NULL; - if (self->traymon) { - sockbuf = g_strdup_printf("%s %s %i", self->traymon, "resize", self->curwidth); - } - else { - sockbuf = g_strdup_printf("%s %s %i", "all", "resize", self->curwidth); - } + snprintf(sockaddr.sun_path, sizeof(sockaddr.sun_path), "%s/dwlb/dwlb-0", g_get_user_runtime_dir()); + + char sockbuf[64]; + snprintf(sockbuf, sizeof(sockbuf), "%s %s %i", self->mon, "resize", self->curwidth); size_t len = strlen(sockbuf); int sock_fd = socket(AF_UNIX, SOCK_STREAM, 1); - int connstatus = connect(sock_fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)); - if (connstatus != 0) { + int connstatus = + connect(sock_fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)); + + if (connstatus != 0) g_error("Error connecting to dwlb socket"); - } - if (send(sock_fd, sockbuf, len, 0) == -1) + size_t sendstatus = + send(sock_fd, sockbuf, len, 0); + + if (sendstatus == (size_t)-1) g_error("Could not send size update to %s", sockaddr.sun_path); - close(sock_fd); - g_free(socketpath); - g_free(sockbuf); + close(sock_fd); } static void -sn_host_register_item(SnHost *self, +sn_host_register_item(SnWatcher *watcher, const char *busname, - const char *busobj) + const char *busobj, + SnHost *self) { - g_debug("Registering %s", busname); + g_debug("Adding %s to snhost %s", busname, self->mon); + SnItem *snitem = sn_item_new(busname, busobj, self->iconsize); - gtk_box_append(GTK_BOX(self), GTK_WIDGET(snitem)); + gtk_box_append(GTK_BOX(self->box), GTK_WIDGET(snitem)); + g_hash_table_insert(self->snitems, g_strdup(busname), snitem); + self->nitems = self->nitems + 1; - self->trayitems = g_slist_prepend(self->trayitems, snitem); dwlb_request_resize(self); - - GError *err = NULL; - g_dbus_connection_emit_signal(self->conn, - NULL, - "/StatusNotifierWatcher", - "org.kde.StatusNotifierWatcher", - "StatusNotifierItemRegistered", - g_variant_new("(s)", busname), - &err); - if (err) { - g_warning("%s", err->message); - g_error_free(err); - } } static void -sn_host_unregister_item(SnHost *self, SnItem *snitem) +sn_host_unregister_item(SnWatcher *watcher, const char *busname, SnHost *self) { - char *busname = sn_item_get_busname(snitem); - g_debug("Unregistering %s", busname); + g_debug("Removing %s from snhost %s", busname, self->mon); - self->trayitems = g_slist_remove(self->trayitems, snitem); + GtkBox *box = GTK_BOX(self->box); + void *match = g_hash_table_lookup(self->snitems, busname); + GtkWidget *snitem = GTK_WIDGET(match); - gtk_box_remove(GTK_BOX(self), GTK_WIDGET(snitem)); + gtk_box_remove(box, snitem); + g_hash_table_remove(self->snitems, busname); self->nitems = self->nitems - 1; - dwlb_request_resize(self); - - GError *err = NULL; - - g_dbus_connection_emit_signal(self->conn, - NULL, - "/StatusNotifierWatcher", - "org.kde.StatusNotifierWatcher", - "StatusNotifierItemUnregistered", - g_variant_new("(s)", busname), - &err); - if (err) { - g_warning("%s", err->message); - g_error_free(err); - } - - g_free(busname); -} - -static int -nameowner_find_helper(SnItem *snitem, const char *busname_match) -{ - char *busname = sn_item_get_busname(snitem); - int ret = 1; - if (strcmp(busname, busname_match) == 0) - ret = 0; - else - ret = -1; - - g_free(busname); - return ret; -} - -static void -sn_host_bus_monitor(GDBusConnection* conn, - const char* sender, - const char* objpath, - const char* iface_name, - const char* signame, - GVariant *params, - void *data) -{ - SnHost *self = SN_HOST(data); - - if (strcmp(signame, "NameOwnerChanged") == 0) { - if (!self->trayitems) - return; - - const char *name; - const char *old_owner; - const char *new_owner; - g_variant_get(params, "(&s&s&s)", &name, &old_owner, &new_owner); - if (strcmp(new_owner, "") == 0) { - GSList *pmatch = g_slist_find_custom(self->trayitems, name, (GCompareFunc)nameowner_find_helper); - if (pmatch) { - SnItem *snitem = pmatch->data; - sn_host_unregister_item(self, snitem); - } - } - - } -} - -static void -unregister_all_helper(SnItem* item, SnHost *host) -{ - sn_host_unregister_item(host, item); -} - - -static void -sn_host_unregister_all(SnHost *self) -{ - g_slist_foreach(self->trayitems, (GFunc)unregister_all_helper, self); -} - -static void -sn_host_bus_call_method_handler(GDBusConnection *conn, - const char *sender, - const char *obj_path, - const char *iface_name, - const char *method_name, - GVariant *params, - GDBusMethodInvocation *invoc, - void *data) -{ - SnHost *self = SN_HOST(data); - - if (strcmp(method_name, "RegisterStatusNotifierItem") == 0) { - const char *param; - const char *busobj; - const char *registree_name; - - g_variant_get(params, "(&s)", &param); - - if (g_str_has_prefix(param, "/")) { - busobj = param; - } else { - busobj = "/StatusNotifierItem"; - } - - if (g_str_has_prefix(param, ":") && strcmp(sender, param) != 0) - registree_name = param; - else - registree_name = sender; - - sn_host_register_item(self, registree_name, busobj); - g_dbus_method_invocation_return_value(invoc, NULL); - - } else { - g_dbus_method_invocation_return_dbus_error(invoc, - "org.freedesktop.DBus.Error.UnknownMethod", - "Unknown method"); - } -} - -static void -bus_get_snitems_helper(void *data, void *udata) -{ - SnItem *item = SN_ITEM(data); - GVariantBuilder *builder = (GVariantBuilder*)udata; - - char *busname = sn_item_get_busname(item); - g_variant_builder_add_value(builder, g_variant_new_string(busname)); - g_free(busname); -} - -static GVariant* -sn_host_bus_prop_get_handler(GDBusConnection* conn, - const char* sender, - const char* object_path, - const char* interface_name, - const char* property_name, - GError** err, - void *data) -{ - SnHost *self = SN_HOST(data); - - if (strcmp(property_name, "ProtocolVersion") == 0) { - return g_variant_new("i", 0); - } else if (strcmp(property_name, "IsStatusNotifierHostRegistered") == 0) { - return g_variant_new("b", TRUE); - } else if (strcmp(property_name, "RegisteredStatusNotifierItems") == 0) { - if (!self->trayitems) - return g_variant_new("as", NULL); - - GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY); - g_slist_foreach(self->trayitems, bus_get_snitems_helper, builder); - GVariant *as = g_variant_builder_end(builder); - - g_variant_builder_unref(builder); - - return as; - } else { - g_set_error(err, - G_DBUS_ERROR, - G_DBUS_ERROR_UNKNOWN_PROPERTY, - "Unknown property '%s'.", - property_name); - return NULL; - } -} - -static void -sn_host_bus_acquired_handler(GDBusConnection *conn, const char *busname, void *data) -{ - SnHost *self = SN_HOST(data); - - self->conn = conn; - - GError *err = NULL; - GDBusNodeInfo *nodeinfo = g_dbus_node_info_new_for_xml(STATUSNOTIFIERWATCHER_XML, NULL); - - self->obj_reg_id = - g_dbus_connection_register_object(self->conn, - "/StatusNotifierWatcher", - nodeinfo->interfaces[0], - &interface_vtable, - self, - NULL, - &err); - - g_dbus_node_info_unref(nodeinfo); - - if (err) { - g_error("%s", err->message); - g_error_free(err); - exit(-1); - } - - self->sig_sub_id = - g_dbus_connection_signal_subscribe(self->conn, - NULL, // Listen to all senders); - "org.freedesktop.DBus", - "NameOwnerChanged", - NULL, // Match all obj paths - NULL, // Match all arg0s - G_DBUS_SIGNAL_FLAGS_NONE, - sn_host_bus_monitor, - self, - NULL); -} - -static void -sn_host_bus_name_acquired_handler(GDBusConnection *conn, const char *busname, void *data) -{ - SnHost *self = SN_HOST(data); - - GError *err = NULL; - - g_dbus_connection_emit_signal(self->conn, - NULL, - "/StatusNotifierWatcher", - "org.kde.StatusNotifierWatcher", - "StatusNotifierHostRegistered", - NULL, - &err); - - if (err) { - g_warning("%s", err->message); - g_error_free(err); - } -} - -static void -sn_host_bus_name_lost_handler(GDBusConnection *conn, const char *busname, void *data) -{ - g_error("Could not acquire %s, maybe another instance is running?", busname); - exit(-1); } static void @@ -392,8 +144,14 @@ sn_host_set_property(GObject *object, uint property_id, const GValue *value, GPa SnHost *self = SN_HOST(object); switch (property_id) { - case PROP_TRAYMON: - self->traymon = g_strdup(g_value_get_string(value)); + case PROP_MON: + self->mon = g_value_dup_string(value); + break; + case PROP_DEFAULTWIDTH: + self->defaultwidth = g_value_get_int(value); + break; + case PROP_DEFAULTHEIGHT: + self->defaultheight = g_value_get_int(value); break; case PROP_ICONSIZE: self->iconsize = g_value_get_int(value); @@ -406,6 +164,7 @@ sn_host_set_property(GObject *object, uint property_id, const GValue *value, GPa break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; } } @@ -426,12 +185,37 @@ sn_host_class_init(SnHostClass *klass) object_class->dispose = sn_host_dispose; object_class->finalize = sn_host_finalize; + obj_properties[PROP_MON] = + g_param_spec_string("mon", NULL, NULL, + NULL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_WRITABLE | + G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_DEFAULTWIDTH] = + g_param_spec_int("defaultwidth", NULL, NULL, + G_MININT, + G_MAXINT, + 22, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_WRITABLE | + G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_DEFAULTHEIGHT] = + g_param_spec_int("defaultheight", NULL, NULL, + G_MININT, + G_MAXINT, + 22, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_WRITABLE | + G_PARAM_STATIC_STRINGS); + obj_properties[PROP_ICONSIZE] = g_param_spec_int("iconsize", NULL, NULL, G_MININT, G_MAXINT, 22, - G_PARAM_CONSTRUCT_ONLY | + G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); @@ -440,8 +224,8 @@ sn_host_class_init(SnHostClass *klass) G_MININT, G_MAXINT, 4, - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_SPACING] = @@ -449,14 +233,8 @@ sn_host_class_init(SnHostClass *klass) G_MININT, G_MAXINT, 4, - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_WRITABLE | - G_PARAM_STATIC_STRINGS); - obj_properties[PROP_TRAYMON] = - g_param_spec_string("traymon", NULL, NULL, - NULL, - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties(object_class, N_PROPERTIES, obj_properties); @@ -465,63 +243,75 @@ sn_host_class_init(SnHostClass *klass) static void sn_host_init(SnHost *self) { + self->snitems = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + self->exiting = FALSE; self->nitems = 0; - self->trayitems = NULL; - - self->owner_id = - g_bus_own_name(G_BUS_TYPE_SESSION, - "org.kde.StatusNotifierWatcher", - G_BUS_NAME_OWNER_FLAGS_NONE, - sn_host_bus_acquired_handler, - sn_host_bus_name_acquired_handler, - sn_host_bus_name_lost_handler, - self, - NULL); + + self->watcher = sn_watcher_new(); + + self->reg_sub_id = g_signal_connect(self->watcher, + "trayitem-registered", + G_CALLBACK(sn_host_register_item), + self); + + self->unreg_sub_id = g_signal_connect(self->watcher, + "trayitem-unregistered", + G_CALLBACK(sn_host_unregister_item), + self); } static void sn_host_constructed(GObject *obj) { SnHost *self = SN_HOST(obj); - GtkWidget *widget = GTK_WIDGET(obj); + + GtkWindow *window = GTK_WINDOW(self); + gtk_window_set_decorated(window, FALSE); + gtk_window_set_default_size(window, self->defaultwidth, self->defaultheight); + + self->box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, self->spacing); + gtk_box_set_homogeneous(GTK_BOX(self->box), TRUE); + gtk_box_set_spacing(GTK_BOX(self->box), self->margins); + + GtkWidget *widget = GTK_WIDGET(self->box); gtk_widget_set_vexpand(widget, TRUE); gtk_widget_set_hexpand(widget, TRUE); gtk_widget_set_margin_start(widget, self->margins); gtk_widget_set_margin_end(widget, self->margins); gtk_widget_set_margin_top(widget, self->margins); gtk_widget_set_margin_bottom(widget, self->margins); - gtk_box_set_homogeneous(GTK_BOX(obj), TRUE); - gtk_box_set_spacing(GTK_BOX(obj), self->margins); + + gtk_window_set_child(GTK_WINDOW(self), self->box); dwlb_request_resize(self); + g_debug("Created snhost for monitor %s", self->mon); + G_OBJECT_CLASS(sn_host_parent_class)->constructed(obj); } static void sn_host_dispose(GObject *obj) { - g_debug("Disposing snhost"); SnHost *self = SN_HOST(obj); - self->exiting = TRUE; - sn_host_unregister_all(self); + g_debug("Disposing snhost of monitor %s", self->mon); + self->exiting = TRUE; - if (self->sig_sub_id > 0) { - g_dbus_connection_signal_unsubscribe(self->conn, self->sig_sub_id); - self->sig_sub_id = 0; + if (self->reg_sub_id > 0) { + g_signal_handler_disconnect(self->watcher, self->reg_sub_id); + self->reg_sub_id = 0; } - if (self->obj_reg_id > 0) { - g_dbus_connection_unregister_object(self->conn, self->obj_reg_id); - self->obj_reg_id = 0; + if (self->unreg_sub_id > 0) { + g_signal_handler_disconnect(self->watcher, self->unreg_sub_id); + self->reg_sub_id = 0; } - if (self->owner_id > 0) { - g_bus_unown_name(self->owner_id); - self->owner_id = 0; - self->conn = NULL; + if (self->watcher) { + g_object_unref(self->watcher); + self->watcher = NULL; } dwlb_request_resize(self); @@ -534,19 +324,26 @@ sn_host_finalize(GObject *obj) { SnHost *self = SN_HOST(obj); - g_free(self->traymon); - g_slist_free(self->trayitems); + g_hash_table_destroy(self->snitems); + g_free(self->mon); G_OBJECT_CLASS(sn_host_parent_class)->finalize(obj); } SnHost* -sn_host_new(const char *traymon, int iconsize, int margins, int spacing) +sn_host_new(int defaultwidth, + int defaultheight, + int iconsize, + int margins, + int spacing, + const char *conn) { return g_object_new(SN_TYPE_HOST, - "traymon", traymon, + "defaultwidth", defaultwidth, + "defaultheight", defaultheight, "iconsize", iconsize, "margins", margins, "spacing", spacing, + "mon", conn, NULL); } diff --git a/systray/snhost.h b/systray/snhost.h @@ -7,37 +7,15 @@ G_BEGIN_DECLS #define SN_TYPE_HOST sn_host_get_type() -G_DECLARE_FINAL_TYPE(SnHost, sn_host, SN, HOST, GtkBox) +G_DECLARE_FINAL_TYPE(SnHost, sn_host, SN, HOST, GtkWindow) -SnHost *sn_host_new (const char *traymon, +SnHost *sn_host_new (int defaultwidth, + int defaultheight, int iconsize, int margins, - int spacing); + int spacing, + const char *conn); G_END_DECLS -#define STATUSNOTIFIERWATCHER_XML \ - "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" \ - "<node>\n" \ - " <interface name=\"org.kde.StatusNotifierWatcher\">\n" \ - " <!-- methods -->\n" \ - " <method name=\"RegisterStatusNotifierItem\">\n" \ - " <arg name=\"service\" type=\"s\" direction=\"in\" />\n" \ - " </method>\n" \ - " <!-- properties -->\n" \ - " <property name=\"IsStatusNotifierHostRegistered\" type=\"b\" access=\"read\" />\n" \ - " <property name=\"ProtocolVersion\" type=\"i\" access=\"read\" />\n" \ - " <property name=\"RegisteredStatusNotifierItems\" type=\"as\" access=\"read\" />\n" \ - " <!-- signals -->\n" \ - " <signal name=\"StatusNotifierItemRegistered\">\n" \ - " <arg type=\"s\"/>\n" \ - " </signal>\n" \ - " <signal name=\"StatusNotifierItemUnregistered\">\n" \ - " <arg type=\"s\"/>\n" \ - " </signal>\n" \ - " <signal name=\"StatusNotifierHostRegistered\">\n" \ - " </signal>\n" \ - " </interface>\n" \ - "</node>\n" - #endif /* SNHOST_H */ diff --git a/systray/snitem.c b/systray/snitem.c @@ -72,17 +72,17 @@ static void sn_item_dispose (GObject *obj); static void sn_item_finalize (GObject *obj); static void sn_item_size_allocate (GtkWidget *widget, - int width, - int height, - int baseline); + int width, + int height, + int baseline); static void sn_item_measure (GtkWidget *widget, - GtkOrientation orientation, - int for_size, - int *minimum, - int *natural, - int *minimum_baseline, - int *natural_baseline); + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline); static void @@ -91,11 +91,11 @@ argb_to_rgba(int32_t width, int32_t height, unsigned char *icon_data) // Icon data is ARGB, gdk textures are RGBA. Flip the channels // Shamelessly copied from Waybar for (int32_t i = 0; i < 4 * width * height; i += 4) { - unsigned char alpha = icon_data[i]; - icon_data[i] = icon_data[i + 1]; - icon_data[i + 1] = icon_data[i + 2]; - icon_data[i + 2] = icon_data[i + 3]; - icon_data[i + 3] = alpha; + unsigned char alpha = icon_data[i]; + icon_data[i] = icon_data[i + 1]; + icon_data[i + 1] = icon_data[i + 2]; + icon_data[i + 2] = icon_data[i + 3]; + icon_data[i + 3] = alpha; } } @@ -590,6 +590,7 @@ sn_item_set_property(GObject *object, uint property_id, const GValue *value, GPa break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; } } @@ -616,6 +617,7 @@ sn_item_get_property(GObject *object, uint property_id, GValue *value, GParamSpe break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; } } @@ -750,8 +752,7 @@ static void sn_item_dispose(GObject *obj) { SnItem *self = SN_ITEM(obj); - g_debug("Disposing snitem %s %s", - self->busname, self->busobj); + g_debug("Disposing snitem %s %s", self->busname, self->busobj); self->exiting = TRUE; diff --git a/systray/snwatcher.c b/systray/snwatcher.c @@ -0,0 +1,409 @@ +#include "snwatcher.h" + +#include <stdlib.h> +#include <string.h> + +#include <glib.h> +#include <glib-object.h> +#include <gio/gio.h> +#include <gtk/gtk.h> + +struct _SnWatcher +{ + GObject parent_instance; + + GDBusConnection* conn; + GList* tracked_items; + + int owner_id; + int obj_reg_id; + int sig_sub_id; +}; + +G_DEFINE_FINAL_TYPE(SnWatcher, sn_watcher, G_TYPE_OBJECT) + +enum +{ + N_PROPERTIES = 1, +}; + +enum +{ + TRAYITEM_REGISTERED, + TRAYITEM_UNREGISTERED, + LAST_SIGNAL +}; + +static uint signals[LAST_SIGNAL]; + +static void sn_watcher_dispose (GObject *obj); + +static void sn_watcher_call_method_handler (GDBusConnection *conn, + const char *sender, + const char *object_path, + const char *interface_name, + const char *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + void *data); + +static GVariant* sn_watcher_prop_get_handler (GDBusConnection* conn, + const char* sender, + const char* object_path, + const char* interface_name, + const char* property_name, + GError** err, + void *data); + +static GDBusInterfaceVTable interface_vtable = { + sn_watcher_call_method_handler, + sn_watcher_prop_get_handler, + NULL +}; + + +static void +sn_watcher_register_item(SnWatcher *self, + const char *busname, + const char *busobj) +{ + g_debug("Registering %s", busname); + self->tracked_items = g_list_prepend(self->tracked_items, g_strdup(busname)); + + g_signal_emit(self, signals[TRAYITEM_REGISTERED], 0, busname, busobj); + + // Dbus signal is emitted only to conform to the specification. + // We don't use this ourselves. + GError *err = NULL; + g_dbus_connection_emit_signal(self->conn, + NULL, + "/StatusNotifierWatcher", + "org.kde.StatusNotifierWatcher", + "StatusNotifierItemRegistered", + g_variant_new("(s)", busname), + &err); + if (err) { + g_warning("%s", err->message); + g_error_free(err); + } +} + +static void +sn_watcher_unregister_item(SnWatcher *self, const char *busname) +{ + g_debug("Unregistering %s", busname); + + g_signal_emit(self, signals[TRAYITEM_UNREGISTERED], 0, busname); + + // Dbus signal is emitted only to conform to the specification. + // We don't use this ourselves. + GError *err = NULL; + g_dbus_connection_emit_signal(self->conn, + NULL, + "/StatusNotifierWatcher", + "org.kde.StatusNotifierWatcher", + "StatusNotifierItemUnregistered", + g_variant_new("(s)", busname), + &err); + if (err) { + g_warning("%s", err->message); + g_error_free(err); + } +} + +static void +bus_get_snitems_helper(void *data, void *udata) +{ + char *busname = (char*)data; + GVariantBuilder *builder = (GVariantBuilder*)udata; + + g_variant_builder_add_value(builder, g_variant_new_string(busname)); +} + +static GVariant* +sn_watcher_prop_get_handler(GDBusConnection* conn, + const char* sender, + const char* object_path, + const char* interface_name, + const char* property_name, + GError** err, + void *data) +{ + SnWatcher *self = SN_WATCHER(data); + + if (strcmp(property_name, "ProtocolVersion") == 0) { + return g_variant_new("i", 0); + } else if (strcmp(property_name, "IsStatusNotifierHostRegistered") == 0) { + return g_variant_new("b", TRUE); + } else if (strcmp(property_name, "RegisteredStatusNotifierItems") == 0) { + if (!self->tracked_items) + return g_variant_new("as", NULL); + + GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY); + g_list_foreach(self->tracked_items, bus_get_snitems_helper, builder); + GVariant *as = g_variant_builder_end(builder); + + g_variant_builder_unref(builder); + + return as; + } else { + g_set_error(err, + G_DBUS_ERROR, + G_DBUS_ERROR_UNKNOWN_PROPERTY, + "Unknown property '%s'.", + property_name); + return NULL; + } +} + + +static void +sn_watcher_call_method_handler(GDBusConnection *conn, + const char *sender, + const char *obj_path, + const char *iface_name, + const char *method_name, + GVariant *params, + GDBusMethodInvocation *invoc, + void *data) +{ + SnWatcher *self = SN_WATCHER(data); + + if (strcmp(method_name, "RegisterStatusNotifierItem") == 0) { + const char *param; + const char *busobj; + const char *registree_name; + + g_variant_get(params, "(&s)", &param); + + if (g_str_has_prefix(param, "/")) + busobj = param; + else + busobj = "/StatusNotifierItem"; + + if (g_str_has_prefix(param, ":")) + registree_name = param; + else + registree_name = sender; + + sn_watcher_register_item(self, registree_name, busobj); + g_dbus_method_invocation_return_value(invoc, NULL); + + } else { + g_dbus_method_invocation_return_dbus_error(invoc, + "org.freedesktop.DBus.Error.UnknownMethod", + "Unknown method"); + } +} + +static void +sn_watcher_monitor_bus(GDBusConnection* conn, + const char* sender, + const char* objpath, + const char* iface_name, + const char* signame, + GVariant *params, + void *data) +{ + SnWatcher *self = SN_WATCHER(data); + + if (strcmp(signame, "NameOwnerChanged") == 0) { + if (!self->tracked_items) + return; + + const char *name; + const char *old_owner; + const char *new_owner; + g_variant_get(params, "(&s&s&s)", &name, &old_owner, &new_owner); + if (strcmp(new_owner, "") == 0) { + GList *pmatch = g_list_find_custom(self->tracked_items, name, (GCompareFunc)strcmp); + if (pmatch) { + sn_watcher_unregister_item(self, pmatch->data); + g_free(pmatch->data); + self->tracked_items = g_list_delete_link(self->tracked_items, pmatch); + } + } + } +} + +static void +sn_watcher_unregister_all(SnWatcher *self) +{ + GList *tmp = self->tracked_items; + + while (tmp) { + GList *next = tmp->next; + sn_watcher_unregister_item(self, tmp->data); + g_free(tmp->data); + self->tracked_items = g_list_delete_link(self->tracked_items, tmp); + tmp = next; + } +} + +static void +sn_watcher_bus_acquired_handler(GDBusConnection *conn, const char *busname, void *data) +{ + SnWatcher *self = SN_WATCHER(data); + + self->conn = conn; + + GError *err = NULL; + GDBusNodeInfo *nodeinfo = g_dbus_node_info_new_for_xml(STATUSNOTIFIERWATCHER_XML, NULL); + + self->obj_reg_id = + g_dbus_connection_register_object(self->conn, + "/StatusNotifierWatcher", + nodeinfo->interfaces[0], + &interface_vtable, + self, + NULL, + &err); + + g_dbus_node_info_unref(nodeinfo); + + if (err) { + g_error("%s", err->message); + g_error_free(err); + exit(-1); + } + + self->sig_sub_id = + g_dbus_connection_signal_subscribe(self->conn, + NULL, // Listen to all senders); + "org.freedesktop.DBus", + "NameOwnerChanged", + NULL, // Match all obj paths + NULL, // Match all arg0s + G_DBUS_SIGNAL_FLAGS_NONE, + sn_watcher_monitor_bus, + self, + NULL); +} + +static void +sn_watcher_name_acquired_handler(GDBusConnection *conn, const char *busname, void *data) +{ + SnWatcher *self = SN_WATCHER(data); + + GError *err = NULL; + + g_dbus_connection_emit_signal(self->conn, + NULL, + "/StatusNotifierWatcher", + "org.kde.StatusNotifierWatcher", + "StatusNotifierHostRegistered", + NULL, + &err); + + if (err) { + g_warning("%s", err->message); + g_error_free(err); + } +} + +static void +sn_watcher_name_lost_handler(GDBusConnection *conn, const char *busname, void *data) +{ + g_error("Could not acquire %s, maybe another instance is running?", busname); + exit(-1); +} + +static GObject* +sn_watcher_constructor(GType type, uint n_construct_properties, GObjectConstructParam *construct_properties) +{ + static GObject *singleton = NULL; + + if (singleton == NULL) { + singleton = G_OBJECT_CLASS(sn_watcher_parent_class)->constructor(type, + n_construct_properties, + construct_properties); + g_object_add_weak_pointer(singleton, (void*)&singleton); + + return singleton; + } + + return g_object_ref(singleton); +} + + +static void +sn_watcher_class_init(SnWatcherClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->constructor = sn_watcher_constructor; + object_class->dispose = sn_watcher_dispose; + + signals[TRAYITEM_REGISTERED] = g_signal_new("trayitem-registered", + SN_TYPE_WATCHER, + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_STRING, + G_TYPE_STRING); + + signals[TRAYITEM_UNREGISTERED] = g_signal_new("trayitem-unregistered", + SN_TYPE_WATCHER, + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_STRING); +} + +static void +sn_watcher_init(SnWatcher *self) +{ + self->owner_id = + g_bus_own_name(G_BUS_TYPE_SESSION, + "org.kde.StatusNotifierWatcher", + G_BUS_NAME_OWNER_FLAGS_NONE, + sn_watcher_bus_acquired_handler, + sn_watcher_name_acquired_handler, + sn_watcher_name_lost_handler, + self, + NULL); + + g_debug("Created snwatcher"); +} + +static void +sn_watcher_dispose(GObject *obj) +{ + g_debug("Disposing snwatcher"); + SnWatcher *self = SN_WATCHER(obj); + + if (self->sig_sub_id > 0) { + g_dbus_connection_signal_unsubscribe(self->conn, self->sig_sub_id); + self->sig_sub_id = 0; + } + + if (self->obj_reg_id > 0) { + g_dbus_connection_unregister_object(self->conn, self->obj_reg_id); + self->obj_reg_id = 0; + } + + if (self->tracked_items) { + sn_watcher_unregister_all(self); + self->tracked_items = NULL; + } + + if (self->owner_id > 0) { + g_bus_unown_name(self->owner_id); + self->owner_id = 0; + self->conn = NULL; + } + + G_OBJECT_CLASS(sn_watcher_parent_class)->dispose(obj); +} + +SnWatcher* +sn_watcher_new(void) +{ + return g_object_new(SN_TYPE_WATCHER, NULL); +} diff --git a/systray/snwatcher.h b/systray/snwatcher.h @@ -0,0 +1,40 @@ +#ifndef SNWATCHER_H +#define SNWATCHER_H + +#include <glib-object.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define SN_TYPE_WATCHER sn_watcher_get_type() +G_DECLARE_FINAL_TYPE(SnWatcher, sn_watcher, SN, WATCHER, GObject) + +SnWatcher *sn_watcher_new (void); + +G_END_DECLS + +#define STATUSNOTIFIERWATCHER_XML \ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" \ + "<node>\n" \ + " <interface name=\"org.kde.StatusNotifierWatcher\">\n" \ + " <!-- methods -->\n" \ + " <method name=\"RegisterStatusNotifierItem\">\n" \ + " <arg name=\"service\" type=\"s\" direction=\"in\" />\n" \ + " </method>\n" \ + " <!-- properties -->\n" \ + " <property name=\"IsStatusNotifierHostRegistered\" type=\"b\" access=\"read\" />\n" \ + " <property name=\"ProtocolVersion\" type=\"i\" access=\"read\" />\n" \ + " <property name=\"RegisteredStatusNotifierItems\" type=\"as\" access=\"read\" />\n" \ + " <!-- signals -->\n" \ + " <signal name=\"StatusNotifierItemRegistered\">\n" \ + " <arg type=\"s\"/>\n" \ + " </signal>\n" \ + " <signal name=\"StatusNotifierItemUnregistered\">\n" \ + " <arg type=\"s\"/>\n" \ + " </signal>\n" \ + " <signal name=\"StatusNotifierHostRegistered\">\n" \ + " </signal>\n" \ + " </interface>\n" \ + "</node>\n" + +#endif /* SNWATCHER_H */