aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--HACKING.md6
-rw-r--r--common/background-image.c8
-rw-r--r--common/ipc-client.c2
-rw-r--r--common/list.c15
-rw-r--r--common/log.c4
-rw-r--r--common/pango.c2
-rw-r--r--common/readline.c4
-rw-r--r--common/unicode.c2
-rw-r--r--common/util.c4
-rw-r--r--completions/zsh/_swaymsg1
-rw-r--r--include/ipc.h5
-rw-r--r--include/list.h2
-rw-r--r--include/log.h10
-rw-r--r--include/sway/commands.h9
-rw-r--r--include/sway/config.h19
-rw-r--r--include/sway/criteria.h5
-rw-r--r--include/sway/debug.h8
-rw-r--r--include/sway/desktop.h4
-rw-r--r--include/sway/desktop/idle_inhibit_v1.h28
-rw-r--r--include/sway/desktop/transaction.h33
-rw-r--r--include/sway/input/cursor.h4
-rw-r--r--include/sway/input/seat.h43
-rw-r--r--include/sway/ipc-server.h4
-rw-r--r--include/sway/output.h43
-rw-r--r--include/sway/scratchpad.h26
-rw-r--r--include/sway/server.h11
-rw-r--r--include/sway/tree/arrange.h22
-rw-r--r--include/sway/tree/container.h38
-rw-r--r--include/sway/tree/layout.h11
-rw-r--r--include/sway/tree/view.h20
-rw-r--r--include/sway/tree/workspace.h3
-rw-r--r--include/swaybar/bar.h22
-rw-r--r--include/swaybar/status_line.h6
-rw-r--r--include/swaylock/swaylock.h25
-rw-r--r--sway/commands.c47
-rw-r--r--sway/commands/assign.c3
-rw-r--r--sway/commands/bar.c6
-rw-r--r--sway/commands/bar/binding_mode_indicator.c4
-rw-r--r--sway/commands/bar/font.c4
-rw-r--r--sway/commands/bar/height.c2
-rw-r--r--sway/commands/bar/hidden_state.c2
-rw-r--r--sway/commands/bar/id.c2
-rw-r--r--sway/commands/bar/mode.c2
-rw-r--r--sway/commands/bar/modifier.c7
-rw-r--r--sway/commands/bar/output.c2
-rw-r--r--sway/commands/bar/pango_markup.c4
-rw-r--r--sway/commands/bar/position.c3
-rw-r--r--sway/commands/bar/separator_symbol.c2
-rw-r--r--sway/commands/bar/status_command.c2
-rw-r--r--sway/commands/bar/strip_workspace_numbers.c4
-rw-r--r--sway/commands/bar/swaybar_command.c2
-rw-r--r--sway/commands/bar/workspace_buttons.c4
-rw-r--r--sway/commands/bar/wrap_scroll.c4
-rw-r--r--sway/commands/bind.c4
-rw-r--r--sway/commands/border.c2
-rw-r--r--sway/commands/default_floating_border.c29
-rw-r--r--sway/commands/exec.c2
-rw-r--r--sway/commands/exec_always.c35
-rw-r--r--sway/commands/floating.c2
-rw-r--r--sway/commands/floating_minmax_size.c53
-rw-r--r--sway/commands/floating_modifier.c20
-rw-r--r--sway/commands/focus.c71
-rw-r--r--sway/commands/for_window.c2
-rw-r--r--sway/commands/force_display_urgency_hint.c23
-rw-r--r--sway/commands/fullscreen.c2
-rw-r--r--sway/commands/gaps.c8
-rw-r--r--sway/commands/input.c4
-rw-r--r--sway/commands/input/accel_profile.c1
-rw-r--r--sway/commands/input/click_method.c1
-rw-r--r--sway/commands/input/drag_lock.c1
-rw-r--r--sway/commands/input/dwt.c1
-rw-r--r--sway/commands/input/events.c3
-rw-r--r--sway/commands/input/left_handed.c1
-rw-r--r--sway/commands/input/map_from_region.c8
-rw-r--r--sway/commands/input/middle_emulation.c1
-rw-r--r--sway/commands/input/natural_scroll.c1
-rw-r--r--sway/commands/input/pointer_accel.c1
-rw-r--r--sway/commands/input/repeat_delay.c1
-rw-r--r--sway/commands/input/repeat_rate.c1
-rw-r--r--sway/commands/input/scroll_button.c44
-rw-r--r--sway/commands/input/scroll_method.c1
-rw-r--r--sway/commands/input/tap.c3
-rw-r--r--sway/commands/input/tap_button_map.c33
-rw-r--r--sway/commands/input/xkb_layout.c2
-rw-r--r--sway/commands/input/xkb_model.c2
-rw-r--r--sway/commands/input/xkb_options.c2
-rw-r--r--sway/commands/input/xkb_rules.c2
-rw-r--r--sway/commands/input/xkb_variant.c2
-rw-r--r--sway/commands/layout.c2
-rw-r--r--sway/commands/mode.c19
-rw-r--r--sway/commands/move.c166
-rw-r--r--sway/commands/no_focus.c26
-rw-r--r--sway/commands/output.c54
-rw-r--r--sway/commands/output/background.c11
-rw-r--r--sway/commands/output/mode.c2
-rw-r--r--sway/commands/output/position.c2
-rw-r--r--sway/commands/reload.c4
-rw-r--r--sway/commands/rename.c2
-rw-r--r--sway/commands/resize.c410
-rw-r--r--sway/commands/scratchpad.c36
-rw-r--r--sway/commands/set.c2
-rw-r--r--sway/commands/smart_gaps.c2
-rw-r--r--sway/commands/split.c2
-rw-r--r--sway/commands/swap.c9
-rw-r--r--sway/commands/swaybg_command.c2
-rw-r--r--sway/commands/urgent.c36
-rw-r--r--sway/commands/workspace.c2
-rw-r--r--sway/config.c157
-rw-r--r--sway/config/bar.c40
-rw-r--r--sway/config/input.c16
-rw-r--r--sway/config/output.c172
-rw-r--r--sway/config/seat.c6
-rw-r--r--sway/criteria.c57
-rw-r--r--sway/desktop/desktop.c9
-rw-r--r--sway/desktop/idle_inhibit_v1.c80
-rw-r--r--sway/desktop/layer_shell.c21
-rw-r--r--sway/desktop/output.c957
-rw-r--r--sway/desktop/render.c919
-rw-r--r--sway/desktop/transaction.c107
-rw-r--r--sway/desktop/xdg_shell.c115
-rw-r--r--sway/desktop/xdg_shell_v6.c115
-rw-r--r--sway/desktop/xwayland.c189
-rw-r--r--sway/input/cursor.c295
-rw-r--r--sway/input/input-manager.c56
-rw-r--r--sway/input/keyboard.c13
-rw-r--r--sway/input/seat.c159
-rw-r--r--sway/ipc-json.c9
-rw-r--r--sway/ipc-server.c198
-rw-r--r--sway/main.c83
-rw-r--r--sway/meson.build12
-rw-r--r--sway/scratchpad.c175
-rw-r--r--sway/server.c44
-rw-r--r--sway/sway-input.5.scd15
-rw-r--r--sway/sway.5.scd15
-rw-r--r--sway/tree/arrange.c102
-rw-r--r--sway/tree/container.c209
-rw-r--r--sway/tree/layout.c97
-rw-r--r--sway/tree/output.c6
-rw-r--r--sway/tree/view.c191
-rw-r--r--sway/tree/workspace.c57
-rw-r--r--swaybar/bar.c25
-rw-r--r--swaybar/i3bar.c41
-rw-r--r--swaybar/ipc.c16
-rw-r--r--swaybar/main.c8
-rw-r--r--swaybar/render.c24
-rw-r--r--swaybar/status_line.c6
-rw-r--r--swaybg/main.c9
-rw-r--r--swayidle/main.c93
-rw-r--r--swaylock/main.c568
-rw-r--r--swaylock/password.c25
-rw-r--r--swaylock/render.c77
-rw-r--r--swaylock/seat.c4
-rw-r--r--swaylock/swaylock.1.scd92
-rw-r--r--swaymsg/main.c51
-rw-r--r--swaymsg/swaymsg.1.scd10
156 files changed, 5352 insertions, 2108 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8a6c0208..f450563a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -107,7 +107,7 @@ int main(int argc, const char **argv) {
107 } 107 }
108 108
109 int desired_output = atoi(argv[1]); 109 int desired_output = atoi(argv[1]);
110 sway_log(L_INFO, "Using output %d of %d", desired_output, registry->outputs->length); 110 sway_log(WLR_INFO, "Using output %d of %d", desired_output, registry->outputs->length);
111 int i; 111 int i;
112 struct output_state *output = registry->outputs->items[desired_output]; 112 struct output_state *output = registry->outputs->items[desired_output];
113 struct window *window = window_setup(registry, 100, 100, false); 113 struct window *window = window_setup(registry, 100, 100, false);
diff --git a/HACKING.md b/HACKING.md
index 3600db24..8965d3ec 100644
--- a/HACKING.md
+++ b/HACKING.md
@@ -3,9 +3,9 @@
3Use `sway_log(importance, fmt, ...)` to log. The following importances are 3Use `sway_log(importance, fmt, ...)` to log. The following importances are
4available: 4available:
5 5
6* `L_DEBUG`: Debug messages, only shows with `sway -d` 6* `WLR_DEBUG`: Debug messages, only shows with `sway -d`
7* `L_INFO`: Informational messages 7* `WLR_INFO`: Informational messages
8* `L_ERROR`: Error messages 8* `WLR_ERROR`: Error messages
9 9
10`sway_log` is a macro that calls `_sway_log` with the current filename and line 10`sway_log` is a macro that calls `_sway_log` with the current filename and line
11number, which are written into the log with your message. 11number, which are written into the log with your message.
diff --git a/common/background-image.c b/common/background-image.c
index e5fb4433..f3d2551e 100644
--- a/common/background-image.c
+++ b/common/background-image.c
@@ -18,7 +18,7 @@ enum background_mode parse_background_mode(const char *mode) {
18 } else if (strcmp(mode, "solid_color") == 0) { 18 } else if (strcmp(mode, "solid_color") == 0) {
19 return BACKGROUND_MODE_SOLID_COLOR; 19 return BACKGROUND_MODE_SOLID_COLOR;
20 } 20 }
21 wlr_log(L_ERROR, "Unsupported background mode: %s", mode); 21 wlr_log(WLR_ERROR, "Unsupported background mode: %s", mode);
22 return BACKGROUND_MODE_INVALID; 22 return BACKGROUND_MODE_INVALID;
23} 23}
24 24
@@ -28,7 +28,7 @@ cairo_surface_t *load_background_image(const char *path) {
28 GError *err = NULL; 28 GError *err = NULL;
29 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &err); 29 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &err);
30 if (!pixbuf) { 30 if (!pixbuf) {
31 wlr_log(L_ERROR, "Failed to load background image (%s).", 31 wlr_log(WLR_ERROR, "Failed to load background image (%s).",
32 err->message); 32 err->message);
33 return false; 33 return false;
34 } 34 }
@@ -38,11 +38,11 @@ cairo_surface_t *load_background_image(const char *path) {
38 image = cairo_image_surface_create_from_png(path); 38 image = cairo_image_surface_create_from_png(path);
39#endif //HAVE_GDK_PIXBUF 39#endif //HAVE_GDK_PIXBUF
40 if (!image) { 40 if (!image) {
41 wlr_log(L_ERROR, "Failed to read background image."); 41 wlr_log(WLR_ERROR, "Failed to read background image.");
42 return NULL; 42 return NULL;
43 } 43 }
44 if (cairo_surface_status(image) != CAIRO_STATUS_SUCCESS) { 44 if (cairo_surface_status(image) != CAIRO_STATUS_SUCCESS) {
45 wlr_log(L_ERROR, "Failed to read background image: %s." 45 wlr_log(WLR_ERROR, "Failed to read background image: %s."
46#ifndef HAVE_GDK_PIXBUF 46#ifndef HAVE_GDK_PIXBUF
47 "\nSway was compiled without gdk_pixbuf support, so only" 47 "\nSway was compiled without gdk_pixbuf support, so only"
48 "\nPNG images can be loaded. This is the likely cause." 48 "\nPNG images can be loaded. This is the likely cause."
diff --git a/common/ipc-client.c b/common/ipc-client.c
index a88df080..4d2d88cc 100644
--- a/common/ipc-client.c
+++ b/common/ipc-client.c
@@ -97,7 +97,7 @@ struct ipc_response *ipc_recv_response(int socketfd) {
97error_2: 97error_2:
98 free(response); 98 free(response);
99error_1: 99error_1:
100 wlr_log(L_ERROR, "Unable to allocate memory for IPC response"); 100 wlr_log(WLR_ERROR, "Unable to allocate memory for IPC response");
101 return NULL; 101 return NULL;
102} 102}
103 103
diff --git a/common/list.c b/common/list.c
index 39cc10e1..66d52f70 100644
--- a/common/list.c
+++ b/common/list.c
@@ -2,6 +2,7 @@
2#include <stdio.h> 2#include <stdio.h>
3#include <stdlib.h> 3#include <stdlib.h>
4#include <string.h> 4#include <string.h>
5#include "log.h"
5 6
6list_t *create_list(void) { 7list_t *create_list(void) {
7 list_t *list = malloc(sizeof(list_t)); 8 list_t *list = malloc(sizeof(list_t));
@@ -82,6 +83,20 @@ void list_swap(list_t *list, int src, int dest) {
82 list->items[dest] = tmp; 83 list->items[dest] = tmp;
83} 84}
84 85
86void list_move_to_end(list_t *list, void *item) {
87 int i;
88 for (i = 0; i < list->length; ++i) {
89 if (list->items[i] == item) {
90 break;
91 }
92 }
93 if (!sway_assert(i < list->length, "Item not found in list")) {
94 return;
95 }
96 list_del(list, i);
97 list_add(list, item);
98}
99
85static void list_rotate(list_t *list, int from, int to) { 100static void list_rotate(list_t *list, int from, int to) {
86 void *tmp = list->items[to]; 101 void *tmp = list->items[to];
87 102
diff --git a/common/log.c b/common/log.c
index 2cc7289c..847f3952 100644
--- a/common/log.c
+++ b/common/log.c
@@ -8,7 +8,7 @@ void sway_terminate(int code);
8void _sway_abort(const char *format, ...) { 8void _sway_abort(const char *format, ...) {
9 va_list args; 9 va_list args;
10 va_start(args, format); 10 va_start(args, format);
11 _wlr_vlog(L_ERROR, format, args); 11 _wlr_vlog(WLR_ERROR, format, args);
12 va_end(args); 12 va_end(args);
13 sway_terminate(EXIT_FAILURE); 13 sway_terminate(EXIT_FAILURE);
14} 14}
@@ -20,7 +20,7 @@ bool _sway_assert(bool condition, const char *format, ...) {
20 20
21 va_list args; 21 va_list args;
22 va_start(args, format); 22 va_start(args, format);
23 _wlr_vlog(L_ERROR, format, args); 23 _wlr_vlog(WLR_ERROR, format, args);
24 va_end(args); 24 va_end(args);
25 25
26#ifndef NDEBUG 26#ifndef NDEBUG
diff --git a/common/pango.c b/common/pango.c
index c88e50ce..92703f80 100644
--- a/common/pango.c
+++ b/common/pango.c
@@ -81,7 +81,7 @@ PangoLayout *get_pango_layout(cairo_t *cairo, const char *font,
81 pango_layout_set_markup(layout, buf, -1); 81 pango_layout_set_markup(layout, buf, -1);
82 free(buf); 82 free(buf);
83 } else { 83 } else {
84 wlr_log(L_ERROR, "pango_parse_markup '%s' -> error %s", text, 84 wlr_log(WLR_ERROR, "pango_parse_markup '%s' -> error %s", text,
85 error->message); 85 error->message);
86 g_error_free(error); 86 g_error_free(error);
87 markup = false; // fallback to plain text 87 markup = false; // fallback to plain text
diff --git a/common/readline.c b/common/readline.c
index 1c396a90..a2c69018 100644
--- a/common/readline.c
+++ b/common/readline.c
@@ -9,7 +9,7 @@ char *read_line(FILE *file) {
9 char *string = malloc(size); 9 char *string = malloc(size);
10 char lastChar = '\0'; 10 char lastChar = '\0';
11 if (!string) { 11 if (!string) {
12 wlr_log(L_ERROR, "Unable to allocate memory for read_line"); 12 wlr_log(WLR_ERROR, "Unable to allocate memory for read_line");
13 return NULL; 13 return NULL;
14 } 14 }
15 while (1) { 15 while (1) {
@@ -30,7 +30,7 @@ char *read_line(FILE *file) {
30 char *new_string = realloc(string, size *= 2); 30 char *new_string = realloc(string, size *= 2);
31 if (!new_string) { 31 if (!new_string) {
32 free(string); 32 free(string);
33 wlr_log(L_ERROR, "Unable to allocate memory for read_line"); 33 wlr_log(WLR_ERROR, "Unable to allocate memory for read_line");
34 return NULL; 34 return NULL;
35 } 35 }
36 string = new_string; 36 string = new_string;
diff --git a/common/unicode.c b/common/unicode.c
index 38a9b48e..5070e083 100644
--- a/common/unicode.c
+++ b/common/unicode.c
@@ -92,7 +92,7 @@ static const struct {
92 92
93int utf8_size(const char *s) { 93int utf8_size(const char *s) {
94 uint8_t c = (uint8_t)*s; 94 uint8_t c = (uint8_t)*s;
95 for (size_t i = 0; i < sizeof(sizes) / 2; ++i) { 95 for (size_t i = 0; i < sizeof(sizes) / sizeof(*sizes); ++i) {
96 if ((c & sizes[i].mask) == sizes[i].result) { 96 if ((c & sizes[i].mask) == sizes[i].result) {
97 return sizes[i].octets; 97 return sizes[i].octets;
98 } 98 }
diff --git a/common/util.c b/common/util.c
index fb7f9454..e8a88772 100644
--- a/common/util.c
+++ b/common/util.c
@@ -95,7 +95,7 @@ pid_t get_parent_pid(pid_t child) {
95 token = strtok(NULL, sep); // parent pid 95 token = strtok(NULL, sep); // parent pid
96 parent = strtol(token, NULL, 10); 96 parent = strtol(token, NULL, 10);
97 } 97 }
98 98 free(buffer);
99 fclose(stat); 99 fclose(stat);
100 } 100 }
101 101
@@ -113,7 +113,7 @@ uint32_t parse_color(const char *color) {
113 113
114 int len = strlen(color); 114 int len = strlen(color);
115 if (len != 6 && len != 8) { 115 if (len != 6 && len != 8) {
116 wlr_log(L_DEBUG, "Invalid color %s, defaulting to color 0xFFFFFFFF", color); 116 wlr_log(WLR_DEBUG, "Invalid color %s, defaulting to color 0xFFFFFFFF", color);
117 return 0xFFFFFFFF; 117 return 0xFFFFFFFF;
118 } 118 }
119 uint32_t res = (uint32_t)strtoul(color, NULL, 16); 119 uint32_t res = (uint32_t)strtoul(color, NULL, 16);
diff --git a/completions/zsh/_swaymsg b/completions/zsh/_swaymsg
index 6bb03279..2e39deb6 100644
--- a/completions/zsh/_swaymsg
+++ b/completions/zsh/_swaymsg
@@ -22,7 +22,6 @@ types=(
22'get_marks' 22'get_marks'
23'get_bar_config' 23'get_bar_config'
24'get_version' 24'get_version'
25'get_clipboard'
26) 25)
27 26
28_arguments -s \ 27_arguments -s \
diff --git a/include/ipc.h b/include/ipc.h
index 8172c782..0010718b 100644
--- a/include/ipc.h
+++ b/include/ipc.h
@@ -13,11 +13,12 @@ enum ipc_command_type {
13 IPC_GET_MARKS = 5, 13 IPC_GET_MARKS = 5,
14 IPC_GET_BAR_CONFIG = 6, 14 IPC_GET_BAR_CONFIG = 6,
15 IPC_GET_VERSION = 7, 15 IPC_GET_VERSION = 7,
16 IPC_GET_BINDING_MODES = 8,
17 IPC_GET_CONFIG = 9,
16 18
17 // sway-specific command types 19 // sway-specific command types
18 IPC_GET_INPUTS = 100, 20 IPC_GET_INPUTS = 100,
19 IPC_GET_CLIPBOARD = 101, 21 IPC_GET_SEATS = 101,
20 IPC_GET_SEATS = 102,
21 22
22 // Events sent from sway to clients. Events have the highest bits set. 23 // Events sent from sway to clients. Events have the highest bits set.
23 IPC_EVENT_WORKSPACE = ((1<<31) | 0), 24 IPC_EVENT_WORKSPACE = ((1<<31) | 0),
diff --git a/include/list.h b/include/list.h
index 7eead4ac..5a0d7d80 100644
--- a/include/list.h
+++ b/include/list.h
@@ -24,4 +24,6 @@ int list_seq_find(list_t *list, int compare(const void *item, const void *cmp_to
24void list_stable_sort(list_t *list, int compare(const void *a, const void *b)); 24void list_stable_sort(list_t *list, int compare(const void *a, const void *b));
25// swap two elements in a list 25// swap two elements in a list
26void list_swap(list_t *list, int src, int dest); 26void list_swap(list_t *list, int src, int dest);
27// move item to end of list
28void list_move_to_end(list_t *list, void *item);
27#endif 29#endif
diff --git a/include/log.h b/include/log.h
index a9748127..dd526143 100644
--- a/include/log.h
+++ b/include/log.h
@@ -3,13 +3,19 @@
3#include <stdbool.h> 3#include <stdbool.h>
4#include <wlr/util/log.h> 4#include <wlr/util/log.h>
5 5
6#ifdef __GNUC__
7#define ATTRIB_PRINTF(start, end) __attribute__((format(printf, start, end)))
8#else
9#define ATTRIB_PRINTF(start, end)
10#endif
11
6void _sway_abort(const char *filename, ...) ATTRIB_PRINTF(1, 2); 12void _sway_abort(const char *filename, ...) ATTRIB_PRINTF(1, 2);
7#define sway_abort(FMT, ...) \ 13#define sway_abort(FMT, ...) \
8 _sway_abort("[%s:%d] " FMT, wlr_strip_path(__FILE__), __LINE__, ##__VA_ARGS__) 14 _sway_abort("[%s:%d] " FMT, _wlr_strip_path(__FILE__), __LINE__, ##__VA_ARGS__)
9 15
10bool _sway_assert(bool condition, const char* format, ...) ATTRIB_PRINTF(2, 3); 16bool _sway_assert(bool condition, const char* format, ...) ATTRIB_PRINTF(2, 3);
11#define sway_assert(COND, FMT, ...) \ 17#define sway_assert(COND, FMT, ...) \
12 _sway_assert(COND, "[%s:%d] %s:" FMT, wlr_strip_path(__FILE__), __LINE__, __PRETTY_FUNCTION__, ##__VA_ARGS__) 18 _sway_assert(COND, "[%s:%d] %s:" FMT, _wlr_strip_path(__FILE__), __LINE__, __PRETTY_FUNCTION__, ##__VA_ARGS__)
13 19
14void error_handler(int sig); 20void error_handler(int sig);
15 21
diff --git a/include/sway/commands.h b/include/sway/commands.h
index 7ca0bda8..f53d335a 100644
--- a/include/sway/commands.h
+++ b/include/sway/commands.h
@@ -79,7 +79,7 @@ void free_cmd_results(struct cmd_results *results);
79 * 79 *
80 * Free the JSON string later on. 80 * Free the JSON string later on.
81 */ 81 */
82const char *cmd_results_to_json(struct cmd_results *results); 82char *cmd_results_to_json(struct cmd_results *results);
83 83
84struct cmd_results *add_color(const char *name, 84struct cmd_results *add_color(const char *name,
85 char *buffer, const char *color); 85 char *buffer, const char *color);
@@ -95,7 +95,6 @@ sway_cmd cmd_client_unfocused;
95sway_cmd cmd_client_urgent; 95sway_cmd cmd_client_urgent;
96sway_cmd cmd_client_placeholder; 96sway_cmd cmd_client_placeholder;
97sway_cmd cmd_client_background; 97sway_cmd cmd_client_background;
98sway_cmd cmd_clipboard;
99sway_cmd cmd_commands; 98sway_cmd cmd_commands;
100sway_cmd cmd_debuglog; 99sway_cmd cmd_debuglog;
101sway_cmd cmd_default_border; 100sway_cmd cmd_default_border;
@@ -107,13 +106,14 @@ sway_cmd cmd_exit;
107sway_cmd cmd_floating; 106sway_cmd cmd_floating;
108sway_cmd cmd_floating_maximum_size; 107sway_cmd cmd_floating_maximum_size;
109sway_cmd cmd_floating_minimum_size; 108sway_cmd cmd_floating_minimum_size;
110sway_cmd cmd_floating_mod; 109sway_cmd cmd_floating_modifier;
111sway_cmd cmd_floating_scroll; 110sway_cmd cmd_floating_scroll;
112sway_cmd cmd_focus; 111sway_cmd cmd_focus;
113sway_cmd cmd_focus_follows_mouse; 112sway_cmd cmd_focus_follows_mouse;
114sway_cmd cmd_focus_wrapping; 113sway_cmd cmd_focus_wrapping;
115sway_cmd cmd_font; 114sway_cmd cmd_font;
116sway_cmd cmd_for_window; 115sway_cmd cmd_for_window;
116sway_cmd cmd_force_display_urgency_hint;
117sway_cmd cmd_force_focus_wrapping; 117sway_cmd cmd_force_focus_wrapping;
118sway_cmd cmd_fullscreen; 118sway_cmd cmd_fullscreen;
119sway_cmd cmd_gaps; 119sway_cmd cmd_gaps;
@@ -153,6 +153,7 @@ sway_cmd cmd_swaybg_command;
153sway_cmd cmd_swap; 153sway_cmd cmd_swap;
154sway_cmd cmd_title_format; 154sway_cmd cmd_title_format;
155sway_cmd cmd_unmark; 155sway_cmd cmd_unmark;
156sway_cmd cmd_urgent;
156sway_cmd cmd_workspace; 157sway_cmd cmd_workspace;
157sway_cmd cmd_ws_auto_back_and_forth; 158sway_cmd cmd_ws_auto_back_and_forth;
158sway_cmd cmd_workspace_layout; 159sway_cmd cmd_workspace_layout;
@@ -208,8 +209,10 @@ sway_cmd input_cmd_natural_scroll;
208sway_cmd input_cmd_pointer_accel; 209sway_cmd input_cmd_pointer_accel;
209sway_cmd input_cmd_repeat_delay; 210sway_cmd input_cmd_repeat_delay;
210sway_cmd input_cmd_repeat_rate; 211sway_cmd input_cmd_repeat_rate;
212sway_cmd input_cmd_scroll_button;
211sway_cmd input_cmd_scroll_method; 213sway_cmd input_cmd_scroll_method;
212sway_cmd input_cmd_tap; 214sway_cmd input_cmd_tap;
215sway_cmd input_cmd_tap_button_map;
213sway_cmd input_cmd_xkb_layout; 216sway_cmd input_cmd_xkb_layout;
214sway_cmd input_cmd_xkb_model; 217sway_cmd input_cmd_xkb_model;
215sway_cmd input_cmd_xkb_options; 218sway_cmd input_cmd_xkb_options;
diff --git a/include/sway/config.h b/include/sway/config.h
index 9b583323..9d2e1bf9 100644
--- a/include/sway/config.h
+++ b/include/sway/config.h
@@ -49,6 +49,7 @@ struct sway_mode {
49 char *name; 49 char *name;
50 list_t *keysym_bindings; 50 list_t *keysym_bindings;
51 list_t *keycode_bindings; 51 list_t *keycode_bindings;
52 bool pango;
52}; 53};
53 54
54struct input_config_mapped_from_region { 55struct input_config_mapped_from_region {
@@ -73,9 +74,11 @@ struct input_config {
73 float pointer_accel; 74 float pointer_accel;
74 int repeat_delay; 75 int repeat_delay;
75 int repeat_rate; 76 int repeat_rate;
77 int scroll_button;
76 int scroll_method; 78 int scroll_method;
77 int send_events; 79 int send_events;
78 int tap; 80 int tap;
81 int tap_button_map;
79 82
80 char *xkb_layout; 83 char *xkb_layout;
81 char *xkb_model; 84 char *xkb_model;
@@ -263,11 +266,10 @@ enum ipc_feature {
263 IPC_FEATURE_EVENT_WINDOW = 2048, 266 IPC_FEATURE_EVENT_WINDOW = 2048,
264 IPC_FEATURE_EVENT_BINDING = 4096, 267 IPC_FEATURE_EVENT_BINDING = 4096,
265 IPC_FEATURE_EVENT_INPUT = 8192, 268 IPC_FEATURE_EVENT_INPUT = 8192,
266 IPC_FEATURE_GET_CLIPBOARD = 16384, 269 IPC_FEATURE_GET_SEATS = 16384,
267 IPC_FEATURE_GET_SEATS = 32768,
268 270
269 IPC_FEATURE_ALL_COMMANDS = 271 IPC_FEATURE_ALL_COMMANDS =
270 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | 16384 | 32768, 272 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | 16384,
271 IPC_FEATURE_ALL_EVENTS = 256 | 512 | 1024 | 2048 | 4096 | 8192, 273 IPC_FEATURE_ALL_EVENTS = 256 | 512 | 1024 | 2048 | 4096 | 8192,
272 274
273 IPC_FEATURE_ALL = IPC_FEATURE_ALL_COMMANDS | IPC_FEATURE_ALL_EVENTS, 275 IPC_FEATURE_ALL = IPC_FEATURE_ALL_COMMANDS | IPC_FEATURE_ALL_EVENTS,
@@ -314,6 +316,7 @@ struct sway_config {
314 char *font; 316 char *font;
315 size_t font_height; 317 size_t font_height;
316 bool pango_markup; 318 bool pango_markup;
319 size_t urgent_timeout;
317 320
318 // Flags 321 // Flags
319 bool focus_follows_mouse; 322 bool focus_follows_mouse;
@@ -332,6 +335,7 @@ struct sway_config {
332 int gaps_outer; 335 int gaps_outer;
333 336
334 list_t *config_chain; 337 list_t *config_chain;
338 const char *current_config_path;
335 const char *current_config; 339 const char *current_config;
336 340
337 enum sway_container_border border; 341 enum sway_container_border border;
@@ -447,8 +451,14 @@ void merge_output_config(struct output_config *dst, struct output_config *src);
447void apply_output_config(struct output_config *oc, 451void apply_output_config(struct output_config *oc,
448 struct sway_container *output); 452 struct sway_container *output);
449 453
454struct output_config *store_output_config(struct output_config *oc);
455
456void apply_output_config_to_outputs(struct output_config *oc);
457
450void free_output_config(struct output_config *oc); 458void free_output_config(struct output_config *oc);
451 459
460void create_default_output_configs(void);
461
452int workspace_output_cmp_workspace(const void *a, const void *b); 462int workspace_output_cmp_workspace(const void *a, const void *b);
453 463
454int sway_binding_cmp(const void *a, const void *b); 464int sway_binding_cmp(const void *a, const void *b);
@@ -484,7 +494,4 @@ void config_update_font_height(bool recalculate);
484/* Global config singleton. */ 494/* Global config singleton. */
485extern struct sway_config *config; 495extern struct sway_config *config;
486 496
487/* Config file currently being read */
488extern const char *current_config_path;
489
490#endif 497#endif
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/debug.h b/include/sway/debug.h
index 2430d319..38d4eccd 100644
--- a/include/sway/debug.h
+++ b/include/sway/debug.h
@@ -1,7 +1,15 @@
1#ifndef SWAY_DEBUG_H 1#ifndef SWAY_DEBUG_H
2#define SWAY_DEBUG_H 2#define SWAY_DEBUG_H
3 3
4// Tree
4extern bool enable_debug_tree; 5extern bool enable_debug_tree;
5void update_debug_tree(); 6void update_debug_tree();
6 7
8// Damage
9extern const char *damage_debug;
10
11// Transactions
12extern int txn_timeout_ms;
13extern bool txn_debug;
14
7#endif 15#endif
diff --git a/include/sway/desktop.h b/include/sway/desktop.h
index f1ad759a..348fb187 100644
--- a/include/sway/desktop.h
+++ b/include/sway/desktop.h
@@ -1,4 +1,8 @@
1#include <wlr/types/wlr_surface.h> 1#include <wlr/types/wlr_surface.h>
2 2
3struct sway_container;
4
3void desktop_damage_surface(struct wlr_surface *surface, double lx, double ly, 5void desktop_damage_surface(struct wlr_surface *surface, double lx, double ly,
4 bool whole); 6 bool whole);
7
8void desktop_damage_whole_container(struct sway_container *con);
diff --git a/include/sway/desktop/idle_inhibit_v1.h b/include/sway/desktop/idle_inhibit_v1.h
new file mode 100644
index 00000000..e5ed8a3d
--- /dev/null
+++ b/include/sway/desktop/idle_inhibit_v1.h
@@ -0,0 +1,28 @@
1#ifndef _SWAY_DESKTOP_IDLE_INHIBIT_V1_H
2#define _SWAY_DESKTOP_IDLE_INHIBIT_V1_H
3#include <wlr/types/wlr_idle_inhibit_v1.h>
4#include <wlr/types/wlr_idle.h>
5#include "sway/server.h"
6
7struct sway_idle_inhibit_manager_v1 {
8 struct wlr_idle_inhibit_manager_v1 *wlr_manager;
9 struct wl_listener new_idle_inhibitor_v1;
10 struct wl_list inhibitors;
11
12 struct wlr_idle *idle;
13};
14
15struct sway_idle_inhibitor_v1 {
16 struct sway_idle_inhibit_manager_v1 *manager;
17 struct sway_view *view;
18
19 struct wl_list link;
20 struct wl_listener destroy;
21};
22
23void idle_inhibit_v1_check_active(
24 struct sway_idle_inhibit_manager_v1 *manager);
25
26struct sway_idle_inhibit_manager_v1 *sway_idle_inhibit_manager_v1_create(
27 struct wl_display *wl_display, struct wlr_idle *idle);
28#endif
diff --git a/include/sway/desktop/transaction.h b/include/sway/desktop/transaction.h
index 7ab80eb8..cee4afed 100644
--- a/include/sway/desktop/transaction.h
+++ b/include/sway/desktop/transaction.h
@@ -6,34 +6,25 @@
6/** 6/**
7 * Transactions enable us to perform atomic layout updates. 7 * Transactions enable us to perform atomic layout updates.
8 * 8 *
9 * When we want to make adjustments to the layout, we create a transaction. 9 * A transaction contains a list of containers and their new state.
10 * A transaction contains a list of affected containers and their new state.
11 * A state might contain a new size, or new border settings, or new parent/child 10 * A state might contain a new size, or new border settings, or new parent/child
12 * relationships. 11 * relationships.
13 * 12 *
14 * Calling transaction_commit() makes sway notify of all the affected clients 13 * Committing a transaction makes sway notify of all the affected clients with
15 * with their new sizes. We then wait for all the views to respond with their 14 * their new sizes. We then wait for all the views to respond with their new
16 * new surface sizes. When all are ready, or when a timeout has passed, we apply 15 * surface sizes. When all are ready, or when a timeout has passed, we apply the
17 * the updates all at the same time. 16 * updates all at the same time.
18 */ 17 *
19 18 * When we want to make adjustments to the layout, we change the pending state
20struct sway_transaction; 19 * in containers, mark them as dirty and call transaction_commit_dirty(). This
21 20 * create and commits a transaction from the dirty containers.
22/**
23 * Create a new transaction.
24 */
25struct sway_transaction *transaction_create(void);
26
27/**
28 * Add a container's pending state to the transaction.
29 */ 21 */
30void transaction_add_container(struct sway_transaction *transaction,
31 struct sway_container *container);
32 22
33/** 23/**
34 * Submit a transaction to the client views for configuration. 24 * Find all dirty containers, create and commit a transaction containing them,
25 * and unmark them as dirty.
35 */ 26 */
36void transaction_commit(struct sway_transaction *transaction); 27void transaction_commit_dirty(void);
37 28
38/** 29/**
39 * Notify the transaction system that a view is ready for the new layout. 30 * Notify the transaction system that a view is ready for the new layout.
diff --git a/include/sway/input/cursor.h b/include/sway/input/cursor.h
index 5dd109ca..b0a3a7c5 100644
--- a/include/sway/input/cursor.h
+++ b/include/sway/input/cursor.h
@@ -11,6 +11,7 @@ struct sway_cursor {
11 } previous; 11 } previous;
12 struct wlr_xcursor_manager *xcursor_manager; 12 struct wlr_xcursor_manager *xcursor_manager;
13 13
14 const char *image;
14 struct wl_client *image_client; 15 struct wl_client *image_client;
15 16
16 struct wl_listener motion; 17 struct wl_listener motion;
@@ -37,4 +38,7 @@ void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec,
37void dispatch_cursor_button(struct sway_cursor *cursor, uint32_t time_msec, 38void dispatch_cursor_button(struct sway_cursor *cursor, uint32_t time_msec,
38 uint32_t button, enum wlr_button_state state); 39 uint32_t button, enum wlr_button_state state);
39 40
41void cursor_set_image(struct sway_cursor *cursor, const char *image,
42 struct wl_client *client);
43
40#endif 44#endif
diff --git a/include/sway/input/seat.h b/include/sway/input/seat.h
index 0e440701..ab25788f 100644
--- a/include/sway/input/seat.h
+++ b/include/sway/input/seat.h
@@ -3,6 +3,7 @@
3 3
4#include <wlr/types/wlr_layer_shell.h> 4#include <wlr/types/wlr_layer_shell.h>
5#include <wlr/types/wlr_seat.h> 5#include <wlr/types/wlr_seat.h>
6#include <wlr/util/edges.h>
6#include "sway/input/input-manager.h" 7#include "sway/input/input-manager.h"
7 8
8struct sway_seat_device { 9struct sway_seat_device {
@@ -52,6 +53,24 @@ struct sway_seat {
52 int32_t touch_id; 53 int32_t touch_id;
53 double touch_x, touch_y; 54 double touch_x, touch_y;
54 55
56 // Operations (drag and resize)
57 enum {
58 OP_NONE,
59 OP_MOVE,
60 OP_RESIZE,
61 } operation;
62
63 struct sway_container *op_container;
64 enum wlr_edges op_resize_edge;
65 uint32_t op_button;
66 bool op_resize_preserve_ratio;
67 double op_ref_lx, op_ref_ly; // cursor's x/y at start of op
68 double op_ref_width, op_ref_height; // container's size at start of op
69 double op_ref_con_lx, op_ref_con_ly; // container's x/y at start of op
70
71 uint32_t last_button;
72 uint32_t last_button_serial;
73
55 struct wl_listener focus_destroy; 74 struct wl_listener focus_destroy;
56 struct wl_listener new_container; 75 struct wl_listener new_container;
57 struct wl_listener new_drag_icon; 76 struct wl_listener new_drag_icon;
@@ -83,7 +102,7 @@ void seat_set_focus_warp(struct sway_seat *seat,
83 struct sway_container *container, bool warp); 102 struct sway_container *container, bool warp);
84 103
85void seat_set_focus_surface(struct sway_seat *seat, 104void seat_set_focus_surface(struct sway_seat *seat,
86 struct wlr_surface *surface); 105 struct wlr_surface *surface, bool unfocus);
87 106
88void seat_set_focus_layer(struct sway_seat *seat, 107void seat_set_focus_layer(struct sway_seat *seat,
89 struct wlr_layer_surface *layer); 108 struct wlr_layer_surface *layer);
@@ -119,17 +138,6 @@ struct sway_container *seat_get_active_child(struct sway_seat *seat,
119 struct sway_container *container); 138 struct sway_container *container);
120 139
121/** 140/**
122 * Return the immediate child of container which was most recently focused, with
123 * fallback to selecting the child in the parent's `current` (rendered) children
124 * list.
125 *
126 * This is useful for when a tabbed container and its children are destroyed but
127 * still being rendered, and we have to render an appropriate child.
128 */
129struct sway_container *seat_get_active_current_child(struct sway_seat *seat,
130 struct sway_container *container);
131
132/**
133 * Iterate over the focus-inactive children of the container calling the 141 * Iterate over the focus-inactive children of the container calling the
134 * function on each. 142 * function on each.
135 */ 143 */
@@ -145,4 +153,15 @@ bool seat_is_input_allowed(struct sway_seat *seat, struct wlr_surface *surface);
145 153
146void drag_icon_update_position(struct sway_drag_icon *icon); 154void drag_icon_update_position(struct sway_drag_icon *icon);
147 155
156void seat_begin_move(struct sway_seat *seat, struct sway_container *con,
157 uint32_t button);
158
159void seat_begin_resize(struct sway_seat *seat, struct sway_container *con,
160 uint32_t button, enum wlr_edges edge);
161
162void seat_end_mouse_operation(struct sway_seat *seat);
163
164void seat_pointer_notify_button(struct sway_seat *seat, uint32_t time_msec,
165 uint32_t button, enum wlr_button_state state);
166
148#endif 167#endif
diff --git a/include/sway/ipc-server.h b/include/sway/ipc-server.h
index dd16a175..6469f097 100644
--- a/include/sway/ipc-server.h
+++ b/include/sway/ipc-server.h
@@ -9,14 +9,12 @@ struct sway_server;
9 9
10void ipc_init(struct sway_server *server); 10void ipc_init(struct sway_server *server);
11 11
12void ipc_terminate(void);
13
14struct sockaddr_un *ipc_user_sockaddr(void); 12struct sockaddr_un *ipc_user_sockaddr(void);
15 13
16void ipc_event_workspace(struct sway_container *old, 14void ipc_event_workspace(struct sway_container *old,
17 struct sway_container *new, const char *change); 15 struct sway_container *new, const char *change);
18void ipc_event_window(struct sway_container *window, const char *change); 16void ipc_event_window(struct sway_container *window, const char *change);
19void ipc_event_barconfig_update(struct bar_config *bar); 17void ipc_event_barconfig_update(struct bar_config *bar);
20void ipc_event_mode(const char *mode); 18void ipc_event_mode(const char *mode, bool pango);
21 19
22#endif 20#endif
diff --git a/include/sway/output.h b/include/sway/output.h
index 19fc5e99..b6cda83c 100644
--- a/include/sway/output.h
+++ b/include/sway/output.h
@@ -38,6 +38,16 @@ struct sway_output {
38 } events; 38 } events;
39}; 39};
40 40
41/**
42 * Contains a surface's root geometry information. For instance, when rendering
43 * a popup, this will contain the parent view's position and size.
44 */
45struct root_geometry {
46 double x, y;
47 int width, height;
48 float rotation;
49};
50
41void output_damage_whole(struct sway_output *output); 51void output_damage_whole(struct sway_output *output);
42 52
43void output_damage_surface(struct sway_output *output, double ox, double oy, 53void output_damage_surface(struct sway_output *output, double ox, double oy,
@@ -54,4 +64,37 @@ void output_damage_whole_container(struct sway_output *output,
54struct sway_container *output_by_name(const char *name); 64struct sway_container *output_by_name(const char *name);
55 65
56void output_enable(struct sway_output *output); 66void output_enable(struct sway_output *output);
67
68bool output_has_opaque_lockscreen(struct sway_output *output,
69 struct sway_seat *seat);
70
71struct sway_container *output_get_active_workspace(struct sway_output *output);
72
73void output_render(struct sway_output *output, struct timespec *when,
74 pixman_region32_t *damage);
75
76bool output_get_surface_box(struct root_geometry *geo,
77 struct sway_output *output, struct wlr_surface *surface, int sx, int sy,
78 struct wlr_box *surface_box);
79
80void output_surface_for_each_surface(struct wlr_surface *surface,
81 double ox, double oy, struct root_geometry *geo,
82 wlr_surface_iterator_func_t iterator, void *user_data);
83
84void output_view_for_each_surface(struct sway_view *view,
85 struct sway_output *output, struct root_geometry *geo,
86 wlr_surface_iterator_func_t iterator, void *user_data);
87
88void output_layer_for_each_surface(struct wl_list *layer_surfaces,
89 struct root_geometry *geo, wlr_surface_iterator_func_t iterator,
90 void *user_data);
91
92void output_unmanaged_for_each_surface(struct wl_list *unmanaged,
93 struct sway_output *output, struct root_geometry *geo,
94 wlr_surface_iterator_func_t iterator, void *user_data);
95
96void output_drag_icons_for_each_surface(struct wl_list *drag_icons,
97 struct sway_output *output, struct root_geometry *geo,
98 wlr_surface_iterator_func_t iterator, void *user_data);
99
57#endif 100#endif
diff --git a/include/sway/scratchpad.h b/include/sway/scratchpad.h
new file mode 100644
index 00000000..5af5256f
--- /dev/null
+++ b/include/sway/scratchpad.h
@@ -0,0 +1,26 @@
1#ifndef _SWAY_SCRATCHPAD_H
2#define _SWAY_SCRATCHPAD_H
3
4#include "tree/container.h"
5
6/**
7 * Move a container to the scratchpad.
8 */
9void scratchpad_add_container(struct sway_container *con);
10
11/**
12 * Remove a container from the scratchpad.
13 */
14void scratchpad_remove_container(struct sway_container *con);
15
16/**
17 * Show or hide the next container on the scratchpad.
18 */
19void scratchpad_toggle_auto(void);
20
21/**
22 * Show or hide a specific container on the scratchpad.
23 */
24void scratchpad_toggle_container(struct sway_container *con);
25
26#endif
diff --git a/include/sway/server.h b/include/sway/server.h
index 1e1aa3cc..70bde6d4 100644
--- a/include/sway/server.h
+++ b/include/sway/server.h
@@ -23,12 +23,14 @@ struct sway_server {
23 23
24 struct wlr_compositor *compositor; 24 struct wlr_compositor *compositor;
25 struct wlr_data_device_manager *data_device_manager; 25 struct wlr_data_device_manager *data_device_manager;
26 struct wlr_idle *idle;
27 26
28 struct sway_input_manager *input; 27 struct sway_input_manager *input;
29 28
30 struct wl_listener new_output; 29 struct wl_listener new_output;
31 30
31 struct wlr_idle *idle;
32 struct sway_idle_inhibit_manager_v1 *idle_inhibit_manager_v1;
33
32 struct wlr_layer_shell *layer_shell; 34 struct wlr_layer_shell *layer_shell;
33 struct wl_listener layer_shell_surface; 35 struct wl_listener layer_shell_surface;
34 36
@@ -45,10 +47,7 @@ struct sway_server {
45 bool debug_txn_timings; 47 bool debug_txn_timings;
46 48
47 list_t *transactions; 49 list_t *transactions;
48 50 list_t *dirty_containers;
49 // When a view is being destroyed and is waiting for a transaction to
50 // complete it will be stored here.
51 list_t *destroying_containers;
52}; 51};
53 52
54struct sway_server server; 53struct sway_server server;
@@ -57,10 +56,12 @@ struct sway_server server;
57bool server_privileged_prepare(struct sway_server *server); 56bool server_privileged_prepare(struct sway_server *server);
58bool server_init(struct sway_server *server); 57bool server_init(struct sway_server *server);
59void server_fini(struct sway_server *server); 58void server_fini(struct sway_server *server);
59bool server_start_backend(struct sway_server *server);
60void server_run(struct sway_server *server); 60void server_run(struct sway_server *server);
61 61
62void handle_new_output(struct wl_listener *listener, void *data); 62void handle_new_output(struct wl_listener *listener, void *data);
63 63
64void handle_idle_inhibitor_v1(struct wl_listener *listener, void *data);
64void handle_layer_shell_surface(struct wl_listener *listener, void *data); 65void handle_layer_shell_surface(struct wl_listener *listener, void *data);
65void handle_xdg_shell_v6_surface(struct wl_listener *listener, void *data); 66void handle_xdg_shell_v6_surface(struct wl_listener *listener, void *data);
66void handle_xdg_shell_surface(struct wl_listener *listener, void *data); 67void handle_xdg_shell_surface(struct wl_listener *listener, void *data);
diff --git a/include/sway/tree/arrange.h b/include/sway/tree/arrange.h
index 58235642..d6abcc81 100644
--- a/include/sway/tree/arrange.h
+++ b/include/sway/tree/arrange.h
@@ -11,26 +11,8 @@ void remove_gaps(struct sway_container *c);
11void add_gaps(struct sway_container *c); 11void add_gaps(struct sway_container *c);
12 12
13/** 13/**
14 * Arrange layout for all the children of the given container, and add them to 14 * Arrange layout for all the children of the given container.
15 * the given transaction.
16 *
17 * Use this function if you need to arrange multiple sections of the tree in one
18 * transaction.
19 *
20 * You must set the desired state of the container before calling
21 * arrange_windows, then don't change any state-tracked properties in the
22 * container until you've called transaction_commit.
23 */ 15 */
24void arrange_windows(struct sway_container *container, 16void arrange_windows(struct sway_container *container);
25 struct sway_transaction *transaction);
26
27/**
28 * Arrange layout for the given container and commit the transaction.
29 *
30 * This function is a wrapper around arrange_windows, and handles creating and
31 * committing the transaction for you. Use this function if you're only doing
32 * one arrange operation.
33 */
34void arrange_and_commit(struct sway_container *container);
35 17
36#endif 18#endif
diff --git a/include/sway/tree/container.h b/include/sway/tree/container.h
index 728daa84..2a4be18c 100644
--- a/include/sway/tree/container.h
+++ b/include/sway/tree/container.h
@@ -68,6 +68,9 @@ struct sway_container_state {
68 struct sway_container *parent; 68 struct sway_container *parent;
69 list_t *children; 69 list_t *children;
70 70
71 struct sway_container *focused_inactive_child;
72 bool focused;
73
71 // View properties 74 // View properties
72 double view_x, view_y; 75 double view_x, view_y;
73 double view_width, view_height; 76 double view_width, view_height;
@@ -132,6 +135,11 @@ struct sway_container {
132 135
133 struct sway_container *parent; 136 struct sway_container *parent;
134 137
138 // Indicates that the container is a scratchpad container.
139 // Both hidden and visible scratchpad containers have scratchpad=true.
140 // Hidden scratchpad containers have a NULL parent.
141 bool scratchpad;
142
135 float alpha; 143 float alpha;
136 144
137 struct wlr_texture *title_focused; 145 struct wlr_texture *title_focused;
@@ -144,6 +152,10 @@ struct sway_container {
144 152
145 bool destroying; 153 bool destroying;
146 154
155 // If true, indicates that the container has pending state that differs from
156 // the current.
157 bool dirty;
158
147 struct { 159 struct {
148 struct wl_signal destroy; 160 struct wl_signal destroy;
149 // Raised after the tree updates, but before arrange_windows 161 // Raised after the tree updates, but before arrange_windows
@@ -297,4 +309,30 @@ bool container_is_floating(struct sway_container *container);
297 */ 309 */
298void container_get_box(struct sway_container *container, struct wlr_box *box); 310void container_get_box(struct sway_container *container, struct wlr_box *box);
299 311
312/**
313 * Move a floating container by the specified amount.
314 */
315void container_floating_translate(struct sway_container *con,
316 double x_amount, double y_amount);
317
318/**
319 * Move a floating container to a new layout-local position.
320 */
321void container_floating_move_to(struct sway_container *con,
322 double lx, double ly);
323
324/**
325 * Mark a container as dirty if it isn't already. Dirty containers will be
326 * included in the next transaction then unmarked as dirty.
327 */
328void container_set_dirty(struct sway_container *container);
329
330bool container_has_urgent_child(struct sway_container *container);
331
332/**
333 * If the container is involved in a drag or resize operation via a mouse, this
334 * ends the operation.
335 */
336void container_end_mouse_operation(struct sway_container *container);
337
300#endif 338#endif
diff --git a/include/sway/tree/layout.h b/include/sway/tree/layout.h
index ba265623..7d7da2d7 100644
--- a/include/sway/tree/layout.h
+++ b/include/sway/tree/layout.h
@@ -14,10 +14,11 @@ enum movement_direction {
14}; 14};
15 15
16enum resize_edge { 16enum resize_edge {
17 RESIZE_EDGE_LEFT, 17 RESIZE_EDGE_NONE = 0,
18 RESIZE_EDGE_RIGHT, 18 RESIZE_EDGE_LEFT = 1,
19 RESIZE_EDGE_TOP, 19 RESIZE_EDGE_RIGHT = 2,
20 RESIZE_EDGE_BOTTOM, 20 RESIZE_EDGE_TOP = 4,
21 RESIZE_EDGE_BOTTOM = 8,
21}; 22};
22 23
23struct sway_container; 24struct sway_container;
@@ -34,6 +35,8 @@ struct sway_root {
34 35
35 struct wl_list outputs; // sway_output::link 36 struct wl_list outputs; // sway_output::link
36 37
38 list_t *scratchpad; // struct sway_container
39
37 struct { 40 struct {
38 struct wl_signal new_container; 41 struct wl_signal new_container;
39 } events; 42 } events;
diff --git a/include/sway/tree/view.h b/include/sway/tree/view.h
index 7dc8ac46..3bdfe252 100644
--- a/include/sway/tree/view.h
+++ b/include/sway/tree/view.h
@@ -26,6 +26,8 @@ enum sway_view_prop {
26}; 26};
27 27
28struct sway_view_impl { 28struct sway_view_impl {
29 void (*get_constraints)(struct sway_view *view, double *min_width,
30 double *max_width, double *min_height, double *max_height);
29 const char *(*get_string_prop)(struct sway_view *view, 31 const char *(*get_string_prop)(struct sway_view *view,
30 enum sway_view_prop prop); 32 enum sway_view_prop prop);
31 uint32_t (*get_int_prop)(struct sway_view *view, enum sway_view_prop prop); 33 uint32_t (*get_int_prop)(struct sway_view *view, enum sway_view_prop prop);
@@ -35,6 +37,7 @@ struct sway_view_impl {
35 void (*set_tiled)(struct sway_view *view, bool tiled); 37 void (*set_tiled)(struct sway_view *view, bool tiled);
36 void (*set_fullscreen)(struct sway_view *view, bool fullscreen); 38 void (*set_fullscreen)(struct sway_view *view, bool fullscreen);
37 bool (*wants_floating)(struct sway_view *view); 39 bool (*wants_floating)(struct sway_view *view);
40 bool (*has_client_side_decorations)(struct sway_view *view);
38 void (*for_each_surface)(struct sway_view *view, 41 void (*for_each_surface)(struct sway_view *view,
39 wlr_surface_iterator_func_t iterator, void *user_data); 42 wlr_surface_iterator_func_t iterator, void *user_data);
40 void (*close)(struct sway_view *view); 43 void (*close)(struct sway_view *view);
@@ -68,6 +71,11 @@ struct sway_view {
68 bool border_bottom; 71 bool border_bottom;
69 bool border_left; 72 bool border_left;
70 bool border_right; 73 bool border_right;
74 bool using_csd;
75
76 struct timespec urgent;
77 bool allow_request_urgent;
78 struct wl_event_source *urgent_timer;
71 79
72 bool destroying; 80 bool destroying;
73 81
@@ -102,6 +110,8 @@ struct sway_xdg_shell_v6_view {
102 struct wl_listener request_resize; 110 struct wl_listener request_resize;
103 struct wl_listener request_maximize; 111 struct wl_listener request_maximize;
104 struct wl_listener request_fullscreen; 112 struct wl_listener request_fullscreen;
113 struct wl_listener set_title;
114 struct wl_listener set_app_id;
105 struct wl_listener new_popup; 115 struct wl_listener new_popup;
106 struct wl_listener map; 116 struct wl_listener map;
107 struct wl_listener unmap; 117 struct wl_listener unmap;
@@ -116,6 +126,8 @@ struct sway_xdg_shell_view {
116 struct wl_listener request_resize; 126 struct wl_listener request_resize;
117 struct wl_listener request_maximize; 127 struct wl_listener request_maximize;
118 struct wl_listener request_fullscreen; 128 struct wl_listener request_fullscreen;
129 struct wl_listener set_title;
130 struct wl_listener set_app_id;
119 struct wl_listener new_popup; 131 struct wl_listener new_popup;
120 struct wl_listener map; 132 struct wl_listener map;
121 struct wl_listener unmap; 133 struct wl_listener unmap;
@@ -134,6 +146,7 @@ struct sway_xwayland_view {
134 struct wl_listener set_title; 146 struct wl_listener set_title;
135 struct wl_listener set_class; 147 struct wl_listener set_class;
136 struct wl_listener set_window_type; 148 struct wl_listener set_window_type;
149 struct wl_listener set_hints;
137 struct wl_listener map; 150 struct wl_listener map;
138 struct wl_listener unmap; 151 struct wl_listener unmap;
139 struct wl_listener destroy; 152 struct wl_listener destroy;
@@ -208,6 +221,9 @@ uint32_t view_get_window_type(struct sway_view *view);
208 221
209const char *view_get_shell(struct sway_view *view); 222const char *view_get_shell(struct sway_view *view);
210 223
224void view_get_constraints(struct sway_view *view, double *min_width,
225 double *max_width, double *min_height, double *max_height);
226
211uint32_t view_configure(struct sway_view *view, double lx, double ly, int width, 227uint32_t view_configure(struct sway_view *view, double lx, double ly, int width,
212 int height); 228 int height);
213 229
@@ -304,4 +320,8 @@ void view_update_marks_textures(struct sway_view *view);
304 */ 320 */
305bool view_is_visible(struct sway_view *view); 321bool view_is_visible(struct sway_view *view);
306 322
323void view_set_urgent(struct sway_view *view, bool enable);
324
325bool view_is_urgent(struct sway_view *view);
326
307#endif 327#endif
diff --git a/include/sway/tree/workspace.h b/include/sway/tree/workspace.h
index d84e4a02..ff66da6b 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;
@@ -47,4 +48,6 @@ struct sway_container *workspace_for_pid(pid_t pid);
47 48
48void workspace_record_pid(pid_t pid); 49void workspace_record_pid(pid_t pid);
49 50
51void workspace_detect_urgent(struct sway_container *workspace);
52
50#endif 53#endif
diff --git a/include/swaybar/bar.h b/include/swaybar/bar.h
index af478f33..1cecea71 100644
--- a/include/swaybar/bar.h
+++ b/include/swaybar/bar.h
@@ -16,11 +16,29 @@ struct swaybar_pointer {
16 int x, y; 16 int x, y;
17}; 17};
18 18
19enum x11_button {
20 NONE,
21 LEFT,
22 MIDDLE,
23 RIGHT,
24 SCROLL_UP,
25 SCROLL_DOWN,
26 SCROLL_LEFT,
27 SCROLL_RIGHT,
28 BACK,
29 FORWARD,
30};
31
32enum hotspot_event_handling {
33 HOTSPOT_IGNORE,
34 HOTSPOT_PROCESS,
35};
36
19struct swaybar_hotspot { 37struct swaybar_hotspot {
20 struct wl_list link; 38 struct wl_list link;
21 int x, y, width, height; 39 int x, y, width, height;
22 void (*callback)(struct swaybar_output *output, 40 enum hotspot_event_handling (*callback)(struct swaybar_output *output,
23 int x, int y, uint32_t button, void *data); 41 int x, int y, enum x11_button button, void *data);
24 void (*destroy)(void *data); 42 void (*destroy)(void *data);
25 void *data; 43 void *data;
26}; 44};
diff --git a/include/swaybar/status_line.h b/include/swaybar/status_line.h
index bf12a842..de9b98d7 100644
--- a/include/swaybar/status_line.h
+++ b/include/swaybar/status_line.h
@@ -71,8 +71,10 @@ void status_error(struct status_line *status, const char *text);
71bool status_handle_readable(struct status_line *status); 71bool status_handle_readable(struct status_line *status);
72void status_line_free(struct status_line *status); 72void status_line_free(struct status_line *status);
73bool i3bar_handle_readable(struct status_line *status); 73bool i3bar_handle_readable(struct status_line *status);
74void i3bar_block_send_click(struct status_line *status, 74enum hotspot_event_handling i3bar_block_send_click(struct status_line *status,
75 struct i3bar_block *block, int x, int y, uint32_t button); 75 struct i3bar_block *block, int x, int y, enum x11_button button);
76void i3bar_block_free(struct i3bar_block *block); 76void i3bar_block_free(struct i3bar_block *block);
77enum x11_button wl_button_to_x11_button(uint32_t button);
78enum x11_button wl_axis_to_x11_button(uint32_t axis, wl_fixed_t value);
77 79
78#endif 80#endif
diff --git a/include/swaylock/swaylock.h b/include/swaylock/swaylock.h
index 2931fd61..950cfaaf 100644
--- a/include/swaylock/swaylock.h
+++ b/include/swaylock/swaylock.h
@@ -19,10 +19,33 @@ enum auth_state {
19 AUTH_STATE_INVALID, 19 AUTH_STATE_INVALID,
20}; 20};
21 21
22struct swaylock_colorset {
23 uint32_t input;
24 uint32_t cleared;
25 uint32_t verifying;
26 uint32_t wrong;
27};
28
29struct swaylock_colors {
30 uint32_t background;
31 uint32_t bs_highlight;
32 uint32_t key_highlight;
33 uint32_t separator;
34 struct swaylock_colorset inside;
35 struct swaylock_colorset line;
36 struct swaylock_colorset ring;
37 struct swaylock_colorset text;
38};
39
22struct swaylock_args { 40struct swaylock_args {
23 uint32_t color; 41 struct swaylock_colors colors;
24 enum background_mode mode; 42 enum background_mode mode;
43 char *font;
44 uint32_t radius;
45 uint32_t thickness;
46 bool ignore_empty;
25 bool show_indicator; 47 bool show_indicator;
48 bool daemonize;
26}; 49};
27 50
28struct swaylock_password { 51struct swaylock_password {
diff --git a/sway/commands.c b/sway/commands.c
index 5b20857a..fdae1961 100644
--- a/sway/commands.c
+++ b/sway/commands.c
@@ -98,12 +98,18 @@ 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 },
104 { "floating_maximum_size", cmd_floating_maximum_size },
105 { "floating_minimum_size", cmd_floating_minimum_size },
106 { "floating_modifier", cmd_floating_modifier },
107 { "focus", cmd_focus },
103 { "focus_follows_mouse", cmd_focus_follows_mouse }, 108 { "focus_follows_mouse", cmd_focus_follows_mouse },
104 { "focus_wrapping", cmd_focus_wrapping }, 109 { "focus_wrapping", cmd_focus_wrapping },
105 { "font", cmd_font }, 110 { "font", cmd_font },
106 { "for_window", cmd_for_window }, 111 { "for_window", cmd_for_window },
112 { "force_display_urgency_hint", cmd_force_display_urgency_hint },
107 { "force_focus_wrapping", cmd_force_focus_wrapping }, 113 { "force_focus_wrapping", cmd_force_focus_wrapping },
108 { "fullscreen", cmd_fullscreen }, 114 { "fullscreen", cmd_fullscreen },
109 { "gaps", cmd_gaps }, 115 { "gaps", cmd_gaps },
@@ -112,6 +118,7 @@ static struct cmd_handler handlers[] = {
112 { "input", cmd_input }, 118 { "input", cmd_input },
113 { "mode", cmd_mode }, 119 { "mode", cmd_mode },
114 { "mouse_warping", cmd_mouse_warping }, 120 { "mouse_warping", cmd_mouse_warping },
121 { "no_focus", cmd_no_focus },
115 { "output", cmd_output }, 122 { "output", cmd_output },
116 { "seat", cmd_seat }, 123 { "seat", cmd_seat },
117 { "set", cmd_set }, 124 { "set", cmd_set },
@@ -133,7 +140,6 @@ static struct cmd_handler command_handlers[] = {
133 { "border", cmd_border }, 140 { "border", cmd_border },
134 { "exit", cmd_exit }, 141 { "exit", cmd_exit },
135 { "floating", cmd_floating }, 142 { "floating", cmd_floating },
136 { "focus", cmd_focus },
137 { "fullscreen", cmd_fullscreen }, 143 { "fullscreen", cmd_fullscreen },
138 { "kill", cmd_kill }, 144 { "kill", cmd_kill },
139 { "layout", cmd_layout }, 145 { "layout", cmd_layout },
@@ -143,6 +149,7 @@ static struct cmd_handler command_handlers[] = {
143 { "reload", cmd_reload }, 149 { "reload", cmd_reload },
144 { "rename", cmd_rename }, 150 { "rename", cmd_rename },
145 { "resize", cmd_resize }, 151 { "resize", cmd_resize },
152 { "scratchpad", cmd_scratchpad },
146 { "split", cmd_split }, 153 { "split", cmd_split },
147 { "splith", cmd_splith }, 154 { "splith", cmd_splith },
148 { "splitt", cmd_splitt }, 155 { "splitt", cmd_splitt },
@@ -151,6 +158,7 @@ static struct cmd_handler command_handlers[] = {
151 { "swap", cmd_swap }, 158 { "swap", cmd_swap },
152 { "title_format", cmd_title_format }, 159 { "title_format", cmd_title_format },
153 { "unmark", cmd_unmark }, 160 { "unmark", cmd_unmark },
161 { "urgent", cmd_urgent },
154}; 162};
155 163
156static int handler_compare(const void *_a, const void *_b) { 164static int handler_compare(const void *_a, const void *_b) {
@@ -163,7 +171,7 @@ struct cmd_handler *find_handler(char *line, struct cmd_handler *cmd_handlers,
163 int handlers_size) { 171 int handlers_size) {
164 struct cmd_handler d = { .command=line }; 172 struct cmd_handler d = { .command=line };
165 struct cmd_handler *res = NULL; 173 struct cmd_handler *res = NULL;
166 wlr_log(L_DEBUG, "find_handler(%s)", line); 174 wlr_log(WLR_DEBUG, "find_handler(%s)", line);
167 175
168 bool config_loading = config->reading || !config->active; 176 bool config_loading = config->reading || !config->active;
169 177
@@ -248,10 +256,10 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) {
248 cmd = argsep(&cmdlist, ","); 256 cmd = argsep(&cmdlist, ",");
249 cmd += strspn(cmd, whitespace); 257 cmd += strspn(cmd, whitespace);
250 if (strcmp(cmd, "") == 0) { 258 if (strcmp(cmd, "") == 0) {
251 wlr_log(L_INFO, "Ignoring empty command."); 259 wlr_log(WLR_INFO, "Ignoring empty command.");
252 continue; 260 continue;
253 } 261 }
254 wlr_log(L_INFO, "Handling command '%s'", cmd); 262 wlr_log(WLR_INFO, "Handling command '%s'", cmd);
255 //TODO better handling of argv 263 //TODO better handling of argv
256 int argc; 264 int argc;
257 char **argv = split_args(cmd, &argc); 265 char **argv = split_args(cmd, &argc);
@@ -319,7 +327,7 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) {
319 } while(head); 327 } while(head);
320cleanup: 328cleanup:
321 free(exec); 329 free(exec);
322 free(views); 330 list_free(views);
323 if (!results) { 331 if (!results) {
324 results = cmd_results_new(CMD_SUCCESS, NULL, NULL); 332 results = cmd_results_new(CMD_SUCCESS, NULL, NULL);
325 } 333 }
@@ -344,7 +352,7 @@ struct cmd_results *config_command(char *exec) {
344 352
345 // Start block 353 // Start block
346 if (argc > 1 && strcmp(argv[argc - 1], "{") == 0) { 354 if (argc > 1 && strcmp(argv[argc - 1], "{") == 0) {
347 char *block = join_args(argv, argc - 1); 355 char *block = join_args(argv, argc - 1);
348 results = cmd_results_new(CMD_BLOCK, block, NULL); 356 results = cmd_results_new(CMD_BLOCK, block, NULL);
349 free(block); 357 free(block);
350 goto cleanup; 358 goto cleanup;
@@ -355,7 +363,7 @@ struct cmd_results *config_command(char *exec) {
355 results = cmd_results_new(CMD_BLOCK_END, NULL, NULL); 363 results = cmd_results_new(CMD_BLOCK_END, NULL, NULL);
356 goto cleanup; 364 goto cleanup;
357 } 365 }
358 wlr_log(L_INFO, "handling config command '%s'", exec); 366 wlr_log(WLR_INFO, "handling config command '%s'", exec);
359 struct cmd_handler *handler = find_handler(argv[0], NULL, 0); 367 struct cmd_handler *handler = find_handler(argv[0], NULL, 0);
360 if (!handler) { 368 if (!handler) {
361 char *input = argv[0] ? argv[0] : "(empty)"; 369 char *input = argv[0] ? argv[0] : "(empty)";
@@ -388,7 +396,7 @@ cleanup:
388struct cmd_results *config_subcommand(char **argv, int argc, 396struct cmd_results *config_subcommand(char **argv, int argc,
389 struct cmd_handler *handlers, size_t handlers_size) { 397 struct cmd_handler *handlers, size_t handlers_size) {
390 char *command = join_args(argv, argc); 398 char *command = join_args(argv, argc);
391 wlr_log(L_DEBUG, "Subcommand: %s", command); 399 wlr_log(WLR_DEBUG, "Subcommand: %s", command);
392 free(command); 400 free(command);
393 401
394 struct cmd_handler *handler = find_handler(argv[0], handlers, 402 struct cmd_handler *handler = find_handler(argv[0], handlers,
@@ -428,8 +436,7 @@ struct cmd_results *config_commands_command(char *exec) {
428 436
429 struct cmd_handler *handler = find_handler(cmd, NULL, 0); 437 struct cmd_handler *handler = find_handler(cmd, NULL, 0);
430 if (!handler && strcmp(cmd, "*") != 0) { 438 if (!handler && strcmp(cmd, "*") != 0) {
431 char *input = cmd ? cmd : "(empty)"; 439 results = cmd_results_new(CMD_INVALID, cmd, "Unknown/invalid command");
432 results = cmd_results_new(CMD_INVALID, input, "Unknown/invalid command");
433 goto cleanup; 440 goto cleanup;
434 } 441 }
435 442
@@ -471,14 +478,16 @@ struct cmd_results *config_commands_command(char *exec) {
471 } 478 }
472 if (!policy) { 479 if (!policy) {
473 policy = alloc_command_policy(cmd); 480 policy = alloc_command_policy(cmd);
474 sway_assert(policy, "Unable to allocate security policy"); 481 if (!sway_assert(policy, "Unable to allocate security policy")) {
475 if (policy) { 482 results = cmd_results_new(CMD_INVALID, cmd,
476 list_add(config->command_policies, policy); 483 "Unable to allocate memory");
484 goto cleanup;
477 } 485 }
486 list_add(config->command_policies, policy);
478 } 487 }
479 policy->context = context; 488 policy->context = context;
480 489
481 wlr_log(L_INFO, "Set command policy for %s to %d", 490 wlr_log(WLR_INFO, "Set command policy for %s to %d",
482 policy->command, policy->context); 491 policy->command, policy->context);
483 492
484 results = cmd_results_new(CMD_SUCCESS, NULL, NULL); 493 results = cmd_results_new(CMD_SUCCESS, NULL, NULL);
@@ -492,7 +501,7 @@ struct cmd_results *cmd_results_new(enum cmd_status status,
492 const char *input, const char *format, ...) { 501 const char *input, const char *format, ...) {
493 struct cmd_results *results = malloc(sizeof(struct cmd_results)); 502 struct cmd_results *results = malloc(sizeof(struct cmd_results));
494 if (!results) { 503 if (!results) {
495 wlr_log(L_ERROR, "Unable to allocate command results"); 504 wlr_log(WLR_ERROR, "Unable to allocate command results");
496 return NULL; 505 return NULL;
497 } 506 }
498 results->status = status; 507 results->status = status;
@@ -526,7 +535,7 @@ void free_cmd_results(struct cmd_results *results) {
526 free(results); 535 free(results);
527} 536}
528 537
529const char *cmd_results_to_json(struct cmd_results *results) { 538char *cmd_results_to_json(struct cmd_results *results) {
530 json_object *result_array = json_object_new_array(); 539 json_object *result_array = json_object_new_array();
531 json_object *root = json_object_new_object(); 540 json_object *root = json_object_new_object();
532 json_object_object_add(root, "success", 541 json_object_object_add(root, "success",
@@ -541,9 +550,9 @@ const char *cmd_results_to_json(struct cmd_results *results) {
541 } 550 }
542 json_object_array_add(result_array, root); 551 json_object_array_add(result_array, root);
543 const char *json = json_object_to_json_string(result_array); 552 const char *json = json_object_to_json_string(result_array);
544 free(result_array); 553 char *res = strdup(json);
545 free(root); 554 json_object_put(result_array);
546 return json; 555 return res;
547} 556}
548 557
549/** 558/**
diff --git a/sway/commands/assign.c b/sway/commands/assign.c
index 9d15e166..0bc0929a 100644
--- a/sway/commands/assign.c
+++ b/sway/commands/assign.c
@@ -27,6 +27,7 @@ struct cmd_results *cmd_assign(int argc, char **argv) {
27 27
28 if (strncmp(*argv, "→", strlen("→")) == 0) { 28 if (strncmp(*argv, "→", strlen("→")) == 0) {
29 if (argc < 3) { 29 if (argc < 3) {
30 free(criteria);
30 return cmd_results_new(CMD_INVALID, "assign", "Missing workspace"); 31 return cmd_results_new(CMD_INVALID, "assign", "Missing workspace");
31 } 32 }
32 ++argv; 33 ++argv;
@@ -44,7 +45,7 @@ struct cmd_results *cmd_assign(int argc, char **argv) {
44 criteria->target = join_args(argv, target_len); 45 criteria->target = join_args(argv, target_len);
45 46
46 list_add(config->criteria, criteria); 47 list_add(config->criteria, criteria);
47 wlr_log(L_DEBUG, "assign: '%s' -> '%s' added", criteria->raw, 48 wlr_log(WLR_DEBUG, "assign: '%s' -> '%s' added", criteria->raw,
48 criteria->target); 49 criteria->target);
49 50
50 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 51 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
diff --git a/sway/commands/bar.c b/sway/commands/bar.c
index d84ce808..f6a70c17 100644
--- a/sway/commands/bar.c
+++ b/sway/commands/bar.c
@@ -63,13 +63,13 @@ struct cmd_results *cmd_bar(int argc, char **argv) {
63 for (int i = 0; i < config->bars->length; ++i) { 63 for (int i = 0; i < config->bars->length; ++i) {
64 struct bar_config *item = config->bars->items[i]; 64 struct bar_config *item = config->bars->items[i];
65 if (strcmp(item->id, argv[0]) == 0) { 65 if (strcmp(item->id, argv[0]) == 0) {
66 wlr_log(L_DEBUG, "Selecting bar: %s", argv[0]); 66 wlr_log(WLR_DEBUG, "Selecting bar: %s", argv[0]);
67 bar = item; 67 bar = item;
68 break; 68 break;
69 } 69 }
70 } 70 }
71 if (!bar) { 71 if (!bar) {
72 wlr_log(L_DEBUG, "Creating bar: %s", argv[0]); 72 wlr_log(WLR_DEBUG, "Creating bar: %s", argv[0]);
73 bar = default_bar_config(); 73 bar = default_bar_config();
74 if (!bar) { 74 if (!bar) {
75 return cmd_results_new(CMD_FAILURE, "bar", 75 return cmd_results_new(CMD_FAILURE, "bar",
@@ -108,7 +108,7 @@ struct cmd_results *cmd_bar(int argc, char **argv) {
108 108
109 // Set current bar 109 // Set current bar
110 config->current_bar = bar; 110 config->current_bar = bar;
111 wlr_log(L_DEBUG, "Creating bar %s", bar->id); 111 wlr_log(WLR_DEBUG, "Creating bar %s", bar->id);
112 } 112 }
113 113
114 return config_subcommand(argv, argc, bar_handlers, sizeof(bar_handlers)); 114 return config_subcommand(argv, argc, bar_handlers, sizeof(bar_handlers));
diff --git a/sway/commands/bar/binding_mode_indicator.c b/sway/commands/bar/binding_mode_indicator.c
index 3ba5f33f..0c48bee9 100644
--- a/sway/commands/bar/binding_mode_indicator.c
+++ b/sway/commands/bar/binding_mode_indicator.c
@@ -15,11 +15,11 @@ struct cmd_results *bar_cmd_binding_mode_indicator(int argc, char **argv) {
15 } 15 }
16 if (strcasecmp("yes", argv[0]) == 0) { 16 if (strcasecmp("yes", argv[0]) == 0) {
17 config->current_bar->binding_mode_indicator = true; 17 config->current_bar->binding_mode_indicator = true;
18 wlr_log(L_DEBUG, "Enabling binding mode indicator on bar: %s", 18 wlr_log(WLR_DEBUG, "Enabling binding mode indicator on bar: %s",
19 config->current_bar->id); 19 config->current_bar->id);
20 } else if (strcasecmp("no", argv[0]) == 0) { 20 } else if (strcasecmp("no", argv[0]) == 0) {
21 config->current_bar->binding_mode_indicator = false; 21 config->current_bar->binding_mode_indicator = false;
22 wlr_log(L_DEBUG, "Disabling binding mode indicator on bar: %s", 22 wlr_log(WLR_DEBUG, "Disabling binding mode indicator on bar: %s",
23 config->current_bar->id); 23 config->current_bar->id);
24 } 24 }
25 return cmd_results_new(CMD_INVALID, "binding_mode_indicator", 25 return cmd_results_new(CMD_INVALID, "binding_mode_indicator",
diff --git a/sway/commands/bar/font.c b/sway/commands/bar/font.c
index 80b7a593..2aa4e895 100644
--- a/sway/commands/bar/font.c
+++ b/sway/commands/bar/font.c
@@ -14,8 +14,8 @@ struct cmd_results *bar_cmd_font(int argc, char **argv) {
14 } 14 }
15 char *font = join_args(argv, argc); 15 char *font = join_args(argv, argc);
16 free(config->current_bar->font); 16 free(config->current_bar->font);
17 config->current_bar->font = strdup(font); 17 config->current_bar->font = font;
18 wlr_log(L_DEBUG, "Settings font '%s' for bar: %s", 18 wlr_log(WLR_DEBUG, "Settings font '%s' for bar: %s",
19 config->current_bar->font, config->current_bar->id); 19 config->current_bar->font, config->current_bar->id);
20 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 20 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
21} 21}
diff --git a/sway/commands/bar/height.c b/sway/commands/bar/height.c
index 3160caed..18258526 100644
--- a/sway/commands/bar/height.c
+++ b/sway/commands/bar/height.c
@@ -14,7 +14,7 @@ struct cmd_results *bar_cmd_height(int argc, char **argv) {
14 "Invalid height value: %s", argv[0]); 14 "Invalid height value: %s", argv[0]);
15 } 15 }
16 config->current_bar->height = height; 16 config->current_bar->height = height;
17 wlr_log(L_DEBUG, "Setting bar height to %d on bar: %s", 17 wlr_log(WLR_DEBUG, "Setting bar height to %d on bar: %s",
18 height, config->current_bar->id); 18 height, config->current_bar->id);
19 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 19 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
20} 20}
diff --git a/sway/commands/bar/hidden_state.c b/sway/commands/bar/hidden_state.c
index 6641f184..502ce2c4 100644
--- a/sway/commands/bar/hidden_state.c
+++ b/sway/commands/bar/hidden_state.c
@@ -27,7 +27,7 @@ static struct cmd_results *bar_set_hidden_state(struct bar_config *bar,
27 if (!config->reading) { 27 if (!config->reading) {
28 ipc_event_barconfig_update(bar); 28 ipc_event_barconfig_update(bar);
29 } 29 }
30 wlr_log(L_DEBUG, "Setting hidden_state: '%s' for bar: %s", 30 wlr_log(WLR_DEBUG, "Setting hidden_state: '%s' for bar: %s",
31 bar->hidden_state, bar->id); 31 bar->hidden_state, bar->id);
32 } 32 }
33 // free old mode 33 // free old mode
diff --git a/sway/commands/bar/id.c b/sway/commands/bar/id.c
index 6ce86fef..65fa69fd 100644
--- a/sway/commands/bar/id.c
+++ b/sway/commands/bar/id.c
@@ -24,7 +24,7 @@ struct cmd_results *bar_cmd_id(int argc, char **argv) {
24 } 24 }
25 } 25 }
26 26
27 wlr_log(L_DEBUG, "Renaming bar: '%s' to '%s'", oldname, name); 27 wlr_log(WLR_DEBUG, "Renaming bar: '%s' to '%s'", oldname, name);
28 28
29 // free old bar id 29 // free old bar id
30 free(config->current_bar->id); 30 free(config->current_bar->id);
diff --git a/sway/commands/bar/mode.c b/sway/commands/bar/mode.c
index 34bb0a4f..28e2d77b 100644
--- a/sway/commands/bar/mode.c
+++ b/sway/commands/bar/mode.c
@@ -28,7 +28,7 @@ static struct cmd_results *bar_set_mode(struct bar_config *bar, const char *mode
28 if (!config->reading) { 28 if (!config->reading) {
29 ipc_event_barconfig_update(bar); 29 ipc_event_barconfig_update(bar);
30 } 30 }
31 wlr_log(L_DEBUG, "Setting mode: '%s' for bar: %s", bar->mode, bar->id); 31 wlr_log(WLR_DEBUG, "Setting mode: '%s' for bar: %s", bar->mode, bar->id);
32 } 32 }
33 33
34 // free old mode 34 // free old mode
diff --git a/sway/commands/bar/modifier.c b/sway/commands/bar/modifier.c
index 7ba4b125..09025fff 100644
--- a/sway/commands/bar/modifier.c
+++ b/sway/commands/bar/modifier.c
@@ -22,14 +22,15 @@ struct cmd_results *bar_cmd_modifier(int argc, char **argv) {
22 mod |= tmp_mod; 22 mod |= tmp_mod;
23 continue; 23 continue;
24 } else { 24 } else {
25 error = cmd_results_new(CMD_INVALID, "modifier",
26 "Unknown modifier '%s'", split->items[i]);
25 free_flat_list(split); 27 free_flat_list(split);
26 return cmd_results_new(CMD_INVALID, "modifier", 28 return error;
27 "Unknown modifier '%s'", split->items[i]);
28 } 29 }
29 } 30 }
30 free_flat_list(split); 31 free_flat_list(split);
31 config->current_bar->modifier = mod; 32 config->current_bar->modifier = mod;
32 wlr_log(L_DEBUG, 33 wlr_log(WLR_DEBUG,
33 "Show/Hide the bar when pressing '%s' in hide mode.", argv[0]); 34 "Show/Hide the bar when pressing '%s' in hide mode.", argv[0]);
34 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 35 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
35} 36}
diff --git a/sway/commands/bar/output.c b/sway/commands/bar/output.c
index f7ca0aa4..72754e05 100644
--- a/sway/commands/bar/output.c
+++ b/sway/commands/bar/output.c
@@ -42,7 +42,7 @@ struct cmd_results *bar_cmd_output(int argc, char **argv) {
42 42
43 if (add_output) { 43 if (add_output) {
44 list_add(outputs, strdup(output)); 44 list_add(outputs, strdup(output));
45 wlr_log(L_DEBUG, "Adding bar: '%s' to output '%s'", 45 wlr_log(WLR_DEBUG, "Adding bar: '%s' to output '%s'",
46 config->current_bar->id, output); 46 config->current_bar->id, output);
47 } 47 }
48 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 48 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
diff --git a/sway/commands/bar/pango_markup.c b/sway/commands/bar/pango_markup.c
index 480af724..857571fb 100644
--- a/sway/commands/bar/pango_markup.c
+++ b/sway/commands/bar/pango_markup.c
@@ -13,11 +13,11 @@ struct cmd_results *bar_cmd_pango_markup(int argc, char **argv) {
13 } 13 }
14 if (strcasecmp("enabled", argv[0]) == 0) { 14 if (strcasecmp("enabled", argv[0]) == 0) {
15 config->current_bar->pango_markup = true; 15 config->current_bar->pango_markup = true;
16 wlr_log(L_DEBUG, "Enabling pango markup for bar: %s", 16 wlr_log(WLR_DEBUG, "Enabling pango markup for bar: %s",
17 config->current_bar->id); 17 config->current_bar->id);
18 } else if (strcasecmp("disabled", argv[0]) == 0) { 18 } else if (strcasecmp("disabled", argv[0]) == 0) {
19 config->current_bar->pango_markup = false; 19 config->current_bar->pango_markup = false;
20 wlr_log(L_DEBUG, "Disabling pango markup for bar: %s", 20 wlr_log(WLR_DEBUG, "Disabling pango markup for bar: %s",
21 config->current_bar->id); 21 config->current_bar->id);
22 } else { 22 } else {
23 error = cmd_results_new(CMD_INVALID, "pango_markup", 23 error = cmd_results_new(CMD_INVALID, "pango_markup",
diff --git a/sway/commands/bar/position.c b/sway/commands/bar/position.c
index 9c580483..44bb4ae3 100644
--- a/sway/commands/bar/position.c
+++ b/sway/commands/bar/position.c
@@ -15,8 +15,9 @@ struct cmd_results *bar_cmd_position(int argc, char **argv) {
15 char *valid[] = { "top", "bottom", "left", "right" }; 15 char *valid[] = { "top", "bottom", "left", "right" };
16 for (size_t i = 0; i < sizeof(valid) / sizeof(valid[0]); ++i) { 16 for (size_t i = 0; i < sizeof(valid) / sizeof(valid[0]); ++i) {
17 if (strcasecmp(valid[i], argv[0]) == 0) { 17 if (strcasecmp(valid[i], argv[0]) == 0) {
18 wlr_log(L_DEBUG, "Setting bar position '%s' for bar: %s", 18 wlr_log(WLR_DEBUG, "Setting bar position '%s' for bar: %s",
19 argv[0], config->current_bar->id); 19 argv[0], config->current_bar->id);
20 free(config->current_bar->position);
20 config->current_bar->position = strdup(argv[0]); 21 config->current_bar->position = strdup(argv[0]);
21 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 22 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
22 } 23 }
diff --git a/sway/commands/bar/separator_symbol.c b/sway/commands/bar/separator_symbol.c
index 1e08df6d..392ab730 100644
--- a/sway/commands/bar/separator_symbol.c
+++ b/sway/commands/bar/separator_symbol.c
@@ -14,7 +14,7 @@ struct cmd_results *bar_cmd_separator_symbol(int argc, char **argv) {
14 } 14 }
15 free(config->current_bar->separator_symbol); 15 free(config->current_bar->separator_symbol);
16 config->current_bar->separator_symbol = strdup(argv[0]); 16 config->current_bar->separator_symbol = strdup(argv[0]);
17 wlr_log(L_DEBUG, "Settings separator_symbol '%s' for bar: %s", 17 wlr_log(WLR_DEBUG, "Settings separator_symbol '%s' for bar: %s",
18 config->current_bar->separator_symbol, config->current_bar->id); 18 config->current_bar->separator_symbol, config->current_bar->id);
19 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 19 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
20} 20}
diff --git a/sway/commands/bar/status_command.c b/sway/commands/bar/status_command.c
index 5e199cde..6f6f81a3 100644
--- a/sway/commands/bar/status_command.c
+++ b/sway/commands/bar/status_command.c
@@ -14,7 +14,7 @@ struct cmd_results *bar_cmd_status_command(int argc, char **argv) {
14 } 14 }
15 free(config->current_bar->status_command); 15 free(config->current_bar->status_command);
16 config->current_bar->status_command = join_args(argv, argc); 16 config->current_bar->status_command = join_args(argv, argc);
17 wlr_log(L_DEBUG, "Feeding bar with status command: %s", 17 wlr_log(WLR_DEBUG, "Feeding bar with status command: %s",
18 config->current_bar->status_command); 18 config->current_bar->status_command);
19 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 19 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
20} 20}
diff --git a/sway/commands/bar/strip_workspace_numbers.c b/sway/commands/bar/strip_workspace_numbers.c
index 4f24a356..4e47d047 100644
--- a/sway/commands/bar/strip_workspace_numbers.c
+++ b/sway/commands/bar/strip_workspace_numbers.c
@@ -15,11 +15,11 @@ struct cmd_results *bar_cmd_strip_workspace_numbers(int argc, char **argv) {
15 } 15 }
16 if (strcasecmp("yes", argv[0]) == 0) { 16 if (strcasecmp("yes", argv[0]) == 0) {
17 config->current_bar->strip_workspace_numbers = true; 17 config->current_bar->strip_workspace_numbers = true;
18 wlr_log(L_DEBUG, "Stripping workspace numbers on bar: %s", 18 wlr_log(WLR_DEBUG, "Stripping workspace numbers on bar: %s",
19 config->current_bar->id); 19 config->current_bar->id);
20 } else if (strcasecmp("no", argv[0]) == 0) { 20 } else if (strcasecmp("no", argv[0]) == 0) {
21 config->current_bar->strip_workspace_numbers = false; 21 config->current_bar->strip_workspace_numbers = false;
22 wlr_log(L_DEBUG, "Enabling workspace numbers on bar: %s", 22 wlr_log(WLR_DEBUG, "Enabling workspace numbers on bar: %s",
23 config->current_bar->id); 23 config->current_bar->id);
24 } else { 24 } else {
25 return cmd_results_new(CMD_INVALID, 25 return cmd_results_new(CMD_INVALID,
diff --git a/sway/commands/bar/swaybar_command.c b/sway/commands/bar/swaybar_command.c
index 520cdd11..04e78e77 100644
--- a/sway/commands/bar/swaybar_command.c
+++ b/sway/commands/bar/swaybar_command.c
@@ -14,7 +14,7 @@ struct cmd_results *bar_cmd_swaybar_command(int argc, char **argv) {
14 } 14 }
15 free(config->current_bar->swaybar_command); 15 free(config->current_bar->swaybar_command);
16 config->current_bar->swaybar_command = join_args(argv, argc); 16 config->current_bar->swaybar_command = join_args(argv, argc);
17 wlr_log(L_DEBUG, "Using custom swaybar command: %s", 17 wlr_log(WLR_DEBUG, "Using custom swaybar command: %s",
18 config->current_bar->swaybar_command); 18 config->current_bar->swaybar_command);
19 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 19 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
20} 20}
diff --git a/sway/commands/bar/workspace_buttons.c b/sway/commands/bar/workspace_buttons.c
index 6edc3a0d..a4079b2a 100644
--- a/sway/commands/bar/workspace_buttons.c
+++ b/sway/commands/bar/workspace_buttons.c
@@ -14,11 +14,11 @@ struct cmd_results *bar_cmd_workspace_buttons(int argc, char **argv) {
14 } 14 }
15 if (strcasecmp("yes", argv[0]) == 0) { 15 if (strcasecmp("yes", argv[0]) == 0) {
16 config->current_bar->workspace_buttons = true; 16 config->current_bar->workspace_buttons = true;
17 wlr_log(L_DEBUG, "Enabling workspace buttons on bar: %s", 17 wlr_log(WLR_DEBUG, "Enabling workspace buttons on bar: %s",
18 config->current_bar->id); 18 config->current_bar->id);
19 } else if (strcasecmp("no", argv[0]) == 0) { 19 } else if (strcasecmp("no", argv[0]) == 0) {
20 config->current_bar->workspace_buttons = false; 20 config->current_bar->workspace_buttons = false;
21 wlr_log(L_DEBUG, "Disabling workspace buttons on bar: %s", 21 wlr_log(WLR_DEBUG, "Disabling workspace buttons on bar: %s",
22 config->current_bar->id); 22 config->current_bar->id);
23 } else { 23 } else {
24 return cmd_results_new(CMD_INVALID, "workspace_buttons", 24 return cmd_results_new(CMD_INVALID, "workspace_buttons",
diff --git a/sway/commands/bar/wrap_scroll.c b/sway/commands/bar/wrap_scroll.c
index 7386f82c..701de00a 100644
--- a/sway/commands/bar/wrap_scroll.c
+++ b/sway/commands/bar/wrap_scroll.c
@@ -13,11 +13,11 @@ struct cmd_results *bar_cmd_wrap_scroll(int argc, char **argv) {
13 } 13 }
14 if (strcasecmp("yes", argv[0]) == 0) { 14 if (strcasecmp("yes", argv[0]) == 0) {
15 config->current_bar->wrap_scroll = true; 15 config->current_bar->wrap_scroll = true;
16 wlr_log(L_DEBUG, "Enabling wrap scroll on bar: %s", 16 wlr_log(WLR_DEBUG, "Enabling wrap scroll on bar: %s",
17 config->current_bar->id); 17 config->current_bar->id);
18 } else if (strcasecmp("no", argv[0]) == 0) { 18 } else if (strcasecmp("no", argv[0]) == 0) {
19 config->current_bar->wrap_scroll = false; 19 config->current_bar->wrap_scroll = false;
20 wlr_log(L_DEBUG, "Disabling wrap scroll on bar: %s", 20 wlr_log(WLR_DEBUG, "Disabling wrap scroll on bar: %s",
21 config->current_bar->id); 21 config->current_bar->id);
22 } else { 22 } else {
23 return cmd_results_new(CMD_INVALID, 23 return cmd_results_new(CMD_INVALID,
diff --git a/sway/commands/bind.c b/sway/commands/bind.c
index 821f9cd1..83e9e432 100644
--- a/sway/commands/bind.c
+++ b/sway/commands/bind.c
@@ -184,7 +184,7 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv,
184 for (int i = 0; i < mode_bindings->length; ++i) { 184 for (int i = 0; i < mode_bindings->length; ++i) {
185 struct sway_binding *config_binding = mode_bindings->items[i]; 185 struct sway_binding *config_binding = mode_bindings->items[i];
186 if (binding_key_compare(binding, config_binding)) { 186 if (binding_key_compare(binding, config_binding)) {
187 wlr_log(L_DEBUG, "overwriting old binding with command '%s'", 187 wlr_log(WLR_DEBUG, "overwriting old binding with command '%s'",
188 config_binding->command); 188 config_binding->command);
189 free_sway_binding(config_binding); 189 free_sway_binding(config_binding);
190 mode_bindings->items[i] = binding; 190 mode_bindings->items[i] = binding;
@@ -196,7 +196,7 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv,
196 list_add(mode_bindings, binding); 196 list_add(mode_bindings, binding);
197 } 197 }
198 198
199 wlr_log(L_DEBUG, "%s - Bound %s to command %s", 199 wlr_log(WLR_DEBUG, "%s - Bound %s to command %s",
200 bindtype, argv[0], binding->command); 200 bindtype, argv[0], binding->command);
201 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 201 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
202 202
diff --git a/sway/commands/border.c b/sway/commands/border.c
index 6db85395..9c19e20a 100644
--- a/sway/commands/border.c
+++ b/sway/commands/border.c
@@ -42,7 +42,7 @@ struct cmd_results *cmd_border(int argc, char **argv) {
42 container_set_geometry_from_floating_view(view->swayc); 42 container_set_geometry_from_floating_view(view->swayc);
43 } 43 }
44 44
45 arrange_and_commit(view->swayc); 45 arrange_windows(view->swayc);
46 46
47 struct sway_seat *seat = input_manager_current_seat(input_manager); 47 struct sway_seat *seat = input_manager_current_seat(input_manager);
48 if (seat->cursor) { 48 if (seat->cursor) {
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/exec.c b/sway/commands/exec.c
index 363d5bef..7fc54123 100644
--- a/sway/commands/exec.c
+++ b/sway/commands/exec.c
@@ -8,7 +8,7 @@ struct cmd_results *cmd_exec(int argc, char **argv) {
8 if (!config->active) return cmd_results_new(CMD_DEFER, "exec", NULL); 8 if (!config->active) return cmd_results_new(CMD_DEFER, "exec", NULL);
9 if (config->reloading) { 9 if (config->reloading) {
10 char *args = join_args(argv, argc); 10 char *args = join_args(argv, argc);
11 wlr_log(L_DEBUG, "Ignoring 'exec %s' due to reload", args); 11 wlr_log(WLR_DEBUG, "Ignoring 'exec %s' due to reload", args);
12 free(args); 12 free(args);
13 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 13 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
14 } 14 }
diff --git a/sway/commands/exec_always.c b/sway/commands/exec_always.c
index abd52e59..9bf2b320 100644
--- a/sway/commands/exec_always.c
+++ b/sway/commands/exec_always.c
@@ -20,7 +20,7 @@ struct cmd_results *cmd_exec_always(int argc, char **argv) {
20 20
21 char *tmp = NULL; 21 char *tmp = NULL;
22 if (strcmp((char*)*argv, "--no-startup-id") == 0) { 22 if (strcmp((char*)*argv, "--no-startup-id") == 0) {
23 wlr_log(L_INFO, "exec switch '--no-startup-id' not supported, ignored."); 23 wlr_log(WLR_INFO, "exec switch '--no-startup-id' not supported, ignored.");
24 if ((error = checkarg(argc - 1, "exec_always", EXPECTED_MORE_THAN, 0))) { 24 if ((error = checkarg(argc - 1, "exec_always", EXPECTED_MORE_THAN, 0))) {
25 return error; 25 return error;
26 } 26 }
@@ -35,50 +35,49 @@ struct cmd_results *cmd_exec_always(int argc, char **argv) {
35 strncpy(cmd, tmp, sizeof(cmd) - 1); 35 strncpy(cmd, tmp, sizeof(cmd) - 1);
36 cmd[sizeof(cmd) - 1] = 0; 36 cmd[sizeof(cmd) - 1] = 0;
37 free(tmp); 37 free(tmp);
38 wlr_log(L_DEBUG, "Executing %s", cmd); 38 wlr_log(WLR_DEBUG, "Executing %s", cmd);
39 39
40 int fd[2]; 40 int fd[2];
41 if (pipe(fd) != 0) { 41 if (pipe(fd) != 0) {
42 wlr_log(L_ERROR, "Unable to create pipe for fork"); 42 wlr_log(WLR_ERROR, "Unable to create pipe for fork");
43 } 43 }
44 44
45 pid_t pid; 45 pid_t pid, child;
46 pid_t *child = malloc(sizeof(pid_t)); // malloc'd so that Linux can avoid copying the process space
47 if (!child) {
48 return cmd_results_new(CMD_FAILURE, "exec_always", "Unable to allocate child pid");
49 }
50 // Fork process 46 // Fork process
51 if ((pid = fork()) == 0) { 47 if ((pid = fork()) == 0) {
52 // Fork child process again 48 // Fork child process again
53 setsid(); 49 setsid();
54 if ((*child = fork()) == 0) { 50 close(fd[0]);
51 if ((child = fork()) == 0) {
52 close(fd[1]);
55 execl("/bin/sh", "/bin/sh", "-c", cmd, (void *)NULL); 53 execl("/bin/sh", "/bin/sh", "-c", cmd, (void *)NULL);
56 // Not reached 54 _exit(0);
57 } 55 }
58 close(fd[0]);
59 ssize_t s = 0; 56 ssize_t s = 0;
60 while ((size_t)s < sizeof(pid_t)) { 57 while ((size_t)s < sizeof(pid_t)) {
61 s += write(fd[1], ((uint8_t *)child) + s, sizeof(pid_t) - s); 58 s += write(fd[1], ((uint8_t *)&child) + s, sizeof(pid_t) - s);
62 } 59 }
63 close(fd[1]); 60 close(fd[1]);
64 _exit(0); // Close child process 61 _exit(0); // Close child process
65 } else if (pid < 0) { 62 } else if (pid < 0) {
66 free(child); 63 close(fd[0]);
64 close(fd[1]);
67 return cmd_results_new(CMD_FAILURE, "exec_always", "fork() failed"); 65 return cmd_results_new(CMD_FAILURE, "exec_always", "fork() failed");
68 } 66 }
69 close(fd[1]); // close write 67 close(fd[1]); // close write
70 ssize_t s = 0; 68 ssize_t s = 0;
71 while ((size_t)s < sizeof(pid_t)) { 69 while ((size_t)s < sizeof(pid_t)) {
72 s += read(fd[0], ((uint8_t *)child) + s, sizeof(pid_t) - s); 70 s += read(fd[0], ((uint8_t *)&child) + s, sizeof(pid_t) - s);
73 } 71 }
74 close(fd[0]); 72 close(fd[0]);
75 // cleanup child process 73 // cleanup child process
76 waitpid(pid, NULL, 0); 74 waitpid(pid, NULL, 0);
77 if (*child > 0) { 75 if (child > 0) {
78 wlr_log(L_DEBUG, "Child process created with pid %d", *child); 76 wlr_log(WLR_DEBUG, "Child process created with pid %d", child);
79 workspace_record_pid(*child); 77 workspace_record_pid(child);
80 } else { 78 } else {
81 free(child); 79 return cmd_results_new(CMD_FAILURE, "exec_always",
80 "Second fork() failed");
82 } 81 }
83 82
84 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 83 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
diff --git a/sway/commands/floating.c b/sway/commands/floating.c
index e6003521..6ab56c3b 100644
--- a/sway/commands/floating.c
+++ b/sway/commands/floating.c
@@ -37,7 +37,7 @@ struct cmd_results *cmd_floating(int argc, char **argv) {
37 container_set_floating(container, wants_floating); 37 container_set_floating(container, wants_floating);
38 38
39 struct sway_container *workspace = container_parent(container, C_WORKSPACE); 39 struct sway_container *workspace = container_parent(container, C_WORKSPACE);
40 arrange_and_commit(workspace); 40 arrange_windows(workspace);
41 41
42 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 42 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
43} 43}
diff --git a/sway/commands/floating_minmax_size.c b/sway/commands/floating_minmax_size.c
new file mode 100644
index 00000000..0af78908
--- /dev/null
+++ b/sway/commands/floating_minmax_size.c
@@ -0,0 +1,53 @@
1#include <errno.h>
2#include <math.h>
3#include <stdbool.h>
4#include <stdlib.h>
5#include <string.h>
6#include <strings.h>
7#include <wlr/util/log.h>
8#include "sway/commands.h"
9#include "log.h"
10
11static const char* min_usage =
12 "Expected 'floating_minimum_size <width> x <height>'";
13
14static const char* max_usage =
15 "Expected 'floating_maximum_size <width> x <height>'";
16
17static struct cmd_results *handle_command(int argc, char **argv, char *cmd_name,
18 const char *usage, int *config_width, int *config_height) {
19 struct cmd_results *error;
20 if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 3))) {
21 return error;
22 }
23
24 char *err;
25 int width = (int)strtol(argv[0], &err, 10);
26 if (*err) {
27 return cmd_results_new(CMD_INVALID, cmd_name, usage);
28 }
29
30 if (strcmp(argv[1], "x") != 0) {
31 return cmd_results_new(CMD_INVALID, cmd_name, usage);
32 }
33
34 int height = (int)strtol(argv[2], &err, 10);
35 if (*err) {
36 return cmd_results_new(CMD_INVALID, cmd_name, usage);
37 }
38
39 *config_width = width;
40 *config_height = height;
41
42 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
43}
44
45struct cmd_results *cmd_floating_minimum_size(int argc, char **argv) {
46 return handle_command(argc, argv, "floating_minimum_size", min_usage,
47 &config->floating_minimum_width, &config->floating_minimum_height);
48}
49
50struct cmd_results *cmd_floating_maximum_size(int argc, char **argv) {
51 return handle_command(argc, argv, "floating_maximum_size", max_usage,
52 &config->floating_maximum_width, &config->floating_maximum_height);
53}
diff --git a/sway/commands/floating_modifier.c b/sway/commands/floating_modifier.c
new file mode 100644
index 00000000..9432c9f1
--- /dev/null
+++ b/sway/commands/floating_modifier.c
@@ -0,0 +1,20 @@
1#include "sway/commands.h"
2#include "sway/config.h"
3#include "util.h"
4
5struct cmd_results *cmd_floating_modifier(int argc, char **argv) {
6 struct cmd_results *error = NULL;
7 if ((error = checkarg(argc, "floating_modifier", EXPECTED_EQUAL_TO, 1))) {
8 return error;
9 }
10
11 uint32_t mod = get_modifier_mask_by_name(argv[0]);
12 if (!mod) {
13 return cmd_results_new(CMD_INVALID, "floating_modifier",
14 "Invalid modifier");
15 }
16
17 config->floating_mod = mod;
18
19 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
20}
diff --git a/sway/commands/focus.c b/sway/commands/focus.c
index 74d9d535..9cd8bfae 100644
--- a/sway/commands/focus.c
+++ b/sway/commands/focus.c
@@ -1,10 +1,14 @@
1#include <strings.h> 1#include <strings.h>
2#include <wlr/util/log.h> 2#include <wlr/util/log.h>
3#include "log.h" 3#include "log.h"
4#include "sway/commands.h"
4#include "sway/input/input-manager.h" 5#include "sway/input/input-manager.h"
5#include "sway/input/seat.h" 6#include "sway/input/seat.h"
7#include "sway/output.h"
8#include "sway/tree/arrange.h"
6#include "sway/tree/view.h" 9#include "sway/tree/view.h"
7#include "sway/commands.h" 10#include "sway/tree/workspace.h"
11#include "stringop.h"
8 12
9static bool parse_movement_direction(const char *name, 13static bool parse_movement_direction(const char *name,
10 enum movement_direction *out) { 14 enum movement_direction *out) {
@@ -27,7 +31,55 @@ static bool parse_movement_direction(const char *name,
27 return true; 31 return true;
28} 32}
29 33
34static struct cmd_results *focus_mode(struct sway_container *con,
35 struct sway_seat *seat, bool floating) {
36 struct sway_container *ws = con->type == C_WORKSPACE ?
37 con : container_parent(con, C_WORKSPACE);
38 struct sway_container *new_focus = ws;
39 if (floating) {
40 new_focus = ws->sway_workspace->floating;
41 if (new_focus->children->length == 0) {
42 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
43 }
44 }
45 seat_set_focus(seat, seat_get_active_child(seat, new_focus));
46 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
47}
48
49static struct cmd_results *focus_output(struct sway_container *con,
50 struct sway_seat *seat, int argc, char **argv) {
51 if (!argc) {
52 return cmd_results_new(CMD_INVALID, "focus",
53 "Expected 'focus output <direction|name>'");
54 }
55 char *identifier = join_args(argv, argc);
56 struct sway_container *output = output_by_name(identifier);
57
58 if (!output) {
59 enum movement_direction direction;
60 if (!parse_movement_direction(identifier, &direction) ||
61 direction == MOVE_PARENT || direction == MOVE_CHILD) {
62 free(identifier);
63 return cmd_results_new(CMD_INVALID, "focus",
64 "There is no output with that name");
65 }
66 struct sway_container *focus = seat_get_focus(seat);
67 focus = container_parent(focus, C_OUTPUT);
68 output = container_get_in_direction(focus, seat, direction);
69 }
70
71 free(identifier);
72 if (output) {
73 seat_set_focus(seat, seat_get_focus_inactive(seat, output));
74 }
75
76 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
77}
78
30struct cmd_results *cmd_focus(int argc, char **argv) { 79struct cmd_results *cmd_focus(int argc, char **argv) {
80 if (config->reading || !config->active) {
81 return cmd_results_new(CMD_DEFER, NULL, NULL);
82 }
31 struct sway_container *con = config->handler_context.current_container; 83 struct sway_container *con = config->handler_context.current_container;
32 struct sway_seat *seat = config->handler_context.seat; 84 struct sway_seat *seat = config->handler_context.seat;
33 if (con->type < C_WORKSPACE) { 85 if (con->type < C_WORKSPACE) {
@@ -40,11 +92,24 @@ struct cmd_results *cmd_focus(int argc, char **argv) {
40 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 92 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
41 } 93 }
42 94
43 // TODO mode_toggle 95 if (strcmp(argv[0], "floating") == 0) {
96 return focus_mode(con, seat, true);
97 } else if (strcmp(argv[0], "tiling") == 0) {
98 return focus_mode(con, seat, false);
99 } else if (strcmp(argv[0], "mode_toggle") == 0) {
100 return focus_mode(con, seat, !container_is_floating(con));
101 }
102
103 if (strcmp(argv[0], "output") == 0) {
104 argc--; argv++;
105 return focus_output(con, seat, argc, argv);
106 }
107
44 enum movement_direction direction = 0; 108 enum movement_direction direction = 0;
45 if (!parse_movement_direction(argv[0], &direction)) { 109 if (!parse_movement_direction(argv[0], &direction)) {
46 return cmd_results_new(CMD_INVALID, "focus", 110 return cmd_results_new(CMD_INVALID, "focus",
47 "Expected 'focus <direction|parent|child|mode_toggle>' or 'focus output <direction|name>'"); 111 "Expected 'focus <direction|parent|child|mode_toggle|floating|tiling>' "
112 "or 'focus output <direction|name>'");
48 } 113 }
49 114
50 struct sway_container *next_focus = container_get_in_direction( 115 struct sway_container *next_focus = container_get_in_direction(
diff --git a/sway/commands/for_window.c b/sway/commands/for_window.c
index 8c425a1d..ac4d6563 100644
--- a/sway/commands/for_window.c
+++ b/sway/commands/for_window.c
@@ -24,7 +24,7 @@ struct cmd_results *cmd_for_window(int argc, char **argv) {
24 criteria->cmdlist = join_args(argv + 1, argc - 1); 24 criteria->cmdlist = join_args(argv + 1, argc - 1);
25 25
26 list_add(config->criteria, criteria); 26 list_add(config->criteria, criteria);
27 wlr_log(L_DEBUG, "for_window: '%s' -> '%s' added", criteria->raw, criteria->cmdlist); 27 wlr_log(WLR_DEBUG, "for_window: '%s' -> '%s' added", criteria->raw, criteria->cmdlist);
28 28
29 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 29 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
30} 30}
diff --git a/sway/commands/force_display_urgency_hint.c b/sway/commands/force_display_urgency_hint.c
new file mode 100644
index 00000000..5e5e2d55
--- /dev/null
+++ b/sway/commands/force_display_urgency_hint.c
@@ -0,0 +1,23 @@
1#include "sway/commands.h"
2#include "sway/config.h"
3
4struct cmd_results *cmd_force_display_urgency_hint(int argc, char **argv) {
5 struct cmd_results *error = NULL;
6 if ((error = checkarg(argc, "force_display_urgency_hint",
7 EXPECTED_AT_LEAST, 1))) {
8 return error;
9 }
10
11 char *err;
12 int timeout = (int)strtol(argv[0], &err, 10);
13 if (*err) {
14 if (strcmp(err, "ms") != 0) {
15 return cmd_results_new(CMD_INVALID, "force_display_urgency_hint",
16 "Expected 'force_display_urgency_hint <timeout> ms'");
17 }
18 }
19
20 config->urgent_timeout = timeout > 0 ? timeout : 0;
21
22 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
23}
diff --git a/sway/commands/fullscreen.c b/sway/commands/fullscreen.c
index 1a4d8b41..0b5beaa2 100644
--- a/sway/commands/fullscreen.c
+++ b/sway/commands/fullscreen.c
@@ -34,7 +34,7 @@ struct cmd_results *cmd_fullscreen(int argc, char **argv) {
34 view_set_fullscreen(view, wants_fullscreen); 34 view_set_fullscreen(view, wants_fullscreen);
35 35
36 struct sway_container *workspace = container_parent(container, C_WORKSPACE); 36 struct sway_container *workspace = container_parent(container, C_WORKSPACE);
37 arrange_and_commit(workspace->parent); 37 arrange_windows(workspace->parent);
38 38
39 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 39 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
40} 40}
diff --git a/sway/commands/gaps.c b/sway/commands/gaps.c
index 801fb179..3906eb70 100644
--- a/sway/commands/gaps.c
+++ b/sway/commands/gaps.c
@@ -43,7 +43,7 @@ struct cmd_results *cmd_gaps(int argc, char **argv) {
43 return cmd_results_new(CMD_INVALID, "gaps", 43 return cmd_results_new(CMD_INVALID, "gaps",
44 "gaps edge_gaps on|off|toggle"); 44 "gaps edge_gaps on|off|toggle");
45 } 45 }
46 arrange_and_commit(&root_container); 46 arrange_windows(&root_container);
47 } else { 47 } else {
48 int amount_idx = 0; // the current index in argv 48 int amount_idx = 0; // the current index in argv
49 enum gaps_op op = GAPS_OP_SET; 49 enum gaps_op op = GAPS_OP_SET;
@@ -124,7 +124,7 @@ struct cmd_results *cmd_gaps(int argc, char **argv) {
124 if (amount_idx == 0) { // gaps <amount> 124 if (amount_idx == 0) { // gaps <amount>
125 config->gaps_inner = val; 125 config->gaps_inner = val;
126 config->gaps_outer = val; 126 config->gaps_outer = val;
127 arrange_and_commit(&root_container); 127 arrange_windows(&root_container);
128 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 128 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
129 } 129 }
130 // Other variants. The middle-length variant (gaps inner|outer <amount>) 130 // Other variants. The middle-length variant (gaps inner|outer <amount>)
@@ -155,7 +155,7 @@ struct cmd_results *cmd_gaps(int argc, char **argv) {
155 } else { 155 } else {
156 config->gaps_outer = total; 156 config->gaps_outer = total;
157 } 157 }
158 arrange_and_commit(&root_container); 158 arrange_windows(&root_container);
159 } else { 159 } else {
160 struct sway_container *c = 160 struct sway_container *c =
161 config->handler_context.current_container; 161 config->handler_context.current_container;
@@ -169,7 +169,7 @@ struct cmd_results *cmd_gaps(int argc, char **argv) {
169 c->gaps_outer = total; 169 c->gaps_outer = total;
170 } 170 }
171 171
172 arrange_and_commit(c->parent ? c->parent : &root_container); 172 arrange_windows(c->parent ? c->parent : &root_container);
173 } 173 }
174 } 174 }
175 175
diff --git a/sway/commands/input.c b/sway/commands/input.c
index 678c57c4..5b203ea0 100644
--- a/sway/commands/input.c
+++ b/sway/commands/input.c
@@ -20,8 +20,10 @@ static struct cmd_handler input_handlers[] = {
20 { "pointer_accel", input_cmd_pointer_accel }, 20 { "pointer_accel", input_cmd_pointer_accel },
21 { "repeat_delay", input_cmd_repeat_delay }, 21 { "repeat_delay", input_cmd_repeat_delay },
22 { "repeat_rate", input_cmd_repeat_rate }, 22 { "repeat_rate", input_cmd_repeat_rate },
23 { "scroll_button", input_cmd_scroll_button },
23 { "scroll_method", input_cmd_scroll_method }, 24 { "scroll_method", input_cmd_scroll_method },
24 { "tap", input_cmd_tap }, 25 { "tap", input_cmd_tap },
26 { "tap_button_map", input_cmd_tap_button_map },
25 { "xkb_layout", input_cmd_xkb_layout }, 27 { "xkb_layout", input_cmd_xkb_layout },
26 { "xkb_model", input_cmd_xkb_model }, 28 { "xkb_model", input_cmd_xkb_model },
27 { "xkb_options", input_cmd_xkb_options }, 29 { "xkb_options", input_cmd_xkb_options },
@@ -35,7 +37,7 @@ struct cmd_results *cmd_input(int argc, char **argv) {
35 return error; 37 return error;
36 } 38 }
37 39
38 wlr_log(L_DEBUG, "entering input block: %s", argv[0]); 40 wlr_log(WLR_DEBUG, "entering input block: %s", argv[0]);
39 41
40 config->handler_context.input_config = new_input_config(argv[0]); 42 config->handler_context.input_config = new_input_config(argv[0]);
41 if (!config->handler_context.input_config) { 43 if (!config->handler_context.input_config) {
diff --git a/sway/commands/input/accel_profile.c b/sway/commands/input/accel_profile.c
index 37d6e133..a4108ec3 100644
--- a/sway/commands/input/accel_profile.c
+++ b/sway/commands/input/accel_profile.c
@@ -23,6 +23,7 @@ struct cmd_results *input_cmd_accel_profile(int argc, char **argv) {
23 } else if (strcasecmp(argv[0], "flat") == 0) { 23 } else if (strcasecmp(argv[0], "flat") == 0) {
24 new_config->accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT; 24 new_config->accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT;
25 } else { 25 } else {
26 free_input_config(new_config);
26 return cmd_results_new(CMD_INVALID, "accel_profile", 27 return cmd_results_new(CMD_INVALID, "accel_profile",
27 "Expected 'accel_profile <adaptive|flat>'"); 28 "Expected 'accel_profile <adaptive|flat>'");
28 } 29 }
diff --git a/sway/commands/input/click_method.c b/sway/commands/input/click_method.c
index 8f1f0aa7..5d0d8cc2 100644
--- a/sway/commands/input/click_method.c
+++ b/sway/commands/input/click_method.c
@@ -26,6 +26,7 @@ struct cmd_results *input_cmd_click_method(int argc, char **argv) {
26 } else if (strcasecmp(argv[0], "clickfinger") == 0) { 26 } else if (strcasecmp(argv[0], "clickfinger") == 0) {
27 new_config->click_method = LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER; 27 new_config->click_method = LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER;
28 } else { 28 } else {
29 free_input_config(new_config);
29 return cmd_results_new(CMD_INVALID, "click_method", 30 return cmd_results_new(CMD_INVALID, "click_method",
30 "Expected 'click_method <none|button_areas|clickfinger'"); 31 "Expected 'click_method <none|button_areas|clickfinger'");
31 } 32 }
diff --git a/sway/commands/input/drag_lock.c b/sway/commands/input/drag_lock.c
index 8273a7d4..9e32816f 100644
--- a/sway/commands/input/drag_lock.c
+++ b/sway/commands/input/drag_lock.c
@@ -23,6 +23,7 @@ struct cmd_results *input_cmd_drag_lock(int argc, char **argv) {
23 } else if (strcasecmp(argv[0], "disabled") == 0) { 23 } else if (strcasecmp(argv[0], "disabled") == 0) {
24 new_config->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_DISABLED; 24 new_config->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_DISABLED;
25 } else { 25 } else {
26 free_input_config(new_config);
26 return cmd_results_new(CMD_INVALID, "drag_lock", 27 return cmd_results_new(CMD_INVALID, "drag_lock",
27 "Expected 'drag_lock <enabled|disabled>'"); 28 "Expected 'drag_lock <enabled|disabled>'");
28 } 29 }
diff --git a/sway/commands/input/dwt.c b/sway/commands/input/dwt.c
index 995a2f47..73937507 100644
--- a/sway/commands/input/dwt.c
+++ b/sway/commands/input/dwt.c
@@ -22,6 +22,7 @@ struct cmd_results *input_cmd_dwt(int argc, char **argv) {
22 } else if (strcasecmp(argv[0], "disabled") == 0) { 22 } else if (strcasecmp(argv[0], "disabled") == 0) {
23 new_config->dwt = LIBINPUT_CONFIG_DWT_DISABLED; 23 new_config->dwt = LIBINPUT_CONFIG_DWT_DISABLED;
24 } else { 24 } else {
25 free_input_config(new_config);
25 return cmd_results_new(CMD_INVALID, "dwt", 26 return cmd_results_new(CMD_INVALID, "dwt",
26 "Expected 'dwt <enabled|disabled>'"); 27 "Expected 'dwt <enabled|disabled>'");
27 } 28 }
diff --git a/sway/commands/input/events.c b/sway/commands/input/events.c
index 2217f5ce..abfe3b12 100644
--- a/sway/commands/input/events.c
+++ b/sway/commands/input/events.c
@@ -16,7 +16,7 @@ struct cmd_results *input_cmd_events(int argc, char **argv) {
16 return cmd_results_new(CMD_FAILURE, "events", 16 return cmd_results_new(CMD_FAILURE, "events",
17 "No input device defined."); 17 "No input device defined.");
18 } 18 }
19 wlr_log(L_DEBUG, "events for device: %s", 19 wlr_log(WLR_DEBUG, "events for device: %s",
20 current_input_config->identifier); 20 current_input_config->identifier);
21 struct input_config *new_config = 21 struct input_config *new_config =
22 new_input_config(current_input_config->identifier); 22 new_input_config(current_input_config->identifier);
@@ -29,6 +29,7 @@ struct cmd_results *input_cmd_events(int argc, char **argv) {
29 new_config->send_events = 29 new_config->send_events =
30 LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE; 30 LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE;
31 } else { 31 } else {
32 free_input_config(new_config);
32 return cmd_results_new(CMD_INVALID, "events", 33 return cmd_results_new(CMD_INVALID, "events",
33 "Expected 'events <enabled|disabled|disabled_on_external_mouse>'"); 34 "Expected 'events <enabled|disabled|disabled_on_external_mouse>'");
34 } 35 }
diff --git a/sway/commands/input/left_handed.c b/sway/commands/input/left_handed.c
index 94b8e03e..769ce98c 100644
--- a/sway/commands/input/left_handed.c
+++ b/sway/commands/input/left_handed.c
@@ -23,6 +23,7 @@ struct cmd_results *input_cmd_left_handed(int argc, char **argv) {
23 } else if (strcasecmp(argv[0], "disabled") == 0) { 23 } else if (strcasecmp(argv[0], "disabled") == 0) {
24 new_config->left_handed = 0; 24 new_config->left_handed = 0;
25 } else { 25 } else {
26 free_input_config(new_config);
26 return cmd_results_new(CMD_INVALID, "left_handed", 27 return cmd_results_new(CMD_INVALID, "left_handed",
27 "Expected 'left_handed <enabled|disabled>'"); 28 "Expected 'left_handed <enabled|disabled>'");
28 } 29 }
diff --git a/sway/commands/input/map_from_region.c b/sway/commands/input/map_from_region.c
index 80bb856d..40f04214 100644
--- a/sway/commands/input/map_from_region.c
+++ b/sway/commands/input/map_from_region.c
@@ -54,20 +54,28 @@ struct cmd_results *input_cmd_map_from_region(int argc, char **argv) {
54 bool mm1, mm2; 54 bool mm1, mm2;
55 if (!parse_coords(argv[0], &new_config->mapped_from_region->x1, 55 if (!parse_coords(argv[0], &new_config->mapped_from_region->x1,
56 &new_config->mapped_from_region->y1, &mm1)) { 56 &new_config->mapped_from_region->y1, &mm1)) {
57 free(new_config->mapped_from_region);
58 free_input_config(new_config);
57 return cmd_results_new(CMD_FAILURE, "map_from_region", 59 return cmd_results_new(CMD_FAILURE, "map_from_region",
58 "Invalid top-left coordinates"); 60 "Invalid top-left coordinates");
59 } 61 }
60 if (!parse_coords(argv[1], &new_config->mapped_from_region->x2, 62 if (!parse_coords(argv[1], &new_config->mapped_from_region->x2,
61 &new_config->mapped_from_region->y2, &mm2)) { 63 &new_config->mapped_from_region->y2, &mm2)) {
64 free(new_config->mapped_from_region);
65 free_input_config(new_config);
62 return cmd_results_new(CMD_FAILURE, "map_from_region", 66 return cmd_results_new(CMD_FAILURE, "map_from_region",
63 "Invalid bottom-right coordinates"); 67 "Invalid bottom-right coordinates");
64 } 68 }
65 if (new_config->mapped_from_region->x1 > new_config->mapped_from_region->x2 || 69 if (new_config->mapped_from_region->x1 > new_config->mapped_from_region->x2 ||
66 new_config->mapped_from_region->y1 > new_config->mapped_from_region->y2) { 70 new_config->mapped_from_region->y1 > new_config->mapped_from_region->y2) {
71 free(new_config->mapped_from_region);
72 free_input_config(new_config);
67 return cmd_results_new(CMD_FAILURE, "map_from_region", 73 return cmd_results_new(CMD_FAILURE, "map_from_region",
68 "Invalid rectangle"); 74 "Invalid rectangle");
69 } 75 }
70 if (mm1 != mm2) { 76 if (mm1 != mm2) {
77 free(new_config->mapped_from_region);
78 free_input_config(new_config);
71 return cmd_results_new(CMD_FAILURE, "map_from_region", 79 return cmd_results_new(CMD_FAILURE, "map_from_region",
72 "Both coordinates must be in the same unit"); 80 "Both coordinates must be in the same unit");
73 } 81 }
diff --git a/sway/commands/input/middle_emulation.c b/sway/commands/input/middle_emulation.c
index a551fd51..7ca01629 100644
--- a/sway/commands/input/middle_emulation.c
+++ b/sway/commands/input/middle_emulation.c
@@ -24,6 +24,7 @@ struct cmd_results *input_cmd_middle_emulation(int argc, char **argv) {
24 new_config->middle_emulation = 24 new_config->middle_emulation =
25 LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED; 25 LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED;
26 } else { 26 } else {
27 free_input_config(new_config);
27 return cmd_results_new(CMD_INVALID, "middle_emulation", 28 return cmd_results_new(CMD_INVALID, "middle_emulation",
28 "Expected 'middle_emulation <enabled|disabled>'"); 29 "Expected 'middle_emulation <enabled|disabled>'");
29 } 30 }
diff --git a/sway/commands/input/natural_scroll.c b/sway/commands/input/natural_scroll.c
index c4e19b78..55236790 100644
--- a/sway/commands/input/natural_scroll.c
+++ b/sway/commands/input/natural_scroll.c
@@ -23,6 +23,7 @@ struct cmd_results *input_cmd_natural_scroll(int argc, char **argv) {
23 } else if (strcasecmp(argv[0], "disabled") == 0) { 23 } else if (strcasecmp(argv[0], "disabled") == 0) {
24 new_config->natural_scroll = 0; 24 new_config->natural_scroll = 0;
25 } else { 25 } else {
26 free_input_config(new_config);
26 return cmd_results_new(CMD_INVALID, "natural_scroll", 27 return cmd_results_new(CMD_INVALID, "natural_scroll",
27 "Expected 'natural_scroll <enabled|disabled>'"); 28 "Expected 'natural_scroll <enabled|disabled>'");
28 } 29 }
diff --git a/sway/commands/input/pointer_accel.c b/sway/commands/input/pointer_accel.c
index 171063aa..8bbd0724 100644
--- a/sway/commands/input/pointer_accel.c
+++ b/sway/commands/input/pointer_accel.c
@@ -20,6 +20,7 @@ struct cmd_results *input_cmd_pointer_accel(int argc, char **argv) {
20 20
21 float pointer_accel = atof(argv[0]); 21 float pointer_accel = atof(argv[0]);
22 if (pointer_accel < -1 || pointer_accel > 1) { 22 if (pointer_accel < -1 || pointer_accel > 1) {
23 free_input_config(new_config);
23 return cmd_results_new(CMD_INVALID, "pointer_accel", 24 return cmd_results_new(CMD_INVALID, "pointer_accel",
24 "Input out of range [-1, 1]"); 25 "Input out of range [-1, 1]");
25 } 26 }
diff --git a/sway/commands/input/repeat_delay.c b/sway/commands/input/repeat_delay.c
index ce265841..c9ddbf0e 100644
--- a/sway/commands/input/repeat_delay.c
+++ b/sway/commands/input/repeat_delay.c
@@ -20,6 +20,7 @@ struct cmd_results *input_cmd_repeat_delay(int argc, char **argv) {
20 20
21 int repeat_delay = atoi(argv[0]); 21 int repeat_delay = atoi(argv[0]);
22 if (repeat_delay < 0) { 22 if (repeat_delay < 0) {
23 free_input_config(new_config);
23 return cmd_results_new(CMD_INVALID, "repeat_delay", 24 return cmd_results_new(CMD_INVALID, "repeat_delay",
24 "Repeat delay cannot be negative"); 25 "Repeat delay cannot be negative");
25 } 26 }
diff --git a/sway/commands/input/repeat_rate.c b/sway/commands/input/repeat_rate.c
index f2ea2e69..56878176 100644
--- a/sway/commands/input/repeat_rate.c
+++ b/sway/commands/input/repeat_rate.c
@@ -20,6 +20,7 @@ struct cmd_results *input_cmd_repeat_rate(int argc, char **argv) {
20 20
21 int repeat_rate = atoi(argv[0]); 21 int repeat_rate = atoi(argv[0]);
22 if (repeat_rate < 0) { 22 if (repeat_rate < 0) {
23 free_input_config(new_config);
23 return cmd_results_new(CMD_INVALID, "repeat_rate", 24 return cmd_results_new(CMD_INVALID, "repeat_rate",
24 "Repeat rate cannot be negative"); 25 "Repeat rate cannot be negative");
25 } 26 }
diff --git a/sway/commands/input/scroll_button.c b/sway/commands/input/scroll_button.c
new file mode 100644
index 00000000..350fcca2
--- /dev/null
+++ b/sway/commands/input/scroll_button.c
@@ -0,0 +1,44 @@
1#include <string.h>
2#include <strings.h>
3#include <errno.h>
4#include "sway/config.h"
5#include "sway/commands.h"
6#include "sway/input/input-manager.h"
7
8struct cmd_results *input_cmd_scroll_button(int argc, char **argv) {
9 struct cmd_results *error = NULL;
10 if ((error = checkarg(argc, "scroll_button", EXPECTED_AT_LEAST, 1))) {
11 return error;
12 }
13 struct input_config *current_input_config =
14 config->handler_context.input_config;
15 if (!current_input_config) {
16 return cmd_results_new(CMD_FAILURE, "scroll_button",
17 "No input device defined.");
18 }
19 struct input_config *new_config =
20 new_input_config(current_input_config->identifier);
21
22 errno = 0;
23 char *endptr;
24 int scroll_button = strtol(*argv, &endptr, 10);
25 if (endptr == *argv && scroll_button == 0) {
26 free_input_config(new_config);
27 return cmd_results_new(CMD_INVALID, "scroll_button",
28 "Scroll button identifier must be an integer.");
29 }
30 if (errno == ERANGE) {
31 free_input_config(new_config);
32 return cmd_results_new(CMD_INVALID, "scroll_button",
33 "Scroll button identifier out of range.");
34 }
35 if (scroll_button < 0) {
36 free_input_config(new_config);
37 return cmd_results_new(CMD_INVALID, "scroll_button",
38 "Scroll button identifier cannot be negative.");
39 }
40 new_config->scroll_button = scroll_button;
41
42 apply_input_config(new_config);
43 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
44}
diff --git a/sway/commands/input/scroll_method.c b/sway/commands/input/scroll_method.c
index 0a1c57ac..4c6ac6b6 100644
--- a/sway/commands/input/scroll_method.c
+++ b/sway/commands/input/scroll_method.c
@@ -27,6 +27,7 @@ struct cmd_results *input_cmd_scroll_method(int argc, char **argv) {
27 } else if (strcasecmp(argv[0], "on_button_down") == 0) { 27 } else if (strcasecmp(argv[0], "on_button_down") == 0) {
28 new_config->scroll_method = LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; 28 new_config->scroll_method = LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN;
29 } else { 29 } else {
30 free_input_config(new_config);
30 return cmd_results_new(CMD_INVALID, "scroll_method", 31 return cmd_results_new(CMD_INVALID, "scroll_method",
31 "Expected 'scroll_method <none|two_finger|edge|on_button_down>'"); 32 "Expected 'scroll_method <none|two_finger|edge|on_button_down>'");
32 } 33 }
diff --git a/sway/commands/input/tap.c b/sway/commands/input/tap.c
index e7f03058..a8d1a10c 100644
--- a/sway/commands/input/tap.c
+++ b/sway/commands/input/tap.c
@@ -23,11 +23,12 @@ struct cmd_results *input_cmd_tap(int argc, char **argv) {
23 } else if (strcasecmp(argv[0], "disabled") == 0) { 23 } else if (strcasecmp(argv[0], "disabled") == 0) {
24 new_config->tap = LIBINPUT_CONFIG_TAP_DISABLED; 24 new_config->tap = LIBINPUT_CONFIG_TAP_DISABLED;
25 } else { 25 } else {
26 free_input_config(new_config);
26 return cmd_results_new(CMD_INVALID, "tap", 27 return cmd_results_new(CMD_INVALID, "tap",
27 "Expected 'tap <enabled|disabled>'"); 28 "Expected 'tap <enabled|disabled>'");
28 } 29 }
29 30
30 wlr_log(L_DEBUG, "apply-tap for device: %s", 31 wlr_log(WLR_DEBUG, "apply-tap for device: %s",
31 current_input_config->identifier); 32 current_input_config->identifier);
32 apply_input_config(new_config); 33 apply_input_config(new_config);
33 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 34 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
diff --git a/sway/commands/input/tap_button_map.c b/sway/commands/input/tap_button_map.c
new file mode 100644
index 00000000..bdbba472
--- /dev/null
+++ b/sway/commands/input/tap_button_map.c
@@ -0,0 +1,33 @@
1#include <string.h>
2#include <strings.h>
3#include "sway/config.h"
4#include "sway/commands.h"
5#include "sway/input/input-manager.h"
6
7struct cmd_results *input_cmd_tap_button_map(int argc, char **argv) {
8 struct cmd_results *error = NULL;
9 if ((error = checkarg(argc, "tap_button_map", EXPECTED_AT_LEAST, 1))) {
10 return error;
11 }
12 struct input_config *current_input_config =
13 config->handler_context.input_config;
14 if (!current_input_config) {
15 return cmd_results_new(CMD_FAILURE, "tap_button_map",
16 "No input device defined.");
17 }
18 struct input_config *new_config =
19 new_input_config(current_input_config->identifier);
20
21 if (strcasecmp(argv[0], "lrm") == 0) {
22 new_config->tap_button_map = LIBINPUT_CONFIG_TAP_MAP_LRM;
23 } else if (strcasecmp(argv[0], "lmr") == 0) {
24 new_config->tap_button_map = LIBINPUT_CONFIG_TAP_MAP_LMR;
25 } else {
26 free_input_config(new_config);
27 return cmd_results_new(CMD_INVALID, "tap_button_map",
28 "Expected 'tap_button_map <lrm|lmr>'");
29 }
30
31 apply_input_config(new_config);
32 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
33}
diff --git a/sway/commands/input/xkb_layout.c b/sway/commands/input/xkb_layout.c
index 867e65d3..9fa5a344 100644
--- a/sway/commands/input/xkb_layout.c
+++ b/sway/commands/input/xkb_layout.c
@@ -19,7 +19,7 @@ struct cmd_results *input_cmd_xkb_layout(int argc, char **argv) {
19 19
20 new_config->xkb_layout = strdup(argv[0]); 20 new_config->xkb_layout = strdup(argv[0]);
21 21
22 wlr_log(L_DEBUG, "apply-xkb_layout for device: %s layout: %s", 22 wlr_log(WLR_DEBUG, "apply-xkb_layout for device: %s layout: %s",
23 current_input_config->identifier, new_config->xkb_layout); 23 current_input_config->identifier, new_config->xkb_layout);
24 apply_input_config(new_config); 24 apply_input_config(new_config);
25 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 25 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
diff --git a/sway/commands/input/xkb_model.c b/sway/commands/input/xkb_model.c
index e8c8e04e..0d082625 100644
--- a/sway/commands/input/xkb_model.c
+++ b/sway/commands/input/xkb_model.c
@@ -19,7 +19,7 @@ struct cmd_results *input_cmd_xkb_model(int argc, char **argv) {
19 19
20 new_config->xkb_model = strdup(argv[0]); 20 new_config->xkb_model = strdup(argv[0]);
21 21
22 wlr_log(L_DEBUG, "apply-xkb_model for device: %s model: %s", 22 wlr_log(WLR_DEBUG, "apply-xkb_model for device: %s model: %s",
23 current_input_config->identifier, new_config->xkb_model); 23 current_input_config->identifier, new_config->xkb_model);
24 apply_input_config(new_config); 24 apply_input_config(new_config);
25 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 25 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
diff --git a/sway/commands/input/xkb_options.c b/sway/commands/input/xkb_options.c
index e9ddd6e3..3059d941 100644
--- a/sway/commands/input/xkb_options.c
+++ b/sway/commands/input/xkb_options.c
@@ -19,7 +19,7 @@ struct cmd_results *input_cmd_xkb_options(int argc, char **argv) {
19 19
20 new_config->xkb_options = strdup(argv[0]); 20 new_config->xkb_options = strdup(argv[0]);
21 21
22 wlr_log(L_DEBUG, "apply-xkb_options for device: %s options: %s", 22 wlr_log(WLR_DEBUG, "apply-xkb_options for device: %s options: %s",
23 current_input_config->identifier, new_config->xkb_options); 23 current_input_config->identifier, new_config->xkb_options);
24 apply_input_config(new_config); 24 apply_input_config(new_config);
25 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 25 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
diff --git a/sway/commands/input/xkb_rules.c b/sway/commands/input/xkb_rules.c
index 926d0ac1..560f088e 100644
--- a/sway/commands/input/xkb_rules.c
+++ b/sway/commands/input/xkb_rules.c
@@ -19,7 +19,7 @@ struct cmd_results *input_cmd_xkb_rules(int argc, char **argv) {
19 19
20 new_config->xkb_rules = strdup(argv[0]); 20 new_config->xkb_rules = strdup(argv[0]);
21 21
22 wlr_log(L_DEBUG, "apply-xkb_rules for device: %s rules: %s", 22 wlr_log(WLR_DEBUG, "apply-xkb_rules for device: %s rules: %s",
23 current_input_config->identifier, new_config->xkb_rules); 23 current_input_config->identifier, new_config->xkb_rules);
24 apply_input_config(new_config); 24 apply_input_config(new_config);
25 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 25 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
diff --git a/sway/commands/input/xkb_variant.c b/sway/commands/input/xkb_variant.c
index 0e3ffd41..0aa03440 100644
--- a/sway/commands/input/xkb_variant.c
+++ b/sway/commands/input/xkb_variant.c
@@ -19,7 +19,7 @@ struct cmd_results *input_cmd_xkb_variant(int argc, char **argv) {
19 19
20 new_config->xkb_variant = strdup(argv[0]); 20 new_config->xkb_variant = strdup(argv[0]);
21 21
22 wlr_log(L_DEBUG, "apply-xkb_variant for device: %s variant: %s", 22 wlr_log(WLR_DEBUG, "apply-xkb_variant for device: %s variant: %s",
23 current_input_config->identifier, new_config->xkb_variant); 23 current_input_config->identifier, new_config->xkb_variant);
24 apply_input_config(new_config); 24 apply_input_config(new_config);
25 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 25 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
diff --git a/sway/commands/layout.c b/sway/commands/layout.c
index 9945fa5c..c446f1f9 100644
--- a/sway/commands/layout.c
+++ b/sway/commands/layout.c
@@ -49,7 +49,7 @@ struct cmd_results *cmd_layout(int argc, char **argv) {
49 } 49 }
50 50
51 container_notify_subtree_changed(parent); 51 container_notify_subtree_changed(parent);
52 arrange_and_commit(parent); 52 arrange_windows(parent);
53 53
54 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 54 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
55} 55}
diff --git a/sway/commands/mode.c b/sway/commands/mode.c
index 00331ccc..b460fcb5 100644
--- a/sway/commands/mode.c
+++ b/sway/commands/mode.c
@@ -26,7 +26,17 @@ struct cmd_results *cmd_mode(int argc, char **argv) {
26 "mode", "Can only be used in config file."); 26 "mode", "Can only be used in config file.");
27 } 27 }
28 28
29 const char *mode_name = argv[0]; 29 bool pango = strcmp(*argv, "--pango_markup") == 0;
30 if (pango) {
31 argc--; argv++;
32 if (argc == 0) {
33 return cmd_results_new(CMD_FAILURE, "mode",
34 "Mode name is missing");
35 }
36 }
37
38 char *mode_name = *argv;
39 strip_quotes(mode_name);
30 struct sway_mode *mode = NULL; 40 struct sway_mode *mode = NULL;
31 // Find mode 41 // Find mode
32 for (int i = 0; i < config->modes->length; ++i) { 42 for (int i = 0; i < config->modes->length; ++i) {
@@ -46,6 +56,7 @@ struct cmd_results *cmd_mode(int argc, char **argv) {
46 mode->name = strdup(mode_name); 56 mode->name = strdup(mode_name);
47 mode->keysym_bindings = create_list(); 57 mode->keysym_bindings = create_list();
48 mode->keycode_bindings = create_list(); 58 mode->keycode_bindings = create_list();
59 mode->pango = pango;
49 list_add(config->modes, mode); 60 list_add(config->modes, mode);
50 } 61 }
51 if (!mode) { 62 if (!mode) {
@@ -54,13 +65,15 @@ struct cmd_results *cmd_mode(int argc, char **argv) {
54 return error; 65 return error;
55 } 66 }
56 if ((config->reading && argc > 1) || (!config->reading && argc == 1)) { 67 if ((config->reading && argc > 1) || (!config->reading && argc == 1)) {
57 wlr_log(L_DEBUG, "Switching to mode `%s'",mode->name); 68 wlr_log(WLR_DEBUG, "Switching to mode `%s' (pango=%d)",
69 mode->name, mode->pango);
58 } 70 }
59 // Set current mode 71 // Set current mode
60 config->current_mode = mode; 72 config->current_mode = mode;
61 if (argc == 1) { 73 if (argc == 1) {
62 // trigger IPC mode event 74 // trigger IPC mode event
63 ipc_event_mode(config->current_mode->name); 75 ipc_event_mode(config->current_mode->name,
76 config->current_mode->pango);
64 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 77 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
65 } 78 }
66 79
diff --git a/sway/commands/move.c b/sway/commands/move.c
index a4fae388..1940043d 100644
--- a/sway/commands/move.c
+++ b/sway/commands/move.c
@@ -1,13 +1,15 @@
1#define _XOPEN_SOURCE 500 1#define _XOPEN_SOURCE 500
2#include <string.h> 2#include <string.h>
3#include <strings.h> 3#include <strings.h>
4#include <wlr/types/wlr_cursor.h>
4#include <wlr/types/wlr_output.h> 5#include <wlr/types/wlr_output.h>
5#include <wlr/types/wlr_output_layout.h> 6#include <wlr/types/wlr_output_layout.h>
6#include <wlr/util/log.h> 7#include <wlr/util/log.h>
7#include "sway/commands.h" 8#include "sway/commands.h"
8#include "sway/desktop/transaction.h" 9#include "sway/input/cursor.h"
9#include "sway/input/seat.h" 10#include "sway/input/seat.h"
10#include "sway/output.h" 11#include "sway/output.h"
12#include "sway/scratchpad.h"
11#include "sway/tree/arrange.h" 13#include "sway/tree/arrange.h"
12#include "sway/tree/container.h" 14#include "sway/tree/container.h"
13#include "sway/tree/layout.h" 15#include "sway/tree/layout.h"
@@ -103,10 +105,8 @@ static struct cmd_results *cmd_move_container(struct sway_container *current,
103 // TODO: Ideally we would arrange the surviving parent after reaping, 105 // TODO: Ideally we would arrange the surviving parent after reaping,
104 // but container_reap_empty does not return it, so we arrange the 106 // but container_reap_empty does not return it, so we arrange the
105 // workspace instead. 107 // workspace instead.
106 struct sway_transaction *txn = transaction_create(); 108 arrange_windows(old_ws);
107 arrange_windows(old_ws, txn); 109 arrange_windows(destination->parent);
108 arrange_windows(destination->parent, txn);
109 transaction_commit(txn);
110 110
111 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 111 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
112 } else if (strcasecmp(argv[1], "to") == 0 112 } else if (strcasecmp(argv[1], "to") == 0
@@ -142,10 +142,8 @@ static struct cmd_results *cmd_move_container(struct sway_container *current,
142 // TODO: Ideally we would arrange the surviving parent after reaping, 142 // TODO: Ideally we would arrange the surviving parent after reaping,
143 // but container_reap_empty does not return it, so we arrange the 143 // but container_reap_empty does not return it, so we arrange the
144 // workspace instead. 144 // workspace instead.
145 struct sway_transaction *txn = transaction_create(); 145 arrange_windows(old_ws);
146 arrange_windows(old_ws, txn); 146 arrange_windows(focus->parent);
147 arrange_windows(focus->parent, txn);
148 transaction_commit(txn);
149 147
150 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 148 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
151 } 149 }
@@ -175,20 +173,56 @@ static struct cmd_results *cmd_move_workspace(struct sway_container *current,
175 } 173 }
176 container_move_to(current, destination); 174 container_move_to(current, destination);
177 175
178 struct sway_transaction *txn = transaction_create(); 176 arrange_windows(source);
179 arrange_windows(source, txn); 177 arrange_windows(destination);
180 arrange_windows(destination, txn);
181 transaction_commit(txn);
182 178
183 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 179 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
184} 180}
185 181
186static struct cmd_results *move_in_direction(struct sway_container *container, 182static struct cmd_results *move_in_direction(struct sway_container *container,
187 enum movement_direction direction, int move_amt) { 183 enum movement_direction direction, int argc, char **argv) {
184 int move_amt = 10;
185 if (argc > 1) {
186 char *inv;
187 move_amt = (int)strtol(argv[1], &inv, 10);
188 if (*inv != '\0' && strcasecmp(inv, "px") != 0) {
189 return cmd_results_new(CMD_FAILURE, "move",
190 "Invalid distance specified");
191 }
192 }
193
188 if (container->type == C_WORKSPACE) { 194 if (container->type == C_WORKSPACE) {
189 return cmd_results_new(CMD_FAILURE, "move", 195 return cmd_results_new(CMD_FAILURE, "move",
190 "Cannot move workspaces in a direction"); 196 "Cannot move workspaces in a direction");
191 } 197 }
198 if (container_is_floating(container)) {
199 if (container->type == C_VIEW && container->sway_view->is_fullscreen) {
200 return cmd_results_new(CMD_FAILURE, "move",
201 "Cannot move fullscreen floating container");
202 }
203 double lx = container->x;
204 double ly = container->y;
205 switch (direction) {
206 case MOVE_LEFT:
207 lx -= move_amt;
208 break;
209 case MOVE_RIGHT:
210 lx += move_amt;
211 break;
212 case MOVE_UP:
213 ly -= move_amt;
214 break;
215 case MOVE_DOWN:
216 ly += move_amt;
217 break;
218 case MOVE_PARENT:
219 case MOVE_CHILD:
220 return cmd_results_new(CMD_FAILURE, "move",
221 "Cannot move floating container to parent or child");
222 }
223 container_floating_move_to(container, lx, ly);
224 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
225 }
192 // For simplicity, we'll arrange the entire workspace. The reason for this 226 // For simplicity, we'll arrange the entire workspace. The reason for this
193 // is moving the container might reap the old parent, and container_move 227 // is moving the container might reap the old parent, and container_move
194 // does not return a surviving parent. 228 // does not return a surviving parent.
@@ -198,54 +232,112 @@ static struct cmd_results *move_in_direction(struct sway_container *container,
198 container_move(container, direction, move_amt); 232 container_move(container, direction, move_amt);
199 struct sway_container *new_ws = container_parent(container, C_WORKSPACE); 233 struct sway_container *new_ws = container_parent(container, C_WORKSPACE);
200 234
201 struct sway_transaction *txn = transaction_create(); 235 arrange_windows(old_ws);
202 arrange_windows(old_ws, txn);
203 if (new_ws != old_ws) { 236 if (new_ws != old_ws) {
204 arrange_windows(new_ws, txn); 237 arrange_windows(new_ws);
205 } 238 }
206 transaction_commit(txn);
207 239
208 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 240 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
209} 241}
210 242
243static const char* expected_position_syntax =
244 "Expected 'move [absolute] position <x> <y>' or "
245 "'move [absolute] position mouse'";
246
247static struct cmd_results *move_to_position(struct sway_container *container,
248 int argc, char **argv) {
249 if (!container_is_floating(container)) {
250 return cmd_results_new(CMD_FAILURE, "move",
251 "Only floating containers "
252 "can be moved to an absolute position");
253 }
254 if (!argc) {
255 return cmd_results_new(CMD_FAILURE, "move", expected_position_syntax);
256 }
257 if (strcmp(argv[0], "absolute") == 0) {
258 --argc;
259 ++argv;
260 }
261 if (!argc) {
262 return cmd_results_new(CMD_FAILURE, "move", expected_position_syntax);
263 }
264 if (strcmp(argv[0], "position") == 0) {
265 --argc;
266 ++argv;
267 }
268 if (!argc) {
269 return cmd_results_new(CMD_FAILURE, "move", expected_position_syntax);
270 }
271 if (strcmp(argv[0], "mouse") == 0) {
272 struct sway_seat *seat = config->handler_context.seat;
273 if (!seat->cursor) {
274 return cmd_results_new(CMD_FAILURE, "move", "No cursor device");
275 }
276 double lx = seat->cursor->cursor->x - container->width / 2;
277 double ly = seat->cursor->cursor->y - container->height / 2;
278 container_floating_move_to(container, lx, ly);
279 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
280 }
281 if (argc != 2) {
282 return cmd_results_new(CMD_FAILURE, "move", expected_position_syntax);
283 }
284 double lx, ly;
285 char *inv;
286 lx = (double)strtol(argv[0], &inv, 10);
287 if (*inv != '\0' && strcasecmp(inv, "px") != 0) {
288 return cmd_results_new(CMD_FAILURE, "move",
289 "Invalid position specified");
290 }
291 ly = (double)strtol(argv[1], &inv, 10);
292 if (*inv != '\0' && strcasecmp(inv, "px") != 0) {
293 return cmd_results_new(CMD_FAILURE, "move",
294 "Invalid position specified");
295 }
296 container_floating_move_to(container, lx, ly);
297 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
298}
299
300static struct cmd_results *move_to_scratchpad(struct sway_container *con) {
301 if (con->type != C_CONTAINER && con->type != C_VIEW) {
302 return cmd_results_new(CMD_INVALID, "move",
303 "Only views and containers can be moved to the scratchpad");
304 }
305 if (con->scratchpad) {
306 return cmd_results_new(CMD_INVALID, "move",
307 "Container is already in the scratchpad");
308 }
309 scratchpad_add_container(con);
310 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
311}
312
211struct cmd_results *cmd_move(int argc, char **argv) { 313struct cmd_results *cmd_move(int argc, char **argv) {
212 struct cmd_results *error = NULL; 314 struct cmd_results *error = NULL;
213 int move_amt = 10;
214 if ((error = checkarg(argc, "move", EXPECTED_AT_LEAST, 1))) { 315 if ((error = checkarg(argc, "move", EXPECTED_AT_LEAST, 1))) {
215 return error; 316 return error;
216 } 317 }
217 struct sway_container *current = config->handler_context.current_container; 318 struct sway_container *current = config->handler_context.current_container;
218 319
219 if (argc == 2 || (argc == 3 && strcasecmp(argv[2], "px") == 0)) {
220 char *inv;
221 move_amt = (int)strtol(argv[1], &inv, 10);
222 if (*inv != '\0' && strcasecmp(inv, "px") != 0) {
223 return cmd_results_new(CMD_FAILURE, "move",
224 "Invalid distance specified");
225 }
226 }
227
228 if (strcasecmp(argv[0], "left") == 0) { 320 if (strcasecmp(argv[0], "left") == 0) {
229 return move_in_direction(current, MOVE_LEFT, move_amt); 321 return move_in_direction(current, MOVE_LEFT, argc, argv);
230 } else if (strcasecmp(argv[0], "right") == 0) { 322 } else if (strcasecmp(argv[0], "right") == 0) {
231 return move_in_direction(current, MOVE_RIGHT, move_amt); 323 return move_in_direction(current, MOVE_RIGHT, argc, argv);
232 } else if (strcasecmp(argv[0], "up") == 0) { 324 } else if (strcasecmp(argv[0], "up") == 0) {
233 return move_in_direction(current, MOVE_UP, move_amt); 325 return move_in_direction(current, MOVE_UP, argc, argv);
234 } else if (strcasecmp(argv[0], "down") == 0) { 326 } else if (strcasecmp(argv[0], "down") == 0) {
235 return move_in_direction(current, MOVE_DOWN, move_amt); 327 return move_in_direction(current, MOVE_DOWN, argc, argv);
236 } else if (strcasecmp(argv[0], "container") == 0 328 } else if (strcasecmp(argv[0], "container") == 0
237 || strcasecmp(argv[0], "window") == 0) { 329 || strcasecmp(argv[0], "window") == 0) {
238 return cmd_move_container(current, argc, argv); 330 return cmd_move_container(current, argc, argv);
239 } else if (strcasecmp(argv[0], "workspace") == 0) { 331 } else if (strcasecmp(argv[0], "workspace") == 0) {
240 return cmd_move_workspace(current, argc, argv); 332 return cmd_move_workspace(current, argc, argv);
241 } else if (strcasecmp(argv[0], "scratchpad") == 0 333 } else if (strcasecmp(argv[0], "scratchpad") == 0
242 || (strcasecmp(argv[0], "to") == 0 334 || (strcasecmp(argv[0], "to") == 0 && argc == 2
243 && strcasecmp(argv[1], "scratchpad") == 0)) { 335 && strcasecmp(argv[1], "scratchpad") == 0)) {
244 // TODO: scratchpad 336 return move_to_scratchpad(current);
245 return cmd_results_new(CMD_FAILURE, "move", "Unimplemented");
246 } else if (strcasecmp(argv[0], "position") == 0) { 337 } else if (strcasecmp(argv[0], "position") == 0) {
247 // TODO: floating 338 return move_to_position(current, argc, argv);
248 return cmd_results_new(CMD_FAILURE, "move", "Unimplemented"); 339 } else if (strcasecmp(argv[0], "absolute") == 0) {
340 return move_to_position(current, argc, argv);
249 } else { 341 } else {
250 return cmd_results_new(CMD_INVALID, "move", expected_syntax); 342 return cmd_results_new(CMD_INVALID, "move", expected_syntax);
251 } 343 }
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/output.c b/sway/commands/output.c
index f955bf90..ef1b7a69 100644
--- a/sway/commands/output.c
+++ b/sway/commands/output.c
@@ -29,7 +29,7 @@ struct cmd_results *cmd_output(int argc, char **argv) {
29 29
30 struct output_config *output = new_output_config(argv[0]); 30 struct output_config *output = new_output_config(argv[0]);
31 if (!output) { 31 if (!output) {
32 wlr_log(L_ERROR, "Failed to allocate output config"); 32 wlr_log(WLR_ERROR, "Failed to allocate output config");
33 return NULL; 33 return NULL;
34 } 34 }
35 argc--; argv++; 35 argc--; argv++;
@@ -60,53 +60,13 @@ struct cmd_results *cmd_output(int argc, char **argv) {
60 config->handler_context.leftovers.argc = 0; 60 config->handler_context.leftovers.argc = 0;
61 config->handler_context.leftovers.argv = NULL; 61 config->handler_context.leftovers.argv = NULL;
62 62
63 int i = list_seq_find(config->output_configs, output_name_cmp, output->name); 63 output = store_output_config(output);
64 if (i >= 0) {
65 // Merge existing config
66 struct output_config *current = config->output_configs->items[i];
67 merge_output_config(current, output);
68 free_output_config(output);
69 output = current;
70 } else {
71 list_add(config->output_configs, output);
72 }
73
74 wlr_log(L_DEBUG, "Config stored for output %s (enabled: %d) (%dx%d@%fHz "
75 "position %d,%d scale %f transform %d) (bg %s %s) (dpms %d)",
76 output->name, output->enabled, output->width, output->height,
77 output->refresh_rate, output->x, output->y, output->scale,
78 output->transform, output->background, output->background_option, output->dpms_state);
79
80 // Try to find the output container and apply configuration now. If
81 // this is during startup then there will be no container and config
82 // will be applied during normal "new output" event from wlroots.
83 char identifier[128];
84 bool all = strcmp(output->name, "*") == 0;
85 struct sway_output *sway_output;
86 wl_list_for_each(sway_output, &root_container.sway_root->outputs, link) {
87 output_get_identifier(identifier, sizeof(identifier), sway_output);
88 wlr_log(L_DEBUG, "Checking identifier %s", identifier);
89 if (all || strcmp(sway_output->wlr_output->name, output->name) == 0
90 || strcmp(identifier, output->name) == 0) {
91 if (!sway_output->swayc) {
92 if (!output->enabled) {
93 if (!all) {
94 break;
95 }
96 continue;
97 }
98 64
99 output_enable(sway_output); 65 // If reloading, the output configs will be applied after reading the
100 } 66 // entire config and before the deferred commands so that an auto generated
101 67 // workspace name is not given to re-enabled outputs.
102 apply_output_config(output, sway_output->swayc); 68 if (!config->reloading) {
103 69 apply_output_config_to_outputs(output);
104 if (!all) {
105 // Stop looking if the output config isn't applicable to all
106 // outputs
107 break;
108 }
109 }
110 } 70 }
111 71
112 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 72 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
diff --git a/sway/commands/output/background.c b/sway/commands/output/background.c
index 55cbdff0..4ed56c2a 100644
--- a/sway/commands/output/background.c
+++ b/sway/commands/output/background.c
@@ -72,7 +72,7 @@ struct cmd_results *output_cmd_background(int argc, char **argv) {
72 src = strdup(p.we_wordv[0]); 72 src = strdup(p.we_wordv[0]);
73 wordfree(&p); 73 wordfree(&p);
74 if (!src) { 74 if (!src) {
75 wlr_log(L_ERROR, "Failed to duplicate string"); 75 wlr_log(WLR_ERROR, "Failed to duplicate string");
76 return cmd_results_new(CMD_FAILURE, "output", 76 return cmd_results_new(CMD_FAILURE, "output",
77 "Unable to allocate resource"); 77 "Unable to allocate resource");
78 } 78 }
@@ -80,9 +80,10 @@ struct cmd_results *output_cmd_background(int argc, char **argv) {
80 if (config->reading && *src != '/') { 80 if (config->reading && *src != '/') {
81 // src file is inside configuration dir 81 // src file is inside configuration dir
82 82
83 char *conf = strdup(config->current_config); 83 char *conf = strdup(config->current_config_path);
84 if(!conf) { 84 if (!conf) {
85 wlr_log(L_ERROR, "Failed to duplicate string"); 85 wlr_log(WLR_ERROR, "Failed to duplicate string");
86 free(src);
86 return cmd_results_new(CMD_FAILURE, "output", 87 return cmd_results_new(CMD_FAILURE, "output",
87 "Unable to allocate resources"); 88 "Unable to allocate resources");
88 } 89 }
@@ -93,7 +94,7 @@ struct cmd_results *output_cmd_background(int argc, char **argv) {
93 if (!src) { 94 if (!src) {
94 free(rel_path); 95 free(rel_path);
95 free(conf); 96 free(conf);
96 wlr_log(L_ERROR, "Unable to allocate memory"); 97 wlr_log(WLR_ERROR, "Unable to allocate memory");
97 return cmd_results_new(CMD_FAILURE, "output", 98 return cmd_results_new(CMD_FAILURE, "output",
98 "Unable to allocate resources"); 99 "Unable to allocate resources");
99 } 100 }
diff --git a/sway/commands/output/mode.c b/sway/commands/output/mode.c
index daec6d44..ef56ae9e 100644
--- a/sway/commands/output/mode.c
+++ b/sway/commands/output/mode.c
@@ -36,11 +36,11 @@ struct cmd_results *output_cmd_mode(int argc, char **argv) {
36 } 36 }
37 } else { 37 } else {
38 // Format is 1234 4321 38 // Format is 1234 4321
39 argc--; argv++;
39 if (!argc) { 40 if (!argc) {
40 return cmd_results_new(CMD_INVALID, "output", 41 return cmd_results_new(CMD_INVALID, "output",
41 "Missing mode argument (height)."); 42 "Missing mode argument (height).");
42 } 43 }
43 argc--; argv++;
44 output->height = strtol(*argv, &end, 10); 44 output->height = strtol(*argv, &end, 10);
45 if (*end) { 45 if (*end) {
46 return cmd_results_new(CMD_INVALID, "output", 46 return cmd_results_new(CMD_INVALID, "output",
diff --git a/sway/commands/output/position.c b/sway/commands/output/position.c
index c2aeb281..449767b1 100644
--- a/sway/commands/output/position.c
+++ b/sway/commands/output/position.c
@@ -27,11 +27,11 @@ struct cmd_results *output_cmd_position(int argc, char **argv) {
27 } 27 }
28 } else { 28 } else {
29 // Format is 1234 4321 (legacy) 29 // Format is 1234 4321 (legacy)
30 argc--; argv++;
30 if (!argc) { 31 if (!argc) {
31 return cmd_results_new(CMD_INVALID, "output", 32 return cmd_results_new(CMD_INVALID, "output",
32 "Missing position argument (y)."); 33 "Missing position argument (y).");
33 } 34 }
34 argc--; argv++;
35 config->handler_context.output_config->y = strtol(*argv, &end, 10); 35 config->handler_context.output_config->y = strtol(*argv, &end, 10);
36 if (*end) { 36 if (*end) {
37 return cmd_results_new(CMD_INVALID, "output", 37 return cmd_results_new(CMD_INVALID, "output",
diff --git a/sway/commands/reload.c b/sway/commands/reload.c
index 9fc213c4..cea6a94b 100644
--- a/sway/commands/reload.c
+++ b/sway/commands/reload.c
@@ -7,11 +7,11 @@ struct cmd_results *cmd_reload(int argc, char **argv) {
7 if ((error = checkarg(argc, "reload", EXPECTED_EQUAL_TO, 0))) { 7 if ((error = checkarg(argc, "reload", EXPECTED_EQUAL_TO, 0))) {
8 return error; 8 return error;
9 } 9 }
10 if (!load_main_config(config->current_config, true)) { 10 if (!load_main_config(config->current_config_path, true)) {
11 return cmd_results_new(CMD_FAILURE, "reload", "Error(s) reloading config."); 11 return cmd_results_new(CMD_FAILURE, "reload", "Error(s) reloading config.");
12 } 12 }
13 13
14 load_swaybars(); 14 load_swaybars();
15 arrange_and_commit(&root_container); 15 arrange_windows(&root_container);
16 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 16 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
17} 17}
diff --git a/sway/commands/rename.c b/sway/commands/rename.c
index 104a3392..a380ff9c 100644
--- a/sway/commands/rename.c
+++ b/sway/commands/rename.c
@@ -68,7 +68,7 @@ struct cmd_results *cmd_rename(int argc, char **argv) {
68 "Workspace already exists"); 68 "Workspace already exists");
69 } 69 }
70 70
71 wlr_log(L_DEBUG, "renaming workspace '%s' to '%s'", workspace->name, new_name); 71 wlr_log(WLR_DEBUG, "renaming workspace '%s' to '%s'", workspace->name, new_name);
72 free(workspace->name); 72 free(workspace->name);
73 workspace->name = new_name; 73 workspace->name = new_name;
74 74
diff --git a/sway/commands/resize.c b/sway/commands/resize.c
index 6357343e..e657864c 100644
--- a/sway/commands/resize.c
+++ b/sway/commands/resize.c
@@ -1,4 +1,5 @@
1#include <errno.h> 1#include <errno.h>
2#include <limits.h>
2#include <math.h> 3#include <math.h>
3#include <stdbool.h> 4#include <stdbool.h>
4#include <stdlib.h> 5#include <stdlib.h>
@@ -7,6 +8,7 @@
7#include <wlr/util/log.h> 8#include <wlr/util/log.h>
8#include "sway/commands.h" 9#include "sway/commands.h"
9#include "sway/tree/arrange.h" 10#include "sway/tree/arrange.h"
11#include "sway/tree/view.h"
10#include "log.h" 12#include "log.h"
11 13
12static const int MIN_SANE_W = 100, MIN_SANE_H = 60; 14static const int MIN_SANE_W = 100, MIN_SANE_H = 60;
@@ -21,9 +23,18 @@ enum resize_unit {
21enum resize_axis { 23enum resize_axis {
22 RESIZE_AXIS_HORIZONTAL, 24 RESIZE_AXIS_HORIZONTAL,
23 RESIZE_AXIS_VERTICAL, 25 RESIZE_AXIS_VERTICAL,
26 RESIZE_AXIS_UP,
27 RESIZE_AXIS_DOWN,
28 RESIZE_AXIS_LEFT,
29 RESIZE_AXIS_RIGHT,
24 RESIZE_AXIS_INVALID, 30 RESIZE_AXIS_INVALID,
25}; 31};
26 32
33struct resize_amount {
34 int amount;
35 enum resize_unit unit;
36};
37
27static enum resize_unit parse_resize_unit(const char *unit) { 38static enum resize_unit parse_resize_unit(const char *unit) {
28 if (strcasecmp(unit, "px") == 0) { 39 if (strcasecmp(unit, "px") == 0) {
29 return RESIZE_UNIT_PX; 40 return RESIZE_UNIT_PX;
@@ -37,6 +48,69 @@ static enum resize_unit parse_resize_unit(const char *unit) {
37 return RESIZE_UNIT_INVALID; 48 return RESIZE_UNIT_INVALID;
38} 49}
39 50
51// Parse arguments such as "10", "10px" or "10 px".
52// Returns the number of arguments consumed.
53static int parse_resize_amount(int argc, char **argv,
54 struct resize_amount *amount) {
55 char *err;
56 amount->amount = (int)strtol(argv[0], &err, 10);
57 if (*err) {
58 // e.g. 10px
59 amount->unit = parse_resize_unit(err);
60 return 1;
61 }
62 if (argc == 1) {
63 amount->unit = RESIZE_UNIT_DEFAULT;
64 return 1;
65 }
66 // Try the second argument
67 amount->unit = parse_resize_unit(argv[1]);
68 if (amount->unit == RESIZE_UNIT_INVALID) {
69 amount->unit = RESIZE_UNIT_DEFAULT;
70 return 1;
71 }
72 return 2;
73}
74
75static void calculate_constraints(int *min_width, int *max_width,
76 int *min_height, int *max_height) {
77 struct sway_container *con = config->handler_context.current_container;
78
79 if (config->floating_minimum_width == -1) { // no minimum
80 *min_width = 0;
81 } else if (config->floating_minimum_width == 0) { // automatic
82 *min_width = 75;
83 } else {
84 *min_width = config->floating_minimum_width;
85 }
86
87 if (config->floating_minimum_height == -1) { // no minimum
88 *min_height = 0;
89 } else if (config->floating_minimum_height == 0) { // automatic
90 *min_height = 50;
91 } else {
92 *min_height = config->floating_minimum_height;
93 }
94
95 if (config->floating_maximum_width == -1) { // no maximum
96 *max_width = INT_MAX;
97 } else if (config->floating_maximum_width == 0) { // automatic
98 struct sway_container *ws = container_parent(con, C_WORKSPACE);
99 *max_width = ws->width;
100 } else {
101 *max_width = config->floating_maximum_width;
102 }
103
104 if (config->floating_maximum_height == -1) { // no maximum
105 *max_height = INT_MAX;
106 } else if (config->floating_maximum_height == 0) { // automatic
107 struct sway_container *ws = container_parent(con, C_WORKSPACE);
108 *max_height = ws->height;
109 } else {
110 *max_height = config->floating_maximum_height;
111 }
112}
113
40static enum resize_axis parse_resize_axis(const char *axis) { 114static enum resize_axis parse_resize_axis(const char *axis) {
41 if (strcasecmp(axis, "width") == 0 || strcasecmp(axis, "horizontal") == 0) { 115 if (strcasecmp(axis, "width") == 0 || strcasecmp(axis, "horizontal") == 0) {
42 return RESIZE_AXIS_HORIZONTAL; 116 return RESIZE_AXIS_HORIZONTAL;
@@ -44,6 +118,18 @@ static enum resize_axis parse_resize_axis(const char *axis) {
44 if (strcasecmp(axis, "height") == 0 || strcasecmp(axis, "vertical") == 0) { 118 if (strcasecmp(axis, "height") == 0 || strcasecmp(axis, "vertical") == 0) {
45 return RESIZE_AXIS_VERTICAL; 119 return RESIZE_AXIS_VERTICAL;
46 } 120 }
121 if (strcasecmp(axis, "up") == 0) {
122 return RESIZE_AXIS_UP;
123 }
124 if (strcasecmp(axis, "down") == 0) {
125 return RESIZE_AXIS_DOWN;
126 }
127 if (strcasecmp(axis, "left") == 0) {
128 return RESIZE_AXIS_LEFT;
129 }
130 if (strcasecmp(axis, "right") == 0) {
131 return RESIZE_AXIS_RIGHT;
132 }
47 return RESIZE_AXIS_INVALID; 133 return RESIZE_AXIS_INVALID;
48} 134}
49 135
@@ -95,7 +181,7 @@ static void resize_tiled(int amount, enum resize_axis axis) {
95 return; 181 return;
96 } 182 }
97 183
98 wlr_log(L_DEBUG, 184 wlr_log(WLR_DEBUG,
99 "Found the proper parent: %p. It has %d l conts, and %d r conts", 185 "Found the proper parent: %p. It has %d l conts, and %d r conts",
100 parent->parent, minor_weight, major_weight); 186 parent->parent, minor_weight, major_weight);
101 187
@@ -182,105 +268,315 @@ static void resize_tiled(int amount, enum resize_axis axis) {
182 } 268 }
183 } 269 }
184 270
185 arrange_and_commit(parent->parent); 271 arrange_windows(parent->parent);
186} 272}
187 273
188static void resize(int amount, enum resize_axis axis, enum resize_unit unit) { 274/**
189 struct sway_container *current = config->handler_context.current_container; 275 * Implement `resize <grow|shrink>` for a floating container.
190 if (unit == RESIZE_UNIT_DEFAULT) { 276 */
191 // Default for tiling; TODO floating should be px 277static struct cmd_results *resize_adjust_floating(enum resize_axis axis,
192 unit = RESIZE_UNIT_PPT; 278 struct resize_amount *amount) {
279 struct sway_container *con = config->handler_context.current_container;
280 int grow_width = 0, grow_height = 0;
281 switch (axis) {
282 case RESIZE_AXIS_HORIZONTAL:
283 case RESIZE_AXIS_LEFT:
284 case RESIZE_AXIS_RIGHT:
285 grow_width = amount->amount;
286 break;
287 case RESIZE_AXIS_VERTICAL:
288 case RESIZE_AXIS_UP:
289 case RESIZE_AXIS_DOWN:
290 grow_height = amount->amount;
291 break;
292 case RESIZE_AXIS_INVALID:
293 return cmd_results_new(CMD_INVALID, "resize", "Invalid axis/direction");
294 }
295 // Make sure we're not adjusting beyond floating min/max size
296 int min_width, max_width, min_height, max_height;
297 calculate_constraints(&min_width, &max_width, &min_height, &max_height);
298 if (con->width + grow_width < min_width) {
299 grow_width = min_width - con->width;
300 } else if (con->width + grow_width > max_width) {
301 grow_width = max_width - con->width;
193 } 302 }
303 if (con->height + grow_height < min_height) {
304 grow_height = min_height - con->height;
305 } else if (con->height + grow_height > max_height) {
306 grow_height = max_height - con->height;
307 }
308 int grow_x = 0, grow_y = 0;
309 switch (axis) {
310 case RESIZE_AXIS_HORIZONTAL:
311 grow_x = -grow_width / 2;
312 break;
313 case RESIZE_AXIS_VERTICAL:
314 grow_y = -grow_height / 2;
315 break;
316 case RESIZE_AXIS_UP:
317 grow_y = -grow_height;
318 break;
319 case RESIZE_AXIS_LEFT:
320 grow_x = -grow_width;
321 break;
322 case RESIZE_AXIS_DOWN:
323 case RESIZE_AXIS_RIGHT:
324 break;
325 case RESIZE_AXIS_INVALID:
326 return cmd_results_new(CMD_INVALID, "resize", "Invalid axis/direction");
327 }
328 con->x += grow_x;
329 con->y += grow_y;
330 con->width += grow_width;
331 con->height += grow_height;
332
333 if (con->type == C_VIEW) {
334 struct sway_view *view = con->sway_view;
335 view->x += grow_x;
336 view->y += grow_y;
337 view->width += grow_width;
338 view->height += grow_height;
339 }
340
341 arrange_windows(con);
342
343 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
344}
194 345
195 if (unit == RESIZE_UNIT_PPT) { 346/**
196 float pct = amount / 100.0f; 347 * Implement `resize <grow|shrink>` for a tiled container.
348 */
349static struct cmd_results *resize_adjust_tiled(enum resize_axis axis,
350 struct resize_amount *amount) {
351 struct sway_container *current = config->handler_context.current_container;
352
353 if (amount->unit == RESIZE_UNIT_DEFAULT) {
354 amount->unit = RESIZE_UNIT_PPT;
355 }
356 if (amount->unit == RESIZE_UNIT_PPT) {
357 float pct = amount->amount / 100.0f;
358 // TODO: Make left/right/up/down resize in that direction?
197 switch (axis) { 359 switch (axis) {
360 case RESIZE_AXIS_LEFT:
361 case RESIZE_AXIS_RIGHT:
198 case RESIZE_AXIS_HORIZONTAL: 362 case RESIZE_AXIS_HORIZONTAL:
199 amount = (float)current->width * pct; 363 amount->amount = (float)current->width * pct;
200 break; 364 break;
365 case RESIZE_AXIS_UP:
366 case RESIZE_AXIS_DOWN:
201 case RESIZE_AXIS_VERTICAL: 367 case RESIZE_AXIS_VERTICAL:
202 amount = (float)current->height * pct; 368 amount->amount = (float)current->height * pct;
203 break; 369 break;
204 default: 370 case RESIZE_AXIS_INVALID:
205 sway_assert(0, "invalid resize axis"); 371 return cmd_results_new(CMD_INVALID, "resize",
206 return; 372 "Invalid resize axis/direction");
207 } 373 }
208 } 374 }
209 375
210 return resize_tiled(amount, axis); 376 resize_tiled(amount->amount, axis);
377 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
211} 378}
212 379
213struct cmd_results *cmd_resize(int argc, char **argv) { 380/**
214 struct sway_container *current = config->handler_context.current_container; 381 * Implement `resize set` for a tiled container.
215 if (!current) { 382 */
216 return cmd_results_new(CMD_INVALID, "resize", "Cannot resize nothing"); 383static struct cmd_results *resize_set_tiled(struct sway_container *con,
217 } 384 struct resize_amount *width, struct resize_amount *height) {
218 if (current->type != C_VIEW && current->type != C_CONTAINER) { 385 return cmd_results_new(CMD_INVALID, "resize",
219 return cmd_results_new(CMD_INVALID, "resize", 386 "'resize set' is not implemented for tiled views");
220 "Can only resize views/containers"); 387}
388
389/**
390 * Implement `resize set` for a floating container.
391 */
392static struct cmd_results *resize_set_floating(struct sway_container *con,
393 struct resize_amount *width, struct resize_amount *height) {
394 int min_width, max_width, min_height, max_height;
395 calculate_constraints(&min_width, &max_width, &min_height, &max_height);
396 width->amount = fmax(min_width, fmin(width->amount, max_width));
397 height->amount = fmax(min_height, fmin(height->amount, max_height));
398 int grow_width = width->amount - con->width;
399 int grow_height = height->amount - con->height;
400 con->x -= grow_width / 2;
401 con->y -= grow_height / 2;
402 con->width = width->amount;
403 con->height = height->amount;
404
405 if (con->type == C_VIEW) {
406 struct sway_view *view = con->sway_view;
407 view->x -= grow_width / 2;
408 view->y -= grow_height / 2;
409 view->width += grow_width;
410 view->height += grow_height;
221 } 411 }
222 412
413 arrange_windows(con);
414
415 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
416}
417
418/**
419 * resize set <args>
420 *
421 * args: <width> [px|ppt] <height> [px|ppt]
422 */
423static struct cmd_results *cmd_resize_set(int argc, char **argv) {
223 struct cmd_results *error; 424 struct cmd_results *error;
224 if ((error = checkarg(argc, "resize", EXPECTED_AT_LEAST, 2))) { 425 if ((error = checkarg(argc, "resize", EXPECTED_AT_LEAST, 2))) {
225 return error; 426 return error;
226 } 427 }
227 428 const char *usage = "Expected 'resize set <width> <height>'";
228 if (strcasecmp(argv[0], "set") == 0) { 429
229 // TODO 430 // Width
230 //return cmd_resize_set(argc - 1, &argv[1]); 431 struct resize_amount width;
231 return cmd_results_new(CMD_INVALID, "resize", "resize set unimplemented"); 432 int num_consumed_args = parse_resize_amount(argc, argv, &width);
433 argc -= num_consumed_args;
434 argv += num_consumed_args;
435 if (width.unit == RESIZE_UNIT_INVALID) {
436 return cmd_results_new(CMD_INVALID, "resize", usage);
437 }
438 if (!argc) {
439 return cmd_results_new(CMD_INVALID, "resize", usage);
232 } 440 }
233 441
234 // TODO: resize grow|shrink left|right|up|down 442 // Height
443 struct resize_amount height;
444 num_consumed_args = parse_resize_amount(argc, argv, &height);
445 argc -= num_consumed_args;
446 argv += num_consumed_args;
447 if (height.unit == RESIZE_UNIT_INVALID) {
448 return cmd_results_new(CMD_INVALID, "resize", usage);
449 }
235 450
236 const char *usage = "Expected 'resize <shrink|grow> " 451 // If 0, don't resize that dimension
237 "<width|height> [<amount>] [px|ppt]'"; 452 struct sway_container *con = config->handler_context.current_container;
453 if (width.amount <= 0) {
454 width.amount = con->width;
455 }
456 if (height.amount <= 0) {
457 height.amount = con->height;
458 }
238 459
239 int multiplier = 0; 460 if (container_is_floating(con)) {
240 if (strcasecmp(*argv, "grow") == 0) { 461 return resize_set_floating(con, &width, &height);
241 multiplier = 1;
242 } else if (strcasecmp(*argv, "shrink") == 0) {
243 multiplier = -1;
244 } else {
245 return cmd_results_new(CMD_INVALID, "resize", usage);
246 } 462 }
247 --argc; ++argv; 463 return resize_set_tiled(con, &width, &height);
464}
248 465
466/**
467 * resize <grow|shrink> <args>
468 *
469 * args: <direction>
470 * args: <direction> <amount> <unit>
471 * args: <direction> <amount> <unit> or <amount> <other_unit>
472 */
473static struct cmd_results *cmd_resize_adjust(int argc, char **argv,
474 int multiplier) {
475 const char *usage = "Expected 'resize grow|shrink <direction> "
476 "[<amount> px|ppt [or <amount> px|ppt]]'";
249 enum resize_axis axis = parse_resize_axis(*argv); 477 enum resize_axis axis = parse_resize_axis(*argv);
250 if (axis == RESIZE_AXIS_INVALID) { 478 if (axis == RESIZE_AXIS_INVALID) {
251 return cmd_results_new(CMD_INVALID, "resize", usage); 479 return cmd_results_new(CMD_INVALID, "resize", usage);
252 } 480 }
253 --argc; ++argv; 481 --argc; ++argv;
254 482
255 int amount = 10; // Default amount 483 // First amount
256 enum resize_unit unit = RESIZE_UNIT_DEFAULT; 484 struct resize_amount first_amount;
257
258 if (argc) { 485 if (argc) {
259 char *err; 486 int num_consumed_args = parse_resize_amount(argc, argv, &first_amount);
260 amount = (int)strtol(*argv, &err, 10); 487 argc -= num_consumed_args;
261 if (*err) { 488 argv += num_consumed_args;
262 // e.g. `resize grow width 10px` 489 if (first_amount.unit == RESIZE_UNIT_INVALID) {
263 unit = parse_resize_unit(err); 490 return cmd_results_new(CMD_INVALID, "resize", usage);
264 if (unit == RESIZE_UNIT_INVALID) {
265 return cmd_results_new(CMD_INVALID, "resize", usage);
266 }
267 } 491 }
268 --argc; ++argv; 492 } else {
493 first_amount.amount = 10;
494 first_amount.unit = RESIZE_UNIT_DEFAULT;
269 } 495 }
270 496
497 // "or"
271 if (argc) { 498 if (argc) {
272 unit = parse_resize_unit(*argv); 499 if (strcmp(*argv, "or") != 0) {
273 if (unit == RESIZE_UNIT_INVALID) {
274 return cmd_results_new(CMD_INVALID, "resize", usage); 500 return cmd_results_new(CMD_INVALID, "resize", usage);
275 } 501 }
276 --argc; ++argv; 502 --argc; ++argv;
277 } 503 }
278 504
505 // Second amount
506 struct resize_amount second_amount;
279 if (argc) { 507 if (argc) {
280 // Provied too many args, the bastard 508 int num_consumed_args = parse_resize_amount(argc, argv, &second_amount);
281 return cmd_results_new(CMD_INVALID, "resize", usage); 509 argc -= num_consumed_args;
510 argv += num_consumed_args;
511 if (second_amount.unit == RESIZE_UNIT_INVALID) {
512 return cmd_results_new(CMD_INVALID, "resize", usage);
513 }
514 } else {
515 second_amount.unit = RESIZE_UNIT_INVALID;
282 } 516 }
283 517
284 resize(amount * multiplier, axis, unit); 518 first_amount.amount *= multiplier;
285 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 519 second_amount.amount *= multiplier;
520
521 struct sway_container *con = config->handler_context.current_container;
522 if (container_is_floating(con)) {
523 // Floating containers can only resize in px. Choose an amount which
524 // uses px, with fallback to an amount that specified no unit.
525 if (first_amount.unit == RESIZE_UNIT_PX) {
526 return resize_adjust_floating(axis, &first_amount);
527 } else if (second_amount.unit == RESIZE_UNIT_PX) {
528 return resize_adjust_floating(axis, &second_amount);
529 } else if (first_amount.unit == RESIZE_UNIT_DEFAULT) {
530 return resize_adjust_floating(axis, &first_amount);
531 } else if (second_amount.unit == RESIZE_UNIT_DEFAULT) {
532 return resize_adjust_floating(axis, &second_amount);
533 } else {
534 return cmd_results_new(CMD_INVALID, "resize",
535 "Floating containers cannot use ppt measurements");
536 }
537 }
538
539 // For tiling, prefer ppt -> default -> px
540 if (first_amount.unit == RESIZE_UNIT_PPT) {
541 return resize_adjust_tiled(axis, &first_amount);
542 } else if (second_amount.unit == RESIZE_UNIT_PPT) {
543 return resize_adjust_tiled(axis, &second_amount);
544 } else if (first_amount.unit == RESIZE_UNIT_DEFAULT) {
545 return resize_adjust_tiled(axis, &first_amount);
546 } else if (second_amount.unit == RESIZE_UNIT_DEFAULT) {
547 return resize_adjust_tiled(axis, &second_amount);
548 } else {
549 return resize_adjust_tiled(axis, &first_amount);
550 }
551}
552
553struct cmd_results *cmd_resize(int argc, char **argv) {
554 struct sway_container *current = config->handler_context.current_container;
555 if (!current) {
556 return cmd_results_new(CMD_INVALID, "resize", "Cannot resize nothing");
557 }
558 if (current->type != C_VIEW && current->type != C_CONTAINER) {
559 return cmd_results_new(CMD_INVALID, "resize",
560 "Can only resize views/containers");
561 }
562
563 struct cmd_results *error;
564 if ((error = checkarg(argc, "resize", EXPECTED_AT_LEAST, 2))) {
565 return error;
566 }
567
568 if (strcasecmp(argv[0], "set") == 0) {
569 return cmd_resize_set(argc - 1, &argv[1]);
570 }
571 if (strcasecmp(argv[0], "grow") == 0) {
572 return cmd_resize_adjust(argc - 1, &argv[1], 1);
573 }
574 if (strcasecmp(argv[0], "shrink") == 0) {
575 return cmd_resize_adjust(argc - 1, &argv[1], -1);
576 }
577
578 const char *usage = "Expected 'resize <shrink|grow> "
579 "<width|height|up|down|left|right> [<amount>] [px|ppt]'";
580
581 return cmd_results_new(CMD_INVALID, "resize", usage);
286} 582}
diff --git a/sway/commands/scratchpad.c b/sway/commands/scratchpad.c
new file mode 100644
index 00000000..ccc07c87
--- /dev/null
+++ b/sway/commands/scratchpad.c
@@ -0,0 +1,36 @@
1#include "log.h"
2#include "sway/commands.h"
3#include "sway/config.h"
4#include "sway/scratchpad.h"
5#include "sway/tree/container.h"
6
7struct cmd_results *cmd_scratchpad(int argc, char **argv) {
8 struct cmd_results *error = NULL;
9 if ((error = checkarg(argc, "scratchpad", EXPECTED_EQUAL_TO, 1))) {
10 return error;
11 }
12 if (strcmp(argv[0], "show") != 0) {
13 return cmd_results_new(CMD_INVALID, "scratchpad",
14 "Expected 'scratchpad show'");
15 }
16 if (!root_container.sway_root->scratchpad->length) {
17 return cmd_results_new(CMD_INVALID, "scratchpad",
18 "Scratchpad is empty");
19 }
20
21 if (config->handler_context.using_criteria) {
22 // If using criteria, this command is executed for every container which
23 // matches the criteria. If this container isn't in the scratchpad,
24 // we'll just silently return a success.
25 struct sway_container *con = config->handler_context.current_container;
26 wlr_log(WLR_INFO, "cmd_scratchpad(%s)", con->name);
27 if (!con->scratchpad) {
28 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
29 }
30 scratchpad_toggle_container(con);
31 } else {
32 scratchpad_toggle_auto();
33 }
34
35 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
36}
diff --git a/sway/commands/set.c b/sway/commands/set.c
index 84e9b792..ea388d3b 100644
--- a/sway/commands/set.c
+++ b/sway/commands/set.c
@@ -32,7 +32,7 @@ struct cmd_results *cmd_set(int argc, char **argv) {
32 } 32 }
33 33
34 if (argv[0][0] != '$') { 34 if (argv[0][0] != '$') {
35 wlr_log(L_INFO, "Warning: variable '%s' doesn't start with $", argv[0]); 35 wlr_log(WLR_INFO, "Warning: variable '%s' doesn't start with $", argv[0]);
36 36
37 size_t size = snprintf(NULL, 0, "$%s", argv[0]); 37 size_t size = snprintf(NULL, 0, "$%s", argv[0]);
38 tmp = malloc(size + 1); 38 tmp = malloc(size + 1);
diff --git a/sway/commands/smart_gaps.c b/sway/commands/smart_gaps.c
index f687e78e..7d27e571 100644
--- a/sway/commands/smart_gaps.c
+++ b/sway/commands/smart_gaps.c
@@ -23,7 +23,7 @@ struct cmd_results *cmd_smart_gaps(int argc, char **argv) {
23 "Expected 'smart_gaps <on|off>' "); 23 "Expected 'smart_gaps <on|off>' ");
24 } 24 }
25 25
26 arrange_and_commit(&root_container); 26 arrange_windows(&root_container);
27 27
28 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 28 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
29} 29}
diff --git a/sway/commands/split.c b/sway/commands/split.c
index c40f4d9f..313799da 100644
--- a/sway/commands/split.c
+++ b/sway/commands/split.c
@@ -16,7 +16,7 @@ static struct cmd_results *do_split(int layout) {
16 } 16 }
17 struct sway_container *parent = container_split(con, layout); 17 struct sway_container *parent = container_split(con, layout);
18 container_create_notify(parent); 18 container_create_notify(parent);
19 arrange_and_commit(parent->parent); 19 arrange_windows(parent->parent);
20 20
21 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 21 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
22} 22}
diff --git a/sway/commands/swap.c b/sway/commands/swap.c
index e052058f..2fc88308 100644
--- a/sway/commands/swap.c
+++ b/sway/commands/swap.c
@@ -1,7 +1,6 @@
1#include <strings.h> 1#include <strings.h>
2#include <wlr/util/log.h> 2#include <wlr/util/log.h>
3#include "sway/commands.h" 3#include "sway/commands.h"
4#include "sway/desktop/transaction.h"
5#include "sway/tree/arrange.h" 4#include "sway/tree/arrange.h"
6#include "sway/tree/layout.h" 5#include "sway/tree/layout.h"
7#include "sway/tree/view.h" 6#include "sway/tree/view.h"
@@ -79,14 +78,10 @@ struct cmd_results *cmd_swap(int argc, char **argv) {
79 78
80 container_swap(current, other); 79 container_swap(current, other);
81 80
82 struct sway_transaction *txn = transaction_create(); 81 arrange_windows(current->parent);
83 arrange_windows(current->parent, txn);
84
85 if (other->parent != current->parent) { 82 if (other->parent != current->parent) {
86 arrange_windows(other->parent, txn); 83 arrange_windows(other->parent);
87 } 84 }
88 85
89 transaction_commit(txn);
90
91 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 86 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
92} 87}
diff --git a/sway/commands/swaybg_command.c b/sway/commands/swaybg_command.c
index 770d4821..36f7fdcd 100644
--- a/sway/commands/swaybg_command.c
+++ b/sway/commands/swaybg_command.c
@@ -13,7 +13,7 @@ struct cmd_results *cmd_swaybg_command(int argc, char **argv) {
13 free(config->swaybg_command); 13 free(config->swaybg_command);
14 } 14 }
15 config->swaybg_command = join_args(argv, argc); 15 config->swaybg_command = join_args(argv, argc);
16 wlr_log(L_DEBUG, "Using custom swaybg command: %s", 16 wlr_log(WLR_DEBUG, "Using custom swaybg command: %s",
17 config->swaybg_command); 17 config->swaybg_command);
18 18
19 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 19 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
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/commands/workspace.c b/sway/commands/workspace.c
index d15be571..e8b37182 100644
--- a/sway/commands/workspace.c
+++ b/sway/commands/workspace.c
@@ -51,7 +51,7 @@ struct cmd_results *cmd_workspace(int argc, char **argv) {
51 free(old); // workspaces can only be assigned to a single output 51 free(old); // workspaces can only be assigned to a single output
52 list_del(config->workspace_outputs, i); 52 list_del(config->workspace_outputs, i);
53 } 53 }
54 wlr_log(L_DEBUG, "Assigning workspace %s to output %s", wso->workspace, wso->output); 54 wlr_log(WLR_DEBUG, "Assigning workspace %s to output %s", wso->workspace, wso->output);
55 list_add(config->workspace_outputs, wso); 55 list_add(config->workspace_outputs, wso);
56 } else { 56 } else {
57 if (config->reading || !config->active) { 57 if (config->reading || !config->active) {
diff --git a/sway/config.c b/sway/config.c
index 512cab31..5f6dd7ad 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"
@@ -86,7 +87,12 @@ void free_config(struct sway_config *config) {
86 } 87 }
87 list_free(config->cmd_queue); 88 list_free(config->cmd_queue);
88 list_free(config->workspace_outputs); 89 list_free(config->workspace_outputs);
89 list_free(config->output_configs); 90 if (config->output_configs) {
91 for (int i = 0; i < config->output_configs->length; i++) {
92 free_output_config(config->output_configs->items[i]);
93 }
94 list_free(config->output_configs);
95 }
90 if (config->input_configs) { 96 if (config->input_configs) {
91 for (int i = 0; i < config->input_configs->length; i++) { 97 for (int i = 0; i < config->input_configs->length; i++) {
92 free_input_config(config->input_configs->items[i]); 98 free_input_config(config->input_configs->items[i]);
@@ -99,7 +105,12 @@ void free_config(struct sway_config *config) {
99 } 105 }
100 list_free(config->seat_configs); 106 list_free(config->seat_configs);
101 } 107 }
102 list_free(config->criteria); 108 if (config->criteria) {
109 for (int i = 0; i < config->criteria->length; ++i) {
110 criteria_destroy(config->criteria->items[i]);
111 }
112 list_free(config->criteria);
113 }
103 list_free(config->no_focus); 114 list_free(config->no_focus);
104 list_free(config->active_bar_modifiers); 115 list_free(config->active_bar_modifiers);
105 list_free(config->config_chain); 116 list_free(config->config_chain);
@@ -111,6 +122,7 @@ void free_config(struct sway_config *config) {
111 free(config->floating_scroll_left_cmd); 122 free(config->floating_scroll_left_cmd);
112 free(config->floating_scroll_right_cmd); 123 free(config->floating_scroll_right_cmd);
113 free(config->font); 124 free(config->font);
125 free((char *)config->current_config_path);
114 free((char *)config->current_config); 126 free((char *)config->current_config);
115 free(config); 127 free(config);
116} 128}
@@ -172,6 +184,7 @@ static void config_defaults(struct sway_config *config) {
172 config->default_orientation = L_NONE; 184 config->default_orientation = L_NONE;
173 if (!(config->font = strdup("monospace 10"))) goto cleanup; 185 if (!(config->font = strdup("monospace 10"))) goto cleanup;
174 config->font_height = 17; // height of monospace 10 186 config->font_height = 17; // height of monospace 10
187 config->urgent_timeout = 500;
175 188
176 // floating view 189 // floating view
177 config->floating_maximum_width = 0; 190 config->floating_maximum_width = 0;
@@ -198,6 +211,7 @@ static void config_defaults(struct sway_config *config) {
198 if (!(config->active_bar_modifiers = create_list())) goto cleanup; 211 if (!(config->active_bar_modifiers = create_list())) goto cleanup;
199 212
200 if (!(config->config_chain = create_list())) goto cleanup; 213 if (!(config->config_chain = create_list())) goto cleanup;
214 config->current_config_path = NULL;
201 config->current_config = NULL; 215 config->current_config = NULL;
202 216
203 // borders 217 // borders
@@ -269,12 +283,12 @@ static char *get_config_path(void) {
269 char *home = getenv("HOME"); 283 char *home = getenv("HOME");
270 char *config_home = malloc(strlen(home) + strlen("/.config") + 1); 284 char *config_home = malloc(strlen(home) + strlen("/.config") + 1);
271 if (!config_home) { 285 if (!config_home) {
272 wlr_log(L_ERROR, "Unable to allocate $HOME/.config"); 286 wlr_log(WLR_ERROR, "Unable to allocate $HOME/.config");
273 } else { 287 } else {
274 strcpy(config_home, home); 288 strcpy(config_home, home);
275 strcat(config_home, "/.config"); 289 strcat(config_home, "/.config");
276 setenv("XDG_CONFIG_HOME", config_home, 1); 290 setenv("XDG_CONFIG_HOME", config_home, 1);
277 wlr_log(L_DEBUG, "Set XDG_CONFIG_HOME to %s", config_home); 291 wlr_log(WLR_DEBUG, "Set XDG_CONFIG_HOME to %s", config_home);
278 free(config_home); 292 free(config_home);
279 } 293 }
280 } 294 }
@@ -297,25 +311,22 @@ static char *get_config_path(void) {
297 return NULL; // Not reached 311 return NULL; // Not reached
298} 312}
299 313
300const char *current_config_path;
301
302static bool load_config(const char *path, struct sway_config *config) { 314static bool load_config(const char *path, struct sway_config *config) {
303 wlr_log(L_INFO, "Loading config from %s", path); 315 if (path == NULL) {
304 current_config_path = path; 316 wlr_log(WLR_ERROR, "Unable to find a config file!");
305
306 struct stat sb;
307 if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) {
308 return false; 317 return false;
309 } 318 }
310 319
311 if (path == NULL) { 320 wlr_log(WLR_INFO, "Loading config from %s", path);
312 wlr_log(L_ERROR, "Unable to find a config file!"); 321
322 struct stat sb;
323 if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) {
313 return false; 324 return false;
314 } 325 }
315 326
316 FILE *f = fopen(path, "r"); 327 FILE *f = fopen(path, "r");
317 if (!f) { 328 if (!f) {
318 wlr_log(L_ERROR, "Unable to open %s for reading", path); 329 wlr_log(WLR_ERROR, "Unable to open %s for reading", path);
319 return false; 330 return false;
320 } 331 }
321 332
@@ -323,10 +334,9 @@ static bool load_config(const char *path, struct sway_config *config) {
323 fclose(f); 334 fclose(f);
324 335
325 if (!config_load_success) { 336 if (!config_load_success) {
326 wlr_log(L_ERROR, "Error(s) loading config!"); 337 wlr_log(WLR_ERROR, "Error(s) loading config!");
327 } 338 }
328 339
329 current_config_path = NULL;
330 return true; 340 return true;
331} 341}
332 342
@@ -346,12 +356,13 @@ bool load_main_config(const char *file, bool is_active) {
346 356
347 config_defaults(config); 357 config_defaults(config);
348 if (is_active) { 358 if (is_active) {
349 wlr_log(L_DEBUG, "Performing configuration file reload"); 359 wlr_log(WLR_DEBUG, "Performing configuration file reload");
350 config->reloading = true; 360 config->reloading = true;
351 config->active = true; 361 config->active = true;
362 create_default_output_configs();
352 } 363 }
353 364
354 config->current_config = path; 365 config->current_config_path = path;
355 list_add(config->config_chain, path); 366 list_add(config->config_chain, path);
356 367
357 config->reading = true; 368 config->reading = true;
@@ -362,7 +373,7 @@ bool load_main_config(const char *file, bool is_active) {
362 /* 373 /*
363 DIR *dir = opendir(SYSCONFDIR "/sway/security.d"); 374 DIR *dir = opendir(SYSCONFDIR "/sway/security.d");
364 if (!dir) { 375 if (!dir) {
365 wlr_log(L_ERROR, 376 wlr_log(WLR_ERROR,
366 "%s does not exist, sway will have no security configuration" 377 "%s does not exist, sway will have no security configuration"
367 " and will probably be broken", SYSCONFDIR "/sway/security.d"); 378 " and will probably be broken", SYSCONFDIR "/sway/security.d");
368 } else { 379 } else {
@@ -391,7 +402,7 @@ bool load_main_config(const char *file, bool is_active) {
391 if (stat(_path, &s) || s.st_uid != 0 || s.st_gid != 0 || 402 if (stat(_path, &s) || s.st_uid != 0 || s.st_gid != 0 ||
392 (((s.st_mode & 0777) != 0644) && 403 (((s.st_mode & 0777) != 0644) &&
393 (s.st_mode & 0777) != 0444)) { 404 (s.st_mode & 0777) != 0444)) {
394 wlr_log(L_ERROR, 405 wlr_log(WLR_ERROR,
395 "Refusing to load %s - it must be owned by root " 406 "Refusing to load %s - it must be owned by root "
396 "and mode 644 or 444", _path); 407 "and mode 644 or 444", _path);
397 success = false; 408 success = false;
@@ -407,6 +418,9 @@ bool load_main_config(const char *file, bool is_active) {
407 success = success && load_config(path, config); 418 success = success && load_config(path, config);
408 419
409 if (is_active) { 420 if (is_active) {
421 for (int i = 0; i < config->output_configs->length; i++) {
422 apply_output_config_to_outputs(config->output_configs->items[i]);
423 }
410 config->reloading = false; 424 config->reloading = false;
411 } 425 }
412 426
@@ -421,26 +435,28 @@ bool load_main_config(const char *file, bool is_active) {
421static bool load_include_config(const char *path, const char *parent_dir, 435static bool load_include_config(const char *path, const char *parent_dir,
422 struct sway_config *config) { 436 struct sway_config *config) {
423 // save parent config 437 // save parent config
424 const char *parent_config = config->current_config; 438 const char *parent_config = config->current_config_path;
425 439
426 char *full_path = strdup(path); 440 char *full_path;
427 int len = strlen(path); 441 int len = strlen(path);
428 if (len >= 1 && path[0] != '/') { 442 if (len >= 1 && path[0] != '/') {
429 len = len + strlen(parent_dir) + 2; 443 len = len + strlen(parent_dir) + 2;
430 full_path = malloc(len * sizeof(char)); 444 full_path = malloc(len * sizeof(char));
431 if (!full_path) { 445 if (!full_path) {
432 wlr_log(L_ERROR, 446 wlr_log(WLR_ERROR,
433 "Unable to allocate full path to included config"); 447 "Unable to allocate full path to included config");
434 return false; 448 return false;
435 } 449 }
436 snprintf(full_path, len, "%s/%s", parent_dir, path); 450 snprintf(full_path, len, "%s/%s", parent_dir, path);
451 } else {
452 full_path = strdup(path);
437 } 453 }
438 454
439 char *real_path = realpath(full_path, NULL); 455 char *real_path = realpath(full_path, NULL);
440 free(full_path); 456 free(full_path);
441 457
442 if (real_path == NULL) { 458 if (real_path == NULL) {
443 wlr_log(L_DEBUG, "%s not found.", path); 459 wlr_log(WLR_DEBUG, "%s not found.", path);
444 return false; 460 return false;
445 } 461 }
446 462
@@ -449,7 +465,7 @@ static bool load_include_config(const char *path, const char *parent_dir,
449 for (j = 0; j < config->config_chain->length; ++j) { 465 for (j = 0; j < config->config_chain->length; ++j) {
450 char *old_path = config->config_chain->items[j]; 466 char *old_path = config->config_chain->items[j];
451 if (strcmp(real_path, old_path) == 0) { 467 if (strcmp(real_path, old_path) == 0) {
452 wlr_log(L_DEBUG, 468 wlr_log(WLR_DEBUG,
453 "%s already included once, won't be included again.", 469 "%s already included once, won't be included again.",
454 real_path); 470 real_path);
455 free(real_path); 471 free(real_path);
@@ -457,25 +473,25 @@ static bool load_include_config(const char *path, const char *parent_dir,
457 } 473 }
458 } 474 }
459 475
460 config->current_config = real_path; 476 config->current_config_path = real_path;
461 list_add(config->config_chain, real_path); 477 list_add(config->config_chain, real_path);
462 int index = config->config_chain->length - 1; 478 int index = config->config_chain->length - 1;
463 479
464 if (!load_config(real_path, config)) { 480 if (!load_config(real_path, config)) {
465 free(real_path); 481 free(real_path);
466 config->current_config = parent_config; 482 config->current_config_path = parent_config;
467 list_del(config->config_chain, index); 483 list_del(config->config_chain, index);
468 return false; 484 return false;
469 } 485 }
470 486
471 // restore current_config 487 // restore current_config_path
472 config->current_config = parent_config; 488 config->current_config_path = parent_config;
473 return true; 489 return true;
474} 490}
475 491
476bool load_include_configs(const char *path, struct sway_config *config) { 492bool load_include_configs(const char *path, struct sway_config *config) {
477 char *wd = getcwd(NULL, 0); 493 char *wd = getcwd(NULL, 0);
478 char *parent_path = strdup(config->current_config); 494 char *parent_path = strdup(config->current_config_path);
479 const char *parent_dir = dirname(parent_path); 495 const char *parent_dir = dirname(parent_path);
480 496
481 if (chdir(parent_dir) < 0) { 497 if (chdir(parent_dir) < 0) {
@@ -503,7 +519,7 @@ bool load_include_configs(const char *path, struct sway_config *config) {
503 // restore wd 519 // restore wd
504 if (chdir(wd) < 0) { 520 if (chdir(wd) < 0) {
505 free(wd); 521 free(wd);
506 wlr_log(L_ERROR, "failed to restore working directory"); 522 wlr_log(WLR_ERROR, "failed to restore working directory");
507 return false; 523 return false;
508 } 524 }
509 525
@@ -518,13 +534,13 @@ static int detect_brace_on_following_line(FILE *file, char *line,
518 char *peeked = NULL; 534 char *peeked = NULL;
519 long position = 0; 535 long position = 0;
520 do { 536 do {
521 wlr_log(L_DEBUG, "Peeking line %d", line_number + lines + 1); 537 wlr_log(WLR_DEBUG, "Peeking line %d", line_number + lines + 1);
522 free(peeked); 538 free(peeked);
523 peeked = peek_line(file, lines, &position); 539 peeked = peek_line(file, lines, &position);
524 if (peeked) { 540 if (peeked) {
525 peeked = strip_whitespace(peeked); 541 peeked = strip_whitespace(peeked);
526 } 542 }
527 wlr_log(L_DEBUG, "Peeked line: `%s`", peeked); 543 wlr_log(WLR_DEBUG, "Peeked line: `%s`", peeked);
528 lines++; 544 lines++;
529 } while (peeked && strlen(peeked) == 0); 545 } while (peeked && strlen(peeked) == 0);
530 546
@@ -543,7 +559,7 @@ static char *expand_line(const char *block, const char *line, bool add_brace) {
543 + (add_brace ? 2 : 0) + 1; 559 + (add_brace ? 2 : 0) + 1;
544 char *expanded = calloc(1, size); 560 char *expanded = calloc(1, size);
545 if (!expanded) { 561 if (!expanded) {
546 wlr_log(L_ERROR, "Cannot allocate expanded line buffer"); 562 wlr_log(WLR_ERROR, "Cannot allocate expanded line buffer");
547 return NULL; 563 return NULL;
548 } 564 }
549 snprintf(expanded, size, "%s%s%s%s", block ? block : "", 565 snprintf(expanded, size, "%s%s%s%s", block ? block : "",
@@ -552,10 +568,33 @@ static char *expand_line(const char *block, const char *line, bool add_brace) {
552} 568}
553 569
554bool read_config(FILE *file, struct sway_config *config) { 570bool read_config(FILE *file, struct sway_config *config) {
571 bool reading_main_config = false;
572 char *this_config = NULL;
573 size_t config_size = 0;
574 if (config->current_config == NULL) {
575 reading_main_config = true;
576
577 int ret_seek = fseek(file, 0, SEEK_END);
578 long ret_tell = ftell(file);
579 if (ret_seek == -1 || ret_tell == -1) {
580 wlr_log(WLR_ERROR, "Unable to get size of config file");
581 return false;
582 }
583 config_size = ret_tell;
584 rewind(file);
585
586 config->current_config = this_config = calloc(1, config_size + 1);
587 if (this_config == NULL) {
588 wlr_log(WLR_ERROR, "Unable to allocate buffer for config contents");
589 return false;
590 }
591 }
592
555 bool success = true; 593 bool success = true;
556 int line_number = 0; 594 int line_number = 0;
557 char *line; 595 char *line;
558 list_t *stack = create_list(); 596 list_t *stack = create_list();
597 size_t read = 0;
559 while (!feof(file)) { 598 while (!feof(file)) {
560 char *block = stack->length ? stack->items[0] : NULL; 599 char *block = stack->length ? stack->items[0] : NULL;
561 line = read_line(file); 600 line = read_line(file);
@@ -563,7 +602,26 @@ bool read_config(FILE *file, struct sway_config *config) {
563 continue; 602 continue;
564 } 603 }
565 line_number++; 604 line_number++;
566 wlr_log(L_DEBUG, "Read line %d: %s", line_number, line); 605 wlr_log(WLR_DEBUG, "Read line %d: %s", line_number, line);
606
607 if (reading_main_config) {
608 size_t length = strlen(line);
609
610 if (read + length > config_size) {
611 wlr_log(WLR_ERROR, "Config file changed during reading");
612 list_foreach(stack, free);
613 list_free(stack);
614 free(line);
615 return false;
616 }
617
618 strcpy(this_config + read, line);
619 if (line_number != 1) {
620 this_config[read - 1] = '\n';
621 }
622 read += length + 1;
623 }
624
567 line = strip_whitespace(line); 625 line = strip_whitespace(line);
568 if (line[0] == '#') { 626 if (line[0] == '#') {
569 free(line); 627 free(line);
@@ -577,13 +635,16 @@ bool read_config(FILE *file, struct sway_config *config) {
577 line_number); 635 line_number);
578 if (brace_detected > 0) { 636 if (brace_detected > 0) {
579 line_number += brace_detected; 637 line_number += brace_detected;
580 wlr_log(L_DEBUG, "Detected open brace on line %d", line_number); 638 wlr_log(WLR_DEBUG, "Detected open brace on line %d", line_number);
581 } 639 }
582 char *expanded = expand_line(block, line, brace_detected > 0); 640 char *expanded = expand_line(block, line, brace_detected > 0);
583 if (!expanded) { 641 if (!expanded) {
642 list_foreach(stack, free);
643 list_free(stack);
644 free(line);
584 return false; 645 return false;
585 } 646 }
586 wlr_log(L_DEBUG, "Expanded line: %s", expanded); 647 wlr_log(WLR_DEBUG, "Expanded line: %s", expanded);
587 struct cmd_results *res; 648 struct cmd_results *res;
588 if (block && strcmp(block, "<commands>") == 0) { 649 if (block && strcmp(block, "<commands>") == 0) {
589 // Special case 650 // Special case
@@ -591,27 +652,26 @@ bool read_config(FILE *file, struct sway_config *config) {
591 } else { 652 } else {
592 res = config_command(expanded); 653 res = config_command(expanded);
593 } 654 }
594 free(expanded);
595 switch(res->status) { 655 switch(res->status) {
596 case CMD_FAILURE: 656 case CMD_FAILURE:
597 case CMD_INVALID: 657 case CMD_INVALID:
598 wlr_log(L_ERROR, "Error on line %i '%s': %s (%s)", line_number, 658 wlr_log(WLR_ERROR, "Error on line %i '%s': %s (%s)", line_number,
599 line, res->error, config->current_config); 659 line, res->error, config->current_config_path);
600 success = false; 660 success = false;
601 break; 661 break;
602 662
603 case CMD_DEFER: 663 case CMD_DEFER:
604 wlr_log(L_DEBUG, "Deferring command `%s'", line); 664 wlr_log(WLR_DEBUG, "Deferring command `%s'", line);
605 list_add(config->cmd_queue, strdup(line)); 665 list_add(config->cmd_queue, strdup(expanded));
606 break; 666 break;
607 667
608 case CMD_BLOCK_COMMANDS: 668 case CMD_BLOCK_COMMANDS:
609 wlr_log(L_DEBUG, "Entering commands block"); 669 wlr_log(WLR_DEBUG, "Entering commands block");
610 list_insert(stack, 0, "<commands>"); 670 list_insert(stack, 0, "<commands>");
611 break; 671 break;
612 672
613 case CMD_BLOCK: 673 case CMD_BLOCK:
614 wlr_log(L_DEBUG, "Entering block '%s'", res->input); 674 wlr_log(WLR_DEBUG, "Entering block '%s'", res->input);
615 list_insert(stack, 0, strdup(res->input)); 675 list_insert(stack, 0, strdup(res->input));
616 if (strcmp(res->input, "bar") == 0) { 676 if (strcmp(res->input, "bar") == 0) {
617 config->current_bar = NULL; 677 config->current_bar = NULL;
@@ -620,7 +680,7 @@ bool read_config(FILE *file, struct sway_config *config) {
620 680
621 case CMD_BLOCK_END: 681 case CMD_BLOCK_END:
622 if (!block) { 682 if (!block) {
623 wlr_log(L_DEBUG, "Unmatched '}' on line %i", line_number); 683 wlr_log(WLR_DEBUG, "Unmatched '}' on line %i", line_number);
624 success = false; 684 success = false;
625 break; 685 break;
626 } 686 }
@@ -628,13 +688,14 @@ bool read_config(FILE *file, struct sway_config *config) {
628 config->current_bar = NULL; 688 config->current_bar = NULL;
629 } 689 }
630 690
631 wlr_log(L_DEBUG, "Exiting block '%s'", block); 691 wlr_log(WLR_DEBUG, "Exiting block '%s'", block);
632 list_del(stack, 0); 692 list_del(stack, 0);
633 free(block); 693 free(block);
634 memset(&config->handler_context, 0, 694 memset(&config->handler_context, 0,
635 sizeof(config->handler_context)); 695 sizeof(config->handler_context));
636 default:; 696 default:;
637 } 697 }
698 free(expanded);
638 free(line); 699 free(line);
639 free_cmd_results(res); 700 free_cmd_results(res);
640 } 701 }
@@ -671,7 +732,7 @@ char *do_var_replacement(char *str) {
671 int vvlen = strlen(var->value); 732 int vvlen = strlen(var->value);
672 char *newstr = malloc(strlen(str) - vnlen + vvlen + 1); 733 char *newstr = malloc(strlen(str) - vnlen + vvlen + 1);
673 if (!newstr) { 734 if (!newstr) {
674 wlr_log(L_ERROR, 735 wlr_log(WLR_ERROR,
675 "Unable to allocate replacement " 736 "Unable to allocate replacement "
676 "during variable expansion"); 737 "during variable expansion");
677 break; 738 break;
@@ -733,6 +794,6 @@ void config_update_font_height(bool recalculate) {
733 } 794 }
734 795
735 if (config->font_height != prev_max_height) { 796 if (config->font_height != prev_max_height) {
736 arrange_and_commit(&root_container); 797 arrange_windows(&root_container);
737 } 798 }
738} 799}
diff --git a/sway/config/bar.c b/sway/config/bar.c
index 5a97c3cc..3a74331e 100644
--- a/sway/config/bar.c
+++ b/sway/config/bar.c
@@ -16,10 +16,10 @@
16#include "log.h" 16#include "log.h"
17 17
18static void terminate_swaybar(pid_t pid) { 18static void terminate_swaybar(pid_t pid) {
19 wlr_log(L_DEBUG, "Terminating swaybar %d", pid); 19 wlr_log(WLR_DEBUG, "Terminating swaybar %d", pid);
20 int ret = kill(-pid, SIGTERM); 20 int ret = kill(-pid, SIGTERM);
21 if (ret != 0) { 21 if (ret != 0) {
22 wlr_log_errno(L_ERROR, "Unable to terminate swaybar %d", pid); 22 wlr_log_errno(WLR_ERROR, "Unable to terminate swaybar %d", pid);
23 } else { 23 } else {
24 int status; 24 int status;
25 waitpid(pid, &status, 0); 25 waitpid(pid, &status, 0);
@@ -30,6 +30,7 @@ void free_bar_config(struct bar_config *bar) {
30 if (!bar) { 30 if (!bar) {
31 return; 31 return;
32 } 32 }
33 free(bar->id);
33 free(bar->mode); 34 free(bar->mode);
34 free(bar->position); 35 free(bar->position);
35 free(bar->hidden_state); 36 free(bar->hidden_state);
@@ -70,16 +71,12 @@ void free_bar_config(struct bar_config *bar) {
70 71
71struct bar_config *default_bar_config(void) { 72struct bar_config *default_bar_config(void) {
72 struct bar_config *bar = NULL; 73 struct bar_config *bar = NULL;
73 bar = malloc(sizeof(struct bar_config)); 74 bar = calloc(1, sizeof(struct bar_config));
74 if (!bar) { 75 if (!bar) {
75 return NULL; 76 return NULL;
76 } 77 }
77 if (!(bar->mode = strdup("dock"))) goto cleanup;
78 if (!(bar->hidden_state = strdup("hide"))) goto cleanup;
79 bar->outputs = NULL; 78 bar->outputs = NULL;
80 bar->position = strdup("bottom"); 79 bar->position = strdup("bottom");
81 if (!(bar->bindings = create_list())) goto cleanup;
82 if (!(bar->status_command = strdup("while :; do date +'%Y-%m-%d %l:%M:%S %p'; sleep 1; done"))) goto cleanup;
83 bar->pango_markup = false; 80 bar->pango_markup = false;
84 bar->swaybar_command = NULL; 81 bar->swaybar_command = NULL;
85 bar->font = NULL; 82 bar->font = NULL;
@@ -91,6 +88,19 @@ struct bar_config *default_bar_config(void) {
91 bar->binding_mode_indicator = true; 88 bar->binding_mode_indicator = true;
92 bar->verbose = false; 89 bar->verbose = false;
93 bar->pid = 0; 90 bar->pid = 0;
91 if (!(bar->mode = strdup("dock"))) {
92 goto cleanup;
93 }
94 if (!(bar->hidden_state = strdup("hide"))) {
95 goto cleanup;
96 }
97 if (!(bar->bindings = create_list())) {
98 goto cleanup;
99 }
100 if (!(bar->status_command =
101 strdup("while date +'%Y-%m-%d %l:%M:%S %p'; do sleep 1; done"))) {
102 goto cleanup;
103 }
94 // set default colors 104 // set default colors
95 if (!(bar->colors.background = strndup("#000000ff", 9))) { 105 if (!(bar->colors.background = strndup("#000000ff", 9))) {
96 goto cleanup; 106 goto cleanup;
@@ -157,7 +167,7 @@ void invoke_swaybar(struct bar_config *bar) {
157 // Pipe to communicate errors 167 // Pipe to communicate errors
158 int filedes[2]; 168 int filedes[2];
159 if (pipe(filedes) == -1) { 169 if (pipe(filedes) == -1) {
160 wlr_log(L_ERROR, "Pipe setup failed! Cannot fork into bar"); 170 wlr_log(WLR_ERROR, "Pipe setup failed! Cannot fork into bar");
161 return; 171 return;
162 } 172 }
163 173
@@ -174,7 +184,7 @@ void invoke_swaybar(struct bar_config *bar) {
174 if (!command) { 184 if (!command) {
175 const char msg[] = "Unable to allocate swaybar command string"; 185 const char msg[] = "Unable to allocate swaybar command string";
176 size_t msg_len = sizeof(msg); 186 size_t msg_len = sizeof(msg);
177 if (write(filedes[1], &msg_len, sizeof(int))) {}; 187 if (write(filedes[1], &msg_len, sizeof(size_t))) {};
178 if (write(filedes[1], msg, msg_len)) {}; 188 if (write(filedes[1], msg, msg_len)) {};
179 close(filedes[1]); 189 close(filedes[1]);
180 exit(1); 190 exit(1);
@@ -187,17 +197,17 @@ void invoke_swaybar(struct bar_config *bar) {
187 execvp(cmd[0], cmd); 197 execvp(cmd[0], cmd);
188 exit(1); 198 exit(1);
189 } 199 }
190 wlr_log(L_DEBUG, "Spawned swaybar %d", bar->pid); 200 wlr_log(WLR_DEBUG, "Spawned swaybar %d", bar->pid);
191 close(filedes[0]); 201 close(filedes[0]);
192 ssize_t len; 202 size_t len;
193 if (read(filedes[1], &len, sizeof(int)) == sizeof(int)) { 203 if (read(filedes[1], &len, sizeof(size_t)) == sizeof(size_t)) {
194 char *buf = malloc(len); 204 char *buf = malloc(len);
195 if(!buf) { 205 if(!buf) {
196 wlr_log(L_ERROR, "Cannot allocate error string"); 206 wlr_log(WLR_ERROR, "Cannot allocate error string");
197 return; 207 return;
198 } 208 }
199 if (read(filedes[1], buf, len)) { 209 if (read(filedes[1], buf, len)) {
200 wlr_log(L_ERROR, "%s", buf); 210 wlr_log(WLR_ERROR, "%s", buf);
201 } 211 }
202 free(buf); 212 free(buf);
203 } 213 }
@@ -234,7 +244,7 @@ void load_swaybars() {
234 if (bar->pid != 0) { 244 if (bar->pid != 0) {
235 terminate_swaybar(bar->pid); 245 terminate_swaybar(bar->pid);
236 } 246 }
237 wlr_log(L_DEBUG, "Invoking swaybar for bar id '%s'", bar->id); 247 wlr_log(WLR_DEBUG, "Invoking swaybar for bar id '%s'", bar->id);
238 invoke_swaybar(bar); 248 invoke_swaybar(bar);
239 } 249 }
240 } 250 }
diff --git a/sway/config/input.c b/sway/config/input.c
index 17303ccc..8d687a6d 100644
--- a/sway/config/input.c
+++ b/sway/config/input.c
@@ -8,17 +8,18 @@
8struct input_config *new_input_config(const char* identifier) { 8struct input_config *new_input_config(const char* identifier) {
9 struct input_config *input = calloc(1, sizeof(struct input_config)); 9 struct input_config *input = calloc(1, sizeof(struct input_config));
10 if (!input) { 10 if (!input) {
11 wlr_log(L_DEBUG, "Unable to allocate input config"); 11 wlr_log(WLR_DEBUG, "Unable to allocate input config");
12 return NULL; 12 return NULL;
13 } 13 }
14 wlr_log(L_DEBUG, "new_input_config(%s)", identifier); 14 wlr_log(WLR_DEBUG, "new_input_config(%s)", identifier);
15 if (!(input->identifier = strdup(identifier))) { 15 if (!(input->identifier = strdup(identifier))) {
16 free(input); 16 free(input);
17 wlr_log(L_DEBUG, "Unable to allocate input config"); 17 wlr_log(WLR_DEBUG, "Unable to allocate input config");
18 return NULL; 18 return NULL;
19 } 19 }
20 20
21 input->tap = INT_MIN; 21 input->tap = INT_MIN;
22 input->tap_button_map = INT_MIN;
22 input->drag_lock = INT_MIN; 23 input->drag_lock = INT_MIN;
23 input->dwt = INT_MIN; 24 input->dwt = INT_MIN;
24 input->send_events = INT_MIN; 25 input->send_events = INT_MIN;
@@ -27,6 +28,7 @@ struct input_config *new_input_config(const char* identifier) {
27 input->natural_scroll = INT_MIN; 28 input->natural_scroll = INT_MIN;
28 input->accel_profile = INT_MIN; 29 input->accel_profile = INT_MIN;
29 input->pointer_accel = FLT_MIN; 30 input->pointer_accel = FLT_MIN;
31 input->scroll_button = INT_MIN;
30 input->scroll_method = INT_MIN; 32 input->scroll_method = INT_MIN;
31 input->left_handed = INT_MIN; 33 input->left_handed = INT_MIN;
32 input->repeat_delay = INT_MIN; 34 input->repeat_delay = INT_MIN;
@@ -70,12 +72,18 @@ void merge_input_config(struct input_config *dst, struct input_config *src) {
70 if (src->scroll_method != INT_MIN) { 72 if (src->scroll_method != INT_MIN) {
71 dst->scroll_method = src->scroll_method; 73 dst->scroll_method = src->scroll_method;
72 } 74 }
75 if (src->scroll_button != INT_MIN) {
76 dst->scroll_button = src->scroll_button;
77 }
73 if (src->send_events != INT_MIN) { 78 if (src->send_events != INT_MIN) {
74 dst->send_events = src->send_events; 79 dst->send_events = src->send_events;
75 } 80 }
76 if (src->tap != INT_MIN) { 81 if (src->tap != INT_MIN) {
77 dst->tap = src->tap; 82 dst->tap = src->tap;
78 } 83 }
84 if (src->tap_button_map != INT_MIN) {
85 dst->tap_button_map = src->tap_button_map;
86 }
79 if (src->xkb_layout) { 87 if (src->xkb_layout) {
80 free(dst->xkb_layout); 88 free(dst->xkb_layout);
81 dst->xkb_layout = strdup(src->xkb_layout); 89 dst->xkb_layout = strdup(src->xkb_layout);
@@ -112,7 +120,7 @@ void merge_input_config(struct input_config *dst, struct input_config *src) {
112struct input_config *copy_input_config(struct input_config *ic) { 120struct input_config *copy_input_config(struct input_config *ic) {
113 struct input_config *copy = calloc(1, sizeof(struct input_config)); 121 struct input_config *copy = calloc(1, sizeof(struct input_config));
114 if (copy == NULL) { 122 if (copy == NULL) {
115 wlr_log(L_ERROR, "could not allocate input config"); 123 wlr_log(WLR_ERROR, "could not allocate input config");
116 return NULL; 124 return NULL;
117 } 125 }
118 merge_input_config(copy, ic); 126 merge_input_config(copy, ic);
diff --git a/sway/config/output.c b/sway/config/output.c
index 648ded27..504c48c6 100644
--- a/sway/config/output.c
+++ b/sway/config/output.c
@@ -45,10 +45,6 @@ struct output_config *new_output_config(const char *name) {
45} 45}
46 46
47void merge_output_config(struct output_config *dst, struct output_config *src) { 47void merge_output_config(struct output_config *dst, struct output_config *src) {
48 if (src->name) {
49 free(dst->name);
50 dst->name = strdup(src->name);
51 }
52 if (src->enabled != -1) { 48 if (src->enabled != -1) {
53 dst->enabled = src->enabled; 49 dst->enabled = src->enabled;
54 } 50 }
@@ -86,11 +82,61 @@ void merge_output_config(struct output_config *dst, struct output_config *src) {
86 } 82 }
87} 83}
88 84
85static void merge_wildcard_on_all(struct output_config *wildcard) {
86 for (int i = 0; i < config->output_configs->length; i++) {
87 struct output_config *oc = config->output_configs->items[i];
88 if (strcmp(wildcard->name, oc->name) != 0) {
89 wlr_log(WLR_DEBUG, "Merging output * config on %s", oc->name);
90 merge_output_config(oc, wildcard);
91 }
92 }
93}
94
95struct output_config *store_output_config(struct output_config *oc) {
96 bool wildcard = strcmp(oc->name, "*") == 0;
97 if (wildcard) {
98 merge_wildcard_on_all(oc);
99 }
100
101 int i = list_seq_find(config->output_configs, output_name_cmp, oc->name);
102 if (i >= 0) {
103 wlr_log(WLR_DEBUG, "Merging on top of existing output config");
104 struct output_config *current = config->output_configs->items[i];
105 merge_output_config(current, oc);
106 free_output_config(oc);
107 oc = current;
108 } else if (!wildcard) {
109 wlr_log(WLR_DEBUG, "Adding non-wildcard output config");
110 i = list_seq_find(config->output_configs, output_name_cmp, "*");
111 if (i >= 0) {
112 wlr_log(WLR_DEBUG, "Merging on top of output * config");
113 struct output_config *current = new_output_config(oc->name);
114 merge_output_config(current, config->output_configs->items[i]);
115 merge_output_config(current, oc);
116 free_output_config(oc);
117 oc = current;
118 }
119 list_add(config->output_configs, oc);
120 } else {
121 // New wildcard config. Just add it
122 wlr_log(WLR_DEBUG, "Adding output * config");
123 list_add(config->output_configs, oc);
124 }
125
126 wlr_log(WLR_DEBUG, "Config stored for output %s (enabled: %d) (%dx%d@%fHz "
127 "position %d,%d scale %f transform %d) (bg %s %s) (dpms %d)",
128 oc->name, oc->enabled, oc->width, oc->height, oc->refresh_rate,
129 oc->x, oc->y, oc->scale, oc->transform, oc->background,
130 oc->background_option, oc->dpms_state);
131
132 return oc;
133}
134
89static void set_mode(struct wlr_output *output, int width, int height, 135static void set_mode(struct wlr_output *output, int width, int height,
90 float refresh_rate) { 136 float refresh_rate) {
91 int mhz = (int)(refresh_rate * 1000); 137 int mhz = (int)(refresh_rate * 1000);
92 if (wl_list_empty(&output->modes)) { 138 if (wl_list_empty(&output->modes)) {
93 wlr_log(L_DEBUG, "Assigning custom mode to %s", output->name); 139 wlr_log(WLR_DEBUG, "Assigning custom mode to %s", output->name);
94 wlr_output_set_custom_mode(output, width, height, mhz); 140 wlr_output_set_custom_mode(output, width, height, mhz);
95 return; 141 return;
96 } 142 }
@@ -106,9 +152,9 @@ static void set_mode(struct wlr_output *output, int width, int height,
106 } 152 }
107 } 153 }
108 if (!best) { 154 if (!best) {
109 wlr_log(L_ERROR, "Configured mode for %s not available", output->name); 155 wlr_log(WLR_ERROR, "Configured mode for %s not available", output->name);
110 } else { 156 } else {
111 wlr_log(L_DEBUG, "Assigning configured mode to %s", output->name); 157 wlr_log(WLR_DEBUG, "Assigning configured mode to %s", output->name);
112 wlr_output_set_mode(output, best); 158 wlr_output_set_mode(output, best);
113 } 159 }
114} 160}
@@ -116,7 +162,7 @@ static void set_mode(struct wlr_output *output, int width, int height,
116void terminate_swaybg(pid_t pid) { 162void terminate_swaybg(pid_t pid) {
117 int ret = kill(pid, SIGTERM); 163 int ret = kill(pid, SIGTERM);
118 if (ret != 0) { 164 if (ret != 0) {
119 wlr_log(L_ERROR, "Unable to terminate swaybg [pid: %d]", pid); 165 wlr_log(WLR_ERROR, "Unable to terminate swaybg [pid: %d]", pid);
120 } else { 166 } else {
121 int status; 167 int status;
122 waitpid(pid, &status, 0); 168 waitpid(pid, &status, 0);
@@ -144,37 +190,27 @@ void apply_output_config(struct output_config *oc, struct sway_container *output
144 } 190 }
145 191
146 if (oc && oc->width > 0 && oc->height > 0) { 192 if (oc && oc->width > 0 && oc->height > 0) {
147 wlr_log(L_DEBUG, "Set %s mode to %dx%d (%f GHz)", oc->name, oc->width, 193 wlr_log(WLR_DEBUG, "Set %s mode to %dx%d (%f GHz)", oc->name, oc->width,
148 oc->height, oc->refresh_rate); 194 oc->height, oc->refresh_rate);
149 set_mode(wlr_output, oc->width, oc->height, oc->refresh_rate); 195 set_mode(wlr_output, oc->width, oc->height, oc->refresh_rate);
150 } 196 }
151 if (oc && oc->scale > 0) { 197 if (oc && oc->scale > 0) {
152 wlr_log(L_DEBUG, "Set %s scale to %f", oc->name, oc->scale); 198 wlr_log(WLR_DEBUG, "Set %s scale to %f", oc->name, oc->scale);
153 wlr_output_set_scale(wlr_output, oc->scale); 199 wlr_output_set_scale(wlr_output, oc->scale);
154 } 200 }
155 if (oc && oc->transform >= 0) { 201 if (oc && oc->transform >= 0) {
156 wlr_log(L_DEBUG, "Set %s transform to %d", oc->name, oc->transform); 202 wlr_log(WLR_DEBUG, "Set %s transform to %d", oc->name, oc->transform);
157 wlr_output_set_transform(wlr_output, oc->transform); 203 wlr_output_set_transform(wlr_output, oc->transform);
158 } 204 }
159 205
160 // Find position for it 206 // Find position for it
161 if (oc && (oc->x != -1 || oc->y != -1)) { 207 if (oc && (oc->x != -1 || oc->y != -1)) {
162 wlr_log(L_DEBUG, "Set %s position to %d, %d", oc->name, oc->x, oc->y); 208 wlr_log(WLR_DEBUG, "Set %s position to %d, %d", oc->name, oc->x, oc->y);
163 wlr_output_layout_add(output_layout, wlr_output, oc->x, oc->y); 209 wlr_output_layout_add(output_layout, wlr_output, oc->x, oc->y);
164 } else { 210 } else {
165 wlr_output_layout_add_auto(output_layout, wlr_output); 211 wlr_output_layout_add_auto(output_layout, wlr_output);
166 } 212 }
167 213
168 if (!oc || !oc->background) {
169 // Look for a * config for background
170 int i = list_seq_find(config->output_configs, output_name_cmp, "*");
171 if (i >= 0) {
172 oc = config->output_configs->items[i];
173 } else {
174 oc = NULL;
175 }
176 }
177
178 int output_i; 214 int output_i;
179 for (output_i = 0; output_i < root_container.children->length; ++output_i) { 215 for (output_i = 0; output_i < root_container.children->length; ++output_i) {
180 if (root_container.children->items[output_i] == output) { 216 if (root_container.children->items[output_i] == output) {
@@ -187,7 +223,7 @@ void apply_output_config(struct output_config *oc, struct sway_container *output
187 terminate_swaybg(output->sway_output->bg_pid); 223 terminate_swaybg(output->sway_output->bg_pid);
188 } 224 }
189 225
190 wlr_log(L_DEBUG, "Setting background for output %d to %s", 226 wlr_log(WLR_DEBUG, "Setting background for output %d to %s",
191 output_i, oc->background); 227 output_i, oc->background);
192 228
193 size_t len = snprintf(NULL, 0, "%s %d %s %s", 229 size_t len = snprintf(NULL, 0, "%s %d %s %s",
@@ -195,28 +231,30 @@ void apply_output_config(struct output_config *oc, struct sway_container *output
195 output_i, oc->background, oc->background_option); 231 output_i, oc->background, oc->background_option);
196 char *command = malloc(len + 1); 232 char *command = malloc(len + 1);
197 if (!command) { 233 if (!command) {
198 wlr_log(L_DEBUG, "Unable to allocate swaybg command"); 234 wlr_log(WLR_DEBUG, "Unable to allocate swaybg command");
199 return; 235 return;
200 } 236 }
201 snprintf(command, len + 1, "%s %d %s %s", 237 snprintf(command, len + 1, "%s %d %s %s",
202 config->swaybg_command ? config->swaybg_command : "swaybg", 238 config->swaybg_command ? config->swaybg_command : "swaybg",
203 output_i, oc->background, oc->background_option); 239 output_i, oc->background, oc->background_option);
204 wlr_log(L_DEBUG, "-> %s", command); 240 wlr_log(WLR_DEBUG, "-> %s", command);
205 241
206 char *const cmd[] = { "sh", "-c", command, NULL }; 242 char *const cmd[] = { "sh", "-c", command, NULL };
207 output->sway_output->bg_pid = fork(); 243 output->sway_output->bg_pid = fork();
208 if (output->sway_output->bg_pid == 0) { 244 if (output->sway_output->bg_pid == 0) {
209 execvp(cmd[0], cmd); 245 execvp(cmd[0], cmd);
246 } else {
247 free(command);
210 } 248 }
211 } 249 }
212 if (oc && oc->dpms_state != DPMS_IGNORE) { 250 if (oc && oc->dpms_state != DPMS_IGNORE) {
213 switch (oc->dpms_state) { 251 switch (oc->dpms_state) {
214 case DPMS_ON: 252 case DPMS_ON:
215 wlr_log(L_DEBUG, "Turning on screen"); 253 wlr_log(WLR_DEBUG, "Turning on screen");
216 wlr_output_enable(wlr_output, true); 254 wlr_output_enable(wlr_output, true);
217 break; 255 break;
218 case DPMS_OFF: 256 case DPMS_OFF:
219 wlr_log(L_DEBUG, "Turning off screen"); 257 wlr_log(WLR_DEBUG, "Turning off screen");
220 wlr_output_enable(wlr_output, false); 258 wlr_output_enable(wlr_output, false);
221 break; 259 break;
222 case DPMS_IGNORE: 260 case DPMS_IGNORE:
@@ -225,6 +263,60 @@ void apply_output_config(struct output_config *oc, struct sway_container *output
225 } 263 }
226} 264}
227 265
266static struct output_config *get_output_config(char *name, char *identifier) {
267 int i = list_seq_find(config->output_configs, output_name_cmp, name);
268 if (i >= 0) {
269 return config->output_configs->items[i];
270 }
271
272 i = list_seq_find(config->output_configs, output_name_cmp, identifier);
273 if (i >= 0) {
274 return config->output_configs->items[i];
275 }
276
277 return NULL;
278}
279
280void apply_output_config_to_outputs(struct output_config *oc) {
281 // Try to find the output container and apply configuration now. If
282 // this is during startup then there will be no container and config
283 // will be applied during normal "new output" event from wlroots.
284 bool wildcard = strcmp(oc->name, "*") == 0;
285 char id[128];
286 struct sway_output *sway_output;
287 wl_list_for_each(sway_output, &root_container.sway_root->outputs, link) {
288 char *name = sway_output->wlr_output->name;
289 output_get_identifier(id, sizeof(id), sway_output);
290 if (wildcard || !strcmp(name, oc->name) || !strcmp(id, oc->name)) {
291 if (!sway_output->swayc) {
292 if (!oc->enabled) {
293 if (!wildcard) {
294 break;
295 }
296 continue;
297 }
298
299 output_enable(sway_output);
300 }
301
302 struct output_config *current = oc;
303 if (wildcard) {
304 struct output_config *tmp = get_output_config(name, id);
305 if (tmp) {
306 current = tmp;
307 }
308 }
309 apply_output_config(current, sway_output->swayc);
310
311 if (!wildcard) {
312 // Stop looking if the output config isn't applicable to all
313 // outputs
314 break;
315 }
316 }
317 }
318}
319
228void free_output_config(struct output_config *oc) { 320void free_output_config(struct output_config *oc) {
229 if (!oc) { 321 if (!oc) {
230 return; 322 return;
@@ -234,3 +326,29 @@ void free_output_config(struct output_config *oc) {
234 free(oc->background_option); 326 free(oc->background_option);
235 free(oc); 327 free(oc);
236} 328}
329
330static void default_output_config(struct output_config *oc,
331 struct wlr_output *wlr_output) {
332 oc->enabled = 1;
333 if (!wl_list_empty(&wlr_output->modes)) {
334 struct wlr_output_mode *mode =
335 wl_container_of(wlr_output->modes.prev, mode, link);
336 oc->width = mode->width;
337 oc->height = mode->height;
338 oc->refresh_rate = mode->refresh;
339 }
340 oc->x = oc->y = -1;
341 oc->scale = 1;
342 oc->transform = WL_OUTPUT_TRANSFORM_NORMAL;
343}
344
345void create_default_output_configs(void) {
346 struct sway_output *sway_output;
347 wl_list_for_each(sway_output, &root_container.sway_root->outputs, link) {
348 char *name = sway_output->wlr_output->name;
349 struct output_config *oc = new_output_config(name);
350 default_output_config(oc, sway_output->wlr_output);
351 list_add(config->output_configs, oc);
352 }
353}
354
diff --git a/sway/config/seat.c b/sway/config/seat.c
index bd8b45c8..83dac4c0 100644
--- a/sway/config/seat.c
+++ b/sway/config/seat.c
@@ -7,11 +7,11 @@
7struct seat_config *new_seat_config(const char* name) { 7struct seat_config *new_seat_config(const char* name) {
8 struct seat_config *seat = calloc(1, sizeof(struct seat_config)); 8 struct seat_config *seat = calloc(1, sizeof(struct seat_config));
9 if (!seat) { 9 if (!seat) {
10 wlr_log(L_DEBUG, "Unable to allocate seat config"); 10 wlr_log(WLR_DEBUG, "Unable to allocate seat config");
11 return NULL; 11 return NULL;
12 } 12 }
13 13
14 wlr_log(L_DEBUG, "new_seat_config(%s)", name); 14 wlr_log(WLR_DEBUG, "new_seat_config(%s)", name);
15 seat->name = strdup(name); 15 seat->name = strdup(name);
16 if (!sway_assert(seat->name, "could not allocate name for seat")) { 16 if (!sway_assert(seat->name, "could not allocate name for seat")) {
17 free(seat); 17 free(seat);
@@ -34,7 +34,7 @@ struct seat_attachment_config *seat_attachment_config_new() {
34 struct seat_attachment_config *attachment = 34 struct seat_attachment_config *attachment =
35 calloc(1, sizeof(struct seat_attachment_config)); 35 calloc(1, sizeof(struct seat_attachment_config));
36 if (!attachment) { 36 if (!attachment) {
37 wlr_log(L_DEBUG, "cannot allocate attachment config"); 37 wlr_log(WLR_DEBUG, "cannot allocate attachment config");
38 return NULL; 38 return NULL;
39 } 39 }
40 return attachment; 40 return attachment;
diff --git a/sway/criteria.c b/sway/criteria.c
index d9f09ecc..c2e9c07e 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) {
@@ -185,6 +225,15 @@ list_t *criteria_get_views(struct criteria *criteria) {
185 }; 225 };
186 container_for_each_descendant_dfs(&root_container, 226 container_for_each_descendant_dfs(&root_container,
187 criteria_get_views_iterator, &data); 227 criteria_get_views_iterator, &data);
228
229 // Scratchpad items which are hidden are not in the tree.
230 for (int i = 0; i < root_container.sway_root->scratchpad->length; ++i) {
231 struct sway_container *con =
232 root_container.sway_root->scratchpad->items[i];
233 if (!con->parent) {
234 criteria_get_views_iterator(con, &data);
235 }
236 }
188 return matches; 237 return matches;
189} 238}
190 239
@@ -507,7 +556,7 @@ struct criteria *criteria_parse(char *raw, char **error_arg) {
507 } 556 }
508 unescape(value); 557 unescape(value);
509 } 558 }
510 wlr_log(L_DEBUG, "Found pair: %s=%s", name, value); 559 wlr_log(WLR_DEBUG, "Found pair: %s=%s", name, value);
511 if (!parse_token(criteria, name, value)) { 560 if (!parse_token(criteria, name, value)) {
512 *error_arg = error; 561 *error_arg = error;
513 goto cleanup; 562 goto cleanup;
diff --git a/sway/desktop/desktop.c b/sway/desktop/desktop.c
index e495790c..6575519d 100644
--- a/sway/desktop/desktop.c
+++ b/sway/desktop/desktop.c
@@ -13,3 +13,12 @@ void desktop_damage_surface(struct wlr_surface *surface, double lx, double ly,
13 } 13 }
14 } 14 }
15} 15}
16
17void desktop_damage_whole_container(struct sway_container *con) {
18 for (int i = 0; i < root_container.children->length; ++i) {
19 struct sway_container *cont = root_container.children->items[i];
20 if (cont->type == C_OUTPUT) {
21 output_damage_whole_container(cont->sway_output, con);
22 }
23 }
24}
diff --git a/sway/desktop/idle_inhibit_v1.c b/sway/desktop/idle_inhibit_v1.c
new file mode 100644
index 00000000..da17d0f2
--- /dev/null
+++ b/sway/desktop/idle_inhibit_v1.c
@@ -0,0 +1,80 @@
1#include <stdlib.h>
2#include <wlr/types/wlr_idle.h>
3#include "log.h"
4#include "sway/desktop/idle_inhibit_v1.h"
5#include "sway/tree/view.h"
6#include "sway/server.h"
7
8
9static void handle_destroy(struct wl_listener *listener, void *data) {
10 struct sway_idle_inhibitor_v1 *inhibitor =
11 wl_container_of(listener, inhibitor, destroy);
12 wlr_log(WLR_DEBUG, "Sway idle inhibitor destroyed");
13 wl_list_remove(&inhibitor->link);
14 wl_list_remove(&inhibitor->destroy.link);
15 idle_inhibit_v1_check_active(inhibitor->manager);
16 free(inhibitor);
17}
18
19void handle_idle_inhibitor_v1(struct wl_listener *listener, void *data) {
20 struct wlr_idle_inhibitor_v1 *wlr_inhibitor = data;
21 struct sway_idle_inhibit_manager_v1 *manager =
22 wl_container_of(listener, manager, new_idle_inhibitor_v1);
23 wlr_log(WLR_DEBUG, "New sway idle inhibitor");
24
25 struct sway_idle_inhibitor_v1 *inhibitor =
26 calloc(1, sizeof(struct sway_idle_inhibitor_v1));
27 if (!inhibitor) {
28 return;
29 }
30
31 inhibitor->manager = manager;
32 inhibitor->view = view_from_wlr_surface(wlr_inhibitor->surface);
33 wl_list_insert(&manager->inhibitors, &inhibitor->link);
34
35
36 inhibitor->destroy.notify = handle_destroy;
37 wl_signal_add(&wlr_inhibitor->events.destroy, &inhibitor->destroy);
38
39 idle_inhibit_v1_check_active(manager);
40}
41
42void idle_inhibit_v1_check_active(
43 struct sway_idle_inhibit_manager_v1 *manager) {
44 struct sway_idle_inhibitor_v1 *inhibitor;
45 bool inhibited = false;
46 wl_list_for_each(inhibitor, &manager->inhibitors, link) {
47 if (!inhibitor->view) {
48 /* Cannot guess if view is visible so assume it is */
49 inhibited = true;
50 break;
51 }
52 if (view_is_visible(inhibitor->view)) {
53 inhibited = true;
54 break;
55 }
56 }
57 wlr_idle_set_enabled(manager->idle, NULL, !inhibited);
58}
59
60struct sway_idle_inhibit_manager_v1 *sway_idle_inhibit_manager_v1_create(
61 struct wl_display *wl_display, struct wlr_idle *idle) {
62 struct sway_idle_inhibit_manager_v1 *manager =
63 calloc(1, sizeof(struct sway_idle_inhibit_manager_v1));
64 if (!manager) {
65 return NULL;
66 }
67
68 manager->wlr_manager = wlr_idle_inhibit_v1_create(wl_display);
69 if (!manager->wlr_manager) {
70 free(manager);
71 return NULL;
72 }
73 manager->idle = idle;
74 wl_signal_add(&manager->wlr_manager->events.new_inhibitor,
75 &manager->new_idle_inhibitor_v1);
76 manager->new_idle_inhibitor_v1.notify = handle_idle_inhibitor_v1;
77 wl_list_init(&manager->inhibitors);
78
79 return manager;
80}
diff --git a/sway/desktop/layer_shell.c b/sway/desktop/layer_shell.c
index de1fe349..a7d96717 100644
--- a/sway/desktop/layer_shell.c
+++ b/sway/desktop/layer_shell.c
@@ -12,7 +12,6 @@
12#include "sway/layers.h" 12#include "sway/layers.h"
13#include "sway/output.h" 13#include "sway/output.h"
14#include "sway/server.h" 14#include "sway/server.h"
15#include "sway/tree/arrange.h"
16#include "sway/tree/layout.h" 15#include "sway/tree/layout.h"
17#include "log.h" 16#include "log.h"
18 17
@@ -174,9 +173,9 @@ void arrange_layers(struct sway_output *output) {
174 173
175 if (memcmp(&usable_area, &output->usable_area, 174 if (memcmp(&usable_area, &output->usable_area,
176 sizeof(struct wlr_box)) != 0) { 175 sizeof(struct wlr_box)) != 0) {
177 wlr_log(L_DEBUG, "Usable area changed, rearranging output"); 176 wlr_log(WLR_DEBUG, "Usable area changed, rearranging output");
178 memcpy(&output->usable_area, &usable_area, sizeof(struct wlr_box)); 177 memcpy(&output->usable_area, &usable_area, sizeof(struct wlr_box));
179 arrange_and_commit(output->swayc); 178 container_set_dirty(output->swayc);
180 } 179 }
181 180
182 // Arrange non-exlusive surfaces from top->bottom 181 // Arrange non-exlusive surfaces from top->bottom
@@ -269,7 +268,7 @@ static void unmap(struct sway_layer_surface *sway_layer) {
269static void handle_destroy(struct wl_listener *listener, void *data) { 268static void handle_destroy(struct wl_listener *listener, void *data) {
270 struct sway_layer_surface *sway_layer = 269 struct sway_layer_surface *sway_layer =
271 wl_container_of(listener, sway_layer, destroy); 270 wl_container_of(listener, sway_layer, destroy);
272 wlr_log(L_DEBUG, "Layer surface destroyed (%s)", 271 wlr_log(WLR_DEBUG, "Layer surface destroyed (%s)",
273 sway_layer->layer_surface->namespace); 272 sway_layer->layer_surface->namespace);
274 if (sway_layer->layer_surface->mapped) { 273 if (sway_layer->layer_surface->mapped) {
275 unmap(sway_layer); 274 unmap(sway_layer);
@@ -316,7 +315,7 @@ void handle_layer_shell_surface(struct wl_listener *listener, void *data) {
316 struct wlr_layer_surface *layer_surface = data; 315 struct wlr_layer_surface *layer_surface = data;
317 struct sway_server *server = 316 struct sway_server *server =
318 wl_container_of(listener, server, layer_shell_surface); 317 wl_container_of(listener, server, layer_shell_surface);
319 wlr_log(L_DEBUG, "new layer surface: namespace %s layer %d anchor %d " 318 wlr_log(WLR_DEBUG, "new layer surface: namespace %s layer %d anchor %d "
320 "size %dx%d margin %d,%d,%d,%d", 319 "size %dx%d margin %d,%d,%d,%d",
321 layer_surface->namespace, layer_surface->layer, layer_surface->layer, 320 layer_surface->namespace, layer_surface->layer, layer_surface->layer,
322 layer_surface->client_pending.desired_width, 321 layer_surface->client_pending.desired_width,
@@ -326,12 +325,6 @@ void handle_layer_shell_surface(struct wl_listener *listener, void *data) {
326 layer_surface->client_pending.margin.bottom, 325 layer_surface->client_pending.margin.bottom,
327 layer_surface->client_pending.margin.left); 326 layer_surface->client_pending.margin.left);
328 327
329 struct sway_layer_surface *sway_layer =
330 calloc(1, sizeof(struct sway_layer_surface));
331 if (!sway_layer) {
332 return;
333 }
334
335 if (!layer_surface->output) { 328 if (!layer_surface->output) {
336 // Assign last active output 329 // Assign last active output
337 struct sway_container *output = NULL; 330 struct sway_container *output = NULL;
@@ -353,6 +346,12 @@ void handle_layer_shell_surface(struct wl_listener *listener, void *data) {
353 layer_surface->output = output->sway_output->wlr_output; 346 layer_surface->output = output->sway_output->wlr_output;
354 } 347 }
355 348
349 struct sway_layer_surface *sway_layer =
350 calloc(1, sizeof(struct sway_layer_surface));
351 if (!sway_layer) {
352 return;
353 }
354
356 sway_layer->surface_commit.notify = handle_surface_commit; 355 sway_layer->surface_commit.notify = handle_surface_commit;
357 wl_signal_add(&layer_surface->surface->events.commit, 356 wl_signal_add(&layer_surface->surface->events.commit,
358 &sway_layer->surface_commit); 357 &sway_layer->surface_commit);
diff --git a/sway/desktop/output.c b/sway/desktop/output.c
index 1e7494b3..a206ac6b 100644
--- a/sway/desktop/output.c
+++ b/sway/desktop/output.c
@@ -56,33 +56,15 @@ static void rotate_child_position(double *sx, double *sy, double sw, double sh,
56 *sy = ry + ph/2 - sh/2; 56 *sy = ry + ph/2 - sh/2;
57} 57}
58 58
59/** 59bool output_get_surface_box(struct root_geometry *geo,
60 * Contains a surface's root geometry information. For instance, when rendering
61 * a popup, this will contain the parent view's position and size.
62 */
63struct root_geometry {
64 double x, y;
65 int width, height;
66 float rotation;
67};
68
69struct render_data {
70 struct root_geometry root_geo;
71 struct sway_output *output;
72 pixman_region32_t *damage;
73 struct sway_view *view;
74 float alpha;
75};
76
77static bool get_surface_box(struct root_geometry *geo,
78 struct sway_output *output, struct wlr_surface *surface, int sx, int sy, 60 struct sway_output *output, struct wlr_surface *surface, int sx, int sy,
79 struct wlr_box *surface_box) { 61 struct wlr_box *surface_box) {
80 if (!wlr_surface_has_buffer(surface)) { 62 if (!wlr_surface_has_buffer(surface)) {
81 return false; 63 return false;
82 } 64 }
83 65
84 int sw = surface->current->width; 66 int sw = surface->current.width;
85 int sh = surface->current->height; 67 int sh = surface->current.height;
86 68
87 double _sx = sx, _sy = sy; 69 double _sx = sx, _sy = sy;
88 rotate_child_position(&_sx, &_sy, sw, sh, geo->width, geo->height, 70 rotate_child_position(&_sx, &_sy, sw, sh, geo->width, geo->height,
@@ -110,24 +92,23 @@ static bool get_surface_box(struct root_geometry *geo,
110 return wlr_box_intersection(&output_box, &rotated_box, &intersection); 92 return wlr_box_intersection(&output_box, &rotated_box, &intersection);
111} 93}
112 94
113static void surface_for_each_surface(struct wlr_surface *surface, 95void output_surface_for_each_surface(struct wlr_surface *surface,
114 double ox, double oy, struct root_geometry *geo, 96 double ox, double oy, struct root_geometry *geo,
115 wlr_surface_iterator_func_t iterator, void *user_data) { 97 wlr_surface_iterator_func_t iterator, void *user_data) {
116 geo->x = ox; 98 geo->x = ox;
117 geo->y = oy; 99 geo->y = oy;
118 geo->width = surface->current->width; 100 geo->width = surface->current.width;
119 geo->height = surface->current->height; 101 geo->height = surface->current.height;
120 geo->rotation = 0; 102 geo->rotation = 0;
121 103
122 wlr_surface_for_each_surface(surface, iterator, user_data); 104 wlr_surface_for_each_surface(surface, iterator, user_data);
123} 105}
124 106
125static void output_view_for_each_surface(struct sway_view *view, 107void output_view_for_each_surface(struct sway_view *view,
126 struct root_geometry *geo, wlr_surface_iterator_func_t iterator, 108 struct sway_output *output, struct root_geometry *geo,
127 void *user_data) { 109 wlr_surface_iterator_func_t iterator, void *user_data) {
128 struct render_data *data = user_data; 110 geo->x = view->swayc->current.view_x - output->swayc->current.swayc_x;
129 geo->x = view->swayc->current.view_x - data->output->swayc->current.swayc_x; 111 geo->y = view->swayc->current.view_y - output->swayc->current.swayc_y;
130 geo->y = view->swayc->current.view_y - data->output->swayc->current.swayc_y;
131 geo->width = view->swayc->current.view_width; 112 geo->width = view->swayc->current.view_width;
132 geo->height = view->swayc->current.view_height; 113 geo->height = view->swayc->current.view_height;
133 geo->rotation = 0; // TODO 114 geo->rotation = 0; // TODO
@@ -135,20 +116,20 @@ static void output_view_for_each_surface(struct sway_view *view,
135 view_for_each_surface(view, iterator, user_data); 116 view_for_each_surface(view, iterator, user_data);
136} 117}
137 118
138static void layer_for_each_surface(struct wl_list *layer_surfaces, 119void output_layer_for_each_surface(struct wl_list *layer_surfaces,
139 struct root_geometry *geo, wlr_surface_iterator_func_t iterator, 120 struct root_geometry *geo, wlr_surface_iterator_func_t iterator,
140 void *user_data) { 121 void *user_data) {
141 struct sway_layer_surface *layer_surface; 122 struct sway_layer_surface *layer_surface;
142 wl_list_for_each(layer_surface, layer_surfaces, link) { 123 wl_list_for_each(layer_surface, layer_surfaces, link) {
143 struct wlr_layer_surface *wlr_layer_surface = 124 struct wlr_layer_surface *wlr_layer_surface =
144 layer_surface->layer_surface; 125 layer_surface->layer_surface;
145 surface_for_each_surface(wlr_layer_surface->surface, 126 output_surface_for_each_surface(wlr_layer_surface->surface,
146 layer_surface->geo.x, layer_surface->geo.y, geo, iterator, 127 layer_surface->geo.x, layer_surface->geo.y, geo, iterator,
147 user_data); 128 user_data);
148 } 129 }
149} 130}
150 131
151static void unmanaged_for_each_surface(struct wl_list *unmanaged, 132void output_unmanaged_for_each_surface(struct wl_list *unmanaged,
152 struct sway_output *output, struct root_geometry *geo, 133 struct sway_output *output, struct root_geometry *geo,
153 wlr_surface_iterator_func_t iterator, void *user_data) { 134 wlr_surface_iterator_func_t iterator, void *user_data) {
154 struct sway_xwayland_unmanaged *unmanaged_surface; 135 struct sway_xwayland_unmanaged *unmanaged_surface;
@@ -158,12 +139,12 @@ static void unmanaged_for_each_surface(struct wl_list *unmanaged,
158 double ox = unmanaged_surface->lx - output->swayc->current.swayc_x; 139 double ox = unmanaged_surface->lx - output->swayc->current.swayc_x;
159 double oy = unmanaged_surface->ly - output->swayc->current.swayc_y; 140 double oy = unmanaged_surface->ly - output->swayc->current.swayc_y;
160 141
161 surface_for_each_surface(xsurface->surface, ox, oy, geo, 142 output_surface_for_each_surface(xsurface->surface, ox, oy, geo,
162 iterator, user_data); 143 iterator, user_data);
163 } 144 }
164} 145}
165 146
166static void drag_icons_for_each_surface(struct wl_list *drag_icons, 147void output_drag_icons_for_each_surface(struct wl_list *drag_icons,
167 struct sway_output *output, struct root_geometry *geo, 148 struct sway_output *output, struct root_geometry *geo,
168 wlr_surface_iterator_func_t iterator, void *user_data) { 149 wlr_surface_iterator_func_t iterator, void *user_data) {
169 struct sway_drag_icon *drag_icon; 150 struct sway_drag_icon *drag_icon;
@@ -172,7 +153,7 @@ static void drag_icons_for_each_surface(struct wl_list *drag_icons,
172 double oy = drag_icon->y - output->swayc->y; 153 double oy = drag_icon->y - output->swayc->y;
173 154
174 if (drag_icon->wlr_drag_icon->mapped) { 155 if (drag_icon->wlr_drag_icon->mapped) {
175 surface_for_each_surface(drag_icon->wlr_drag_icon->surface, 156 output_surface_for_each_surface(drag_icon->wlr_drag_icon->surface,
176 ox, oy, geo, iterator, user_data); 157 ox, oy, geo, iterator, user_data);
177 } 158 }
178 } 159 }
@@ -185,722 +166,7 @@ static void scale_box(struct wlr_box *box, float scale) {
185 box->height *= scale; 166 box->height *= scale;
186} 167}
187 168
188static void scissor_output(struct wlr_output *wlr_output, 169struct sway_container *output_get_active_workspace(struct sway_output *output) {
189 pixman_box32_t *rect) {
190 struct wlr_renderer *renderer = wlr_backend_get_renderer(wlr_output->backend);
191 assert(renderer);
192
193 struct wlr_box box = {
194 .x = rect->x1,
195 .y = rect->y1,
196 .width = rect->x2 - rect->x1,
197 .height = rect->y2 - rect->y1,
198 };
199
200 int ow, oh;
201 wlr_output_transformed_resolution(wlr_output, &ow, &oh);
202
203 enum wl_output_transform transform =
204 wlr_output_transform_invert(wlr_output->transform);
205 wlr_box_transform(&box, transform, ow, oh, &box);
206
207 wlr_renderer_scissor(renderer, &box);
208}
209
210static void render_texture(struct wlr_output *wlr_output,
211 pixman_region32_t *output_damage, struct wlr_texture *texture,
212 const struct wlr_box *box, const float matrix[static 9], float alpha) {
213 struct wlr_renderer *renderer =
214 wlr_backend_get_renderer(wlr_output->backend);
215
216 pixman_region32_t damage;
217 pixman_region32_init(&damage);
218 pixman_region32_union_rect(&damage, &damage, box->x, box->y,
219 box->width, box->height);
220 pixman_region32_intersect(&damage, &damage, output_damage);
221 bool damaged = pixman_region32_not_empty(&damage);
222 if (!damaged) {
223 goto damage_finish;
224 }
225
226 int nrects;
227 pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects);
228 for (int i = 0; i < nrects; ++i) {
229 scissor_output(wlr_output, &rects[i]);
230 wlr_render_texture_with_matrix(renderer, texture, matrix, alpha);
231 }
232
233damage_finish:
234 pixman_region32_fini(&damage);
235}
236
237static void render_surface_iterator(struct wlr_surface *surface, int sx, int sy,
238 void *_data) {
239 struct render_data *data = _data;
240 struct wlr_output *wlr_output = data->output->wlr_output;
241 float rotation = data->root_geo.rotation;
242 pixman_region32_t *output_damage = data->damage;
243 float alpha = data->alpha;
244
245 struct wlr_texture *texture = wlr_surface_get_texture(surface);
246 if (!texture) {
247 return;
248 }
249
250 struct wlr_box box;
251 bool intersects = get_surface_box(&data->root_geo, data->output, surface,
252 sx, sy, &box);
253 if (!intersects) {
254 return;
255 }
256
257 scale_box(&box, wlr_output->scale);
258
259 float matrix[9];
260 enum wl_output_transform transform =
261 wlr_output_transform_invert(surface->current->transform);
262 wlr_matrix_project_box(matrix, &box, transform, rotation,
263 wlr_output->transform_matrix);
264
265 render_texture(wlr_output, output_damage, texture, &box, matrix, alpha);
266}
267
268static void render_layer(struct sway_output *output,
269 pixman_region32_t *damage, struct wl_list *layer_surfaces) {
270 struct render_data data = {
271 .output = output,
272 .damage = damage,
273 .alpha = 1.0f,
274 };
275 layer_for_each_surface(layer_surfaces, &data.root_geo,
276 render_surface_iterator, &data);
277}
278
279static void render_unmanaged(struct sway_output *output,
280 pixman_region32_t *damage, struct wl_list *unmanaged) {
281 struct render_data data = {
282 .output = output,
283 .damage = damage,
284 .alpha = 1.0f,
285 };
286 unmanaged_for_each_surface(unmanaged, output, &data.root_geo,
287 render_surface_iterator, &data);
288}
289
290static void render_drag_icons(struct sway_output *output,
291 pixman_region32_t *damage, struct wl_list *drag_icons) {
292 struct render_data data = {
293 .output = output,
294 .damage = damage,
295 .alpha = 1.0f,
296 };
297 drag_icons_for_each_surface(drag_icons, output, &data.root_geo,
298 render_surface_iterator, &data);
299}
300
301static void render_rect(struct wlr_output *wlr_output,
302 pixman_region32_t *output_damage, const struct wlr_box *_box,
303 float color[static 4]) {
304 struct wlr_renderer *renderer =
305 wlr_backend_get_renderer(wlr_output->backend);
306
307 struct wlr_box box;
308 memcpy(&box, _box, sizeof(struct wlr_box));
309 box.x -= wlr_output->lx * wlr_output->scale;
310 box.y -= wlr_output->ly * wlr_output->scale;
311
312 pixman_region32_t damage;
313 pixman_region32_init(&damage);
314 pixman_region32_union_rect(&damage, &damage, box.x, box.y,
315 box.width, box.height);
316 pixman_region32_intersect(&damage, &damage, output_damage);
317 bool damaged = pixman_region32_not_empty(&damage);
318 if (!damaged) {
319 goto damage_finish;
320 }
321
322 int nrects;
323 pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects);
324 for (int i = 0; i < nrects; ++i) {
325 scissor_output(wlr_output, &rects[i]);
326 wlr_render_rect(renderer, &box, color,
327 wlr_output->transform_matrix);
328 }
329
330damage_finish:
331 pixman_region32_fini(&damage);
332}
333
334static void premultiply_alpha(float color[4], float opacity) {
335 color[3] *= opacity;
336 color[0] *= color[3];
337 color[1] *= color[3];
338 color[2] *= color[3];
339}
340
341static void render_view_surfaces(struct sway_view *view,
342 struct sway_output *output, pixman_region32_t *damage, float alpha) {
343 struct render_data data = {
344 .output = output,
345 .damage = damage,
346 .view = view,
347 .alpha = alpha,
348 };
349 output_view_for_each_surface(
350 view, &data.root_geo, render_surface_iterator, &data);
351}
352
353static void render_saved_view(struct sway_view *view,
354 struct sway_output *output, pixman_region32_t *damage, float alpha) {
355 struct wlr_output *wlr_output = output->wlr_output;
356
357 int width, height;
358 struct wlr_texture *texture =
359 transaction_get_saved_texture(view, &width, &height);
360 if (!texture) {
361 return;
362 }
363 struct wlr_box box = {
364 .x = view->swayc->current.view_x - output->swayc->current.swayc_x,
365 .y = view->swayc->current.view_y - output->swayc->current.swayc_y,
366 .width = width,
367 .height = height,
368 };
369
370 struct wlr_box output_box = {
371 .width = output->swayc->current.swayc_width,
372 .height = output->swayc->current.swayc_height,
373 };
374
375 struct wlr_box intersection;
376 bool intersects = wlr_box_intersection(&output_box, &box, &intersection);
377 if (!intersects) {
378 return;
379 }
380
381 scale_box(&box, wlr_output->scale);
382
383 float matrix[9];
384 wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0,
385 wlr_output->transform_matrix);
386
387 render_texture(wlr_output, damage, texture, &box, matrix, alpha);
388}
389
390/**
391 * Render a view's surface and left/bottom/right borders.
392 */
393static void render_view(struct sway_output *output, pixman_region32_t *damage,
394 struct sway_container *con, struct border_colors *colors) {
395 struct sway_view *view = con->sway_view;
396 if (view->swayc->instructions->length) {
397 render_saved_view(view, output, damage, view->swayc->alpha);
398 } else {
399 render_view_surfaces(view, output, damage, view->swayc->alpha);
400 }
401
402 struct wlr_box box;
403 float output_scale = output->wlr_output->scale;
404 float color[4];
405 struct sway_container_state *state = &con->current;
406
407 if (state->border != B_NONE) {
408 if (state->border_left) {
409 memcpy(&color, colors->child_border, sizeof(float) * 4);
410 premultiply_alpha(color, con->alpha);
411 box.x = state->swayc_x;
412 box.y = state->view_y;
413 box.width = state->border_thickness;
414 box.height = state->view_height;
415 scale_box(&box, output_scale);
416 render_rect(output->wlr_output, damage, &box, color);
417 }
418
419 if (state->border_right) {
420 if (state->parent->current.children->length == 1
421 && state->parent->current.layout == L_HORIZ) {
422 memcpy(&color, colors->indicator, sizeof(float) * 4);
423 } else {
424 memcpy(&color, colors->child_border, sizeof(float) * 4);
425 }
426 premultiply_alpha(color, con->alpha);
427 box.x = state->view_x + state->view_width;
428 box.y = state->view_y;
429 box.width = state->border_thickness;
430 box.height = state->view_height;
431 scale_box(&box, output_scale);
432 render_rect(output->wlr_output, damage, &box, color);
433 }
434
435 if (state->border_bottom) {
436 if (state->parent->current.children->length == 1
437 && con->current.parent->current.layout == L_VERT) {
438 memcpy(&color, colors->indicator, sizeof(float) * 4);
439 } else {
440 memcpy(&color, colors->child_border, sizeof(float) * 4);
441 }
442 premultiply_alpha(color, con->alpha);
443 box.x = state->swayc_x;
444 box.y = state->view_y + state->view_height;
445 box.width = state->swayc_width;
446 box.height = state->border_thickness;
447 scale_box(&box, output_scale);
448 render_rect(output->wlr_output, damage, &box, color);
449 }
450 }
451}
452
453/**
454 * Render a titlebar.
455 *
456 * Care must be taken not to render over the same pixel multiple times,
457 * otherwise the colors will be incorrect when using opacity.
458 *
459 * The height is: 1px border, 3px padding, font height, 3px padding, 1px border
460 * The left side for L_TABBED is: 1px border, 2px padding, title
461 * The left side for other layouts is: 3px padding, title
462 */
463static void render_titlebar(struct sway_output *output,
464 pixman_region32_t *output_damage, struct sway_container *con,
465 int x, int y, int width,
466 struct border_colors *colors, struct wlr_texture *title_texture,
467 struct wlr_texture *marks_texture) {
468 struct wlr_box box;
469 float color[4];
470 struct sway_container_state *state = &con->current;
471 float output_scale = output->wlr_output->scale;
472 enum sway_container_layout layout = state->parent->current.layout;
473 list_t *children = state->parent->current.children;
474 bool is_last_child = children->items[children->length - 1] == con;
475 double output_x = output->swayc->current.swayc_x;
476 double output_y = output->swayc->current.swayc_y;
477
478 // Single pixel bar above title
479 memcpy(&color, colors->border, sizeof(float) * 4);
480 premultiply_alpha(color, con->alpha);
481 box.x = x;
482 box.y = y;
483 box.width = width;
484 box.height = TITLEBAR_BORDER_THICKNESS;
485 scale_box(&box, output_scale);
486 render_rect(output->wlr_output, output_damage, &box, color);
487
488 // Single pixel bar below title
489 size_t left_offset = 0, right_offset = 0;
490 bool connects_sides = false;
491 if (layout == L_HORIZ || layout == L_VERT ||
492 (layout == L_STACKED && is_last_child)) {
493 if (con->type == C_VIEW) {
494 left_offset = state->border_left * state->border_thickness;
495 right_offset = state->border_right * state->border_thickness;
496 connects_sides = true;
497 }
498 }
499 box.x = x + left_offset;
500 box.y = y + container_titlebar_height() - TITLEBAR_BORDER_THICKNESS;
501 box.width = width - left_offset - right_offset;
502 box.height = TITLEBAR_BORDER_THICKNESS;
503 scale_box(&box, output_scale);
504 render_rect(output->wlr_output, output_damage, &box, color);
505
506 if (layout == L_TABBED) {
507 // Single pixel left edge
508 box.x = x;
509 box.y = y + TITLEBAR_BORDER_THICKNESS;
510 box.width = TITLEBAR_BORDER_THICKNESS;
511 box.height =
512 container_titlebar_height() - TITLEBAR_BORDER_THICKNESS * 2;
513 scale_box(&box, output_scale);
514 render_rect(output->wlr_output, output_damage, &box, color);
515
516 // Single pixel right edge
517 box.x = (x + width - TITLEBAR_BORDER_THICKNESS) * output_scale;
518 render_rect(output->wlr_output, output_damage, &box, color);
519 }
520
521 size_t inner_width = width - TITLEBAR_H_PADDING * 2;
522
523 // Marks
524 size_t marks_width = 0;
525 if (config->show_marks && marks_texture) {
526 struct wlr_box texture_box;
527 wlr_texture_get_size(marks_texture,
528 &texture_box.width, &texture_box.height);
529 texture_box.x = (x - output_x + width - TITLEBAR_H_PADDING)
530 * output_scale - texture_box.width;
531 texture_box.y = (y - output_y + TITLEBAR_V_PADDING) * output_scale;
532
533 float matrix[9];
534 wlr_matrix_project_box(matrix, &texture_box,
535 WL_OUTPUT_TRANSFORM_NORMAL,
536 0.0, output->wlr_output->transform_matrix);
537
538 if (inner_width * output_scale < texture_box.width) {
539 texture_box.width = inner_width * output_scale;
540 }
541 render_texture(output->wlr_output, output_damage, marks_texture,
542 &texture_box, matrix, con->alpha);
543 marks_width = texture_box.width;
544 }
545
546 // Title text
547 size_t title_width = 0;
548 if (title_texture) {
549 struct wlr_box texture_box;
550 wlr_texture_get_size(title_texture,
551 &texture_box.width, &texture_box.height);
552 texture_box.x = (x - output_x + TITLEBAR_H_PADDING) * output_scale;
553 texture_box.y = (y - output_y + TITLEBAR_V_PADDING) * output_scale;
554
555 float matrix[9];
556 wlr_matrix_project_box(matrix, &texture_box,
557 WL_OUTPUT_TRANSFORM_NORMAL,
558 0.0, output->wlr_output->transform_matrix);
559
560 if (inner_width * output_scale - marks_width < texture_box.width) {
561 texture_box.width = inner_width * output_scale - marks_width;
562 }
563 render_texture(output->wlr_output, output_damage, title_texture,
564 &texture_box, matrix, con->alpha);
565 title_width = texture_box.width;
566 }
567
568 // Padding above title
569 memcpy(&color, colors->background, sizeof(float) * 4);
570 premultiply_alpha(color, con->alpha);
571 box.x = x + (layout == L_TABBED) * TITLEBAR_BORDER_THICKNESS;
572 box.y = y + TITLEBAR_BORDER_THICKNESS;
573 box.width = width - (layout == L_TABBED) * TITLEBAR_BORDER_THICKNESS * 2;
574 box.height = TITLEBAR_V_PADDING - TITLEBAR_BORDER_THICKNESS;
575 scale_box(&box, output_scale);
576 render_rect(output->wlr_output, output_damage, &box, color);
577
578 // Padding below title
579 box.y = (y + TITLEBAR_V_PADDING + config->font_height) * output_scale;
580 render_rect(output->wlr_output, output_damage, &box, color);
581
582 // Filler between title and marks
583 box.width = inner_width * output_scale - title_width - marks_width;
584 if (box.width > 0) {
585 box.x = (x + TITLEBAR_H_PADDING) * output_scale + title_width;
586 box.y = (y + TITLEBAR_V_PADDING) * output_scale;
587 box.height = config->font_height * output_scale;
588 render_rect(output->wlr_output, output_damage, &box, color);
589 }
590
591 // Padding left of title
592 left_offset = (layout == L_TABBED) * TITLEBAR_BORDER_THICKNESS;
593 box.x = x + left_offset;
594 box.y = y + TITLEBAR_V_PADDING;
595 box.width = TITLEBAR_H_PADDING - left_offset;
596 box.height = config->font_height;
597 scale_box(&box, output_scale);
598 render_rect(output->wlr_output, output_damage, &box, color);
599
600 // Padding right of marks
601 right_offset = (layout == L_TABBED) * TITLEBAR_BORDER_THICKNESS;
602 box.x = x + width - TITLEBAR_H_PADDING;
603 box.y = y + TITLEBAR_V_PADDING;
604 box.width = TITLEBAR_H_PADDING - right_offset;
605 box.height = config->font_height;
606 scale_box(&box, output_scale);
607 render_rect(output->wlr_output, output_damage, &box, color);
608
609 if (connects_sides) {
610 // Left pixel in line with bottom bar
611 box.x = x;
612 box.y = y + container_titlebar_height() - TITLEBAR_BORDER_THICKNESS;
613 box.width = state->border_thickness * state->border_left;
614 box.height = TITLEBAR_BORDER_THICKNESS;
615 scale_box(&box, output_scale);
616 render_rect(output->wlr_output, output_damage, &box, color);
617
618 // Right pixel in line with bottom bar
619 box.x = x + width - state->border_thickness * state->border_right;
620 box.y = y + container_titlebar_height() - TITLEBAR_BORDER_THICKNESS;
621 box.width = state->border_thickness * state->border_right;
622 box.height = TITLEBAR_BORDER_THICKNESS;
623 scale_box(&box, output_scale);
624 render_rect(output->wlr_output, output_damage, &box, color);
625 }
626}
627
628/**
629 * Render the top border line for a view using "border pixel".
630 */
631static void render_top_border(struct sway_output *output,
632 pixman_region32_t *output_damage, struct sway_container *con,
633 struct border_colors *colors) {
634 struct sway_container_state *state = &con->current;
635 if (!state->border_top) {
636 return;
637 }
638 struct wlr_box box;
639 float color[4];
640 float output_scale = output->wlr_output->scale;
641
642 // Child border - top edge
643 memcpy(&color, colors->child_border, sizeof(float) * 4);
644 premultiply_alpha(color, con->alpha);
645 box.x = state->swayc_x;
646 box.y = state->swayc_y;
647 box.width = state->swayc_width;
648 box.height = state->border_thickness;
649 scale_box(&box, output_scale);
650 render_rect(output->wlr_output, output_damage, &box, color);
651}
652
653static void render_container(struct sway_output *output,
654 pixman_region32_t *damage, struct sway_container *con, bool parent_focused);
655
656/**
657 * Render a container's children using a L_HORIZ or L_VERT layout.
658 *
659 * Wrap child views in borders and leave child containers borderless because
660 * they'll apply their own borders to their children.
661 */
662static void render_container_simple(struct sway_output *output,
663 pixman_region32_t *damage, struct sway_container *con,
664 bool parent_focused) {
665 struct sway_seat *seat = input_manager_current_seat(input_manager);
666 struct sway_container *focus = seat_get_focus(seat);
667
668 for (int i = 0; i < con->current.children->length; ++i) {
669 struct sway_container *child = con->current.children->items[i];
670
671 if (child->type == C_VIEW) {
672 struct sway_view *view = child->sway_view;
673 struct border_colors *colors;
674 struct wlr_texture *title_texture;
675 struct wlr_texture *marks_texture;
676 struct sway_container_state *state = &child->current;
677
678 if (focus == child || parent_focused) {
679 colors = &config->border_colors.focused;
680 title_texture = child->title_focused;
681 marks_texture = view->marks_focused;
682 } else if (seat_get_focus_inactive(seat, con) == child) {
683 colors = &config->border_colors.focused_inactive;
684 title_texture = child->title_focused_inactive;
685 marks_texture = view->marks_focused_inactive;
686 } else {
687 colors = &config->border_colors.unfocused;
688 title_texture = child->title_unfocused;
689 marks_texture = view->marks_unfocused;
690 }
691
692 if (state->border == B_NORMAL) {
693 render_titlebar(output, damage, child, state->swayc_x,
694 state->swayc_y, state->swayc_width, colors,
695 title_texture, marks_texture);
696 } else {
697 render_top_border(output, damage, child, colors);
698 }
699 render_view(output, damage, child, colors);
700 } else {
701 render_container(output, damage, child,
702 parent_focused || focus == child);
703 }
704 }
705}
706
707/**
708 * Render a container's children using the L_TABBED layout.
709 */
710static void render_container_tabbed(struct sway_output *output,
711 pixman_region32_t *damage, struct sway_container *con,
712 bool parent_focused) {
713 if (!con->current.children->length) {
714 return;
715 }
716 struct sway_seat *seat = input_manager_current_seat(input_manager);
717 struct sway_container *focus = seat_get_focus(seat);
718 struct sway_container *current = seat_get_active_current_child(seat, con);
719 struct border_colors *current_colors = &config->border_colors.unfocused;
720 struct sway_container_state *pstate = &con->current;
721
722 // Render tabs
723 for (int i = 0; i < con->current.children->length; ++i) {
724 struct sway_container *child = con->current.children->items[i];
725 struct sway_view *view = child->type == C_VIEW ? child->sway_view : NULL;
726 struct sway_container_state *cstate = &child->current;
727 struct border_colors *colors;
728 struct wlr_texture *title_texture;
729 struct wlr_texture *marks_texture;
730
731 if (focus == child || parent_focused) {
732 colors = &config->border_colors.focused;
733 title_texture = child->title_focused;
734 marks_texture = view ? view->marks_focused : NULL;
735 } else if (child == current) {
736 colors = &config->border_colors.focused_inactive;
737 title_texture = child->title_focused_inactive;
738 marks_texture = view ? view->marks_focused_inactive : NULL;
739 } else {
740 colors = &config->border_colors.unfocused;
741 title_texture = child->title_unfocused;
742 marks_texture = view ? view->marks_unfocused : NULL;
743 }
744
745 int tab_width = pstate->swayc_width / pstate->children->length;
746 int x = pstate->swayc_x + tab_width * i;
747 // Make last tab use the remaining width of the parent
748 if (i == pstate->children->length - 1) {
749 tab_width = pstate->swayc_width - tab_width * i;
750 }
751
752 render_titlebar(output, damage, child, x, cstate->swayc_y, tab_width,
753 colors, title_texture, marks_texture);
754
755 if (child == current) {
756 current_colors = colors;
757 }
758 }
759
760 // Render surface and left/right/bottom borders
761 if (current) {
762 if (current->type == C_VIEW) {
763 render_view(output, damage, current, current_colors);
764 } else {
765 render_container(output, damage, current,
766 parent_focused || current == focus);
767 }
768 }
769}
770
771/**
772 * Render a container's children using the L_STACKED layout.
773 */
774static void render_container_stacked(struct sway_output *output,
775 pixman_region32_t *damage, struct sway_container *con,
776 bool parent_focused) {
777 if (!con->current.children->length) {
778 return;
779 }
780 struct sway_seat *seat = input_manager_current_seat(input_manager);
781 struct sway_container *focus = seat_get_focus(seat);
782 struct sway_container *current = seat_get_active_current_child(seat, con);
783 struct border_colors *current_colors = &config->border_colors.unfocused;
784 struct sway_container_state *pstate = &con->current;
785
786 // Render titles
787 for (int i = 0; i < con->current.children->length; ++i) {
788 struct sway_container *child = con->current.children->items[i];
789 struct sway_view *view = child->type == C_VIEW ? child->sway_view : NULL;
790 struct sway_container_state *cstate = &child->current;
791 struct border_colors *colors;
792 struct wlr_texture *title_texture;
793 struct wlr_texture *marks_texture;
794
795 if (focus == child || parent_focused) {
796 colors = &config->border_colors.focused;
797 title_texture = child->title_focused;
798 marks_texture = view ? view->marks_focused : NULL;
799 } else if (child == current) {
800 colors = &config->border_colors.focused_inactive;
801 title_texture = child->title_focused_inactive;
802 marks_texture = view ? view->marks_focused_inactive : NULL;
803 } else {
804 colors = &config->border_colors.unfocused;
805 title_texture = child->title_unfocused;
806 marks_texture = view ? view->marks_unfocused : NULL;
807 }
808
809 int y = pstate->swayc_y + container_titlebar_height() * i;
810 render_titlebar(output, damage, child, cstate->swayc_x, y,
811 cstate->swayc_width, colors, title_texture, marks_texture);
812
813 if (child == current) {
814 current_colors = colors;
815 }
816 }
817
818 // Render surface and left/right/bottom borders
819 if (current) {
820 if (current->type == C_VIEW) {
821 render_view(output, damage, current, current_colors);
822 } else {
823 render_container(output, damage, current,
824 parent_focused || current == focus);
825 }
826 }
827}
828
829static void render_container(struct sway_output *output,
830 pixman_region32_t *damage, struct sway_container *con,
831 bool parent_focused) {
832 switch (con->current.layout) {
833 case L_NONE:
834 case L_HORIZ:
835 case L_VERT:
836 render_container_simple(output, damage, con, parent_focused);
837 break;
838 case L_STACKED:
839 render_container_stacked(output, damage, con, parent_focused);
840 break;
841 case L_TABBED:
842 render_container_tabbed(output, damage, con, parent_focused);
843 break;
844 case L_FLOATING:
845 sway_assert(false, "Didn't expect to see floating here");
846 }
847}
848
849static void render_floating_container(struct sway_output *soutput,
850 pixman_region32_t *damage, struct sway_container *con) {
851 if (con->type == C_VIEW) {
852 struct sway_view *view = con->sway_view;
853 struct sway_seat *seat = input_manager_current_seat(input_manager);
854 struct sway_container *focus = seat_get_focus(seat);
855 struct border_colors *colors;
856 struct wlr_texture *title_texture;
857 struct wlr_texture *marks_texture;
858
859 if (focus == con) {
860 colors = &config->border_colors.focused;
861 title_texture = con->title_focused;
862 marks_texture = view->marks_focused;
863 } else {
864 colors = &config->border_colors.unfocused;
865 title_texture = con->title_unfocused;
866 marks_texture = view->marks_unfocused;
867 }
868
869 if (con->current.border == B_NORMAL) {
870 render_titlebar(soutput, damage, con, con->current.swayc_x,
871 con->current.swayc_y, con->current.swayc_width, colors,
872 title_texture, marks_texture);
873 } else if (con->current.border != B_NONE) {
874 render_top_border(soutput, damage, con, colors);
875 }
876 render_view(soutput, damage, con, colors);
877 } else {
878 render_container(soutput, damage, con, false);
879 }
880}
881
882static void render_floating(struct sway_output *soutput,
883 pixman_region32_t *damage) {
884 for (int i = 0; i < root_container.current.children->length; ++i) {
885 struct sway_container *output =
886 root_container.current.children->items[i];
887 for (int j = 0; j < output->current.children->length; ++j) {
888 struct sway_container *ws = output->current.children->items[j];
889 if (!workspace_is_visible(ws)) {
890 continue;
891 }
892 list_t *floating =
893 ws->current.ws_floating->current.children;
894 for (int k = 0; k < floating->length; ++k) {
895 struct sway_container *floater = floating->items[k];
896 render_floating_container(soutput, damage, floater);
897 }
898 }
899 }
900}
901
902static struct sway_container *output_get_active_workspace(
903 struct sway_output *output) {
904 struct sway_seat *seat = input_manager_current_seat(input_manager); 170 struct sway_seat *seat = input_manager_current_seat(input_manager);
905 struct sway_container *focus = 171 struct sway_container *focus =
906 seat_get_focus_inactive(seat, output->swayc); 172 seat_get_focus_inactive(seat, output->swayc);
@@ -915,117 +181,58 @@ static struct sway_container *output_get_active_workspace(
915 return workspace; 181 return workspace;
916} 182}
917 183
918static void render_output(struct sway_output *output, struct timespec *when, 184bool output_has_opaque_lockscreen(struct sway_output *output,
919 pixman_region32_t *damage) { 185 struct sway_seat *seat) {
920 struct wlr_output *wlr_output = output->wlr_output; 186 if (!seat->exclusive_client) {
921 187 return false;
922 struct wlr_renderer *renderer =
923 wlr_backend_get_renderer(wlr_output->backend);
924 if (!sway_assert(renderer != NULL,
925 "expected the output backend to have a renderer")) {
926 return;
927 }
928
929 wlr_renderer_begin(renderer, wlr_output->width, wlr_output->height);
930
931 bool damage_whole_before_swap = false;
932 if (!pixman_region32_not_empty(damage)) {
933 // Output isn't damaged but needs buffer swap
934 goto renderer_end;
935 }
936
937 const char *damage_debug = getenv("SWAY_DAMAGE_DEBUG");
938 if (damage_debug != NULL) {
939 if (strcmp(damage_debug, "highlight") == 0) {
940 wlr_renderer_clear(renderer, (float[]){1, 1, 0, 1});
941 damage_whole_before_swap = true;
942 } else if (strcmp(damage_debug, "rerender") == 0) {
943 int width, height;
944 wlr_output_transformed_resolution(wlr_output, &width, &height);
945 pixman_region32_union_rect(damage, damage, 0, 0, width, height);
946 }
947 } 188 }
948 189
949 struct sway_container *workspace = output_get_active_workspace(output); 190 struct wlr_layer_surface *wlr_layer_surface;
950 struct sway_view *fullscreen_view = workspace->current.ws_fullscreen; 191 wl_list_for_each(wlr_layer_surface, &server.layer_shell->surfaces, link) {
951 192 if (wlr_layer_surface->output != output->wlr_output) {
952 if (fullscreen_view) { 193 continue;
953 float clear_color[] = {0.0f, 0.0f, 0.0f, 1.0f};
954
955 int nrects;
956 pixman_box32_t *rects = pixman_region32_rectangles(damage, &nrects);
957 for (int i = 0; i < nrects; ++i) {
958 scissor_output(wlr_output, &rects[i]);
959 wlr_renderer_clear(renderer, clear_color);
960 } 194 }
961 195 struct wlr_surface *wlr_surface = wlr_layer_surface->surface;
962 // TODO: handle views smaller than the output 196 if (wlr_surface->resource->client != seat->exclusive_client) {
963 render_view_surfaces(fullscreen_view, output, damage, 1.0f); 197 continue;
964
965 if (fullscreen_view->type == SWAY_VIEW_XWAYLAND) {
966 render_unmanaged(output, damage,
967 &root_container.sway_root->xwayland_unmanaged);
968 } 198 }
969 } else { 199 struct sway_layer_surface *sway_layer_surface =
970 float clear_color[] = {0.25f, 0.25f, 0.25f, 1.0f}; 200 layer_from_wlr_layer_surface(wlr_layer_surface);
971 201 pixman_box32_t output_box = {
972 int nrects; 202 .x2 = output->swayc->current.swayc_width,
973 pixman_box32_t *rects = pixman_region32_rectangles(damage, &nrects); 203 .y2 = output->swayc->current.swayc_height,
974 for (int i = 0; i < nrects; ++i) { 204 };
975 scissor_output(wlr_output, &rects[i]); 205 pixman_region32_t surface_opaque_box;
976 wlr_renderer_clear(renderer, clear_color); 206 pixman_region32_init(&surface_opaque_box);
207 pixman_region32_copy(&surface_opaque_box, &wlr_surface->opaque_region);
208 pixman_region32_translate(&surface_opaque_box,
209 sway_layer_surface->geo.x, sway_layer_surface->geo.y);
210 bool contains = pixman_region32_contains_rectangle(&surface_opaque_box,
211 &output_box);
212 pixman_region32_fini(&surface_opaque_box);
213 if (contains) {
214 return true;
977 } 215 }
978
979 render_layer(output, damage,
980 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]);
981 render_layer(output, damage,
982 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]);
983
984 struct sway_seat *seat = input_manager_current_seat(input_manager);
985 struct sway_container *focus = seat_get_focus(seat);
986 render_container(output, damage, workspace, focus == workspace);
987 render_floating(output, damage);
988
989 render_unmanaged(output, damage,
990 &root_container.sway_root->xwayland_unmanaged);
991 render_layer(output, damage,
992 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]);
993 }
994 render_layer(output, damage,
995 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]);
996 render_drag_icons(output, damage, &root_container.sway_root->drag_icons);
997
998renderer_end:
999 if (root_container.sway_root->debug_tree) {
1000 wlr_render_texture(renderer, root_container.sway_root->debug_tree,
1001 wlr_output->transform_matrix, 0, 0, 1);
1002 }
1003
1004 if (damage_whole_before_swap || root_container.sway_root->debug_tree) {
1005 int width, height;
1006 wlr_output_transformed_resolution(wlr_output, &width, &height);
1007 pixman_region32_union_rect(damage, damage, 0, 0, width, height);
1008 } 216 }
1009 217 return false;
1010 wlr_renderer_scissor(renderer, NULL);
1011 wlr_renderer_end(renderer);
1012 if (!wlr_output_damage_swap_buffers(output->damage, when, damage)) {
1013 return;
1014 }
1015 output->last_frame = *when;
1016} 218}
1017 219
1018struct send_frame_done_data { 220struct send_frame_done_data {
1019 struct root_geometry root_geo; 221 struct root_geometry root_geo;
1020 struct sway_output *output; 222 struct sway_output *output;
1021 struct timespec *when; 223 struct timespec *when;
224 struct wl_client *exclusive_client;
1022}; 225};
1023 226
1024static void send_frame_done_iterator(struct wlr_surface *surface, 227static void send_frame_done_iterator(struct wlr_surface *surface,
1025 int sx, int sy, void *_data) { 228 int sx, int sy, void *_data) {
1026 struct send_frame_done_data *data = _data; 229 struct send_frame_done_data *data = _data;
230 if (data->exclusive_client &&
231 data->exclusive_client != surface->resource->client) {
232 return;
233 }
1027 234
1028 bool intersects = get_surface_box(&data->root_geo, data->output, surface, 235 bool intersects = output_get_surface_box(&data->root_geo, data->output, surface,
1029 sx, sy, NULL); 236 sx, sy, NULL);
1030 if (intersects) { 237 if (intersects) {
1031 wlr_surface_send_frame_done(surface, data->when); 238 wlr_surface_send_frame_done(surface, data->when);
@@ -1034,19 +241,19 @@ static void send_frame_done_iterator(struct wlr_surface *surface,
1034 241
1035static void send_frame_done_layer(struct send_frame_done_data *data, 242static void send_frame_done_layer(struct send_frame_done_data *data,
1036 struct wl_list *layer_surfaces) { 243 struct wl_list *layer_surfaces) {
1037 layer_for_each_surface(layer_surfaces, &data->root_geo, 244 output_layer_for_each_surface(layer_surfaces, &data->root_geo,
1038 send_frame_done_iterator, data); 245 send_frame_done_iterator, data);
1039} 246}
1040 247
1041static void send_frame_done_unmanaged(struct send_frame_done_data *data, 248static void send_frame_done_unmanaged(struct send_frame_done_data *data,
1042 struct wl_list *unmanaged) { 249 struct wl_list *unmanaged) {
1043 unmanaged_for_each_surface(unmanaged, data->output, &data->root_geo, 250 output_unmanaged_for_each_surface(unmanaged, data->output, &data->root_geo,
1044 send_frame_done_iterator, data); 251 send_frame_done_iterator, data);
1045} 252}
1046 253
1047static void send_frame_done_drag_icons(struct send_frame_done_data *data, 254static void send_frame_done_drag_icons(struct send_frame_done_data *data,
1048 struct wl_list *drag_icons) { 255 struct wl_list *drag_icons) {
1049 drag_icons_for_each_surface(drag_icons, data->output, &data->root_geo, 256 output_drag_icons_for_each_surface(drag_icons, data->output, &data->root_geo,
1050 send_frame_done_iterator, data); 257 send_frame_done_iterator, data);
1051} 258}
1052 259
@@ -1061,7 +268,7 @@ static void send_frame_done_container_iterator(struct sway_container *con,
1061 return; 268 return;
1062 } 269 }
1063 270
1064 output_view_for_each_surface(con->sway_view, &data->root_geo, 271 output_view_for_each_surface(con->sway_view, data->output, &data->root_geo,
1065 send_frame_done_iterator, data); 272 send_frame_done_iterator, data);
1066} 273}
1067 274
@@ -1072,9 +279,12 @@ static void send_frame_done_container(struct send_frame_done_data *data,
1072} 279}
1073 280
1074static void send_frame_done(struct sway_output *output, struct timespec *when) { 281static void send_frame_done(struct sway_output *output, struct timespec *when) {
282 struct sway_seat *seat = input_manager_current_seat(input_manager);
1075 struct send_frame_done_data data = { 283 struct send_frame_done_data data = {
1076 .output = output, 284 .output = output,
1077 .when = when, 285 .when = when,
286 .exclusive_client = output_has_opaque_lockscreen(output, seat) ?
287 seat->exclusive_client : NULL,
1078 }; 288 };
1079 289
1080 struct sway_container *workspace = output_get_active_workspace(output); 290 struct sway_container *workspace = output_get_active_workspace(output);
@@ -1125,7 +335,7 @@ static void damage_handle_frame(struct wl_listener *listener, void *data) {
1125 } 335 }
1126 336
1127 if (needs_swap) { 337 if (needs_swap) {
1128 render_output(output, &now, &damage); 338 output_render(output, &now, &damage);
1129 } 339 }
1130 340
1131 pixman_region32_fini(&damage); 341 pixman_region32_fini(&damage);
@@ -1152,7 +362,7 @@ static void damage_surface_iterator(struct wlr_surface *surface, int sx, int sy,
1152 bool whole = data->whole; 362 bool whole = data->whole;
1153 363
1154 struct wlr_box box; 364 struct wlr_box box;
1155 bool intersects = get_surface_box(&data->root_geo, data->output, surface, 365 bool intersects = output_get_surface_box(&data->root_geo, data->output, surface,
1156 sx, sy, &box); 366 sx, sy, &box);
1157 if (!intersects) { 367 if (!intersects) {
1158 return; 368 return;
@@ -1163,16 +373,22 @@ static void damage_surface_iterator(struct wlr_surface *surface, int sx, int sy,
1163 int center_x = box.x + box.width/2; 373 int center_x = box.x + box.width/2;
1164 int center_y = box.y + box.height/2; 374 int center_y = box.y + box.height/2;
1165 375
1166 if (pixman_region32_not_empty(&surface->current->surface_damage)) { 376 if (pixman_region32_not_empty(&surface->buffer_damage)) {
377 enum wl_output_transform transform =
378 wlr_output_transform_invert(surface->current.transform);
379
1167 pixman_region32_t damage; 380 pixman_region32_t damage;
1168 pixman_region32_init(&damage); 381 pixman_region32_init(&damage);
1169 pixman_region32_copy(&damage, &surface->current->surface_damage); 382 pixman_region32_copy(&damage, &surface->buffer_damage);
1170 wlr_region_scale(&damage, &damage, output->wlr_output->scale); 383 wlr_region_transform(&damage, &damage, transform,
1171 if (ceil(output->wlr_output->scale) > surface->current->scale) { 384 surface->current.buffer_width, surface->current.buffer_height);
385 wlr_region_scale(&damage, &damage,
386 output->wlr_output->scale / (float)surface->current.scale);
387 if (ceil(output->wlr_output->scale) > surface->current.scale) {
1172 // When scaling up a surface, it'll become blurry so we need to 388 // When scaling up a surface, it'll become blurry so we need to
1173 // expand the damage region 389 // expand the damage region
1174 wlr_region_expand(&damage, &damage, 390 wlr_region_expand(&damage, &damage,
1175 ceil(output->wlr_output->scale) - surface->current->scale); 391 ceil(output->wlr_output->scale) - surface->current.scale);
1176 } 392 }
1177 pixman_region32_translate(&damage, box.x, box.y); 393 pixman_region32_translate(&damage, box.x, box.y);
1178 wlr_region_rotated_bounds(&damage, &damage, rotation, 394 wlr_region_rotated_bounds(&damage, &damage, rotation,
@@ -1196,7 +412,7 @@ void output_damage_surface(struct sway_output *output, double ox, double oy,
1196 .whole = whole, 412 .whole = whole,
1197 }; 413 };
1198 414
1199 surface_for_each_surface(surface, ox, oy, &data.root_geo, 415 output_surface_for_each_surface(surface, ox, oy, &data.root_geo,
1200 damage_surface_iterator, &data); 416 damage_surface_iterator, &data);
1201} 417}
1202 418
@@ -1215,7 +431,7 @@ static void output_damage_view(struct sway_output *output,
1215 .whole = whole, 431 .whole = whole,
1216 }; 432 };
1217 433
1218 output_view_for_each_surface(view, &data.root_geo, 434 output_view_for_each_surface(view, output, &data.root_geo,
1219 damage_surface_iterator, &data); 435 damage_surface_iterator, &data);
1220} 436}
1221 437
@@ -1247,11 +463,12 @@ static void output_damage_whole_container_iterator(struct sway_container *con,
1247 463
1248void output_damage_whole_container(struct sway_output *output, 464void output_damage_whole_container(struct sway_output *output,
1249 struct sway_container *con) { 465 struct sway_container *con) {
466 // Pad the box by 1px, because the width is a double and might be a fraction
1250 struct wlr_box box = { 467 struct wlr_box box = {
1251 .x = con->current.swayc_x - output->wlr_output->lx, 468 .x = con->current.swayc_x - output->wlr_output->lx - 1,
1252 .y = con->current.swayc_y - output->wlr_output->ly, 469 .y = con->current.swayc_y - output->wlr_output->ly - 1,
1253 .width = con->current.swayc_width, 470 .width = con->current.swayc_width + 2,
1254 .height = con->current.swayc_height, 471 .height = con->current.swayc_height + 2,
1255 }; 472 };
1256 scale_box(&box, output->wlr_output->scale); 473 scale_box(&box, output->wlr_output->scale);
1257 wlr_output_damage_add_box(output->damage, &box); 474 wlr_output_damage_add_box(output->damage, &box);
@@ -1276,19 +493,21 @@ static void handle_destroy(struct wl_listener *listener, void *data) {
1276 output->wlr_output->data = NULL; 493 output->wlr_output->data = NULL;
1277 free(output); 494 free(output);
1278 495
1279 arrange_and_commit(&root_container); 496 arrange_windows(&root_container);
1280} 497}
1281 498
1282static void handle_mode(struct wl_listener *listener, void *data) { 499static void handle_mode(struct wl_listener *listener, void *data) {
1283 struct sway_output *output = wl_container_of(listener, output, mode); 500 struct sway_output *output = wl_container_of(listener, output, mode);
1284 arrange_layers(output); 501 arrange_layers(output);
1285 arrange_and_commit(output->swayc); 502 arrange_windows(output->swayc);
503 transaction_commit_dirty();
1286} 504}
1287 505
1288static void handle_transform(struct wl_listener *listener, void *data) { 506static void handle_transform(struct wl_listener *listener, void *data) {
1289 struct sway_output *output = wl_container_of(listener, output, transform); 507 struct sway_output *output = wl_container_of(listener, output, transform);
1290 arrange_layers(output); 508 arrange_layers(output);
1291 arrange_and_commit(output->swayc); 509 arrange_windows(output->swayc);
510 transaction_commit_dirty();
1292} 511}
1293 512
1294static void handle_scale_iterator(struct sway_container *view, void *data) { 513static void handle_scale_iterator(struct sway_container *view, void *data) {
@@ -1299,7 +518,8 @@ static void handle_scale(struct wl_listener *listener, void *data) {
1299 struct sway_output *output = wl_container_of(listener, output, scale); 518 struct sway_output *output = wl_container_of(listener, output, scale);
1300 arrange_layers(output); 519 arrange_layers(output);
1301 container_descendants(output->swayc, C_VIEW, handle_scale_iterator, NULL); 520 container_descendants(output->swayc, C_VIEW, handle_scale_iterator, NULL);
1302 arrange_and_commit(output->swayc); 521 arrange_windows(output->swayc);
522 transaction_commit_dirty();
1303} 523}
1304 524
1305struct sway_output *output_from_wlr_output(struct wlr_output *wlr_output) { 525struct sway_output *output_from_wlr_output(struct wlr_output *wlr_output) {
@@ -1309,7 +529,7 @@ struct sway_output *output_from_wlr_output(struct wlr_output *wlr_output) {
1309void handle_new_output(struct wl_listener *listener, void *data) { 529void handle_new_output(struct wl_listener *listener, void *data) {
1310 struct sway_server *server = wl_container_of(listener, server, new_output); 530 struct sway_server *server = wl_container_of(listener, server, new_output);
1311 struct wlr_output *wlr_output = data; 531 struct wlr_output *wlr_output = data;
1312 wlr_log(L_DEBUG, "New output %p: %s", wlr_output, wlr_output->name); 532 wlr_log(WLR_DEBUG, "New output %p: %s", wlr_output, wlr_output->name);
1313 533
1314 struct sway_output *output = calloc(1, sizeof(struct sway_output)); 534 struct sway_output *output = calloc(1, sizeof(struct sway_output));
1315 if (!output) { 535 if (!output) {
@@ -1368,5 +588,6 @@ void output_enable(struct sway_output *output) {
1368 output->damage_destroy.notify = damage_handle_destroy; 588 output->damage_destroy.notify = damage_handle_destroy;
1369 589
1370 arrange_layers(output); 590 arrange_layers(output);
1371 arrange_and_commit(&root_container); 591 arrange_windows(&root_container);
592 transaction_commit_dirty();
1372} 593}
diff --git a/sway/desktop/render.c b/sway/desktop/render.c
new file mode 100644
index 00000000..7da54594
--- /dev/null
+++ b/sway/desktop/render.c
@@ -0,0 +1,919 @@
1#define _POSIX_C_SOURCE 200809L
2#include <assert.h>
3#include <stdlib.h>
4#include <strings.h>
5#include <time.h>
6#include <wayland-server.h>
7#include <wlr/render/wlr_renderer.h>
8#include <wlr/types/wlr_box.h>
9#include <wlr/types/wlr_buffer.h>
10#include <wlr/types/wlr_matrix.h>
11#include <wlr/types/wlr_output_damage.h>
12#include <wlr/types/wlr_output_layout.h>
13#include <wlr/types/wlr_output.h>
14#include <wlr/types/wlr_surface.h>
15#include <wlr/util/region.h>
16#include "log.h"
17#include "sway/config.h"
18#include "sway/debug.h"
19#include "sway/input/input-manager.h"
20#include "sway/input/seat.h"
21#include "sway/layers.h"
22#include "sway/output.h"
23#include "sway/server.h"
24#include "sway/tree/arrange.h"
25#include "sway/tree/container.h"
26#include "sway/tree/layout.h"
27#include "sway/tree/view.h"
28#include "sway/tree/workspace.h"
29
30struct render_data {
31 struct root_geometry root_geo;
32 struct sway_output *output;
33 pixman_region32_t *damage;
34 struct sway_view *view;
35 float alpha;
36};
37
38static void scale_box(struct wlr_box *box, float scale) {
39 box->x *= scale;
40 box->y *= scale;
41 box->width *= scale;
42 box->height *= scale;
43}
44
45static void scissor_output(struct wlr_output *wlr_output,
46 pixman_box32_t *rect) {
47 struct wlr_renderer *renderer = wlr_backend_get_renderer(wlr_output->backend);
48 assert(renderer);
49
50 struct wlr_box box = {
51 .x = rect->x1,
52 .y = rect->y1,
53 .width = rect->x2 - rect->x1,
54 .height = rect->y2 - rect->y1,
55 };
56
57 int ow, oh;
58 wlr_output_transformed_resolution(wlr_output, &ow, &oh);
59
60 enum wl_output_transform transform =
61 wlr_output_transform_invert(wlr_output->transform);
62 wlr_box_transform(&box, transform, ow, oh, &box);
63
64 wlr_renderer_scissor(renderer, &box);
65}
66
67static void render_texture(struct wlr_output *wlr_output,
68 pixman_region32_t *output_damage, struct wlr_texture *texture,
69 const struct wlr_box *box, const float matrix[static 9], float alpha) {
70 struct wlr_renderer *renderer =
71 wlr_backend_get_renderer(wlr_output->backend);
72
73 pixman_region32_t damage;
74 pixman_region32_init(&damage);
75 pixman_region32_union_rect(&damage, &damage, box->x, box->y,
76 box->width, box->height);
77 pixman_region32_intersect(&damage, &damage, output_damage);
78 bool damaged = pixman_region32_not_empty(&damage);
79 if (!damaged) {
80 goto damage_finish;
81 }
82
83 int nrects;
84 pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects);
85 for (int i = 0; i < nrects; ++i) {
86 scissor_output(wlr_output, &rects[i]);
87 wlr_render_texture_with_matrix(renderer, texture, matrix, alpha);
88 }
89
90damage_finish:
91 pixman_region32_fini(&damage);
92}
93
94static void render_surface_iterator(struct wlr_surface *surface, int sx, int sy,
95 void *_data) {
96 struct render_data *data = _data;
97 struct wlr_output *wlr_output = data->output->wlr_output;
98 float rotation = data->root_geo.rotation;
99 pixman_region32_t *output_damage = data->damage;
100 float alpha = data->alpha;
101
102 struct wlr_texture *texture = wlr_surface_get_texture(surface);
103 if (!texture) {
104 return;
105 }
106
107 struct wlr_box box;
108 bool intersects = output_get_surface_box(&data->root_geo, data->output,
109 surface, sx, sy, &box);
110 if (!intersects) {
111 return;
112 }
113
114 scale_box(&box, wlr_output->scale);
115
116 float matrix[9];
117 enum wl_output_transform transform =
118 wlr_output_transform_invert(surface->current.transform);
119 wlr_matrix_project_box(matrix, &box, transform, rotation,
120 wlr_output->transform_matrix);
121
122 render_texture(wlr_output, output_damage, texture, &box, matrix, alpha);
123}
124
125static void render_layer(struct sway_output *output,
126 pixman_region32_t *damage, struct wl_list *layer_surfaces) {
127 struct render_data data = {
128 .output = output,
129 .damage = damage,
130 .alpha = 1.0f,
131 };
132 output_layer_for_each_surface(layer_surfaces, &data.root_geo,
133 render_surface_iterator, &data);
134}
135
136static void render_unmanaged(struct sway_output *output,
137 pixman_region32_t *damage, struct wl_list *unmanaged) {
138 struct render_data data = {
139 .output = output,
140 .damage = damage,
141 .alpha = 1.0f,
142 };
143 output_unmanaged_for_each_surface(unmanaged, output, &data.root_geo,
144 render_surface_iterator, &data);
145}
146
147static void render_drag_icons(struct sway_output *output,
148 pixman_region32_t *damage, struct wl_list *drag_icons) {
149 struct render_data data = {
150 .output = output,
151 .damage = damage,
152 .alpha = 1.0f,
153 };
154 output_drag_icons_for_each_surface(drag_icons, output, &data.root_geo,
155 render_surface_iterator, &data);
156}
157
158static void render_rect(struct wlr_output *wlr_output,
159 pixman_region32_t *output_damage, const struct wlr_box *_box,
160 float color[static 4]) {
161 struct wlr_renderer *renderer =
162 wlr_backend_get_renderer(wlr_output->backend);
163
164 struct wlr_box box;
165 memcpy(&box, _box, sizeof(struct wlr_box));
166 box.x -= wlr_output->lx * wlr_output->scale;
167 box.y -= wlr_output->ly * wlr_output->scale;
168
169 pixman_region32_t damage;
170 pixman_region32_init(&damage);
171 pixman_region32_union_rect(&damage, &damage, box.x, box.y,
172 box.width, box.height);
173 pixman_region32_intersect(&damage, &damage, output_damage);
174 bool damaged = pixman_region32_not_empty(&damage);
175 if (!damaged) {
176 goto damage_finish;
177 }
178
179 int nrects;
180 pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects);
181 for (int i = 0; i < nrects; ++i) {
182 scissor_output(wlr_output, &rects[i]);
183 wlr_render_rect(renderer, &box, color,
184 wlr_output->transform_matrix);
185 }
186
187damage_finish:
188 pixman_region32_fini(&damage);
189}
190
191static void premultiply_alpha(float color[4], float opacity) {
192 color[3] *= opacity;
193 color[0] *= color[3];
194 color[1] *= color[3];
195 color[2] *= color[3];
196}
197
198static void render_view_surfaces(struct sway_view *view,
199 struct sway_output *output, pixman_region32_t *damage, float alpha) {
200 struct render_data data = {
201 .output = output,
202 .damage = damage,
203 .view = view,
204 .alpha = alpha,
205 };
206 output_view_for_each_surface(view, output, &data.root_geo,
207 render_surface_iterator, &data);
208}
209
210static void render_saved_view(struct sway_view *view,
211 struct sway_output *output, pixman_region32_t *damage, float alpha) {
212 struct wlr_output *wlr_output = output->wlr_output;
213
214 int width, height;
215 struct wlr_texture *texture =
216 transaction_get_saved_texture(view, &width, &height);
217 if (!texture) {
218 return;
219 }
220 struct wlr_box box = {
221 .x = view->swayc->current.view_x - output->swayc->current.swayc_x,
222 .y = view->swayc->current.view_y - output->swayc->current.swayc_y,
223 .width = width,
224 .height = height,
225 };
226
227 struct wlr_box output_box = {
228 .width = output->swayc->current.swayc_width,
229 .height = output->swayc->current.swayc_height,
230 };
231
232 struct wlr_box intersection;
233 bool intersects = wlr_box_intersection(&output_box, &box, &intersection);
234 if (!intersects) {
235 return;
236 }
237
238 scale_box(&box, wlr_output->scale);
239
240 float matrix[9];
241 wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0,
242 wlr_output->transform_matrix);
243
244 render_texture(wlr_output, damage, texture, &box, matrix, alpha);
245}
246
247/**
248 * Render a view's surface and left/bottom/right borders.
249 */
250static void render_view(struct sway_output *output, pixman_region32_t *damage,
251 struct sway_container *con, struct border_colors *colors) {
252 struct sway_view *view = con->sway_view;
253 if (view->swayc->instructions->length) {
254 render_saved_view(view, output, damage, view->swayc->alpha);
255 } else {
256 render_view_surfaces(view, output, damage, view->swayc->alpha);
257 }
258
259 if (view->using_csd) {
260 return;
261 }
262
263 struct wlr_box box;
264 float output_scale = output->wlr_output->scale;
265 float color[4];
266 struct sway_container_state *state = &con->current;
267
268 if (state->border != B_NONE) {
269 if (state->border_left) {
270 memcpy(&color, colors->child_border, sizeof(float) * 4);
271 premultiply_alpha(color, con->alpha);
272 box.x = state->swayc_x;
273 box.y = state->view_y;
274 box.width = state->border_thickness;
275 box.height = state->view_height;
276 scale_box(&box, output_scale);
277 render_rect(output->wlr_output, damage, &box, color);
278 }
279
280 if (state->border_right) {
281 if (state->parent->current.children->length == 1
282 && state->parent->current.layout == L_HORIZ) {
283 memcpy(&color, colors->indicator, sizeof(float) * 4);
284 } else {
285 memcpy(&color, colors->child_border, sizeof(float) * 4);
286 }
287 premultiply_alpha(color, con->alpha);
288 box.x = state->view_x + state->view_width;
289 box.y = state->view_y;
290 box.width = state->border_thickness;
291 box.height = state->view_height;
292 scale_box(&box, output_scale);
293 render_rect(output->wlr_output, damage, &box, color);
294 }
295
296 if (state->border_bottom) {
297 if (state->parent->current.children->length == 1
298 && con->current.parent->current.layout == L_VERT) {
299 memcpy(&color, colors->indicator, sizeof(float) * 4);
300 } else {
301 memcpy(&color, colors->child_border, sizeof(float) * 4);
302 }
303 premultiply_alpha(color, con->alpha);
304 box.x = state->swayc_x;
305 box.y = state->view_y + state->view_height;
306 box.width = state->swayc_width;
307 box.height = state->border_thickness;
308 scale_box(&box, output_scale);
309 render_rect(output->wlr_output, damage, &box, color);
310 }
311 }
312}
313
314/**
315 * Render a titlebar.
316 *
317 * Care must be taken not to render over the same pixel multiple times,
318 * otherwise the colors will be incorrect when using opacity.
319 *
320 * The height is: 1px border, 3px padding, font height, 3px padding, 1px border
321 * The left side for L_TABBED is: 1px border, 2px padding, title
322 * The left side for other layouts is: 3px padding, title
323 */
324static void render_titlebar(struct sway_output *output,
325 pixman_region32_t *output_damage, struct sway_container *con,
326 int x, int y, int width,
327 struct border_colors *colors, struct wlr_texture *title_texture,
328 struct wlr_texture *marks_texture) {
329 struct wlr_box box;
330 float color[4];
331 struct sway_container_state *state = &con->current;
332 float output_scale = output->wlr_output->scale;
333 enum sway_container_layout layout = state->parent->current.layout;
334 list_t *children = state->parent->current.children;
335 bool is_last_child = children->items[children->length - 1] == con;
336 double output_x = output->swayc->current.swayc_x;
337 double output_y = output->swayc->current.swayc_y;
338
339 // Single pixel bar above title
340 memcpy(&color, colors->border, sizeof(float) * 4);
341 premultiply_alpha(color, con->alpha);
342 box.x = x;
343 box.y = y;
344 box.width = width;
345 box.height = TITLEBAR_BORDER_THICKNESS;
346 scale_box(&box, output_scale);
347 render_rect(output->wlr_output, output_damage, &box, color);
348
349 // Single pixel bar below title
350 size_t left_offset = 0, right_offset = 0;
351 bool connects_sides = false;
352 if (layout == L_HORIZ || layout == L_VERT ||
353 (layout == L_STACKED && is_last_child)) {
354 if (con->type == C_VIEW) {
355 left_offset = state->border_left * state->border_thickness;
356 right_offset = state->border_right * state->border_thickness;
357 connects_sides = true;
358 }
359 }
360 box.x = x + left_offset;
361 box.y = y + container_titlebar_height() - TITLEBAR_BORDER_THICKNESS;
362 box.width = width - left_offset - right_offset;
363 box.height = TITLEBAR_BORDER_THICKNESS;
364 scale_box(&box, output_scale);
365 render_rect(output->wlr_output, output_damage, &box, color);
366
367 if (layout == L_TABBED) {
368 // Single pixel left edge
369 box.x = x;
370 box.y = y + TITLEBAR_BORDER_THICKNESS;
371 box.width = TITLEBAR_BORDER_THICKNESS;
372 box.height =
373 container_titlebar_height() - TITLEBAR_BORDER_THICKNESS * 2;
374 scale_box(&box, output_scale);
375 render_rect(output->wlr_output, output_damage, &box, color);
376
377 // Single pixel right edge
378 box.x = (x + width - TITLEBAR_BORDER_THICKNESS) * output_scale;
379 render_rect(output->wlr_output, output_damage, &box, color);
380 }
381
382 size_t inner_width = width - TITLEBAR_H_PADDING * 2;
383
384 // Marks
385 size_t marks_ob_width = 0; // output-buffer-local
386 if (config->show_marks && marks_texture) {
387 struct wlr_box texture_box;
388 wlr_texture_get_size(marks_texture,
389 &texture_box.width, &texture_box.height);
390 texture_box.x = (x - output_x + width - TITLEBAR_H_PADDING)
391 * output_scale - texture_box.width;
392 texture_box.y = (y - output_y + TITLEBAR_V_PADDING) * output_scale;
393
394 float matrix[9];
395 wlr_matrix_project_box(matrix, &texture_box,
396 WL_OUTPUT_TRANSFORM_NORMAL,
397 0.0, output->wlr_output->transform_matrix);
398
399 if (inner_width * output_scale < texture_box.width) {
400 texture_box.width = inner_width * output_scale;
401 }
402 render_texture(output->wlr_output, output_damage, marks_texture,
403 &texture_box, matrix, con->alpha);
404 marks_ob_width = texture_box.width;
405
406 // Gap between the marks and bottom padding, for when the marks texture
407 // height is smaller than the config's font height
408 memcpy(&color, colors->background, sizeof(float) * 4);
409 premultiply_alpha(color, con->alpha);
410 box.x = texture_box.x;
411 box.y = texture_box.y + texture_box.height;
412 box.width = texture_box.width;
413 box.height = config->font_height * output_scale - texture_box.height;
414 if (box.height > 0) {
415 render_rect(output->wlr_output, output_damage, &box, color);
416 }
417 }
418
419 // Title text
420 size_t title_ob_width = 0; // output-buffer-local
421 if (title_texture) {
422 struct wlr_box texture_box;
423 wlr_texture_get_size(title_texture,
424 &texture_box.width, &texture_box.height);
425 texture_box.x = (x - output_x + TITLEBAR_H_PADDING) * output_scale;
426 texture_box.y = (y - output_y + TITLEBAR_V_PADDING) * output_scale;
427
428 float matrix[9];
429 wlr_matrix_project_box(matrix, &texture_box,
430 WL_OUTPUT_TRANSFORM_NORMAL,
431 0.0, output->wlr_output->transform_matrix);
432
433 if (inner_width * output_scale - marks_ob_width < texture_box.width) {
434 texture_box.width = inner_width * output_scale - marks_ob_width;
435 }
436 render_texture(output->wlr_output, output_damage, title_texture,
437 &texture_box, matrix, con->alpha);
438 title_ob_width = texture_box.width;
439
440 // Gap between the title and bottom padding, for when the title texture
441 // height is smaller than the config's font height
442 memcpy(&color, colors->background, sizeof(float) * 4);
443 premultiply_alpha(color, con->alpha);
444 box.x = texture_box.x;
445 box.y = texture_box.y + texture_box.height;
446 box.width = texture_box.width;
447 box.height = config->font_height * output_scale - texture_box.height;
448 if (box.height > 0) {
449 render_rect(output->wlr_output, output_damage, &box, color);
450 }
451 }
452
453 // Padding above title
454 memcpy(&color, colors->background, sizeof(float) * 4);
455 premultiply_alpha(color, con->alpha);
456 box.x = x + (layout == L_TABBED) * TITLEBAR_BORDER_THICKNESS;
457 box.y = y + TITLEBAR_BORDER_THICKNESS;
458 box.width = width - (layout == L_TABBED) * TITLEBAR_BORDER_THICKNESS * 2;
459 box.height = TITLEBAR_V_PADDING - TITLEBAR_BORDER_THICKNESS;
460 scale_box(&box, output_scale);
461 render_rect(output->wlr_output, output_damage, &box, color);
462
463 // Padding below title
464 box.y = (y + TITLEBAR_V_PADDING + config->font_height) * output_scale;
465 render_rect(output->wlr_output, output_damage, &box, color);
466
467 // Filler between title and marks
468 box.width = inner_width * output_scale - title_ob_width - marks_ob_width;
469 if (box.width > 0) {
470 box.x = (x + TITLEBAR_H_PADDING) * output_scale + title_ob_width;
471 box.y = (y + TITLEBAR_V_PADDING) * output_scale;
472 box.height = config->font_height * output_scale;
473 render_rect(output->wlr_output, output_damage, &box, color);
474 }
475
476 // Padding left of title
477 left_offset = (layout == L_TABBED) * TITLEBAR_BORDER_THICKNESS;
478 box.x = x + left_offset;
479 box.y = y + TITLEBAR_V_PADDING;
480 box.width = TITLEBAR_H_PADDING - left_offset;
481 box.height = config->font_height;
482 scale_box(&box, output_scale);
483 render_rect(output->wlr_output, output_damage, &box, color);
484
485 // Padding right of marks
486 right_offset = (layout == L_TABBED) * TITLEBAR_BORDER_THICKNESS;
487 box.x = x + width - TITLEBAR_H_PADDING;
488 box.y = y + TITLEBAR_V_PADDING;
489 box.width = TITLEBAR_H_PADDING - right_offset;
490 box.height = config->font_height;
491 scale_box(&box, output_scale);
492 render_rect(output->wlr_output, output_damage, &box, color);
493
494 if (connects_sides) {
495 // Left pixel in line with bottom bar
496 box.x = x;
497 box.y = y + container_titlebar_height() - TITLEBAR_BORDER_THICKNESS;
498 box.width = state->border_thickness * state->border_left;
499 box.height = TITLEBAR_BORDER_THICKNESS;
500 scale_box(&box, output_scale);
501 render_rect(output->wlr_output, output_damage, &box, color);
502
503 // Right pixel in line with bottom bar
504 box.x = x + width - state->border_thickness * state->border_right;
505 box.y = y + container_titlebar_height() - TITLEBAR_BORDER_THICKNESS;
506 box.width = state->border_thickness * state->border_right;
507 box.height = TITLEBAR_BORDER_THICKNESS;
508 scale_box(&box, output_scale);
509 render_rect(output->wlr_output, output_damage, &box, color);
510 }
511}
512
513/**
514 * Render the top border line for a view using "border pixel".
515 */
516static void render_top_border(struct sway_output *output,
517 pixman_region32_t *output_damage, struct sway_container *con,
518 struct border_colors *colors) {
519 struct sway_container_state *state = &con->current;
520 if (!state->border_top) {
521 return;
522 }
523 struct wlr_box box;
524 float color[4];
525 float output_scale = output->wlr_output->scale;
526
527 // Child border - top edge
528 memcpy(&color, colors->child_border, sizeof(float) * 4);
529 premultiply_alpha(color, con->alpha);
530 box.x = state->swayc_x;
531 box.y = state->swayc_y;
532 box.width = state->swayc_width;
533 box.height = state->border_thickness;
534 scale_box(&box, output_scale);
535 render_rect(output->wlr_output, output_damage, &box, color);
536}
537
538static void render_container(struct sway_output *output,
539 pixman_region32_t *damage, struct sway_container *con, bool parent_focused);
540
541/**
542 * Render a container's children using a L_HORIZ or L_VERT layout.
543 *
544 * Wrap child views in borders and leave child containers borderless because
545 * they'll apply their own borders to their children.
546 */
547static void render_container_simple(struct sway_output *output,
548 pixman_region32_t *damage, struct sway_container *con,
549 bool parent_focused) {
550 for (int i = 0; i < con->current.children->length; ++i) {
551 struct sway_container *child = con->current.children->items[i];
552
553 if (child->type == C_VIEW) {
554 struct sway_view *view = child->sway_view;
555 struct border_colors *colors;
556 struct wlr_texture *title_texture;
557 struct wlr_texture *marks_texture;
558 struct sway_container_state *state = &child->current;
559
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) {
565 colors = &config->border_colors.focused;
566 title_texture = child->title_focused;
567 marks_texture = view->marks_focused;
568 } else if (con->current.focused_inactive_child == child) {
569 colors = &config->border_colors.focused_inactive;
570 title_texture = child->title_focused_inactive;
571 marks_texture = view->marks_focused_inactive;
572 } else {
573 colors = &config->border_colors.unfocused;
574 title_texture = child->title_unfocused;
575 marks_texture = view->marks_unfocused;
576 }
577
578 if (!view->using_csd) {
579 if (state->border == B_NORMAL) {
580 render_titlebar(output, damage, child, state->swayc_x,
581 state->swayc_y, state->swayc_width, colors,
582 title_texture, marks_texture);
583 } else {
584 render_top_border(output, damage, child, colors);
585 }
586 }
587 render_view(output, damage, child, colors);
588 } else {
589 render_container(output, damage, child,
590 parent_focused || child->current.focused);
591 }
592 }
593}
594
595/**
596 * Render a container's children using the L_TABBED layout.
597 */
598static void render_container_tabbed(struct sway_output *output,
599 pixman_region32_t *damage, struct sway_container *con,
600 bool parent_focused) {
601 if (!con->current.children->length) {
602 return;
603 }
604 struct sway_container_state *pstate = &con->current;
605 struct sway_container *current = pstate->focused_inactive_child;
606 struct border_colors *current_colors = &config->border_colors.unfocused;
607
608 double width_gap_adjustment = 2 * pstate->current_gaps;
609 int tab_width =
610 (pstate->swayc_width - width_gap_adjustment) / pstate->children->length;
611
612 // Render tabs
613 for (int i = 0; i < pstate->children->length; ++i) {
614 struct sway_container *child = pstate->children->items[i];
615 struct sway_view *view = child->type == C_VIEW ? child->sway_view : NULL;
616 struct sway_container_state *cstate = &child->current;
617 struct border_colors *colors;
618 struct wlr_texture *title_texture;
619 struct wlr_texture *marks_texture;
620 bool urgent = view ?
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) {
628 colors = &config->border_colors.focused;
629 title_texture = child->title_focused;
630 marks_texture = view ? view->marks_focused : NULL;
631 } else if (child == pstate->focused_inactive_child) {
632 colors = &config->border_colors.focused_inactive;
633 title_texture = child->title_focused_inactive;
634 marks_texture = view ? view->marks_focused_inactive : NULL;
635 } else {
636 colors = &config->border_colors.unfocused;
637 title_texture = child->title_unfocused;
638 marks_texture = view ? view->marks_unfocused : NULL;
639 }
640
641 int x = cstate->swayc_x + tab_width * i;
642
643 // Make last tab use the remaining width of the parent
644 if (i == pstate->children->length - 1) {
645 tab_width =
646 pstate->swayc_width - width_gap_adjustment - tab_width * i;
647 }
648
649 render_titlebar(output, damage, child, x, cstate->swayc_y, tab_width,
650 colors, title_texture, marks_texture);
651
652 if (child == current) {
653 current_colors = colors;
654 }
655 }
656
657 // Render surface and left/right/bottom borders
658 if (current->type == C_VIEW) {
659 render_view(output, damage, current, current_colors);
660 } else {
661 render_container(output, damage, current,
662 parent_focused || current->current.focused);
663 }
664}
665
666/**
667 * Render a container's children using the L_STACKED layout.
668 */
669static void render_container_stacked(struct sway_output *output,
670 pixman_region32_t *damage, struct sway_container *con,
671 bool parent_focused) {
672 if (!con->current.children->length) {
673 return;
674 }
675 struct sway_container_state *pstate = &con->current;
676 struct sway_container *current = pstate->focused_inactive_child;
677 struct border_colors *current_colors = &config->border_colors.unfocused;
678
679 size_t titlebar_height = container_titlebar_height();
680
681 // Render titles
682 for (int i = 0; i < pstate->children->length; ++i) {
683 struct sway_container *child = pstate->children->items[i];
684 struct sway_view *view = child->type == C_VIEW ? child->sway_view : NULL;
685 struct sway_container_state *cstate = &child->current;
686 struct border_colors *colors;
687 struct wlr_texture *title_texture;
688 struct wlr_texture *marks_texture;
689 bool urgent = view ?
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) {
697 colors = &config->border_colors.focused;
698 title_texture = child->title_focused;
699 marks_texture = view ? view->marks_focused : NULL;
700 } else if (child == pstate->focused_inactive_child) {
701 colors = &config->border_colors.focused_inactive;
702 title_texture = child->title_focused_inactive;
703 marks_texture = view ? view->marks_focused_inactive : NULL;
704 } else {
705 colors = &config->border_colors.unfocused;
706 title_texture = child->title_unfocused;
707 marks_texture = view ? view->marks_unfocused : NULL;
708 }
709
710 int y = cstate->swayc_y + titlebar_height * i;
711 render_titlebar(output, damage, child, cstate->swayc_x, y,
712 cstate->swayc_width, colors, title_texture, marks_texture);
713
714 if (child == current) {
715 current_colors = colors;
716 }
717 }
718
719 // Render surface and left/right/bottom borders
720 if (current->type == C_VIEW) {
721 render_view(output, damage, current, current_colors);
722 } else {
723 render_container(output, damage, current,
724 parent_focused || current->current.focused);
725 }
726}
727
728static void render_container(struct sway_output *output,
729 pixman_region32_t *damage, struct sway_container *con,
730 bool parent_focused) {
731 switch (con->current.layout) {
732 case L_NONE:
733 case L_HORIZ:
734 case L_VERT:
735 render_container_simple(output, damage, con, parent_focused);
736 break;
737 case L_STACKED:
738 render_container_stacked(output, damage, con, parent_focused);
739 break;
740 case L_TABBED:
741 render_container_tabbed(output, damage, con, parent_focused);
742 break;
743 case L_FLOATING:
744 sway_assert(false, "Didn't expect to see floating here");
745 }
746}
747
748static void render_floating_container(struct sway_output *soutput,
749 pixman_region32_t *damage, struct sway_container *con) {
750 if (con->type == C_VIEW) {
751 struct sway_view *view = con->sway_view;
752 struct border_colors *colors;
753 struct wlr_texture *title_texture;
754 struct wlr_texture *marks_texture;
755
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) {
761 colors = &config->border_colors.focused;
762 title_texture = con->title_focused;
763 marks_texture = view->marks_focused;
764 } else {
765 colors = &config->border_colors.unfocused;
766 title_texture = con->title_unfocused;
767 marks_texture = view->marks_unfocused;
768 }
769
770 if (!view->using_csd) {
771 if (con->current.border == B_NORMAL) {
772 render_titlebar(soutput, damage, con, con->current.swayc_x,
773 con->current.swayc_y, con->current.swayc_width, colors,
774 title_texture, marks_texture);
775 } else if (con->current.border != B_NONE) {
776 render_top_border(soutput, damage, con, colors);
777 }
778 }
779 render_view(soutput, damage, con, colors);
780 } else {
781 render_container(soutput, damage, con, false);
782 }
783}
784
785static void render_floating(struct sway_output *soutput,
786 pixman_region32_t *damage) {
787 for (int i = 0; i < root_container.current.children->length; ++i) {
788 struct sway_container *output =
789 root_container.current.children->items[i];
790 for (int j = 0; j < output->current.children->length; ++j) {
791 struct sway_container *ws = output->current.children->items[j];
792 if (!workspace_is_visible(ws)) {
793 continue;
794 }
795 list_t *floating =
796 ws->current.ws_floating->current.children;
797 for (int k = 0; k < floating->length; ++k) {
798 struct sway_container *floater = floating->items[k];
799 render_floating_container(soutput, damage, floater);
800 }
801 }
802 }
803}
804
805const char *damage_debug = NULL;
806
807void output_render(struct sway_output *output, struct timespec *when,
808 pixman_region32_t *damage) {
809 struct wlr_output *wlr_output = output->wlr_output;
810
811 struct wlr_renderer *renderer =
812 wlr_backend_get_renderer(wlr_output->backend);
813 if (!sway_assert(renderer != NULL,
814 "expected the output backend to have a renderer")) {
815 return;
816 }
817
818 wlr_renderer_begin(renderer, wlr_output->width, wlr_output->height);
819
820 bool damage_whole_before_swap = false;
821 if (!pixman_region32_not_empty(damage)) {
822 // Output isn't damaged but needs buffer swap
823 goto renderer_end;
824 }
825
826 if (damage_debug != NULL) {
827 if (strcmp(damage_debug, "highlight") == 0) {
828 wlr_renderer_clear(renderer, (float[]){1, 1, 0, 1});
829 damage_whole_before_swap = true;
830 } else if (strcmp(damage_debug, "rerender") == 0) {
831 int width, height;
832 wlr_output_transformed_resolution(wlr_output, &width, &height);
833 pixman_region32_union_rect(damage, damage, 0, 0, width, height);
834 }
835 }
836
837 struct sway_container *workspace = output_get_active_workspace(output);
838 struct sway_view *fullscreen_view = workspace->current.ws_fullscreen;
839 struct sway_seat *seat = input_manager_current_seat(input_manager);
840
841 if (output_has_opaque_lockscreen(output, seat) && seat->focused_layer) {
842 struct wlr_layer_surface *wlr_layer_surface = seat->focused_layer;
843 struct sway_layer_surface *sway_layer_surface =
844 layer_from_wlr_layer_surface(seat->focused_layer);
845 struct render_data data = {
846 .output = output,
847 .damage = damage,
848 .alpha = 1.0f,
849 };
850 output_surface_for_each_surface(wlr_layer_surface->surface,
851 sway_layer_surface->geo.x, sway_layer_surface->geo.y,
852 &data.root_geo, render_surface_iterator, &data);
853 } else if (fullscreen_view) {
854 float clear_color[] = {0.0f, 0.0f, 0.0f, 1.0f};
855
856 int nrects;
857 pixman_box32_t *rects = pixman_region32_rectangles(damage, &nrects);
858 for (int i = 0; i < nrects; ++i) {
859 scissor_output(wlr_output, &rects[i]);
860 wlr_renderer_clear(renderer, clear_color);
861 }
862
863 // TODO: handle views smaller than the output
864 if (fullscreen_view->swayc->instructions->length) {
865 render_saved_view(fullscreen_view, output, damage, 1.0f);
866 } else {
867 render_view_surfaces(fullscreen_view, output, damage, 1.0f);
868 }
869
870 if (fullscreen_view->type == SWAY_VIEW_XWAYLAND) {
871 render_unmanaged(output, damage,
872 &root_container.sway_root->xwayland_unmanaged);
873 }
874 } else {
875 float clear_color[] = {0.25f, 0.25f, 0.25f, 1.0f};
876
877 int nrects;
878 pixman_box32_t *rects = pixman_region32_rectangles(damage, &nrects);
879 for (int i = 0; i < nrects; ++i) {
880 scissor_output(wlr_output, &rects[i]);
881 wlr_renderer_clear(renderer, clear_color);
882 }
883
884 render_layer(output, damage,
885 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]);
886 render_layer(output, damage,
887 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]);
888
889 render_container(output, damage, workspace, workspace->current.focused);
890 render_floating(output, damage);
891
892 render_unmanaged(output, damage,
893 &root_container.sway_root->xwayland_unmanaged);
894 render_layer(output, damage,
895 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]);
896 }
897 render_layer(output, damage,
898 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]);
899 render_drag_icons(output, damage, &root_container.sway_root->drag_icons);
900
901renderer_end:
902 if (root_container.sway_root->debug_tree) {
903 wlr_render_texture(renderer, root_container.sway_root->debug_tree,
904 wlr_output->transform_matrix, 0, 0, 1);
905 }
906
907 if (damage_whole_before_swap || root_container.sway_root->debug_tree) {
908 int width, height;
909 wlr_output_transformed_resolution(wlr_output, &width, &height);
910 pixman_region32_union_rect(damage, damage, 0, 0, width, height);
911 }
912
913 wlr_renderer_scissor(renderer, NULL);
914 wlr_renderer_end(renderer);
915 if (!wlr_output_damage_swap_buffers(output->damage, when, damage)) {
916 return;
917 }
918 output->last_frame = *when;
919}
diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c
index d2932c87..2a89880a 100644
--- a/sway/desktop/transaction.c
+++ b/sway/desktop/transaction.c
@@ -4,8 +4,8 @@
4#include <string.h> 4#include <string.h>
5#include <time.h> 5#include <time.h>
6#include <wlr/types/wlr_buffer.h> 6#include <wlr/types/wlr_buffer.h>
7#include <wlr/types/wlr_linux_dmabuf.h>
8#include "sway/debug.h" 7#include "sway/debug.h"
8#include "sway/desktop/idle_inhibit_v1.h"
9#include "sway/desktop/transaction.h" 9#include "sway/desktop/transaction.h"
10#include "sway/output.h" 10#include "sway/output.h"
11#include "sway/tree/container.h" 11#include "sway/tree/container.h"
@@ -18,14 +18,14 @@
18 * How long we should wait for views to respond to the configure before giving 18 * How long we should wait for views to respond to the configure before giving
19 * up and applying the transaction anyway. 19 * up and applying the transaction anyway.
20 */ 20 */
21#define TIMEOUT_MS 200 21int txn_timeout_ms = 200;
22 22
23/** 23/**
24 * If enabled, sway will always wait for the transaction timeout before 24 * If enabled, sway will always wait for the transaction timeout before
25 * applying it, rather than applying it when the views are ready. This allows us 25 * applying it, rather than applying it when the views are ready. This allows us
26 * to observe the rendered state while a transaction is in progress. 26 * to observe the rendered state while a transaction is in progress.
27 */ 27 */
28#define TRANSACTION_DEBUG false 28bool txn_debug = false;
29 29
30struct sway_transaction { 30struct sway_transaction {
31 struct wl_event_source *timer; 31 struct wl_event_source *timer;
@@ -46,7 +46,7 @@ struct sway_transaction_instruction {
46 bool ready; 46 bool ready;
47}; 47};
48 48
49struct sway_transaction *transaction_create() { 49static struct sway_transaction *transaction_create() {
50 struct sway_transaction *transaction = 50 struct sway_transaction *transaction =
51 calloc(1, sizeof(struct sway_transaction)); 51 calloc(1, sizeof(struct sway_transaction));
52 transaction->instructions = create_list(); 52 transaction->instructions = create_list();
@@ -72,8 +72,8 @@ static void save_view_buffer(struct sway_view *view,
72 } 72 }
73 if (view->surface && wlr_surface_has_buffer(view->surface)) { 73 if (view->surface && wlr_surface_has_buffer(view->surface)) {
74 instruction->saved_buffer = wlr_buffer_ref(view->surface->buffer); 74 instruction->saved_buffer = wlr_buffer_ref(view->surface->buffer);
75 instruction->saved_buffer_width = view->surface->current->width; 75 instruction->saved_buffer_width = view->surface->current.width;
76 instruction->saved_buffer_height = view->surface->current->height; 76 instruction->saved_buffer_height = view->surface->current.height;
77 } 77 }
78} 78}
79 79
@@ -138,25 +138,18 @@ static void copy_pending_state(struct sway_container *container,
138 state->children = create_list(); 138 state->children = create_list();
139 list_cat(state->children, container->children); 139 list_cat(state->children, container->children);
140 } 140 }
141}
142 141
143static bool transaction_has_container(struct sway_transaction *transaction, 142 struct sway_seat *seat = input_manager_current_seat(input_manager);
144 struct sway_container *container) { 143 state->focused = seat_get_focus(seat) == container;
145 for (int i = 0; i < transaction->instructions->length; ++i) { 144
146 struct sway_transaction_instruction *instruction = 145 if (container->type != C_VIEW) {
147 transaction->instructions->items[i]; 146 state->focused_inactive_child =
148 if (instruction->container == container) { 147 seat_get_active_child(seat, container);
149 return true;
150 }
151 } 148 }
152 return false;
153} 149}
154 150
155void transaction_add_container(struct sway_transaction *transaction, 151static void transaction_add_container(struct sway_transaction *transaction,
156 struct sway_container *container) { 152 struct sway_container *container) {
157 if (transaction_has_container(transaction, container)) {
158 return;
159 }
160 struct sway_transaction_instruction *instruction = 153 struct sway_transaction_instruction *instruction =
161 calloc(1, sizeof(struct sway_transaction_instruction)); 154 calloc(1, sizeof(struct sway_transaction_instruction));
162 instruction->transaction = transaction; 155 instruction->transaction = transaction;
@@ -174,7 +167,7 @@ void transaction_add_container(struct sway_transaction *transaction,
174 * Apply a transaction to the "current" state of the tree. 167 * Apply a transaction to the "current" state of the tree.
175 */ 168 */
176static void transaction_apply(struct sway_transaction *transaction) { 169static void transaction_apply(struct sway_transaction *transaction) {
177 wlr_log(L_DEBUG, "Applying transaction %p", transaction); 170 wlr_log(WLR_DEBUG, "Applying transaction %p", transaction);
178 if (server.debug_txn_timings) { 171 if (server.debug_txn_timings) {
179 struct timespec now; 172 struct timespec now;
180 clock_gettime(CLOCK_MONOTONIC, &now); 173 clock_gettime(CLOCK_MONOTONIC, &now);
@@ -185,9 +178,9 @@ static void transaction_apply(struct sway_transaction *transaction) {
185 float ms_waiting = (now.tv_sec - commit->tv_sec) * 1000 + 178 float ms_waiting = (now.tv_sec - commit->tv_sec) * 1000 +
186 (now.tv_nsec - commit->tv_nsec) / 1000000.0; 179 (now.tv_nsec - commit->tv_nsec) / 1000000.0;
187 float ms_total = ms_arranging + ms_waiting; 180 float ms_total = ms_arranging + ms_waiting;
188 wlr_log(L_DEBUG, "Transaction %p: %.1fms arranging, %.1fms waiting, " 181 wlr_log(WLR_DEBUG, "Transaction %p: %.1fms arranging, %.1fms waiting, "
189 "%.1fms total (%.1f frames if 60Hz)", transaction, 182 "%.1fms total (%.1f frames if 60Hz)", transaction,
190 ms_arranging, ms_waiting, ms_total, ms_total / (1000 / 60)); 183 ms_arranging, ms_waiting, ms_total, ms_total / (1000.0f / 60));
191 } 184 }
192 185
193 // Apply the instruction state to the container's current state 186 // Apply the instruction state to the container's current state
@@ -209,10 +202,12 @@ static void transaction_apply(struct sway_transaction *transaction) {
209 .width = instruction->state.swayc_width, 202 .width = instruction->state.swayc_width,
210 .height = instruction->state.swayc_height, 203 .height = instruction->state.swayc_height,
211 }; 204 };
212 for (int j = 0; j < root_container.children->length; ++j) { 205 for (int j = 0; j < root_container.current.children->length; ++j) {
213 struct sway_container *output = root_container.children->items[j]; 206 struct sway_container *output = root_container.current.children->items[j];
214 output_damage_box(output->sway_output, &old_box); 207 if (output->sway_output) {
215 output_damage_box(output->sway_output, &new_box); 208 output_damage_box(output->sway_output, &old_box);
209 output_damage_box(output->sway_output, &new_box);
210 }
216 } 211 }
217 212
218 // There are separate children lists for each instruction state, the 213 // There are separate children lists for each instruction state, the
@@ -227,29 +222,22 @@ static void transaction_apply(struct sway_transaction *transaction) {
227 } 222 }
228} 223}
229 224
230/**
231 * For simplicity, we only progress the queue if it can be completely flushed.
232 */
233static void transaction_progress_queue() { 225static void transaction_progress_queue() {
234 // We iterate this list in reverse because we're more likely to find a 226 while (server.transactions->length) {
235 // waiting transactions at the end of the list. 227 struct sway_transaction *transaction = server.transactions->items[0];
236 for (int i = server.transactions->length - 1; i >= 0; --i) {
237 struct sway_transaction *transaction = server.transactions->items[i];
238 if (transaction->num_waiting) { 228 if (transaction->num_waiting) {
239 return; 229 return;
240 } 230 }
241 }
242 for (int i = 0; i < server.transactions->length; ++i) {
243 struct sway_transaction *transaction = server.transactions->items[i];
244 transaction_apply(transaction); 231 transaction_apply(transaction);
245 transaction_destroy(transaction); 232 transaction_destroy(transaction);
233 list_del(server.transactions, 0);
246 } 234 }
247 server.transactions->length = 0; 235 idle_inhibit_v1_check_active(server.idle_inhibit_manager_v1);
248} 236}
249 237
250static int handle_timeout(void *data) { 238static int handle_timeout(void *data) {
251 struct sway_transaction *transaction = data; 239 struct sway_transaction *transaction = data;
252 wlr_log(L_DEBUG, "Transaction %p timed out (%li waiting)", 240 wlr_log(WLR_DEBUG, "Transaction %p timed out (%li waiting)",
253 transaction, transaction->num_waiting); 241 transaction, transaction->num_waiting);
254 transaction->num_waiting = 0; 242 transaction->num_waiting = 0;
255 transaction_progress_queue(); 243 transaction_progress_queue();
@@ -283,8 +271,8 @@ static bool should_configure(struct sway_container *con,
283 return true; 271 return true;
284} 272}
285 273
286void transaction_commit(struct sway_transaction *transaction) { 274static void transaction_commit(struct sway_transaction *transaction) {
287 wlr_log(L_DEBUG, "Transaction %p committing with %i instructions", 275 wlr_log(WLR_DEBUG, "Transaction %p committing with %i instructions",
288 transaction, transaction->instructions->length); 276 transaction, transaction->instructions->length);
289 transaction->num_waiting = 0; 277 transaction->num_waiting = 0;
290 for (int i = 0; i < transaction->instructions->length; ++i) { 278 for (int i = 0; i < transaction->instructions->length; ++i) {
@@ -317,9 +305,10 @@ void transaction_commit(struct sway_transaction *transaction) {
317 } else { 305 } else {
318 // There are no other transactions in progress, and this one has nothing 306 // There are no other transactions in progress, and this one has nothing
319 // to wait for, so we can skip the queue. 307 // to wait for, so we can skip the queue.
320 wlr_log(L_DEBUG, "Transaction %p has nothing to wait for", transaction); 308 wlr_log(WLR_DEBUG, "Transaction %p has nothing to wait for", transaction);
321 transaction_apply(transaction); 309 transaction_apply(transaction);
322 transaction_destroy(transaction); 310 transaction_destroy(transaction);
311 idle_inhibit_v1_check_active(server.idle_inhibit_manager_v1);
323 return; 312 return;
324 } 313 }
325 314
@@ -327,7 +316,7 @@ void transaction_commit(struct sway_transaction *transaction) {
327 // Set up a timer which the views must respond within 316 // Set up a timer which the views must respond within
328 transaction->timer = wl_event_loop_add_timer(server.wl_event_loop, 317 transaction->timer = wl_event_loop_add_timer(server.wl_event_loop,
329 handle_timeout, transaction); 318 handle_timeout, transaction);
330 wl_event_source_timer_update(transaction->timer, TIMEOUT_MS); 319 wl_event_source_timer_update(transaction->timer, txn_timeout_ms);
331 } 320 }
332 321
333 // The debug tree shows the pending/live tree. Here is a good place to 322 // The debug tree shows the pending/live tree. Here is a good place to
@@ -347,7 +336,7 @@ static void set_instruction_ready(
347 struct timespec *start = &transaction->commit_time; 336 struct timespec *start = &transaction->commit_time;
348 float ms = (now.tv_sec - start->tv_sec) * 1000 + 337 float ms = (now.tv_sec - start->tv_sec) * 1000 +
349 (now.tv_nsec - start->tv_nsec) / 1000000.0; 338 (now.tv_nsec - start->tv_nsec) / 1000000.0;
350 wlr_log(L_DEBUG, "Transaction %p: %li/%li ready in %.1fms (%s)", 339 wlr_log(WLR_DEBUG, "Transaction %p: %li/%li ready in %.1fms (%s)",
351 transaction, 340 transaction,
352 transaction->num_configures - transaction->num_waiting + 1, 341 transaction->num_configures - transaction->num_waiting + 1,
353 transaction->num_configures, ms, 342 transaction->num_configures, ms,
@@ -358,11 +347,11 @@ static void set_instruction_ready(
358 // If all views are ready, apply the transaction. 347 // If all views are ready, apply the transaction.
359 // If the transaction has timed out then its num_waiting will be 0 already. 348 // If the transaction has timed out then its num_waiting will be 0 already.
360 if (transaction->num_waiting > 0 && --transaction->num_waiting == 0) { 349 if (transaction->num_waiting > 0 && --transaction->num_waiting == 0) {
361#if !TRANSACTION_DEBUG 350 if (!txn_debug) {
362 wlr_log(L_DEBUG, "Transaction %p is ready", transaction); 351 wlr_log(WLR_DEBUG, "Transaction %p is ready", transaction);
363 wl_event_source_timer_update(transaction->timer, 0); 352 wl_event_source_timer_update(transaction->timer, 0);
364 transaction_progress_queue(); 353 transaction_progress_queue();
365#endif 354 }
366 } 355 }
367} 356}
368 357
@@ -374,7 +363,9 @@ static void set_instructions_ready(struct sway_view *view, int index) {
374 for (int i = 0; i <= index; ++i) { 363 for (int i = 0; i <= index; ++i) {
375 struct sway_transaction_instruction *instruction = 364 struct sway_transaction_instruction *instruction =
376 view->swayc->instructions->items[i]; 365 view->swayc->instructions->items[i];
377 set_instruction_ready(instruction); 366 if (!instruction->ready) {
367 set_instruction_ready(instruction);
368 }
378 } 369 }
379} 370}
380 371
@@ -413,3 +404,17 @@ struct wlr_texture *transaction_get_saved_texture(struct sway_view *view,
413 *height = instruction->saved_buffer_height; 404 *height = instruction->saved_buffer_height;
414 return instruction->saved_buffer->texture; 405 return instruction->saved_buffer->texture;
415} 406}
407
408void transaction_commit_dirty(void) {
409 if (!server.dirty_containers->length) {
410 return;
411 }
412 struct sway_transaction *transaction = transaction_create();
413 for (int i = 0; i < server.dirty_containers->length; ++i) {
414 struct sway_container *container = server.dirty_containers->items[i];
415 transaction_add_container(transaction, container);
416 container->dirty = false;
417 }
418 server.dirty_containers->length = 0;
419 transaction_commit(transaction);
420}
diff --git a/sway/desktop/xdg_shell.c b/sway/desktop/xdg_shell.c
index 47604c31..f3e4fef8 100644
--- a/sway/desktop/xdg_shell.c
+++ b/sway/desktop/xdg_shell.c
@@ -1,4 +1,5 @@
1#define _POSIX_C_SOURCE 199309L 1#define _POSIX_C_SOURCE 199309L
2#include <float.h>
2#include <stdbool.h> 3#include <stdbool.h>
3#include <stdlib.h> 4#include <stdlib.h>
4#include <wayland-server.h> 5#include <wayland-server.h>
@@ -45,6 +46,24 @@ static void popup_handle_destroy(struct wl_listener *listener, void *data) {
45 view_child_destroy(&popup->child); 46 view_child_destroy(&popup->child);
46} 47}
47 48
49static void popup_unconstrain(struct sway_xdg_popup *popup) {
50 struct sway_view *view = popup->child.view;
51 struct wlr_xdg_popup *wlr_popup = popup->wlr_xdg_surface->popup;
52
53 struct sway_container *output = container_parent(view->swayc, C_OUTPUT);
54
55 // the output box expressed in the coordinate system of the toplevel parent
56 // of the popup
57 struct wlr_box output_toplevel_sx_box = {
58 .x = output->x - view->x,
59 .y = output->y - view->y,
60 .width = output->width,
61 .height = output->height,
62 };
63
64 wlr_xdg_popup_unconstrain_from_box(wlr_popup, &output_toplevel_sx_box);
65}
66
48static struct sway_xdg_popup *popup_create( 67static struct sway_xdg_popup *popup_create(
49 struct wlr_xdg_popup *wlr_popup, struct sway_view *view) { 68 struct wlr_xdg_popup *wlr_popup, struct sway_view *view) {
50 struct wlr_xdg_surface *xdg_surface = wlr_popup->base; 69 struct wlr_xdg_surface *xdg_surface = wlr_popup->base;
@@ -55,12 +74,15 @@ static struct sway_xdg_popup *popup_create(
55 return NULL; 74 return NULL;
56 } 75 }
57 view_child_init(&popup->child, &popup_impl, view, xdg_surface->surface); 76 view_child_init(&popup->child, &popup_impl, view, xdg_surface->surface);
77 popup->wlr_xdg_surface = xdg_surface;
58 78
59 wl_signal_add(&xdg_surface->events.new_popup, &popup->new_popup); 79 wl_signal_add(&xdg_surface->events.new_popup, &popup->new_popup);
60 popup->new_popup.notify = popup_handle_new_popup; 80 popup->new_popup.notify = popup_handle_new_popup;
61 wl_signal_add(&xdg_surface->events.destroy, &popup->destroy); 81 wl_signal_add(&xdg_surface->events.destroy, &popup->destroy);
62 popup->destroy.notify = popup_handle_destroy; 82 popup->destroy.notify = popup_handle_destroy;
63 83
84 popup_unconstrain(popup);
85
64 return popup; 86 return popup;
65} 87}
66 88
@@ -74,6 +96,16 @@ static struct sway_xdg_shell_view *xdg_shell_view_from_view(
74 return (struct sway_xdg_shell_view *)view; 96 return (struct sway_xdg_shell_view *)view;
75} 97}
76 98
99static void get_constraints(struct sway_view *view, double *min_width,
100 double *max_width, double *min_height, double *max_height) {
101 struct wlr_xdg_toplevel_state *state =
102 &view->wlr_xdg_surface->toplevel->current;
103 *min_width = state->min_width > 0 ? state->min_width : DBL_MIN;
104 *max_width = state->max_width > 0 ? state->max_width : DBL_MAX;
105 *min_height = state->min_height > 0 ? state->min_height : DBL_MIN;
106 *max_height = state->max_height > 0 ? state->max_height : DBL_MAX;
107}
108
77static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) { 109static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) {
78 if (xdg_shell_view_from_view(view) == NULL) { 110 if (xdg_shell_view_from_view(view) == NULL) {
79 return NULL; 111 return NULL;
@@ -167,6 +199,7 @@ static void destroy(struct sway_view *view) {
167} 199}
168 200
169static const struct sway_view_impl view_impl = { 201static const struct sway_view_impl view_impl = {
202 .get_constraints = get_constraints,
170 .get_string_prop = get_string_prop, 203 .get_string_prop = get_string_prop,
171 .configure = configure, 204 .configure = configure,
172 .set_activated = set_activated, 205 .set_activated = set_activated,
@@ -192,10 +225,24 @@ static void handle_commit(struct wl_listener *listener, void *data) {
192 transaction_notify_view_ready(view, xdg_surface->configure_serial); 225 transaction_notify_view_ready(view, xdg_surface->configure_serial);
193 } 226 }
194 227
195 view_update_title(view, false);
196 view_damage_from(view); 228 view_damage_from(view);
197} 229}
198 230
231static void handle_set_title(struct wl_listener *listener, void *data) {
232 struct sway_xdg_shell_view *xdg_shell_view =
233 wl_container_of(listener, xdg_shell_view, set_title);
234 struct sway_view *view = &xdg_shell_view->view;
235 view_update_title(view, false);
236 view_execute_criteria(view);
237}
238
239static void handle_set_app_id(struct wl_listener *listener, void *data) {
240 struct sway_xdg_shell_view *xdg_shell_view =
241 wl_container_of(listener, xdg_shell_view, set_app_id);
242 struct sway_view *view = &xdg_shell_view->view;
243 view_execute_criteria(view);
244}
245
199static void handle_new_popup(struct wl_listener *listener, void *data) { 246static void handle_new_popup(struct wl_listener *listener, void *data) {
200 struct sway_xdg_shell_view *xdg_shell_view = 247 struct sway_xdg_shell_view *xdg_shell_view =
201 wl_container_of(listener, xdg_shell_view, new_popup); 248 wl_container_of(listener, xdg_shell_view, new_popup);
@@ -222,8 +269,37 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data)
222 269
223 view_set_fullscreen(view, e->fullscreen); 270 view_set_fullscreen(view, e->fullscreen);
224 271
225 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE); 272 struct sway_container *output = container_parent(view->swayc, C_OUTPUT);
226 arrange_and_commit(ws); 273 arrange_windows(output);
274 transaction_commit_dirty();
275}
276
277static void handle_request_move(struct wl_listener *listener, void *data) {
278 struct sway_xdg_shell_view *xdg_shell_view =
279 wl_container_of(listener, xdg_shell_view, request_move);
280 struct sway_view *view = &xdg_shell_view->view;
281 if (!container_is_floating(view->swayc)) {
282 return;
283 }
284 struct wlr_xdg_toplevel_move_event *e = data;
285 struct sway_seat *seat = e->seat->seat->data;
286 if (e->serial == seat->last_button_serial) {
287 seat_begin_move(seat, view->swayc, seat->last_button);
288 }
289}
290
291static void handle_request_resize(struct wl_listener *listener, void *data) {
292 struct sway_xdg_shell_view *xdg_shell_view =
293 wl_container_of(listener, xdg_shell_view, request_resize);
294 struct sway_view *view = &xdg_shell_view->view;
295 if (!container_is_floating(view->swayc)) {
296 return;
297 }
298 struct wlr_xdg_toplevel_resize_event *e = data;
299 struct sway_seat *seat = e->seat->seat->data;
300 if (e->serial == seat->last_button_serial) {
301 seat_begin_resize(seat, view->swayc, seat->last_button, e->edges);
302 }
227} 303}
228 304
229static void handle_unmap(struct wl_listener *listener, void *data) { 305static void handle_unmap(struct wl_listener *listener, void *data) {
@@ -240,6 +316,10 @@ static void handle_unmap(struct wl_listener *listener, void *data) {
240 wl_list_remove(&xdg_shell_view->commit.link); 316 wl_list_remove(&xdg_shell_view->commit.link);
241 wl_list_remove(&xdg_shell_view->new_popup.link); 317 wl_list_remove(&xdg_shell_view->new_popup.link);
242 wl_list_remove(&xdg_shell_view->request_fullscreen.link); 318 wl_list_remove(&xdg_shell_view->request_fullscreen.link);
319 wl_list_remove(&xdg_shell_view->request_move.link);
320 wl_list_remove(&xdg_shell_view->request_resize.link);
321 wl_list_remove(&xdg_shell_view->set_title.link);
322 wl_list_remove(&xdg_shell_view->set_app_id.link);
243} 323}
244 324
245static void handle_map(struct wl_listener *listener, void *data) { 325static void handle_map(struct wl_listener *listener, void *data) {
@@ -251,8 +331,8 @@ static void handle_map(struct wl_listener *listener, void *data) {
251 view->natural_width = view->wlr_xdg_surface->geometry.width; 331 view->natural_width = view->wlr_xdg_surface->geometry.width;
252 view->natural_height = view->wlr_xdg_surface->geometry.height; 332 view->natural_height = view->wlr_xdg_surface->geometry.height;
253 if (!view->natural_width && !view->natural_height) { 333 if (!view->natural_width && !view->natural_height) {
254 view->natural_width = view->wlr_xdg_surface->surface->current->width; 334 view->natural_width = view->wlr_xdg_surface->surface->current.width;
255 view->natural_height = view->wlr_xdg_surface->surface->current->height; 335 view->natural_height = view->wlr_xdg_surface->surface->current.height;
256 } 336 }
257 337
258 view_map(view, view->wlr_xdg_surface->surface); 338 view_map(view, view->wlr_xdg_surface->surface);
@@ -260,10 +340,11 @@ static void handle_map(struct wl_listener *listener, void *data) {
260 if (xdg_surface->toplevel->client_pending.fullscreen) { 340 if (xdg_surface->toplevel->client_pending.fullscreen) {
261 view_set_fullscreen(view, true); 341 view_set_fullscreen(view, true);
262 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE); 342 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE);
263 arrange_and_commit(ws); 343 arrange_windows(ws);
264 } else { 344 } else {
265 arrange_and_commit(view->swayc->parent); 345 arrange_windows(view->swayc->parent);
266 } 346 }
347 transaction_commit_dirty();
267 348
268 xdg_shell_view->commit.notify = handle_commit; 349 xdg_shell_view->commit.notify = handle_commit;
269 wl_signal_add(&xdg_surface->surface->events.commit, 350 wl_signal_add(&xdg_surface->surface->events.commit,
@@ -276,6 +357,22 @@ static void handle_map(struct wl_listener *listener, void *data) {
276 xdg_shell_view->request_fullscreen.notify = handle_request_fullscreen; 357 xdg_shell_view->request_fullscreen.notify = handle_request_fullscreen;
277 wl_signal_add(&xdg_surface->toplevel->events.request_fullscreen, 358 wl_signal_add(&xdg_surface->toplevel->events.request_fullscreen,
278 &xdg_shell_view->request_fullscreen); 359 &xdg_shell_view->request_fullscreen);
360
361 xdg_shell_view->request_move.notify = handle_request_move;
362 wl_signal_add(&xdg_surface->toplevel->events.request_move,
363 &xdg_shell_view->request_move);
364
365 xdg_shell_view->request_resize.notify = handle_request_resize;
366 wl_signal_add(&xdg_surface->toplevel->events.request_resize,
367 &xdg_shell_view->request_resize);
368
369 xdg_shell_view->set_title.notify = handle_set_title;
370 wl_signal_add(&xdg_surface->toplevel->events.set_title,
371 &xdg_shell_view->set_title);
372
373 xdg_shell_view->set_app_id.notify = handle_set_app_id;
374 wl_signal_add(&xdg_surface->toplevel->events.set_app_id,
375 &xdg_shell_view->set_app_id);
279} 376}
280 377
281static void handle_destroy(struct wl_listener *listener, void *data) { 378static void handle_destroy(struct wl_listener *listener, void *data) {
@@ -304,11 +401,11 @@ void handle_xdg_shell_surface(struct wl_listener *listener, void *data) {
304 struct wlr_xdg_surface *xdg_surface = data; 401 struct wlr_xdg_surface *xdg_surface = data;
305 402
306 if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP) { 403 if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP) {
307 wlr_log(L_DEBUG, "New xdg_shell popup"); 404 wlr_log(WLR_DEBUG, "New xdg_shell popup");
308 return; 405 return;
309 } 406 }
310 407
311 wlr_log(L_DEBUG, "New xdg_shell toplevel title='%s' app_id='%s'", 408 wlr_log(WLR_DEBUG, "New xdg_shell toplevel title='%s' app_id='%s'",
312 xdg_surface->toplevel->title, xdg_surface->toplevel->app_id); 409 xdg_surface->toplevel->title, xdg_surface->toplevel->app_id);
313 wlr_xdg_surface_ping(xdg_surface); 410 wlr_xdg_surface_ping(xdg_surface);
314 411
diff --git a/sway/desktop/xdg_shell_v6.c b/sway/desktop/xdg_shell_v6.c
index b28c4b9c..46fd4769 100644
--- a/sway/desktop/xdg_shell_v6.c
+++ b/sway/desktop/xdg_shell_v6.c
@@ -1,4 +1,5 @@
1#define _POSIX_C_SOURCE 199309L 1#define _POSIX_C_SOURCE 199309L
2#include <float.h>
2#include <stdbool.h> 3#include <stdbool.h>
3#include <stdlib.h> 4#include <stdlib.h>
4#include <wayland-server.h> 5#include <wayland-server.h>
@@ -44,6 +45,24 @@ static void popup_handle_destroy(struct wl_listener *listener, void *data) {
44 view_child_destroy(&popup->child); 45 view_child_destroy(&popup->child);
45} 46}
46 47
48static void popup_unconstrain(struct sway_xdg_popup_v6 *popup) {
49 struct sway_view *view = popup->child.view;
50 struct wlr_xdg_popup_v6 *wlr_popup = popup->wlr_xdg_surface_v6->popup;
51
52 struct sway_container *output = container_parent(view->swayc, C_OUTPUT);
53
54 // the output box expressed in the coordinate system of the toplevel parent
55 // of the popup
56 struct wlr_box output_toplevel_sx_box = {
57 .x = output->x - view->x,
58 .y = output->y - view->y,
59 .width = output->width,
60 .height = output->height,
61 };
62
63 wlr_xdg_popup_v6_unconstrain_from_box(wlr_popup, &output_toplevel_sx_box);
64}
65
47static struct sway_xdg_popup_v6 *popup_create( 66static struct sway_xdg_popup_v6 *popup_create(
48 struct wlr_xdg_popup_v6 *wlr_popup, struct sway_view *view) { 67 struct wlr_xdg_popup_v6 *wlr_popup, struct sway_view *view) {
49 struct wlr_xdg_surface_v6 *xdg_surface = wlr_popup->base; 68 struct wlr_xdg_surface_v6 *xdg_surface = wlr_popup->base;
@@ -54,12 +73,15 @@ static struct sway_xdg_popup_v6 *popup_create(
54 return NULL; 73 return NULL;
55 } 74 }
56 view_child_init(&popup->child, &popup_impl, view, xdg_surface->surface); 75 view_child_init(&popup->child, &popup_impl, view, xdg_surface->surface);
76 popup->wlr_xdg_surface_v6 = xdg_surface;
57 77
58 wl_signal_add(&xdg_surface->events.new_popup, &popup->new_popup); 78 wl_signal_add(&xdg_surface->events.new_popup, &popup->new_popup);
59 popup->new_popup.notify = popup_handle_new_popup; 79 popup->new_popup.notify = popup_handle_new_popup;
60 wl_signal_add(&xdg_surface->events.destroy, &popup->destroy); 80 wl_signal_add(&xdg_surface->events.destroy, &popup->destroy);
61 popup->destroy.notify = popup_handle_destroy; 81 popup->destroy.notify = popup_handle_destroy;
62 82
83 popup_unconstrain(popup);
84
63 return popup; 85 return popup;
64} 86}
65 87
@@ -73,6 +95,16 @@ static struct sway_xdg_shell_v6_view *xdg_shell_v6_view_from_view(
73 return (struct sway_xdg_shell_v6_view *)view; 95 return (struct sway_xdg_shell_v6_view *)view;
74} 96}
75 97
98static void get_constraints(struct sway_view *view, double *min_width,
99 double *max_width, double *min_height, double *max_height) {
100 struct wlr_xdg_toplevel_v6_state *state =
101 &view->wlr_xdg_surface_v6->toplevel->current;
102 *min_width = state->min_width > 0 ? state->min_width : DBL_MIN;
103 *max_width = state->max_width > 0 ? state->max_width : DBL_MAX;
104 *min_height = state->min_height > 0 ? state->min_height : DBL_MIN;
105 *max_height = state->max_height > 0 ? state->max_height : DBL_MAX;
106}
107
76static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) { 108static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) {
77 if (xdg_shell_v6_view_from_view(view) == NULL) { 109 if (xdg_shell_v6_view_from_view(view) == NULL) {
78 return NULL; 110 return NULL;
@@ -163,6 +195,7 @@ static void destroy(struct sway_view *view) {
163} 195}
164 196
165static const struct sway_view_impl view_impl = { 197static const struct sway_view_impl view_impl = {
198 .get_constraints = get_constraints,
166 .get_string_prop = get_string_prop, 199 .get_string_prop = get_string_prop,
167 .configure = configure, 200 .configure = configure,
168 .set_activated = set_activated, 201 .set_activated = set_activated,
@@ -187,10 +220,24 @@ static void handle_commit(struct wl_listener *listener, void *data) {
187 transaction_notify_view_ready(view, xdg_surface_v6->configure_serial); 220 transaction_notify_view_ready(view, xdg_surface_v6->configure_serial);
188 } 221 }
189 222
190 view_update_title(view, false);
191 view_damage_from(view); 223 view_damage_from(view);
192} 224}
193 225
226static void handle_set_title(struct wl_listener *listener, void *data) {
227 struct sway_xdg_shell_v6_view *xdg_shell_v6_view =
228 wl_container_of(listener, xdg_shell_v6_view, set_title);
229 struct sway_view *view = &xdg_shell_v6_view->view;
230 view_update_title(view, false);
231 view_execute_criteria(view);
232}
233
234static void handle_set_app_id(struct wl_listener *listener, void *data) {
235 struct sway_xdg_shell_v6_view *xdg_shell_v6_view =
236 wl_container_of(listener, xdg_shell_v6_view, set_app_id);
237 struct sway_view *view = &xdg_shell_v6_view->view;
238 view_execute_criteria(view);
239}
240
194static void handle_new_popup(struct wl_listener *listener, void *data) { 241static void handle_new_popup(struct wl_listener *listener, void *data) {
195 struct sway_xdg_shell_v6_view *xdg_shell_v6_view = 242 struct sway_xdg_shell_v6_view *xdg_shell_v6_view =
196 wl_container_of(listener, xdg_shell_v6_view, new_popup); 243 wl_container_of(listener, xdg_shell_v6_view, new_popup);
@@ -217,8 +264,37 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data)
217 264
218 view_set_fullscreen(view, e->fullscreen); 265 view_set_fullscreen(view, e->fullscreen);
219 266
220 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE); 267 struct sway_container *output = container_parent(view->swayc, C_OUTPUT);
221 arrange_and_commit(ws); 268 arrange_windows(output);
269 transaction_commit_dirty();
270}
271
272static void handle_request_move(struct wl_listener *listener, void *data) {
273 struct sway_xdg_shell_v6_view *xdg_shell_v6_view =
274 wl_container_of(listener, xdg_shell_v6_view, request_move);
275 struct sway_view *view = &xdg_shell_v6_view->view;
276 if (!container_is_floating(view->swayc)) {
277 return;
278 }
279 struct wlr_xdg_toplevel_v6_move_event *e = data;
280 struct sway_seat *seat = e->seat->seat->data;
281 if (e->serial == seat->last_button_serial) {
282 seat_begin_move(seat, view->swayc, seat->last_button);
283 }
284}
285
286static void handle_request_resize(struct wl_listener *listener, void *data) {
287 struct sway_xdg_shell_v6_view *xdg_shell_v6_view =
288 wl_container_of(listener, xdg_shell_v6_view, request_resize);
289 struct sway_view *view = &xdg_shell_v6_view->view;
290 if (!container_is_floating(view->swayc)) {
291 return;
292 }
293 struct wlr_xdg_toplevel_v6_resize_event *e = data;
294 struct sway_seat *seat = e->seat->seat->data;
295 if (e->serial == seat->last_button_serial) {
296 seat_begin_resize(seat, view->swayc, seat->last_button, e->edges);
297 }
222} 298}
223 299
224static void handle_unmap(struct wl_listener *listener, void *data) { 300static void handle_unmap(struct wl_listener *listener, void *data) {
@@ -235,6 +311,10 @@ static void handle_unmap(struct wl_listener *listener, void *data) {
235 wl_list_remove(&xdg_shell_v6_view->commit.link); 311 wl_list_remove(&xdg_shell_v6_view->commit.link);
236 wl_list_remove(&xdg_shell_v6_view->new_popup.link); 312 wl_list_remove(&xdg_shell_v6_view->new_popup.link);
237 wl_list_remove(&xdg_shell_v6_view->request_fullscreen.link); 313 wl_list_remove(&xdg_shell_v6_view->request_fullscreen.link);
314 wl_list_remove(&xdg_shell_v6_view->request_move.link);
315 wl_list_remove(&xdg_shell_v6_view->request_resize.link);
316 wl_list_remove(&xdg_shell_v6_view->set_title.link);
317 wl_list_remove(&xdg_shell_v6_view->set_app_id.link);
238} 318}
239 319
240static void handle_map(struct wl_listener *listener, void *data) { 320static void handle_map(struct wl_listener *listener, void *data) {
@@ -246,8 +326,8 @@ static void handle_map(struct wl_listener *listener, void *data) {
246 view->natural_width = view->wlr_xdg_surface_v6->geometry.width; 326 view->natural_width = view->wlr_xdg_surface_v6->geometry.width;
247 view->natural_height = view->wlr_xdg_surface_v6->geometry.height; 327 view->natural_height = view->wlr_xdg_surface_v6->geometry.height;
248 if (!view->natural_width && !view->natural_height) { 328 if (!view->natural_width && !view->natural_height) {
249 view->natural_width = view->wlr_xdg_surface_v6->surface->current->width; 329 view->natural_width = view->wlr_xdg_surface_v6->surface->current.width;
250 view->natural_height = view->wlr_xdg_surface_v6->surface->current->height; 330 view->natural_height = view->wlr_xdg_surface_v6->surface->current.height;
251 } 331 }
252 332
253 view_map(view, view->wlr_xdg_surface_v6->surface); 333 view_map(view, view->wlr_xdg_surface_v6->surface);
@@ -255,10 +335,11 @@ static void handle_map(struct wl_listener *listener, void *data) {
255 if (xdg_surface->toplevel->client_pending.fullscreen) { 335 if (xdg_surface->toplevel->client_pending.fullscreen) {
256 view_set_fullscreen(view, true); 336 view_set_fullscreen(view, true);
257 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE); 337 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE);
258 arrange_and_commit(ws); 338 arrange_windows(ws);
259 } else { 339 } else {
260 arrange_and_commit(view->swayc->parent); 340 arrange_windows(view->swayc->parent);
261 } 341 }
342 transaction_commit_dirty();
262 343
263 xdg_shell_v6_view->commit.notify = handle_commit; 344 xdg_shell_v6_view->commit.notify = handle_commit;
264 wl_signal_add(&xdg_surface->surface->events.commit, 345 wl_signal_add(&xdg_surface->surface->events.commit,
@@ -271,6 +352,22 @@ static void handle_map(struct wl_listener *listener, void *data) {
271 xdg_shell_v6_view->request_fullscreen.notify = handle_request_fullscreen; 352 xdg_shell_v6_view->request_fullscreen.notify = handle_request_fullscreen;
272 wl_signal_add(&xdg_surface->toplevel->events.request_fullscreen, 353 wl_signal_add(&xdg_surface->toplevel->events.request_fullscreen,
273 &xdg_shell_v6_view->request_fullscreen); 354 &xdg_shell_v6_view->request_fullscreen);
355
356 xdg_shell_v6_view->request_move.notify = handle_request_move;
357 wl_signal_add(&xdg_surface->toplevel->events.request_move,
358 &xdg_shell_v6_view->request_move);
359
360 xdg_shell_v6_view->request_resize.notify = handle_request_resize;
361 wl_signal_add(&xdg_surface->toplevel->events.request_resize,
362 &xdg_shell_v6_view->request_resize);
363
364 xdg_shell_v6_view->set_title.notify = handle_set_title;
365 wl_signal_add(&xdg_surface->toplevel->events.set_title,
366 &xdg_shell_v6_view->set_title);
367
368 xdg_shell_v6_view->set_app_id.notify = handle_set_app_id;
369 wl_signal_add(&xdg_surface->toplevel->events.set_app_id,
370 &xdg_shell_v6_view->set_app_id);
274} 371}
275 372
276static void handle_destroy(struct wl_listener *listener, void *data) { 373static void handle_destroy(struct wl_listener *listener, void *data) {
@@ -295,11 +392,11 @@ void handle_xdg_shell_v6_surface(struct wl_listener *listener, void *data) {
295 struct wlr_xdg_surface_v6 *xdg_surface = data; 392 struct wlr_xdg_surface_v6 *xdg_surface = data;
296 393
297 if (xdg_surface->role == WLR_XDG_SURFACE_V6_ROLE_POPUP) { 394 if (xdg_surface->role == WLR_XDG_SURFACE_V6_ROLE_POPUP) {
298 wlr_log(L_DEBUG, "New xdg_shell_v6 popup"); 395 wlr_log(WLR_DEBUG, "New xdg_shell_v6 popup");
299 return; 396 return;
300 } 397 }
301 398
302 wlr_log(L_DEBUG, "New xdg_shell_v6 toplevel title='%s' app_id='%s'", 399 wlr_log(WLR_DEBUG, "New xdg_shell_v6 toplevel title='%s' app_id='%s'",
303 xdg_surface->toplevel->title, xdg_surface->toplevel->app_id); 400 xdg_surface->toplevel->title, xdg_surface->toplevel->app_id);
304 wlr_xdg_surface_v6_ping(xdg_surface); 401 wlr_xdg_surface_v6_ping(xdg_surface);
305 402
diff --git a/sway/desktop/xwayland.c b/sway/desktop/xwayland.c
index b3b1473d..65d4fcd4 100644
--- a/sway/desktop/xwayland.c
+++ b/sway/desktop/xwayland.c
@@ -69,16 +69,11 @@ static void unmanaged_handle_map(struct wl_listener *listener, void *data) {
69 surface->ly = xsurface->y; 69 surface->ly = xsurface->y;
70 desktop_damage_surface(xsurface->surface, surface->lx, surface->ly, true); 70 desktop_damage_surface(xsurface->surface, surface->lx, surface->ly, true);
71 71
72 if (!wlr_xwayland_surface_is_unmanaged(xsurface)) { 72 struct sway_seat *seat = input_manager_current_seat(input_manager);
73 struct sway_seat *seat = input_manager_current_seat(input_manager); 73 struct wlr_xwayland *xwayland =
74 struct wlr_xwayland *xwayland = 74 seat->input->server->xwayland.wlr_xwayland;
75 seat->input->server->xwayland.wlr_xwayland; 75 wlr_xwayland_set_seat(xwayland, seat->wlr_seat);
76 wlr_xwayland_set_seat(xwayland, seat->wlr_seat); 76 seat_set_focus_surface(seat, xsurface->surface, false);
77 seat_set_focus_surface(seat, xsurface->surface);
78 }
79
80 // TODO: we don't send surface enter/leave events to xwayland unmanaged
81 // surfaces, but xwayland doesn't support HiDPI anyway
82} 77}
83 78
84static void unmanaged_handle_unmap(struct wl_listener *listener, void *data) { 79static void unmanaged_handle_unmap(struct wl_listener *listener, void *data) {
@@ -89,18 +84,16 @@ static void unmanaged_handle_unmap(struct wl_listener *listener, void *data) {
89 wl_list_remove(&surface->link); 84 wl_list_remove(&surface->link);
90 wl_list_remove(&surface->commit.link); 85 wl_list_remove(&surface->commit.link);
91 86
92 if (!wlr_xwayland_surface_is_unmanaged(xsurface)) { 87 struct sway_seat *seat = input_manager_current_seat(input_manager);
93 struct sway_seat *seat = input_manager_current_seat(input_manager); 88 if (seat->wlr_seat->keyboard_state.focused_surface ==
94 if (seat->wlr_seat->keyboard_state.focused_surface == 89 xsurface->surface) {
95 xsurface->surface) { 90 // Restore focus
96 // Restore focus 91 struct sway_container *previous =
97 struct sway_container *previous = 92 seat_get_focus_inactive(seat, &root_container);
98 seat_get_focus_inactive(seat, &root_container); 93 if (previous) {
99 if (previous) { 94 // Hack to get seat to re-focus the return value of get_focus
100 // Hack to get seat to re-focus the return value of get_focus 95 seat_set_focus(seat, previous->parent);
101 seat_set_focus(seat, previous->parent); 96 seat_set_focus(seat, previous);
102 seat_set_focus(seat, previous);
103 }
104 } 97 }
105 } 98 }
106} 99}
@@ -119,7 +112,7 @@ static struct sway_xwayland_unmanaged *create_unmanaged(
119 struct sway_xwayland_unmanaged *surface = 112 struct sway_xwayland_unmanaged *surface =
120 calloc(1, sizeof(struct sway_xwayland_unmanaged)); 113 calloc(1, sizeof(struct sway_xwayland_unmanaged));
121 if (surface == NULL) { 114 if (surface == NULL) {
122 wlr_log(L_ERROR, "Allocation failed"); 115 wlr_log(WLR_ERROR, "Allocation failed");
123 return NULL; 116 return NULL;
124 } 117 }
125 118
@@ -246,6 +239,14 @@ static bool wants_floating(struct sway_view *view) {
246 return false; 239 return false;
247} 240}
248 241
242static bool has_client_side_decorations(struct sway_view *view) {
243 if (xwayland_view_from_view(view) == NULL) {
244 return false;
245 }
246 struct wlr_xwayland_surface *surface = view->wlr_xwayland_surface;
247 return surface->decorations != WLR_XWAYLAND_SURFACE_DECORATIONS_ALL;
248}
249
249static void _close(struct sway_view *view) { 250static void _close(struct sway_view *view) {
250 if (xwayland_view_from_view(view) == NULL) { 251 if (xwayland_view_from_view(view) == NULL) {
251 return; 252 return;
@@ -269,6 +270,7 @@ static const struct sway_view_impl view_impl = {
269 .set_tiled = set_tiled, 270 .set_tiled = set_tiled,
270 .set_fullscreen = set_fullscreen, 271 .set_fullscreen = set_fullscreen,
271 .wants_floating = wants_floating, 272 .wants_floating = wants_floating,
273 .has_client_side_decorations = has_client_side_decorations,
272 .close = _close, 274 .close = _close,
273 .destroy = destroy, 275 .destroy = destroy,
274}; 276};
@@ -278,15 +280,42 @@ static void handle_commit(struct wl_listener *listener, void *data) {
278 wl_container_of(listener, xwayland_view, commit); 280 wl_container_of(listener, xwayland_view, commit);
279 struct sway_view *view = &xwayland_view->view; 281 struct sway_view *view = &xwayland_view->view;
280 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 282 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
281 struct wlr_surface_state *surface_state = xsurface->surface->current; 283 struct wlr_surface_state *surface_state = &xsurface->surface->current;
282 284
283 if (view->swayc->instructions->length) { 285 if (view->swayc->instructions->length) {
284 transaction_notify_view_ready_by_size(view, 286 transaction_notify_view_ready_by_size(view,
285 surface_state->width, surface_state->height); 287 surface_state->width, surface_state->height);
288 } else if (container_is_floating(view->swayc)) {
289 view_update_size(view, surface_state->width, surface_state->height);
286 } 290 }
291
287 view_damage_from(view); 292 view_damage_from(view);
288} 293}
289 294
295static void handle_destroy(struct wl_listener *listener, void *data) {
296 struct sway_xwayland_view *xwayland_view =
297 wl_container_of(listener, xwayland_view, destroy);
298 struct sway_view *view = &xwayland_view->view;
299
300 if (view->surface) {
301 view_unmap(view);
302 wl_list_remove(&xwayland_view->commit.link);
303 }
304
305 wl_list_remove(&xwayland_view->destroy.link);
306 wl_list_remove(&xwayland_view->request_configure.link);
307 wl_list_remove(&xwayland_view->request_fullscreen.link);
308 wl_list_remove(&xwayland_view->request_move.link);
309 wl_list_remove(&xwayland_view->request_resize.link);
310 wl_list_remove(&xwayland_view->set_title.link);
311 wl_list_remove(&xwayland_view->set_class.link);
312 wl_list_remove(&xwayland_view->set_window_type.link);
313 wl_list_remove(&xwayland_view->set_hints.link);
314 wl_list_remove(&xwayland_view->map.link);
315 wl_list_remove(&xwayland_view->unmap.link);
316 view_destroy(&xwayland_view->view);
317}
318
290static void handle_unmap(struct wl_listener *listener, void *data) { 319static void handle_unmap(struct wl_listener *listener, void *data) {
291 struct sway_xwayland_view *xwayland_view = 320 struct sway_xwayland_view *xwayland_view =
292 wl_container_of(listener, xwayland_view, unmap); 321 wl_container_of(listener, xwayland_view, unmap);
@@ -307,6 +336,15 @@ static void handle_map(struct wl_listener *listener, void *data) {
307 struct wlr_xwayland_surface *xsurface = data; 336 struct wlr_xwayland_surface *xsurface = data;
308 struct sway_view *view = &xwayland_view->view; 337 struct sway_view *view = &xwayland_view->view;
309 338
339 if (xsurface->override_redirect) {
340 // This window used not to have the override redirect flag and has it
341 // now. Switch to unmanaged.
342 handle_destroy(&xwayland_view->destroy, view);
343 struct sway_xwayland_unmanaged *unmanaged = create_unmanaged(xsurface);
344 unmanaged_handle_map(&unmanaged->map, xsurface);
345 return;
346 }
347
310 view->natural_width = xsurface->width; 348 view->natural_width = xsurface->width;
311 view->natural_height = xsurface->height; 349 view->natural_height = xsurface->height;
312 350
@@ -321,31 +359,11 @@ static void handle_map(struct wl_listener *listener, void *data) {
321 if (xsurface->fullscreen) { 359 if (xsurface->fullscreen) {
322 view_set_fullscreen(view, true); 360 view_set_fullscreen(view, true);
323 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE); 361 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE);
324 arrange_and_commit(ws); 362 arrange_windows(ws);
325 } else { 363 } else {
326 arrange_and_commit(view->swayc->parent); 364 arrange_windows(view->swayc->parent);
327 }
328}
329
330static void handle_destroy(struct wl_listener *listener, void *data) {
331 struct sway_xwayland_view *xwayland_view =
332 wl_container_of(listener, xwayland_view, destroy);
333 struct sway_view *view = &xwayland_view->view;
334
335 if (view->surface) {
336 view_unmap(view);
337 wl_list_remove(&xwayland_view->commit.link);
338 } 365 }
339 366 transaction_commit_dirty();
340 wl_list_remove(&xwayland_view->destroy.link);
341 wl_list_remove(&xwayland_view->request_configure.link);
342 wl_list_remove(&xwayland_view->request_fullscreen.link);
343 wl_list_remove(&xwayland_view->set_title.link);
344 wl_list_remove(&xwayland_view->set_class.link);
345 wl_list_remove(&xwayland_view->set_window_type.link);
346 wl_list_remove(&xwayland_view->map.link);
347 wl_list_remove(&xwayland_view->unmap.link);
348 view_destroy(&xwayland_view->view);
349} 367}
350 368
351static void handle_request_configure(struct wl_listener *listener, void *data) { 369static void handle_request_configure(struct wl_listener *listener, void *data) {
@@ -379,8 +397,40 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data)
379 } 397 }
380 view_set_fullscreen(view, xsurface->fullscreen); 398 view_set_fullscreen(view, xsurface->fullscreen);
381 399
382 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE); 400 struct sway_container *output = container_parent(view->swayc, C_OUTPUT);
383 arrange_and_commit(ws); 401 arrange_windows(output);
402 transaction_commit_dirty();
403}
404
405static void handle_request_move(struct wl_listener *listener, void *data) {
406 struct sway_xwayland_view *xwayland_view =
407 wl_container_of(listener, xwayland_view, request_move);
408 struct sway_view *view = &xwayland_view->view;
409 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
410 if (!xsurface->mapped) {
411 return;
412 }
413 if (!container_is_floating(view->swayc)) {
414 return;
415 }
416 struct sway_seat *seat = input_manager_current_seat(input_manager);
417 seat_begin_move(seat, view->swayc, seat->last_button);
418}
419
420static void handle_request_resize(struct wl_listener *listener, void *data) {
421 struct sway_xwayland_view *xwayland_view =
422 wl_container_of(listener, xwayland_view, request_resize);
423 struct sway_view *view = &xwayland_view->view;
424 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
425 if (!xsurface->mapped) {
426 return;
427 }
428 if (!container_is_floating(view->swayc)) {
429 return;
430 }
431 struct wlr_xwayland_resize_event *e = data;
432 struct sway_seat *seat = input_manager_current_seat(input_manager);
433 seat_begin_resize(seat, view->swayc, seat->last_button, e->edges);
384} 434}
385 435
386static void handle_set_title(struct wl_listener *listener, void *data) { 436static void handle_set_title(struct wl_listener *listener, void *data) {
@@ -417,6 +467,25 @@ static void handle_set_window_type(struct wl_listener *listener, void *data) {
417 view_execute_criteria(view); 467 view_execute_criteria(view);
418} 468}
419 469
470static void handle_set_hints(struct wl_listener *listener, void *data) {
471 struct sway_xwayland_view *xwayland_view =
472 wl_container_of(listener, xwayland_view, set_hints);
473 struct sway_view *view = &xwayland_view->view;
474 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
475 if (!xsurface->mapped) {
476 return;
477 }
478 if (!xsurface->hints_urgency && view->urgent_timer) {
479 // The view is is in the timeout period. We'll ignore the request to
480 // unset urgency so that the view remains urgent until the timer clears
481 // it.
482 return;
483 }
484 if (view->allow_request_urgent) {
485 view_set_urgent(view, (bool)xsurface->hints_urgency);
486 }
487}
488
420struct sway_view *view_from_wlr_xwayland_surface( 489struct sway_view *view_from_wlr_xwayland_surface(
421 struct wlr_xwayland_surface *xsurface) { 490 struct wlr_xwayland_surface *xsurface) {
422 return xsurface->data; 491 return xsurface->data;
@@ -427,14 +496,13 @@ void handle_xwayland_surface(struct wl_listener *listener, void *data) {
427 xwayland_surface); 496 xwayland_surface);
428 struct wlr_xwayland_surface *xsurface = data; 497 struct wlr_xwayland_surface *xsurface = data;
429 498
430 if (wlr_xwayland_surface_is_unmanaged(xsurface) || 499 if (xsurface->override_redirect) {
431 xsurface->override_redirect) { 500 wlr_log(WLR_DEBUG, "New xwayland unmanaged surface");
432 wlr_log(L_DEBUG, "New xwayland unmanaged surface");
433 create_unmanaged(xsurface); 501 create_unmanaged(xsurface);
434 return; 502 return;
435 } 503 }
436 504
437 wlr_log(L_DEBUG, "New xwayland surface title='%s' class='%s'", 505 wlr_log(WLR_DEBUG, "New xwayland surface title='%s' class='%s'",
438 xsurface->title, xsurface->class); 506 xsurface->title, xsurface->class);
439 507
440 struct sway_xwayland_view *xwayland_view = 508 struct sway_xwayland_view *xwayland_view =
@@ -457,6 +525,14 @@ void handle_xwayland_surface(struct wl_listener *listener, void *data) {
457 &xwayland_view->request_fullscreen); 525 &xwayland_view->request_fullscreen);
458 xwayland_view->request_fullscreen.notify = handle_request_fullscreen; 526 xwayland_view->request_fullscreen.notify = handle_request_fullscreen;
459 527
528 wl_signal_add(&xsurface->events.request_move,
529 &xwayland_view->request_move);
530 xwayland_view->request_move.notify = handle_request_move;
531
532 wl_signal_add(&xsurface->events.request_resize,
533 &xwayland_view->request_resize);
534 xwayland_view->request_resize.notify = handle_request_resize;
535
460 wl_signal_add(&xsurface->events.set_title, &xwayland_view->set_title); 536 wl_signal_add(&xsurface->events.set_title, &xwayland_view->set_title);
461 xwayland_view->set_title.notify = handle_set_title; 537 xwayland_view->set_title.notify = handle_set_title;
462 538
@@ -467,6 +543,9 @@ void handle_xwayland_surface(struct wl_listener *listener, void *data) {
467 &xwayland_view->set_window_type); 543 &xwayland_view->set_window_type);
468 xwayland_view->set_window_type.notify = handle_set_window_type; 544 xwayland_view->set_window_type.notify = handle_set_window_type;
469 545
546 wl_signal_add(&xsurface->events.set_hints, &xwayland_view->set_hints);
547 xwayland_view->set_hints.notify = handle_set_hints;
548
470 wl_signal_add(&xsurface->events.unmap, &xwayland_view->unmap); 549 wl_signal_add(&xsurface->events.unmap, &xwayland_view->unmap);
471 xwayland_view->unmap.notify = handle_unmap; 550 xwayland_view->unmap.notify = handle_unmap;
472 551
@@ -484,7 +563,7 @@ void handle_xwayland_ready(struct wl_listener *listener, void *data) {
484 xcb_connection_t *xcb_conn = xcb_connect(NULL, NULL); 563 xcb_connection_t *xcb_conn = xcb_connect(NULL, NULL);
485 int err = xcb_connection_has_error(xcb_conn); 564 int err = xcb_connection_has_error(xcb_conn);
486 if (err) { 565 if (err) {
487 wlr_log(L_ERROR, "XCB connect failed: %d", err); 566 wlr_log(WLR_ERROR, "XCB connect failed: %d", err);
488 return; 567 return;
489 } 568 }
490 569
@@ -503,7 +582,7 @@ void handle_xwayland_ready(struct wl_listener *listener, void *data) {
503 free(reply); 582 free(reply);
504 583
505 if (error != NULL) { 584 if (error != NULL) {
506 wlr_log(L_ERROR, "could not resolve atom %s, X11 error code %d", 585 wlr_log(WLR_ERROR, "could not resolve atom %s, X11 error code %d",
507 atom_map[i], error->error_code); 586 atom_map[i], error->error_code);
508 free(error); 587 free(error);
509 break; 588 break;
diff --git a/sway/input/cursor.c b/sway/input/cursor.c
index 944e35aa..65d04cac 100644
--- a/sway/input/cursor.c
+++ b/sway/input/cursor.c
@@ -5,14 +5,19 @@
5#elif __FreeBSD__ 5#elif __FreeBSD__
6#include <dev/evdev/input-event-codes.h> 6#include <dev/evdev/input-event-codes.h>
7#endif 7#endif
8#include <limits.h>
8#include <wlr/types/wlr_cursor.h> 9#include <wlr/types/wlr_cursor.h>
9#include <wlr/types/wlr_xcursor_manager.h> 10#include <wlr/types/wlr_xcursor_manager.h>
10#include <wlr/types/wlr_idle.h> 11#include <wlr/types/wlr_idle.h>
11#include "list.h" 12#include "list.h"
12#include "log.h" 13#include "log.h"
14#include "sway/desktop.h"
15#include "sway/desktop/transaction.h"
13#include "sway/input/cursor.h" 16#include "sway/input/cursor.h"
17#include "sway/input/keyboard.h"
14#include "sway/layers.h" 18#include "sway/layers.h"
15#include "sway/output.h" 19#include "sway/output.h"
20#include "sway/tree/arrange.h"
16#include "sway/tree/view.h" 21#include "sway/tree/view.h"
17#include "sway/tree/workspace.h" 22#include "sway/tree/workspace.h"
18#include "wlr-layer-shell-unstable-v1-protocol.h" 23#include "wlr-layer-shell-unstable-v1-protocol.h"
@@ -126,7 +131,7 @@ static struct sway_container *container_at_coords(
126 return ws; 131 return ws;
127 } 132 }
128 133
129 c = seat_get_focus_inactive(seat, output->swayc); 134 c = seat_get_active_child(seat, output->swayc);
130 if (c) { 135 if (c) {
131 return c; 136 return c;
132 } 137 }
@@ -138,6 +143,171 @@ static struct sway_container *container_at_coords(
138 return output->swayc; 143 return output->swayc;
139} 144}
140 145
146static enum wlr_edges find_resize_edge(struct sway_container *cont,
147 struct sway_cursor *cursor) {
148 if (cont->type != C_VIEW) {
149 return WLR_EDGE_NONE;
150 }
151 struct sway_view *view = cont->sway_view;
152 if (view->border == B_NONE || !view->border_thickness || view->using_csd) {
153 return WLR_EDGE_NONE;
154 }
155
156 enum wlr_edges edge = 0;
157 if (cursor->cursor->x < cont->x + view->border_thickness) {
158 edge |= WLR_EDGE_LEFT;
159 }
160 if (cursor->cursor->y < cont->y + view->border_thickness) {
161 edge |= WLR_EDGE_TOP;
162 }
163 if (cursor->cursor->x >= cont->x + cont->width - view->border_thickness) {
164 edge |= WLR_EDGE_RIGHT;
165 }
166 if (cursor->cursor->y >= cont->y + cont->height - view->border_thickness) {
167 edge |= WLR_EDGE_BOTTOM;
168 }
169 return edge;
170}
171
172static void handle_move_motion(struct sway_seat *seat,
173 struct sway_cursor *cursor) {
174 struct sway_container *con = seat->op_container;
175 desktop_damage_whole_container(con);
176 container_floating_translate(con,
177 cursor->cursor->x - cursor->previous.x,
178 cursor->cursor->y - cursor->previous.y);
179 desktop_damage_whole_container(con);
180}
181
182static void calculate_floating_constraints(struct sway_container *con,
183 int *min_width, int *max_width, int *min_height, int *max_height) {
184 if (config->floating_minimum_width == -1) { // no minimum
185 *min_width = 0;
186 } else if (config->floating_minimum_width == 0) { // automatic
187 *min_width = 75;
188 } else {
189 *min_width = config->floating_minimum_width;
190 }
191
192 if (config->floating_minimum_height == -1) { // no minimum
193 *min_height = 0;
194 } else if (config->floating_minimum_height == 0) { // automatic
195 *min_height = 50;
196 } else {
197 *min_height = config->floating_minimum_height;
198 }
199
200 if (config->floating_maximum_width == -1) { // no maximum
201 *max_width = INT_MAX;
202 } else if (config->floating_maximum_width == 0) { // automatic
203 struct sway_container *ws = container_parent(con, C_WORKSPACE);
204 *max_width = ws->width;
205 } else {
206 *max_width = config->floating_maximum_width;
207 }
208
209 if (config->floating_maximum_height == -1) { // no maximum
210 *max_height = INT_MAX;
211 } else if (config->floating_maximum_height == 0) { // automatic
212 struct sway_container *ws = container_parent(con, C_WORKSPACE);
213 *max_height = ws->height;
214 } else {
215 *max_height = config->floating_maximum_height;
216 }
217}
218
219static void handle_resize_motion(struct sway_seat *seat,
220 struct sway_cursor *cursor) {
221 struct sway_container *con = seat->op_container;
222 enum wlr_edges edge = seat->op_resize_edge;
223
224 // The amount the mouse has moved since the start of the resize operation
225 // Positive is down/right
226 double mouse_move_x = cursor->cursor->x - seat->op_ref_lx;
227 double mouse_move_y = cursor->cursor->y - seat->op_ref_ly;
228
229 if (edge == WLR_EDGE_TOP || edge == WLR_EDGE_BOTTOM) {
230 mouse_move_x = 0;
231 }
232 if (edge == WLR_EDGE_LEFT || edge == WLR_EDGE_RIGHT) {
233 mouse_move_y = 0;
234 }
235
236 double grow_width = edge & WLR_EDGE_LEFT ? -mouse_move_x : mouse_move_x;
237 double grow_height = edge & WLR_EDGE_TOP ? -mouse_move_y : mouse_move_y;
238
239 if (seat->op_resize_preserve_ratio) {
240 double x_multiplier = grow_width / seat->op_ref_width;
241 double y_multiplier = grow_height / seat->op_ref_height;
242 double max_multiplier = fmax(x_multiplier, y_multiplier);
243 grow_width = seat->op_ref_width * max_multiplier;
244 grow_height = seat->op_ref_height * max_multiplier;
245 }
246
247 // Determine new width/height, and accommodate for floating min/max values
248 double width = seat->op_ref_width + grow_width;
249 double height = seat->op_ref_height + grow_height;
250 int min_width, max_width, min_height, max_height;
251 calculate_floating_constraints(con, &min_width, &max_width,
252 &min_height, &max_height);
253 width = fmax(min_width, fmin(width, max_width));
254 height = fmax(min_height, fmin(height, max_height));
255
256 // Apply the view's min/max size
257 if (con->type == C_VIEW) {
258 double view_min_width, view_max_width, view_min_height, view_max_height;
259 view_get_constraints(con->sway_view, &view_min_width, &view_max_width,
260 &view_min_height, &view_max_height);
261 width = fmax(view_min_width, fmin(width, view_max_width));
262 height = fmax(view_min_height, fmin(height, view_max_height));
263 }
264
265 // Recalculate these, in case we hit a min/max limit
266 grow_width = width - seat->op_ref_width;
267 grow_height = height - seat->op_ref_height;
268
269 // Determine grow x/y values - these are relative to the container's x/y at
270 // the start of the resize operation.
271 double grow_x = 0, grow_y = 0;
272 if (edge & WLR_EDGE_LEFT) {
273 grow_x = -grow_width;
274 } else if (edge & WLR_EDGE_RIGHT) {
275 grow_x = 0;
276 } else {
277 grow_x = -grow_width / 2;
278 }
279 if (edge & WLR_EDGE_TOP) {
280 grow_y = -grow_height;
281 } else if (edge & WLR_EDGE_BOTTOM) {
282 grow_y = 0;
283 } else {
284 grow_y = -grow_height / 2;
285 }
286
287 // Determine the amounts we need to bump everything relative to the current
288 // size.
289 int relative_grow_width = width - con->width;
290 int relative_grow_height = height - con->height;
291 int relative_grow_x = (seat->op_ref_con_lx + grow_x) - con->x;
292 int relative_grow_y = (seat->op_ref_con_ly + grow_y) - con->y;
293
294 // Actually resize stuff
295 con->x += relative_grow_x;
296 con->y += relative_grow_y;
297 con->width += relative_grow_width;
298 con->height += relative_grow_height;
299
300 if (con->type == C_VIEW) {
301 struct sway_view *view = con->sway_view;
302 view->x += relative_grow_x;
303 view->y += relative_grow_y;
304 view->width += relative_grow_width;
305 view->height += relative_grow_height;
306 }
307
308 arrange_windows(con);
309}
310
141void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec, 311void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec,
142 bool allow_refocusing) { 312 bool allow_refocusing) {
143 if (time_msec == 0) { 313 if (time_msec == 0) {
@@ -145,6 +315,18 @@ void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec,
145 } 315 }
146 316
147 struct sway_seat *seat = cursor->seat; 317 struct sway_seat *seat = cursor->seat;
318
319 if (seat->operation != OP_NONE) {
320 if (seat->operation == OP_MOVE) {
321 handle_move_motion(seat, cursor);
322 } else {
323 handle_resize_motion(seat, cursor);
324 }
325 cursor->previous.x = cursor->cursor->x;
326 cursor->previous.y = cursor->cursor->y;
327 return;
328 }
329
148 struct wlr_seat *wlr_seat = seat->wlr_seat; 330 struct wlr_seat *wlr_seat = seat->wlr_seat;
149 struct wlr_surface *surface = NULL; 331 struct wlr_surface *surface = NULL;
150 double sx, sy; 332 double sx, sy;
@@ -193,15 +375,21 @@ void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec,
193 } 375 }
194 } 376 }
195 377
196 // reset cursor if switching between clients 378 // Handle cursor image
197 struct wl_client *client = NULL; 379 if (surface) {
198 if (surface != NULL) { 380 // Reset cursor if switching between clients
199 client = wl_resource_get_client(surface->resource); 381 struct wl_client *client = wl_resource_get_client(surface->resource);
200 } 382 if (client != cursor->image_client) {
201 if (client != cursor->image_client) { 383 cursor_set_image(cursor, "left_ptr", client);
202 wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager, 384 }
203 "left_ptr", cursor->cursor); 385 } else if (c && container_is_floating(c)) {
204 cursor->image_client = client; 386 // Try a floating container's resize edge
387 enum wlr_edges edge = find_resize_edge(c, cursor);
388 const char *image = edge == WLR_EDGE_NONE ?
389 "left_ptr" : wlr_xcursor_get_resize_name(edge);
390 cursor_set_image(cursor, image, NULL);
391 } else {
392 cursor_set_image(cursor, "left_ptr", NULL);
205 } 393 }
206 394
207 // send pointer enter/leave 395 // send pointer enter/leave
@@ -228,6 +416,7 @@ static void handle_cursor_motion(struct wl_listener *listener, void *data) {
228 wlr_cursor_move(cursor->cursor, event->device, 416 wlr_cursor_move(cursor->cursor, event->device,
229 event->delta_x, event->delta_y); 417 event->delta_x, event->delta_y);
230 cursor_send_pointer_motion(cursor, event->time_msec, true); 418 cursor_send_pointer_motion(cursor, event->time_msec, true);
419 transaction_commit_dirty();
231} 420}
232 421
233static void handle_cursor_motion_absolute( 422static void handle_cursor_motion_absolute(
@@ -238,10 +427,56 @@ static void handle_cursor_motion_absolute(
238 struct wlr_event_pointer_motion_absolute *event = data; 427 struct wlr_event_pointer_motion_absolute *event = data;
239 wlr_cursor_warp_absolute(cursor->cursor, event->device, event->x, event->y); 428 wlr_cursor_warp_absolute(cursor->cursor, event->device, event->x, event->y);
240 cursor_send_pointer_motion(cursor, event->time_msec, true); 429 cursor_send_pointer_motion(cursor, event->time_msec, true);
430 transaction_commit_dirty();
431}
432
433static void dispatch_cursor_button_floating(struct sway_cursor *cursor,
434 uint32_t time_msec, uint32_t button, enum wlr_button_state state,
435 struct wlr_surface *surface, double sx, double sy,
436 struct sway_container *cont) {
437 struct sway_seat *seat = cursor->seat;
438
439 // Deny moving or resizing a fullscreen view
440 if (cont->type == C_VIEW && cont->sway_view->is_fullscreen) {
441 seat_pointer_notify_button(seat, time_msec, button, state);
442 return;
443 }
444
445 struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->wlr_seat);
446 bool mod_pressed = keyboard &&
447 (wlr_keyboard_get_modifiers(keyboard) & config->floating_mod);
448 enum wlr_edges edge = find_resize_edge(cont, cursor);
449 bool over_title = edge == WLR_EDGE_NONE && !surface;
450
451 // Check for beginning move
452 if (button == BTN_LEFT && state == WLR_BUTTON_PRESSED &&
453 (mod_pressed || over_title)) {
454 seat_begin_move(seat, cont, BTN_LEFT);
455 return;
456 }
457
458 // Check for beginning resize
459 bool resizing_via_border = button == BTN_LEFT && edge != WLR_EDGE_NONE;
460 bool resizing_via_mod = button == BTN_RIGHT && mod_pressed;
461 if ((resizing_via_border || resizing_via_mod) &&
462 state == WLR_BUTTON_PRESSED) {
463 seat_begin_resize(seat, cont, button, edge);
464 return;
465 }
466
467 // Send event to surface
468 seat_set_focus(seat, cont);
469 seat_pointer_notify_button(seat, time_msec, button, state);
241} 470}
242 471
243void dispatch_cursor_button(struct sway_cursor *cursor, 472void dispatch_cursor_button(struct sway_cursor *cursor,
244 uint32_t time_msec, uint32_t button, enum wlr_button_state state) { 473 uint32_t time_msec, uint32_t button, enum wlr_button_state state) {
474 if (cursor->seat->operation != OP_NONE &&
475 button == cursor->seat->op_button && state == WLR_BUTTON_RELEASED) {
476 seat_end_mouse_operation(cursor->seat);
477 seat_pointer_notify_button(cursor->seat, time_msec, button, state);
478 return;
479 }
245 if (time_msec == 0) { 480 if (time_msec == 0) {
246 time_msec = get_current_time_msec(); 481 time_msec = get_current_time_msec();
247 } 482 }
@@ -255,14 +490,16 @@ void dispatch_cursor_button(struct sway_cursor *cursor,
255 wlr_layer_surface_from_wlr_surface(surface); 490 wlr_layer_surface_from_wlr_surface(surface);
256 if (layer->current.keyboard_interactive) { 491 if (layer->current.keyboard_interactive) {
257 seat_set_focus_layer(cursor->seat, layer); 492 seat_set_focus_layer(cursor->seat, layer);
258 return;
259 } 493 }
260 } 494 seat_pointer_notify_button(cursor->seat, time_msec, button, state);
261 // Avoid moving keyboard focus from a surface that accepts it to one 495 } else if (cont && container_is_floating(cont)) {
262 // that does not unless the change would move us to a new workspace. 496 dispatch_cursor_button_floating(cursor, time_msec, button, state,
263 // 497 surface, sx, sy, cont);
264 // This prevents, for example, losing focus when clicking on swaybar. 498 } else if (surface && cont && cont->type != C_VIEW) {
265 if (surface && cont && cont->type != C_VIEW) { 499 // Avoid moving keyboard focus from a surface that accepts it to one
500 // that does not unless the change would move us to a new workspace.
501 //
502 // This prevents, for example, losing focus when clicking on swaybar.
266 struct sway_container *new_ws = cont; 503 struct sway_container *new_ws = cont;
267 if (new_ws && new_ws->type != C_WORKSPACE) { 504 if (new_ws && new_ws->type != C_WORKSPACE) {
268 new_ws = container_parent(new_ws, C_WORKSPACE); 505 new_ws = container_parent(new_ws, C_WORKSPACE);
@@ -274,12 +511,15 @@ void dispatch_cursor_button(struct sway_cursor *cursor,
274 if (new_ws != old_ws) { 511 if (new_ws != old_ws) {
275 seat_set_focus(cursor->seat, cont); 512 seat_set_focus(cursor->seat, cont);
276 } 513 }
514 seat_pointer_notify_button(cursor->seat, time_msec, button, state);
277 } else if (cont) { 515 } else if (cont) {
278 seat_set_focus(cursor->seat, cont); 516 seat_set_focus(cursor->seat, cont);
517 seat_pointer_notify_button(cursor->seat, time_msec, button, state);
518 } else {
519 seat_pointer_notify_button(cursor->seat, time_msec, button, state);
279 } 520 }
280 521
281 wlr_seat_pointer_notify_button(cursor->seat->wlr_seat, 522 transaction_commit_dirty();
282 time_msec, button, state);
283} 523}
284 524
285static void handle_cursor_button(struct wl_listener *listener, void *data) { 525static void handle_cursor_button(struct wl_listener *listener, void *data) {
@@ -425,6 +665,7 @@ static void handle_tool_axis(struct wl_listener *listener, void *data) {
425 665
426 wlr_cursor_warp_absolute(cursor->cursor, event->device, x, y); 666 wlr_cursor_warp_absolute(cursor->cursor, event->device, x, y);
427 cursor_send_pointer_motion(cursor, event->time_msec, true); 667 cursor_send_pointer_motion(cursor, event->time_msec, true);
668 transaction_commit_dirty();
428} 669}
429 670
430static void handle_tool_tip(struct wl_listener *listener, void *data) { 671static void handle_tool_tip(struct wl_listener *listener, void *data) {
@@ -464,6 +705,9 @@ static void handle_request_set_cursor(struct wl_listener *listener,
464 void *data) { 705 void *data) {
465 struct sway_cursor *cursor = 706 struct sway_cursor *cursor =
466 wl_container_of(listener, cursor, request_set_cursor); 707 wl_container_of(listener, cursor, request_set_cursor);
708 if (cursor->seat->operation != OP_NONE) {
709 return;
710 }
467 struct wlr_seat_pointer_request_set_cursor_event *event = data; 711 struct wlr_seat_pointer_request_set_cursor_event *event = data;
468 712
469 struct wl_client *focused_client = NULL; 713 struct wl_client *focused_client = NULL;
@@ -476,15 +720,26 @@ static void handle_request_set_cursor(struct wl_listener *listener,
476 // TODO: check cursor mode 720 // TODO: check cursor mode
477 if (focused_client == NULL || 721 if (focused_client == NULL ||
478 event->seat_client->client != focused_client) { 722 event->seat_client->client != focused_client) {
479 wlr_log(L_DEBUG, "denying request to set cursor from unfocused client"); 723 wlr_log(WLR_DEBUG, "denying request to set cursor from unfocused client");
480 return; 724 return;
481 } 725 }
482 726
483 wlr_cursor_set_surface(cursor->cursor, event->surface, event->hotspot_x, 727 wlr_cursor_set_surface(cursor->cursor, event->surface, event->hotspot_x,
484 event->hotspot_y); 728 event->hotspot_y);
729 cursor->image = NULL;
485 cursor->image_client = focused_client; 730 cursor->image_client = focused_client;
486} 731}
487 732
733void cursor_set_image(struct sway_cursor *cursor, const char *image,
734 struct wl_client *client) {
735 if (!cursor->image || strcmp(cursor->image, image) != 0) {
736 wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager, image,
737 cursor->cursor);
738 cursor->image = image;
739 }
740 cursor->image_client = client;
741}
742
488void sway_cursor_destroy(struct sway_cursor *cursor) { 743void sway_cursor_destroy(struct sway_cursor *cursor) {
489 if (!cursor) { 744 if (!cursor) {
490 return; 745 return;
diff --git a/sway/input/input-manager.c b/sway/input/input-manager.c
index 29b47a7b..0b7cb766 100644
--- a/sway/input/input-manager.c
+++ b/sway/input/input-manager.c
@@ -61,7 +61,7 @@ static char *get_device_identifier(struct wlr_input_device *device) {
61 int len = snprintf(NULL, 0, fmt, vendor, product, name) + 1; 61 int len = snprintf(NULL, 0, fmt, vendor, product, name) + 1;
62 char *identifier = malloc(len); 62 char *identifier = malloc(len);
63 if (!identifier) { 63 if (!identifier) {
64 wlr_log(L_ERROR, "Unable to allocate unique input device name"); 64 wlr_log(WLR_ERROR, "Unable to allocate unique input device name");
65 return NULL; 65 return NULL;
66 } 66 }
67 67
@@ -104,77 +104,89 @@ static void input_manager_libinput_config_pointer(
104 } 104 }
105 105
106 libinput_device = wlr_libinput_get_device_handle(wlr_device); 106 libinput_device = wlr_libinput_get_device_handle(wlr_device);
107 wlr_log(L_DEBUG, "input_manager_libinput_config_pointer(%s)", 107 wlr_log(WLR_DEBUG, "input_manager_libinput_config_pointer(%s)",
108 ic->identifier); 108 ic->identifier);
109 109
110 if (ic->accel_profile != INT_MIN) { 110 if (ic->accel_profile != INT_MIN) {
111 wlr_log(L_DEBUG, "libinput_config_pointer(%s) accel_set_profile(%d)", 111 wlr_log(WLR_DEBUG, "libinput_config_pointer(%s) accel_set_profile(%d)",
112 ic->identifier, ic->accel_profile); 112 ic->identifier, ic->accel_profile);
113 libinput_device_config_accel_set_profile(libinput_device, 113 libinput_device_config_accel_set_profile(libinput_device,
114 ic->accel_profile); 114 ic->accel_profile);
115 } 115 }
116 if (ic->click_method != INT_MIN) { 116 if (ic->click_method != INT_MIN) {
117 wlr_log(L_DEBUG, "libinput_config_pointer(%s) click_set_method(%d)", 117 wlr_log(WLR_DEBUG, "libinput_config_pointer(%s) click_set_method(%d)",
118 ic->identifier, ic->click_method); 118 ic->identifier, ic->click_method);
119 libinput_device_config_click_set_method(libinput_device, 119 libinput_device_config_click_set_method(libinput_device,
120 ic->click_method); 120 ic->click_method);
121 } 121 }
122 if (ic->drag_lock != INT_MIN) { 122 if (ic->drag_lock != INT_MIN) {
123 wlr_log(L_DEBUG, 123 wlr_log(WLR_DEBUG,
124 "libinput_config_pointer(%s) tap_set_drag_lock_enabled(%d)", 124 "libinput_config_pointer(%s) tap_set_drag_lock_enabled(%d)",
125 ic->identifier, ic->click_method); 125 ic->identifier, ic->click_method);
126 libinput_device_config_tap_set_drag_lock_enabled(libinput_device, 126 libinput_device_config_tap_set_drag_lock_enabled(libinput_device,
127 ic->drag_lock); 127 ic->drag_lock);
128 } 128 }
129 if (ic->dwt != INT_MIN) { 129 if (ic->dwt != INT_MIN) {
130 wlr_log(L_DEBUG, "libinput_config_pointer(%s) dwt_set_enabled(%d)", 130 wlr_log(WLR_DEBUG, "libinput_config_pointer(%s) dwt_set_enabled(%d)",
131 ic->identifier, ic->dwt); 131 ic->identifier, ic->dwt);
132 libinput_device_config_dwt_set_enabled(libinput_device, ic->dwt); 132 libinput_device_config_dwt_set_enabled(libinput_device, ic->dwt);
133 } 133 }
134 if (ic->left_handed != INT_MIN) { 134 if (ic->left_handed != INT_MIN) {
135 wlr_log(L_DEBUG, 135 wlr_log(WLR_DEBUG,
136 "libinput_config_pointer(%s) left_handed_set_enabled(%d)", 136 "libinput_config_pointer(%s) left_handed_set_enabled(%d)",
137 ic->identifier, ic->left_handed); 137 ic->identifier, ic->left_handed);
138 libinput_device_config_left_handed_set(libinput_device, 138 libinput_device_config_left_handed_set(libinput_device,
139 ic->left_handed); 139 ic->left_handed);
140 } 140 }
141 if (ic->middle_emulation != INT_MIN) { 141 if (ic->middle_emulation != INT_MIN) {
142 wlr_log(L_DEBUG, 142 wlr_log(WLR_DEBUG,
143 "libinput_config_pointer(%s) middle_emulation_set_enabled(%d)", 143 "libinput_config_pointer(%s) middle_emulation_set_enabled(%d)",
144 ic->identifier, ic->middle_emulation); 144 ic->identifier, ic->middle_emulation);
145 libinput_device_config_middle_emulation_set_enabled(libinput_device, 145 libinput_device_config_middle_emulation_set_enabled(libinput_device,
146 ic->middle_emulation); 146 ic->middle_emulation);
147 } 147 }
148 if (ic->natural_scroll != INT_MIN) { 148 if (ic->natural_scroll != INT_MIN) {
149 wlr_log(L_DEBUG, 149 wlr_log(WLR_DEBUG,
150 "libinput_config_pointer(%s) natural_scroll_set_enabled(%d)", 150 "libinput_config_pointer(%s) natural_scroll_set_enabled(%d)",
151 ic->identifier, ic->natural_scroll); 151 ic->identifier, ic->natural_scroll);
152 libinput_device_config_scroll_set_natural_scroll_enabled( 152 libinput_device_config_scroll_set_natural_scroll_enabled(
153 libinput_device, ic->natural_scroll); 153 libinput_device, ic->natural_scroll);
154 } 154 }
155 if (ic->pointer_accel != FLT_MIN) { 155 if (ic->pointer_accel != FLT_MIN) {
156 wlr_log(L_DEBUG, "libinput_config_pointer(%s) accel_set_speed(%f)", 156 wlr_log(WLR_DEBUG, "libinput_config_pointer(%s) accel_set_speed(%f)",
157 ic->identifier, ic->pointer_accel); 157 ic->identifier, ic->pointer_accel);
158 libinput_device_config_accel_set_speed(libinput_device, 158 libinput_device_config_accel_set_speed(libinput_device,
159 ic->pointer_accel); 159 ic->pointer_accel);
160 } 160 }
161 if (ic->scroll_button != INT_MIN) {
162 wlr_log(WLR_DEBUG, "libinput_config_pointer(%s) scroll_set_button(%d)",
163 ic->identifier, ic->scroll_button);
164 libinput_device_config_scroll_set_button(libinput_device,
165 ic->scroll_button);
166 }
161 if (ic->scroll_method != INT_MIN) { 167 if (ic->scroll_method != INT_MIN) {
162 wlr_log(L_DEBUG, "libinput_config_pointer(%s) scroll_set_method(%d)", 168 wlr_log(WLR_DEBUG, "libinput_config_pointer(%s) scroll_set_method(%d)",
163 ic->identifier, ic->scroll_method); 169 ic->identifier, ic->scroll_method);
164 libinput_device_config_scroll_set_method(libinput_device, 170 libinput_device_config_scroll_set_method(libinput_device,
165 ic->scroll_method); 171 ic->scroll_method);
166 } 172 }
167 if (ic->send_events != INT_MIN) { 173 if (ic->send_events != INT_MIN) {
168 wlr_log(L_DEBUG, "libinput_config_pointer(%s) send_events_set_mode(%d)", 174 wlr_log(WLR_DEBUG, "libinput_config_pointer(%s) send_events_set_mode(%d)",
169 ic->identifier, ic->send_events); 175 ic->identifier, ic->send_events);
170 libinput_device_config_send_events_set_mode(libinput_device, 176 libinput_device_config_send_events_set_mode(libinput_device,
171 ic->send_events); 177 ic->send_events);
172 } 178 }
173 if (ic->tap != INT_MIN) { 179 if (ic->tap != INT_MIN) {
174 wlr_log(L_DEBUG, "libinput_config_pointer(%s) tap_set_enabled(%d)", 180 wlr_log(WLR_DEBUG, "libinput_config_pointer(%s) tap_set_enabled(%d)",
175 ic->identifier, ic->tap); 181 ic->identifier, ic->tap);
176 libinput_device_config_tap_set_enabled(libinput_device, ic->tap); 182 libinput_device_config_tap_set_enabled(libinput_device, ic->tap);
177 } 183 }
184 if (ic->tap_button_map != INT_MIN) {
185 wlr_log(WLR_DEBUG, "libinput_config_pointer(%s) tap_set_button_map(%d)",
186 ic->identifier, ic->tap);
187 libinput_device_config_tap_set_button_map(libinput_device,
188 ic->tap_button_map);
189 }
178} 190}
179 191
180static void handle_device_destroy(struct wl_listener *listener, void *data) { 192static void handle_device_destroy(struct wl_listener *listener, void *data) {
@@ -187,7 +199,7 @@ static void handle_device_destroy(struct wl_listener *listener, void *data) {
187 return; 199 return;
188 } 200 }
189 201
190 wlr_log(L_DEBUG, "removing device: '%s'", 202 wlr_log(WLR_DEBUG, "removing device: '%s'",
191 input_device->identifier); 203 input_device->identifier);
192 204
193 struct sway_seat *seat = NULL; 205 struct sway_seat *seat = NULL;
@@ -217,16 +229,19 @@ static void handle_new_input(struct wl_listener *listener, void *data) {
217 input_device->identifier = get_device_identifier(device); 229 input_device->identifier = get_device_identifier(device);
218 wl_list_insert(&input->devices, &input_device->link); 230 wl_list_insert(&input->devices, &input_device->link);
219 231
220 wlr_log(L_DEBUG, "adding device: '%s'", 232 wlr_log(WLR_DEBUG, "adding device: '%s'",
221 input_device->identifier); 233 input_device->identifier);
222 234
223 if (input_device->wlr_device->type == WLR_INPUT_DEVICE_POINTER) { 235 if (input_device->wlr_device->type == WLR_INPUT_DEVICE_POINTER) {
224 input_manager_libinput_config_pointer(input_device); 236 input_manager_libinput_config_pointer(input_device);
225 } 237 }
226 238
239 wl_signal_add(&device->events.destroy, &input_device->device_destroy);
240 input_device->device_destroy.notify = handle_device_destroy;
241
227 struct sway_seat *seat = NULL; 242 struct sway_seat *seat = NULL;
228 if (!input_has_seat_configuration(input)) { 243 if (!input_has_seat_configuration(input)) {
229 wlr_log(L_DEBUG, "no seat configuration, using default seat"); 244 wlr_log(WLR_DEBUG, "no seat configuration, using default seat");
230 seat = input_manager_get_seat(input, default_seat); 245 seat = input_manager_get_seat(input, default_seat);
231 seat_add_device(seat, input_device); 246 seat_add_device(seat, input_device);
232 return; 247 return;
@@ -256,13 +271,10 @@ static void handle_new_input(struct wl_listener *listener, void *data) {
256 } 271 }
257 272
258 if (!added) { 273 if (!added) {
259 wlr_log(L_DEBUG, 274 wlr_log(WLR_DEBUG,
260 "device '%s' is not configured on any seats", 275 "device '%s' is not configured on any seats",
261 input_device->identifier); 276 input_device->identifier);
262 } 277 }
263
264 wl_signal_add(&device->events.destroy, &input_device->device_destroy);
265 input_device->device_destroy.notify = handle_device_destroy;
266} 278}
267 279
268static void handle_inhibit_activate(struct wl_listener *listener, void *data) { 280static void handle_inhibit_activate(struct wl_listener *listener, void *data) {
@@ -282,7 +294,7 @@ static void handle_inhibit_deactivate(struct wl_listener *listener, void *data)
282 seat_set_exclusive_client(seat, NULL); 294 seat_set_exclusive_client(seat, NULL);
283 struct sway_container *previous = seat_get_focus(seat); 295 struct sway_container *previous = seat_get_focus(seat);
284 if (previous) { 296 if (previous) {
285 wlr_log(L_DEBUG, "Returning focus to %p %s '%s'", previous, 297 wlr_log(WLR_DEBUG, "Returning focus to %p %s '%s'", previous,
286 container_type_to_str(previous->type), previous->name); 298 container_type_to_str(previous->type), previous->name);
287 // Hack to get seat to re-focus the return value of get_focus 299 // Hack to get seat to re-focus the return value of get_focus
288 seat_set_focus(seat, previous->parent); 300 seat_set_focus(seat, previous->parent);
@@ -359,7 +371,7 @@ void input_manager_apply_input_config(struct sway_input_manager *input,
359 371
360void input_manager_apply_seat_config(struct sway_input_manager *input, 372void input_manager_apply_seat_config(struct sway_input_manager *input,
361 struct seat_config *seat_config) { 373 struct seat_config *seat_config) {
362 wlr_log(L_DEBUG, "applying new seat config for seat %s", 374 wlr_log(WLR_DEBUG, "applying new seat config for seat %s",
363 seat_config->name); 375 seat_config->name);
364 struct sway_seat *seat = input_manager_get_seat(input, seat_config->name); 376 struct sway_seat *seat = input_manager_get_seat(input, seat_config->name);
365 if (!seat) { 377 if (!seat) {
diff --git a/sway/input/keyboard.c b/sway/input/keyboard.c
index ec149d06..ede38519 100644
--- a/sway/input/keyboard.c
+++ b/sway/input/keyboard.c
@@ -3,6 +3,7 @@
3#include <wlr/backend/multi.h> 3#include <wlr/backend/multi.h>
4#include <wlr/backend/session.h> 4#include <wlr/backend/session.h>
5#include <wlr/types/wlr_idle.h> 5#include <wlr/types/wlr_idle.h>
6#include "sway/desktop/transaction.h"
6#include "sway/input/seat.h" 7#include "sway/input/seat.h"
7#include "sway/input/keyboard.h" 8#include "sway/input/keyboard.h"
8#include "sway/input/input-manager.h" 9#include "sway/input/input-manager.h"
@@ -108,7 +109,7 @@ static void get_active_binding(const struct sway_shortcut_state *state,
108 } 109 }
109 110
110 if (*current_binding && *current_binding != binding) { 111 if (*current_binding && *current_binding != binding) {
111 wlr_log(L_DEBUG, "encountered duplicate bindings %d and %d", 112 wlr_log(WLR_DEBUG, "encountered duplicate bindings %d and %d",
112 (*current_binding)->order, binding->order); 113 (*current_binding)->order, binding->order);
113 } else { 114 } else {
114 *current_binding = binding; 115 *current_binding = binding;
@@ -122,12 +123,13 @@ static void get_active_binding(const struct sway_shortcut_state *state,
122 */ 123 */
123static void keyboard_execute_command(struct sway_keyboard *keyboard, 124static void keyboard_execute_command(struct sway_keyboard *keyboard,
124 struct sway_binding *binding) { 125 struct sway_binding *binding) {
125 wlr_log(L_DEBUG, "running command for binding: %s", 126 wlr_log(WLR_DEBUG, "running command for binding: %s",
126 binding->command); 127 binding->command);
127 config->handler_context.seat = keyboard->seat_device->sway_seat; 128 config->handler_context.seat = keyboard->seat_device->sway_seat;
128 struct cmd_results *results = execute_command(binding->command, NULL); 129 struct cmd_results *results = execute_command(binding->command, NULL);
130 transaction_commit_dirty();
129 if (results->status != CMD_SUCCESS) { 131 if (results->status != CMD_SUCCESS) {
130 wlr_log(L_DEBUG, "could not run command for binding: %s (%s)", 132 wlr_log(WLR_DEBUG, "could not run command for binding: %s (%s)",
131 binding->command, results->error); 133 binding->command, results->error);
132 } 134 }
133 free_cmd_results(results); 135 free_cmd_results(results);
@@ -386,7 +388,7 @@ void sway_keyboard_configure(struct sway_keyboard *keyboard) {
386 xkb_keymap_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); 388 xkb_keymap_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS);
387 389
388 if (!keymap) { 390 if (!keymap) {
389 wlr_log(L_DEBUG, "cannot configure keyboard: keymap does not exist"); 391 wlr_log(WLR_DEBUG, "cannot configure keyboard: keymap does not exist");
390 xkb_context_unref(context); 392 xkb_context_unref(context);
391 return; 393 return;
392 } 394 }
@@ -420,6 +422,9 @@ void sway_keyboard_destroy(struct sway_keyboard *keyboard) {
420 if (!keyboard) { 422 if (!keyboard) {
421 return; 423 return;
422 } 424 }
425 if (keyboard->keymap) {
426 xkb_keymap_unref(keyboard->keymap);
427 }
423 wl_list_remove(&keyboard->keyboard_key.link); 428 wl_list_remove(&keyboard->keyboard_key.link);
424 wl_list_remove(&keyboard->keyboard_modifiers.link); 429 wl_list_remove(&keyboard->keyboard_modifiers.link);
425 free(keyboard); 430 free(keyboard);
diff --git a/sway/input/seat.c b/sway/input/seat.c
index 2c2087da..fc9e54b6 100644
--- a/sway/input/seat.c
+++ b/sway/input/seat.c
@@ -1,6 +1,11 @@
1#define _XOPEN_SOURCE 700 1#define _XOPEN_SOURCE 700
2#define _POSIX_C_SOURCE 199309L 2#define _POSIX_C_SOURCE 199309L
3#include <assert.h> 3#include <assert.h>
4#ifdef __linux__
5#include <linux/input-event-codes.h>
6#elif __FreeBSD__
7#include <dev/evdev/input-event-codes.h>
8#endif
4#include <strings.h> 9#include <strings.h>
5#include <time.h> 10#include <time.h>
6#include <wlr/types/wlr_cursor.h> 11#include <wlr/types/wlr_cursor.h>
@@ -75,7 +80,7 @@ static void seat_send_activate(struct sway_container *con,
75 struct sway_seat *seat) { 80 struct sway_seat *seat) {
76 if (con->type == C_VIEW) { 81 if (con->type == C_VIEW) {
77 if (!seat_is_input_allowed(seat, con->sway_view->surface)) { 82 if (!seat_is_input_allowed(seat, con->sway_view->surface)) {
78 wlr_log(L_DEBUG, "Refusing to set focus, input is inhibited"); 83 wlr_log(WLR_DEBUG, "Refusing to set focus, input is inhibited");
79 return; 84 return;
80 } 85 }
81 view_set_activated(con->sway_view, true); 86 view_set_activated(con->sway_view, true);
@@ -219,7 +224,7 @@ static struct sway_seat_container *seat_container_from_container(
219 224
220 seat_con = calloc(1, sizeof(struct sway_seat_container)); 225 seat_con = calloc(1, sizeof(struct sway_seat_container));
221 if (seat_con == NULL) { 226 if (seat_con == NULL) {
222 wlr_log(L_ERROR, "could not allocate seat container"); 227 wlr_log(WLR_ERROR, "could not allocate seat container");
223 return NULL; 228 return NULL;
224 } 229 }
225 230
@@ -301,7 +306,7 @@ static void handle_new_drag_icon(struct wl_listener *listener, void *data) {
301 306
302 struct sway_drag_icon *icon = calloc(1, sizeof(struct sway_drag_icon)); 307 struct sway_drag_icon *icon = calloc(1, sizeof(struct sway_drag_icon));
303 if (icon == NULL) { 308 if (icon == NULL) {
304 wlr_log(L_ERROR, "Allocation failed"); 309 wlr_log(WLR_ERROR, "Allocation failed");
305 return; 310 return;
306 } 311 }
307 icon->seat = seat; 312 icon->seat = seat;
@@ -348,6 +353,7 @@ struct sway_seat *seat_create(struct sway_input_manager *input,
348 free(seat); 353 free(seat);
349 return NULL; 354 return NULL;
350 } 355 }
356 seat->wlr_seat->data = seat;
351 357
352 seat->cursor = sway_cursor_create(seat); 358 seat->cursor = sway_cursor_create(seat);
353 if (!seat->cursor) { 359 if (!seat->cursor) {
@@ -391,7 +397,7 @@ static void seat_apply_input_config(struct sway_seat *seat,
391 struct input_config *ic = input_device_get_config( 397 struct input_config *ic = input_device_get_config(
392 sway_device->input_device); 398 sway_device->input_device);
393 if (ic != NULL) { 399 if (ic != NULL) {
394 wlr_log(L_DEBUG, "Applying input config to %s", 400 wlr_log(WLR_DEBUG, "Applying input config to %s",
395 sway_device->input_device->identifier); 401 sway_device->input_device->identifier);
396 402
397 mapped_to_output = ic->mapped_to_output; 403 mapped_to_output = ic->mapped_to_output;
@@ -401,7 +407,7 @@ static void seat_apply_input_config(struct sway_seat *seat,
401 mapped_to_output = sway_device->input_device->wlr_device->output_name; 407 mapped_to_output = sway_device->input_device->wlr_device->output_name;
402 } 408 }
403 if (mapped_to_output != NULL) { 409 if (mapped_to_output != NULL) {
404 wlr_log(L_DEBUG, "Mapping input device %s to output %s", 410 wlr_log(WLR_DEBUG, "Mapping input device %s to output %s",
405 sway_device->input_device->identifier, mapped_to_output); 411 sway_device->input_device->identifier, mapped_to_output);
406 struct sway_container *output = NULL; 412 struct sway_container *output = NULL;
407 for (int i = 0; i < root_container.children->length; ++i) { 413 for (int i = 0; i < root_container.children->length; ++i) {
@@ -415,7 +421,7 @@ static void seat_apply_input_config(struct sway_seat *seat,
415 wlr_cursor_map_input_to_output(seat->cursor->cursor, 421 wlr_cursor_map_input_to_output(seat->cursor->cursor,
416 sway_device->input_device->wlr_device, 422 sway_device->input_device->wlr_device,
417 output->sway_output->wlr_output); 423 output->sway_output->wlr_output);
418 wlr_log(L_DEBUG, "Mapped to output %s", output->name); 424 wlr_log(WLR_DEBUG, "Mapped to output %s", output->name);
419 } 425 }
420 } 426 }
421} 427}
@@ -495,7 +501,7 @@ void seat_configure_device(struct sway_seat *seat,
495 seat_configure_tablet_tool(seat, seat_device); 501 seat_configure_tablet_tool(seat, seat_device);
496 break; 502 break;
497 case WLR_INPUT_DEVICE_TABLET_PAD: 503 case WLR_INPUT_DEVICE_TABLET_PAD:
498 wlr_log(L_DEBUG, "TODO: configure tablet pad"); 504 wlr_log(WLR_DEBUG, "TODO: configure tablet pad");
499 break; 505 break;
500 } 506 }
501} 507}
@@ -510,11 +516,11 @@ void seat_add_device(struct sway_seat *seat,
510 struct sway_seat_device *seat_device = 516 struct sway_seat_device *seat_device =
511 calloc(1, sizeof(struct sway_seat_device)); 517 calloc(1, sizeof(struct sway_seat_device));
512 if (!seat_device) { 518 if (!seat_device) {
513 wlr_log(L_DEBUG, "could not allocate seat device"); 519 wlr_log(WLR_DEBUG, "could not allocate seat device");
514 return; 520 return;
515 } 521 }
516 522
517 wlr_log(L_DEBUG, "adding device %s to seat %s", 523 wlr_log(WLR_DEBUG, "adding device %s to seat %s",
518 input_device->identifier, seat->wlr_seat->name); 524 input_device->identifier, seat->wlr_seat->name);
519 525
520 seat_device->sway_seat = seat; 526 seat_device->sway_seat = seat;
@@ -533,7 +539,7 @@ void seat_remove_device(struct sway_seat *seat,
533 return; 539 return;
534 } 540 }
535 541
536 wlr_log(L_DEBUG, "removing device %s from seat %s", 542 wlr_log(WLR_DEBUG, "removing device %s from seat %s",
537 input_device->identifier, seat->wlr_seat->name); 543 input_device->identifier, seat->wlr_seat->name);
538 544
539 seat_device_destroy(seat_device); 545 seat_device_destroy(seat_device);
@@ -594,6 +600,12 @@ static void seat_send_unfocus(struct sway_container *container,
594 } 600 }
595} 601}
596 602
603static int handle_urgent_timeout(void *data) {
604 struct sway_view *view = data;
605 view_set_urgent(view, false);
606 return 0;
607}
608
597void seat_set_focus_warp(struct sway_seat *seat, 609void seat_set_focus_warp(struct sway_seat *seat,
598 struct sway_container *container, bool warp) { 610 struct sway_container *container, bool warp) {
599 if (seat->focused_layer) { 611 if (seat->focused_layer) {
@@ -616,6 +628,7 @@ void seat_set_focus_warp(struct sway_seat *seat,
616 628
617 if (last_workspace && last_workspace == new_workspace 629 if (last_workspace && last_workspace == new_workspace
618 && last_workspace->sway_workspace->fullscreen 630 && last_workspace->sway_workspace->fullscreen
631 && container && container->type == C_VIEW
619 && !container->sway_view->is_fullscreen) { 632 && !container->sway_view->is_fullscreen) {
620 return; 633 return;
621 } 634 }
@@ -649,6 +662,7 @@ void seat_set_focus_warp(struct sway_seat *seat,
649 while (parent) { 662 while (parent) {
650 wl_list_remove(&parent->link); 663 wl_list_remove(&parent->link);
651 wl_list_insert(&seat->focus_stack, &parent->link); 664 wl_list_insert(&seat->focus_stack, &parent->link);
665 container_set_dirty(parent->container);
652 666
653 parent = 667 parent =
654 seat_container_from_container(seat, 668 seat_container_from_container(seat,
@@ -661,9 +675,39 @@ void seat_set_focus_warp(struct sway_seat *seat,
661 if (last_focus) { 675 if (last_focus) {
662 seat_send_unfocus(last_focus, seat); 676 seat_send_unfocus(last_focus, seat);
663 } 677 }
664
665 seat_send_focus(container, seat); 678 seat_send_focus(container, seat);
666 container_damage_whole(container); 679
680 container_set_dirty(container);
681 container_set_dirty(container->parent); // for focused_inactive_child
682 if (last_focus) {
683 container_set_dirty(last_focus);
684 }
685 }
686
687 // If urgent, either unset the urgency or start a timer to unset it
688 if (container && container->type == C_VIEW &&
689 view_is_urgent(container->sway_view) &&
690 !container->sway_view->urgent_timer) {
691 struct sway_view *view = container->sway_view;
692 if (last_workspace && last_workspace != new_workspace &&
693 config->urgent_timeout > 0) {
694 view->urgent_timer = wl_event_loop_add_timer(server.wl_event_loop,
695 handle_urgent_timeout, view);
696 wl_event_source_timer_update(view->urgent_timer,
697 config->urgent_timeout);
698 } else {
699 view_set_urgent(view, false);
700 }
701 }
702
703 // If we've focused a floating container, bring it to the front.
704 // We do this by putting it at the end of the floating list.
705 // This must happen for both the pending and current children lists.
706 if (container && container_is_floating(container)) {
707 list_move_to_end(container->parent->children, container);
708 if (container_has_ancestor(container, container->current.parent)) {
709 list_move_to_end(container->parent->current.children, container);
710 }
667 } 711 }
668 712
669 // clean up unfocused empty workspace on new output 713 // clean up unfocused empty workspace on new output
@@ -707,11 +751,7 @@ void seat_set_focus_warp(struct sway_seat *seat,
707 } 751 }
708 } 752 }
709 753
710 if (last_focus) { 754 if (last_focus != NULL) {
711 container_damage_whole(last_focus);
712 }
713
714 if (last_workspace && last_workspace != new_workspace) {
715 cursor_send_pointer_motion(seat->cursor, 0, true); 755 cursor_send_pointer_motion(seat->cursor, 0, true);
716 } 756 }
717 757
@@ -726,11 +766,11 @@ void seat_set_focus(struct sway_seat *seat,
726} 766}
727 767
728void seat_set_focus_surface(struct sway_seat *seat, 768void seat_set_focus_surface(struct sway_seat *seat,
729 struct wlr_surface *surface) { 769 struct wlr_surface *surface, bool unfocus) {
730 if (seat->focused_layer != NULL) { 770 if (seat->focused_layer != NULL) {
731 return; 771 return;
732 } 772 }
733 if (seat->has_focus) { 773 if (seat->has_focus && unfocus) {
734 struct sway_container *focus = seat_get_focus(seat); 774 struct sway_container *focus = seat_get_focus(seat);
735 seat_send_unfocus(focus, seat); 775 seat_send_unfocus(focus, seat);
736 seat->has_focus = false; 776 seat->has_focus = false;
@@ -752,7 +792,7 @@ void seat_set_focus_layer(struct sway_seat *seat,
752 struct sway_container *previous = 792 struct sway_container *previous =
753 seat_get_focus_inactive(seat, &root_container); 793 seat_get_focus_inactive(seat, &root_container);
754 if (previous) { 794 if (previous) {
755 wlr_log(L_DEBUG, "Returning focus to %p %s '%s'", previous, 795 wlr_log(WLR_DEBUG, "Returning focus to %p %s '%s'", previous,
756 container_type_to_str(previous->type), previous->name); 796 container_type_to_str(previous->type), previous->name);
757 // Hack to get seat to re-focus the return value of get_focus 797 // Hack to get seat to re-focus the return value of get_focus
758 seat_set_focus(seat, previous->parent); 798 seat_set_focus(seat, previous->parent);
@@ -762,7 +802,7 @@ void seat_set_focus_layer(struct sway_seat *seat,
762 } else if (!layer || seat->focused_layer == layer) { 802 } else if (!layer || seat->focused_layer == layer) {
763 return; 803 return;
764 } 804 }
765 seat_set_focus_surface(seat, layer->surface); 805 seat_set_focus_surface(seat, layer->surface, true);
766 if (layer->layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) { 806 if (layer->layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) {
767 seat->focused_layer = layer; 807 seat->focused_layer = layer;
768 } 808 }
@@ -830,18 +870,6 @@ struct sway_container *seat_get_active_child(struct sway_seat *seat,
830 return NULL; 870 return NULL;
831} 871}
832 872
833struct sway_container *seat_get_active_current_child(struct sway_seat *seat,
834 struct sway_container *container) {
835 struct sway_container *child = seat_get_active_child(seat, container);
836 if (child) {
837 return child;
838 }
839 if (container->current.children->length == 1) {
840 return container->current.children->items[0];
841 }
842 return NULL;
843}
844
845struct sway_container *seat_get_focus(struct sway_seat *seat) { 873struct sway_container *seat_get_focus(struct sway_seat *seat) {
846 if (!seat->has_focus) { 874 if (!seat->has_focus) {
847 return NULL; 875 return NULL;
@@ -873,3 +901,68 @@ struct seat_config *seat_get_config(struct sway_seat *seat) {
873 901
874 return NULL; 902 return NULL;
875} 903}
904
905void seat_begin_move(struct sway_seat *seat, struct sway_container *con,
906 uint32_t button) {
907 if (!seat->cursor) {
908 wlr_log(WLR_DEBUG, "Ignoring move request due to no cursor device");
909 return;
910 }
911 seat->operation = OP_MOVE;
912 seat->op_container = con;
913 seat->op_button = button;
914 cursor_set_image(seat->cursor, "grab", NULL);
915}
916
917void seat_begin_resize(struct sway_seat *seat, struct sway_container *con,
918 uint32_t button, enum wlr_edges edge) {
919 if (!seat->cursor) {
920 wlr_log(WLR_DEBUG, "Ignoring resize request due to no cursor device");
921 return;
922 }
923 struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->wlr_seat);
924 seat->operation = OP_RESIZE;
925 seat->op_container = con;
926 seat->op_resize_preserve_ratio = keyboard &&
927 (wlr_keyboard_get_modifiers(keyboard) & WLR_MODIFIER_SHIFT);
928 seat->op_resize_edge = edge == WLR_EDGE_NONE ?
929 RESIZE_EDGE_BOTTOM | RESIZE_EDGE_RIGHT : edge;
930 seat->op_button = button;
931 seat->op_ref_lx = seat->cursor->cursor->x;
932 seat->op_ref_ly = seat->cursor->cursor->y;
933 seat->op_ref_con_lx = con->x;
934 seat->op_ref_con_ly = con->y;
935 seat->op_ref_width = con->width;
936 seat->op_ref_height = con->height;
937
938 const char *image = edge == WLR_EDGE_NONE ?
939 "se-resize" : wlr_xcursor_get_resize_name(edge);
940 cursor_set_image(seat->cursor, image, NULL);
941}
942
943void seat_end_mouse_operation(struct sway_seat *seat) {
944 switch (seat->operation) {
945 case OP_MOVE:
946 {
947 // We "move" the container to its own location so it discovers its
948 // output again.
949 struct sway_container *con = seat->op_container;
950 container_floating_move_to(con, con->x, con->y);
951 }
952 case OP_RESIZE:
953 // Don't need to do anything here.
954 break;
955 case OP_NONE:
956 break;
957 }
958 seat->operation = OP_NONE;
959 seat->op_container = NULL;
960 cursor_set_image(seat->cursor, "left_ptr", NULL);
961}
962
963void seat_pointer_notify_button(struct sway_seat *seat, uint32_t time_msec,
964 uint32_t button, enum wlr_button_state state) {
965 seat->last_button = button;
966 seat->last_button_serial = wlr_seat_pointer_notify_button(seat->wlr_seat,
967 time_msec, button, state);
968}
diff --git a/sway/ipc-json.c b/sway/ipc-json.c
index b9289e25..c49ea47e 100644
--- a/sway/ipc-json.c
+++ b/sway/ipc-json.c
@@ -2,6 +2,7 @@
2#include <stdio.h> 2#include <stdio.h>
3#include <ctype.h> 3#include <ctype.h>
4#include "log.h" 4#include "log.h"
5#include "sway/config.h"
5#include "sway/ipc-json.h" 6#include "sway/ipc-json.h"
6#include "sway/tree/container.h" 7#include "sway/tree/container.h"
7#include "sway/tree/workspace.h" 8#include "sway/tree/workspace.h"
@@ -41,6 +42,7 @@ json_object *ipc_json_get_version() {
41 json_object_object_add(version, "major", json_object_new_int(major)); 42 json_object_object_add(version, "major", json_object_new_int(major));
42 json_object_object_add(version, "minor", json_object_new_int(minor)); 43 json_object_object_add(version, "minor", json_object_new_int(minor));
43 json_object_object_add(version, "patch", json_object_new_int(patch)); 44 json_object_object_add(version, "patch", json_object_new_int(patch));
45 json_object_object_add(version, "loaded_config_file_name", json_object_new_string(config->current_config_path));
44 46
45 return version; 47 return version;
46} 48}
@@ -168,7 +170,8 @@ static void ipc_json_describe_workspace(struct sway_container *workspace,
168 json_object_object_add(object, "output", workspace->parent ? 170 json_object_object_add(object, "output", workspace->parent ?
169 json_object_new_string(workspace->parent->name) : NULL); 171 json_object_new_string(workspace->parent->name) : NULL);
170 json_object_object_add(object, "type", json_object_new_string("workspace")); 172 json_object_object_add(object, "type", json_object_new_string("workspace"));
171 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));
172 json_object_object_add(object, "representation", workspace->formatted_title ? 175 json_object_object_add(object, "representation", workspace->formatted_title ?
173 json_object_new_string(workspace->formatted_title) : NULL); 176 json_object_new_string(workspace->formatted_title) : NULL);
174 177
@@ -194,6 +197,10 @@ static void ipc_json_describe_view(struct sway_container *c, json_object *object
194 json_object_object_add(object, "layout", 197 json_object_object_add(object, "layout",
195 json_object_new_string(ipc_json_layout_description(c->layout))); 198 json_object_new_string(ipc_json_layout_description(c->layout)));
196 } 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));
197} 204}
198 205
199static 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/ipc-server.c b/sway/ipc-server.c
index 241fe742..be703915 100644
--- a/sway/ipc-server.c
+++ b/sway/ipc-server.c
@@ -17,6 +17,8 @@
17#include <unistd.h> 17#include <unistd.h>
18#include <wayland-server.h> 18#include <wayland-server.h>
19#include "sway/commands.h" 19#include "sway/commands.h"
20#include "sway/config.h"
21#include "sway/desktop/transaction.h"
20#include "sway/ipc-json.h" 22#include "sway/ipc-json.h"
21#include "sway/ipc-server.h" 23#include "sway/ipc-server.h"
22#include "sway/output.h" 24#include "sway/output.h"
@@ -31,6 +33,7 @@ static int ipc_socket = -1;
31static struct wl_event_source *ipc_event_source = NULL; 33static struct wl_event_source *ipc_event_source = NULL;
32static struct sockaddr_un *ipc_sockaddr = NULL; 34static struct sockaddr_un *ipc_sockaddr = NULL;
33static list_t *ipc_client_list = NULL; 35static list_t *ipc_client_list = NULL;
36static struct wl_listener ipc_display_destroy;
34 37
35static const char ipc_magic[] = {'i', '3', '-', 'i', 'p', 'c'}; 38static const char ipc_magic[] = {'i', '3', '-', 'i', 'p', 'c'};
36 39
@@ -56,6 +59,26 @@ void ipc_client_disconnect(struct ipc_client *client);
56void ipc_client_handle_command(struct ipc_client *client); 59void ipc_client_handle_command(struct ipc_client *client);
57bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t payload_length); 60bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t payload_length);
58 61
62static void handle_display_destroy(struct wl_listener *listener, void *data) {
63 if (ipc_event_source) {
64 wl_event_source_remove(ipc_event_source);
65 }
66 close(ipc_socket);
67 unlink(ipc_sockaddr->sun_path);
68
69 while (ipc_client_list->length) {
70 struct ipc_client *client = ipc_client_list->items[0];
71 ipc_client_disconnect(client);
72 }
73 list_free(ipc_client_list);
74
75 if (ipc_sockaddr) {
76 free(ipc_sockaddr);
77 }
78
79 wl_list_remove(&ipc_display_destroy.link);
80}
81
59void ipc_init(struct sway_server *server) { 82void ipc_init(struct sway_server *server) {
60 ipc_socket = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); 83 ipc_socket = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
61 if (ipc_socket == -1) { 84 if (ipc_socket == -1) {
@@ -85,24 +108,13 @@ void ipc_init(struct sway_server *server) {
85 108
86 ipc_client_list = create_list(); 109 ipc_client_list = create_list();
87 110
111 ipc_display_destroy.notify = handle_display_destroy;
112 wl_display_add_destroy_listener(server->wl_display, &ipc_display_destroy);
113
88 ipc_event_source = wl_event_loop_add_fd(server->wl_event_loop, ipc_socket, 114 ipc_event_source = wl_event_loop_add_fd(server->wl_event_loop, ipc_socket,
89 WL_EVENT_READABLE, ipc_handle_connection, server); 115 WL_EVENT_READABLE, ipc_handle_connection, server);
90} 116}
91 117
92void ipc_terminate(void) {
93 if (ipc_event_source) {
94 wl_event_source_remove(ipc_event_source);
95 }
96 close(ipc_socket);
97 unlink(ipc_sockaddr->sun_path);
98
99 list_free(ipc_client_list);
100
101 if (ipc_sockaddr) {
102 free(ipc_sockaddr);
103 }
104}
105
106struct sockaddr_un *ipc_user_sockaddr(void) { 118struct sockaddr_un *ipc_user_sockaddr(void) {
107 struct sockaddr_un *ipc_sockaddr = malloc(sizeof(struct sockaddr_un)); 119 struct sockaddr_un *ipc_sockaddr = malloc(sizeof(struct sockaddr_un));
108 if (ipc_sockaddr == NULL) { 120 if (ipc_sockaddr == NULL) {
@@ -128,32 +140,32 @@ struct sockaddr_un *ipc_user_sockaddr(void) {
128int ipc_handle_connection(int fd, uint32_t mask, void *data) { 140int ipc_handle_connection(int fd, uint32_t mask, void *data) {
129 (void) fd; 141 (void) fd;
130 struct sway_server *server = data; 142 struct sway_server *server = data;
131 wlr_log(L_DEBUG, "Event on IPC listening socket"); 143 wlr_log(WLR_DEBUG, "Event on IPC listening socket");
132 assert(mask == WL_EVENT_READABLE); 144 assert(mask == WL_EVENT_READABLE);
133 145
134 int client_fd = accept(ipc_socket, NULL, NULL); 146 int client_fd = accept(ipc_socket, NULL, NULL);
135 if (client_fd == -1) { 147 if (client_fd == -1) {
136 wlr_log_errno(L_ERROR, "Unable to accept IPC client connection"); 148 wlr_log_errno(WLR_ERROR, "Unable to accept IPC client connection");
137 return 0; 149 return 0;
138 } 150 }
139 151
140 int flags; 152 int flags;
141 if ((flags = fcntl(client_fd, F_GETFD)) == -1 153 if ((flags = fcntl(client_fd, F_GETFD)) == -1
142 || fcntl(client_fd, F_SETFD, flags|FD_CLOEXEC) == -1) { 154 || fcntl(client_fd, F_SETFD, flags|FD_CLOEXEC) == -1) {
143 wlr_log_errno(L_ERROR, "Unable to set CLOEXEC on IPC client socket"); 155 wlr_log_errno(WLR_ERROR, "Unable to set CLOEXEC on IPC client socket");
144 close(client_fd); 156 close(client_fd);
145 return 0; 157 return 0;
146 } 158 }
147 if ((flags = fcntl(client_fd, F_GETFL)) == -1 159 if ((flags = fcntl(client_fd, F_GETFL)) == -1
148 || fcntl(client_fd, F_SETFL, flags|O_NONBLOCK) == -1) { 160 || fcntl(client_fd, F_SETFL, flags|O_NONBLOCK) == -1) {
149 wlr_log_errno(L_ERROR, "Unable to set NONBLOCK on IPC client socket"); 161 wlr_log_errno(WLR_ERROR, "Unable to set NONBLOCK on IPC client socket");
150 close(client_fd); 162 close(client_fd);
151 return 0; 163 return 0;
152 } 164 }
153 165
154 struct ipc_client *client = malloc(sizeof(struct ipc_client)); 166 struct ipc_client *client = malloc(sizeof(struct ipc_client));
155 if (!client) { 167 if (!client) {
156 wlr_log(L_ERROR, "Unable to allocate ipc client"); 168 wlr_log(WLR_ERROR, "Unable to allocate ipc client");
157 close(client_fd); 169 close(client_fd);
158 return 0; 170 return 0;
159 } 171 }
@@ -169,12 +181,12 @@ int ipc_handle_connection(int fd, uint32_t mask, void *data) {
169 client->write_buffer_len = 0; 181 client->write_buffer_len = 0;
170 client->write_buffer = malloc(client->write_buffer_size); 182 client->write_buffer = malloc(client->write_buffer_size);
171 if (!client->write_buffer) { 183 if (!client->write_buffer) {
172 wlr_log(L_ERROR, "Unable to allocate ipc client write buffer"); 184 wlr_log(WLR_ERROR, "Unable to allocate ipc client write buffer");
173 close(client_fd); 185 close(client_fd);
174 return 0; 186 return 0;
175 } 187 }
176 188
177 wlr_log(L_DEBUG, "New client: fd %d", client_fd); 189 wlr_log(WLR_DEBUG, "New client: fd %d", client_fd);
178 list_add(ipc_client_list, client); 190 list_add(ipc_client_list, client);
179 return 0; 191 return 0;
180} 192}
@@ -185,22 +197,22 @@ int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data) {
185 struct ipc_client *client = data; 197 struct ipc_client *client = data;
186 198
187 if (mask & WL_EVENT_ERROR) { 199 if (mask & WL_EVENT_ERROR) {
188 wlr_log(L_ERROR, "IPC Client socket error, removing client"); 200 wlr_log(WLR_ERROR, "IPC Client socket error, removing client");
189 ipc_client_disconnect(client); 201 ipc_client_disconnect(client);
190 return 0; 202 return 0;
191 } 203 }
192 204
193 if (mask & WL_EVENT_HANGUP) { 205 if (mask & WL_EVENT_HANGUP) {
194 wlr_log(L_DEBUG, "Client %d hung up", client->fd); 206 wlr_log(WLR_DEBUG, "Client %d hung up", client->fd);
195 ipc_client_disconnect(client); 207 ipc_client_disconnect(client);
196 return 0; 208 return 0;
197 } 209 }
198 210
199 wlr_log(L_DEBUG, "Client %d readable", client->fd); 211 wlr_log(WLR_DEBUG, "Client %d readable", client->fd);
200 212
201 int read_available; 213 int read_available;
202 if (ioctl(client_fd, FIONREAD, &read_available) == -1) { 214 if (ioctl(client_fd, FIONREAD, &read_available) == -1) {
203 wlr_log_errno(L_INFO, "Unable to read IPC socket buffer size"); 215 wlr_log_errno(WLR_INFO, "Unable to read IPC socket buffer size");
204 ipc_client_disconnect(client); 216 ipc_client_disconnect(client);
205 return 0; 217 return 0;
206 } 218 }
@@ -222,13 +234,13 @@ int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data) {
222 // Should be fully available, because read_available >= ipc_header_size 234 // Should be fully available, because read_available >= ipc_header_size
223 ssize_t received = recv(client_fd, buf, ipc_header_size, 0); 235 ssize_t received = recv(client_fd, buf, ipc_header_size, 0);
224 if (received == -1) { 236 if (received == -1) {
225 wlr_log_errno(L_INFO, "Unable to receive header from IPC client"); 237 wlr_log_errno(WLR_INFO, "Unable to receive header from IPC client");
226 ipc_client_disconnect(client); 238 ipc_client_disconnect(client);
227 return 0; 239 return 0;
228 } 240 }
229 241
230 if (memcmp(buf, ipc_magic, sizeof(ipc_magic)) != 0) { 242 if (memcmp(buf, ipc_magic, sizeof(ipc_magic)) != 0) {
231 wlr_log(L_DEBUG, "IPC header check failed"); 243 wlr_log(WLR_DEBUG, "IPC header check failed");
232 ipc_client_disconnect(client); 244 ipc_client_disconnect(client);
233 return 0; 245 return 0;
234 } 246 }
@@ -262,8 +274,11 @@ static void ipc_send_event(const char *json_string, enum ipc_command_type event)
262 } 274 }
263 client->current_command = event; 275 client->current_command = event;
264 if (!ipc_send_reply(client, json_string, (uint32_t) strlen(json_string))) { 276 if (!ipc_send_reply(client, json_string, (uint32_t) strlen(json_string))) {
265 wlr_log_errno(L_INFO, "Unable to send reply to IPC client"); 277 wlr_log_errno(WLR_INFO, "Unable to send reply to IPC client");
266 ipc_client_disconnect(client); 278 /* ipc_send_reply destroys client on error, which also
279 * removes it from the list, so we need to process
280 * current index again */
281 i--;
267 } 282 }
268 } 283 }
269} 284}
@@ -273,7 +288,7 @@ void ipc_event_workspace(struct sway_container *old,
273 if (!ipc_has_event_listeners(IPC_EVENT_WORKSPACE)) { 288 if (!ipc_has_event_listeners(IPC_EVENT_WORKSPACE)) {
274 return; 289 return;
275 } 290 }
276 wlr_log(L_DEBUG, "Sending workspace::%s event", change); 291 wlr_log(WLR_DEBUG, "Sending workspace::%s event", change);
277 json_object *obj = json_object_new_object(); 292 json_object *obj = json_object_new_object();
278 json_object_object_add(obj, "change", json_object_new_string(change)); 293 json_object_object_add(obj, "change", json_object_new_string(change));
279 if (strcmp("focus", change) == 0) { 294 if (strcmp("focus", change) == 0) {
@@ -301,7 +316,7 @@ void ipc_event_window(struct sway_container *window, const char *change) {
301 if (!ipc_has_event_listeners(IPC_EVENT_WINDOW)) { 316 if (!ipc_has_event_listeners(IPC_EVENT_WINDOW)) {
302 return; 317 return;
303 } 318 }
304 wlr_log(L_DEBUG, "Sending window::%s event", change); 319 wlr_log(WLR_DEBUG, "Sending window::%s event", change);
305 json_object *obj = json_object_new_object(); 320 json_object *obj = json_object_new_object();
306 json_object_object_add(obj, "change", json_object_new_string(change)); 321 json_object_object_add(obj, "change", json_object_new_string(change));
307 json_object_object_add(obj, "container", ipc_json_describe_container_recursive(window)); 322 json_object_object_add(obj, "container", ipc_json_describe_container_recursive(window));
@@ -315,7 +330,7 @@ void ipc_event_barconfig_update(struct bar_config *bar) {
315 if (!ipc_has_event_listeners(IPC_EVENT_BARCONFIG_UPDATE)) { 330 if (!ipc_has_event_listeners(IPC_EVENT_BARCONFIG_UPDATE)) {
316 return; 331 return;
317 } 332 }
318 wlr_log(L_DEBUG, "Sending barconfig_update event"); 333 wlr_log(WLR_DEBUG, "Sending barconfig_update event");
319 json_object *json = ipc_json_describe_bar_config(bar); 334 json_object *json = ipc_json_describe_bar_config(bar);
320 335
321 const char *json_string = json_object_to_json_string(json); 336 const char *json_string = json_object_to_json_string(json);
@@ -323,13 +338,15 @@ void ipc_event_barconfig_update(struct bar_config *bar) {
323 json_object_put(json); 338 json_object_put(json);
324} 339}
325 340
326void ipc_event_mode(const char *mode) { 341void ipc_event_mode(const char *mode, bool pango) {
327 if (!ipc_has_event_listeners(IPC_EVENT_MODE)) { 342 if (!ipc_has_event_listeners(IPC_EVENT_MODE)) {
328 return; 343 return;
329 } 344 }
330 wlr_log(L_DEBUG, "Sending mode::%s event", mode); 345 wlr_log(WLR_DEBUG, "Sending mode::%s event", mode);
331 json_object *obj = json_object_new_object(); 346 json_object *obj = json_object_new_object();
332 json_object_object_add(obj, "change", json_object_new_string(mode)); 347 json_object_object_add(obj, "change", json_object_new_string(mode));
348 json_object_object_add(obj, "pango_markup",
349 json_object_new_boolean(pango));
333 350
334 const char *json_string = json_object_to_json_string(obj); 351 const char *json_string = json_object_to_json_string(obj);
335 ipc_send_event(json_string, IPC_EVENT_MODE); 352 ipc_send_event(json_string, IPC_EVENT_MODE);
@@ -340,13 +357,13 @@ int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data) {
340 struct ipc_client *client = data; 357 struct ipc_client *client = data;
341 358
342 if (mask & WL_EVENT_ERROR) { 359 if (mask & WL_EVENT_ERROR) {
343 wlr_log(L_ERROR, "IPC Client socket error, removing client"); 360 wlr_log(WLR_ERROR, "IPC Client socket error, removing client");
344 ipc_client_disconnect(client); 361 ipc_client_disconnect(client);
345 return 0; 362 return 0;
346 } 363 }
347 364
348 if (mask & WL_EVENT_HANGUP) { 365 if (mask & WL_EVENT_HANGUP) {
349 wlr_log(L_DEBUG, "Client %d hung up", client->fd); 366 wlr_log(WLR_DEBUG, "Client %d hung up", client->fd);
350 ipc_client_disconnect(client); 367 ipc_client_disconnect(client);
351 return 0; 368 return 0;
352 } 369 }
@@ -355,14 +372,14 @@ int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data) {
355 return 0; 372 return 0;
356 } 373 }
357 374
358 wlr_log(L_DEBUG, "Client %d writable", client->fd); 375 wlr_log(WLR_DEBUG, "Client %d writable", client->fd);
359 376
360 ssize_t written = write(client->fd, client->write_buffer, client->write_buffer_len); 377 ssize_t written = write(client->fd, client->write_buffer, client->write_buffer_len);
361 378
362 if (written == -1 && errno == EAGAIN) { 379 if (written == -1 && errno == EAGAIN) {
363 return 0; 380 return 0;
364 } else if (written == -1) { 381 } else if (written == -1) {
365 wlr_log_errno(L_INFO, "Unable to send data from queue to IPC client"); 382 wlr_log_errno(WLR_INFO, "Unable to send data from queue to IPC client");
366 ipc_client_disconnect(client); 383 ipc_client_disconnect(client);
367 return 0; 384 return 0;
368 } 385 }
@@ -383,11 +400,9 @@ void ipc_client_disconnect(struct ipc_client *client) {
383 return; 400 return;
384 } 401 }
385 402
386 if (client->fd != -1) { 403 shutdown(client->fd, SHUT_RDWR);
387 shutdown(client->fd, SHUT_RDWR);
388 }
389 404
390 wlr_log(L_INFO, "IPC Client %d disconnected", client->fd); 405 wlr_log(WLR_INFO, "IPC Client %d disconnected", client->fd);
391 wl_event_source_remove(client->event_source); 406 wl_event_source_remove(client->event_source);
392 if (client->writable_event_source) { 407 if (client->writable_event_source) {
393 wl_event_source_remove(client->writable_event_source); 408 wl_event_source_remove(client->writable_event_source);
@@ -448,7 +463,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
448 463
449 char *buf = malloc(client->payload_length + 1); 464 char *buf = malloc(client->payload_length + 1);
450 if (!buf) { 465 if (!buf) {
451 wlr_log_errno(L_INFO, "Unable to allocate IPC payload"); 466 wlr_log_errno(WLR_INFO, "Unable to allocate IPC payload");
452 ipc_client_disconnect(client); 467 ipc_client_disconnect(client);
453 return; 468 return;
454 } 469 }
@@ -457,7 +472,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
457 ssize_t received = recv(client->fd, buf, client->payload_length, 0); 472 ssize_t received = recv(client->fd, buf, client->payload_length, 0);
458 if (received == -1) 473 if (received == -1)
459 { 474 {
460 wlr_log_errno(L_INFO, "Unable to receive payload from IPC client"); 475 wlr_log_errno(WLR_INFO, "Unable to receive payload from IPC client");
461 ipc_client_disconnect(client); 476 ipc_client_disconnect(client);
462 free(buf); 477 free(buf);
463 return; 478 return;
@@ -465,16 +480,16 @@ void ipc_client_handle_command(struct ipc_client *client) {
465 } 480 }
466 buf[client->payload_length] = '\0'; 481 buf[client->payload_length] = '\0';
467 482
468 const char *error_denied = "{ \"success\": false, \"error\": \"Permission denied\" }"; 483 bool client_valid = true;
469
470 switch (client->current_command) { 484 switch (client->current_command) {
471 case IPC_COMMAND: 485 case IPC_COMMAND:
472 { 486 {
473 struct cmd_results *results = execute_command(buf, NULL); 487 struct cmd_results *results = execute_command(buf, NULL);
474 const char *json = cmd_results_to_json(results); 488 transaction_commit_dirty();
475 char reply[256]; 489 char *json = cmd_results_to_json(results);
476 int length = snprintf(reply, sizeof(reply), "%s", json); 490 int length = strlen(json);
477 ipc_send_reply(client, reply, (uint32_t) length); 491 client_valid = ipc_send_reply(client, json, (uint32_t)length);
492 free(json);
478 free_cmd_results(results); 493 free_cmd_results(results);
479 goto exit_cleanup; 494 goto exit_cleanup;
480 } 495 }
@@ -497,7 +512,8 @@ void ipc_client_handle_command(struct ipc_client *client) {
497 } 512 }
498 } 513 }
499 const char *json_string = json_object_to_json_string(outputs); 514 const char *json_string = json_object_to_json_string(outputs);
500 ipc_send_reply(client, json_string, (uint32_t) strlen(json_string)); 515 client_valid =
516 ipc_send_reply(client, json_string, (uint32_t)strlen(json_string));
501 json_object_put(outputs); // free 517 json_object_put(outputs); // free
502 goto exit_cleanup; 518 goto exit_cleanup;
503 } 519 }
@@ -508,7 +524,8 @@ void ipc_client_handle_command(struct ipc_client *client) {
508 container_for_each_descendant_dfs(&root_container, 524 container_for_each_descendant_dfs(&root_container,
509 ipc_get_workspaces_callback, workspaces); 525 ipc_get_workspaces_callback, workspaces);
510 const char *json_string = json_object_to_json_string(workspaces); 526 const char *json_string = json_object_to_json_string(workspaces);
511 ipc_send_reply(client, json_string, (uint32_t) strlen(json_string)); 527 client_valid =
528 ipc_send_reply(client, json_string, (uint32_t)strlen(json_string));
512 json_object_put(workspaces); // free 529 json_object_put(workspaces); // free
513 goto exit_cleanup; 530 goto exit_cleanup;
514 } 531 }
@@ -518,8 +535,8 @@ void ipc_client_handle_command(struct ipc_client *client) {
518 // TODO: Check if they're permitted to use these events 535 // TODO: Check if they're permitted to use these events
519 struct json_object *request = json_tokener_parse(buf); 536 struct json_object *request = json_tokener_parse(buf);
520 if (request == NULL) { 537 if (request == NULL) {
521 ipc_send_reply(client, "{\"success\": false}", 18); 538 client_valid = ipc_send_reply(client, "{\"success\": false}", 18);
522 wlr_log_errno(L_INFO, "Failed to read request"); 539 wlr_log_errno(WLR_INFO, "Failed to read request");
523 goto exit_cleanup; 540 goto exit_cleanup;
524 } 541 }
525 542
@@ -539,15 +556,16 @@ void ipc_client_handle_command(struct ipc_client *client) {
539 } else if (strcmp(event_type, "binding") == 0) { 556 } else if (strcmp(event_type, "binding") == 0) {
540 client->subscribed_events |= event_mask(IPC_EVENT_BINDING); 557 client->subscribed_events |= event_mask(IPC_EVENT_BINDING);
541 } else { 558 } else {
542 ipc_send_reply(client, "{\"success\": false}", 18); 559 client_valid =
560 ipc_send_reply(client, "{\"success\": false}", 18);
543 json_object_put(request); 561 json_object_put(request);
544 wlr_log_errno(L_INFO, "Failed to parse request"); 562 wlr_log_errno(WLR_INFO, "Failed to parse request");
545 goto exit_cleanup; 563 goto exit_cleanup;
546 } 564 }
547 } 565 }
548 566
549 json_object_put(request); 567 json_object_put(request);
550 ipc_send_reply(client, "{\"success\": true}", 17); 568 client_valid = ipc_send_reply(client, "{\"success\": true}", 17);
551 goto exit_cleanup; 569 goto exit_cleanup;
552 } 570 }
553 571
@@ -559,7 +577,8 @@ void ipc_client_handle_command(struct ipc_client *client) {
559 json_object_array_add(inputs, ipc_json_describe_input(device)); 577 json_object_array_add(inputs, ipc_json_describe_input(device));
560 } 578 }
561 const char *json_string = json_object_to_json_string(inputs); 579 const char *json_string = json_object_to_json_string(inputs);
562 ipc_send_reply(client, json_string, (uint32_t)strlen(json_string)); 580 client_valid =
581 ipc_send_reply(client, json_string, (uint32_t)strlen(json_string));
563 json_object_put(inputs); // free 582 json_object_put(inputs); // free
564 goto exit_cleanup; 583 goto exit_cleanup;
565 } 584 }
@@ -572,7 +591,8 @@ void ipc_client_handle_command(struct ipc_client *client) {
572 json_object_array_add(seats, ipc_json_describe_seat(seat)); 591 json_object_array_add(seats, ipc_json_describe_seat(seat));
573 } 592 }
574 const char *json_string = json_object_to_json_string(seats); 593 const char *json_string = json_object_to_json_string(seats);
575 ipc_send_reply(client, json_string, (uint32_t)strlen(json_string)); 594 client_valid =
595 ipc_send_reply(client, json_string, (uint32_t)strlen(json_string));
576 json_object_put(seats); // free 596 json_object_put(seats); // free
577 goto exit_cleanup; 597 goto exit_cleanup;
578 } 598 }
@@ -582,7 +602,8 @@ void ipc_client_handle_command(struct ipc_client *client) {
582 json_object *tree = 602 json_object *tree =
583 ipc_json_describe_container_recursive(&root_container); 603 ipc_json_describe_container_recursive(&root_container);
584 const char *json_string = json_object_to_json_string(tree); 604 const char *json_string = json_object_to_json_string(tree);
585 ipc_send_reply(client, json_string, (uint32_t) strlen(json_string)); 605 client_valid =
606 ipc_send_reply(client, json_string, (uint32_t) strlen(json_string));
586 json_object_put(tree); 607 json_object_put(tree);
587 goto exit_cleanup; 608 goto exit_cleanup;
588 } 609 }
@@ -593,7 +614,8 @@ void ipc_client_handle_command(struct ipc_client *client) {
593 container_descendants(&root_container, C_VIEW, ipc_get_marks_callback, 614 container_descendants(&root_container, C_VIEW, ipc_get_marks_callback,
594 marks); 615 marks);
595 const char *json_string = json_object_to_json_string(marks); 616 const char *json_string = json_object_to_json_string(marks);
596 ipc_send_reply(client, json_string, (uint32_t)strlen(json_string)); 617 client_valid =
618 ipc_send_reply(client, json_string, (uint32_t)strlen(json_string));
597 json_object_put(marks); 619 json_object_put(marks);
598 goto exit_cleanup; 620 goto exit_cleanup;
599 } 621 }
@@ -602,7 +624,8 @@ void ipc_client_handle_command(struct ipc_client *client) {
602 { 624 {
603 json_object *version = ipc_json_get_version(); 625 json_object *version = ipc_json_get_version();
604 const char *json_string = json_object_to_json_string(version); 626 const char *json_string = json_object_to_json_string(version);
605 ipc_send_reply(client, json_string, (uint32_t)strlen(json_string)); 627 client_valid =
628 ipc_send_reply(client, json_string, (uint32_t)strlen(json_string));
606 json_object_put(version); // free 629 json_object_put(version); // free
607 goto exit_cleanup; 630 goto exit_cleanup;
608 } 631 }
@@ -617,7 +640,9 @@ void ipc_client_handle_command(struct ipc_client *client) {
617 json_object_array_add(bars, json_object_new_string(bar->id)); 640 json_object_array_add(bars, json_object_new_string(bar->id));
618 } 641 }
619 const char *json_string = json_object_to_json_string(bars); 642 const char *json_string = json_object_to_json_string(bars);
620 ipc_send_reply(client, json_string, (uint32_t)strlen(json_string)); 643 client_valid =
644 ipc_send_reply(client, json_string,
645 (uint32_t)strlen(json_string));
621 json_object_put(bars); // free 646 json_object_put(bars); // free
622 } else { 647 } else {
623 // Send particular bar's details 648 // Send particular bar's details
@@ -631,27 +656,54 @@ void ipc_client_handle_command(struct ipc_client *client) {
631 } 656 }
632 if (!bar) { 657 if (!bar) {
633 const char *error = "{ \"success\": false, \"error\": \"No bar with that ID\" }"; 658 const char *error = "{ \"success\": false, \"error\": \"No bar with that ID\" }";
634 ipc_send_reply(client, error, (uint32_t)strlen(error)); 659 client_valid =
660 ipc_send_reply(client, error, (uint32_t)strlen(error));
635 goto exit_cleanup; 661 goto exit_cleanup;
636 } 662 }
637 json_object *json = ipc_json_describe_bar_config(bar); 663 json_object *json = ipc_json_describe_bar_config(bar);
638 const char *json_string = json_object_to_json_string(json); 664 const char *json_string = json_object_to_json_string(json);
639 ipc_send_reply(client, json_string, (uint32_t)strlen(json_string)); 665 client_valid =
666 ipc_send_reply(client, json_string,
667 (uint32_t)strlen(json_string));
640 json_object_put(json); // free 668 json_object_put(json); // free
641 } 669 }
642 goto exit_cleanup; 670 goto exit_cleanup;
643 } 671 }
644 672
645 default: 673 case IPC_GET_BINDING_MODES:
646 wlr_log(L_INFO, "Unknown IPC command type %i", client->current_command); 674 {
675 json_object *modes = json_object_new_array();
676 for (int i = 0; i < config->modes->length; i++) {
677 struct sway_mode *mode = config->modes->items[i];
678 json_object_array_add(modes, json_object_new_string(mode->name));
679 }
680 const char *json_string = json_object_to_json_string(modes);
681 client_valid =
682 ipc_send_reply(client, json_string, (uint32_t)strlen(json_string));
683 json_object_put(modes); // free
647 goto exit_cleanup; 684 goto exit_cleanup;
648 } 685 }
649 686
650 ipc_send_reply(client, error_denied, (uint32_t)strlen(error_denied)); 687 case IPC_GET_CONFIG:
651 wlr_log(L_DEBUG, "Denied IPC client access to %i", client->current_command); 688 {
689 json_object *json = json_object_new_object();
690 json_object_object_add(json, "config", json_object_new_string(config->current_config));
691 const char *json_string = json_object_to_json_string(json);
692 client_valid =
693 ipc_send_reply(client, json_string, (uint32_t)strlen(json_string));
694 json_object_put(json); // free
695 goto exit_cleanup;
696 }
697
698 default:
699 wlr_log(WLR_INFO, "Unknown IPC command type %i", client->current_command);
700 goto exit_cleanup;
701 }
652 702
653exit_cleanup: 703exit_cleanup:
654 client->payload_length = 0; 704 if (client_valid) {
705 client->payload_length = 0;
706 }
655 free(buf); 707 free(buf);
656 return; 708 return;
657} 709}
@@ -672,14 +724,14 @@ bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t pay
672 } 724 }
673 725
674 if (client->write_buffer_size > 4e6) { // 4 MB 726 if (client->write_buffer_size > 4e6) { // 4 MB
675 wlr_log(L_ERROR, "Client write buffer too big, disconnecting client"); 727 wlr_log(WLR_ERROR, "Client write buffer too big, disconnecting client");
676 ipc_client_disconnect(client); 728 ipc_client_disconnect(client);
677 return false; 729 return false;
678 } 730 }
679 731
680 char *new_buffer = realloc(client->write_buffer, client->write_buffer_size); 732 char *new_buffer = realloc(client->write_buffer, client->write_buffer_size);
681 if (!new_buffer) { 733 if (!new_buffer) {
682 wlr_log(L_ERROR, "Unable to reallocate ipc client write buffer"); 734 wlr_log(WLR_ERROR, "Unable to reallocate ipc client write buffer");
683 ipc_client_disconnect(client); 735 ipc_client_disconnect(client);
684 return false; 736 return false;
685 } 737 }
@@ -696,6 +748,6 @@ bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t pay
696 ipc_client_handle_writable, client); 748 ipc_client_handle_writable, client);
697 } 749 }
698 750
699 wlr_log(L_DEBUG, "Added IPC reply to client %d queue: %s", client->fd, payload); 751 wlr_log(WLR_DEBUG, "Added IPC reply to client %d queue: %s", client->fd, payload);
700 return true; 752 return true;
701} 753}
diff --git a/sway/main.c b/sway/main.c
index a325dc3a..a20f1dac 100644
--- a/sway/main.c
+++ b/sway/main.c
@@ -1,6 +1,7 @@
1#define _XOPEN_SOURCE 700 1#define _XOPEN_SOURCE 700
2#define _POSIX_C_SOURCE 200112L 2#define _POSIX_C_SOURCE 200112L
3#include <getopt.h> 3#include <getopt.h>
4#include <pango/pangocairo.h>
4#include <signal.h> 5#include <signal.h>
5#include <stdbool.h> 6#include <stdbool.h>
6#include <stdlib.h> 7#include <stdlib.h>
@@ -19,6 +20,7 @@
19#include "sway/commands.h" 20#include "sway/commands.h"
20#include "sway/config.h" 21#include "sway/config.h"
21#include "sway/debug.h" 22#include "sway/debug.h"
23#include "sway/desktop/transaction.h"
22#include "sway/server.h" 24#include "sway/server.h"
23#include "sway/tree/layout.h" 25#include "sway/tree/layout.h"
24#include "sway/ipc-server.h" 26#include "sway/ipc-server.h"
@@ -128,7 +130,7 @@ static void log_env() {
128 "SWAYSOCK" 130 "SWAYSOCK"
129 }; 131 };
130 for (size_t i = 0; i < sizeof(log_vars) / sizeof(char *); ++i) { 132 for (size_t i = 0; i < sizeof(log_vars) / sizeof(char *); ++i) {
131 wlr_log(L_INFO, "%s=%s", log_vars[i], getenv(log_vars[i])); 133 wlr_log(WLR_INFO, "%s=%s", log_vars[i], getenv(log_vars[i]));
132 } 134 }
133} 135}
134 136
@@ -143,14 +145,14 @@ static void log_distro() {
143 for (size_t i = 0; i < sizeof(paths) / sizeof(char *); ++i) { 145 for (size_t i = 0; i < sizeof(paths) / sizeof(char *); ++i) {
144 FILE *f = fopen(paths[i], "r"); 146 FILE *f = fopen(paths[i], "r");
145 if (f) { 147 if (f) {
146 wlr_log(L_INFO, "Contents of %s:", paths[i]); 148 wlr_log(WLR_INFO, "Contents of %s:", paths[i]);
147 while (!feof(f)) { 149 while (!feof(f)) {
148 char *line; 150 char *line;
149 if (!(line = read_line(f))) { 151 if (!(line = read_line(f))) {
150 break; 152 break;
151 } 153 }
152 if (*line) { 154 if (*line) {
153 wlr_log(L_INFO, "%s", line); 155 wlr_log(WLR_INFO, "%s", line);
154 } 156 }
155 free(line); 157 free(line);
156 } 158 }
@@ -162,7 +164,7 @@ static void log_distro() {
162static void log_kernel() { 164static void log_kernel() {
163 FILE *f = popen("uname -a", "r"); 165 FILE *f = popen("uname -a", "r");
164 if (!f) { 166 if (!f) {
165 wlr_log(L_INFO, "Unable to determine kernel version"); 167 wlr_log(WLR_INFO, "Unable to determine kernel version");
166 return; 168 return;
167 } 169 }
168 while (!feof(f)) { 170 while (!feof(f)) {
@@ -171,25 +173,25 @@ static void log_kernel() {
171 break; 173 break;
172 } 174 }
173 if (*line) { 175 if (*line) {
174 wlr_log(L_INFO, "%s", line); 176 wlr_log(WLR_INFO, "%s", line);
175 } 177 }
176 free(line); 178 free(line);
177 } 179 }
178 fclose(f); 180 pclose(f);
179} 181}
180 182
181static void security_sanity_check() { 183static void security_sanity_check() {
182 // TODO: Notify users visually if this has issues 184 // TODO: Notify users visually if this has issues
183 struct stat s; 185 struct stat s;
184 if (stat("/proc", &s)) { 186 if (stat("/proc", &s)) {
185 wlr_log(L_ERROR, 187 wlr_log(WLR_ERROR,
186 "!! DANGER !! /proc is not available - sway CANNOT enforce security rules!"); 188 "!! DANGER !! /proc is not available - sway CANNOT enforce security rules!");
187 } 189 }
188#ifdef __linux__ 190#ifdef __linux__
189 cap_flag_value_t v; 191 cap_flag_value_t v;
190 cap_t cap = cap_get_proc(); 192 cap_t cap = cap_get_proc();
191 if (!cap || cap_get_flag(cap, CAP_SYS_PTRACE, CAP_PERMITTED, &v) != 0 || v != CAP_SET) { 193 if (!cap || cap_get_flag(cap, CAP_SYS_PTRACE, CAP_PERMITTED, &v) != 0 || v != CAP_SET) {
192 wlr_log(L_ERROR, 194 wlr_log(WLR_ERROR,
193 "!! DANGER !! Sway does not have CAP_SYS_PTRACE and cannot enforce security rules for processes running as other users."); 195 "!! DANGER !! Sway does not have CAP_SYS_PTRACE and cannot enforce security rules for processes running as other users.");
194 } 196 }
195 if (cap) { 197 if (cap) {
@@ -205,13 +207,13 @@ static void executable_sanity_check() {
205 stat(exe, &sb); 207 stat(exe, &sb);
206 // We assume that cap_get_file returning NULL implies ENODATA 208 // We assume that cap_get_file returning NULL implies ENODATA
207 if (sb.st_mode & (S_ISUID|S_ISGID) && cap_get_file(exe)) { 209 if (sb.st_mode & (S_ISUID|S_ISGID) && cap_get_file(exe)) {
208 wlr_log(L_ERROR, 210 wlr_log(WLR_ERROR,
209 "sway executable has both the s(g)uid bit AND file caps set."); 211 "sway executable has both the s(g)uid bit AND file caps set.");
210 wlr_log(L_ERROR, 212 wlr_log(WLR_ERROR,
211 "This is strongly discouraged (and completely broken)."); 213 "This is strongly discouraged (and completely broken).");
212 wlr_log(L_ERROR, 214 wlr_log(WLR_ERROR,
213 "Please clear one of them (either the suid bit, or the file caps)."); 215 "Please clear one of them (either the suid bit, or the file caps).");
214 wlr_log(L_ERROR, 216 wlr_log(WLR_ERROR,
215 "If unsure, strip the file caps."); 217 "If unsure, strip the file caps.");
216 exit(EXIT_FAILURE); 218 exit(EXIT_FAILURE);
217 } 219 }
@@ -222,16 +224,16 @@ static void executable_sanity_check() {
222static void drop_permissions(bool keep_caps) { 224static void drop_permissions(bool keep_caps) {
223 if (getuid() != geteuid() || getgid() != getegid()) { 225 if (getuid() != geteuid() || getgid() != getegid()) {
224 if (setgid(getgid()) != 0) { 226 if (setgid(getgid()) != 0) {
225 wlr_log(L_ERROR, "Unable to drop root"); 227 wlr_log(WLR_ERROR, "Unable to drop root");
226 exit(EXIT_FAILURE); 228 exit(EXIT_FAILURE);
227 } 229 }
228 if (setuid(getuid()) != 0) { 230 if (setuid(getuid()) != 0) {
229 wlr_log(L_ERROR, "Unable to drop root"); 231 wlr_log(WLR_ERROR, "Unable to drop root");
230 exit(EXIT_FAILURE); 232 exit(EXIT_FAILURE);
231 } 233 }
232 } 234 }
233 if (setuid(0) != -1) { 235 if (setuid(0) != -1) {
234 wlr_log(L_ERROR, "Root privileges can be restored."); 236 wlr_log(WLR_ERROR, "Root privileges can be restored.");
235 exit(EXIT_FAILURE); 237 exit(EXIT_FAILURE);
236 } 238 }
237#ifdef __linux__ 239#ifdef __linux__
@@ -239,17 +241,29 @@ static void drop_permissions(bool keep_caps) {
239 // Drop every cap except CAP_SYS_PTRACE 241 // Drop every cap except CAP_SYS_PTRACE
240 cap_t caps = cap_init(); 242 cap_t caps = cap_init();
241 cap_value_t keep = CAP_SYS_PTRACE; 243 cap_value_t keep = CAP_SYS_PTRACE;
242 wlr_log(L_INFO, "Dropping extra capabilities"); 244 wlr_log(WLR_INFO, "Dropping extra capabilities");
243 if (cap_set_flag(caps, CAP_PERMITTED, 1, &keep, CAP_SET) || 245 if (cap_set_flag(caps, CAP_PERMITTED, 1, &keep, CAP_SET) ||
244 cap_set_flag(caps, CAP_EFFECTIVE, 1, &keep, CAP_SET) || 246 cap_set_flag(caps, CAP_EFFECTIVE, 1, &keep, CAP_SET) ||
245 cap_set_proc(caps)) { 247 cap_set_proc(caps)) {
246 wlr_log(L_ERROR, "Failed to drop extra capabilities"); 248 wlr_log(WLR_ERROR, "Failed to drop extra capabilities");
247 exit(EXIT_FAILURE); 249 exit(EXIT_FAILURE);
248 } 250 }
249 } 251 }
250#endif 252#endif
251} 253}
252 254
255void enable_debug_flag(const char *flag) {
256 if (strcmp(flag, "render-tree") == 0) {
257 enable_debug_tree = true;
258 } else if (strncmp(flag, "damage=", 7) == 0) {
259 damage_debug = &flag[7];
260 } else if (strcmp(flag, "txn-debug") == 0) {
261 txn_debug = true;
262 } else if (strncmp(flag, "txn-timeout=", 12) == 0) {
263 txn_timeout_ms = atoi(&flag[12]);
264 }
265}
266
253int main(int argc, char **argv) { 267int main(int argc, char **argv) {
254 static int verbose = 0, debug = 0, validate = 0; 268 static int verbose = 0, debug = 0, validate = 0;
255 269
@@ -289,7 +303,7 @@ int main(int argc, char **argv) {
289 int c; 303 int c;
290 while (1) { 304 while (1) {
291 int option_index = 0; 305 int option_index = 0;
292 c = getopt_long(argc, argv, "hCdDvVc:", long_options, &option_index); 306 c = getopt_long(argc, argv, "hCdD:vVc:", long_options, &option_index);
293 if (c == -1) { 307 if (c == -1) {
294 break; 308 break;
295 } 309 }
@@ -308,7 +322,7 @@ int main(int argc, char **argv) {
308 debug = 1; 322 debug = 1;
309 break; 323 break;
310 case 'D': // extended debug options 324 case 'D': // extended debug options
311 enable_debug_tree = true; 325 enable_debug_flag(optarg);
312 break; 326 break;
313 case 'v': // version 327 case 'v': // version
314 fprintf(stdout, "sway version " SWAY_VERSION "\n"); 328 fprintf(stdout, "sway version " SWAY_VERSION "\n");
@@ -334,22 +348,22 @@ int main(int argc, char **argv) {
334 348
335 // TODO: switch logging over to wlroots? 349 // TODO: switch logging over to wlroots?
336 if (debug) { 350 if (debug) {
337 wlr_log_init(L_DEBUG, NULL); 351 wlr_log_init(WLR_DEBUG, NULL);
338 } else if (verbose || validate) { 352 } else if (verbose || validate) {
339 wlr_log_init(L_INFO, NULL); 353 wlr_log_init(WLR_INFO, NULL);
340 } else { 354 } else {
341 wlr_log_init(L_ERROR, NULL); 355 wlr_log_init(WLR_ERROR, NULL);
342 } 356 }
343 357
344 if (optind < argc) { // Behave as IPC client 358 if (optind < argc) { // Behave as IPC client
345 if(optind != 1) { 359 if(optind != 1) {
346 wlr_log(L_ERROR, "Don't use options with the IPC client"); 360 wlr_log(WLR_ERROR, "Don't use options with the IPC client");
347 exit(EXIT_FAILURE); 361 exit(EXIT_FAILURE);
348 } 362 }
349 drop_permissions(false); 363 drop_permissions(false);
350 char *socket_path = getenv("SWAYSOCK"); 364 char *socket_path = getenv("SWAYSOCK");
351 if (!socket_path) { 365 if (!socket_path) {
352 wlr_log(L_ERROR, "Unable to retrieve socket path"); 366 wlr_log(WLR_ERROR, "Unable to retrieve socket path");
353 exit(EXIT_FAILURE); 367 exit(EXIT_FAILURE);
354 } 368 }
355 char *command = join_args(argv + optind, argc - optind); 369 char *command = join_args(argv + optind, argc - optind);
@@ -368,7 +382,7 @@ int main(int argc, char **argv) {
368 if (getuid() != geteuid() || getgid() != getegid()) { 382 if (getuid() != geteuid() || getgid() != getegid()) {
369 // Retain capabilities after setuid() 383 // Retain capabilities after setuid()
370 if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0)) { 384 if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0)) {
371 wlr_log(L_ERROR, "Cannot keep caps after setuid()"); 385 wlr_log(WLR_ERROR, "Cannot keep caps after setuid()");
372 exit(EXIT_FAILURE); 386 exit(EXIT_FAILURE);
373 } 387 }
374 suid = true; 388 suid = true;
@@ -389,7 +403,7 @@ int main(int argc, char **argv) {
389 // prevent ipc from crashing sway 403 // prevent ipc from crashing sway
390 signal(SIGPIPE, SIG_IGN); 404 signal(SIGPIPE, SIG_IGN);
391 405
392 wlr_log(L_INFO, "Starting sway version " SWAY_VERSION); 406 wlr_log(WLR_INFO, "Starting sway version " SWAY_VERSION);
393 407
394 layout_init(); 408 layout_init();
395 409
@@ -415,32 +429,41 @@ int main(int argc, char **argv) {
415 429
416 security_sanity_check(); 430 security_sanity_check();
417 431
432 setenv("WAYLAND_DISPLAY", server.socket, true);
433 if (!terminate_request) {
434 if (!server_start_backend(&server)) {
435 sway_terminate(EXIT_FAILURE);
436 }
437 }
438
418 config->active = true; 439 config->active = true;
419 // Execute commands until there are none left 440 // Execute commands until there are none left
441 wlr_log(WLR_DEBUG, "Running deferred commands");
420 while (config->cmd_queue->length) { 442 while (config->cmd_queue->length) {
421 char *line = config->cmd_queue->items[0]; 443 char *line = config->cmd_queue->items[0];
422 struct cmd_results *res = execute_command(line, NULL); 444 struct cmd_results *res = execute_command(line, NULL);
423 if (res->status != CMD_SUCCESS) { 445 if (res->status != CMD_SUCCESS) {
424 wlr_log(L_ERROR, "Error on line '%s': %s", line, res->error); 446 wlr_log(WLR_ERROR, "Error on line '%s': %s", line, res->error);
425 } 447 }
426 free_cmd_results(res); 448 free_cmd_results(res);
427 free(line); 449 free(line);
428 list_del(config->cmd_queue, 0); 450 list_del(config->cmd_queue, 0);
429 } 451 }
452 transaction_commit_dirty();
430 453
431 if (!terminate_request) { 454 if (!terminate_request) {
432 server_run(&server); 455 server_run(&server);
433 } 456 }
434 457
435 wlr_log(L_INFO, "Shutting down sway"); 458 wlr_log(WLR_INFO, "Shutting down sway");
436 459
437 server_fini(&server); 460 server_fini(&server);
438 461
439 ipc_terminate();
440
441 if (config) { 462 if (config) {
442 free_config(config); 463 free_config(config);
443 } 464 }
444 465
466 pango_cairo_font_map_set_default(NULL);
467
445 return exit_value; 468 return exit_value;
446} 469}
diff --git a/sway/meson.build b/sway/meson.build
index 9ff3f05f..30c848e2 100644
--- a/sway/meson.build
+++ b/sway/meson.build
@@ -7,11 +7,14 @@ sway_sources = files(
7 'debug-tree.c', 7 'debug-tree.c',
8 'ipc-json.c', 8 'ipc-json.c',
9 'ipc-server.c', 9 'ipc-server.c',
10 'scratchpad.c',
10 'security.c', 11 'security.c',
11 12
12 'desktop/desktop.c', 13 'desktop/desktop.c',
14 'desktop/idle_inhibit_v1.c',
13 'desktop/layer_shell.c', 15 'desktop/layer_shell.c',
14 'desktop/output.c', 16 'desktop/output.c',
17 'desktop/render.c',
15 'desktop/transaction.c', 18 'desktop/transaction.c',
16 'desktop/xdg_shell_v6.c', 19 'desktop/xdg_shell_v6.c',
17 'desktop/xdg_shell.c', 20 'desktop/xdg_shell.c',
@@ -33,16 +36,20 @@ sway_sources = files(
33 'commands/border.c', 36 'commands/border.c',
34 'commands/client.c', 37 'commands/client.c',
35 'commands/default_border.c', 38 'commands/default_border.c',
39 'commands/default_floating_border.c',
36 'commands/default_orientation.c', 40 'commands/default_orientation.c',
37 'commands/exit.c', 41 'commands/exit.c',
38 'commands/exec.c', 42 'commands/exec.c',
39 'commands/exec_always.c', 43 'commands/exec_always.c',
40 'commands/floating.c', 44 'commands/floating.c',
45 'commands/floating_minmax_size.c',
46 'commands/floating_modifier.c',
41 'commands/focus.c', 47 'commands/focus.c',
42 'commands/focus_follows_mouse.c', 48 'commands/focus_follows_mouse.c',
43 'commands/focus_wrapping.c', 49 'commands/focus_wrapping.c',
44 'commands/font.c', 50 'commands/font.c',
45 'commands/for_window.c', 51 'commands/for_window.c',
52 'commands/force_display_urgency_hint.c',
46 'commands/force_focus_wrapping.c', 53 'commands/force_focus_wrapping.c',
47 'commands/fullscreen.c', 54 'commands/fullscreen.c',
48 'commands/gaps.c', 55 'commands/gaps.c',
@@ -56,10 +63,12 @@ sway_sources = files(
56 'commands/mode.c', 63 'commands/mode.c',
57 'commands/mouse_warping.c', 64 'commands/mouse_warping.c',
58 'commands/move.c', 65 'commands/move.c',
66 'commands/no_focus.c',
59 'commands/output.c', 67 'commands/output.c',
60 'commands/reload.c', 68 'commands/reload.c',
61 'commands/rename.c', 69 'commands/rename.c',
62 'commands/resize.c', 70 'commands/resize.c',
71 'commands/scratchpad.c',
63 'commands/seat.c', 72 'commands/seat.c',
64 'commands/seat/attach.c', 73 'commands/seat/attach.c',
65 'commands/seat/cursor.c', 74 'commands/seat/cursor.c',
@@ -73,6 +82,7 @@ sway_sources = files(
73 'commands/swap.c', 82 'commands/swap.c',
74 'commands/title_format.c', 83 'commands/title_format.c',
75 'commands/unmark.c', 84 'commands/unmark.c',
85 'commands/urgent.c',
76 'commands/workspace.c', 86 'commands/workspace.c',
77 'commands/workspace_layout.c', 87 'commands/workspace_layout.c',
78 'commands/ws_auto_back_and_forth.c', 88 'commands/ws_auto_back_and_forth.c',
@@ -115,8 +125,10 @@ sway_sources = files(
115 'commands/input/pointer_accel.c', 125 'commands/input/pointer_accel.c',
116 'commands/input/repeat_delay.c', 126 'commands/input/repeat_delay.c',
117 'commands/input/repeat_rate.c', 127 'commands/input/repeat_rate.c',
128 'commands/input/scroll_button.c',
118 'commands/input/scroll_method.c', 129 'commands/input/scroll_method.c',
119 'commands/input/tap.c', 130 'commands/input/tap.c',
131 'commands/input/tap_button_map.c',
120 'commands/input/xkb_layout.c', 132 'commands/input/xkb_layout.c',
121 'commands/input/xkb_model.c', 133 'commands/input/xkb_model.c',
122 'commands/input/xkb_options.c', 134 'commands/input/xkb_options.c',
diff --git a/sway/scratchpad.c b/sway/scratchpad.c
new file mode 100644
index 00000000..1e836e7d
--- /dev/null
+++ b/sway/scratchpad.c
@@ -0,0 +1,175 @@
1#define _XOPEN_SOURCE 700
2#include <stdlib.h>
3#include <stdio.h>
4#include <stdbool.h>
5#include "sway/scratchpad.h"
6#include "sway/input/seat.h"
7#include "sway/tree/arrange.h"
8#include "sway/tree/container.h"
9#include "sway/tree/view.h"
10#include "sway/tree/workspace.h"
11#include "list.h"
12#include "log.h"
13
14void scratchpad_add_container(struct sway_container *con) {
15 if (!sway_assert(!con->scratchpad, "Container is already in scratchpad")) {
16 return;
17 }
18 con->scratchpad = true;
19 list_add(root_container.sway_root->scratchpad, con);
20
21 struct sway_container *parent = con->parent;
22 container_set_floating(con, true);
23 container_remove_child(con);
24 arrange_windows(parent);
25
26 struct sway_seat *seat = input_manager_current_seat(input_manager);
27 seat_set_focus(seat, seat_get_focus_inactive(seat, parent));
28}
29
30void scratchpad_remove_container(struct sway_container *con) {
31 if (!sway_assert(con->scratchpad, "Container is not in scratchpad")) {
32 return;
33 }
34 con->scratchpad = false;
35 for (int i = 0; i < root_container.sway_root->scratchpad->length; ++i) {
36 if (root_container.sway_root->scratchpad->items[i] == con) {
37 list_del(root_container.sway_root->scratchpad, i);
38 break;
39 }
40 }
41}
42
43/**
44 * Show a single scratchpad container.
45 * The container might be visible on another workspace already.
46 */
47static void scratchpad_show(struct sway_container *con) {
48 struct sway_seat *seat = input_manager_current_seat(input_manager);
49 struct sway_container *ws = seat_get_focus(seat);
50 if (ws->type != C_WORKSPACE) {
51 ws = container_parent(ws, C_WORKSPACE);
52 }
53
54 // If the current con or any of its parents are in fullscreen mode, we
55 // first need to disable it before showing the scratchpad con.
56 if (ws->sway_workspace->fullscreen) {
57 view_set_fullscreen(ws->sway_workspace->fullscreen, false);
58 }
59
60 // Show the container
61 if (con->parent) {
62 container_remove_child(con);
63 }
64 container_add_child(ws->sway_workspace->floating, con);
65
66 // Make sure the container's center point overlaps this workspace
67 double center_lx = con->x + con->width / 2;
68 double center_ly = con->y + con->height / 2;
69
70 struct wlr_box workspace_box;
71 container_get_box(ws, &workspace_box);
72 if (!wlr_box_contains_point(&workspace_box, center_lx, center_ly)) {
73 // Maybe resize it
74 if (con->width > ws->width || con->height > ws->height) {
75 // TODO: Do this properly once we can float C_CONTAINERs
76 if (con->type == C_VIEW) {
77 view_init_floating(con->sway_view);
78 arrange_windows(con);
79 }
80 }
81
82 // Center it
83 double new_lx = ws->x + (ws->width - con->width) / 2;
84 double new_ly = ws->y + (ws->height - con->height) / 2;
85 container_floating_move_to(con, new_lx, new_ly);
86 }
87
88 seat_set_focus(seat, con);
89
90 container_set_dirty(con->parent);
91}
92
93/**
94 * Hide a single scratchpad container.
95 * The container might not be the focused container (eg. when using criteria).
96 */
97static void scratchpad_hide(struct sway_container *con) {
98 struct sway_seat *seat = input_manager_current_seat(input_manager);
99 struct sway_container *focus = seat_get_focus(seat);
100 struct sway_container *ws = container_parent(con, C_WORKSPACE);
101
102 container_remove_child(con);
103 arrange_windows(ws);
104 if (con == focus) {
105 seat_set_focus(seat, seat_get_focus_inactive(seat, ws));
106 }
107 list_move_to_end(root_container.sway_root->scratchpad, con);
108}
109
110void scratchpad_toggle_auto(void) {
111 struct sway_seat *seat = input_manager_current_seat(input_manager);
112 struct sway_container *focus = seat_get_focus(seat);
113 struct sway_container *ws = focus->type == C_WORKSPACE ?
114 focus : container_parent(focus, C_WORKSPACE);
115
116 // Check if the currently focused window is a scratchpad window and should
117 // be hidden again.
118 if (focus->scratchpad) {
119 wlr_log(WLR_DEBUG, "Focus is a scratchpad window - hiding %s",
120 focus->name);
121 scratchpad_hide(focus);
122 return;
123 }
124
125 // Check if there is an unfocused scratchpad window on the current workspace
126 // and focus it.
127 for (int i = 0; i < ws->sway_workspace->floating->children->length; ++i) {
128 struct sway_container *floater =
129 ws->sway_workspace->floating->children->items[i];
130 if (floater->scratchpad && focus != floater) {
131 wlr_log(WLR_DEBUG,
132 "Focusing other scratchpad window (%s) in this workspace",
133 floater->name);
134 scratchpad_show(floater);
135 return;
136 }
137 }
138
139 // Check if there is a visible scratchpad window on another workspace.
140 // In this case we move it to the current workspace.
141 for (int i = 0; i < root_container.sway_root->scratchpad->length; ++i) {
142 struct sway_container *con =
143 root_container.sway_root->scratchpad->items[i];
144 if (con->parent) {
145 wlr_log(WLR_DEBUG,
146 "Moving a visible scratchpad window (%s) to this workspace",
147 con->name);
148 scratchpad_show(con);
149 return;
150 }
151 }
152
153 // Take the container at the bottom of the scratchpad list
154 if (!sway_assert(root_container.sway_root->scratchpad->length,
155 "Scratchpad is empty")) {
156 return;
157 }
158 struct sway_container *con = root_container.sway_root->scratchpad->items[0];
159 wlr_log(WLR_DEBUG, "Showing %s from list", con->name);
160 scratchpad_show(con);
161}
162
163void scratchpad_toggle_container(struct sway_container *con) {
164 if (!sway_assert(con->scratchpad, "Container isn't in the scratchpad")) {
165 return;
166 }
167
168 // Check if it matches a currently visible scratchpad window and hide it.
169 if (con->parent) {
170 scratchpad_hide(con);
171 return;
172 }
173
174 scratchpad_show(con);
175}
diff --git a/sway/server.c b/sway/server.c
index f5700c09..89dfbf8c 100644
--- a/sway/server.c
+++ b/sway/server.c
@@ -7,13 +7,13 @@
7#include <wlr/backend/session.h> 7#include <wlr/backend/session.h>
8#include <wlr/render/wlr_renderer.h> 8#include <wlr/render/wlr_renderer.h>
9#include <wlr/types/wlr_compositor.h> 9#include <wlr/types/wlr_compositor.h>
10#include <wlr/types/wlr_export_dmabuf_v1.h>
10#include <wlr/types/wlr_gamma_control.h> 11#include <wlr/types/wlr_gamma_control.h>
11#include <wlr/types/wlr_idle.h> 12#include <wlr/types/wlr_idle.h>
12#include <wlr/types/wlr_layer_shell.h> 13#include <wlr/types/wlr_layer_shell.h>
13#include <wlr/types/wlr_linux_dmabuf.h> 14#include <wlr/types/wlr_linux_dmabuf_v1.h>
14#include <wlr/types/wlr_export_dmabuf_v1.h>
15#include <wlr/types/wlr_primary_selection.h> 15#include <wlr/types/wlr_primary_selection.h>
16#include <wlr/types/wlr_screenshooter.h> 16#include <wlr/types/wlr_screencopy_v1.h>
17#include <wlr/types/wlr_server_decoration.h> 17#include <wlr/types/wlr_server_decoration.h>
18#include <wlr/types/wlr_xcursor_manager.h> 18#include <wlr/types/wlr_xcursor_manager.h>
19#include <wlr/types/wlr_xdg_output.h> 19#include <wlr/types/wlr_xdg_output.h>
@@ -21,26 +21,27 @@
21// TODO WLR: make Xwayland optional 21// TODO WLR: make Xwayland optional
22#include "list.h" 22#include "list.h"
23#include "sway/config.h" 23#include "sway/config.h"
24#include "sway/desktop/idle_inhibit_v1.h"
24#include "sway/input/input-manager.h" 25#include "sway/input/input-manager.h"
25#include "sway/server.h" 26#include "sway/server.h"
26#include "sway/tree/layout.h" 27#include "sway/tree/layout.h"
27#include "sway/xwayland.h" 28#include "sway/xwayland.h"
28 29
29bool server_privileged_prepare(struct sway_server *server) { 30bool server_privileged_prepare(struct sway_server *server) {
30 wlr_log(L_DEBUG, "Preparing Wayland server initialization"); 31 wlr_log(WLR_DEBUG, "Preparing Wayland server initialization");
31 server->wl_display = wl_display_create(); 32 server->wl_display = wl_display_create();
32 server->wl_event_loop = wl_display_get_event_loop(server->wl_display); 33 server->wl_event_loop = wl_display_get_event_loop(server->wl_display);
33 server->backend = wlr_backend_autocreate(server->wl_display, NULL); 34 server->backend = wlr_backend_autocreate(server->wl_display, NULL);
34 35
35 if (!server->backend) { 36 if (!server->backend) {
36 wlr_log(L_ERROR, "Unable to create backend"); 37 wlr_log(WLR_ERROR, "Unable to create backend");
37 return false; 38 return false;
38 } 39 }
39 return true; 40 return true;
40} 41}
41 42
42bool server_init(struct sway_server *server) { 43bool server_init(struct sway_server *server) {
43 wlr_log(L_DEBUG, "Initializing Wayland server"); 44 wlr_log(WLR_DEBUG, "Initializing Wayland server");
44 45
45 struct wlr_renderer *renderer = wlr_backend_get_renderer(server->backend); 46 struct wlr_renderer *renderer = wlr_backend_get_renderer(server->backend);
46 assert(renderer); 47 assert(renderer);
@@ -51,8 +52,6 @@ bool server_init(struct sway_server *server) {
51 server->data_device_manager = 52 server->data_device_manager =
52 wlr_data_device_manager_create(server->wl_display); 53 wlr_data_device_manager_create(server->wl_display);
53 54
54 server->idle = wlr_idle_create(server->wl_display);
55 wlr_screenshooter_create(server->wl_display);
56 wlr_gamma_control_manager_create(server->wl_display); 55 wlr_gamma_control_manager_create(server->wl_display);
57 wlr_primary_selection_device_manager_create(server->wl_display); 56 wlr_primary_selection_device_manager_create(server->wl_display);
58 57
@@ -62,6 +61,10 @@ bool server_init(struct sway_server *server) {
62 wlr_xdg_output_manager_create(server->wl_display, 61 wlr_xdg_output_manager_create(server->wl_display,
63 root_container.sway_root->output_layout); 62 root_container.sway_root->output_layout);
64 63
64 server->idle = wlr_idle_create(server->wl_display);
65 server->idle_inhibit_manager_v1 =
66 sway_idle_inhibit_manager_v1_create(server->wl_display, server->idle);
67
65 server->layer_shell = wlr_layer_shell_create(server->wl_display); 68 server->layer_shell = wlr_layer_shell_create(server->wl_display);
66 wl_signal_add(&server->layer_shell->events.new_surface, 69 wl_signal_add(&server->layer_shell->events.new_surface,
67 &server->layer_shell_surface); 70 &server->layer_shell_surface);
@@ -105,12 +108,13 @@ bool server_init(struct sway_server *server) {
105 wlr_server_decoration_manager_set_default_mode( 108 wlr_server_decoration_manager_set_default_mode(
106 deco_manager, WLR_SERVER_DECORATION_MANAGER_MODE_SERVER); 109 deco_manager, WLR_SERVER_DECORATION_MANAGER_MODE_SERVER);
107 110
108 wlr_linux_dmabuf_create(server->wl_display, renderer); 111 wlr_linux_dmabuf_v1_create(server->wl_display, renderer);
109 wlr_export_dmabuf_manager_v1_create(server->wl_display); 112 wlr_export_dmabuf_manager_v1_create(server->wl_display);
113 wlr_screencopy_manager_v1_create(server->wl_display);
110 114
111 server->socket = wl_display_add_socket_auto(server->wl_display); 115 server->socket = wl_display_add_socket_auto(server->wl_display);
112 if (!server->socket) { 116 if (!server->socket) {
113 wlr_log(L_ERROR, "Unable to open wayland socket"); 117 wlr_log(WLR_ERROR, "Unable to open wayland socket");
114 wlr_backend_destroy(server->backend); 118 wlr_backend_destroy(server->backend);
115 return false; 119 return false;
116 } 120 }
@@ -119,8 +123,7 @@ bool server_init(struct sway_server *server) {
119 if (debug != NULL && strcmp(debug, "txn_timings") == 0) { 123 if (debug != NULL && strcmp(debug, "txn_timings") == 0) {
120 server->debug_txn_timings = true; 124 server->debug_txn_timings = true;
121 } 125 }
122 server->destroying_containers = create_list(); 126 server->dirty_containers = create_list();
123
124 server->transactions = create_list(); 127 server->transactions = create_list();
125 128
126 input_manager = input_manager_create(server); 129 input_manager = input_manager_create(server);
@@ -130,18 +133,23 @@ bool server_init(struct sway_server *server) {
130void server_fini(struct sway_server *server) { 133void server_fini(struct sway_server *server) {
131 // TODO: free sway-specific resources 134 // TODO: free sway-specific resources
132 wl_display_destroy(server->wl_display); 135 wl_display_destroy(server->wl_display);
133 list_free(server->destroying_containers); 136 list_free(server->dirty_containers);
134 list_free(server->transactions); 137 list_free(server->transactions);
135} 138}
136 139
137void server_run(struct sway_server *server) { 140bool server_start_backend(struct sway_server *server) {
138 wlr_log(L_INFO, "Running compositor on wayland display '%s'", 141 wlr_log(WLR_INFO, "Starting backend on wayland display '%s'",
139 server->socket); 142 server->socket);
140 setenv("WAYLAND_DISPLAY", server->socket, true);
141 if (!wlr_backend_start(server->backend)) { 143 if (!wlr_backend_start(server->backend)) {
142 wlr_log(L_ERROR, "Failed to start backend"); 144 wlr_log(WLR_ERROR, "Failed to start backend");
143 wlr_backend_destroy(server->backend); 145 wlr_backend_destroy(server->backend);
144 return; 146 return false;
145 } 147 }
148 return true;
149}
150
151void server_run(struct sway_server *server) {
152 wlr_log(WLR_INFO, "Running compositor on wayland display '%s'",
153 server->socket);
146 wl_display_run(server->wl_display); 154 wl_display_run(server->wl_display);
147} 155}
diff --git a/sway/sway-input.5.scd b/sway/sway-input.5.scd
index c07460b1..b6391431 100644
--- a/sway/sway-input.5.scd
+++ b/sway/sway-input.5.scd
@@ -67,8 +67,8 @@ For more information on these xkb configuration options, see
67 Enables or disables disable-while-typing for the specified input device. 67 Enables or disables disable-while-typing for the specified input device.
68 68
69*input* <identifier> events enabled|disabled|disabled\_on\_external\_mouse 69*input* <identifier> events enabled|disabled|disabled\_on\_external\_mouse
70 Enables or disables send_events for specified input device. (Disabling 70 Enables or disables send\_events for specified input device. (Disabling
71 send_events disables the input device) 71 send\_events disables the input device)
72 72
73*input* <identifier> left\_handed enabled|disabled 73*input* <identifier> left\_handed enabled|disabled
74 Enables or disables left handed mode for specified input device. 74 Enables or disables left handed mode for specified input device.
@@ -92,9 +92,20 @@ For more information on these xkb configuration options, see
92*input* <identifier> scroll\_method none|two\_finger|edge|on\_button\_down 92*input* <identifier> scroll\_method none|two\_finger|edge|on\_button\_down
93 Changes the scroll method for the specified input device. 93 Changes the scroll method for the specified input device.
94 94
95*input* <identifier> scroll\_button <button\_identifier>
96 Sets button used for scroll\_method on\_button\_down. The button identifier
97 can be obtained from `libinput debug-events`.
98 If set to 0, it disables the scroll\_button on\_button\_down.
99
95*input* <identifier> tap enabled|disabled 100*input* <identifier> tap enabled|disabled
96 Enables or disables tap for specified input device. 101 Enables or disables tap for specified input device.
97 102
103*input* <identifier> tap_button_map lrm|lmr
104 Specifies which button mapping to use for tapping. _lrm_ treats 1 finger as
105 left click, 2 fingers as right click, and 3 fingers as middle click. _lmr_
106 treats 1 finger as left click, 2 fingers as middle click, and 3 fingers as
107 right click.
108
98## SEAT CONFIGURATION 109## SEAT CONFIGURATION
99 110
100Configure options for multiseat mode. sway-seat commands must be used inside a 111Configure options for multiseat mode. sway-seat commands must be used inside a
diff --git a/sway/sway.5.scd b/sway/sway.5.scd
index 5ce6bf06..d369d7b6 100644
--- a/sway/sway.5.scd
+++ b/sway/sway.5.scd
@@ -92,6 +92,12 @@ They are expected to be used with *bindsym* or at runtime through *swaymsg*(1).
92*focus* output <name> 92*focus* output <name>
93 Moves focus to the named output. 93 Moves focus to the named output.
94 94
95*focus tiling*
96 Sets focus to the last focused tiling container.
97
98*focus floating*
99 Sets focus to the last focused floating container.
100
95*focus* mode\_toggle 101*focus* mode\_toggle
96 Moves focus between the floating and tiled layers. 102 Moves focus between the floating and tiled layers.
97 103
@@ -413,10 +419,12 @@ The default colors are:
413*mode* <mode> 419*mode* <mode>
414 Switches to the specified mode. The default mode _default_. 420 Switches to the specified mode. The default mode _default_.
415 421
416*mode* <mode> *{* <commands...> *}* 422*mode* [--pango\_markup] <mode> *{* <commands...> *}*
417 _commands..._ after *{* will be added to the specified mode. A newline is 423 _commands..._ after *{* will be added to the specified mode. A newline is
418 required between *{* and the first command, and *}* must be alone on a 424 required between *{* and the first command, and *}* must be alone on a
419 line. Only *bindsym* and *bindcode* commands are permitted in mode blocks. 425 line. Only *bindsym* and *bindcode* commands are permitted in mode blocks.
426 If _--pango\_markup_ is given, then _mode_ will be interpreted as pango
427 markup.
420 428
421*mouse\_warping* output|none 429*mouse\_warping* output|none
422 If _output_ is specified, the mouse will be moved to new outputs as you 430 If _output_ is specified, the mouse will be moved to new outputs as you
@@ -491,6 +499,11 @@ config after the others, or it will be matched instead of the others.
491 *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
492 window. If _identifier_ is omitted, all marks are removed. 500 window. If _identifier_ is omitted, all marks are removed.
493 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
494*workspace* [number] <name> 507*workspace* [number] <name>
495 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
496 used to sort workspaces. 509 used to sort workspaces.
diff --git a/sway/tree/arrange.c b/sway/tree/arrange.c
index 582b2891..533cf71c 100644
--- a/sway/tree/arrange.c
+++ b/sway/tree/arrange.c
@@ -47,11 +47,11 @@ static void apply_horiz_layout(struct sway_container *parent) {
47 double scale = parent->width / total_width; 47 double scale = parent->width / total_width;
48 48
49 // Resize windows 49 // Resize windows
50 wlr_log(L_DEBUG, "Arranging %p horizontally", parent); 50 wlr_log(WLR_DEBUG, "Arranging %p horizontally", parent);
51 double child_x = parent->x; 51 double child_x = parent->x;
52 for (size_t i = 0; i < num_children; ++i) { 52 for (size_t i = 0; i < num_children; ++i) {
53 struct sway_container *child = parent->children->items[i]; 53 struct sway_container *child = parent->children->items[i];
54 wlr_log(L_DEBUG, 54 wlr_log(WLR_DEBUG,
55 "Calculating arrangement for %p:%d (will scale %f by %f)", 55 "Calculating arrangement for %p:%d (will scale %f by %f)",
56 child, child->type, child->width, scale); 56 child, child->type, child->width, scale);
57 child->x = child_x; 57 child->x = child_x;
@@ -99,11 +99,11 @@ static void apply_vert_layout(struct sway_container *parent) {
99 double scale = parent_height / total_height; 99 double scale = parent_height / total_height;
100 100
101 // Resize 101 // Resize
102 wlr_log(L_DEBUG, "Arranging %p vertically", parent); 102 wlr_log(WLR_DEBUG, "Arranging %p vertically", parent);
103 double child_y = parent->y + parent_offset; 103 double child_y = parent->y + parent_offset;
104 for (size_t i = 0; i < num_children; ++i) { 104 for (size_t i = 0; i < num_children; ++i) {
105 struct sway_container *child = parent->children->items[i]; 105 struct sway_container *child = parent->children->items[i];
106 wlr_log(L_DEBUG, 106 wlr_log(WLR_DEBUG,
107 "Calculating arrangement for %p:%d (will scale %f by %f)", 107 "Calculating arrangement for %p:%d (will scale %f by %f)",
108 child, child->type, child->height, scale); 108 child, child->type, child->height, scale);
109 child->x = parent->x; 109 child->x = parent->x;
@@ -144,42 +144,26 @@ static void apply_tabbed_or_stacked_layout(struct sway_container *parent) {
144 } 144 }
145} 145}
146 146
147/** 147static void arrange_children_of(struct sway_container *parent);
148 * If a container has been deleted from the pending tree state, we must add it
149 * to the transaction so it can be freed afterwards. To do this, we iterate the
150 * server's destroying_containers list and add all of them. We may add more than
151 * what we need to, but this is easy and has no negative consequences.
152 */
153static void add_deleted_containers(struct sway_transaction *transaction) {
154 for (int i = 0; i < server.destroying_containers->length; ++i) {
155 struct sway_container *child = server.destroying_containers->items[i];
156 transaction_add_container(transaction, child);
157 }
158}
159
160static void arrange_children_of(struct sway_container *parent,
161 struct sway_transaction *transaction);
162 148
163static void arrange_floating(struct sway_container *floating, 149static void arrange_floating(struct sway_container *floating) {
164 struct sway_transaction *transaction) {
165 for (int i = 0; i < floating->children->length; ++i) { 150 for (int i = 0; i < floating->children->length; ++i) {
166 struct sway_container *floater = floating->children->items[i]; 151 struct sway_container *floater = floating->children->items[i];
167 if (floater->type == C_VIEW) { 152 if (floater->type == C_VIEW) {
168 view_autoconfigure(floater->sway_view); 153 view_autoconfigure(floater->sway_view);
169 } else { 154 } else {
170 arrange_children_of(floater, transaction); 155 arrange_children_of(floater);
171 } 156 }
172 transaction_add_container(transaction, floater); 157 container_set_dirty(floater);
173 } 158 }
174 transaction_add_container(transaction, floating); 159 container_set_dirty(floating);
175} 160}
176 161
177static void arrange_children_of(struct sway_container *parent, 162static void arrange_children_of(struct sway_container *parent) {
178 struct sway_transaction *transaction) {
179 if (config->reloading) { 163 if (config->reloading) {
180 return; 164 return;
181 } 165 }
182 wlr_log(L_DEBUG, "Arranging layout for %p %s %fx%f+%f,%f", parent, 166 wlr_log(WLR_DEBUG, "Arranging layout for %p %s %fx%f+%f,%f", parent,
183 parent->name, parent->width, parent->height, parent->x, parent->y); 167 parent->name, parent->width, parent->height, parent->x, parent->y);
184 168
185 // Calculate x, y, width and height of children 169 // Calculate x, y, width and height of children
@@ -198,7 +182,7 @@ static void arrange_children_of(struct sway_container *parent,
198 apply_horiz_layout(parent); 182 apply_horiz_layout(parent);
199 break; 183 break;
200 case L_FLOATING: 184 case L_FLOATING:
201 arrange_floating(parent, transaction); 185 arrange_floating(parent);
202 break; 186 break;
203 } 187 }
204 188
@@ -213,20 +197,19 @@ static void arrange_children_of(struct sway_container *parent,
213 if (child->type == C_VIEW) { 197 if (child->type == C_VIEW) {
214 view_autoconfigure(child->sway_view); 198 view_autoconfigure(child->sway_view);
215 } else { 199 } else {
216 arrange_children_of(child, transaction); 200 arrange_children_of(child);
217 } 201 }
218 transaction_add_container(transaction, child); 202 container_set_dirty(child);
219 } 203 }
220} 204}
221 205
222static void arrange_workspace(struct sway_container *workspace, 206static void arrange_workspace(struct sway_container *workspace) {
223 struct sway_transaction *transaction) {
224 if (config->reloading) { 207 if (config->reloading) {
225 return; 208 return;
226 } 209 }
227 struct sway_container *output = workspace->parent; 210 struct sway_container *output = workspace->parent;
228 struct wlr_box *area = &output->sway_output->usable_area; 211 struct wlr_box *area = &output->sway_output->usable_area;
229 wlr_log(L_DEBUG, "Usable area for ws: %dx%d@%d,%d", 212 wlr_log(WLR_DEBUG, "Usable area for ws: %dx%d@%d,%d",
230 area->width, area->height, area->x, area->y); 213 area->width, area->height, area->x, area->y);
231 remove_gaps(workspace); 214 remove_gaps(workspace);
232 workspace->width = area->width; 215 workspace->width = area->width;
@@ -234,15 +217,14 @@ static void arrange_workspace(struct sway_container *workspace,
234 workspace->x = output->x + area->x; 217 workspace->x = output->x + area->x;
235 workspace->y = output->y + area->y; 218 workspace->y = output->y + area->y;
236 add_gaps(workspace); 219 add_gaps(workspace);
237 transaction_add_container(transaction, workspace); 220 container_set_dirty(workspace);
238 wlr_log(L_DEBUG, "Arranging workspace '%s' at %f, %f", workspace->name, 221 wlr_log(WLR_DEBUG, "Arranging workspace '%s' at %f, %f", workspace->name,
239 workspace->x, workspace->y); 222 workspace->x, workspace->y);
240 arrange_floating(workspace->sway_workspace->floating, transaction); 223 arrange_floating(workspace->sway_workspace->floating);
241 arrange_children_of(workspace, transaction); 224 arrange_children_of(workspace);
242} 225}
243 226
244static void arrange_output(struct sway_container *output, 227static void arrange_output(struct sway_container *output) {
245 struct sway_transaction *transaction) {
246 if (config->reloading) { 228 if (config->reloading) {
247 return; 229 return;
248 } 230 }
@@ -253,16 +235,16 @@ static void arrange_output(struct sway_container *output,
253 output->y = output_box->y; 235 output->y = output_box->y;
254 output->width = output_box->width; 236 output->width = output_box->width;
255 output->height = output_box->height; 237 output->height = output_box->height;
256 transaction_add_container(transaction, output); 238 container_set_dirty(output);
257 wlr_log(L_DEBUG, "Arranging output '%s' at %f,%f", 239 wlr_log(WLR_DEBUG, "Arranging output '%s' at %f,%f",
258 output->name, output->x, output->y); 240 output->name, output->x, output->y);
259 for (int i = 0; i < output->children->length; ++i) { 241 for (int i = 0; i < output->children->length; ++i) {
260 struct sway_container *workspace = output->children->items[i]; 242 struct sway_container *workspace = output->children->items[i];
261 arrange_workspace(workspace, transaction); 243 arrange_workspace(workspace);
262 } 244 }
263} 245}
264 246
265static void arrange_root(struct sway_transaction *transaction) { 247static void arrange_root() {
266 if (config->reloading) { 248 if (config->reloading) {
267 return; 249 return;
268 } 250 }
@@ -274,48 +256,40 @@ static void arrange_root(struct sway_transaction *transaction) {
274 root_container.y = layout_box->y; 256 root_container.y = layout_box->y;
275 root_container.width = layout_box->width; 257 root_container.width = layout_box->width;
276 root_container.height = layout_box->height; 258 root_container.height = layout_box->height;
277 transaction_add_container(transaction, &root_container); 259 container_set_dirty(&root_container);
278 for (int i = 0; i < root_container.children->length; ++i) { 260 for (int i = 0; i < root_container.children->length; ++i) {
279 struct sway_container *output = root_container.children->items[i]; 261 struct sway_container *output = root_container.children->items[i];
280 arrange_output(output, transaction); 262 arrange_output(output);
281 } 263 }
282} 264}
283 265
284void arrange_windows(struct sway_container *container, 266void arrange_windows(struct sway_container *container) {
285 struct sway_transaction *transaction) {
286 switch (container->type) { 267 switch (container->type) {
287 case C_ROOT: 268 case C_ROOT:
288 arrange_root(transaction); 269 arrange_root();
289 break; 270 break;
290 case C_OUTPUT: 271 case C_OUTPUT:
291 arrange_output(container, transaction); 272 arrange_output(container);
292 break; 273 break;
293 case C_WORKSPACE: 274 case C_WORKSPACE:
294 arrange_workspace(container, transaction); 275 arrange_workspace(container);
295 break; 276 break;
296 case C_CONTAINER: 277 case C_CONTAINER:
297 arrange_children_of(container, transaction); 278 arrange_children_of(container);
298 transaction_add_container(transaction, container); 279 container_set_dirty(container);
299 break; 280 break;
300 case C_VIEW: 281 case C_VIEW:
301 view_autoconfigure(container->sway_view); 282 view_autoconfigure(container->sway_view);
302 transaction_add_container(transaction, container); 283 container_set_dirty(container);
303 break; 284 break;
304 case C_TYPES: 285 case C_TYPES:
305 break; 286 break;
306 } 287 }
307 add_deleted_containers(transaction);
308}
309
310void arrange_and_commit(struct sway_container *container) {
311 struct sway_transaction *transaction = transaction_create();
312 arrange_windows(container, transaction);
313 transaction_commit(transaction);
314} 288}
315 289
316void remove_gaps(struct sway_container *c) { 290void remove_gaps(struct sway_container *c) {
317 if (c->current_gaps == 0) { 291 if (c->current_gaps == 0) {
318 wlr_log(L_DEBUG, "Removing gaps: not gapped: %p", c); 292 wlr_log(WLR_DEBUG, "Removing gaps: not gapped: %p", c);
319 return; 293 return;
320 } 294 }
321 295
@@ -326,12 +300,12 @@ void remove_gaps(struct sway_container *c) {
326 300
327 c->current_gaps = 0; 301 c->current_gaps = 0;
328 302
329 wlr_log(L_DEBUG, "Removing gaps %p", c); 303 wlr_log(WLR_DEBUG, "Removing gaps %p", c);
330} 304}
331 305
332void add_gaps(struct sway_container *c) { 306void add_gaps(struct sway_container *c) {
333 if (c->current_gaps > 0 || c->type == C_CONTAINER) { 307 if (c->current_gaps > 0 || c->type == C_CONTAINER) {
334 wlr_log(L_DEBUG, "Not adding gaps: %p", c); 308 wlr_log(WLR_DEBUG, "Not adding gaps: %p", c);
335 return; 309 return;
336 } 310 }
337 311
@@ -348,5 +322,5 @@ void add_gaps(struct sway_container *c) {
348 c->height -= 2 * gaps; 322 c->height -= 2 * gaps;
349 c->current_gaps = gaps; 323 c->current_gaps = gaps;
350 324
351 wlr_log(L_DEBUG, "Adding gaps: %p", c); 325 wlr_log(WLR_DEBUG, "Adding gaps: %p", c);
352} 326}
diff --git a/sway/tree/container.c b/sway/tree/container.c
index 6f6137c4..4f743c40 100644
--- a/sway/tree/container.c
+++ b/sway/tree/container.c
@@ -11,11 +11,15 @@
11#include "cairo.h" 11#include "cairo.h"
12#include "pango.h" 12#include "pango.h"
13#include "sway/config.h" 13#include "sway/config.h"
14#include "sway/desktop.h"
15#include "sway/desktop/transaction.h"
14#include "sway/input/input-manager.h" 16#include "sway/input/input-manager.h"
15#include "sway/input/seat.h" 17#include "sway/input/seat.h"
16#include "sway/ipc-server.h" 18#include "sway/ipc-server.h"
17#include "sway/output.h" 19#include "sway/output.h"
20#include "sway/scratchpad.h"
18#include "sway/server.h" 21#include "sway/server.h"
22#include "sway/tree/arrange.h"
19#include "sway/tree/layout.h" 23#include "sway/tree/layout.h"
20#include "sway/tree/view.h" 24#include "sway/tree/view.h"
21#include "sway/tree/workspace.h" 25#include "sway/tree/workspace.h"
@@ -28,7 +32,7 @@ static list_t *get_bfs_queue() {
28 if (!bfs_queue) { 32 if (!bfs_queue) {
29 bfs_queue = create_list(); 33 bfs_queue = create_list();
30 if (!bfs_queue) { 34 if (!bfs_queue) {
31 wlr_log(L_ERROR, "could not allocate list for bfs queue"); 35 wlr_log(WLR_ERROR, "could not allocate list for bfs queue");
32 return NULL; 36 return NULL;
33 } 37 }
34 } 38 }
@@ -151,18 +155,11 @@ void container_free(struct sway_container *cont) {
151 return; 155 return;
152 } 156 }
153 free(cont->name); 157 free(cont->name);
158 free(cont->formatted_title);
154 wlr_texture_destroy(cont->title_focused); 159 wlr_texture_destroy(cont->title_focused);
155 wlr_texture_destroy(cont->title_focused_inactive); 160 wlr_texture_destroy(cont->title_focused_inactive);
156 wlr_texture_destroy(cont->title_unfocused); 161 wlr_texture_destroy(cont->title_unfocused);
157 wlr_texture_destroy(cont->title_urgent); 162 wlr_texture_destroy(cont->title_urgent);
158
159 for (int i = 0; i < server.destroying_containers->length; ++i) {
160 if (server.destroying_containers->items[i] == cont) {
161 list_del(server.destroying_containers, i);
162 break;
163 }
164 }
165
166 list_free(cont->instructions); 163 list_free(cont->instructions);
167 list_free(cont->children); 164 list_free(cont->children);
168 list_free(cont->current.children); 165 list_free(cont->current.children);
@@ -203,16 +200,23 @@ static struct sway_container *container_workspace_destroy(
203 return NULL; 200 return NULL;
204 } 201 }
205 202
206 // Do not destroy this if it's the last workspace on this output
207 struct sway_container *output = container_parent(workspace, C_OUTPUT); 203 struct sway_container *output = container_parent(workspace, C_OUTPUT);
208 if (output && output->children->length == 1) { 204
205 // If we're destroying the output, it will be NULL here. Return the root so
206 // that it doesn't appear that the workspace has refused to be destoyed,
207 // which would leave it in a broken state with no parent.
208 if (output == NULL) {
209 return &root_container;
210 }
211
212 // Do not destroy this if it's the last workspace on this output
213 if (output->children->length == 1) {
209 return NULL; 214 return NULL;
210 } 215 }
211 216
212 wlr_log(L_DEBUG, "destroying workspace '%s'", workspace->name); 217 wlr_log(WLR_DEBUG, "destroying workspace '%s'", workspace->name);
213 218
214 struct sway_container *parent = workspace->parent; 219 if (!workspace_is_empty(workspace)) {
215 if (!workspace_is_empty(workspace) && output) {
216 // Move children to a different workspace on this output 220 // Move children to a different workspace on this output
217 struct sway_container *new_workspace = NULL; 221 struct sway_container *new_workspace = NULL;
218 for (int i = 0; i < output->children->length; i++) { 222 for (int i = 0; i < output->children->length; i++) {
@@ -222,7 +226,7 @@ static struct sway_container *container_workspace_destroy(
222 } 226 }
223 } 227 }
224 228
225 wlr_log(L_DEBUG, "moving children to different workspace '%s' -> '%s'", 229 wlr_log(WLR_DEBUG, "moving children to different workspace '%s' -> '%s'",
226 workspace->name, new_workspace->name); 230 workspace->name, new_workspace->name);
227 for (int i = 0; i < workspace->children->length; i++) { 231 for (int i = 0; i < workspace->children->length; i++) {
228 container_move_to(workspace->children->items[i], new_workspace); 232 container_move_to(workspace->children->items[i], new_workspace);
@@ -234,7 +238,7 @@ static struct sway_container *container_workspace_destroy(
234 } 238 }
235 } 239 }
236 240
237 return parent; 241 return output;
238} 242}
239 243
240static struct sway_container *container_output_destroy( 244static struct sway_container *container_output_destroy(
@@ -288,7 +292,7 @@ static struct sway_container *container_output_destroy(
288 output->sway_output->swayc = NULL; 292 output->sway_output->swayc = NULL;
289 output->sway_output = NULL; 293 output->sway_output = NULL;
290 294
291 wlr_log(L_DEBUG, "OUTPUT: Destroying output '%s'", output->name); 295 wlr_log(WLR_DEBUG, "OUTPUT: Destroying output '%s'", output->name);
292 296
293 return &root_container; 297 return &root_container;
294} 298}
@@ -315,13 +319,19 @@ static struct sway_container *container_destroy_noreaping(
315 // Workspaces will refuse to be destroyed if they're the last workspace 319 // Workspaces will refuse to be destroyed if they're the last workspace
316 // on their output. 320 // on their output.
317 if (!container_workspace_destroy(con)) { 321 if (!container_workspace_destroy(con)) {
318 wlr_log(L_ERROR, "workspace doesn't want to destroy"); 322 wlr_log(WLR_ERROR, "workspace doesn't want to destroy");
319 return NULL; 323 return NULL;
320 } 324 }
321 } 325 }
322 326
327 container_end_mouse_operation(con);
328
323 con->destroying = true; 329 con->destroying = true;
324 list_add(server.destroying_containers, con); 330 container_set_dirty(con);
331
332 if (con->scratchpad) {
333 scratchpad_remove_container(con);
334 }
325 335
326 if (!con->parent) { 336 if (!con->parent) {
327 return NULL; 337 return NULL;
@@ -342,7 +352,7 @@ bool container_reap_empty(struct sway_container *con) {
342 break; 352 break;
343 case C_WORKSPACE: 353 case C_WORKSPACE:
344 if (!workspace_is_visible(con) && workspace_is_empty(con)) { 354 if (!workspace_is_visible(con) && workspace_is_empty(con)) {
345 wlr_log(L_DEBUG, "Destroying workspace via reaper"); 355 wlr_log(WLR_DEBUG, "Destroying workspace via reaper");
346 container_destroy_noreaping(con); 356 container_destroy_noreaping(con);
347 return true; 357 return true;
348 } 358 }
@@ -435,7 +445,7 @@ struct sway_container *container_view_create(struct sway_container *sibling,
435 } 445 }
436 const char *title = view_get_title(sway_view); 446 const char *title = view_get_title(sway_view);
437 struct sway_container *swayc = container_create(C_VIEW); 447 struct sway_container *swayc = container_create(C_VIEW);
438 wlr_log(L_DEBUG, "Adding new view %p:%s to container %p %d %s", 448 wlr_log(WLR_DEBUG, "Adding new view %p:%s to container %p %d %s",
439 swayc, title, sibling, sibling ? sibling->type : 0, sibling->name); 449 swayc, title, sibling, sibling ? sibling->type : 0, sibling->name);
440 // Setup values 450 // Setup values
441 swayc->sway_view = sway_view; 451 swayc->sway_view = sway_view;
@@ -657,7 +667,9 @@ struct sway_container *floating_container_at(double lx, double ly,
657 if (!workspace_is_visible(workspace)) { 667 if (!workspace_is_visible(workspace)) {
658 continue; 668 continue;
659 } 669 }
660 for (int k = 0; k < ws->floating->children->length; ++k) { 670 // Items at the end of the list are on top, so iterate the list in
671 // reverse.
672 for (int k = ws->floating->children->length - 1; k >= 0; --k) {
661 struct sway_container *floater = 673 struct sway_container *floater =
662 ws->floating->children->items[k]; 674 ws->floating->children->items[k];
663 struct wlr_box box = { 675 struct wlr_box box = {
@@ -678,16 +690,23 @@ struct sway_container *floating_container_at(double lx, double ly,
678void container_for_each_descendant_dfs(struct sway_container *container, 690void container_for_each_descendant_dfs(struct sway_container *container,
679 void (*f)(struct sway_container *container, void *data), 691 void (*f)(struct sway_container *container, void *data),
680 void *data) { 692 void *data) {
681 if (container) { 693 if (!container) {
682 if (container->children) { 694 return;
683 for (int i = 0; i < container->children->length; ++i) { 695 }
684 struct sway_container *child = 696 if (container->children) {
685 container->children->items[i]; 697 for (int i = 0; i < container->children->length; ++i) {
686 container_for_each_descendant_dfs(child, f, data); 698 struct sway_container *child = container->children->items[i];
687 } 699 container_for_each_descendant_dfs(child, f, data);
688 } 700 }
689 f(container, data);
690 } 701 }
702 if (container->type == C_WORKSPACE) {
703 struct sway_container *floating = container->sway_workspace->floating;
704 for (int i = 0; i < floating->children->length; ++i) {
705 struct sway_container *child = floating->children->items[i];
706 container_for_each_descendant_dfs(child, f, data);
707 }
708 }
709 f(container, data);
691} 710}
692 711
693void container_for_each_descendant_bfs(struct sway_container *con, 712void container_for_each_descendant_bfs(struct sway_container *con,
@@ -698,7 +717,7 @@ void container_for_each_descendant_bfs(struct sway_container *con,
698 } 717 }
699 718
700 if (queue == NULL) { 719 if (queue == NULL) {
701 wlr_log(L_ERROR, "could not allocate list"); 720 wlr_log(WLR_ERROR, "could not allocate list");
702 return; 721 return;
703 } 722 }
704 723
@@ -782,7 +801,7 @@ static void update_title_texture(struct sway_container *con,
782 801
783 double scale = output->sway_output->wlr_output->scale; 802 double scale = output->sway_output->wlr_output->scale;
784 int width = 0; 803 int width = 0;
785 int height = config->font_height * scale; 804 int height = con->title_height * scale;
786 805
787 cairo_t *c = cairo_create(NULL); 806 cairo_t *c = cairo_create(NULL);
788 get_text_size(c, config->font, &width, NULL, scale, config->pango_markup, 807 get_text_size(c, config->font, &width, NULL, scale, config->pango_markup,
@@ -935,11 +954,15 @@ void container_set_floating(struct sway_container *container, bool enable) {
935 container_add_child(workspace->sway_workspace->floating, container); 954 container_add_child(workspace->sway_workspace->floating, container);
936 if (container->type == C_VIEW) { 955 if (container->type == C_VIEW) {
937 view_init_floating(container->sway_view); 956 view_init_floating(container->sway_view);
957 view_set_tiled(container->sway_view, false);
938 } 958 }
939 seat_set_focus(seat, seat_get_focus_inactive(seat, container)); 959 seat_set_focus(seat, seat_get_focus_inactive(seat, container));
940 container_reap_empty_recursive(workspace); 960 container_reap_empty_recursive(workspace);
941 } else { 961 } else {
942 // Returning to tiled 962 // Returning to tiled
963 if (container->scratchpad) {
964 scratchpad_remove_container(container);
965 }
943 container_remove_child(container); 966 container_remove_child(container);
944 container_add_child(workspace, container); 967 container_add_child(workspace, container);
945 container->width = container->parent->width; 968 container->width = container->parent->width;
@@ -951,6 +974,8 @@ void container_set_floating(struct sway_container *container, bool enable) {
951 container_reap_empty_recursive(workspace->sway_workspace->floating); 974 container_reap_empty_recursive(workspace->sway_workspace->floating);
952 } 975 }
953 976
977 container_end_mouse_operation(container);
978
954 ipc_event_window(container, "floating"); 979 ipc_event_window(container, "floating");
955} 980}
956 981
@@ -963,9 +988,14 @@ void container_set_geometry_from_floating_view(struct sway_container *con) {
963 return; 988 return;
964 } 989 }
965 struct sway_view *view = con->sway_view; 990 struct sway_view *view = con->sway_view;
966 size_t border_width = view->border_thickness * (view->border != B_NONE); 991 size_t border_width = 0;
967 size_t top = 992 size_t top = 0;
968 view->border == B_NORMAL ? container_titlebar_height() : border_width; 993
994 if (!view->using_csd) {
995 border_width = view->border_thickness * (view->border != B_NONE);
996 top = view->border == B_NORMAL ?
997 container_titlebar_height() : border_width;
998 }
969 999
970 con->x = view->x - border_width; 1000 con->x = view->x - border_width;
971 con->y = view->y - top; 1001 con->y = view->y - top;
@@ -987,3 +1017,112 @@ void container_get_box(struct sway_container *container, struct wlr_box *box) {
987 box->width = container->width; 1017 box->width = container->width;
988 box->height = container->height; 1018 box->height = container->height;
989} 1019}
1020
1021/**
1022 * Translate the container's position as well as all children.
1023 */
1024void container_floating_translate(struct sway_container *con,
1025 double x_amount, double y_amount) {
1026 con->x += x_amount;
1027 con->y += y_amount;
1028 con->current.swayc_x += x_amount;
1029 con->current.swayc_y += y_amount;
1030 if (con->type == C_VIEW) {
1031 con->sway_view->x += x_amount;
1032 con->sway_view->y += y_amount;
1033 con->current.view_x += x_amount;
1034 con->current.view_y += y_amount;
1035 } else {
1036 for (int i = 0; i < con->children->length; ++i) {
1037 struct sway_container *child = con->children->items[i];
1038 container_floating_translate(child, x_amount, y_amount);
1039 }
1040 }
1041}
1042
1043/**
1044 * Choose an output for the floating container's new position.
1045 *
1046 * If the center of the container intersects an output then we'll choose that
1047 * one, otherwise we'll choose whichever output is closest to the container's
1048 * center.
1049 */
1050static struct sway_container *container_floating_find_output(
1051 struct sway_container *con) {
1052 double center_x = con->x + con->width / 2;
1053 double center_y = con->y + con->height / 2;
1054 struct sway_container *closest_output = NULL;
1055 double closest_distance = DBL_MAX;
1056 for (int i = 0; i < root_container.children->length; ++i) {
1057 struct sway_container *output = root_container.children->items[i];
1058 struct wlr_box output_box;
1059 double closest_x, closest_y;
1060 container_get_box(output, &output_box);
1061 wlr_box_closest_point(&output_box, center_x, center_y,
1062 &closest_x, &closest_y);
1063 if (center_x == closest_x && center_y == closest_y) {
1064 // The center of the floating container is on this output
1065 return output;
1066 }
1067 double x_dist = closest_x - center_x;
1068 double y_dist = closest_y - center_y;
1069 double distance = x_dist * x_dist + y_dist * y_dist;
1070 if (distance < closest_distance) {
1071 closest_output = output;
1072 closest_distance = distance;
1073 }
1074 }
1075 return closest_output;
1076}
1077
1078void container_floating_move_to(struct sway_container *con,
1079 double lx, double ly) {
1080 if (!sway_assert(container_is_floating(con),
1081 "Expected a floating container")) {
1082 return;
1083 }
1084 desktop_damage_whole_container(con);
1085 container_floating_translate(con, lx - con->x, ly - con->y);
1086 desktop_damage_whole_container(con);
1087 struct sway_container *old_workspace = container_parent(con, C_WORKSPACE);
1088 struct sway_container *new_output = container_floating_find_output(con);
1089 if (!sway_assert(new_output, "Unable to find any output")) {
1090 return;
1091 }
1092 struct sway_container *new_workspace =
1093 output_get_active_workspace(new_output->sway_output);
1094 if (old_workspace != new_workspace) {
1095 container_remove_child(con);
1096 container_add_child(new_workspace->sway_workspace->floating, con);
1097 arrange_windows(old_workspace);
1098 arrange_windows(new_workspace);
1099 workspace_detect_urgent(old_workspace);
1100 workspace_detect_urgent(new_workspace);
1101 }
1102}
1103
1104void container_set_dirty(struct sway_container *container) {
1105 if (container->dirty) {
1106 return;
1107 }
1108 container->dirty = true;
1109 list_add(server.dirty_containers, container);
1110}
1111
1112static bool find_urgent_iterator(struct sway_container *con,
1113 void *data) {
1114 return con->type == C_VIEW && view_is_urgent(con->sway_view);
1115}
1116
1117bool container_has_urgent_child(struct sway_container *container) {
1118 return container_find(container, find_urgent_iterator, NULL);
1119}
1120
1121void container_end_mouse_operation(struct sway_container *container) {
1122 struct sway_seat *seat;
1123 wl_list_for_each(seat, &input_manager->seats, link) {
1124 if (seat->op_container == container) {
1125 seat_end_mouse_operation(seat);
1126 }
1127 }
1128}
diff --git a/sway/tree/layout.c b/sway/tree/layout.c
index 14631ad4..a2be0ef3 100644
--- a/sway/tree/layout.c
+++ b/sway/tree/layout.c
@@ -22,7 +22,8 @@ struct sway_container root_container;
22 22
23static void output_layout_handle_change(struct wl_listener *listener, 23static void output_layout_handle_change(struct wl_listener *listener,
24 void *data) { 24 void *data) {
25 arrange_and_commit(&root_container); 25 arrange_windows(&root_container);
26 transaction_commit_dirty();
26} 27}
27 28
28void layout_init(void) { 29void layout_init(void) {
@@ -41,6 +42,7 @@ void layout_init(void) {
41 wl_list_init(&root_container.sway_root->xwayland_unmanaged); 42 wl_list_init(&root_container.sway_root->xwayland_unmanaged);
42 wl_list_init(&root_container.sway_root->drag_icons); 43 wl_list_init(&root_container.sway_root->drag_icons);
43 wl_signal_init(&root_container.sway_root->events.new_container); 44 wl_signal_init(&root_container.sway_root->events.new_container);
45 root_container.sway_root->scratchpad = create_list();
44 46
45 root_container.sway_root->output_layout_change.notify = 47 root_container.sway_root->output_layout_change.notify =
46 output_layout_handle_change; 48 output_layout_handle_change;
@@ -101,7 +103,7 @@ void container_insert_child(struct sway_container *parent,
101 if (old_parent) { 103 if (old_parent) {
102 container_remove_child(child); 104 container_remove_child(child);
103 } 105 }
104 wlr_log(L_DEBUG, "Inserting id:%zd at index %d", child->id, i); 106 wlr_log(WLR_DEBUG, "Inserting id:%zd at index %d", child->id, i);
105 list_insert(parent->children, i, child); 107 list_insert(parent->children, i, child);
106 child->parent = parent; 108 child->parent = parent;
107 container_handle_fullscreen_reparent(child, old_parent); 109 container_handle_fullscreen_reparent(child, old_parent);
@@ -127,13 +129,17 @@ struct sway_container *container_add_sibling(struct sway_container *fixed,
127 129
128void container_add_child(struct sway_container *parent, 130void container_add_child(struct sway_container *parent,
129 struct sway_container *child) { 131 struct sway_container *child) {
130 wlr_log(L_DEBUG, "Adding %p (%d, %fx%f) to %p (%d, %fx%f)", 132 wlr_log(WLR_DEBUG, "Adding %p (%d, %fx%f) to %p (%d, %fx%f)",
131 child, child->type, child->width, child->height, 133 child, child->type, child->width, child->height,
132 parent, parent->type, parent->width, parent->height); 134 parent, parent->type, parent->width, parent->height);
133 struct sway_container *old_parent = child->parent; 135 struct sway_container *old_parent = child->parent;
134 list_add(parent->children, child); 136 list_add(parent->children, child);
135 child->parent = parent; 137 child->parent = parent;
136 container_handle_fullscreen_reparent(child, old_parent); 138 container_handle_fullscreen_reparent(child, old_parent);
139 if (old_parent) {
140 container_set_dirty(old_parent);
141 }
142 container_set_dirty(child);
137} 143}
138 144
139struct sway_container *container_remove_child(struct sway_container *child) { 145struct sway_container *container_remove_child(struct sway_container *child) {
@@ -152,6 +158,9 @@ struct sway_container *container_remove_child(struct sway_container *child) {
152 child->parent = NULL; 158 child->parent = NULL;
153 container_notify_subtree_changed(parent); 159 container_notify_subtree_changed(parent);
154 160
161 container_set_dirty(parent);
162 container_set_dirty(child);
163
155 return parent; 164 return parent;
156} 165}
157 166
@@ -168,7 +177,12 @@ void container_move_to(struct sway_container *container,
168 struct sway_container *old_parent = container_remove_child(container); 177 struct sway_container *old_parent = container_remove_child(container);
169 container->width = container->height = 0; 178 container->width = container->height = 0;
170 container->saved_width = container->saved_height = 0; 179 container->saved_width = container->saved_height = 0;
171 struct sway_container *new_parent; 180
181 struct sway_container *new_parent, *new_parent_focus;
182 struct sway_seat *seat = input_manager_get_default_seat(input_manager);
183
184 // Get the focus of the destination before we change it.
185 new_parent_focus = seat_get_focus_inactive(seat, destination);
172 if (destination->type == C_VIEW) { 186 if (destination->type == C_VIEW) {
173 new_parent = container_add_sibling(destination, container); 187 new_parent = container_add_sibling(destination, container);
174 } else { 188 } else {
@@ -176,17 +190,20 @@ void container_move_to(struct sway_container *container,
176 container_add_child(destination, container); 190 container_add_child(destination, container);
177 } 191 }
178 wl_signal_emit(&container->events.reparent, old_parent); 192 wl_signal_emit(&container->events.reparent, old_parent);
193
179 if (container->type == C_WORKSPACE) { 194 if (container->type == C_WORKSPACE) {
180 // If moving a workspace to a new output, maybe create a new workspace 195 // If moving a workspace to a new output, maybe create a new workspace
181 // on the previous output 196 // on the previous output
182 struct sway_seat *seat = input_manager_get_default_seat(input_manager);
183 if (old_parent->children->length == 0) { 197 if (old_parent->children->length == 0) {
184 char *ws_name = workspace_next_name(old_parent->name); 198 char *ws_name = workspace_next_name(old_parent->name);
185 struct sway_container *ws = 199 struct sway_container *ws = workspace_create(old_parent, ws_name);
186 workspace_create(old_parent, ws_name);
187 free(ws_name); 200 free(ws_name);
188 seat_set_focus(seat, ws); 201 seat_set_focus(seat, ws);
189 } 202 }
203
204 // Try to remove an empty workspace from the destination output.
205 container_reap_empty_recursive(new_parent_focus);
206
190 container_sort_workspaces(new_parent); 207 container_sort_workspaces(new_parent);
191 seat_set_focus(seat, new_parent); 208 seat_set_focus(seat, new_parent);
192 workspace_output_raise_priority(container, old_parent, new_parent); 209 workspace_output_raise_priority(container, old_parent, new_parent);
@@ -216,6 +233,17 @@ void container_move_to(struct sway_container *container,
216 } 233 }
217 } 234 }
218 } 235 }
236 // Update workspace urgent state
237 struct sway_container *old_workspace = old_parent;
238 if (old_workspace->type != C_WORKSPACE) {
239 old_workspace = container_parent(old_workspace, C_WORKSPACE);
240 }
241 if (new_workspace != old_workspace) {
242 workspace_detect_urgent(new_workspace);
243 if (old_workspace) {
244 workspace_detect_urgent(old_workspace);
245 }
246 }
219} 247}
220 248
221static bool sway_dir_to_wlr(enum movement_direction dir, 249static bool sway_dir_to_wlr(enum movement_direction dir,
@@ -311,13 +339,13 @@ static void move_out_of_tabs_stacks(struct sway_container *container,
311 int offs) { 339 int offs) {
312 if (container->parent == current->parent 340 if (container->parent == current->parent
313 && current->parent->children->length == 1) { 341 && current->parent->children->length == 1) {
314 wlr_log(L_DEBUG, "Changing layout of %zd", current->parent->id); 342 wlr_log(WLR_DEBUG, "Changing layout of %zd", current->parent->id);
315 current->parent->layout = move_dir == 343 current->parent->layout = move_dir ==
316 MOVE_LEFT || move_dir == MOVE_RIGHT ? L_HORIZ : L_VERT; 344 MOVE_LEFT || move_dir == MOVE_RIGHT ? L_HORIZ : L_VERT;
317 return; 345 return;
318 } 346 }
319 347
320 wlr_log(L_DEBUG, "Moving out of tab/stack into a split"); 348 wlr_log(WLR_DEBUG, "Moving out of tab/stack into a split");
321 bool is_workspace = current->parent->type == C_WORKSPACE; 349 bool is_workspace = current->parent->type == C_WORKSPACE;
322 struct sway_container *new_parent = container_split(current->parent, 350 struct sway_container *new_parent = container_split(current->parent,
323 move_dir == MOVE_LEFT || move_dir == MOVE_RIGHT ? L_HORIZ : L_VERT); 351 move_dir == MOVE_LEFT || move_dir == MOVE_RIGHT ? L_HORIZ : L_VERT);
@@ -362,7 +390,7 @@ void container_move(struct sway_container *container,
362 } 390 }
363 391
364 parent = current->parent; 392 parent = current->parent;
365 wlr_log(L_DEBUG, "Visiting %p %s '%s'", current, 393 wlr_log(WLR_DEBUG, "Visiting %p %s '%s'", current,
366 container_type_to_str(current->type), current->name); 394 container_type_to_str(current->type), current->name);
367 395
368 int index = index_child(current); 396 int index = index_child(current);
@@ -380,12 +408,12 @@ void container_move(struct sway_container *container,
380 root_container.sway_root->output_layout, wlr_dir, 408 root_container.sway_root->output_layout, wlr_dir,
381 current->sway_output->wlr_output, ref_lx, ref_ly); 409 current->sway_output->wlr_output, ref_lx, ref_ly);
382 if (!next) { 410 if (!next) {
383 wlr_log(L_DEBUG, "Hit edge of output, nowhere else to go"); 411 wlr_log(WLR_DEBUG, "Hit edge of output, nowhere else to go");
384 return; 412 return;
385 } 413 }
386 struct sway_output *next_output = next->data; 414 struct sway_output *next_output = next->data;
387 current = next_output->swayc; 415 current = next_output->swayc;
388 wlr_log(L_DEBUG, "Selected next output (%s)", current->name); 416 wlr_log(WLR_DEBUG, "Selected next output (%s)", current->name);
389 // Select workspace and get outta here 417 // Select workspace and get outta here
390 current = seat_get_focus_inactive( 418 current = seat_get_focus_inactive(
391 config->handler_context.seat, current); 419 config->handler_context.seat, current);
@@ -398,20 +426,20 @@ void container_move(struct sway_container *container,
398 case C_WORKSPACE: 426 case C_WORKSPACE:
399 if (!is_parallel(current->layout, move_dir)) { 427 if (!is_parallel(current->layout, move_dir)) {
400 if (current->children->length >= 2) { 428 if (current->children->length >= 2) {
401 wlr_log(L_DEBUG, "Rejiggering the workspace (%d kiddos)", 429 wlr_log(WLR_DEBUG, "Rejiggering the workspace (%d kiddos)",
402 current->children->length); 430 current->children->length);
403 workspace_rejigger(current, container, move_dir); 431 workspace_rejigger(current, container, move_dir);
404 return; 432 return;
405 } else { 433 } else {
406 wlr_log(L_DEBUG, "Selecting output"); 434 wlr_log(WLR_DEBUG, "Selecting output");
407 current = current->parent; 435 current = current->parent;
408 } 436 }
409 } else if (current->layout == L_TABBED 437 } else if (current->layout == L_TABBED
410 || current->layout == L_STACKED) { 438 || current->layout == L_STACKED) {
411 wlr_log(L_DEBUG, "Rejiggering out of tabs/stacks"); 439 wlr_log(WLR_DEBUG, "Rejiggering out of tabs/stacks");
412 workspace_rejigger(current, container, move_dir); 440 workspace_rejigger(current, container, move_dir);
413 } else { 441 } else {
414 wlr_log(L_DEBUG, "Selecting output"); 442 wlr_log(WLR_DEBUG, "Selecting output");
415 current = current->parent; 443 current = current->parent;
416 } 444 }
417 break; 445 break;
@@ -427,11 +455,11 @@ void container_move(struct sway_container *container,
427 move_dir, offs); 455 move_dir, offs);
428 return; 456 return;
429 } else { 457 } else {
430 wlr_log(L_DEBUG, "Hit limit, selecting parent"); 458 wlr_log(WLR_DEBUG, "Hit limit, selecting parent");
431 current = current->parent; 459 current = current->parent;
432 } 460 }
433 } else { 461 } else {
434 wlr_log(L_DEBUG, "Hit limit, " 462 wlr_log(WLR_DEBUG, "Hit limit, "
435 "promoting descendant to sibling"); 463 "promoting descendant to sibling");
436 // Special case 464 // Special case
437 container_insert_child(current->parent, container, 465 container_insert_child(current->parent, container,
@@ -441,14 +469,14 @@ void container_move(struct sway_container *container,
441 } 469 }
442 } else { 470 } else {
443 sibling = parent->children->items[index + offs]; 471 sibling = parent->children->items[index + offs];
444 wlr_log(L_DEBUG, "Selecting sibling id:%zd", sibling->id); 472 wlr_log(WLR_DEBUG, "Selecting sibling id:%zd", sibling->id);
445 } 473 }
446 } else if (parent->layout == L_TABBED 474 } else if (parent->layout == L_TABBED
447 || parent->layout == L_STACKED) { 475 || parent->layout == L_STACKED) {
448 move_out_of_tabs_stacks(container, current, move_dir, offs); 476 move_out_of_tabs_stacks(container, current, move_dir, offs);
449 return; 477 return;
450 } else { 478 } else {
451 wlr_log(L_DEBUG, "Moving up to find a parallel container"); 479 wlr_log(WLR_DEBUG, "Moving up to find a parallel container");
452 current = current->parent; 480 current = current->parent;
453 } 481 }
454 break; 482 break;
@@ -467,11 +495,11 @@ void container_move(struct sway_container *container,
467 switch (sibling->type) { 495 switch (sibling->type) {
468 case C_VIEW: 496 case C_VIEW:
469 if (sibling->parent == container->parent) { 497 if (sibling->parent == container->parent) {
470 wlr_log(L_DEBUG, "Swapping siblings"); 498 wlr_log(WLR_DEBUG, "Swapping siblings");
471 sibling->parent->children->items[index + offs] = container; 499 sibling->parent->children->items[index + offs] = container;
472 sibling->parent->children->items[index] = sibling; 500 sibling->parent->children->items[index] = sibling;
473 } else { 501 } else {
474 wlr_log(L_DEBUG, "Promoting to sibling of cousin"); 502 wlr_log(WLR_DEBUG, "Promoting to sibling of cousin");
475 container_insert_child(sibling->parent, container, 503 container_insert_child(sibling->parent, container,
476 index_child(sibling) + (offs > 0 ? 0 : 1)); 504 index_child(sibling) + (offs > 0 ? 0 : 1));
477 container->width = container->height = 0; 505 container->width = container->height = 0;
@@ -482,31 +510,31 @@ void container_move(struct sway_container *container,
482 case C_CONTAINER: 510 case C_CONTAINER:
483 if (is_parallel(sibling->layout, move_dir)) { 511 if (is_parallel(sibling->layout, move_dir)) {
484 int limit = container_limit(sibling, invert_movement(move_dir)); 512 int limit = container_limit(sibling, invert_movement(move_dir));
485 wlr_log(L_DEBUG, "limit: %d", limit); 513 wlr_log(WLR_DEBUG, "limit: %d", limit);
486 wlr_log(L_DEBUG, 514 wlr_log(WLR_DEBUG,
487 "Reparenting container (parallel) to index %d " 515 "Reparenting container (parallel) to index %d "
488 "(move dir: %d)", limit, move_dir); 516 "(move dir: %d)", limit, move_dir);
489 container_insert_child(sibling, container, limit); 517 container_insert_child(sibling, container, limit);
490 container->width = container->height = 0; 518 container->width = container->height = 0;
491 sibling = NULL; 519 sibling = NULL;
492 } else { 520 } else {
493 wlr_log(L_DEBUG, "Reparenting container (perpendicular)"); 521 wlr_log(WLR_DEBUG, "Reparenting container (perpendicular)");
494 struct sway_container *focus_inactive = seat_get_focus_inactive( 522 struct sway_container *focus_inactive = seat_get_focus_inactive(
495 config->handler_context.seat, sibling); 523 config->handler_context.seat, sibling);
496 if (focus_inactive && focus_inactive != sibling) { 524 if (focus_inactive && focus_inactive != sibling) {
497 while (focus_inactive->parent != sibling) { 525 while (focus_inactive->parent != sibling) {
498 focus_inactive = focus_inactive->parent; 526 focus_inactive = focus_inactive->parent;
499 } 527 }
500 wlr_log(L_DEBUG, "Focus inactive: id:%zd", 528 wlr_log(WLR_DEBUG, "Focus inactive: id:%zd",
501 focus_inactive->id); 529 focus_inactive->id);
502 sibling = focus_inactive; 530 sibling = focus_inactive;
503 continue; 531 continue;
504 } else if (sibling->children->length) { 532 } else if (sibling->children->length) {
505 wlr_log(L_DEBUG, "No focus-inactive, adding arbitrarily"); 533 wlr_log(WLR_DEBUG, "No focus-inactive, adding arbitrarily");
506 container_remove_child(container); 534 container_remove_child(container);
507 container_add_sibling(sibling->children->items[0], container); 535 container_add_sibling(sibling->children->items[0], container);
508 } else { 536 } else {
509 wlr_log(L_DEBUG, "No kiddos, adding container alone"); 537 wlr_log(WLR_DEBUG, "No kiddos, adding container alone");
510 container_remove_child(container); 538 container_remove_child(container);
511 container_add_child(sibling, container); 539 container_add_child(sibling, container);
512 } 540 }
@@ -539,7 +567,10 @@ void container_move(struct sway_container *container,
539 } 567 }
540 if (last_ws && next_ws && last_ws != next_ws) { 568 if (last_ws && next_ws && last_ws != next_ws) {
541 ipc_event_workspace(last_ws, container, "focus"); 569 ipc_event_workspace(last_ws, container, "focus");
570 workspace_detect_urgent(last_ws);
571 workspace_detect_urgent(next_ws);
542 } 572 }
573 container_end_mouse_operation(container);
543} 574}
544 575
545enum sway_container_layout container_get_default_layout( 576enum sway_container_layout container_get_default_layout(
@@ -603,7 +634,7 @@ static struct sway_container *get_swayc_in_output_direction(
603 } 634 }
604 635
605 if (ws == NULL) { 636 if (ws == NULL) {
606 wlr_log(L_ERROR, "got an output without a workspace"); 637 wlr_log(WLR_ERROR, "got an output without a workspace");
607 return NULL; 638 return NULL;
608 } 639 }
609 640
@@ -775,7 +806,7 @@ struct sway_container *container_get_in_direction(
775 } else { 806 } else {
776 struct sway_container *desired_con = 807 struct sway_container *desired_con =
777 parent->children->items[desired]; 808 parent->children->items[desired];
778 wlr_log(L_DEBUG, 809 wlr_log(WLR_DEBUG,
779 "cont %d-%p dir %i sibling %d: %p", idx, 810 "cont %d-%p dir %i sibling %d: %p", idx,
780 container, dir, desired, desired_con); 811 container, dir, desired, desired_con);
781 return seat_get_focus_inactive_view(seat, desired_con); 812 return seat_get_focus_inactive_view(seat, desired_con);
@@ -840,7 +871,7 @@ struct sway_container *container_split(struct sway_container *child,
840 871
841 struct sway_container *cont = container_create(C_CONTAINER); 872 struct sway_container *cont = container_create(C_CONTAINER);
842 873
843 wlr_log(L_DEBUG, "creating container %p around %p", cont, child); 874 wlr_log(WLR_DEBUG, "creating container %p around %p", cont, child);
844 875
845 remove_gaps(child); 876 remove_gaps(child);
846 877
@@ -888,7 +919,7 @@ struct sway_container *container_split(struct sway_container *child,
888void container_recursive_resize(struct sway_container *container, 919void container_recursive_resize(struct sway_container *container,
889 double amount, enum resize_edge edge) { 920 double amount, enum resize_edge edge) {
890 bool layout_match = true; 921 bool layout_match = true;
891 wlr_log(L_DEBUG, "Resizing %p with amount: %f", container, amount); 922 wlr_log(WLR_DEBUG, "Resizing %p with amount: %f", container, amount);
892 if (edge == RESIZE_EDGE_LEFT || edge == RESIZE_EDGE_RIGHT) { 923 if (edge == RESIZE_EDGE_LEFT || edge == RESIZE_EDGE_RIGHT) {
893 container->width += amount; 924 container->width += amount;
894 layout_match = container->layout == L_HORIZ; 925 layout_match = container->layout == L_HORIZ;
@@ -978,7 +1009,7 @@ void container_swap(struct sway_container *con1, struct sway_container *con2) {
978 return; 1009 return;
979 } 1010 }
980 1011
981 wlr_log(L_DEBUG, "Swapping containers %zu and %zu", con1->id, con2->id); 1012 wlr_log(WLR_DEBUG, "Swapping containers %zu and %zu", con1->id, con2->id);
982 1013
983 int fs1 = con1->type == C_VIEW && con1->sway_view->is_fullscreen; 1014 int fs1 = con1->type == C_VIEW && con1->sway_view->is_fullscreen;
984 int fs2 = con2->type == C_VIEW && con2->sway_view->is_fullscreen; 1015 int fs2 = con2->type == C_VIEW && con2->sway_view->is_fullscreen;
diff --git a/sway/tree/output.c b/sway/tree/output.c
index e2927cdb..da535c18 100644
--- a/sway/tree/output.c
+++ b/sway/tree/output.c
@@ -43,11 +43,11 @@ struct sway_container *output_create(
43 43
44 if (strcasecmp(name, cur->name) == 0 || 44 if (strcasecmp(name, cur->name) == 0 ||
45 strcasecmp(identifier, cur->name) == 0) { 45 strcasecmp(identifier, cur->name) == 0) {
46 wlr_log(L_DEBUG, "Matched output config for %s", name); 46 wlr_log(WLR_DEBUG, "Matched output config for %s", name);
47 oc = cur; 47 oc = cur;
48 } 48 }
49 if (strcasecmp("*", cur->name) == 0) { 49 if (strcasecmp("*", cur->name) == 0) {
50 wlr_log(L_DEBUG, "Matched wildcard output config for %s", name); 50 wlr_log(WLR_DEBUG, "Matched wildcard output config for %s", name);
51 all = cur; 51 all = cur;
52 } 52 }
53 53
@@ -86,7 +86,7 @@ struct sway_container *output_create(
86 if (!output->children->length) { 86 if (!output->children->length) {
87 // Create workspace 87 // Create workspace
88 char *ws_name = workspace_next_name(output->name); 88 char *ws_name = workspace_next_name(output->name);
89 wlr_log(L_DEBUG, "Creating default workspace %s", ws_name); 89 wlr_log(WLR_DEBUG, "Creating default workspace %s", ws_name);
90 struct sway_container *ws = workspace_create(output, ws_name); 90 struct sway_container *ws = workspace_create(output, ws_name);
91 // Set each seat's focus if not already set 91 // Set each seat's focus if not already set
92 struct sway_seat *seat = NULL; 92 struct sway_seat *seat = NULL;
diff --git a/sway/tree/view.c b/sway/tree/view.c
index 24fb6864..a55c8a29 100644
--- a/sway/tree/view.c
+++ b/sway/tree/view.c
@@ -26,6 +26,7 @@ void view_init(struct sway_view *view, enum sway_view_type type,
26 view->impl = impl; 26 view->impl = impl;
27 view->executed_criteria = create_list(); 27 view->executed_criteria = create_list();
28 view->marks = create_list(); 28 view->marks = create_list();
29 view->allow_request_urgent = true;
29 wl_signal_init(&view->events.unmap); 30 wl_signal_init(&view->events.unmap);
30} 31}
31 32
@@ -141,6 +142,19 @@ const char *view_get_shell(struct sway_view *view) {
141 return "unknown"; 142 return "unknown";
142} 143}
143 144
145void view_get_constraints(struct sway_view *view, double *min_width,
146 double *max_width, double *min_height, double *max_height) {
147 if (view->impl->get_constraints) {
148 view->impl->get_constraints(view,
149 min_width, max_width, min_height, max_height);
150 } else {
151 *min_width = DBL_MIN;
152 *max_width = DBL_MAX;
153 *min_height = DBL_MIN;
154 *max_height = DBL_MAX;
155 }
156}
157
144uint32_t view_configure(struct sway_view *view, double lx, double ly, int width, 158uint32_t view_configure(struct sway_view *view, double lx, double ly, int width,
145 int height) { 159 int height) {
146 if (view->impl->configure) { 160 if (view->impl->configure) {
@@ -151,12 +165,43 @@ uint32_t view_configure(struct sway_view *view, double lx, double ly, int width,
151 165
152void view_init_floating(struct sway_view *view) { 166void view_init_floating(struct sway_view *view) {
153 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE); 167 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE);
154 int max_width = ws->width * 0.6666; 168 int min_width, min_height;
155 int max_height = ws->height * 0.6666; 169 int max_width, max_height;
156 view->width = 170
157 view->natural_width > max_width ? max_width : view->natural_width; 171 if (config->floating_minimum_width == -1) { // no minimum
158 view->height = 172 min_width = 0;
159 view->natural_height > max_height ? max_height : view->natural_height; 173 } else if (config->floating_minimum_width == 0) { // automatic
174 min_width = 75;
175 } else {
176 min_width = config->floating_minimum_width;
177 }
178
179 if (config->floating_minimum_height == -1) { // no minimum
180 min_height = 0;
181 } else if (config->floating_minimum_height == 0) { // automatic
182 min_height = 50;
183 } else {
184 min_height = config->floating_minimum_height;
185 }
186
187 if (config->floating_maximum_width == -1) { // no maximum
188 max_width = INT_MAX;
189 } else if (config->floating_maximum_width == 0) { // automatic
190 max_width = ws->width * 0.6666;
191 } else {
192 max_width = config->floating_maximum_width;
193 }
194
195 if (config->floating_maximum_height == -1) { // no maximum
196 max_height = INT_MAX;
197 } else if (config->floating_maximum_height == 0) { // automatic
198 max_height = ws->height * 0.6666;
199 } else {
200 max_height = config->floating_maximum_height;
201 }
202
203 view->width = fmax(min_width, fmin(view->natural_width, max_width));
204 view->height = fmax(min_height, fmin(view->natural_height, max_height));
160 view->x = ws->x + (ws->width - view->width) / 2; 205 view->x = ws->x + (ws->width - view->width) / 2;
161 view->y = ws->y + (ws->height - view->height) / 2; 206 view->y = ws->y + (ws->height - view->height) / 2;
162 207
@@ -165,9 +210,6 @@ void view_init_floating(struct sway_view *view) {
165 view->border_left = view->border_right = true; 210 view->border_left = view->border_right = true;
166 211
167 container_set_geometry_from_floating_view(view->swayc); 212 container_set_geometry_from_floating_view(view->swayc);
168
169 // Don't maximize floating windows
170 view_set_tiled(view, false);
171} 213}
172 214
173void view_autoconfigure(struct sway_view *view) { 215void view_autoconfigure(struct sway_view *view) {
@@ -279,7 +321,6 @@ void view_autoconfigure(struct sway_view *view) {
279 view->y = y; 321 view->y = y;
280 view->width = width; 322 view->width = width;
281 view->height = height; 323 view->height = height;
282 view_set_tiled(view, true);
283} 324}
284 325
285void view_set_activated(struct sway_view *view, bool activated) { 326void view_set_activated(struct sway_view *view, bool activated) {
@@ -289,7 +330,15 @@ void view_set_activated(struct sway_view *view, bool activated) {
289} 330}
290 331
291void view_set_tiled(struct sway_view *view, bool tiled) { 332void view_set_tiled(struct sway_view *view, bool tiled) {
292 view->border = tiled ? config->border : B_NONE; 333 if (!tiled) {
334 view->using_csd = true;
335 if (view->impl->has_client_side_decorations) {
336 view->using_csd = view->impl->has_client_side_decorations(view);
337 }
338 } else {
339 view->using_csd = false;
340 }
341
293 if (view->impl->set_tiled) { 342 if (view->impl->set_tiled) {
294 view->impl->set_tiled(view, tiled); 343 view->impl->set_tiled(view, tiled);
295 } 344 }
@@ -352,6 +401,8 @@ void view_set_fullscreen(struct sway_view *view, bool fullscreen) {
352 } 401 }
353 } 402 }
354 403
404 container_end_mouse_operation(view->swayc);
405
355 ipc_event_window(view->swayc, "fullscreen_mode"); 406 ipc_event_window(view->swayc, "fullscreen_mode");
356} 407}
357 408
@@ -467,27 +518,45 @@ void view_execute_criteria(struct sway_view *view) {
467 list_t *criterias = criteria_for_view(view, CT_COMMAND); 518 list_t *criterias = criteria_for_view(view, CT_COMMAND);
468 for (int i = 0; i < criterias->length; i++) { 519 for (int i = 0; i < criterias->length; i++) {
469 struct criteria *criteria = criterias->items[i]; 520 struct criteria *criteria = criterias->items[i];
470 wlr_log(L_DEBUG, "Checking criteria %s", criteria->raw); 521 wlr_log(WLR_DEBUG, "Checking criteria %s", criteria->raw);
471 if (view_has_executed_criteria(view, criteria)) { 522 if (view_has_executed_criteria(view, criteria)) {
472 wlr_log(L_DEBUG, "Criteria already executed"); 523 wlr_log(WLR_DEBUG, "Criteria already executed");
473 continue; 524 continue;
474 } 525 }
475 wlr_log(L_DEBUG, "for_window '%s' matches view %p, cmd: '%s'", 526 wlr_log(WLR_DEBUG, "for_window '%s' matches view %p, cmd: '%s'",
476 criteria->raw, view, criteria->cmdlist); 527 criteria->raw, view, criteria->cmdlist);
528 seat_set_focus(seat, view->swayc);
477 list_add(view->executed_criteria, criteria); 529 list_add(view->executed_criteria, criteria);
478 struct cmd_results *res = execute_command(criteria->cmdlist, NULL); 530 struct cmd_results *res = execute_command(criteria->cmdlist, NULL);
479 if (res->status != CMD_SUCCESS) { 531 if (res->status != CMD_SUCCESS) {
480 wlr_log(L_ERROR, "Command '%s' failed: %s", res->input, res->error); 532 wlr_log(WLR_ERROR, "Command '%s' failed: %s", res->input, res->error);
481 } 533 }
482 free_cmd_results(res); 534 free_cmd_results(res);
483 // view must be focused for commands to affect it,
484 // so always refocus in-between command lists
485 seat_set_focus(seat, view->swayc);
486 } 535 }
487 list_free(criterias); 536 list_free(criterias);
488 seat_set_focus(seat, prior_focus); 537 seat_set_focus(seat, prior_focus);
489} 538}
490 539
540static bool should_focus(struct sway_view *view) {
541 // If the view is the only one in the focused workspace, it'll get focus
542 // regardless of any no_focus criteria.
543 struct sway_container *parent = view->swayc->parent;
544 struct sway_seat *seat = input_manager_current_seat(input_manager);
545 if (parent->type == C_WORKSPACE && seat_get_focus(seat) == parent) {
546 size_t num_children = parent->children->length +
547 parent->sway_workspace->floating->children->length;
548 if (num_children == 1) {
549 return true;
550 }
551 }
552
553 // Check no_focus criteria
554 list_t *criterias = criteria_for_view(view, CT_NO_FOCUS);
555 size_t len = criterias->length;
556 list_free(criterias);
557 return len == 0;
558}
559
491void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) { 560void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) {
492 if (!sway_assert(view->surface == NULL, "cannot map mapped view")) { 561 if (!sway_assert(view->surface == NULL, "cannot map mapped view")) {
493 return; 562 return;
@@ -524,7 +593,11 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) {
524 prev_focus = target_sibling; 593 prev_focus = target_sibling;
525 target_sibling = seat_get_focus_inactive(seat, workspace); 594 target_sibling = seat_get_focus_inactive(seat, workspace);
526 } else { 595 } else {
527 // TODO: CT_ASSIGN_OUTPUT 596 // CT_ASSIGN_OUTPUT
597 struct sway_container *output = output_by_name(criteria->target);
598 if (output) {
599 prev_focus = seat_get_focus_inactive(seat, output);
600 }
528 } 601 }
529 } 602 }
530 list_free(criterias); 603 list_free(criterias);
@@ -549,8 +622,6 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) {
549 622
550 view->surface = wlr_surface; 623 view->surface = wlr_surface;
551 view->swayc = cont; 624 view->swayc = cont;
552 view->border = config->border;
553 view->border_thickness = config->border_thickness;
554 625
555 view_init_subsurfaces(view, wlr_surface); 626 view_init_subsurfaces(view, wlr_surface);
556 wl_signal_add(&wlr_surface->events.new_subsurface, 627 wl_signal_add(&wlr_surface->events.new_subsurface,
@@ -561,11 +632,20 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) {
561 view->container_reparent.notify = view_handle_container_reparent; 632 view->container_reparent.notify = view_handle_container_reparent;
562 633
563 if (view->impl->wants_floating && view->impl->wants_floating(view)) { 634 if (view->impl->wants_floating && view->impl->wants_floating(view)) {
635 view->border = config->floating_border;
636 view->border_thickness = config->floating_border_thickness;
564 container_set_floating(view->swayc, true); 637 container_set_floating(view->swayc, true);
638 } else {
639 view->border = config->border;
640 view->border_thickness = config->border_thickness;
641 view_set_tiled(view, true);
565 } 642 }
566 643
567 if (prev_focus == target_sibling) { 644 if (should_focus(view) && prev_focus == target_sibling) {
568 input_manager_set_focus(input_manager, cont); 645 input_manager_set_focus(input_manager, cont);
646 if (workspace) {
647 workspace_switch(workspace);
648 }
569 } 649 }
570 650
571 view_update_title(view, false); 651 view_update_title(view, false);
@@ -581,16 +661,27 @@ void view_unmap(struct sway_view *view) {
581 wl_list_remove(&view->surface_new_subsurface.link); 661 wl_list_remove(&view->surface_new_subsurface.link);
582 wl_list_remove(&view->container_reparent.link); 662 wl_list_remove(&view->container_reparent.link);
583 663
664 if (view->urgent_timer) {
665 wl_event_source_remove(view->urgent_timer);
666 view->urgent_timer = NULL;
667 }
668
669 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE);
670
671 struct sway_container *parent;
584 if (view->is_fullscreen) { 672 if (view->is_fullscreen) {
585 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE);
586 ws->sway_workspace->fullscreen = NULL; 673 ws->sway_workspace->fullscreen = NULL;
587 container_destroy(view->swayc); 674 parent = container_destroy(view->swayc);
588 675
589 arrange_and_commit(ws->parent); 676 arrange_windows(ws->parent);
590 } else { 677 } else {
591 struct sway_container *parent = container_destroy(view->swayc); 678 parent = container_destroy(view->swayc);
592 arrange_and_commit(parent); 679 arrange_windows(parent);
680 }
681 if (parent->type >= C_WORKSPACE) { // if the workspace still exists
682 workspace_detect_urgent(ws);
593 } 683 }
684 transaction_commit_dirty();
594 view->surface = NULL; 685 view->surface = NULL;
595} 686}
596 687
@@ -601,6 +692,8 @@ void view_update_position(struct sway_view *view, double lx, double ly) {
601 container_damage_whole(view->swayc); 692 container_damage_whole(view->swayc);
602 view->x = lx; 693 view->x = lx;
603 view->y = ly; 694 view->y = ly;
695 view->swayc->current.view_x = lx;
696 view->swayc->current.view_y = ly;
604 if (container_is_floating(view->swayc)) { 697 if (container_is_floating(view->swayc)) {
605 container_set_geometry_from_floating_view(view->swayc); 698 container_set_geometry_from_floating_view(view->swayc);
606 } 699 }
@@ -614,6 +707,8 @@ void view_update_size(struct sway_view *view, int width, int height) {
614 container_damage_whole(view->swayc); 707 container_damage_whole(view->swayc);
615 view->width = width; 708 view->width = width;
616 view->height = height; 709 view->height = height;
710 view->swayc->current.view_width = width;
711 view->swayc->current.view_height = height;
617 if (container_is_floating(view->swayc)) { 712 if (container_is_floating(view->swayc)) {
618 container_set_geometry_from_floating_view(view->swayc); 713 container_set_geometry_from_floating_view(view->swayc);
619 } 714 }
@@ -624,7 +719,7 @@ static void view_subsurface_create(struct sway_view *view,
624 struct wlr_subsurface *subsurface) { 719 struct wlr_subsurface *subsurface) {
625 struct sway_view_child *child = calloc(1, sizeof(struct sway_view_child)); 720 struct sway_view_child *child = calloc(1, sizeof(struct sway_view_child));
626 if (child == NULL) { 721 if (child == NULL) {
627 wlr_log(L_ERROR, "Allocation failed"); 722 wlr_log(WLR_ERROR, "Allocation failed");
628 return; 723 return;
629 } 724 }
630 view_child_init(child, NULL, view, subsurface->surface); 725 view_child_init(child, NULL, view, subsurface->surface);
@@ -744,8 +839,9 @@ struct sway_view *view_from_wlr_surface(struct wlr_surface *wlr_surface) {
744 return NULL; 839 return NULL;
745 } 840 }
746 841
747 wlr_log(L_DEBUG, "Surface of unknown type (role %s): %p", 842 const char *role = wlr_surface->role ? wlr_surface->role->name : NULL;
748 wlr_surface->role, wlr_surface); 843 wlr_log(WLR_DEBUG, "Surface of unknown type (role %s): %p",
844 role, wlr_surface);
749 return NULL; 845 return NULL;
750} 846}
751 847
@@ -812,7 +908,7 @@ static char *escape_title(char *buffer) {
812 char *escaped_title = calloc(length + 1, sizeof(char)); 908 char *escaped_title = calloc(length + 1, sizeof(char));
813 int result = escape_markup_text(buffer, escaped_title, length); 909 int result = escape_markup_text(buffer, escaped_title, length);
814 if (result != length) { 910 if (result != length) {
815 wlr_log(L_ERROR, "Could not escape title: %s", buffer); 911 wlr_log(WLR_ERROR, "Could not escape title: %s", buffer);
816 free(escaped_title); 912 free(escaped_title);
817 return buffer; 913 return buffer;
818 } 914 }
@@ -946,7 +1042,7 @@ static void update_marks_texture(struct sway_view *view,
946 1042
947 double scale = output->sway_output->wlr_output->scale; 1043 double scale = output->sway_output->wlr_output->scale;
948 int width = 0; 1044 int width = 0;
949 int height = config->font_height * scale; 1045 int height = view->swayc->title_height * scale;
950 1046
951 cairo_t *c = cairo_create(NULL); 1047 cairo_t *c = cairo_create(NULL);
952 get_text_size(c, config->font, &width, NULL, scale, false, "%s", buffer); 1048 get_text_size(c, config->font, &width, NULL, scale, false, "%s", buffer);
@@ -995,7 +1091,7 @@ void view_update_marks_textures(struct sway_view *view) {
995} 1091}
996 1092
997bool view_is_visible(struct sway_view *view) { 1093bool view_is_visible(struct sway_view *view) {
998 if (!view->swayc || view->swayc->destroying) { 1094 if (!view->swayc || view->swayc->destroying || !view->swayc->parent) {
999 return false; 1095 return false;
1000 } 1096 }
1001 struct sway_container *workspace = 1097 struct sway_container *workspace =
@@ -1033,3 +1129,32 @@ bool view_is_visible(struct sway_view *view) {
1033 } 1129 }
1034 return true; 1130 return true;
1035} 1131}
1132
1133void view_set_urgent(struct sway_view *view, bool enable) {
1134 if (view_is_urgent(view) == enable) {
1135 return;
1136 }
1137 if (enable) {
1138 struct sway_seat *seat = input_manager_current_seat(input_manager);
1139 if (seat_get_focus(seat) == view->swayc) {
1140 return;
1141 }
1142 clock_gettime(CLOCK_MONOTONIC, &view->urgent);
1143 } else {
1144 view->urgent = (struct timespec){ 0 };
1145 if (view->urgent_timer) {
1146 wl_event_source_remove(view->urgent_timer);
1147 view->urgent_timer = NULL;
1148 }
1149 }
1150 container_damage_whole(view->swayc);
1151
1152 ipc_event_window(view->swayc, "urgent");
1153
1154 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE);
1155 workspace_detect_urgent(ws);
1156}
1157
1158bool view_is_urgent(struct sway_view *view) {
1159 return view->urgent.tv_sec || view->urgent.tv_nsec;
1160}
diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c
index 651cc011..a93d9f44 100644
--- a/sway/tree/workspace.c
+++ b/sway/tree/workspace.c
@@ -12,6 +12,7 @@
12#include "sway/output.h" 12#include "sway/output.h"
13#include "sway/tree/arrange.h" 13#include "sway/tree/arrange.h"
14#include "sway/tree/container.h" 14#include "sway/tree/container.h"
15#include "sway/tree/view.h"
15#include "sway/tree/workspace.h" 16#include "sway/tree/workspace.h"
16#include "list.h" 17#include "list.h"
17#include "log.h" 18#include "log.h"
@@ -50,7 +51,7 @@ struct sway_container *workspace_create(struct sway_container *output,
50 output = get_workspace_initial_output(name); 51 output = get_workspace_initial_output(name);
51 } 52 }
52 53
53 wlr_log(L_DEBUG, "Added workspace %s for output %s", name, output->name); 54 wlr_log(WLR_DEBUG, "Added workspace %s for output %s", name, output->name);
54 struct sway_container *workspace = container_create(C_WORKSPACE); 55 struct sway_container *workspace = container_create(C_WORKSPACE);
55 56
56 workspace->x = output->x; 57 workspace->x = output->x;
@@ -108,9 +109,8 @@ static bool workspace_valid_on_output(const char *output_name,
108} 109}
109 110
110char *workspace_next_name(const char *output_name) { 111char *workspace_next_name(const char *output_name) {
111 wlr_log(L_DEBUG, "Workspace: Generating new workspace name for output %s", 112 wlr_log(WLR_DEBUG, "Workspace: Generating new workspace name for output %s",
112 output_name); 113 output_name);
113 int l = 1;
114 // Scan all workspace bindings to find the next available workspace name, 114 // Scan all workspace bindings to find the next available workspace name,
115 // if none are found/available then default to a number 115 // if none are found/available then default to a number
116 struct sway_mode *mode = config->current_mode; 116 struct sway_mode *mode = config->current_mode;
@@ -137,7 +137,7 @@ char *workspace_next_name(const char *output_name) {
137 while (isspace(*_target)) { 137 while (isspace(*_target)) {
138 memmove(_target, _target+1, strlen(_target+1)); 138 memmove(_target, _target+1, strlen(_target+1));
139 } 139 }
140 wlr_log(L_DEBUG, "Got valid workspace command for target: '%s'", 140 wlr_log(WLR_DEBUG, "Got valid workspace command for target: '%s'",
141 _target); 141 _target);
142 142
143 // Make sure that the command references an actual workspace 143 // Make sure that the command references an actual workspace
@@ -163,7 +163,7 @@ char *workspace_next_name(const char *output_name) {
163 temp[length - 1] = '\0'; 163 temp[length - 1] = '\0';
164 free(_target); 164 free(_target);
165 _target = temp; 165 _target = temp;
166 wlr_log(L_DEBUG, "Isolated name from workspace number: '%s'", _target); 166 wlr_log(WLR_DEBUG, "Isolated name from workspace number: '%s'", _target);
167 167
168 // Make sure the workspace number doesn't already exist 168 // Make sure the workspace number doesn't already exist
169 if (workspace_by_number(_target)) { 169 if (workspace_by_number(_target)) {
@@ -192,7 +192,9 @@ char *workspace_next_name(const char *output_name) {
192 order = binding->order; 192 order = binding->order;
193 free(target); 193 free(target);
194 target = _target; 194 target = _target;
195 wlr_log(L_DEBUG, "Workspace: Found free name %s", _target); 195 wlr_log(WLR_DEBUG, "Workspace: Found free name %s", _target);
196 } else {
197 free(_target);
196 } 198 }
197 } 199 }
198 free(dup); 200 free(dup);
@@ -203,14 +205,9 @@ char *workspace_next_name(const char *output_name) {
203 // As a fall back, get the current number of active workspaces 205 // As a fall back, get the current number of active workspaces
204 // and return that + 1 for the next workspace's name 206 // and return that + 1 for the next workspace's name
205 int ws_num = root_container.children->length; 207 int ws_num = root_container.children->length;
206 if (ws_num >= 10) { 208 int l = snprintf(NULL, 0, "%d", ws_num);
207 l = 2;
208 } else if (ws_num >= 100) {
209 l = 3;
210 }
211 char *name = malloc(l + 1); 209 char *name = malloc(l + 1);
212 if (!name) { 210 if (!sway_assert(name, "Cloud not allocate workspace name")) {
213 wlr_log(L_ERROR, "Could not allocate workspace name");
214 return NULL; 211 return NULL;
215 } 212 }
216 sprintf(name, "%d", ws_num++); 213 sprintf(name, "%d", ws_num++);
@@ -272,6 +269,9 @@ struct sway_container *workspace_by_name(const char *name) {
272 */ 269 */
273struct sway_container *workspace_output_prev_next_impl( 270struct sway_container *workspace_output_prev_next_impl(
274 struct sway_container *output, bool next) { 271 struct sway_container *output, bool next) {
272 if (!output) {
273 return NULL;
274 }
275 if (!sway_assert(output->type == C_OUTPUT, 275 if (!sway_assert(output->type == C_OUTPUT,
276 "Argument must be an output, is %d", output->type)) { 276 "Argument must be an output, is %d", output->type)) {
277 return NULL; 277 return NULL;
@@ -304,6 +304,9 @@ struct sway_container *workspace_output_prev_next_impl(
304 */ 304 */
305struct sway_container *workspace_prev_next_impl( 305struct sway_container *workspace_prev_next_impl(
306 struct sway_container *workspace, bool next) { 306 struct sway_container *workspace, bool next) {
307 if (!workspace) {
308 return NULL;
309 }
307 if (!sway_assert(workspace->type == C_WORKSPACE, 310 if (!sway_assert(workspace->type == C_WORKSPACE,
308 "Argument must be a workspace, is %d", workspace->type)) { 311 "Argument must be a workspace, is %d", workspace->type)) {
309 return NULL; 312 return NULL;
@@ -386,7 +389,7 @@ bool workspace_switch(struct sway_container *workspace) {
386 free(prev_workspace_name); 389 free(prev_workspace_name);
387 prev_workspace_name = malloc(strlen(active_ws->name) + 1); 390 prev_workspace_name = malloc(strlen(active_ws->name) + 1);
388 if (!prev_workspace_name) { 391 if (!prev_workspace_name) {
389 wlr_log(L_ERROR, "Unable to allocate previous workspace name"); 392 wlr_log(WLR_ERROR, "Unable to allocate previous workspace name");
390 return false; 393 return false;
391 } 394 }
392 strcpy(prev_workspace_name, active_ws->name); 395 strcpy(prev_workspace_name, active_ws->name);
@@ -408,7 +411,7 @@ bool workspace_switch(struct sway_container *workspace) {
408 } 411 }
409 } 412 }
410 413
411 wlr_log(L_DEBUG, "Switching to workspace %p:%s", 414 wlr_log(WLR_DEBUG, "Switching to workspace %p:%s",
412 workspace, workspace->name); 415 workspace, workspace->name);
413 struct sway_container *next = seat_get_focus_inactive(seat, workspace); 416 struct sway_container *next = seat_get_focus_inactive(seat, workspace);
414 if (next == NULL) { 417 if (next == NULL) {
@@ -426,7 +429,7 @@ bool workspace_switch(struct sway_container *workspace) {
426 } 429 }
427 seat_set_focus(seat, next); 430 seat_set_focus(seat, next);
428 struct sway_container *output = container_parent(workspace, C_OUTPUT); 431 struct sway_container *output = container_parent(workspace, C_OUTPUT);
429 arrange_and_commit(output); 432 arrange_windows(output);
430 return true; 433 return true;
431} 434}
432 435
@@ -518,6 +521,16 @@ struct sway_container *workspace_output_get_highest_available(
518 return NULL; 521 return NULL;
519} 522}
520 523
524void workspace_detect_urgent(struct sway_container *workspace) {
525 bool new_urgent = container_has_urgent_child(workspace);
526
527 if (workspace->sway_workspace->urgent != new_urgent) {
528 workspace->sway_workspace->urgent = new_urgent;
529 ipc_event_workspace(NULL, workspace, "urgent");
530 container_damage_whole(workspace);
531 }
532}
533
521struct pid_workspace { 534struct pid_workspace {
522 pid_t pid; 535 pid_t pid;
523 char *workspace; 536 char *workspace;
@@ -540,14 +553,14 @@ struct sway_container *workspace_for_pid(pid_t pid) {
540 struct sway_container *ws = NULL; 553 struct sway_container *ws = NULL;
541 struct pid_workspace *pw = NULL; 554 struct pid_workspace *pw = NULL;
542 555
543 wlr_log(L_DEBUG, "Looking up workspace for pid %d", pid); 556 wlr_log(WLR_DEBUG, "Looking up workspace for pid %d", pid);
544 557
545 do { 558 do {
546 struct pid_workspace *_pw = NULL; 559 struct pid_workspace *_pw = NULL;
547 wl_list_for_each(_pw, &pid_workspaces, link) { 560 wl_list_for_each(_pw, &pid_workspaces, link) {
548 if (pid == _pw->pid) { 561 if (pid == _pw->pid) {
549 pw = _pw; 562 pw = _pw;
550 wlr_log(L_DEBUG, 563 wlr_log(WLR_DEBUG,
551 "found pid_workspace for pid %d, workspace %s", 564 "found pid_workspace for pid %d, workspace %s",
552 pid, pw->workspace); 565 pid, pw->workspace);
553 goto found; 566 goto found;
@@ -561,7 +574,7 @@ found:
561 ws = workspace_by_name(pw->workspace); 574 ws = workspace_by_name(pw->workspace);
562 575
563 if (!ws) { 576 if (!ws) {
564 wlr_log(L_DEBUG, 577 wlr_log(WLR_DEBUG,
565 "Creating workspace %s for pid %d because it disappeared", 578 "Creating workspace %s for pid %d because it disappeared",
566 pw->workspace, pid); 579 pw->workspace, pid);
567 ws = workspace_create(pw->output, pw->workspace); 580 ws = workspace_create(pw->output, pw->workspace);
@@ -582,7 +595,7 @@ static void pw_handle_output_destroy(struct wl_listener *listener, void *data) {
582} 595}
583 596
584void workspace_record_pid(pid_t pid) { 597void workspace_record_pid(pid_t pid) {
585 wlr_log(L_DEBUG, "Recording workspace for process %d", pid); 598 wlr_log(WLR_DEBUG, "Recording workspace for process %d", pid);
586 if (!pid_workspaces.prev && !pid_workspaces.next) { 599 if (!pid_workspaces.prev && !pid_workspaces.next) {
587 wl_list_init(&pid_workspaces); 600 wl_list_init(&pid_workspaces);
588 } 601 }
@@ -594,12 +607,12 @@ void workspace_record_pid(pid_t pid) {
594 ws = container_parent(ws, C_WORKSPACE); 607 ws = container_parent(ws, C_WORKSPACE);
595 } 608 }
596 if (!ws) { 609 if (!ws) {
597 wlr_log(L_DEBUG, "Bailing out, no workspace"); 610 wlr_log(WLR_DEBUG, "Bailing out, no workspace");
598 return; 611 return;
599 } 612 }
600 struct sway_container *output = ws->parent; 613 struct sway_container *output = ws->parent;
601 if (!output) { 614 if (!output) {
602 wlr_log(L_DEBUG, "Bailing out, no output"); 615 wlr_log(WLR_DEBUG, "Bailing out, no output");
603 return; 616 return;
604 } 617 }
605 618
diff --git a/swaybar/bar.c b/swaybar/bar.c
index 5b8028e5..62a7727e 100644
--- a/swaybar/bar.c
+++ b/swaybar/bar.c
@@ -46,7 +46,7 @@ static void swaybar_output_free(struct swaybar_output *output) {
46 if (!output) { 46 if (!output) {
47 return; 47 return;
48 } 48 }
49 wlr_log(L_DEBUG, "Removing output %s", output->name); 49 wlr_log(WLR_DEBUG, "Removing output %s", output->name);
50 zwlr_layer_surface_v1_destroy(output->layer_surface); 50 zwlr_layer_surface_v1_destroy(output->layer_surface);
51 wl_surface_destroy(output->surface); 51 wl_surface_destroy(output->surface);
52 wl_output_destroy(output->output); 52 wl_output_destroy(output->output);
@@ -146,8 +146,10 @@ static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
146 && y >= hotspot->y 146 && y >= hotspot->y
147 && x < hotspot->x + hotspot->width 147 && x < hotspot->x + hotspot->width
148 && y < hotspot->y + hotspot->height) { 148 && y < hotspot->y + hotspot->height) {
149 hotspot->callback(output, pointer->x, pointer->y, 149 if (HOTSPOT_IGNORE == hotspot->callback(output, pointer->x, pointer->y,
150 button, hotspot->data); 150 wl_button_to_x11_button(button), hotspot->data)) {
151 return;
152 }
151 } 153 }
152 } 154 }
153} 155}
@@ -155,11 +157,28 @@ static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
155static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer, 157static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer,
156 uint32_t time, uint32_t axis, wl_fixed_t value) { 158 uint32_t time, uint32_t axis, wl_fixed_t value) {
157 struct swaybar *bar = data; 159 struct swaybar *bar = data;
160 struct swaybar_pointer *pointer = &bar->pointer;
158 struct swaybar_output *output = bar->pointer.current; 161 struct swaybar_output *output = bar->pointer.current;
159 if (!sway_assert(output, "axis with no active output")) { 162 if (!sway_assert(output, "axis with no active output")) {
160 return; 163 return;
161 } 164 }
162 165
166 struct swaybar_hotspot *hotspot;
167 wl_list_for_each(hotspot, &output->hotspots, link) {
168 double x = pointer->x * output->scale;
169 double y = pointer->y * output->scale;
170 if (x >= hotspot->x
171 && y >= hotspot->y
172 && x < hotspot->x + hotspot->width
173 && y < hotspot->y + hotspot->height) {
174 if (HOTSPOT_IGNORE == hotspot->callback(
175 output, pointer->x, pointer->y,
176 wl_axis_to_x11_button(axis, value), hotspot->data)) {
177 return;
178 }
179 }
180 }
181
163 double amt = wl_fixed_to_double(value); 182 double amt = wl_fixed_to_double(value);
164 if (amt == 0.0) { 183 if (amt == 0.0) {
165 return; 184 return;
diff --git a/swaybar/i3bar.c b/swaybar/i3bar.c
index 141612a6..ae37eeb9 100644
--- a/swaybar/i3bar.c
+++ b/swaybar/i3bar.c
@@ -1,5 +1,6 @@
1#define _POSIX_C_SOURCE 200809L 1#define _POSIX_C_SOURCE 200809L
2#include <json-c/json.h> 2#include <json-c/json.h>
3#include <linux/input-event-codes.h>
3#include <stdlib.h> 4#include <stdlib.h>
4#include <string.h> 5#include <string.h>
5#include <unistd.h> 6#include <unistd.h>
@@ -31,7 +32,7 @@ static bool i3bar_parse_json(struct status_line *status, const char *text) {
31 status_error(status, "[failed to parse i3bar json]"); 32 status_error(status, "[failed to parse i3bar json]");
32 return false; 33 return false;
33 } 34 }
34 wlr_log(L_DEBUG, "Got i3bar json: '%s'", text); 35 wlr_log(WLR_DEBUG, "Got i3bar json: '%s'", text);
35 for (size_t i = 0; i < json_object_array_length(results); ++i) { 36 for (size_t i = 0; i < json_object_array_length(results); ++i) {
36 json_object *full_text, *short_text, *color, *min_width, *align, *urgent; 37 json_object *full_text, *short_text, *color, *min_width, *align, *urgent;
37 json_object *name, *instance, *separator, *separator_block_width; 38 json_object *name, *instance, *separator, *separator_block_width;
@@ -191,11 +192,11 @@ bool i3bar_handle_readable(struct status_line *status) {
191 return redraw; 192 return redraw;
192} 193}
193 194
194void i3bar_block_send_click(struct status_line *status, 195enum hotspot_event_handling i3bar_block_send_click(struct status_line *status,
195 struct i3bar_block *block, int x, int y, uint32_t button) { 196 struct i3bar_block *block, int x, int y, enum x11_button button) {
196 wlr_log(L_DEBUG, "block %s clicked", block->name ? block->name : "(nil)"); 197 wlr_log(WLR_DEBUG, "block %s clicked", block->name ? block->name : "(nil)");
197 if (!block->name || !status->i3bar_state.click_events) { 198 if (!block->name || !status->i3bar_state.click_events) {
198 return; 199 return HOTSPOT_PROCESS;
199 } 200 }
200 201
201 struct json_object *event_json = json_object_new_object(); 202 struct json_object *event_json = json_object_new_object();
@@ -214,4 +215,34 @@ void i3bar_block_send_click(struct status_line *status,
214 status_error(status, "[failed to write click event]"); 215 status_error(status, "[failed to write click event]");
215 } 216 }
216 json_object_put(event_json); 217 json_object_put(event_json);
218 return HOTSPOT_IGNORE;
219}
220
221enum x11_button wl_button_to_x11_button(uint32_t button) {
222 switch (button) {
223 case BTN_LEFT:
224 return LEFT;
225 case BTN_MIDDLE:
226 return MIDDLE;
227 case BTN_RIGHT:
228 return RIGHT;
229 case BTN_SIDE:
230 return BACK;
231 case BTN_EXTRA:
232 return FORWARD;
233 default:
234 return NONE;
235 }
236}
237
238enum x11_button wl_axis_to_x11_button(uint32_t axis, wl_fixed_t value) {
239 switch (axis) {
240 case WL_POINTER_AXIS_VERTICAL_SCROLL:
241 return wl_fixed_to_double(value) < 0 ? SCROLL_UP : SCROLL_DOWN;
242 case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
243 return wl_fixed_to_double(value) < 0 ? SCROLL_LEFT : SCROLL_RIGHT;
244 default:
245 wlr_log(WLR_DEBUG, "Unexpected axis value on mouse scroll");
246 return NONE;
247 }
217} 248}
diff --git a/swaybar/ipc.c b/swaybar/ipc.c
index 959fa095..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));
@@ -327,7 +339,7 @@ bool handle_ipc_readable(struct swaybar *bar) {
327 json_object *result = json_tokener_parse(resp->payload); 339 json_object *result = json_tokener_parse(resp->payload);
328 if (!result) { 340 if (!result) {
329 free_ipc_response(resp); 341 free_ipc_response(resp);
330 wlr_log(L_ERROR, "failed to parse payload as json"); 342 wlr_log(WLR_ERROR, "failed to parse payload as json");
331 return false; 343 return false;
332 } 344 }
333 json_object *json_change, *json_pango_markup; 345 json_object *json_change, *json_pango_markup;
@@ -340,7 +352,7 @@ bool handle_ipc_readable(struct swaybar *bar) {
340 bar->config->mode = strdup(change); 352 bar->config->mode = strdup(change);
341 } 353 }
342 } else { 354 } else {
343 wlr_log(L_ERROR, "failed to parse response"); 355 wlr_log(WLR_ERROR, "failed to parse response");
344 json_object_put(result); 356 json_object_put(result);
345 free_ipc_response(resp); 357 free_ipc_response(resp);
346 return false; 358 return false;
diff --git a/swaybar/main.c b/swaybar/main.c
index c897e1c9..60e4b37c 100644
--- a/swaybar/main.c
+++ b/swaybar/main.c
@@ -75,13 +75,13 @@ int main(int argc, char **argv) {
75 } 75 }
76 76
77 if (debug) { 77 if (debug) {
78 wlr_log_init(L_DEBUG, NULL); 78 wlr_log_init(WLR_DEBUG, NULL);
79 } else { 79 } else {
80 wlr_log_init(L_ERROR, NULL); 80 wlr_log_init(WLR_ERROR, NULL);
81 } 81 }
82 82
83 if (!bar_id) { 83 if (!bar_id) {
84 wlr_log(L_ERROR, "No bar_id passed. " 84 wlr_log(WLR_ERROR, "No bar_id passed. "
85 "Provide --bar_id or let sway start swaybar"); 85 "Provide --bar_id or let sway start swaybar");
86 return 1; 86 return 1;
87 } 87 }
@@ -89,7 +89,7 @@ int main(int argc, char **argv) {
89 if (!socket_path) { 89 if (!socket_path) {
90 socket_path = get_socketpath(); 90 socket_path = get_socketpath();
91 if (!socket_path) { 91 if (!socket_path) {
92 wlr_log(L_ERROR, "Unable to retrieve socket path"); 92 wlr_log(WLR_ERROR, "Unable to retrieve socket path");
93 return 1; 93 return 1;
94 } 94 }
95 } 95 }
diff --git a/swaybar/render.c b/swaybar/render.c
index 327a6f5f..6f370077 100644
--- a/swaybar/render.c
+++ b/swaybar/render.c
@@ -108,11 +108,11 @@ static void render_sharp_line(cairo_t *cairo, uint32_t color,
108 } 108 }
109} 109}
110 110
111static void block_hotspot_callback(struct swaybar_output *output, 111static enum hotspot_event_handling block_hotspot_callback(struct swaybar_output *output,
112 int x, int y, uint32_t button, void *data) { 112 int x, int y, enum x11_button button, void *data) {
113 struct i3bar_block *block = data; 113 struct i3bar_block *block = data;
114 struct status_line *status = output->bar->status; 114 struct status_line *status = output->bar->status;
115 i3bar_block_send_click(status, block, x, y, button); 115 return i3bar_block_send_click(status, block, x, y, button);
116} 116}
117 117
118static uint32_t render_status_block(cairo_t *cairo, 118static uint32_t render_status_block(cairo_t *cairo,
@@ -298,7 +298,8 @@ static uint32_t render_binding_mode_indicator(cairo_t *cairo,
298 298
299 int text_width, text_height; 299 int text_width, text_height;
300 get_text_size(cairo, config->font, &text_width, &text_height, 300 get_text_size(cairo, config->font, &text_width, &text_height,
301 output->scale, config->pango_markup, "%s", mode); 301 output->scale, config->mode_pango_markup,
302 "%s", mode);
302 303
303 int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale; 304 int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale;
304 int ws_horizontal_padding = WS_HORIZONTAL_PADDING * output->scale; 305 int ws_horizontal_padding = WS_HORIZONTAL_PADDING * output->scale;
@@ -329,7 +330,7 @@ static uint32_t render_binding_mode_indicator(cairo_t *cairo,
329 double text_y = height / 2.0 - text_height / 2.0; 330 double text_y = height / 2.0 - text_height / 2.0;
330 cairo_set_source_u32(cairo, config->colors.binding_mode.text); 331 cairo_set_source_u32(cairo, config->colors.binding_mode.text);
331 cairo_move_to(cairo, x + width / 2 - text_width / 2, (int)floor(text_y)); 332 cairo_move_to(cairo, x + width / 2 - text_width / 2, (int)floor(text_y));
332 pango_printf(cairo, config->font, output->scale, config->pango_markup, 333 pango_printf(cairo, config->font, output->scale, config->mode_pango_markup,
333 "%s", mode); 334 "%s", mode);
334 return surface_height; 335 return surface_height;
335} 336}
@@ -347,9 +348,13 @@ static const char *strip_workspace_number(const char *ws_name) {
347 return ws_name; 348 return ws_name;
348} 349}
349 350
350static void workspace_hotspot_callback(struct swaybar_output *output, 351static enum hotspot_event_handling workspace_hotspot_callback(struct swaybar_output *output,
351 int x, int y, uint32_t button, void *data) { 352 int x, int y, enum x11_button button, void *data) {
353 if (button != LEFT) {
354 return HOTSPOT_PROCESS;
355 }
352 ipc_send_workspace_command(output->bar, (const char *)data); 356 ipc_send_workspace_command(output->bar, (const char *)data);
357 return HOTSPOT_IGNORE;
353} 358}
354 359
355static uint32_t render_workspace_button(cairo_t *cairo, 360static uint32_t render_workspace_button(cairo_t *cairo,
@@ -496,12 +501,15 @@ void render_frame(struct swaybar *bar, struct swaybar_output *output) {
496 // different height than what we asked for 501 // different height than what we asked for
497 wl_surface_commit(output->surface); 502 wl_surface_commit(output->surface);
498 wl_display_roundtrip(bar->display); 503 wl_display_roundtrip(bar->display);
499 } else { 504 } else if (height > 0) {
500 // Replay recording into shm and send it off 505 // Replay recording into shm and send it off
501 output->current_buffer = get_next_buffer(bar->shm, 506 output->current_buffer = get_next_buffer(bar->shm,
502 output->buffers, 507 output->buffers,
503 output->width * output->scale, 508 output->width * output->scale,
504 output->height * output->scale); 509 output->height * output->scale);
510 if (!output->current_buffer) {
511 return;
512 }
505 cairo_t *shm = output->current_buffer->cairo; 513 cairo_t *shm = output->current_buffer->cairo;
506 514
507 cairo_save(shm); 515 cairo_save(shm);
diff --git a/swaybar/status_line.c b/swaybar/status_line.c
index e0e7414a..bc47580b 100644
--- a/swaybar/status_line.c
+++ b/swaybar/status_line.c
@@ -49,14 +49,14 @@ bool status_handle_readable(struct status_line *status) {
49 json_object *version; 49 json_object *version;
50 if (json_object_object_get_ex(proto, "version", &version) 50 if (json_object_object_get_ex(proto, "version", &version)
51 && json_object_get_int(version) == 1) { 51 && json_object_get_int(version) == 1) {
52 wlr_log(L_DEBUG, "Switched to i3bar protocol."); 52 wlr_log(WLR_DEBUG, "Switched to i3bar protocol.");
53 status->protocol = PROTOCOL_I3BAR; 53 status->protocol = PROTOCOL_I3BAR;
54 } 54 }
55 json_object *click_events; 55 json_object *click_events;
56 if (json_object_object_get_ex( 56 if (json_object_object_get_ex(
57 proto, "click_events", &click_events) 57 proto, "click_events", &click_events)
58 && json_object_get_boolean(click_events)) { 58 && json_object_get_boolean(click_events)) {
59 wlr_log(L_DEBUG, "Enabled click events."); 59 wlr_log(WLR_DEBUG, "Enabled click events.");
60 status->i3bar_state.click_events = true; 60 status->i3bar_state.click_events = true;
61 const char *events_array = "[\n"; 61 const char *events_array = "[\n";
62 ssize_t len = strlen(events_array); 62 ssize_t len = strlen(events_array);
@@ -91,7 +91,7 @@ struct status_line *status_line_init(char *cmd) {
91 int pipe_read_fd[2]; 91 int pipe_read_fd[2];
92 int pipe_write_fd[2]; 92 int pipe_write_fd[2];
93 if (pipe(pipe_read_fd) != 0 || pipe(pipe_write_fd) != 0) { 93 if (pipe(pipe_read_fd) != 0 || pipe(pipe_write_fd) != 0) {
94 wlr_log(L_ERROR, "Unable to create pipes for status_command fork"); 94 wlr_log(WLR_ERROR, "Unable to create pipes for status_command fork");
95 exit(1); 95 exit(1);
96 } 96 }
97 97
diff --git a/swaybg/main.c b/swaybg/main.c
index 5b6c378c..f8e7e7ef 100644
--- a/swaybg/main.c
+++ b/swaybg/main.c
@@ -48,7 +48,7 @@ struct swaybg_state {
48bool is_valid_color(const char *color) { 48bool is_valid_color(const char *color) {
49 int len = strlen(color); 49 int len = strlen(color);
50 if (len != 7 || color[0] != '#') { 50 if (len != 7 || color[0] != '#') {
51 wlr_log(L_ERROR, "%s is not a valid color for swaybg. " 51 wlr_log(WLR_ERROR, "%s is not a valid color for swaybg. "
52 "Color should be specified as #rrggbb (no alpha).", color); 52 "Color should be specified as #rrggbb (no alpha).", color);
53 return false; 53 return false;
54 } 54 }
@@ -68,6 +68,9 @@ static void render_frame(struct swaybg_state *state) {
68 buffer_height = state->height * state->scale; 68 buffer_height = state->height * state->scale;
69 state->current_buffer = get_next_buffer(state->shm, 69 state->current_buffer = get_next_buffer(state->shm,
70 state->buffers, buffer_width, buffer_height); 70 state->buffers, buffer_width, buffer_height);
71 if (!state->current_buffer) {
72 return;
73 }
71 cairo_t *cairo = state->current_buffer->cairo; 74 cairo_t *cairo = state->current_buffer->cairo;
72 if (state->args->mode == BACKGROUND_MODE_SOLID_COLOR) { 75 if (state->args->mode == BACKGROUND_MODE_SOLID_COLOR) {
73 cairo_set_source_u32(cairo, state->context.color); 76 cairo_set_source_u32(cairo, state->context.color);
@@ -185,10 +188,10 @@ int main(int argc, const char **argv) {
185 struct swaybg_args args = {0}; 188 struct swaybg_args args = {0};
186 struct swaybg_state state = {0}; 189 struct swaybg_state state = {0};
187 state.args = &args; 190 state.args = &args;
188 wlr_log_init(L_DEBUG, NULL); 191 wlr_log_init(WLR_DEBUG, NULL);
189 192
190 if (argc != 4) { 193 if (argc != 4) {
191 wlr_log(L_ERROR, "Do not run this program manually. " 194 wlr_log(WLR_ERROR, "Do not run this program manually. "
192 "See man 5 sway and look for output options."); 195 "See man 5 sway and look for output options.");
193 return 1; 196 return 1;
194 } 197 }
diff --git a/swayidle/main.c b/swayidle/main.c
index 7666578f..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;
@@ -59,24 +57,24 @@ static void cmd_exec(void *data) {
59 return; 57 return;
60 } 58 }
61 char *param = (char *)data; 59 char *param = (char *)data;
62 wlr_log(L_DEBUG, "Cmd exec %s", param); 60 wlr_log(WLR_DEBUG, "Cmd exec %s", param);
63 pid_t pid = fork(); 61 pid_t pid = fork();
64 if (pid == 0) { 62 if (pid == 0) {
65 pid = fork(); 63 pid = fork();
66 if (pid == 0) { 64 if (pid == 0) {
67 char *const cmd[] = { "sh", "-c", param, NULL, }; 65 char *const cmd[] = { "sh", "-c", param, NULL, };
68 execvp(cmd[0], cmd); 66 execvp(cmd[0], cmd);
69 wlr_log_errno(L_ERROR, "execve failed!"); 67 wlr_log_errno(WLR_ERROR, "execve failed!");
70 exit(1); 68 exit(1);
71 } else if (pid < 0) { 69 } else if (pid < 0) {
72 wlr_log_errno(L_ERROR, "fork failed"); 70 wlr_log_errno(WLR_ERROR, "fork failed");
73 exit(1); 71 exit(1);
74 } 72 }
75 exit(0); 73 exit(0);
76 } else if (pid < 0) { 74 } else if (pid < 0) {
77 wlr_log_errno(L_ERROR, "fork failed"); 75 wlr_log_errno(WLR_ERROR, "fork failed");
78 } else { 76 } else {
79 wlr_log(L_DEBUG, "Spawned process %s", param); 77 wlr_log(WLR_DEBUG, "Spawned process %s", param);
80 waitpid(pid, NULL, 0); 78 waitpid(pid, NULL, 0);
81 } 79 }
82} 80}
@@ -86,7 +84,7 @@ static int lock_fd = -1;
86static int ongoing_fd = -1; 84static int ongoing_fd = -1;
87 85
88static int release_lock(void *data) { 86static int release_lock(void *data) {
89 wlr_log(L_INFO, "Releasing sleep lock %d", ongoing_fd); 87 wlr_log(WLR_INFO, "Releasing sleep lock %d", ongoing_fd);
90 if (ongoing_fd >= 0) { 88 if (ongoing_fd >= 0) {
91 close(ongoing_fd); 89 close(ongoing_fd);
92 } 90 }
@@ -101,7 +99,7 @@ void acquire_sleep_lock() {
101 int ret = sd_bus_default_system(&bus); 99 int ret = sd_bus_default_system(&bus);
102 100
103 if (ret < 0) { 101 if (ret < 0) {
104 wlr_log(L_ERROR, "Failed to open D-Bus connection: %s", 102 wlr_log(WLR_ERROR, "Failed to open D-Bus connection: %s",
105 strerror(-ret)); 103 strerror(-ret));
106 return; 104 return;
107 } 105 }
@@ -112,17 +110,17 @@ void acquire_sleep_lock() {
112 &error, &msg, "ssss", "sleep", "swayidle", 110 &error, &msg, "ssss", "sleep", "swayidle",
113 "Setup Up Lock Screen", "delay"); 111 "Setup Up Lock Screen", "delay");
114 if (ret < 0) { 112 if (ret < 0) {
115 wlr_log(L_ERROR, "Failed to send Inhibit signal: %s", 113 wlr_log(WLR_ERROR, "Failed to send Inhibit signal: %s",
116 strerror(-ret)); 114 strerror(-ret));
117 } else { 115 } else {
118 ret = sd_bus_message_read(msg, "h", &lock_fd); 116 ret = sd_bus_message_read(msg, "h", &lock_fd);
119 if (ret < 0) { 117 if (ret < 0) {
120 wlr_log(L_ERROR, 118 wlr_log(WLR_ERROR,
121 "Failed to parse D-Bus response for Inhibit: %s", 119 "Failed to parse D-Bus response for Inhibit: %s",
122 strerror(-ret)); 120 strerror(-ret));
123 } 121 }
124 } 122 }
125 wlr_log(L_INFO, "Got sleep lock: %d", lock_fd); 123 wlr_log(WLR_INFO, "Got sleep lock: %d", lock_fd);
126} 124}
127 125
128static int prepare_for_sleep(sd_bus_message *msg, void *userdata, 126static int prepare_for_sleep(sd_bus_message *msg, void *userdata,
@@ -131,10 +129,10 @@ static int prepare_for_sleep(sd_bus_message *msg, void *userdata,
131 int going_down = 1; 129 int going_down = 1;
132 int ret = sd_bus_message_read(msg, "b", &going_down); 130 int ret = sd_bus_message_read(msg, "b", &going_down);
133 if (ret < 0) { 131 if (ret < 0) {
134 wlr_log(L_ERROR, "Failed to parse D-Bus response for Inhibit: %s", 132 wlr_log(WLR_ERROR, "Failed to parse D-Bus response for Inhibit: %s",
135 strerror(-ret)); 133 strerror(-ret));
136 } 134 }
137 wlr_log(L_DEBUG, "PrepareForSleep signal received %d", going_down); 135 wlr_log(WLR_DEBUG, "PrepareForSleep signal received %d", going_down);
138 if (!going_down) { 136 if (!going_down) {
139 acquire_sleep_lock(); 137 acquire_sleep_lock();
140 return 0; 138 return 0;
@@ -151,7 +149,7 @@ static int prepare_for_sleep(sd_bus_message *msg, void *userdata,
151 wl_event_loop_add_timer(state.event_loop, release_lock, NULL); 149 wl_event_loop_add_timer(state.event_loop, release_lock, NULL);
152 wl_event_source_timer_update(source, 1000); 150 wl_event_source_timer_update(source, 1000);
153 } 151 }
154 wlr_log(L_DEBUG, "Prepare for sleep done"); 152 wlr_log(WLR_DEBUG, "Prepare for sleep done");
155 return 0; 153 return 0;
156} 154}
157 155
@@ -165,10 +163,10 @@ 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(L_ERROR, "Failed to open D-Bus connection: %s", 169 wlr_log(WLR_ERROR, "Failed to open D-Bus connection: %s",
172 strerror(-ret)); 170 strerror(-ret));
173 return; 171 return;
174 } 172 }
@@ -183,7 +181,7 @@ void setup_sleep_listener() {
183 "/org/freedesktop/login1"); 181 "/org/freedesktop/login1");
184 ret = sd_bus_add_match(bus, NULL, str, prepare_for_sleep, NULL); 182 ret = sd_bus_add_match(bus, NULL, str, prepare_for_sleep, NULL);
185 if (ret < 0) { 183 if (ret < 0) {
186 wlr_log(L_ERROR, "Failed to add D-Bus match: %s", strerror(-ret)); 184 wlr_log(WLR_ERROR, "Failed to add D-Bus match: %s", strerror(-ret));
187 return; 185 return;
188 } 186 }
189 acquire_sleep_lock(); 187 acquire_sleep_lock();
@@ -214,7 +212,7 @@ static const struct wl_registry_listener registry_listener = {
214 212
215static void handle_idle(void *data, struct org_kde_kwin_idle_timeout *timer) { 213static void handle_idle(void *data, struct org_kde_kwin_idle_timeout *timer) {
216 struct swayidle_timeout_cmd *cmd = data; 214 struct swayidle_timeout_cmd *cmd = data;
217 wlr_log(L_DEBUG, "idle state"); 215 wlr_log(WLR_DEBUG, "idle state");
218 if (cmd && cmd->idle_cmd && cmd->idle_cmd->callback) { 216 if (cmd && cmd->idle_cmd && cmd->idle_cmd->callback) {
219 cmd->idle_cmd->callback(cmd->idle_cmd->param); 217 cmd->idle_cmd->callback(cmd->idle_cmd->param);
220 } 218 }
@@ -222,7 +220,7 @@ static void handle_idle(void *data, struct org_kde_kwin_idle_timeout *timer) {
222 220
223static void handle_resume(void *data, struct org_kde_kwin_idle_timeout *timer) { 221static void handle_resume(void *data, struct org_kde_kwin_idle_timeout *timer) {
224 struct swayidle_timeout_cmd *cmd = data; 222 struct swayidle_timeout_cmd *cmd = data;
225 wlr_log(L_DEBUG, "active state"); 223 wlr_log(WLR_DEBUG, "active state");
226 if (cmd && cmd->resume_cmd && cmd->resume_cmd->callback) { 224 if (cmd && cmd->resume_cmd && cmd->resume_cmd->callback) {
227 cmd->resume_cmd->callback(cmd->resume_cmd->param); 225 cmd->resume_cmd->callback(cmd->resume_cmd->param);
228 } 226 }
@@ -235,12 +233,12 @@ static const struct org_kde_kwin_idle_timeout_listener idle_timer_listener = {
235 233
236struct swayidle_cmd *parse_command(int argc, char **argv) { 234struct swayidle_cmd *parse_command(int argc, char **argv) {
237 if (argc < 1) { 235 if (argc < 1) {
238 wlr_log(L_ERROR, "Too few parameters for command in parse_command"); 236 wlr_log(WLR_ERROR, "Too few parameters for command in parse_command");
239 return NULL; 237 return NULL;
240 } 238 }
241 239
242 struct swayidle_cmd *cmd = calloc(1, sizeof(struct swayidle_cmd)); 240 struct swayidle_cmd *cmd = calloc(1, sizeof(struct swayidle_cmd));
243 wlr_log(L_DEBUG, "Command: %s", argv[0]); 241 wlr_log(WLR_DEBUG, "Command: %s", argv[0]);
244 cmd->callback = cmd_exec; 242 cmd->callback = cmd_exec;
245 cmd->param = argv[0]; 243 cmd->param = argv[0];
246 return cmd; 244 return cmd;
@@ -248,7 +246,7 @@ struct swayidle_cmd *parse_command(int argc, char **argv) {
248 246
249int parse_timeout(int argc, char **argv) { 247int parse_timeout(int argc, char **argv) {
250 if (argc < 3) { 248 if (argc < 3) {
251 wlr_log(L_ERROR, "Too few parameters to timeout command. " 249 wlr_log(WLR_ERROR, "Too few parameters to timeout command. "
252 "Usage: timeout <seconds> <command>"); 250 "Usage: timeout <seconds> <command>");
253 exit(-1); 251 exit(-1);
254 } 252 }
@@ -256,7 +254,7 @@ int parse_timeout(int argc, char **argv) {
256 char *endptr; 254 char *endptr;
257 int seconds = strtoul(argv[1], &endptr, 10); 255 int seconds = strtoul(argv[1], &endptr, 10);
258 if (errno != 0 || *endptr != '\0') { 256 if (errno != 0 || *endptr != '\0') {
259 wlr_log(L_ERROR, "Invalid timeout parameter '%s', it should be a " 257 wlr_log(WLR_ERROR, "Invalid timeout parameter '%s', it should be a "
260 "numeric value representing seconds", optarg); 258 "numeric value representing seconds", optarg);
261 exit(-1); 259 exit(-1);
262 } 260 }
@@ -264,13 +262,13 @@ int parse_timeout(int argc, char **argv) {
264 calloc(1, sizeof(struct swayidle_timeout_cmd)); 262 calloc(1, sizeof(struct swayidle_timeout_cmd));
265 cmd->timeout = seconds * 1000; 263 cmd->timeout = seconds * 1000;
266 264
267 wlr_log(L_DEBUG, "Register idle timeout at %d ms", cmd->timeout); 265 wlr_log(WLR_DEBUG, "Register idle timeout at %d ms", cmd->timeout);
268 wlr_log(L_DEBUG, "Setup idle"); 266 wlr_log(WLR_DEBUG, "Setup idle");
269 cmd->idle_cmd = parse_command(argc - 2, &argv[2]); 267 cmd->idle_cmd = parse_command(argc - 2, &argv[2]);
270 268
271 int result = 3; 269 int result = 3;
272 if (argc >= 5 && !strcmp("resume", argv[3])) { 270 if (argc >= 5 && !strcmp("resume", argv[3])) {
273 wlr_log(L_DEBUG, "Setup resume"); 271 wlr_log(WLR_DEBUG, "Setup resume");
274 cmd->resume_cmd = parse_command(argc - 4, &argv[4]); 272 cmd->resume_cmd = parse_command(argc - 4, &argv[4]);
275 result = 5; 273 result = 5;
276 } 274 }
@@ -280,14 +278,14 @@ int parse_timeout(int argc, char **argv) {
280 278
281int parse_sleep(int argc, char **argv) { 279int parse_sleep(int argc, char **argv) {
282 if (argc < 2) { 280 if (argc < 2) {
283 wlr_log(L_ERROR, "Too few parameters to before-sleep command. " 281 wlr_log(WLR_ERROR, "Too few parameters to before-sleep command. "
284 "Usage: before-sleep <command>"); 282 "Usage: before-sleep <command>");
285 exit(-1); 283 exit(-1);
286 } 284 }
287 285
288 lock_cmd = parse_command(argc - 1, &argv[1]); 286 lock_cmd = parse_command(argc - 1, &argv[1]);
289 if (lock_cmd) { 287 if (lock_cmd) {
290 wlr_log(L_DEBUG, "Setup sleep lock: %s", lock_cmd->param); 288 wlr_log(WLR_DEBUG, "Setup sleep lock: %s", lock_cmd->param);
291 } 289 }
292 290
293 return 2; 291 return 2;
@@ -314,10 +312,10 @@ int parse_args(int argc, char *argv[]) {
314 } 312 }
315 313
316 if (debug) { 314 if (debug) {
317 wlr_log_init(L_DEBUG, NULL); 315 wlr_log_init(WLR_DEBUG, NULL);
318 wlr_log(L_DEBUG, "Loglevel debug"); 316 wlr_log(WLR_DEBUG, "Loglevel debug");
319 } else { 317 } else {
320 wlr_log_init(L_INFO, NULL); 318 wlr_log_init(WLR_INFO, NULL);
321 } 319 }
322 320
323 321
@@ -326,13 +324,13 @@ int parse_args(int argc, char *argv[]) {
326 int i = optind; 324 int i = optind;
327 while (i < argc) { 325 while (i < argc) {
328 if (!strcmp("timeout", argv[i])) { 326 if (!strcmp("timeout", argv[i])) {
329 wlr_log(L_DEBUG, "Got timeout"); 327 wlr_log(WLR_DEBUG, "Got timeout");
330 i += parse_timeout(argc - i, &argv[i]); 328 i += parse_timeout(argc - i, &argv[i]);
331 } else if (!strcmp("before-sleep", argv[i])) { 329 } else if (!strcmp("before-sleep", argv[i])) {
332 wlr_log(L_DEBUG, "Got before-sleep"); 330 wlr_log(WLR_DEBUG, "Got before-sleep");
333 i += parse_sleep(argc - i, &argv[i]); 331 i += parse_sleep(argc - i, &argv[i]);
334 } else { 332 } else {
335 wlr_log(L_ERROR, "Unsupported command '%s'", argv[i]); 333 wlr_log(WLR_ERROR, "Unsupported command '%s'", argv[i]);
336 exit(-1); 334 exit(-1);
337 } 335 }
338 } 336 }
@@ -358,16 +356,16 @@ static int display_event(int fd, uint32_t mask, void *data) {
358 sway_terminate(0); 356 sway_terminate(0);
359 } 357 }
360 if (wl_display_dispatch(state.display) < 0) { 358 if (wl_display_dispatch(state.display) < 0) {
361 wlr_log_errno(L_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
367void register_idle_timeout(void *item) { 365void register_idle_timeout(void *item) {
368 struct swayidle_timeout_cmd *cmd = item; 366 struct swayidle_timeout_cmd *cmd = item;
369 if (cmd == NULL || !cmd->timeout) { 367 if (cmd == NULL || !cmd->timeout) {
370 wlr_log(L_ERROR, "Invalid idle cmd, will not register"); 368 wlr_log(WLR_ERROR, "Invalid idle cmd, will not register");
371 return; 369 return;
372 } 370 }
373 state.idle_timer = 371 state.idle_timer =
@@ -376,7 +374,7 @@ void register_idle_timeout(void *item) {
376 org_kde_kwin_idle_timeout_add_listener(state.idle_timer, 374 org_kde_kwin_idle_timeout_add_listener(state.idle_timer,
377 &idle_timer_listener, cmd); 375 &idle_timer_listener, cmd);
378 } else { 376 } else {
379 wlr_log(L_ERROR, "Could not create idle timer"); 377 wlr_log(WLR_ERROR, "Could not create idle timer");
380 } 378 }
381} 379}
382 380
@@ -390,22 +388,21 @@ int main(int argc, char *argv[]) {
390 388
391 state.display = wl_display_connect(NULL); 389 state.display = wl_display_connect(NULL);
392 if (state.display == NULL) { 390 if (state.display == NULL) {
393 wlr_log(L_ERROR, "Failed to create display"); 391 wlr_log(WLR_ERROR, "Failed to create display");
394 return -3; 392 return -3;
395 } 393 }
396 394
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) {
404 wlr_log(L_ERROR, "Display doesn't support idle protocol"); 401 wlr_log(WLR_ERROR, "Display doesn't support idle protocol");
405 return -4; 402 return -4;
406 } 403 }
407 if (seat == NULL) { 404 if (seat == NULL) {
408 wlr_log(L_ERROR, "Seat error"); 405 wlr_log(WLR_ERROR, "Seat error");
409 return -5; 406 return -5;
410 } 407 }
411 408
@@ -417,7 +414,7 @@ int main(int argc, char *argv[]) {
417 } 414 }
418#endif 415#endif
419 if (!should_run) { 416 if (!should_run) {
420 wlr_log(L_INFO, "No command specified! Nothing to do, will exit"); 417 wlr_log(WLR_INFO, "No command specified! Nothing to do, will exit");
421 sway_terminate(0); 418 sway_terminate(0);
422 } 419 }
423 list_foreach(state.timeout_cmds, register_idle_timeout); 420 list_foreach(state.timeout_cmds, register_idle_timeout);
diff --git a/swaylock/main.c b/swaylock/main.c
index 591df7b4..668a8742 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"
@@ -34,14 +35,16 @@ void sway_terminate(int exit_code) {
34static void daemonize() { 35static void daemonize() {
35 int fds[2]; 36 int fds[2];
36 if (pipe(fds) != 0) { 37 if (pipe(fds) != 0) {
37 wlr_log(L_ERROR, "Failed to pipe"); 38 wlr_log(WLR_ERROR, "Failed to pipe");
38 exit(1); 39 exit(1);
39 } 40 }
40 if (fork() == 0) { 41 if (fork() == 0) {
42 setsid();
41 close(fds[0]); 43 close(fds[0]);
42 int devnull = open("/dev/null", O_RDWR); 44 int devnull = open("/dev/null", O_RDWR);
43 dup2(STDOUT_FILENO, devnull); 45 dup2(STDOUT_FILENO, devnull);
44 dup2(STDERR_FILENO, devnull); 46 dup2(STDERR_FILENO, devnull);
47 close(devnull);
45 uint8_t success = 0; 48 uint8_t success = 0;
46 if (chdir("/") != 0) { 49 if (chdir("/") != 0) {
47 write(fds[1], &success, 1); 50 write(fds[1], &success, 1);
@@ -56,7 +59,7 @@ static void daemonize() {
56 close(fds[1]); 59 close(fds[1]);
57 uint8_t success; 60 uint8_t success;
58 if (read(fds[0], &success, 1) != 1 || !success) { 61 if (read(fds[0], &success, 1) != 1 || !success) {
59 wlr_log(L_ERROR, "Failed to daemonize"); 62 wlr_log(WLR_ERROR, "Failed to daemonize");
60 exit(1); 63 exit(1);
61 } 64 }
62 close(fds[0]); 65 close(fds[0]);
@@ -83,6 +86,13 @@ static const struct zwlr_layer_surface_v1_listener layer_surface_listener;
83static cairo_surface_t *select_image(struct swaylock_state *state, 86static cairo_surface_t *select_image(struct swaylock_state *state,
84 struct swaylock_surface *surface); 87 struct swaylock_surface *surface);
85 88
89static bool surface_is_opaque(struct swaylock_surface *surface) {
90 if (surface->image) {
91 return cairo_surface_get_content(surface->image) == CAIRO_CONTENT_COLOR;
92 }
93 return (surface->state->args.colors.background & 0xff) == 0xff;
94}
95
86static void create_layer_surface(struct swaylock_surface *surface) { 96static void create_layer_surface(struct swaylock_surface *surface) {
87 struct swaylock_state *state = surface->state; 97 struct swaylock_state *state = surface->state;
88 98
@@ -107,6 +117,17 @@ static void create_layer_surface(struct swaylock_surface *surface) {
107 surface->layer_surface, true); 117 surface->layer_surface, true);
108 zwlr_layer_surface_v1_add_listener(surface->layer_surface, 118 zwlr_layer_surface_v1_add_listener(surface->layer_surface,
109 &layer_surface_listener, surface); 119 &layer_surface_listener, surface);
120
121 if (surface_is_opaque(surface) &&
122 surface->state->args.mode != BACKGROUND_MODE_CENTER &&
123 surface->state->args.mode != BACKGROUND_MODE_FIT) {
124 struct wl_region *region =
125 wl_compositor_create_region(surface->state->compositor);
126 wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX);
127 wl_surface_set_opaque_region(surface->surface, region);
128 wl_region_destroy(region);
129 }
130
110 wl_surface_commit(surface->surface); 131 wl_surface_commit(surface->surface);
111} 132}
112 133
@@ -218,7 +239,7 @@ static void handle_xdg_output_logical_position(void *data,
218 239
219static void handle_xdg_output_name(void *data, struct zxdg_output_v1 *output, 240static void handle_xdg_output_name(void *data, struct zxdg_output_v1 *output,
220 const char *name) { 241 const char *name) {
221 wlr_log(L_DEBUG, "output name is %s", name); 242 wlr_log(WLR_DEBUG, "output name is %s", name);
222 struct swaylock_surface *surface = data; 243 struct swaylock_surface *surface = data;
223 surface->xdg_output = output; 244 surface->xdg_output = output;
224 surface->output_name = strdup(name); 245 surface->output_name = strdup(name);
@@ -324,22 +345,25 @@ static void load_image(char *arg, struct swaylock_state *state) {
324 image->path = strdup(arg); 345 image->path = strdup(arg);
325 } 346 }
326 347
327 bool exists = false; 348 struct swaylock_image *iter_image, *temp;
328 struct swaylock_image *iter_image; 349 wl_list_for_each_safe(iter_image, temp, &state->images, link) {
329 wl_list_for_each(iter_image, &state->images, link) {
330 if (lenient_strcmp(iter_image->output_name, image->output_name) == 0) { 350 if (lenient_strcmp(iter_image->output_name, image->output_name) == 0) {
331 exists = true; 351 if (image->output_name) {
352 wlr_log(WLR_DEBUG,
353 "Replacing image defined for output %s with %s",
354 image->output_name, image->path);
355 } else {
356 wlr_log(WLR_DEBUG, "Replacing default image with %s",
357 image->path);
358 }
359 wl_list_remove(&iter_image->link);
360 free(iter_image->cairo_surface);
361 free(iter_image->output_name);
362 free(iter_image->path);
363 free(iter_image);
332 break; 364 break;
333 } 365 }
334 } 366 }
335 if (exists) {
336 if (image->output_name) {
337 wlr_log(L_ERROR, "Multiple images defined for output %s",
338 image->output_name);
339 } else {
340 wlr_log(L_ERROR, "Multiple default images defined");
341 }
342 }
343 367
344 // Bash doesn't replace the ~ with $HOME if the output name is supplied 368 // Bash doesn't replace the ~ with $HOME if the output name is supplied
345 wordexp_t p; 369 wordexp_t p;
@@ -357,71 +381,252 @@ static void load_image(char *arg, struct swaylock_state *state) {
357 } 381 }
358 wl_list_insert(&state->images, &image->link); 382 wl_list_insert(&state->images, &image->link);
359 state->args.mode = BACKGROUND_MODE_FILL; 383 state->args.mode = BACKGROUND_MODE_FILL;
360 wlr_log(L_DEBUG, "Loaded image %s for output %s", 384 wlr_log(WLR_DEBUG, "Loaded image %s for output %s",
361 image->path, image->output_name ? image->output_name : "*"); 385 image->path, image->output_name ? image->output_name : "*");
362} 386}
363 387
364static struct swaylock_state state; 388static void set_default_colors(struct swaylock_colors *colors) {
389 colors->background = 0xFFFFFFFF;
390 colors->bs_highlight = 0xDB3300FF;
391 colors->key_highlight = 0x33DB00FF;
392 colors->separator = 0x000000FF;
393 colors->inside = (struct swaylock_colorset){
394 .input = 0x000000C0,
395 .cleared = 0xE5A445C0,
396 .verifying = 0x0072FFC0,
397 .wrong = 0xFA0000C0,
398 };
399 colors->line = (struct swaylock_colorset){
400 .input = 0x000000FF,
401 .cleared = 0x000000FF,
402 .verifying = 0x000000FF,
403 .wrong = 0x000000FF,
404 };
405 colors->ring = (struct swaylock_colorset){
406 .input = 0x337D00FF,
407 .cleared = 0xE5A445FF,
408 .verifying = 0x3300FFFF,
409 .wrong = 0x7D3300FF,
410 };
411 colors->text = (struct swaylock_colorset){
412 .input = 0xE5A445FF,
413 .cleared = 0x000000FF,
414 .verifying = 0x000000FF,
415 .wrong = 0x000000FF,
416 };
417}
418
419enum line_mode {
420 LM_LINE,
421 LM_INSIDE,
422 LM_RING,
423};
424
425static int parse_options(int argc, char **argv, struct swaylock_state *state,
426 enum line_mode *line_mode, char **config_path) {
427 enum long_option_codes {
428 LO_BS_HL_COLOR = 256,
429 LO_FONT,
430 LO_IND_RADIUS,
431 LO_IND_THICKNESS,
432 LO_INSIDE_COLOR,
433 LO_INSIDE_CLEAR_COLOR,
434 LO_INSIDE_VER_COLOR,
435 LO_INSIDE_WRONG_COLOR,
436 LO_KEY_HL_COLOR,
437 LO_LINE_COLOR,
438 LO_LINE_CLEAR_COLOR,
439 LO_LINE_VER_COLOR,
440 LO_LINE_WRONG_COLOR,
441 LO_RING_COLOR,
442 LO_RING_CLEAR_COLOR,
443 LO_RING_VER_COLOR,
444 LO_RING_WRONG_COLOR,
445 LO_SEP_COLOR,
446 LO_TEXT_COLOR,
447 LO_TEXT_CLEAR_COLOR,
448 LO_TEXT_VER_COLOR,
449 LO_TEXT_WRONG_COLOR,
450 };
365 451
366int main(int argc, char **argv) {
367 static struct option long_options[] = { 452 static struct option long_options[] = {
368 {"help", no_argument, NULL, 'h'}, 453 {"config", required_argument, NULL, 'C'},
369 {"color", required_argument, NULL, 'c'}, 454 {"color", required_argument, NULL, 'c'},
455 {"ignore-empty-password", no_argument, NULL, 'e'},
456 {"daemonize", no_argument, NULL, 'f'},
457 {"help", no_argument, NULL, 'h'},
370 {"image", required_argument, NULL, 'i'}, 458 {"image", required_argument, NULL, 'i'},
459 {"line-uses-inside", no_argument, NULL, 'n'},
460 {"socket", required_argument, NULL, 'p'},
461 {"line-uses-ring", no_argument, NULL, 'r'},
371 {"scaling", required_argument, NULL, 's'}, 462 {"scaling", required_argument, NULL, 's'},
372 {"tiling", no_argument, NULL, 't'}, 463 {"tiling", no_argument, NULL, 't'},
373 {"version", no_argument, NULL, 'v'},
374 {"socket", required_argument, NULL, 'p'},
375 {"no-unlock-indicator", no_argument, NULL, 'u'}, 464 {"no-unlock-indicator", no_argument, NULL, 'u'},
376 {"daemonize", no_argument, NULL, 'f'}, 465 {"version", no_argument, NULL, 'v'},
466 {"bs-hl-color", required_argument, NULL, LO_BS_HL_COLOR},
467 {"font", required_argument, NULL, LO_FONT},
468 {"indicator-radius", required_argument, NULL, LO_IND_RADIUS},
469 {"indicator-thickness", required_argument, NULL, LO_IND_THICKNESS},
470 {"inside-color", required_argument, NULL, LO_INSIDE_COLOR},
471 {"inside-clear-color", required_argument, NULL, LO_INSIDE_CLEAR_COLOR},
472 {"inside-ver-color", required_argument, NULL, LO_INSIDE_VER_COLOR},
473 {"inside-wrong-color", required_argument, NULL, LO_INSIDE_WRONG_COLOR},
474 {"key-hl-color", required_argument, NULL, LO_KEY_HL_COLOR},
475 {"line-color", required_argument, NULL, LO_LINE_COLOR},
476 {"line-clear-color", required_argument, NULL, LO_LINE_CLEAR_COLOR},
477 {"line-ver-color", required_argument, NULL, LO_LINE_VER_COLOR},
478 {"line-wrong-color", required_argument, NULL, LO_LINE_WRONG_COLOR},
479 {"ring-color", required_argument, NULL, LO_RING_COLOR},
480 {"ring-clear-color", required_argument, NULL, LO_RING_CLEAR_COLOR},
481 {"ring-ver-color", required_argument, NULL, LO_RING_VER_COLOR},
482 {"ring-wrong-color", required_argument, NULL, LO_RING_WRONG_COLOR},
483 {"separator-color", required_argument, NULL, LO_SEP_COLOR},
484 {"text-color", required_argument, NULL, LO_TEXT_COLOR},
485 {"text-clear-color", required_argument, NULL, LO_TEXT_CLEAR_COLOR},
486 {"text-ver-color", required_argument, NULL, LO_TEXT_VER_COLOR},
487 {"text-wrong-color", required_argument, NULL, LO_TEXT_WRONG_COLOR},
377 {0, 0, 0, 0} 488 {0, 0, 0, 0}
378 }; 489 };
379 490
380 const char usage[] = 491 const char usage[] =
381 "Usage: swaylock [options...]\n" 492 "Usage: swaylock [options...]\n"
382 "\n" 493 "\n"
383 " -h, --help Show help message and quit.\n" 494 " -C, --config <config_file> "
384 " -c, --color <rrggbb[aa]> Turn the screen into the given color instead of white.\n" 495 "Path to the config file.\n"
385 " -s, --scaling Scaling mode: stretch, fill, fit, center, tile.\n" 496 " -c, --color <color> "
386 " -t, --tiling Same as --scaling=tile.\n" 497 "Turn the screen into the given color instead of white.\n"
387 " -v, --version Show the version number and quit.\n" 498 " -e, --ignore-empty-password "
388 " -i, --image [<output>:]<path> Display the given image.\n" 499 "When an empty password is provided, do not validate it.\n"
389 " -u, --no-unlock-indicator Disable the unlock indicator.\n" 500 " -f, --daemonize "
390 " -f, --daemonize Detach from the controlling terminal.\n"; 501 "Detach from the controlling terminal after locking.\n"
391 502 " -h, --help "
392 state.args = (struct swaylock_args){ 503 "Show help message and quit.\n"
393 .mode = BACKGROUND_MODE_SOLID_COLOR, 504 " -i, --image [<output>:]<path> "
394 .color = 0xFFFFFFFF, 505 "Display the given image.\n"
395 .show_indicator = true, 506 " -s, --scaling <mode> "
396 }; 507 "Scaling mode: stretch, fill, fit, center, tile.\n"
397 wl_list_init(&state.images); 508 " -t, --tiling "
398 509 "Same as --scaling=tile.\n"
399 wlr_log_init(L_DEBUG, NULL); 510 " -u, --no-unlock-indicator "
511 "Disable the unlock indicator.\n"
512 " -v, --version "
513 "Show the version number and quit.\n"
514 " --bs-hl-color <color> "
515 "Sets the color of backspace highlight segments.\n"
516 " --font <font> "
517 "Sets the font of the text.\n"
518 " --indicator-radius <radius> "
519 "Sets the indicator radius.\n"
520 " --indicator-thickness <thick> "
521 "Sets the indicator thickness.\n"
522 " --inside-color <color> "
523 "Sets the color of the inside of the indicator.\n"
524 " --inside-clear-color <color> "
525 "Sets the color of the inside of the indicator when cleared.\n"
526 " --inside-ver-color <color> "
527 "Sets the color of the inside of the indicator when verifying.\n"
528 " --inside-wrong-color <color> "
529 "Sets the color of the inside of the indicator when invalid.\n"
530 " --key-hl-color <color> "
531 "Sets the color of the key press highlight segments.\n"
532 " --line-color <color> "
533 "Sets the color of the line between the inside and ring.\n"
534 " --line-clear-color <color> "
535 "Sets the color of the line between the inside and ring when "
536 "cleared.\n"
537 " --line-ver-color <color> "
538 "Sets the color of the line between the inside and ring when "
539 "verifying.\n"
540 " --line-wrong-color <color> "
541 "Sets the color of the line between the inside and ring when "
542 "invalid.\n"
543 " -n, --line-uses-inside "
544 "Use the inside color for the line between the inside and ring.\n"
545 " -r, --line-uses-ring "
546 "Use the ring color for the line between the inside and ring.\n"
547 " --ring-color <color> "
548 "Sets the color of the ring of the indicator.\n"
549 " --ring-clear-color <color> "
550 "Sets the color of the ring of the indicator when cleared.\n"
551 " --ring-ver-color <color> "
552 "Sets the color of the ring of the indicator when verifying.\n"
553 " --ring-wrong-color <color> "
554 "Sets the color of the ring of the indicator when invalid.\n"
555 " --separator-color <color> "
556 "Sets the color of the lines that separate highlight segments.\n"
557 " --text-color <color> "
558 "Sets the color of the text.\n"
559 " --text-clear-color <color> "
560 "Sets the color of the text when cleared.\n"
561 " --text-ver-color <color> "
562 "Sets the color of the text when verifying.\n"
563 " --text-wrong-color <color> "
564 "Sets the color of the text when invalid.\n"
565 "\n"
566 "All <color> options are of the form <rrggbb[aa]>.\n";
400 567
401 int c; 568 int c;
569 optind = 1;
402 while (1) { 570 while (1) {
403 int option_index = 0; 571 int opt_idx = 0;
404 c = getopt_long(argc, argv, "hc:i:s:tvuf", long_options, &option_index); 572 c = getopt_long(argc, argv, "c:efhi:nrs:tuvC:", long_options, &opt_idx);
405 if (c == -1) { 573 if (c == -1) {
406 break; 574 break;
407 } 575 }
408 switch (c) { 576 switch (c) {
409 case 'c': { 577 case 'C':
410 state.args.color = parse_color(optarg); 578 if (config_path) {
411 state.args.mode = BACKGROUND_MODE_SOLID_COLOR; 579 *config_path = strdup(optarg);
580 }
581 break;
582 case 'c':
583 if (state) {
584 state->args.colors.background = parse_color(optarg);
585 state->args.mode = BACKGROUND_MODE_SOLID_COLOR;
586 }
587 break;
588 case 'e':
589 if (state) {
590 state->args.ignore_empty = true;
591 }
592 break;
593 case 'f':
594 if (state) {
595 state->args.daemonize = true;
596 }
412 break; 597 break;
413 }
414 case 'i': 598 case 'i':
415 load_image(optarg, &state); 599 if (state) {
600 load_image(optarg, state);
601 }
602 break;
603 case 'n':
604 if (line_mode) {
605 *line_mode = LM_INSIDE;
606 }
607 break;
608 case 'r':
609 if (line_mode) {
610 *line_mode = LM_RING;
611 }
416 break; 612 break;
417 case 's': 613 case 's':
418 state.args.mode = parse_background_mode(optarg); 614 if (state) {
419 if (state.args.mode == BACKGROUND_MODE_INVALID) { 615 state->args.mode = parse_background_mode(optarg);
420 return 1; 616 if (state->args.mode == BACKGROUND_MODE_INVALID) {
617 return 1;
618 }
421 } 619 }
422 break; 620 break;
423 case 't': 621 case 't':
424 state.args.mode = BACKGROUND_MODE_TILE; 622 if (state) {
623 state->args.mode = BACKGROUND_MODE_TILE;
624 }
625 break;
626 case 'u':
627 if (state) {
628 state->args.show_indicator = false;
629 }
425 break; 630 break;
426 case 'v': 631 case 'v':
427#if defined SWAY_GIT_VERSION && defined SWAY_GIT_BRANCH && defined SWAY_VERSION_DATE 632#if defined SWAY_GIT_VERSION && defined SWAY_GIT_BRANCH && defined SWAY_VERSION_DATE
@@ -430,12 +635,117 @@ int main(int argc, char **argv) {
430#else 635#else
431 fprintf(stdout, "version unknown\n"); 636 fprintf(stdout, "version unknown\n");
432#endif 637#endif
433 return 0; 638 return 1;
434 case 'u': 639 case LO_BS_HL_COLOR:
435 state.args.show_indicator = false; 640 if (state) {
641 state->args.colors.bs_highlight = parse_color(optarg);
642 }
436 break; 643 break;
437 case 'f': 644 case LO_FONT:
438 daemonize(); 645 if (state) {
646 free(state->args.font);
647 state->args.font = strdup(optarg);
648 }
649 break;
650 case LO_IND_RADIUS:
651 if (state) {
652 state->args.radius = strtol(optarg, NULL, 0);
653 }
654 break;
655 case LO_IND_THICKNESS:
656 if (state) {
657 state->args.thickness = strtol(optarg, NULL, 0);
658 }
659 break;
660 case LO_INSIDE_COLOR:
661 if (state) {
662 state->args.colors.inside.input = parse_color(optarg);
663 }
664 break;
665 case LO_INSIDE_CLEAR_COLOR:
666 if (state) {
667 state->args.colors.inside.cleared = parse_color(optarg);
668 }
669 break;
670 case LO_INSIDE_VER_COLOR:
671 if (state) {
672 state->args.colors.inside.verifying = parse_color(optarg);
673 }
674 break;
675 case LO_INSIDE_WRONG_COLOR:
676 if (state) {
677 state->args.colors.inside.wrong = parse_color(optarg);
678 }
679 break;
680 case LO_KEY_HL_COLOR:
681 if (state) {
682 state->args.colors.key_highlight = parse_color(optarg);
683 }
684 break;
685 case LO_LINE_COLOR:
686 if (state) {
687 state->args.colors.line.input = parse_color(optarg);
688 }
689 break;
690 case LO_LINE_CLEAR_COLOR:
691 if (state) {
692 state->args.colors.line.cleared = parse_color(optarg);
693 }
694 break;
695 case LO_LINE_VER_COLOR:
696 if (state) {
697 state->args.colors.line.verifying = parse_color(optarg);
698 }
699 break;
700 case LO_LINE_WRONG_COLOR:
701 if (state) {
702 state->args.colors.line.wrong = parse_color(optarg);
703 }
704 break;
705 case LO_RING_COLOR:
706 if (state) {
707 state->args.colors.ring.input = parse_color(optarg);
708 }
709 break;
710 case LO_RING_CLEAR_COLOR:
711 if (state) {
712 state->args.colors.ring.cleared = parse_color(optarg);
713 }
714 break;
715 case LO_RING_VER_COLOR:
716 if (state) {
717 state->args.colors.ring.verifying = parse_color(optarg);
718 }
719 break;
720 case LO_RING_WRONG_COLOR:
721 if (state) {
722 state->args.colors.ring.wrong = parse_color(optarg);
723 }
724 break;
725 case LO_SEP_COLOR:
726 if (state) {
727 state->args.colors.separator = parse_color(optarg);
728 }
729 break;
730 case LO_TEXT_COLOR:
731 if (state) {
732 state->args.colors.text.input = parse_color(optarg);
733 }
734 break;
735 case LO_TEXT_CLEAR_COLOR:
736 if (state) {
737 state->args.colors.text.cleared = parse_color(optarg);
738 }
739 break;
740 case LO_TEXT_VER_COLOR:
741 if (state) {
742 state->args.colors.text.verifying = parse_color(optarg);
743 }
744 break;
745 case LO_TEXT_WRONG_COLOR:
746 if (state) {
747 state->args.colors.text.wrong = parse_color(optarg);
748 }
439 break; 749 break;
440 default: 750 default:
441 fprintf(stderr, "%s", usage); 751 fprintf(stderr, "%s", usage);
@@ -443,6 +753,141 @@ int main(int argc, char **argv) {
443 } 753 }
444 } 754 }
445 755
756 return 0;
757}
758
759static bool file_exists(const char *path) {
760 return path && access(path, R_OK) != -1;
761}
762
763static char *get_config_path(void) {
764 static const char *config_paths[] = {
765 "$HOME/.swaylock/config",
766 "$XDG_CONFIG_HOME/swaylock/config",
767 SYSCONFDIR "/swaylock/config",
768 };
769
770 if (!getenv("XDG_CONFIG_HOME")) {
771 char *home = getenv("HOME");
772 char *config_home = malloc(strlen(home) + strlen("/.config") + 1);
773 if (!config_home) {
774 wlr_log(WLR_ERROR, "Unable to allocate $HOME/.config");
775 } else {
776 strcpy(config_home, home);
777 strcat(config_home, "/.config");
778 setenv("XDG_CONFIG_HOME", config_home, 1);
779 wlr_log(WLR_DEBUG, "Set XDG_CONFIG_HOME to %s", config_home);
780 free(config_home);
781 }
782 }
783
784 wordexp_t p;
785 char *path;
786 for (size_t i = 0; i < sizeof(config_paths) / sizeof(char *); ++i) {
787 if (wordexp(config_paths[i], &p, 0) == 0) {
788 path = strdup(p.we_wordv[0]);
789 wordfree(&p);
790 if (file_exists(path)) {
791 return path;
792 }
793 free(path);
794 }
795 }
796
797 return NULL;
798}
799
800static int load_config(char *path, struct swaylock_state *state,
801 enum line_mode *line_mode) {
802 FILE *config = fopen(path, "r");
803 if (!config) {
804 wlr_log(WLR_ERROR, "Failed to read config. Running without it.");
805 return 0;
806 }
807 char *line;
808 int line_number = 0;
809 while (!feof(config)) {
810 line = read_line(config);
811 if (!line) {
812 continue;
813 }
814
815 line_number++;
816 if (line[0] == '#') {
817 free(line);
818 continue;
819 }
820 if (strlen(line) == 0) {
821 free(line);
822 continue;
823 }
824
825 wlr_log(WLR_DEBUG, "Config Line #%d: %s", line_number, line);
826 char flag[strlen(line) + 3];
827 sprintf(flag, "--%s", line);
828 char *argv[] = {"swaylock", flag};
829 int result = parse_options(2, argv, state, line_mode, NULL);
830 if (result != 0) {
831 free(line);
832 fclose(config);
833 return result;
834 }
835 free(line);
836 }
837 fclose(config);
838 return 0;
839}
840
841static struct swaylock_state state;
842
843int main(int argc, char **argv) {
844 enum line_mode line_mode = LM_LINE;
845 state.args = (struct swaylock_args){
846 .mode = BACKGROUND_MODE_SOLID_COLOR,
847 .font = strdup("sans-serif"),
848 .radius = 50,
849 .thickness = 10,
850 .ignore_empty = false,
851 .show_indicator = true,
852 };
853 wl_list_init(&state.images);
854 set_default_colors(&state.args.colors);
855
856 wlr_log_init(WLR_DEBUG, NULL);
857
858 char *config_path = NULL;
859 int result = parse_options(argc, argv, NULL, NULL, &config_path);
860 if (result != 0) {
861 free(config_path);
862 return result;
863 }
864 if (!config_path) {
865 config_path = get_config_path();
866 }
867
868 if (config_path) {
869 wlr_log(WLR_DEBUG, "Found config at %s", config_path);
870 int config_status = load_config(config_path, &state, &line_mode);
871 free(config_path);
872 if (config_status != 0) {
873 return config_status;
874 }
875 }
876
877 if (argc > 1) {
878 wlr_log(WLR_DEBUG, "Parsing CLI Args");
879 int result = parse_options(argc, argv, &state, &line_mode, NULL);
880 if (result != 0) {
881 return result;
882 }
883 }
884
885 if (line_mode == LM_INSIDE) {
886 state.args.colors.line = state.args.colors.inside;
887 } else if (line_mode == LM_RING) {
888 state.args.colors.line = state.args.colors.ring;
889 }
890
446#ifdef __linux__ 891#ifdef __linux__
447 // Most non-linux platforms require root to mlock() 892 // Most non-linux platforms require root to mlock()
448 if (mlock(state.password.buffer, sizeof(state.password.buffer)) != 0) { 893 if (mlock(state.password.buffer, sizeof(state.password.buffer)) != 0) {
@@ -460,13 +905,13 @@ int main(int argc, char **argv) {
460 wl_display_roundtrip(state.display); 905 wl_display_roundtrip(state.display);
461 assert(state.compositor && state.layer_shell && state.shm); 906 assert(state.compositor && state.layer_shell && state.shm);
462 if (!state.input_inhibit_manager) { 907 if (!state.input_inhibit_manager) {
463 wlr_log(L_ERROR, "Compositor does not support the input inhibitor " 908 wlr_log(WLR_ERROR, "Compositor does not support the input inhibitor "
464 "protocol, refusing to run insecurely"); 909 "protocol, refusing to run insecurely");
465 return 1; 910 return 1;
466 } 911 }
467 912
468 if (wl_list_empty(&state.surfaces)) { 913 if (wl_list_empty(&state.surfaces)) {
469 wlr_log(L_DEBUG, "Exiting - no outputs to show on."); 914 wlr_log(WLR_DEBUG, "Exiting - no outputs to show on.");
470 return 0; 915 return 0;
471 } 916 }
472 917
@@ -482,7 +927,7 @@ int main(int argc, char **argv) {
482 } 927 }
483 wl_display_roundtrip(state.display); 928 wl_display_roundtrip(state.display);
484 } else { 929 } else {
485 wlr_log(L_INFO, "Compositor does not support zxdg output manager, " 930 wlr_log(WLR_INFO, "Compositor does not support zxdg output manager, "
486 "images assigned to named outputs will not work"); 931 "images assigned to named outputs will not work");
487 } 932 }
488 933
@@ -491,9 +936,16 @@ int main(int argc, char **argv) {
491 create_layer_surface(surface); 936 create_layer_surface(surface);
492 } 937 }
493 938
939 if (state.args.daemonize) {
940 wl_display_roundtrip(state.display);
941 daemonize();
942 }
943
494 state.run_display = true; 944 state.run_display = true;
495 while (wl_display_dispatch(state.display) != -1 && state.run_display) { 945 while (wl_display_dispatch(state.display) != -1 && state.run_display) {
496 // This space intentionally left blank 946 // This space intentionally left blank
497 } 947 }
948
949 free(state.args.font);
498 return 0; 950 return 0;
499} 951}
diff --git a/swaylock/password.c b/swaylock/password.c
index bb32286e..7c686b34 100644
--- a/swaylock/password.c
+++ b/swaylock/password.c
@@ -53,15 +53,15 @@ static bool attempt_password(struct swaylock_password *pw) {
53 // TODO: only call pam_start once. keep the same handle the whole time 53 // TODO: only call pam_start once. keep the same handle the whole time
54 if ((pam_err = pam_start("swaylock", username, 54 if ((pam_err = pam_start("swaylock", username,
55 &local_conversation, &local_auth_handle)) != PAM_SUCCESS) { 55 &local_conversation, &local_auth_handle)) != PAM_SUCCESS) {
56 wlr_log(L_ERROR, "PAM returned error %d", pam_err); 56 wlr_log(WLR_ERROR, "PAM returned error %d", pam_err);
57 } 57 }
58 if ((pam_err = pam_authenticate(local_auth_handle, 0)) != PAM_SUCCESS) { 58 if ((pam_err = pam_authenticate(local_auth_handle, 0)) != PAM_SUCCESS) {
59 wlr_log(L_ERROR, "pam_authenticate failed"); 59 wlr_log(WLR_ERROR, "pam_authenticate failed");
60 goto fail; 60 goto fail;
61 } 61 }
62 // TODO: only call pam_end once we succeed at authing. refresh tokens beforehand 62 // TODO: only call pam_end once we succeed at authing. refresh tokens beforehand
63 if ((pam_err = pam_end(local_auth_handle, pam_err)) != PAM_SUCCESS) { 63 if ((pam_err = pam_end(local_auth_handle, pam_err)) != PAM_SUCCESS) {
64 wlr_log(L_ERROR, "pam_end failed"); 64 wlr_log(WLR_ERROR, "pam_end failed");
65 goto fail; 65 goto fail;
66 } 66 }
67 clear_password_buffer(pw); 67 clear_password_buffer(pw);
@@ -95,9 +95,26 @@ void swaylock_handle_key(struct swaylock_state *state,
95 switch (keysym) { 95 switch (keysym) {
96 case XKB_KEY_KP_Enter: /* fallthrough */ 96 case XKB_KEY_KP_Enter: /* fallthrough */
97 case XKB_KEY_Return: 97 case XKB_KEY_Return:
98 if (state->args.ignore_empty && state->password.len == 0) {
99 break;
100 }
101
98 state->auth_state = AUTH_STATE_VALIDATING; 102 state->auth_state = AUTH_STATE_VALIDATING;
99 damage_state(state); 103 damage_state(state);
100 wl_display_roundtrip(state->display); 104 while (wl_display_dispatch(state->display) != -1 && state->run_display) {
105 bool ok = 1;
106 struct swaylock_surface *surface;
107 wl_list_for_each(surface, &state->surfaces, link) {
108 if (surface->dirty) {
109 ok = 0;
110 }
111 }
112 if (ok) {
113 break;
114 }
115 }
116 wl_display_flush(state->display);
117
101 if (attempt_password(&state->password)) { 118 if (attempt_password(&state->password)) {
102 state->run_display = false; 119 state->run_display = false;
103 break; 120 break;
diff --git a/swaylock/render.c b/swaylock/render.c
index 2032ddcf..66c55965 100644
--- a/swaylock/render.c
+++ b/swaylock/render.c
@@ -7,11 +7,22 @@
7#include "swaylock/swaylock.h" 7#include "swaylock/swaylock.h"
8 8
9#define M_PI 3.14159265358979323846 9#define M_PI 3.14159265358979323846
10const int ARC_RADIUS = 50;
11const int ARC_THICKNESS = 10;
12const float TYPE_INDICATOR_RANGE = M_PI / 3.0f; 10const float TYPE_INDICATOR_RANGE = M_PI / 3.0f;
13const float TYPE_INDICATOR_BORDER_THICKNESS = M_PI / 128.0f; 11const float TYPE_INDICATOR_BORDER_THICKNESS = M_PI / 128.0f;
14 12
13static void set_color_for_state(cairo_t *cairo, struct swaylock_state *state,
14 struct swaylock_colorset *colorset) {
15 if (state->auth_state == AUTH_STATE_VALIDATING) {
16 cairo_set_source_u32(cairo, colorset->verifying);
17 } else if (state->auth_state == AUTH_STATE_INVALID) {
18 cairo_set_source_u32(cairo, colorset->wrong);
19 } else if (state->auth_state == AUTH_STATE_CLEAR) {
20 cairo_set_source_u32(cairo, colorset->cleared);
21 } else {
22 cairo_set_source_u32(cairo, colorset->input);
23 }
24}
25
15void render_frame(struct swaylock_surface *surface) { 26void render_frame(struct swaylock_surface *surface) {
16 struct swaylock_state *state = surface->state; 27 struct swaylock_state *state = surface->state;
17 28
@@ -30,58 +41,37 @@ void render_frame(struct swaylock_surface *surface) {
30 cairo_t *cairo = surface->current_buffer->cairo; 41 cairo_t *cairo = surface->current_buffer->cairo;
31 cairo_identity_matrix(cairo); 42 cairo_identity_matrix(cairo);
32 43
44 cairo_save(cairo);
45 cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
33 if (state->args.mode == BACKGROUND_MODE_SOLID_COLOR || !surface->image) { 46 if (state->args.mode == BACKGROUND_MODE_SOLID_COLOR || !surface->image) {
34 cairo_set_source_u32(cairo, state->args.color); 47 cairo_set_source_u32(cairo, state->args.colors.background);
35 cairo_paint(cairo); 48 cairo_paint(cairo);
36 } else { 49 } else {
37 render_background_image(cairo, surface->image, 50 render_background_image(cairo, surface->image,
38 state->args.mode, buffer_width, buffer_height); 51 state->args.mode, buffer_width, buffer_height);
39 } 52 }
53 cairo_restore(cairo);
40 cairo_identity_matrix(cairo); 54 cairo_identity_matrix(cairo);
41 55
42 int arc_radius = ARC_RADIUS * surface->scale; 56 int arc_radius = state->args.radius * surface->scale;
43 int arc_thickness = ARC_THICKNESS * surface->scale; 57 int arc_thickness = state->args.thickness * surface->scale;
44 float type_indicator_border_thickness = 58 float type_indicator_border_thickness =
45 TYPE_INDICATOR_BORDER_THICKNESS * surface->scale; 59 TYPE_INDICATOR_BORDER_THICKNESS * surface->scale;
46 60
47 if (state->args.show_indicator && state->auth_state != AUTH_STATE_IDLE) { 61 if (state->args.show_indicator && state->auth_state != AUTH_STATE_IDLE) {
48 // Draw circle 62 // Draw circle
49 cairo_set_line_width(cairo, arc_thickness); 63 cairo_set_line_width(cairo, arc_thickness);
50 cairo_arc(cairo, buffer_width / 2, buffer_height / 2, arc_radius, 0, 2 * M_PI); 64 cairo_arc(cairo, buffer_width / 2, buffer_height / 2, arc_radius,
51 switch (state->auth_state) { 65 0, 2 * M_PI);
52 case AUTH_STATE_INPUT: 66 set_color_for_state(cairo, state, &state->args.colors.inside);
53 case AUTH_STATE_INPUT_NOP: 67 cairo_fill_preserve(cairo);
54 case AUTH_STATE_BACKSPACE: { 68 set_color_for_state(cairo, state, &state->args.colors.ring);
55 cairo_set_source_rgba(cairo, 0, 0, 0, 0.75); 69 cairo_stroke(cairo);
56 cairo_fill_preserve(cairo);
57 cairo_set_source_rgb(cairo, 51.0 / 255, 125.0 / 255, 0);
58 cairo_stroke(cairo);
59 } break;
60 case AUTH_STATE_VALIDATING: {
61 cairo_set_source_rgba(cairo, 0, 114.0 / 255, 255.0 / 255, 0.75);
62 cairo_fill_preserve(cairo);
63 cairo_set_source_rgb(cairo, 51.0 / 255, 0, 250.0 / 255);
64 cairo_stroke(cairo);
65 } break;
66 case AUTH_STATE_INVALID: {
67 cairo_set_source_rgba(cairo, 250.0 / 255, 0, 0, 0.75);
68 cairo_fill_preserve(cairo);
69 cairo_set_source_rgb(cairo, 125.0 / 255, 51.0 / 255, 0);
70 cairo_stroke(cairo);
71 } break;
72 case AUTH_STATE_CLEAR: {
73 cairo_set_source_rgba(cairo, 229.0/255, 164.0/255, 69.0/255, 0.75);
74 cairo_fill_preserve(cairo);
75 cairo_set_source_rgb(cairo, 229.0/255, 164.0/255, 69.0/255);
76 cairo_stroke(cairo);
77 } break;
78 default: break;
79 }
80 70
81 // Draw a message 71 // Draw a message
82 char *text = NULL; 72 char *text = NULL;
83 cairo_set_source_rgb(cairo, 0, 0, 0); 73 set_color_for_state(cairo, state, &state->args.colors.text);
84 cairo_select_font_face(cairo, "sans-serif", 74 cairo_select_font_face(cairo, state->args.font,
85 CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); 75 CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
86 cairo_set_font_size(cairo, arc_radius / 3.0f); 76 cairo_set_font_size(cairo, arc_radius / 3.0f);
87 switch (state->auth_state) { 77 switch (state->auth_state) {
@@ -98,9 +88,10 @@ void render_frame(struct swaylock_surface *surface) {
98 case AUTH_STATE_INPUT_NOP: 88 case AUTH_STATE_INPUT_NOP:
99 if (state->xkb.caps_lock) { 89 if (state->xkb.caps_lock) {
100 text = "Caps Lock"; 90 text = "Caps Lock";
101 cairo_set_source_rgb(cairo, 229.0/255, 164.0/255, 69.0/255);
102 } 91 }
103 default: break; 92 break;
93 default:
94 break;
104 } 95 }
105 96
106 if (text) { 97 if (text) {
@@ -128,14 +119,14 @@ void render_frame(struct swaylock_surface *surface) {
128 arc_radius, highlight_start, 119 arc_radius, highlight_start,
129 highlight_start + TYPE_INDICATOR_RANGE); 120 highlight_start + TYPE_INDICATOR_RANGE);
130 if (state->auth_state == AUTH_STATE_INPUT) { 121 if (state->auth_state == AUTH_STATE_INPUT) {
131 cairo_set_source_rgb(cairo, 51.0 / 255, 219.0 / 255, 0); 122 cairo_set_source_u32(cairo, state->args.colors.key_highlight);
132 } else { 123 } else {
133 cairo_set_source_rgb(cairo, 219.0 / 255, 51.0 / 255, 0); 124 cairo_set_source_u32(cairo, state->args.colors.bs_highlight);
134 } 125 }
135 cairo_stroke(cairo); 126 cairo_stroke(cairo);
136 127
137 // Draw borders 128 // Draw borders
138 cairo_set_source_rgb(cairo, 0, 0, 0); 129 cairo_set_source_u32(cairo, state->args.colors.separator);
139 cairo_arc(cairo, buffer_width / 2, buffer_height / 2, 130 cairo_arc(cairo, buffer_width / 2, buffer_height / 2,
140 arc_radius, highlight_start, 131 arc_radius, highlight_start,
141 highlight_start + type_indicator_border_thickness); 132 highlight_start + type_indicator_border_thickness);
@@ -149,7 +140,7 @@ void render_frame(struct swaylock_surface *surface) {
149 } 140 }
150 141
151 // Draw inner + outer border of the circle 142 // Draw inner + outer border of the circle
152 cairo_set_source_rgb(cairo, 0, 0, 0); 143 set_color_for_state(cairo, state, &state->args.colors.line);
153 cairo_set_line_width(cairo, 2.0 * surface->scale); 144 cairo_set_line_width(cairo, 2.0 * surface->scale);
154 cairo_arc(cairo, buffer_width / 2, buffer_height / 2, 145 cairo_arc(cairo, buffer_width / 2, buffer_height / 2,
155 arc_radius - arc_thickness / 2, 0, 2 * M_PI); 146 arc_radius - arc_thickness / 2, 0, 2 * M_PI);
diff --git a/swaylock/seat.c b/swaylock/seat.c
index 6c66d220..c2630d87 100644
--- a/swaylock/seat.c
+++ b/swaylock/seat.c
@@ -12,13 +12,13 @@ static void keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
12 struct swaylock_state *state = data; 12 struct swaylock_state *state = data;
13 if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { 13 if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
14 close(fd); 14 close(fd);
15 wlr_log(L_ERROR, "Unknown keymap format %d, aborting", format); 15 wlr_log(WLR_ERROR, "Unknown keymap format %d, aborting", format);
16 exit(1); 16 exit(1);
17 } 17 }
18 char *map_shm = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); 18 char *map_shm = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
19 if (map_shm == MAP_FAILED) { 19 if (map_shm == MAP_FAILED) {
20 close(fd); 20 close(fd);
21 wlr_log(L_ERROR, "Unable to initialize keymap shm, aborting"); 21 wlr_log(WLR_ERROR, "Unable to initialize keymap shm, aborting");
22 exit(1); 22 exit(1);
23 } 23 }
24 struct xkb_keymap *keymap = xkb_keymap_new_from_string( 24 struct xkb_keymap *keymap = xkb_keymap_new_from_string(
diff --git a/swaylock/swaylock.1.scd b/swaylock/swaylock.1.scd
index 35d6444c..3107124f 100644
--- a/swaylock/swaylock.1.scd
+++ b/swaylock/swaylock.1.scd
@@ -12,23 +12,34 @@ Locks your Wayland session.
12 12
13# OPTIONS 13# OPTIONS
14 14
15*-h, --help* 15*-C, --config* <path>
16 Show help message and quit. 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.
17 23
18*-c, --color* <rrggbb[aa]> 24*-c, --color* <rrggbb[aa]>
19 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
20 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
21 transparent (00000000) if an image is in use. 27 transparent (00000000) if an image is in use.
22 28
29*-e, --ignore-empty-password*
30 When an empty password is provided by the user, do not validate it.
31
23*-f, --daemonize* 32*-f, --daemonize*
24 Fork into the background after spawning. Note: this is the default behavior 33 Detach from the controlling terminal after locking.
25 of i3lock. 34
35*-h, --help*
36 Show help message and quit.
26 37
27*-i, --image* [<output>:]<path> 38*-i, --image* [<output>:]<path>
28 Display the given image, optionally only on the given output. Use -c to set 39 Display the given image, optionally only on the given output. Use -c to set
29 a background color. 40 a background color.
30 41
31*--scaling* 42*-s, --scaling*
32 Scaling mode for images: _stretch_, _fill_, _fit_, _center_, or _tile_. 43 Scaling mode for images: _stretch_, _fill_, _fit_, _center_, or _tile_.
33 44
34*-t, --tiling* 45*-t, --tiling*
@@ -42,29 +53,52 @@ Locks your Wayland session.
42 53
43# APPEARANCE 54# APPEARANCE
44 55
45*--bshlcolor* <rrggbb[aa]> 56*--bs-hl-color* <rrggbb[aa]>
46 Sets the color of backspace highlight segments. 57 Sets the color of backspace highlight segments.
47 58
48*--font* <font> 59*--font* <font>
49 Sets the font of the text inside the indicator. 60 Sets the font of the text inside the indicator.
50 61
51*--insidecolor* <rrggbb[aa]> 62*--indicator-radius* <radius>
63 Sets the radius of the indicator to _radius_ pixels. The default value is
64 50.
65
66*--indicator-thickness* <thickness>
67 Sets the thickness of the indicator to _thickness_ pixels. The default value
68 is 10.
69
70*--inside-color* <rrggbb[aa]>
52 Sets the color of the inside of the indicator when typing or idle. 71 Sets the color of the inside of the indicator when typing or idle.
53 72
54*--insidevercolor* <rrggbb[aa]> 73*--inside-clear-color* <rrggbb[aa]>
74 Sets the color of the inside of the indicator when cleared.
75
76*--inside-ver-color* <rrggbb[aa]>
55 Sets the color of the inside of the indicator when verifying. 77 Sets the color of the inside of the indicator when verifying.
56 78
57*--insidewrongcolor* <rrggbb[aa]> 79*--inside-wrong-color* <rrggbb[aa]>
58 Sets the color of the inside of the indicator when invalid. 80 Sets the color of the inside of the indicator when invalid.
59 81
60*--keyhlcolor* <rrggbb[aa]> 82*--key-hl-color* <rrggbb[aa]>
61 Sets the color of keypress highlight segments. 83 Sets the color of key press highlight segments.
62 84
63*--linecolor* <rrggbb[aa]> 85*--line-color* <rrggbb[aa]>
64 Sets the color of the lines that separate the inside and outside of the 86 Sets the color of the lines that separate the inside and outside of the
65 indicator. 87 indicator when typing or idle.
66 88
67*-s, --line-uses-inside* 89*--line-clear-color* <rrggbb[aa]>
90 Sets the color of the lines that separate the inside and outside of the
91 indicator when cleared.
92
93*--line-ver-color* <rrggbb[aa]>
94 Sets the color of the lines that separate the inside and outside of the
95 indicator when verifying.
96
97*--line-wrong-color* <rrggbb[aa]>
98 Sets the color of the lines that separate the inside and outside of the
99 indicator when invalid.
100
101*-n, --line-uses-inside*
68 Use the color of the inside of the indicator for the line separating the 102 Use the color of the inside of the indicator for the line separating the
69 inside and outside of the indicator. 103 inside and outside of the indicator.
70 104
@@ -72,28 +106,32 @@ Locks your Wayland session.
72 Use the outer ring's color for the line separating the inside and outside of 106 Use the outer ring's color for the line separating the inside and outside of
73 the indicator. 107 the indicator.
74 108
75*--ringcolor* <rrggbb[aa]> 109*--ring-color* <rrggbb[aa]>
76 Sets the color of the outside of the indicator when typing or idle. 110 Sets the color of the outside of the indicator when typing or idle.
77 111
78*--ringvercolor* <rrggbb[aa]> 112*--ring-clear-color* <rrggbb[aa]>
113 Sets the color of the outside of the indicator when cleared.
114
115*--ring-ver-color* <rrggbb[aa]>
79 Sets the color of the outside of the indicator when verifying. 116 Sets the color of the outside of the indicator when verifying.
80 117
81*--ringwrongcolor* <rrggbb[aa]> 118*--ring-wrong-color* <rrggbb[aa]>
82 Sets the color of the outside of the indicator when invalid. 119 Sets the color of the outside of the indicator when invalid.
83 120
84*--separatorcolor* <rrggbb[aa]> 121*--separator-color* <rrggbb[aa]>
85 Sets the color of the lines that seperate highlight segments. 122 Sets the color of the lines that separate highlight segments.
86 123
87*--textcolor* <rrggbb[aa]> 124*--text-color* <rrggbb[aa]>
88 Sets the color of the text inside the indicator. 125 Sets the color of the text inside the indicator when typing or idle.
89 126
90*--indicator-radius* <radius> 127*--text-clear-color* <rrggbb[aa]>
91 Sets the radius of the indicator to _radius_ pixels. The default value is 128 Sets the color of the text inside the indicator when cleared.
92 50.
93 129
94*--indicator-thickness* <thickness> 130*--text-ver-color* <rrggbb[aa]>
95 Sets the thickness of the indicator to _thickness_ pixels. The default value 131 Sets the color of the text inside the indicator when verifying.
96 is 10. 132
133*--text-wrong-color* <rrggbb[aa]>
134 Sets the color of the text inside the indicator when invalid.
97 135
98# AUTHORS 136# AUTHORS
99 137
diff --git a/swaymsg/main.c b/swaymsg/main.c
index 4283bf00..c4141ca5 100644
--- a/swaymsg/main.c
+++ b/swaymsg/main.c
@@ -240,44 +240,17 @@ static void pretty_print_version(json_object *v) {
240 printf("sway version %s\n", json_object_get_string(ver)); 240 printf("sway version %s\n", json_object_get_string(ver));
241} 241}
242 242
243static void pretty_print_clipboard(json_object *v) { 243static void pretty_print_config(json_object *c) {
244 if (success(v, true)) { 244 json_object *config;
245 if (json_object_is_type(v, json_type_array)) { 245 json_object_object_get_ex(c, "config", &config);
246 for (size_t i = 0; i < json_object_array_length(v); ++i) { 246 printf("%s\n", json_object_get_string(config));
247 json_object *o = json_object_array_get_idx(v, i);
248 printf("%s\n", json_object_get_string(o));
249 }
250 } else {
251 // NOTE: could be extended to print all received types
252 // instead just the first one when sways ipc server
253 // supports it
254 struct json_object_iterator iter = json_object_iter_begin(v);
255 struct json_object_iterator end = json_object_iter_end(v);
256 if (!json_object_iter_equal(&iter, &end)) {
257 json_object *obj = json_object_iter_peek_value(&iter);
258 if (success(obj, false)) {
259 json_object *content;
260 json_object_object_get_ex(obj, "content", &content);
261 printf("%s\n", json_object_get_string(content));
262 } else {
263 json_object *error;
264 json_object_object_get_ex(obj, "error", &error);
265 printf("Error: %s\n", json_object_get_string(error));
266 }
267 }
268 }
269 } else {
270 json_object *error;
271 json_object_object_get_ex(v, "error", &error);
272 printf("Error: %s\n", json_object_get_string(error));
273 }
274} 247}
275 248
276static void pretty_print(int type, json_object *resp) { 249static void pretty_print(int type, json_object *resp) {
277 if (type != IPC_COMMAND && type != IPC_GET_WORKSPACES && 250 if (type != IPC_COMMAND && type != IPC_GET_WORKSPACES &&
278 type != IPC_GET_INPUTS && type != IPC_GET_OUTPUTS && 251 type != IPC_GET_INPUTS && type != IPC_GET_OUTPUTS &&
279 type != IPC_GET_VERSION && type != IPC_GET_CLIPBOARD && 252 type != IPC_GET_VERSION && type != IPC_GET_SEATS &&
280 type != IPC_GET_SEATS) { 253 type != IPC_GET_CONFIG) {
281 printf("%s\n", json_object_to_json_string_ext(resp, 254 printf("%s\n", json_object_to_json_string_ext(resp,
282 JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED)); 255 JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED));
283 return; 256 return;
@@ -288,8 +261,8 @@ static void pretty_print(int type, json_object *resp) {
288 return; 261 return;
289 } 262 }
290 263
291 if (type == IPC_GET_CLIPBOARD) { 264 if (type == IPC_GET_CONFIG) {
292 pretty_print_clipboard(resp); 265 pretty_print_config(resp);
293 return; 266 return;
294 } 267 }
295 268
@@ -323,7 +296,7 @@ int main(int argc, char **argv) {
323 char *socket_path = NULL; 296 char *socket_path = NULL;
324 char *cmdtype = NULL; 297 char *cmdtype = NULL;
325 298
326 wlr_log_init(L_INFO, NULL); 299 wlr_log_init(WLR_INFO, NULL);
327 300
328 static struct option long_options[] = { 301 static struct option long_options[] = {
329 {"help", no_argument, NULL, 'h'}, 302 {"help", no_argument, NULL, 'h'},
@@ -407,8 +380,10 @@ int main(int argc, char **argv) {
407 type = IPC_GET_BAR_CONFIG; 380 type = IPC_GET_BAR_CONFIG;
408 } else if (strcasecmp(cmdtype, "get_version") == 0) { 381 } else if (strcasecmp(cmdtype, "get_version") == 0) {
409 type = IPC_GET_VERSION; 382 type = IPC_GET_VERSION;
410 } else if (strcasecmp(cmdtype, "get_clipboard") == 0) { 383 } else if (strcasecmp(cmdtype, "get_binding_modes") == 0) {
411 type = IPC_GET_CLIPBOARD; 384 type = IPC_GET_BINDING_MODES;
385 } else if (strcasecmp(cmdtype, "get_config") == 0) {
386 type = IPC_GET_CONFIG;
412 } else { 387 } else {
413 sway_abort("Unknown message type %s", cmdtype); 388 sway_abort("Unknown message type %s", cmdtype);
414 } 389 }
diff --git a/swaymsg/swaymsg.1.scd b/swaymsg/swaymsg.1.scd
index 1aa6a1b0..a6e279da 100644
--- a/swaymsg/swaymsg.1.scd
+++ b/swaymsg/swaymsg.1.scd
@@ -59,8 +59,8 @@ _swaymsg_ [options...] [message]
59*get\_version* 59*get\_version*
60 Get JSON-encoded version information for the running instance of sway. 60 Get JSON-encoded version information for the running instance of sway.
61 61
62*get\_clipboard* 62*get\_binding\_modes*
63 Get JSON-encoded information about the clipboard. 63 Gets a JSON-encoded list of currently configured binding modes.
64 Returns the current clipboard mime-types if called without 64
65 arguments, otherwise returns the clipboard data in the requested 65*get\_config*
66 formats. Encodes the data using base64 for non-text mime types. 66 Gets a JSON-encoded copy of the current configuration.