aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--common/meson.build3
-rw-r--r--common/pango.c67
-rw-r--r--include/pango.h16
-rw-r--r--include/sway/config.h7
-rw-r--r--include/swaybar/bar.h89
-rw-r--r--include/swaybar/config.h43
-rw-r--r--include/swaybar/event_loop.h4
-rw-r--r--include/swaybar/ipc.h22
-rw-r--r--include/swaybar/render.h22
-rw-r--r--meson.build3
-rw-r--r--swaybar/bar.c444
-rw-r--r--swaybar/config.c37
-rw-r--r--swaybar/event_loop.c10
-rw-r--r--swaybar/ipc.c410
-rw-r--r--swaybar/main.c33
-rw-r--r--swaybar/meson.build25
-rw-r--r--swaybar/render.c388
-rw-r--r--swaybar/status_line.c530
-rw-r--r--swaybar/tray/dbus.c197
-rw-r--r--swaybar/tray/icon.c400
-rw-r--r--swaybar/tray/sni.c481
-rw-r--r--swaybar/tray/sni_watcher.c497
-rw-r--r--swaybar/tray/tray.c398
23 files changed, 342 insertions, 3784 deletions
diff --git a/common/meson.build b/common/meson.build
index 01736ca6..4ad47077 100644
--- a/common/meson.build
+++ b/common/meson.build
@@ -1,5 +1,7 @@
1deps = [ 1deps = [
2 cairo, 2 cairo,
3 pango,
4 pangocairo,
3 wlroots 5 wlroots
4] 6]
5 7
@@ -14,6 +16,7 @@ lib_sway_common = static_library(
14 'ipc-client.c', 16 'ipc-client.c',
15 'log.c', 17 'log.c',
16 'list.c', 18 'list.c',
19 'pango.c',
17 'readline.c', 20 'readline.c',
18 'stringop.c', 21 'stringop.c',
19 'util.c' 22 'util.c'
diff --git a/common/pango.c b/common/pango.c
new file mode 100644
index 00000000..212d96cf
--- /dev/null
+++ b/common/pango.c
@@ -0,0 +1,67 @@
1#include <cairo/cairo.h>
2#include <pango/pangocairo.h>
3#include <stdarg.h>
4#include <stdbool.h>
5#include <stdint.h>
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9
10PangoLayout *get_pango_layout(cairo_t *cairo, const char *font,
11 const char *text, int32_t scale, bool markup) {
12 PangoLayout *layout = pango_cairo_create_layout(cairo);
13 PangoAttrList *attrs;
14 if (markup) {
15 char *buf;
16 pango_parse_markup(text, -1, 0, &attrs, &buf, NULL, NULL);
17 pango_layout_set_markup(layout, buf, -1);
18 free(buf);
19 } else {
20 attrs = pango_attr_list_new();
21 pango_layout_set_text(layout, text, -1);
22 }
23 pango_attr_list_insert(attrs, pango_attr_scale_new(scale));
24 PangoFontDescription *desc = pango_font_description_from_string(font);
25 pango_layout_set_font_description(layout, desc);
26 pango_layout_set_single_paragraph_mode(layout, 1);
27 pango_layout_set_attributes(layout, attrs);
28 pango_attr_list_unref(attrs);
29 pango_font_description_free(desc);
30 return layout;
31}
32
33void get_text_size(cairo_t *cairo, const char *font, int *width, int *height,
34 int32_t scale, bool markup, const char *fmt, ...) {
35 char *buf = malloc(2048);
36
37 va_list args;
38 va_start(args, fmt);
39 if (vsnprintf(buf, 2048, fmt, args) >= 2048) {
40 strcpy(buf, "[buffer overflow]");
41 }
42 va_end(args);
43
44 PangoLayout *layout = get_pango_layout(cairo, font, buf, scale, markup);
45 pango_cairo_update_layout(cairo, layout);
46 pango_layout_get_pixel_size(layout, width, height);
47 g_object_unref(layout);
48 free(buf);
49}
50
51void pango_printf(cairo_t *cairo, const char *font,
52 int32_t scale, bool markup, const char *fmt, ...) {
53 char *buf = malloc(2048);
54
55 va_list args;
56 va_start(args, fmt);
57 if (vsnprintf(buf, 2048, fmt, args) >= 2048) {
58 strcpy(buf, "[buffer overflow]");
59 }
60 va_end(args);
61
62 PangoLayout *layout = get_pango_layout(cairo, font, buf, scale, markup);
63 pango_cairo_update_layout(cairo, layout);
64 pango_cairo_show_layout(cairo, layout);
65 g_object_unref(layout);
66 free(buf);
67}
diff --git a/include/pango.h b/include/pango.h
new file mode 100644
index 00000000..f6325f28
--- /dev/null
+++ b/include/pango.h
@@ -0,0 +1,16 @@
1#ifndef _SWAY_PANGO_H
2#define _SWAY_PANGO_H
3#include <stdarg.h>
4#include <stdbool.h>
5#include <stdint.h>
6#include <cairo/cairo.h>
7#include <pango/pangocairo.h>
8
9PangoLayout *get_pango_layout(cairo_t *cairo, const char *font,
10 const char *text, int32_t scale, bool markup);
11void get_text_size(cairo_t *cairo, const char *font, int *width, int *height,
12 int32_t scale, bool markup, const char *fmt, ...);
13void pango_printf(cairo_t *cairo, const char *font,
14 int32_t scale, bool markup, const char *fmt, ...);
15
16#endif
diff --git a/include/sway/config.h b/include/sway/config.h
index 48a8b0ab..8c9e04de 100644
--- a/include/sway/config.h
+++ b/include/sway/config.h
@@ -1,17 +1,16 @@
1#ifndef _SWAY_CONFIG_H 1#ifndef _SWAY_CONFIG_H
2#define _SWAY_CONFIG_H 2#define _SWAY_CONFIG_H
3
4#define PID_WORKSPACE_TIMEOUT 60 3#define PID_WORKSPACE_TIMEOUT 60
5
6#include <libinput.h> 4#include <libinput.h>
7#include <stdint.h> 5#include <stdint.h>
8#include <string.h> 6#include <string.h>
7#include <time.h>
9#include <wlr/types/wlr_box.h> 8#include <wlr/types/wlr_box.h>
10#include <xkbcommon/xkbcommon.h> 9#include <xkbcommon/xkbcommon.h>
11#include <time.h>
12#include "list.h" 10#include "list.h"
13#include "layout.h" 11#include "layout.h"
14#include "container.h" 12#include "container.h"
13#include "wlr-layer-shell-unstable-v1-protocol.h"
15 14
16/** 15/**
17 * Describes a variable created via the `set` command. 16 * Describes a variable created via the `set` command.
@@ -152,7 +151,7 @@ struct bar_config {
152 char *id; 151 char *id;
153 uint32_t modifier; 152 uint32_t modifier;
154 list_t *outputs; 153 list_t *outputs;
155 //enum desktop_shell_panel_position position; // TODO 154 char *position;
156 list_t *bindings; 155 list_t *bindings;
157 char *status_command; 156 char *status_command;
158 bool pango_markup; 157 bool pango_markup;
diff --git a/include/swaybar/bar.h b/include/swaybar/bar.h
index 50d36e76..3ae8c0b3 100644
--- a/include/swaybar/bar.h
+++ b/include/swaybar/bar.h
@@ -1,72 +1,45 @@
1#ifndef _SWAYBAR_BAR_H 1#ifndef _SWAYBAR_BAR_H
2#define _SWAYBAR_BAR_H 2#define _SWAYBAR_BAR_H
3 3#include <wayland-client.h>
4#include "client/registry.h" 4#include "pool-buffer.h"
5#include "client/window.h"
6#include "list.h" 5#include "list.h"
7 6
8struct bar { 7struct swaybar_config;
9 struct config *config; 8struct swaybar_output;
10 struct status_line *status; 9struct swaybar_workspace;
11 list_t *outputs; 10
12 struct output *focused_output; 11struct swaybar {
12 struct wl_display *display;
13 struct wl_compositor *compositor;
14 struct zwlr_layer_shell_v1 *layer_shell;
15 struct wl_shm *shm;
13 16
14 int ipc_event_socketfd; 17 struct swaybar_config *config;
15 int ipc_socketfd; 18 struct swaybar_output *focused_output;
16 int status_read_fd; 19
17 int status_write_fd; 20 struct wl_list outputs;
18 pid_t status_command_pid;
19}; 21};
20 22
21struct output { 23struct swaybar_output {
22 struct window *window; 24 struct wl_list link;
23 struct registry *registry; 25 struct swaybar *bar;
24 list_t *workspaces; 26 struct wl_output *output;
25#ifdef ENABLE_TRAY 27 struct wl_surface *surface;
26 list_t *items; 28 struct zwlr_layer_surface_v1 *layer_surface;
27#endif 29
28 char *name; 30 char *name;
29 int idx; 31 int idx;
30 bool focused; 32 bool focused;
31};
32 33
33struct workspace { 34 uint32_t width, height;
34 int num; 35 struct pool_buffer buffers[2];
35 char *name; 36 struct pool_buffer *current_buffer;
36 bool focused;
37 bool visible;
38 bool urgent;
39}; 37};
40 38
41/** Global bar state */ 39void bar_setup(struct swaybar *bar,
42extern struct bar swaybar; 40 const char *socket_path,
41 const char *bar_id);
42void bar_run(struct swaybar *bar);
43void bar_teardown(struct swaybar *bar);
43 44
44/** True if sway needs to render */ 45#endif
45extern bool dirty;
46
47/**
48 * Setup bar.
49 */
50void bar_setup(struct bar *bar, const char *socket_path, const char *bar_id);
51
52/**
53 * Create new output struct from name.
54 */
55struct output *new_output(const char *name);
56
57/**
58 * Bar mainloop.
59 */
60void bar_run(struct bar *bar);
61
62/**
63 * free workspace list.
64 */
65void free_workspaces(list_t *workspaces);
66
67/**
68 * Teardown bar.
69 */
70void bar_teardown(struct bar *bar);
71
72#endif /* _SWAYBAR_BAR_H */
diff --git a/include/swaybar/config.h b/include/swaybar/config.h
index 651f0ee3..1bfe4843 100644
--- a/include/swaybar/config.h
+++ b/include/swaybar/config.h
@@ -1,9 +1,7 @@
1#ifndef _SWAYBAR_CONFIG_H 1#ifndef _SWAYBAR_CONFIG_H
2#define _SWAYBAR_CONFIG_H 2#define _SWAYBAR_CONFIG_H
3
4#include <stdint.h>
5#include <stdbool.h> 3#include <stdbool.h>
6 4#include <stdint.h>
7#include "list.h" 5#include "list.h"
8#include "util.h" 6#include "util.h"
9 7
@@ -19,10 +17,10 @@ struct box_colors {
19/** 17/**
20 * Swaybar config. 18 * Swaybar config.
21 */ 19 */
22struct config { 20struct swaybar_config {
23 char *status_command; 21 char *status_command;
24 bool pango_markup; 22 bool pango_markup;
25 uint32_t position; 23 uint32_t position; // zwlr_layer_surface_v1_anchor
26 char *font; 24 char *font;
27 char *sep_symbol; 25 char *sep_symbol;
28 char *mode; 26 char *mode;
@@ -32,18 +30,6 @@ struct config {
32 bool workspace_buttons; 30 bool workspace_buttons;
33 bool all_outputs; 31 bool all_outputs;
34 list_t *outputs; 32 list_t *outputs;
35
36#ifdef ENABLE_TRAY
37 // Tray
38 char *tray_output;
39 char *icon_theme;
40
41 uint32_t tray_padding;
42 uint32_t activate_button;
43 uint32_t context_button;
44 uint32_t secondary_button;
45#endif
46
47 int height; 33 int height;
48 34
49 struct { 35 struct {
@@ -63,24 +49,7 @@ struct config {
63 } colors; 49 } colors;
64}; 50};
65 51
66/** 52struct swaybar_config *init_config();
67 * Parse position top|bottom|left|right. 53void free_config(struct swaybar_config *config);
68 */
69uint32_t parse_position(const char *position);
70
71/**
72 * Parse font.
73 */
74char *parse_font(const char *font);
75
76/**
77 * Initialize default sway config.
78 */
79struct config *init_config();
80
81/**
82 * Free config struct.
83 */
84void free_config(struct config *config);
85 54
86#endif /* _SWAYBAR_CONFIG_H */ 55#endif
diff --git a/include/swaybar/event_loop.h b/include/swaybar/event_loop.h
index a0cde07f..99f6ed36 100644
--- a/include/swaybar/event_loop.h
+++ b/include/swaybar/event_loop.h
@@ -1,6 +1,5 @@
1#ifndef _SWAYBAR_EVENT_LOOP_H 1#ifndef _SWAYBAR_EVENT_LOOP_H
2#define _SWAYBAR_EVENT_LOOP_H 2#define _SWAYBAR_EVENT_LOOP_H
3
4#include <stdbool.h> 3#include <stdbool.h>
5#include <time.h> 4#include <time.h>
6 5
@@ -23,4 +22,5 @@ bool remove_timer(timer_t timer);
23void event_loop_poll(); 22void event_loop_poll();
24 23
25void init_event_loop(); 24void init_event_loop();
26#endif /*_SWAYBAR_EVENT_LOOP_H */ 25
26#endif
diff --git a/include/swaybar/ipc.h b/include/swaybar/ipc.h
index c11931d0..57a1b925 100644
--- a/include/swaybar/ipc.h
+++ b/include/swaybar/ipc.h
@@ -1,23 +1,9 @@
1#ifndef _SWAYBAR_IPC_H 1#ifndef _SWAYBAR_IPC_H
2#define _SWAYBAR_IPC_H 2#define _SWAYBAR_IPC_H
3#include "swaybar/bar.h"
3 4
4#include "bar.h" 5void ipc_bar_init(struct swaybar *bar, const char *bar_id);
5 6bool handle_ipc_event(struct swaybar *bar);
6/**
7 * Initialize ipc connection to sway and get sway state, outputs, bar_config.
8 */
9void ipc_bar_init(struct bar *bar, const char *bar_id);
10
11/**
12 * Handle ipc event from sway.
13 */
14bool handle_ipc_event(struct bar *bar);
15
16
17/**
18 * Send workspace command to sway
19 */
20void ipc_send_workspace_command(const char *workspace_name); 7void ipc_send_workspace_command(const char *workspace_name);
21 8
22#endif /* _SWAYBAR_IPC_H */ 9#endif
23
diff --git a/include/swaybar/render.h b/include/swaybar/render.h
index 114f43f4..071e2298 100644
--- a/include/swaybar/render.h
+++ b/include/swaybar/render.h
@@ -1,22 +1,10 @@
1#ifndef _SWAYBAR_RENDER_H 1#ifndef _SWAYBAR_RENDER_H
2#define _SWAYBAR_RENDER_H 2#define _SWAYBAR_RENDER_H
3 3
4#include "config.h" 4struct swaybar;
5#include "bar.h" 5struct swaybar_output;
6struct swaybar_config;
6 7
7/** 8void render_frame(struct swaybar *bar, struct swaybar_output *output);
8 * Render swaybar.
9 */
10void render(struct output *output, struct config *config, struct status_line *line);
11 9
12/** 10#endif
13 * Set window height and modify internal spacing accordingly.
14 */
15void set_window_height(struct window *window, int height);
16
17/**
18 * Compute the size of a workspace name
19 */
20void workspace_button_size(struct window *window, const char *workspace_name, int *width, int *height);
21
22#endif /* _SWAYBAR_RENDER_H */
diff --git a/meson.build b/meson.build
index b681f43a..49824b30 100644
--- a/meson.build
+++ b/meson.build
@@ -35,6 +35,7 @@ pixman = dependency('pixman-1')
35libcap = dependency('libcap') 35libcap = dependency('libcap')
36libinput = dependency('libinput') 36libinput = dependency('libinput')
37math = cc.find_library('m') 37math = cc.find_library('m')
38rt = cc.find_library('rt')
38git = find_program('git', required: false) 39git = find_program('git', required: false)
39a2x = find_program('a2x', required: false) 40a2x = find_program('a2x', required: false)
40 41
@@ -99,8 +100,10 @@ subdir('protocols')
99subdir('common') 100subdir('common')
100subdir('sway') 101subdir('sway')
101subdir('swaymsg') 102subdir('swaymsg')
103
102subdir('client') 104subdir('client')
103subdir('swaybg') 105subdir('swaybg')
106subdir('swaybar')
104 107
105config = configuration_data() 108config = configuration_data()
106config.set('sysconfdir', join_paths(prefix, sysconfdir)) 109config.set('sysconfdir', join_paths(prefix, sysconfdir))
diff --git a/swaybar/bar.c b/swaybar/bar.c
index f12923a8..e1d594b4 100644
--- a/swaybar/bar.c
+++ b/swaybar/bar.c
@@ -1,390 +1,146 @@
1#define _XOPEN_SOURCE 500 1#define _XOPEN_SOURCE 500
2#include <assert.h>
3#include <errno.h>
4#include <fcntl.h>
5#include <poll.h>
6#include <signal.h>
2#include <stdlib.h> 7#include <stdlib.h>
3#include <unistd.h>
4#include <string.h> 8#include <string.h>
5#include <fcntl.h>
6#include <errno.h>
7#include <sys/wait.h> 9#include <sys/wait.h>
8#include <signal.h> 10#include <unistd.h>
9#include <poll.h> 11#include <wayland-client.h>
10#ifdef __FreeBSD__ 12#include <wlr/util/log.h>
11#include <dev/evdev/input-event-codes.h>
12#else
13#include <linux/input-event-codes.h>
14#endif
15#ifdef ENABLE_TRAY
16#include <dbus/dbus.h>
17#include "swaybar/tray/sni_watcher.h"
18#include "swaybar/tray/tray.h"
19#include "swaybar/tray/sni.h"
20#endif
21#include "swaybar/ipc.h"
22#include "swaybar/render.h" 13#include "swaybar/render.h"
23#include "swaybar/config.h" 14#include "swaybar/config.h"
24#include "swaybar/status_line.h"
25#include "swaybar/event_loop.h" 15#include "swaybar/event_loop.h"
26#include "swaybar/bar.h" 16#include "swaybar/bar.h"
27#include "ipc-client.h"
28#include "list.h" 17#include "list.h"
29#include "log.h" 18#include "pango.h"
19#include "pool-buffer.h"
20#include "wlr-layer-shell-unstable-v1-client-protocol.h"
30 21
31static void bar_init(struct bar *bar) { 22static void bar_init(struct swaybar *bar) {
32 bar->config = init_config(); 23 bar->config = init_config();
33 bar->status = init_status_line(); 24 wl_list_init(&bar->outputs);
34 bar->outputs = create_list();
35} 25}
36 26
37static void spawn_status_cmd_proc(struct bar *bar) { 27struct swaybar_output *new_output(const char *name) {
38 if (bar->config->status_command) { 28 struct swaybar_output *output = malloc(sizeof(struct swaybar_output));
39 int pipe_read_fd[2];
40 int pipe_write_fd[2];
41
42 if (pipe(pipe_read_fd) != 0) {
43 sway_log(L_ERROR, "Unable to create pipes for status_command fork");
44 return;
45 }
46 if (pipe(pipe_write_fd) != 0) {
47 sway_log(L_ERROR, "Unable to create pipe for status_command fork (write)");
48 close(pipe_read_fd[0]);
49 close(pipe_read_fd[1]);
50 return;
51 }
52
53 bar->status_command_pid = fork();
54 if (bar->status_command_pid == 0) {
55 close(pipe_read_fd[0]);
56 dup2(pipe_read_fd[1], STDOUT_FILENO);
57 close(pipe_read_fd[1]);
58
59 dup2(pipe_write_fd[0], STDIN_FILENO);
60 close(pipe_write_fd[0]);
61 close(pipe_write_fd[1]);
62
63 char *const cmd[] = {
64 "sh",
65 "-c",
66 bar->config->status_command,
67 NULL,
68 };
69 execvp(cmd[0], cmd);
70 return;
71 }
72
73 close(pipe_read_fd[1]);
74 bar->status_read_fd = pipe_read_fd[0];
75 fcntl(bar->status_read_fd, F_SETFL, O_NONBLOCK);
76
77 close(pipe_write_fd[0]);
78 bar->status_write_fd = pipe_write_fd[1];
79 fcntl(bar->status_write_fd, F_SETFL, O_NONBLOCK);
80 }
81}
82
83struct output *new_output(const char *name) {
84 struct output *output = malloc(sizeof(struct output));
85 output->name = strdup(name); 29 output->name = strdup(name);
86 output->window = NULL;
87 output->registry = NULL;
88 output->workspaces = create_list();
89#ifdef ENABLE_TRAY
90 output->items = create_list();
91#endif
92 return output; 30 return output;
93} 31}
94 32
95static void mouse_button_notify(struct window *window, int x, int y, 33static void layer_surface_configure(void *data,
96 uint32_t button, uint32_t state_w) { 34 struct zwlr_layer_surface_v1 *surface,
97 sway_log(L_DEBUG, "Mouse button %d clicked at %d %d %d", button, x, y, state_w); 35 uint32_t serial, uint32_t width, uint32_t height) {
98 if (!state_w) { 36 struct swaybar_output *output = data;
99 return; 37 output->width = width;
100 } 38 output->height = height;
101 39 zwlr_layer_surface_v1_ack_configure(surface, serial);
102 struct output *clicked_output = NULL; 40 render_frame(output->bar, output);
103 for (int i = 0; i < swaybar.outputs->length; i++) {
104 struct output *output = swaybar.outputs->items[i];
105 if (window == output->window) {
106 clicked_output = output;
107 break;
108 }
109 }
110
111 if (!sway_assert(clicked_output != NULL, "Got pointer event for non-existing output")) {
112 return;
113 }
114
115 double button_x = 0.5;
116 for (int i = 0; i < clicked_output->workspaces->length; i++) {
117 struct workspace *workspace = clicked_output->workspaces->items[i];
118 int button_width, button_height;
119
120 workspace_button_size(window, workspace->name, &button_width, &button_height);
121
122 button_x += button_width;
123 if (x <= button_x) {
124 ipc_send_workspace_command(workspace->name);
125 break;
126 }
127 }
128
129 switch (button) {
130 case BTN_LEFT:
131 status_line_mouse_event(&swaybar, x, y, 1);
132 break;
133 case BTN_MIDDLE:
134 status_line_mouse_event(&swaybar, x, y, 2);
135 break;
136 case BTN_RIGHT:
137 status_line_mouse_event(&swaybar, x, y, 3);
138 break;
139 }
140
141#ifdef ENABLE_TRAY
142 tray_mouse_event(clicked_output, x, y, button, state_w);
143#endif
144
145} 41}
146 42
147static void mouse_scroll_notify(struct window *window, enum scroll_direction direction) { 43static void layer_surface_closed(void *_output,
148 sway_log(L_DEBUG, "Mouse wheel scrolled %s", direction == SCROLL_UP ? "up" : "down"); 44 struct zwlr_layer_surface_v1 *surface) {
149 45 // TODO: Deal with hotplugging
150 // If there are status blocks and click_events are enabled 46 struct swaybar_output *output = output;
151 // check if the position is within the status area and if so 47 zwlr_layer_surface_v1_destroy(output->layer_surface);
152 // tell the status line to output the event and skip workspace 48 wl_surface_destroy(output->surface);
153 // switching below. 49}
154 int num_blocks = swaybar.status->block_line->length;
155 if (swaybar.status->click_events && num_blocks > 0) {
156 struct status_block *first_block = swaybar.status->block_line->items[0];
157 int x = window->pointer_input.last_x;
158 int y = window->pointer_input.last_y;
159 if (x > first_block->x) {
160 if (direction == SCROLL_UP) {
161 status_line_mouse_event(&swaybar, x, y, 4);
162 } else {
163 status_line_mouse_event(&swaybar, x, y, 5);
164 }
165 return;
166 }
167 }
168 50
169 if (!swaybar.config->wrap_scroll) { 51struct zwlr_layer_surface_v1_listener layer_surface_listener = {
170 // Find output this window lives on 52 .configure = layer_surface_configure,
171 int i; 53 .closed = layer_surface_closed,
172 struct output *output = NULL; 54};
173 for (i = 0; i < swaybar.outputs->length; ++i) { 55
174 output = swaybar.outputs->items[i]; 56static void handle_global(void *data, struct wl_registry *registry,
175 if (output->window == window) { 57 uint32_t name, const char *interface, uint32_t version) {
176 break; 58 struct swaybar *bar = data;
177 } 59 if (strcmp(interface, wl_compositor_interface.name) == 0) {
178 } 60 bar->compositor = wl_registry_bind(registry, name,
179 if (!sway_assert(i != swaybar.outputs->length, "Unknown window in scroll event")) { 61 &wl_compositor_interface, 1);
180 return; 62 } else if (strcmp(interface, wl_shm_interface.name) == 0) {
181 } 63 bar->shm = wl_registry_bind(registry, name,
182 int focused = -1; 64 &wl_shm_interface, 1);
183 for (i = 0; i < output->workspaces->length; ++i) { 65 } else if (strcmp(interface, wl_output_interface.name) == 0) {
184 struct workspace *ws = output->workspaces->items[i]; 66 static int idx = 0;
185 if (ws->focused) { 67 struct swaybar_output *output =
186 focused = i; 68 calloc(1, sizeof(struct swaybar_output));
187 break; 69 output->bar = bar;
188 } 70 output->output = wl_registry_bind(registry, name,
189 } 71 &wl_output_interface, 1);
190 if (!sway_assert(focused != -1, "Scroll wheel event received on inactive output")) { 72 output->idx = idx++;
191 return; 73 wl_list_insert(&bar->outputs, &output->link);
192 } 74 } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
193 if ((focused == 0 && direction == SCROLL_UP) || 75 bar->layer_shell = wl_registry_bind(
194 (focused == output->workspaces->length - 1 && direction == SCROLL_DOWN)) { 76 registry, name, &zwlr_layer_shell_v1_interface, 1);
195 // Do not wrap
196 return;
197 }
198 } 77 }
78}
199 79
200 const char *workspace_name = direction == SCROLL_UP ? "prev_on_output" : "next_on_output"; 80static void handle_global_remove(void *data, struct wl_registry *registry,
201 ipc_send_workspace_command(workspace_name); 81 uint32_t name) {
82 // who cares
202} 83}
203 84
204void bar_setup(struct bar *bar, const char *socket_path, const char *bar_id) { 85static const struct wl_registry_listener registry_listener = {
205 /* initialize bar with default values */ 86 .global = handle_global,
206 bar_init(bar); 87 .global_remove = handle_global_remove,
88};
207 89
208 /* Initialize event loop lists */ 90void bar_setup(struct swaybar *bar,
91 const char *socket_path, const char *bar_id) {
92 bar_init(bar);
209 init_event_loop(); 93 init_event_loop();
210 94
211 /* connect to sway ipc */ 95 assert(bar->display = wl_display_connect(NULL));
212 bar->ipc_socketfd = ipc_open_socket(socket_path); 96
213 bar->ipc_event_socketfd = ipc_open_socket(socket_path); 97 struct wl_registry *registry = wl_display_get_registry(bar->display);
214 98 wl_registry_add_listener(registry, &registry_listener, bar);
215 ipc_bar_init(bar, bar_id); 99 wl_display_roundtrip(bar->display);
216 100 assert(bar->compositor && bar->layer_shell && bar->shm);
217 int i; 101
218 for (i = 0; i < bar->outputs->length; ++i) { 102 // TODO: we might not necessarily be meant to do all of the outputs
219 struct output *bar_output = bar->outputs->items[i]; 103 struct swaybar_output *output;
220 104 wl_list_for_each(output, &bar->outputs, link) {
221 bar_output->registry = registry_poll(); 105 assert(output->surface = wl_compositor_create_surface(bar->compositor));
222 106 output->layer_surface = zwlr_layer_shell_v1_get_layer_surface(
223 if (!bar_output->registry->desktop_shell) { 107 bar->layer_shell, output->surface, output->output,
224 sway_abort("swaybar requires the compositor to support the desktop-shell extension."); 108 ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, "panel");
225 } 109 assert(output->layer_surface);
226 110 zwlr_layer_surface_v1_add_listener(output->layer_surface,
227 struct output_state *output = bar_output->registry->outputs->items[bar_output->idx]; 111 &layer_surface_listener, output);
228 112 zwlr_layer_surface_v1_set_anchor(output->layer_surface,
229 bar_output->window = window_setup(bar_output->registry,
230 output->width / output->scale, 30, output->scale, false);
231 if (!bar_output->window) {
232 sway_abort("Failed to create window.");
233 }
234 desktop_shell_set_panel(bar_output->registry->desktop_shell,
235 output->output, bar_output->window->surface);
236 desktop_shell_set_panel_position(bar_output->registry->desktop_shell,
237 bar->config->position); 113 bar->config->position);
238 114 render_frame(bar, output);
239 window_make_shell(bar_output->window);
240
241 /* set font */
242 bar_output->window->font = bar->config->font;
243
244 /* set mouse event callbacks */
245 bar_output->window->pointer_input.notify_button = mouse_button_notify;
246 bar_output->window->pointer_input.notify_scroll = mouse_scroll_notify;
247
248 /* set window height */
249 set_window_height(bar_output->window, bar->config->height);
250 } 115 }
251 /* spawn status command */
252 spawn_status_cmd_proc(bar);
253
254#ifdef ENABLE_TRAY
255 init_tray(bar);
256#endif
257} 116}
258 117
259bool dirty = true; 118static void display_in(int fd, short mask, void *_bar) {
260 119 struct swaybar *bar = (struct swaybar *)_bar;
261static void respond_ipc(int fd, short mask, void *_bar) { 120 if (wl_display_dispatch(bar->display) == -1) {
262 struct bar *bar = (struct bar *)_bar; 121 wlr_log(L_ERROR, "failed to dispatch wl: %d", errno);
263 sway_log(L_DEBUG, "Got IPC event.");
264 dirty = handle_ipc_event(bar);
265}
266
267static void respond_command(int fd, short mask, void *_bar) {
268 struct bar *bar = (struct bar *)_bar;
269 dirty = handle_status_line(bar);
270}
271
272static void respond_output(int fd, short mask, void *_output) {
273 struct output *output = (struct output *)_output;
274 if (wl_display_dispatch(output->registry->display) == -1) {
275 sway_log(L_ERROR, "failed to dispatch wl: %d", errno);
276 } 122 }
277} 123}
278 124
279void bar_run(struct bar *bar) { 125void bar_run(struct swaybar *bar) {
280 add_event(bar->ipc_event_socketfd, POLLIN, respond_ipc, bar); 126 add_event(wl_display_get_fd(bar->display), POLLIN, display_in, bar);
281 add_event(bar->status_read_fd, POLLIN, respond_command, bar);
282
283 int i;
284 for (i = 0; i < bar->outputs->length; ++i) {
285 struct output *output = bar->outputs->items[i];
286 add_event(wl_display_get_fd(output->registry->display),
287 POLLIN, respond_output, output);
288 }
289
290 while (1) { 127 while (1) {
291 if (dirty) {
292 int i;
293 for (i = 0; i < bar->outputs->length; ++i) {
294 struct output *output = bar->outputs->items[i];
295 if (window_prerender(output->window) && output->window->cairo) {
296 render(output, bar->config, bar->status);
297 window_render(output->window);
298 wl_display_flush(output->registry->display);
299 }
300 }
301 }
302
303 dirty = false;
304
305 event_loop_poll(); 128 event_loop_poll();
306#ifdef ENABLE_TRAY
307 dispatch_dbus();
308#endif
309 } 129 }
310} 130}
311 131
312void free_workspaces(list_t *workspaces) { 132static void free_outputs(struct wl_list *list) {
313 int i; 133 struct swaybar_output *output, *tmp;
314 for (i = 0; i < workspaces->length; ++i) { 134 wl_list_for_each_safe(output, tmp, list, link) {
315 struct workspace *ws = workspaces->items[i]; 135 wl_list_remove(&output->link);
316 free(ws->name); 136 free(output->name);
317 free(ws); 137 free(output);
318 } 138 }
319 list_free(workspaces);
320} 139}
321 140
322static void free_output(struct output *output) { 141void bar_teardown(struct swaybar *bar) {
323 window_teardown(output->window); 142 free_outputs(&bar->outputs);
324 if (output->registry) {
325 registry_teardown(output->registry);
326 }
327
328 free(output->name);
329
330 if (output->workspaces) {
331 free_workspaces(output->workspaces);
332 }
333
334 free(output);
335}
336
337static void free_outputs(list_t *outputs) {
338 int i;
339 for (i = 0; i < outputs->length; ++i) {
340 free_output(outputs->items[i]);
341 }
342 list_free(outputs);
343}
344
345static void terminate_status_command(pid_t pid) {
346 if (pid) {
347 // terminate status_command process
348 int ret = kill(pid, SIGTERM);
349 if (ret != 0) {
350 sway_log(L_ERROR, "Unable to terminate status_command [pid: %d]", pid);
351 } else {
352 int status;
353 waitpid(pid, &status, 0);
354 }
355 }
356}
357
358void bar_teardown(struct bar *bar) {
359 if (bar->config) { 143 if (bar->config) {
360 free_config(bar->config); 144 free_config(bar->config);
361 } 145 }
362
363 if (bar->outputs) {
364 free_outputs(bar->outputs);
365 }
366
367 if (bar->status) {
368 free_status_line(bar->status);
369 }
370
371 /* close sockets/pipes */
372 if (bar->status_read_fd) {
373 close(bar->status_read_fd);
374 }
375
376 if (bar->status_write_fd) {
377 close(bar->status_write_fd);
378 }
379
380 if (bar->ipc_socketfd) {
381 close(bar->ipc_socketfd);
382 }
383
384 if (bar->ipc_event_socketfd) {
385 close(bar->ipc_event_socketfd);
386 }
387
388 /* terminate status command process */
389 terminate_status_command(bar->status_command_pid);
390} 146}
diff --git a/swaybar/config.c b/swaybar/config.c
index 8fe552f2..0c2b57e0 100644
--- a/swaybar/config.c
+++ b/swaybar/config.c
@@ -1,21 +1,24 @@
1#define _XOPEN_SOURCE 500 1#define _XOPEN_SOURCE 500
2#include <stdlib.h> 2#include <stdlib.h>
3#include <string.h> 3#include <string.h>
4#include "wayland-desktop-shell-client-protocol.h"
5#include "log.h"
6#include "swaybar/config.h" 4#include "swaybar/config.h"
5#include "wlr-layer-shell-unstable-v1-client-protocol.h"
7 6
8uint32_t parse_position(const char *position) { 7uint32_t parse_position(const char *position) {
8 uint32_t horiz = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
9 ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
10 uint32_t vert = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
11 ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
9 if (strcmp("top", position) == 0) { 12 if (strcmp("top", position) == 0) {
10 return DESKTOP_SHELL_PANEL_POSITION_TOP; 13 return ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | horiz;
11 } else if (strcmp("bottom", position) == 0) { 14 } else if (strcmp("bottom", position) == 0) {
12 return DESKTOP_SHELL_PANEL_POSITION_BOTTOM; 15 return ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | horiz;
13 } else if (strcmp("left", position) == 0) { 16 } else if (strcmp("left", position) == 0) {
14 return DESKTOP_SHELL_PANEL_POSITION_LEFT; 17 return ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | vert;
15 } else if (strcmp("right", position) == 0) { 18 } else if (strcmp("right", position) == 0) {
16 return DESKTOP_SHELL_PANEL_POSITION_RIGHT; 19 return ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | vert;
17 } else { 20 } else {
18 return DESKTOP_SHELL_PANEL_POSITION_BOTTOM; 21 return ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | horiz;
19 } 22 }
20} 23}
21 24
@@ -30,11 +33,11 @@ char *parse_font(const char *font) {
30 return new_font; 33 return new_font;
31} 34}
32 35
33struct config *init_config() { 36struct swaybar_config *init_config() {
34 struct config *config = calloc(1, sizeof(struct config)); 37 struct swaybar_config *config = calloc(1, sizeof(struct swaybar_config));
35 config->status_command = NULL; 38 config->status_command = NULL;
36 config->pango_markup = false; 39 config->pango_markup = false;
37 config->position = DESKTOP_SHELL_PANEL_POSITION_BOTTOM; 40 config->position = parse_position("bottom");
38 config->font = strdup("monospace 10"); 41 config->font = strdup("monospace 10");
39 config->mode = NULL; 42 config->mode = NULL;
40 config->sep_symbol = NULL; 43 config->sep_symbol = NULL;
@@ -48,18 +51,6 @@ struct config *init_config() {
48 /* height */ 51 /* height */
49 config->height = 0; 52 config->height = 0;
50 53
51#ifdef ENABLE_TRAY
52 config->tray_output = NULL;
53 config->icon_theme = NULL;
54 config->tray_padding = 2;
55 /**
56 * These constants are used by wayland and are defined in
57 * linux/input-event-codes.h
58 */
59 config->activate_button = 0x110; /* BTN_LEFT */
60 config->context_button = 0x111; /* BTN_RIGHT */
61#endif
62
63 /* colors */ 54 /* colors */
64 config->colors.background = 0x000000FF; 55 config->colors.background = 0x000000FF;
65 config->colors.statusline = 0xFFFFFFFF; 56 config->colors.statusline = 0xFFFFFFFF;
@@ -88,7 +79,7 @@ struct config *init_config() {
88 return config; 79 return config;
89} 80}
90 81
91void free_config(struct config *config) { 82void free_config(struct swaybar_config *config) {
92 free(config->status_command); 83 free(config->status_command);
93 free(config->font); 84 free(config->font);
94 free(config->mode); 85 free(config->mode);
diff --git a/swaybar/event_loop.c b/swaybar/event_loop.c
index 0d1be1da..748372ed 100644
--- a/swaybar/event_loop.c
+++ b/swaybar/event_loop.c
@@ -4,19 +4,18 @@
4#include <string.h> 4#include <string.h>
5#include <strings.h> 5#include <strings.h>
6#include <poll.h> 6#include <poll.h>
7#include "swaybar/bar.h" 7#include <time.h>
8#include "swaybar/event_loop.h" 8#include "swaybar/event_loop.h"
9#include "list.h" 9#include "list.h"
10#include "log.h"
11 10
12struct event_item { 11struct event_item {
13 void(*cb)(int fd, short mask, void *data); 12 void (*cb)(int fd, short mask, void *data);
14 void *data; 13 void *data;
15}; 14};
16 15
17struct timer_item { 16struct timer_item {
18 timer_t timer; 17 timer_t timer;
19 void(*cb)(timer_t timer, void *data); 18 void (*cb)(timer_t timer, void *data);
20 void *data; 19 void *data;
21}; 20};
22 21
@@ -138,7 +137,8 @@ void event_loop_poll() {
138void init_event_loop() { 137void init_event_loop() {
139 event_loop.fds.length = 0; 138 event_loop.fds.length = 0;
140 event_loop.fds.capacity = 10; 139 event_loop.fds.capacity = 10;
141 event_loop.fds.items = malloc(event_loop.fds.capacity * sizeof(struct pollfd)); 140 event_loop.fds.items = malloc(
141 event_loop.fds.capacity * sizeof(struct pollfd));
142 event_loop.items = create_list(); 142 event_loop.items = create_list();
143 event_loop.timers = create_list(); 143 event_loop.timers = create_list();
144} 144}
diff --git a/swaybar/ipc.c b/swaybar/ipc.c
deleted file mode 100644
index 93d1219c..00000000
--- a/swaybar/ipc.c
+++ /dev/null
@@ -1,410 +0,0 @@
1#define _XOPEN_SOURCE 500
2#include <string.h>
3#include <strings.h>
4#include <json-c/json.h>
5#include "swaybar/config.h"
6#include "swaybar/ipc.h"
7#include "ipc-client.h"
8#include "list.h"
9#include "log.h"
10
11void ipc_send_workspace_command(const char *workspace_name) {
12 uint32_t size = strlen("workspace \"\"") + strlen(workspace_name) + 1;
13
14 char command[size];
15 sprintf(command, "workspace \"%s\"", workspace_name);
16
17 ipc_single_command(swaybar.ipc_socketfd, IPC_COMMAND, command, &size);
18}
19
20static void ipc_parse_config(struct config *config, const char *payload) {
21 json_object *bar_config = json_tokener_parse(payload);
22 json_object *markup, *mode, *hidden_bar, *position, *status_command;
23 json_object *font, *bar_height, *wrap_scroll, *workspace_buttons, *strip_workspace_numbers;
24 json_object *binding_mode_indicator, *verbose, *colors, *sep_symbol, *outputs;
25#ifdef ENABLE_TRAY
26 json_object *tray_output, *icon_theme, *tray_padding, *activate_button, *context_button;
27 json_object *secondary_button;
28 json_object_object_get_ex(bar_config, "tray_output", &tray_output);
29 json_object_object_get_ex(bar_config, "icon_theme", &icon_theme);
30 json_object_object_get_ex(bar_config, "tray_padding", &tray_padding);
31 json_object_object_get_ex(bar_config, "activate_button", &activate_button);
32 json_object_object_get_ex(bar_config, "context_button", &context_button);
33 json_object_object_get_ex(bar_config, "secondary_button", &secondary_button);
34#endif
35 json_object_object_get_ex(bar_config, "mode", &mode);
36 json_object_object_get_ex(bar_config, "hidden_bar", &hidden_bar);
37 json_object_object_get_ex(bar_config, "position", &position);
38 json_object_object_get_ex(bar_config, "status_command", &status_command);
39 json_object_object_get_ex(bar_config, "font", &font);
40 json_object_object_get_ex(bar_config, "bar_height", &bar_height);
41 json_object_object_get_ex(bar_config, "wrap_scroll", &wrap_scroll);
42 json_object_object_get_ex(bar_config, "workspace_buttons", &workspace_buttons);
43 json_object_object_get_ex(bar_config, "strip_workspace_numbers", &strip_workspace_numbers);
44 json_object_object_get_ex(bar_config, "binding_mode_indicator", &binding_mode_indicator);
45 json_object_object_get_ex(bar_config, "verbose", &verbose);
46 json_object_object_get_ex(bar_config, "separator_symbol", &sep_symbol);
47 json_object_object_get_ex(bar_config, "colors", &colors);
48 json_object_object_get_ex(bar_config, "outputs", &outputs);
49 json_object_object_get_ex(bar_config, "pango_markup", &markup);
50
51 if (status_command) {
52 free(config->status_command);
53 config->status_command = strdup(json_object_get_string(status_command));
54 }
55
56 if (position) {
57 config->position = parse_position(json_object_get_string(position));
58 }
59
60 if (font) {
61 free(config->font);
62 config->font = parse_font(json_object_get_string(font));
63 }
64
65 if (sep_symbol) {
66 free(config->sep_symbol);
67 config->sep_symbol = strdup(json_object_get_string(sep_symbol));
68 }
69
70 if (strip_workspace_numbers) {
71 config->strip_workspace_numbers = json_object_get_boolean(strip_workspace_numbers);
72 }
73
74 if (binding_mode_indicator) {
75 config->binding_mode_indicator = json_object_get_boolean(binding_mode_indicator);
76 }
77
78 if (wrap_scroll) {
79 config->wrap_scroll = json_object_get_boolean(wrap_scroll);
80 }
81
82 if (workspace_buttons) {
83 config->workspace_buttons = json_object_get_boolean(workspace_buttons);
84 }
85
86 if (bar_height) {
87 config->height = json_object_get_int(bar_height);
88 }
89
90 if (markup) {
91 config->pango_markup = json_object_get_boolean(markup);
92 }
93
94#ifdef ENABLE_TRAY
95 if (tray_output) {
96 free(config->tray_output);
97 config->tray_output = strdup(json_object_get_string(tray_output));
98 }
99
100 if (icon_theme) {
101 free(config->icon_theme);
102 config->icon_theme = strdup(json_object_get_string(icon_theme));
103 }
104
105 if (tray_padding) {
106 config->tray_padding = json_object_get_int(tray_padding);
107 }
108
109 if (activate_button) {
110 config->activate_button = json_object_get_int(activate_button);
111 }
112
113 if (context_button) {
114 config->context_button = json_object_get_int(context_button);
115 }
116
117 if (secondary_button) {
118 config->secondary_button = json_object_get_int(secondary_button);
119 }
120#endif
121
122 // free previous outputs list
123 int i;
124 for (i = 0; i < config->outputs->length; ++i) {
125 free(config->outputs->items[i]);
126 }
127 list_free(config->outputs);
128 config->outputs = create_list();
129
130 if (outputs) {
131 int length = json_object_array_length(outputs);
132 json_object *output;
133 const char *output_str;
134 for (i = 0; i < length; ++i) {
135 output = json_object_array_get_idx(outputs, i);
136 output_str = json_object_get_string(output);
137 if (strcmp("*", output_str) == 0) {
138 config->all_outputs = true;
139 break;
140 }
141 list_add(config->outputs, strdup(output_str));
142 }
143 } else {
144 config->all_outputs = true;
145 }
146
147 if (colors) {
148 json_object *background, *statusline, *separator;
149 json_object *focused_background, *focused_statusline, *focused_separator;
150 json_object *focused_workspace_border, *focused_workspace_bg, *focused_workspace_text;
151 json_object *inactive_workspace_border, *inactive_workspace_bg, *inactive_workspace_text;
152 json_object *active_workspace_border, *active_workspace_bg, *active_workspace_text;
153 json_object *urgent_workspace_border, *urgent_workspace_bg, *urgent_workspace_text;
154 json_object *binding_mode_border, *binding_mode_bg, *binding_mode_text;
155 json_object_object_get_ex(colors, "background", &background);
156 json_object_object_get_ex(colors, "statusline", &statusline);
157 json_object_object_get_ex(colors, "separator", &separator);
158 json_object_object_get_ex(colors, "focused_background", &focused_background);
159 json_object_object_get_ex(colors, "focused_statusline", &focused_statusline);
160 json_object_object_get_ex(colors, "focused_separator", &focused_separator);
161 json_object_object_get_ex(colors, "focused_workspace_border", &focused_workspace_border);
162 json_object_object_get_ex(colors, "focused_workspace_bg", &focused_workspace_bg);
163 json_object_object_get_ex(colors, "focused_workspace_text", &focused_workspace_text);
164 json_object_object_get_ex(colors, "active_workspace_border", &active_workspace_border);
165 json_object_object_get_ex(colors, "active_workspace_bg", &active_workspace_bg);
166 json_object_object_get_ex(colors, "active_workspace_text", &active_workspace_text);
167 json_object_object_get_ex(colors, "inactive_workspace_border", &inactive_workspace_border);
168 json_object_object_get_ex(colors, "inactive_workspace_bg", &inactive_workspace_bg);
169 json_object_object_get_ex(colors, "inactive_workspace_text", &inactive_workspace_text);
170 json_object_object_get_ex(colors, "urgent_workspace_border", &urgent_workspace_border);
171 json_object_object_get_ex(colors, "urgent_workspace_bg", &urgent_workspace_bg);
172 json_object_object_get_ex(colors, "urgent_workspace_text", &urgent_workspace_text);
173 json_object_object_get_ex(colors, "binding_mode_border", &binding_mode_border);
174 json_object_object_get_ex(colors, "binding_mode_bg", &binding_mode_bg);
175 json_object_object_get_ex(colors, "binding_mode_text", &binding_mode_text);
176 if (background) {
177 config->colors.background = parse_color(json_object_get_string(background));
178 }
179
180 if (statusline) {
181 config->colors.statusline = parse_color(json_object_get_string(statusline));
182 }
183
184 if (separator) {
185 config->colors.separator = parse_color(json_object_get_string(separator));
186 }
187
188 if (focused_background) {
189 config->colors.focused_background = parse_color(json_object_get_string(focused_background));
190 }
191
192 if (focused_statusline) {
193 config->colors.focused_statusline = parse_color(json_object_get_string(focused_statusline));
194 }
195
196 if (focused_separator) {
197 config->colors.focused_separator = parse_color(json_object_get_string(focused_separator));
198 }
199
200 if (focused_workspace_border) {
201 config->colors.focused_workspace.border = parse_color(json_object_get_string(focused_workspace_border));
202 }
203
204 if (focused_workspace_bg) {
205 config->colors.focused_workspace.background = parse_color(json_object_get_string(focused_workspace_bg));
206 }
207
208 if (focused_workspace_text) {
209 config->colors.focused_workspace.text = parse_color(json_object_get_string(focused_workspace_text));
210 }
211
212 if (active_workspace_border) {
213 config->colors.active_workspace.border = parse_color(json_object_get_string(active_workspace_border));
214 }
215
216 if (active_workspace_bg) {
217 config->colors.active_workspace.background = parse_color(json_object_get_string(active_workspace_bg));
218 }
219
220 if (active_workspace_text) {
221 config->colors.active_workspace.text = parse_color(json_object_get_string(active_workspace_text));
222 }
223
224 if (inactive_workspace_border) {
225 config->colors.inactive_workspace.border = parse_color(json_object_get_string(inactive_workspace_border));
226 }
227
228 if (inactive_workspace_bg) {
229 config->colors.inactive_workspace.background = parse_color(json_object_get_string(inactive_workspace_bg));
230 }
231
232 if (inactive_workspace_text) {
233 config->colors.inactive_workspace.text = parse_color(json_object_get_string(inactive_workspace_text));
234 }
235
236 if (binding_mode_border) {
237 config->colors.binding_mode.border = parse_color(json_object_get_string(binding_mode_border));
238 }
239
240 if (binding_mode_bg) {
241 config->colors.binding_mode.background = parse_color(json_object_get_string(binding_mode_bg));
242 }
243
244 if (binding_mode_text) {
245 config->colors.binding_mode.text = parse_color(json_object_get_string(binding_mode_text));
246 }
247 }
248
249 json_object_put(bar_config);
250}
251
252static void ipc_update_workspaces(struct bar *bar) {
253 int i;
254 for (i = 0; i < bar->outputs->length; ++i) {
255 struct output *output = bar->outputs->items[i];
256 if (output->workspaces) {
257 free_workspaces(output->workspaces);
258 }
259 output->workspaces = create_list();
260 }
261
262 uint32_t len = 0;
263 char *res = ipc_single_command(bar->ipc_socketfd, IPC_GET_WORKSPACES, NULL, &len);
264 json_object *results = json_tokener_parse(res);
265 if (!results) {
266 free(res);
267 return;
268 }
269
270 int length = json_object_array_length(results);
271 json_object *ws_json;
272 json_object *num, *name, *visible, *focused, *out, *urgent;
273 for (i = 0; i < length; ++i) {
274 ws_json = json_object_array_get_idx(results, i);
275
276 json_object_object_get_ex(ws_json, "num", &num);
277 json_object_object_get_ex(ws_json, "name", &name);
278 json_object_object_get_ex(ws_json, "visible", &visible);
279 json_object_object_get_ex(ws_json, "focused", &focused);
280 json_object_object_get_ex(ws_json, "output", &out);
281 json_object_object_get_ex(ws_json, "urgent", &urgent);
282
283 int j;
284 for (j = 0; j < bar->outputs->length; ++j) {
285 struct output *output = bar->outputs->items[j];
286 if (strcmp(json_object_get_string(out), output->name) == 0) {
287 struct workspace *ws = malloc(sizeof(struct workspace));
288 ws->num = json_object_get_int(num);
289 ws->name = strdup(json_object_get_string(name));
290 ws->visible = json_object_get_boolean(visible);
291 ws->focused = json_object_get_boolean(focused);
292 if (ws->focused) {
293 if (bar->focused_output) {
294 bar->focused_output->focused = false;
295 }
296 bar->focused_output = output;
297 output->focused = true;
298 }
299 ws->urgent = json_object_get_boolean(urgent);
300 list_add(output->workspaces, ws);
301 }
302 }
303 }
304
305 json_object_put(results);
306 free(res);
307}
308
309void ipc_bar_init(struct bar *bar, const char *bar_id) {
310 // Get bar config
311 uint32_t len = strlen(bar_id);
312 char *res = ipc_single_command(bar->ipc_socketfd, IPC_GET_BAR_CONFIG, bar_id, &len);
313
314 ipc_parse_config(bar->config, res);
315 free(res);
316
317 // Get outputs
318 len = 0;
319 res = ipc_single_command(bar->ipc_socketfd, IPC_GET_OUTPUTS, NULL, &len);
320 json_object *outputs = json_tokener_parse(res);
321 int i;
322 int length = json_object_array_length(outputs);
323 json_object *output, *output_name, *output_active;
324 const char *name;
325 bool active;
326 for (i = 0; i < length; ++i) {
327 output = json_object_array_get_idx(outputs, i);
328 json_object_object_get_ex(output, "name", &output_name);
329 json_object_object_get_ex(output, "active", &output_active);
330 name = json_object_get_string(output_name);
331 active = json_object_get_boolean(output_active);
332 if (!active) {
333 continue;
334 }
335
336 bool use_output = false;
337 if (bar->config->all_outputs) {
338 use_output = true;
339 } else {
340 int j = 0;
341 for (j = 0; j < bar->config->outputs->length; ++j) {
342 const char *conf_name = bar->config->outputs->items[j];
343 if (strcasecmp(name, conf_name) == 0) {
344 use_output = true;
345 break;
346 }
347 }
348 }
349
350 if (!use_output) {
351 continue;
352 }
353
354 // add bar to the output
355 struct output *bar_output = new_output(name);
356 bar_output->idx = i;
357 list_add(bar->outputs, bar_output);
358 }
359 free(res);
360 json_object_put(outputs);
361
362 const char *subscribe_json = "[ \"workspace\", \"mode\" ]";
363 len = strlen(subscribe_json);
364 res = ipc_single_command(bar->ipc_event_socketfd, IPC_SUBSCRIBE, subscribe_json, &len);
365 free(res);
366
367 ipc_update_workspaces(bar);
368}
369
370bool handle_ipc_event(struct bar *bar) {
371 struct ipc_response *resp = ipc_recv_response(bar->ipc_event_socketfd);
372 if (!resp) {
373 return false;
374 }
375 switch (resp->type) {
376 case IPC_EVENT_WORKSPACE:
377 ipc_update_workspaces(bar);
378 break;
379 case IPC_EVENT_MODE: {
380 json_object *result = json_tokener_parse(resp->payload);
381 if (!result) {
382 free_ipc_response(resp);
383 sway_log(L_ERROR, "failed to parse payload as json");
384 return false;
385 }
386 json_object *json_change;
387 if (json_object_object_get_ex(result, "change", &json_change)) {
388 const char *change = json_object_get_string(json_change);
389
390 free(bar->config->mode);
391 if (strcmp(change, "default") == 0) {
392 bar->config->mode = NULL;
393 } else {
394 bar->config->mode = strdup(change);
395 }
396 } else {
397 sway_log(L_ERROR, "failed to parse response");
398 }
399
400 json_object_put(result);
401 break;
402 }
403 default:
404 free_ipc_response(resp);
405 return false;
406 }
407
408 free_ipc_response(resp);
409 return true;
410}
diff --git a/swaybar/main.c b/swaybar/main.c
index 0abd0755..c897e1c9 100644
--- a/swaybar/main.c
+++ b/swaybar/main.c
@@ -4,21 +4,20 @@
4#include <string.h> 4#include <string.h>
5#include <stdbool.h> 5#include <stdbool.h>
6#include <getopt.h> 6#include <getopt.h>
7#include <wlr/util/log.h>
7#include "swaybar/bar.h" 8#include "swaybar/bar.h"
8#include "ipc-client.h" 9#include "ipc-client.h"
9#include "log.h"
10 10
11/* global bar state */ 11static struct swaybar swaybar;
12struct bar swaybar;
13 12
14void sway_terminate(int exit_code) { 13void sig_handler(int signal) {
15 bar_teardown(&swaybar); 14 bar_teardown(&swaybar);
16 exit(exit_code); 15 exit(0);
17} 16}
18 17
19void sig_handler(int signal) { 18void sway_terminate(int code) {
20 bar_teardown(&swaybar); 19 bar_teardown(&swaybar);
21 exit(0); 20 exit(code);
22} 21}
23 22
24int main(int argc, char **argv) { 23int main(int argc, char **argv) {
@@ -75,20 +74,23 @@ int main(int argc, char **argv) {
75 } 74 }
76 } 75 }
77 76
78 if (!bar_id) {
79 sway_abort("No bar_id passed. Provide --bar_id or let sway start swaybar");
80 }
81
82 if (debug) { 77 if (debug) {
83 init_log(L_DEBUG); 78 wlr_log_init(L_DEBUG, NULL);
84 } else { 79 } else {
85 init_log(L_ERROR); 80 wlr_log_init(L_ERROR, NULL);
81 }
82
83 if (!bar_id) {
84 wlr_log(L_ERROR, "No bar_id passed. "
85 "Provide --bar_id or let sway start swaybar");
86 return 1;
86 } 87 }
87 88
88 if (!socket_path) { 89 if (!socket_path) {
89 socket_path = get_socketpath(); 90 socket_path = get_socketpath();
90 if (!socket_path) { 91 if (!socket_path) {
91 sway_abort("Unable to retrieve socket path"); 92 wlr_log(L_ERROR, "Unable to retrieve socket path");
93 return 1;
92 } 94 }
93 } 95 }
94 96
@@ -100,9 +102,6 @@ int main(int argc, char **argv) {
100 free(bar_id); 102 free(bar_id);
101 103
102 bar_run(&swaybar); 104 bar_run(&swaybar);
103
104 // gracefully shutdown swaybar and status_command
105 bar_teardown(&swaybar); 105 bar_teardown(&swaybar);
106
107 return 0; 106 return 0;
108} 107}
diff --git a/swaybar/meson.build b/swaybar/meson.build
new file mode 100644
index 00000000..fd87e51d
--- /dev/null
+++ b/swaybar/meson.build
@@ -0,0 +1,25 @@
1executable(
2 'swaybar',
3 [
4 'bar.c',
5 'config.c',
6 'event_loop.c',
7 'main.c',
8 'render.c',
9 ],
10 include_directories: [sway_inc],
11 dependencies: [
12 cairo,
13 client_protos,
14 gdk_pixbuf,
15 jsonc,
16 math,
17 pango,
18 pangocairo,
19 rt,
20 wayland_client,
21 wlroots,
22 ],
23 link_with: [lib_sway_common, lib_sway_client],
24 install: true
25)
diff --git a/swaybar/render.c b/swaybar/render.c
index 6fc09078..2eaa0195 100644
--- a/swaybar/render.c
+++ b/swaybar/render.c
@@ -1,367 +1,63 @@
1#include <stdlib.h> 1#include <stdlib.h>
2#include <stdint.h> 2#include <stdint.h>
3#include <string.h> 3#include <string.h>
4 4#include <wlr/util/log.h>
5#include "client/cairo.h" 5#include "cairo.h"
6#include "client/pango.h" 6#include "pango.h"
7#include "client/window.h" 7#include "pool-buffer.h"
8#include "swaybar/bar.h"
8#include "swaybar/config.h" 9#include "swaybar/config.h"
9#include "swaybar/status_line.h"
10#include "swaybar/render.h" 10#include "swaybar/render.h"
11#ifdef ENABLE_TRAY 11#include "wlr-layer-shell-unstable-v1-client-protocol.h"
12#include "swaybar/tray/tray.h"
13#include "swaybar/tray/sni.h"
14#endif
15#include "log.h"
16
17
18/* internal spacing */
19static int margin = 3;
20static int ws_horizontal_padding = 5;
21static double ws_vertical_padding = 1.5;
22static int ws_spacing = 1;
23
24/**
25 * Renders a sharp line of any width and height.
26 *
27 * The line is drawn from (x,y) to (x+width,y+height) where width/height is 0
28 * if the line has a width/height of one pixel, respectively.
29 */
30static void render_sharp_line(cairo_t *cairo, uint32_t color, double x, double y, double width, double height) {
31 cairo_set_source_u32(cairo, color);
32
33 if (width > 1 && height > 1) {
34 cairo_rectangle(cairo, x, y, width, height);
35 cairo_fill(cairo);
36 } else {
37 if (width == 1) {
38 x += 0.5;
39 height += y;
40 width = x;
41 }
42
43 if (height == 1) {
44 y += 0.5;
45 width += x;
46 height = y;
47 }
48
49 cairo_move_to(cairo, x, y);
50 cairo_set_line_width(cairo, 1.0);
51 cairo_line_to(cairo, width, height);
52 cairo_stroke(cairo);
53 }
54}
55
56static void render_block(struct window *window, struct config *config, struct status_block *block, double *x, bool edge, bool is_focused) {
57 int width, height, sep_width;
58 get_text_size(window->cairo, window->font, &width, &height,
59 window->scale, block->markup, "%s", block->full_text);
60
61 int textwidth = width;
62 double block_width = width;
63
64 if (width < block->min_width) {
65 width = block->min_width;
66 }
67
68 *x -= width;
69
70 if (block->border != 0 && block->border_left > 0) {
71 *x -= (block->border_left + margin);
72 block_width += block->border_left + margin;
73 }
74
75 if (block->border != 0 && block->border_right > 0) {
76 *x -= (block->border_right + margin);
77 block_width += block->border_right + margin;
78 }
79
80 // Add separator
81 if (!edge) {
82 if (config->sep_symbol) {
83 get_text_size(window->cairo, window->font, &sep_width, &height,
84 window->scale, false, "%s", config->sep_symbol);
85 if (sep_width > block->separator_block_width) {
86 block->separator_block_width = sep_width + margin * 2;
87 }
88 }
89
90 *x -= block->separator_block_width;
91 } else {
92 *x -= margin;
93 }
94
95 double pos = *x;
96
97 block->x = (int)pos;
98 block->width = (int)block_width;
99
100 // render background
101 if (block->background != 0x0) {
102 cairo_set_source_u32(window->cairo, block->background);
103 cairo_rectangle(window->cairo, pos - 0.5, 1, block_width, (window->height * window->scale) - 2);
104 cairo_fill(window->cairo);
105 }
106
107 // render top border
108 if (block->border != 0 && block->border_top > 0) {
109 render_sharp_line(window->cairo, block->border,
110 pos - 0.5,
111 1,
112 block_width,
113 block->border_top);
114 }
115
116 // render bottom border
117 if (block->border != 0 && block->border_bottom > 0) {
118 render_sharp_line(window->cairo, block->border,
119 pos - 0.5,
120 (window->height * window->scale) - 1 - block->border_bottom,
121 block_width,
122 block->border_bottom);
123 }
124
125 // render left border
126 if (block->border != 0 && block->border_left > 0) {
127 render_sharp_line(window->cairo, block->border,
128 pos - 0.5,
129 1,
130 block->border_left,
131 (window->height * window->scale) - 2);
132
133 pos += block->border_left + margin;
134 }
135
136 // render text
137 double offset = 0;
138
139 if (strncmp(block->align, "left", 5) == 0) {
140 offset = pos;
141 } else if (strncmp(block->align, "right", 5) == 0) {
142 offset = pos + width - textwidth;
143 } else if (strncmp(block->align, "center", 6) == 0) {
144 offset = pos + (width - textwidth) / 2;
145 }
146
147 cairo_move_to(window->cairo, offset, margin);
148 cairo_set_source_u32(window->cairo, block->color);
149 pango_printf(window->cairo, window->font, window->scale,
150 block->markup, "%s", block->full_text);
151
152 pos += width;
153 12
154 // render right border 13static uint32_t render_to_cairo(cairo_t *cairo, struct swaybar *bar,
155 if (block->border != 0 && block->border_right > 0) { 14 struct swaybar_output *output) {
156 pos += margin; 15 struct swaybar_config *config = bar->config;
157
158 render_sharp_line(window->cairo, block->border,
159 pos - 0.5,
160 1,
161 block->border_right,
162 (window->height * window->scale) - 2);
163
164 pos += block->border_right;
165 }
166
167 // render separator
168 if (!edge && block->separator) {
169 if (is_focused) {
170 cairo_set_source_u32(window->cairo, config->colors.focused_separator);
171 } else {
172 cairo_set_source_u32(window->cairo, config->colors.separator);
173 }
174 if (config->sep_symbol) {
175 offset = pos + (block->separator_block_width - sep_width) / 2;
176 cairo_move_to(window->cairo, offset, margin);
177 pango_printf(window->cairo, window->font, window->scale,
178 false, "%s", config->sep_symbol);
179 } else {
180 cairo_set_line_width(window->cairo, 1);
181 cairo_move_to(window->cairo, pos + block->separator_block_width/2,
182 margin);
183 cairo_line_to(window->cairo, pos + block->separator_block_width/2,
184 (window->height * window->scale) - margin);
185 cairo_stroke(window->cairo);
186 }
187 }
188 16
189}
190
191static const char *strip_workspace_name(bool strip_num, const char *ws_name) {
192 bool strip = false;
193 int i;
194
195 if (strip_num) {
196 int len = strlen(ws_name);
197 for (i = 0; i < len; ++i) {
198 if (!('0' <= ws_name[i] && ws_name[i] <= '9')) {
199 if (':' == ws_name[i] && i < len-1 && i > 0) {
200 strip = true;
201 ++i;
202 }
203 break;
204 }
205 }
206 }
207
208 if (strip) {
209 return ws_name + i;
210 }
211
212 return ws_name;
213}
214
215void workspace_button_size(struct window *window, const char *workspace_name, int *width, int *height) {
216 const char *stripped_name = strip_workspace_name(swaybar.config->strip_workspace_numbers, workspace_name);
217
218 get_text_size(window->cairo, window->font, width, height,
219 window->scale, true, "%s", stripped_name);
220 *width += 2 * ws_horizontal_padding;
221 *height += 2 * ws_vertical_padding;
222}
223
224static void render_workspace_button(struct window *window, struct config *config, struct workspace *ws, double *x) {
225 const char *stripped_name = strip_workspace_name(config->strip_workspace_numbers, ws->name);
226
227 struct box_colors box_colors;
228 if (ws->urgent) {
229 box_colors = config->colors.urgent_workspace;
230 } else if (ws->focused) {
231 box_colors = config->colors.focused_workspace;
232 } else if (ws->visible) {
233 box_colors = config->colors.active_workspace;
234 } else {
235 box_colors = config->colors.inactive_workspace;
236 }
237
238 int width, height;
239 workspace_button_size(window, stripped_name, &width, &height);
240
241 // background
242 cairo_set_source_u32(window->cairo, box_colors.background);
243 cairo_rectangle(window->cairo, *x, 1.5, width - 1, height);
244 cairo_fill(window->cairo);
245
246 // border
247 cairo_set_source_u32(window->cairo, box_colors.border);
248 cairo_rectangle(window->cairo, *x, 1.5, width - 1, height);
249 cairo_stroke(window->cairo);
250
251 // text
252 cairo_set_source_u32(window->cairo, box_colors.text);
253 cairo_move_to(window->cairo, (int)*x + ws_horizontal_padding, margin);
254 pango_printf(window->cairo, window->font, window->scale,
255 true, "%s", stripped_name);
256
257 *x += width + ws_spacing;
258}
259
260static void render_binding_mode_indicator(struct window *window, struct config *config, double pos) {
261 int width, height;
262 get_text_size(window->cairo, window->font, &width, &height,
263 window->scale, false, "%s", config->mode);
264
265 // background
266 cairo_set_source_u32(window->cairo, config->colors.binding_mode.background);
267 cairo_rectangle(window->cairo, pos, 1.5, width + ws_horizontal_padding * 2 - 1,
268 height + ws_vertical_padding * 2);
269 cairo_fill(window->cairo);
270
271 // border
272 cairo_set_source_u32(window->cairo, config->colors.binding_mode.border);
273 cairo_rectangle(window->cairo, pos, 1.5, width + ws_horizontal_padding * 2 - 1,
274 height + ws_vertical_padding * 2);
275 cairo_stroke(window->cairo);
276
277 // text
278 cairo_set_source_u32(window->cairo, config->colors.binding_mode.text);
279 cairo_move_to(window->cairo, (int)pos + ws_horizontal_padding, margin);
280 pango_printf(window->cairo, window->font, window->scale,
281 false, "%s", config->mode);
282}
283
284void render(struct output *output, struct config *config, struct status_line *line) {
285 int i;
286
287 struct window *window = output->window;
288 cairo_t *cairo = window->cairo;
289 bool is_focused = output->focused;
290
291 // Clear
292 cairo_save(cairo); 17 cairo_save(cairo);
293 cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR); 18 cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
294 cairo_paint(cairo); 19 cairo_paint(cairo);
295 cairo_restore(cairo); 20 cairo_restore(cairo);
296 21
297 cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); 22 cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
298 23 if (output->focused) {
299 // Background
300 if (is_focused) {
301 cairo_set_source_u32(cairo, config->colors.focused_background); 24 cairo_set_source_u32(cairo, config->colors.focused_background);
302 } else { 25 } else {
303 cairo_set_source_u32(cairo, config->colors.background); 26 cairo_set_source_u32(cairo, config->colors.background);
304 } 27 }
305 cairo_paint(cairo); 28 cairo_paint(cairo);
306 29
307#ifdef ENABLE_TRAY 30 // TODO: use actual height
308 uint32_t tray_width = tray_render(output, config); 31 return 20;
309#else 32}
310 const uint32_t tray_width = window->width * window->scale; 33
311#endif 34void render_frame(struct swaybar *bar,
312 35 struct swaybar_output *output) {
313 // Command output 36 cairo_surface_t *recorder = cairo_recording_surface_create(
314 if (is_focused) { 37 CAIRO_CONTENT_COLOR_ALPHA, NULL);
315 cairo_set_source_u32(cairo, config->colors.focused_statusline); 38 cairo_t *cairo = cairo_create(recorder);
39 uint32_t height = render_to_cairo(cairo, bar, output);
40 if (height != output->height) {
41 // Reconfigure surface
42 zwlr_layer_surface_v1_set_size(
43 output->layer_surface, 0, height);
44 // TODO: this could infinite loop if the compositor assigns us a
45 // different height than what we asked for
46 wl_surface_commit(output->surface);
47 wl_display_roundtrip(bar->display);
316 } else { 48 } else {
317 cairo_set_source_u32(cairo, config->colors.statusline); 49 // Replay recording into shm and send it off
318 } 50 output->current_buffer = get_next_buffer(bar->shm,
319 51 output->buffers, output->width, output->height);
320 int width, height; 52 cairo_t *shm = output->current_buffer->cairo;
321 53 cairo_set_source_surface(shm, recorder, 0.0, 0.0);
322 if (line->protocol == TEXT) { 54 cairo_paint(shm);
323 get_text_size(window->cairo, window->font, &width, &height, 55 wl_surface_attach(output->surface,
324 window->scale, config->pango_markup, "%s", line->text_line); 56 output->current_buffer->buffer, 0, 0);
325 cairo_move_to(cairo, tray_width - margin - width, margin); 57 wl_surface_damage(output->surface, 0, 0, output->width, output->height);
326 pango_printf(window->cairo, window->font, window->scale, 58 wl_surface_commit(output->surface);
327 config->pango_markup, "%s", line->text_line); 59 wl_display_roundtrip(bar->display);
328 } else if (line->protocol == I3BAR && line->block_line) { 60 }
329 double pos = tray_width - 0.5; 61 cairo_surface_destroy(recorder);
330 bool edge = true; 62 cairo_destroy(cairo);
331 for (i = line->block_line->length - 1; i >= 0; --i) {
332 struct status_block *block = line->block_line->items[i];
333 if (block->full_text && block->full_text[0]) {
334 render_block(window, config, block, &pos, edge, is_focused);
335 edge = false;
336 }
337 }
338 }
339
340 cairo_set_line_width(cairo, 1.0);
341 double x = 0.5;
342
343 // Workspaces
344 if (config->workspace_buttons) {
345 for (i = 0; i < output->workspaces->length; ++i) {
346 struct workspace *ws = output->workspaces->items[i];
347 render_workspace_button(window, config, ws, &x);
348 }
349 }
350
351 // binding mode indicator
352 if (config->mode && config->binding_mode_indicator) {
353 render_binding_mode_indicator(window, config, x);
354 }
355}
356
357void set_window_height(struct window *window, int height) {
358 int text_width, text_height;
359 get_text_size(window->cairo, window->font,
360 &text_width, &text_height, window->scale, false,
361 "Test string for measuring purposes");
362 if (height > 0) {
363 margin = (height - text_height) / 2;
364 ws_vertical_padding = margin - 1.5;
365 }
366 window->height = (text_height + margin * 2) / window->scale;
367} 63}
diff --git a/swaybar/status_line.c b/swaybar/status_line.c
deleted file mode 100644
index 87e90caf..00000000
--- a/swaybar/status_line.c
+++ /dev/null
@@ -1,530 +0,0 @@
1#define _XOPEN_SOURCE 700
2#include <stdlib.h>
3#include <string.h>
4#include <unistd.h>
5#include <json-c/json.h>
6
7#include "swaybar/config.h"
8#include "swaybar/status_line.h"
9#include "log.h"
10#include "util.h"
11
12#define I3JSON_MAXDEPTH 4
13#define I3JSON_UNKNOWN 0
14#define I3JSON_ARRAY 1
15#define I3JSON_STRING 2
16
17struct {
18 int bufsize;
19 char *buffer;
20 char *line_start;
21 char *parserpos;
22 bool escape;
23 int depth;
24 int bar[I3JSON_MAXDEPTH+1];
25} i3json_state = { 0, NULL, NULL, NULL, false, 0, { I3JSON_UNKNOWN } };
26
27static char line[1024];
28static char line_rest[1024];
29
30static char event_buff[1024];
31
32static void free_status_block(void *item) {
33 if (!item) {
34 return;
35 }
36 struct status_block *sb = (struct status_block*)item;
37 if (sb->full_text) {
38 free(sb->full_text);
39 }
40 if (sb->short_text) {
41 free(sb->short_text);
42 }
43 if (sb->align) {
44 free(sb->align);
45 }
46 if (sb->name) {
47 free(sb->name);
48 }
49 if (sb->instance) {
50 free(sb->instance);
51 }
52 free(sb);
53}
54
55static void parse_json(struct bar *bar, const char *text) {
56 json_object *results = json_tokener_parse(text);
57 if (!results) {
58 sway_log(L_DEBUG, "Failed to parse json");
59 return;
60 }
61
62 if (json_object_array_length(results) < 1) {
63 return;
64 }
65
66 if (bar->status->block_line) {
67 list_foreach(bar->status->block_line, free_status_block);
68 list_free(bar->status->block_line);
69 }
70
71 bar->status->block_line = create_list();
72
73 int i;
74 for (i = 0; i < json_object_array_length(results); ++i) {
75 json_object *full_text, *short_text, *color, *min_width, *align, *urgent;
76 json_object *name, *instance, *separator, *separator_block_width;
77 json_object *background, *border, *border_top, *border_bottom;
78 json_object *border_left, *border_right, *markup;
79
80 json_object *json = json_object_array_get_idx(results, i);
81 if (!json) {
82 continue;
83 }
84
85 json_object_object_get_ex(json, "full_text", &full_text);
86 json_object_object_get_ex(json, "short_text", &short_text);
87 json_object_object_get_ex(json, "color", &color);
88 json_object_object_get_ex(json, "min_width", &min_width);
89 json_object_object_get_ex(json, "align", &align);
90 json_object_object_get_ex(json, "urgent", &urgent);
91 json_object_object_get_ex(json, "name", &name);
92 json_object_object_get_ex(json, "instance", &instance);
93 json_object_object_get_ex(json, "markup", &markup);
94 json_object_object_get_ex(json, "separator", &separator);
95 json_object_object_get_ex(json, "separator_block_width", &separator_block_width);
96 json_object_object_get_ex(json, "background", &background);
97 json_object_object_get_ex(json, "border", &border);
98 json_object_object_get_ex(json, "border_top", &border_top);
99 json_object_object_get_ex(json, "border_bottom", &border_bottom);
100 json_object_object_get_ex(json, "border_left", &border_left);
101 json_object_object_get_ex(json, "border_right", &border_right);
102
103 struct status_block *new = calloc(1, sizeof(struct status_block));
104
105 if (full_text) {
106 new->full_text = strdup(json_object_get_string(full_text));
107 }
108
109 if (short_text) {
110 new->short_text = strdup(json_object_get_string(short_text));
111 }
112
113 if (color) {
114 new->color = parse_color(json_object_get_string(color));
115 } else {
116 new->color = bar->config->colors.statusline;
117 }
118
119 if (min_width) {
120 json_type type = json_object_get_type(min_width);
121 if (type == json_type_int) {
122 new->min_width = json_object_get_int(min_width);
123 } else if (type == json_type_string) {
124 /* the width will be calculated when rendering */
125 new->min_width = 0;
126 }
127 }
128
129 if (align) {
130 new->align = strdup(json_object_get_string(align));
131 } else {
132 new->align = strdup("left");
133 }
134
135 if (urgent) {
136 new->urgent = json_object_get_int(urgent);
137 }
138
139 if (name) {
140 new->name = strdup(json_object_get_string(name));
141 }
142
143 if (instance) {
144 new->instance = strdup(json_object_get_string(instance));
145 }
146
147 if (markup) {
148 new->markup = false;
149 const char *markup_str = json_object_get_string(markup);
150 if (strcmp(markup_str, "pango") == 0) {
151 new->markup = true;
152 }
153 }
154
155 if (separator) {
156 new->separator = json_object_get_int(separator);
157 } else {
158 new->separator = true; // i3bar spec
159 }
160
161 if (separator_block_width) {
162 new->separator_block_width = json_object_get_int(separator_block_width);
163 } else {
164 new->separator_block_width = 9; // i3bar spec
165 }
166
167 // Airblader features
168 if (background) {
169 new->background = parse_color(json_object_get_string(background));
170 } else {
171 new->background = 0x0; // transparent
172 }
173
174 if (border) {
175 new->border = parse_color(json_object_get_string(border));
176 } else {
177 new->border = 0x0; // transparent
178 }
179
180 if (border_top) {
181 new->border_top = json_object_get_int(border_top);
182 } else {
183 new->border_top = 1;
184 }
185
186 if (border_bottom) {
187 new->border_bottom = json_object_get_int(border_bottom);
188 } else {
189 new->border_bottom = 1;
190 }
191
192 if (border_left) {
193 new->border_left = json_object_get_int(border_left);
194 } else {
195 new->border_left = 1;
196 }
197
198 if (border_right) {
199 new->border_right = json_object_get_int(border_right);
200 } else {
201 new->border_right = 1;
202 }
203
204 list_add(bar->status->block_line, new);
205 }
206
207 json_object_put(results);
208}
209
210// continue parsing from last parserpos
211static int i3json_parse(struct bar *bar) {
212 char *c = i3json_state.parserpos;
213 int handled = 0;
214 while (*c) {
215 if (i3json_state.bar[i3json_state.depth] == I3JSON_STRING) {
216 if (!i3json_state.escape && *c == '"') {
217 --i3json_state.depth;
218 }
219 i3json_state.escape = !i3json_state.escape && *c == '\\';
220 } else {
221 switch (*c) {
222 case '[':
223 ++i3json_state.depth;
224 if (i3json_state.depth > I3JSON_MAXDEPTH) {
225 sway_abort("JSON too deep");
226 }
227 i3json_state.bar[i3json_state.depth] = I3JSON_ARRAY;
228 if (i3json_state.depth == 2) {
229 i3json_state.line_start = c;
230 }
231 break;
232 case ']':
233 if (i3json_state.bar[i3json_state.depth] != I3JSON_ARRAY) {
234 sway_abort("JSON malformed");
235 }
236 --i3json_state.depth;
237 if (i3json_state.depth == 1) {
238 // c[1] is valid since c[0] != '\0'
239 char p = c[1];
240 c[1] = '\0';
241 parse_json(bar, i3json_state.line_start);
242 c[1] = p;
243 ++handled;
244 i3json_state.line_start = c+1;
245 }
246 break;
247 case '"':
248 ++i3json_state.depth;
249 if (i3json_state.depth > I3JSON_MAXDEPTH) {
250 sway_abort("JSON too deep");
251 }
252 i3json_state.bar[i3json_state.depth] = I3JSON_STRING;
253 break;
254 }
255 }
256 ++c;
257 }
258 i3json_state.parserpos = c;
259 return handled;
260}
261
262// Read line from file descriptor, only show the line tail if it is too long.
263// In non-blocking mode treat "no more data" as a linebreak.
264// If data after a line break has been read, return it in rest.
265// If rest is non-empty, then use that as the start of the next line.
266static int read_line_tail(int fd, char *buf, int nbyte, char *rest) {
267 if (fd < 0 || !buf || !nbyte) {
268 return -1;
269 }
270 int l;
271 char *buffer = malloc(nbyte*2+1);
272 char *readpos = buffer;
273 char *lf;
274 // prepend old data to new line if necessary
275 if (rest) {
276 l = strlen(rest);
277 if (l > nbyte) {
278 strcpy(buffer, rest + l - nbyte);
279 readpos += nbyte;
280 } else if (l) {
281 strcpy(buffer, rest);
282 readpos += l;
283 }
284 }
285 // read until a linefeed is found or no more data is available
286 while ((l = read(fd, readpos, nbyte)) > 0) {
287 readpos[l] = '\0';
288 lf = strchr(readpos, '\n');
289 if (lf) {
290 // linefeed found, replace with \0
291 *lf = '\0';
292 // give data from the end of the line, try to fill the buffer
293 if (lf-buffer > nbyte) {
294 strcpy(buf, lf - nbyte + 1);
295 } else {
296 strcpy(buf, buffer);
297 }
298 // we may have read data from the next line, save it to rest
299 if (rest) {
300 rest[0] = '\0';
301 strcpy(rest, lf + 1);
302 }
303 free(buffer);
304 return strlen(buf);
305 } else {
306 // no linefeed found, slide data back.
307 int overflow = readpos - buffer + l - nbyte;
308 if (overflow > 0) {
309 memmove(buffer, buffer + overflow , nbyte + 1);
310 }
311 }
312 }
313 if (l < 0) {
314 free(buffer);
315 return l;
316 }
317 readpos[l]='\0';
318 if (rest) {
319 rest[0] = '\0';
320 }
321 if (nbyte < readpos - buffer + l - 1) {
322 memcpy(buf, readpos - nbyte + l + 1, nbyte);
323 } else {
324 strncpy(buf, buffer, nbyte);
325 }
326 buf[nbyte-1] = '\0';
327 free(buffer);
328 return strlen(buf);
329}
330
331// make sure that enough buffer space is available starting from parserpos
332static void i3json_ensure_free(int min_free) {
333 int _step = 10240;
334 int r = min_free % _step;
335 if (r) {
336 min_free += _step - r;
337 }
338 if (!i3json_state.buffer) {
339 i3json_state.buffer = malloc(min_free);
340 i3json_state.bufsize = min_free;
341 i3json_state.parserpos = i3json_state.buffer;
342 } else {
343 int len = 0;
344 int pos = 0;
345 if (i3json_state.line_start) {
346 len = strlen(i3json_state.line_start);
347 pos = i3json_state.parserpos - i3json_state.line_start;
348 if (i3json_state.line_start != i3json_state.buffer) {
349 memmove(i3json_state.buffer, i3json_state.line_start, len+1);
350 }
351 } else {
352 len = strlen(i3json_state.buffer);
353 }
354 if (i3json_state.bufsize < len+min_free) {
355 i3json_state.bufsize += min_free;
356 if (i3json_state.bufsize > 1024000) {
357 sway_abort("Status line json too long or malformed.");
358 }
359 i3json_state.buffer = realloc(i3json_state.buffer, i3json_state.bufsize);
360 if (!i3json_state.buffer) {
361 sway_abort("Could not allocate json buffer");
362 }
363 }
364 if (i3json_state.line_start) {
365 i3json_state.line_start = i3json_state.buffer;
366 i3json_state.parserpos = i3json_state.buffer + pos;
367 } else {
368 i3json_state.parserpos = i3json_state.buffer;
369 }
370 }
371 if (!i3json_state.buffer) {
372 sway_abort("Could not allocate buffer.");
373 }
374}
375
376// append data and parse it.
377static int i3json_handle_data(struct bar *bar, char *data) {
378 int len = strlen(data);
379 i3json_ensure_free(len);
380 strcpy(i3json_state.parserpos, data);
381 return i3json_parse(bar);
382}
383
384// read data from fd and parse it.
385static int i3json_handle_fd(struct bar *bar) {
386 i3json_ensure_free(10240);
387 // get fresh data at the end of the buffer
388 int readlen = read(bar->status_read_fd, i3json_state.parserpos, 10239);
389 if (readlen < 0) {
390 return readlen;
391 }
392 i3json_state.parserpos[readlen] = '\0';
393 return i3json_parse(bar);
394}
395
396bool status_line_mouse_event(struct bar *bar, int x, int y, uint32_t button) {
397 sway_log(L_DEBUG, "status_line_mouse_event.");
398 if (!bar->status->click_events) {
399 sway_log(L_DEBUG, "click_events are not enabled.");
400 return false;
401 }
402
403 if (bar->status->protocol == I3BAR) {
404 sway_log(L_DEBUG, "Sending click event.");
405
406 // find clicked block
407 struct status_block *clicked_block = NULL;
408 struct status_block *current_block = NULL;
409 int num_blocks = bar->status->block_line->length;
410
411 if (num_blocks == 0) {
412 return false;
413 } else {
414 current_block = bar->status->block_line->items[0];
415 if (x < current_block->x) {
416 return false;
417 }
418 }
419
420 for (int i = 0; i < num_blocks; i++) {
421 current_block = bar->status->block_line->items[i];
422 if (x < (current_block->x + current_block->width)) {
423 clicked_block = current_block;
424 break;
425 }
426 }
427
428 if (!clicked_block || !clicked_block->name) {
429 return false;
430 }
431
432 // event example {"name":"capture","instance":"label","button":1,"x":3431,"y":18}
433
434 struct json_object *event_json = json_object_new_object();
435 json_object_object_add(event_json, "name", json_object_new_string(clicked_block->name));
436 if (clicked_block->instance) {
437 json_object_object_add(event_json, "instance", json_object_new_string(clicked_block->instance));
438 }
439 json_object_object_add(event_json, "button", json_object_new_int(button));
440 json_object_object_add(event_json, "x", json_object_new_int(x));
441 json_object_object_add(event_json, "y", json_object_new_int(y));
442
443 int len = snprintf(event_buff, sizeof(event_buff), "%s\n", json_object_to_json_string(event_json));
444
445 json_object_put(event_json);
446
447 if (len <= (int)sizeof(event_buff)) { // if not truncated
448 write(bar->status_write_fd, event_buff, len);
449 return true;
450 }
451 }
452
453 return false;
454}
455
456bool handle_status_line(struct bar *bar) {
457 bool dirty = false;
458
459 switch (bar->status->protocol) {
460 case I3BAR:
461 sway_log(L_DEBUG, "Got i3bar protocol.");
462 if (i3json_handle_fd(bar) > 0) {
463 dirty = true;
464 }
465 break;
466 case TEXT:
467 sway_log(L_DEBUG, "Got text protocol.");
468 read_line_tail(bar->status_read_fd, line, sizeof(line), line_rest);
469 dirty = true;
470 bar->status->text_line = line;
471 break;
472 case UNDEF:
473 sway_log(L_DEBUG, "Detecting protocol...");
474 if (read_line_tail(bar->status_read_fd, line, sizeof(line), line_rest) < 0) {
475 break;
476 }
477 dirty = true;
478 bar->status->text_line = line;
479 bar->status->protocol = TEXT;
480 if (line[0] == '{') {
481 // detect i3bar json protocol
482 json_object *proto = json_tokener_parse(line);
483 if (proto) {
484
485 json_object *version;
486 if (json_object_object_get_ex(proto, "version", &version)
487 && json_object_get_int(version) == 1
488 ) {
489 sway_log(L_DEBUG, "Switched to i3bar protocol.");
490 bar->status->protocol = I3BAR;
491 }
492
493 json_object *click_events;
494 if (json_object_object_get_ex(proto, "click_events", &click_events)
495 && json_object_get_boolean(click_events)) {
496
497 sway_log(L_DEBUG, "Enabling click events.");
498 bar->status->click_events = true;
499
500 const char *events_array = "[\n";
501 write(bar->status_write_fd, events_array, strlen(events_array));
502 }
503
504 i3json_handle_data(bar, line_rest);
505
506 json_object_put(proto);
507 }
508 }
509 break;
510 }
511
512 return dirty;
513}
514
515struct status_line *init_status_line() {
516 struct status_line *line = malloc(sizeof(struct status_line));
517 line->block_line = create_list();
518 line->text_line = NULL;
519 line->protocol = UNDEF;
520 line->click_events = false;
521
522 return line;
523}
524
525void free_status_line(struct status_line *line) {
526 if (line->block_line) {
527 list_foreach(line->block_line, free_status_block);
528 list_free(line->block_line);
529 }
530}
diff --git a/swaybar/tray/dbus.c b/swaybar/tray/dbus.c
deleted file mode 100644
index 8e719fd9..00000000
--- a/swaybar/tray/dbus.c
+++ /dev/null
@@ -1,197 +0,0 @@
1#define _XOPEN_SOURCE 700
2#include <stdio.h>
3#include <stdlib.h>
4#include <stdint.h>
5#include <stdbool.h>
6#include <poll.h>
7#include <signal.h>
8#include <time.h>
9#include <dbus/dbus.h>
10#include "swaybar/tray/dbus.h"
11#include "swaybar/event_loop.h"
12#include "log.h"
13
14DBusConnection *conn = NULL;
15
16static void dispatch_watch(int fd, short mask, void *data) {
17 sway_log(L_DEBUG, "Dispatching watch");
18 DBusWatch *watch = data;
19
20 if (!dbus_watch_get_enabled(watch)) {
21 return;
22 }
23
24 uint32_t flags = 0;
25
26 if (mask & POLLIN) {
27 flags |= DBUS_WATCH_READABLE;
28 } if (mask & POLLOUT) {
29 flags |= DBUS_WATCH_WRITABLE;
30 } if (mask & POLLHUP) {
31 flags |= DBUS_WATCH_HANGUP;
32 } if (mask & POLLERR) {
33 flags |= DBUS_WATCH_ERROR;
34 }
35
36 dbus_watch_handle(watch, flags);
37}
38
39static dbus_bool_t add_watch(DBusWatch *watch, void *_data) {
40 if (!dbus_watch_get_enabled(watch)) {
41 // Watch should not be polled
42 return TRUE;
43 }
44
45 short mask = 0;
46 uint32_t flags = dbus_watch_get_flags(watch);
47
48 if (flags & DBUS_WATCH_READABLE) {
49 mask |= POLLIN;
50 } if (flags & DBUS_WATCH_WRITABLE) {
51 mask |= POLLOUT;
52 }
53
54 int fd = dbus_watch_get_unix_fd(watch);
55
56 sway_log(L_DEBUG, "Adding DBus watch fd: %d", fd);
57 add_event(fd, mask, dispatch_watch, watch);
58
59 return TRUE;
60}
61
62static void remove_watch(DBusWatch *watch, void *_data) {
63 int fd = dbus_watch_get_unix_fd(watch);
64
65 remove_event(fd);
66}
67
68static void dispatch_timeout(timer_t timer, void *data) {
69 sway_log(L_DEBUG, "Dispatching DBus timeout");
70 DBusTimeout *timeout = data;
71
72 if (dbus_timeout_get_enabled(timeout)) {
73 dbus_timeout_handle(timeout);
74 }
75}
76
77static dbus_bool_t add_timeout(DBusTimeout *timeout, void *_data) {
78 if (!dbus_timeout_get_enabled(timeout)) {
79 return TRUE;
80 }
81
82 timer_t *timer = malloc(sizeof(timer_t));
83 if (!timer) {
84 sway_log(L_ERROR, "Cannot allocate memory");
85 return FALSE;
86 }
87 struct sigevent ev = {
88 .sigev_notify = SIGEV_NONE,
89 };
90
91 if (timer_create(CLOCK_MONOTONIC, &ev, timer)) {
92 sway_log(L_ERROR, "Could not create DBus timer");
93 return FALSE;
94 }
95
96 int interval = dbus_timeout_get_interval(timeout);
97 int interval_sec = interval / 1000;
98 int interval_msec = (interval_sec * 1000) - interval;
99
100 struct timespec period = {
101 (time_t) interval_sec,
102 ((long) interval_msec) * 1000 * 1000,
103 };
104 struct itimerspec time = {
105 period,
106 period,
107 };
108
109 timer_settime(*timer, 0, &time, NULL);
110
111 dbus_timeout_set_data(timeout, timer, NULL);
112
113 sway_log(L_DEBUG, "Adding DBus timeout. Interval: %ds %dms", interval_sec, interval_msec);
114 add_timer(*timer, dispatch_timeout, timeout);
115
116 return TRUE;
117}
118static void remove_timeout(DBusTimeout *timeout, void *_data) {
119 timer_t *timer = (timer_t *) dbus_timeout_get_data(timeout);
120 sway_log(L_DEBUG, "Removing DBus timeout.");
121
122 if (timer) {
123 remove_timer(*timer);
124 timer_delete(*timer);
125 free(timer);
126 }
127}
128
129static bool should_dispatch = true;
130
131static void dispatch_status(DBusConnection *connection, DBusDispatchStatus new_status,
132 void *_data) {
133 if (new_status == DBUS_DISPATCH_DATA_REMAINS) {
134 should_dispatch = true;
135 }
136}
137
138/* Public functions below */
139
140void dispatch_dbus() {
141 if (!should_dispatch || !conn) {
142 return;
143 }
144
145 DBusDispatchStatus status;
146
147 do {
148 status = dbus_connection_dispatch(conn);
149 } while (status == DBUS_DISPATCH_DATA_REMAINS);
150
151 if (status != DBUS_DISPATCH_COMPLETE) {
152 sway_log(L_ERROR, "Cannot dispatch dbus events: %d", status);
153 }
154
155 should_dispatch = false;
156}
157
158int dbus_init() {
159 DBusError error;
160 dbus_error_init(&error);
161
162 conn = dbus_bus_get(DBUS_BUS_SESSION, &error);
163 if (conn == NULL) {
164 sway_log(L_INFO, "Compiled with dbus support, but unable to connect to dbus");
165 sway_log(L_INFO, "swaybar will be unable to display tray icons.");
166 return -1;
167 }
168
169 dbus_connection_set_exit_on_disconnect(conn, FALSE);
170 if (dbus_error_is_set(&error)) {
171 sway_log(L_ERROR, "Cannot get bus connection: %s\n", error.message);
172 conn = NULL;
173 return -1;
174 }
175
176 sway_log(L_INFO, "Unique name: %s\n", dbus_bus_get_unique_name(conn));
177
178 // Will be called if dispatch status changes
179 dbus_connection_set_dispatch_status_function(conn, dispatch_status, NULL, NULL);
180
181 if (!dbus_connection_set_watch_functions(conn, add_watch, remove_watch,
182 NULL, NULL, NULL)) {
183 dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL);
184 sway_log(L_ERROR, "Failed to activate DBUS watch functions");
185 return -1;
186 }
187
188 if (!dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout,
189 NULL, NULL, NULL)) {
190 dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL);
191 dbus_connection_set_timeout_functions(conn, NULL, NULL, NULL, NULL, NULL);
192 sway_log(L_ERROR, "Failed to activate DBUS timeout functions");
193 return -1;
194 }
195
196 return 0;
197}
diff --git a/swaybar/tray/icon.c b/swaybar/tray/icon.c
deleted file mode 100644
index c146bf32..00000000
--- a/swaybar/tray/icon.c
+++ /dev/null
@@ -1,400 +0,0 @@
1#define _XOPEN_SOURCE 700
2#define _POSIX_C_SOURCE 200809L
3#include <stdio.h>
4#include <stdlib.h>
5#include <unistd.h>
6#include <string.h>
7#include <dirent.h>
8#include <sys/stat.h>
9#include <stdbool.h>
10#include <stdint.h>
11#include <limits.h>
12#include "swaybar/tray/icon.h"
13#include "swaybar/bar.h"
14#include "swaybar/config.h"
15#include "stringop.h"
16#include "log.h"
17
18/**
19 * REVIEW:
20 * This file repeats lots of "costly" operations that are the same for every
21 * icon. It's possible to create a dictionary or some other structure to cache
22 * these, though it may complicate things somewhat.
23 *
24 * Also parsing (index.theme) is currently pretty messy, so that could be made
25 * much better as well. Over all, things work, but are not optimal.
26 */
27
28/* Finds all themes that the given theme inherits */
29static list_t *find_inherits(const char *theme_dir) {
30 const char inherits[] = "Inherits";
31 const char index_name[] = "index.theme";
32 list_t *themes = create_list();
33 FILE *index = NULL;
34 char *path = malloc(strlen(theme_dir) + sizeof(index_name));
35 if (!path) {
36 goto fail;
37 }
38 if (!themes) {
39 goto fail;
40 }
41
42 strcpy(path, theme_dir);
43 strcat(path, index_name);
44
45 index = fopen(path, "r");
46 if (!index) {
47 goto fail;
48 }
49
50 char *buf = NULL;
51 size_t n = 0;
52 while (!feof(index) && getline(&buf, &n, index) != -1) {
53 if (n <= sizeof(inherits) + 1) {
54 continue;
55 }
56 if (strncmp(inherits, buf, sizeof(inherits) - 1) == 0) {
57 char *themestr = buf + sizeof(inherits);
58 themes = split_string(themestr, ",");
59 break;
60 }
61 }
62 free(buf);
63
64fail:
65 free(path);
66 if (index) {
67 fclose(index);
68 }
69 return themes;
70}
71
72static bool isdir(const char *path) {
73 struct stat statbuf;
74 if (stat(path, &statbuf) != -1) {
75 if (S_ISDIR(statbuf.st_mode)) {
76 return true;
77 }
78 }
79 return false;
80
81}
82
83/**
84 * Returns the directory of a given theme if it exists.
85 * The returned pointer must be freed.
86 */
87static char *find_theme_dir(const char *theme) {
88 char *basedir;
89 char *icon_dir;
90
91 if (!theme) {
92 return NULL;
93 }
94
95 if (!(icon_dir = malloc(1024))) {
96 sway_log(L_ERROR, "Out of memory!");
97 goto fail;
98 }
99
100 if ((basedir = getenv("HOME"))) {
101 if (snprintf(icon_dir, 1024, "%s/.icons/%s", basedir, theme) >= 1024) {
102 sway_log(L_ERROR, "Path too long to render");
103 // XXX perhaps just goto trying in /usr/share? This
104 // shouldn't happen anyway, but might with a long global
105 goto fail;
106 }
107
108 if (isdir(icon_dir)) {
109 return icon_dir;
110 }
111 }
112
113 if ((basedir = getenv("XDG_DATA_DIRS"))) {
114 if (snprintf(icon_dir, 1024, "%s/icons/%s", basedir, theme) >= 1024) {
115 sway_log(L_ERROR, "Path too long to render");
116 // ditto
117 goto fail;
118 }
119
120 if (isdir(icon_dir)) {
121 return icon_dir;
122 }
123 }
124
125 // Spec says use "/usr/share/pixmaps/", but I see everything in
126 // "/usr/share/icons/" look it both, I suppose.
127 if (snprintf(icon_dir, 1024, "/usr/share/pixmaps/%s", theme) >= 1024) {
128 sway_log(L_ERROR, "Path too long to render");
129 goto fail;
130 }
131 if (isdir(icon_dir)) {
132 return icon_dir;
133 }
134
135 if (snprintf(icon_dir, 1024, "/usr/share/icons/%s", theme) >= 1024) {
136 sway_log(L_ERROR, "Path too long to render");
137 goto fail;
138 }
139 if (isdir(icon_dir)) {
140 return icon_dir;
141 }
142
143fail:
144 free(icon_dir);
145 sway_log(L_ERROR, "Could not find dir for theme: %s", theme);
146 return NULL;
147}
148
149/**
150 * Returns all theme dirs needed to be looked in for an icon.
151 * Does not check for duplicates
152 */
153static list_t *find_all_theme_dirs(const char *theme) {
154 list_t *dirs = create_list();
155 if (!dirs) {
156 return NULL;
157 }
158 char *dir = find_theme_dir(theme);
159 if (dir) {
160 list_add(dirs, dir);
161 list_t *inherits = find_inherits(dir);
162 list_cat(dirs, inherits);
163 list_free(inherits);
164 }
165 dir = find_theme_dir("hicolor");
166 if (dir) {
167 list_add(dirs, dir);
168 }
169
170 return dirs;
171}
172
173struct subdir {
174 int size;
175 char name[];
176};
177
178static int subdir_str_cmp(const void *_subdir, const void *_str) {
179 const struct subdir *subdir = _subdir;
180 const char *str = _str;
181 return strcmp(subdir->name, str);
182}
183/**
184 * Helper to find_subdirs. Acts similar to `split_string(subdirs, ",")` but
185 * generates a list of struct subdirs
186 */
187static list_t *split_subdirs(char *subdir_str) {
188 list_t *subdir_list = create_list();
189 char *copy = strdup(subdir_str);
190 if (!subdir_list || !copy) {
191 list_free(subdir_list);
192 free(copy);
193 return NULL;
194 }
195
196 char *token;
197 token = strtok(copy, ",");
198 while(token) {
199 int len = strlen(token) + 1;
200 struct subdir *subdir =
201 malloc(sizeof(struct subdir) + sizeof(char [len]));
202 if (!subdir) {
203 // Return what we have
204 return subdir_list;
205 }
206 subdir->size = 0;
207 strcpy(subdir->name, token);
208
209 list_add(subdir_list, subdir);
210
211 token = strtok(NULL, ",");
212 }
213 free(copy);
214
215 return subdir_list;
216}
217/**
218 * Returns a list of all subdirectories of a theme.
219 * Take note: the subdir names are all relative to `theme_dir` and must be
220 * combined with it to form a valid directory.
221 *
222 * Each member of the list is of type (struct subdir *) this struct contains
223 * the name of the subdir, along with size information. These must be freed
224 * bye the caller.
225 *
226 * This currently ignores min and max sizes of icons.
227 */
228static list_t* find_theme_subdirs(const char *theme_dir) {
229 const char index_name[] = "/index.theme";
230 list_t *dirs = NULL;
231 char *path = malloc(strlen(theme_dir) + sizeof(index_name));
232 FILE *index = NULL;
233 if (!path) {
234 sway_log(L_ERROR, "Failed to allocate memory");
235 goto fail;
236 }
237
238 strcpy(path, theme_dir);
239 strcat(path, index_name);
240
241 index = fopen(path, "r");
242 if (!index) {
243 sway_log(L_ERROR, "Could not open file: %s", path);
244 goto fail;
245 }
246
247 char *buf = NULL;
248 size_t n = 0;
249 const char directories[] = "Directories";
250 while (!feof(index) && getline(&buf, &n, index) != -1) {
251 if (n <= sizeof(directories) + 1) {
252 continue;
253 }
254 if (strncmp(directories, buf, sizeof(directories) - 1) == 0) {
255 char *dirstr = buf + sizeof(directories);
256 dirs = split_subdirs(dirstr);
257 break;
258 }
259 }
260 // Now, find the size of each dir
261 struct subdir *current_subdir = NULL;
262 const char size[] = "Size";
263 while (!feof(index) && getline(&buf, &n, index) != -1) {
264 if (buf[0] == '[') {
265 int len = strlen(buf);
266 if (buf[len-1] == '\n') {
267 len--;
268 }
269 // replace ']'
270 buf[len-1] = '\0';
271
272 int index;
273 if ((index = list_seq_find(dirs, subdir_str_cmp, buf+1)) != -1) {
274 current_subdir = (dirs->items[index]);
275 }
276 }
277
278 if (strncmp(size, buf, sizeof(size) - 1) == 0) {
279 if (current_subdir) {
280 current_subdir->size = atoi(buf + sizeof(size));
281 }
282 }
283 }
284 free(buf);
285fail:
286 free(path);
287 if (index) {
288 fclose(index);
289 }
290 return dirs;
291}
292
293/* Returns the file of an icon given its name and size */
294static char *find_icon_file(const char *name, int size) {
295 int namelen = strlen(name);
296 list_t *dirs = find_all_theme_dirs(swaybar.config->icon_theme);
297 if (!dirs) {
298 return NULL;
299 }
300 int min_size_diff = INT_MAX;
301 char *current_file = NULL;
302
303 for (int i = 0; i < dirs->length; ++i) {
304 char *dir = dirs->items[i];
305 list_t *subdirs = find_theme_subdirs(dir);
306
307 if (!subdirs) {
308 continue;
309 }
310
311 for (int i = 0; i < subdirs->length; ++i) {
312 struct subdir *subdir = subdirs->items[i];
313
314 // Only use an unsized if we don't already have a
315 // canidate this should probably change to allow svgs
316 if (!subdir->size && current_file) {
317 continue;
318 }
319
320 int size_diff = abs(size - subdir->size);
321
322 if (size_diff >= min_size_diff) {
323 continue;
324 }
325
326 char *path = malloc(strlen(subdir->name) + strlen(dir) + 2);
327
328 strcpy(path, dir);
329 path[strlen(dir)] = '/';
330 strcpy(path + strlen(dir) + 1, subdir->name);
331
332 DIR *icons = opendir(path);
333 if (!icons) {
334 free(path);
335 continue;
336 }
337
338 struct dirent *direntry;
339 while ((direntry = readdir(icons)) != NULL) {
340 int len = strlen(direntry->d_name);
341 if (len <= namelen + 2) { //must have some ext
342 continue;
343 }
344 if (strncmp(direntry->d_name, name, namelen) == 0) {
345 char *ext = direntry->d_name + namelen + 1;
346#ifdef WITH_GDK_PIXBUF
347 if (strcmp(ext, "png") == 0 ||
348 strcmp(ext, "xpm") == 0 ||
349 strcmp(ext, "svg") == 0) {
350#else
351 if (strcmp(ext, "png") == 0) {
352#endif
353 free(current_file);
354 char *icon_path = malloc(strlen(path) + len + 2);
355
356 strcpy(icon_path, path);
357 icon_path[strlen(path)] = '/';
358 strcpy(icon_path + strlen(path) + 1, direntry->d_name);
359 current_file = icon_path;
360 min_size_diff = size_diff;
361 }
362 }
363 }
364 free(path);
365 closedir(icons);
366 }
367 free_flat_list(subdirs);
368 }
369 free_flat_list(dirs);
370
371 return current_file;
372}
373
374cairo_surface_t *find_icon(const char *name, int size) {
375 char *image_path = find_icon_file(name, size);
376 if (image_path == NULL) {
377 return NULL;
378 }
379
380 cairo_surface_t *image = NULL;
381#ifdef WITH_GDK_PIXBUF
382 GError *err = NULL;
383 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(image_path, &err);
384 if (!pixbuf) {
385 sway_log(L_ERROR, "Failed to load icon image: %s", err->message);
386 }
387 image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf);
388 g_object_unref(pixbuf);
389#else
390 // TODO make svg work? cairo supports it. maybe remove gdk alltogether
391 image = cairo_image_surface_create_from_png(image_path);
392#endif //WITH_GDK_PIXBUF
393 if (!image) {
394 sway_log(L_ERROR, "Could not read icon image");
395 return NULL;
396 }
397
398 free(image_path);
399 return image;
400}
diff --git a/swaybar/tray/sni.c b/swaybar/tray/sni.c
deleted file mode 100644
index c9d00657..00000000
--- a/swaybar/tray/sni.c
+++ /dev/null
@@ -1,481 +0,0 @@
1#define _XOPEN_SOURCE 500
2#include <stdlib.h>
3#include <stdio.h>
4#include <string.h>
5#include <stdint.h>
6#include <stdbool.h>
7#include <dbus/dbus.h>
8#include <arpa/inet.h>
9#include <netinet/in.h>
10#include "swaybar/tray/dbus.h"
11#include "swaybar/tray/sni.h"
12#include "swaybar/tray/icon.h"
13#include "swaybar/bar.h"
14#include "client/cairo.h"
15#include "log.h"
16
17// Not sure what this is but cairo needs it.
18static const cairo_user_data_key_t cairo_user_data_key;
19
20struct sni_icon_ref *sni_icon_ref_create(struct StatusNotifierItem *item,
21 int height) {
22 struct sni_icon_ref *sni_ref = malloc(sizeof(struct sni_icon_ref));
23 if (!sni_ref) {
24 return NULL;
25 }
26 sni_ref->icon = cairo_image_surface_scale(item->image, height, height);
27 sni_ref->ref = item;
28
29 return sni_ref;
30}
31
32void sni_icon_ref_free(struct sni_icon_ref *sni_ref) {
33 if (!sni_ref) {
34 return;
35 }
36 cairo_surface_destroy(sni_ref->icon);
37 free(sni_ref);
38}
39
40/* Gets the pixmap of an icon */
41static void reply_icon(DBusPendingCall *pending, void *_data) {
42 struct StatusNotifierItem *item = _data;
43
44 DBusMessage *reply = dbus_pending_call_steal_reply(pending);
45
46 if (!reply) {
47 sway_log(L_ERROR, "Did not get reply");
48 goto bail;
49 }
50
51 int message_type = dbus_message_get_type(reply);
52
53 if (message_type == DBUS_MESSAGE_TYPE_ERROR) {
54 char *msg;
55
56 dbus_message_get_args(reply, NULL,
57 DBUS_TYPE_STRING, &msg,
58 DBUS_TYPE_INVALID);
59
60 sway_log(L_ERROR, "Message is error: %s", msg);
61 goto bail;
62 }
63
64 DBusMessageIter iter;
65 DBusMessageIter variant; /* v[a(iiay)] */
66 DBusMessageIter array; /* a(iiay) */
67 DBusMessageIter d_struct; /* (iiay) */
68 DBusMessageIter icon; /* ay */
69
70 dbus_message_iter_init(reply, &iter);
71
72 // Each if here checks the types above before recursing
73 if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
74 sway_log(L_ERROR, "Relpy type incorrect");
75 sway_log(L_ERROR, "Should be \"v\", is \"%s\"",
76 dbus_message_iter_get_signature(&iter));
77 goto bail;
78 }
79 dbus_message_iter_recurse(&iter, &variant);
80
81 if (strcmp("a(iiay)", dbus_message_iter_get_signature(&variant)) != 0) {
82 sway_log(L_ERROR, "Relpy type incorrect");
83 sway_log(L_ERROR, "Should be \"a(iiay)\", is \"%s\"",
84 dbus_message_iter_get_signature(&variant));
85 goto bail;
86 }
87
88 if (dbus_message_iter_get_element_count(&variant) == 0) {
89 // Can't recurse if there are no items
90 sway_log(L_INFO, "Item has no icon");
91 goto bail;
92 }
93 dbus_message_iter_recurse(&variant, &array);
94
95 dbus_message_iter_recurse(&array, &d_struct);
96
97 int width;
98 dbus_message_iter_get_basic(&d_struct, &width);
99 dbus_message_iter_next(&d_struct);
100
101 int height;
102 dbus_message_iter_get_basic(&d_struct, &height);
103 dbus_message_iter_next(&d_struct);
104
105 int len = dbus_message_iter_get_element_count(&d_struct);
106
107 if (!len) {
108 sway_log(L_ERROR, "No icon data");
109 goto bail;
110 }
111
112 // Also implies len % 4 == 0, useful below
113 if (len != width * height * 4) {
114 sway_log(L_ERROR, "Incorrect array size passed");
115 goto bail;
116 }
117
118 dbus_message_iter_recurse(&d_struct, &icon);
119
120 int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
121 // FIXME support a variable stride
122 // (works on my machine though for all tested widths)
123 if (!sway_assert(stride == width * 4, "Stride must be equal to byte length")) {
124 goto bail;
125 }
126
127 // Data is by reference, no need to free
128 uint8_t *message_data;
129 dbus_message_iter_get_fixed_array(&icon, &message_data, &len);
130
131 uint8_t *image_data = malloc(stride * height);
132 if (!image_data) {
133 sway_log(L_ERROR, "Could not allocate memory for icon");
134 goto bail;
135 }
136
137 // Transform from network byte order to host byte order
138 // Assumptions are safe because the equality above
139 uint32_t *network = (uint32_t *) message_data;
140 uint32_t *host = (uint32_t *)image_data;
141 for (int i = 0; i < width * height; ++i) {
142 host[i] = ntohl(network[i]);
143 }
144
145 cairo_surface_t *image = cairo_image_surface_create_for_data(
146 image_data, CAIRO_FORMAT_ARGB32,
147 width, height, stride);
148
149 if (image) {
150 if (item->image) {
151 cairo_surface_destroy(item->image);
152 }
153 item->image = image;
154 // Free the image data on surface destruction
155 cairo_surface_set_user_data(image,
156 &cairo_user_data_key,
157 image_data,
158 free);
159 item->dirty = true;
160 dirty = true;
161
162 dbus_message_unref(reply);
163 dbus_pending_call_unref(pending);
164 return;
165 } else {
166 sway_log(L_ERROR, "Could not create image surface");
167 free(image_data);
168 }
169
170bail:
171 if (reply) {
172 dbus_message_unref(reply);
173 }
174 dbus_pending_call_unref(pending);
175 sway_log(L_ERROR, "Could not get icon from item");
176 return;
177}
178static void send_icon_msg(struct StatusNotifierItem *item) {
179 DBusPendingCall *pending;
180 DBusMessage *message = dbus_message_new_method_call(
181 item->name,
182 "/StatusNotifierItem",
183 "org.freedesktop.DBus.Properties",
184 "Get");
185 const char *iface;
186 if (item->kde_special_snowflake) {
187 iface = "org.kde.StatusNotifierItem";
188 } else {
189 iface = "org.freedesktop.StatusNotifierItem";
190 }
191 const char *prop = "IconPixmap";
192
193 dbus_message_append_args(message,
194 DBUS_TYPE_STRING, &iface,
195 DBUS_TYPE_STRING, &prop,
196 DBUS_TYPE_INVALID);
197
198 bool status =
199 dbus_connection_send_with_reply(conn, message, &pending, -1);
200
201 dbus_message_unref(message);
202
203 if (!(pending || status)) {
204 sway_log(L_ERROR, "Could not get item icon");
205 return;
206 }
207
208 dbus_pending_call_set_notify(pending, reply_icon, item, NULL);
209}
210
211/* Get an icon by its name */
212static void reply_icon_name(DBusPendingCall *pending, void *_data) {
213 struct StatusNotifierItem *item = _data;
214
215 DBusMessage *reply = dbus_pending_call_steal_reply(pending);
216
217 if (!reply) {
218 sway_log(L_INFO, "Got no icon name reply from item");
219 goto bail;
220 }
221
222 int message_type = dbus_message_get_type(reply);
223
224 if (message_type == DBUS_MESSAGE_TYPE_ERROR) {
225 char *msg;
226
227 dbus_message_get_args(reply, NULL,
228 DBUS_TYPE_STRING, &msg,
229 DBUS_TYPE_INVALID);
230
231 sway_log(L_INFO, "Could not get icon name: %s", msg);
232 goto bail;
233 }
234
235 DBusMessageIter iter; /* v[s] */
236 DBusMessageIter variant; /* s */
237
238 dbus_message_iter_init(reply, &iter);
239 if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
240 sway_log(L_ERROR, "Relpy type incorrect");
241 sway_log(L_ERROR, "Should be \"v\", is \"%s\"",
242 dbus_message_iter_get_signature(&iter));
243 goto bail;
244 }
245 dbus_message_iter_recurse(&iter, &variant);
246
247
248 if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_STRING) {
249 sway_log(L_ERROR, "Relpy type incorrect");
250 sway_log(L_ERROR, "Should be \"s\", is \"%s\"",
251 dbus_message_iter_get_signature(&iter));
252 goto bail;
253 }
254
255 char *icon_name;
256 dbus_message_iter_get_basic(&variant, &icon_name);
257
258 cairo_surface_t *image = find_icon(icon_name, 256);
259
260 if (image) {
261 sway_log(L_DEBUG, "Icon for %s found with size %d", icon_name,
262 cairo_image_surface_get_width(image));
263 if (item->image) {
264 cairo_surface_destroy(item->image);
265 }
266 item->image = image;
267 item->dirty = true;
268 dirty = true;
269
270 dbus_message_unref(reply);
271 dbus_pending_call_unref(pending);
272 return;
273 }
274
275bail:
276 if (reply) {
277 dbus_message_unref(reply);
278 }
279 dbus_pending_call_unref(pending);
280 // Now try the pixmap
281 send_icon_msg(item);
282 return;
283}
284static void send_icon_name_msg(struct StatusNotifierItem *item) {
285 DBusPendingCall *pending;
286 DBusMessage *message = dbus_message_new_method_call(
287 item->name,
288 "/StatusNotifierItem",
289 "org.freedesktop.DBus.Properties",
290 "Get");
291 const char *iface;
292 if (item->kde_special_snowflake) {
293 iface = "org.kde.StatusNotifierItem";
294 } else {
295 iface = "org.freedesktop.StatusNotifierItem";
296 }
297 const char *prop = "IconName";
298
299 dbus_message_append_args(message,
300 DBUS_TYPE_STRING, &iface,
301 DBUS_TYPE_STRING, &prop,
302 DBUS_TYPE_INVALID);
303
304 bool status =
305 dbus_connection_send_with_reply(conn, message, &pending, -1);
306
307 dbus_message_unref(message);
308
309 if (!(pending || status)) {
310 sway_log(L_ERROR, "Could not get item icon name");
311 return;
312 }
313
314 dbus_pending_call_set_notify(pending, reply_icon_name, item, NULL);
315}
316
317void get_icon(struct StatusNotifierItem *item) {
318 send_icon_name_msg(item);
319}
320
321void sni_activate(struct StatusNotifierItem *item, uint32_t x, uint32_t y) {
322 const char *iface =
323 (item->kde_special_snowflake ? "org.kde.StatusNotifierItem"
324 : "org.freedesktop.StatusNotifierItem");
325 DBusMessage *message = dbus_message_new_method_call(
326 item->name,
327 "/StatusNotifierItem",
328 iface,
329 "Activate");
330
331 dbus_message_append_args(message,
332 DBUS_TYPE_INT32, &x,
333 DBUS_TYPE_INT32, &y,
334 DBUS_TYPE_INVALID);
335
336 dbus_connection_send(conn, message, NULL);
337
338 dbus_message_unref(message);
339}
340
341void sni_context_menu(struct StatusNotifierItem *item, uint32_t x, uint32_t y) {
342 const char *iface =
343 (item->kde_special_snowflake ? "org.kde.StatusNotifierItem"
344 : "org.freedesktop.StatusNotifierItem");
345 DBusMessage *message = dbus_message_new_method_call(
346 item->name,
347 "/StatusNotifierItem",
348 iface,
349 "ContextMenu");
350
351 dbus_message_append_args(message,
352 DBUS_TYPE_INT32, &x,
353 DBUS_TYPE_INT32, &y,
354 DBUS_TYPE_INVALID);
355
356 dbus_connection_send(conn, message, NULL);
357
358 dbus_message_unref(message);
359}
360void sni_secondary(struct StatusNotifierItem *item, uint32_t x, uint32_t y) {
361 const char *iface =
362 (item->kde_special_snowflake ? "org.kde.StatusNotifierItem"
363 : "org.freedesktop.StatusNotifierItem");
364 DBusMessage *message = dbus_message_new_method_call(
365 item->name,
366 "/StatusNotifierItem",
367 iface,
368 "SecondaryActivate");
369
370 dbus_message_append_args(message,
371 DBUS_TYPE_INT32, &x,
372 DBUS_TYPE_INT32, &y,
373 DBUS_TYPE_INVALID);
374
375 dbus_connection_send(conn, message, NULL);
376
377 dbus_message_unref(message);
378}
379
380static void get_unique_name(struct StatusNotifierItem *item) {
381 // I think that we're fine being sync here becaues the message is
382 // directly to the message bus. Could be async though.
383 DBusMessage *message = dbus_message_new_method_call(
384 "org.freedesktop.DBus",
385 "/org/freedesktop/DBus",
386 "org.freedesktop.DBus",
387 "GetNameOwner");
388
389 dbus_message_append_args(message,
390 DBUS_TYPE_STRING, &item->name,
391 DBUS_TYPE_INVALID);
392
393 DBusMessage *reply = dbus_connection_send_with_reply_and_block(
394 conn, message, -1, NULL);
395
396 dbus_message_unref(message);
397
398 if (!reply) {
399 sway_log(L_ERROR, "Could not get unique name for item: %s",
400 item->name);
401 return;
402 }
403
404 char *unique_name;
405 if (!dbus_message_get_args(reply, NULL,
406 DBUS_TYPE_STRING, &unique_name,
407 DBUS_TYPE_INVALID)) {
408 sway_log(L_ERROR, "Error parsing method args");
409 } else {
410 if (item->unique_name) {
411 free(item->unique_name);
412 }
413 item->unique_name = strdup(unique_name);
414 }
415
416 dbus_message_unref(reply);
417}
418
419struct StatusNotifierItem *sni_create(const char *name) {
420 // Make sure `name` is well formed
421 if (!dbus_validate_bus_name(name, NULL)) {
422 sway_log(L_INFO, "Name (%s) is not a bus name. We cannot create an item.", name);
423 return NULL;
424 }
425
426 struct StatusNotifierItem *item = malloc(sizeof(struct StatusNotifierItem));
427 item->name = strdup(name);
428 item->unique_name = NULL;
429 item->image = NULL;
430 item->dirty = false;
431
432 // If it doesn't use this name then assume that it uses the KDE spec
433 // This is because xembed-sni-proxy uses neither "org.freedesktop" nor
434 // "org.kde" and just gives us the items "unique name"
435 //
436 // We could use this to our advantage and fill out the "unique name"
437 // field with the given name if it is neither freedesktop or kde, but
438 // that's makes us rely on KDE hackyness which is bad practice
439 const char freedesktop_name[] = "org.freedesktop";
440 if (strncmp(name, freedesktop_name, sizeof(freedesktop_name) - 1) != 0) {
441 item->kde_special_snowflake = true;
442 } else {
443 item->kde_special_snowflake = false;
444 }
445
446 get_icon(item);
447
448 get_unique_name(item);
449
450 return item;
451}
452/* Return 0 if `item` has a name of `str` */
453int sni_str_cmp(const void *_item, const void *_str) {
454 const struct StatusNotifierItem *item = _item;
455 const char *str = _str;
456
457 return strcmp(item->name, str);
458}
459/* Returns 0 if `item` has a unique name of `str` */
460int sni_uniq_cmp(const void *_item, const void *_str) {
461 const struct StatusNotifierItem *item = _item;
462 const char *str = _str;
463
464 if (!item->unique_name) {
465 return false;
466 }
467 return strcmp(item->unique_name, str);
468}
469void sni_free(struct StatusNotifierItem *item) {
470 if (!item) {
471 return;
472 }
473 free(item->name);
474 if (item->unique_name) {
475 free(item->unique_name);
476 }
477 if (item->image) {
478 cairo_surface_destroy(item->image);
479 }
480 free(item);
481}
diff --git a/swaybar/tray/sni_watcher.c b/swaybar/tray/sni_watcher.c
deleted file mode 100644
index 86453e70..00000000
--- a/swaybar/tray/sni_watcher.c
+++ /dev/null
@@ -1,497 +0,0 @@
1#define _XOPEN_SOURCE 500
2#include <unistd.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6#include <stdbool.h>
7#include <dbus/dbus.h>
8#include "swaybar/tray/dbus.h"
9#include "list.h"
10#include "log.h"
11
12static list_t *items = NULL;
13static list_t *hosts = NULL;
14
15/**
16 * Describes the function of the StatusNotifierWatcher
17 * See https://freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierWatcher/
18 *
19 * We also implement KDE's special snowflake protocol, it's like this but with
20 * all occurrences 'freedesktop' replaced with 'kde'. There is no KDE introspect.
21 */
22static const char *interface_xml =
23 "<!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'"
24 "'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>"
25 "<node>"
26 " <interface name='org.freedesktop.DBus.Introspectable'>"
27 " <method name='Introspect'>"
28 " <arg name='xml_data' direction='out' type='s'/>"
29 " </method>"
30 " </interface>"
31 " <interface name='org.freedesktop.DBus.Properties'>"
32 " <method name='Get'>"
33 " <arg name='interface' direction='in' type='s'/>"
34 " <arg name='propname' direction='in' type='s'/>"
35 " <arg name='value' direction='out' type='v'/>"
36 " </method>"
37 " <method name='Set'>"
38 " <arg name='interface' direction='in' type='s'/>"
39 " <arg name='propname' direction='in' type='s'/>"
40 " <arg name='value' direction='in' type='v'/>"
41 " </method>"
42 " <method name='GetAll'>"
43 " <arg name='interface' direction='in' type='s'/>"
44 " <arg name='props' direction='out' type='a{sv}'/>"
45 " </method>"
46 " </interface>"
47 " <interface name='org.freedesktop.StatusNotifierWatcher'>"
48 " <method name='RegisterStatusNotifierItem'>"
49 " <arg type='s' name='service' direction='in'/>"
50 " </method>"
51 " <method name='RegisterStatusNotifierHost'>"
52 " <arg type='s' name='service' direction='in'/>"
53 " </method>"
54 " <property name='RegisteredStatusNotifierItems' type='as' access='read'/>"
55 " <property name='IsStatusNotifierHostRegistered' type='b' access='read'/>"
56 " <property name='ProtocolVersion' type='i' access='read'/>"
57 " <signal name='StatusNotifierItemRegistered'>"
58 " <arg type='s' name='service' direction='out'/>"
59 " </signal>"
60 " <signal name='StatusNotifierItemUnregistered'>"
61 " <arg type='s' name='service' direction='out'/>"
62 " </signal>"
63 " <signal name='StatusNotifierHostRegistered'>"
64 " <arg type='' name='service' direction='out'/>"
65 " </signal>"
66 " </interface>"
67 "</node>";
68
69static void host_registered_signal(DBusConnection *connection) {
70 // Send one signal for each protocol
71 DBusMessage *signal = dbus_message_new_signal(
72 "/StatusNotifierWatcher",
73 "org.freedesktop.StatusNotifierWatcher",
74 "StatusNotifierHostRegistered");
75
76 dbus_connection_send(connection, signal, NULL);
77 dbus_message_unref(signal);
78
79
80 signal = dbus_message_new_signal(
81 "/StatusNotifierWatcher",
82 "org.kde.StatusNotifierWatcher",
83 "StatusNotifierHostRegistered");
84
85 dbus_connection_send(connection, signal, NULL);
86 dbus_message_unref(signal);
87}
88static void item_registered_signal(DBusConnection *connection, const char *name) {
89 DBusMessage *signal = dbus_message_new_signal(
90 "/StatusNotifierWatcher",
91 "org.freedesktop.StatusNotifierWatcher",
92 "StatusNotifierItemRegistered");
93 dbus_message_append_args(signal,
94 DBUS_TYPE_STRING, &name,
95 DBUS_TYPE_INVALID);
96 dbus_connection_send(connection, signal, NULL);
97 dbus_message_unref(signal);
98
99 signal = dbus_message_new_signal(
100 "/StatusNotifierWatcher",
101 "org.kde.StatusNotifierWatcher",
102 "StatusNotifierItemRegistered");
103 dbus_message_append_args(signal,
104 DBUS_TYPE_STRING, &name,
105 DBUS_TYPE_INVALID);
106 dbus_connection_send(connection, signal, NULL);
107 dbus_message_unref(signal);
108}
109static void item_unregistered_signal(DBusConnection *connection, const char *name) {
110 DBusMessage *signal = dbus_message_new_signal(
111 "/StatusNotifierWatcher",
112 "org.freedesktop.StatusNotifierWatcher",
113 "StatusNotifierItemUnregistered");
114 dbus_message_append_args(signal,
115 DBUS_TYPE_STRING, &name,
116 DBUS_TYPE_INVALID);
117 dbus_connection_send(connection, signal, NULL);
118 dbus_message_unref(signal);
119
120 signal = dbus_message_new_signal(
121 "/StatusNotifierWatcher",
122 "org.kde.StatusNotifierWatcher",
123 "StatusNotifierItemUnregistered");
124 dbus_message_append_args(signal,
125 DBUS_TYPE_STRING, &name,
126 DBUS_TYPE_INVALID);
127 dbus_connection_send(connection, signal, NULL);
128 dbus_message_unref(signal);
129}
130
131static void respond_to_introspect(DBusConnection *connection, DBusMessage *request) {
132 DBusMessage *reply;
133
134 reply = dbus_message_new_method_return(request);
135 dbus_message_append_args(reply,
136 DBUS_TYPE_STRING, &interface_xml,
137 DBUS_TYPE_INVALID);
138 dbus_connection_send(connection, reply, NULL);
139 dbus_message_unref(reply);
140}
141
142static void register_item(DBusConnection *connection, DBusMessage *message) {
143 DBusError error;
144 char *name;
145
146 dbus_error_init(&error);
147 if (!dbus_message_get_args(message, &error,
148 DBUS_TYPE_STRING, &name,
149 DBUS_TYPE_INVALID)) {
150 sway_log(L_ERROR, "Error parsing method args: %s\n", error.message);
151 }
152
153 sway_log(L_INFO, "RegisterStatusNotifierItem called with \"%s\"\n", name);
154
155 // Don't add duplicate or not real item
156 if (!dbus_validate_bus_name(name, NULL)) {
157 sway_log(L_INFO, "This item is not valid, we cannot keep track of it.");
158 return;
159 }
160
161 if (list_seq_find(items, (int (*)(const void *, const void *))strcmp, name) != -1) {
162 return;
163 }
164 if (!dbus_bus_name_has_owner(connection, name, &error)) {
165 return;
166 }
167
168 list_add(items, strdup(name));
169 item_registered_signal(connection, name);
170
171 // It's silly, but xembedsniproxy wants a reply for this function
172 DBusMessage *reply = dbus_message_new_method_return(message);
173 dbus_connection_send(connection, reply, NULL);
174 dbus_message_unref(reply);
175}
176
177static void register_host(DBusConnection *connection, DBusMessage *message) {
178 DBusError error;
179 char *name;
180
181 dbus_error_init(&error);
182 if (!dbus_message_get_args(message, &error,
183 DBUS_TYPE_STRING, &name,
184 DBUS_TYPE_INVALID)) {
185 sway_log(L_ERROR, "Error parsing method args: %s\n", error.message);
186 }
187
188 sway_log(L_INFO, "RegisterStatusNotifierHost called with \"%s\"\n", name);
189
190 // Don't add duplicate or not real host
191 if (!dbus_validate_bus_name(name, NULL)) {
192 sway_log(L_INFO, "This item is not valid, we cannot keep track of it.");
193 return;
194 }
195
196
197 if (list_seq_find(hosts, (int (*)(const void *, const void *))strcmp, name) != -1) {
198 return;
199 }
200 if (!dbus_bus_name_has_owner(connection, name, &error)) {
201 return;
202 }
203
204 list_add(hosts, strdup(name));
205 host_registered_signal(connection);
206}
207
208static void get_property(DBusConnection *connection, DBusMessage *message) {
209 DBusError error;
210 char *interface;
211 char *property;
212
213 dbus_error_init(&error);
214 if (!dbus_message_get_args(message, &error,
215 DBUS_TYPE_STRING, &interface,
216 DBUS_TYPE_STRING, &property,
217 DBUS_TYPE_INVALID)) {
218 sway_log(L_ERROR, "Error parsing prop args: %s\n", error.message);
219 return;
220 }
221
222 if (strcmp(property, "RegisteredStatusNotifierItems") == 0) {
223 sway_log(L_INFO, "Replying with items\n");
224 DBusMessage *reply;
225 reply = dbus_message_new_method_return(message);
226 DBusMessageIter iter;
227 DBusMessageIter sub;
228 DBusMessageIter subsub;
229
230 dbus_message_iter_init_append(reply, &iter);
231
232 dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
233 "as", &sub);
234 dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY,
235 "s", &subsub);
236
237 for (int i = 0; i < items->length; ++i) {
238 dbus_message_iter_append_basic(&subsub,
239 DBUS_TYPE_STRING, &items->items[i]);
240 }
241
242 dbus_message_iter_close_container(&sub, &subsub);
243 dbus_message_iter_close_container(&iter, &sub);
244
245 dbus_connection_send(connection, reply, NULL);
246 dbus_message_unref(reply);
247 } else if (strcmp(property, "IsStatusNotifierHostRegistered") == 0) {
248 DBusMessage *reply;
249 DBusMessageIter iter;
250 DBusMessageIter sub;
251 int registered = (hosts == NULL || hosts->length == 0) ? 0 : 1;
252
253 reply = dbus_message_new_method_return(message);
254
255 dbus_message_iter_init_append(reply, &iter);
256
257 dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
258 "b", &sub);
259 dbus_message_iter_append_basic(&sub,
260 DBUS_TYPE_BOOLEAN, &registered);
261
262 dbus_message_iter_close_container(&iter, &sub);
263
264 dbus_connection_send(connection, reply, NULL);
265 dbus_message_unref(reply);
266 } else if (strcmp(property, "ProtocolVersion") == 0) {
267 DBusMessage *reply;
268 DBusMessageIter iter;
269 DBusMessageIter sub;
270 const int version = 0;
271
272 reply = dbus_message_new_method_return(message);
273
274 dbus_message_iter_init_append(reply, &iter);
275
276 dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
277 "i", &sub);
278 dbus_message_iter_append_basic(&sub,
279 DBUS_TYPE_INT32, &version);
280
281 dbus_message_iter_close_container(&iter, &sub);
282 dbus_connection_send(connection, reply, NULL);
283 dbus_message_unref(reply);
284 }
285}
286
287static void set_property(DBusConnection *connection, DBusMessage *message) {
288 // All properties are read only and we don't allow new properties
289 return;
290}
291
292static void get_all(DBusConnection *connection, DBusMessage *message) {
293 DBusMessage *reply;
294 reply = dbus_message_new_method_return(message);
295 DBusMessageIter iter; /* a{v} */
296 DBusMessageIter arr;
297 DBusMessageIter dict;
298 DBusMessageIter sub;
299 DBusMessageIter subsub;
300 int registered = (hosts == NULL || hosts->length == 0) ? 0 : 1;
301 const int version = 0;
302 const char *prop;
303
304 // Could clean this up with a function for each prop
305 dbus_message_iter_init_append(reply, &iter);
306 dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
307 "{sv}", &arr);
308
309 prop = "RegisteredStatusNotifierItems";
310 dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY,
311 NULL, &dict);
312 dbus_message_iter_append_basic(&dict,
313 DBUS_TYPE_STRING, &prop);
314 dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT,
315 "as", &sub);
316 dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY,
317 "s", &subsub);
318 for (int i = 0; i < items->length; ++i) {
319 dbus_message_iter_append_basic(&subsub,
320 DBUS_TYPE_STRING, &items->items[i]);
321 }
322 dbus_message_iter_close_container(&sub, &subsub);
323 dbus_message_iter_close_container(&dict, &sub);
324 dbus_message_iter_close_container(&arr, &dict);
325
326 prop = "IsStatusNotifierHostRegistered";
327 dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY,
328 NULL, &dict);
329 dbus_message_iter_append_basic(&dict,
330 DBUS_TYPE_STRING, &prop);
331 dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT,
332 "b", &sub);
333 dbus_message_iter_append_basic(&sub,
334 DBUS_TYPE_BOOLEAN, &registered);
335 dbus_message_iter_close_container(&dict, &sub);
336 dbus_message_iter_close_container(&arr, &dict);
337
338 prop = "ProtocolVersion";
339 dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY,
340 NULL, &dict);
341 dbus_message_iter_append_basic(&dict,
342 DBUS_TYPE_STRING, &prop);
343 dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT,
344 "i", &sub);
345 dbus_message_iter_append_basic(&sub,
346 DBUS_TYPE_INT32, &version);
347 dbus_message_iter_close_container(&dict, &sub);
348 dbus_message_iter_close_container(&arr, &dict);
349
350 dbus_message_iter_close_container(&iter, &arr);
351
352 dbus_connection_send(connection, reply, NULL);
353 dbus_message_unref(reply);
354}
355
356static DBusHandlerResult message_handler(DBusConnection *connection,
357 DBusMessage *message, void *data) {
358 const char *interface_name = dbus_message_get_interface(message);
359 const char *member_name = dbus_message_get_member(message);
360
361 // In order of the xml above
362 if (strcmp(interface_name, "org.freedesktop.DBus.Introspectable") == 0 &&
363 strcmp(member_name, "Introspect") == 0) {
364 // We don't have an introspect for KDE
365 respond_to_introspect(connection, message);
366 return DBUS_HANDLER_RESULT_HANDLED;
367 } else if (strcmp(interface_name, "org.freedesktop.DBus.Properties") == 0) {
368 if (strcmp(member_name, "Get") == 0) {
369 get_property(connection, message);
370 return DBUS_HANDLER_RESULT_HANDLED;
371 } else if (strcmp(member_name, "Set") == 0) {
372 set_property(connection, message);
373 return DBUS_HANDLER_RESULT_HANDLED;
374 } else if (strcmp(member_name, "GetAll") == 0) {
375 get_all(connection, message);
376 return DBUS_HANDLER_RESULT_HANDLED;
377 } else {
378 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
379 }
380 } else if (strcmp(interface_name, "org.freedesktop.StatusNotifierWatcher") == 0 ||
381 strcmp(interface_name, "org.kde.StatusNotifierWatcher") == 0) {
382 if (strcmp(member_name, "RegisterStatusNotifierItem") == 0) {
383 register_item(connection, message);
384 return DBUS_HANDLER_RESULT_HANDLED;
385 } else if (strcmp(member_name, "RegisterStatusNotifierHost") == 0) {
386 register_host(connection, message);
387 return DBUS_HANDLER_RESULT_HANDLED;
388 } else {
389 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
390 }
391 }
392 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
393}
394
395static DBusHandlerResult signal_handler(DBusConnection *connection,
396 DBusMessage *message, void *_data) {
397 if (dbus_message_is_signal(message, "org.freedesktop.DBus", "NameOwnerChanged")) {
398 // Only eat the message if it is name that we are watching
399 const char *name;
400 const char *old_owner;
401 const char *new_owner;
402 int index;
403 if (!dbus_message_get_args(message, NULL,
404 DBUS_TYPE_STRING, &name,
405 DBUS_TYPE_STRING, &old_owner,
406 DBUS_TYPE_STRING, &new_owner,
407 DBUS_TYPE_INVALID)) {
408 sway_log(L_ERROR, "Error getting LostName args");
409 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
410 }
411 if (strcmp(new_owner, "") != 0) {
412 // Name is not lost
413 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
414 }
415 if ((index = list_seq_find(items, (int (*)(const void *, const void *))strcmp, name)) != -1) {
416 sway_log(L_INFO, "Status Notifier Item lost %s", name);
417 free(items->items[index]);
418 list_del(items, index);
419 item_unregistered_signal(connection, name);
420
421 return DBUS_HANDLER_RESULT_HANDLED;
422 }
423 if ((index = list_seq_find(hosts, (int (*)(const void *, const void *))strcmp, name)) != -1) {
424 sway_log(L_INFO, "Status Notifier Host lost %s", name);
425 free(hosts->items[index]);
426 list_del(hosts, index);
427
428 return DBUS_HANDLER_RESULT_HANDLED;
429 }
430 }
431 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
432}
433
434static const DBusObjectPathVTable vtable = {
435 .message_function = message_handler,
436 .unregister_function = NULL,
437};
438
439int init_sni_watcher() {
440 DBusError error;
441 dbus_error_init(&error);
442 if (!conn) {
443 sway_log(L_ERROR, "Connection is null, cannot initiate StatusNotifierWatcher");
444 return -1;
445 }
446
447 items = create_list();
448 hosts = create_list();
449
450 int status = dbus_bus_request_name(conn, "org.freedesktop.StatusNotifierWatcher",
451 DBUS_NAME_FLAG_REPLACE_EXISTING,
452 &error);
453 if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
454 sway_log(L_DEBUG, "Got watcher name");
455 } else if (status == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) {
456 sway_log(L_INFO, "Could not get watcher name, it may start later");
457 }
458 if (dbus_error_is_set(&error)) {
459 sway_log(L_ERROR, "dbus err getting watcher name: %s\n", error.message);
460 return -1;
461 }
462
463 status = dbus_bus_request_name(conn, "org.kde.StatusNotifierWatcher",
464 DBUS_NAME_FLAG_REPLACE_EXISTING,
465 &error);
466 if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
467 sway_log(L_DEBUG, "Got kde watcher name");
468 } else if (status == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) {
469 sway_log(L_INFO, "Could not get kde watcher name, it may start later");
470 }
471 if (dbus_error_is_set(&error)) {
472 sway_log(L_ERROR, "dbus err getting kde watcher name: %s\n", error.message);
473 return -1;
474 }
475
476 dbus_connection_try_register_object_path(conn,
477 "/StatusNotifierWatcher",
478 &vtable, NULL, &error);
479 if (dbus_error_is_set(&error)) {
480 sway_log(L_ERROR, "dbus_err: %s\n", error.message);
481 return -1;
482 }
483
484 dbus_bus_add_match(conn,
485 "type='signal',\
486 sender='org.freedesktop.DBus',\
487 interface='org.freedesktop.DBus',\
488 member='NameOwnerChanged'",
489 &error);
490
491 if (dbus_error_is_set(&error)) {
492 sway_log(L_ERROR, "DBus error getting match args: %s", error.message);
493 }
494
495 dbus_connection_add_filter(conn, signal_handler, NULL, NULL);
496 return 0;
497}
diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c
deleted file mode 100644
index 91c3af06..00000000
--- a/swaybar/tray/tray.c
+++ /dev/null
@@ -1,398 +0,0 @@
1#define _XOPEN_SOURCE 700
2#include <unistd.h>
3#include <stdlib.h>
4#include <string.h>
5#include <sys/wait.h>
6#include <dbus/dbus.h>
7#include "swaybar/bar.h"
8#include "swaybar/tray/tray.h"
9#include "swaybar/tray/dbus.h"
10#include "swaybar/tray/sni.h"
11#include "swaybar/tray/sni_watcher.h"
12#include "swaybar/bar.h"
13#include "swaybar/config.h"
14#include "list.h"
15#include "log.h"
16
17struct tray *tray;
18
19static void register_host(char *name) {
20 DBusMessage *message;
21
22 message = dbus_message_new_method_call(
23 "org.freedesktop.StatusNotifierWatcher",
24 "/StatusNotifierWatcher",
25 "org.freedesktop.StatusNotifierWatcher",
26 "RegisterStatusNotifierHost");
27 if (!message) {
28 sway_log(L_ERROR, "Cannot allocate dbus method call");
29 return;
30 }
31
32 dbus_message_append_args(message,
33 DBUS_TYPE_STRING, &name,
34 DBUS_TYPE_INVALID);
35
36 dbus_connection_send(conn, message, NULL);
37
38 dbus_message_unref(message);
39}
40
41static void get_items_reply(DBusPendingCall *pending, void *_data) {
42 DBusMessage *reply = dbus_pending_call_steal_reply(pending);
43
44 if (!reply) {
45 sway_log(L_ERROR, "Got no items reply from sni watcher");
46 goto bail;
47 }
48
49 int message_type = dbus_message_get_type(reply);
50
51 if (message_type == DBUS_MESSAGE_TYPE_ERROR) {
52 char *msg;
53
54 dbus_message_get_args(reply, NULL,
55 DBUS_TYPE_STRING, &msg,
56 DBUS_TYPE_INVALID);
57
58 sway_log(L_ERROR, "Message is error: %s", msg);
59 goto bail;
60 }
61
62 DBusMessageIter iter;
63 DBusMessageIter variant;
64 DBusMessageIter array;
65
66 dbus_message_iter_init(reply, &iter);
67 if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
68 sway_log(L_ERROR, "Replyed with wrong type, not v(as)");
69 goto bail;
70 }
71 dbus_message_iter_recurse(&iter, &variant);
72 if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_ARRAY ||
73 dbus_message_iter_get_element_type(&variant) != DBUS_TYPE_STRING) {
74 sway_log(L_ERROR, "Replyed with wrong type, not v(as)");
75 goto bail;
76 }
77
78 // Clear list
79 list_foreach(tray->items, (void (*)(void *))sni_free);
80 list_free(tray->items);
81 tray->items = create_list();
82
83 // O(n) function, could be faster dynamically reading values
84 int len = dbus_message_iter_get_element_count(&variant);
85
86 dbus_message_iter_recurse(&variant, &array);
87 for (int i = 0; i < len; i++) {
88 const char *name;
89 dbus_message_iter_get_basic(&array, &name);
90
91 struct StatusNotifierItem *item = sni_create(name);
92
93 if (item) {
94 sway_log(L_DEBUG, "Item registered with host: %s", name);
95 list_add(tray->items, item);
96 dirty = true;
97 }
98 }
99
100bail:
101 dbus_message_unref(reply);
102 dbus_pending_call_unref(pending);
103 return;
104}
105static void get_items() {
106 DBusPendingCall *pending;
107 DBusMessage *message = dbus_message_new_method_call(
108 "org.freedesktop.StatusNotifierWatcher",
109 "/StatusNotifierWatcher",
110 "org.freedesktop.DBus.Properties",
111 "Get");
112
113 const char *iface = "org.freedesktop.StatusNotifierWatcher";
114 const char *prop = "RegisteredStatusNotifierItems";
115 dbus_message_append_args(message,
116 DBUS_TYPE_STRING, &iface,
117 DBUS_TYPE_STRING, &prop,
118 DBUS_TYPE_INVALID);
119
120 bool status =
121 dbus_connection_send_with_reply(conn, message, &pending, -1);
122 dbus_message_unref(message);
123
124 if (!(pending || status)) {
125 sway_log(L_ERROR, "Could not get items");
126 return;
127 }
128
129 dbus_pending_call_set_notify(pending, get_items_reply, NULL, NULL);
130}
131
132static DBusHandlerResult signal_handler(DBusConnection *connection,
133 DBusMessage *message, void *_data) {
134 if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher",
135 "StatusNotifierItemRegistered")) {
136 const char *name;
137 if (!dbus_message_get_args(message, NULL,
138 DBUS_TYPE_STRING, &name,
139 DBUS_TYPE_INVALID)) {
140 sway_log(L_ERROR, "Error getting StatusNotifierItemRegistered args");
141 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
142 }
143
144 if (list_seq_find(tray->items, sni_str_cmp, name) == -1) {
145 struct StatusNotifierItem *item = sni_create(name);
146
147 if (item) {
148 list_add(tray->items, item);
149 dirty = true;
150 }
151 }
152
153 return DBUS_HANDLER_RESULT_HANDLED;
154 } else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher",
155 "StatusNotifierItemUnregistered")) {
156 const char *name;
157 if (!dbus_message_get_args(message, NULL,
158 DBUS_TYPE_STRING, &name,
159 DBUS_TYPE_INVALID)) {
160 sway_log(L_ERROR, "Error getting StatusNotifierItemUnregistered args");
161 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
162 }
163
164 int index;
165 if ((index = list_seq_find(tray->items, sni_str_cmp, name)) != -1) {
166 sni_free(tray->items->items[index]);
167 list_del(tray->items, index);
168 dirty = true;
169 } else {
170 // If it's not in our list, then our list is incorrect.
171 // Fetch all items again
172 sway_log(L_INFO, "Host item list incorrect, refreshing");
173 get_items();
174 }
175
176 return DBUS_HANDLER_RESULT_HANDLED;
177 } else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierItem",
178 "NewIcon") || dbus_message_is_signal(message,
179 "org.kde.StatusNotifierItem", "NewIcon")) {
180 const char *name;
181 int index;
182 struct StatusNotifierItem *item;
183
184 name = dbus_message_get_sender(message);
185 if ((index = list_seq_find(tray->items, sni_uniq_cmp, name)) != -1) {
186 item = tray->items->items[index];
187 sway_log(L_INFO, "NewIcon signal from item %s", item->name);
188 get_icon(item);
189 }
190
191 return DBUS_HANDLER_RESULT_HANDLED;
192 }
193 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
194}
195
196static int init_host() {
197 tray = (struct tray *)malloc(sizeof(tray));
198
199 tray->items = create_list();
200
201 DBusError error;
202 dbus_error_init(&error);
203 char *name = NULL;
204 if (!conn) {
205 sway_log(L_ERROR, "Connection is null, cannot init SNI host");
206 goto err;
207 }
208 name = calloc(sizeof(char), 256);
209
210 if (!name) {
211 sway_log(L_ERROR, "Cannot allocate name");
212 goto err;
213 }
214
215 pid_t pid = getpid();
216 if (snprintf(name, 256, "org.freedesktop.StatusNotifierHost-%d", pid)
217 >= 256) {
218 sway_log(L_ERROR, "Cannot get host name because string is too short."
219 "This should not happen");
220 goto err;
221 }
222
223 // We want to be the sole owner of this name
224 if (dbus_bus_request_name(conn, name, DBUS_NAME_FLAG_DO_NOT_QUEUE,
225 &error) != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
226 sway_log(L_ERROR, "Cannot get host name and start the tray");
227 goto err;
228 }
229 if (dbus_error_is_set(&error)) {
230 sway_log(L_ERROR, "Dbus err getting host name: %s\n", error.message);
231 goto err;
232 }
233 sway_log(L_DEBUG, "Got host name");
234
235 register_host(name);
236
237 get_items();
238
239 // Perhaps use addmatch helper functions like wlc does?
240 dbus_bus_add_match(conn,
241 "type='signal',\
242 sender='org.freedesktop.StatusNotifierWatcher',\
243 member='StatusNotifierItemRegistered'",
244 &error);
245 if (dbus_error_is_set(&error)) {
246 sway_log(L_ERROR, "dbus_err: %s", error.message);
247 goto err;
248 }
249 dbus_bus_add_match(conn,
250 "type='signal',\
251 sender='org.freedesktop.StatusNotifierWatcher',\
252 member='StatusNotifierItemUnregistered'",
253 &error);
254 if (dbus_error_is_set(&error)) {
255 sway_log(L_ERROR, "dbus_err: %s", error.message);
256 return -1;
257 }
258
259 // SNI matches
260 dbus_bus_add_match(conn,
261 "type='signal',\
262 interface='org.freedesktop.StatusNotifierItem',\
263 member='NewIcon'",
264 &error);
265 if (dbus_error_is_set(&error)) {
266 sway_log(L_ERROR, "dbus_err %s", error.message);
267 goto err;
268 }
269 dbus_bus_add_match(conn,
270 "type='signal',\
271 interface='org.kde.StatusNotifierItem',\
272 member='NewIcon'",
273 &error);
274 if (dbus_error_is_set(&error)) {
275 sway_log(L_ERROR, "dbus_err %s", error.message);
276 goto err;
277 }
278
279 dbus_connection_add_filter(conn, signal_handler, NULL, NULL);
280
281 free(name);
282 return 0;
283
284err:
285 // TODO better handle errors
286 free(name);
287 return -1;
288}
289
290void tray_mouse_event(struct output *output, int x, int y,
291 uint32_t button, uint32_t state) {
292
293 struct window *window = output->window;
294 uint32_t tray_padding = swaybar.config->tray_padding;
295 int tray_width = window->width * window->scale;
296
297 for (int i = 0; i < output->items->length; ++i) {
298 struct sni_icon_ref *item =
299 output->items->items[i];
300 int icon_width = cairo_image_surface_get_width(item->icon);
301
302 tray_width -= tray_padding;
303 if (x <= tray_width && x >= tray_width - icon_width) {
304 if (button == swaybar.config->activate_button) {
305 sni_activate(item->ref, x, y);
306 } else if (button == swaybar.config->context_button) {
307 sni_context_menu(item->ref, x, y);
308 } else if (button == swaybar.config->secondary_button) {
309 sni_secondary(item->ref, x, y);
310 }
311 break;
312 }
313 tray_width -= icon_width;
314 }
315}
316
317uint32_t tray_render(struct output *output, struct config *config) {
318 struct window *window = output->window;
319 cairo_t *cairo = window->cairo;
320
321 // Tray icons
322 uint32_t tray_padding = config->tray_padding;
323 uint32_t tray_width = window->width * window->scale;
324 const int item_size = (window->height * window->scale) - (2 * tray_padding);
325
326 if (item_size < 0) {
327 // Can't render items if the padding is too large
328 return tray_width;
329 }
330
331 if (config->tray_output && strcmp(config->tray_output, output->name) != 0) {
332 return tray_width;
333 }
334
335 for (int i = 0; i < tray->items->length; ++i) {
336 struct StatusNotifierItem *item =
337 tray->items->items[i];
338 if (!item->image) {
339 continue;
340 }
341
342 struct sni_icon_ref *render_item = NULL;
343 int j;
344 for (j = i; j < output->items->length; ++j) {
345 struct sni_icon_ref *ref =
346 output->items->items[j];
347 if (ref->ref == item) {
348 render_item = ref;
349 break;
350 } else {
351 sni_icon_ref_free(ref);
352 list_del(output->items, j);
353 }
354 }
355
356 if (!render_item) {
357 render_item = sni_icon_ref_create(item, item_size);
358 list_add(output->items, render_item);
359 } else if (item->dirty) {
360 // item needs re-render
361 sni_icon_ref_free(render_item);
362 output->items->items[j] = render_item =
363 sni_icon_ref_create(item, item_size);
364 }
365
366 tray_width -= tray_padding;
367 tray_width -= item_size;
368
369 cairo_operator_t op = cairo_get_operator(cairo);
370 cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
371 cairo_set_source_surface(cairo, render_item->icon, tray_width, tray_padding);
372 cairo_rectangle(cairo, tray_width, tray_padding, item_size, item_size);
373 cairo_fill(cairo);
374 cairo_set_operator(cairo, op);
375
376 item->dirty = false;
377 }
378
379
380 if (tray_width != window->width * window->scale) {
381 tray_width -= tray_padding;
382 }
383
384 return tray_width;
385}
386
387void init_tray(struct bar *bar) {
388 if (!bar->config->tray_output || strcmp(bar->config->tray_output, "none") != 0) {
389 /* Connect to the D-Bus */
390 dbus_init();
391
392 /* Start the SNI watcher */
393 init_sni_watcher();
394
395 /* Start the SNI host */
396 init_host();
397 }
398}