aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Drew DeVault <sir@cmpwn.com>2018-03-28 23:04:20 -0400
committerLibravatar Drew DeVault <sir@cmpwn.com>2018-03-29 22:11:08 -0400
commitcab1352801b62d1b8a12ca1c995cb24445ce4bc9 (patch)
treebc67373916c06d48700c4f69b8c2470a2f86887f
parentAllow sway IPC clients to fall back to i3 socket (diff)
downloadsway-cab1352801b62d1b8a12ca1c995cb24445ce4bc9.tar.gz
sway-cab1352801b62d1b8a12ca1c995cb24445ce4bc9.tar.zst
sway-cab1352801b62d1b8a12ca1c995cb24445ce4bc9.zip
Start port of swaybar to layer shell
This starts up the event loop and wayland display and shims out the basic top level rendering concepts. Also includes some changes to incorporate pango into the 1.x codebase properly.
-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}