summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/ipc-client.h2
-rw-r--r--swaybar/CMakeLists.txt6
-rw-r--r--swaybar/bar.c185
-rw-r--r--swaybar/bar.h55
-rw-r--r--swaybar/config.c92
-rw-r--r--swaybar/config.h69
-rw-r--r--swaybar/ipc.c258
-rw-r--r--swaybar/ipc.h17
-rw-r--r--swaybar/main.c1224
-rw-r--r--swaybar/render.c328
-rw-r--r--swaybar/render.h17
-rw-r--r--swaybar/status_line.c442
-rw-r--r--swaybar/status_line.h50
13 files changed, 1531 insertions, 1214 deletions
diff --git a/include/ipc-client.h b/include/ipc-client.h
index 030c80b6..c9f5b344 100644
--- a/include/ipc-client.h
+++ b/include/ipc-client.h
@@ -1,6 +1,8 @@
1#ifndef _SWAY_IPC_CLIENT_H 1#ifndef _SWAY_IPC_CLIENT_H
2#define _SWAY_IPC_CLIENT_H 2#define _SWAY_IPC_CLIENT_H
3 3
4#include <stdint.h>
5
4#include "ipc.h" 6#include "ipc.h"
5 7
6/** 8/**
diff --git a/swaybar/CMakeLists.txt b/swaybar/CMakeLists.txt
index a03ffcf4..60975f40 100644
--- a/swaybar/CMakeLists.txt
+++ b/swaybar/CMakeLists.txt
@@ -8,6 +8,11 @@ include_directories(
8 8
9add_executable(swaybar 9add_executable(swaybar
10 main.c 10 main.c
11 config.c
12 render.c
13 bar.c
14 status_line.c
15 ipc.c
11) 16)
12 17
13target_link_libraries(swaybar 18target_link_libraries(swaybar
@@ -18,7 +23,6 @@ target_link_libraries(swaybar
18 ${CAIRO_LIBRARIES} 23 ${CAIRO_LIBRARIES}
19 ${PANGO_LIBRARIES} 24 ${PANGO_LIBRARIES}
20 ${JSONC_LIBRARIES} 25 ${JSONC_LIBRARIES}
21 m
22) 26)
23 27
24install( 28install(
diff --git a/swaybar/bar.c b/swaybar/bar.c
new file mode 100644
index 00000000..fc01863b
--- /dev/null
+++ b/swaybar/bar.c
@@ -0,0 +1,185 @@
1#include <stdlib.h>
2#include <unistd.h>
3#include <fcntl.h>
4#include <errno.h>
5#include <sys/types.h>
6#include <sys/wait.h>
7
8#include "ipc-client.h"
9#include "list.h"
10#include "log.h"
11#include "ipc.h"
12#include "render.h"
13#include "config.h"
14#include "status_line.h"
15#include "bar.h"
16
17static void bar_init(struct bar *bar) {
18 bar->config = init_config();
19 bar->status = init_status_line();
20 bar->output = malloc(sizeof(struct output));
21 bar->output->window = NULL;
22 bar->output->registry = NULL;
23 bar->output->workspaces = create_list();
24 bar->output->name = NULL;
25}
26
27static void spawn_status_cmd_proc(struct bar *bar) {
28 if (bar->config->status_command) {
29 int pipefd[2];
30 pipe(pipefd);
31 bar->status_command_pid = fork();
32 if (bar->status_command_pid == 0) {
33 close(pipefd[0]);
34 dup2(pipefd[1], STDOUT_FILENO);
35 close(pipefd[1]);
36 char *const cmd[] = {
37 "sh",
38 "-c",
39 bar->config->status_command,
40 NULL,
41 };
42 execvp(cmd[0], cmd);
43 return;
44 }
45
46 close(pipefd[1]);
47 bar->status_read_fd = pipefd[0];
48 fcntl(bar->status_read_fd, F_SETFL, O_NONBLOCK);
49 }
50}
51
52
53void bar_setup(struct bar *bar, const char *socket_path, const char *bar_id, int desired_output) {
54 /* initialize bar with default values */
55 bar_init(bar);
56
57 bar->output->registry = registry_poll();
58
59 if (!bar->output->registry->desktop_shell) {
60 sway_abort("swaybar requires the compositor to support the desktop-shell extension.");
61 }
62
63 /* connect to sway ipc */
64 bar->ipc_socketfd = ipc_open_socket(socket_path);
65 bar->ipc_event_socketfd = ipc_open_socket(socket_path);
66
67 ipc_bar_init(bar, desired_output, bar_id);
68
69 struct output_state *output = bar->output->registry->outputs->items[desired_output];
70
71 bar->output->window = window_setup(bar->output->registry, output->width, 30, false);
72 if (!bar->output->window) {
73 sway_abort("Failed to create window.");
74 }
75 desktop_shell_set_panel(bar->output->registry->desktop_shell, output->output, bar->output->window->surface);
76 desktop_shell_set_panel_position(bar->output->registry->desktop_shell, bar->config->position);
77
78 /* set font */
79 bar->output->window->font = bar->config->font;
80
81 /* set window height */
82 set_window_height(bar->output->window, bar->config->height);
83
84 /* spawn status command */
85 spawn_status_cmd_proc(bar);
86}
87
88void bar_run(struct bar *bar) {
89 fd_set readfds;
90 int activity;
91 bool dirty = true;
92
93 while (1) {
94 if (dirty) {
95 struct output *output = bar->output;
96 if (window_prerender(output->window) && output->window->cairo) {
97 render(output, bar->config, bar->status);
98 window_render(output->window);
99 if (wl_display_dispatch(output->registry->display) == -1) {
100 break;
101 }
102 }
103 }
104
105 dirty = false;
106 FD_ZERO(&readfds);
107 FD_SET(bar->ipc_event_socketfd, &readfds);
108 FD_SET(bar->status_read_fd, &readfds);
109
110 activity = select(FD_SETSIZE, &readfds, NULL, NULL, NULL);
111 if (activity < 0) {
112 sway_log(L_ERROR, "polling failed: %d", errno);
113 }
114
115 if (FD_ISSET(bar->ipc_event_socketfd, &readfds)) {
116 sway_log(L_DEBUG, "Got IPC event.");
117 dirty = handle_ipc_event(bar);
118 }
119
120 if (bar->config->status_command && FD_ISSET(bar->status_read_fd, &readfds)) {
121 sway_log(L_DEBUG, "Got update from status command.");
122 dirty = handle_status_line(bar);
123 }
124 }
125}
126
127void free_workspaces(list_t *workspaces) {
128 int i;
129 for (i = 0; i < workspaces->length; ++i) {
130 struct workspace *ws = workspaces->items[i];
131 free(ws->name);
132 free(ws);
133 }
134 list_free(workspaces);
135}
136
137static void free_output(struct output *output) {
138 window_teardown(output->window);
139 if (output->registry) {
140 registry_teardown(output->registry);
141 }
142
143 free(output->name);
144
145 if (output->workspaces) {
146 free_workspaces(output->workspaces);
147 }
148
149 free(output);
150}
151
152static void terminate_status_command(pid_t pid) {
153 if (pid) {
154 // terminate status_command process
155 int ret = kill(pid, SIGTERM);
156 if (ret != 0) {
157 sway_log(L_ERROR, "Unable to terminate status_command [pid: %d]", pid);
158 } else {
159 int status;
160 waitpid(pid, &status, 0);
161 }
162 }
163}
164
165void bar_teardown(struct bar *bar) {
166 free_config(bar->config);
167 free_output(bar->output);
168 free_status_line(bar->status);
169
170 /* close sockets/pipes */
171 if (bar->status_read_fd) {
172 close(bar->status_read_fd);
173 }
174
175 if (bar->ipc_socketfd) {
176 close(bar->ipc_socketfd);
177 }
178
179 if (bar->ipc_event_socketfd) {
180 close(bar->ipc_event_socketfd);
181 }
182
183 /* terminate status command process */
184 terminate_status_command(bar->status_command_pid);
185}
diff --git a/swaybar/bar.h b/swaybar/bar.h
new file mode 100644
index 00000000..89496da6
--- /dev/null
+++ b/swaybar/bar.h
@@ -0,0 +1,55 @@
1#ifndef _SWAYBAR_BAR_H
2#define _SWAYBAR_BAR_H
3
4#include "client/registry.h"
5#include "client/window.h"
6#include "list.h"
7
8struct bar {
9 struct config *config;
10 struct status_line *status;
11 struct output *output;
12 /* list_t *outputs; */
13
14 int ipc_event_socketfd;
15 int ipc_socketfd;
16 int status_read_fd;
17 pid_t status_command_pid;
18};
19
20struct output {
21 struct window *window;
22 struct registry *registry;
23 list_t *workspaces;
24 char *name;
25};
26
27struct workspace {
28 int num;
29 char *name;
30 bool focused;
31 bool visible;
32 bool urgent;
33};
34
35/**
36 * Setup bar.
37 */
38void bar_setup(struct bar *bar, const char *socket_path, const char *bar_id, int desired_output);
39
40/**
41 * Bar mainloop.
42 */
43void bar_run(struct bar *bar);
44
45/**
46 * free workspace list.
47 */
48void free_workspaces(list_t *workspaces);
49
50/**
51 * Teardown bar.
52 */
53void bar_teardown(struct bar *bar);
54
55#endif /* _SWAYBAR_BAR_H */
diff --git a/swaybar/config.c b/swaybar/config.c
new file mode 100644
index 00000000..28b609e6
--- /dev/null
+++ b/swaybar/config.c
@@ -0,0 +1,92 @@
1#include <stdlib.h>
2#include <string.h>
3
4#include "wayland-desktop-shell-client-protocol.h"
5#include "log.h"
6#include "config.h"
7
8uint32_t parse_color(const char *color) {
9 if (color[0] != '#') {
10 sway_log(L_DEBUG, "Invalid color %s, defaulting to color 0xFFFFFFFF", color);
11 return 0xFFFFFFFF;
12 }
13 char *end;
14 uint32_t res = (uint32_t)strtol(color + 1, &end, 16);
15 if (strlen(color) == 7) {
16 res = (res << 8) | 0xFF;
17 }
18 return res;
19}
20
21uint32_t parse_position(const char *position) {
22 if (strcmp("top", position) == 0) {
23 return DESKTOP_SHELL_PANEL_POSITION_TOP;
24 } else if (strcmp("bottom", position) == 0) {
25 return DESKTOP_SHELL_PANEL_POSITION_BOTTOM;
26 } else if (strcmp("left", position) == 0) {
27 return DESKTOP_SHELL_PANEL_POSITION_LEFT;
28 } else if (strcmp("right", position) == 0) {
29 return DESKTOP_SHELL_PANEL_POSITION_RIGHT;
30 } else {
31 return DESKTOP_SHELL_PANEL_POSITION_BOTTOM;
32 }
33}
34
35char *parse_font(const char *font) {
36 char *new_font = NULL;
37 if (strncmp("pango:", font, 6) == 0) {
38 new_font = strdup(font + 6);
39 }
40
41 return new_font;
42}
43
44struct config *init_config() {
45 struct config *config = calloc(1, sizeof(struct config));
46 config->status_command = NULL;
47 config->position = DESKTOP_SHELL_PANEL_POSITION_BOTTOM;
48 config->font = strdup("monospace 10");
49 config->mode = NULL;
50 config->sep_symbol = NULL;
51 config->strip_workspace_numbers = false;
52 config->binding_mode_indicator = true;
53 config->workspace_buttons = true;
54
55 /* height */
56 config->height = 0;
57
58 /* colors */
59 config->colors.background = 0x000000FF;
60 config->colors.statusline = 0xFFFFFFFF;
61 config->colors.separator = 0x666666FF;
62
63 config->colors.focused_workspace.border = 0x4C7899FF;
64 config->colors.focused_workspace.background = 0x285577FF;
65 config->colors.focused_workspace.text = 0xFFFFFFFF;
66
67 config->colors.active_workspace.border = 0x333333FF;
68 config->colors.active_workspace.background = 0x5F676AFF;
69 config->colors.active_workspace.text = 0xFFFFFFFF;
70
71 config->colors.inactive_workspace.border = 0x333333FF;
72 config->colors.inactive_workspace.background = 0x222222FF;
73 config->colors.inactive_workspace.text = 0x888888FF;
74
75 config->colors.urgent_workspace.border = 0x2F343AFF;
76 config->colors.urgent_workspace.background = 0x900000FF;
77 config->colors.urgent_workspace.text = 0xFFFFFFFF;
78
79 config->colors.binding_mode.border = 0x2F343AFF;
80 config->colors.binding_mode.background = 0x900000FF;
81 config->colors.binding_mode.text = 0xFFFFFFFF;
82
83 return config;
84}
85
86void free_config(struct config *config) {
87 free(config->status_command);
88 free(config->font);
89 free(config->mode);
90 free(config->sep_symbol);
91 free(config);
92}
diff --git a/swaybar/config.h b/swaybar/config.h
new file mode 100644
index 00000000..508b9c42
--- /dev/null
+++ b/swaybar/config.h
@@ -0,0 +1,69 @@
1#ifndef _SWAYBAR_CONFIG_H
2#define _SWAYBAR_CONFIG_H
3
4#include <stdint.h>
5#include <stdbool.h>
6
7/**
8 * Colors for a box with background, border and text colors.
9 */
10struct box_colors {
11 uint32_t border;
12 uint32_t background;
13 uint32_t text;
14};
15
16/**
17 * Swaybar config.
18 */
19struct config {
20 char *status_command;
21 uint32_t position;
22 char *font;
23 char *sep_symbol;
24 char *mode;
25 bool strip_workspace_numbers;
26 bool binding_mode_indicator;
27 bool workspace_buttons;
28
29 int height;
30
31 struct {
32 uint32_t background;
33 uint32_t statusline;
34 uint32_t separator;
35
36 struct box_colors focused_workspace;
37 struct box_colors active_workspace;
38 struct box_colors inactive_workspace;
39 struct box_colors urgent_workspace;
40 struct box_colors binding_mode;
41 } colors;
42};
43
44/**
45 * Parse colors defined as hex string to uint32_t.
46 */
47uint32_t parse_color(const char *color);
48
49/**
50 * Parse position top|bottom|left|right.
51 */
52uint32_t parse_position(const char *position);
53
54/**
55 * Parse font.
56 */
57char *parse_font(const char *font);
58
59/**
60 * Initialize default sway config.
61 */
62struct config *init_config();
63
64/**
65 * Free config struct.
66 */
67void free_config(struct config *config);
68
69#endif /* _SWAYBAR_CONFIG_H */
diff --git a/swaybar/ipc.c b/swaybar/ipc.c
new file mode 100644
index 00000000..547041ce
--- /dev/null
+++ b/swaybar/ipc.c
@@ -0,0 +1,258 @@
1#include <string.h>
2#include <json-c/json.h>
3
4#include "ipc-client.h"
5#include "list.h"
6#include "log.h"
7#include "config.h"
8#include "ipc.h"
9
10static void ipc_parse_config(struct config *config, const char *payload) {
11 json_object *bar_config = json_tokener_parse(payload);
12 json_object *tray_output, *mode, *hidden_bar, *position, *status_command;
13 json_object *font, *bar_height, *workspace_buttons, *strip_workspace_numbers;
14 json_object *binding_mode_indicator, *verbose, *colors, *sep_symbol;
15 json_object_object_get_ex(bar_config, "tray_output", &tray_output);
16 json_object_object_get_ex(bar_config, "mode", &mode);
17 json_object_object_get_ex(bar_config, "hidden_bar", &hidden_bar);
18 json_object_object_get_ex(bar_config, "position", &position);
19 json_object_object_get_ex(bar_config, "status_command", &status_command);
20 json_object_object_get_ex(bar_config, "font", &font);
21 json_object_object_get_ex(bar_config, "bar_height", &bar_height);
22 json_object_object_get_ex(bar_config, "workspace_buttons", &workspace_buttons);
23 json_object_object_get_ex(bar_config, "strip_workspace_numbers", &strip_workspace_numbers);
24 json_object_object_get_ex(bar_config, "binding_mode_indicator", &binding_mode_indicator);
25 json_object_object_get_ex(bar_config, "verbose", &verbose);
26 json_object_object_get_ex(bar_config, "separator_symbol", &sep_symbol);
27 json_object_object_get_ex(bar_config, "colors", &colors);
28
29 if (status_command) {
30 free(config->status_command);
31 config->status_command = strdup(json_object_get_string(status_command));
32 }
33
34 if (position) {
35 config->position = parse_position(json_object_get_string(position));
36 }
37
38 if (font) {
39 free(config->font);
40 config->font = parse_font(json_object_get_string(font));
41 }
42
43 if (sep_symbol) {
44 free(config->sep_symbol);
45 config->sep_symbol = strdup(json_object_get_string(sep_symbol));
46 }
47
48 if (strip_workspace_numbers) {
49 config->strip_workspace_numbers = json_object_get_boolean(strip_workspace_numbers);
50 }
51
52 if (binding_mode_indicator) {
53 config->binding_mode_indicator = json_object_get_boolean(binding_mode_indicator);
54 }
55
56 if (workspace_buttons) {
57 config->workspace_buttons = json_object_get_boolean(workspace_buttons);
58 }
59
60 if (bar_height) {
61 config->height = json_object_get_int(bar_height);
62 }
63
64 if (colors) {
65 json_object *background, *statusline, *separator;
66 json_object *focused_workspace_border, *focused_workspace_bg, *focused_workspace_text;
67 json_object *inactive_workspace_border, *inactive_workspace_bg, *inactive_workspace_text;
68 json_object *active_workspace_border, *active_workspace_bg, *active_workspace_text;
69 json_object *urgent_workspace_border, *urgent_workspace_bg, *urgent_workspace_text;
70 json_object *binding_mode_border, *binding_mode_bg, *binding_mode_text;
71 json_object_object_get_ex(colors, "background", &background);
72 json_object_object_get_ex(colors, "statusline", &statusline);
73 json_object_object_get_ex(colors, "separator", &separator);
74 json_object_object_get_ex(colors, "focused_workspace_border", &focused_workspace_border);
75 json_object_object_get_ex(colors, "focused_workspace_bg", &focused_workspace_bg);
76 json_object_object_get_ex(colors, "focused_workspace_text", &focused_workspace_text);
77 json_object_object_get_ex(colors, "active_workspace_border", &active_workspace_border);
78 json_object_object_get_ex(colors, "active_workspace_bg", &active_workspace_bg);
79 json_object_object_get_ex(colors, "active_workspace_text", &active_workspace_text);
80 json_object_object_get_ex(colors, "inactive_workspace_border", &inactive_workspace_border);
81 json_object_object_get_ex(colors, "inactive_workspace_bg", &inactive_workspace_bg);
82 json_object_object_get_ex(colors, "inactive_workspace_text", &inactive_workspace_text);
83 json_object_object_get_ex(colors, "urgent_workspace_border", &urgent_workspace_border);
84 json_object_object_get_ex(colors, "urgent_workspace_bg", &urgent_workspace_bg);
85 json_object_object_get_ex(colors, "urgent_workspace_text", &urgent_workspace_text);
86 json_object_object_get_ex(colors, "binding_mode_border", &binding_mode_border);
87 json_object_object_get_ex(colors, "binding_mode_bg", &binding_mode_bg);
88 json_object_object_get_ex(colors, "binding_mode_text", &binding_mode_text);
89 if (background) {
90 config->colors.background = parse_color(json_object_get_string(background));
91 }
92
93 if (statusline) {
94 config->colors.statusline = parse_color(json_object_get_string(statusline));
95 }
96
97 if (separator) {
98 config->colors.separator = parse_color(json_object_get_string(separator));
99 }
100
101 if (focused_workspace_border) {
102 config->colors.focused_workspace.border = parse_color(json_object_get_string(focused_workspace_border));
103 }
104
105 if (focused_workspace_bg) {
106 config->colors.focused_workspace.background = parse_color(json_object_get_string(focused_workspace_bg));
107 }
108
109 if (focused_workspace_text) {
110 config->colors.focused_workspace.text = parse_color(json_object_get_string(focused_workspace_text));
111 }
112
113 if (active_workspace_border) {
114 config->colors.active_workspace.border = parse_color(json_object_get_string(active_workspace_border));
115 }
116
117 if (active_workspace_bg) {
118 config->colors.active_workspace.background = parse_color(json_object_get_string(active_workspace_bg));
119 }
120
121 if (active_workspace_text) {
122 config->colors.active_workspace.text = parse_color(json_object_get_string(active_workspace_text));
123 }
124
125 if (inactive_workspace_border) {
126 config->colors.inactive_workspace.border = parse_color(json_object_get_string(inactive_workspace_border));
127 }
128
129 if (inactive_workspace_bg) {
130 config->colors.inactive_workspace.background = parse_color(json_object_get_string(inactive_workspace_bg));
131 }
132
133 if (inactive_workspace_text) {
134 config->colors.inactive_workspace.text = parse_color(json_object_get_string(inactive_workspace_text));
135 }
136
137 if (binding_mode_border) {
138 config->colors.binding_mode.border = parse_color(json_object_get_string(binding_mode_border));
139 }
140
141 if (binding_mode_bg) {
142 config->colors.binding_mode.background = parse_color(json_object_get_string(binding_mode_bg));
143 }
144
145 if (binding_mode_text) {
146 config->colors.binding_mode.text = parse_color(json_object_get_string(binding_mode_text));
147 }
148 }
149
150 json_object_put(bar_config);
151}
152
153static void ipc_update_workspaces(struct bar *bar) {
154 if (bar->output->workspaces) {
155 free_workspaces(bar->output->workspaces);
156 }
157 bar->output->workspaces = create_list();
158
159 uint32_t len = 0;
160 char *res = ipc_single_command(bar->ipc_socketfd, IPC_GET_WORKSPACES, NULL, &len);
161 json_object *results = json_tokener_parse(res);
162 if (!results) {
163 free(res);
164 return;
165 }
166
167 int i;
168 int length = json_object_array_length(results);
169 json_object *ws_json;
170 json_object *num, *name, *visible, *focused, *out, *urgent;
171 for (i = 0; i < length; ++i) {
172 ws_json = json_object_array_get_idx(results, i);
173
174 json_object_object_get_ex(ws_json, "num", &num);
175 json_object_object_get_ex(ws_json, "name", &name);
176 json_object_object_get_ex(ws_json, "visible", &visible);
177 json_object_object_get_ex(ws_json, "focused", &focused);
178 json_object_object_get_ex(ws_json, "output", &out);
179 json_object_object_get_ex(ws_json, "urgent", &urgent);
180
181 if (strcmp(json_object_get_string(out), bar->output->name) == 0) {
182 struct workspace *ws = malloc(sizeof(struct workspace));
183 ws->num = json_object_get_int(num);
184 ws->name = strdup(json_object_get_string(name));
185 ws->visible = json_object_get_boolean(visible);
186 ws->focused = json_object_get_boolean(focused);
187 ws->urgent = json_object_get_boolean(urgent);
188 list_add(bar->output->workspaces, ws);
189 }
190 }
191
192 json_object_put(results);
193 free(res);
194}
195
196void ipc_bar_init(struct bar *bar, int outputi, const char *bar_id) {
197 uint32_t len = 0;
198 char *res = ipc_single_command(bar->ipc_socketfd, IPC_GET_OUTPUTS, NULL, &len);
199 json_object *outputs = json_tokener_parse(res);
200 json_object *info = json_object_array_get_idx(outputs, outputi);
201 json_object *name;
202 json_object_object_get_ex(info, "name", &name);
203 bar->output->name = strdup(json_object_get_string(name));
204 free(res);
205 json_object_put(outputs);
206
207 len = strlen(bar_id);
208 res = ipc_single_command(bar->ipc_socketfd, IPC_GET_BAR_CONFIG, bar_id, &len);
209
210 ipc_parse_config(bar->config, res);
211 free(res);
212
213 const char *subscribe_json = "[ \"workspace\", \"mode\" ]";
214 len = strlen(subscribe_json);
215 res = ipc_single_command(bar->ipc_event_socketfd, IPC_SUBSCRIBE, subscribe_json, &len);
216 free(res);
217
218 ipc_update_workspaces(bar);
219}
220
221bool handle_ipc_event(struct bar *bar) {
222 struct ipc_response *resp = ipc_recv_response(bar->ipc_event_socketfd);
223 switch (resp->type) {
224 case IPC_EVENT_WORKSPACE:
225 ipc_update_workspaces(bar);
226 break;
227 case IPC_EVENT_MODE: {
228 json_object *result = json_tokener_parse(resp->payload);
229 if (!result) {
230 free_ipc_response(resp);
231 sway_log(L_ERROR, "failed to parse payload as json");
232 return false;
233 }
234 json_object *json_change;
235 if (json_object_object_get_ex(result, "change", &json_change)) {
236 const char *change = json_object_get_string(json_change);
237
238 free(bar->config->mode);
239 if (strcmp(change, "default") == 0) {
240 bar->config->mode = NULL;
241 } else {
242 bar->config->mode = strdup(change);
243 }
244 } else {
245 sway_log(L_ERROR, "failed to parse response");
246 }
247
248 json_object_put(result);
249 break;
250 }
251 default:
252 free_ipc_response(resp);
253 return false;
254 }
255
256 free_ipc_response(resp);
257 return true;
258}
diff --git a/swaybar/ipc.h b/swaybar/ipc.h
new file mode 100644
index 00000000..c3f661f8
--- /dev/null
+++ b/swaybar/ipc.h
@@ -0,0 +1,17 @@
1#ifndef _SWAYBAR_IPC_H
2#define _SWAYBAR_IPC_H
3
4#include "bar.h"
5
6/**
7 * Initialize ipc connection to sway and get sway state, outputs, bar_config.
8 */
9void ipc_bar_init(struct bar *bar, int outputi, const char *bar_id);
10
11/**
12 * Handle ipc event from sway.
13 */
14bool handle_ipc_event(struct bar *bar);
15
16#endif /* _SWAYBAR_IPC_H */
17
diff --git a/swaybar/main.c b/swaybar/main.c
index a3a3b56a..fc5acdae 100644
--- a/swaybar/main.c
+++ b/swaybar/main.c
@@ -1,1191 +1,26 @@
1#include <fcntl.h>
2#include <stdio.h> 1#include <stdio.h>
3#include <stdlib.h> 2#include <stdlib.h>
4#include <string.h> 3#include <string.h>
5#include <stdint.h>
6#include <stdbool.h> 4#include <stdbool.h>
7#include <unistd.h>
8#include <sys/select.h>
9#include <sys/wait.h>
10#include <errno.h>
11#include <json-c/json.h>
12#include <sys/un.h>
13#include <sys/socket.h>
14#include <sys/ioctl.h>
15#include <getopt.h> 5#include <getopt.h>
16#include "ipc-client.h" 6#include "ipc-client.h"
17#include "client/registry.h"
18#include "client/window.h"
19#include "client/pango.h"
20#include "stringop.h"
21#include "log.h" 7#include "log.h"
8#include "bar.h"
22 9
23struct box_colors { 10/* global bar state */
24 uint32_t border; 11struct bar swaybar;
25 uint32_t background;
26 uint32_t text;
27};
28
29struct colors {
30 uint32_t background;
31 uint32_t statusline;
32 uint32_t separator;
33
34 struct box_colors focused_workspace;
35 struct box_colors active_workspace;
36 struct box_colors inactive_workspace;
37 struct box_colors urgent_workspace;
38 struct box_colors binding_mode;
39};
40
41struct workspace {
42 int num;
43 char *name;
44 bool focused;
45 bool visible;
46 bool urgent;
47};
48
49struct status_block {
50 char *full_text, *short_text, *align;
51 bool urgent;
52 uint32_t color;
53 int min_width;
54 char *name, *instance;
55 bool separator;
56 int separator_block_width;
57 // Airblader features
58 uint32_t background;
59 uint32_t border;
60 int border_top;
61 int border_bottom;
62 int border_left;
63 int border_right;
64};
65
66list_t *status_line = NULL;
67
68list_t *workspaces = NULL;
69int ipc_socketfd, ipc_event_socketfd;
70pid_t pid;
71int status_read_fd;
72char line[1024];
73char line_rest[1024];
74char *output, *status_command;
75struct registry *registry;
76struct window *window;
77bool dirty = true;
78char *separator_symbol = NULL;
79char *mode = NULL;
80bool binding_mode_indicator = true;
81bool strip_workspace_numbers = false;
82bool workspace_buttons = true;
83typedef enum {UNDEF, TEXT, I3BAR} command_protocol;
84command_protocol protocol = UNDEF;
85
86struct colors colors = {
87 .background = 0x000000FF,
88 .statusline = 0xFFFFFFFF,
89 .separator = 0x666666FF,
90
91 .focused_workspace = {
92 .border = 0x4C7899FF,
93 .background = 0x285577FF,
94 .text = 0xFFFFFFFF
95 },
96 .active_workspace = {
97 .border = 0x333333FF,
98 .background = 0x5F676AFF,
99 .text = 0xFFFFFFFF
100 },
101 .inactive_workspace = {
102 .border = 0x333333FF,
103 .background = 0x222222FF,
104 .text = 0x888888FF
105 },
106 .urgent_workspace = {
107 .border = 0x2F343AFF,
108 .background = 0x900000FF,
109 .text = 0xFFFFFFFF
110 },
111 .binding_mode = {
112 .border = 0x2F343AFF,
113 .background = 0x900000FF,
114 .text = 0xFFFFFFFF
115 },
116};
117
118#define I3JSON_MAXDEPTH 4
119#define I3JSON_UNKNOWN 0
120#define I3JSON_ARRAY 1
121#define I3JSON_STRING 2
122struct {
123 int bufsize;
124 char *buffer;
125 char *line_start;
126 char *parserpos;
127 bool escape;
128 int depth;
129 int state[I3JSON_MAXDEPTH+1];
130} i3json_state = { 0, NULL, NULL, NULL, false, 0, { I3JSON_UNKNOWN } };
131
132
133void swaybar_teardown() {
134 window_teardown(window);
135 if (registry) {
136 registry_teardown(registry);
137 }
138
139 if (status_read_fd) {
140 close(status_read_fd);
141 }
142
143 if (pid) {
144 // terminate status_command process
145 int ret = kill(pid, SIGTERM);
146 if (ret != 0) {
147 sway_log(L_ERROR, "Unable to terminate status_command [pid: %d]", pid);
148 } else {
149 int status;
150 waitpid(pid, &status, 0);
151 }
152 }
153
154 if (status_read_fd) {
155 close(status_read_fd);
156 }
157
158 if (ipc_socketfd) {
159 close(ipc_socketfd);
160 }
161
162 if (ipc_event_socketfd) {
163 close(ipc_event_socketfd);
164 }
165}
166 12
167void sway_terminate(void) { 13void sway_terminate(void) {
168 swaybar_teardown(); 14 bar_teardown(&swaybar);
169 exit(EXIT_FAILURE); 15 exit(EXIT_FAILURE);
170} 16}
171 17
172void sig_handler(int signal) { 18void sig_handler(int signal) {
173 swaybar_teardown(); 19 bar_teardown(&swaybar);
174 exit(0); 20 exit(0);
175} 21}
176 22
177void cairo_set_source_u32(cairo_t *cairo, uint32_t color) {
178 cairo_set_source_rgba(cairo,
179 ((color & 0xFF000000) >> 24) / 256.0,
180 ((color & 0xFF0000) >> 16) / 256.0,
181 ((color & 0xFF00) >> 8) / 256.0,
182 (color & 0xFF) / 256.0);
183}
184
185void free_workspace(void *item) {
186 if (!item) {
187 return;
188 }
189 struct workspace *ws = (struct workspace *)item;
190 if (ws->name) {
191 free(ws->name);
192 }
193 free(ws);
194}
195
196void ipc_update_workspaces() {
197 if (workspaces) {
198 list_foreach(workspaces, free_workspace);
199 list_free(workspaces);
200 }
201 workspaces = create_list();
202
203 uint32_t len = 0;
204 char *res = ipc_single_command(ipc_socketfd, IPC_GET_WORKSPACES, NULL, &len);
205 json_object *results = json_tokener_parse(res);
206 if (!results) {
207 free(res);
208 return;
209 }
210
211 int i;
212 int length = json_object_array_length(results);
213 json_object *ws_json;
214 json_object *num, *name, *visible, *focused, *out, *urgent;
215 for (i = 0; i < length; ++i) {
216 ws_json = json_object_array_get_idx(results, i);
217
218 json_object_object_get_ex(ws_json, "num", &num);
219 json_object_object_get_ex(ws_json, "name", &name);
220 json_object_object_get_ex(ws_json, "visible", &visible);
221 json_object_object_get_ex(ws_json, "focused", &focused);
222 json_object_object_get_ex(ws_json, "output", &out);
223 json_object_object_get_ex(ws_json, "urgent", &urgent);
224
225 if (strcmp(json_object_get_string(out), output) == 0) {
226 struct workspace *ws = malloc(sizeof(struct workspace));
227 ws->num = json_object_get_int(num);
228 ws->name = strdup(json_object_get_string(name));
229 ws->visible = json_object_get_boolean(visible);
230 ws->focused = json_object_get_boolean(focused);
231 ws->urgent = json_object_get_boolean(urgent);
232 list_add(workspaces, ws);
233 }
234 }
235
236 json_object_put(results);
237 free(res);
238}
239
240uint32_t parse_color(const char *color) {
241 if (color[0] != '#') {
242 sway_abort("Invalid color %s", color);
243 }
244 char *end;
245 uint32_t res = (uint32_t)strtol(color + 1, &end, 16);
246 if (strlen(color) == 7) {
247 res = (res << 8) | 0xFF;
248 }
249 return res;
250}
251
252uint32_t parse_position(const char *position) {
253 if (strcmp("top", position) == 0) {
254 return DESKTOP_SHELL_PANEL_POSITION_TOP;
255 } else if (strcmp("bottom", position) == 0) {
256 return DESKTOP_SHELL_PANEL_POSITION_BOTTOM;
257 } else if (strcmp("left", position) == 0) {
258 return DESKTOP_SHELL_PANEL_POSITION_LEFT;
259 } else if (strcmp("right", position) == 0) {
260 return DESKTOP_SHELL_PANEL_POSITION_RIGHT;
261 } else {
262 return DESKTOP_SHELL_PANEL_POSITION_BOTTOM;
263 }
264}
265
266char *parse_font(const char *font) {
267 char *new_font = NULL;
268 if (strncmp("pango:", font, 6) == 0) {
269 new_font = strdup(font + 6);
270 }
271
272 return new_font;
273}
274
275static int margin = 3;
276static const int ws_hor_padding = 5;
277static double ws_ver_padding = 1.5;
278static const int ws_spacing = 1;
279
280void bar_ipc_init(int outputi, const char *bar_id) {
281 uint32_t len = 0;
282 char *res = ipc_single_command(ipc_socketfd, IPC_GET_OUTPUTS, NULL, &len);
283 json_object *outputs = json_tokener_parse(res);
284 json_object *info = json_object_array_get_idx(outputs, outputi);
285 json_object *name;
286 json_object_object_get_ex(info, "name", &name);
287 output = strdup(json_object_get_string(name));
288 free(res);
289 json_object_put(outputs);
290
291 len = strlen(bar_id);
292 res = ipc_single_command(ipc_socketfd, IPC_GET_BAR_CONFIG, bar_id, &len);
293
294 json_object *bar_config = json_tokener_parse(res);
295 json_object *tray_output, *mode, *hidden_state, *position, *_status_command;
296 json_object *font, *bar_height, *_workspace_buttons, *_strip_workspace_numbers;
297 json_object *_binding_mode_indicator, *verbose, *_colors, *sep_symbol;
298 json_object_object_get_ex(bar_config, "tray_output", &tray_output);
299 json_object_object_get_ex(bar_config, "mode", &mode);
300 json_object_object_get_ex(bar_config, "hidden_state", &hidden_state);
301 json_object_object_get_ex(bar_config, "position", &position);
302 json_object_object_get_ex(bar_config, "status_command", &_status_command);
303 json_object_object_get_ex(bar_config, "font", &font);
304 json_object_object_get_ex(bar_config, "bar_height", &bar_height);
305 json_object_object_get_ex(bar_config, "workspace_buttons", &_workspace_buttons);
306 json_object_object_get_ex(bar_config, "strip_workspace_numbers", &_strip_workspace_numbers);
307 json_object_object_get_ex(bar_config, "binding_mode_indicator", &_binding_mode_indicator);
308 json_object_object_get_ex(bar_config, "verbose", &verbose);
309 json_object_object_get_ex(bar_config, "separator_symbol", &sep_symbol);
310 json_object_object_get_ex(bar_config, "colors", &_colors);
311
312 // TODO: More of these options
313 // TODO: Refactor swaybar into several files, create a bar config struct (shared with compositor?)
314 if (_status_command) {
315 status_command = strdup(json_object_get_string(_status_command));
316 }
317
318 if (position) {
319 desktop_shell_set_panel_position(registry->desktop_shell, parse_position(json_object_get_string(position)));
320 }
321
322 if (font) {
323 window->font = parse_font(json_object_get_string(font));
324 }
325
326 if (sep_symbol) {
327 separator_symbol = strdup(json_object_get_string(sep_symbol));
328 }
329
330 if (_strip_workspace_numbers) {
331 strip_workspace_numbers = json_object_get_boolean(_strip_workspace_numbers);
332 }
333
334 if (_binding_mode_indicator) {
335 binding_mode_indicator = json_object_get_boolean(_binding_mode_indicator);
336 }
337
338 if (_workspace_buttons) {
339 workspace_buttons = json_object_get_boolean(_workspace_buttons);
340 }
341
342 if (bar_height) {
343 int width, height;
344 get_text_size(window, &width, &height, "Test string for measuring purposes");
345 int bar_height_value = json_object_get_int(bar_height);
346 if (bar_height_value > 0) {
347 margin = (bar_height_value - height) / 2;
348 ws_ver_padding = margin - 1.5;
349 }
350 window->height = height + margin * 2;
351 }
352
353 if (_colors) {
354 json_object *background, *statusline, *separator;
355 json_object *focused_workspace_border, *focused_workspace_bg, *focused_workspace_text;
356 json_object *inactive_workspace_border, *inactive_workspace_bg, *inactive_workspace_text;
357 json_object *active_workspace_border, *active_workspace_bg, *active_workspace_text;
358 json_object *urgent_workspace_border, *urgent_workspace_bg, *urgent_workspace_text;
359 json_object *binding_mode_border, *binding_mode_bg, *binding_mode_text;
360 json_object_object_get_ex(_colors, "background", &background);
361 json_object_object_get_ex(_colors, "statusline", &statusline);
362 json_object_object_get_ex(_colors, "separator", &separator);
363 json_object_object_get_ex(_colors, "focused_workspace_border", &focused_workspace_border);
364 json_object_object_get_ex(_colors, "focused_workspace_bg", &focused_workspace_bg);
365 json_object_object_get_ex(_colors, "focused_workspace_text", &focused_workspace_text);
366 json_object_object_get_ex(_colors, "active_workspace_border", &active_workspace_border);
367 json_object_object_get_ex(_colors, "active_workspace_bg", &active_workspace_bg);
368 json_object_object_get_ex(_colors, "active_workspace_text", &active_workspace_text);
369 json_object_object_get_ex(_colors, "inactive_workspace_border", &inactive_workspace_border);
370 json_object_object_get_ex(_colors, "inactive_workspace_bg", &inactive_workspace_bg);
371 json_object_object_get_ex(_colors, "inactive_workspace_text", &inactive_workspace_text);
372 json_object_object_get_ex(_colors, "urgent_workspace_border", &urgent_workspace_border);
373 json_object_object_get_ex(_colors, "urgent_workspace_bg", &urgent_workspace_bg);
374 json_object_object_get_ex(_colors, "urgent_workspace_text", &urgent_workspace_text);
375 json_object_object_get_ex(_colors, "binding_mode_border", &binding_mode_border);
376 json_object_object_get_ex(_colors, "binding_mode_bg", &binding_mode_bg);
377 json_object_object_get_ex(_colors, "binding_mode_text", &binding_mode_text);
378 if (background) colors.background = parse_color(json_object_get_string(background));
379 if (statusline) colors.statusline = parse_color(json_object_get_string(statusline));
380 if (separator) colors.separator = parse_color(json_object_get_string(separator));
381 if (focused_workspace_border) {
382 colors.focused_workspace.border = parse_color(json_object_get_string(focused_workspace_border));
383 }
384 if (focused_workspace_bg) {
385 colors.focused_workspace.background = parse_color(json_object_get_string(focused_workspace_bg));
386 }
387 if (focused_workspace_text) {
388 colors.focused_workspace.text = parse_color(json_object_get_string(focused_workspace_text));
389 }
390 if (active_workspace_border) {
391 colors.active_workspace.border = parse_color(json_object_get_string(active_workspace_border));
392 }
393 if (active_workspace_bg) {
394 colors.active_workspace.background = parse_color(json_object_get_string(active_workspace_bg));
395 }
396 if (active_workspace_text) {
397 colors.active_workspace.text = parse_color(json_object_get_string(active_workspace_text));
398 }
399 if (inactive_workspace_border) {
400 colors.inactive_workspace.border = parse_color(json_object_get_string(inactive_workspace_border));
401 }
402 if (inactive_workspace_bg) {
403 colors.inactive_workspace.background = parse_color(json_object_get_string(inactive_workspace_bg));
404 }
405 if (inactive_workspace_text) {
406 colors.inactive_workspace.text = parse_color(json_object_get_string(inactive_workspace_text));
407 }
408 if (binding_mode_border) {
409 colors.binding_mode.border = parse_color(json_object_get_string(binding_mode_border));
410 }
411 if (binding_mode_bg) {
412 colors.binding_mode.background = parse_color(json_object_get_string(binding_mode_bg));
413 }
414 if (binding_mode_text) {
415 colors.binding_mode.text = parse_color(json_object_get_string(binding_mode_text));
416 }
417 }
418
419 json_object_put(bar_config);
420 free(res);
421
422 const char *subscribe_json = "[ \"workspace\", \"mode\" ]";
423 len = strlen(subscribe_json);
424 res = ipc_single_command(ipc_event_socketfd, IPC_SUBSCRIBE, subscribe_json, &len);
425 free(res);
426
427 ipc_update_workspaces();
428}
429
430/**
431 * Renders a sharp line of any width and height.
432 *
433 * The line is drawn from (x,y) to (x+width,y+height) where width/height is 0
434 * if the line has a width/height of one pixel, respectively.
435 */
436void render_sharp_line(cairo_t *cairo, uint32_t color, double x, double y, double width, double height) {
437 cairo_set_source_u32(cairo, color);
438
439 if (width > 1 && height > 1) {
440 cairo_rectangle(cairo, x, y, width, height);
441 cairo_fill(cairo);
442 } else {
443 if (width == 1) {
444 x += 0.5;
445 height += y;
446 width = x;
447 }
448
449 if (height == 1) {
450 y += 0.5;
451 width += x;
452 height = y;
453 }
454
455 cairo_move_to(cairo, x, y);
456 cairo_set_line_width(cairo, 1.0);
457 cairo_line_to(cairo, width, height);
458 cairo_stroke(cairo);
459 }
460}
461
462void render_block(struct status_block *block, double *x, bool edge) {
463 int width, height, sep_width;
464 get_text_size(window, &width, &height, "%s", block->full_text);
465
466 int textwidth = width;
467 double block_width = width;
468
469 if (width < block->min_width) {
470 width = block->min_width;
471 }
472
473 *x -= width;
474
475 if (block->border != 0 && block->border_left > 0) {
476 *x -= (block->border_left + margin);
477 block_width += block->border_left + margin;
478 }
479
480 if (block->border != 0 && block->border_right > 0) {
481 *x -= (block->border_right + margin);
482 block_width += block->border_right + margin;
483 }
484
485 // Add separator
486 if (!edge) {
487 if (separator_symbol) {
488 get_text_size(window, &sep_width, &height, "%s", separator_symbol);
489 if (sep_width > block->separator_block_width) {
490 block->separator_block_width = sep_width + margin * 2;
491 }
492 }
493
494 *x -= block->separator_block_width;
495 } else {
496 *x -= margin;
497 }
498
499 double pos = *x;
500
501 // render background
502 if (block->background != 0x0) {
503 cairo_set_source_u32(window->cairo, block->background);
504 cairo_rectangle(window->cairo, pos - 0.5, 1, block_width, window->height - 2);
505 cairo_fill(window->cairo);
506 }
507
508 // render top border
509 if (block->border != 0 && block->border_top > 0) {
510 render_sharp_line(window->cairo, block->border,
511 pos - 0.5,
512 1,
513 block_width,
514 block->border_top);
515 }
516
517 // render bottom border
518 if (block->border != 0 && block->border_bottom > 0) {
519 render_sharp_line(window->cairo, block->border,
520 pos - 0.5,
521 window->height - 1 - block->border_bottom,
522 block_width,
523 block->border_bottom);
524 }
525
526 // render left border
527 if (block->border != 0 && block->border_left > 0) {
528 render_sharp_line(window->cairo, block->border,
529 pos - 0.5,
530 1,
531 block->border_left,
532 window->height - 2);
533
534 pos += block->border_left + margin;
535 }
536
537 // render text
538 double offset = 0;
539
540 if (strncmp(block->align, "left", 5) == 0) {
541 offset = pos;
542 } else if (strncmp(block->align, "right", 5) == 0) {
543 offset = pos + width - textwidth;
544 } else if (strncmp(block->align, "center", 6) == 0) {
545 offset = pos + (width - textwidth) / 2;
546 }
547
548 cairo_move_to(window->cairo, offset, margin);
549 cairo_set_source_u32(window->cairo, block->color);
550 pango_printf(window, "%s", block->full_text);
551
552 pos += width;
553
554 // render right border
555 if (block->border != 0 && block->border_right > 0) {
556 pos += margin;
557
558 render_sharp_line(window->cairo, block->border,
559 pos - 0.5,
560 1,
561 block->border_right,
562 window->height - 2);
563
564 pos += block->border_right;
565 }
566
567 // render separator
568 if (!edge && block->separator) {
569 cairo_set_source_u32(window->cairo, colors.separator);
570 if (separator_symbol) {
571 offset = pos + (block->separator_block_width - sep_width) / 2;
572 cairo_move_to(window->cairo, offset, margin);
573 pango_printf(window, "%s", separator_symbol);
574 } else {
575 cairo_set_line_width(window->cairo, 1);
576 cairo_move_to(window->cairo, pos + block->separator_block_width/2,
577 margin);
578 cairo_line_to(window->cairo, pos + block->separator_block_width/2,
579 window->height - margin);
580 cairo_stroke(window->cairo);
581 }
582 }
583
584}
585
586char *handle_workspace_number(bool strip_num, const char *ws_name) {
587 bool strip = false;
588 int i;
589
590 if (strip_num) {
591 int len = strlen(ws_name);
592 for (i = 0; i < len; ++i) {
593 if (!('0' <= ws_name[i] && ws_name[i] <= '9')) {
594 if (':' == ws_name[i] && i < len-1 && i > 0) {
595 strip = true;
596 ++i;
597 }
598 break;
599 }
600 }
601 }
602
603 if (strip) {
604 return strdup(ws_name + i);
605 }
606
607 return strdup(ws_name);
608}
609
610void render_workspace_button(struct workspace *ws, double *x) {
611 // strip workspace numbers if required
612 char *name = handle_workspace_number(strip_workspace_numbers, ws->name);
613
614 int width, height;
615 get_text_size(window, &width, &height, "%s", name);
616 struct box_colors box_colors;
617 if (ws->urgent) {
618 box_colors = colors.urgent_workspace;
619 } else if (ws->focused) {
620 box_colors = colors.focused_workspace;
621 } else if (ws->visible) {
622 box_colors = colors.active_workspace;
623 } else {
624 box_colors = colors.inactive_workspace;
625 }
626
627 // background
628 cairo_set_source_u32(window->cairo, box_colors.background);
629 cairo_rectangle(window->cairo, *x, 1.5, width + ws_hor_padding * 2 - 1,
630 height + ws_ver_padding * 2);
631 cairo_fill(window->cairo);
632
633 // border
634 cairo_set_source_u32(window->cairo, box_colors.border);
635 cairo_rectangle(window->cairo, *x, 1.5, width + ws_hor_padding * 2 - 1,
636 height + ws_ver_padding * 2);
637 cairo_stroke(window->cairo);
638
639 // text
640 cairo_set_source_u32(window->cairo, box_colors.text);
641 cairo_move_to(window->cairo, (int)*x + ws_hor_padding, margin);
642 pango_printf(window, "%s", name);
643
644 *x += width + ws_hor_padding * 2 + ws_spacing;
645
646 free(name);
647}
648
649void render_binding_mode_indicator(double pos) {
650 int width, height;
651 get_text_size(window, &width, &height, "%s", mode);
652
653 // background
654 cairo_set_source_u32(window->cairo, colors.binding_mode.background);
655 cairo_rectangle(window->cairo, pos, 1.5, width + ws_hor_padding * 2 - 1,
656 height + ws_ver_padding * 2);
657 cairo_fill(window->cairo);
658
659 // border
660 cairo_set_source_u32(window->cairo, colors.binding_mode.border);
661 cairo_rectangle(window->cairo, pos, 1.5, width + ws_hor_padding * 2 - 1,
662 height + ws_ver_padding * 2);
663 cairo_stroke(window->cairo);
664
665 // text
666 cairo_set_source_u32(window->cairo, colors.binding_mode.text);
667 cairo_move_to(window->cairo, (int)pos + ws_hor_padding, margin);
668 pango_printf(window, "%s", mode);
669}
670
671void render() {
672 int i;
673
674 // Clear
675 cairo_save(window->cairo);
676 cairo_set_operator(window->cairo, CAIRO_OPERATOR_CLEAR);
677 cairo_paint(window->cairo);
678 cairo_restore(window->cairo);
679
680 // Background
681 cairo_set_source_u32(window->cairo, colors.background);
682 cairo_paint(window->cairo);
683
684 // Command output
685 cairo_set_source_u32(window->cairo, colors.statusline);
686 int width, height;
687
688 if (protocol == TEXT) {
689 get_text_size(window, &width, &height, "%s", line);
690 cairo_move_to(window->cairo, window->width - margin - width, margin);
691 pango_printf(window, "%s", line);
692 } else if (protocol == I3BAR && status_line) {
693 double pos = window->width - 0.5;
694 bool edge = true;
695 for (i = status_line->length - 1; i >= 0; --i) {
696 struct status_block *block = status_line->items[i];
697 if (block->full_text && block->full_text[0]) {
698 render_block(block, &pos, edge);
699 edge = false;
700 }
701 }
702 }
703
704 cairo_set_line_width(window->cairo, 1.0);
705 double x = 0.5;
706
707 // Workspaces
708 if (workspace_buttons) {
709 for (i = 0; i < workspaces->length; ++i) {
710 struct workspace *ws = workspaces->items[i];
711 render_workspace_button(ws, &x);
712 }
713 }
714
715 // binding mode indicator
716 if (mode && binding_mode_indicator) {
717 render_binding_mode_indicator(x);
718 }
719}
720
721void free_status_block(void *item) {
722 if (!item) {
723 return;
724 }
725 struct status_block *sb = (struct status_block*)item;
726 if (sb->full_text) {
727 free(sb->full_text);
728 }
729 if (sb->short_text) {
730 free(sb->short_text);
731 }
732 if (sb->align) {
733 free(sb->align);
734 }
735 if (sb->name) {
736 free(sb->name);
737 }
738 if (sb->instance) {
739 free(sb->instance);
740 }
741 free(sb);
742}
743
744void parse_json(const char *text) {
745 json_object *results = json_tokener_parse(text);
746 if (!results) {
747 sway_log(L_DEBUG, "Failed to parse json");
748 return;
749 }
750
751 if (json_object_array_length(results) < 1) {
752 return;
753 }
754
755 if (status_line) {
756 list_foreach(status_line, free_status_block);
757 list_free(status_line);
758 }
759
760 status_line = create_list();
761
762 int i;
763 for (i = 0; i < json_object_array_length(results); ++i) {
764 json_object *full_text, *short_text, *color, *min_width, *align, *urgent;
765 json_object *name, *instance, *separator, *separator_block_width;
766 json_object *background, *border, *border_top, *border_bottom;
767 json_object *border_left, *border_right;
768
769 json_object *json = json_object_array_get_idx(results, i);
770 if (!json) {
771 continue;
772 }
773
774 json_object_object_get_ex(json, "full_text", &full_text);
775 json_object_object_get_ex(json, "short_text", &short_text);
776 json_object_object_get_ex(json, "color", &color);
777 json_object_object_get_ex(json, "min_width", &min_width);
778 json_object_object_get_ex(json, "align", &align);
779 json_object_object_get_ex(json, "urgent", &urgent);
780 json_object_object_get_ex(json, "name", &name);
781 json_object_object_get_ex(json, "instance", &instance);
782 json_object_object_get_ex(json, "separator", &separator);
783 json_object_object_get_ex(json, "separator_block_width", &separator_block_width);
784 json_object_object_get_ex(json, "background", &background);
785 json_object_object_get_ex(json, "border", &border);
786 json_object_object_get_ex(json, "border_top", &border_top);
787 json_object_object_get_ex(json, "border_bottom", &border_bottom);
788 json_object_object_get_ex(json, "border_left", &border_left);
789 json_object_object_get_ex(json, "border_right", &border_right);
790
791 struct status_block *new = malloc(sizeof(struct status_block));
792 memset(new, 0, sizeof(struct status_block));
793
794 if (full_text) {
795 new->full_text = strdup(json_object_get_string(full_text));
796 }
797
798 if (short_text) {
799 new->short_text = strdup(json_object_get_string(short_text));
800 }
801
802 if (color) {
803 new->color = parse_color(json_object_get_string(color));
804 } else {
805 new->color = colors.statusline;
806 }
807
808 if (min_width) {
809 json_type type = json_object_get_type(min_width);
810 if (type == json_type_int) {
811 new->min_width = json_object_get_int(min_width);
812 } else if (type == json_type_string) {
813 int width, height;
814 get_text_size(window, &width, &height, "%s", json_object_get_string(min_width));
815 new->min_width = width;
816 }
817 }
818
819 if (align) {
820 new->align = strdup(json_object_get_string(align));
821 } else {
822 new->align = strdup("left");
823 }
824
825 if (urgent) {
826 new->urgent = json_object_get_int(urgent);
827 }
828
829 if (name) {
830 new->name = strdup(json_object_get_string(name));
831 }
832
833 if (instance) {
834 new->instance = strdup(json_object_get_string(instance));
835 }
836
837 if (separator) {
838 new->separator = json_object_get_int(separator);
839 } else {
840 new->separator = true; // i3bar spec
841 }
842
843 if (separator_block_width) {
844 new->separator_block_width = json_object_get_int(separator_block_width);
845 } else {
846 new->separator_block_width = 9; // i3bar spec
847 }
848
849 // Airblader features
850 if (background) {
851 new->background = parse_color(json_object_get_string(background));
852 } else {
853 new->background = 0x0; // transparent
854 }
855
856 if (border) {
857 new->border = parse_color(json_object_get_string(border));
858 } else {
859 new->border = 0x0; // transparent
860 }
861
862 if (border_top) {
863 new->border_top = json_object_get_int(border_top);
864 } else {
865 new->border_top = 1;
866 }
867
868 if (border_bottom) {
869 new->border_bottom = json_object_get_int(border_bottom);
870 } else {
871 new->border_bottom = 1;
872 }
873
874 if (border_left) {
875 new->border_left = json_object_get_int(border_left);
876 } else {
877 new->border_left = 1;
878 }
879
880 if (border_right) {
881 new->border_right = json_object_get_int(border_right);
882 } else {
883 new->border_right = 1;
884 }
885
886 list_add(status_line, new);
887 }
888
889 json_object_put(results);
890}
891
892// Read line from file descriptor, only show the line tail if it is too long.
893// In non-blocking mode treat "no more data" as a linebreak.
894// If data after a line break has been read, return it in rest.
895// If rest is non-empty, then use that as the start of the next line.
896int read_line_tail(int fd, char *buf, int nbyte, char *rest) {
897 if (fd < 0 || !buf || !nbyte) {
898 return -1;
899 }
900 int l;
901 char *buffer = malloc(nbyte*2+1);
902 char *readpos = buffer;
903 char *lf;
904 // prepend old data to new line if necessary
905 if (rest) {
906 l = strlen(rest);
907 if (l > nbyte) {
908 strcpy(buffer, rest + l - nbyte);
909 readpos += nbyte;
910 } else if (l) {
911 strcpy(buffer, rest);
912 readpos += l;
913 }
914 }
915 // read until a linefeed is found or no more data is available
916 while ((l = read(fd, readpos, nbyte)) > 0) {
917 readpos[l] = '\0';
918 lf = strchr(readpos, '\n');
919 if (lf) {
920 // linefeed found, replace with \0
921 *lf = '\0';
922 // give data from the end of the line, try to fill the buffer
923 if (lf-buffer > nbyte) {
924 strcpy(buf, lf - nbyte + 1);
925 } else {
926 strcpy(buf, buffer);
927 }
928 // we may have read data from the next line, save it to rest
929 if (rest) {
930 rest[0] = '\0';
931 strcpy(rest, lf + 1);
932 }
933 free(buffer);
934 return strlen(buf);
935 } else {
936 // no linefeed found, slide data back.
937 int overflow = readpos - buffer + l - nbyte;
938 if (overflow > 0) {
939 memmove(buffer, buffer + overflow , nbyte + 1);
940 }
941 }
942 }
943 if (l < 0) {
944 free(buffer);
945 return l;
946 }
947 readpos[l]='\0';
948 if (rest) {
949 rest[0] = '\0';
950 }
951 if (nbyte < readpos - buffer + l - 1) {
952 memcpy(buf, readpos - nbyte + l + 1, nbyte);
953 } else {
954 strncpy(buf, buffer, nbyte);
955 }
956 buf[nbyte-1] = '\0';
957 free(buffer);
958 return strlen(buf);
959}
960
961// make sure that enough buffer space is available starting from parserpos
962void i3json_ensure_free(int min_free) {
963 int _step = 10240;
964 int r = min_free % _step;
965 if (r) {
966 min_free += _step - r;
967 }
968 if (!i3json_state.buffer) {
969 i3json_state.buffer = malloc(min_free);
970 i3json_state.bufsize = min_free;
971 i3json_state.parserpos = i3json_state.buffer;
972 } else {
973 int len = 0;
974 int pos = 0;
975 if (i3json_state.line_start) {
976 len = strlen(i3json_state.line_start);
977 pos = i3json_state.parserpos - i3json_state.line_start;
978 if (i3json_state.line_start != i3json_state.buffer) {
979 memmove(i3json_state.buffer, i3json_state.line_start, len+1);
980 }
981 } else {
982 len = strlen(i3json_state.buffer);
983 }
984 if (i3json_state.bufsize < len+min_free) {
985 i3json_state.bufsize += min_free;
986 if (i3json_state.bufsize > 1024000) {
987 sway_abort("Status line json too long or malformed.");
988 }
989 i3json_state.buffer = realloc(i3json_state.buffer, i3json_state.bufsize);
990 if (!i3json_state.buffer) {
991 sway_abort("Could not allocate json buffer");
992 }
993 }
994 if (i3json_state.line_start) {
995 i3json_state.line_start = i3json_state.buffer;
996 i3json_state.parserpos = i3json_state.buffer + pos;
997 } else {
998 i3json_state.parserpos = i3json_state.buffer;
999 }
1000 }
1001 if (!i3json_state.buffer) {
1002 sway_abort("Could not allocate buffer.");
1003 }
1004}
1005
1006// continue parsing from last parserpos
1007int i3json_parse() {
1008 char *c = i3json_state.parserpos;
1009 int handled = 0;
1010 while (*c) {
1011 if (i3json_state.state[i3json_state.depth] == I3JSON_STRING) {
1012 if (!i3json_state.escape && *c == '"') {
1013 --i3json_state.depth;
1014 }
1015 i3json_state.escape = !i3json_state.escape && *c == '\\';
1016 } else {
1017 switch (*c) {
1018 case '[':
1019 ++i3json_state.depth;
1020 if (i3json_state.depth > I3JSON_MAXDEPTH) {
1021 sway_abort("JSON too deep");
1022 }
1023 i3json_state.state[i3json_state.depth] = I3JSON_ARRAY;
1024 if (i3json_state.depth == 2) {
1025 i3json_state.line_start = c;
1026 }
1027 break;
1028 case ']':
1029 if (i3json_state.state[i3json_state.depth] != I3JSON_ARRAY) {
1030 sway_abort("JSON malformed");
1031 }
1032 --i3json_state.depth;
1033 if (i3json_state.depth == 1) {
1034 // c[1] is valid since c[0] != '\0'
1035 char p = c[1];
1036 c[1] = '\0';
1037 parse_json(i3json_state.line_start);
1038 c[1] = p;
1039 ++handled;
1040 i3json_state.line_start = c+1;
1041 }
1042 break;
1043 case '"':
1044 ++i3json_state.depth;
1045 if (i3json_state.depth > I3JSON_MAXDEPTH) {
1046 sway_abort("JSON too deep");
1047 }
1048 i3json_state.state[i3json_state.depth] = I3JSON_STRING;
1049 break;
1050 }
1051 }
1052 ++c;
1053 }
1054 i3json_state.parserpos = c;
1055 return handled;
1056}
1057
1058// append data and parse it.
1059int i3json_handle_data(char *data) {
1060 int len = strlen(data);
1061 i3json_ensure_free(len);
1062 strcpy(i3json_state.parserpos, data);
1063 return i3json_parse();
1064}
1065
1066// read data from fd and parse it.
1067int i3json_handle_fd(int fd) {
1068 i3json_ensure_free(10240);
1069 // get fresh data at the end of the buffer
1070 int readlen = read(fd, i3json_state.parserpos, 10239);
1071 if (readlen < 0) {
1072 return readlen;
1073 }
1074 i3json_state.parserpos[readlen] = '\0';
1075 return i3json_parse();
1076}
1077
1078bool handle_ipc_event() {
1079 struct ipc_response *resp = ipc_recv_response(ipc_event_socketfd);
1080 switch (resp->type) {
1081 case IPC_EVENT_WORKSPACE:
1082 ipc_update_workspaces();
1083 break;
1084 case IPC_EVENT_MODE: {
1085 json_object *result = json_tokener_parse(resp->payload);
1086 if (!result) {
1087 free_ipc_response(resp);
1088 sway_log(L_ERROR, "failed to parse payload as json");
1089 return false;
1090 }
1091 json_object *json_change;
1092 if (json_object_object_get_ex(result, "change", &json_change)) {
1093 const char *change = json_object_get_string(json_change);
1094
1095 free(mode);
1096 if (strcmp(change, "default") == 0) {
1097 mode = NULL;
1098 } else {
1099 mode = strdup(change);
1100 }
1101 } else {
1102 sway_log(L_ERROR, "failed to parse response");
1103 }
1104
1105 json_object_put(result);
1106 break;
1107 }
1108 default:
1109 free_ipc_response(resp);
1110 return false;
1111 }
1112
1113 free_ipc_response(resp);
1114 return true;
1115}
1116
1117void poll_for_update() {
1118 fd_set readfds;
1119 int activity;
1120
1121 while (1) {
1122 if (dirty && window_prerender(window) && window->cairo) {
1123 render();
1124 window_render(window);
1125 if (wl_display_dispatch(registry->display) == -1) {
1126 break;
1127 }
1128 }
1129
1130 dirty = false;
1131 FD_ZERO(&readfds);
1132 FD_SET(ipc_event_socketfd, &readfds);
1133 FD_SET(status_read_fd, &readfds);
1134
1135 activity = select(FD_SETSIZE, &readfds, NULL, NULL, NULL);
1136 if (activity < 0) {
1137 sway_log(L_ERROR, "polling failed: %d", errno);
1138 }
1139
1140 if (FD_ISSET(ipc_event_socketfd, &readfds)) {
1141 sway_log(L_DEBUG, "Got IPC event.");
1142 dirty = handle_ipc_event();
1143 }
1144
1145 if (status_command && FD_ISSET(status_read_fd, &readfds)) {
1146 sway_log(L_DEBUG, "Got update from status command.");
1147 switch (protocol) {
1148 case I3BAR:
1149 sway_log(L_DEBUG, "Got i3bar protocol.");
1150 if (i3json_handle_fd(status_read_fd) > 0) {
1151 dirty = true;
1152 }
1153 break;
1154 case TEXT:
1155 sway_log(L_DEBUG, "Got text protocol.");
1156 read_line_tail(status_read_fd, line, sizeof(line), line_rest);
1157 dirty = true;
1158 break;
1159 case UNDEF:
1160 sway_log(L_DEBUG, "Detecting protocol...");
1161 if (read_line_tail(status_read_fd, line, sizeof(line), line_rest) < 0) {
1162 break;
1163 }
1164 dirty = true;
1165 protocol = TEXT;
1166 if (line[0] == '{') {
1167 // detect i3bar json protocol
1168 json_object *proto = json_tokener_parse(line);
1169 json_object *version;
1170 if (proto) {
1171 if (json_object_object_get_ex(proto, "version", &version)
1172 && json_object_get_int(version) == 1
1173 ) {
1174 sway_log(L_DEBUG, "Switched to i3bar protocol.");
1175 protocol = I3BAR;
1176 i3json_handle_data(line_rest);
1177 }
1178 json_object_put(proto);
1179 }
1180 }
1181 break;
1182 }
1183 }
1184 }
1185}
1186
1187int main(int argc, char **argv) { 23int main(int argc, char **argv) {
1188
1189 char *socket_path = NULL; 24 char *socket_path = NULL;
1190 char *bar_id = NULL; 25 char *bar_id = NULL;
1191 bool debug = false; 26 bool debug = false;
@@ -1253,67 +88,30 @@ int main(int argc, char **argv) {
1253 init_log(L_ERROR); 88 init_log(L_ERROR);
1254 } 89 }
1255 90
1256 registry = registry_poll();
1257
1258 if (!registry->desktop_shell) {
1259 sway_abort("swaybar requires the compositor to support the desktop-shell extension.");
1260 }
1261
1262 if (!socket_path) { 91 if (!socket_path) {
1263 socket_path = get_socketpath(); 92 socket_path = get_socketpath();
1264 if (!socket_path) { 93 if (!socket_path) {
1265 sway_abort("Unable to retrieve socket path"); 94 sway_abort("Unable to retrieve socket path");
1266 } 95 }
1267 } 96 }
1268 ipc_socketfd = ipc_open_socket(socket_path);
1269 ipc_event_socketfd = ipc_open_socket(socket_path);
1270
1271 97
1272 if (argc == optind) { 98 if (argc == optind) {
1273 sway_abort("No output index provided"); 99 sway_abort("No output index provided");
1274 } 100 }
1275 101
1276 int desired_output = atoi(argv[optind]); 102 int desired_output = atoi(argv[optind]);
1277 struct output_state *output = registry->outputs->items[desired_output];
1278
1279 window = window_setup(registry, output->width, 30, false);
1280 if (!window) {
1281 sway_abort("Failed to create window.");
1282 }
1283 desktop_shell_set_panel(registry->desktop_shell, output->output, window->surface);
1284
1285 bar_ipc_init(desired_output, bar_id);
1286 103
1287 if (status_command) { 104 signal(SIGTERM, sig_handler);
1288 int pipefd[2];
1289 pipe(pipefd);
1290 pid = fork();
1291 if (pid == 0) {
1292 close(pipefd[0]);
1293 dup2(pipefd[1], STDOUT_FILENO);
1294 close(pipefd[1]);
1295 char *const cmd[] = {
1296 "sh",
1297 "-c",
1298 status_command,
1299 NULL,
1300 };
1301 execvp(cmd[0], cmd);
1302 return 0;
1303 }
1304 105
1305 close(pipefd[1]); 106 bar_setup(&swaybar, socket_path, bar_id, desired_output);
1306 status_read_fd = pipefd[0];
1307 fcntl(status_read_fd, F_SETFL, O_NONBLOCK);
1308 line[0] = '\0';
1309 }
1310 107
1311 signal(SIGTERM, sig_handler); 108 free(socket_path);
109 free(bar_id);
1312 110
1313 poll_for_update(); 111 bar_run(&swaybar);
1314 112
1315 // gracefully shutdown swaybar and status_command 113 // gracefully shutdown swaybar and status_command
1316 swaybar_teardown(); 114 bar_teardown(&swaybar);
1317 115
1318 return 0; 116 return 0;
1319} 117}
diff --git a/swaybar/render.c b/swaybar/render.c
new file mode 100644
index 00000000..f3ce6010
--- /dev/null
+++ b/swaybar/render.c
@@ -0,0 +1,328 @@
1#include <stdlib.h>
2#include <stdint.h>
3#include <string.h>
4
5#include "client/pango.h"
6#include "client/window.h"
7#include "config.h"
8#include "status_line.h"
9#include "render.h"
10
11
12/* internal spacing */
13static int margin = 3;
14static int ws_horizontal_padding = 5;
15static double ws_vertical_padding = 1.5;
16static int ws_spacing = 1;
17
18static void cairo_set_source_u32(cairo_t *cairo, uint32_t color) {
19 cairo_set_source_rgba(cairo,
20 ((color & 0xFF000000) >> 24) / 256.0,
21 ((color & 0xFF0000) >> 16) / 256.0,
22 ((color & 0xFF00) >> 8) / 256.0,
23 (color & 0xFF) / 256.0);
24}
25
26/**
27 * Renders a sharp line of any width and height.
28 *
29 * The line is drawn from (x,y) to (x+width,y+height) where width/height is 0
30 * if the line has a width/height of one pixel, respectively.
31 */
32static void render_sharp_line(cairo_t *cairo, uint32_t color, double x, double y, double width, double height) {
33 cairo_set_source_u32(cairo, color);
34
35 if (width > 1 && height > 1) {
36 cairo_rectangle(cairo, x, y, width, height);
37 cairo_fill(cairo);
38 } else {
39 if (width == 1) {
40 x += 0.5;
41 height += y;
42 width = x;
43 }
44
45 if (height == 1) {
46 y += 0.5;
47 width += x;
48 height = y;
49 }
50
51 cairo_move_to(cairo, x, y);
52 cairo_set_line_width(cairo, 1.0);
53 cairo_line_to(cairo, width, height);
54 cairo_stroke(cairo);
55 }
56}
57
58static void render_block(struct window *window, struct config *config, struct status_block *block, double *x, bool edge) {
59 int width, height, sep_width;
60 get_text_size(window, &width, &height, "%s", block->full_text);
61
62 int textwidth = width;
63 double block_width = width;
64
65 if (width < block->min_width) {
66 width = block->min_width;
67 }
68
69 *x -= width;
70
71 if (block->border != 0 && block->border_left > 0) {
72 *x -= (block->border_left + margin);
73 block_width += block->border_left + margin;
74 }
75
76 if (block->border != 0 && block->border_right > 0) {
77 *x -= (block->border_right + margin);
78 block_width += block->border_right + margin;
79 }
80
81 // Add separator
82 if (!edge) {
83 if (config->sep_symbol) {
84 get_text_size(window, &sep_width, &height, "%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 // render background
98 if (block->background != 0x0) {
99 cairo_set_source_u32(window->cairo, block->background);
100 cairo_rectangle(window->cairo, pos - 0.5, 1, block_width, window->height - 2);
101 cairo_fill(window->cairo);
102 }
103
104 // render top border
105 if (block->border != 0 && block->border_top > 0) {
106 render_sharp_line(window->cairo, block->border,
107 pos - 0.5,
108 1,
109 block_width,
110 block->border_top);
111 }
112
113 // render bottom border
114 if (block->border != 0 && block->border_bottom > 0) {
115 render_sharp_line(window->cairo, block->border,
116 pos - 0.5,
117 window->height - 1 - block->border_bottom,
118 block_width,
119 block->border_bottom);
120 }
121
122 // render left border
123 if (block->border != 0 && block->border_left > 0) {
124 render_sharp_line(window->cairo, block->border,
125 pos - 0.5,
126 1,
127 block->border_left,
128 window->height - 2);
129
130 pos += block->border_left + margin;
131 }
132
133 // render text
134 double offset = 0;
135
136 if (strncmp(block->align, "left", 5) == 0) {
137 offset = pos;
138 } else if (strncmp(block->align, "right", 5) == 0) {
139 offset = pos + width - textwidth;
140 } else if (strncmp(block->align, "center", 6) == 0) {
141 offset = pos + (width - textwidth) / 2;
142 }
143
144 cairo_move_to(window->cairo, offset, margin);
145 cairo_set_source_u32(window->cairo, block->color);
146 pango_printf(window, "%s", block->full_text);
147
148 pos += width;
149
150 // render right border
151 if (block->border != 0 && block->border_right > 0) {
152 pos += margin;
153
154 render_sharp_line(window->cairo, block->border,
155 pos - 0.5,
156 1,
157 block->border_right,
158 window->height - 2);
159
160 pos += block->border_right;
161 }
162
163 // render separator
164 if (!edge && block->separator) {
165 cairo_set_source_u32(window->cairo, config->colors.separator);
166 if (config->sep_symbol) {
167 offset = pos + (block->separator_block_width - sep_width) / 2;
168 cairo_move_to(window->cairo, offset, margin);
169 pango_printf(window, "%s", config->sep_symbol);
170 } else {
171 cairo_set_line_width(window->cairo, 1);
172 cairo_move_to(window->cairo, pos + block->separator_block_width/2,
173 margin);
174 cairo_line_to(window->cairo, pos + block->separator_block_width/2,
175 window->height - margin);
176 cairo_stroke(window->cairo);
177 }
178 }
179
180}
181
182static char *handle_workspace_number(bool strip_num, const char *ws_name) {
183 bool strip = false;
184 int i;
185
186 if (strip_num) {
187 int len = strlen(ws_name);
188 for (i = 0; i < len; ++i) {
189 if (!('0' <= ws_name[i] && ws_name[i] <= '9')) {
190 if (':' == ws_name[i] && i < len-1 && i > 0) {
191 strip = true;
192 ++i;
193 }
194 break;
195 }
196 }
197 }
198
199 if (strip) {
200 return strdup(ws_name + i);
201 }
202
203 return strdup(ws_name);
204}
205
206static void render_workspace_button(struct window *window, struct config *config, struct workspace *ws, double *x) {
207 // strip workspace numbers if required
208 char *name = handle_workspace_number(config->strip_workspace_numbers, ws->name);
209
210 int width, height;
211 get_text_size(window, &width, &height, "%s", name);
212 struct box_colors box_colors;
213 if (ws->urgent) {
214 box_colors = config->colors.urgent_workspace;
215 } else if (ws->focused) {
216 box_colors = config->colors.focused_workspace;
217 } else if (ws->visible) {
218 box_colors = config->colors.active_workspace;
219 } else {
220 box_colors = config->colors.inactive_workspace;
221 }
222
223 // background
224 cairo_set_source_u32(window->cairo, box_colors.background);
225 cairo_rectangle(window->cairo, *x, 1.5, width + ws_horizontal_padding * 2 - 1,
226 height + ws_vertical_padding * 2);
227 cairo_fill(window->cairo);
228
229 // border
230 cairo_set_source_u32(window->cairo, box_colors.border);
231 cairo_rectangle(window->cairo, *x, 1.5, width + ws_horizontal_padding * 2 - 1,
232 height + ws_vertical_padding * 2);
233 cairo_stroke(window->cairo);
234
235 // text
236 cairo_set_source_u32(window->cairo, box_colors.text);
237 cairo_move_to(window->cairo, (int)*x + ws_horizontal_padding, margin);
238 pango_printf(window, "%s", name);
239
240 *x += width + ws_horizontal_padding * 2 + ws_spacing;
241
242 free(name);
243}
244
245static void render_binding_mode_indicator(struct window *window, struct config *config, double pos) {
246 int width, height;
247 get_text_size(window, &width, &height, "%s", config->mode);
248
249 // background
250 cairo_set_source_u32(window->cairo, config->colors.binding_mode.background);
251 cairo_rectangle(window->cairo, pos, 1.5, width + ws_horizontal_padding * 2 - 1,
252 height + ws_vertical_padding * 2);
253 cairo_fill(window->cairo);
254
255 // border
256 cairo_set_source_u32(window->cairo, config->colors.binding_mode.border);
257 cairo_rectangle(window->cairo, pos, 1.5, width + ws_horizontal_padding * 2 - 1,
258 height + ws_vertical_padding * 2);
259 cairo_stroke(window->cairo);
260
261 // text
262 cairo_set_source_u32(window->cairo, config->colors.binding_mode.text);
263 cairo_move_to(window->cairo, (int)pos + ws_horizontal_padding, margin);
264 pango_printf(window, "%s", config->mode);
265}
266
267void render(struct output *output, struct config *config, struct status_line *line) {
268 int i;
269
270 struct window *window = output->window;
271 cairo_t *cairo = window->cairo;
272
273 // Clear
274 cairo_save(cairo);
275 cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
276 cairo_paint(cairo);
277 cairo_restore(cairo);
278
279 // Background
280 cairo_set_source_u32(cairo, config->colors.background);
281 cairo_paint(cairo);
282
283 // Command output
284 cairo_set_source_u32(cairo, config->colors.statusline);
285 int width, height;
286
287 if (line->protocol == TEXT) {
288 get_text_size(window, &width, &height, "%s", line->text_line);
289 cairo_move_to(cairo, window->width - margin - width, margin);
290 pango_printf(window, "%s", line);
291 } else if (line->protocol == I3BAR && line->block_line) {
292 double pos = window->width - 0.5;
293 bool edge = true;
294 for (i = line->block_line->length - 1; i >= 0; --i) {
295 struct status_block *block = line->block_line->items[i];
296 if (block->full_text && block->full_text[0]) {
297 render_block(window, config, block, &pos, edge);
298 edge = false;
299 }
300 }
301 }
302
303 cairo_set_line_width(cairo, 1.0);
304 double x = 0.5;
305
306 // Workspaces
307 if (config->workspace_buttons) {
308 for (i = 0; i < output->workspaces->length; ++i) {
309 struct workspace *ws = output->workspaces->items[i];
310 render_workspace_button(window, config, ws, &x);
311 }
312 }
313
314 // binding mode indicator
315 if (config->mode && config->binding_mode_indicator) {
316 render_binding_mode_indicator(window, config, x);
317 }
318}
319
320void set_window_height(struct window *window, int height) {
321 int text_width, text_height;
322 get_text_size(window, &text_width, &text_height, "Test string for measuring purposes");
323 if (height > 0) {
324 margin = (height - text_height) / 2;
325 ws_vertical_padding = margin - 1.5;
326 }
327 window->height = text_height + margin * 2;
328}
diff --git a/swaybar/render.h b/swaybar/render.h
new file mode 100644
index 00000000..931a1cdd
--- /dev/null
+++ b/swaybar/render.h
@@ -0,0 +1,17 @@
1#ifndef _SWAYBAR_RENDER_H
2#define _SWAYBAR_RENDER_H
3
4#include "config.h"
5#include "bar.h"
6
7/**
8 * Render swaybar.
9 */
10void render(struct output *output, struct config *config, struct status_line *line);
11
12/**
13 * Set window height and modify internal spacing accordingly.
14 */
15void set_window_height(struct window *window, int height);
16
17#endif /* _SWAYBAR_RENDER_H */
diff --git a/swaybar/status_line.c b/swaybar/status_line.c
new file mode 100644
index 00000000..6b630c49
--- /dev/null
+++ b/swaybar/status_line.c
@@ -0,0 +1,442 @@
1#include <stdlib.h>
2#include <string.h>
3#include <unistd.h>
4#include <json-c/json.h>
5
6#include "log.h"
7#include "config.h"
8#include "status_line.h"
9
10#define I3JSON_MAXDEPTH 4
11#define I3JSON_UNKNOWN 0
12#define I3JSON_ARRAY 1
13#define I3JSON_STRING 2
14
15struct {
16 int bufsize;
17 char *buffer;
18 char *line_start;
19 char *parserpos;
20 bool escape;
21 int depth;
22 int bar[I3JSON_MAXDEPTH+1];
23} i3json_state = { 0, NULL, NULL, NULL, false, 0, { I3JSON_UNKNOWN } };
24
25static char line[1024];
26static char line_rest[1024];
27
28static void free_status_block(void *item) {
29 if (!item) {
30 return;
31 }
32 struct status_block *sb = (struct status_block*)item;
33 if (sb->full_text) {
34 free(sb->full_text);
35 }
36 if (sb->short_text) {
37 free(sb->short_text);
38 }
39 if (sb->align) {
40 free(sb->align);
41 }
42 if (sb->name) {
43 free(sb->name);
44 }
45 if (sb->instance) {
46 free(sb->instance);
47 }
48 free(sb);
49}
50
51static void parse_json(struct bar *bar, const char *text) {
52 json_object *results = json_tokener_parse(text);
53 if (!results) {
54 sway_log(L_DEBUG, "Failed to parse json");
55 return;
56 }
57
58 if (json_object_array_length(results) < 1) {
59 return;
60 }
61
62 if (bar->status->block_line) {
63 list_foreach(bar->status->block_line, free_status_block);
64 list_free(bar->status->block_line);
65 }
66
67 bar->status->block_line = create_list();
68
69 int i;
70 for (i = 0; i < json_object_array_length(results); ++i) {
71 json_object *full_text, *short_text, *color, *min_width, *align, *urgent;
72 json_object *name, *instance, *separator, *separator_block_width;
73 json_object *background, *border, *border_top, *border_bottom;
74 json_object *border_left, *border_right;
75
76 json_object *json = json_object_array_get_idx(results, i);
77 if (!json) {
78 continue;
79 }
80
81 json_object_object_get_ex(json, "full_text", &full_text);
82 json_object_object_get_ex(json, "short_text", &short_text);
83 json_object_object_get_ex(json, "color", &color);
84 json_object_object_get_ex(json, "min_width", &min_width);
85 json_object_object_get_ex(json, "align", &align);
86 json_object_object_get_ex(json, "urgent", &urgent);
87 json_object_object_get_ex(json, "name", &name);
88 json_object_object_get_ex(json, "instance", &instance);
89 json_object_object_get_ex(json, "separator", &separator);
90 json_object_object_get_ex(json, "separator_block_width", &separator_block_width);
91 json_object_object_get_ex(json, "background", &background);
92 json_object_object_get_ex(json, "border", &border);
93 json_object_object_get_ex(json, "border_top", &border_top);
94 json_object_object_get_ex(json, "border_bottom", &border_bottom);
95 json_object_object_get_ex(json, "border_left", &border_left);
96 json_object_object_get_ex(json, "border_right", &border_right);
97
98 struct status_block *new = calloc(1, sizeof(struct status_block));
99
100 if (full_text) {
101 new->full_text = strdup(json_object_get_string(full_text));
102 }
103
104 if (short_text) {
105 new->short_text = strdup(json_object_get_string(short_text));
106 }
107
108 if (color) {
109 new->color = parse_color(json_object_get_string(color));
110 } else {
111 new->color = bar->config->colors.statusline;
112 }
113
114 if (min_width) {
115 json_type type = json_object_get_type(min_width);
116 if (type == json_type_int) {
117 new->min_width = json_object_get_int(min_width);
118 } else if (type == json_type_string) {
119 /* the width will be calculated when rendering */
120 new->min_width = 0;
121 }
122 }
123
124 if (align) {
125 new->align = strdup(json_object_get_string(align));
126 } else {
127 new->align = strdup("left");
128 }
129
130 if (urgent) {
131 new->urgent = json_object_get_int(urgent);
132 }
133
134 if (name) {
135 new->name = strdup(json_object_get_string(name));
136 }
137
138 if (instance) {
139 new->instance = strdup(json_object_get_string(instance));
140 }
141
142 if (separator) {
143 new->separator = json_object_get_int(separator);
144 } else {
145 new->separator = true; // i3bar spec
146 }
147
148 if (separator_block_width) {
149 new->separator_block_width = json_object_get_int(separator_block_width);
150 } else {
151 new->separator_block_width = 9; // i3bar spec
152 }
153
154 // Airblader features
155 if (background) {
156 new->background = parse_color(json_object_get_string(background));
157 } else {
158 new->background = 0x0; // transparent
159 }
160
161 if (border) {
162 new->border = parse_color(json_object_get_string(border));
163 } else {
164 new->border = 0x0; // transparent
165 }
166
167 if (border_top) {
168 new->border_top = json_object_get_int(border_top);
169 } else {
170 new->border_top = 1;
171 }
172
173 if (border_bottom) {
174 new->border_bottom = json_object_get_int(border_bottom);
175 } else {
176 new->border_bottom = 1;
177 }
178
179 if (border_left) {
180 new->border_left = json_object_get_int(border_left);
181 } else {
182 new->border_left = 1;
183 }
184
185 if (border_right) {
186 new->border_right = json_object_get_int(border_right);
187 } else {
188 new->border_right = 1;
189 }
190
191 list_add(bar->status->block_line, new);
192 }
193
194 json_object_put(results);
195}
196
197// continue parsing from last parserpos
198static int i3json_parse(struct bar *bar) {
199 char *c = i3json_state.parserpos;
200 int handled = 0;
201 while (*c) {
202 if (i3json_state.bar[i3json_state.depth] == I3JSON_STRING) {
203 if (!i3json_state.escape && *c == '"') {
204 --i3json_state.depth;
205 }
206 i3json_state.escape = !i3json_state.escape && *c == '\\';
207 } else {
208 switch (*c) {
209 case '[':
210 ++i3json_state.depth;
211 if (i3json_state.depth > I3JSON_MAXDEPTH) {
212 sway_abort("JSON too deep");
213 }
214 i3json_state.bar[i3json_state.depth] = I3JSON_ARRAY;
215 if (i3json_state.depth == 2) {
216 i3json_state.line_start = c;
217 }
218 break;
219 case ']':
220 if (i3json_state.bar[i3json_state.depth] != I3JSON_ARRAY) {
221 sway_abort("JSON malformed");
222 }
223 --i3json_state.depth;
224 if (i3json_state.depth == 1) {
225 // c[1] is valid since c[0] != '\0'
226 char p = c[1];
227 c[1] = '\0';
228 parse_json(bar, i3json_state.line_start);
229 c[1] = p;
230 ++handled;
231 i3json_state.line_start = c+1;
232 }
233 break;
234 case '"':
235 ++i3json_state.depth;
236 if (i3json_state.depth > I3JSON_MAXDEPTH) {
237 sway_abort("JSON too deep");
238 }
239 i3json_state.bar[i3json_state.depth] = I3JSON_STRING;
240 break;
241 }
242 }
243 ++c;
244 }
245 i3json_state.parserpos = c;
246 return handled;
247}
248
249// Read line from file descriptor, only show the line tail if it is too long.
250// In non-blocking mode treat "no more data" as a linebreak.
251// If data after a line break has been read, return it in rest.
252// If rest is non-empty, then use that as the start of the next line.
253static int read_line_tail(int fd, char *buf, int nbyte, char *rest) {
254 if (fd < 0 || !buf || !nbyte) {
255 return -1;
256 }
257 int l;
258 char *buffer = malloc(nbyte*2+1);
259 char *readpos = buffer;
260 char *lf;
261 // prepend old data to new line if necessary
262 if (rest) {
263 l = strlen(rest);
264 if (l > nbyte) {
265 strcpy(buffer, rest + l - nbyte);
266 readpos += nbyte;
267 } else if (l) {
268 strcpy(buffer, rest);
269 readpos += l;
270 }
271 }
272 // read until a linefeed is found or no more data is available
273 while ((l = read(fd, readpos, nbyte)) > 0) {
274 readpos[l] = '\0';
275 lf = strchr(readpos, '\n');
276 if (lf) {
277 // linefeed found, replace with \0
278 *lf = '\0';
279 // give data from the end of the line, try to fill the buffer
280 if (lf-buffer > nbyte) {
281 strcpy(buf, lf - nbyte + 1);
282 } else {
283 strcpy(buf, buffer);
284 }
285 // we may have read data from the next line, save it to rest
286 if (rest) {
287 rest[0] = '\0';
288 strcpy(rest, lf + 1);
289 }
290 free(buffer);
291 return strlen(buf);
292 } else {
293 // no linefeed found, slide data back.
294 int overflow = readpos - buffer + l - nbyte;
295 if (overflow > 0) {
296 memmove(buffer, buffer + overflow , nbyte + 1);
297 }
298 }
299 }
300 if (l < 0) {
301 free(buffer);
302 return l;
303 }
304 readpos[l]='\0';
305 if (rest) {
306 rest[0] = '\0';
307 }
308 if (nbyte < readpos - buffer + l - 1) {
309 memcpy(buf, readpos - nbyte + l + 1, nbyte);
310 } else {
311 strncpy(buf, buffer, nbyte);
312 }
313 buf[nbyte-1] = '\0';
314 free(buffer);
315 return strlen(buf);
316}
317
318// make sure that enough buffer space is available starting from parserpos
319static void i3json_ensure_free(int min_free) {
320 int _step = 10240;
321 int r = min_free % _step;
322 if (r) {
323 min_free += _step - r;
324 }
325 if (!i3json_state.buffer) {
326 i3json_state.buffer = malloc(min_free);
327 i3json_state.bufsize = min_free;
328 i3json_state.parserpos = i3json_state.buffer;
329 } else {
330 int len = 0;
331 int pos = 0;
332 if (i3json_state.line_start) {
333 len = strlen(i3json_state.line_start);
334 pos = i3json_state.parserpos - i3json_state.line_start;
335 if (i3json_state.line_start != i3json_state.buffer) {
336 memmove(i3json_state.buffer, i3json_state.line_start, len+1);
337 }
338 } else {
339 len = strlen(i3json_state.buffer);
340 }
341 if (i3json_state.bufsize < len+min_free) {
342 i3json_state.bufsize += min_free;
343 if (i3json_state.bufsize > 1024000) {
344 sway_abort("Status line json too long or malformed.");
345 }
346 i3json_state.buffer = realloc(i3json_state.buffer, i3json_state.bufsize);
347 if (!i3json_state.buffer) {
348 sway_abort("Could not allocate json buffer");
349 }
350 }
351 if (i3json_state.line_start) {
352 i3json_state.line_start = i3json_state.buffer;
353 i3json_state.parserpos = i3json_state.buffer + pos;
354 } else {
355 i3json_state.parserpos = i3json_state.buffer;
356 }
357 }
358 if (!i3json_state.buffer) {
359 sway_abort("Could not allocate buffer.");
360 }
361}
362
363// append data and parse it.
364static int i3json_handle_data(struct bar *bar, char *data) {
365 int len = strlen(data);
366 i3json_ensure_free(len);
367 strcpy(i3json_state.parserpos, data);
368 return i3json_parse(bar);
369}
370
371// read data from fd and parse it.
372static int i3json_handle_fd(struct bar *bar) {
373 i3json_ensure_free(10240);
374 // get fresh data at the end of the buffer
375 int readlen = read(bar->status_read_fd, i3json_state.parserpos, 10239);
376 if (readlen < 0) {
377 return readlen;
378 }
379 i3json_state.parserpos[readlen] = '\0';
380 return i3json_parse(bar);
381}
382
383bool handle_status_line(struct bar *bar) {
384 bool dirty = false;
385
386 switch (bar->status->protocol) {
387 case I3BAR:
388 sway_log(L_DEBUG, "Got i3bar protocol.");
389 if (i3json_handle_fd(bar) > 0) {
390 dirty = true;
391 }
392 break;
393 case TEXT:
394 sway_log(L_DEBUG, "Got text protocol.");
395 read_line_tail(bar->status_read_fd, line, sizeof(line), line_rest);
396 dirty = true;
397 bar->status->text_line = line;
398 break;
399 case UNDEF:
400 sway_log(L_DEBUG, "Detecting protocol...");
401 if (read_line_tail(bar->status_read_fd, line, sizeof(line), line_rest) < 0) {
402 break;
403 }
404 dirty = true;
405 bar->status->text_line = line;
406 bar->status->protocol = TEXT;
407 if (line[0] == '{') {
408 // detect i3bar json protocol
409 json_object *proto = json_tokener_parse(line);
410 json_object *version;
411 if (proto) {
412 if (json_object_object_get_ex(proto, "version", &version)
413 && json_object_get_int(version) == 1
414 ) {
415 sway_log(L_DEBUG, "Switched to i3bar protocol.");
416 bar->status->protocol = I3BAR;
417 i3json_handle_data(bar, line_rest);
418 }
419 json_object_put(proto);
420 }
421 }
422 break;
423 }
424
425 return dirty;
426}
427
428struct status_line *init_status_line() {
429 struct status_line *line = malloc(sizeof(struct status_line));
430 line->block_line = create_list();
431 line->text_line = NULL;
432 line->protocol = UNDEF;
433
434 return line;
435}
436
437void free_status_line(struct status_line *line) {
438 if (line->block_line) {
439 list_foreach(line->block_line, free_status_block);
440 list_free(line->block_line);
441 }
442}
diff --git a/swaybar/status_line.h b/swaybar/status_line.h
new file mode 100644
index 00000000..273542dc
--- /dev/null
+++ b/swaybar/status_line.h
@@ -0,0 +1,50 @@
1#ifndef _SWAYBAR_STATUS_LINE_H
2#define _SWAYBAR_STATUS_LINE_H
3
4#include <stdint.h>
5#include <stdbool.h>
6
7#include "list.h"
8#include "bar.h"
9
10typedef enum {UNDEF, TEXT, I3BAR} command_protocol;
11
12struct status_line {
13 list_t *block_line;
14 const char *text_line;
15 command_protocol protocol;
16};
17
18struct status_block {
19 char *full_text, *short_text, *align;
20 bool urgent;
21 uint32_t color;
22 int min_width;
23 char *name, *instance;
24 bool separator;
25 int separator_block_width;
26 // Airblader features
27 uint32_t background;
28 uint32_t border;
29 int border_top;
30 int border_bottom;
31 int border_left;
32 int border_right;
33};
34
35/**
36 * Initialize status line struct.
37 */
38struct status_line *init_status_line();
39
40/**
41 * handle status line activity.
42 */
43bool handle_status_line(struct bar *bar);
44
45/**
46 * Free status line struct.
47 */
48void free_status_line(struct status_line *line);
49
50#endif /* _SWAYBAR_STATUS_LINE_H */