diff options
author | Drew DeVault <sir@cmpwn.com> | 2018-12-31 15:43:23 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-12-31 15:43:23 -0500 |
commit | 3d7c20f062bebe39199d3872e5b64d5e8d61d100 (patch) | |
tree | 93b8ddb51873cae14db8d5f07db74ca96236f184 | |
parent | Apply implicit fallback seat config (diff) | |
parent | swaybar: use KDE's SNI IconThemePath property (diff) | |
download | sway-3d7c20f062bebe39199d3872e5b64d5e8d61d100.tar.gz sway-3d7c20f062bebe39199d3872e5b64d5e8d61d100.tar.zst sway-3d7c20f062bebe39199d3872e5b64d5e8d61d100.zip |
Merge pull request #3249 from ianyfan/tray
Swaybar tray
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; | |||
182 | sway_cmd cmd_ws_auto_back_and_forth; | 182 | sway_cmd cmd_ws_auto_back_and_forth; |
183 | sway_cmd cmd_workspace_layout; | 183 | sway_cmd cmd_workspace_layout; |
184 | 184 | ||
185 | sway_cmd bar_cmd_activate_button; | ||
186 | sway_cmd bar_cmd_binding_mode_indicator; | 185 | sway_cmd bar_cmd_binding_mode_indicator; |
187 | sway_cmd bar_cmd_bindsym; | 186 | sway_cmd bar_cmd_bindsym; |
188 | sway_cmd bar_cmd_colors; | 187 | sway_cmd bar_cmd_colors; |
189 | sway_cmd bar_cmd_context_button; | ||
190 | sway_cmd bar_cmd_font; | 188 | sway_cmd bar_cmd_font; |
191 | sway_cmd bar_cmd_gaps; | 189 | sway_cmd bar_cmd_gaps; |
192 | sway_cmd bar_cmd_mode; | 190 | sway_cmd bar_cmd_mode; |
@@ -197,13 +195,13 @@ sway_cmd bar_cmd_hidden_state; | |||
197 | sway_cmd bar_cmd_icon_theme; | 195 | sway_cmd bar_cmd_icon_theme; |
198 | sway_cmd bar_cmd_id; | 196 | sway_cmd bar_cmd_id; |
199 | sway_cmd bar_cmd_position; | 197 | sway_cmd bar_cmd_position; |
200 | sway_cmd bar_cmd_secondary_button; | ||
201 | sway_cmd bar_cmd_separator_symbol; | 198 | sway_cmd bar_cmd_separator_symbol; |
202 | sway_cmd bar_cmd_status_command; | 199 | sway_cmd bar_cmd_status_command; |
203 | sway_cmd bar_cmd_pango_markup; | 200 | sway_cmd bar_cmd_pango_markup; |
204 | sway_cmd bar_cmd_strip_workspace_numbers; | 201 | sway_cmd bar_cmd_strip_workspace_numbers; |
205 | sway_cmd bar_cmd_strip_workspace_name; | 202 | sway_cmd bar_cmd_strip_workspace_name; |
206 | sway_cmd bar_cmd_swaybar_command; | 203 | sway_cmd bar_cmd_swaybar_command; |
204 | sway_cmd bar_cmd_tray_bindsym; | ||
207 | sway_cmd bar_cmd_tray_output; | 205 | sway_cmd bar_cmd_tray_output; |
208 | sway_cmd bar_cmd_tray_padding; | 206 | sway_cmd bar_cmd_tray_padding; |
209 | sway_cmd bar_cmd_wrap_scroll; | 207 | sway_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 | ||
258 | struct bar_binding { | 266 | struct 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 | ||
9 | struct swaybar_config; | 10 | struct swaybar_config; |
10 | struct swaybar_output; | 11 | struct swaybar_output; |
12 | #if HAVE_TRAY | ||
13 | struct swaybar_tray; | ||
14 | #endif | ||
11 | struct swaybar_workspace; | 15 | struct swaybar_workspace; |
12 | struct loop; | 16 | struct 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 | ||
43 | struct swaybar_output { | 51 | struct 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 | ||
67 | struct swaybar_workspace { | 77 | struct swaybar_workspace { |
@@ -78,6 +88,8 @@ bool bar_setup(struct swaybar *bar, const char *socket_path); | |||
78 | void bar_run(struct swaybar *bar); | 88 | void bar_run(struct swaybar *bar); |
79 | void bar_teardown(struct swaybar *bar); | 89 | void bar_teardown(struct swaybar *bar); |
80 | 90 | ||
91 | void 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 | ||
69 | struct swaybar_config *init_config(void); | 78 | struct 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 | |||
6 | struct swaybar_tray; | ||
7 | |||
8 | struct swaybar_host { | ||
9 | struct swaybar_tray *tray; | ||
10 | char *service; | ||
11 | char *watcher_interface; | ||
12 | }; | ||
13 | |||
14 | bool init_host(struct swaybar_host *host, char *protocol, struct swaybar_tray *tray); | ||
15 | void 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 | /** | 6 | enum subdir_type { |
9 | * Returns the image found by `name` that is closest to `size` | 7 | THRESHOLD, |
10 | */ | 8 | SCALABLE, |
11 | cairo_surface_t *find_icon(const char *name, int size); | 9 | FIXED |
10 | }; | ||
11 | |||
12 | struct 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 | |||
21 | struct 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; |
14 | struct subdir; | 28 | list_t *subdirs; // struct icon_theme_subdir * |
29 | }; | ||
30 | |||
31 | void init_themes(list_t **themes, list_t **basedirs); | ||
32 | void 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 | */ | ||
40 | char *find_icon(list_t *themes, list_t *basedirs, char *name, int size, | ||
41 | char *theme, int *min_size, int *max_size); | ||
42 | char *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 | |||
10 | struct swaybar_output; | ||
11 | |||
12 | struct swaybar_pixmap { | ||
13 | int size; | ||
14 | unsigned char pixels[]; | ||
15 | }; | ||
16 | |||
17 | struct 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 | |||
40 | struct swaybar_sni *create_sni(char *id, struct swaybar_tray *tray); | ||
41 | void destroy_sni(struct swaybar_sni *sni); | ||
42 | uint32_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 | |||
7 | struct 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 */ | ||
19 | struct sni_icon_ref { | ||
20 | cairo_surface_t *icon; | ||
21 | struct StatusNotifierItem *ref; | ||
22 | }; | ||
23 | |||
24 | struct sni_icon_ref *sni_icon_ref_create(struct StatusNotifierItem *item, | ||
25 | int height); | ||
26 | |||
27 | void 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 | */ | ||
33 | struct 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 | */ | ||
41 | int 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 | */ | ||
47 | int 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 | */ | ||
56 | void 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 | */ | ||
63 | void 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 | */ | ||
68 | void 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 | */ | ||
75 | void sni_secondary(struct StatusNotifierItem *item, uint32_t x, uint32_t y); | ||
76 | |||
77 | /** | ||
78 | * Deconstructs `item` | ||
79 | */ | ||
80 | void 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 | |||
15 | struct swaybar; | ||
16 | struct swaybar_output; | ||
17 | struct swaybar_watcher; | ||
18 | |||
19 | struct 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 | |||
35 | struct swaybar_tray *create_tray(struct swaybar *bar); | ||
36 | void destroy_tray(struct swaybar_tray *tray); | ||
37 | void tray_in(int fd, short mask, void *data); | ||
38 | uint32_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 | |||
7 | struct swaybar_watcher { | ||
8 | char *interface; | ||
9 | sd_bus *bus; | ||
10 | list_t *hosts; | ||
11 | list_t *items; | ||
12 | int version; | ||
13 | }; | ||
14 | |||
15 | struct swaybar_watcher *create_watcher(char *protocol, sd_bus *bus); | ||
16 | void 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 | |||
66 | conf_data.set10('HAVE_GDK_PIXBUF', gdk_pixbuf.found()) | 66 | conf_data.set10('HAVE_GDK_PIXBUF', gdk_pixbuf.found()) |
67 | conf_data.set10('HAVE_SYSTEMD', systemd.found()) | 67 | conf_data.set10('HAVE_SYSTEMD', systemd.found()) |
68 | conf_data.set10('HAVE_ELOGIND', elogind.found()) | 68 | conf_data.set10('HAVE_ELOGIND', elogind.found()) |
69 | conf_data.set10('HAVE_TRAY', get_option('enable-tray') and (systemd.found() or elogind.found())) | ||
69 | 70 | ||
70 | if not systemd.found() and not elogind.found() | 71 | if 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 | |||
6 | option('bash-completions', type: 'boolean', value: true, description: 'Install bash shell completions.') | 6 | option('bash-completions', type: 'boolean', value: true, description: 'Install bash shell completions.') |
7 | option('fish-completions', type: 'boolean', value: true, description: 'Install fish shell completions.') | 7 | option('fish-completions', type: 'boolean', value: true, description: 'Install fish shell completions.') |
8 | option('enable-xwayland', type: 'boolean', value: true, description: 'Enable support for X11 applications') | 8 | option('enable-xwayland', type: 'boolean', value: true, description: 'Enable support for X11 applications') |
9 | option('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 |
10 | static struct cmd_handler bar_handlers[] = { | 10 | static 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 | |||
5 | struct 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 | |||
5 | struct 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 | ||
4 | struct cmd_results *bar_cmd_icon_theme(int argc, char **argv) { | 8 | struct 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 | |||
5 | struct 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 | |||
7 | struct 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 | ||
4 | struct cmd_results *bar_cmd_tray_output(int argc, char **argv) { | 9 | struct 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 | ||
6 | struct cmd_results *bar_cmd_tray_padding(int argc, char **argv) { | 8 | struct 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; |
170 | cleanup: | 179 | cleanup: |
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. | |||
100 | The _button_ argument in all cases is a platform-specific button code. On Linux | 100 | The _button_ argument in all cases is a platform-specific button code. On Linux |
101 | you can find a list of these at linux/input-event-codes.h. | 101 | you 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 | ||
123 | static void set_bar_dirty(struct swaybar *bar) { | 127 | void 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 | ||
212 | static void xdg_output_handle_logical_position(void *data, | 216 | static 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 | ||
217 | static void xdg_output_handle_logical_size(void *data, | 223 | static 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 | ||
222 | static void xdg_output_handle_done(void *data, | 230 | static 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 | ||
422 | void bar_teardown(struct swaybar *bar) { | 441 | void 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 @@ | |||
1 | tray_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 | |||
9 | swaybar_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 | ] | ||
22 | if get_option('enable-tray') | ||
23 | if systemd.found() | ||
24 | swaybar_deps += systemd | ||
25 | elif elogind.found() | ||
26 | swaybar_deps += elogind | ||
27 | endif | ||
28 | endif | ||
29 | |||
1 | executable( | 30 | executable( |
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 | ||
19 | static const int WS_HORIZONTAL_PADDING = 5; | 22 | static 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 | |||
14 | static const char *watcher_path = "/StatusNotifierWatcher"; | ||
15 | |||
16 | static 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 | |||
21 | static 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 | |||
32 | static 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 | |||
46 | static 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 | |||
65 | static 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 | |||
96 | static 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 | |||
118 | static 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 | |||
137 | bool 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, ®_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; | ||
198 | error: | ||
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 | |||
206 | void 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 | |||
17 | static bool dir_exists(char *path) { | ||
18 | struct stat sb; | ||
19 | return stat(path, &sb) == 0 && S_ISDIR(sb.st_mode); | ||
20 | } | ||
21 | |||
22 | static 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 | |||
62 | static 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 | |||
81 | static int cmp_group(const void *item, const void *cmp_to) { | ||
82 | return strcmp(item, cmp_to); | ||
83 | } | ||
84 | |||
85 | static 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 | |||
131 | static 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 | */ | ||
189 | static 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 | |||
280 | static 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 | |||
299 | void 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 | |||
318 | void 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 | |||
326 | static 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 | |||
354 | static 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 | |||
363 | static 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 | |||
425 | char *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 | |||
435 | static 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 | |||
446 | char *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 | |||
21 | static 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 | |||
27 | static 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 | |||
34 | static 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; | ||
84 | error: | ||
85 | list_free_items_and_destroy(pixmaps); | ||
86 | return ret; | ||
87 | } | ||
88 | |||
89 | struct get_property_data { | ||
90 | struct swaybar_sni *sni; | ||
91 | const char *prop; | ||
92 | const char *type; | ||
93 | void *dest; | ||
94 | }; | ||
95 | |||
96 | static 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 | } | ||
139 | cleanup: | ||
140 | free(data); | ||
141 | return ret; | ||
142 | } | ||
143 | |||
144 | static 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 | |||
159 | static 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 | |||
174 | static 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 | |||
190 | static 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 | |||
205 | static 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 | |||
215 | struct 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 | |||
251 | void 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 | |||
267 | static 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 | |||
312 | static 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 | |||
317 | static 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 | |||
344 | uint32_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 | |||
15 | static 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 | |||
36 | struct 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 | |||
75 | void 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 | |||
92 | void 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 | |||
103 | static int cmp_output(const void *item, const void *cmp_to) { | ||
104 | return strcmp(item, cmp_to); | ||
105 | } | ||
106 | |||
107 | uint32_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 | |||
11 | static const char *obj_path = "/StatusNotifierWatcher"; | ||
12 | |||
13 | static bool using_standard_protocol(struct swaybar_watcher *watcher) { | ||
14 | return watcher->interface[strlen("org.")] == 'f'; // freedesktop | ||
15 | } | ||
16 | |||
17 | static int cmp_id(const void *item, const void *cmp_to) { | ||
18 | return strcmp(item, cmp_to); | ||
19 | } | ||
20 | |||
21 | static int cmp_service(const void *item, const void *cmp_to) { | ||
22 | return strncmp(item, cmp_to, strlen(cmp_to)); | ||
23 | } | ||
24 | |||
25 | static 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 | |||
58 | static 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 | |||
96 | static 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 | |||
117 | static 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 | |||
127 | static 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 | |||
135 | static 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 | |||
154 | struct 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; | ||
197 | error: | ||
198 | sd_bus_slot_unref(signal_slot); | ||
199 | sd_bus_slot_unref(vtable_slot); | ||
200 | destroy_watcher(watcher); | ||
201 | return NULL; | ||
202 | } | ||
203 | |||
204 | void 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 | } | ||