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:
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)", ¶m);
-
- 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)", ¶m);
+
+ 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 */