dwlb

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

commit 5420ac3f72571a2eb73918e41c59834100469ce2
parent 3fcbff751fa7f2b447d290212da3317e6db2024d
Author: Janne Veteläinen <janne.vetelainen@elisanet.fi>
Date:   Mon, 15 Apr 2024 14:44:05 +0300

Merge testing branch -- add dynamically changing menus and lots of refactoring

Diffstat:
Mdbusmenu.c | 256+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mdwlb.c | 82+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mdwlbtray.c | 18+++++++++++++++++-
Mdwlbtray.h | 30+++++++++++-------------------
Mstatusnotifierhost.c | 258+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mstatusnotifieritem.c | 249++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
6 files changed, 551 insertions(+), 342 deletions(-)

diff --git a/dbusmenu.c b/dbusmenu.c @@ -1,3 +1,6 @@ +/* + * This whole thing is a mess ....... + */ #include <time.h> #include <glib.h> @@ -11,8 +14,6 @@ static GMenu* create_menumodel(GVariant *data, StatusNotifierItem *snitem); static void action_activated_cb(GSimpleAction *action, GVariant* param, ActionCallbackData *data) { - // GError *err = NULL; - g_dbus_proxy_call(data->proxy, "Event", g_variant_new("(isvu)", @@ -25,26 +26,6 @@ action_activated_cb(GSimpleAction *action, GVariant* param, ActionCallbackData * NULL, NULL, NULL); - /* - GVariant *retval = g_dbus_proxy_call_sync(data->proxy, - "Event", - g_variant_new("(isvu)", - data->id, - "clicked", - g_variant_new_string(""), - time(NULL)), - G_DBUS_CALL_FLAGS_NONE, - -1, - NULL, - &err); - - if (err) { - fprintf(stderr, "%s\n", err->message); - g_error_free(err); - } else { - g_variant_unref(retval); - } - */ } @@ -67,6 +48,28 @@ create_action(uint32_t id, StatusNotifierItem *snitem) } +static gboolean +check_has_sections(GVariant *data, StatusNotifierItem *snitem) +{ + gboolean ret = FALSE; + char *val; + GVariant *menuitem_data; + + GVariantIter iter; + g_variant_iter_init(&iter, data); + while ((g_variant_iter_next(&iter, "v", &menuitem_data))) { + GVariant *menu_data = g_variant_get_child_value(menuitem_data, 1); + gboolean check = g_variant_lookup(menu_data, "type", "&s", &val); + if (check && strcmp(val, "separator") == 0) + ret = TRUE; + g_variant_unref(menu_data); + } + + return ret; +} + + +//TODO: Ignore visible=false items static GMenuItem* create_menuitem(GVariant *data, StatusNotifierItem *snitem) { @@ -87,7 +90,7 @@ create_menuitem(GVariant *data, StatusNotifierItem *snitem) gboolean isvisible = TRUE; gboolean has_submenu = FALSE; - /* TODO: dynamic menu updates + /* * gboolean ischeckmark = FALSE; * gboolean isradio = FALSE; * int32_t toggle_state = 99; @@ -102,16 +105,18 @@ create_menuitem(GVariant *data, StatusNotifierItem *snitem) g_variant_dict_lookup(&dict, "enabled", "b", &isenabled); g_variant_dict_lookup(&dict, "visible", "b", &isvisible); g_variant_dict_lookup(&dict, "children-display", "&s", &has_submenu_s); - /* TODO: dynamic menu updates + + /* * g_variant_dict_lookup(&dict, "toggle-type", "&s", &toggle_type); * g_variant_dict_lookup(&dict, "toggle-state", "i", &toggle_state); */ + g_variant_dict_clear(&dict); if (has_submenu_s && strcmp(has_submenu_s, "submenu") == 0) has_submenu = TRUE; - /* TODO: dynamic menu updates + /* * if (toggle_type && strcmp(toggle_type, "checkmark") == 0) * ischeckmark = TRUE; * else if (toggle_type && strcmp(toggle_type, "radio") == 0) @@ -127,9 +132,8 @@ create_menuitem(GVariant *data, StatusNotifierItem *snitem) g_free(action_name); g_object_unref(action); - } else if ((label && (!isvisible || !isenabled)) && !(type && strcmp(type, "separator") == 0)) { - if (!isvisible) - g_debug("menuitem %i, label %s should be invisible\n", id, label); + + } else if ((label && !(type && strcmp(type, "separator") == 0))) { GSimpleAction *action = create_action(id, snitem); g_simple_action_set_enabled(action, FALSE); char *action_name = g_strdup_printf("%s.%u", "menuitem", id); @@ -150,6 +154,7 @@ create_menuitem(GVariant *data, StatusNotifierItem *snitem) } g_variant_unref(menu_data); + g_variant_unref(data); return menuitem; } @@ -158,38 +163,40 @@ create_menuitem(GVariant *data, StatusNotifierItem *snitem) static GMenu* create_menumodel(GVariant *data, StatusNotifierItem *snitem) { - GMenu *ret = NULL; - + GMenu *ret = g_menu_new(); GVariantIter iter; GVariant *menuitem_data; - gboolean has_sections = FALSE; - GMenu *menu = g_menu_new(); - GMenu *section = g_menu_new(); - g_variant_iter_init(&iter, data); - - while ((g_variant_iter_next(&iter, "v", &menuitem_data))) { - GMenuItem *menuitem = create_menuitem(menuitem_data, snitem); - if (menuitem) { - g_menu_append_item(section, menuitem); - g_object_unref(menuitem); - } - else { - g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); - g_object_unref(section); - has_sections = TRUE; - section = g_menu_new(); - } - - g_variant_unref(menuitem_data); - } + gboolean has_sections = check_has_sections(data, snitem); if (has_sections) { - g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); - ret = menu; + GMenu *section = g_menu_new(); + g_variant_iter_init(&iter, data); + while ((g_variant_iter_next(&iter, "v", &menuitem_data))) { + GMenuItem *menuitem = create_menuitem(menuitem_data, snitem); + if (menuitem) { + g_menu_append_item(section, menuitem); + g_object_unref(menuitem); + } + else { + g_menu_append_section(ret, NULL, G_MENU_MODEL(section)); + g_object_unref(section); + section = g_menu_new(); + } + g_variant_unref(menuitem_data); + } + g_menu_append_section(ret, NULL, G_MENU_MODEL(section)); g_object_unref(section); + } else { - ret = section; - g_object_unref(menu); + g_variant_iter_init(&iter, data); + while ((g_variant_iter_next(&iter, "v", &menuitem_data))) { + GMenuItem *menuitem = create_menuitem(menuitem_data, snitem); + if (menuitem) { + g_menu_append_item(ret, menuitem); + g_object_unref(menuitem); + } + g_variant_unref(menuitem_data); + } } return ret; @@ -203,50 +210,86 @@ on_menulayout_ready(GDBusProxy *proxy, GAsyncResult *res, StatusNotifierItem *sn GVariant *data = g_dbus_proxy_call_finish(proxy, res, &err); // (u(ia{sv}av)) - if (err) { - // Sometimes apps send update messages just as they are closing - // So the dbus object might not exist in this case - g_debug("%s\n", err->message); + // "No such object path '/NO_DBUSMENU'" + // generated by QBittorrent when it sends a broken trayitem on startup + // and replaces it later + if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_OBJECT)) { + g_error_free(err); + return; + } else if (err) { + g_warning("%sfrom on_menulayout_ready\n", err->message); g_error_free(err); - return; } - - uint32_t revision; + uint32_t revision = 0; GVariant *layout; - // (ia{sv}av) - // uint32_t GVariant * GVariant * - // 0, {"children-display": "submenu"}, {GVariant *menuitem, GVariant *menuitem ...} GVariant *menuitems; - // (ia{sv}av) - // uint32_t GVariant * GVariant * - // 1, {"label": "foobar", ...}, {GVariant *menuitem, GVariant *menuitem ...} g_variant_get_child(data, 0, "u", &revision); - if (snitem->menurevision != UINT32_MAX && revision <= snitem->menurevision) { - g_variant_unref(data); + + layout = g_variant_get_child_value(data, 1); + menuitems = g_variant_get_child_value(layout, 2); + + GMenu *menu = create_menumodel(menuitems, snitem); + GtkWidget *popovermenu = gtk_popover_menu_new_from_model(NULL); + gtk_popover_set_has_arrow(GTK_POPOVER(popovermenu), FALSE); + gtk_popover_menu_set_menu_model(GTK_POPOVER_MENU(popovermenu), G_MENU_MODEL(menu)); + gtk_widget_set_parent(popovermenu, snitem->icon); + + snitem->popovermenu = popovermenu; + + g_object_unref(menu); + g_variant_unref(menuitems); + g_variant_unref(layout); + g_variant_unref(data); +} + + +static void +on_layout_updated(GDBusProxy *proxy, GAsyncResult *res, StatusNotifierItem *snitem) +{ + GError *err = NULL; + GVariant *data = g_dbus_proxy_call_finish(proxy, res, &err); + + // Errors which might occur when the tray is running slowly (eg under valgrind) + // and user is spam clicking already exited icons + + // "No such object path '/MenuBar' + if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_OBJECT)) { + g_error_free(err); + return; + + // "The name is not activatable" + } else if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) { + g_error_free(err); + return; + + // "Remote peer disconnected" + } else if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_NO_REPLY)) { + g_error_free(err); return; - } else { - snitem->menurevision = revision; - } - if (snitem->menu && snitem->popovermenu) { - gtk_widget_unparent(snitem->popovermenu); - g_menu_remove_all(snitem->menu); + } else if (err) { + g_warning("%s\n", err->message); + fprintf(stderr, "from on_layout_updated\n"); + g_error_free(err); + return; } - layout = g_variant_get_child_value(data, 1); + GVariant *layout; + GVariant *menuitems; + + layout = g_variant_get_child_value(data, 1); menuitems = g_variant_get_child_value(layout, 2); - GMenu *menu = create_menumodel(menuitems, snitem); - snitem->menu = menu; - snitem->popovermenu = gtk_popover_menu_new_from_model(G_MENU_MODEL(snitem->menu)); - gtk_popover_set_has_arrow(GTK_POPOVER(snitem->popovermenu), FALSE); - gtk_popover_menu_set_menu_model(GTK_POPOVER_MENU(snitem->popovermenu), - G_MENU_MODEL(snitem->menu)); - gtk_widget_set_parent(snitem->popovermenu, snitem->icon); + if (snitem && snitem->icon && GTK_IS_WIDGET(snitem->icon) && + snitem->popovermenu && GTK_IS_WIDGET(snitem->popovermenu)) { + GMenu *newmenu = create_menumodel(menuitems, snitem); + + gtk_popover_menu_set_menu_model(GTK_POPOVER_MENU(snitem->popovermenu), G_MENU_MODEL(newmenu)); + } g_variant_unref(menuitems); g_variant_unref(layout); @@ -254,6 +297,10 @@ on_menulayout_ready(GDBusProxy *proxy, GAsyncResult *res, StatusNotifierItem *sn } +// We just rebuild the entire menu every time... +// ItemsPropertiesUpdated signal would carry data of +// which menuitems got updated and which got removed +// but the GMenu api doesn't allow easy manipulation as it is static void on_menuproxy_signal(GDBusProxy *proxy, const char *sender, @@ -262,36 +309,63 @@ on_menuproxy_signal(GDBusProxy *proxy, StatusNotifierItem *snitem) { if (strcmp(signal, "LayoutUpdated") == 0) { - g_debug("%s's menu got LayoutUpdated\n", snitem->busname); + uint32_t revision = UINT32_MAX; + int32_t parentid; + g_variant_get(params, "(ui)", &revision, &parentid); + if (snitem->menurevision != UINT32_MAX && revision <= snitem->menurevision) { + // g_debug("%s got %s, but menurevision didn't change. Ignoring\n", snitem->busname, signal); + return; + } else if (!snitem || !snitem->icon || !GTK_IS_WIDGET(snitem->icon) || + !snitem->popovermenu || !GTK_IS_WIDGET(snitem->popovermenu)) { + // g_debug("%s got %s, but menu was already in destruction. Ignoring\n", snitem->busname, signal); + return; + } else { + snitem->menurevision = revision; + } + g_dbus_proxy_call(snitem->menuproxy, "GetLayout", g_variant_new("(iias)", 0, -1, NULL), G_DBUS_CALL_FLAGS_NONE, -1, NULL, - (GAsyncReadyCallback)on_menulayout_ready, + (GAsyncReadyCallback)on_layout_updated, snitem); - // TODO: dynamic menu updates + } else if (strcmp(signal, "ItemsPropertiesUpdated") == 0) { - g_debug("%s's menu got LayoutUpdated\n", snitem->busname); + if (!snitem || !snitem->icon || !GTK_IS_WIDGET(snitem->icon) || + !snitem->popovermenu || !GTK_IS_WIDGET(snitem->popovermenu)) { + // g_debug("Menu was already in destruction\n"); + return; + } + g_dbus_proxy_call(snitem->menuproxy, + "GetLayout", + g_variant_new("(iias)", 0, -1, NULL), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback)on_layout_updated, + snitem); } } void -create_menu(GDBusProxy *proxy, GAsyncResult *res, StatusNotifierItem *snitem) +create_menu(GObject *obj, GAsyncResult *res, StatusNotifierItem *snitem) { GError *err = NULL; - snitem->menuproxy = g_dbus_proxy_new_for_bus_finish(res, &err); + GDBusProxy *proxy = g_dbus_proxy_new_for_bus_finish(res, &err); + snitem->menuproxy = proxy; snitem->menurevision = UINT32_MAX; if (err) { - g_debug("%s\n", err->message); + g_warning("%s\n", err->message); + fprintf(stderr, "from create_menu\n"); g_error_free(err); return; } - g_dbus_proxy_call(snitem->menuproxy, + g_dbus_proxy_call(proxy, "GetLayout", g_variant_new ("(iias)", 0, -1, NULL), G_DBUS_CALL_FLAGS_NONE, diff --git a/dwlb.c b/dwlb.c @@ -1432,6 +1432,9 @@ copy_customtext(CustomText *from, CustomText *to) static void request_resize(Bar *bar, char *data) { + if (!bar) + return; + uint32_t traywidth = (uint32_t)atoi(data); bar->width = bar->width_orig - traywidth; @@ -1586,7 +1589,12 @@ read_socket(void) set_bottom(bar); } } else if (!strcmp(wordbeg, "resize")) { - request_resize(bar, wordend); + if (all) { + wl_list_for_each(bar, &bar_list, link) + request_resize(bar, wordend); + } else { + request_resize(bar, wordend); + } } } @@ -1678,6 +1686,41 @@ sig_handler(int sig) run_display = false; } +static void +start_systray(const char *parent_progname, const char *traymon) +{ + char tray_exe_path[PATH_MAX]; + char traypath_maybe[PATH_MAX]; + char traybg_arg[64]; + char height_arg[64]; + char traymon_arg[64]; + + pixman_color_t *traybg_clr = &inactive_bg_color; + snprintf(traybg_arg, + sizeof(traybg_arg), + "--bg-color=#%02x%02x%02x", + (traybg_clr->red / 0x100), + (traybg_clr->green / 0x100), + (traybg_clr->blue) / 0x100); + + snprintf(traypath_maybe, sizeof(traypath_maybe), "%stray", parent_progname); + if (access(traypath_maybe, X_OK) == 0) + strcpy(tray_exe_path, traypath_maybe); + else + strcpy(tray_exe_path, "dwlbtray"); + + snprintf(height_arg, sizeof(height_arg), "--height=%u", height); + snprintf(traymon_arg, sizeof(traymon_arg), "--traymon=%s", traymon); + char *args[] = { tray_exe_path, height_arg, traybg_arg, traymon_arg, NULL }; + if (!traymon) + args[3] = NULL; + + int child_pid = fork(); + if (child_pid == 0) { + execvp(args[0], args); + } +} + int main(int argc, char **argv) { @@ -1685,7 +1728,7 @@ main(int argc, char **argv) struct sockaddr_un sock_address; Bar *bar, *bar2; Seat *seat, *seat2; - char *traymon = NULL; + const char *traymon = NULL; /* Establish socket directory */ if (!(xdgruntimedir = getenv("XDG_RUNTIME_DIR"))) @@ -1960,40 +2003,9 @@ main(int argc, char **argv) signal(SIGHUP, sig_handler); signal(SIGTERM, sig_handler); signal(SIGCHLD, SIG_IGN); - - /* Start tray program */ - char tray_exe_path[PATH_MAX]; - char traypath_maybe[PATH_MAX]; - char traybg_arg[64]; - char height_arg[64]; - char traymon_arg[64]; - - pixman_color_t *traybg_clr = &inactive_bg_color; - snprintf(traybg_arg, - sizeof(traybg_arg), - "--bg-color=#%02x%02x%02x", - (traybg_clr->red / 0x100), - (traybg_clr->green / 0x100), - (traybg_clr->blue) / 0x100); - printf("%s\n", traybg_arg); - - snprintf(traypath_maybe, sizeof(traypath_maybe), "%stray", argv[0]); - if (access(traypath_maybe, X_OK) == 0) - strcpy(tray_exe_path, traypath_maybe); - else - strcpy(tray_exe_path, "dwlbtray"); - - snprintf(height_arg, sizeof(height_arg), "--height=%u", height); - snprintf(traymon_arg, sizeof(traymon_arg), "--traymon=%s", traymon); - char *args[] = { tray_exe_path, height_arg, traybg_arg, traymon_arg, NULL }; - if (!traymon) - args[3] = NULL; - - int child_pid = fork(); - if (child_pid == 0) { - execvp(args[0], args); - } + /* Start tray program */ + start_systray(argv[0], traymon); /* Run */ run_display = true; diff --git a/dwlbtray.c b/dwlbtray.c @@ -2,6 +2,7 @@ #include <unistd.h> #include <glib.h> +#include <glib-unix.h> #include <gtk4-layer-shell.h> #include <gtk/gtk.h> @@ -22,6 +23,8 @@ activate(GtkApplication* app, StatusNotifierHost *snhost) GTK_STYLE_PROVIDER(css), GTK_STYLE_PROVIDER_PRIORITY_USER); gtk_widget_add_css_class(GTK_WIDGET(window), "dwlbtray"); + g_free(snhost->cssdata); + snhost->cssdata = NULL; gtk_layer_init_for_window(window); @@ -49,11 +52,21 @@ activate(GtkApplication* app, StatusNotifierHost *snhost) gtk_widget_set_margin_start(box, snhost->margin); gtk_widget_set_margin_end(box, snhost->margin); gtk_window_set_child(window, box); - snhost->box = box; dwlb_request_resize(snhost); gtk_window_present(window); + snhost->box = box; + snhost->window = window; +} + + +static gboolean +terminate_app(StatusNotifierHost *snhost) +{ + terminate_statusnotifierhost(snhost); + + return G_SOURCE_REMOVE; } @@ -91,6 +104,9 @@ main(int argc, char *argv[]) g_signal_connect(app, "activate", G_CALLBACK(activate), snhost); + g_unix_signal_add(SIGINT, (GSourceFunc)terminate_app, snhost); + g_unix_signal_add(SIGTERM, (GSourceFunc)terminate_app, snhost); + char *argv_inner[] = { argv[0], NULL }; int status = g_application_run(G_APPLICATION(app), 1, argv_inner); diff --git a/dwlbtray.h b/dwlbtray.h @@ -13,29 +13,24 @@ typedef struct { typedef struct StatusNotifierHost { GDBusConnection *conn; - GDBusNodeInfo *nodeinfo; - GDBusProxy *watcherproxy; GSList *trayitems; GtkWidget *box; GtkWindow *window; - int bus_obj_reg_id; - int cursize; + char *cssdata; + char *traymon; + int curwidth; int height; int margin; int noitems; + int obj_id; int owner_id; - uint nameowner_sig_sub_id; - uint watcher_id; - char *traymon; - char *cssdata; + int sub_id; } StatusNotifierHost; + typedef struct StatusNotifierItem { - GDBusNodeInfo *menunodeinfo; - GDBusNodeInfo *nodeinfo; GDBusProxy *menuproxy; GDBusProxy *proxy; - GMenu *menu; GSList *action_cb_data_slist; GSimpleActionGroup *actiongroup; GVariant *iconpixmap_v; @@ -44,20 +39,17 @@ typedef struct StatusNotifierItem { GtkWidget *popovermenu; StatusNotifierHost *host; char *busname; - char *busobj; char *iconname; - char *menuobj; + gboolean isclosing; uint32_t menurevision; - unsigned char *icon_data; } StatusNotifierItem; -void create_trayitem(GDBusConnection *conn, GAsyncResult *res, StatusNotifierItem *snitem); -void create_menu(GDBusProxy *proxy, GAsyncResult *res, StatusNotifierItem *snitem); +void create_trayitem(GObject *obj, GAsyncResult *res, StatusNotifierItem *snitem); +void create_menu(GObject *obj, GAsyncResult *res, StatusNotifierItem *snitem); StatusNotifierHost* start_statusnotifierhost(); -// void terminate_statusnotifierhost(StatusNotifierHost *snhost); -GDBusNodeInfo* get_interface_info(const char *xmlpath); void dwlb_request_resize(StatusNotifierHost *snhost); +void terminate_statusnotifierhost(StatusNotifierHost *snhost); #define DBUSMENU_XML \ @@ -210,4 +202,4 @@ void dwlb_request_resize(StatusNotifierHost *snhost); " </interface>\n" \ "</node>\n" -#endif /* STATUSNOTIFIERHOST_H */ +#endif /* GTKTRAY_H */ diff --git a/statusnotifierhost.c b/statusnotifierhost.c @@ -9,14 +9,15 @@ #include "dwlbtray.h" - +static void unregister_all(StatusNotifierHost *snhost); +static void sub_finalize(StatusNotifierHost *snhost); +static void busobj_finalize(StatusNotifierHost *snhost); +static void unregister_statusnotifieritem(StatusNotifierItem *snitem); static void handle_method_call(GDBusConnection* conn, const char* sender, const char* object_path, const char* iface, const char* method, GVariant* parameters, GDBusMethodInvocation* invocation, StatusNotifierHost* snhost ); - - static GVariant* handle_get_prop(GDBusConnection* conn, const char* sender, const char* obj_path, const char* iface_name, const char* prop, GError** error, StatusNotifierHost* snhost @@ -30,13 +31,44 @@ static GDBusInterfaceVTable interface_vtable = { }; +static void +add_trayitem_name_to_builder(StatusNotifierItem *snitem, GVariantBuilder *builder) +{ + g_variant_builder_add_value(builder, g_variant_new_string(snitem->busname)); +} + + +static int +find_snitem(StatusNotifierItem *snitem, const char *busname_match) +{ + if (strcmp(snitem->busname, busname_match) == 0) + return 0; + else + return -1; +} + + +static void +unregister_all_wrap(StatusNotifierItem *snitem, void *data) +{ + unregister_statusnotifieritem(snitem); +} + + +static void +unregister_all(StatusNotifierHost *snhost) +{ + g_slist_foreach(snhost->trayitems, (GFunc)unregister_all_wrap, NULL); +} + + void dwlb_request_resize(StatusNotifierHost *snhost) { if (snhost->noitems <= 1) - snhost->cursize = 22; + snhost->curwidth = 22; else - snhost->cursize = 22 * snhost->noitems - 6; // dunno why substract 6 to make it align, just trial and error until it worked + snhost->curwidth = 22 * snhost->noitems - 6; // dunno why substract 6 to make it align, just trial and error until it worked struct sockaddr_un sockaddr; sockaddr.sun_family = AF_UNIX; @@ -44,10 +76,10 @@ dwlb_request_resize(StatusNotifierHost *snhost) snprintf(sockaddr.sun_path, sizeof(sockaddr.sun_path), "%s", socketpath); char *sockbuf = NULL; if (snhost->traymon) { - sockbuf = g_strdup_printf("%s %s %i", snhost->traymon, "resize", snhost->cursize); + sockbuf = g_strdup_printf("%s %s %i", snhost->traymon, "resize", snhost->curwidth); } else { - sockbuf = g_strdup_printf("%s %s %i", "selected", "resize", snhost->cursize); + sockbuf = g_strdup_printf("%s %s %i", "all", "resize", snhost->curwidth); } size_t len = strlen(sockbuf); @@ -59,45 +91,47 @@ dwlb_request_resize(StatusNotifierHost *snhost) } if (send(sock_fd, sockbuf, len, 0) == -1) - fprintf(stderr, "Could not send size update to %s\n", sockaddr.sun_path); + g_error("Could not send size update to %s\n", sockaddr.sun_path); close(sock_fd); - g_free(sockbuf); g_free(socketpath); + g_free(sockbuf); } static void -register_statusnotifieritem(const char *busname, +register_statusnotifieritem(GDBusConnection *conn, + const char *busname, const char *busobj, StatusNotifierHost *snhost) { + g_debug("Registering %s\n", busname); StatusNotifierItem *snitem; snitem = g_malloc0(sizeof(StatusNotifierItem)); - snitem->host = snhost; snitem->busname = g_strdup(busname); - snitem->busobj = g_strdup(busobj); - snitem->nodeinfo = g_dbus_node_info_new_for_xml(STATUSNOTIFIERITEM_XML, NULL); + snitem->isclosing = FALSE; + snitem->host->noitems = snitem->host->noitems + 1; - snhost->noitems = snhost->noitems + 1; snhost->trayitems = g_slist_prepend(snhost->trayitems, snitem); + dwlb_request_resize(snitem->host); - // g_free(xml_path); - dwlb_request_resize(snhost); + GDBusNodeInfo *nodeinfo = g_dbus_node_info_new_for_xml(STATUSNOTIFIERITEM_XML, NULL); - g_dbus_proxy_new(snhost->conn, + g_dbus_proxy_new(conn, G_DBUS_PROXY_FLAGS_NONE, - snitem->nodeinfo->interfaces[0], + nodeinfo->interfaces[0], snitem->busname, - snitem->busobj, + busobj, "org.kde.StatusNotifierItem", - NULL, + NULL, (GAsyncReadyCallback)create_trayitem, snitem); + g_dbus_node_info_unref(nodeinfo); + GError *err = NULL; - g_dbus_connection_emit_signal(snhost->conn, + g_dbus_connection_emit_signal(conn, NULL, "/StatusNotifierWatcher", "org.kde.StatusNotifierWatcher", @@ -105,34 +139,36 @@ register_statusnotifieritem(const char *busname, g_variant_new("(s)", snitem->busname), &err); if (err) { - g_debug("%s\n", err->message); + g_warning("%s\n", err->message); + fprintf(stderr, "from register_statusnotifieritem\n"); g_error_free(err); } } -static int -find_snitem(StatusNotifierItem *snitem, char *busname_match) -{ - if (strcmp(snitem->busname, busname_match) == 0) - return 0; - else - return -1; -} - - static void -unregister_statusnotifieritem(StatusNotifierItem *snitem, StatusNotifierHost *snhost) +unregister_statusnotifieritem(StatusNotifierItem *snitem) { - g_debug("item %s is closing\n", snitem->busname); - if (snitem->popovermenu) + g_debug("Unregistering %s\n", snitem->busname); + if (snitem->popovermenu) { + gtk_popover_menu_set_menu_model(GTK_POPOVER_MENU(snitem->popovermenu), NULL); gtk_widget_unparent(snitem->popovermenu); - if (snitem->icon) - gtk_box_remove(GTK_BOX(snhost->box), snitem->icon); + snitem->popovermenu = NULL; + } + + if (snitem->icon) { + // TODO: Why do we still have children left??? + GtkWidget *test; + while ((test = gtk_widget_get_first_child(snitem->icon))) { + gtk_widget_unparent(test); + } + gtk_box_remove(GTK_BOX(snitem->host->box), snitem->icon); + snitem->icon = NULL; + } GError *err = NULL; - g_dbus_connection_emit_signal(snhost->conn, + g_dbus_connection_emit_signal(snitem->host->conn, NULL, "/StatusNotifierWatcher", "org.kde.StatusNotifierWatcher", @@ -140,17 +176,15 @@ unregister_statusnotifieritem(StatusNotifierItem *snitem, StatusNotifierHost *sn g_variant_new("(s)", snitem->busname), &err); if (err) { - g_debug("%s\n", err->message); + g_warning("%s\n", err->message); + fprintf(stderr, "from unregister_statusnotifieritem\n"); g_error_free(err); } - if (snitem->menu) { + if (snitem->menuproxy) g_object_unref(snitem->menuproxy); + if (snitem->action_cb_data_slist) g_slist_free_full(snitem->action_cb_data_slist, g_free); - g_object_unref(snitem->menu); - } - if (snitem->menunodeinfo) - g_dbus_node_info_unref(snitem->menunodeinfo); if (snitem->paintable) { g_object_unref(snitem->paintable); @@ -160,19 +194,15 @@ unregister_statusnotifieritem(StatusNotifierItem *snitem, StatusNotifierHost *sn g_free(snitem->iconname); } - g_object_unref(snitem->proxy); - g_dbus_node_info_unref(snitem->nodeinfo); g_object_unref(snitem->actiongroup); g_free(snitem->busname); - g_free(snitem->busobj); - g_free(snitem->menuobj); - snhost->trayitems = g_slist_remove(snhost->trayitems, snitem); + snitem->host->trayitems = g_slist_remove(snitem->host->trayitems, snitem); + snitem->host->noitems = snitem->host->noitems - 1; + dwlb_request_resize(snitem->host); g_free(snitem); snitem = NULL; - snhost->noitems = snhost->noitems - 1; - dwlb_request_resize(snhost); } @@ -187,10 +217,10 @@ handle_method_call(GDBusConnection *conn, StatusNotifierHost *snhost) { if (strcmp(method_name, "RegisterStatusNotifierItem") == 0) { - char *param; + const char *param; const char *busobj; - g_variant_get(parameters, "(s)", &param); + g_variant_get(parameters, "(&s)", &param); if (g_str_has_prefix(param, "/")) { busobj = param; @@ -198,9 +228,9 @@ handle_method_call(GDBusConnection *conn, busobj = "/StatusNotifierItem"; } - register_statusnotifieritem(sender, busobj, snhost); + register_statusnotifieritem(conn, sender, busobj, snhost); g_dbus_method_invocation_return_value(invocation, NULL); - g_free(param); + } else { g_dbus_method_invocation_return_dbus_error(invocation, "org.freedesktop.DBus.Error.UnknownMethod", @@ -209,12 +239,6 @@ handle_method_call(GDBusConnection *conn, } -static void -add_trayitem_name_to_builder(StatusNotifierItem *snitem, GVariantBuilder *builder) -{ - g_variant_builder_add_value(builder, g_variant_new_string(snitem->busname)); -} - static GVariant* handle_get_prop(GDBusConnection* conn, const char* sender, @@ -250,6 +274,14 @@ handle_get_prop(GDBusConnection* conn, } } +// ugly +static gboolean +unregister_after_timeout(StatusNotifierItem *snitem) +{ + unregister_statusnotifieritem(snitem); + return G_SOURCE_REMOVE; +} + // Finds trayitems which dropped from the bus and untracks them static void @@ -265,29 +297,21 @@ monitor_bus(GDBusConnection* conn, if (!snhost->trayitems) return; - char *name; - char *old_owner; - char *new_owner; + const char *name; + const char *old_owner; + const char *new_owner; - g_variant_get(params, "(sss)", &name, &old_owner, &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(snhost->trayitems, name, (GCompareFunc)find_snitem); - - - if (!pmatch) { - g_free(name); - g_free(old_owner); - g_free(new_owner); - return; + if (pmatch) { + StatusNotifierItem *snitem = pmatch->data; + snitem->isclosing = TRUE; + // ugly + g_timeout_add_seconds(2, (GSourceFunc)unregister_after_timeout, snitem); } - - StatusNotifierItem *snitem = pmatch->data; - unregister_statusnotifieritem(snitem, snhost); } - g_free(name); - g_free(old_owner); - g_free(new_owner); } } @@ -295,15 +319,18 @@ monitor_bus(GDBusConnection* conn, static void bus_acquired_handler(GDBusConnection *conn, const char *busname, StatusNotifierHost *snhost) { - snhost->conn = conn; GError *err = NULL; - snhost->bus_obj_reg_id = g_dbus_connection_register_object(conn, - "/StatusNotifierWatcher", - snhost->nodeinfo->interfaces[0], - &interface_vtable, - snhost, // udata - NULL, // udata_free_func - &err); + GDBusNodeInfo *nodeinfo = g_dbus_node_info_new_for_xml(STATUSNOTIFIERWATCHER_XML, NULL); + + snhost->obj_id = g_dbus_connection_register_object(conn, + "/StatusNotifierWatcher", + nodeinfo->interfaces[0], + &interface_vtable, + snhost, // udata + (GDestroyNotify)busobj_finalize, // udata_free_func + &err); + + g_dbus_node_info_unref(nodeinfo); if (err) { g_error("%s\n", err->message); @@ -311,16 +338,16 @@ bus_acquired_handler(GDBusConnection *conn, const char *busname, StatusNotifierH exit(-1); } - snhost->nameowner_sig_sub_id = g_dbus_connection_signal_subscribe(conn, - NULL, // Listen to all senders); - "org.freedesktop.DBus", - "NameOwnerChanged", - NULL, // Match all obj paths - NULL, // Match all arg0s - G_DBUS_SIGNAL_FLAGS_NONE, - (GDBusSignalCallback)monitor_bus, - snhost, // udata - NULL); // udata free func + snhost->sub_id = g_dbus_connection_signal_subscribe(conn, + NULL, // Listen to all senders); + "org.freedesktop.DBus", + "NameOwnerChanged", + NULL, // Match all obj paths + NULL, // Match all arg0s + G_DBUS_SIGNAL_FLAGS_NONE, + (GDBusSignalCallback)monitor_bus, + snhost, + (GDestroyNotify)sub_finalize); } @@ -328,6 +355,7 @@ static void name_acquired_handler(GDBusConnection *conn, const char *busname, StatusNotifierHost *snhost) { GError *err = NULL; + snhost->conn = conn; g_dbus_connection_emit_signal(conn, NULL, @@ -338,7 +366,8 @@ name_acquired_handler(GDBusConnection *conn, const char *busname, StatusNotifier &err); if (err) { - g_debug("%s\n", err->message); + g_warning("%s\n", err->message); + fprintf(stderr, "from name_acquired_handler\n"); g_error_free(err); } } @@ -352,11 +381,42 @@ name_lost_handler(GDBusConnection *conn, const char *busname, StatusNotifierHost } +static void +snhost_finalize(StatusNotifierHost *snhost) +{ + gtk_window_close(snhost->window); + g_free(snhost); + snhost = NULL; +} + + +static void +busobj_finalize(StatusNotifierHost *snhost) +{ + unregister_all(snhost); + g_slist_free(snhost->trayitems); + g_bus_unown_name(snhost->owner_id); +} + + +static void +sub_finalize(StatusNotifierHost *snhost) +{ + g_dbus_connection_unregister_object(snhost->conn, snhost->obj_id); +} + + +void +terminate_statusnotifierhost(StatusNotifierHost *snhost) +{ + g_dbus_connection_signal_unsubscribe(snhost->conn, snhost->sub_id); +} + + StatusNotifierHost* start_statusnotifierhost() { StatusNotifierHost *snhost = g_malloc0(sizeof(StatusNotifierHost)); - snhost->nodeinfo = g_dbus_node_info_new_for_xml(STATUSNOTIFIERWATCHER_XML, NULL); snhost->height = 22; snhost->margin = 4; @@ -369,7 +429,7 @@ start_statusnotifierhost() (GBusNameAcquiredCallback)name_acquired_handler, (GBusNameLostCallback)name_lost_handler, snhost, - NULL); // (GDestroyNotify)snhost_finalize); + (GDestroyNotify)snhost_finalize); return snhost; } diff --git a/statusnotifieritem.c b/statusnotifieritem.c @@ -22,45 +22,59 @@ argb_to_rgba(int32_t width, int32_t height, unsigned char *icon_data) static void -about_to_show_cb(GDBusProxy *proxy, GAsyncResult *res, StatusNotifierItem *snitem) -{ - GError *err = NULL; - GVariant *retval = g_dbus_proxy_call_finish(proxy, res, &err); - - if (err) { - // Some apps require "AboutToShow" to be called before activating a menuitem - // Others do not implement this method - // In case of the latter we log a debug warning and continue - g_debug("%s\n", err->message); - g_error_free(err); - } else { - g_variant_unref(retval); - } - - // Widget may be about to be destroyed - if (GTK_IS_WIDGET(snitem->popovermenu)) - gtk_popover_popup(GTK_POPOVER(snitem->popovermenu)); -} - - -static void on_leftclick_cb(GtkGestureClick *click, int n_press, double x, double y, StatusNotifierItem *snitem) { - g_dbus_proxy_call(snitem->proxy, - "Activate", - g_variant_new("(ii)", 0, 0), - G_DBUS_CALL_FLAGS_NONE, - -1, - NULL, - NULL, - snitem); + if (snitem && snitem->menuproxy && !snitem->isclosing) + g_dbus_proxy_call(snitem->proxy, + "Activate", + g_variant_new("(ii)", 0, 0), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + NULL, + NULL); } +static void +rightclick_validate(GDBusProxy *proxy, GAsyncResult *res, StatusNotifierItem *snitem) +{ + GError *err = NULL; + GVariant *val = g_dbus_proxy_call_finish(proxy, res, &err); + + // This error is generated when answer for the call arrives after + // icon was finalized. + if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_NO_REPLY)) { + g_error_free(err); + return; + + // Discord generates the following error here: + // 'G_DBUS_ERROR' 'G_DBUS_ERROR_FAILED' 'error occurred in AboutToShow' + // We ignore it. + } else if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_FAILED) && + g_strrstr(err->message, "error occured in AboutToShow") == 0) { + g_error_free(err); + + if (snitem && snitem->icon && GTK_IS_WIDGET(snitem->icon) && + snitem->popovermenu && GTK_IS_WIDGET(snitem->popovermenu)) + gtk_popover_popup(GTK_POPOVER(snitem->popovermenu)); + + // Report rest of possible errors + } else if (err) { + g_warning("%sfrom on_rightclick_cb\n", err->message); + g_error_free(err); + + } else { + g_variant_unref(val); + if (snitem && snitem->icon && GTK_IS_WIDGET(snitem->icon) && + snitem->popovermenu && GTK_IS_WIDGET(snitem->popovermenu)) + gtk_popover_popup(GTK_POPOVER(snitem->popovermenu)); + } +} static void @@ -70,14 +84,20 @@ on_rightclick_cb(GtkGestureClick *click, double y, StatusNotifierItem *snitem) { - g_dbus_proxy_call(snitem->menuproxy, - "AboutToShow", - g_variant_new("(i)", 0), - G_DBUS_CALL_FLAGS_NONE, - -1, - NULL, - (GAsyncReadyCallback)about_to_show_cb, - snitem); + if (snitem && snitem->menuproxy && !snitem->isclosing) { + if (snitem && snitem->icon && GTK_IS_WIDGET(snitem->icon) && + snitem->popovermenu && GTK_IS_WIDGET(snitem->popovermenu)) + gtk_popover_popdown(GTK_POPOVER(snitem->popovermenu)); + + g_dbus_proxy_call(snitem->menuproxy, + "AboutToShow", + g_variant_new("(i)", 0), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback)rightclick_validate, + snitem); + } } @@ -200,8 +220,12 @@ new_iconname_handler(GDBusProxy *proxy, GAsyncResult *res, StatusNotifierItem *s GVariant *data = g_dbus_proxy_call_finish(proxy, res, &err); // (v) - if (err) { - g_debug("%s\n", err->message); + if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_OBJECT)) { + g_error_free(err); + return; + } else if (err) { + g_warning("%s\n", err->message); + fprintf(stderr, "from new_iconname_handler\n"); g_error_free(err); return; } @@ -213,7 +237,7 @@ new_iconname_handler(GDBusProxy *proxy, GAsyncResult *res, StatusNotifierItem *s g_variant_unref(iconname_v); if (strcmp(iconname, snitem->iconname) == 0) { - g_debug("%s\n", "pixmap didnt change, nothing to"); + // g_debug("%s got NewIcon, but iconname didn't change. Ignoring\n", snitem->busname); g_variant_unref(data); return; } @@ -236,8 +260,12 @@ new_iconpixmap_handler(GDBusProxy *proxy, GAsyncResult *res, StatusNotifierItem GVariant *data = g_dbus_proxy_call_finish(proxy, res, &err); // (v) - if (err) { - g_debug("%s\n", err->message); + if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_OBJECT)) { + g_error_free(err); + return; + } else if (err) { + g_warning("%s\n", err->message); + fprintf(stderr, "from new_iconpixmap_handler\n"); g_error_free(err); return; } @@ -247,7 +275,7 @@ new_iconpixmap_handler(GDBusProxy *proxy, GAsyncResult *res, StatusNotifierItem g_variant_unref(data); if (g_variant_equal(newpixmap_v, snitem->iconpixmap_v)) { - g_debug ("%s\n", "iconname didnt change, nothing to"); + // g_debug ("%s got NewIcon, but iconpixmap didn't change. Ignoring\n", snitem->busname); g_variant_unref(newpixmap_v); return; } @@ -267,7 +295,6 @@ trayitem_signal_handler(GDBusProxy *proxy, GVariant *data_v, StatusNotifierItem *snitem) { - // TODO: this can fire many times in a short amount of time if (strcmp(signal, "NewIcon") == 0) { if (snitem->iconpixmap_v) g_dbus_proxy_call(proxy, @@ -330,78 +357,106 @@ create_icon(GDBusProxy *proxy, StatusNotifierItem *snitem) void -create_trayitem(GDBusConnection *conn, GAsyncResult *res, StatusNotifierItem *snitem) +create_trayitem(GObject *obj, GAsyncResult *res, StatusNotifierItem *snitem) { GError *err = NULL; - snitem->proxy = g_dbus_proxy_new_finish(res, &err); + GDBusProxy *proxy = g_dbus_proxy_new_finish(res, &err); if (err) { - fprintf(stderr, "%s\n", err->message); + g_error("%s\n", err->message); g_error_free(err); - return; } - GtkIconTheme *theme = gtk_icon_theme_get_for_display(gdk_display_get_default ()); - GVariant *iconthemepath_v = g_dbus_proxy_get_cached_property(snitem->proxy, "IconThemePath"); + // If this happens for whatever reason, we lose track of + // our window size (there will be a gap between systray and bar) + if (!snitem && proxy) { + g_object_unref(proxy); + return; + } else if (!snitem) { + return; + } + snitem->proxy = proxy; + + GVariant *iconthemepath_v; + const char *iconthemepath; + GtkIconTheme *theme; + GtkGesture *leftclick; + GtkGesture *rightclick; + GVariant *menu_buspath_v; + const char *menu_buspath; + GSimpleActionGroup *actiongroup; + GtkWidget *icon; + + /* + * const char *valid_menupaths[] = { + * "/MenuBar", + * "/com/canonical/dbusmenu", + * "/org/ayatana/NotificationItem", + * NULL + * }; + */ + + g_signal_connect(proxy, "g-signal", G_CALLBACK(trayitem_signal_handler), snitem); + + iconthemepath_v = g_dbus_proxy_get_cached_property(proxy, "IconThemePath"); + theme = gtk_icon_theme_get_for_display(gdk_display_get_default()); if (iconthemepath_v) { - const char *path = g_variant_get_string(iconthemepath_v, NULL); - gtk_icon_theme_add_search_path(theme, path); + g_variant_get(iconthemepath_v, "&s", &iconthemepath); + gtk_icon_theme_add_search_path(theme, iconthemepath); g_variant_unref(iconthemepath_v); } - GtkGesture *leftclick = gtk_gesture_click_new(); + icon = create_icon(proxy, snitem); + + leftclick = gtk_gesture_click_new(); gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(leftclick), 1); g_signal_connect(leftclick, "pressed", G_CALLBACK(on_leftclick_cb), snitem); - GtkGesture *rightclick = gtk_gesture_click_new(); + rightclick = gtk_gesture_click_new(); gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(rightclick), 3); g_signal_connect(rightclick, "pressed", G_CALLBACK(on_rightclick_cb), snitem); - const char *valid_menuobjects[] = { - "/MenuBar", - "/com/canonical/dbusmenu", - "/org/ayatana/NotificationItem", - NULL - }; - - GVariant *menuobj_v = g_dbus_proxy_get_cached_property(snitem->proxy, "Menu"); - if (menuobj_v) { - snitem->menuobj = g_strdup(g_variant_get_string(menuobj_v, NULL)); - g_variant_unref(menuobj_v); - } else { - snitem->menuobj = g_strdup("Invalid_menuobj"); + actiongroup = g_simple_action_group_new(); + if (snitem && icon && GTK_IS_WIDGET(icon)) { + gtk_widget_insert_action_group(icon, + "menuitem", + G_ACTION_GROUP(actiongroup)); } - snitem->actiongroup = g_simple_action_group_new(); - - snitem->menunodeinfo = g_dbus_node_info_new_for_xml(DBUSMENU_XML, NULL); - - g_signal_connect(snitem->proxy, "g-signal", G_CALLBACK(trayitem_signal_handler), snitem); - - for (int i = 0; valid_menuobjects[i] != NULL; i++) { - if (g_strrstr(snitem->menuobj, valid_menuobjects[i]) != NULL) { - g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION, - G_DBUS_PROXY_FLAGS_NONE, - snitem->menunodeinfo->interfaces[0], - snitem->busname, - snitem->menuobj, - "com.canonical.dbusmenu", - NULL, - (GAsyncReadyCallback)create_menu, - snitem); - } + snitem->actiongroup = actiongroup; + + menu_buspath_v = g_dbus_proxy_get_cached_property(proxy, "Menu"); + if (menu_buspath_v) + g_variant_get(menu_buspath_v, "&o", &menu_buspath); + else + menu_buspath = NULL; + + // for (int i = 0; valid_menupaths[i]; i++) { + // if (menu_buspath && g_strrstr(menu_buspath, valid_menupaths[i]) == 0) { + if (menu_buspath) { + GDBusNodeInfo *nodeinfo = g_dbus_node_info_new_for_xml(DBUSMENU_XML, NULL); + g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + nodeinfo->interfaces[0], + snitem->busname, + menu_buspath, + "com.canonical.dbusmenu", + NULL, + (GAsyncReadyCallback)create_menu, + snitem); + g_dbus_node_info_unref(nodeinfo); + } + // } + // } + + if (icon) { + gtk_widget_add_controller(icon, GTK_EVENT_CONTROLLER(leftclick)); + gtk_widget_add_controller(icon, GTK_EVENT_CONTROLLER(rightclick)); + gtk_box_append(GTK_BOX(snitem->host->box), icon); + snitem->icon = icon; } - GtkWidget *image = create_icon(snitem->proxy, snitem); - if (!image) - return; - - snitem->icon = image; - gtk_box_append(GTK_BOX(snitem->host->box), snitem->icon); - gtk_widget_add_controller(snitem->icon, GTK_EVENT_CONTROLLER(leftclick)); - gtk_widget_add_controller(snitem->icon, GTK_EVENT_CONTROLLER(rightclick)); - gtk_widget_insert_action_group(snitem->icon, - "menuitem", - G_ACTION_GROUP (snitem->actiongroup)); + if (menu_buspath_v) + g_variant_unref(menu_buspath_v); }