From 843ad38b3c427adb0bf319e9613d9813c8d9246c Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Wed, 7 Jun 2017 16:45:28 -0700 Subject: Implement Tray Icons This commit implements the StatusNotifierItem protocol, and enables swaybar to show tray icons. It also uses `xembedsniproxy` in order to communicate with xembed applications. The tray is completely optional, and can be disabled on compile time with the `enable-tray` option. Or on runtime with the bar config option `tray_output none`. Overview of changes: In swaybar very little is changed outside the tray subfolder except that all events are now polled in `event_loop.c`, this creates no functional difference. Six bar configuration options were added, these are detailed in sway-bar(5) The tray subfolder is where all protocol implementation takes place and is organised as follows: tray/sni_watcher.c: This file contains the StatusNotifierWatcher. It keeps track of items and hosts and reports when they come or go. tray/tray.c This file contains the StatusNotifierHost. It keeps track of sway's version of the items and represents the tray itself. tray/sni.c This file contains the StatusNotifierItem struct and all communication with individual items. tray/icon.c This file implements the icon theme protocol. It allows for finding icons by name, rather than by pixmap. tray/dbus.c This file allows for asynchronous DBus communication. See #986 #343 --- swaybar/CMakeLists.txt | 11 + swaybar/bar.c | 135 +++++++++---- swaybar/config.c | 12 ++ swaybar/event_loop.c | 143 +++++++++++++ swaybar/ipc.c | 40 +++- swaybar/render.c | 75 ++++++- swaybar/tray/dbus.c | 189 ++++++++++++++++++ swaybar/tray/icon.c | 404 +++++++++++++++++++++++++++++++++++++ swaybar/tray/sni.c | 463 ++++++++++++++++++++++++++++++++++++++++++ swaybar/tray/sni_watcher.c | 487 +++++++++++++++++++++++++++++++++++++++++++++ swaybar/tray/tray.c | 279 ++++++++++++++++++++++++++ 11 files changed, 2199 insertions(+), 39 deletions(-) create mode 100644 swaybar/event_loop.c create mode 100644 swaybar/tray/dbus.c create mode 100644 swaybar/tray/icon.c create mode 100644 swaybar/tray/sni.c create mode 100644 swaybar/tray/sni_watcher.c create mode 100644 swaybar/tray/tray.c (limited to 'swaybar') diff --git a/swaybar/CMakeLists.txt b/swaybar/CMakeLists.txt index f59a48fd..373719de 100644 --- a/swaybar/CMakeLists.txt +++ b/swaybar/CMakeLists.txt @@ -5,7 +5,13 @@ include_directories( ${PANGO_INCLUDE_DIRS} ${JSONC_INCLUDE_DIRS} ${XKBCOMMON_INCLUDE_DIRS} + ${DBUS_INCLUDE_DIRS} ) +if (enable-tray) + file(GLOB tray + tray/*.c + ) +endif() add_executable(swaybar main.c @@ -14,6 +20,8 @@ add_executable(swaybar bar.c status_line.c ipc.c + event_loop.c + ${tray} ) target_link_libraries(swaybar @@ -24,6 +32,7 @@ target_link_libraries(swaybar ${CAIRO_LIBRARIES} ${PANGO_LIBRARIES} ${JSONC_LIBRARIES} + ${DBUS_LIBRARIES} ) if (WITH_GDK_PIXBUF) @@ -32,6 +41,8 @@ if (WITH_GDK_PIXBUF) ) endif() +target_link_libraries(swaybar rt) + install( TARGETS swaybar RUNTIME diff --git a/swaybar/bar.c b/swaybar/bar.c index abde1cc9..cdaf6a37 100644 --- a/swaybar/bar.c +++ b/swaybar/bar.c @@ -7,10 +7,17 @@ #include #include #include +#ifdef ENABLE_TRAY +#include +#include "swaybar/tray/sni_watcher.h" +#include "swaybar/tray/tray.h" +#include "swaybar/tray/sni.h" +#endif #include "swaybar/ipc.h" #include "swaybar/render.h" #include "swaybar/config.h" #include "swaybar/status_line.h" +#include "swaybar/event_loop.h" #include "swaybar/bar.h" #include "ipc-client.h" #include "list.h" @@ -50,18 +57,39 @@ static void spawn_status_cmd_proc(struct bar *bar) { } } +#ifdef ENABLE_TRAY +static void spawn_xembed_sni_proxy() { + pid_t pid = fork(); + if (pid == 0) { + int wstatus; + do { + pid = fork(); + if (pid == 0) { + execlp("xembedsniproxy", "xembedsniproxy", NULL); + _exit(EXIT_FAILURE); + } + waitpid(pid, &wstatus, 0); + } while (!WIFEXITED(wstatus)); + _exit(EXIT_FAILURE); + } +} +#endif + struct output *new_output(const char *name) { struct output *output = malloc(sizeof(struct output)); output->name = strdup(name); output->window = NULL; output->registry = NULL; output->workspaces = create_list(); +#ifdef ENABLE_TRAY + output->items = create_list(); +#endif return output; } static void mouse_button_notify(struct window *window, int x, int y, uint32_t button, uint32_t state_w) { - sway_log(L_DEBUG, "Mouse button %d clicked at %d %d %d\n", button, x, y, state_w); + sway_log(L_DEBUG, "Mouse button %d clicked at %d %d %d", button, x, y, state_w); if (!state_w) { return; } @@ -92,6 +120,30 @@ static void mouse_button_notify(struct window *window, int x, int y, break; } } + +#ifdef ENABLE_TRAY + uint32_t tray_padding = swaybar.config->tray_padding; + int tray_width = window->width * window->scale; + + for (int i = 0; i < clicked_output->items->length; ++i) { + struct sni_icon_ref *item = + clicked_output->items->items[i]; + int icon_width = cairo_image_surface_get_width(item->icon); + + tray_width -= tray_padding; + if (x <= tray_width && x >= tray_width - icon_width) { + if (button == swaybar.config->activate_button) { + sni_activate(item->ref, x, y); + } else if (button == swaybar.config->context_button) { + sni_context_menu(item->ref, x, y); + } else if (button == swaybar.config->secondary_button) { + sni_secondary(item->ref, x, y); + } + break; + } + tray_width -= icon_width; + } +#endif } static void mouse_scroll_notify(struct window *window, enum scroll_direction direction) { @@ -136,6 +188,9 @@ void bar_setup(struct bar *bar, const char *socket_path, const char *bar_id) { /* initialize bar with default values */ bar_init(bar); + /* Initialize event loop lists */ + init_event_loop(); + /* connect to sway ipc */ bar->ipc_socketfd = ipc_open_socket(socket_path); bar->ipc_event_socketfd = ipc_open_socket(socket_path); @@ -178,23 +233,54 @@ void bar_setup(struct bar *bar, const char *socket_path, const char *bar_id) { } /* spawn status command */ spawn_status_cmd_proc(bar); + +#ifdef ENABLE_TRAY + // We should have at least one output to serve the tray to + if (!swaybar.config->tray_output || strcmp(swaybar.config->tray_output, "none") != 0) { + /* Connect to the D-Bus */ + dbus_init(); + + /* Start the SNI watcher */ + init_sni_watcher(); + + /* Start the SNI host */ + init_tray(); + + /* Start xembedsniproxy */ + spawn_xembed_sni_proxy(); + } +#endif } -void bar_run(struct bar *bar) { - int pfds = bar->outputs->length + 2; - struct pollfd *pfd = malloc(pfds * sizeof(struct pollfd)); - bool dirty = true; +bool dirty = true; - pfd[0].fd = bar->ipc_event_socketfd; - pfd[0].events = POLLIN; - pfd[1].fd = bar->status_read_fd; - pfd[1].events = POLLIN; +static void respond_ipc(int fd, short mask, void *_bar) { + struct bar *bar = (struct bar *)_bar; + sway_log(L_DEBUG, "Got IPC event."); + dirty = handle_ipc_event(bar); +} + +static void respond_command(int fd, short mask, void *_bar) { + struct bar *bar = (struct bar *)_bar; + dirty = handle_status_line(bar); +} + +static void respond_output(int fd, short mask, void *_output) { + struct output *output = (struct output *)_output; + if (wl_display_dispatch(output->registry->display) == -1) { + sway_log(L_ERROR, "failed to dispatch wl: %d", errno); + } +} + +void bar_run(struct bar *bar) { + add_event(bar->ipc_event_socketfd, POLLIN, respond_ipc, bar); + add_event(bar->status_read_fd, POLLIN, respond_command, bar); int i; for (i = 0; i < bar->outputs->length; ++i) { struct output *output = bar->outputs->items[i]; - pfd[i+2].fd = wl_display_get_fd(output->registry->display); - pfd[i+2].events = POLLIN; + add_event(wl_display_get_fd(output->registry->display), + POLLIN, respond_output, output); } while (1) { @@ -212,29 +298,10 @@ void bar_run(struct bar *bar) { dirty = false; - poll(pfd, pfds, -1); - - if (pfd[0].revents & POLLIN) { - sway_log(L_DEBUG, "Got IPC event."); - dirty = handle_ipc_event(bar); - } - - if (bar->config->status_command && pfd[1].revents & POLLIN) { - sway_log(L_DEBUG, "Got update from status command."); - dirty = handle_status_line(bar); - } - - // dispatch wl_display events - for (i = 0; i < bar->outputs->length; ++i) { - struct output *output = bar->outputs->items[i]; - if (pfd[i+2].revents & POLLIN) { - if (wl_display_dispatch(output->registry->display) == -1) { - sway_log(L_ERROR, "failed to dispatch wl: %d", errno); - } - } else { - wl_display_dispatch_pending(output->registry->display); - } - } + event_loop_poll(); +#ifdef ENABLE_TRAY + dispatch_dbus(); +#endif } } diff --git a/swaybar/config.c b/swaybar/config.c index 1d802022..8fe552f2 100644 --- a/swaybar/config.c +++ b/swaybar/config.c @@ -48,6 +48,18 @@ struct config *init_config() { /* height */ config->height = 0; +#ifdef ENABLE_TRAY + config->tray_output = NULL; + config->icon_theme = NULL; + config->tray_padding = 2; + /** + * These constants are used by wayland and are defined in + * linux/input-event-codes.h + */ + config->activate_button = 0x110; /* BTN_LEFT */ + config->context_button = 0x111; /* BTN_RIGHT */ +#endif + /* colors */ config->colors.background = 0x000000FF; config->colors.statusline = 0xFFFFFFFF; diff --git a/swaybar/event_loop.c b/swaybar/event_loop.c new file mode 100644 index 00000000..80655d8b --- /dev/null +++ b/swaybar/event_loop.c @@ -0,0 +1,143 @@ +#define _XOPEN_SOURCE 500 +#include +#include +#include +#include +#include +#include "swaybar/bar.h" +#include "swaybar/event_loop.h" +#include "list.h" +#include "log.h" + +struct event_item { + void(*cb)(int fd, short mask, void *data); + void *data; +}; + +struct timer_item { + timer_t timer; + void(*cb)(timer_t timer, void *data); + void *data; +}; + +static struct { + // The order of each must be kept consistent + struct { /* pollfd array */ + struct pollfd *items; + int capacity; + int length; + } fds; + list_t *items; /* event_item list */ + + // Timer list + list_t *timers; +} event_loop; + +void add_timer(timer_t timer, + void(*cb)(timer_t timer, void *data), + void *data) { + + struct timer_item *item = malloc(sizeof(struct timer_item)); + item->timer = timer; + item->cb = cb; + item->data = data; + + list_add(event_loop.timers, item); +} + +void add_event(int fd, short mask, + void(*cb)(int fd, short mask, void *data), void *data) { + + struct pollfd pollfd = { + fd, + mask, + 0, + }; + + // Resize + if (event_loop.fds.length == event_loop.fds.capacity) { + event_loop.fds.capacity += 10; + event_loop.fds.items = realloc(event_loop.fds.items, + sizeof(struct pollfd) * event_loop.fds.capacity); + } + + event_loop.fds.items[event_loop.fds.length++] = pollfd; + + struct event_item *item = malloc(sizeof(struct event_item)); + item->cb = cb; + item->data = data; + + list_add(event_loop.items, item); + + return; +} + +bool remove_event(int fd) { + int index = -1; + for (int i = 0; i < event_loop.fds.length; ++i) { + if (event_loop.fds.items[i].fd == fd) { + index = i; + } + } + if (index != -1) { + free(event_loop.items->items[index]); + + --event_loop.fds.length; + memmove(&event_loop.fds.items[index], &event_loop.fds.items[index + 1], + sizeof(struct pollfd) * event_loop.fds.length - index); + + list_del(event_loop.items, index); + return true; + } else { + return false; + } +} + +static int timer_item_timer_cmp(const void *_timer_item, const void *_timer) { + const struct timer_item *timer_item = _timer_item; + const timer_t *timer = _timer; + if (timer_item->timer == timer) { + return 0; + } else { + return -1; + } +} +bool remove_timer(timer_t timer) { + int index = list_seq_find(event_loop.timers, timer_item_timer_cmp, &timer); + if (index != -1) { + list_del(event_loop.timers, index); + return true; + } + return false; +} + +void event_loop_poll() { + poll(event_loop.fds.items, event_loop.fds.length, -1); + + for (int i = 0; i < event_loop.fds.length; ++i) { + struct pollfd pfd = event_loop.fds.items[i]; + struct event_item *item = (struct event_item *)event_loop.items->items[i]; + + if (pfd.revents & pfd.events) { + item->cb(pfd.fd, pfd.revents, item->data); + } + } + + // check timers + // not tested, but seems to work + for (int i = 0; i < event_loop.timers->length; ++i) { + struct timer_item *item = event_loop.timers->items[i]; + int overrun = timer_getoverrun(item->timer); + if (overrun && overrun != -1) { + item->cb(item->timer, item->data); + } + } +} + +void init_event_loop() { + event_loop.fds.length = 0; + event_loop.fds.capacity = 10; + event_loop.fds.items = malloc(event_loop.fds.capacity * sizeof(struct pollfd)); + event_loop.items = create_list(); + event_loop.timers = create_list(); +} diff --git a/swaybar/ipc.c b/swaybar/ipc.c index b08eeea8..93d1219c 100644 --- a/swaybar/ipc.c +++ b/swaybar/ipc.c @@ -19,11 +19,19 @@ void ipc_send_workspace_command(const char *workspace_name) { static void ipc_parse_config(struct config *config, const char *payload) { json_object *bar_config = json_tokener_parse(payload); - json_object *tray_output, *mode, *hidden_bar, *position, *status_command; + json_object *markup, *mode, *hidden_bar, *position, *status_command; json_object *font, *bar_height, *wrap_scroll, *workspace_buttons, *strip_workspace_numbers; json_object *binding_mode_indicator, *verbose, *colors, *sep_symbol, *outputs; - json_object *markup; +#ifdef ENABLE_TRAY + json_object *tray_output, *icon_theme, *tray_padding, *activate_button, *context_button; + json_object *secondary_button; json_object_object_get_ex(bar_config, "tray_output", &tray_output); + json_object_object_get_ex(bar_config, "icon_theme", &icon_theme); + json_object_object_get_ex(bar_config, "tray_padding", &tray_padding); + json_object_object_get_ex(bar_config, "activate_button", &activate_button); + json_object_object_get_ex(bar_config, "context_button", &context_button); + json_object_object_get_ex(bar_config, "secondary_button", &secondary_button); +#endif json_object_object_get_ex(bar_config, "mode", &mode); json_object_object_get_ex(bar_config, "hidden_bar", &hidden_bar); json_object_object_get_ex(bar_config, "position", &position); @@ -83,6 +91,34 @@ static void ipc_parse_config(struct config *config, const char *payload) { config->pango_markup = json_object_get_boolean(markup); } +#ifdef ENABLE_TRAY + if (tray_output) { + free(config->tray_output); + config->tray_output = strdup(json_object_get_string(tray_output)); + } + + if (icon_theme) { + free(config->icon_theme); + config->icon_theme = strdup(json_object_get_string(icon_theme)); + } + + if (tray_padding) { + config->tray_padding = json_object_get_int(tray_padding); + } + + if (activate_button) { + config->activate_button = json_object_get_int(activate_button); + } + + if (context_button) { + config->context_button = json_object_get_int(context_button); + } + + if (secondary_button) { + config->secondary_button = json_object_get_int(secondary_button); + } +#endif + // free previous outputs list int i; for (i = 0; i < config->outputs->length; ++i) { diff --git a/swaybar/render.c b/swaybar/render.c index 2eae997f..0b2ac438 100644 --- a/swaybar/render.c +++ b/swaybar/render.c @@ -8,6 +8,10 @@ #include "swaybar/config.h" #include "swaybar/status_line.h" #include "swaybar/render.h" +#ifdef ENABLE_TRAY +#include "swaybar/tray/tray.h" +#include "swaybar/tray/sni.h" +#endif #include "log.h" @@ -297,6 +301,72 @@ void render(struct output *output, struct config *config, struct status_line *li } cairo_paint(cairo); +#ifdef ENABLE_TRAY + // Tray icons + uint32_t tray_padding = config->tray_padding; + unsigned int tray_width = window->width * window->scale; + const int item_size = (window->height * window->scale) - (2 * tray_padding); + + if (item_size < 0) { + // Can't render items if the padding is too large + goto no_tray; + } + + if (config->tray_output && strcmp(config->tray_output, output->name) != 0) { + goto no_tray; + } + + for (int i = 0; i < tray->items->length; ++i) { + struct StatusNotifierItem *item = + tray->items->items[i]; + if (!item->image) { + continue; + } + + struct sni_icon_ref *render_item = NULL; + int j; + for (j = i; j < output->items->length; ++j) { + struct sni_icon_ref *ref = + output->items->items[j]; + if (ref->ref == item) { + render_item = ref; + break; + } else { + sni_icon_ref_free(ref); + list_del(output->items, j); + } + } + + if (!render_item) { + render_item = sni_icon_ref_create(item, item_size); + list_add(output->items, render_item); + } else if (item->dirty) { + // item needs re-render + sni_icon_ref_free(render_item); + output->items->items[j] = render_item = + sni_icon_ref_create(item, item_size); + } + + tray_width -= tray_padding; + tray_width -= item_size; + + cairo_set_source_surface(cairo, render_item->icon, tray_width, tray_padding); + cairo_rectangle(cairo, tray_width, tray_padding, item_size, item_size); + cairo_fill(cairo); + + item->dirty = false; + } + + + if (tray_width != window->width * window->scale) { + tray_width -= tray_padding; + } + +no_tray: +#else + const int tray_width = window->width * window->scale; +#endif + // Command output if (is_focused) { cairo_set_source_u32(cairo, config->colors.focused_statusline); @@ -309,12 +379,11 @@ void render(struct output *output, struct config *config, struct status_line *li if (line->protocol == TEXT) { get_text_size(window->cairo, window->font, &width, &height, window->scale, config->pango_markup, "%s", line->text_line); - cairo_move_to(cairo, (window->width * window->scale) - - margin - width, margin); + cairo_move_to(cairo, tray_width - margin - width, margin); pango_printf(window->cairo, window->font, window->scale, config->pango_markup, "%s", line->text_line); } else if (line->protocol == I3BAR && line->block_line) { - double pos = (window->width * window->scale) - 0.5; + double pos = tray_width - 0.5; bool edge = true; for (i = line->block_line->length - 1; i >= 0; --i) { struct status_block *block = line->block_line->items[i]; diff --git a/swaybar/tray/dbus.c b/swaybar/tray/dbus.c new file mode 100644 index 00000000..333d398e --- /dev/null +++ b/swaybar/tray/dbus.c @@ -0,0 +1,189 @@ +#define _XOPEN_SOURCE 500 +#include +#include +#include +#include +#include +#include +#include +#include +#include "swaybar/tray/dbus.h" +#include "swaybar/event_loop.h" +#include "log.h" + +DBusConnection *conn = NULL; + +static void dispatch_watch(int fd, short mask, void *data) { + sway_log(L_DEBUG, "Dispatching watch"); + DBusWatch *watch = data; + + if (!dbus_watch_get_enabled(watch)) { + return; + } + + uint32_t flags = 0; + + if (mask & POLLIN) { + flags |= DBUS_WATCH_READABLE; + } if (mask & POLLOUT) { + flags |= DBUS_WATCH_WRITABLE; + } if (mask & POLLHUP) { + flags |= DBUS_WATCH_HANGUP; + } if (mask & POLLERR) { + flags |= DBUS_WATCH_ERROR; + } + + dbus_watch_handle(watch, flags); +} + +static dbus_bool_t add_watch(DBusWatch *watch, void *_data) { + if (!dbus_watch_get_enabled(watch)) { + // Watch should not be polled + return TRUE; + } + + short mask = 0; + uint32_t flags = dbus_watch_get_flags(watch); + + if (flags & DBUS_WATCH_READABLE) { + mask |= POLLIN; + } if (flags & DBUS_WATCH_WRITABLE) { + mask |= POLLOUT; + } + + int fd = dbus_watch_get_unix_fd(watch); + + sway_log(L_DEBUG, "Adding DBus watch fd: %d", fd); + add_event(fd, mask, dispatch_watch, watch); + + return TRUE; +} + +static void remove_watch(DBusWatch *watch, void *_data) { + int fd = dbus_watch_get_unix_fd(watch); + + remove_event(fd); +} + +static void dispatch_timeout(timer_t timer, void *data) { + sway_log(L_DEBUG, "Dispatching DBus timeout"); + DBusTimeout *timeout = data; + + if (dbus_timeout_get_enabled(timeout)) { + dbus_timeout_handle(timeout); + } +} + +static dbus_bool_t add_timeout(DBusTimeout *timeout, void *_data) { + if (!dbus_timeout_get_enabled(timeout)) { + return TRUE; + } + + timer_t *timer = malloc(sizeof(timer_t)); + if (!timer) { + sway_log(L_ERROR, "Cannot allocate memory"); + return FALSE; + } + struct sigevent ev = { + .sigev_notify = SIGEV_NONE, + }; + + if (timer_create(CLOCK_MONOTONIC, &ev, timer)) { + sway_log(L_ERROR, "Could not create DBus timer"); + return FALSE; + } + + int interval = dbus_timeout_get_interval(timeout); + int interval_sec = interval / 1000; + int interval_msec = (interval_sec * 1000) - interval; + + struct timespec period = { + (time_t) interval_sec, + ((long) interval_msec) * 1000 * 1000, + }; + struct itimerspec time = { + period, + period, + }; + + timer_settime(*timer, 0, &time, NULL); + + dbus_timeout_set_data(timeout, timer, free); + + sway_log(L_DEBUG, "Adding DBus timeout. Interval: %ds %dms", interval_sec, interval_msec); + add_timer(*timer, dispatch_timeout, timeout); + + return TRUE; +} +static void remove_timeout(DBusTimeout *timeout, void *_data) { + timer_t *timer = (timer_t *) dbus_timeout_get_data(timeout); + sway_log(L_DEBUG, "Removing DBus timeout."); + + if (timer) { + remove_timer(*timer); + } +} + +static bool should_dispatch = true; + +static void dispatch_status(DBusConnection *connection, DBusDispatchStatus new_status, + void *_data) { + if (new_status == DBUS_DISPATCH_DATA_REMAINS) { + should_dispatch = true; + } +} + +/* Public functions below */ + +void dispatch_dbus() { + if (!should_dispatch) { + return; + } + + DBusDispatchStatus status; + + do { + status = dbus_connection_dispatch(conn); + } while (status == DBUS_DISPATCH_DATA_REMAINS); + + if (status != DBUS_DISPATCH_COMPLETE) { + sway_log(L_ERROR, "Cannot dispatch dbus events: %d", status); + } + + should_dispatch = false; +} + +int dbus_init() { + DBusError error; + dbus_error_init(&error); + + conn = dbus_bus_get(DBUS_BUS_SESSION, &error); + dbus_connection_set_exit_on_disconnect(conn, FALSE); + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "Cannot get bus connection: %s\n", error.message); + conn = NULL; + return -1; + } + + sway_log(L_INFO, "Unique name: %s\n", dbus_bus_get_unique_name(conn)); + + // Will be called if dispatch status changes + dbus_connection_set_dispatch_status_function(conn, dispatch_status, NULL, NULL); + + if (!dbus_connection_set_watch_functions(conn, add_watch, remove_watch, + NULL, NULL, NULL)) { + dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL); + sway_log(L_ERROR, "Failed to activate DBUS watch functions"); + return -1; + } + + if (!dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout, + NULL, NULL, NULL)) { + dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL); + dbus_connection_set_timeout_functions(conn, NULL, NULL, NULL, NULL, NULL); + sway_log(L_ERROR, "Failed to activate DBUS timeout functions"); + return -1; + } + + return 0; +} diff --git a/swaybar/tray/icon.c b/swaybar/tray/icon.c new file mode 100644 index 00000000..29151a74 --- /dev/null +++ b/swaybar/tray/icon.c @@ -0,0 +1,404 @@ +#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "swaybar/tray/icon.h" +#include "swaybar/bar.h" +#include "swaybar/config.h" +#include "stringop.h" +#include "log.h" + +/** + * REVIEW: + * This file repeats lots of "costly" operations that are the same for every + * icon. It's possible to create a dictionary or some other structure to cache + * these, though it may complicate things somewhat. + * + * Also parsing (index.theme) is currently pretty messy, so that could be made + * much better as well. Over all, things work, but are not optimal. + */ + +/* Finds all themes that the given theme inherits */ +static list_t *find_inherits(const char *theme_dir) { + const char inherits[] = "Inherits"; + const char index_name[] = "index.theme"; + list_t *themes = create_list(); + FILE *index = NULL; + char *path = malloc(strlen(theme_dir) + sizeof(index_name)); + if (!path) { + goto fail; + } + if (!themes) { + goto fail; + } + + strcpy(path, theme_dir); + strcat(path, index_name); + + index = fopen(path, "r"); + if (!index) { + goto fail; + } + + char *buf = NULL; + size_t n = 0; + while (!feof(index)) { + getline(&buf, &n, index); + if (n <= sizeof(inherits) + 1) { + continue; + } + if (strncmp(inherits, buf, sizeof(inherits) - 1) == 0) { + char *themestr = buf + sizeof(inherits); + themes = split_string(themestr, ","); + break; + } + } + free(buf); + +fail: + free(path); + if (index) { + fclose(index); + } + return themes; +} + +static bool isdir(const char *path) { + struct stat statbuf; + if (stat(path, &statbuf) != -1) { + if (S_ISDIR(statbuf.st_mode)) { + return true; + } + } + return false; + +} + +/** + * Returns the directory of a given theme if it exists. + * The returned pointer must be freed. + */ +static char *find_theme_dir(const char *theme) { + char *basedir; + char *icon_dir; + + if (!theme) { + return NULL; + } + + if (!(icon_dir = malloc(1024))) { + sway_log(L_ERROR, "Out of memory!"); + goto fail; + } + + if ((basedir = getenv("HOME"))) { + if (snprintf(icon_dir, 1024, "%s/.icons/%s", basedir, theme) >= 1024) { + sway_log(L_ERROR, "Path too long to render"); + // XXX perhaps just goto trying in /usr/share? This + // shouldn't happen anyway, but might with a long global + goto fail; + } + + if (isdir(icon_dir)) { + return icon_dir; + } + } + + if ((basedir = getenv("XDG_DATA_DIRS"))) { + if (snprintf(icon_dir, 1024, "%s/icons/%s", basedir, theme) >= 1024) { + sway_log(L_ERROR, "Path too long to render"); + // ditto + goto fail; + } + + if (isdir(icon_dir)) { + return icon_dir; + } + } + + // Spec says use "/usr/share/pixmaps/", but I see everything in + // "/usr/share/icons/" look it both, I suppose. + if (snprintf(icon_dir, 1024, "/usr/share/pixmaps/%s", theme) >= 1024) { + sway_log(L_ERROR, "Path too long to render"); + goto fail; + } + if (isdir(icon_dir)) { + return icon_dir; + } + + if (snprintf(icon_dir, 1024, "/usr/share/icons/%s", theme) >= 1024) { + sway_log(L_ERROR, "Path too long to render"); + goto fail; + } + if (isdir(icon_dir)) { + return icon_dir; + } + +fail: + free(icon_dir); + sway_log(L_ERROR, "Could not find dir for theme: %s", theme); + return NULL; +} + +/** + * Returns all theme dirs needed to be looked in for an icon. + * Does not check for duplicates + */ +static list_t *find_all_theme_dirs(const char *theme) { + list_t *dirs = create_list(); + if (!dirs) { + return NULL; + } + char *dir = find_theme_dir(theme); + if (dir) { + list_add(dirs, dir); + list_t *inherits = find_inherits(dir); + list_cat(dirs, inherits); + list_free(inherits); + } + dir = find_theme_dir("hicolor"); + if (dir) { + list_add(dirs, dir); + } + + return dirs; +} + +struct subdir { + int size; + char name[]; +}; + +static int subdir_str_cmp(const void *_subdir, const void *_str) { + const struct subdir *subdir = _subdir; + const char *str = _str; + return strcmp(subdir->name, str); +} +/** + * Helper to find_subdirs. Acts similar to `split_string(subdirs, ",")` but + * generates a list of struct subdirs + */ +static list_t *split_subdirs(char *subdir_str) { + list_t *subdir_list = create_list(); + char *copy = strdup(subdir_str); + if (!subdir_list || !copy) { + list_free(subdir_list); + free(copy); + return NULL; + } + + char *token; + token = strtok(copy, ","); + while(token) { + int len = strlen(token) + 1; + struct subdir *subdir = + malloc(sizeof(struct subdir) + sizeof(char [len])); + if (!subdir) { + // Return what we have + return subdir_list; + } + subdir->size = 0; + strcpy(subdir->name, token); + + list_add(subdir_list, subdir); + + token = strtok(NULL, ","); + } + free(copy); + + return subdir_list; +} +/** + * Returns a list of all subdirectories of a theme. + * Take note: the subdir names are all relative to `theme_dir` and must be + * combined with it to form a valid directory. + * + * Each member of the list is of type (struct subdir *) this struct contains + * the name of the subdir, along with size information. These must be freed + * bye the caller. + * + * This currently ignores min and max sizes of icons. + */ +static list_t* find_theme_subdirs(const char *theme_dir) { + const char index_name[] = "/index.theme"; + list_t *dirs = NULL; + char *path = malloc(strlen(theme_dir) + sizeof(index_name)); + FILE *index = NULL; + if (!path) { + sway_log(L_ERROR, "Failed to allocate memory"); + goto fail; + } + + strcpy(path, theme_dir); + strcat(path, index_name); + + index = fopen(path, "r"); + if (!index) { + sway_log(L_ERROR, "Could not open file: %s", path); + goto fail; + } + + char *buf = NULL; + size_t n = 0; + while (!feof(index)) { + const char directories[] = "Directories"; + getline(&buf, &n, index); + if (n <= sizeof(directories) + 1) { + continue; + } + if (strncmp(directories, buf, sizeof(directories) - 1) == 0) { + char *dirstr = buf + sizeof(directories); + dirs = split_subdirs(dirstr); + break; + } + } + // Now, find the size of each dir + struct subdir *current_subdir = NULL; + while (!feof(index)) { + const char size[] = "Size"; + getline(&buf, &n, index); + + if (buf[0] == '[') { + int len = strlen(buf); + if (buf[len-1] == '\n') { + len--; + } + // replace ']' + buf[len-1] = '\0'; + + int index; + if ((index = list_seq_find(dirs, subdir_str_cmp, buf+1)) != -1) { + current_subdir = (dirs->items[index]); + } + } + + if (strncmp(size, buf, sizeof(size) - 1) == 0) { + if (current_subdir) { + current_subdir->size = atoi(buf + sizeof(size)); + } + } + } + free(buf); +fail: + free(path); + if (index) { + fclose(index); + } + return dirs; +} + +/* Returns the file of an icon given its name and size */ +static char *find_icon_file(const char *name, int size) { + int namelen = strlen(name); + list_t *dirs = find_all_theme_dirs(swaybar.config->icon_theme); + if (!dirs) { + return NULL; + } + int min_size_diff = INT_MAX; + char *current_file = NULL; + + for (int i = 0; i < dirs->length; ++i) { + char *dir = dirs->items[i]; + list_t *subdirs = find_theme_subdirs(dir); + + if (!subdirs) { + continue; + } + + for (int i = 0; i < subdirs->length; ++i) { + struct subdir *subdir = subdirs->items[i]; + + // Only use an unsized if we don't already have a + // canidate this should probably change to allow svgs + if (!subdir->size && current_file) { + continue; + } + + int size_diff = abs(size - subdir->size); + + if (size_diff >= min_size_diff) { + continue; + } + + char *path = malloc(strlen(subdir->name) + strlen(dir) + 2); + + strcpy(path, dir); + path[strlen(dir)] = '/'; + strcpy(path + strlen(dir) + 1, subdir->name); + + DIR *icons = opendir(path); + if (!icons) { + free(path); + continue; + } + + struct dirent *direntry; + while ((direntry = readdir(icons)) != NULL) { + int len = strlen(direntry->d_name); + if (len <= namelen + 2) { //must have some ext + continue; + } + if (strncmp(direntry->d_name, name, namelen) == 0) { + char *ext = direntry->d_name + namelen + 1; +#ifdef WITH_GDK_PIXBUF + if (strcmp(ext, "png") == 0 || + strcmp(ext, "xpm") == 0 || + strcmp(ext, "svg") == 0) { +#else + if (strcmp(ext, "png") == 0) { +#endif + free(current_file); + char *icon_path = malloc(strlen(path) + len + 2); + + strcpy(icon_path, path); + icon_path[strlen(path)] = '/'; + strcpy(icon_path + strlen(path) + 1, direntry->d_name); + current_file = icon_path; + min_size_diff = size_diff; + } + } + } + free(path); + closedir(icons); + } + free_flat_list(subdirs); + } + free_flat_list(dirs); + + return current_file; +} + +cairo_surface_t *find_icon(const char *name, int size) { + char *image_path = find_icon_file(name, size); + if (image_path == NULL) { + return NULL; + } + + cairo_surface_t *image = NULL; +#ifdef WITH_GDK_PIXBUF + GError *err = NULL; + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(image_path, &err); + if (!pixbuf) { + sway_log(L_ERROR, "Failed to load icon image: %s", err->message); + } + image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf); + g_object_unref(pixbuf); +#else + // TODO make svg work? cairo supports it. maybe remove gdk alltogether + image = cairo_image_surface_create_from_png(image_path); +#endif //WITH_GDK_PIXBUF + if (!image) { + sway_log(L_ERROR, "Could not read icon image"); + return NULL; + } + + free(image_path); + return image; +} diff --git a/swaybar/tray/sni.c b/swaybar/tray/sni.c new file mode 100644 index 00000000..f0638dca --- /dev/null +++ b/swaybar/tray/sni.c @@ -0,0 +1,463 @@ +#define _XOPEN_SOURCE 500 +#include +#include +#include +#include +#include +#include +#include +#include +#include "swaybar/tray/dbus.h" +#include "swaybar/tray/sni.h" +#include "swaybar/tray/icon.h" +#include "swaybar/bar.h" +#include "client/cairo.h" +#include "log.h" + +// Not sure what this is but cairo needs it. +static const cairo_user_data_key_t cairo_user_data_key; + +struct sni_icon_ref *sni_icon_ref_create(struct StatusNotifierItem *item, + int height) { + struct sni_icon_ref *sni_ref = malloc(sizeof(struct sni_icon_ref)); + if (!sni_ref) { + return NULL; + } + sni_ref->icon = cairo_image_surface_scale(item->image, height, height); + sni_ref->ref = item; + + return sni_ref; +} + +void sni_icon_ref_free(struct sni_icon_ref *sni_ref) { + if (!sni_ref) { + return; + } + cairo_surface_destroy(sni_ref->icon); + free(sni_ref); +} + +/* Gets the pixmap of an icon */ +static void reply_icon(DBusPendingCall *pending, void *_data) { + struct StatusNotifierItem *item = _data; + + DBusMessage *reply = dbus_pending_call_steal_reply(pending); + + if (!reply) { + sway_log(L_ERROR, "Did not get reply"); + goto bail; + } + + int message_type = dbus_message_get_type(reply); + + if (message_type == DBUS_MESSAGE_TYPE_ERROR) { + char *msg; + + dbus_message_get_args(reply, NULL, + DBUS_TYPE_STRING, &msg, + DBUS_TYPE_INVALID); + + sway_log(L_ERROR, "Message is error: %s", msg); + goto bail; + } + + DBusMessageIter iter; + DBusMessageIter variant; /* v[a(iiay)] */ + DBusMessageIter array; /* a(iiay) */ + DBusMessageIter d_struct; /* (iiay) */ + DBusMessageIter icon; /* ay */ + + dbus_message_iter_init(reply, &iter); + + // Each if here checks the types above before recursing + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + sway_log(L_ERROR, "Relpy type incorrect"); + sway_log(L_ERROR, "Should be \"v\", is \"%s\"", + dbus_message_iter_get_signature(&iter)); + goto bail; + } + dbus_message_iter_recurse(&iter, &variant); + + if (strcmp("a(iiay)", dbus_message_iter_get_signature(&variant)) != 0) { + sway_log(L_ERROR, "Relpy type incorrect"); + sway_log(L_ERROR, "Should be \"a(iiay)\", is \"%s\"", + dbus_message_iter_get_signature(&variant)); + goto bail; + } + + if (dbus_message_iter_get_element_count(&variant) == 0) { + // Can't recurse if there are no items + sway_log(L_INFO, "Item has no icon"); + goto bail; + } + dbus_message_iter_recurse(&variant, &array); + + dbus_message_iter_recurse(&array, &d_struct); + + int width; + dbus_message_iter_get_basic(&d_struct, &width); + dbus_message_iter_next(&d_struct); + + int height; + dbus_message_iter_get_basic(&d_struct, &height); + dbus_message_iter_next(&d_struct); + + int len = dbus_message_iter_get_element_count(&d_struct); + + if (!len) { + sway_log(L_ERROR, "No icon data"); + goto bail; + } + + // Also implies len % 4 == 0, useful below + if (len != width * height * 4) { + sway_log(L_ERROR, "Incorrect array size passed"); + goto bail; + } + + dbus_message_iter_recurse(&d_struct, &icon); + + int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); + // FIXME support a variable stride + // (works on my machine though for all tested widths) + if (!sway_assert(stride == width * 4, "Stride must be equal to byte length")) { + goto bail; + } + + // Data is by reference, no need to free + uint8_t *message_data; + dbus_message_iter_get_fixed_array(&icon, &message_data, &len); + + uint8_t *image_data = malloc(stride * height); + if (!image_data) { + sway_log(L_ERROR, "Could not allocate memory for icon"); + goto bail; + } + + // Transform from network byte order to host byte order + // Assumptions are safe because the equality above + uint32_t *network = (uint32_t *) message_data; + uint32_t *host = (uint32_t *)image_data; + for (int i = 0; i < width * height; ++i) { + host[i] = ntohl(network[i]); + } + + cairo_surface_t *image = cairo_image_surface_create_for_data( + image_data, CAIRO_FORMAT_ARGB32, + width, height, stride); + + if (image) { + if (item->image) { + cairo_surface_destroy(item->image); + } + item->image = image; + // Free the image data on surface destruction + cairo_surface_set_user_data(image, + &cairo_user_data_key, + image_data, + free); + item->dirty = true; + dirty = true; + + dbus_message_unref(reply); + return; + } else { + sway_log(L_ERROR, "Could not create image surface"); + free(image_data); + } + +bail: + if (reply) { + dbus_message_unref(reply); + } + sway_log(L_ERROR, "Could not get icon from item"); + return; +} +static void send_icon_msg(struct StatusNotifierItem *item) { + DBusPendingCall *pending; + DBusMessage *message = dbus_message_new_method_call( + item->name, + "/StatusNotifierItem", + "org.freedesktop.DBus.Properties", + "Get"); + const char *iface; + if (item->kde_special_snowflake) { + iface = "org.kde.StatusNotifierItem"; + } else { + iface = "org.freedesktop.StatusNotifierItem"; + } + const char *prop = "IconPixmap"; + + dbus_message_append_args(message, + DBUS_TYPE_STRING, &iface, + DBUS_TYPE_STRING, &prop, + DBUS_TYPE_INVALID); + + bool status = + dbus_connection_send_with_reply(conn, message, &pending, -1); + + dbus_message_unref(message); + + if (!(pending || status)) { + sway_log(L_ERROR, "Could not get item icon"); + return; + } + + dbus_pending_call_set_notify(pending, reply_icon, item, NULL); +} + +/* Get an icon by its name */ +static void reply_icon_name(DBusPendingCall *pending, void *_data) { + struct StatusNotifierItem *item = _data; + + DBusMessage *reply = dbus_pending_call_steal_reply(pending); + + if (!reply) { + sway_log(L_INFO, "Got no icon name reply from item"); + goto bail; + } + + int message_type = dbus_message_get_type(reply); + + if (message_type == DBUS_MESSAGE_TYPE_ERROR) { + char *msg; + + dbus_message_get_args(reply, NULL, + DBUS_TYPE_STRING, &msg, + DBUS_TYPE_INVALID); + + sway_log(L_INFO, "Could not get icon name: %s", msg); + goto bail; + } + + DBusMessageIter iter; /* v[s] */ + DBusMessageIter variant; /* s */ + + dbus_message_iter_init(reply, &iter); + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + sway_log(L_ERROR, "Relpy type incorrect"); + sway_log(L_ERROR, "Should be \"v\", is \"%s\"", + dbus_message_iter_get_signature(&iter)); + goto bail; + } + dbus_message_iter_recurse(&iter, &variant); + + + if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_STRING) { + sway_log(L_ERROR, "Relpy type incorrect"); + sway_log(L_ERROR, "Should be \"s\", is \"%s\"", + dbus_message_iter_get_signature(&iter)); + goto bail; + } + + char *icon_name; + dbus_message_iter_get_basic(&variant, &icon_name); + + cairo_surface_t *image = find_icon(icon_name, 256); + + if (image) { + sway_log(L_DEBUG, "Icon for %s found with size %d", icon_name, + cairo_image_surface_get_width(image)); + if (item->image) { + cairo_surface_destroy(item->image); + } + item->image = image; + item->dirty = true; + dirty = true; + + dbus_message_unref(reply); + return; + } + +bail: + if (reply) { + dbus_message_unref(reply); + } + // Now try the pixmap + send_icon_msg(item); + return; +} +static void send_icon_name_msg(struct StatusNotifierItem *item) { + DBusPendingCall *pending; + DBusMessage *message = dbus_message_new_method_call( + item->name, + "/StatusNotifierItem", + "org.freedesktop.DBus.Properties", + "Get"); + const char *iface; + if (item->kde_special_snowflake) { + iface = "org.kde.StatusNotifierItem"; + } else { + iface = "org.freedesktop.StatusNotifierItem"; + } + const char *prop = "IconName"; + + dbus_message_append_args(message, + DBUS_TYPE_STRING, &iface, + DBUS_TYPE_STRING, &prop, + DBUS_TYPE_INVALID); + + bool status = + dbus_connection_send_with_reply(conn, message, &pending, -1); + + dbus_message_unref(message); + + if (!(pending || status)) { + sway_log(L_ERROR, "Could not get item icon name"); + return; + } + + dbus_pending_call_set_notify(pending, reply_icon_name, item, NULL); +} + +void get_icon(struct StatusNotifierItem *item) { + send_icon_name_msg(item); +} + +void sni_activate(struct StatusNotifierItem *item, uint32_t x, uint32_t y) { + const char *iface = + (item->kde_special_snowflake ? "org.kde.StatusNotifierItem" + : "org.freedesktop.StatusNotifierItem"); + DBusMessage *message = dbus_message_new_method_call( + item->name, + "/StatusNotifierItem", + iface, + "Activate"); + + dbus_message_append_args(message, + DBUS_TYPE_INT32, &x, + DBUS_TYPE_INT32, &y, + DBUS_TYPE_INVALID); + + dbus_connection_send(conn, message, NULL); + + dbus_message_unref(message); +} + +void sni_context_menu(struct StatusNotifierItem *item, uint32_t x, uint32_t y) { + const char *iface = + (item->kde_special_snowflake ? "org.kde.StatusNotifierItem" + : "org.freedesktop.StatusNotifierItem"); + DBusMessage *message = dbus_message_new_method_call( + item->name, + "/StatusNotifierItem", + iface, + "ContextMenu"); + + dbus_message_append_args(message, + DBUS_TYPE_INT32, &x, + DBUS_TYPE_INT32, &y, + DBUS_TYPE_INVALID); + + dbus_connection_send(conn, message, NULL); + + dbus_message_unref(message); +} +void sni_secondary(struct StatusNotifierItem *item, uint32_t x, uint32_t y) { + const char *iface = + (item->kde_special_snowflake ? "org.kde.StatusNotifierItem" + : "org.freedesktop.StatusNotifierItem"); + DBusMessage *message = dbus_message_new_method_call( + item->name, + "/StatusNotifierItem", + iface, + "SecondaryActivate"); + + dbus_message_append_args(message, + DBUS_TYPE_INT32, &x, + DBUS_TYPE_INT32, &y, + DBUS_TYPE_INVALID); + + dbus_connection_send(conn, message, NULL); + + dbus_message_unref(message); +} + +static void get_unique_name(struct StatusNotifierItem *item) { + // I think that we're fine being sync here becaues the message is + // directly to the message bus. Could be async though. + DBusMessage *message = dbus_message_new_method_call( + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetNameOwner"); + + dbus_message_append_args(message, + DBUS_TYPE_STRING, &item->name, + DBUS_TYPE_INVALID); + + DBusMessage *reply = dbus_connection_send_with_reply_and_block( + conn, message, -1, NULL); + + dbus_message_unref(message); + + if (!reply) { + sway_log(L_ERROR, "Could not get unique name for item: %s", + item->name); + return; + } + + if (!dbus_message_get_args(reply, NULL, + DBUS_TYPE_STRING, &item->unique_name, + DBUS_TYPE_INVALID)) { + item->unique_name = NULL; + sway_log(L_ERROR, "Error parsing method args"); + } + + dbus_message_unref(reply); +} + +struct StatusNotifierItem *sni_create(const char *name) { + struct StatusNotifierItem *item = malloc(sizeof(struct StatusNotifierItem)); + item->name = strdup(name); + item->unique_name = NULL; + item->image = NULL; + item->dirty = false; + + // If it doesn't use this name then assume that it uses the KDE spec + // This is because xembed-sni-proxy uses neither "org.freedesktop" nor + // "org.kde" and just gives us the items "unique name" + // + // We could use this to our advantage and fill out the "unique name" + // field with the given name if it is neither freedesktop or kde, but + // that's makes us rely on KDE hackyness which is bad practice + const char freedesktop_name[] = "org.freedesktop"; + if (strncmp(name, freedesktop_name, sizeof(freedesktop_name) - 1) != 0) { + item->kde_special_snowflake = true; + } else { + item->kde_special_snowflake = false; + } + + get_icon(item); + + get_unique_name(item); + + return item; +} +/* Return true if `item` has a name of `str` */ +int sni_str_cmp(const void *_item, const void *_str) { + const struct StatusNotifierItem *item = _item; + const char *str = _str; + + return strcmp(item->name, str); +} +/* Returns true if `item` has a unique name of `str` */ +int sni_uniq_cmp(const void *_item, const void *_str) { + const struct StatusNotifierItem *item = _item; + const char *str = _str; + + if (!item->unique_name) { + return false; + } + return strcmp(item->unique_name, str); +} +void sni_free(struct StatusNotifierItem *item) { + if (!item) { + return; + } + free(item->name); + if (item->image) { + cairo_surface_destroy(item->image); + } + free(item); +} diff --git a/swaybar/tray/sni_watcher.c b/swaybar/tray/sni_watcher.c new file mode 100644 index 00000000..388e181d --- /dev/null +++ b/swaybar/tray/sni_watcher.c @@ -0,0 +1,487 @@ +#define _XOPEN_SOURCE 500 +#include +#include +#include +#include +#include +#include +#include "swaybar/tray/dbus.h" +#include "list.h" +#include "log.h" + +static list_t *items = NULL; +static list_t *hosts = NULL; + +/** + * Describes the function of the StatusNotifierWatcher + * See https://freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierWatcher/ + * + * We also implement KDE's special snowflake protocol, it's like this but with + * all occurrences 'freedesktop' replaced with 'kde'. There is no KDE introspect. + */ +static const char *interface_xml = + "" + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + +static void host_registered_signal(DBusConnection *connection) { + // Send one signal for each protocol + DBusMessage *signal = dbus_message_new_signal( + "/StatusNotifierWatcher", + "org.freedesktop.StatusNotifierWatcher", + "StatusNotifierHostRegistered"); + + dbus_connection_send(connection, signal, NULL); + dbus_message_unref(signal); + + + signal = dbus_message_new_signal( + "/StatusNotifierWatcher", + "org.kde.StatusNotifierWatcher", + "StatusNotifierHostRegistered"); + + dbus_connection_send(connection, signal, NULL); + dbus_message_unref(signal); +} +static void item_registered_signal(DBusConnection *connection, const char *name) { + DBusMessage *signal = dbus_message_new_signal( + "/StatusNotifierWatcher", + "org.freedesktop.StatusNotifierWatcher", + "StatusNotifierItemRegistered"); + dbus_message_append_args(signal, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID); + dbus_connection_send(connection, signal, NULL); + dbus_message_unref(signal); + + signal = dbus_message_new_signal( + "/StatusNotifierWatcher", + "org.kde.StatusNotifierWatcher", + "StatusNotifierItemRegistered"); + dbus_message_append_args(signal, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID); + dbus_connection_send(connection, signal, NULL); + dbus_message_unref(signal); +} +static void item_unregistered_signal(DBusConnection *connection, const char *name) { + DBusMessage *signal = dbus_message_new_signal( + "/StatusNotifierWatcher", + "org.freedesktop.StatusNotifierWatcher", + "StatusNotifierItemUnregistered"); + dbus_message_append_args(signal, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID); + dbus_connection_send(connection, signal, NULL); + dbus_message_unref(signal); + + signal = dbus_message_new_signal( + "/StatusNotifierWatcher", + "org.kde.StatusNotifierWatcher", + "StatusNotifierItemUnregistered"); + dbus_message_append_args(signal, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID); + dbus_connection_send(connection, signal, NULL); + dbus_message_unref(signal); +} + +static void respond_to_introspect(DBusConnection *connection, DBusMessage *request) { + DBusMessage *reply; + + reply = dbus_message_new_method_return(request); + dbus_message_append_args(reply, + DBUS_TYPE_STRING, &interface_xml, + DBUS_TYPE_INVALID); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); +} + +static void register_item(DBusConnection *connection, DBusMessage *message) { + DBusError error; + char *name; + + dbus_error_init(&error); + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) { + sway_log(L_ERROR, "Error parsing method args: %s\n", error.message); + } + + name = strdup(name); + sway_log(L_INFO, "RegisterStatusNotifierItem called with \"%s\"\n", name); + + // Don't add duplicate or not real item + if (list_seq_find(items, (int (*)(const void *, const void *))strcmp, name) != -1) { + return; + } + if (!dbus_bus_name_has_owner(connection, name, &error)) { + return; + } + + list_add(items, name); + item_registered_signal(connection, name); + + // It's silly, but xembedsniproxy wants a reply for this function + DBusMessage *reply = dbus_message_new_method_return(message); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); +} + +static void register_host(DBusConnection *connection, DBusMessage *message) { + DBusError error; + char *name; + + dbus_error_init(&error); + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) { + sway_log(L_ERROR, "Error parsing method args: %s\n", error.message); + } + + sway_log(L_INFO, "RegisterStatusNotifierHost called with \"%s\"\n", name); + + // Don't add duplicate or not real host + if (list_seq_find(hosts, (int (*)(const void *, const void *))strcmp, name) != -1) { + return; + } + if (!dbus_bus_name_has_owner(connection, name, &error)) { + return; + } + + list_add(hosts, strdup(name)); + host_registered_signal(connection); +} + +static void get_property(DBusConnection *connection, DBusMessage *message) { + DBusError error; + char *interface; + char *property; + + dbus_error_init(&error); + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &property, + DBUS_TYPE_INVALID)) { + sway_log(L_ERROR, "Error parsing prop args: %s\n", error.message); + return; + } + + if (strcmp(property, "RegisteredStatusNotifierItems") == 0) { + sway_log(L_INFO, "Replying with items\n"); + DBusMessage *reply; + reply = dbus_message_new_method_return(message); + DBusMessageIter iter; + DBusMessageIter sub; + DBusMessageIter subsub; + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, + "as", &sub); + dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY, + "s", &subsub); + + for (int i = 0; i < items->length; ++i) { + dbus_message_iter_append_basic(&subsub, + DBUS_TYPE_STRING, &items->items[i]); + } + + dbus_message_iter_close_container(&sub, &subsub); + dbus_message_iter_close_container(&iter, &sub); + + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + } else if (strcmp(property, "IsStatusNotifierHostRegistered") == 0) { + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter sub; + int registered = (hosts == NULL || hosts->length == 0) ? 0 : 1; + + reply = dbus_message_new_method_return(message); + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, + "b", &sub); + dbus_message_iter_append_basic(&sub, + DBUS_TYPE_BOOLEAN, ®istered); + + dbus_message_iter_close_container(&iter, &sub); + + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + } else if (strcmp(property, "ProtocolVersion") == 0) { + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter sub; + const int version = 0; + + reply = dbus_message_new_method_return(message); + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, + "i", &sub); + dbus_message_iter_append_basic(&sub, + DBUS_TYPE_INT32, &version); + + dbus_message_iter_close_container(&iter, &sub); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + } +} + +static void set_property(DBusConnection *connection, DBusMessage *message) { + // All properties are read only and we don't allow new properties + return; +} + +static void get_all(DBusConnection *connection, DBusMessage *message) { + DBusMessage *reply; + reply = dbus_message_new_method_return(message); + DBusMessageIter iter; /* a{v} */ + DBusMessageIter arr; + DBusMessageIter dict; + DBusMessageIter sub; + DBusMessageIter subsub; + int registered = (hosts == NULL || hosts->length == 0) ? 0 : 1; + const int version = 0; + const char *prop; + + // Could clean this up with a function for each prop + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + "{sv}", &arr); + + prop = "RegisteredStatusNotifierItems"; + dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY, + NULL, &dict); + dbus_message_iter_append_basic(&dict, + DBUS_TYPE_STRING, &prop); + dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, + "as", &sub); + dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY, + "s", &subsub); + for (int i = 0; i < items->length; ++i) { + dbus_message_iter_append_basic(&subsub, + DBUS_TYPE_STRING, &items->items[i]); + } + dbus_message_iter_close_container(&sub, &subsub); + dbus_message_iter_close_container(&dict, &sub); + dbus_message_iter_close_container(&arr, &dict); + + prop = "IsStatusNotifierHostRegistered"; + dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY, + NULL, &dict); + dbus_message_iter_append_basic(&dict, + DBUS_TYPE_STRING, &prop); + dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, + "b", &sub); + dbus_message_iter_append_basic(&sub, + DBUS_TYPE_BOOLEAN, ®istered); + dbus_message_iter_close_container(&dict, &sub); + dbus_message_iter_close_container(&arr, &dict); + + prop = "ProtocolVersion"; + dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY, + NULL, &dict); + dbus_message_iter_append_basic(&dict, + DBUS_TYPE_STRING, &prop); + dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, + "i", &sub); + dbus_message_iter_append_basic(&sub, + DBUS_TYPE_INT32, &version); + dbus_message_iter_close_container(&dict, &sub); + dbus_message_iter_close_container(&arr, &dict); + + dbus_message_iter_close_container(&iter, &arr); + + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); +} + +static DBusHandlerResult message_handler(DBusConnection *connection, + DBusMessage *message, void *data) { + const char *interface_name = dbus_message_get_interface(message); + const char *member_name = dbus_message_get_member(message); + + // In order of the xml above + if (strcmp(interface_name, "org.freedesktop.DBus.Introspectable") == 0 && + strcmp(member_name, "Introspect") == 0) { + // We don't have an introspect for KDE + respond_to_introspect(connection, message); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (strcmp(interface_name, "org.freedesktop.DBus.Properties") == 0) { + if (strcmp(member_name, "Get") == 0) { + get_property(connection, message); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (strcmp(member_name, "Set") == 0) { + set_property(connection, message); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (strcmp(member_name, "GetAll") == 0) { + get_all(connection, message); + return DBUS_HANDLER_RESULT_HANDLED; + } else { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + } else if (strcmp(interface_name, "org.freedesktop.StatusNotifierWatcher") == 0 || + strcmp(interface_name, "org.kde.StatusNotifierWatcher") == 0) { + if (strcmp(member_name, "RegisterStatusNotifierItem") == 0) { + register_item(connection, message); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (strcmp(member_name, "RegisterStatusNotifierHost") == 0) { + register_host(connection, message); + return DBUS_HANDLER_RESULT_HANDLED; + } else { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + } + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult signal_handler(DBusConnection *connection, + DBusMessage *message, void *_data) { + if (dbus_message_is_signal(message, "org.freedesktop.DBus", "NameOwnerChanged")) { + // Only eat the message if it is name that we are watching + const char *name; + const char *old_owner; + const char *new_owner; + int index; + if (!dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &old_owner, + DBUS_TYPE_STRING, &new_owner, + DBUS_TYPE_INVALID)) { + sway_log(L_ERROR, "Error getting LostName args"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + if (strcmp(new_owner, "") != 0) { + // Name is not lost + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + if ((index = list_seq_find(items, (int (*)(const void *, const void *))strcmp, name)) != -1) { + sway_log(L_INFO, "Status Notifier Item lost %s", name); + free(items->items[index]); + list_del(items, index); + item_unregistered_signal(connection, name); + + return DBUS_HANDLER_RESULT_HANDLED; + } + if ((index = list_seq_find(hosts, (int (*)(const void *, const void *))strcmp, name)) != -1) { + sway_log(L_INFO, "Status Notifier Host lost %s", name); + free(hosts->items[index]); + list_del(hosts, index); + + return DBUS_HANDLER_RESULT_HANDLED; + } + } + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static const DBusObjectPathVTable vtable = { + .message_function = message_handler, + .unregister_function = NULL, +}; + +int init_sni_watcher() { + DBusError error; + dbus_error_init(&error); + if (!conn) { + sway_log(L_ERROR, "Connection is null, cannot initiate StatusNotifierWatcher"); + return -1; + } + + items = create_list(); + hosts = create_list(); + + int status = dbus_bus_request_name(conn, "org.freedesktop.StatusNotifierWatcher", + DBUS_NAME_FLAG_REPLACE_EXISTING, + &error); + if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + sway_log(L_DEBUG, "Got watcher name"); + } else if (status == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) { + sway_log(L_INFO, "Could not get watcher name, it may start later"); + } + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "dbus err getting watcher name: %s\n", error.message); + return -1; + } + + status = dbus_bus_request_name(conn, "org.kde.StatusNotifierWatcher", + DBUS_NAME_FLAG_REPLACE_EXISTING, + &error); + if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + sway_log(L_DEBUG, "Got kde watcher name"); + } else if (status == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) { + sway_log(L_INFO, "Could not get kde watcher name, it may start later"); + } + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "dbus err getting kde watcher name: %s\n", error.message); + return -1; + } + + dbus_connection_try_register_object_path(conn, + "/StatusNotifierWatcher", + &vtable, NULL, &error); + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "dbus_err: %s\n", error.message); + return -1; + } + + dbus_bus_add_match(conn, + "type='signal',\ + sender='org.freedesktop.DBus',\ + interface='org.freedesktop.DBus',\ + member='NameOwnerChanged'", + &error); + + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "DBus error getting match args: %s", error.message); + } + + dbus_connection_add_filter(conn, signal_handler, NULL, NULL); + return 0; +} diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c new file mode 100644 index 00000000..9a709fe4 --- /dev/null +++ b/swaybar/tray/tray.c @@ -0,0 +1,279 @@ +#define _XOPEN_SOURCE 500 +#include +#include +#include +#include +#include "swaybar/bar.h" +#include "swaybar/tray/tray.h" +#include "swaybar/tray/dbus.h" +#include "swaybar/tray/sni.h" +#include "swaybar/bar.h" +#include "list.h" +#include "log.h" + +struct tray *tray; + +static void register_host(char *name) { + DBusMessage *message; + + message = dbus_message_new_method_call( + "org.freedesktop.StatusNotifierWatcher", + "/StatusNotifierWatcher", + "org.freedesktop.StatusNotifierWatcher", + "RegisterStatusNotifierHost"); + if (!message) { + sway_log(L_ERROR, "Cannot allocate dbus method call"); + return; + } + + dbus_message_append_args(message, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID); + + dbus_connection_send(conn, message, NULL); + + dbus_message_unref(message); +} + +static void get_items_reply(DBusPendingCall *pending, void *_data) { + DBusMessage *reply = dbus_pending_call_steal_reply(pending); + + if (!reply) { + sway_log(L_ERROR, "Got no items reply from sni watcher"); + goto bail; + } + + int message_type = dbus_message_get_type(reply); + + if (message_type == DBUS_MESSAGE_TYPE_ERROR) { + char *msg; + + dbus_message_get_args(reply, NULL, + DBUS_TYPE_STRING, &msg, + DBUS_TYPE_INVALID); + + sway_log(L_ERROR, "Message is error: %s", msg); + goto bail; + } + + DBusMessageIter iter; + DBusMessageIter variant; + DBusMessageIter array; + + dbus_message_iter_init(reply, &iter); + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + sway_log(L_ERROR, "Replyed with wrong type, not v(as)"); + goto bail; + } + dbus_message_iter_recurse(&iter, &variant); + if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&variant) != DBUS_TYPE_STRING) { + sway_log(L_ERROR, "Replyed with wrong type, not v(as)"); + goto bail; + } + + // Clear list + list_foreach(tray->items, (void (*)(void *))sni_free); + list_free(tray->items); + tray->items = create_list(); + + // O(n) function, could be faster dynamically reading values + int len = dbus_message_iter_get_element_count(&variant); + + dbus_message_iter_recurse(&variant, &array); + for (int i = 0; i < len; i++) { + const char *name; + dbus_message_iter_get_basic(&array, &name); + + struct StatusNotifierItem *item = sni_create(name); + + sway_log(L_DEBUG, "Item registered with host: %s", name); + list_add(tray->items, item); + dirty = true; + } + +bail: + dbus_message_unref(reply); + return; +} +static void get_items() { + DBusPendingCall *pending; + DBusMessage *message = dbus_message_new_method_call( + "org.freedesktop.StatusNotifierWatcher", + "/StatusNotifierWatcher", + "org.freedesktop.DBus.Properties", + "Get"); + + const char *iface = "org.freedesktop.StatusNotifierWatcher"; + const char *prop = "RegisteredStatusNotifierItems"; + dbus_message_append_args(message, + DBUS_TYPE_STRING, &iface, + DBUS_TYPE_STRING, &prop, + DBUS_TYPE_INVALID); + + bool status = + dbus_connection_send_with_reply(conn, message, &pending, -1); + dbus_message_unref(message); + + if (!(pending || status)) { + sway_log(L_ERROR, "Could not get items"); + return; + } + + dbus_pending_call_set_notify(pending, get_items_reply, NULL, NULL); +} + +static DBusHandlerResult signal_handler(DBusConnection *connection, + DBusMessage *message, void *_data) { + if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher", + "StatusNotifierItemRegistered")) { + const char *name; + if (!dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) { + sway_log(L_ERROR, "Error getting StatusNotifierItemRegistered args"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + if (list_seq_find(tray->items, sni_str_cmp, name) == -1) { + struct StatusNotifierItem *item = sni_create(name); + + list_add(tray->items, item); + dirty = true; + } + + return DBUS_HANDLER_RESULT_HANDLED; + } else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher", + "StatusNotifierItemUnregistered")) { + const char *name; + if (!dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) { + sway_log(L_ERROR, "Error getting StatusNotifierItemUnregistered args"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + int index; + if ((index = list_seq_find(tray->items, sni_str_cmp, name)) != -1) { + sni_free(tray->items->items[index]); + list_del(tray->items, index); + dirty = true; + } else { + // If it's not in our list, then our list is incorrect. + // Fetch all items again + sway_log(L_INFO, "Host item list incorrect, refreshing"); + get_items(); + } + + return DBUS_HANDLER_RESULT_HANDLED; + } else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierItem", + "NewIcon") || dbus_message_is_signal(message, + "org.kde.StatusNotifierItem", "NewIcon")) { + const char *name; + int index; + struct StatusNotifierItem *item; + + name = dbus_message_get_sender(message); + if ((index = list_seq_find(tray->items, sni_uniq_cmp, name)) != -1) { + item = tray->items->items[index]; + get_icon(item); + } + + return DBUS_HANDLER_RESULT_HANDLED; + } + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +int init_tray() { + tray = (struct tray *)malloc(sizeof(tray)); + + tray->items = create_list(); + + DBusError error; + dbus_error_init(&error); + char *name = NULL; + if (!conn) { + sway_log(L_ERROR, "Connection is null, cannot init SNI host"); + goto err; + } + name = calloc(sizeof(char), 256); + + if (!name) { + sway_log(L_ERROR, "Cannot allocate name"); + goto err; + } + + pid_t pid = getpid(); + if (snprintf(name, 256, "org.freedesktop.StatusNotifierHost-%d", pid) + >= 256) { + sway_log(L_ERROR, "Cannot get host name because string is too short." + "This should not happen"); + goto err; + } + + // We want to be the sole owner of this name + if (dbus_bus_request_name(conn, name, DBUS_NAME_FLAG_DO_NOT_QUEUE, + &error) != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + sway_log(L_ERROR, "Cannot get host name and start the tray"); + goto err; + } + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "Dbus err getting host name: %s\n", error.message); + goto err; + } + sway_log(L_DEBUG, "Got host name"); + + register_host(name); + + get_items(); + + // Perhaps use addmatch helper functions like wlc does? + dbus_bus_add_match(conn, + "type='signal',\ + sender='org.freedesktop.StatusNotifierWatcher',\ + member='StatusNotifierItemRegistered'", + &error); + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "dbus_err: %s", error.message); + goto err; + } + dbus_bus_add_match(conn, + "type='signal',\ + sender='org.freedesktop.StatusNotifierWatcher',\ + member='StatusNotifierItemUnregistered'", + &error); + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "dbus_err: %s", error.message); + return -1; + } + + // SNI matches + dbus_bus_add_match(conn, + "type='signal',\ + interface='org.freedesktop.StatusNotifierItem',\ + member='NewIcon'", + &error); + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "dbus_err %s", error.message); + goto err; + } + dbus_bus_add_match(conn, + "type='signal',\ + interface='org.kde.StatusNotifierItem',\ + member='NewIcon'", + &error); + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "dbus_err %s", error.message); + goto err; + } + + dbus_connection_add_filter(conn, signal_handler, NULL, NULL); + + free(name); + return 0; + +err: + // TODO better handle errors + free(name); + return -1; +} -- cgit v1.2.3-70-g09d2 From 790887ce762fb51d18e966de22bd2ab5b6a593c7 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Wed, 7 Jun 2017 22:45:33 -0400 Subject: Fix cairo blending on tray icons --- swaybar/render.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'swaybar') diff --git a/swaybar/render.c b/swaybar/render.c index 0b2ac438..d02ecbbb 100644 --- a/swaybar/render.c +++ b/swaybar/render.c @@ -350,9 +350,12 @@ void render(struct output *output, struct config *config, struct status_line *li tray_width -= tray_padding; tray_width -= item_size; + cairo_operator_t op = cairo_get_operator(cairo); + cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); cairo_set_source_surface(cairo, render_item->icon, tray_width, tray_padding); cairo_rectangle(cairo, tray_width, tray_padding, item_size, item_size); cairo_fill(cairo); + cairo_set_operator(cairo, op); item->dirty = false; } -- cgit v1.2.3-70-g09d2 From 1451ee8fd13dd35227d11e393c80871c70ad90f0 Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Wed, 7 Jun 2017 21:32:48 -0700 Subject: Reorganize Tray Code Remove tray code from bar.c and render.c --- include/swaybar/bar.h | 3 + include/swaybar/tray/tray.h | 14 +++-- swaybar/bar.c | 60 +++----------------- swaybar/render.c | 67 +--------------------- swaybar/tray/tray.c | 131 +++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 152 insertions(+), 123 deletions(-) (limited to 'swaybar') diff --git a/include/swaybar/bar.h b/include/swaybar/bar.h index 010e1f84..9f5bf400 100644 --- a/include/swaybar/bar.h +++ b/include/swaybar/bar.h @@ -15,6 +15,9 @@ struct bar { int ipc_socketfd; int status_read_fd; pid_t status_command_pid; +#ifdef ENABLE_TRAY + pid_t xembed_pid; +#endif }; struct output { diff --git a/include/swaybar/tray/tray.h b/include/swaybar/tray/tray.h index 7d371008..b718e555 100644 --- a/include/swaybar/tray/tray.h +++ b/include/swaybar/tray/tray.h @@ -5,6 +5,7 @@ #include #include "swaybar/tray/dbus.h" #include "swaybar/tray/sni.h" +#include "swaybar/bar.h" #include "list.h" extern struct tray *tray; @@ -14,13 +15,18 @@ struct tray { }; /** - * Initializes the tray host with D-Bus + * Processes a mouse event on the bar */ -int init_tray(); +void tray_mouse_event(struct output *output, int x, int y, + uint32_t button, uint32_t state); + +uint32_t tray_render(struct output *output, struct config *config); + +void tray_upkeep(struct bar *bar); /** - * Returns an item if `x` and `y` collide with it and NULL otherwise + * Initializes the tray with D-Bus */ -struct StatusNotifierItem *collides_with_sni(int x, int y); +void init_tray(); #endif /* _SWAYBAR_TRAY_H */ diff --git a/swaybar/bar.c b/swaybar/bar.c index cdaf6a37..5d480b63 100644 --- a/swaybar/bar.c +++ b/swaybar/bar.c @@ -27,6 +27,9 @@ static void bar_init(struct bar *bar) { bar->config = init_config(); bar->status = init_status_line(); bar->outputs = create_list(); +#ifdef ENABLE_TRAY + bar->xembed_pid = 0; +#endif } static void spawn_status_cmd_proc(struct bar *bar) { @@ -57,24 +60,6 @@ static void spawn_status_cmd_proc(struct bar *bar) { } } -#ifdef ENABLE_TRAY -static void spawn_xembed_sni_proxy() { - pid_t pid = fork(); - if (pid == 0) { - int wstatus; - do { - pid = fork(); - if (pid == 0) { - execlp("xembedsniproxy", "xembedsniproxy", NULL); - _exit(EXIT_FAILURE); - } - waitpid(pid, &wstatus, 0); - } while (!WIFEXITED(wstatus)); - _exit(EXIT_FAILURE); - } -} -#endif - struct output *new_output(const char *name) { struct output *output = malloc(sizeof(struct output)); output->name = strdup(name); @@ -122,27 +107,7 @@ static void mouse_button_notify(struct window *window, int x, int y, } #ifdef ENABLE_TRAY - uint32_t tray_padding = swaybar.config->tray_padding; - int tray_width = window->width * window->scale; - - for (int i = 0; i < clicked_output->items->length; ++i) { - struct sni_icon_ref *item = - clicked_output->items->items[i]; - int icon_width = cairo_image_surface_get_width(item->icon); - - tray_width -= tray_padding; - if (x <= tray_width && x >= tray_width - icon_width) { - if (button == swaybar.config->activate_button) { - sni_activate(item->ref, x, y); - } else if (button == swaybar.config->context_button) { - sni_context_menu(item->ref, x, y); - } else if (button == swaybar.config->secondary_button) { - sni_secondary(item->ref, x, y); - } - break; - } - tray_width -= icon_width; - } + tray_mouse_event(clicked_output, x, y, button, state_w); #endif } @@ -235,20 +200,7 @@ void bar_setup(struct bar *bar, const char *socket_path, const char *bar_id) { spawn_status_cmd_proc(bar); #ifdef ENABLE_TRAY - // We should have at least one output to serve the tray to - if (!swaybar.config->tray_output || strcmp(swaybar.config->tray_output, "none") != 0) { - /* Connect to the D-Bus */ - dbus_init(); - - /* Start the SNI watcher */ - init_sni_watcher(); - - /* Start the SNI host */ - init_tray(); - - /* Start xembedsniproxy */ - spawn_xembed_sni_proxy(); - } + init_tray(bar); #endif } @@ -300,6 +252,8 @@ void bar_run(struct bar *bar) { event_loop_poll(); #ifdef ENABLE_TRAY + tray_upkeep(bar); + dispatch_dbus(); #endif } diff --git a/swaybar/render.c b/swaybar/render.c index d02ecbbb..6ec47e79 100644 --- a/swaybar/render.c +++ b/swaybar/render.c @@ -302,72 +302,9 @@ void render(struct output *output, struct config *config, struct status_line *li cairo_paint(cairo); #ifdef ENABLE_TRAY - // Tray icons - uint32_t tray_padding = config->tray_padding; - unsigned int tray_width = window->width * window->scale; - const int item_size = (window->height * window->scale) - (2 * tray_padding); - - if (item_size < 0) { - // Can't render items if the padding is too large - goto no_tray; - } - - if (config->tray_output && strcmp(config->tray_output, output->name) != 0) { - goto no_tray; - } - - for (int i = 0; i < tray->items->length; ++i) { - struct StatusNotifierItem *item = - tray->items->items[i]; - if (!item->image) { - continue; - } - - struct sni_icon_ref *render_item = NULL; - int j; - for (j = i; j < output->items->length; ++j) { - struct sni_icon_ref *ref = - output->items->items[j]; - if (ref->ref == item) { - render_item = ref; - break; - } else { - sni_icon_ref_free(ref); - list_del(output->items, j); - } - } - - if (!render_item) { - render_item = sni_icon_ref_create(item, item_size); - list_add(output->items, render_item); - } else if (item->dirty) { - // item needs re-render - sni_icon_ref_free(render_item); - output->items->items[j] = render_item = - sni_icon_ref_create(item, item_size); - } - - tray_width -= tray_padding; - tray_width -= item_size; - - cairo_operator_t op = cairo_get_operator(cairo); - cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); - cairo_set_source_surface(cairo, render_item->icon, tray_width, tray_padding); - cairo_rectangle(cairo, tray_width, tray_padding, item_size, item_size); - cairo_fill(cairo); - cairo_set_operator(cairo, op); - - item->dirty = false; - } - - - if (tray_width != window->width * window->scale) { - tray_width -= tray_padding; - } - -no_tray: + uint32_t tray_width = tray_render(output, config); #else - const int tray_width = window->width * window->scale; + const uint32_t tray_width = window->width * window->scale; #endif // Command output diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c index 9a709fe4..ca8b1341 100644 --- a/swaybar/tray/tray.c +++ b/swaybar/tray/tray.c @@ -2,12 +2,15 @@ #include #include #include +#include #include #include "swaybar/bar.h" #include "swaybar/tray/tray.h" #include "swaybar/tray/dbus.h" #include "swaybar/tray/sni.h" +#include "swaybar/tray/sni_watcher.h" #include "swaybar/bar.h" +#include "swaybar/config.h" #include "list.h" #include "log.h" @@ -184,7 +187,7 @@ static DBusHandlerResult signal_handler(DBusConnection *connection, return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } -int init_tray() { +static int init_host() { tray = (struct tray *)malloc(sizeof(tray)); tray->items = create_list(); @@ -277,3 +280,129 @@ err: free(name); return -1; } + +void tray_mouse_event(struct output *output, int x, int y, + uint32_t button, uint32_t state) { + + struct window *window = output->window; + uint32_t tray_padding = swaybar.config->tray_padding; + int tray_width = window->width * window->scale; + + for (int i = 0; i < output->items->length; ++i) { + struct sni_icon_ref *item = + output->items->items[i]; + int icon_width = cairo_image_surface_get_width(item->icon); + + tray_width -= tray_padding; + if (x <= tray_width && x >= tray_width - icon_width) { + if (button == swaybar.config->activate_button) { + sni_activate(item->ref, x, y); + } else if (button == swaybar.config->context_button) { + sni_context_menu(item->ref, x, y); + } else if (button == swaybar.config->secondary_button) { + sni_secondary(item->ref, x, y); + } + break; + } + tray_width -= icon_width; + } +} + +uint32_t tray_render(struct output *output, struct config *config) { + struct window *window = output->window; + cairo_t *cairo = window->cairo; + + // Tray icons + uint32_t tray_padding = config->tray_padding; + uint32_t tray_width = window->width * window->scale; + const int item_size = (window->height * window->scale) - (2 * tray_padding); + + if (item_size < 0) { + // Can't render items if the padding is too large + return tray_width; + } + + if (config->tray_output && strcmp(config->tray_output, output->name) != 0) { + return tray_width; + } + + for (int i = 0; i < tray->items->length; ++i) { + struct StatusNotifierItem *item = + tray->items->items[i]; + if (!item->image) { + continue; + } + + struct sni_icon_ref *render_item = NULL; + int j; + for (j = i; j < output->items->length; ++j) { + struct sni_icon_ref *ref = + output->items->items[j]; + if (ref->ref == item) { + render_item = ref; + break; + } else { + sni_icon_ref_free(ref); + list_del(output->items, j); + } + } + + if (!render_item) { + render_item = sni_icon_ref_create(item, item_size); + list_add(output->items, render_item); + } else if (item->dirty) { + // item needs re-render + sni_icon_ref_free(render_item); + output->items->items[j] = render_item = + sni_icon_ref_create(item, item_size); + } + + tray_width -= tray_padding; + tray_width -= item_size; + + cairo_operator_t op = cairo_get_operator(cairo); + cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); + cairo_set_source_surface(cairo, render_item->icon, tray_width, tray_padding); + cairo_rectangle(cairo, tray_width, tray_padding, item_size, item_size); + cairo_fill(cairo); + cairo_set_operator(cairo, op); + + item->dirty = false; + } + + + if (tray_width != window->width * window->scale) { + tray_width -= tray_padding; + } + + return tray_width; +} + +void tray_upkeep(struct bar *bar) { + if (!bar->xembed_pid || + (bar->xembed_pid == waitpid(bar->xembed_pid, NULL, WNOHANG))) { + pid_t pid = fork(); + if (pid == 0) { + execlp("xembedsniproxy", "xembedsniproxy", NULL); + _exit(EXIT_FAILURE); + } else { + bar->xembed_pid = pid; + } + } +} + +void init_tray(struct bar *bar) { + if (!bar->config->tray_output || strcmp(bar->config->tray_output, "none") != 0) { + /* Connect to the D-Bus */ + dbus_init(); + + /* Start the SNI watcher */ + init_sni_watcher(); + + /* Start the SNI host */ + init_host(); + + /* Start xembedsniproxy */ + tray_upkeep(bar); + } +} -- cgit v1.2.3-70-g09d2 From 0a71aa6e97a96ffbd34fe18ec42b27d8fe5952e8 Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Thu, 8 Jun 2017 05:36:17 -0700 Subject: Fix Catching NewIcon Signal The unique name was not copied out of the wire marshalled DBus message data so `sni_uniq_cmp` would always match against junk data. --- swaybar/tray/sni.c | 16 ++++++++++++---- swaybar/tray/tray.c | 1 + 2 files changed, 13 insertions(+), 4 deletions(-) (limited to 'swaybar') diff --git a/swaybar/tray/sni.c b/swaybar/tray/sni.c index f0638dca..0c46d5c0 100644 --- a/swaybar/tray/sni.c +++ b/swaybar/tray/sni.c @@ -397,11 +397,16 @@ static void get_unique_name(struct StatusNotifierItem *item) { return; } + char *unique_name; if (!dbus_message_get_args(reply, NULL, - DBUS_TYPE_STRING, &item->unique_name, + DBUS_TYPE_STRING, &unique_name, DBUS_TYPE_INVALID)) { - item->unique_name = NULL; sway_log(L_ERROR, "Error parsing method args"); + } else { + if (item->unique_name) { + free(item->unique_name); + } + item->unique_name = strdup(unique_name); } dbus_message_unref(reply); @@ -434,14 +439,14 @@ struct StatusNotifierItem *sni_create(const char *name) { return item; } -/* Return true if `item` has a name of `str` */ +/* Return 0 if `item` has a name of `str` */ int sni_str_cmp(const void *_item, const void *_str) { const struct StatusNotifierItem *item = _item; const char *str = _str; return strcmp(item->name, str); } -/* Returns true if `item` has a unique name of `str` */ +/* Returns 0 if `item` has a unique name of `str` */ int sni_uniq_cmp(const void *_item, const void *_str) { const struct StatusNotifierItem *item = _item; const char *str = _str; @@ -456,6 +461,9 @@ void sni_free(struct StatusNotifierItem *item) { return; } free(item->name); + if (item->unique_name) { + free(item->unique_name); + } if (item->image) { cairo_surface_destroy(item->image); } diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c index ca8b1341..b2fa647e 100644 --- a/swaybar/tray/tray.c +++ b/swaybar/tray/tray.c @@ -179,6 +179,7 @@ static DBusHandlerResult signal_handler(DBusConnection *connection, name = dbus_message_get_sender(message); if ((index = list_seq_find(tray->items, sni_uniq_cmp, name)) != -1) { item = tray->items->items[index]; + sway_log(L_INFO, "NewIcon signal from item %s", item->name); get_icon(item); } -- cgit v1.2.3-70-g09d2 From 8f4bc0a3741b63c64bb6059bb632e7584c595d14 Mon Sep 17 00:00:00 2001 From: Yury Krivopalov Date: Mon, 12 Jun 2017 23:01:19 +0300 Subject: swaybar: Group child processes for signalling Fixes child proccess killing, when status_command is a complex command like "i3status | wrapper.sh". Set the process group id of the child process by calling `setpgid` after forking and before calling `exec`. The process group ID will be set to the process ID of the forked process. Processes spawned by this child process will also have this group ID. Send signals to the process group with `killpg`. This will send the signal to all of the process group. --- swaybar/bar.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'swaybar') diff --git a/swaybar/bar.c b/swaybar/bar.c index abde1cc9..5ed0d266 100644 --- a/swaybar/bar.c +++ b/swaybar/bar.c @@ -34,6 +34,7 @@ static void spawn_status_cmd_proc(struct bar *bar) { close(pipefd[0]); dup2(pipefd[1], STDOUT_FILENO); close(pipefd[1]); + setpgid(bar->status_command_pid, 0); char *const cmd[] = { "sh", "-c", @@ -274,7 +275,7 @@ static void free_outputs(list_t *outputs) { static void terminate_status_command(pid_t pid) { if (pid) { // terminate status_command process - int ret = kill(pid, SIGTERM); + int ret = killpg(pid, SIGTERM); if (ret != 0) { sway_log(L_ERROR, "Unable to terminate status_command [pid: %d]", pid); } else { -- cgit v1.2.3-70-g09d2 From 33fdae2001f489c40667797ce3bc50eedb352ee0 Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Tue, 13 Jun 2017 12:42:11 -0700 Subject: Remove Xembed Support Xembed support is premature in sway and should be postponed. This commit only removes swaybar starting xembedsniproxy, if users would like, they can still start xembedsniproxy manually, however there will be no official support. --- include/swaybar/bar.h | 3 --- swaybar/bar.c | 5 ----- swaybar/tray/tray.c | 16 ---------------- 3 files changed, 24 deletions(-) (limited to 'swaybar') diff --git a/include/swaybar/bar.h b/include/swaybar/bar.h index 9f5bf400..010e1f84 100644 --- a/include/swaybar/bar.h +++ b/include/swaybar/bar.h @@ -15,9 +15,6 @@ struct bar { int ipc_socketfd; int status_read_fd; pid_t status_command_pid; -#ifdef ENABLE_TRAY - pid_t xembed_pid; -#endif }; struct output { diff --git a/swaybar/bar.c b/swaybar/bar.c index 5d480b63..5e87eac9 100644 --- a/swaybar/bar.c +++ b/swaybar/bar.c @@ -27,9 +27,6 @@ static void bar_init(struct bar *bar) { bar->config = init_config(); bar->status = init_status_line(); bar->outputs = create_list(); -#ifdef ENABLE_TRAY - bar->xembed_pid = 0; -#endif } static void spawn_status_cmd_proc(struct bar *bar) { @@ -252,8 +249,6 @@ void bar_run(struct bar *bar) { event_loop_poll(); #ifdef ENABLE_TRAY - tray_upkeep(bar); - dispatch_dbus(); #endif } diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c index b2fa647e..00f1a44f 100644 --- a/swaybar/tray/tray.c +++ b/swaybar/tray/tray.c @@ -379,19 +379,6 @@ uint32_t tray_render(struct output *output, struct config *config) { return tray_width; } -void tray_upkeep(struct bar *bar) { - if (!bar->xembed_pid || - (bar->xembed_pid == waitpid(bar->xembed_pid, NULL, WNOHANG))) { - pid_t pid = fork(); - if (pid == 0) { - execlp("xembedsniproxy", "xembedsniproxy", NULL); - _exit(EXIT_FAILURE); - } else { - bar->xembed_pid = pid; - } - } -} - void init_tray(struct bar *bar) { if (!bar->config->tray_output || strcmp(bar->config->tray_output, "none") != 0) { /* Connect to the D-Bus */ @@ -402,8 +389,5 @@ void init_tray(struct bar *bar) { /* Start the SNI host */ init_host(); - - /* Start xembedsniproxy */ - tray_upkeep(bar); } } -- cgit v1.2.3-70-g09d2