diff options
Diffstat (limited to 'swaybar')
-rw-r--r-- | swaybar/bar.c | 83 | ||||
-rw-r--r-- | swaybar/config.c | 23 | ||||
-rw-r--r-- | swaybar/i3bar.c | 36 | ||||
-rw-r--r-- | swaybar/input.c | 84 | ||||
-rw-r--r-- | swaybar/ipc.c | 115 | ||||
-rw-r--r-- | swaybar/main.c | 4 | ||||
-rw-r--r-- | swaybar/meson.build | 44 | ||||
-rw-r--r-- | swaybar/render.c | 190 | ||||
-rw-r--r-- | swaybar/status_line.c | 2 | ||||
-rw-r--r-- | swaybar/tray/host.c | 210 | ||||
-rw-r--r-- | swaybar/tray/icon.c | 462 | ||||
-rw-r--r-- | swaybar/tray/item.c | 472 | ||||
-rw-r--r-- | swaybar/tray/tray.c | 131 | ||||
-rw-r--r-- | swaybar/tray/watcher.c | 212 |
14 files changed, 1885 insertions, 183 deletions
diff --git a/swaybar/bar.c b/swaybar/bar.c index 08c386a7..d36367fc 100644 --- a/swaybar/bar.c +++ b/swaybar/bar.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L #include <assert.h> #include <errno.h> #include <fcntl.h> @@ -11,6 +11,7 @@ #include <wayland-client.h> #include <wayland-cursor.h> #include <wlr/util/log.h> +#include "config.h" #include "swaybar/bar.h" #include "swaybar/config.h" #include "swaybar/i3bar.h" @@ -18,6 +19,9 @@ #include "swaybar/ipc.h" #include "swaybar/status_line.h" #include "swaybar/render.h" +#if HAVE_TRAY +#include "swaybar/tray/tray.h" +#endif #include "ipc-client.h" #include "list.h" #include "log.h" @@ -31,6 +35,7 @@ void free_workspaces(struct wl_list *list) { wl_list_for_each_safe(ws, tmp, list, link) { wl_list_remove(&ws->link); free(ws->name); + free(ws->label); free(ws); } } @@ -54,6 +59,7 @@ static void swaybar_output_free(struct swaybar_output *output) { free_workspaces(&output->workspaces); wl_list_remove(&output->link); free(output->name); + free(output->identifier); free(output); } @@ -119,7 +125,7 @@ static void destroy_layer_surface(struct swaybar_output *output) { output->frame_scheduled = false; } -static void set_bar_dirty(struct swaybar *bar) { +void set_bar_dirty(struct swaybar *bar) { struct swaybar_output *output; wl_list_for_each(output, &bar->outputs, link) { set_output_dirty(output); @@ -161,13 +167,15 @@ bool determine_bar_visibility(struct swaybar *bar, bool moving_layer) { return visible; } -static bool bar_uses_output(struct swaybar *bar, const char *name) { - if (bar->config->all_outputs) { +static bool bar_uses_output(struct swaybar_output *output) { + if (output->bar->config->all_outputs) { return true; } + char *identifier = output->identifier; struct config_output *coutput; - wl_list_for_each(coutput, &bar->config->outputs, link) { - if (strcmp(coutput->name, name) == 0) { + wl_list_for_each(coutput, &output->bar->config->outputs, link) { + if (strcmp(coutput->name, output->name) == 0 || + (identifier && strcmp(coutput->name, identifier) == 0)) { return true; } } @@ -195,6 +203,10 @@ static void output_scale(void *data, struct wl_output *wl_output, int32_t factor) { struct swaybar_output *output = data; output->scale = factor; + if (output == output->bar->pointer.current) { + update_cursor(output->bar); + render_frame(output); + } } struct wl_output_listener output_listener = { @@ -206,12 +218,16 @@ struct wl_output_listener output_listener = { static void xdg_output_handle_logical_position(void *data, struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) { - // Who cares + struct swaybar_output *output = data; + output->output_x = x; + output->output_y = y; } static void xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) { - // Who cares + struct swaybar_output *output = data; + output->output_height = height; + output->output_width = width; } static void xdg_output_handle_done(void *data, @@ -220,7 +236,7 @@ static void xdg_output_handle_done(void *data, struct swaybar *bar = output->bar; assert(output->name != NULL); - if (!bar_uses_output(bar, output->name)) { + if (!bar_uses_output(output)) { swaybar_output_free(output); return; } @@ -245,7 +261,22 @@ static void xdg_output_handle_name(void *data, static void xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, const char *description) { - // Who cares + // wlroots currently sets the description to `make model serial (name)` + // If this changes in the future, this will need to be modified. + struct swaybar_output *output = data; + free(output->identifier); + output->identifier = NULL; + char *paren = strrchr(description, '('); + if (paren) { + size_t length = paren - description; + output->identifier = malloc(length); + if (!output->identifier) { + wlr_log(WLR_ERROR, "Failed to allocate output identifier"); + return; + } + strncpy(output->identifier, description, length); + output->identifier[length - 1] = '\0'; + } } struct zxdg_output_v1_listener xdg_output_listener = { @@ -272,7 +303,7 @@ static void handle_global(void *data, struct wl_registry *registry, struct swaybar *bar = data; if (strcmp(interface, wl_compositor_interface.name) == 0) { bar->compositor = wl_registry_bind(registry, name, - &wl_compositor_interface, 3); + &wl_compositor_interface, 4); } else if (strcmp(interface, wl_seat_interface.name) == 0) { bar->seat = wl_registry_bind(registry, name, &wl_seat_interface, 3); @@ -354,25 +385,15 @@ bool bar_setup(struct swaybar *bar, const char *socket_path) { wl_display_roundtrip(bar->display); struct swaybar_pointer *pointer = &bar->pointer; - - int max_scale = 1; - struct swaybar_output *output; - wl_list_for_each(output, &bar->outputs, link) { - if (output->scale > max_scale) { - max_scale = output->scale; - } - } - - pointer->cursor_theme = - wl_cursor_theme_load(NULL, 24 * max_scale, bar->shm); - assert(pointer->cursor_theme); - struct wl_cursor *cursor; - cursor = wl_cursor_theme_get_cursor(pointer->cursor_theme, "left_ptr"); - assert(cursor); - pointer->cursor_image = cursor->images[0]; pointer->cursor_surface = wl_compositor_create_surface(bar->compositor); assert(pointer->cursor_surface); +#if HAVE_TRAY + if (!bar->config->tray_hidden) { + bar->tray = create_tray(bar); + } +#endif + if (bar->config->workspace_buttons) { ipc_get_workspaces(bar); } @@ -414,6 +435,11 @@ void bar_run(struct swaybar *bar) { loop_add_fd(bar->eventloop, bar->status->read_fd, POLLIN, status_in, bar); } +#if HAVE_TRAY + if (bar->tray) { + loop_add_fd(bar->eventloop, bar->tray->fd, POLLIN, tray_in, bar->tray->bus); + } +#endif while (1) { errno = 0; if (wl_display_flush(bar->display) == -1 && errno != EAGAIN) { @@ -431,6 +457,9 @@ static void free_outputs(struct wl_list *list) { } void bar_teardown(struct swaybar *bar) { +#if HAVE_TRAY + destroy_tray(bar->tray); +#endif free_outputs(&bar->outputs); if (bar->config) { free_config(bar->config); diff --git a/swaybar/config.c b/swaybar/config.c index 0fd1f02e..d4cc9b1a 100644 --- a/swaybar/config.c +++ b/swaybar/config.c @@ -1,9 +1,10 @@ -#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L #include <stdlib.h> #include <string.h> #include <wlr/util/log.h> #include "swaybar/config.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h" +#include "config.h" #include "stringop.h" #include "list.h" @@ -30,15 +31,24 @@ struct swaybar_config *init_config(void) { config->hidden_state = strdup("hide"); config->sep_symbol = NULL; config->strip_workspace_numbers = false; + config->strip_workspace_name = false; config->binding_mode_indicator = true; config->wrap_scroll = false; config->workspace_buttons = true; config->bindings = create_list(); wl_list_init(&config->outputs); + config->status_padding = 1; + config->status_edge_padding = 3; /* height */ config->height = 0; + /* gaps */ + config->gaps.top = 0; + config->gaps.right = 0; + config->gaps.bottom = 0; + config->gaps.left = 0; + /* colors */ config->colors.background = 0x000000FF; config->colors.focused_background = 0x000000FF; @@ -66,6 +76,10 @@ struct swaybar_config *init_config(void) { config->colors.binding_mode.background = 0x900000FF; config->colors.binding_mode.text = 0xFFFFFFFF; +#if HAVE_TRAY + config->tray_padding = 2; +#endif + return config; } @@ -95,5 +109,12 @@ void free_config(struct swaybar_config *config) { free(coutput->name); free(coutput); } +#if HAVE_TRAY + list_free_items_and_destroy(config->tray_outputs); + for (int i = 0; i < 10; ++i) { + free(config->tray_bindings[i]); + } + free(config->icon_theme); +#endif free(config); } diff --git a/swaybar/i3bar.c b/swaybar/i3bar.c index 3ea74e13..116c8f6e 100644 --- a/swaybar/i3bar.c +++ b/swaybar/i3bar.c @@ -259,8 +259,34 @@ bool i3bar_handle_readable(struct status_line *status) { } } +static uint32_t event_to_x11_button(uint32_t event) { + switch (event) { + case BTN_LEFT: + return 1; + case BTN_MIDDLE: + return 2; + case BTN_RIGHT: + return 3; + case SWAY_SCROLL_UP: + return 4; + case SWAY_SCROLL_DOWN: + return 5; + case SWAY_SCROLL_LEFT: + return 6; + case SWAY_SCROLL_RIGHT: + return 7; + case BTN_SIDE: + return 8; + case BTN_EXTRA: + return 9; + default: + return 0; + } +} + enum hotspot_event_handling i3bar_block_send_click(struct status_line *status, - struct i3bar_block *block, int x, int y, enum x11_button button) { + struct i3bar_block *block, int x, int y, int rx, int ry, int w, int h, + uint32_t button) { wlr_log(WLR_DEBUG, "block %s clicked", block->name); if (!block->name || !status->click_events) { return HOTSPOT_PROCESS; @@ -274,9 +300,15 @@ enum hotspot_event_handling i3bar_block_send_click(struct status_line *status, json_object_new_string(block->instance)); } - json_object_object_add(event_json, "button", json_object_new_int(button)); + json_object_object_add(event_json, "button", + json_object_new_int(event_to_x11_button(button))); + json_object_object_add(event_json, "event", json_object_new_int(button)); json_object_object_add(event_json, "x", json_object_new_int(x)); json_object_object_add(event_json, "y", json_object_new_int(y)); + json_object_object_add(event_json, "relative_x", json_object_new_int(rx)); + json_object_object_add(event_json, "relative_y", json_object_new_int(ry)); + json_object_object_add(event_json, "width", json_object_new_int(w)); + json_object_object_add(event_json, "height", json_object_new_int(h)); if (dprintf(status->write_fd, "%s%s\n", status->clicked ? "," : "", json_object_to_json_string(event_json)) < 0) { status_error(status, "[failed to write click event]"); diff --git a/swaybar/input.c b/swaybar/input.c index 263d0253..bdd55e58 100644 --- a/swaybar/input.c +++ b/swaybar/input.c @@ -1,9 +1,5 @@ #include <assert.h> -#ifdef __FreeBSD__ -#include <dev/evdev/input-event-codes.h> -#else #include <linux/input-event-codes.h> -#endif #include <stdlib.h> #include <wayland-client.h> #include <wayland-cursor.h> @@ -26,33 +22,39 @@ void free_hotspots(struct wl_list *list) { } } -static enum x11_button wl_button_to_x11_button(uint32_t button) { - switch (button) { - case BTN_LEFT: - return LEFT; - case BTN_MIDDLE: - return MIDDLE; - case BTN_RIGHT: - return RIGHT; - case BTN_SIDE: - return BACK; - case BTN_EXTRA: - return FORWARD; - default: - return NONE; - } -} - -static enum x11_button wl_axis_to_x11_button(uint32_t axis, wl_fixed_t value) { +static uint32_t wl_axis_to_button(uint32_t axis, wl_fixed_t value) { + bool negative = wl_fixed_to_double(value) < 0; switch (axis) { case WL_POINTER_AXIS_VERTICAL_SCROLL: - return wl_fixed_to_double(value) < 0 ? SCROLL_UP : SCROLL_DOWN; + return negative ? SWAY_SCROLL_UP : SWAY_SCROLL_DOWN; case WL_POINTER_AXIS_HORIZONTAL_SCROLL: - return wl_fixed_to_double(value) < 0 ? SCROLL_LEFT : SCROLL_RIGHT; + return negative ? SWAY_SCROLL_LEFT : SWAY_SCROLL_RIGHT; default: wlr_log(WLR_DEBUG, "Unexpected axis value on mouse scroll"); - return NONE; + return 0; + } +} + +void update_cursor(struct swaybar *bar) { + struct swaybar_pointer *pointer = &bar->pointer; + if (pointer->cursor_theme) { + wl_cursor_theme_destroy(pointer->cursor_theme); } + int scale = pointer->current ? pointer->current->scale : 1; + pointer->cursor_theme = wl_cursor_theme_load(NULL, 24 * scale, bar->shm); + struct wl_cursor *cursor; + cursor = wl_cursor_theme_get_cursor(pointer->cursor_theme, "left_ptr"); + pointer->cursor_image = cursor->images[0]; + wl_surface_set_buffer_scale(pointer->cursor_surface, scale); + wl_surface_attach(pointer->cursor_surface, + wl_cursor_image_get_buffer(pointer->cursor_image), 0, 0); + wl_pointer_set_cursor(pointer->pointer, pointer->serial, + pointer->cursor_surface, + pointer->cursor_image->hotspot_x / scale, + pointer->cursor_image->hotspot_y / scale); + wl_surface_damage_buffer(pointer->cursor_surface, 0, 0, + INT32_MAX, INT32_MAX); + wl_surface_commit(pointer->cursor_surface); } static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, @@ -60,6 +62,7 @@ static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, wl_fixed_t surface_x, wl_fixed_t surface_y) { struct swaybar *bar = data; struct swaybar_pointer *pointer = &bar->pointer; + pointer->serial = serial; struct swaybar_output *output; wl_list_for_each(output, &bar->outputs, link) { if (output->surface == surface) { @@ -67,20 +70,7 @@ static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, break; } } - int max_scale = 1; - struct swaybar_output *_output; - wl_list_for_each(_output, &bar->outputs, link) { - if (_output->scale > max_scale) { - max_scale = _output->scale; - } - } - wl_surface_set_buffer_scale(pointer->cursor_surface, max_scale); - wl_surface_attach(pointer->cursor_surface, - wl_cursor_image_get_buffer(pointer->cursor_image), 0, 0); - wl_pointer_set_cursor(wl_pointer, serial, pointer->cursor_surface, - pointer->cursor_image->hotspot_x / max_scale, - pointer->cursor_image->hotspot_y / max_scale); - wl_surface_commit(pointer->cursor_surface); + update_cursor(bar); } static void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, @@ -96,12 +86,12 @@ static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, bar->pointer.y = wl_fixed_to_int(surface_y); } -static bool check_bindings(struct swaybar *bar, uint32_t x11_button, +static bool check_bindings(struct swaybar *bar, uint32_t button, uint32_t state) { bool released = state == WL_POINTER_BUTTON_STATE_RELEASED; for (int i = 0; i < bar->config->bindings->length; i++) { struct swaybar_binding *binding = bar->config->bindings->items[i]; - if (binding->button == x11_button && binding->release == released) { + if (binding->button == button && binding->release == released) { ipc_execute_binding(bar, binding); return true; } @@ -118,7 +108,7 @@ static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer, return; } - if (check_bindings(bar, wl_button_to_x11_button(button), state)) { + if (check_bindings(bar, button, state)) { return; } @@ -133,8 +123,8 @@ static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer, && y >= hotspot->y && x < hotspot->x + hotspot->width && y < hotspot->y + hotspot->height) { - if (HOTSPOT_IGNORE == hotspot->callback(output, pointer->x, pointer->y, - wl_button_to_x11_button(button), hotspot->data)) { + if (HOTSPOT_IGNORE == hotspot->callback(output, hotspot, + pointer->x, pointer->y, button, hotspot->data)) { return; } } @@ -152,7 +142,7 @@ static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer, // If there is a button press binding, execute it, skip default behavior, // and check button release bindings - enum x11_button button = wl_axis_to_x11_button(axis, value); + uint32_t button = wl_axis_to_button(axis, value); if (check_bindings(bar, button, WL_POINTER_BUTTON_STATE_PRESSED)) { check_bindings(bar, button, WL_POINTER_BUTTON_STATE_RELEASED); return; @@ -166,8 +156,8 @@ static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer, && y >= hotspot->y && x < hotspot->x + hotspot->width && y < hotspot->y + hotspot->height) { - if (HOTSPOT_IGNORE == hotspot->callback( - output, pointer->x, pointer->y, button, hotspot->data)) { + if (HOTSPOT_IGNORE == hotspot->callback(output, hotspot, + pointer->x, pointer->y, button, hotspot->data)) { return; } } diff --git a/swaybar/ipc.c b/swaybar/ipc.c index 706f968d..097f9161 100644 --- a/swaybar/ipc.c +++ b/swaybar/ipc.c @@ -6,6 +6,7 @@ #include <wlr/util/log.h> #include "swaybar/config.h" #include "swaybar/ipc.h" +#include "config.h" #include "ipc-client.h" #include "list.h" @@ -153,18 +154,21 @@ static bool ipc_parse_config( return false; } json_object *markup, *mode, *hidden_state, *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 *bindings; + json_object *font, *gaps, *bar_height, *wrap_scroll, *workspace_buttons; + json_object *strip_workspace_numbers, *strip_workspace_name; + json_object *binding_mode_indicator, *verbose, *colors, *sep_symbol; + json_object *outputs, *bindings, *status_padding, *status_edge_padding; json_object_object_get_ex(bar_config, "mode", &mode); json_object_object_get_ex(bar_config, "hidden_state", &hidden_state); json_object_object_get_ex(bar_config, "position", &position); json_object_object_get_ex(bar_config, "status_command", &status_command); json_object_object_get_ex(bar_config, "font", &font); + json_object_object_get_ex(bar_config, "gaps", &gaps); json_object_object_get_ex(bar_config, "bar_height", &bar_height); json_object_object_get_ex(bar_config, "wrap_scroll", &wrap_scroll); json_object_object_get_ex(bar_config, "workspace_buttons", &workspace_buttons); json_object_object_get_ex(bar_config, "strip_workspace_numbers", &strip_workspace_numbers); + json_object_object_get_ex(bar_config, "strip_workspace_name", &strip_workspace_name); json_object_object_get_ex(bar_config, "binding_mode_indicator", &binding_mode_indicator); json_object_object_get_ex(bar_config, "verbose", &verbose); json_object_object_get_ex(bar_config, "separator_symbol", &sep_symbol); @@ -172,6 +176,9 @@ static bool ipc_parse_config( json_object_object_get_ex(bar_config, "outputs", &outputs); json_object_object_get_ex(bar_config, "pango_markup", &markup); json_object_object_get_ex(bar_config, "bindings", &bindings); + json_object_object_get_ex(bar_config, "status_padding", &status_padding); + json_object_object_get_ex(bar_config, "status_edge_padding", + &status_edge_padding); if (status_command) { free(config->status_command); config->status_command = strdup(json_object_get_string(status_command)); @@ -190,6 +197,9 @@ static bool ipc_parse_config( if (strip_workspace_numbers) { config->strip_workspace_numbers = json_object_get_boolean(strip_workspace_numbers); } + if (strip_workspace_name) { + config->strip_workspace_name = json_object_get_boolean(strip_workspace_name); + } if (binding_mode_indicator) { config->binding_mode_indicator = json_object_get_boolean(binding_mode_indicator); } @@ -202,6 +212,30 @@ static bool ipc_parse_config( if (bar_height) { config->height = json_object_get_int(bar_height); } + if (status_padding) { + config->status_padding = json_object_get_int(status_padding); + } + if (status_edge_padding) { + config->status_edge_padding = json_object_get_int(status_edge_padding); + } + if (gaps) { + json_object *top = json_object_object_get(gaps, "top"); + if (top) { + config->gaps.top = json_object_get_int(top); + } + json_object *right = json_object_object_get(gaps, "right"); + if (right) { + config->gaps.right = json_object_get_int(right); + } + json_object *bottom = json_object_object_get(gaps, "bottom"); + if (bottom) { + config->gaps.bottom = json_object_get_int(bottom); + } + json_object *left = json_object_object_get(gaps, "left"); + if (left) { + config->gaps.left = json_object_get_int(left); + } + } if (markup) { config->pango_markup = json_object_get_boolean(markup); } @@ -212,7 +246,7 @@ static bool ipc_parse_config( struct swaybar_binding *binding = calloc(1, sizeof(struct swaybar_binding)); binding->button = json_object_get_int( - json_object_object_get(bindobj, "input_code")); + json_object_object_get(bindobj, "event_code")); binding->command = strdup(json_object_get_string( json_object_object_get(bindobj, "command"))); binding->release = json_object_get_boolean( @@ -258,6 +292,40 @@ static bool ipc_parse_config( ipc_parse_colors(config, colors); } +#if HAVE_TRAY + json_object *tray_outputs, *tray_padding, *tray_bindings, *icon_theme; + + if ((json_object_object_get_ex(bar_config, "tray_outputs", &tray_outputs))) { + config->tray_outputs = create_list(); + int length = json_object_array_length(tray_outputs); + for (int i = 0; i < length; ++i) { + json_object *o = json_object_array_get_idx(tray_outputs, i); + list_add(config->tray_outputs, strdup(json_object_get_string(o))); + } + config->tray_hidden = strcmp(config->tray_outputs->items[0], "none") == 0; + } + + if ((json_object_object_get_ex(bar_config, "tray_padding", &tray_padding))) { + config->tray_padding = json_object_get_int(tray_padding); + } + + if ((json_object_object_get_ex(bar_config, "tray_bindings", &tray_bindings))) { + int length = json_object_array_length(tray_bindings); + for (int i = 0; i < length; ++i) { + json_object *bind = json_object_array_get_idx(tray_bindings, i); + json_object *button, *command; + json_object_object_get_ex(bind, "input_code", &button); + json_object_object_get_ex(bind, "command", &command); + config->tray_bindings[json_object_get_int(button)] = + strdup(json_object_get_string(command)); + } + } + + if ((json_object_object_get_ex(bar_config, "icon_theme", &icon_theme))) { + config->icon_theme = strdup(json_object_get_string(icon_theme)); + } +#endif + json_object_put(bar_config); return true; } @@ -298,6 +366,24 @@ bool ipc_get_workspaces(struct swaybar *bar) { calloc(1, sizeof(struct swaybar_workspace)); ws->num = json_object_get_int(num); ws->name = strdup(json_object_get_string(name)); + ws->label = strdup(ws->name); + // ws->num will be -1 if workspace name doesn't begin with int. + if (ws->num != -1) { + size_t len_offset = numlen(ws->num); + if (bar->config->strip_workspace_name) { + free(ws->label); + ws->label = malloc(len_offset + 1 * sizeof(char)); + ws->label[len_offset] = '\0'; + strncpy(ws->label, ws->name, len_offset); + } else if (bar->config->strip_workspace_numbers) { + len_offset += ws->label[len_offset] == ':'; + if (strlen(ws->name) > len_offset) { + free(ws->label); + // Strip number prefix [1-?:] using len_offset. + ws->label = strdup(ws->name + len_offset); + } + } + } ws->visible = json_object_get_boolean(visible); ws->focused = json_object_get_boolean(focused); if (ws->focused) { @@ -423,6 +509,27 @@ static bool handle_barconfig_update(struct swaybar *bar, config->mode = strdup(json_object_get_string(json_mode)); wlr_log(WLR_DEBUG, "Changing bar mode to %s", config->mode); + json_object *gaps; + json_object_object_get_ex(json_config, "gaps", &gaps); + if (gaps) { + json_object *top = json_object_object_get(gaps, "top"); + if (top) { + config->gaps.top = json_object_get_int(top); + } + json_object *right = json_object_object_get(gaps, "right"); + if (right) { + config->gaps.right = json_object_get_int(right); + } + json_object *bottom = json_object_object_get(gaps, "bottom"); + if (bottom) { + config->gaps.bottom = json_object_get_int(bottom); + } + json_object *left = json_object_object_get(gaps, "left"); + if (left) { + config->gaps.left = json_object_get_int(left); + } + } + return determine_bar_visibility(bar, true); } diff --git a/swaybar/main.c b/swaybar/main.c index 2672abef..fa99b1ba 100644 --- a/swaybar/main.c +++ b/swaybar/main.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -76,7 +76,7 @@ int main(int argc, char **argv) { if (debug) { wlr_log_init(WLR_DEBUG, NULL); } else { - wlr_log_init(WLR_ERROR, NULL); + wlr_log_init(WLR_INFO, NULL); } if (!swaybar.id) { diff --git a/swaybar/meson.build b/swaybar/meson.build index c27cf2c2..312ca97b 100644 --- a/swaybar/meson.build +++ b/swaybar/meson.build @@ -1,3 +1,32 @@ +tray_files = get_option('enable-tray') ? [ + 'tray/host.c', + 'tray/icon.c', + 'tray/item.c', + 'tray/tray.c', + 'tray/watcher.c' +] : [] + +swaybar_deps = [ + cairo, + client_protos, + gdk_pixbuf, + jsonc, + math, + pango, + pangocairo, + rt, + wayland_client, + wayland_cursor, + wlroots, +] +if get_option('enable-tray') + if systemd.found() + swaybar_deps += systemd + elif elogind.found() + swaybar_deps += elogind + endif +endif + executable( 'swaybar', [ 'bar.c', @@ -8,21 +37,10 @@ executable( 'main.c', 'render.c', 'status_line.c', + tray_files ], include_directories: [sway_inc], - dependencies: [ - cairo, - client_protos, - gdk_pixbuf, - jsonc, - math, - pango, - pangocairo, - rt, - wayland_client, - wayland_cursor, - wlroots, - ], + dependencies: swaybar_deps, link_with: [lib_sway_common, lib_sway_client], install_rpath : rpathdir, install: true diff --git a/swaybar/render.c b/swaybar/render.c index 4ebf922e..55f680ed 100644 --- a/swaybar/render.c +++ b/swaybar/render.c @@ -1,5 +1,6 @@ #define _POSIX_C_SOURCE 200809L #include <assert.h> +#include <linux/input-event-codes.h> #include <limits.h> #include <stdlib.h> #include <stdint.h> @@ -14,6 +15,9 @@ #include "swaybar/ipc.h" #include "swaybar/render.h" #include "swaybar/status_line.h" +#if HAVE_TRAY +#include "swaybar/tray/tray.h" +#endif #include "wlr-layer-shell-unstable-v1-client-protocol.h" static const int WS_HORIZONTAL_PADDING = 5; @@ -32,7 +36,8 @@ static uint32_t render_status_line_error(cairo_t *cairo, cairo_set_source_u32(cairo, 0xFF0000FF); int margin = 3 * output->scale; - int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale; + double ws_vertical_padding = + output->bar->config->status_padding * output->scale; char *font = output->bar->config->font; int text_width, text_height; @@ -41,7 +46,8 @@ static uint32_t render_status_line_error(cairo_t *cairo, uint32_t ideal_height = text_height + ws_vertical_padding * 2; uint32_t ideal_surface_height = ideal_height / output->scale; - if (output->height < ideal_surface_height) { + if (!output->bar->config->height && + output->height < ideal_surface_height) { return ideal_surface_height; } *x -= text_width + margin; @@ -68,12 +74,13 @@ static uint32_t render_status_line_text(cairo_t *cairo, get_text_size(cairo, config->font, &text_width, &text_height, NULL, output->scale, config->pango_markup, "%s", text); - int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale; + double ws_vertical_padding = config->status_padding * output->scale; int margin = 3 * output->scale; uint32_t ideal_height = text_height + ws_vertical_padding * 2; uint32_t ideal_surface_height = ideal_height / output->scale; - if (output->height < ideal_surface_height) { + if (!output->bar->config->height && + output->height < ideal_surface_height) { return ideal_surface_height; } @@ -87,13 +94,24 @@ static uint32_t render_status_line_text(cairo_t *cairo, return output->height; } -static void render_sharp_line(cairo_t *cairo, uint32_t color, +static void render_sharp_rectangle(cairo_t *cairo, uint32_t color, double x, double y, double width, double height) { + cairo_save(cairo); cairo_set_source_u32(cairo, color); + cairo_set_antialias(cairo, CAIRO_ANTIALIAS_NONE); + cairo_rectangle(cairo, x, y, width, height); + cairo_fill(cairo); + cairo_restore(cairo); +} + +static void render_sharp_line(cairo_t *cairo, uint32_t color, + double x, double y, double width, double height) { if (width > 1 && height > 1) { - cairo_rectangle(cairo, x, y, width, height); - cairo_fill(cairo); + render_sharp_rectangle(cairo, color, x, y, width, height); } else { + cairo_save(cairo); + cairo_set_source_u32(cairo, color); + cairo_set_antialias(cairo, CAIRO_ANTIALIAS_NONE); if (width == 1) { x += 0.5; height += y; @@ -108,14 +126,17 @@ static void render_sharp_line(cairo_t *cairo, uint32_t color, cairo_set_line_width(cairo, 1.0); cairo_line_to(cairo, width, height); cairo_stroke(cairo); + cairo_restore(cairo); } } -static enum hotspot_event_handling block_hotspot_callback(struct swaybar_output *output, - int x, int y, enum x11_button button, void *data) { +static enum hotspot_event_handling block_hotspot_callback( + struct swaybar_output *output, struct swaybar_hotspot *hotspot, + int x, int y, uint32_t button, void *data) { struct i3bar_block *block = data; struct status_line *status = output->bar->status; - return i3bar_block_send_click(status, block, x, y, button); + return i3bar_block_send_click(status, block, x, y, x - hotspot->x, + y - hotspot->y, hotspot->width, hotspot->height, button); } static void i3bar_block_unref_callback(void *data) { @@ -136,7 +157,7 @@ static uint32_t render_status_block(cairo_t *cairo, output->scale, block->markup, "%s", block->full_text); int margin = 3 * output->scale; - int ws_vertical_padding = WS_VERTICAL_PADDING * 2; + double ws_vertical_padding = config->status_padding * output->scale; int width = text_width; if (width < block->min_width) { @@ -146,37 +167,40 @@ static uint32_t render_status_block(cairo_t *cairo, double block_width = width; uint32_t ideal_height = text_height + ws_vertical_padding * 2; uint32_t ideal_surface_height = ideal_height / output->scale; - if (output->height < ideal_surface_height) { + if (!output->bar->config->height && + output->height < ideal_surface_height) { return ideal_surface_height; } *x -= width; - if (block->border && block->border_left > 0) { - *x -= (block->border_left + margin); - block_width += block->border_left + margin; + if ((block->border || block->urgent) && block->border_left > 0) { + *x -= (block->border_left * output->scale + margin); + block_width += block->border_left * output->scale + margin; } - if (block->border && block->border_right > 0) { - *x -= (block->border_right + margin); - block_width += block->border_right + margin; + if ((block->border || block->urgent) && block->border_right > 0) { + *x -= (block->border_right * output->scale + margin); + block_width += block->border_right * output->scale + margin; } int sep_width, sep_height; + int sep_block_width = block->separator_block_width; if (!edge) { if (config->sep_symbol) { get_text_size(cairo, config->font, &sep_width, &sep_height, NULL, output->scale, false, "%s", config->sep_symbol); uint32_t _ideal_height = sep_height + ws_vertical_padding * 2; uint32_t _ideal_surface_height = _ideal_height / output->scale; - if (output->height < _ideal_surface_height) { + if (!output->bar->config->height && + output->height < _ideal_surface_height) { return _ideal_surface_height; } - if (sep_width > block->separator_block_width) { - block->separator_block_width = sep_width + margin * 2; + if (sep_width > sep_block_width) { + sep_block_width = sep_width + margin * 2; } } - *x -= block->separator_block_width; - } else { - *x -= margin; + *x -= sep_block_width; + } else if (config->status_edge_padding) { + *x -= config->status_edge_padding * output->scale; } uint32_t height = output->height * output->scale; @@ -193,53 +217,55 @@ static uint32_t render_status_block(cairo_t *cairo, wl_list_insert(&output->hotspots, &hotspot->link); } - double pos = *x; - if (block->background) { - cairo_set_source_u32(cairo, block->background); - cairo_rectangle(cairo, pos - 0.5 * output->scale, - output->scale, width, height); - cairo_fill(cairo); + double x_pos = *x; + double y_pos = ws_vertical_padding; + double render_height = height - ws_vertical_padding * 2; + + uint32_t bg_color = block->urgent + ? config->colors.urgent_workspace.background : block->background; + if (bg_color) { + render_sharp_rectangle(cairo, bg_color, x_pos, y_pos, + block_width, render_height); } - if (block->border && block->border_top > 0) { - render_sharp_line(cairo, block->border, - pos - 0.5 * output->scale, output->scale, - block_width, block->border_top); + uint32_t border_color = block->urgent + ? config->colors.urgent_workspace.border : block->border; + if (border_color && block->border_top > 0) { + render_sharp_line(cairo, border_color, x_pos, y_pos, + block_width, block->border_top * output->scale); } - if (block->border && block->border_bottom > 0) { - render_sharp_line(cairo, block->border, - pos - 0.5 * output->scale, - height - output->scale - block->border_bottom, - block_width, block->border_bottom); + if (border_color && block->border_bottom > 0) { + render_sharp_line(cairo, border_color, x_pos, + y_pos + render_height - block->border_bottom * output->scale, + block_width, block->border_bottom * output->scale); } - if (block->border != 0 && block->border_left > 0) { - render_sharp_line(cairo, block->border, - pos - 0.5 * output->scale, output->scale, - block->border_left, height); - pos += block->border_left + margin; + if (border_color && block->border_left > 0) { + render_sharp_line(cairo, border_color, x_pos, y_pos, + block->border_left * output->scale, render_height); + x_pos += block->border_left * output->scale + margin; } double offset = 0; if (strncmp(block->align, "left", 5) == 0) { - offset = pos; + offset = x_pos; } else if (strncmp(block->align, "right", 5) == 0) { - offset = pos + width - text_width; + offset = x_pos + width - text_width; } else if (strncmp(block->align, "center", 6) == 0) { - offset = pos + (width - text_width) / 2; + offset = x_pos + (width - text_width) / 2; } cairo_move_to(cairo, offset, height / 2.0 - text_height / 2.0); uint32_t color = block->color ? *block->color : config->colors.statusline; + color = block->urgent ? config->colors.urgent_workspace.text : color; cairo_set_source_u32(cairo, color); pango_printf(cairo, config->font, output->scale, block->markup, "%s", block->full_text); - pos += width; + x_pos += width; if (block->border && block->border_right > 0) { - pos += margin; - render_sharp_line(cairo, block->border, - pos - 0.5 * output->scale, output->scale, - block->border_right, height); - pos += block->border_right; + x_pos += margin; + render_sharp_line(cairo, border_color, x_pos, y_pos, + block->border_right * output->scale, render_height); + x_pos += block->border_right * output->scale; } if (!edge && block->separator) { @@ -249,16 +275,14 @@ static uint32_t render_status_block(cairo_t *cairo, cairo_set_source_u32(cairo, config->colors.separator); } if (config->sep_symbol) { - offset = pos + (block->separator_block_width - sep_width) / 2; + offset = x_pos + (sep_block_width - sep_width) / 2; cairo_move_to(cairo, offset, height / 2.0 - sep_height / 2.0); pango_printf(cairo, config->font, output->scale, false, "%s", config->sep_symbol); } else { cairo_set_line_width(cairo, 1); - cairo_move_to(cairo, - pos + block->separator_block_width / 2, margin); - cairo_line_to(cairo, - pos + block->separator_block_width / 2, height - margin); + cairo_move_to(cairo, x_pos + sep_block_width / 2, margin); + cairo_line_to(cairo, x_pos + sep_block_width / 2, height - margin); cairo_stroke(cairo); } } @@ -268,7 +292,7 @@ static uint32_t render_status_block(cairo_t *cairo, static uint32_t render_status_line_i3bar(cairo_t *cairo, struct swaybar_output *output, double *x) { uint32_t max_height = 0; - bool edge = true; + bool edge = *x == output->width * output->scale; struct i3bar_block *block; wl_list_for_each(block, &output->bar->status->blocks, link) { uint32_t h = render_status_block(cairo, output, block, x, edge); @@ -314,7 +338,8 @@ static uint32_t render_binding_mode_indicator(cairo_t *cairo, uint32_t ideal_height = text_height + ws_vertical_padding * 2 + border_width * 2; uint32_t ideal_surface_height = ideal_height / output->scale; - if (output->height < ideal_surface_height) { + if (!output->bar->config->height && + output->height < ideal_surface_height) { return ideal_surface_height; } uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2; @@ -342,22 +367,10 @@ static uint32_t render_binding_mode_indicator(cairo_t *cairo, return output->height; } -static const char *strip_workspace_number(const char *ws_name) { - size_t len = strlen(ws_name); - for (size_t i = 0; i < len; ++i) { - if (ws_name[i] < '0' || ws_name[i] > '9') { - if (':' == ws_name[i] && i < len - 1 && i > 0) { - return ws_name + i + 1; - } - return ws_name; - } - } - return ws_name; -} - -static enum hotspot_event_handling workspace_hotspot_callback(struct swaybar_output *output, - int x, int y, enum x11_button button, void *data) { - if (button != LEFT) { +static enum hotspot_event_handling workspace_hotspot_callback( + struct swaybar_output *output, struct swaybar_hotspot *hotspot, + int x, int y, uint32_t button, void *data) { + if (button != BTN_LEFT) { return HOTSPOT_PROCESS; } ipc_send_workspace_command(output->bar, (const char *)data); @@ -368,11 +381,6 @@ static uint32_t render_workspace_button(cairo_t *cairo, struct swaybar_output *output, struct swaybar_workspace *ws, double *x) { struct swaybar_config *config = output->bar->config; - const char *name = ws->name; - if (config->strip_workspace_numbers) { - name = strip_workspace_number(ws->name); - } - struct box_colors box_colors; if (ws->urgent) { box_colors = config->colors.urgent_workspace; @@ -388,7 +396,7 @@ static uint32_t render_workspace_button(cairo_t *cairo, int text_width, text_height; get_text_size(cairo, config->font, &text_width, &text_height, NULL, - output->scale, config->pango_markup, "%s", name); + output->scale, config->pango_markup, "%s", ws->label); int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale; int ws_horizontal_padding = WS_HORIZONTAL_PADDING * output->scale; @@ -397,7 +405,8 @@ static uint32_t render_workspace_button(cairo_t *cairo, uint32_t ideal_height = ws_vertical_padding * 2 + text_height + border_width * 2; uint32_t ideal_surface_height = ideal_height / output->scale; - if (output->height < ideal_surface_height) { + if (!output->bar->config->height && + output->height < ideal_surface_height) { return ideal_surface_height; } @@ -421,7 +430,7 @@ static uint32_t render_workspace_button(cairo_t *cairo, cairo_set_source_u32(cairo, box_colors.text); cairo_move_to(cairo, *x + width / 2 - text_width / 2, (int)floor(text_y)); pango_printf(cairo, config->font, output->scale, config->pango_markup, - "%s", name); + "%s", ws->label); struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot)); hotspot->x = *x; @@ -459,6 +468,12 @@ static uint32_t render_to_cairo(cairo_t *cairo, struct swaybar_output *output) { * utilize the available space. */ double x = output->width * output->scale; +#if HAVE_TRAY + if (bar->tray) { + uint32_t h = render_tray(cairo, output, &x); + max_height = h > max_height ? h : max_height; + } +#endif if (bar->status) { uint32_t h = render_status_line(cairo, output, &x); max_height = h > max_height ? h : max_height; @@ -518,12 +533,17 @@ void render_frame(struct swaybar_output *output) { cairo_restore(cairo); uint32_t height = render_to_cairo(cairo, output); int config_height = output->bar->config->height; - if (config_height >= 0 && height < (uint32_t)config_height) { + if (config_height > 0) { height = config_height; } if (height != output->height || output->width == 0) { // Reconfigure surface zwlr_layer_surface_v1_set_size(output->layer_surface, 0, height); + zwlr_layer_surface_v1_set_margin(output->layer_surface, + output->bar->config->gaps.top, + output->bar->config->gaps.right, + output->bar->config->gaps.bottom, + output->bar->config->gaps.left); if (strcmp(output->bar->config->mode, "dock") == 0) { zwlr_layer_surface_v1_set_exclusive_zone(output->layer_surface, height); } diff --git a/swaybar/status_line.c b/swaybar/status_line.c index 744d2785..f0e2c300 100644 --- a/swaybar/status_line.c +++ b/swaybar/status_line.c @@ -12,7 +12,6 @@ #include "swaybar/config.h" #include "swaybar/i3bar.h" #include "swaybar/status_line.h" -#include "readline.h" static void status_line_close_fds(struct status_line *status) { if (status->read_fd != -1) { @@ -185,7 +184,6 @@ void status_line_free(struct status_line *status) { } free(status->read); free(status->write); - free((char*) status->text); free(status->buffer); free(status); } diff --git a/swaybar/tray/host.c b/swaybar/tray/host.c new file mode 100644 index 00000000..cc8dd188 --- /dev/null +++ b/swaybar/tray/host.c @@ -0,0 +1,210 @@ +#define _POSIX_C_SOURCE 200809L +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "swaybar/bar.h" +#include "swaybar/tray/host.h" +#include "swaybar/tray/item.h" +#include "swaybar/tray/tray.h" +#include "list.h" +#include "log.h" + +static const char *watcher_path = "/StatusNotifierWatcher"; + +static int cmp_sni_id(const void *item, const void *cmp_to) { + const struct swaybar_sni *sni = item; + return strcmp(sni->watcher_id, cmp_to); +} + +static void add_sni(struct swaybar_tray *tray, char *id) { + int idx = list_seq_find(tray->items, cmp_sni_id, id); + if (idx == -1) { + wlr_log(WLR_INFO, "Registering Status Notifier Item '%s'", id); + struct swaybar_sni *sni = create_sni(id, tray); + if (sni) { + list_add(tray->items, sni); + } + } +} + +static int handle_sni_registered(sd_bus_message *msg, void *data, + sd_bus_error *error) { + char *id; + int ret = sd_bus_message_read(msg, "s", &id); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse register SNI message: %s", strerror(-ret)); + } + + struct swaybar_tray *tray = data; + add_sni(tray, id); + + return ret; +} + +static int handle_sni_unregistered(sd_bus_message *msg, void *data, + sd_bus_error *error) { + char *id; + int ret = sd_bus_message_read(msg, "s", &id); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse unregister SNI message: %s", strerror(-ret)); + } + + struct swaybar_tray *tray = data; + int idx = list_seq_find(tray->items, cmp_sni_id, id); + if (idx != -1) { + wlr_log(WLR_INFO, "Unregistering Status Notifier Item '%s'", id); + destroy_sni(tray->items->items[idx]); + list_del(tray->items, idx); + set_bar_dirty(tray->bar); + } + return ret; +} + +static int get_registered_snis_callback(sd_bus_message *msg, void *data, + sd_bus_error *error) { + if (sd_bus_message_is_method_error(msg, NULL)) { + sd_bus_error err = *sd_bus_message_get_error(msg); + wlr_log(WLR_ERROR, "Failed to get registered SNIs: %s", err.message); + return -sd_bus_error_get_errno(&err); + } + + int ret = sd_bus_message_enter_container(msg, 'v', NULL); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to read registered SNIs: %s", strerror(-ret)); + return ret; + } + + char **ids; + ret = sd_bus_message_read_strv(msg, &ids); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to read registered SNIs: %s", strerror(-ret)); + return ret; + } + + if (ids) { + struct swaybar_tray *tray = data; + for (char **id = ids; *id; ++id) { + add_sni(tray, *id); + } + } + + return ret; +} + +static bool register_to_watcher(struct swaybar_host *host) { + // this is called asynchronously in case the watcher is owned by this process + int ret = sd_bus_call_method_async(host->tray->bus, NULL, + host->watcher_interface, watcher_path, host->watcher_interface, + "RegisterStatusNotifierHost", NULL, NULL, "s", host->service); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to send register call: %s", strerror(-ret)); + return false; + } + + ret = sd_bus_call_method_async(host->tray->bus, NULL, + host->watcher_interface, watcher_path, + "org.freedesktop.DBus.Properties", "Get", + get_registered_snis_callback, host->tray, "ss", + host->watcher_interface, "RegisteredStatusNotifierItems"); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to get registered SNIs: %s", strerror(-ret)); + } + + return ret >= 0; +} + +static int handle_new_watcher(sd_bus_message *msg, + void *data, sd_bus_error *error) { + char *service, *old_owner, *new_owner; + int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse owner change message: %s", strerror(-ret)); + return ret; + } + + if (!*old_owner) { + struct swaybar_host *host = data; + if (strcmp(service, host->watcher_interface) == 0) { + register_to_watcher(host); + } + } + + return 0; +} + +bool init_host(struct swaybar_host *host, char *protocol, + struct swaybar_tray *tray) { + size_t len = snprintf(NULL, 0, "org.%s.StatusNotifierWatcher", protocol) + 1; + host->watcher_interface = malloc(len); + if (!host->watcher_interface) { + return false; + } + snprintf(host->watcher_interface, len, "org.%s.StatusNotifierWatcher", protocol); + + sd_bus_slot *reg_slot = NULL, *unreg_slot = NULL, *watcher_slot = NULL; + int ret = sd_bus_match_signal(tray->bus, ®_slot, host->watcher_interface, + watcher_path, host->watcher_interface, + "StatusNotifierItemRegistered", handle_sni_registered, tray); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to subscribe to registering events: %s", + strerror(-ret)); + goto error; + } + ret = sd_bus_match_signal(tray->bus, &unreg_slot, host->watcher_interface, + watcher_path, host->watcher_interface, + "StatusNotifierItemUnregistered", handle_sni_unregistered, tray); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s", + strerror(-ret)); + goto error; + } + + ret = sd_bus_match_signal(tray->bus, &watcher_slot, "org.freedesktop.DBus", + "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged", + handle_new_watcher, host); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s", + strerror(-ret)); + goto error; + } + + pid_t pid = getpid(); + size_t service_len = snprintf(NULL, 0, "org.%s.StatusNotifierHost-%d", + protocol, pid) + 1; + host->service = malloc(service_len); + if (!host->service) { + goto error; + } + snprintf(host->service, service_len, "org.%s.StatusNotifierHost-%d", protocol, pid); + ret = sd_bus_request_name(tray->bus, host->service, 0); + if (ret < 0) { + wlr_log(WLR_DEBUG, "Failed to acquire service name: %s", strerror(-ret)); + goto error; + } + + host->tray = tray; + if (!register_to_watcher(host)) { + goto error; + } + + sd_bus_slot_set_floating(reg_slot, 1); + sd_bus_slot_set_floating(unreg_slot, 1); + sd_bus_slot_set_floating(watcher_slot, 1); + + wlr_log(WLR_DEBUG, "Registered %s", host->service); + return true; +error: + sd_bus_slot_unref(reg_slot); + sd_bus_slot_unref(unreg_slot); + sd_bus_slot_unref(watcher_slot); + finish_host(host); + return false; +} + +void finish_host(struct swaybar_host *host) { + sd_bus_release_name(host->tray->bus, host->service); + free(host->service); + free(host->watcher_interface); +} diff --git a/swaybar/tray/icon.c b/swaybar/tray/icon.c new file mode 100644 index 00000000..67805858 --- /dev/null +++ b/swaybar/tray/icon.c @@ -0,0 +1,462 @@ +#define _POSIX_C_SOURCE 200809L +#include <ctype.h> +#include <dirent.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> +#include <wordexp.h> +#include "swaybar/tray/icon.h" +#include "config.h" +#include "list.h" +#include "log.h" +#include "stringop.h" + +static bool dir_exists(char *path) { + struct stat sb; + return stat(path, &sb) == 0 && S_ISDIR(sb.st_mode); +} + +static list_t *get_basedirs(void) { + list_t *basedirs = create_list(); + list_add(basedirs, strdup("$HOME/.icons")); // deprecated + + char *data_home = getenv("XDG_DATA_HOME"); + list_add(basedirs, strdup(data_home && *data_home ? + "$XDG_DATA_HOME/icons" : "$HOME/.local/share/icons")); + + list_add(basedirs, strdup("/usr/share/pixmaps")); + + char *data_dirs = getenv("XDG_DATA_DIRS"); + if (!(data_dirs && *data_dirs)) { + data_dirs = "/usr/local/share:/usr/share"; + } + data_dirs = strdup(data_dirs); + char *dir = strtok(data_dirs, ":"); + do { + size_t path_len = snprintf(NULL, 0, "%s/icons", dir) + 1; + char *path = malloc(path_len); + snprintf(path, path_len, "%s/icons", dir); + list_add(basedirs, path); + } while ((dir = strtok(NULL, ":"))); + free(data_dirs); + + list_t *basedirs_expanded = create_list(); + for (int i = 0; i < basedirs->length; ++i) { + wordexp_t p; + if (wordexp(basedirs->items[i], &p, WRDE_UNDEF) == 0) { + if (dir_exists(p.we_wordv[0])) { + list_add(basedirs_expanded, strdup(p.we_wordv[0])); + } + wordfree(&p); + } + } + + list_free_items_and_destroy(basedirs); + + return basedirs_expanded; +} + +static void destroy_theme(struct icon_theme *theme) { + if (!theme) { + return; + } + free(theme->name); + free(theme->comment); + free(theme->inherits); + list_free_items_and_destroy(theme->directories); + free(theme->dir); + + for (int i = 0; i < theme->subdirs->length; ++i) { + struct icon_theme_subdir *subdir = theme->subdirs->items[i]; + free(subdir->name); + free(subdir); + } + list_free(theme->subdirs); + free(theme); +} + +static int cmp_group(const void *item, const void *cmp_to) { + return strcmp(item, cmp_to); +} + +static bool group_handler(char *old_group, char *new_group, + struct icon_theme *theme) { + if (!old_group) { // first group must be "Icon Theme" + return strcmp(new_group, "Icon Theme"); + } + + if (strcmp(old_group, "Icon Theme") == 0) { + if (!(theme->name && theme->comment && theme->directories)) { + return true; + } + } else { + if (theme->subdirs->length == 0) { // skip + return false; + } + + struct icon_theme_subdir *subdir = + theme->subdirs->items[theme->subdirs->length - 1]; + if (!subdir->size) return true; + + switch (subdir->type) { + case FIXED: subdir->max_size = subdir->min_size = subdir->size; + break; + case SCALABLE: { + if (!subdir->max_size) subdir->max_size = subdir->size; + if (!subdir->min_size) subdir->min_size = subdir->size; + break; + } + case THRESHOLD: + subdir->max_size = subdir->size + subdir->threshold; + subdir->min_size = subdir->size - subdir->threshold; + } + } + + if (new_group && list_seq_find(theme->directories, cmp_group, new_group) != -1) { + struct icon_theme_subdir *subdir = calloc(1, sizeof(struct icon_theme_subdir)); + if (!subdir) { + return true; + } + subdir->name = strdup(new_group); + subdir->threshold = 2; + list_add(theme->subdirs, subdir); + } + + return false; +} + +static int entry_handler(char *group, char *key, char *value, + struct icon_theme *theme) { + if (strcmp(group, "Icon Theme") == 0) { + if (strcmp(key, "Name") == 0) { + theme->name = strdup(value); + } else if (strcmp(key, "Comment") == 0) { + theme->comment = strdup(value); + } else if (strcmp(key, "Inherists") == 0) { + theme->inherits = strdup(value); + } else if (strcmp(key, "Directories") == 0) { + theme->directories = split_string(value, ","); + } // Ignored: ScaledDirectories, Hidden, Example + } else { + if (theme->subdirs->length == 0) { // skip + return false; + } + + struct icon_theme_subdir *subdir = + theme->subdirs->items[theme->subdirs->length - 1]; + if (strcmp(subdir->name, group) != 0) { // skip + return false; + } + + char *end; + int n = strtol(value, &end, 10); + if (strcmp(key, "Size") == 0) { + subdir->size = n; + return *end != '\0'; + } else if (strcmp(key, "Type") == 0) { + if (strcmp(value, "Fixed") == 0) { + subdir->type = FIXED; + } else if (strcmp(value, "Scalable") == 0) { + subdir->type = SCALABLE; + } else if (strcmp(value, "Threshold") == 0) { + subdir->type = THRESHOLD; + } else { + return true; + } + } else if (strcmp(key, "MaxSize") == 0) { + subdir->max_size = n; + return *end != '\0'; + } else if (strcmp(key, "MinSize") == 0) { + subdir->min_size = n; + return *end != '\0'; + } else if (strcmp(key, "Threshold") == 0) { + subdir->threshold = n; + return *end != '\0'; + } // Ignored: Scale, Applications + } + return false; +} + +/* + * This is a Freedesktop Desktop Entry parser (essentially INI) + * It calls entry_handler for every entry + * and group_handler between every group (as well as at both ends) + * Handlers return whether an error occured, which stops parsing + */ +static struct icon_theme *read_theme_file(char *basedir, char *theme_name) { + // look for index.theme file + size_t path_len = snprintf(NULL, 0, "%s/%s/index.theme", basedir, + theme_name) + 1; + char *path = malloc(path_len); + snprintf(path, path_len, "%s/%s/index.theme", basedir, theme_name); + FILE *theme_file = fopen(path, "r"); + if (!theme_file) { + return NULL; + } + + struct icon_theme *theme = calloc(1, sizeof(struct icon_theme)); + if (!theme) { + return NULL; + } + theme->subdirs = create_list(); + + bool error = false; + char *group = NULL; + char *full_line = NULL; + size_t full_len = 0; + ssize_t nread; + while ((nread = getline(&full_line, &full_len, theme_file)) != -1) { + char *line = full_line - 1; + while (isspace(*++line)) {} // remove leading whitespace + if (!*line || line[0] == '#') continue; // ignore blank lines & comments + + int len = nread - (line - full_line); + while (isspace(line[--len])) {} + line[++len] = '\0'; // remove trailing whitespace + + if (line[0] == '[') { // group header + // check well-formed + if (line[--len] != ']') { + error = true; + break; + } + int i = 1; + for (; !iscntrl(line[i]) && line[i] != '[' && line[i] != ']'; ++i) {} + if (i < len) { + error = true; + break; + } + + // call handler + line[len] = '\0'; + error = group_handler(group, &line[1], theme); + if (error) { + break; + } + free(group); + group = strdup(&line[1]); + } else { // key-value pair + // check well-formed + int eok = 0; + for (; isalnum(line[eok]) || line[eok] == '-'; ++eok) {} // TODO locale? + int i = eok - 1; + while (isspace(line[++i])) {} + if (line[i] != '=') { + error = true; + break; + } + + line[eok] = '\0'; // split into key-value pair + char *value = &line[i]; + while (isspace(*++value)) {} + // TODO unescape value + error = entry_handler(group, line, value, theme); + if (error) { + break; + } + } + } + + if (!error && group) { + error = group_handler(group, NULL, theme); + } + + free(group); + free(full_line); + fclose(theme_file); + + if (!error) { + theme->dir = strdup(theme_name); + return theme; + } else { + destroy_theme(theme); + return NULL; + } +} + +static list_t *load_themes_in_dir(char *basedir) { + DIR *dir; + if (!(dir = opendir(basedir))) { + return NULL; + } + + list_t *themes = create_list(); + struct dirent *entry; + while ((entry = readdir(dir))) { + if (entry->d_name[0] == '.') continue; + + struct icon_theme *theme = read_theme_file(basedir, entry->d_name); + if (theme) { + list_add(themes, theme); + } + } + return themes; +} + +void init_themes(list_t **themes, list_t **basedirs) { + *basedirs = get_basedirs(); + + *themes = create_list(); + for (int i = 0; i < (*basedirs)->length; ++i) { + list_t *dir_themes = load_themes_in_dir((*basedirs)->items[i]); + list_cat(*themes, dir_themes); + list_free(dir_themes); + } + + list_t *theme_names = create_list(); + for (int i = 0; i < (*themes)->length; ++i) { + struct icon_theme *theme = (*themes)->items[i]; + list_add(theme_names, theme->name); + } + wlr_log(WLR_DEBUG, "Loaded themes: %s", join_list(theme_names, ", ")); + list_free(theme_names); +} + +void finish_themes(list_t *themes, list_t *basedirs) { + for (int i = 0; i < themes->length; ++i) { + destroy_theme(themes->items[i]); + } + list_free(themes); + list_free_items_and_destroy(basedirs); +} + +static char *find_icon_in_subdir(char *name, char *basedir, char *theme, + char *subdir) { + static const char *extensions[] = { +#if HAVE_GDK_PIXBUF + "svg", +#endif + "png", +#if HAVE_GDK_PIXBUF + "xpm" +#endif + }; + + size_t path_len = snprintf(NULL, 0, "%s/%s/%s/%s.EXT", basedir, theme, + subdir, name) + 1; + char *path = malloc(path_len); + + for (size_t i = 0; i < sizeof(extensions) / sizeof(*extensions); ++i) { + snprintf(path, path_len, "%s/%s/%s/%s.%s", basedir, theme, subdir, + name, extensions[i]); + if (access(path, R_OK) == 0) { + return path; + } + } + + free(path); + return NULL; +} + +static bool theme_exists_in_basedir(char *theme, char *basedir) { + size_t path_len = snprintf(NULL, 0, "%s/%s", basedir, theme) + 1; + char *path = malloc(path_len); + snprintf(path, path_len, "%s/%s", basedir, theme); + bool ret = dir_exists(path); + free(path); + return ret; +} + +static char *find_icon_with_theme(list_t *basedirs, list_t *themes, char *name, + int size, char *theme_name, int *min_size, int *max_size) { + struct icon_theme *theme = NULL; + for (int i = 0; i < themes->length; ++i) { + theme = themes->items[i]; + if (strcmp(theme->name, theme_name) == 0) { + break; + } + theme = NULL; + } + if (!theme) return NULL; + + char *icon = NULL; + for (int i = 0; i < basedirs->length; ++i) { + if (!theme_exists_in_basedir(theme->dir, basedirs->items[i])) { + continue; + } + // search backwards to hopefully hit scalable/larger icons first + for (int j = theme->subdirs->length - 1; j >= 0; --j) { + struct icon_theme_subdir *subdir = theme->subdirs->items[j]; + if (size >= subdir->min_size && size <= subdir->max_size) { + if ((icon = find_icon_in_subdir(name, basedirs->items[i], + theme->dir, subdir->name))) { + *min_size = subdir->min_size; + *max_size = subdir->max_size; + return icon; + } + } + } + } + + // inexact match + unsigned smallest_error = -1; // UINT_MAX + for (int i = 0; i < basedirs->length; ++i) { + if (!theme_exists_in_basedir(theme->dir, basedirs->items[i])) { + continue; + } + for (int j = theme->subdirs->length - 1; j >= 0; --j) { + struct icon_theme_subdir *subdir = theme->subdirs->items[j]; + unsigned error = (size > subdir->max_size ? size - subdir->max_size : 0) + + (size < subdir->min_size ? subdir->min_size - size : 0); + if (error < smallest_error) { + char *test_icon = find_icon_in_subdir(name, basedirs->items[i], + theme->dir, subdir->name); + if (test_icon) { + icon = test_icon; + smallest_error = error; + *min_size = subdir->min_size; + *max_size = subdir->max_size; + } + } + } + } + + if (!icon && theme->inherits) { + icon = find_icon_with_theme(basedirs, themes, name, size, + theme->inherits, min_size, max_size); + } + + return icon; +} + +char *find_icon_in_dir(char *name, char *dir, int *min_size, int *max_size) { + char *icon = find_icon_in_subdir(name, dir, "", ""); + if (icon) { + *min_size = 1; + *max_size = 512; + } + return icon; + +} + +static char *find_fallback_icon(list_t *basedirs, char *name, int *min_size, + int *max_size) { + for (int i = 0; i < basedirs->length; ++i) { + char *icon = find_icon_in_dir(name, basedirs->items[i], min_size, max_size); + if (icon) { + return icon; + } + } + return NULL; +} + +char *find_icon(list_t *themes, list_t *basedirs, char *name, int size, + char *theme, int *min_size, int *max_size) { + // TODO https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#implementation_notes + char *icon = NULL; + if (theme) { + icon = find_icon_with_theme(basedirs, themes, name, size, theme, + min_size, max_size); + } + if (!icon) { + icon = find_icon_with_theme(basedirs, themes, name, size, "Hicolor", + min_size, max_size); + } + if (!icon) { + icon = find_fallback_icon(basedirs, name, min_size, max_size); + } + return icon; +} diff --git a/swaybar/tray/item.c b/swaybar/tray/item.c new file mode 100644 index 00000000..0833dcb9 --- /dev/null +++ b/swaybar/tray/item.c @@ -0,0 +1,472 @@ +#define _POSIX_C_SOURCE 200809L +#include <cairo.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include "swaybar/bar.h" +#include "swaybar/config.h" +#include "swaybar/input.h" +#include "swaybar/tray/host.h" +#include "swaybar/tray/icon.h" +#include "swaybar/tray/item.h" +#include "swaybar/tray/tray.h" +#include "background-image.h" +#include "cairo.h" +#include "list.h" +#include "log.h" +#include "wlr-layer-shell-unstable-v1-client-protocol.h" + +// TODO menu + +static bool sni_ready(struct swaybar_sni *sni) { + return sni->status && (sni->status[0] == 'N' ? // NeedsAttention + sni->attention_icon_name || sni->attention_icon_pixmap : + sni->icon_name || sni->icon_pixmap); +} + +static void set_sni_dirty(struct swaybar_sni *sni) { + if (sni_ready(sni)) { + sni->min_size = sni->max_size = 0; // invalidate previous icon + set_bar_dirty(sni->tray->bar); + } +} + +static int read_pixmap(sd_bus_message *msg, struct swaybar_sni *sni, + const char *prop, list_t **dest) { + int ret = sd_bus_message_enter_container(msg, 'a', "(iiay)"); + if (ret < 0) { + wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret)); + return ret; + } + + if (sd_bus_message_at_end(msg, 0)) { + wlr_log(WLR_DEBUG, "%s %s no. of icons = 0", sni->watcher_id, prop); + return ret; + } + + list_t *pixmaps = create_list(); + if (!pixmaps) { + return -12; // -ENOMEM + } + + while (!sd_bus_message_at_end(msg, 0)) { + ret = sd_bus_message_enter_container(msg, 'r', "iiay"); + if (ret < 0) { + wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret)); + goto error; + } + + int size; + ret = sd_bus_message_read(msg, "ii", NULL, &size); + if (ret < 0) { + wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret)); + goto error; + } + + const void *pixels; + size_t npixels; + ret = sd_bus_message_read_array(msg, 'y', &pixels, &npixels); + if (ret < 0) { + wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret)); + goto error; + } + + struct swaybar_pixmap *pixmap = + malloc(sizeof(struct swaybar_pixmap) + npixels); + pixmap->size = size; + memcpy(pixmap->pixels, pixels, npixels); + list_add(pixmaps, pixmap); + + sd_bus_message_exit_container(msg); + } + list_free_items_and_destroy(*dest); + *dest = pixmaps; + wlr_log(WLR_DEBUG, "%s %s no. of icons = %d", sni->watcher_id, prop, + pixmaps->length); + + return ret; +error: + list_free_items_and_destroy(pixmaps); + return ret; +} + +struct get_property_data { + struct swaybar_sni *sni; + const char *prop; + const char *type; + void *dest; +}; + +static int get_property_callback(sd_bus_message *msg, void *data, + sd_bus_error *error) { + struct get_property_data *d = data; + struct swaybar_sni *sni = d->sni; + const char *prop = d->prop; + const char *type = d->type; + void *dest = d->dest; + + int ret; + if (sd_bus_message_is_method_error(msg, NULL)) { + wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, + sd_bus_message_get_error(msg)->message); + ret = sd_bus_message_get_errno(msg); + goto cleanup; + } + + ret = sd_bus_message_enter_container(msg, 'v', type); + if (ret < 0) { + wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret)); + goto cleanup; + } + + if (!type) { + ret = read_pixmap(msg, sni, prop, dest); + if (ret < 0) { + goto cleanup; + } + } else { + if (*type == 's' || *type == 'o') { + free(*(char **)dest); + } + + ret = sd_bus_message_read(msg, type, dest); + if (ret < 0) { + wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret)); + goto cleanup; + } + + if (*type == 's' || *type == 'o') { + char **str = dest; + *str = strdup(*str); + wlr_log(WLR_DEBUG, "%s %s = '%s'", sni->watcher_id, prop, *str); + } else if (*type == 'b') { + wlr_log(WLR_DEBUG, "%s %s = %s", sni->watcher_id, prop, + *(bool *)dest ? "true" : "false"); + } + } + + if (strcmp(prop, "Status") == 0 || (sni->status && (sni->status[0] == 'N' ? + prop[0] == 'A' : strncmp(prop, "Icon", 4) == 0))) { + set_sni_dirty(sni); + } +cleanup: + free(data); + return ret; +} + +static void sni_get_property_async(struct swaybar_sni *sni, const char *prop, + const char *type, void *dest) { + struct get_property_data *data = malloc(sizeof(struct get_property_data)); + data->sni = sni; + data->prop = prop; + data->type = type; + data->dest = dest; + int ret = sd_bus_call_method_async(sni->tray->bus, NULL, sni->service, + sni->path, "org.freedesktop.DBus.Properties", "Get", + get_property_callback, data, "ss", sni->interface, prop); + if (ret < 0) { + wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret)); + } +} + +/* + * There is a quirk in sd-bus that in some systems, it is unable to get the + * well-known names on the bus, so it cannot identify if an incoming signal, + * which uses the sender's unique name, actually matches the callback's matching + * sender if the callback uses a well-known name, in which case it just calls + * the callback and hopes for the best, resulting in false positives. In the + * case of NewIcon & NewAttentionIcon, this doesn't affect anything, but it + * means that for NewStatus, if the SNI does not definitely match the sender, + * then the safe thing to do is to query the status independently. + * This function returns 1 if the SNI definitely matches the signal sender, + * which is returned by the calling function to indicate that signal matching + * can stop since it has already found the required callback, otherwise, it + * returns 0, which allows matching to continue. + */ +static int sni_check_msg_sender(struct swaybar_sni *sni, sd_bus_message *msg, + const char *signal) { + bool has_well_known_names = + sd_bus_creds_get_mask(sd_bus_message_get_creds(msg)) & SD_BUS_CREDS_WELL_KNOWN_NAMES; + if (sni->service[0] == ':' || has_well_known_names) { + wlr_log(WLR_DEBUG, "%s has new %s", sni->watcher_id, signal); + return 1; + } else { + wlr_log(WLR_DEBUG, "%s may have new %s", sni->watcher_id, signal); + return 0; + } +} + +static int handle_new_icon(sd_bus_message *msg, void *data, sd_bus_error *error) { + struct swaybar_sni *sni = data; + sni_get_property_async(sni, "IconName", "s", &sni->icon_name); + sni_get_property_async(sni, "IconPixmap", NULL, &sni->icon_pixmap); + return sni_check_msg_sender(sni, msg, "icon"); +} + +static int handle_new_attention_icon(sd_bus_message *msg, void *data, + sd_bus_error *error) { + struct swaybar_sni *sni = data; + sni_get_property_async(sni, "AttentionIconName", "s", &sni->attention_icon_name); + sni_get_property_async(sni, "AttentionIconPixmap", NULL, &sni->attention_icon_pixmap); + return sni_check_msg_sender(sni, msg, "attention icon"); +} + +static int handle_new_status(sd_bus_message *msg, void *data, sd_bus_error *error) { + struct swaybar_sni *sni = data; + int ret = sni_check_msg_sender(sni, msg, "status"); + if (ret == 1) { + char *status; + int r = sd_bus_message_read(msg, "s", &status); + if (r < 0) { + wlr_log(WLR_ERROR, "%s new status error: %s", sni->watcher_id, strerror(-ret)); + ret = r; + } else { + free(sni->status); + sni->status = strdup(status); + wlr_log(WLR_DEBUG, "%s has new status = '%s'", sni->watcher_id, status); + set_sni_dirty(sni); + } + } else { + sni_get_property_async(sni, "Status", "s", &sni->status); + } + + return ret; +} + +static void sni_match_signal(struct swaybar_sni *sni, sd_bus_slot **slot, + char *signal, sd_bus_message_handler_t callback) { + int ret = sd_bus_match_signal(sni->tray->bus, slot, sni->service, sni->path, + sni->interface, signal, callback, sni); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to subscribe to signal %s: %s", signal, + strerror(-ret)); + } +} + +struct swaybar_sni *create_sni(char *id, struct swaybar_tray *tray) { + struct swaybar_sni *sni = calloc(1, sizeof(struct swaybar_sni)); + if (!sni) { + return NULL; + } + sni->tray = tray; + sni->watcher_id = strdup(id); + char *path_ptr = strchr(id, '/'); + if (!path_ptr) { + sni->service = strdup(id); + sni->path = strdup("/StatusNotifierItem"); + sni->interface = "org.freedesktop.StatusNotifierItem"; + } else { + sni->service = strndup(id, path_ptr - id); + sni->path = strdup(path_ptr); + sni->interface = "org.kde.StatusNotifierItem"; + sni_get_property_async(sni, "IconThemePath", "s", &sni->icon_theme_path); + } + + // Ignored: Category, Id, Title, WindowId, OverlayIconName, + // OverlayIconPixmap, AttentionMovieName, ToolTip + sni_get_property_async(sni, "Status", "s", &sni->status); + sni_get_property_async(sni, "IconName", "s", &sni->icon_name); + sni_get_property_async(sni, "IconPixmap", NULL, &sni->icon_pixmap); + sni_get_property_async(sni, "AttentionIconName", "s", &sni->attention_icon_name); + sni_get_property_async(sni, "AttentionIconPixmap", NULL, &sni->attention_icon_pixmap); + sni_get_property_async(sni, "ItemIsMenu", "b", &sni->item_is_menu); + sni_get_property_async(sni, "Menu", "o", &sni->menu); + + sni_match_signal(sni, &sni->new_icon_slot, "NewIcon", handle_new_icon); + sni_match_signal(sni, &sni->new_attention_icon_slot, "NewAttentionIcon", + handle_new_attention_icon); + sni_match_signal(sni, &sni->new_status_slot, "NewStatus", handle_new_status); + + return sni; +} + +void destroy_sni(struct swaybar_sni *sni) { + if (!sni) { + return; + } + + sd_bus_slot_unref(sni->new_icon_slot); + sd_bus_slot_unref(sni->new_attention_icon_slot); + sd_bus_slot_unref(sni->new_status_slot); + + free(sni->watcher_id); + free(sni->service); + free(sni->path); + free(sni->status); + free(sni->icon_name); + free(sni->icon_pixmap); + free(sni->attention_icon_name); + free(sni->menu); + free(sni); +} + +static void handle_click(struct swaybar_sni *sni, int x, int y, + enum x11_button button, int delta) { + const char *method = sni->tray->bar->config->tray_bindings[button]; + if (!method) { + static const char *default_bindings[10] = { + "nop", + "Activate", + "SecondaryActivate", + "ContextMenu", + "ScrollUp", + "ScrollDown", + "ScrollLeft", + "ScrollRight", + "nop", + "nop" + }; + method = default_bindings[button]; + } + if (strcmp(method, "nop") == 0) { + return; + } + if (sni->item_is_menu && strcmp(method, "Activate") == 0) { + method = "ContextMenu"; + } + + if (strncmp(method, "Scroll", strlen("Scroll")) == 0) { + char dir = method[strlen("Scroll")]; + char *orientation = (dir = 'U' || dir == 'D') ? "vertical" : "horizontal"; + int sign = (dir == 'U' || dir == 'L') ? -1 : 1; + + sd_bus_call_method_async(sni->tray->bus, NULL, sni->service, sni->path, + sni->interface, "Scroll", NULL, NULL, "is", delta*sign, orientation); + } else { + sd_bus_call_method_async(sni->tray->bus, NULL, sni->service, sni->path, + sni->interface, method, NULL, NULL, "ii", x, y); + } +} + +static int cmp_sni_id(const void *item, const void *cmp_to) { + const struct swaybar_sni *sni = item; + return strcmp(sni->watcher_id, cmp_to); +} + +static enum hotspot_event_handling icon_hotspot_callback( + struct swaybar_output *output, struct swaybar_hotspot *hotspot, + int x, int y, enum x11_button button, void *data) { + wlr_log(WLR_DEBUG, "Clicked on %s", (char *)data); + + struct swaybar_tray *tray = output->bar->tray; + int idx = list_seq_find(tray->items, cmp_sni_id, data); + + if (idx != -1) { + struct swaybar_sni *sni = tray->items->items[idx]; + // guess global position since wayland doesn't expose it + struct swaybar_config *config = tray->bar->config; + int global_x = output->output_x + config->gaps.left + x; + bool top_bar = config->position & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; + int global_y = output->output_y + (top_bar ? config->gaps.top + y: + (int) output->output_height - config->gaps.bottom - y); + + wlr_log(WLR_DEBUG, "Guessing click position at (%d, %d)", global_x, global_y); + handle_click(sni, global_x, global_y, button, 1); // TODO get delta from event + return HOTSPOT_IGNORE; + } else { + wlr_log(WLR_DEBUG, "but it doesn't exist"); + } + + return HOTSPOT_PROCESS; +} + +uint32_t render_sni(cairo_t *cairo, struct swaybar_output *output, double *x, + struct swaybar_sni *sni) { + uint32_t height = output->height * output->scale; + int padding = output->bar->config->tray_padding; + int ideal_size = height - 2*padding; + if ((ideal_size < sni->min_size || ideal_size > sni->max_size) && sni_ready(sni)) { + bool icon_found = false; + char *icon_name = sni->status[0] == 'N' ? + sni->attention_icon_name : sni->icon_name; + if (icon_name) { + char *icon_path = find_icon(sni->tray->themes, sni->tray->basedirs, + icon_name, ideal_size, output->bar->config->icon_theme, + &sni->min_size, &sni->max_size); + if (!icon_path && sni->icon_theme_path) { + icon_path = find_icon_in_dir(icon_name, sni->icon_theme_path, + &sni->min_size, &sni->max_size); + } + if (icon_path) { + cairo_surface_destroy(sni->icon); + sni->icon = load_background_image(icon_path); + free(icon_path); + icon_found = true; + } + } + if (!icon_found) { + list_t *pixmaps = sni->status[0] == 'N' ? + sni->attention_icon_pixmap : sni->icon_pixmap; + if (pixmaps) { + int idx = -1; + unsigned smallest_error = -1; // UINT_MAX + for (int i = 0; i < pixmaps->length; ++i) { + struct swaybar_pixmap *pixmap = pixmaps->items[i]; + unsigned error = (ideal_size - pixmap->size) * + (ideal_size < pixmap->size ? -1 : 1); + if (error < smallest_error) { + smallest_error = error; + idx = i; + } + } + struct swaybar_pixmap *pixmap = pixmaps->items[idx]; + cairo_surface_destroy(sni->icon); + sni->icon = cairo_image_surface_create_for_data(pixmap->pixels, + CAIRO_FORMAT_ARGB32, pixmap->size, pixmap->size, + cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, pixmap->size)); + } + } + } + + int icon_size; + cairo_surface_t *icon; + if (sni->icon) { + int actual_size = cairo_image_surface_get_height(sni->icon); + icon_size = actual_size < ideal_size ? + actual_size*(ideal_size/actual_size) : ideal_size; + icon = cairo_image_surface_scale(sni->icon, icon_size, icon_size); + } else { // draw a :( + icon_size = ideal_size*0.8; + icon = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, icon_size, icon_size); + cairo_t *cairo_icon = cairo_create(icon); + cairo_set_source_u32(cairo_icon, 0xFF0000FF); + cairo_translate(cairo_icon, icon_size/2, icon_size/2); + cairo_scale(cairo_icon, icon_size/2, icon_size/2); + cairo_arc(cairo_icon, 0, 0, 1, 0, 7); + cairo_fill(cairo_icon); + cairo_set_operator(cairo_icon, CAIRO_OPERATOR_CLEAR); + cairo_arc(cairo_icon, 0.35, -0.3, 0.1, 0, 7); + cairo_fill(cairo_icon); + cairo_arc(cairo_icon, -0.35, -0.3, 0.1, 0, 7); + cairo_fill(cairo_icon); + cairo_arc(cairo_icon, 0, 0.75, 0.5, 3.71238898038469, 5.71238898038469); + cairo_set_line_width(cairo_icon, 0.1); + cairo_stroke(cairo_icon); + cairo_destroy(cairo_icon); + } + + int padded_size = icon_size + 2*padding; + *x -= padded_size; + int y = floor((height - padded_size) / 2.0); + + cairo_operator_t op = cairo_get_operator(cairo); + cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); + cairo_set_source_surface(cairo, icon, *x + padding, y + padding); + cairo_rectangle(cairo, *x, y, padded_size, padded_size); + cairo_fill(cairo); + cairo_set_operator(cairo, op); + + cairo_surface_destroy(icon); + + struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot)); + hotspot->x = *x; + hotspot->y = 0; + hotspot->width = height; + hotspot->height = height; + hotspot->callback = icon_hotspot_callback; + hotspot->destroy = free; + hotspot->data = strdup(sni->watcher_id); + wl_list_insert(&output->hotspots, &hotspot->link); + + return output->height; +} diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c new file mode 100644 index 00000000..d5d0c84e --- /dev/null +++ b/swaybar/tray/tray.c @@ -0,0 +1,131 @@ +#include <cairo.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include "swaybar/config.h" +#include "swaybar/bar.h" +#include "swaybar/tray/icon.h" +#include "swaybar/tray/host.h" +#include "swaybar/tray/item.h" +#include "swaybar/tray/tray.h" +#include "swaybar/tray/watcher.h" +#include "list.h" +#include "log.h" + +static int handle_lost_watcher(sd_bus_message *msg, + void *data, sd_bus_error *error) { + char *service, *old_owner, *new_owner; + int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse owner change message: %s", strerror(-ret)); + return ret; + } + + if (!*new_owner) { + struct swaybar_tray *tray = data; + if (strcmp(service, "org.freedesktop.StatusNotifierWatcher") == 0) { + tray->watcher_xdg = create_watcher("freedesktop", tray->bus); + } else if (strcmp(service, "org.kde.StatusNotifierWatcher") == 0) { + tray->watcher_kde = create_watcher("kde", tray->bus); + } + } + + return 0; +} + +struct swaybar_tray *create_tray(struct swaybar *bar) { + wlr_log(WLR_DEBUG, "Initializing tray"); + + sd_bus *bus; + int ret = sd_bus_open_user(&bus); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to connect to user bus: %s", strerror(-ret)); + return NULL; + } + + struct swaybar_tray *tray = calloc(1, sizeof(struct swaybar_tray)); + if (!tray) { + return NULL; + } + tray->bar = bar; + tray->bus = bus; + tray->fd = sd_bus_get_fd(tray->bus); + + tray->watcher_xdg = create_watcher("freedesktop", tray->bus); + tray->watcher_kde = create_watcher("kde", tray->bus); + + ret = sd_bus_match_signal(bus, NULL, "org.freedesktop.DBus", + "/org/freedesktop/DBus", "org.freedesktop.DBus", + "NameOwnerChanged", handle_lost_watcher, tray); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s", + strerror(-ret)); + } + + tray->items = create_list(); + + init_host(&tray->host_xdg, "freedesktop", tray); + init_host(&tray->host_kde, "kde", tray); + + init_themes(&tray->themes, &tray->basedirs); + + return tray; +} + +void destroy_tray(struct swaybar_tray *tray) { + if (!tray) { + return; + } + finish_host(&tray->host_xdg); + finish_host(&tray->host_kde); + for (int i = 0; i < tray->items->length; ++i) { + destroy_sni(tray->items->items[i]); + } + list_free(tray->items); + destroy_watcher(tray->watcher_xdg); + destroy_watcher(tray->watcher_kde); + sd_bus_flush_close_unref(tray->bus); + finish_themes(tray->themes, tray->basedirs); + free(tray); +} + +void tray_in(int fd, short mask, void *data) { + sd_bus *bus = data; + int ret; + while ((ret = sd_bus_process(bus, NULL)) > 0) { + // This space intentionally left blank + } + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to process bus: %s", strerror(-ret)); + } +} + +static int cmp_output(const void *item, const void *cmp_to) { + const struct swaybar_output *output = cmp_to; + if (output->identifier && strcmp(item, output->identifier) == 0) { + return 0; + } + return strcmp(item, output->name); +} + +uint32_t render_tray(cairo_t *cairo, struct swaybar_output *output, double *x) { + struct swaybar_config *config = output->bar->config; + if (config->tray_outputs) { + if (list_seq_find(config->tray_outputs, cmp_output, output) == -1) { + return 0; + } + } // else display on all + + if ((int) output->height*output->scale <= 2*config->tray_padding) { + return 2*config->tray_padding + 1; + } + + uint32_t max_height = 0; + struct swaybar_tray *tray = output->bar->tray; + for (int i = 0; i < tray->items->length; ++i) { + uint32_t h = render_sni(cairo, output, x, tray->items->items[i]); + max_height = h > max_height ? h : max_height; + } + + return max_height; +} diff --git a/swaybar/tray/watcher.c b/swaybar/tray/watcher.c new file mode 100644 index 00000000..198c6c85 --- /dev/null +++ b/swaybar/tray/watcher.c @@ -0,0 +1,212 @@ +#define _POSIX_C_SOURCE 200809L +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "list.h" +#include "log.h" +#include "swaybar/tray/watcher.h" + +static const char *obj_path = "/StatusNotifierWatcher"; + +static bool using_standard_protocol(struct swaybar_watcher *watcher) { + return watcher->interface[strlen("org.")] == 'f'; // freedesktop +} + +static int cmp_id(const void *item, const void *cmp_to) { + return strcmp(item, cmp_to); +} + +static int cmp_service(const void *item, const void *cmp_to) { + return strncmp(item, cmp_to, strlen(cmp_to)); +} + +static int handle_lost_service(sd_bus_message *msg, + void *data, sd_bus_error *error) { + char *service, *old_owner, *new_owner; + int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse owner change message: %s", strerror(-ret)); + return ret; + } + + if (!*new_owner) { + struct swaybar_watcher *watcher = data; + int idx = list_seq_find(watcher->items, + using_standard_protocol(watcher) ? cmp_id : cmp_service, service); + if (idx != -1) { + char *id = watcher->items->items[idx]; + wlr_log(WLR_DEBUG, "Unregistering Status Notifier Item '%s'", id); + list_del(watcher->items, idx); + sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface, + "StatusNotifierItemUnregistered", "s", id); + free(id); + } + + idx = list_seq_find(watcher->hosts, cmp_id, service); + if (idx != -1) { + wlr_log(WLR_DEBUG, "Unregistering Status Notifier Host '%s'", service); + free(watcher->hosts->items[idx]); + list_del(watcher->hosts, idx); + } + } + + return 0; +} + +static int register_sni(sd_bus_message *msg, void *data, sd_bus_error *error) { + char *service_or_path, *id; + int ret = sd_bus_message_read(msg, "s", &service_or_path); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse register SNI message: %s", strerror(-ret)); + return ret; + } + + struct swaybar_watcher *watcher = data; + if (using_standard_protocol(watcher)) { + id = strdup(service_or_path); + } else { + const char *service, *path; + if (service_or_path[0] == '/') { + service = sd_bus_message_get_sender(msg); + path = service_or_path; + } else { + service = service_or_path; + path = "/StatusNotifierItem"; + } + size_t id_len = snprintf(NULL, 0, "%s%s", service, path) + 1; + id = malloc(id_len); + snprintf(id, id_len, "%s%s", service, path); + } + + if (list_seq_find(watcher->items, cmp_id, id) == -1) { + wlr_log(WLR_DEBUG, "Registering Status Notifier Item '%s'", id); + list_add(watcher->items, id); + sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface, + "StatusNotifierItemRegistered", "s", id); + } else { + wlr_log(WLR_DEBUG, "Status Notifier Item '%s' already registered", id); + free(id); + } + + return sd_bus_reply_method_return(msg, ""); +} + +static int register_host(sd_bus_message *msg, void *data, sd_bus_error *error) { + char *service; + int ret = sd_bus_message_read(msg, "s", &service); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse register host message: %s", strerror(-ret)); + return ret; + } + + struct swaybar_watcher *watcher = data; + if (list_seq_find(watcher->hosts, cmp_id, service) == -1) { + wlr_log(WLR_DEBUG, "Registering Status Notifier Host '%s'", service); + list_add(watcher->hosts, strdup(service)); + sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface, + "StatusNotifierHostRegistered", "s", service); + } else { + wlr_log(WLR_DEBUG, "Status Notifier Host '%s' already registered", service); + } + + return sd_bus_reply_method_return(msg, ""); +} + +static int get_registered_snis(sd_bus *bus, const char *obj_path, + const char *interface, const char *property, sd_bus_message *reply, + void *data, sd_bus_error *error) { + struct swaybar_watcher *watcher = data; + list_add(watcher->items, NULL); // strv expects NULL-terminated string array + int ret = sd_bus_message_append_strv(reply, (char **)watcher->items->items); + list_del(watcher->items, watcher->items->length - 1); + return ret; +} + +static int is_host_registered(sd_bus *bus, const char *obj_path, + const char *interface, const char *property, sd_bus_message *reply, + void *data, sd_bus_error *error) { + struct swaybar_watcher *watcher = data; + int val = watcher->hosts->length > 0; // dbus expects int rather than bool + return sd_bus_message_append_basic(reply, 'b', &val); +} + +static const sd_bus_vtable watcher_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("RegisterStatusNotifierItem", "s", "", register_sni, + SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RegisterStatusNotifierHost", "s", "", register_host, + SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_PROPERTY("RegisteredStatusNotifierItems", "as", get_registered_snis, + 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("IsStatusNotifierHostRegistered", "b", is_host_registered, + 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("ProtocolVersion", "i", NULL, + offsetof(struct swaybar_watcher, version), + SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_SIGNAL("StatusNotifierItemRegistered", "s", 0), + SD_BUS_SIGNAL("StatusNotifierItemUnregistered", "s", 0), + SD_BUS_SIGNAL("StatusNotifierHostRegistered", NULL, 0), + SD_BUS_VTABLE_END +}; + +struct swaybar_watcher *create_watcher(char *protocol, sd_bus *bus) { + struct swaybar_watcher *watcher = + calloc(1, sizeof(struct swaybar_watcher)); + if (!watcher) { + return NULL; + } + + size_t len = snprintf(NULL, 0, "org.%s.StatusNotifierWatcher", protocol) + 1; + watcher->interface = malloc(len); + snprintf(watcher->interface, len, "org.%s.StatusNotifierWatcher", protocol); + + sd_bus_slot *signal_slot = NULL, *vtable_slot = NULL; + int ret = sd_bus_add_object_vtable(bus, &vtable_slot, obj_path, + watcher->interface, watcher_vtable, watcher); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to add object vtable: %s", strerror(-ret)); + goto error; + } + + ret = sd_bus_match_signal(bus, &signal_slot, "org.freedesktop.DBus", + "/org/freedesktop/DBus", "org.freedesktop.DBus", + "NameOwnerChanged", handle_lost_service, watcher); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s", + strerror(-ret)); + goto error; + } + + ret = sd_bus_request_name(bus, watcher->interface, 0); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to acquire service name: %s", strerror(-ret)); + goto error; + } + + sd_bus_slot_set_floating(signal_slot, 0); + sd_bus_slot_set_floating(vtable_slot, 0); + + watcher->bus = bus; + watcher->hosts = create_list(); + watcher->items = create_list(); + watcher->version = 0; + wlr_log(WLR_DEBUG, "Registered %s", watcher->interface); + return watcher; +error: + sd_bus_slot_unref(signal_slot); + sd_bus_slot_unref(vtable_slot); + destroy_watcher(watcher); + return NULL; +} + +void destroy_watcher(struct swaybar_watcher *watcher) { + if (!watcher) { + return; + } + list_free_items_and_destroy(watcher->hosts); + list_free_items_and_destroy(watcher->items); + free(watcher->interface); + free(watcher); +} |