summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/sway/commands.h1
-rw-r--r--include/sway/criteria.h5
-rw-r--r--include/sway/tree/container.h2
-rw-r--r--include/sway/tree/view.h9
-rw-r--r--include/sway/tree/workspace.h4
-rw-r--r--sway/commands.c3
-rw-r--r--sway/commands/default_floating_border.c29
-rw-r--r--sway/commands/no_focus.c26
-rw-r--r--sway/commands/urgent.c36
-rw-r--r--sway/config.c9
-rw-r--r--sway/criteria.c46
-rw-r--r--sway/desktop/idle_inhibit_v1.c1
-rw-r--r--sway/desktop/layer_shell.c12
-rw-r--r--sway/desktop/render.c64
-rw-r--r--sway/desktop/xwayland.c4
-rw-r--r--sway/input/seat.c17
-rw-r--r--sway/ipc-json.c7
-rw-r--r--sway/meson.build3
-rw-r--r--sway/sway.5.scd5
-rw-r--r--sway/tree/container.c45
-rw-r--r--sway/tree/layout.c11
-rw-r--r--sway/tree/view.c96
-rw-r--r--sway/tree/workspace.c11
-rw-r--r--swaybar/ipc.c12
-rw-r--r--swayidle/main.c15
-rw-r--r--swaylock/main.c244
-rw-r--r--swaylock/swaylock.1.scd9
27 files changed, 602 insertions, 124 deletions
diff --git a/include/sway/commands.h b/include/sway/commands.h
index 3ebd0002..1e93e2a3 100644
--- a/include/sway/commands.h
+++ b/include/sway/commands.h
@@ -152,6 +152,7 @@ sway_cmd cmd_swaybg_command;
152sway_cmd cmd_swap; 152sway_cmd cmd_swap;
153sway_cmd cmd_title_format; 153sway_cmd cmd_title_format;
154sway_cmd cmd_unmark; 154sway_cmd cmd_unmark;
155sway_cmd cmd_urgent;
155sway_cmd cmd_workspace; 156sway_cmd cmd_workspace;
156sway_cmd cmd_ws_auto_back_and_forth; 157sway_cmd cmd_ws_auto_back_and_forth;
157sway_cmd cmd_workspace_layout; 158sway_cmd cmd_workspace_layout;
diff --git a/include/sway/criteria.h b/include/sway/criteria.h
index bd3ca0ac..6a8337c5 100644
--- a/include/sway/criteria.h
+++ b/include/sway/criteria.h
@@ -6,9 +6,10 @@
6#include "tree/view.h" 6#include "tree/view.h"
7 7
8enum criteria_type { 8enum criteria_type {
9 CT_COMMAND = 1 << 0, 9 CT_COMMAND = 1 << 0,
10 CT_ASSIGN_OUTPUT = 1 << 1, 10 CT_ASSIGN_OUTPUT = 1 << 1,
11 CT_ASSIGN_WORKSPACE = 1 << 2, 11 CT_ASSIGN_WORKSPACE = 1 << 2,
12 CT_NO_FOCUS = 1 << 3,
12}; 13};
13 14
14struct criteria { 15struct criteria {
diff --git a/include/sway/tree/container.h b/include/sway/tree/container.h
index 04e50fc6..ca7a3288 100644
--- a/include/sway/tree/container.h
+++ b/include/sway/tree/container.h
@@ -316,4 +316,6 @@ void container_floating_move_to(struct sway_container *con,
316 */ 316 */
317void container_set_dirty(struct sway_container *container); 317void container_set_dirty(struct sway_container *container);
318 318
319bool container_has_urgent_child(struct sway_container *container);
320
319#endif 321#endif
diff --git a/include/sway/tree/view.h b/include/sway/tree/view.h
index 21d6403e..e270f851 100644
--- a/include/sway/tree/view.h
+++ b/include/sway/tree/view.h
@@ -69,6 +69,11 @@ struct sway_view {
69 bool border_bottom; 69 bool border_bottom;
70 bool border_left; 70 bool border_left;
71 bool border_right; 71 bool border_right;
72 bool using_csd;
73
74 struct timespec urgent;
75 bool allow_request_urgent;
76 struct wl_event_source *urgent_timer;
72 77
73 bool destroying; 78 bool destroying;
74 79
@@ -305,4 +310,8 @@ void view_update_marks_textures(struct sway_view *view);
305 */ 310 */
306bool view_is_visible(struct sway_view *view); 311bool view_is_visible(struct sway_view *view);
307 312
313void view_set_urgent(struct sway_view *view, bool enable);
314
315bool view_is_urgent(struct sway_view *view);
316
308#endif 317#endif
diff --git a/include/sway/tree/workspace.h b/include/sway/tree/workspace.h
index c72a4ac0..bc95317a 100644
--- a/include/sway/tree/workspace.h
+++ b/include/sway/tree/workspace.h
@@ -10,6 +10,7 @@ struct sway_workspace {
10 struct sway_view *fullscreen; 10 struct sway_view *fullscreen;
11 struct sway_container *floating; 11 struct sway_container *floating;
12 list_t *output_priority; 12 list_t *output_priority;
13 bool urgent;
13}; 14};
14 15
15extern char *prev_workspace_name; 16extern char *prev_workspace_name;
@@ -42,4 +43,7 @@ void workspace_output_add_priority(struct sway_container *workspace,
42 43
43struct sway_container *workspace_output_get_highest_available( 44struct sway_container *workspace_output_get_highest_available(
44 struct sway_container *ws, struct sway_container *exclude); 45 struct sway_container *ws, struct sway_container *exclude);
46
47void workspace_detect_urgent(struct sway_container *workspace);
48
45#endif 49#endif
diff --git a/sway/commands.c b/sway/commands.c
index addd64a6..a3e6a500 100644
--- a/sway/commands.c
+++ b/sway/commands.c
@@ -98,6 +98,7 @@ static struct cmd_handler handlers[] = {
98 { "client.unfocused", cmd_client_unfocused }, 98 { "client.unfocused", cmd_client_unfocused },
99 { "client.urgent", cmd_client_urgent }, 99 { "client.urgent", cmd_client_urgent },
100 { "default_border", cmd_default_border }, 100 { "default_border", cmd_default_border },
101 { "default_floating_border", cmd_default_floating_border },
101 { "exec", cmd_exec }, 102 { "exec", cmd_exec },
102 { "exec_always", cmd_exec_always }, 103 { "exec_always", cmd_exec_always },
103 { "floating_maximum_size", cmd_floating_maximum_size }, 104 { "floating_maximum_size", cmd_floating_maximum_size },
@@ -114,6 +115,7 @@ static struct cmd_handler handlers[] = {
114 { "input", cmd_input }, 115 { "input", cmd_input },
115 { "mode", cmd_mode }, 116 { "mode", cmd_mode },
116 { "mouse_warping", cmd_mouse_warping }, 117 { "mouse_warping", cmd_mouse_warping },
118 { "no_focus", cmd_no_focus },
117 { "output", cmd_output }, 119 { "output", cmd_output },
118 { "seat", cmd_seat }, 120 { "seat", cmd_seat },
119 { "set", cmd_set }, 121 { "set", cmd_set },
@@ -153,6 +155,7 @@ static struct cmd_handler command_handlers[] = {
153 { "swap", cmd_swap }, 155 { "swap", cmd_swap },
154 { "title_format", cmd_title_format }, 156 { "title_format", cmd_title_format },
155 { "unmark", cmd_unmark }, 157 { "unmark", cmd_unmark },
158 { "urgent", cmd_urgent },
156}; 159};
157 160
158static int handler_compare(const void *_a, const void *_b) { 161static int handler_compare(const void *_a, const void *_b) {
diff --git a/sway/commands/default_floating_border.c b/sway/commands/default_floating_border.c
new file mode 100644
index 00000000..1bfc24af
--- /dev/null
+++ b/sway/commands/default_floating_border.c
@@ -0,0 +1,29 @@
1#include "log.h"
2#include "sway/commands.h"
3#include "sway/config.h"
4#include "sway/tree/container.h"
5
6struct cmd_results *cmd_default_floating_border(int argc, char **argv) {
7 struct cmd_results *error = NULL;
8 if ((error = checkarg(argc, "default_floating_border",
9 EXPECTED_AT_LEAST, 1))) {
10 return error;
11 }
12
13 if (strcmp(argv[0], "none") == 0) {
14 config->floating_border = B_NONE;
15 } else if (strcmp(argv[0], "normal") == 0) {
16 config->floating_border = B_NORMAL;
17 } else if (strcmp(argv[0], "pixel") == 0) {
18 config->floating_border = B_PIXEL;
19 } else {
20 return cmd_results_new(CMD_INVALID, "default_floating_border",
21 "Expected 'default_floating_border <none|normal|pixel>' "
22 "or 'default_floating_border <normal|pixel> <px>'");
23 }
24 if (argc == 2) {
25 config->floating_border_thickness = atoi(argv[1]);
26 }
27
28 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
29}
diff --git a/sway/commands/no_focus.c b/sway/commands/no_focus.c
new file mode 100644
index 00000000..61a8de7e
--- /dev/null
+++ b/sway/commands/no_focus.c
@@ -0,0 +1,26 @@
1#define _XOPEN_SOURCE 500
2#include <string.h>
3#include "sway/commands.h"
4#include "sway/criteria.h"
5#include "list.h"
6#include "log.h"
7
8struct cmd_results *cmd_no_focus(int argc, char **argv) {
9 struct cmd_results *error = NULL;
10 if ((error = checkarg(argc, "no_focus", EXPECTED_AT_LEAST, 1))) {
11 return error;
12 }
13
14 char *err_str = NULL;
15 struct criteria *criteria = criteria_parse(argv[0], &err_str);
16 if (!criteria) {
17 error = cmd_results_new(CMD_INVALID, "no_focus", err_str);
18 free(err_str);
19 return error;
20 }
21
22 criteria->type = CT_NO_FOCUS;
23 list_add(config->criteria, criteria);
24
25 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
26}
diff --git a/sway/commands/urgent.c b/sway/commands/urgent.c
new file mode 100644
index 00000000..d199858a
--- /dev/null
+++ b/sway/commands/urgent.c
@@ -0,0 +1,36 @@
1#include "log.h"
2#include "sway/commands.h"
3#include "sway/config.h"
4#include "sway/tree/arrange.h"
5#include "sway/tree/container.h"
6#include "sway/tree/view.h"
7#include "sway/tree/layout.h"
8
9struct cmd_results *cmd_urgent(int argc, char **argv) {
10 struct cmd_results *error = NULL;
11 if ((error = checkarg(argc, "urgent", EXPECTED_EQUAL_TO, 1))) {
12 return error;
13 }
14 struct sway_container *container =
15 config->handler_context.current_container;
16 if (container->type != C_VIEW) {
17 return cmd_results_new(CMD_INVALID, "urgent",
18 "Only views can be urgent");
19 }
20 struct sway_view *view = container->sway_view;
21
22 if (strcmp(argv[0], "enable") == 0) {
23 view_set_urgent(view, true);
24 } else if (strcmp(argv[0], "disable") == 0) {
25 view_set_urgent(view, false);
26 } else if (strcmp(argv[0], "allow") == 0) {
27 view->allow_request_urgent = true;
28 } else if (strcmp(argv[0], "deny") == 0) {
29 view->allow_request_urgent = false;
30 } else {
31 return cmd_results_new(CMD_INVALID, "urgent",
32 "Expected 'urgent <enable|disable|allow|deny>'");
33 }
34
35 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
36}
diff --git a/sway/config.c b/sway/config.c
index f63835bf..c620e4c7 100644
--- a/sway/config.c
+++ b/sway/config.c
@@ -24,6 +24,7 @@
24#include "sway/input/seat.h" 24#include "sway/input/seat.h"
25#include "sway/commands.h" 25#include "sway/commands.h"
26#include "sway/config.h" 26#include "sway/config.h"
27#include "sway/criteria.h"
27#include "sway/tree/arrange.h" 28#include "sway/tree/arrange.h"
28#include "sway/tree/layout.h" 29#include "sway/tree/layout.h"
29#include "sway/tree/workspace.h" 30#include "sway/tree/workspace.h"
@@ -105,7 +106,12 @@ void free_config(struct sway_config *config) {
105 } 106 }
106 list_free(config->seat_configs); 107 list_free(config->seat_configs);
107 } 108 }
108 list_free(config->criteria); 109 if (config->criteria) {
110 for (int i = 0; i < config->criteria->length; ++i) {
111 criteria_destroy(config->criteria->items[i]);
112 }
113 list_free(config->criteria);
114 }
109 list_free(config->no_focus); 115 list_free(config->no_focus);
110 list_free(config->active_bar_modifiers); 116 list_free(config->active_bar_modifiers);
111 list_free(config->config_chain); 117 list_free(config->config_chain);
@@ -474,7 +480,6 @@ static bool load_include_config(const char *path, const char *parent_dir,
474 list_del(config->config_chain, index); 480 list_del(config->config_chain, index);
475 return false; 481 return false;
476 } 482 }
477 free(real_path);
478 483
479 // restore current_config_path 484 // restore current_config_path
480 config->current_config_path = parent_config; 485 config->current_config_path = parent_config;
diff --git a/sway/criteria.c b/sway/criteria.c
index 29a3668b..e2b248de 100644
--- a/sway/criteria.c
+++ b/sway/criteria.c
@@ -37,7 +37,7 @@ void criteria_destroy(struct criteria *criteria) {
37 pcre_free(criteria->con_mark); 37 pcre_free(criteria->con_mark);
38 pcre_free(criteria->window_role); 38 pcre_free(criteria->window_role);
39 free(criteria->workspace); 39 free(criteria->workspace);
40 40 free(criteria->cmdlist);
41 free(criteria->raw); 41 free(criteria->raw);
42 free(criteria); 42 free(criteria);
43} 43}
@@ -46,6 +46,31 @@ static int regex_cmp(const char *item, const pcre *regex) {
46 return pcre_exec(regex, NULL, item, strlen(item), 0, 0, NULL, 0); 46 return pcre_exec(regex, NULL, item, strlen(item), 0, 0, NULL, 0);
47} 47}
48 48
49static int cmp_urgent(const void *_a, const void *_b) {
50 struct sway_view *a = *(void **)_a;
51 struct sway_view *b = *(void **)_b;
52
53 if (a->urgent.tv_sec < b->urgent.tv_sec) {
54 return -1;
55 } else if (a->urgent.tv_sec > b->urgent.tv_sec) {
56 return 1;
57 }
58 if (a->urgent.tv_nsec < b->urgent.tv_nsec) {
59 return -1;
60 } else if (a->urgent.tv_nsec > b->urgent.tv_nsec) {
61 return 1;
62 }
63 return 0;
64}
65
66static void find_urgent_iterator(struct sway_container *swayc, void *data) {
67 if (swayc->type != C_VIEW || !view_is_urgent(swayc->sway_view)) {
68 return;
69 }
70 list_t *urgent_views = data;
71 list_add(urgent_views, swayc->sway_view);
72}
73
49static bool criteria_matches_view(struct criteria *criteria, 74static bool criteria_matches_view(struct criteria *criteria,
50 struct sway_view *view) { 75 struct sway_view *view) {
51 if (criteria->title) { 76 if (criteria->title) {
@@ -133,8 +158,23 @@ static bool criteria_matches_view(struct criteria *criteria,
133 } 158 }
134 159
135 if (criteria->urgent) { 160 if (criteria->urgent) {
136 // TODO 161 if (!view_is_urgent(view)) {
137 return false; 162 return false;
163 }
164 list_t *urgent_views = create_list();
165 container_for_each_descendant_dfs(&root_container,
166 find_urgent_iterator, urgent_views);
167 list_stable_sort(urgent_views, cmp_urgent);
168 struct sway_view *target;
169 if (criteria->urgent == 'o') { // oldest
170 target = urgent_views->items[0];
171 } else { // latest
172 target = urgent_views->items[urgent_views->length - 1];
173 }
174 list_free(urgent_views);
175 if (view != target) {
176 return false;
177 }
138 } 178 }
139 179
140 if (criteria->workspace) { 180 if (criteria->workspace) {
diff --git a/sway/desktop/idle_inhibit_v1.c b/sway/desktop/idle_inhibit_v1.c
index 108a8417..da17d0f2 100644
--- a/sway/desktop/idle_inhibit_v1.c
+++ b/sway/desktop/idle_inhibit_v1.c
@@ -67,6 +67,7 @@ struct sway_idle_inhibit_manager_v1 *sway_idle_inhibit_manager_v1_create(
67 67
68 manager->wlr_manager = wlr_idle_inhibit_v1_create(wl_display); 68 manager->wlr_manager = wlr_idle_inhibit_v1_create(wl_display);
69 if (!manager->wlr_manager) { 69 if (!manager->wlr_manager) {
70 free(manager);
70 return NULL; 71 return NULL;
71 } 72 }
72 manager->idle = idle; 73 manager->idle = idle;
diff --git a/sway/desktop/layer_shell.c b/sway/desktop/layer_shell.c
index 91baa6f8..a7d96717 100644
--- a/sway/desktop/layer_shell.c
+++ b/sway/desktop/layer_shell.c
@@ -325,12 +325,6 @@ void handle_layer_shell_surface(struct wl_listener *listener, void *data) {
325 layer_surface->client_pending.margin.bottom, 325 layer_surface->client_pending.margin.bottom,
326 layer_surface->client_pending.margin.left); 326 layer_surface->client_pending.margin.left);
327 327
328 struct sway_layer_surface *sway_layer =
329 calloc(1, sizeof(struct sway_layer_surface));
330 if (!sway_layer) {
331 return;
332 }
333
334 if (!layer_surface->output) { 328 if (!layer_surface->output) {
335 // Assign last active output 329 // Assign last active output
336 struct sway_container *output = NULL; 330 struct sway_container *output = NULL;
@@ -352,6 +346,12 @@ void handle_layer_shell_surface(struct wl_listener *listener, void *data) {
352 layer_surface->output = output->sway_output->wlr_output; 346 layer_surface->output = output->sway_output->wlr_output;
353 } 347 }
354 348
349 struct sway_layer_surface *sway_layer =
350 calloc(1, sizeof(struct sway_layer_surface));
351 if (!sway_layer) {
352 return;
353 }
354
355 sway_layer->surface_commit.notify = handle_surface_commit; 355 sway_layer->surface_commit.notify = handle_surface_commit;
356 wl_signal_add(&layer_surface->surface->events.commit, 356 wl_signal_add(&layer_surface->surface->events.commit,
357 &sway_layer->surface_commit); 357 &sway_layer->surface_commit);
diff --git a/sway/desktop/render.c b/sway/desktop/render.c
index 17fe823a..4c85e516 100644
--- a/sway/desktop/render.c
+++ b/sway/desktop/render.c
@@ -256,6 +256,10 @@ static void render_view(struct sway_output *output, pixman_region32_t *damage,
256 render_view_surfaces(view, output, damage, view->swayc->alpha); 256 render_view_surfaces(view, output, damage, view->swayc->alpha);
257 } 257 }
258 258
259 if (view->using_csd) {
260 return;
261 }
262
259 struct wlr_box box; 263 struct wlr_box box;
260 float output_scale = output->wlr_output->scale; 264 float output_scale = output->wlr_output->scale;
261 float color[4]; 265 float color[4];
@@ -553,7 +557,11 @@ static void render_container_simple(struct sway_output *output,
553 struct wlr_texture *marks_texture; 557 struct wlr_texture *marks_texture;
554 struct sway_container_state *state = &child->current; 558 struct sway_container_state *state = &child->current;
555 559
556 if (state->focused || parent_focused) { 560 if (view_is_urgent(view)) {
561 colors = &config->border_colors.urgent;
562 title_texture = child->title_urgent;
563 marks_texture = view->marks_urgent;
564 } else if (state->focused || parent_focused) {
557 colors = &config->border_colors.focused; 565 colors = &config->border_colors.focused;
558 title_texture = child->title_focused; 566 title_texture = child->title_focused;
559 marks_texture = view->marks_focused; 567 marks_texture = view->marks_focused;
@@ -567,12 +575,14 @@ static void render_container_simple(struct sway_output *output,
567 marks_texture = view->marks_unfocused; 575 marks_texture = view->marks_unfocused;
568 } 576 }
569 577
570 if (state->border == B_NORMAL) { 578 if (!view->using_csd) {
571 render_titlebar(output, damage, child, state->swayc_x, 579 if (state->border == B_NORMAL) {
572 state->swayc_y, state->swayc_width, colors, 580 render_titlebar(output, damage, child, state->swayc_x,
573 title_texture, marks_texture); 581 state->swayc_y, state->swayc_width, colors,
574 } else { 582 title_texture, marks_texture);
575 render_top_border(output, damage, child, colors); 583 } else {
584 render_top_border(output, damage, child, colors);
585 }
576 } 586 }
577 render_view(output, damage, child, colors); 587 render_view(output, damage, child, colors);
578 } else { 588 } else {
@@ -607,8 +617,14 @@ static void render_container_tabbed(struct sway_output *output,
607 struct border_colors *colors; 617 struct border_colors *colors;
608 struct wlr_texture *title_texture; 618 struct wlr_texture *title_texture;
609 struct wlr_texture *marks_texture; 619 struct wlr_texture *marks_texture;
610 620 bool urgent = view ?
611 if (cstate->focused || parent_focused) { 621 view_is_urgent(view) : container_has_urgent_child(child);
622
623 if (urgent) {
624 colors = &config->border_colors.urgent;
625 title_texture = child->title_urgent;
626 marks_texture = view ? view->marks_urgent : NULL;
627 } else if (cstate->focused || parent_focused) {
612 colors = &config->border_colors.focused; 628 colors = &config->border_colors.focused;
613 title_texture = child->title_focused; 629 title_texture = child->title_focused;
614 marks_texture = view ? view->marks_focused : NULL; 630 marks_texture = view ? view->marks_focused : NULL;
@@ -670,8 +686,14 @@ static void render_container_stacked(struct sway_output *output,
670 struct border_colors *colors; 686 struct border_colors *colors;
671 struct wlr_texture *title_texture; 687 struct wlr_texture *title_texture;
672 struct wlr_texture *marks_texture; 688 struct wlr_texture *marks_texture;
673 689 bool urgent = view ?
674 if (cstate->focused || parent_focused) { 690 view_is_urgent(view) : container_has_urgent_child(child);
691
692 if (urgent) {
693 colors = &config->border_colors.urgent;
694 title_texture = child->title_urgent;
695 marks_texture = view ? view->marks_urgent : NULL;
696 } else if (cstate->focused || parent_focused) {
675 colors = &config->border_colors.focused; 697 colors = &config->border_colors.focused;
676 title_texture = child->title_focused; 698 title_texture = child->title_focused;
677 marks_texture = view ? view->marks_focused : NULL; 699 marks_texture = view ? view->marks_focused : NULL;
@@ -731,7 +753,11 @@ static void render_floating_container(struct sway_output *soutput,
731 struct wlr_texture *title_texture; 753 struct wlr_texture *title_texture;
732 struct wlr_texture *marks_texture; 754 struct wlr_texture *marks_texture;
733 755
734 if (con->current.focused) { 756 if (view_is_urgent(view)) {
757 colors = &config->border_colors.urgent;
758 title_texture = con->title_urgent;
759 marks_texture = view->marks_urgent;
760 } else if (con->current.focused) {
735 colors = &config->border_colors.focused; 761 colors = &config->border_colors.focused;
736 title_texture = con->title_focused; 762 title_texture = con->title_focused;
737 marks_texture = view->marks_focused; 763 marks_texture = view->marks_focused;
@@ -741,12 +767,14 @@ static void render_floating_container(struct sway_output *soutput,
741 marks_texture = view->marks_unfocused; 767 marks_texture = view->marks_unfocused;
742 } 768 }
743 769
744 if (con->current.border == B_NORMAL) { 770 if (!view->using_csd) {
745 render_titlebar(soutput, damage, con, con->current.swayc_x, 771 if (con->current.border == B_NORMAL) {
746 con->current.swayc_y, con->current.swayc_width, colors, 772 render_titlebar(soutput, damage, con, con->current.swayc_x,
747 title_texture, marks_texture); 773 con->current.swayc_y, con->current.swayc_width, colors,
748 } else if (con->current.border != B_NONE) { 774 title_texture, marks_texture);
749 render_top_border(soutput, damage, con, colors); 775 } else if (con->current.border != B_NONE) {
776 render_top_border(soutput, damage, con, colors);
777 }
750 } 778 }
751 render_view(soutput, damage, con, colors); 779 render_view(soutput, damage, con, colors);
752 } else { 780 } else {
diff --git a/sway/desktop/xwayland.c b/sway/desktop/xwayland.c
index 11516673..9df7977d 100644
--- a/sway/desktop/xwayland.c
+++ b/sway/desktop/xwayland.c
@@ -297,6 +297,10 @@ static void handle_commit(struct wl_listener *listener, void *data) {
297 } 297 }
298 298
299 view_damage_from(view); 299 view_damage_from(view);
300
301 if (view->allow_request_urgent) {
302 view_set_urgent(view, (bool)xsurface->hints_urgency);
303 }
300} 304}
301 305
302static void handle_unmap(struct wl_listener *listener, void *data) { 306static void handle_unmap(struct wl_listener *listener, void *data) {
diff --git a/sway/input/seat.c b/sway/input/seat.c
index 74f1375e..12b1fab5 100644
--- a/sway/input/seat.c
+++ b/sway/input/seat.c
@@ -594,6 +594,12 @@ static void seat_send_unfocus(struct sway_container *container,
594 } 594 }
595} 595}
596 596
597static int handle_urgent_timeout(void *data) {
598 struct sway_view *view = data;
599 view_set_urgent(view, false);
600 return 0;
601}
602
597void seat_set_focus_warp(struct sway_seat *seat, 603void seat_set_focus_warp(struct sway_seat *seat,
598 struct sway_container *container, bool warp) { 604 struct sway_container *container, bool warp) {
599 if (seat->focused_layer) { 605 if (seat->focused_layer) {
@@ -649,6 +655,7 @@ void seat_set_focus_warp(struct sway_seat *seat,
649 while (parent) { 655 while (parent) {
650 wl_list_remove(&parent->link); 656 wl_list_remove(&parent->link);
651 wl_list_insert(&seat->focus_stack, &parent->link); 657 wl_list_insert(&seat->focus_stack, &parent->link);
658 container_set_dirty(parent->container);
652 659
653 parent = 660 parent =
654 seat_container_from_container(seat, 661 seat_container_from_container(seat,
@@ -670,6 +677,16 @@ void seat_set_focus_warp(struct sway_seat *seat,
670 } 677 }
671 } 678 }
672 679
680 // If urgent, start a timer to unset it
681 if (container && container->type == C_VIEW &&
682 view_is_urgent(container->sway_view) &&
683 !container->sway_view->urgent_timer) {
684 struct sway_view *view = container->sway_view;
685 view->urgent_timer = wl_event_loop_add_timer(server.wl_event_loop,
686 handle_urgent_timeout, view);
687 wl_event_source_timer_update(view->urgent_timer, 1000);
688 }
689
673 // If we've focused a floating container, bring it to the front. 690 // If we've focused a floating container, bring it to the front.
674 // We do this by putting it at the end of the floating list. 691 // We do this by putting it at the end of the floating list.
675 // This must happen for both the pending and current children lists. 692 // This must happen for both the pending and current children lists.
diff --git a/sway/ipc-json.c b/sway/ipc-json.c
index 3d0e88f0..c49ea47e 100644
--- a/sway/ipc-json.c
+++ b/sway/ipc-json.c
@@ -170,7 +170,8 @@ static void ipc_json_describe_workspace(struct sway_container *workspace,
170 json_object_object_add(object, "output", workspace->parent ? 170 json_object_object_add(object, "output", workspace->parent ?
171 json_object_new_string(workspace->parent->name) : NULL); 171 json_object_new_string(workspace->parent->name) : NULL);
172 json_object_object_add(object, "type", json_object_new_string("workspace")); 172 json_object_object_add(object, "type", json_object_new_string("workspace"));
173 json_object_object_add(object, "urgent", json_object_new_boolean(false)); 173 json_object_object_add(object, "urgent",
174 json_object_new_boolean(workspace->sway_workspace->urgent));
174 json_object_object_add(object, "representation", workspace->formatted_title ? 175 json_object_object_add(object, "representation", workspace->formatted_title ?
175 json_object_new_string(workspace->formatted_title) : NULL); 176 json_object_new_string(workspace->formatted_title) : NULL);
176 177
@@ -196,6 +197,10 @@ static void ipc_json_describe_view(struct sway_container *c, json_object *object
196 json_object_object_add(object, "layout", 197 json_object_object_add(object, "layout",
197 json_object_new_string(ipc_json_layout_description(c->layout))); 198 json_object_new_string(ipc_json_layout_description(c->layout)));
198 } 199 }
200
201 bool urgent = c->type == C_VIEW ?
202 view_is_urgent(c->sway_view) : container_has_urgent_child(c);
203 json_object_object_add(object, "urgent", json_object_new_boolean(urgent));
199} 204}
200 205
201static void focus_inactive_children_iterator(struct sway_container *c, void *data) { 206static void focus_inactive_children_iterator(struct sway_container *c, void *data) {
diff --git a/sway/meson.build b/sway/meson.build
index f878450d..c58d3470 100644
--- a/sway/meson.build
+++ b/sway/meson.build
@@ -35,6 +35,7 @@ sway_sources = files(
35 'commands/border.c', 35 'commands/border.c',
36 'commands/client.c', 36 'commands/client.c',
37 'commands/default_border.c', 37 'commands/default_border.c',
38 'commands/default_floating_border.c',
38 'commands/default_orientation.c', 39 'commands/default_orientation.c',
39 'commands/exit.c', 40 'commands/exit.c',
40 'commands/exec.c', 41 'commands/exec.c',
@@ -59,6 +60,7 @@ sway_sources = files(
59 'commands/mode.c', 60 'commands/mode.c',
60 'commands/mouse_warping.c', 61 'commands/mouse_warping.c',
61 'commands/move.c', 62 'commands/move.c',
63 'commands/no_focus.c',
62 'commands/output.c', 64 'commands/output.c',
63 'commands/reload.c', 65 'commands/reload.c',
64 'commands/rename.c', 66 'commands/rename.c',
@@ -76,6 +78,7 @@ sway_sources = files(
76 'commands/swap.c', 78 'commands/swap.c',
77 'commands/title_format.c', 79 'commands/title_format.c',
78 'commands/unmark.c', 80 'commands/unmark.c',
81 'commands/urgent.c',
79 'commands/workspace.c', 82 'commands/workspace.c',
80 'commands/workspace_layout.c', 83 'commands/workspace_layout.c',
81 'commands/ws_auto_back_and_forth.c', 84 'commands/ws_auto_back_and_forth.c',
diff --git a/sway/sway.5.scd b/sway/sway.5.scd
index c6eb5e6d..d369d7b6 100644
--- a/sway/sway.5.scd
+++ b/sway/sway.5.scd
@@ -499,6 +499,11 @@ config after the others, or it will be matched instead of the others.
499 *unmark* will remove _identifier_ from the list of current marks on a 499 *unmark* will remove _identifier_ from the list of current marks on a
500 window. If _identifier_ is omitted, all marks are removed. 500 window. If _identifier_ is omitted, all marks are removed.
501 501
502*urgent* enable|disable|allow|deny
503 Using _enable_ or _disable_ manually sets or unsets the window's urgent
504 state. Using _allow_ or _deny_ controls the window's ability to set itself
505 as urgent. By default, windows are allowed to set their own urgency.
506
502*workspace* [number] <name> 507*workspace* [number] <name>
503 Switches to the specified workspace. The string "number" is optional and is 508 Switches to the specified workspace. The string "number" is optional and is
504 used to sort workspaces. 509 used to sort workspaces.
diff --git a/sway/tree/container.c b/sway/tree/container.c
index 35f67cce..3f9d701a 100644
--- a/sway/tree/container.c
+++ b/sway/tree/container.c
@@ -674,16 +674,23 @@ struct sway_container *floating_container_at(double lx, double ly,
674void container_for_each_descendant_dfs(struct sway_container *container, 674void container_for_each_descendant_dfs(struct sway_container *container,
675 void (*f)(struct sway_container *container, void *data), 675 void (*f)(struct sway_container *container, void *data),
676 void *data) { 676 void *data) {
677 if (container) { 677 if (!container) {
678 if (container->children) { 678 return;
679 for (int i = 0; i < container->children->length; ++i) { 679 }
680 struct sway_container *child = 680 if (container->children) {
681 container->children->items[i]; 681 for (int i = 0; i < container->children->length; ++i) {
682 container_for_each_descendant_dfs(child, f, data); 682 struct sway_container *child = container->children->items[i];
683 } 683 container_for_each_descendant_dfs(child, f, data);
684 } 684 }
685 f(container, data);
686 } 685 }
686 if (container->type == C_WORKSPACE) {
687 struct sway_container *floating = container->sway_workspace->floating;
688 for (int i = 0; i < floating->children->length; ++i) {
689 struct sway_container *child = floating->children->items[i];
690 container_for_each_descendant_dfs(child, f, data);
691 }
692 }
693 f(container, data);
687} 694}
688 695
689void container_for_each_descendant_bfs(struct sway_container *con, 696void container_for_each_descendant_bfs(struct sway_container *con,
@@ -960,9 +967,14 @@ void container_set_geometry_from_floating_view(struct sway_container *con) {
960 return; 967 return;
961 } 968 }
962 struct sway_view *view = con->sway_view; 969 struct sway_view *view = con->sway_view;
963 size_t border_width = view->border_thickness * (view->border != B_NONE); 970 size_t border_width = 0;
964 size_t top = 971 size_t top = 0;
965 view->border == B_NORMAL ? container_titlebar_height() : border_width; 972
973 if (!view->using_csd) {
974 border_width = view->border_thickness * (view->border != B_NONE);
975 top = view->border == B_NORMAL ?
976 container_titlebar_height() : border_width;
977 }
966 978
967 con->x = view->x - border_width; 979 con->x = view->x - border_width;
968 con->y = view->y - top; 980 con->y = view->y - top;
@@ -1063,6 +1075,8 @@ void container_floating_move_to(struct sway_container *con,
1063 container_add_child(new_workspace->sway_workspace->floating, con); 1075 container_add_child(new_workspace->sway_workspace->floating, con);
1064 arrange_windows(old_workspace); 1076 arrange_windows(old_workspace);
1065 arrange_windows(new_workspace); 1077 arrange_windows(new_workspace);
1078 workspace_detect_urgent(old_workspace);
1079 workspace_detect_urgent(new_workspace);
1066 } 1080 }
1067} 1081}
1068 1082
@@ -1073,3 +1087,12 @@ void container_set_dirty(struct sway_container *container) {
1073 container->dirty = true; 1087 container->dirty = true;
1074 list_add(server.dirty_containers, container); 1088 list_add(server.dirty_containers, container);
1075} 1089}
1090
1091static bool find_urgent_iterator(struct sway_container *con,
1092 void *data) {
1093 return con->type == C_VIEW && view_is_urgent(con->sway_view);
1094}
1095
1096bool container_has_urgent_child(struct sway_container *container) {
1097 return container_find(container, find_urgent_iterator, NULL);
1098}
diff --git a/sway/tree/layout.c b/sway/tree/layout.c
index 54ddb3f9..197a2fc8 100644
--- a/sway/tree/layout.c
+++ b/sway/tree/layout.c
@@ -225,6 +225,15 @@ void container_move_to(struct sway_container *container,
225 } 225 }
226 } 226 }
227 } 227 }
228 // Update workspace urgent state
229 struct sway_container *old_workspace = old_parent;
230 if (old_workspace->type != C_WORKSPACE) {
231 old_workspace = container_parent(old_workspace, C_WORKSPACE);
232 }
233 if (new_workspace != old_workspace) {
234 workspace_detect_urgent(new_workspace);
235 workspace_detect_urgent(old_workspace);
236 }
228} 237}
229 238
230static bool sway_dir_to_wlr(enum movement_direction dir, 239static bool sway_dir_to_wlr(enum movement_direction dir,
@@ -548,6 +557,8 @@ void container_move(struct sway_container *container,
548 } 557 }
549 if (last_ws && next_ws && last_ws != next_ws) { 558 if (last_ws && next_ws && last_ws != next_ws) {
550 ipc_event_workspace(last_ws, container, "focus"); 559 ipc_event_workspace(last_ws, container, "focus");
560 workspace_detect_urgent(last_ws);
561 workspace_detect_urgent(next_ws);
551 } 562 }
552} 563}
553 564
diff --git a/sway/tree/view.c b/sway/tree/view.c
index bf380d98..fc31699c 100644
--- a/sway/tree/view.c
+++ b/sway/tree/view.c
@@ -25,6 +25,7 @@ void view_init(struct sway_view *view, enum sway_view_type type,
25 view->impl = impl; 25 view->impl = impl;
26 view->executed_criteria = create_list(); 26 view->executed_criteria = create_list();
27 view->marks = create_list(); 27 view->marks = create_list();
28 view->allow_request_urgent = true;
28 wl_signal_init(&view->events.unmap); 29 wl_signal_init(&view->events.unmap);
29} 30}
30 31
@@ -315,11 +316,15 @@ void view_set_activated(struct sway_view *view, bool activated) {
315} 316}
316 317
317void view_set_tiled(struct sway_view *view, bool tiled) { 318void view_set_tiled(struct sway_view *view, bool tiled) {
318 bool csd = true; 319 if (!tiled) {
319 if (view->impl->has_client_side_decorations) { 320 view->using_csd = true;
320 csd = view->impl->has_client_side_decorations(view); 321 if (view->impl->has_client_side_decorations) {
322 view->using_csd = view->impl->has_client_side_decorations(view);
323 }
324 } else {
325 view->using_csd = false;
321 } 326 }
322 view->border = tiled || !csd ? config->border : B_NONE; 327
323 if (view->impl->set_tiled) { 328 if (view->impl->set_tiled) {
324 view->impl->set_tiled(view, tiled); 329 view->impl->set_tiled(view, tiled);
325 } 330 }
@@ -504,20 +509,38 @@ void view_execute_criteria(struct sway_view *view) {
504 } 509 }
505 wlr_log(WLR_DEBUG, "for_window '%s' matches view %p, cmd: '%s'", 510 wlr_log(WLR_DEBUG, "for_window '%s' matches view %p, cmd: '%s'",
506 criteria->raw, view, criteria->cmdlist); 511 criteria->raw, view, criteria->cmdlist);
512 seat_set_focus(seat, view->swayc);
507 list_add(view->executed_criteria, criteria); 513 list_add(view->executed_criteria, criteria);
508 struct cmd_results *res = execute_command(criteria->cmdlist, NULL); 514 struct cmd_results *res = execute_command(criteria->cmdlist, NULL);
509 if (res->status != CMD_SUCCESS) { 515 if (res->status != CMD_SUCCESS) {
510 wlr_log(WLR_ERROR, "Command '%s' failed: %s", res->input, res->error); 516 wlr_log(WLR_ERROR, "Command '%s' failed: %s", res->input, res->error);
511 } 517 }
512 free_cmd_results(res); 518 free_cmd_results(res);
513 // view must be focused for commands to affect it,
514 // so always refocus in-between command lists
515 seat_set_focus(seat, view->swayc);
516 } 519 }
517 list_free(criterias); 520 list_free(criterias);
518 seat_set_focus(seat, prior_focus); 521 seat_set_focus(seat, prior_focus);
519} 522}
520 523
524static bool should_focus(struct sway_view *view) {
525 // If the view is the only one in the focused workspace, it'll get focus
526 // regardless of any no_focus criteria.
527 struct sway_container *parent = view->swayc->parent;
528 struct sway_seat *seat = input_manager_current_seat(input_manager);
529 if (parent->type == C_WORKSPACE && seat_get_focus(seat) == parent) {
530 size_t num_children = parent->children->length +
531 parent->sway_workspace->floating->children->length;
532 if (num_children == 1) {
533 return true;
534 }
535 }
536
537 // Check no_focus criteria
538 list_t *criterias = criteria_for_view(view, CT_NO_FOCUS);
539 size_t len = criterias->length;
540 list_free(criterias);
541 return len == 0;
542}
543
521void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) { 544void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) {
522 if (!sway_assert(view->surface == NULL, "cannot map mapped view")) { 545 if (!sway_assert(view->surface == NULL, "cannot map mapped view")) {
523 return; 546 return;
@@ -554,8 +577,6 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) {
554 577
555 view->surface = wlr_surface; 578 view->surface = wlr_surface;
556 view->swayc = cont; 579 view->swayc = cont;
557 view->border = config->border;
558 view->border_thickness = config->border_thickness;
559 580
560 view_init_subsurfaces(view, wlr_surface); 581 view_init_subsurfaces(view, wlr_surface);
561 wl_signal_add(&wlr_surface->events.new_subsurface, 582 wl_signal_add(&wlr_surface->events.new_subsurface,
@@ -566,14 +587,20 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) {
566 view->container_reparent.notify = view_handle_container_reparent; 587 view->container_reparent.notify = view_handle_container_reparent;
567 588
568 if (view->impl->wants_floating && view->impl->wants_floating(view)) { 589 if (view->impl->wants_floating && view->impl->wants_floating(view)) {
590 view->border = config->floating_border;
591 view->border_thickness = config->floating_border_thickness;
569 container_set_floating(view->swayc, true); 592 container_set_floating(view->swayc, true);
570 } else { 593 } else {
594 view->border = config->border;
595 view->border_thickness = config->border_thickness;
571 view_set_tiled(view, true); 596 view_set_tiled(view, true);
572 } 597 }
573 598
574 input_manager_set_focus(input_manager, cont); 599 if (should_focus(view)) {
575 if (workspace) { 600 input_manager_set_focus(input_manager, cont);
576 workspace_switch(workspace); 601 if (workspace) {
602 workspace_switch(workspace);
603 }
577 } 604 }
578 605
579 view_update_title(view, false); 606 view_update_title(view, false);
@@ -589,16 +616,26 @@ void view_unmap(struct sway_view *view) {
589 wl_list_remove(&view->surface_new_subsurface.link); 616 wl_list_remove(&view->surface_new_subsurface.link);
590 wl_list_remove(&view->container_reparent.link); 617 wl_list_remove(&view->container_reparent.link);
591 618
619 if (view->urgent_timer) {
620 wl_event_source_remove(view->urgent_timer);
621 view->urgent_timer = NULL;
622 }
623
624 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE);
625
626 struct sway_container *parent;
592 if (view->is_fullscreen) { 627 if (view->is_fullscreen) {
593 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE);
594 ws->sway_workspace->fullscreen = NULL; 628 ws->sway_workspace->fullscreen = NULL;
595 container_destroy(view->swayc); 629 parent = container_destroy(view->swayc);
596 630
597 arrange_windows(ws->parent); 631 arrange_windows(ws->parent);
598 } else { 632 } else {
599 struct sway_container *parent = container_destroy(view->swayc); 633 parent = container_destroy(view->swayc);
600 arrange_windows(parent); 634 arrange_windows(parent);
601 } 635 }
636 if (parent->type >= C_WORKSPACE) { // if the workspace still exists
637 workspace_detect_urgent(ws);
638 }
602 transaction_commit_dirty(); 639 transaction_commit_dirty();
603 view->surface = NULL; 640 view->surface = NULL;
604} 641}
@@ -1047,3 +1084,32 @@ bool view_is_visible(struct sway_view *view) {
1047 } 1084 }
1048 return true; 1085 return true;
1049} 1086}
1087
1088void view_set_urgent(struct sway_view *view, bool enable) {
1089 if (view_is_urgent(view) == enable) {
1090 return;
1091 }
1092 if (enable) {
1093 struct sway_seat *seat = input_manager_current_seat(input_manager);
1094 if (seat_get_focus(seat) == view->swayc) {
1095 return;
1096 }
1097 clock_gettime(CLOCK_MONOTONIC, &view->urgent);
1098 } else {
1099 view->urgent = (struct timespec){ 0 };
1100 if (view->urgent_timer) {
1101 wl_event_source_remove(view->urgent_timer);
1102 view->urgent_timer = NULL;
1103 }
1104 }
1105 container_damage_whole(view->swayc);
1106
1107 ipc_event_window(view->swayc, "urgent");
1108
1109 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE);
1110 workspace_detect_urgent(ws);
1111}
1112
1113bool view_is_urgent(struct sway_view *view) {
1114 return view->urgent.tv_sec || view->urgent.tv_nsec;
1115}
diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c
index 2a2d834a..622f01ec 100644
--- a/sway/tree/workspace.c
+++ b/sway/tree/workspace.c
@@ -11,6 +11,7 @@
11#include "sway/ipc-server.h" 11#include "sway/ipc-server.h"
12#include "sway/tree/arrange.h" 12#include "sway/tree/arrange.h"
13#include "sway/tree/container.h" 13#include "sway/tree/container.h"
14#include "sway/tree/view.h"
14#include "sway/tree/workspace.h" 15#include "sway/tree/workspace.h"
15#include "list.h" 16#include "list.h"
16#include "log.h" 17#include "log.h"
@@ -518,3 +519,13 @@ struct sway_container *workspace_output_get_highest_available(
518 519
519 return NULL; 520 return NULL;
520} 521}
522
523void workspace_detect_urgent(struct sway_container *workspace) {
524 bool new_urgent = container_has_urgent_child(workspace);
525
526 if (workspace->sway_workspace->urgent != new_urgent) {
527 workspace->sway_workspace->urgent = new_urgent;
528 ipc_event_workspace(NULL, workspace, "urgent");
529 container_damage_whole(workspace);
530 }
531}
diff --git a/swaybar/ipc.c b/swaybar/ipc.c
index 08531f2a..c2d05920 100644
--- a/swaybar/ipc.c
+++ b/swaybar/ipc.c
@@ -115,6 +115,18 @@ static void ipc_parse_colors(
115 config->colors.inactive_workspace.text = parse_color( 115 config->colors.inactive_workspace.text = parse_color(
116 json_object_get_string(inactive_workspace_text)); 116 json_object_get_string(inactive_workspace_text));
117 } 117 }
118 if (urgent_workspace_border) {
119 config->colors.urgent_workspace.border = parse_color(
120 json_object_get_string(urgent_workspace_border));
121 }
122 if (urgent_workspace_bg) {
123 config->colors.urgent_workspace.background = parse_color(
124 json_object_get_string(urgent_workspace_bg));
125 }
126 if (urgent_workspace_text) {
127 config->colors.urgent_workspace.text = parse_color(
128 json_object_get_string(urgent_workspace_text));
129 }
118 if (binding_mode_border) { 130 if (binding_mode_border) {
119 config->colors.binding_mode.border = parse_color( 131 config->colors.binding_mode.border = parse_color(
120 json_object_get_string(binding_mode_border)); 132 json_object_get_string(binding_mode_border));
diff --git a/swayidle/main.c b/swayidle/main.c
index 64e45036..678d622f 100644
--- a/swayidle/main.c
+++ b/swayidle/main.c
@@ -1,22 +1,21 @@
1#define _XOPEN_SOURCE 500 1#define _XOPEN_SOURCE 500
2#include <errno.h>
2#include <getopt.h> 3#include <getopt.h>
3#include <signal.h>
4#include <pthread.h> 4#include <pthread.h>
5#include <signal.h>
5#include <stdio.h> 6#include <stdio.h>
6#include <stdlib.h> 7#include <stdlib.h>
7#include <errno.h>
8#include <string.h> 8#include <string.h>
9#include <sys/wait.h> 9#include <sys/wait.h>
10#include <unistd.h> 10#include <unistd.h>
11#include <wayland-client-protocol.h> 11#include <wayland-client-protocol.h>
12#include <wayland-client.h> 12#include <wayland-client.h>
13#include <wayland-server.h>
13#include <wayland-util.h> 14#include <wayland-util.h>
14#include <wlr/config.h> 15#include <wlr/config.h>
15#include <wlr/util/log.h> 16#include <wlr/util/log.h>
16#include <wlr/types/wlr_output_layout.h>
17#include <wlr/types/wlr_output.h>
18#include "idle-client-protocol.h"
19#include "config.h" 17#include "config.h"
18#include "idle-client-protocol.h"
20#include "list.h" 19#include "list.h"
21#ifdef SWAY_IDLE_HAS_SYSTEMD 20#ifdef SWAY_IDLE_HAS_SYSTEMD
22#include <systemd/sd-bus.h> 21#include <systemd/sd-bus.h>
@@ -36,7 +35,6 @@ struct swayidle_state {
36 struct wl_display *display; 35 struct wl_display *display;
37 struct org_kde_kwin_idle_timeout *idle_timer; 36 struct org_kde_kwin_idle_timeout *idle_timer;
38 struct org_kde_kwin_idle_timeout *lock_timer; 37 struct org_kde_kwin_idle_timeout *lock_timer;
39 struct wlr_output_layout *layout;
40 struct wl_event_loop *event_loop; 38 struct wl_event_loop *event_loop;
41 list_t *timeout_cmds; 39 list_t *timeout_cmds;
42} state; 40} state;
@@ -165,7 +163,7 @@ static int dbus_event(int fd, uint32_t mask, void *data) {
165 163
166void setup_sleep_listener() { 164void setup_sleep_listener() {
167 struct sd_bus *bus; 165 struct sd_bus *bus;
168 166
169 int ret = sd_bus_default_system(&bus); 167 int ret = sd_bus_default_system(&bus);
170 if (ret < 0) { 168 if (ret < 0) {
171 wlr_log(WLR_ERROR, "Failed to open D-Bus connection: %s", 169 wlr_log(WLR_ERROR, "Failed to open D-Bus connection: %s",
@@ -360,7 +358,7 @@ static int display_event(int fd, uint32_t mask, void *data) {
360 if (wl_display_dispatch(state.display) < 0) { 358 if (wl_display_dispatch(state.display) < 0) {
361 wlr_log_errno(WLR_ERROR, "wl_display_dispatch failed, exiting"); 359 wlr_log_errno(WLR_ERROR, "wl_display_dispatch failed, exiting");
362 sway_terminate(0); 360 sway_terminate(0);
363 }; 361 }
364 return 0; 362 return 0;
365} 363}
366 364
@@ -397,7 +395,6 @@ int main(int argc, char *argv[]) {
397 struct wl_registry *registry = wl_display_get_registry(state.display); 395 struct wl_registry *registry = wl_display_get_registry(state.display);
398 wl_registry_add_listener(registry, &registry_listener, NULL); 396 wl_registry_add_listener(registry, &registry_listener, NULL);
399 wl_display_roundtrip(state.display); 397 wl_display_roundtrip(state.display);
400 state.layout = wlr_output_layout_create();
401 state.event_loop = wl_event_loop_create(); 398 state.event_loop = wl_event_loop_create();
402 399
403 if (idle_manager == NULL) { 400 if (idle_manager == NULL) {
diff --git a/swaylock/main.c b/swaylock/main.c
index faebc757..ae5b86b9 100644
--- a/swaylock/main.c
+++ b/swaylock/main.c
@@ -21,6 +21,7 @@
21#include "pool-buffer.h" 21#include "pool-buffer.h"
22#include "cairo.h" 22#include "cairo.h"
23#include "log.h" 23#include "log.h"
24#include "readline.h"
24#include "stringop.h" 25#include "stringop.h"
25#include "util.h" 26#include "util.h"
26#include "wlr-input-inhibitor-unstable-v1-client-protocol.h" 27#include "wlr-input-inhibitor-unstable-v1-client-protocol.h"
@@ -412,15 +413,14 @@ static void set_default_colors(struct swaylock_colors *colors) {
412 }; 413 };
413} 414}
414 415
415static struct swaylock_state state; 416enum line_mode {
416 417 LM_LINE,
417int main(int argc, char **argv) { 418 LM_INSIDE,
418 enum line_mode { 419 LM_RING,
419 LM_LINE, 420};
420 LM_INSIDE,
421 LM_RING,
422 };
423 421
422static int parse_options(int argc, char **argv, struct swaylock_state *state,
423 enum line_mode *line_mode) {
424 enum long_option_codes { 424 enum long_option_codes {
425 LO_BS_HL_COLOR = 256, 425 LO_BS_HL_COLOR = 256,
426 LO_FONT, 426 LO_FONT,
@@ -447,6 +447,7 @@ int main(int argc, char **argv) {
447 }; 447 };
448 448
449 static struct option long_options[] = { 449 static struct option long_options[] = {
450 {"config", required_argument, NULL, 'C'},
450 {"color", required_argument, NULL, 'c'}, 451 {"color", required_argument, NULL, 'c'},
451 {"ignore-empty-password", no_argument, NULL, 'e'}, 452 {"ignore-empty-password", no_argument, NULL, 'e'},
452 {"daemonize", no_argument, NULL, 'f'}, 453 {"daemonize", no_argument, NULL, 'f'},
@@ -487,6 +488,8 @@ int main(int argc, char **argv) {
487 const char usage[] = 488 const char usage[] =
488 "Usage: swaylock [options...]\n" 489 "Usage: swaylock [options...]\n"
489 "\n" 490 "\n"
491 " -C, --config <config_file> "
492 "Path to the config file.\n"
490 " -c, --color <color> " 493 " -c, --color <color> "
491 "Turn the screen into the given color instead of white.\n" 494 "Turn the screen into the given color instead of white.\n"
492 " -e, --ignore-empty-password " 495 " -e, --ignore-empty-password "
@@ -559,58 +562,48 @@ int main(int argc, char **argv) {
559 "\n" 562 "\n"
560 "All <color> options are of the form <rrggbb[aa]>.\n"; 563 "All <color> options are of the form <rrggbb[aa]>.\n";
561 564
562 enum line_mode line_mode = LM_LINE;
563 state.args = (struct swaylock_args){
564 .mode = BACKGROUND_MODE_SOLID_COLOR,
565 .font = strdup("sans-serif"),
566 .radius = 50,
567 .thickness = 10,
568 .ignore_empty = false,
569 .show_indicator = true,
570 };
571 wl_list_init(&state.images);
572 set_default_colors(&state.args.colors);
573
574 wlr_log_init(WLR_DEBUG, NULL);
575
576 int c; 565 int c;
566 optind = 1;
577 while (1) { 567 while (1) {
578 int opt_idx = 0; 568 int opt_idx = 0;
579 c = getopt_long(argc, argv, "c:efhi:nrs:tuv", long_options, &opt_idx); 569 c = getopt_long(argc, argv, "c:efhi:nrs:tuvC:", long_options, &opt_idx);
580 if (c == -1) { 570 if (c == -1) {
581 break; 571 break;
582 } 572 }
583 switch (c) { 573 switch (c) {
574 case 'C':
575 // Config file. This will have already been handled so just ignore.
576 break;
584 case 'c': 577 case 'c':
585 state.args.colors.background = parse_color(optarg); 578 state->args.colors.background = parse_color(optarg);
586 state.args.mode = BACKGROUND_MODE_SOLID_COLOR; 579 state->args.mode = BACKGROUND_MODE_SOLID_COLOR;
587 break; 580 break;
588 case 'e': 581 case 'e':
589 state.args.ignore_empty = true; 582 state->args.ignore_empty = true;
590 break; 583 break;
591 case 'f': 584 case 'f':
592 state.args.daemonize = true; 585 state->args.daemonize = true;
593 break; 586 break;
594 case 'i': 587 case 'i':
595 load_image(optarg, &state); 588 load_image(optarg, state);
596 break; 589 break;
597 case 'n': 590 case 'n':
598 line_mode = LM_INSIDE; 591 *line_mode = LM_INSIDE;
599 break; 592 break;
600 case 'r': 593 case 'r':
601 line_mode = LM_RING; 594 *line_mode = LM_RING;
602 break; 595 break;
603 case 's': 596 case 's':
604 state.args.mode = parse_background_mode(optarg); 597 state->args.mode = parse_background_mode(optarg);
605 if (state.args.mode == BACKGROUND_MODE_INVALID) { 598 if (state->args.mode == BACKGROUND_MODE_INVALID) {
606 return 1; 599 return 1;
607 } 600 }
608 break; 601 break;
609 case 't': 602 case 't':
610 state.args.mode = BACKGROUND_MODE_TILE; 603 state->args.mode = BACKGROUND_MODE_TILE;
611 break; 604 break;
612 case 'u': 605 case 'u':
613 state.args.show_indicator = false; 606 state->args.show_indicator = false;
614 break; 607 break;
615 case 'v': 608 case 'v':
616#if defined SWAY_GIT_VERSION && defined SWAY_GIT_BRANCH && defined SWAY_VERSION_DATE 609#if defined SWAY_GIT_VERSION && defined SWAY_GIT_BRANCH && defined SWAY_VERSION_DATE
@@ -621,71 +614,71 @@ int main(int argc, char **argv) {
621#endif 614#endif
622 return 0; 615 return 0;
623 case LO_BS_HL_COLOR: 616 case LO_BS_HL_COLOR:
624 state.args.colors.bs_highlight = parse_color(optarg); 617 state->args.colors.bs_highlight = parse_color(optarg);
625 break; 618 break;
626 case LO_FONT: 619 case LO_FONT:
627 free(state.args.font); 620 free(state->args.font);
628 state.args.font = strdup(optarg); 621 state->args.font = strdup(optarg);
629 break; 622 break;
630 case LO_IND_RADIUS: 623 case LO_IND_RADIUS:
631 state.args.radius = strtol(optarg, NULL, 0); 624 state->args.radius = strtol(optarg, NULL, 0);
632 break; 625 break;
633 case LO_IND_THICKNESS: 626 case LO_IND_THICKNESS:
634 state.args.thickness = strtol(optarg, NULL, 0); 627 state->args.thickness = strtol(optarg, NULL, 0);
635 break; 628 break;
636 case LO_INSIDE_COLOR: 629 case LO_INSIDE_COLOR:
637 state.args.colors.inside.input = parse_color(optarg); 630 state->args.colors.inside.input = parse_color(optarg);
638 break; 631 break;
639 case LO_INSIDE_CLEAR_COLOR: 632 case LO_INSIDE_CLEAR_COLOR:
640 state.args.colors.inside.cleared = parse_color(optarg); 633 state->args.colors.inside.cleared = parse_color(optarg);
641 break; 634 break;
642 case LO_INSIDE_VER_COLOR: 635 case LO_INSIDE_VER_COLOR:
643 state.args.colors.inside.verifying = parse_color(optarg); 636 state->args.colors.inside.verifying = parse_color(optarg);
644 break; 637 break;
645 case LO_INSIDE_WRONG_COLOR: 638 case LO_INSIDE_WRONG_COLOR:
646 state.args.colors.inside.wrong = parse_color(optarg); 639 state->args.colors.inside.wrong = parse_color(optarg);
647 break; 640 break;
648 case LO_KEY_HL_COLOR: 641 case LO_KEY_HL_COLOR:
649 state.args.colors.key_highlight = parse_color(optarg); 642 state->args.colors.key_highlight = parse_color(optarg);
650 break; 643 break;
651 case LO_LINE_COLOR: 644 case LO_LINE_COLOR:
652 state.args.colors.line.input = parse_color(optarg); 645 state->args.colors.line.input = parse_color(optarg);
653 break; 646 break;
654 case LO_LINE_CLEAR_COLOR: 647 case LO_LINE_CLEAR_COLOR:
655 state.args.colors.line.cleared = parse_color(optarg); 648 state->args.colors.line.cleared = parse_color(optarg);
656 break; 649 break;
657 case LO_LINE_VER_COLOR: 650 case LO_LINE_VER_COLOR:
658 state.args.colors.line.verifying = parse_color(optarg); 651 state->args.colors.line.verifying = parse_color(optarg);
659 break; 652 break;
660 case LO_LINE_WRONG_COLOR: 653 case LO_LINE_WRONG_COLOR:
661 state.args.colors.line.wrong = parse_color(optarg); 654 state->args.colors.line.wrong = parse_color(optarg);
662 break; 655 break;
663 case LO_RING_COLOR: 656 case LO_RING_COLOR:
664 state.args.colors.ring.input = parse_color(optarg); 657 state->args.colors.ring.input = parse_color(optarg);
665 break; 658 break;
666 case LO_RING_CLEAR_COLOR: 659 case LO_RING_CLEAR_COLOR:
667 state.args.colors.ring.cleared = parse_color(optarg); 660 state->args.colors.ring.cleared = parse_color(optarg);
668 break; 661 break;
669 case LO_RING_VER_COLOR: 662 case LO_RING_VER_COLOR:
670 state.args.colors.ring.verifying = parse_color(optarg); 663 state->args.colors.ring.verifying = parse_color(optarg);
671 break; 664 break;
672 case LO_RING_WRONG_COLOR: 665 case LO_RING_WRONG_COLOR:
673 state.args.colors.ring.wrong = parse_color(optarg); 666 state->args.colors.ring.wrong = parse_color(optarg);
674 break; 667 break;
675 case LO_SEP_COLOR: 668 case LO_SEP_COLOR:
676 state.args.colors.separator = parse_color(optarg); 669 state->args.colors.separator = parse_color(optarg);
677 break; 670 break;
678 case LO_TEXT_COLOR: 671 case LO_TEXT_COLOR:
679 state.args.colors.text.input = parse_color(optarg); 672 state->args.colors.text.input = parse_color(optarg);
680 break; 673 break;
681 case LO_TEXT_CLEAR_COLOR: 674 case LO_TEXT_CLEAR_COLOR:
682 state.args.colors.text.cleared = parse_color(optarg); 675 state->args.colors.text.cleared = parse_color(optarg);
683 break; 676 break;
684 case LO_TEXT_VER_COLOR: 677 case LO_TEXT_VER_COLOR:
685 state.args.colors.text.verifying = parse_color(optarg); 678 state->args.colors.text.verifying = parse_color(optarg);
686 break; 679 break;
687 case LO_TEXT_WRONG_COLOR: 680 case LO_TEXT_WRONG_COLOR:
688 state.args.colors.text.wrong = parse_color(optarg); 681 state->args.colors.text.wrong = parse_color(optarg);
689 break; 682 break;
690 default: 683 default:
691 fprintf(stderr, "%s", usage); 684 fprintf(stderr, "%s", usage);
@@ -693,6 +686,143 @@ int main(int argc, char **argv) {
693 } 686 }
694 } 687 }
695 688
689 return 0;
690}
691
692static bool file_exists(const char *path) {
693 return path && access(path, R_OK) != -1;
694}
695
696static char *get_config_path(void) {
697 static const char *config_paths[] = {
698 "$HOME/.swaylock/config",
699 "$XDG_CONFIG_HOME/swaylock/config",
700 SYSCONFDIR "/swaylock/config",
701 };
702
703 if (!getenv("XDG_CONFIG_HOME")) {
704 char *home = getenv("HOME");
705 char *config_home = malloc(strlen(home) + strlen("/.config") + 1);
706 if (!config_home) {
707 wlr_log(WLR_ERROR, "Unable to allocate $HOME/.config");
708 } else {
709 strcpy(config_home, home);
710 strcat(config_home, "/.config");
711 setenv("XDG_CONFIG_HOME", config_home, 1);
712 wlr_log(WLR_DEBUG, "Set XDG_CONFIG_HOME to %s", config_home);
713 free(config_home);
714 }
715 }
716
717 wordexp_t p;
718 char *path;
719 for (size_t i = 0; i < sizeof(config_paths) / sizeof(char *); ++i) {
720 if (wordexp(config_paths[i], &p, 0) == 0) {
721 path = strdup(p.we_wordv[0]);
722 wordfree(&p);
723 if (file_exists(path)) {
724 return path;
725 }
726 free(path);
727 }
728 }
729
730 return NULL;
731}
732
733static int load_config(char *path, struct swaylock_state *state,
734 enum line_mode *line_mode) {
735 FILE *config = fopen(path, "r");
736 if (!config) {
737 wlr_log(WLR_ERROR, "Failed to read config. Running without it.");
738 return 0;
739 }
740 char *line;
741 int line_number = 0;
742 while (!feof(config)) {
743 line = read_line(config);
744 if (!line) {
745 continue;
746 }
747
748 line_number++;
749 if (line[0] == '#') {
750 free(line);
751 continue;
752 }
753 if (strlen(line) == 0) {
754 free(line);
755 continue;
756 }
757
758 wlr_log(WLR_DEBUG, "Config Line #%d: %s", line_number, line);
759 char flag[strlen(line) + 3];
760 sprintf(flag, "--%s", line);
761 char *argv[] = {"swaylock", flag};
762 int result = parse_options(2, argv, state, line_mode);
763 if (result != 0) {
764 free(line);
765 fclose(config);
766 return result;
767 }
768 free(line);
769 }
770 fclose(config);
771 return 0;
772}
773
774static struct swaylock_state state;
775
776int main(int argc, char **argv) {
777 enum line_mode line_mode = LM_LINE;
778 state.args = (struct swaylock_args){
779 .mode = BACKGROUND_MODE_SOLID_COLOR,
780 .font = strdup("sans-serif"),
781 .radius = 50,
782 .thickness = 10,
783 .ignore_empty = false,
784 .show_indicator = true,
785 };
786 wl_list_init(&state.images);
787 set_default_colors(&state.args.colors);
788
789 wlr_log_init(WLR_DEBUG, NULL);
790
791 char *config_path = NULL;
792 static struct option long_options[] = {
793 {"config", required_argument, NULL, 'C'},
794 {0, 0, 0, 0},
795 };
796 while (1) {
797 int c = getopt_long(argc, argv, "C:", long_options, NULL);
798 if (c == -1) {
799 break;
800 } else if (c == 'C') {
801 config_path = strdup(optarg);
802 break;
803 }
804 }
805 if (!config_path) {
806 config_path = get_config_path();
807 }
808
809 if (config_path) {
810 wlr_log(WLR_DEBUG, "Found config at %s", config_path);
811 int config_status = load_config(config_path, &state, &line_mode);
812 free(config_path);
813 if (config_status != 0) {
814 return config_status;
815 }
816 }
817
818 if (argc > 1) {
819 wlr_log(WLR_DEBUG, "Parsing CLI Args");
820 int result = parse_options(argc, argv, &state, &line_mode);
821 if (result != 0) {
822 return result;
823 }
824 }
825
696 if (line_mode == LM_INSIDE) { 826 if (line_mode == LM_INSIDE) {
697 state.args.colors.line = state.args.colors.inside; 827 state.args.colors.line = state.args.colors.inside;
698 } else if (line_mode == LM_RING) { 828 } else if (line_mode == LM_RING) {
diff --git a/swaylock/swaylock.1.scd b/swaylock/swaylock.1.scd
index eea62c2a..3107124f 100644
--- a/swaylock/swaylock.1.scd
+++ b/swaylock/swaylock.1.scd
@@ -12,6 +12,15 @@ Locks your Wayland session.
12 12
13# OPTIONS 13# OPTIONS
14 14
15*-C, --config* <path>
16 The config file to use. By default, the following paths are checked:
17 _$HOME/.swaylock/config_, _$XDG\_CONFIG\_HOME/swaylock/config_, and
18 _SYSCONFDIR/swaylock/config_. All flags aside from this one are valid
19 options in the configuration file using the format _long-option=value_.
20 For options such as _ignore-empty-password_, just supply the _long-option_.
21 All leading dashes should be omitted and the equals sign is required for
22 flags that take an argument.
23
15*-c, --color* <rrggbb[aa]> 24*-c, --color* <rrggbb[aa]>
16 Turn the screen into the given color. If -i is used, this sets the 25 Turn the screen into the given color. If -i is used, this sets the
17 background of the image to the given color. Defaults to white (FFFFFF), or 26 background of the image to the given color. Defaults to white (FFFFFF), or