aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Drew DeVault <sir@cmpwn.com>2018-12-31 15:43:23 -0500
committerLibravatar GitHub <noreply@github.com>2018-12-31 15:43:23 -0500
commit3d7c20f062bebe39199d3872e5b64d5e8d61d100 (patch)
tree93b8ddb51873cae14db8d5f07db74ca96236f184
parentApply implicit fallback seat config (diff)
parentswaybar: use KDE's SNI IconThemePath property (diff)
downloadsway-3d7c20f062bebe39199d3872e5b64d5e8d61d100.tar.gz
sway-3d7c20f062bebe39199d3872e5b64d5e8d61d100.tar.zst
sway-3d7c20f062bebe39199d3872e5b64d5e8d61d100.zip
Merge pull request #3249 from ianyfan/tray
Swaybar tray
-rw-r--r--include/sway/commands.h4
-rw-r--r--include/sway/config.h8
-rw-r--r--include/swaybar/bar.h12
-rw-r--r--include/swaybar/config.h9
-rw-r--r--include/swaybar/tray/host.h17
-rw-r--r--include/swaybar/tray/icon.h52
-rw-r--r--include/swaybar/tray/item.h45
-rw-r--r--include/swaybar/tray/sni.h82
-rw-r--r--include/swaybar/tray/tray.h40
-rw-r--r--include/swaybar/tray/watcher.h18
-rw-r--r--meson.build1
-rw-r--r--meson_options.txt1
-rw-r--r--sway/commands/bar.c4
-rw-r--r--sway/commands/bar/activate_button.c8
-rw-r--r--sway/commands/bar/context_button.c8
-rw-r--r--sway/commands/bar/icon_theme.c25
-rw-r--r--sway/commands/bar/secondary_button.c8
-rw-r--r--sway/commands/bar/tray_bindsym.c55
-rw-r--r--sway/commands/bar/tray_output.c39
-rw-r--r--sway/commands/bar/tray_padding.c37
-rw-r--r--sway/config/bar.c9
-rw-r--r--sway/ipc-json.c37
-rw-r--r--sway/meson.build4
-rw-r--r--sway/sway-bar.5.scd25
-rw-r--r--swaybar/bar.c28
-rw-r--r--swaybar/config.c12
-rw-r--r--swaybar/ipc.c35
-rw-r--r--swaybar/meson.build44
-rw-r--r--swaybar/render.c9
-rw-r--r--swaybar/tray/host.c210
-rw-r--r--swaybar/tray/icon.c462
-rw-r--r--swaybar/tray/item.c443
-rw-r--r--swaybar/tray/tray.c127
-rw-r--r--swaybar/tray/watcher.c212
34 files changed, 1965 insertions, 165 deletions
diff --git a/include/sway/commands.h b/include/sway/commands.h
index 7bee2538..0e2d7931 100644
--- a/include/sway/commands.h
+++ b/include/sway/commands.h
@@ -182,11 +182,9 @@ sway_cmd cmd_workspace;
182sway_cmd cmd_ws_auto_back_and_forth; 182sway_cmd cmd_ws_auto_back_and_forth;
183sway_cmd cmd_workspace_layout; 183sway_cmd cmd_workspace_layout;
184 184
185sway_cmd bar_cmd_activate_button;
186sway_cmd bar_cmd_binding_mode_indicator; 185sway_cmd bar_cmd_binding_mode_indicator;
187sway_cmd bar_cmd_bindsym; 186sway_cmd bar_cmd_bindsym;
188sway_cmd bar_cmd_colors; 187sway_cmd bar_cmd_colors;
189sway_cmd bar_cmd_context_button;
190sway_cmd bar_cmd_font; 188sway_cmd bar_cmd_font;
191sway_cmd bar_cmd_gaps; 189sway_cmd bar_cmd_gaps;
192sway_cmd bar_cmd_mode; 190sway_cmd bar_cmd_mode;
@@ -197,13 +195,13 @@ sway_cmd bar_cmd_hidden_state;
197sway_cmd bar_cmd_icon_theme; 195sway_cmd bar_cmd_icon_theme;
198sway_cmd bar_cmd_id; 196sway_cmd bar_cmd_id;
199sway_cmd bar_cmd_position; 197sway_cmd bar_cmd_position;
200sway_cmd bar_cmd_secondary_button;
201sway_cmd bar_cmd_separator_symbol; 198sway_cmd bar_cmd_separator_symbol;
202sway_cmd bar_cmd_status_command; 199sway_cmd bar_cmd_status_command;
203sway_cmd bar_cmd_pango_markup; 200sway_cmd bar_cmd_pango_markup;
204sway_cmd bar_cmd_strip_workspace_numbers; 201sway_cmd bar_cmd_strip_workspace_numbers;
205sway_cmd bar_cmd_strip_workspace_name; 202sway_cmd bar_cmd_strip_workspace_name;
206sway_cmd bar_cmd_swaybar_command; 203sway_cmd bar_cmd_swaybar_command;
204sway_cmd bar_cmd_tray_bindsym;
207sway_cmd bar_cmd_tray_output; 205sway_cmd bar_cmd_tray_output;
208sway_cmd bar_cmd_tray_padding; 206sway_cmd bar_cmd_tray_padding;
209sway_cmd bar_cmd_wrap_scroll; 207sway_cmd bar_cmd_wrap_scroll;
diff --git a/include/sway/config.h b/include/sway/config.h
index 86473e17..f604b054 100644
--- a/include/sway/config.h
+++ b/include/sway/config.h
@@ -6,6 +6,7 @@
6#include <time.h> 6#include <time.h>
7#include <wlr/types/wlr_box.h> 7#include <wlr/types/wlr_box.h>
8#include <xkbcommon/xkbcommon.h> 8#include <xkbcommon/xkbcommon.h>
9#include "../include/config.h"
9#include "list.h" 10#include "list.h"
10#include "swaynag.h" 11#include "swaynag.h"
11#include "tree/container.h" 12#include "tree/container.h"
@@ -253,6 +254,13 @@ struct bar_config {
253 char *binding_mode_bg; 254 char *binding_mode_bg;
254 char *binding_mode_text; 255 char *binding_mode_text;
255 } colors; 256 } colors;
257
258#if HAVE_TRAY
259 char *icon_theme;
260 const char *tray_bindings[10]; // mouse buttons 0-9
261 list_t *tray_outputs; // char *
262 int tray_padding;
263#endif
256}; 264};
257 265
258struct bar_binding { 266struct bar_binding {
diff --git a/include/swaybar/bar.h b/include/swaybar/bar.h
index 57c5114e..e377b8de 100644
--- a/include/swaybar/bar.h
+++ b/include/swaybar/bar.h
@@ -1,6 +1,7 @@
1#ifndef _SWAYBAR_BAR_H 1#ifndef _SWAYBAR_BAR_H
2#define _SWAYBAR_BAR_H 2#define _SWAYBAR_BAR_H
3#include <wayland-client.h> 3#include <wayland-client.h>
4#include "config.h"
4#include "input.h" 5#include "input.h"
5#include "pool-buffer.h" 6#include "pool-buffer.h"
6#include "wlr-layer-shell-unstable-v1-client-protocol.h" 7#include "wlr-layer-shell-unstable-v1-client-protocol.h"
@@ -8,6 +9,9 @@
8 9
9struct swaybar_config; 10struct swaybar_config;
10struct swaybar_output; 11struct swaybar_output;
12#if HAVE_TRAY
13struct swaybar_tray;
14#endif
11struct swaybar_workspace; 15struct swaybar_workspace;
12struct loop; 16struct loop;
13 17
@@ -38,6 +42,10 @@ struct swaybar {
38 int ipc_socketfd; 42 int ipc_socketfd;
39 43
40 struct wl_list outputs; // swaybar_output::link 44 struct wl_list outputs; // swaybar_output::link
45
46#if HAVE_TRAY
47 struct swaybar_tray *tray;
48#endif
41}; 49};
42 50
43struct swaybar_output { 51struct swaybar_output {
@@ -62,6 +70,8 @@ struct swaybar_output {
62 struct pool_buffer *current_buffer; 70 struct pool_buffer *current_buffer;
63 bool dirty; 71 bool dirty;
64 bool frame_scheduled; 72 bool frame_scheduled;
73
74 uint32_t output_height, output_width, output_x, output_y;
65}; 75};
66 76
67struct swaybar_workspace { 77struct swaybar_workspace {
@@ -78,6 +88,8 @@ bool bar_setup(struct swaybar *bar, const char *socket_path);
78void bar_run(struct swaybar *bar); 88void bar_run(struct swaybar *bar);
79void bar_teardown(struct swaybar *bar); 89void bar_teardown(struct swaybar *bar);
80 90
91void set_bar_dirty(struct swaybar *bar);
92
81/* 93/*
82 * Determines whether the bar should be visible and changes it to be so. 94 * Determines whether the bar should be visible and changes it to be so.
83 * If the current visibility of the bar is the different to what it should be, 95 * If the current visibility of the bar is the different to what it should be,
diff --git a/include/swaybar/config.h b/include/swaybar/config.h
index fd7c6ec4..1f6577bd 100644
--- a/include/swaybar/config.h
+++ b/include/swaybar/config.h
@@ -3,6 +3,7 @@
3#include <stdbool.h> 3#include <stdbool.h>
4#include <stdint.h> 4#include <stdint.h>
5#include <wayland-client.h> 5#include <wayland-client.h>
6#include "../include/config.h"
6#include "list.h" 7#include "list.h"
7#include "util.h" 8#include "util.h"
8 9
@@ -64,6 +65,14 @@ struct swaybar_config {
64 struct box_colors urgent_workspace; 65 struct box_colors urgent_workspace;
65 struct box_colors binding_mode; 66 struct box_colors binding_mode;
66 } colors; 67 } colors;
68
69#if HAVE_TRAY
70 char *icon_theme;
71 char *tray_bindings[10]; // mouse buttons 0-9
72 bool tray_hidden;
73 list_t *tray_outputs; // char *
74 int tray_padding;
75#endif
67}; 76};
68 77
69struct swaybar_config *init_config(void); 78struct swaybar_config *init_config(void);
diff --git a/include/swaybar/tray/host.h b/include/swaybar/tray/host.h
new file mode 100644
index 00000000..2d4cf82b
--- /dev/null
+++ b/include/swaybar/tray/host.h
@@ -0,0 +1,17 @@
1#ifndef _SWAYBAR_TRAY_HOST_H
2#define _SWAYBAR_TRAY_HOST_H
3
4#include <stdbool.h>
5
6struct swaybar_tray;
7
8struct swaybar_host {
9 struct swaybar_tray *tray;
10 char *service;
11 char *watcher_interface;
12};
13
14bool init_host(struct swaybar_host *host, char *protocol, struct swaybar_tray *tray);
15void finish_host(struct swaybar_host *host);
16
17#endif
diff --git a/include/swaybar/tray/icon.h b/include/swaybar/tray/icon.h
index 1cc6ff9c..7a6c400c 100644
--- a/include/swaybar/tray/icon.h
+++ b/include/swaybar/tray/icon.h
@@ -1,16 +1,44 @@
1#ifndef _SWAYBAR_ICON_H 1#ifndef _SWAYBAR_TRAY_ICON_H
2#define _SWAYBAR_ICON_H 2#define _SWAYBAR_TRAY_ICON_H
3 3
4#include <stdint.h> 4#include "list.h"
5#include <stdbool.h>
6#include <client/cairo.h>
7 5
8/** 6enum subdir_type {
9 * Returns the image found by `name` that is closest to `size` 7 THRESHOLD,
10 */ 8 SCALABLE,
11cairo_surface_t *find_icon(const char *name, int size); 9 FIXED
10};
11
12struct icon_theme_subdir {
13 char *name;
14 int size;
15 enum subdir_type type;
16 int max_size;
17 int min_size;
18 int threshold;
19};
20
21struct icon_theme {
22 char *name;
23 char *comment;
24 char *inherits;
25 list_t *directories; // char *
12 26
13/* Struct used internally only */ 27 char *dir;
14struct subdir; 28 list_t *subdirs; // struct icon_theme_subdir *
29};
30
31void init_themes(list_t **themes, list_t **basedirs);
32void finish_themes(list_t *themes, list_t *basedirs);
33
34/*
35 * Finds an icon of a specified size given a list of themes and base directories.
36 * If the icon is found, the pointers min_size & max_size are set to minimum &
37 * maximum size that the icon can be scaled to, respectively.
38 * Returns: path of icon (which should be freed), or NULL if the icon is not found.
39 */
40char *find_icon(list_t *themes, list_t *basedirs, char *name, int size,
41 char *theme, int *min_size, int *max_size);
42char *find_icon_in_dir(char *name, char *dir, int *min_size, int *max_size);
15 43
16#endif /* _SWAYBAR_ICON_H */ 44#endif
diff --git a/include/swaybar/tray/item.h b/include/swaybar/tray/item.h
new file mode 100644
index 00000000..9bba7951
--- /dev/null
+++ b/include/swaybar/tray/item.h
@@ -0,0 +1,45 @@
1#ifndef _SWAYBAR_TRAY_ITEM_H
2#define _SWAYBAR_TRAY_ITEM_H
3
4#include <cairo.h>
5#include <stdbool.h>
6#include <stdint.h>
7#include "swaybar/tray/tray.h"
8#include "list.h"
9
10struct swaybar_output;
11
12struct swaybar_pixmap {
13 int size;
14 unsigned char pixels[];
15};
16
17struct swaybar_sni {
18 // icon properties
19 struct swaybar_tray *tray;
20 cairo_surface_t *icon;
21 int min_size;
22 int max_size;
23
24 // dbus properties
25 char *watcher_id;
26 char *service;
27 char *path;
28 char *interface;
29
30 char *status;
31 char *icon_name;
32 list_t *icon_pixmap; // struct swaybar_pixmap *
33 char *attention_icon_name;
34 list_t *attention_icon_pixmap; // struct swaybar_pixmap *
35 bool item_is_menu;
36 char *menu;
37 char *icon_theme_path; // non-standard KDE property
38};
39
40struct swaybar_sni *create_sni(char *id, struct swaybar_tray *tray);
41void destroy_sni(struct swaybar_sni *sni);
42uint32_t render_sni(cairo_t *cairo, struct swaybar_output *output, double *x,
43 struct swaybar_sni *sni);
44
45#endif
diff --git a/include/swaybar/tray/sni.h b/include/swaybar/tray/sni.h
deleted file mode 100644
index c2544e2a..00000000
--- a/include/swaybar/tray/sni.h
+++ /dev/null
@@ -1,82 +0,0 @@
1#ifndef _SWAYBAR_SNI_H
2#define _SWAYBAR_SNI_H
3
4#include <stdbool.h>
5#include <client/cairo.h>
6
7struct StatusNotifierItem {
8 /* Name registered to sni watcher */
9 char *name;
10 /* Unique bus name, needed for determining signal origins */
11 char *unique_name;
12 bool kde_special_snowflake;
13
14 cairo_surface_t *image;
15 bool dirty;
16};
17
18/* Each output holds an sni_icon_ref of each item to render */
19struct sni_icon_ref {
20 cairo_surface_t *icon;
21 struct StatusNotifierItem *ref;
22};
23
24struct sni_icon_ref *sni_icon_ref_create(struct StatusNotifierItem *item,
25 int height);
26
27void sni_icon_ref_free(struct sni_icon_ref *sni_ref);
28
29/**
30 * Will return a new item and get its icon. (see warning below)
31 * May return `NULL` if `name` is not valid.
32 */
33struct StatusNotifierItem *sni_create(const char *name);
34
35/**
36 * `item` must be a struct StatusNotifierItem *
37 * `str` must be a NUL terminated char *
38 *
39 * Returns 0 if `item` has a name of `str`
40 */
41int sni_str_cmp(const void *item, const void *str);
42
43/**
44 * Returns 0 if `item` has a unique name of `str` or if
45 * `item->unique_name == NULL`
46 */
47int sni_uniq_cmp(const void *item, const void *str);
48
49/**
50 * Gets an icon for the given item if found.
51 *
52 * XXX
53 * This function keeps a reference to the item until it gets responses, make
54 * sure that the reference and item are valid during this time.
55 */
56void get_icon(struct StatusNotifierItem *item);
57
58/**
59 * Calls the "activate" method on the given StatusNotifierItem
60 *
61 * x and y should be where the item was clicked
62 */
63void sni_activate(struct StatusNotifierItem *item, uint32_t x, uint32_t y);
64
65/**
66 * Asks the item to draw a context menu at the given x and y coords
67 */
68void sni_context_menu(struct StatusNotifierItem *item, uint32_t x, uint32_t y);
69
70/**
71 * Calls the "secondary activate" method on the given StatusNotifierItem
72 *
73 * x and y should be where the item was clicked
74 */
75void sni_secondary(struct StatusNotifierItem *item, uint32_t x, uint32_t y);
76
77/**
78 * Deconstructs `item`
79 */
80void sni_free(struct StatusNotifierItem *item);
81
82#endif /* _SWAYBAR_SNI_H */
diff --git a/include/swaybar/tray/tray.h b/include/swaybar/tray/tray.h
new file mode 100644
index 00000000..8958b69a
--- /dev/null
+++ b/include/swaybar/tray/tray.h
@@ -0,0 +1,40 @@
1#ifndef _SWAYBAR_TRAY_TRAY_H
2#define _SWAYBAR_TRAY_TRAY_H
3
4#include "config.h"
5#ifdef HAVE_SYSTEMD
6#include <systemd/sd-bus.h>
7#elif HAVE_ELOGIND
8#include <elogind/sd-bus.h>
9#endif
10#include <cairo.h>
11#include <stdint.h>
12#include "swaybar/tray/host.h"
13#include "list.h"
14
15struct swaybar;
16struct swaybar_output;
17struct swaybar_watcher;
18
19struct swaybar_tray {
20 struct swaybar *bar;
21
22 int fd;
23 sd_bus *bus;
24
25 struct swaybar_host host_xdg;
26 struct swaybar_host host_kde;
27 list_t *items; // struct swaybar_sni *
28 struct swaybar_watcher *watcher_xdg;
29 struct swaybar_watcher *watcher_kde;
30
31 list_t *basedirs; // char *
32 list_t *themes; // struct swaybar_theme *
33};
34
35struct swaybar_tray *create_tray(struct swaybar *bar);
36void destroy_tray(struct swaybar_tray *tray);
37void tray_in(int fd, short mask, void *data);
38uint32_t render_tray(cairo_t *cairo, struct swaybar_output *output, double *x);
39
40#endif
diff --git a/include/swaybar/tray/watcher.h b/include/swaybar/tray/watcher.h
new file mode 100644
index 00000000..8f276da8
--- /dev/null
+++ b/include/swaybar/tray/watcher.h
@@ -0,0 +1,18 @@
1#ifndef _SWAYBAR_TRAY_WATCHER_H
2#define _SWAYBAR_TRAY_WATCHER_H
3
4#include "swaybar/tray/tray.h"
5#include "list.h"
6
7struct swaybar_watcher {
8 char *interface;
9 sd_bus *bus;
10 list_t *hosts;
11 list_t *items;
12 int version;
13};
14
15struct swaybar_watcher *create_watcher(char *protocol, sd_bus *bus);
16void destroy_watcher(struct swaybar_watcher *watcher);
17
18#endif
diff --git a/meson.build b/meson.build
index e1e0fc2d..981f74ac 100644
--- a/meson.build
+++ b/meson.build
@@ -66,6 +66,7 @@ endif
66conf_data.set10('HAVE_GDK_PIXBUF', gdk_pixbuf.found()) 66conf_data.set10('HAVE_GDK_PIXBUF', gdk_pixbuf.found())
67conf_data.set10('HAVE_SYSTEMD', systemd.found()) 67conf_data.set10('HAVE_SYSTEMD', systemd.found())
68conf_data.set10('HAVE_ELOGIND', elogind.found()) 68conf_data.set10('HAVE_ELOGIND', elogind.found())
69conf_data.set10('HAVE_TRAY', get_option('enable-tray') and (systemd.found() or elogind.found()))
69 70
70if not systemd.found() and not elogind.found() 71if not systemd.found() and not elogind.found()
71 warning('The sway binary must be setuid when compiled without (e)logind') 72 warning('The sway binary must be setuid when compiled without (e)logind')
diff --git a/meson_options.txt b/meson_options.txt
index 2db852fc..4640618e 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -6,3 +6,4 @@ option('zsh-completions', type: 'boolean', value: true, description: 'Install zs
6option('bash-completions', type: 'boolean', value: true, description: 'Install bash shell completions.') 6option('bash-completions', type: 'boolean', value: true, description: 'Install bash shell completions.')
7option('fish-completions', type: 'boolean', value: true, description: 'Install fish shell completions.') 7option('fish-completions', type: 'boolean', value: true, description: 'Install fish shell completions.')
8option('enable-xwayland', type: 'boolean', value: true, description: 'Enable support for X11 applications') 8option('enable-xwayland', type: 'boolean', value: true, description: 'Enable support for X11 applications')
9option('enable-tray', type: 'boolean', value: false, description: 'Enable support for swaybar tray')
diff --git a/sway/commands/bar.c b/sway/commands/bar.c
index 0cf94907..507ee10a 100644
--- a/sway/commands/bar.c
+++ b/sway/commands/bar.c
@@ -8,11 +8,9 @@
8 8
9// Must be in alphabetical order for bsearch 9// Must be in alphabetical order for bsearch
10static struct cmd_handler bar_handlers[] = { 10static struct cmd_handler bar_handlers[] = {
11 { "activate_button", bar_cmd_activate_button },
12 { "binding_mode_indicator", bar_cmd_binding_mode_indicator }, 11 { "binding_mode_indicator", bar_cmd_binding_mode_indicator },
13 { "bindsym", bar_cmd_bindsym }, 12 { "bindsym", bar_cmd_bindsym },
14 { "colors", bar_cmd_colors }, 13 { "colors", bar_cmd_colors },
15 { "context_button", bar_cmd_context_button },
16 { "font", bar_cmd_font }, 14 { "font", bar_cmd_font },
17 { "gaps", bar_cmd_gaps }, 15 { "gaps", bar_cmd_gaps },
18 { "height", bar_cmd_height }, 16 { "height", bar_cmd_height },
@@ -23,11 +21,11 @@ static struct cmd_handler bar_handlers[] = {
23 { "output", bar_cmd_output }, 21 { "output", bar_cmd_output },
24 { "pango_markup", bar_cmd_pango_markup }, 22 { "pango_markup", bar_cmd_pango_markup },
25 { "position", bar_cmd_position }, 23 { "position", bar_cmd_position },
26 { "secondary_button", bar_cmd_secondary_button },
27 { "separator_symbol", bar_cmd_separator_symbol }, 24 { "separator_symbol", bar_cmd_separator_symbol },
28 { "status_command", bar_cmd_status_command }, 25 { "status_command", bar_cmd_status_command },
29 { "strip_workspace_name", bar_cmd_strip_workspace_name }, 26 { "strip_workspace_name", bar_cmd_strip_workspace_name },
30 { "strip_workspace_numbers", bar_cmd_strip_workspace_numbers }, 27 { "strip_workspace_numbers", bar_cmd_strip_workspace_numbers },
28 { "tray_bindsym", bar_cmd_tray_bindsym },
31 { "tray_output", bar_cmd_tray_output }, 29 { "tray_output", bar_cmd_tray_output },
32 { "tray_padding", bar_cmd_tray_padding }, 30 { "tray_padding", bar_cmd_tray_padding },
33 { "workspace_buttons", bar_cmd_workspace_buttons }, 31 { "workspace_buttons", bar_cmd_workspace_buttons },
diff --git a/sway/commands/bar/activate_button.c b/sway/commands/bar/activate_button.c
deleted file mode 100644
index 7310e7ec..00000000
--- a/sway/commands/bar/activate_button.c
+++ /dev/null
@@ -1,8 +0,0 @@
1#include <stdlib.h>
2#include "sway/commands.h"
3#include "log.h"
4
5struct cmd_results *bar_cmd_activate_button(int argc, char **argv) {
6 // TODO TRAY
7 return cmd_results_new(CMD_INVALID, "activate_button", "TODO TRAY");
8}
diff --git a/sway/commands/bar/context_button.c b/sway/commands/bar/context_button.c
deleted file mode 100644
index 3b76885a..00000000
--- a/sway/commands/bar/context_button.c
+++ /dev/null
@@ -1,8 +0,0 @@
1#include <stdlib.h>
2#include "sway/commands.h"
3#include "log.h"
4
5struct cmd_results *bar_cmd_context_button(int argc, char **argv) {
6 // TODO TRAY
7 return cmd_results_new(CMD_INVALID, "context_button", "TODO TRAY");
8}
diff --git a/sway/commands/bar/icon_theme.c b/sway/commands/bar/icon_theme.c
index 0e30409b..9d3b6040 100644
--- a/sway/commands/bar/icon_theme.c
+++ b/sway/commands/bar/icon_theme.c
@@ -1,7 +1,28 @@
1#define _POSIX_C_SOURCE 200809L
1#include <string.h> 2#include <string.h>
3#include "config.h"
2#include "sway/commands.h" 4#include "sway/commands.h"
5#include "sway/config.h"
6#include "log.h"
3 7
4struct cmd_results *bar_cmd_icon_theme(int argc, char **argv) { 8struct cmd_results *bar_cmd_icon_theme(int argc, char **argv) {
5 // TODO TRAY 9#if HAVE_TRAY
6 return cmd_results_new(CMD_INVALID, "icon_theme", "TODO TRAY"); 10 struct cmd_results *error = NULL;
11 if ((error = checkarg(argc, "icon_theme", EXPECTED_EQUAL_TO, 1))) {
12 return error;
13 }
14
15 if (!config->current_bar) {
16 return cmd_results_new(CMD_FAILURE, "tray_padding", "No bar defined.");
17 }
18
19 wlr_log(WLR_DEBUG, "[Bar %s] Setting icon theme to %s",
20 config->current_bar->id, argv[0]);
21 free(config->current_bar->icon_theme);
22 config->current_bar->icon_theme = strdup(argv[0]);
23 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
24#else
25 return cmd_results_new(CMD_INVALID, "icon_theme",
26 "Sway has been compiled without tray support");
27#endif
7} 28}
diff --git a/sway/commands/bar/secondary_button.c b/sway/commands/bar/secondary_button.c
deleted file mode 100644
index 449124cb..00000000
--- a/sway/commands/bar/secondary_button.c
+++ /dev/null
@@ -1,8 +0,0 @@
1#include <stdlib.h>
2#include "sway/commands.h"
3#include "log.h"
4
5struct cmd_results *bar_cmd_secondary_button(int argc, char **argv) {
6 // TODO TRAY
7 return cmd_results_new(CMD_INVALID, "secondary_button", "TODO TRAY");
8}
diff --git a/sway/commands/bar/tray_bindsym.c b/sway/commands/bar/tray_bindsym.c
new file mode 100644
index 00000000..ad413446
--- /dev/null
+++ b/sway/commands/bar/tray_bindsym.c
@@ -0,0 +1,55 @@
1#include <strings.h>
2#include "config.h"
3#include "sway/commands.h"
4#include "sway/config.h"
5#include "log.h"
6
7struct cmd_results *bar_cmd_tray_bindsym(int argc, char **argv) {
8#if HAVE_TRAY
9 struct cmd_results *error = NULL;
10 if ((error = checkarg(argc, "tray_bindsym", EXPECTED_EQUAL_TO, 2))) {
11 return error;
12 }
13
14 if (!config->current_bar) {
15 return cmd_results_new(CMD_FAILURE, "tray_bindsym", "No bar defined.");
16 }
17
18 int button = 0;
19 if (strncasecmp(argv[0], "button", strlen("button")) == 0 &&
20 strlen(argv[0]) == strlen("button0")) {
21 button = argv[0][strlen("button")] - '0';
22 }
23 if (button < 1 || button > 9) {
24 return cmd_results_new(CMD_FAILURE, "tray_bindsym",
25 "[Bar %s] Only buttons 1 to 9 are supported",
26 config->current_bar->id);
27 }
28
29 static const char *commands[] = {
30 "ContextMenu",
31 "Activate",
32 "SecondaryActivate",
33 "ScrollDown",
34 "ScrollLeft",
35 "ScrollRight",
36 "ScrollUp",
37 "nop"
38 };
39
40 for (size_t i = 0; i < sizeof(commands) / sizeof(commands[0]); ++i) {
41 if (strcasecmp(argv[1], commands[i]) == 0) {
42 wlr_log(WLR_DEBUG, "[Bar %s] Binding button %d to %s",
43 config->current_bar->id, button, commands[i]);
44 config->current_bar->tray_bindings[button] = commands[i];
45 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
46 }
47 }
48
49 return cmd_results_new(CMD_INVALID, "tray_bindsym",
50 "[Bar %s] Invalid command %s", config->current_bar->id, argv[1]);
51#else
52 return cmd_results_new(CMD_INVALID, "tray_bindsym",
53 "Sway has been compiled without tray support");
54#endif
55}
diff --git a/sway/commands/bar/tray_output.c b/sway/commands/bar/tray_output.c
index e6c77128..a1169c20 100644
--- a/sway/commands/bar/tray_output.c
+++ b/sway/commands/bar/tray_output.c
@@ -1,7 +1,42 @@
1#define _POSIX_C_SOURCE 200809L
1#include <string.h> 2#include <string.h>
3#include "config.h"
2#include "sway/commands.h" 4#include "sway/commands.h"
5#include "sway/config.h"
6#include "list.h"
7#include "log.h"
3 8
4struct cmd_results *bar_cmd_tray_output(int argc, char **argv) { 9struct cmd_results *bar_cmd_tray_output(int argc, char **argv) {
5 // TODO TRAY 10#if HAVE_TRAY
6 return cmd_results_new(CMD_INVALID, "tray_output", "TODO TRAY"); 11 struct cmd_results *error = NULL;
12 if ((error = checkarg(argc, "tray_output", EXPECTED_EQUAL_TO, 1))) {
13 return error;
14 }
15
16 if (!config->current_bar) {
17 return cmd_results_new(CMD_FAILURE, "tray_output", "No bar defined.");
18 }
19
20 list_t *outputs = config->current_bar->tray_outputs;
21 if (!outputs) {
22 config->current_bar->tray_outputs = outputs = create_list();
23 }
24
25 if (strcmp(argv[0], "none") == 0) {
26 wlr_log(WLR_DEBUG, "Hiding tray on bar: %s", config->current_bar->id);
27 for (int i = 0; i < outputs->length; ++i) {
28 free(outputs->items[i]);
29 }
30 outputs->length = 0;
31 } else {
32 wlr_log(WLR_DEBUG, "Showing tray on output '%s' for bar: %s", argv[0],
33 config->current_bar->id);
34 }
35 list_add(outputs, strdup(argv[0]));
36
37 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
38#else
39 return cmd_results_new(CMD_INVALID, "tray_output",
40 "Sway has been compiled without tray support");
41#endif
7} 42}
diff --git a/sway/commands/bar/tray_padding.c b/sway/commands/bar/tray_padding.c
index 91c56f19..eb795b00 100644
--- a/sway/commands/bar/tray_padding.c
+++ b/sway/commands/bar/tray_padding.c
@@ -1,9 +1,42 @@
1#include <stdlib.h> 1#include <stdlib.h>
2#include <strings.h> 2#include <strings.h>
3#include "config.h"
3#include "sway/commands.h" 4#include "sway/commands.h"
5#include "sway/config.h"
4#include "log.h" 6#include "log.h"
5 7
6struct cmd_results *bar_cmd_tray_padding(int argc, char **argv) { 8struct cmd_results *bar_cmd_tray_padding(int argc, char **argv) {
7 // TODO TRAY 9#if HAVE_TRAY
8 return cmd_results_new(CMD_INVALID, "tray_padding", "TODO TRAY"); 10 struct cmd_results *error = NULL;
11 if ((error = checkarg(argc, "tray_padding", EXPECTED_AT_LEAST, 1))) {
12 return error;
13 }
14 if ((error = checkarg(argc, "tray_padding", EXPECTED_AT_MOST, 2))) {
15 return error;
16 }
17
18 if (!config->current_bar) {
19 return cmd_results_new(CMD_FAILURE, "tray_padding", "No bar defined.");
20 }
21 struct bar_config *bar = config->current_bar;
22
23 char *end;
24 int padding = strtol(argv[0], &end, 10);
25 if (padding < 0 || (*end != '\0' && strcasecmp(end, "px") != 0)) {
26 return cmd_results_new(CMD_INVALID, "tray_padding",
27 "[Bar %s] Invalid tray padding value: %s", bar->id, argv[0]);
28 }
29
30 if (argc == 2 && strcasecmp(argv[1], "px") != 0) {
31 return cmd_results_new(CMD_INVALID, "tray_padding",
32 "Expected 'tray_padding <px> [px]'");
33 }
34
35 wlr_log(WLR_DEBUG, "[Bar %s] Setting tray padding to %d", bar->id, padding);
36 config->current_bar->tray_padding = padding;
37 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
38#else
39 return cmd_results_new(CMD_INVALID, "tray_padding",
40 "Sway has been compiled without tray support");
41#endif
9} 42}
diff --git a/sway/config/bar.c b/sway/config/bar.c
index 45c9e998..670219f1 100644
--- a/sway/config/bar.c
+++ b/sway/config/bar.c
@@ -12,6 +12,7 @@
12#include <signal.h> 12#include <signal.h>
13#include "sway/config.h" 13#include "sway/config.h"
14#include "sway/output.h" 14#include "sway/output.h"
15#include "config.h"
15#include "stringop.h" 16#include "stringop.h"
16#include "list.h" 17#include "list.h"
17#include "log.h" 18#include "log.h"
@@ -77,6 +78,10 @@ void free_bar_config(struct bar_config *bar) {
77 free(bar->colors.binding_mode_border); 78 free(bar->colors.binding_mode_border);
78 free(bar->colors.binding_mode_bg); 79 free(bar->colors.binding_mode_bg);
79 free(bar->colors.binding_mode_text); 80 free(bar->colors.binding_mode_text);
81#if HAVE_TRAY
82 list_free_items_and_destroy(bar->tray_outputs);
83 free(bar->icon_theme);
84#endif
80 free(bar); 85 free(bar);
81} 86}
82 87
@@ -165,6 +170,10 @@ struct bar_config *default_bar_config(void) {
165 bar->colors.binding_mode_bg = NULL; 170 bar->colors.binding_mode_bg = NULL;
166 bar->colors.binding_mode_text = NULL; 171 bar->colors.binding_mode_text = NULL;
167 172
173#if HAVE_TRAY
174 bar->tray_padding = 2;
175#endif
176
168 list_add(config->bars, bar); 177 list_add(config->bars, bar);
169 return bar; 178 return bar;
170cleanup: 179cleanup:
diff --git a/sway/ipc-json.c b/sway/ipc-json.c
index 96701dc2..53e0e335 100644
--- a/sway/ipc-json.c
+++ b/sway/ipc-json.c
@@ -1,6 +1,7 @@
1#include <json-c/json.h> 1#include <json-c/json.h>
2#include <stdio.h> 2#include <stdio.h>
3#include <ctype.h> 3#include <ctype.h>
4#include "config.h"
4#include "log.h" 5#include "log.h"
5#include "sway/config.h" 6#include "sway/config.h"
6#include "sway/ipc-json.h" 7#include "sway/ipc-json.h"
@@ -785,5 +786,41 @@ json_object *ipc_json_describe_bar_config(struct bar_config *bar) {
785 } 786 }
786 json_object_object_add(json, "outputs", outputs); 787 json_object_object_add(json, "outputs", outputs);
787 } 788 }
789#if HAVE_TRAY
790 // Add tray outputs if defined
791 if (bar->tray_outputs && bar->tray_outputs->length > 0) {
792 json_object *tray_outputs = json_object_new_array();
793 for (int i = 0; i < bar->tray_outputs->length; ++i) {
794 const char *name = bar->tray_outputs->items[i];
795 json_object_array_add(tray_outputs, json_object_new_string(name));
796 }
797 json_object_object_add(json, "tray_outputs", tray_outputs);
798 }
799
800 json_object *tray_bindings = json_object_new_array();
801 for (int i = 0; i < 10; ++i) {
802 if (bar->tray_bindings[i]) {
803 json_object *bind = json_object_new_object();
804 json_object_object_add(bind, "input_code",
805 json_object_new_int(i));
806 json_object_object_add(bind, "command",
807 json_object_new_string(bar->tray_bindings[i]));
808 json_object_array_add(tray_bindings, bind);
809 }
810 }
811 if (json_object_array_length(tray_bindings) > 0) {
812 json_object_object_add(json, "tray_bindings", tray_bindings);
813 } else {
814 json_object_put(tray_bindings);
815 }
816
817 if (bar->icon_theme) {
818 json_object_object_add(json, "icon_theme",
819 json_object_new_string(bar->icon_theme));
820 }
821
822 json_object_object_add(json, "tray_padding",
823 json_object_new_int(bar->tray_padding));
824#endif
788 return json; 825 return json;
789} 826}
diff --git a/sway/meson.build b/sway/meson.build
index 6d446acb..7f739287 100644
--- a/sway/meson.build
+++ b/sway/meson.build
@@ -99,11 +99,9 @@ sway_sources = files(
99 'commands/workspace_layout.c', 99 'commands/workspace_layout.c',
100 'commands/ws_auto_back_and_forth.c', 100 'commands/ws_auto_back_and_forth.c',
101 101
102 'commands/bar/activate_button.c',
103 'commands/bar/binding_mode_indicator.c', 102 'commands/bar/binding_mode_indicator.c',
104 'commands/bar/bindsym.c', 103 'commands/bar/bindsym.c',
105 'commands/bar/colors.c', 104 'commands/bar/colors.c',
106 'commands/bar/context_button.c',
107 'commands/bar/font.c', 105 'commands/bar/font.c',
108 'commands/bar/gaps.c', 106 'commands/bar/gaps.c',
109 'commands/bar/height.c', 107 'commands/bar/height.c',
@@ -115,12 +113,12 @@ sway_sources = files(
115 'commands/bar/output.c', 113 'commands/bar/output.c',
116 'commands/bar/pango_markup.c', 114 'commands/bar/pango_markup.c',
117 'commands/bar/position.c', 115 'commands/bar/position.c',
118 'commands/bar/secondary_button.c',
119 'commands/bar/separator_symbol.c', 116 'commands/bar/separator_symbol.c',
120 'commands/bar/status_command.c', 117 'commands/bar/status_command.c',
121 'commands/bar/strip_workspace_numbers.c', 118 'commands/bar/strip_workspace_numbers.c',
122 'commands/bar/strip_workspace_name.c', 119 'commands/bar/strip_workspace_name.c',
123 'commands/bar/swaybar_command.c', 120 'commands/bar/swaybar_command.c',
121 'commands/bar/tray_bindsym.c',
124 'commands/bar/tray_output.c', 122 'commands/bar/tray_output.c',
125 'commands/bar/tray_padding.c', 123 'commands/bar/tray_padding.c',
126 'commands/bar/workspace_buttons.c', 124 'commands/bar/workspace_buttons.c',
diff --git a/sway/sway-bar.5.scd b/sway/sway-bar.5.scd
index a3c6af2e..2357591d 100644
--- a/sway/sway-bar.5.scd
+++ b/sway/sway-bar.5.scd
@@ -100,27 +100,20 @@ The following commands configure the tray.
100The _button_ argument in all cases is a platform-specific button code. On Linux 100The _button_ argument in all cases is a platform-specific button code. On Linux
101you can find a list of these at linux/input-event-codes.h. 101you can find a list of these at linux/input-event-codes.h.
102 102
103*activate\_button* <button> 103*tray\_bindsym* button<n> ContextMenu|Activate|SecondaryActivate|ScrollDown|ScrollLeft|ScrollRight|ScrollUp|nop
104 Sets the button to be used for the _activate_ (primary click) tray item 104 Binds mouse button _n_ (1 to 9) to the specified action. Use the command
105 event. The default is BTN\_LEFT (0x110). 105 _nop_ to disable the default action (Activate for button 1, ContextMenu for
106 106 button 2 and SecondaryActivate for button 3).
107*context\_button* <button>
108 Sets the button to be used for the _context menu_ (right click) tray item
109 event. The default is BTN\_RIGHT (0x111).
110
111*secondary\_button* <button>
112 Sets the button to be used for the _secondary_ (middle click) tray item
113 event. The default is BTN\_MIDDLE (0x112).
114
115*tray\_output* none|all|<output>
116 Sets the output that the tray will appear on or none. Unlike i3bar, swaybar
117 is able to show icons on any number of bars and outputs without races.
118 The default is _all_.
119 107
120*tray\_padding* <px> [px] 108*tray\_padding* <px> [px]
121 Sets the pixel padding of the system tray. This padding will surround the 109 Sets the pixel padding of the system tray. This padding will surround the
122 tray on all sides and between each item. The default value for _px_ is 2. 110 tray on all sides and between each item. The default value for _px_ is 2.
123 111
112*tray\_output* none|<output>
113 Restrict the tray to a certain output, can be specified multiple times. If
114 omitted, the tray will be displayed on all outputs. Unlike i3bar, swaybar
115 can show icons on any number of bars and outputs without races.
116
124*icon\_theme* <name> 117*icon\_theme* <name>
125 Sets the icon theme that sway will look for item icons in. This option has 118 Sets the icon theme that sway will look for item icons in. This option has
126 no default value, because sway will always default to the fallback theme, 119 no default value, because sway will always default to the fallback theme,
diff --git a/swaybar/bar.c b/swaybar/bar.c
index 53e798bc..7aed4dca 100644
--- a/swaybar/bar.c
+++ b/swaybar/bar.c
@@ -11,6 +11,7 @@
11#include <wayland-client.h> 11#include <wayland-client.h>
12#include <wayland-cursor.h> 12#include <wayland-cursor.h>
13#include <wlr/util/log.h> 13#include <wlr/util/log.h>
14#include "config.h"
14#include "swaybar/bar.h" 15#include "swaybar/bar.h"
15#include "swaybar/config.h" 16#include "swaybar/config.h"
16#include "swaybar/i3bar.h" 17#include "swaybar/i3bar.h"
@@ -18,6 +19,9 @@
18#include "swaybar/ipc.h" 19#include "swaybar/ipc.h"
19#include "swaybar/status_line.h" 20#include "swaybar/status_line.h"
20#include "swaybar/render.h" 21#include "swaybar/render.h"
22#if HAVE_TRAY
23#include "swaybar/tray/tray.h"
24#endif
21#include "ipc-client.h" 25#include "ipc-client.h"
22#include "list.h" 26#include "list.h"
23#include "log.h" 27#include "log.h"
@@ -120,7 +124,7 @@ static void destroy_layer_surface(struct swaybar_output *output) {
120 output->frame_scheduled = false; 124 output->frame_scheduled = false;
121} 125}
122 126
123static void set_bar_dirty(struct swaybar *bar) { 127void set_bar_dirty(struct swaybar *bar) {
124 struct swaybar_output *output; 128 struct swaybar_output *output;
125 wl_list_for_each(output, &bar->outputs, link) { 129 wl_list_for_each(output, &bar->outputs, link) {
126 set_output_dirty(output); 130 set_output_dirty(output);
@@ -211,12 +215,16 @@ struct wl_output_listener output_listener = {
211 215
212static void xdg_output_handle_logical_position(void *data, 216static void xdg_output_handle_logical_position(void *data,
213 struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) { 217 struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) {
214 // Who cares 218 struct swaybar_output *output = data;
219 output->output_x = x;
220 output->output_y = y;
215} 221}
216 222
217static void xdg_output_handle_logical_size(void *data, 223static void xdg_output_handle_logical_size(void *data,
218 struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) { 224 struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) {
219 // Who cares 225 struct swaybar_output *output = data;
226 output->output_height = height;
227 output->output_width = width;
220} 228}
221 229
222static void xdg_output_handle_done(void *data, 230static void xdg_output_handle_done(void *data,
@@ -362,6 +370,12 @@ bool bar_setup(struct swaybar *bar, const char *socket_path) {
362 pointer->cursor_surface = wl_compositor_create_surface(bar->compositor); 370 pointer->cursor_surface = wl_compositor_create_surface(bar->compositor);
363 assert(pointer->cursor_surface); 371 assert(pointer->cursor_surface);
364 372
373#if HAVE_TRAY
374 if (!bar->config->tray_hidden) {
375 bar->tray = create_tray(bar);
376 }
377#endif
378
365 if (bar->config->workspace_buttons) { 379 if (bar->config->workspace_buttons) {
366 ipc_get_workspaces(bar); 380 ipc_get_workspaces(bar);
367 } 381 }
@@ -403,6 +417,11 @@ void bar_run(struct swaybar *bar) {
403 loop_add_fd(bar->eventloop, bar->status->read_fd, POLLIN, 417 loop_add_fd(bar->eventloop, bar->status->read_fd, POLLIN,
404 status_in, bar); 418 status_in, bar);
405 } 419 }
420#if HAVE_TRAY
421 if (bar->tray) {
422 loop_add_fd(bar->eventloop, bar->tray->fd, POLLIN, tray_in, bar->tray->bus);
423 }
424#endif
406 while (1) { 425 while (1) {
407 errno = 0; 426 errno = 0;
408 if (wl_display_flush(bar->display) == -1 && errno != EAGAIN) { 427 if (wl_display_flush(bar->display) == -1 && errno != EAGAIN) {
@@ -420,6 +439,9 @@ static void free_outputs(struct wl_list *list) {
420} 439}
421 440
422void bar_teardown(struct swaybar *bar) { 441void bar_teardown(struct swaybar *bar) {
442#if HAVE_TRAY
443 destroy_tray(bar->tray);
444#endif
423 free_outputs(&bar->outputs); 445 free_outputs(&bar->outputs);
424 if (bar->config) { 446 if (bar->config) {
425 free_config(bar->config); 447 free_config(bar->config);
diff --git a/swaybar/config.c b/swaybar/config.c
index 10c78c8a..9cafe061 100644
--- a/swaybar/config.c
+++ b/swaybar/config.c
@@ -4,6 +4,7 @@
4#include <wlr/util/log.h> 4#include <wlr/util/log.h>
5#include "swaybar/config.h" 5#include "swaybar/config.h"
6#include "wlr-layer-shell-unstable-v1-client-protocol.h" 6#include "wlr-layer-shell-unstable-v1-client-protocol.h"
7#include "config.h"
7#include "stringop.h" 8#include "stringop.h"
8#include "list.h" 9#include "list.h"
9 10
@@ -73,6 +74,10 @@ struct swaybar_config *init_config(void) {
73 config->colors.binding_mode.background = 0x900000FF; 74 config->colors.binding_mode.background = 0x900000FF;
74 config->colors.binding_mode.text = 0xFFFFFFFF; 75 config->colors.binding_mode.text = 0xFFFFFFFF;
75 76
77#if HAVE_TRAY
78 config->tray_padding = 2;
79#endif
80
76 return config; 81 return config;
77} 82}
78 83
@@ -102,5 +107,12 @@ void free_config(struct swaybar_config *config) {
102 free(coutput->name); 107 free(coutput->name);
103 free(coutput); 108 free(coutput);
104 } 109 }
110#if HAVE_TRAY
111 list_free_items_and_destroy(config->tray_outputs);
112 for (int i = 0; i < 10; ++i) {
113 free(config->tray_bindings[i]);
114 }
115 free(config->icon_theme);
116#endif
105 free(config); 117 free(config);
106} 118}
diff --git a/swaybar/ipc.c b/swaybar/ipc.c
index 2b930786..8e7a542e 100644
--- a/swaybar/ipc.c
+++ b/swaybar/ipc.c
@@ -6,6 +6,7 @@
6#include <wlr/util/log.h> 6#include <wlr/util/log.h>
7#include "swaybar/config.h" 7#include "swaybar/config.h"
8#include "swaybar/ipc.h" 8#include "swaybar/ipc.h"
9#include "config.h"
9#include "ipc-client.h" 10#include "ipc-client.h"
10#include "list.h" 11#include "list.h"
11 12
@@ -282,6 +283,40 @@ static bool ipc_parse_config(
282 ipc_parse_colors(config, colors); 283 ipc_parse_colors(config, colors);
283 } 284 }
284 285
286#if HAVE_TRAY
287 json_object *tray_outputs, *tray_padding, *tray_bindings, *icon_theme;
288
289 if ((json_object_object_get_ex(bar_config, "tray_outputs", &tray_outputs))) {
290 config->tray_outputs = create_list();
291 int length = json_object_array_length(tray_outputs);
292 for (int i = 0; i < length; ++i) {
293 json_object *o = json_object_array_get_idx(tray_outputs, i);
294 list_add(config->tray_outputs, strdup(json_object_get_string(o)));
295 }
296 config->tray_hidden = strcmp(config->tray_outputs->items[0], "none") == 0;
297 }
298
299 if ((json_object_object_get_ex(bar_config, "tray_padding", &tray_padding))) {
300 config->tray_padding = json_object_get_int(tray_padding);
301 }
302
303 if ((json_object_object_get_ex(bar_config, "tray_bindings", &tray_bindings))) {
304 int length = json_object_array_length(tray_bindings);
305 for (int i = 0; i < length; ++i) {
306 json_object *bind = json_object_array_get_idx(tray_bindings, i);
307 json_object *button, *command;
308 json_object_object_get_ex(bind, "input_code", &button);
309 json_object_object_get_ex(bind, "command", &command);
310 config->tray_bindings[json_object_get_int(button)] =
311 strdup(json_object_get_string(command));
312 }
313 }
314
315 if ((json_object_object_get_ex(bar_config, "icon_theme", &icon_theme))) {
316 config->icon_theme = strdup(json_object_get_string(icon_theme));
317 }
318#endif
319
285 json_object_put(bar_config); 320 json_object_put(bar_config);
286 return true; 321 return true;
287} 322}
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 @@
1tray_files = get_option('enable-tray') ? [
2 'tray/host.c',
3 'tray/icon.c',
4 'tray/item.c',
5 'tray/tray.c',
6 'tray/watcher.c'
7] : []
8
9swaybar_deps = [
10 cairo,
11 client_protos,
12 gdk_pixbuf,
13 jsonc,
14 math,
15 pango,
16 pangocairo,
17 rt,
18 wayland_client,
19 wayland_cursor,
20 wlroots,
21]
22if get_option('enable-tray')
23 if systemd.found()
24 swaybar_deps += systemd
25 elif elogind.found()
26 swaybar_deps += elogind
27 endif
28endif
29
1executable( 30executable(
2 'swaybar', [ 31 'swaybar', [
3 'bar.c', 32 'bar.c',
@@ -8,21 +37,10 @@ executable(
8 'main.c', 37 'main.c',
9 'render.c', 38 'render.c',
10 'status_line.c', 39 'status_line.c',
40 tray_files
11 ], 41 ],
12 include_directories: [sway_inc], 42 include_directories: [sway_inc],
13 dependencies: [ 43 dependencies: swaybar_deps,
14 cairo,
15 client_protos,
16 gdk_pixbuf,
17 jsonc,
18 math,
19 pango,
20 pangocairo,
21 rt,
22 wayland_client,
23 wayland_cursor,
24 wlroots,
25 ],
26 link_with: [lib_sway_common, lib_sway_client], 44 link_with: [lib_sway_common, lib_sway_client],
27 install_rpath : rpathdir, 45 install_rpath : rpathdir,
28 install: true 46 install: true
diff --git a/swaybar/render.c b/swaybar/render.c
index 96118c42..9fe4ee9c 100644
--- a/swaybar/render.c
+++ b/swaybar/render.c
@@ -14,6 +14,9 @@
14#include "swaybar/ipc.h" 14#include "swaybar/ipc.h"
15#include "swaybar/render.h" 15#include "swaybar/render.h"
16#include "swaybar/status_line.h" 16#include "swaybar/status_line.h"
17#if HAVE_TRAY
18#include "swaybar/tray/tray.h"
19#endif
17#include "wlr-layer-shell-unstable-v1-client-protocol.h" 20#include "wlr-layer-shell-unstable-v1-client-protocol.h"
18 21
19static const int WS_HORIZONTAL_PADDING = 5; 22static const int WS_HORIZONTAL_PADDING = 5;
@@ -453,6 +456,12 @@ static uint32_t render_to_cairo(cairo_t *cairo, struct swaybar_output *output) {
453 * utilize the available space. 456 * utilize the available space.
454 */ 457 */
455 double x = output->width * output->scale; 458 double x = output->width * output->scale;
459#if HAVE_TRAY
460 if (bar->tray) {
461 uint32_t h = render_tray(cairo, output, &x);
462 max_height = h > max_height ? h : max_height;
463 }
464#endif
456 if (bar->status) { 465 if (bar->status) {
457 uint32_t h = render_status_line(cairo, output, &x); 466 uint32_t h = render_status_line(cairo, output, &x);
458 max_height = h > max_height ? h : max_height; 467 max_height = h > max_height ? h : max_height;
diff --git a/swaybar/tray/host.c b/swaybar/tray/host.c
new file mode 100644
index 00000000..30339fec
--- /dev/null
+++ b/swaybar/tray/host.c
@@ -0,0 +1,210 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdbool.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6#include <unistd.h>
7#include "swaybar/bar.h"
8#include "swaybar/tray/host.h"
9#include "swaybar/tray/item.h"
10#include "swaybar/tray/tray.h"
11#include "list.h"
12#include "log.h"
13
14static const char *watcher_path = "/StatusNotifierWatcher";
15
16static int cmp_sni_id(const void *item, const void *cmp_to) {
17 const struct swaybar_sni *sni = item;
18 return strcmp(sni->watcher_id, cmp_to);
19}
20
21static void add_sni(struct swaybar_tray *tray, char *id) {
22 int idx = list_seq_find(tray->items, cmp_sni_id, id);
23 if (idx == -1) {
24 wlr_log(WLR_DEBUG, "Registering Status Notifier Item '%s'", id);
25 struct swaybar_sni *sni = create_sni(id, tray);
26 if (sni) {
27 list_add(tray->items, sni);
28 }
29 }
30}
31
32static int handle_sni_registered(sd_bus_message *msg, void *data,
33 sd_bus_error *error) {
34 char *id;
35 int ret = sd_bus_message_read(msg, "s", &id);
36 if (ret < 0) {
37 wlr_log(WLR_ERROR, "Failed to parse register SNI message: %s", strerror(-ret));
38 }
39
40 struct swaybar_tray *tray = data;
41 add_sni(tray, id);
42
43 return ret;
44}
45
46static int handle_sni_unregistered(sd_bus_message *msg, void *data,
47 sd_bus_error *error) {
48 char *id;
49 int ret = sd_bus_message_read(msg, "s", &id);
50 if (ret < 0) {
51 wlr_log(WLR_ERROR, "Failed to parse unregister SNI message: %s", strerror(-ret));
52 }
53
54 struct swaybar_tray *tray = data;
55 int idx = list_seq_find(tray->items, cmp_sni_id, id);
56 if (idx != -1) {
57 wlr_log(WLR_DEBUG, "Unregistering Status Notifier Item '%s'", id);
58 destroy_sni(tray->items->items[idx]);
59 list_del(tray->items, idx);
60 set_bar_dirty(tray->bar);
61 }
62 return ret;
63}
64
65static int get_registered_snis_callback(sd_bus_message *msg, void *data,
66 sd_bus_error *error) {
67 if (sd_bus_message_is_method_error(msg, NULL)) {
68 sd_bus_error err = *sd_bus_message_get_error(msg);
69 wlr_log(WLR_ERROR, "Failed to get registered SNIs: %s", err.message);
70 return -sd_bus_error_get_errno(&err);
71 }
72
73 int ret = sd_bus_message_enter_container(msg, 'v', NULL);
74 if (ret < 0) {
75 wlr_log(WLR_ERROR, "Failed to read registered SNIs: %s", strerror(-ret));
76 return ret;
77 }
78
79 char **ids;
80 ret = sd_bus_message_read_strv(msg, &ids);
81 if (ret < 0) {
82 wlr_log(WLR_ERROR, "Failed to read registered SNIs: %s", strerror(-ret));
83 return ret;
84 }
85
86 if (ids) {
87 struct swaybar_tray *tray = data;
88 for (char **id = ids; *id; ++id) {
89 add_sni(tray, *id);
90 }
91 }
92
93 return ret;
94}
95
96static bool register_to_watcher(struct swaybar_host *host) {
97 // this is called asynchronously in case the watcher is owned by this process
98 int ret = sd_bus_call_method_async(host->tray->bus, NULL,
99 host->watcher_interface, watcher_path, host->watcher_interface,
100 "RegisterStatusNotifierHost", NULL, NULL, "s", host->service);
101 if (ret < 0) {
102 wlr_log(WLR_ERROR, "Failed to send register call: %s", strerror(-ret));
103 return false;
104 }
105
106 ret = sd_bus_call_method_async(host->tray->bus, NULL,
107 host->watcher_interface, watcher_path,
108 "org.freedesktop.DBus.Properties", "Get",
109 get_registered_snis_callback, host->tray, "ss",
110 host->watcher_interface, "RegisteredStatusNotifierItems");
111 if (ret < 0) {
112 wlr_log(WLR_ERROR, "Failed to get registered SNIs: %s", strerror(-ret));
113 }
114
115 return ret >= 0;
116}
117
118static int handle_new_watcher(sd_bus_message *msg,
119 void *data, sd_bus_error *error) {
120 char *service, *old_owner, *new_owner;
121 int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner);
122 if (ret < 0) {
123 wlr_log(WLR_ERROR, "Failed to parse owner change message: %s", strerror(-ret));
124 return ret;
125 }
126
127 if (!*old_owner) {
128 struct swaybar_host *host = data;
129 if (strcmp(service, host->watcher_interface) == 0) {
130 register_to_watcher(host);
131 }
132 }
133
134 return 0;
135}
136
137bool init_host(struct swaybar_host *host, char *protocol,
138 struct swaybar_tray *tray) {
139 size_t len = snprintf(NULL, 0, "org.%s.StatusNotifierWatcher", protocol) + 1;
140 host->watcher_interface = malloc(len);
141 if (!host->watcher_interface) {
142 return false;
143 }
144 snprintf(host->watcher_interface, len, "org.%s.StatusNotifierWatcher", protocol);
145
146 sd_bus_slot *reg_slot = NULL, *unreg_slot = NULL, *watcher_slot = NULL;
147 int ret = sd_bus_match_signal(tray->bus, &reg_slot, host->watcher_interface,
148 watcher_path, host->watcher_interface,
149 "StatusNotifierItemRegistered", handle_sni_registered, tray);
150 if (ret < 0) {
151 wlr_log(WLR_ERROR, "Failed to subscribe to registering events: %s",
152 strerror(-ret));
153 goto error;
154 }
155 ret = sd_bus_match_signal(tray->bus, &unreg_slot, host->watcher_interface,
156 watcher_path, host->watcher_interface,
157 "StatusNotifierItemUnregistered", handle_sni_unregistered, tray);
158 if (ret < 0) {
159 wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s",
160 strerror(-ret));
161 goto error;
162 }
163
164 ret = sd_bus_match_signal(tray->bus, &watcher_slot, "org.freedesktop.DBus",
165 "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged",
166 handle_new_watcher, host);
167 if (ret < 0) {
168 wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s",
169 strerror(-ret));
170 goto error;
171 }
172
173 pid_t pid = getpid();
174 size_t service_len = snprintf(NULL, 0, "org.%s.StatusNotifierHost-%d",
175 protocol, pid) + 1;
176 host->service = malloc(service_len);
177 if (!host->service) {
178 goto error;
179 }
180 snprintf(host->service, service_len, "org.%s.StatusNotifierHost-%d", protocol, pid);
181 ret = sd_bus_request_name(tray->bus, host->service, 0);
182 if (ret < 0) {
183 wlr_log(WLR_DEBUG, "Failed to acquire service name: %s", strerror(-ret));
184 goto error;
185 }
186
187 host->tray = tray;
188 if (!register_to_watcher(host)) {
189 goto error;
190 }
191
192 sd_bus_slot_set_floating(reg_slot, 1);
193 sd_bus_slot_set_floating(unreg_slot, 1);
194 sd_bus_slot_set_floating(watcher_slot, 1);
195
196 wlr_log(WLR_DEBUG, "Registered %s", host->service);
197 return true;
198error:
199 sd_bus_slot_unref(reg_slot);
200 sd_bus_slot_unref(unreg_slot);
201 sd_bus_slot_unref(watcher_slot);
202 finish_host(host);
203 return false;
204}
205
206void finish_host(struct swaybar_host *host) {
207 sd_bus_release_name(host->tray->bus, host->service);
208 free(host->service);
209 free(host->watcher_interface);
210}
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 @@
1#define _POSIX_C_SOURCE 200809L
2#include <ctype.h>
3#include <dirent.h>
4#include <stdbool.h>
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8#include <sys/stat.h>
9#include <unistd.h>
10#include <wordexp.h>
11#include "swaybar/tray/icon.h"
12#include "config.h"
13#include "list.h"
14#include "log.h"
15#include "stringop.h"
16
17static bool dir_exists(char *path) {
18 struct stat sb;
19 return stat(path, &sb) == 0 && S_ISDIR(sb.st_mode);
20}
21
22static list_t *get_basedirs(void) {
23 list_t *basedirs = create_list();
24 list_add(basedirs, strdup("$HOME/.icons")); // deprecated
25
26 char *data_home = getenv("XDG_DATA_HOME");
27 list_add(basedirs, strdup(data_home && *data_home ?
28 "$XDG_DATA_HOME/icons" : "$HOME/.local/share/icons"));
29
30 list_add(basedirs, strdup("/usr/share/pixmaps"));
31
32 char *data_dirs = getenv("XDG_DATA_DIRS");
33 if (!(data_dirs && *data_dirs)) {
34 data_dirs = "/usr/local/share:/usr/share";
35 }
36 data_dirs = strdup(data_dirs);
37 char *dir = strtok(data_dirs, ":");
38 do {
39 size_t path_len = snprintf(NULL, 0, "%s/icons", dir) + 1;
40 char *path = malloc(path_len);
41 snprintf(path, path_len, "%s/icons", dir);
42 list_add(basedirs, path);
43 } while ((dir = strtok(NULL, ":")));
44 free(data_dirs);
45
46 list_t *basedirs_expanded = create_list();
47 for (int i = 0; i < basedirs->length; ++i) {
48 wordexp_t p;
49 if (wordexp(basedirs->items[i], &p, WRDE_UNDEF) == 0) {
50 if (dir_exists(p.we_wordv[0])) {
51 list_add(basedirs_expanded, strdup(p.we_wordv[0]));
52 }
53 wordfree(&p);
54 }
55 }
56
57 list_free_items_and_destroy(basedirs);
58
59 return basedirs_expanded;
60}
61
62static void destroy_theme(struct icon_theme *theme) {
63 if (!theme) {
64 return;
65 }
66 free(theme->name);
67 free(theme->comment);
68 free(theme->inherits);
69 list_free_items_and_destroy(theme->directories);
70 free(theme->dir);
71
72 for (int i = 0; i < theme->subdirs->length; ++i) {
73 struct icon_theme_subdir *subdir = theme->subdirs->items[i];
74 free(subdir->name);
75 free(subdir);
76 }
77 list_free(theme->subdirs);
78 free(theme);
79}
80
81static int cmp_group(const void *item, const void *cmp_to) {
82 return strcmp(item, cmp_to);
83}
84
85static bool group_handler(char *old_group, char *new_group,
86 struct icon_theme *theme) {
87 if (!old_group) { // first group must be "Icon Theme"
88 return strcmp(new_group, "Icon Theme");
89 }
90
91 if (strcmp(old_group, "Icon Theme") == 0) {
92 if (!(theme->name && theme->comment && theme->directories)) {
93 return true;
94 }
95 } else {
96 if (theme->subdirs->length == 0) { // skip
97 return false;
98 }
99
100 struct icon_theme_subdir *subdir =
101 theme->subdirs->items[theme->subdirs->length - 1];
102 if (!subdir->size) return true;
103
104 switch (subdir->type) {
105 case FIXED: subdir->max_size = subdir->min_size = subdir->size;
106 break;
107 case SCALABLE: {
108 if (!subdir->max_size) subdir->max_size = subdir->size;
109 if (!subdir->min_size) subdir->min_size = subdir->size;
110 break;
111 }
112 case THRESHOLD:
113 subdir->max_size = subdir->size + subdir->threshold;
114 subdir->min_size = subdir->size - subdir->threshold;
115 }
116 }
117
118 if (new_group && list_seq_find(theme->directories, cmp_group, new_group) != -1) {
119 struct icon_theme_subdir *subdir = calloc(1, sizeof(struct icon_theme_subdir));
120 if (!subdir) {
121 return true;
122 }
123 subdir->name = strdup(new_group);
124 subdir->threshold = 2;
125 list_add(theme->subdirs, subdir);
126 }
127
128 return false;
129}
130
131static int entry_handler(char *group, char *key, char *value,
132 struct icon_theme *theme) {
133 if (strcmp(group, "Icon Theme") == 0) {
134 if (strcmp(key, "Name") == 0) {
135 theme->name = strdup(value);
136 } else if (strcmp(key, "Comment") == 0) {
137 theme->comment = strdup(value);
138 } else if (strcmp(key, "Inherists") == 0) {
139 theme->inherits = strdup(value);
140 } else if (strcmp(key, "Directories") == 0) {
141 theme->directories = split_string(value, ",");
142 } // Ignored: ScaledDirectories, Hidden, Example
143 } else {
144 if (theme->subdirs->length == 0) { // skip
145 return false;
146 }
147
148 struct icon_theme_subdir *subdir =
149 theme->subdirs->items[theme->subdirs->length - 1];
150 if (strcmp(subdir->name, group) != 0) { // skip
151 return false;
152 }
153
154 char *end;
155 int n = strtol(value, &end, 10);
156 if (strcmp(key, "Size") == 0) {
157 subdir->size = n;
158 return *end != '\0';
159 } else if (strcmp(key, "Type") == 0) {
160 if (strcmp(value, "Fixed") == 0) {
161 subdir->type = FIXED;
162 } else if (strcmp(value, "Scalable") == 0) {
163 subdir->type = SCALABLE;
164 } else if (strcmp(value, "Threshold") == 0) {
165 subdir->type = THRESHOLD;
166 } else {
167 return true;
168 }
169 } else if (strcmp(key, "MaxSize") == 0) {
170 subdir->max_size = n;
171 return *end != '\0';
172 } else if (strcmp(key, "MinSize") == 0) {
173 subdir->min_size = n;
174 return *end != '\0';
175 } else if (strcmp(key, "Threshold") == 0) {
176 subdir->threshold = n;
177 return *end != '\0';
178 } // Ignored: Scale, Applications
179 }
180 return false;
181}
182
183/*
184 * This is a Freedesktop Desktop Entry parser (essentially INI)
185 * It calls entry_handler for every entry
186 * and group_handler between every group (as well as at both ends)
187 * Handlers return whether an error occured, which stops parsing
188 */
189static struct icon_theme *read_theme_file(char *basedir, char *theme_name) {
190 // look for index.theme file
191 size_t path_len = snprintf(NULL, 0, "%s/%s/index.theme", basedir,
192 theme_name) + 1;
193 char *path = malloc(path_len);
194 snprintf(path, path_len, "%s/%s/index.theme", basedir, theme_name);
195 FILE *theme_file = fopen(path, "r");
196 if (!theme_file) {
197 return NULL;
198 }
199
200 struct icon_theme *theme = calloc(1, sizeof(struct icon_theme));
201 if (!theme) {
202 return NULL;
203 }
204 theme->subdirs = create_list();
205
206 bool error = false;
207 char *group = NULL;
208 char *full_line = NULL;
209 size_t full_len = 0;
210 ssize_t nread;
211 while ((nread = getline(&full_line, &full_len, theme_file)) != -1) {
212 char *line = full_line - 1;
213 while (isspace(*++line)) {} // remove leading whitespace
214 if (!*line || line[0] == '#') continue; // ignore blank lines & comments
215
216 int len = nread - (line - full_line);
217 while (isspace(line[--len])) {}
218 line[++len] = '\0'; // remove trailing whitespace
219
220 if (line[0] == '[') { // group header
221 // check well-formed
222 if (line[--len] != ']') {
223 error = true;
224 break;
225 }
226 int i = 1;
227 for (; !iscntrl(line[i]) && line[i] != '[' && line[i] != ']'; ++i) {}
228 if (i < len) {
229 error = true;
230 break;
231 }
232
233 // call handler
234 line[len] = '\0';
235 error = group_handler(group, &line[1], theme);
236 if (error) {
237 break;
238 }
239 free(group);
240 group = strdup(&line[1]);
241 } else { // key-value pair
242 // check well-formed
243 int eok = 0;
244 for (; isalnum(line[eok]) || line[eok] == '-'; ++eok) {} // TODO locale?
245 int i = eok - 1;
246 while (isspace(line[++i])) {}
247 if (line[i] != '=') {
248 error = true;
249 break;
250 }
251
252 line[eok] = '\0'; // split into key-value pair
253 char *value = &line[i];
254 while (isspace(*++value)) {}
255 // TODO unescape value
256 error = entry_handler(group, line, value, theme);
257 if (error) {
258 break;
259 }
260 }
261 }
262
263 if (!error && group) {
264 error = group_handler(group, NULL, theme);
265 }
266
267 free(group);
268 free(full_line);
269 fclose(theme_file);
270
271 if (!error) {
272 theme->dir = strdup(theme_name);
273 return theme;
274 } else {
275 destroy_theme(theme);
276 return NULL;
277 }
278}
279
280static list_t *load_themes_in_dir(char *basedir) {
281 DIR *dir;
282 if (!(dir = opendir(basedir))) {
283 return NULL;
284 }
285
286 list_t *themes = create_list();
287 struct dirent *entry;
288 while ((entry = readdir(dir))) {
289 if (entry->d_name[0] == '.') continue;
290
291 struct icon_theme *theme = read_theme_file(basedir, entry->d_name);
292 if (theme) {
293 list_add(themes, theme);
294 }
295 }
296 return themes;
297}
298
299void init_themes(list_t **themes, list_t **basedirs) {
300 *basedirs = get_basedirs();
301
302 *themes = create_list();
303 for (int i = 0; i < (*basedirs)->length; ++i) {
304 list_t *dir_themes = load_themes_in_dir((*basedirs)->items[i]);
305 list_cat(*themes, dir_themes);
306 list_free(dir_themes);
307 }
308
309 list_t *theme_names = create_list();
310 for (int i = 0; i < (*themes)->length; ++i) {
311 struct icon_theme *theme = (*themes)->items[i];
312 list_add(theme_names, theme->name);
313 }
314 wlr_log(WLR_DEBUG, "Loaded themes: %s", join_list(theme_names, ", "));
315 list_free(theme_names);
316}
317
318void finish_themes(list_t *themes, list_t *basedirs) {
319 for (int i = 0; i < themes->length; ++i) {
320 destroy_theme(themes->items[i]);
321 }
322 list_free(themes);
323 list_free_items_and_destroy(basedirs);
324}
325
326static char *find_icon_in_subdir(char *name, char *basedir, char *theme,
327 char *subdir) {
328 static const char *extensions[] = {
329#if HAVE_GDK_PIXBUF
330 "svg",
331#endif
332 "png",
333#if HAVE_GDK_PIXBUF
334 "xpm"
335#endif
336 };
337
338 size_t path_len = snprintf(NULL, 0, "%s/%s/%s/%s.EXT", basedir, theme,
339 subdir, name) + 1;
340 char *path = malloc(path_len);
341
342 for (size_t i = 0; i < sizeof(extensions) / sizeof(*extensions); ++i) {
343 snprintf(path, path_len, "%s/%s/%s/%s.%s", basedir, theme, subdir,
344 name, extensions[i]);
345 if (access(path, R_OK) == 0) {
346 return path;
347 }
348 }
349
350 free(path);
351 return NULL;
352}
353
354static bool theme_exists_in_basedir(char *theme, char *basedir) {
355 size_t path_len = snprintf(NULL, 0, "%s/%s", basedir, theme) + 1;
356 char *path = malloc(path_len);
357 snprintf(path, path_len, "%s/%s", basedir, theme);
358 bool ret = dir_exists(path);
359 free(path);
360 return ret;
361}
362
363static char *find_icon_with_theme(list_t *basedirs, list_t *themes, char *name,
364 int size, char *theme_name, int *min_size, int *max_size) {
365 struct icon_theme *theme = NULL;
366 for (int i = 0; i < themes->length; ++i) {
367 theme = themes->items[i];
368 if (strcmp(theme->name, theme_name) == 0) {
369 break;
370 }
371 theme = NULL;
372 }
373 if (!theme) return NULL;
374
375 char *icon = NULL;
376 for (int i = 0; i < basedirs->length; ++i) {
377 if (!theme_exists_in_basedir(theme->dir, basedirs->items[i])) {
378 continue;
379 }
380 // search backwards to hopefully hit scalable/larger icons first
381 for (int j = theme->subdirs->length - 1; j >= 0; --j) {
382 struct icon_theme_subdir *subdir = theme->subdirs->items[j];
383 if (size >= subdir->min_size && size <= subdir->max_size) {
384 if ((icon = find_icon_in_subdir(name, basedirs->items[i],
385 theme->dir, subdir->name))) {
386 *min_size = subdir->min_size;
387 *max_size = subdir->max_size;
388 return icon;
389 }
390 }
391 }
392 }
393
394 // inexact match
395 unsigned smallest_error = -1; // UINT_MAX
396 for (int i = 0; i < basedirs->length; ++i) {
397 if (!theme_exists_in_basedir(theme->dir, basedirs->items[i])) {
398 continue;
399 }
400 for (int j = theme->subdirs->length - 1; j >= 0; --j) {
401 struct icon_theme_subdir *subdir = theme->subdirs->items[j];
402 unsigned error = (size > subdir->max_size ? size - subdir->max_size : 0)
403 + (size < subdir->min_size ? subdir->min_size - size : 0);
404 if (error < smallest_error) {
405 char *test_icon = find_icon_in_subdir(name, basedirs->items[i],
406 theme->dir, subdir->name);
407 if (test_icon) {
408 icon = test_icon;
409 smallest_error = error;
410 *min_size = subdir->min_size;
411 *max_size = subdir->max_size;
412 }
413 }
414 }
415 }
416
417 if (!icon && theme->inherits) {
418 icon = find_icon_with_theme(basedirs, themes, name, size,
419 theme->inherits, min_size, max_size);
420 }
421
422 return icon;
423}
424
425char *find_icon_in_dir(char *name, char *dir, int *min_size, int *max_size) {
426 char *icon = find_icon_in_subdir(name, dir, "", "");
427 if (icon) {
428 *min_size = 1;
429 *max_size = 512;
430 }
431 return icon;
432
433}
434
435static char *find_fallback_icon(list_t *basedirs, char *name, int *min_size,
436 int *max_size) {
437 for (int i = 0; i < basedirs->length; ++i) {
438 char *icon = find_icon_in_dir(name, basedirs->items[i], min_size, max_size);
439 if (icon) {
440 return icon;
441 }
442 }
443 return NULL;
444}
445
446char *find_icon(list_t *themes, list_t *basedirs, char *name, int size,
447 char *theme, int *min_size, int *max_size) {
448 // TODO https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#implementation_notes
449 char *icon = NULL;
450 if (theme) {
451 icon = find_icon_with_theme(basedirs, themes, name, size, theme,
452 min_size, max_size);
453 }
454 if (!icon) {
455 icon = find_icon_with_theme(basedirs, themes, name, size, "Hicolor",
456 min_size, max_size);
457 }
458 if (!icon) {
459 icon = find_fallback_icon(basedirs, name, min_size, max_size);
460 }
461 return icon;
462}
diff --git a/swaybar/tray/item.c b/swaybar/tray/item.c
new file mode 100644
index 00000000..41cacd16
--- /dev/null
+++ b/swaybar/tray/item.c
@@ -0,0 +1,443 @@
1#define _POSIX_C_SOURCE 200809L
2#include <cairo.h>
3#include <stdbool.h>
4#include <stdlib.h>
5#include <string.h>
6#include "swaybar/bar.h"
7#include "swaybar/config.h"
8#include "swaybar/input.h"
9#include "swaybar/tray/host.h"
10#include "swaybar/tray/icon.h"
11#include "swaybar/tray/item.h"
12#include "swaybar/tray/tray.h"
13#include "background-image.h"
14#include "cairo.h"
15#include "list.h"
16#include "log.h"
17#include "wlr-layer-shell-unstable-v1-client-protocol.h"
18
19// TODO menu
20
21static bool sni_ready(struct swaybar_sni *sni) {
22 return sni->status && (sni->status[0] == 'N' ?
23 sni->attention_icon_name || sni->attention_icon_pixmap :
24 sni->icon_name || sni->icon_pixmap);
25}
26
27static void set_sni_dirty(struct swaybar_sni *sni) {
28 if (sni_ready(sni)) {
29 sni->min_size = sni->max_size = 0; // invalidate previous icon
30 set_bar_dirty(sni->tray->bar);
31 }
32}
33
34static int read_pixmap(sd_bus_message *msg, struct swaybar_sni *sni,
35 const char *prop, list_t **dest) {
36 int ret = sd_bus_message_enter_container(msg, 'a', "(iiay)");
37 if (ret < 0) {
38 wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret));
39 return ret;
40 }
41
42 if (sd_bus_message_at_end(msg, 0)) {
43 return ret;
44 }
45
46 list_t *pixmaps = create_list();
47 if (!pixmaps) {
48 return -12; // -ENOMEM
49 }
50
51 while (!sd_bus_message_at_end(msg, 0)) {
52 ret = sd_bus_message_enter_container(msg, 'r', "iiay");
53 if (ret < 0) {
54 wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret));
55 goto error;
56 }
57
58 int size;
59 ret = sd_bus_message_read(msg, "ii", NULL, &size);
60 if (ret < 0) {
61 wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret));
62 goto error;
63 }
64
65 const void *pixels;
66 size_t npixels;
67 ret = sd_bus_message_read_array(msg, 'y', &pixels, &npixels);
68 if (ret < 0) {
69 wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret));
70 goto error;
71 }
72
73 struct swaybar_pixmap *pixmap =
74 malloc(sizeof(struct swaybar_pixmap) + npixels);
75 pixmap->size = size;
76 memcpy(pixmap->pixels, pixels, npixels);
77 list_add(pixmaps, pixmap);
78
79 sd_bus_message_exit_container(msg);
80 }
81 *dest = pixmaps;
82
83 return ret;
84error:
85 list_free_items_and_destroy(pixmaps);
86 return ret;
87}
88
89struct get_property_data {
90 struct swaybar_sni *sni;
91 const char *prop;
92 const char *type;
93 void *dest;
94};
95
96static int get_property_callback(sd_bus_message *msg, void *data,
97 sd_bus_error *error) {
98 struct get_property_data *d = data;
99 struct swaybar_sni *sni = d->sni;
100 const char *prop = d->prop;
101 const char *type = d->type;
102 void *dest = d->dest;
103
104 int ret;
105 if (sd_bus_message_is_method_error(msg, NULL)) {
106 sd_bus_error err = *sd_bus_message_get_error(msg);
107 wlr_log(WLR_DEBUG, "Failed to get property %s: %s", prop, err.message);
108 ret = -sd_bus_error_get_errno(&err);
109 goto cleanup;
110 }
111
112 ret = sd_bus_message_enter_container(msg, 'v', type);
113 if (ret < 0) {
114 wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret));
115 goto cleanup;
116 }
117
118 if (!type) {
119 ret = read_pixmap(msg, sni, prop, dest);
120 if (ret < 0) {
121 goto cleanup;
122 }
123 } else {
124 ret = sd_bus_message_read(msg, type, dest);
125 if (ret < 0) {
126 wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop,
127 strerror(-ret));
128 goto cleanup;
129 } else if (*type == 's' || *type == 'o') {
130 char **str = dest;
131 *str = strdup(*str);
132 }
133 }
134
135 if (strcmp(prop, "Status") == 0 || (sni->status && (sni->status[0] == 'N' ?
136 prop[0] == 'A' : strncmp(prop, "Icon", 4) == 0))) {
137 set_sni_dirty(sni);
138 }
139cleanup:
140 free(data);
141 return ret;
142}
143
144static void sni_get_property_async(struct swaybar_sni *sni, const char *prop,
145 const char *type, void *dest) {
146 struct get_property_data *data = malloc(sizeof(struct get_property_data));
147 data->sni = sni;
148 data->prop = prop;
149 data->type = type;
150 data->dest = dest;
151 int ret = sd_bus_call_method_async(sni->tray->bus, NULL, sni->service,
152 sni->path, "org.freedesktop.DBus.Properties", "Get",
153 get_property_callback, data, "ss", sni->interface, prop);
154 if (ret < 0) {
155 wlr_log(WLR_DEBUG, "Failed to get property %s: %s", prop, strerror(-ret));
156 }
157}
158
159static int handle_new_icon(sd_bus_message *msg, void *data, sd_bus_error *error) {
160 struct swaybar_sni *sni = data;
161 wlr_log(WLR_DEBUG, "%s has new IconName", sni->watcher_id);
162
163 free(sni->icon_name);
164 sni->icon_name = NULL;
165 sni_get_property_async(sni, "IconName", "s", &sni->icon_name);
166
167 list_free_items_and_destroy(sni->icon_pixmap);
168 sni->icon_pixmap = NULL;
169 sni_get_property_async(sni, "IconPixmap", NULL, &sni->icon_pixmap);
170
171 return 0;
172}
173
174static int handle_new_attention_icon(sd_bus_message *msg, void *data,
175 sd_bus_error *error) {
176 struct swaybar_sni *sni = data;
177 wlr_log(WLR_DEBUG, "%s has new AttentionIconName", sni->watcher_id);
178
179 free(sni->attention_icon_name);
180 sni->attention_icon_name = NULL;
181 sni_get_property_async(sni, "AttentionIconName", "s", &sni->attention_icon_name);
182
183 list_free_items_and_destroy(sni->attention_icon_pixmap);
184 sni->attention_icon_pixmap = NULL;
185 sni_get_property_async(sni, "AttentionIconPixmap", NULL, &sni->attention_icon_pixmap);
186
187 return 0;
188}
189
190static int handle_new_status(sd_bus_message *msg, void *data, sd_bus_error *error) {
191 char *status;
192 int ret = sd_bus_message_read(msg, "s", &status);
193 if (ret < 0) {
194 wlr_log(WLR_DEBUG, "Failed to read new status message: %s", strerror(-ret));
195 } else {
196 struct swaybar_sni *sni = data;
197 free(sni->status);
198 sni->status = strdup(status);
199 wlr_log(WLR_DEBUG, "%s has new Status '%s'", sni->watcher_id, status);
200 set_sni_dirty(sni);
201 }
202 return ret;
203}
204
205static void sni_match_signal(struct swaybar_sni *sni, char *signal,
206 sd_bus_message_handler_t callback) {
207 int ret = sd_bus_match_signal(sni->tray->bus, NULL, sni->service, sni->path,
208 sni->interface, signal, callback, sni);
209 if (ret < 0) {
210 wlr_log(WLR_DEBUG, "Failed to subscribe to signal %s: %s", signal,
211 strerror(-ret));
212 }
213}
214
215struct swaybar_sni *create_sni(char *id, struct swaybar_tray *tray) {
216 struct swaybar_sni *sni = calloc(1, sizeof(struct swaybar_sni));
217 if (!sni) {
218 return NULL;
219 }
220 sni->tray = tray;
221 sni->watcher_id = strdup(id);
222 char *path_ptr = strchr(id, '/');
223 if (!path_ptr) {
224 sni->service = strdup(id);
225 sni->path = strdup("/StatusNotifierItem");
226 sni->interface = "org.freedesktop.StatusNotifierItem";
227 } else {
228 sni->service = strndup(id, path_ptr - id);
229 sni->path = strdup(path_ptr);
230 sni->interface = "org.kde.StatusNotifierItem";
231 sni_get_property_async(sni, "IconThemePath", "s", &sni->icon_theme_path);
232 }
233
234 // Ignored: Category, Id, Title, WindowId, OverlayIconName,
235 // OverlayIconPixmap, AttentionMovieName, ToolTip
236 sni_get_property_async(sni, "Status", "s", &sni->status);
237 sni_get_property_async(sni, "IconName", "s", &sni->icon_name);
238 sni_get_property_async(sni, "IconPixmap", NULL, &sni->icon_pixmap);
239 sni_get_property_async(sni, "AttentionIconName", "s", &sni->attention_icon_name);
240 sni_get_property_async(sni, "AttentionIconPixmap", NULL, &sni->attention_icon_pixmap);
241 sni_get_property_async(sni, "ItemIsMenu", "b", &sni->item_is_menu);
242 sni_get_property_async(sni, "Menu", "o", &sni->menu);
243
244 sni_match_signal(sni, "NewIcon", handle_new_icon);
245 sni_match_signal(sni, "NewAttentionIcon", handle_new_attention_icon);
246 sni_match_signal(sni, "NewStatus", handle_new_status);
247
248 return sni;
249}
250
251void destroy_sni(struct swaybar_sni *sni) {
252 if (!sni) {
253 return;
254 }
255
256 free(sni->watcher_id);
257 free(sni->service);
258 free(sni->path);
259 free(sni->status);
260 free(sni->icon_name);
261 free(sni->icon_pixmap);
262 free(sni->attention_icon_name);
263 free(sni->menu);
264 free(sni);
265}
266
267static void handle_click(struct swaybar_sni *sni, int x, int y,
268 enum x11_button button, int delta) {
269 const char *method = sni->tray->bar->config->tray_bindings[button];
270 if (!method) {
271 static const char *default_bindings[10] = {
272 "nop",
273 "Activate",
274 "SecondaryActivate",
275 "ContextMenu",
276 "ScrollUp",
277 "ScrollDown",
278 "ScrollLeft",
279 "ScrollRight",
280 "nop",
281 "nop"
282 };
283 method = default_bindings[button];
284 }
285 if (strcmp(method, "nop") == 0) {
286 return;
287 }
288 if (sni->item_is_menu && strcmp(method, "Activate") == 0) {
289 method = "ContextMenu";
290 }
291
292 if (strncmp(method, "Scroll", strlen("Scroll")) == 0) {
293 char dir = method[strlen("Scroll")];
294 char *orientation = (dir = 'U' || dir == 'D') ? "vertical" : "horizontal";
295 int sign = (dir == 'U' || dir == 'L') ? -1 : 1;
296
297 int ret = sd_bus_call_method_async(sni->tray->bus, NULL, sni->service,
298 sni->path, sni->interface, "Scroll", NULL, NULL, "is",
299 delta*sign, orientation);
300 if (ret < 0) {
301 wlr_log(WLR_DEBUG, "Failed to scroll on SNI: %s", strerror(-ret));
302 }
303 } else {
304 int ret = sd_bus_call_method_async(sni->tray->bus, NULL, sni->service,
305 sni->path, sni->interface, method, NULL, NULL, "ii", x, y);
306 if (ret < 0) {
307 wlr_log(WLR_DEBUG, "Failed to click on SNI: %s", strerror(-ret));
308 }
309 }
310}
311
312static int cmp_sni_id(const void *item, const void *cmp_to) {
313 const struct swaybar_sni *sni = item;
314 return strcmp(sni->watcher_id, cmp_to);
315}
316
317static enum hotspot_event_handling icon_hotspot_callback(
318 struct swaybar_output *output, struct swaybar_hotspot *hotspot,
319 int x, int y, enum x11_button button, void *data) {
320 wlr_log(WLR_DEBUG, "Clicked on Status Notifier Item '%s'", (char *)data);
321
322 struct swaybar_tray *tray = output->bar->tray;
323 int idx = list_seq_find(tray->items, cmp_sni_id, data);
324
325 if (idx != -1) {
326 struct swaybar_sni *sni = tray->items->items[idx];
327 // guess global position since wayland doesn't expose it
328 struct swaybar_config *config = tray->bar->config;
329 int global_x = output->output_x + config->gaps.left + x;
330 bool top_bar = config->position & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
331 int global_y = output->output_y + (top_bar ? config->gaps.top + y:
332 (int) output->output_height - config->gaps.bottom - y);
333
334 wlr_log(WLR_DEBUG, "Guessing click at (%d, %d)", global_x, global_y);
335 handle_click(sni, global_x, global_y, button, 1); // TODO get delta from event
336 return HOTSPOT_IGNORE;
337 } else {
338 wlr_log(WLR_DEBUG, "but it doesn't exist");
339 }
340
341 return HOTSPOT_PROCESS;
342}
343
344uint32_t render_sni(cairo_t *cairo, struct swaybar_output *output, double *x,
345 struct swaybar_sni *sni) {
346 uint32_t height = output->height * output->scale;
347 int padding = output->bar->config->tray_padding;
348 int ideal_size = height - 2*padding;
349 if ((ideal_size < sni->min_size || ideal_size > sni->max_size) && sni_ready(sni)) {
350 bool icon_found = false;
351 char *icon_name = sni->status[0] == 'N' ?
352 sni->attention_icon_name : sni->icon_name;
353 if (icon_name) {
354 char *icon_path = find_icon(sni->tray->themes, sni->tray->basedirs,
355 icon_name, ideal_size, output->bar->config->icon_theme,
356 &sni->min_size, &sni->max_size);
357 if (!icon_path && sni->icon_theme_path) {
358 icon_path = find_icon_in_dir(icon_name, sni->icon_theme_path,
359 &sni->min_size, &sni->max_size);
360 }
361 if (icon_path) {
362 cairo_surface_destroy(sni->icon);
363 sni->icon = load_background_image(icon_path);
364 free(icon_path);
365 icon_found = true;
366 }
367 }
368 if (!icon_found) {
369 list_t *pixmaps = sni->status[0] == 'N' ?
370 sni->attention_icon_pixmap : sni->icon_pixmap;
371 if (pixmaps) {
372 int idx = -1;
373 unsigned smallest_error = -1; // UINT_MAX
374 for (int i = 0; i < pixmaps->length; ++i) {
375 struct swaybar_pixmap *pixmap = pixmaps->items[i];
376 unsigned error = (ideal_size - pixmap->size) *
377 (ideal_size < pixmap->size ? -1 : 1);
378 if (error < smallest_error) {
379 smallest_error = error;
380 idx = i;
381 }
382 }
383 struct swaybar_pixmap *pixmap = pixmaps->items[idx];
384 cairo_surface_destroy(sni->icon);
385 sni->icon = cairo_image_surface_create_for_data(pixmap->pixels,
386 CAIRO_FORMAT_ARGB32, pixmap->size, pixmap->size,
387 cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, pixmap->size));
388 }
389 }
390 }
391
392 int icon_size;
393 cairo_surface_t *icon;
394 if (sni->icon) {
395 int actual_size = cairo_image_surface_get_height(sni->icon);
396 icon_size = actual_size < ideal_size ?
397 actual_size*(ideal_size/actual_size) : ideal_size;
398 icon = cairo_image_surface_scale(sni->icon, icon_size, icon_size);
399 } else { // draw a sad face
400 icon_size = ideal_size*0.8;
401 icon = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, icon_size, icon_size);
402 cairo_t *cairo_icon = cairo_create(icon);
403 cairo_set_source_u32(cairo_icon, 0xFF0000FF);
404 cairo_translate(cairo_icon, icon_size/2, icon_size/2);
405 cairo_scale(cairo_icon, icon_size/2, icon_size/2);
406 cairo_arc(cairo_icon, 0, 0, 1, 0, 7);
407 cairo_fill(cairo_icon);
408 cairo_set_operator(cairo_icon, CAIRO_OPERATOR_CLEAR);
409 cairo_arc(cairo_icon, 0.35, -0.3, 0.1, 0, 7);
410 cairo_fill(cairo_icon);
411 cairo_arc(cairo_icon, -0.35, -0.3, 0.1, 0, 7);
412 cairo_fill(cairo_icon);
413 cairo_arc(cairo_icon, 0, 0.75, 0.5, 3.71238898038469, 5.71238898038469);
414 cairo_set_line_width(cairo_icon, 0.1);
415 cairo_stroke(cairo_icon);
416 cairo_destroy(cairo_icon);
417 }
418
419 int padded_size = icon_size + 2*padding;
420 *x -= padded_size;
421 int y = floor((height - padded_size) / 2.0);
422
423 cairo_operator_t op = cairo_get_operator(cairo);
424 cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
425 cairo_set_source_surface(cairo, icon, *x + padding, y + padding);
426 cairo_rectangle(cairo, *x, y, padded_size, padded_size);
427 cairo_fill(cairo);
428 cairo_set_operator(cairo, op);
429
430 cairo_surface_destroy(icon);
431
432 struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot));
433 hotspot->x = *x;
434 hotspot->y = 0;
435 hotspot->width = height;
436 hotspot->height = height;
437 hotspot->callback = icon_hotspot_callback;
438 hotspot->destroy = free;
439 hotspot->data = strdup(sni->watcher_id);
440 wl_list_insert(&output->hotspots, &hotspot->link);
441
442 return output->height;
443}
diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c
new file mode 100644
index 00000000..acc300af
--- /dev/null
+++ b/swaybar/tray/tray.c
@@ -0,0 +1,127 @@
1#include <cairo.h>
2#include <stdint.h>
3#include <stdlib.h>
4#include <string.h>
5#include "swaybar/config.h"
6#include "swaybar/bar.h"
7#include "swaybar/tray/icon.h"
8#include "swaybar/tray/host.h"
9#include "swaybar/tray/item.h"
10#include "swaybar/tray/tray.h"
11#include "swaybar/tray/watcher.h"
12#include "list.h"
13#include "log.h"
14
15static int handle_lost_watcher(sd_bus_message *msg,
16 void *data, sd_bus_error *error) {
17 char *service, *old_owner, *new_owner;
18 int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner);
19 if (ret < 0) {
20 wlr_log(WLR_ERROR, "Failed to parse owner change message: %s", strerror(-ret));
21 return ret;
22 }
23
24 if (!*new_owner) {
25 struct swaybar_tray *tray = data;
26 if (strcmp(service, "org.freedesktop.StatusNotifierWatcher") == 0) {
27 tray->watcher_xdg = create_watcher("freedesktop", tray->bus);
28 } else if (strcmp(service, "org.kde.StatusNotifierWatcher") == 0) {
29 tray->watcher_kde = create_watcher("kde", tray->bus);
30 }
31 }
32
33 return 0;
34}
35
36struct swaybar_tray *create_tray(struct swaybar *bar) {
37 wlr_log(WLR_DEBUG, "Initializing tray");
38
39 sd_bus *bus;
40 int ret = sd_bus_open_user(&bus);
41 if (ret < 0) {
42 wlr_log(WLR_ERROR, "Failed to connect to user bus: %s", strerror(-ret));
43 return NULL;
44 }
45
46 struct swaybar_tray *tray = calloc(1, sizeof(struct swaybar_tray));
47 if (!tray) {
48 return NULL;
49 }
50 tray->bar = bar;
51 tray->bus = bus;
52 tray->fd = sd_bus_get_fd(tray->bus);
53
54 tray->watcher_xdg = create_watcher("freedesktop", tray->bus);
55 tray->watcher_kde = create_watcher("kde", tray->bus);
56
57 ret = sd_bus_match_signal(bus, NULL, "org.freedesktop.DBus",
58 "/org/freedesktop/DBus", "org.freedesktop.DBus",
59 "NameOwnerChanged", handle_lost_watcher, tray);
60 if (ret < 0) {
61 wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s",
62 strerror(-ret));
63 }
64
65 tray->items = create_list();
66
67 init_host(&tray->host_xdg, "freedesktop", tray);
68 init_host(&tray->host_kde, "kde", tray);
69
70 init_themes(&tray->themes, &tray->basedirs);
71
72 return tray;
73}
74
75void destroy_tray(struct swaybar_tray *tray) {
76 if (!tray) {
77 return;
78 }
79 finish_host(&tray->host_xdg);
80 finish_host(&tray->host_kde);
81 for (int i = 0; i < tray->items->length; ++i) {
82 destroy_sni(tray->items->items[0]);
83 }
84 list_free(tray->items);
85 destroy_watcher(tray->watcher_xdg);
86 destroy_watcher(tray->watcher_kde);
87 sd_bus_flush_close_unref(tray->bus);
88 finish_themes(tray->themes, tray->basedirs);
89 free(tray);
90}
91
92void tray_in(int fd, short mask, void *data) {
93 sd_bus *bus = data;
94 int ret;
95 while ((ret = sd_bus_process(bus, NULL)) > 0) {
96 // This space intentionally left blank
97 }
98 if (ret < 0) {
99 wlr_log(WLR_ERROR, "Failed to process bus: %s", strerror(-ret));
100 }
101}
102
103static int cmp_output(const void *item, const void *cmp_to) {
104 return strcmp(item, cmp_to);
105}
106
107uint32_t render_tray(cairo_t *cairo, struct swaybar_output *output, double *x) {
108 struct swaybar_config *config = output->bar->config;
109 if (config->tray_outputs) {
110 if (list_seq_find(config->tray_outputs, cmp_output, output->name) == -1) {
111 return 0;
112 }
113 } // else display on all
114
115 if ((int) output->height*output->scale <= 2*config->tray_padding) {
116 return 2*config->tray_padding + 1;
117 }
118
119 uint32_t max_height = 0;
120 struct swaybar_tray *tray = output->bar->tray;
121 for (int i = 0; i < tray->items->length; ++i) {
122 uint32_t h = render_sni(cairo, output, x, tray->items->items[i]);
123 max_height = h > max_height ? h : max_height;
124 }
125
126 return max_height;
127}
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 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdbool.h>
3#include <stddef.h>
4#include <stdio.h>
5#include <stdlib.h>
6#include <string.h>
7#include "list.h"
8#include "log.h"
9#include "swaybar/tray/watcher.h"
10
11static const char *obj_path = "/StatusNotifierWatcher";
12
13static bool using_standard_protocol(struct swaybar_watcher *watcher) {
14 return watcher->interface[strlen("org.")] == 'f'; // freedesktop
15}
16
17static int cmp_id(const void *item, const void *cmp_to) {
18 return strcmp(item, cmp_to);
19}
20
21static int cmp_service(const void *item, const void *cmp_to) {
22 return strncmp(item, cmp_to, strlen(cmp_to));
23}
24
25static int handle_lost_service(sd_bus_message *msg,
26 void *data, sd_bus_error *error) {
27 char *service, *old_owner, *new_owner;
28 int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner);
29 if (ret < 0) {
30 wlr_log(WLR_ERROR, "Failed to parse owner change message: %s", strerror(-ret));
31 return ret;
32 }
33
34 if (!*new_owner) {
35 struct swaybar_watcher *watcher = data;
36 int idx = list_seq_find(watcher->items,
37 using_standard_protocol(watcher) ? cmp_id : cmp_service, service);
38 if (idx != -1) {
39 char *id = watcher->items->items[idx];
40 wlr_log(WLR_DEBUG, "Unregistering Status Notifier Item '%s'", id);
41 list_del(watcher->items, idx);
42 sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface,
43 "StatusNotifierItemUnregistered", "s", id);
44 free(id);
45 }
46
47 idx = list_seq_find(watcher->hosts, cmp_id, service);
48 if (idx != -1) {
49 wlr_log(WLR_DEBUG, "Unregistering Status Notifier Host '%s'", service);
50 free(watcher->hosts->items[idx]);
51 list_del(watcher->hosts, idx);
52 }
53 }
54
55 return 0;
56}
57
58static int register_sni(sd_bus_message *msg, void *data, sd_bus_error *error) {
59 char *service_or_path, *id;
60 int ret = sd_bus_message_read(msg, "s", &service_or_path);
61 if (ret < 0) {
62 wlr_log(WLR_ERROR, "Failed to parse register SNI message: %s", strerror(-ret));
63 return ret;
64 }
65
66 struct swaybar_watcher *watcher = data;
67 if (using_standard_protocol(watcher)) {
68 id = strdup(service_or_path);
69 } else {
70 const char *service, *path;
71 if (service_or_path[0] == '/') {
72 service = sd_bus_message_get_sender(msg);
73 path = service_or_path;
74 } else {
75 service = service_or_path;
76 path = "/StatusNotifierItem";
77 }
78 size_t id_len = snprintf(NULL, 0, "%s%s", service, path) + 1;
79 id = malloc(id_len);
80 snprintf(id, id_len, "%s%s", service, path);
81 }
82
83 if (list_seq_find(watcher->items, cmp_id, id) == -1) {
84 wlr_log(WLR_DEBUG, "Registering Status Notifier Item '%s'", id);
85 list_add(watcher->items, id);
86 sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface,
87 "StatusNotifierItemRegistered", "s", id);
88 } else {
89 wlr_log(WLR_DEBUG, "Status Notifier Item '%s' already registered", id);
90 free(id);
91 }
92
93 return sd_bus_reply_method_return(msg, "");
94}
95
96static int register_host(sd_bus_message *msg, void *data, sd_bus_error *error) {
97 char *service;
98 int ret = sd_bus_message_read(msg, "s", &service);
99 if (ret < 0) {
100 wlr_log(WLR_ERROR, "Failed to parse register host message: %s", strerror(-ret));
101 return ret;
102 }
103
104 struct swaybar_watcher *watcher = data;
105 if (list_seq_find(watcher->hosts, cmp_id, service) == -1) {
106 wlr_log(WLR_DEBUG, "Registering Status Notifier Host '%s'", service);
107 list_add(watcher->hosts, strdup(service));
108 sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface,
109 "StatusNotifierHostRegistered", "s", service);
110 } else {
111 wlr_log(WLR_DEBUG, "Status Notifier Host '%s' already registered", service);
112 }
113
114 return sd_bus_reply_method_return(msg, "");
115}
116
117static int get_registered_snis(sd_bus *bus, const char *obj_path,
118 const char *interface, const char *property, sd_bus_message *reply,
119 void *data, sd_bus_error *error) {
120 struct swaybar_watcher *watcher = data;
121 list_add(watcher->items, NULL); // strv expects NULL-terminated string array
122 int ret = sd_bus_message_append_strv(reply, (char **)watcher->items->items);
123 list_del(watcher->items, watcher->items->length - 1);
124 return ret;
125}
126
127static int is_host_registered(sd_bus *bus, const char *obj_path,
128 const char *interface, const char *property, sd_bus_message *reply,
129 void *data, sd_bus_error *error) {
130 struct swaybar_watcher *watcher = data;
131 int val = watcher->hosts->length > 0; // dbus expects int rather than bool
132 return sd_bus_message_append_basic(reply, 'b', &val);
133}
134
135static const sd_bus_vtable watcher_vtable[] = {
136 SD_BUS_VTABLE_START(0),
137 SD_BUS_METHOD("RegisterStatusNotifierItem", "s", "", register_sni,
138 SD_BUS_VTABLE_UNPRIVILEGED),
139 SD_BUS_METHOD("RegisterStatusNotifierHost", "s", "", register_host,
140 SD_BUS_VTABLE_UNPRIVILEGED),
141 SD_BUS_PROPERTY("RegisteredStatusNotifierItems", "as", get_registered_snis,
142 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
143 SD_BUS_PROPERTY("IsStatusNotifierHostRegistered", "b", is_host_registered,
144 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
145 SD_BUS_PROPERTY("ProtocolVersion", "i", NULL,
146 offsetof(struct swaybar_watcher, version),
147 SD_BUS_VTABLE_PROPERTY_CONST),
148 SD_BUS_SIGNAL("StatusNotifierItemRegistered", "s", 0),
149 SD_BUS_SIGNAL("StatusNotifierItemUnregistered", "s", 0),
150 SD_BUS_SIGNAL("StatusNotifierHostRegistered", NULL, 0),
151 SD_BUS_VTABLE_END
152};
153
154struct swaybar_watcher *create_watcher(char *protocol, sd_bus *bus) {
155 struct swaybar_watcher *watcher =
156 calloc(1, sizeof(struct swaybar_watcher));
157 if (!watcher) {
158 return NULL;
159 }
160
161 size_t len = snprintf(NULL, 0, "org.%s.StatusNotifierWatcher", protocol) + 1;
162 watcher->interface = malloc(len);
163 snprintf(watcher->interface, len, "org.%s.StatusNotifierWatcher", protocol);
164
165 sd_bus_slot *signal_slot = NULL, *vtable_slot = NULL;
166 int ret = sd_bus_add_object_vtable(bus, &vtable_slot, obj_path,
167 watcher->interface, watcher_vtable, watcher);
168 if (ret < 0) {
169 wlr_log(WLR_ERROR, "Failed to add object vtable: %s", strerror(-ret));
170 goto error;
171 }
172
173 ret = sd_bus_match_signal(bus, &signal_slot, "org.freedesktop.DBus",
174 "/org/freedesktop/DBus", "org.freedesktop.DBus",
175 "NameOwnerChanged", handle_lost_service, watcher);
176 if (ret < 0) {
177 wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s",
178 strerror(-ret));
179 goto error;
180 }
181
182 ret = sd_bus_request_name(bus, watcher->interface, 0);
183 if (ret < 0) {
184 wlr_log(WLR_ERROR, "Failed to acquire service name: %s", strerror(-ret));
185 goto error;
186 }
187
188 sd_bus_slot_set_floating(signal_slot, 0);
189 sd_bus_slot_set_floating(vtable_slot, 0);
190
191 watcher->bus = bus;
192 watcher->hosts = create_list();
193 watcher->items = create_list();
194 watcher->version = 0;
195 wlr_log(WLR_DEBUG, "Registered %s", watcher->interface);
196 return watcher;
197error:
198 sd_bus_slot_unref(signal_slot);
199 sd_bus_slot_unref(vtable_slot);
200 destroy_watcher(watcher);
201 return NULL;
202}
203
204void destroy_watcher(struct swaybar_watcher *watcher) {
205 if (!watcher) {
206 return;
207 }
208 list_free_items_and_destroy(watcher->hosts);
209 list_free_items_and_destroy(watcher->items);
210 free(watcher->interface);
211 free(watcher);
212}