diff options
Diffstat (limited to 'swaybar')
-rw-r--r-- | swaybar/CMakeLists.txt | 11 | ||||
-rw-r--r-- | swaybar/bar.c | 87 | ||||
-rw-r--r-- | swaybar/config.c | 12 | ||||
-rw-r--r-- | swaybar/event_loop.c | 143 | ||||
-rw-r--r-- | swaybar/ipc.c | 40 | ||||
-rw-r--r-- | swaybar/render.c | 15 | ||||
-rw-r--r-- | swaybar/tray/dbus.c | 189 | ||||
-rw-r--r-- | swaybar/tray/icon.c | 404 | ||||
-rw-r--r-- | swaybar/tray/sni.c | 471 | ||||
-rw-r--r-- | swaybar/tray/sni_watcher.c | 487 | ||||
-rw-r--r-- | swaybar/tray/tray.c | 393 |
11 files changed, 2212 insertions, 40 deletions
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..3412ff29 100644 --- a/swaybar/bar.c +++ b/swaybar/bar.c @@ -7,10 +7,17 @@ #include <sys/wait.h> #include <signal.h> #include <poll.h> +#ifdef ENABLE_TRAY +#include <dbus/dbus.h> +#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" @@ -34,6 +41,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", @@ -56,12 +64,15 @@ struct output *new_output(const char *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 +103,10 @@ static void mouse_button_notify(struct window *window, int x, int y, break; } } + +#ifdef ENABLE_TRAY + tray_mouse_event(clicked_output, x, y, button, state_w); +#endif } static void mouse_scroll_notify(struct window *window, enum scroll_direction direction) { @@ -136,6 +151,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 +196,41 @@ 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 + init_tray(bar); +#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; + +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); + } +} - pfd[0].fd = bar->ipc_event_socketfd; - pfd[0].events = POLLIN; - pfd[1].fd = bar->status_read_fd; - pfd[1].events = POLLIN; +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 +248,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 } } @@ -274,7 +291,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 { 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 <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <strings.h> +#include <poll.h> +#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..6ec47e79 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,12 @@ void render(struct output *output, struct config *config, struct status_line *li } cairo_paint(cairo); +#ifdef ENABLE_TRAY + uint32_t tray_width = tray_render(output, config); +#else + const uint32_t tray_width = window->width * window->scale; +#endif + // Command output if (is_focused) { cairo_set_source_u32(cairo, config->colors.focused_statusline); @@ -309,12 +319,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 <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <poll.h> +#include <signal.h> +#include <time.h> +#include <dbus/dbus.h> +#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 <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <dirent.h> +#include <sys/stat.h> +#include <stdbool.h> +#include <stdint.h> +#include <limits.h> +#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..0c46d5c0 --- /dev/null +++ b/swaybar/tray/sni.c @@ -0,0 +1,471 @@ +#define _XOPEN_SOURCE 500 +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <stdbool.h> +#include <dbus/dbus.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#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; + } + + char *unique_name; + if (!dbus_message_get_args(reply, NULL, + DBUS_TYPE_STRING, &unique_name, + DBUS_TYPE_INVALID)) { + 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); +} + +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 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 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; + + 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->unique_name) { + free(item->unique_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 <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <dbus/dbus.h> +#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 = + "<!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'" + "'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>" + "<node>" + " <interface name='org.freedesktop.DBus.Introspectable'>" + " <method name='Introspect'>" + " <arg name='xml_data' direction='out' type='s'/>" + " </method>" + " </interface>" + " <interface name='org.freedesktop.DBus.Properties'>" + " <method name='Get'>" + " <arg name='interface' direction='in' type='s'/>" + " <arg name='propname' direction='in' type='s'/>" + " <arg name='value' direction='out' type='v'/>" + " </method>" + " <method name='Set'>" + " <arg name='interface' direction='in' type='s'/>" + " <arg name='propname' direction='in' type='s'/>" + " <arg name='value' direction='in' type='v'/>" + " </method>" + " <method name='GetAll'>" + " <arg name='interface' direction='in' type='s'/>" + " <arg name='props' direction='out' type='a{sv}'/>" + " </method>" + " </interface>" + " <interface name='org.freedesktop.StatusNotifierWatcher'>" + " <method name='RegisterStatusNotifierItem'>" + " <arg type='s' name='service' direction='in'/>" + " </method>" + " <method name='RegisterStatusNotifierHost'>" + " <arg type='s' name='service' direction='in'/>" + " </method>" + " <property name='RegisteredStatusNotifierItems' type='as' access='read'/>" + " <property name='IsStatusNotifierHostRegistered' type='b' access='read'/>" + " <property name='ProtocolVersion' type='i' access='read'/>" + " <signal name='StatusNotifierItemRegistered'>" + " <arg type='s' name='service' direction='out'/>" + " </signal>" + " <signal name='StatusNotifierItemUnregistered'>" + " <arg type='s' name='service' direction='out'/>" + " </signal>" + " <signal name='StatusNotifierHostRegistered'>" + " <arg type='' name='service' direction='out'/>" + " </signal>" + " </interface>" + "</node>"; + +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..00f1a44f --- /dev/null +++ b/swaybar/tray/tray.c @@ -0,0 +1,393 @@ +#define _XOPEN_SOURCE 500 +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/wait.h> +#include <dbus/dbus.h> +#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" + +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]; + sway_log(L_INFO, "NewIcon signal from item %s", item->name); + get_icon(item); + } + + return DBUS_HANDLER_RESULT_HANDLED; + } + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static int init_host() { + 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; +} + +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 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(); + } +} |