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