summaryrefslogtreecommitdiffstats
path: root/sway
diff options
context:
space:
mode:
Diffstat (limited to 'sway')
-rw-r--r--sway/CMakeLists.txt107
-rw-r--r--sway/border.c510
-rw-r--r--sway/commands.c480
-rw-r--r--sway/commands/assign.c57
-rw-r--r--sway/commands/bar.c12
-rw-r--r--sway/commands/bar/activate_button.c22
-rw-r--r--sway/commands/bar/binding_mode_indicator.c20
-rw-r--r--sway/commands/bar/bindsym.c39
-rw-r--r--sway/commands/bar/colors.c51
-rw-r--r--sway/commands/bar/context_button.c22
-rw-r--r--sway/commands/bar/font.c13
-rw-r--r--sway/commands/bar/height.c5
-rw-r--r--sway/commands/bar/hidden_state.c24
-rw-r--r--sway/commands/bar/icon_theme.c21
-rw-r--r--sway/commands/bar/id.c7
-rw-r--r--sway/commands/bar/mode.c15
-rw-r--r--sway/commands/bar/modifier.c8
-rw-r--r--sway/commands/bar/output.c15
-rw-r--r--sway/commands/bar/pango_markup.c11
-rw-r--r--sway/commands/bar/position.c29
-rw-r--r--sway/commands/bar/secondary_button.c22
-rw-r--r--sway/commands/bar/separator_symbol.c9
-rw-r--r--sway/commands/bar/status_command.c9
-rw-r--r--sway/commands/bar/strip_workspace_numbers.c18
-rw-r--r--sway/commands/bar/swaybar_command.c9
-rw-r--r--sway/commands/bar/tray_output.c25
-rw-r--r--sway/commands/bar/tray_padding.c29
-rw-r--r--sway/commands/bar/workspace_buttons.c15
-rw-r--r--sway/commands/bar/wrap_scroll.c12
-rw-r--r--sway/commands/bind.c139
-rw-r--r--sway/commands/border.c65
-rw-r--r--sway/commands/client.c72
-rw-r--r--sway/commands/clipboard.c38
-rw-r--r--sway/commands/commands.c26
-rw-r--r--sway/commands/debuglog.c27
-rw-r--r--sway/commands/default_border.c44
-rw-r--r--sway/commands/default_floating_border.c45
-rw-r--r--sway/commands/default_orientation.c (renamed from sway/commands/orientation.c)8
-rw-r--r--sway/commands/exec.c4
-rw-r--r--sway/commands/exec_always.c22
-rw-r--r--sway/commands/exit.c9
-rw-r--r--sway/commands/floating.c81
-rw-r--r--sway/commands/floating_maximum_size.c38
-rw-r--r--sway/commands/floating_minimum_size.c38
-rw-r--r--sway/commands/floating_mod.c42
-rw-r--r--sway/commands/floating_scroll.c46
-rw-r--r--sway/commands/focus.c134
-rw-r--r--sway/commands/focus_follows_mouse.c1
-rw-r--r--sway/commands/font.c27
-rw-r--r--sway/commands/for_window.c41
-rw-r--r--sway/commands/force_focus_wrapping.c13
-rw-r--r--sway/commands/fullscreen.c60
-rw-r--r--sway/commands/gaps.c166
-rw-r--r--sway/commands/hide_edge_borders.c27
-rw-r--r--sway/commands/input.c100
-rw-r--r--sway/commands/input/accel_profile.c13
-rw-r--r--sway/commands/input/click_method.c17
-rw-r--r--sway/commands/input/drag_lock.c16
-rw-r--r--sway/commands/input/dwt.c13
-rw-r--r--sway/commands/input/events.c22
-rw-r--r--sway/commands/input/left_handed.c16
-rw-r--r--sway/commands/input/map_to_output.c27
-rw-r--r--sway/commands/input/middle_emulation.c19
-rw-r--r--sway/commands/input/natural_scroll.c16
-rw-r--r--sway/commands/input/pointer_accel.c16
-rw-r--r--sway/commands/input/scroll_method.c16
-rw-r--r--sway/commands/input/tap.c17
-rw-r--r--sway/commands/input/xkb_layout.c26
-rw-r--r--sway/commands/input/xkb_model.c26
-rw-r--r--sway/commands/input/xkb_options.c26
-rw-r--r--sway/commands/input/xkb_rules.c26
-rw-r--r--sway/commands/input/xkb_variant.c26
-rw-r--r--sway/commands/ipc.c162
-rw-r--r--sway/commands/kill.c16
-rw-r--r--sway/commands/layout.c173
-rw-r--r--sway/commands/log_colors.c22
-rw-r--r--sway/commands/mark.c87
-rw-r--r--sway/commands/mode.c38
-rw-r--r--sway/commands/mouse_warping.c4
-rw-r--r--sway/commands/move.c328
-rw-r--r--sway/commands/new_float.c8
-rw-r--r--sway/commands/new_window.c8
-rw-r--r--sway/commands/no_focus.c41
-rw-r--r--sway/commands/opacity.c36
-rw-r--r--sway/commands/output.c429
-rw-r--r--sway/commands/permit.c108
-rw-r--r--sway/commands/reload.c4
-rw-r--r--sway/commands/resize.c515
-rw-r--r--sway/commands/scratchpad.c72
-rw-r--r--sway/commands/seamless_mouse.c13
-rw-r--r--sway/commands/seat.c61
-rw-r--r--sway/commands/seat/attach.c28
-rw-r--r--sway/commands/seat/cursor.c89
-rw-r--r--sway/commands/seat/fallback.c32
-rw-r--r--sway/commands/set.c13
-rw-r--r--sway/commands/show_marks.c13
-rw-r--r--sway/commands/smart_gaps.c20
-rw-r--r--sway/commands/split.c103
-rw-r--r--sway/commands/sticky.c25
-rw-r--r--sway/commands/swaybg_command.c20
-rw-r--r--sway/commands/unmark.c31
-rw-r--r--sway/commands/workspace.c48
-rw-r--r--sway/commands/workspace_layout.c40
-rw-r--r--sway/commands/ws_auto_back_and_forth.c (renamed from sway/commands/workspace_auto_back_and_forth.c)8
-rw-r--r--sway/config.c1027
-rw-r--r--sway/config/bar.c240
-rw-r--r--sway/config/input.c119
-rw-r--r--sway/config/output.c213
-rw-r--r--sway/config/seat.c146
-rw-r--r--sway/container.c1016
-rw-r--r--sway/criteria.c156
-rw-r--r--sway/debug-tree.c119
-rw-r--r--sway/debug_log.c103
-rw-r--r--sway/desktop/desktop.c14
-rw-r--r--sway/desktop/layer_shell.c347
-rw-r--r--sway/desktop/output.c579
-rw-r--r--sway/desktop/wl_shell.c131
-rw-r--r--sway/desktop/xdg_shell_v6.c249
-rw-r--r--sway/desktop/xwayland.c310
-rw-r--r--sway/extensions.c407
-rw-r--r--sway/focus.c277
-rw-r--r--sway/handlers.c1143
-rw-r--r--sway/input.c69
-rw-r--r--sway/input/cursor.c384
-rw-r--r--sway/input/input-manager.c445
-rw-r--r--sway/input/keyboard.c504
-rw-r--r--sway/input/seat.c675
-rw-r--r--sway/input_state.c490
-rw-r--r--sway/ipc-json.c655
-rw-r--r--sway/ipc-server.c983
-rw-r--r--sway/layout.c1770
-rw-r--r--sway/main.c254
-rw-r--r--sway/meson.build131
-rw-r--r--sway/output.c277
-rw-r--r--sway/security.c212
-rw-r--r--sway/server.c141
-rw-r--r--sway/sway-input.5.txt74
-rw-r--r--sway/sway.1.txt23
-rw-r--r--sway/sway.5.txt75
-rw-r--r--sway/tree/container.c534
-rw-r--r--sway/tree/layout.c1030
-rw-r--r--sway/tree/output.c73
-rw-r--r--sway/tree/view.c329
-rw-r--r--sway/tree/workspace.c401
-rw-r--r--sway/workspace.c374
145 files changed, 9959 insertions, 12402 deletions
diff --git a/sway/CMakeLists.txt b/sway/CMakeLists.txt
deleted file mode 100644
index 48f7a7a8..00000000
--- a/sway/CMakeLists.txt
+++ /dev/null
@@ -1,107 +0,0 @@
1include_directories(
2 ${PROTOCOLS_INCLUDE_DIRS}
3 ${WLC_INCLUDE_DIRS}
4 ${PCRE_INCLUDE_DIRS}
5 ${JSONC_INCLUDE_DIRS}
6 ${XKBCOMMON_INCLUDE_DIRS}
7 ${LIBINPUT_INCLUDE_DIRS}
8 ${CAIRO_INCLUDE_DIRS}
9 ${PANGO_INCLUDE_DIRS}
10 ${WAYLAND_INCLUDE_DIR}
11)
12
13file(GLOB cmds
14 "commands/*.c"
15 "commands/bar/*.c"
16 "commands/input/*.c"
17)
18
19add_executable(sway
20 commands.c
21 ${cmds}
22 base64.c
23 config.c
24 container.c
25 criteria.c
26 debug_log.c
27 extensions.c
28 focus.c
29 handlers.c
30 input.c
31 input_state.c
32 ipc-json.c
33 ipc-server.c
34 layout.c
35 main.c
36 output.c
37 workspace.c
38 border.c
39 security.c
40)
41
42add_definitions(
43 -DSYSCONFDIR="${CMAKE_INSTALL_FULL_SYSCONFDIR}"
44)
45
46target_link_libraries(sway
47 sway-common
48 sway-protocols
49 sway-wayland
50 ${WLC_LIBRARIES}
51 ${XKBCOMMON_LIBRARIES}
52 ${PCRE_LIBRARIES}
53 ${JSONC_LIBRARIES}
54 ${WAYLAND_SERVER_LIBRARIES}
55 ${LIBINPUT_LIBRARIES}
56 ${PANGO_LIBRARIES}
57 ${JSONC_LIBRARIES}
58 m
59)
60
61if (CMAKE_SYSTEM_NAME STREQUAL Linux)
62 target_link_libraries(sway cap)
63endif (CMAKE_SYSTEM_NAME STREQUAL Linux)
64
65install(
66 TARGETS sway
67 RUNTIME
68 DESTINATION bin
69 COMPONENT runtime
70)
71
72add_custom_target(configs ALL)
73
74function(add_config name source destination)
75 add_custom_command(
76 OUTPUT ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${name}
77 COMMAND sed -r
78 's?__PREFIX__?${CMAKE_INSTALL_PREFIX}?g\; s?__SYSCONFDIR__?${CMAKE_INSTALL_FULL_SYSCONFDIR}?g\; s?__DATADIR__?${CMAKE_INSTALL_FULL_DATADIR}?g'
79 ${PROJECT_SOURCE_DIR}/${source}.in > ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${name}
80 DEPENDS ${PROJECT_SOURCE_DIR}/${source}.in
81 COMMENT "Generating config file ${source}"
82 )
83
84 install(
85 FILES ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${name}
86 DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/${destination}
87 COMPONENT configuration
88 )
89
90 add_custom_target(config-${name} DEPENDS ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${name})
91 add_dependencies(configs config-${name})
92endfunction()
93
94add_config(config config sway)
95add_config(00-defaults security.d/00-defaults sway/security.d)
96
97if (CMAKE_SYSTEM_NAME STREQUAL FreeBSD)
98 add_config(10-freebsd security.d/10-freebsd sway/security.d)
99endif (CMAKE_SYSTEM_NAME STREQUAL FreeBSD)
100
101if (A2X_FOUND)
102 add_manpage(sway 1)
103 add_manpage(sway 5)
104 add_manpage(sway-input 5)
105 add_manpage(sway-bar 5)
106 add_manpage(sway-security 7)
107endif()
diff --git a/sway/border.c b/sway/border.c
deleted file mode 100644
index df0022ce..00000000
--- a/sway/border.c
+++ /dev/null
@@ -1,510 +0,0 @@
1#define _XOPEN_SOURCE 700
2#include <wlc/wlc-render.h>
3#include <cairo/cairo.h>
4#include <pango/pangocairo.h>
5#include <stdlib.h>
6#include <stdio.h>
7#include <string.h>
8#include <strings.h>
9#include <arpa/inet.h>
10#include "sway/border.h"
11#include "sway/container.h"
12#include "sway/config.h"
13#include "client/pango.h"
14
15void cairo_set_source_u32(cairo_t *cairo, uint32_t color) {
16 color = htonl(color);
17
18 cairo_set_source_rgba(cairo,
19 (color >> (2*8) & 0xFF) / 255.0,
20 (color >> (1*8) & 0xFF) / 255.0,
21 (color >> (0*8) & 0xFF) / 255.0,
22 (color >> (3*8) & 0xFF) / 255.0);
23}
24
25void border_clear(struct border *border) {
26 if (border && border->buffer) {
27 free(border->buffer);
28 border->buffer = NULL;
29 }
30}
31
32static cairo_t *create_border_buffer(swayc_t *view, struct wlc_geometry g, cairo_surface_t **surface) {
33 if (view->border == NULL) {
34 view->border = malloc(sizeof(struct border));
35 if (!view->border) {
36 sway_log(L_ERROR, "Unable to allocate window border information");
37 return NULL;
38 }
39 }
40 cairo_t *cr;
41 int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, g.size.w);
42 view->border->buffer = calloc(stride * g.size.h, sizeof(unsigned char));
43 view->border->geometry = g;
44 if (!view->border->buffer) {
45 sway_log(L_ERROR, "Unable to allocate window border buffer");
46 return NULL;
47 }
48 *surface = cairo_image_surface_create_for_data(view->border->buffer,
49 CAIRO_FORMAT_ARGB32, g.size.w, g.size.h, stride);
50 if (cairo_surface_status(*surface) != CAIRO_STATUS_SUCCESS) {
51 border_clear(view->border);
52 sway_log(L_ERROR, "Unable to allocate window border surface");
53 return NULL;
54 }
55 cr = cairo_create(*surface);
56 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
57 if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) {
58 cairo_surface_destroy(*surface);
59 border_clear(view->border);
60 sway_log(L_ERROR, "Unable to create cairo context");
61 return NULL;
62 }
63 return cr;
64}
65
66// TODO: move to client/cairo.h when local set_source_u32 is fixed.
67/**
68 * Renders a sharp line of any width and height.
69 *
70 * The line is drawn from (x,y) to (x+width,y+height) where width/height is 0
71 * if the line has a width/height of one pixel, respectively.
72 */
73static void render_sharp_line(cairo_t *cairo, uint32_t color, double x, double y, double width, double height) {
74 cairo_set_source_u32(cairo, color);
75
76 if (width > 1 && height > 1) {
77 cairo_rectangle(cairo, x, y, width, height);
78 cairo_fill(cairo);
79 } else {
80 if (width == 1) {
81 x += 0.5;
82 height += y;
83 width = x;
84 }
85
86 if (height == 1) {
87 y += 0.5;
88 width += x;
89 height = y;
90 }
91
92 cairo_move_to(cairo, x, y);
93 cairo_set_line_width(cairo, 1.0);
94 cairo_line_to(cairo, width, height);
95 cairo_stroke(cairo);
96 }
97}
98
99int get_font_text_height(const char *font) {
100 cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 200, 200);
101 cairo_t *cr = cairo_create(surface);
102 int width, height;
103 get_text_size(cr, font, &width, &height, 1, false, "Gg");
104 cairo_surface_destroy(surface);
105 cairo_destroy(cr);
106 return height;
107}
108
109static void render_borders(swayc_t *view, cairo_t *cr, struct border_colors *colors, bool top) {
110 struct wlc_geometry *g = &view->border->geometry;
111 struct wlc_geometry *b = &view->border_geometry;
112 struct wlc_geometry *v = &view->actual_geometry;
113 enum swayc_layouts layout = view->parent->layout;
114 uint32_t color;
115
116 int x = b->origin.x - g->origin.x;
117 int y = b->origin.y - g->origin.y;
118
119 // draw vertical/horizontal indicator if container is the only child of its parent container
120 bool is_only_child = view->parent && view->parent->children && view->parent->children->length == 1;
121
122 // left border
123 int left_border = v->origin.x - b->origin.x;
124 if (left_border > 0) {
125 render_sharp_line(cr,
126 colors->child_border,
127 x, y,
128 left_border,
129 b->size.h);
130 }
131
132 // right border
133 int right_border = b->size.w - v->size.w - left_border;
134 if (right_border > 0) {
135 if (is_only_child && layout == L_HORIZ && !view->is_floating) {
136 color = colors->indicator;
137 } else {
138 color = colors->child_border;
139 }
140 render_sharp_line(cr,
141 color,
142 x + b->size.w - right_border,
143 y,
144 right_border,
145 b->size.h);
146 }
147
148 // top border
149 int top_border = v->origin.y - b->origin.y;
150 if (top && top_border > 0) {
151 render_sharp_line(cr,
152 colors->child_border,
153 x, y,
154 b->size.w,
155 top_border);
156 }
157
158 // bottom border
159 int bottom_border = b->size.h - (top_border + v->size.h);
160 if (bottom_border > 0) {
161 if (is_only_child && layout == L_VERT && !view->is_floating) {
162 color = colors->indicator;
163 } else {
164 color = colors->child_border;
165 }
166 render_sharp_line(cr,
167 color,
168 x,
169 y + b->size.h - bottom_border,
170 b->size.w,
171 bottom_border);
172 }
173}
174
175static void render_title_bar(swayc_t *view, cairo_t *cr, struct wlc_geometry *b, struct border_colors *colors) {
176 struct wlc_geometry *tb = &view->title_bar_geometry;
177 int x = MIN(tb->origin.x, tb->origin.x - b->origin.x);
178 int y = MIN(tb->origin.y, tb->origin.y - b->origin.y);
179
180 // title bar background
181 cairo_set_source_u32(cr, colors->background);
182 cairo_rectangle(cr, x, y, tb->size.w, tb->size.h);
183 cairo_fill(cr);
184
185 // header top line
186 render_sharp_line(cr, colors->border, x, y, tb->size.w, 1);
187
188 // text
189 if (view->name) {
190 int width, height;
191 get_text_size(cr, config->font, &width, &height, 1, false, "%s", view->name);
192 cairo_move_to(cr, x + 2, y + 2);
193 cairo_set_source_u32(cr, colors->text);
194 pango_printf(cr, config->font, 1, false, "%s", view->name);
195 }
196 // Marks
197 if (config->show_marks && view->marks) {
198 int total_len = 0;
199
200 for(int i = view->marks->length - 1; i >= 0; --i) {
201 char *mark = (char *)view->marks->items[i];
202 if (*mark != '_') {
203 int width, height;
204 get_text_size(cr, config->font, &width, &height, 1, false, "[%s]", mark);
205 total_len += width;
206 if ((int)tb->size.w + x - (total_len + 2) < x + 2) {
207 break;
208 } else {
209 cairo_move_to(cr, (int)tb->size.w + x - (total_len + 2), y + 2);
210 cairo_set_source_u32(cr, colors->text);
211 pango_printf(cr, config->font, 1, false, "[%s]", mark);
212 }
213 }
214 }
215 }
216
217 // titlebars has a border all around for tabbed layouts
218 if (view->parent->layout == L_TABBED) {
219 // header bottom line
220 render_sharp_line(cr, colors->border, x, y + tb->size.h - 1,
221 tb->size.w, 1);
222
223 // left border
224 render_sharp_line(cr, colors->border, x, y, 1, tb->size.h);
225
226 // right border
227 render_sharp_line(cr, colors->border, x + tb->size.w - 1, y,
228 1, tb->size.h);
229
230 return;
231 }
232
233 if ((uint32_t)(view->actual_geometry.origin.y - tb->origin.y) == tb->size.h) {
234 // header bottom line
235 render_sharp_line(cr, colors->border,
236 x + view->actual_geometry.origin.x - tb->origin.x,
237 y + tb->size.h - 1,
238 view->actual_geometry.size.w, 1);
239 } else {
240 // header bottom line
241 render_sharp_line(cr, colors->border, x,
242 y + tb->size.h - 1,
243 tb->size.w, 1);
244 }
245}
246
247/**
248 * Generate nested container title for tabbed/stacked layouts
249 */
250static char *generate_container_title(swayc_t *container) {
251 char layout = 'H';
252 char *name, *prev_name = NULL;
253 switch (container->layout) {
254 case L_TABBED:
255 layout = 'T';
256 break;
257 case L_STACKED:
258 layout = 'S';
259 break;
260 case L_VERT:
261 layout = 'V';
262 break;
263 default:
264 layout = 'H';
265 }
266 int len = 9;
267 name = malloc(len * sizeof(char));
268 if (!name) {
269 sway_log(L_ERROR, "Unable to allocate container title");
270 return NULL;
271 }
272 snprintf(name, len, "sway: %c[", layout);
273
274 int i;
275 for (i = 0; i < container->children->length; ++i) {
276 prev_name = name;
277 swayc_t* child = container->children->items[i];
278 const char *title = NULL;
279 if (child->type == C_VIEW) {
280 title = child->app_id ? child->app_id :
281 (child->instance ? child->instance :
282 (child->class ? child->class :"(null)"));
283 } else { //child->type == C_CONTAINER
284 title = generate_container_title(child);
285 }
286
287 len = strlen(name) + strlen(title) + 1;
288 if (i < container->children->length-1) {
289 len++;
290 }
291
292 name = malloc(len * sizeof(char));
293 if (!name) {
294 free(prev_name);
295 sway_log(L_ERROR, "Unable to allocate container title");
296 return NULL;
297 }
298 if (i < container->children->length-1) {
299 snprintf(name, len, "%s%s ", prev_name, title);
300 } else {
301 snprintf(name, len, "%s%s", prev_name, title);
302 }
303 free(prev_name);
304 }
305
306 prev_name = name;
307 len = strlen(name) + 2;
308 name = malloc(len * sizeof(char));
309 if (!name) {
310 free(prev_name);
311 sway_log(L_ERROR, "Unable to allocate container title");
312 return NULL;
313 }
314 snprintf(name, len, "%s]", prev_name);
315 free(prev_name);
316 free(container->name);
317 container->name = name;
318 return container->name + 6; // don't include "sway: "
319}
320
321void update_tabbed_stacked_titlebars(swayc_t *c, cairo_t *cr, struct wlc_geometry *g, swayc_t *focused, swayc_t *focused_inactive) {
322 if (c->type == C_CONTAINER) {
323 if (c->parent->focused == c) {
324 render_title_bar(c, cr, g, &config->border_colors.focused_inactive);
325 } else {
326 render_title_bar(c, cr, g, &config->border_colors.unfocused);
327 }
328
329 if (!c->visible) {
330 return;
331 }
332
333 int i;
334 for (i = 0; i < c->children->length; ++i) {
335 swayc_t *child = c->children->items[i];
336 update_tabbed_stacked_titlebars(child, cr, g, focused, focused_inactive);
337 }
338 } else {
339 bool is_child_of_focused = swayc_is_child_of(c, get_focused_container(&root_container));
340
341 if (focused == c || is_child_of_focused) {
342 render_title_bar(c, cr, g, &config->border_colors.focused);
343 } else if (focused_inactive == c) {
344 render_title_bar(c, cr, g, &config->border_colors.focused_inactive);
345 } else {
346 render_title_bar(c, cr, g, &config->border_colors.unfocused);
347 }
348 }
349}
350
351static void update_view_border(swayc_t *view) {
352 if (!view->visible) {
353 return;
354 }
355
356 cairo_t *cr = NULL;
357 cairo_surface_t *surface = NULL;
358
359 // clear previous border buffer.
360 border_clear(view->border);
361
362 // get focused and focused_inactive views
363 swayc_t *focused = get_focused_view(&root_container);
364 swayc_t *container = swayc_parent_by_type(view, C_CONTAINER);
365 swayc_t *focused_inactive = NULL;
366
367 bool is_child_of_focused = swayc_is_parent_of(get_focused_container(&root_container), view);
368
369 if (container) {
370 focused_inactive = swayc_focus_by_type(container, C_VIEW);
371 } else {
372 container = swayc_parent_by_type(view, C_WORKSPACE);
373 if (container) {
374 focused_inactive = swayc_focus_by_type(container, C_VIEW);
375 }
376 }
377
378 // for tabbed/stacked layouts the focused view has to draw all the
379 // titlebars of the hidden views.
380 swayc_t *p = NULL;
381 if (view->parent->focused == view && (p = swayc_tabbed_stacked_ancestor(view))) {
382 struct wlc_geometry g = {
383 .origin = {
384 .x = p->x,
385 .y = p->y
386 },
387 .size = {
388 .w = p->width,
389 .h = p->height
390 }
391 };
392 cr = create_border_buffer(view, g, &surface);
393 if (!cr) {
394 goto cleanup;
395 }
396
397 bool render_top = !should_hide_top_border(view, view->y);
398 if (view == focused || is_child_of_focused) {
399 render_borders(view, cr, &config->border_colors.focused, render_top);
400 } else {
401 render_borders(view, cr, &config->border_colors.focused_inactive, render_top);
402 }
403
404 // generate container titles
405 int i;
406 for (i = 0; i < p->children->length; ++i) {
407 swayc_t *child = p->children->items[i];
408 if (child->type == C_CONTAINER) {
409 generate_container_title(child);
410 }
411 }
412
413 update_tabbed_stacked_titlebars(p, cr, &g, focused, focused_inactive);
414 } else {
415 switch (view->border_type) {
416 case B_NONE:
417 break;
418 case B_PIXEL:
419 cr = create_border_buffer(view, view->border_geometry, &surface);
420 if (!cr) {
421 break;
422 }
423
424 if (focused == view || is_child_of_focused) {
425 render_borders(view, cr, &config->border_colors.focused, true);
426 } else if (focused_inactive == view) {
427 render_borders(view, cr, &config->border_colors.focused_inactive, true);
428 } else {
429 render_borders(view, cr, &config->border_colors.unfocused, true);
430 }
431
432 break;
433 case B_NORMAL:
434 cr = create_border_buffer(view, view->border_geometry, &surface);
435 if (!cr) {
436 break;
437 }
438
439 if (focused == view || is_child_of_focused) {
440 render_borders(view, cr, &config->border_colors.focused, false);
441 render_title_bar(view, cr, &view->border_geometry,
442 &config->border_colors.focused);
443 } else if (focused_inactive == view) {
444 render_borders(view, cr, &config->border_colors.focused_inactive, false);
445 render_title_bar(view, cr, &view->border_geometry,
446 &config->border_colors.focused_inactive);
447 } else {
448 render_borders(view, cr, &config->border_colors.unfocused, false);
449 render_title_bar(view, cr, &view->border_geometry,
450 &config->border_colors.unfocused);
451 }
452
453 break;
454 }
455 }
456
457cleanup:
458
459 if (surface) {
460 cairo_surface_flush(surface);
461 cairo_surface_destroy(surface);
462 }
463
464 if (cr) {
465 cairo_destroy(cr);
466 }
467}
468
469void update_container_border(swayc_t *container) {
470 if (container->type == C_VIEW) {
471 update_view_border(container);
472 return;
473 } else {
474 for (int i = 0; i < container->children->length; ++i) {
475 update_container_border(container->children->items[i]);
476 }
477 }
478}
479
480void render_view_borders(wlc_handle view) {
481 swayc_t *c = swayc_by_handle(view);
482
483
484 // emulate i3 behavior for drawing borders for tabbed and stacked layouts:
485 // if we are not the only child in the container, always draw borders,
486 // regardless of the border setting on the individual view
487 if (!c || (c->border_type == B_NONE
488 && !((c->parent->layout == L_TABBED || c->parent->layout == L_STACKED)
489 && c->parent->children->length > 1))) {
490 return;
491 }
492
493 if (c->border && c->border->buffer) {
494 wlc_pixels_write(WLC_RGBA8888, &c->border->geometry, c->border->buffer);
495 }
496}
497
498bool should_hide_top_border(swayc_t *con, double y) {
499 // returns true if container is child of tabbed/stacked layout and is
500 // sharing top border with tabbed titlebar
501 swayc_t *par = con->parent;
502 while (par->type != C_WORKSPACE) {
503 if (par->layout == L_TABBED || par->layout == L_STACKED) {
504 return con->y == y;
505 }
506 con = par;
507 par = par->parent;
508 }
509 return false;
510}
diff --git a/sway/commands.c b/sway/commands.c
index c7dbf731..54d84450 100644
--- a/sway/commands.c
+++ b/sway/commands.c
@@ -1,39 +1,18 @@
1#define _XOPEN_SOURCE 700 1#define _XOPEN_SOURCE 500
2#include <xkbcommon/xkbcommon.h> 2#include <ctype.h>
3#include <xkbcommon/xkbcommon-names.h> 3#include <stdarg.h>
4#include <wlc/wlc.h>
5#include <wlc/wlc-render.h>
6#include <stdio.h>
7#include <stdlib.h> 4#include <stdlib.h>
8#include <errno.h>
9#include <string.h> 5#include <string.h>
10#include <strings.h> 6#include <strings.h>
11#include <unistd.h> 7#include <stdio.h>
12#include <ctype.h> 8#include <json-c/json.h>
13#include <wordexp.h>
14#include <libgen.h>
15#include <sys/types.h>
16#include <sys/wait.h>
17#include <limits.h>
18#include <float.h>
19#include <libinput.h>
20#include "sway/layout.h"
21#include "sway/focus.h"
22#include "sway/workspace.h"
23#include "sway/commands.h" 9#include "sway/commands.h"
24#include "sway/container.h" 10#include "sway/config.h"
25#include "sway/output.h"
26#include "sway/handlers.h"
27#include "sway/input_state.h"
28#include "sway/criteria.h" 11#include "sway/criteria.h"
29#include "sway/ipc-server.h"
30#include "sway/security.h" 12#include "sway/security.h"
31#include "sway/input.h" 13#include "sway/input/input-manager.h"
32#include "sway/border.h" 14#include "sway/input/seat.h"
33#include "stringop.h" 15#include "stringop.h"
34#include "sway.h"
35#include "util.h"
36#include "list.h"
37#include "log.h" 16#include "log.h"
38 17
39struct cmd_handler { 18struct cmd_handler {
@@ -41,10 +20,6 @@ struct cmd_handler {
41 sway_cmd *handle; 20 sway_cmd *handle;
42}; 21};
43 22
44int sp_index = 0;
45
46swayc_t *current_container = NULL;
47
48// Returns error object, or NULL if check succeeds. 23// Returns error object, or NULL if check succeeds.
49struct cmd_results *checkarg(int argc, const char *name, enum expected_args type, int val) { 24struct cmd_results *checkarg(int argc, const char *name, enum expected_args type, int val) {
50 struct cmd_results *error = NULL; 25 struct cmd_results *error = NULL;
@@ -84,24 +59,7 @@ struct cmd_results *checkarg(int argc, const char *name, enum expected_args type
84 return error; 59 return error;
85} 60}
86 61
87void hide_view_in_scratchpad(swayc_t *sp_view) { 62void apply_input_config(struct input_config *input) {
88 if (sp_view == NULL) {
89 return;
90 }
91
92 wlc_view_set_mask(sp_view->handle, 0);
93 sp_view->visible = false;
94 swayc_t *ws = sp_view->parent;
95 remove_child(sp_view);
96 if (swayc_active_workspace() != ws && ws->floating->length == 0 && ws->children->length == 0) {
97 destroy_workspace(ws);
98 } else {
99 arrange_windows(ws, -1, -1);
100 }
101 set_focused_container(container_under_pointer());
102}
103
104void input_cmd_apply(struct input_config *input) {
105 int i; 63 int i;
106 i = list_seq_find(config->input_configs, input_identifier_cmp, input->identifier); 64 i = list_seq_find(config->input_configs, input_identifier_cmp, input->identifier);
107 if (i >= 0) { 65 if (i >= 0) {
@@ -114,111 +72,41 @@ void input_cmd_apply(struct input_config *input) {
114 list_add(config->input_configs, input); 72 list_add(config->input_configs, input);
115 } 73 }
116 74
117 current_input_config = input; 75 input_manager_apply_input_config(input_manager, input);
118
119 if (input->identifier) {
120 // Try to find the input device and apply configuration now. If
121 // this is during startup then there will be no container and config
122 // will be applied during normal "new input" event from wlc.
123 struct libinput_device *device = NULL;
124 for (int i = 0; i < input_devices->length; ++i) {
125 device = input_devices->items[i];
126 char* dev_identifier = libinput_dev_unique_id(device);
127 if (!dev_identifier) {
128 break;
129 }
130 int match = dev_identifier && strcmp(dev_identifier, input->identifier) == 0;
131 free(dev_identifier);
132 if (match) {
133 apply_input_config(input, device);
134 break;
135 }
136 }
137 }
138} 76}
139 77
140void remove_view_from_scratchpad(swayc_t *view) { 78void apply_seat_config(struct seat_config *seat_config) {
141 int i; 79 int i;
142 for (i = 0; i < scratchpad->length; i++) { 80 i = list_seq_find(config->seat_configs, seat_name_cmp, seat_config->name);
143 if (scratchpad->items[i] == view) { 81 if (i >= 0) {
144 if (sp_index == 0) { 82 // merge existing config
145 sp_index = scratchpad->length - 1; 83 struct seat_config *sc = config->seat_configs->items[i];
146 } else { 84 merge_seat_config(sc, seat_config);
147 sp_index--; 85 free_seat_config(seat_config);
148 } 86 seat_config = sc;
149 list_del(scratchpad, sp_index); 87 } else {
150 sp_view = NULL; 88 list_add(config->seat_configs, seat_config);
151 }
152 } 89 }
90
91 input_manager_apply_seat_config(input_manager, seat_config);
153} 92}
154 93
155/* Keep alphabetized */ 94/* Keep alphabetized */
156static struct cmd_handler handlers[] = { 95static struct cmd_handler handlers[] = {
157 { "assign", cmd_assign },
158 { "bar", cmd_bar }, 96 { "bar", cmd_bar },
159 { "bindcode", cmd_bindcode }, 97 { "bindcode", cmd_bindcode },
160 { "bindsym", cmd_bindsym }, 98 { "bindsym", cmd_bindsym },
161 { "border", cmd_border },
162 { "client.background", cmd_client_background },
163 { "client.focused", cmd_client_focused },
164 { "client.focused_inactive", cmd_client_focused_inactive },
165 { "client.placeholder", cmd_client_placeholder },
166 { "client.unfocused", cmd_client_unfocused },
167 { "client.urgent", cmd_client_urgent },
168 { "clipboard", cmd_clipboard },
169 { "commands", cmd_commands },
170 { "debuglog", cmd_debuglog },
171 { "default_border", cmd_default_border },
172 { "default_floating_border", cmd_default_floating_border },
173 { "default_orientation", cmd_orientation },
174 { "exec", cmd_exec }, 99 { "exec", cmd_exec },
175 { "exec_always", cmd_exec_always }, 100 { "exec_always", cmd_exec_always },
176 { "exit", cmd_exit },
177 { "floating", cmd_floating },
178 { "floating_maximum_size", cmd_floating_maximum_size },
179 { "floating_minimum_size", cmd_floating_minimum_size },
180 { "floating_modifier", cmd_floating_mod },
181 { "floating_scroll", cmd_floating_scroll },
182 { "focus", cmd_focus },
183 { "focus_follows_mouse", cmd_focus_follows_mouse }, 101 { "focus_follows_mouse", cmd_focus_follows_mouse },
184 { "font", cmd_font },
185 { "for_window", cmd_for_window },
186 { "force_focus_wrapping", cmd_force_focus_wrapping },
187 { "fullscreen", cmd_fullscreen },
188 { "gaps", cmd_gaps },
189 { "hide_edge_borders", cmd_hide_edge_borders },
190 { "include", cmd_include }, 102 { "include", cmd_include },
191 { "input", cmd_input }, 103 { "input", cmd_input },
192 { "ipc", cmd_ipc },
193 { "kill", cmd_kill },
194 { "layout", cmd_layout },
195 { "log_colors", cmd_log_colors },
196 { "mark", cmd_mark },
197 { "mode", cmd_mode }, 104 { "mode", cmd_mode },
198 { "mouse_warping", cmd_mouse_warping }, 105 { "mouse_warping", cmd_mouse_warping },
199 { "move", cmd_move },
200 { "new_float", cmd_new_float },
201 { "new_window", cmd_new_window },
202 { "no_focus", cmd_no_focus },
203 { "output", cmd_output }, 106 { "output", cmd_output },
204 { "permit", cmd_permit }, 107 { "seat", cmd_seat },
205 { "reject", cmd_reject },
206 { "reload", cmd_reload },
207 { "resize", cmd_resize },
208 { "scratchpad", cmd_scratchpad },
209 { "seamless_mouse", cmd_seamless_mouse },
210 { "set", cmd_set },
211 { "show_marks", cmd_show_marks },
212 { "smart_gaps", cmd_smart_gaps },
213 { "split", cmd_split },
214 { "splith", cmd_splith },
215 { "splitt", cmd_splitt },
216 { "splitv", cmd_splitv },
217 { "sticky", cmd_sticky },
218 { "unmark", cmd_unmark },
219 { "workspace", cmd_workspace }, 108 { "workspace", cmd_workspace },
220 { "workspace_auto_back_and_forth", cmd_ws_auto_back_and_forth }, 109 { "workspace_auto_back_and_forth", cmd_ws_auto_back_and_forth },
221 { "workspace_layout", cmd_workspace_layout },
222}; 110};
223 111
224static struct cmd_handler bar_handlers[] = { 112static struct cmd_handler bar_handlers[] = {
@@ -248,54 +136,6 @@ static struct cmd_handler bar_handlers[] = {
248 { "wrap_scroll", bar_cmd_wrap_scroll }, 136 { "wrap_scroll", bar_cmd_wrap_scroll },
249}; 137};
250 138
251/**
252 * Check and add color to buffer.
253 *
254 * return error object, or NULL if color is valid.
255 */
256struct cmd_results *add_color(const char *name, char *buffer, const char *color) {
257 int len = strlen(color);
258 if (len != 7 && len != 9) {
259 return cmd_results_new(CMD_INVALID, name, "Invalid color definition %s", color);
260 }
261
262 if (color[0] != '#') {
263 return cmd_results_new(CMD_INVALID, name, "Invalid color definition %s", color);
264 }
265
266 int i;
267 for (i = 1; i < len; ++i) {
268 if (!isxdigit(color[i])) {
269 return cmd_results_new(CMD_INVALID, name, "Invalid color definition %s", color);
270 }
271 }
272
273 // copy color to buffer
274 strncpy(buffer, color, len);
275 // add default alpha channel if color was defined without it
276 if (len == 7) {
277 buffer[7] = 'f';
278 buffer[8] = 'f';
279 }
280 buffer[9] = '\0';
281
282 return NULL;
283}
284
285static struct cmd_handler input_handlers[] = {
286 { "accel_profile", input_cmd_accel_profile },
287 { "click_method", input_cmd_click_method },
288 { "drag_lock", input_cmd_drag_lock },
289 { "dwt", input_cmd_dwt },
290 { "events", input_cmd_events },
291 { "left_handed", input_cmd_left_handed },
292 { "middle_emulation", input_cmd_middle_emulation },
293 { "natural_scroll", input_cmd_natural_scroll },
294 { "pointer_accel", input_cmd_pointer_accel },
295 { "scroll_method", input_cmd_scroll_method },
296 { "tap", input_cmd_tap },
297};
298
299static struct cmd_handler bar_colors_handlers[] = { 139static struct cmd_handler bar_colors_handlers[] = {
300 { "active_workspace", bar_colors_cmd_active_workspace }, 140 { "active_workspace", bar_colors_cmd_active_workspace },
301 { "background", bar_colors_cmd_background }, 141 { "background", bar_colors_cmd_background },
@@ -310,26 +150,27 @@ static struct cmd_handler bar_colors_handlers[] = {
310 { "urgent_workspace", bar_colors_cmd_urgent_workspace }, 150 { "urgent_workspace", bar_colors_cmd_urgent_workspace },
311}; 151};
312 152
313static struct cmd_handler ipc_handlers[] = { 153/* Config-time only commands. Keep alphabetized */
314 { "*", cmd_ipc_cmd }, 154static struct cmd_handler config_handlers[] = {
315 { "bar-config", cmd_ipc_cmd }, 155 { "default_orientation", cmd_default_orientation },
316 { "command", cmd_ipc_cmd }, 156 { "set", cmd_set },
317 { "events", cmd_ipc_events }, 157 { "swaybg_command", cmd_swaybg_command },
318 { "inputs", cmd_ipc_cmd },
319 { "marks", cmd_ipc_cmd },
320 { "outputs", cmd_ipc_cmd },
321 { "tree", cmd_ipc_cmd },
322 { "workspaces", cmd_ipc_cmd },
323}; 158};
324 159
325static struct cmd_handler ipc_event_handlers[] = { 160/* Runtime-only commands. Keep alphabetized */
326 { "*", cmd_ipc_event_cmd }, 161static struct cmd_handler command_handlers[] = {
327 { "binding", cmd_ipc_event_cmd }, 162 { "exit", cmd_exit },
328 { "input", cmd_ipc_event_cmd }, 163 { "focus", cmd_focus },
329 { "mode", cmd_ipc_event_cmd }, 164 { "kill", cmd_kill },
330 { "output", cmd_ipc_event_cmd }, 165 { "layout", cmd_layout },
331 { "window", cmd_ipc_event_cmd }, 166 { "move", cmd_move },
332 { "workspace", cmd_ipc_event_cmd }, 167 { "opacity", cmd_opacity },
168 { "reload", cmd_reload },
169 { "resize", cmd_resize },
170 { "split", cmd_split },
171 { "splith", cmd_splith },
172 { "splitt", cmd_splitt },
173 { "splitv", cmd_splitv },
333}; 174};
334 175
335static int handler_compare(const void *_a, const void *_b) { 176static int handler_compare(const void *_a, const void *_b) {
@@ -338,42 +179,90 @@ static int handler_compare(const void *_a, const void *_b) {
338 return strcasecmp(a->command, b->command); 179 return strcasecmp(a->command, b->command);
339} 180}
340 181
182// must be in order for the bsearch
183static struct cmd_handler input_handlers[] = {
184 { "accel_profile", input_cmd_accel_profile },
185 { "click_method", input_cmd_click_method },
186 { "drag_lock", input_cmd_drag_lock },
187 { "dwt", input_cmd_dwt },
188 { "events", input_cmd_events },
189 { "left_handed", input_cmd_left_handed },
190 { "map_to_output", input_cmd_map_to_output },
191 { "middle_emulation", input_cmd_middle_emulation },
192 { "natural_scroll", input_cmd_natural_scroll },
193 { "pointer_accel", input_cmd_pointer_accel },
194 { "scroll_method", input_cmd_scroll_method },
195 { "tap", input_cmd_tap },
196 { "xkb_layout", input_cmd_xkb_layout },
197 { "xkb_model", input_cmd_xkb_model },
198 { "xkb_options", input_cmd_xkb_options },
199 { "xkb_rules", input_cmd_xkb_rules },
200 { "xkb_variant", input_cmd_xkb_variant },
201};
202
203// must be in order for the bsearch
204static struct cmd_handler seat_handlers[] = {
205 { "attach", seat_cmd_attach },
206 { "cursor", seat_cmd_cursor },
207 { "fallback", seat_cmd_fallback },
208};
209
341static struct cmd_handler *find_handler(char *line, enum cmd_status block) { 210static struct cmd_handler *find_handler(char *line, enum cmd_status block) {
342 struct cmd_handler d = { .command=line }; 211 struct cmd_handler d = { .command=line };
343 struct cmd_handler *res = NULL; 212 struct cmd_handler *res = NULL;
344 sway_log(L_DEBUG, "find_handler(%s) %d", line, block == CMD_BLOCK_INPUT); 213 wlr_log(L_DEBUG, "find_handler(%s) %d", line, block == CMD_BLOCK_SEAT);
214
215 bool config_loading = config->reading || !config->active;
216
345 if (block == CMD_BLOCK_BAR) { 217 if (block == CMD_BLOCK_BAR) {
346 res = bsearch(&d, bar_handlers, 218 return bsearch(&d, bar_handlers,
347 sizeof(bar_handlers) / sizeof(struct cmd_handler), 219 sizeof(bar_handlers) / sizeof(struct cmd_handler),
348 sizeof(struct cmd_handler), handler_compare); 220 sizeof(struct cmd_handler), handler_compare);
349 } else if (block == CMD_BLOCK_BAR_COLORS){ 221 } else if (block == CMD_BLOCK_BAR_COLORS) {
350 res = bsearch(&d, bar_colors_handlers, 222 return bsearch(&d, bar_colors_handlers,
351 sizeof(bar_colors_handlers) / sizeof(struct cmd_handler), 223 sizeof(bar_colors_handlers) / sizeof(struct cmd_handler),
352 sizeof(struct cmd_handler), handler_compare); 224 sizeof(struct cmd_handler), handler_compare);
353 } else if (block == CMD_BLOCK_INPUT) { 225 } else if (block == CMD_BLOCK_INPUT) {
354 res = bsearch(&d, input_handlers, 226 return bsearch(&d, input_handlers,
355 sizeof(input_handlers) / sizeof(struct cmd_handler), 227 sizeof(input_handlers) / sizeof(struct cmd_handler),
356 sizeof(struct cmd_handler), handler_compare); 228 sizeof(struct cmd_handler), handler_compare);
357 } else if (block == CMD_BLOCK_IPC) { 229 } else if (block == CMD_BLOCK_SEAT) {
358 res = bsearch(&d, ipc_handlers, 230 return bsearch(&d, seat_handlers,
359 sizeof(ipc_handlers) / sizeof(struct cmd_handler), 231 sizeof(seat_handlers) / sizeof(struct cmd_handler),
360 sizeof(struct cmd_handler), handler_compare); 232 sizeof(struct cmd_handler), handler_compare);
361 } else if (block == CMD_BLOCK_IPC_EVENTS) { 233 }
362 res = bsearch(&d, ipc_event_handlers, 234
363 sizeof(ipc_event_handlers) / sizeof(struct cmd_handler), 235 if (!config_loading) {
364 sizeof(struct cmd_handler), handler_compare); 236 res = bsearch(&d, command_handlers,
365 } else { 237 sizeof(command_handlers) / sizeof(struct cmd_handler),
366 res = bsearch(&d, handlers, 238 sizeof(struct cmd_handler), handler_compare);
239
240 if (res) {
241 return res;
242 }
243 }
244
245 if (config->reading) {
246 res = bsearch(&d, config_handlers,
247 sizeof(config_handlers) / sizeof(struct cmd_handler),
248 sizeof(struct cmd_handler), handler_compare);
249
250 if (res) {
251 return res;
252 }
253 }
254
255 res = bsearch(&d, handlers,
367 sizeof(handlers) / sizeof(struct cmd_handler), 256 sizeof(handlers) / sizeof(struct cmd_handler),
368 sizeof(struct cmd_handler), handler_compare); 257 sizeof(struct cmd_handler), handler_compare);
369 } 258
370 return res; 259 return res;
371} 260}
372 261
373struct cmd_results *handle_command(char *_exec, enum command_context context) { 262struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) {
374 // Even though this function will process multiple commands we will only 263 // Even though this function will process multiple commands we will only
375 // return the last error, if any (for now). (Since we have access to an 264 // return the last error, if any (for now). (Since we have access to an
376 // error string we could e.g. concatonate all errors there.) 265 // error string we could e.g. concatenate all errors there.)
377 struct cmd_results *results = NULL; 266 struct cmd_results *results = NULL;
378 char *exec = strdup(_exec); 267 char *exec = strdup(_exec);
379 char *head = exec; 268 char *head = exec;
@@ -381,10 +270,22 @@ struct cmd_results *handle_command(char *_exec, enum command_context context) {
381 char *cmd; 270 char *cmd;
382 list_t *containers = NULL; 271 list_t *containers = NULL;
383 272
273 if (seat == NULL) {
274 // passing a NULL seat means we just pick the default seat
275 seat = input_manager_get_default_seat(input_manager);
276 if (!sway_assert(seat, "could not find a seat to run the command on")) {
277 return NULL;
278 }
279 }
280
281 config->handler_context.seat = seat;
282
384 head = exec; 283 head = exec;
385 do { 284 do {
386 // Extract criteria (valid for this command list only). 285 // Extract criteria (valid for this command list only).
286 bool has_criteria = false;
387 if (*head == '[') { 287 if (*head == '[') {
288 has_criteria = true;
388 ++head; 289 ++head;
389 char *criteria_string = argsep(&head, "]"); 290 char *criteria_string = argsep(&head, "]");
390 if (head) { 291 if (head) {
@@ -393,13 +294,14 @@ struct cmd_results *handle_command(char *_exec, enum command_context context) {
393 char *error; 294 char *error;
394 295
395 if ((error = extract_crit_tokens(tokens, criteria_string))) { 296 if ((error = extract_crit_tokens(tokens, criteria_string))) {
297 wlr_log(L_DEBUG, "criteria string parse error: %s", error);
396 results = cmd_results_new(CMD_INVALID, criteria_string, 298 results = cmd_results_new(CMD_INVALID, criteria_string,
397 "Can't parse criteria string: %s", error); 299 "Can't parse criteria string: %s", error);
398 free(error); 300 free(error);
399 free(tokens); 301 free(tokens);
400 goto cleanup; 302 goto cleanup;
401 } 303 }
402 containers = container_for(tokens); 304 containers = container_for_crit_tokens(tokens);
403 305
404 free(tokens); 306 free(tokens);
405 } else { 307 } else {
@@ -419,10 +321,10 @@ struct cmd_results *handle_command(char *_exec, enum command_context context) {
419 cmd = argsep(&cmdlist, ","); 321 cmd = argsep(&cmdlist, ",");
420 cmd += strspn(cmd, whitespace); 322 cmd += strspn(cmd, whitespace);
421 if (strcmp(cmd, "") == 0) { 323 if (strcmp(cmd, "") == 0) {
422 sway_log(L_INFO, "Ignoring empty command."); 324 wlr_log(L_INFO, "Ignoring empty command.");
423 continue; 325 continue;
424 } 326 }
425 sway_log(L_INFO, "Handling command '%s'", cmd); 327 wlr_log(L_INFO, "Handling command '%s'", cmd);
426 //TODO better handling of argv 328 //TODO better handling of argv
427 int argc; 329 int argc;
428 char **argv = split_args(cmd, &argc); 330 char **argv = split_args(cmd, &argc);
@@ -443,31 +345,16 @@ struct cmd_results *handle_command(char *_exec, enum command_context context) {
443 free_argv(argc, argv); 345 free_argv(argc, argv);
444 goto cleanup; 346 goto cleanup;
445 } 347 }
446 if (!(get_command_policy_mask(argv[0]) & context)) {
447 if (results) {
448 free_cmd_results(results);
449 }
450 results = cmd_results_new(CMD_INVALID, cmd,
451 "Permission denied for %s via %s", cmd,
452 command_policy_str(context));
453 free_argv(argc, argv);
454 goto cleanup;
455 }
456 int i = 0;
457 do {
458 if (!containers) {
459 current_container = get_focused_container(&root_container);
460 } else if (containers->length == 0) {
461 if (results) {
462 free_cmd_results(results);
463 }
464 results = cmd_results_new(CMD_FAILURE, argv[0], "No matching container");
465 goto cleanup;
466 } else {
467 current_container = (swayc_t *)containers->items[i];
468 }
469 sway_log(L_INFO, "Running on container '%s'", current_container->name);
470 348
349 if (!has_criteria) {
350 // without criteria, the command acts upon the focused
351 // container
352 config->handler_context.current_container =
353 seat_get_focus_inactive(seat, &root_container);
354 if (!sway_assert(config->handler_context.current_container,
355 "could not get focus-inactive for root container")) {
356 return NULL;
357 }
471 struct cmd_results *res = handler->handle(argc-1, argv+1); 358 struct cmd_results *res = handler->handle(argc-1, argv+1);
472 if (res->status != CMD_SUCCESS) { 359 if (res->status != CMD_SUCCESS) {
473 free_argv(argc, argv); 360 free_argv(argc, argv);
@@ -478,35 +365,39 @@ struct cmd_results *handle_command(char *_exec, enum command_context context) {
478 goto cleanup; 365 goto cleanup;
479 } 366 }
480 free_cmd_results(res); 367 free_cmd_results(res);
481 ++i; 368 } else {
482 } while(containers && i < containers->length); 369 for (int i = 0; i < containers->length; ++i) {
483 370 config->handler_context.current_container = containers->items[i];
371 struct cmd_results *res = handler->handle(argc-1, argv+1);
372 if (res->status != CMD_SUCCESS) {
373 free_argv(argc, argv);
374 if (results) {
375 free_cmd_results(results);
376 }
377 results = res;
378 goto cleanup;
379 }
380 free_cmd_results(res);
381 }
382 }
484 free_argv(argc, argv); 383 free_argv(argc, argv);
485 } while(cmdlist); 384 } while(cmdlist);
486
487 if (containers) {
488 list_free(containers);
489 containers = NULL;
490 }
491 } while(head); 385 } while(head);
492 cleanup: 386cleanup:
493 free(exec); 387 free(exec);
494 if (containers) {
495 free(containers);
496 }
497 if (!results) { 388 if (!results) {
498 results = cmd_results_new(CMD_SUCCESS, NULL, NULL); 389 results = cmd_results_new(CMD_SUCCESS, NULL, NULL);
499 } 390 }
500 return results; 391 return results;
501} 392}
502 393
503// this is like handle_command above, except: 394// this is like execute_command above, except:
504// 1) it ignores empty commands (empty lines) 395// 1) it ignores empty commands (empty lines)
505// 2) it does variable substitution 396// 2) it does variable substitution
506// 3) it doesn't split commands (because the multiple commands are supposed to 397// 3) it doesn't split commands (because the multiple commands are supposed to
507// be chained together) 398// be chained together)
508// 4) handle_command handles all state internally while config_command has some 399// 4) execute_command handles all state internally while config_command has
509// state handled outside (notably the block mode, in read_config) 400// some state handled outside (notably the block mode, in read_config)
510struct cmd_results *config_command(char *exec, enum cmd_status block) { 401struct cmd_results *config_command(char *exec, enum cmd_status block) {
511 struct cmd_results *results = NULL; 402 struct cmd_results *results = NULL;
512 int argc; 403 int argc;
@@ -516,7 +407,7 @@ struct cmd_results *config_command(char *exec, enum cmd_status block) {
516 goto cleanup; 407 goto cleanup;
517 } 408 }
518 409
519 sway_log(L_INFO, "handling config command '%s'", exec); 410 wlr_log(L_INFO, "handling config command '%s'", exec);
520 // Endblock 411 // Endblock
521 if (**argv == '}') { 412 if (**argv == '}') {
522 results = cmd_results_new(CMD_BLOCK_END, NULL, NULL); 413 results = cmd_results_new(CMD_BLOCK_END, NULL, NULL);
@@ -530,12 +421,13 @@ struct cmd_results *config_command(char *exec, enum cmd_status block) {
530 } 421 }
531 int i; 422 int i;
532 // Var replacement, for all but first argument of set 423 // Var replacement, for all but first argument of set
424 // TODO commands
533 for (i = handler->handle == cmd_set ? 2 : 1; i < argc; ++i) { 425 for (i = handler->handle == cmd_set ? 2 : 1; i < argc; ++i) {
534 argv[i] = do_var_replacement(argv[i]); 426 argv[i] = do_var_replacement(argv[i]);
535 unescape_string(argv[i]); 427 unescape_string(argv[i]);
536 } 428 }
537 /* Strip quotes for first argument. 429 // Strip quotes for first argument.
538 * TODO This part needs to be handled much better */ 430 // TODO This part needs to be handled much better
539 if (argc>1 && (*argv[1] == '\"' || *argv[1] == '\'')) { 431 if (argc>1 && (*argv[1] == '\"' || *argv[1] == '\'')) {
540 strip_quotes(argv[1]); 432 strip_quotes(argv[1]);
541 } 433 }
@@ -619,7 +511,7 @@ struct cmd_results *config_commands_command(char *exec) {
619 } 511 }
620 policy->context = context; 512 policy->context = context;
621 513
622 sway_log(L_INFO, "Set command policy for %s to %d", 514 wlr_log(L_INFO, "Set command policy for %s to %d",
623 policy->command, policy->context); 515 policy->command, policy->context);
624 516
625 results = cmd_results_new(CMD_SUCCESS, NULL, NULL); 517 results = cmd_results_new(CMD_SUCCESS, NULL, NULL);
@@ -629,10 +521,11 @@ cleanup:
629 return results; 521 return results;
630} 522}
631 523
632struct cmd_results *cmd_results_new(enum cmd_status status, const char* input, const char *format, ...) { 524struct cmd_results *cmd_results_new(enum cmd_status status,
525 const char *input, const char *format, ...) {
633 struct cmd_results *results = malloc(sizeof(struct cmd_results)); 526 struct cmd_results *results = malloc(sizeof(struct cmd_results));
634 if (!results) { 527 if (!results) {
635 sway_log(L_ERROR, "Unable to allocate command results"); 528 wlr_log(L_ERROR, "Unable to allocate command results");
636 return NULL; 529 return NULL;
637 } 530 }
638 results->status = status; 531 results->status = status;
@@ -669,12 +562,15 @@ void free_cmd_results(struct cmd_results *results) {
669const char *cmd_results_to_json(struct cmd_results *results) { 562const char *cmd_results_to_json(struct cmd_results *results) {
670 json_object *result_array = json_object_new_array(); 563 json_object *result_array = json_object_new_array();
671 json_object *root = json_object_new_object(); 564 json_object *root = json_object_new_object();
672 json_object_object_add(root, "success", json_object_new_boolean(results->status == CMD_SUCCESS)); 565 json_object_object_add(root, "success",
566 json_object_new_boolean(results->status == CMD_SUCCESS));
673 if (results->input) { 567 if (results->input) {
674 json_object_object_add(root, "input", json_object_new_string(results->input)); 568 json_object_object_add(
569 root, "input", json_object_new_string(results->input));
675 } 570 }
676 if (results->error) { 571 if (results->error) {
677 json_object_object_add(root, "error", json_object_new_string(results->error)); 572 json_object_object_add(
573 root, "error", json_object_new_string(results->error));
678 } 574 }
679 json_object_array_add(result_array, root); 575 json_object_array_add(result_array, root);
680 const char *json = json_object_to_json_string(result_array); 576 const char *json = json_object_to_json_string(result_array);
@@ -682,3 +578,35 @@ const char *cmd_results_to_json(struct cmd_results *results) {
682 free(root); 578 free(root);
683 return json; 579 return json;
684} 580}
581
582/**
583 * Check and add color to buffer.
584 *
585 * return error object, or NULL if color is valid.
586 */
587struct cmd_results *add_color(const char *name,
588 char *buffer, const char *color) {
589 int len = strlen(color);
590 if (len != 7 && len != 9) {
591 return cmd_results_new(CMD_INVALID, name,
592 "Invalid color definition %s", color);
593 }
594 if (color[0] != '#') {
595 return cmd_results_new(CMD_INVALID, name,
596 "Invalid color definition %s", color);
597 }
598 for (int i = 1; i < len; ++i) {
599 if (!isxdigit(color[i])) {
600 return cmd_results_new(CMD_INVALID, name,
601 "Invalid color definition %s", color);
602 }
603 }
604 strncpy(buffer, color, len);
605 // add default alpha channel if color was defined without it
606 if (len == 7) {
607 buffer[7] = 'f';
608 buffer[8] = 'f';
609 }
610 buffer[9] = '\0';
611 return NULL;
612}
diff --git a/sway/commands/assign.c b/sway/commands/assign.c
deleted file mode 100644
index c3b03bbc..00000000
--- a/sway/commands/assign.c
+++ /dev/null
@@ -1,57 +0,0 @@
1#define _XOPEN_SOURCE 700
2#include <stdio.h>
3#include <string.h>
4#include "sway/commands.h"
5#include "sway/criteria.h"
6#include "list.h"
7#include "log.h"
8
9struct cmd_results *cmd_assign(int argc, char **argv) {
10 struct cmd_results *error = NULL;
11 if ((error = checkarg(argc, "assign", EXPECTED_AT_LEAST, 2))) {
12 return error;
13 }
14
15 char *criteria = *argv++;
16
17 if (strncmp(*argv, "→", strlen("→")) == 0) {
18 if (argc < 3) {
19 return cmd_results_new(CMD_INVALID, "assign", "Missing workspace");
20 }
21 argv++;
22 }
23
24 char *movecmd = "move container to workspace ";
25 int arglen = strlen(movecmd) + strlen(*argv) + 1;
26 char *cmdlist = calloc(1, arglen);
27 if (!cmdlist) {
28 return cmd_results_new(CMD_FAILURE, "assign", "Unable to allocate command list");
29 }
30 snprintf(cmdlist, arglen, "%s%s", movecmd, *argv);
31
32 struct criteria *crit = malloc(sizeof(struct criteria));
33 if (!crit) {
34 free(cmdlist);
35 return cmd_results_new(CMD_FAILURE, "assign", "Unable to allocate criteria");
36 }
37 crit->crit_raw = strdup(criteria);
38 crit->cmdlist = cmdlist;
39 crit->tokens = create_list();
40 char *err_str = extract_crit_tokens(crit->tokens, crit->crit_raw);
41
42 if (err_str) {
43 error = cmd_results_new(CMD_INVALID, "assign", err_str);
44 free(err_str);
45 free_criteria(crit);
46 } else if (crit->tokens->length == 0) {
47 error = cmd_results_new(CMD_INVALID, "assign", "Found no name/value pairs in criteria");
48 free_criteria(crit);
49 } else if (list_seq_find(config->criteria, criteria_cmp, crit) != -1) {
50 sway_log(L_DEBUG, "assign: Duplicate, skipping.");
51 free_criteria(crit);
52 } else {
53 sway_log(L_DEBUG, "assign: '%s' -> '%s' added", crit->crit_raw, crit->cmdlist);
54 list_add(config->criteria, crit);
55 }
56 return error ? error : cmd_results_new(CMD_SUCCESS, NULL, NULL);
57}
diff --git a/sway/commands/bar.c b/sway/commands/bar.c
index 04745a6e..ff111163 100644
--- a/sway/commands/bar.c
+++ b/sway/commands/bar.c
@@ -1,9 +1,8 @@
1#include <stdio.h>
2#include <string.h> 1#include <string.h>
3#include <strings.h> 2#include <strings.h>
3#include <wlr/util/log.h>
4#include "sway/commands.h" 4#include "sway/commands.h"
5#include "sway/config.h" 5#include "sway/config.h"
6#include "log.h"
7#include "util.h" 6#include "util.h"
8 7
9struct cmd_results *cmd_bar(int argc, char **argv) { 8struct cmd_results *cmd_bar(int argc, char **argv) {
@@ -27,7 +26,6 @@ struct cmd_results *cmd_bar(int argc, char **argv) {
27 return bar_cmd_hidden_state(argc-1, argv + 1); 26 return bar_cmd_hidden_state(argc-1, argv + 1);
28 } 27 }
29 } 28 }
30
31 return cmd_results_new(CMD_FAILURE, "bar", "Can only be used in config file."); 29 return cmd_results_new(CMD_FAILURE, "bar", "Can only be used in config file.");
32 } 30 }
33 31
@@ -38,15 +36,15 @@ struct cmd_results *cmd_bar(int argc, char **argv) {
38 } 36 }
39 37
40 // set bar id 38 // set bar id
41 int i; 39 for (int i = 0; i < config->bars->length; ++i) {
42 for (i = 0; i < config->bars->length; ++i) {
43 if (bar == config->bars->items[i]) { 40 if (bar == config->bars->items[i]) {
44 const int len = 5 + numlen(i); // "bar-" + i + \0 41 const int len = 5 + numlen(i); // "bar-" + i + \0
45 bar->id = malloc(len * sizeof(char)); 42 bar->id = malloc(len * sizeof(char));
46 if (bar->id) { 43 if (bar->id) {
47 snprintf(bar->id, len, "bar-%d", i); 44 snprintf(bar->id, len, "bar-%d", i);
48 } else { 45 } else {
49 return cmd_results_new(CMD_FAILURE, "bar", "Unable to allocate bar ID"); 46 return cmd_results_new(CMD_FAILURE,
47 "bar", "Unable to allocate bar ID");
50 } 48 }
51 break; 49 break;
52 } 50 }
@@ -54,6 +52,6 @@ struct cmd_results *cmd_bar(int argc, char **argv) {
54 52
55 // Set current bar 53 // Set current bar
56 config->current_bar = bar; 54 config->current_bar = bar;
57 sway_log(L_DEBUG, "Configuring bar %s", bar->id); 55 wlr_log(L_DEBUG, "Configuring bar %s", bar->id);
58 return cmd_results_new(CMD_BLOCK_BAR, NULL, NULL); 56 return cmd_results_new(CMD_BLOCK_BAR, NULL, NULL);
59} 57}
diff --git a/sway/commands/bar/activate_button.c b/sway/commands/bar/activate_button.c
index 32a1d3e5..7310e7ec 100644
--- a/sway/commands/bar/activate_button.c
+++ b/sway/commands/bar/activate_button.c
@@ -3,24 +3,6 @@
3#include "log.h" 3#include "log.h"
4 4
5struct cmd_results *bar_cmd_activate_button(int argc, char **argv) { 5struct cmd_results *bar_cmd_activate_button(int argc, char **argv) {
6 const char *cmd_name = "activate_button"; 6 // TODO TRAY
7#ifndef ENABLE_TRAY 7 return cmd_results_new(CMD_INVALID, "activate_button", "TODO TRAY");
8 return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command "
9 "%s called, but sway was compiled without tray support",
10 cmd_name, cmd_name);
11#else
12 struct cmd_results *error = NULL;
13 if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) {
14 return error;
15 }
16
17 if (!config->current_bar) {
18 return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined.");
19 }
20
21 // User should be able to prefix with 0x or whatever they want
22 config->current_bar->secondary_button = strtoul(argv[0], NULL, 0);
23
24 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
25#endif
26} 8}
diff --git a/sway/commands/bar/binding_mode_indicator.c b/sway/commands/bar/binding_mode_indicator.c
index 64f5b84f..3ba5f33f 100644
--- a/sway/commands/bar/binding_mode_indicator.c
+++ b/sway/commands/bar/binding_mode_indicator.c
@@ -5,23 +5,23 @@
5 5
6struct cmd_results *bar_cmd_binding_mode_indicator(int argc, char **argv) { 6struct cmd_results *bar_cmd_binding_mode_indicator(int argc, char **argv) {
7 struct cmd_results *error = NULL; 7 struct cmd_results *error = NULL;
8 if ((error = checkarg(argc, "binding_mode_indicator", EXPECTED_EQUAL_TO, 1))) { 8 if ((error = checkarg(argc,
9 "binding_mode_indicator", EXPECTED_EQUAL_TO, 1))) {
9 return error; 10 return error;
10 } 11 }
11
12 if (!config->current_bar) { 12 if (!config->current_bar) {
13 return cmd_results_new(CMD_FAILURE, "binding_mode_indicator", "No bar defined."); 13 return cmd_results_new(CMD_FAILURE,
14 "binding_mode_indicator", "No bar defined.");
14 } 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 sway_log(L_DEBUG, "Enabling binding mode indicator on bar: %s", config->current_bar->id); 18 wlr_log(L_DEBUG, "Enabling binding mode indicator on bar: %s",
19 config->current_bar->id);
19 } else if (strcasecmp("no", argv[0]) == 0) { 20 } else if (strcasecmp("no", argv[0]) == 0) {
20 config->current_bar->binding_mode_indicator = false; 21 config->current_bar->binding_mode_indicator = false;
21 sway_log(L_DEBUG, "Disabling binding mode indicator on bar: %s", config->current_bar->id); 22 wlr_log(L_DEBUG, "Disabling binding mode indicator on bar: %s",
22 } else { 23 config->current_bar->id);
23 error = cmd_results_new(CMD_INVALID, "binding_mode_indicator", "Invalid value %s", argv[0]);
24 return error;
25 } 24 }
26 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 25 return cmd_results_new(CMD_INVALID, "binding_mode_indicator",
26 "Invalid value %s", argv[0]);
27} 27}
diff --git a/sway/commands/bar/bindsym.c b/sway/commands/bar/bindsym.c
index 5f90b51a..ac09a03f 100644
--- a/sway/commands/bar/bindsym.c
+++ b/sway/commands/bar/bindsym.c
@@ -7,42 +7,5 @@
7#include "stringop.h" 7#include "stringop.h"
8 8
9struct cmd_results *bar_cmd_bindsym(int argc, char **argv) { 9struct cmd_results *bar_cmd_bindsym(int argc, char **argv) {
10 struct cmd_results *error = NULL; 10 return cmd_results_new(CMD_FAILURE, "bindsym", "TODO"); // TODO
11 if ((error = checkarg(argc, "bindsym", EXPECTED_MORE_THAN, 1))) {
12 return error;
13 } else if (!config->reading) {
14 return cmd_results_new(CMD_FAILURE, "bindsym", "Can only be used in config file.");
15 }
16
17 if (!config->current_bar) {
18 return cmd_results_new(CMD_FAILURE, "bindsym", "No bar defined.");
19 }
20
21 if (strlen(argv[1]) != 7) {
22 return cmd_results_new(CMD_INVALID, "bindsym", "Invalid mouse binding %s", argv[1]);
23 }
24 uint32_t numbutton = (uint32_t)atoi(argv[1] + 6);
25 if (numbutton < 1 || numbutton > 5 || strncmp(argv[1], "button", 6) != 0) {
26 return cmd_results_new(CMD_INVALID, "bindsym", "Invalid mouse binding %s", argv[1]);
27 }
28 struct sway_mouse_binding *binding = malloc(sizeof(struct sway_mouse_binding));
29 if (!binding) {
30 return cmd_results_new(CMD_FAILURE, "bindsym", "Unable to allocate binding");
31 }
32 binding->button = numbutton;
33 binding->command = join_args(argv + 1, argc - 1);
34
35 struct bar_config *bar = config->current_bar;
36 int i = list_seq_find(bar->bindings, sway_mouse_binding_cmp_buttons, binding);
37 if (i > -1) {
38 sway_log(L_DEBUG, "bindsym - '%s' for swaybar already exists, overwriting", argv[0]);
39 struct sway_mouse_binding *dup = bar->bindings->items[i];
40 free_sway_mouse_binding(dup);
41 list_del(bar->bindings, i);
42 }
43 list_add(bar->bindings, binding);
44 list_qsort(bar->bindings, sway_mouse_binding_cmp_qsort);
45
46 sway_log(L_DEBUG, "bindsym - Bound %s to command %s when clicking swaybar", argv[0], binding->command);
47 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
48} 11}
diff --git a/sway/commands/bar/colors.c b/sway/commands/bar/colors.c
index 8b3b0aac..17ba9b7c 100644
--- a/sway/commands/bar/colors.c
+++ b/sway/commands/bar/colors.c
@@ -1,47 +1,38 @@
1#include <string.h> 1#include <string.h>
2#include "sway/commands.h" 2#include "sway/commands.h"
3 3
4static struct cmd_results *parse_single_color(char **color, const char *cmd_name, int argc, char **argv) { 4static struct cmd_results *parse_single_color(char **color,
5 const char *cmd_name, int argc, char **argv) {
5 struct cmd_results *error = NULL; 6 struct cmd_results *error = NULL;
6 if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) { 7 if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) {
7 return error; 8 return error;
8 } 9 }
9 10 if (!*color && !(*color = malloc(10))) {
10 if (!*color) { 11 return NULL;
11 *color = malloc(10);
12 if (!*color) {
13 return cmd_results_new(CMD_FAILURE, cmd_name, "Unable to allocate color");
14 }
15 } 12 }
16
17 error = add_color(cmd_name, *color, argv[0]); 13 error = add_color(cmd_name, *color, argv[0]);
18 if (error) { 14 if (error) {
19 return error; 15 return error;
20 } 16 }
21
22 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 17 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
23} 18}
24 19
25static struct cmd_results *parse_three_colors(char ***colors, const char *cmd_name, int argc, char **argv) { 20static struct cmd_results *parse_three_colors(char ***colors,
21 const char *cmd_name, int argc, char **argv) {
26 struct cmd_results *error = NULL; 22 struct cmd_results *error = NULL;
27 if (argc != 3) { 23 if (argc != 3) {
28 return cmd_results_new(CMD_INVALID, cmd_name, "Requires exactly three color values"); 24 return cmd_results_new(CMD_INVALID,
25 cmd_name, "Requires exactly three color values");
29 } 26 }
30 27 for (size_t i = 0; i < 3; i++) {
31 int i; 28 if (!*colors[i] && !(*(colors[i]) = malloc(10))) {
32 for (i = 0; i < 3; i++) { 29 return NULL;
33 if (!*colors[i]) {
34 *(colors[i]) = malloc(10);
35 if (!*(colors[i])) {
36 return cmd_results_new(CMD_FAILURE, cmd_name, "Unable to allocate color");
37 }
38 } 30 }
39 error = add_color(cmd_name, *(colors[i]), argv[i]); 31 error = add_color(cmd_name, *(colors[i]), argv[i]);
40 if (error) { 32 if (error) {
41 return error; 33 return error;
42 } 34 }
43 } 35 }
44
45 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 36 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
46} 37}
47 38
@@ -50,12 +41,10 @@ struct cmd_results *bar_cmd_colors(int argc, char **argv) {
50 if ((error = checkarg(argc, "colors", EXPECTED_EQUAL_TO, 1))) { 41 if ((error = checkarg(argc, "colors", EXPECTED_EQUAL_TO, 1))) {
51 return error; 42 return error;
52 } 43 }
53
54 if (strcmp("{", argv[0]) != 0) { 44 if (strcmp("{", argv[0]) != 0) {
55 return cmd_results_new(CMD_INVALID, "colors", 45 return cmd_results_new(CMD_INVALID, "colors",
56 "Expected '{' at the start of colors config definition."); 46 "Expected '{' at the start of colors config definition.");
57 } 47 }
58
59 return cmd_results_new(CMD_BLOCK_BAR_COLORS, NULL, NULL); 48 return cmd_results_new(CMD_BLOCK_BAR_COLORS, NULL, NULL);
60} 49}
61 50
@@ -69,11 +58,13 @@ struct cmd_results *bar_colors_cmd_active_workspace(int argc, char **argv) {
69} 58}
70 59
71struct cmd_results *bar_colors_cmd_background(int argc, char **argv) { 60struct cmd_results *bar_colors_cmd_background(int argc, char **argv) {
72 return parse_single_color(&(config->current_bar->colors.background), "background", argc, argv); 61 return parse_single_color(&(config->current_bar->colors.background),
62 "background", argc, argv);
73} 63}
74 64
75struct cmd_results *bar_colors_cmd_focused_background(int argc, char **argv) { 65struct cmd_results *bar_colors_cmd_focused_background(int argc, char **argv) {
76 return parse_single_color(&(config->current_bar->colors.focused_background), "focused_background", argc, argv); 66 return parse_single_color(&(config->current_bar->colors.focused_background),
67 "focused_background", argc, argv);
77} 68}
78 69
79struct cmd_results *bar_colors_cmd_binding_mode(int argc, char **argv) { 70struct cmd_results *bar_colors_cmd_binding_mode(int argc, char **argv) {
@@ -104,19 +95,23 @@ struct cmd_results *bar_colors_cmd_inactive_workspace(int argc, char **argv) {
104} 95}
105 96
106struct cmd_results *bar_colors_cmd_separator(int argc, char **argv) { 97struct cmd_results *bar_colors_cmd_separator(int argc, char **argv) {
107 return parse_single_color(&(config->current_bar->colors.separator), "separator", argc, argv); 98 return parse_single_color(&(config->current_bar->colors.separator),
99 "separator", argc, argv);
108} 100}
109 101
110struct cmd_results *bar_colors_cmd_focused_separator(int argc, char **argv) { 102struct cmd_results *bar_colors_cmd_focused_separator(int argc, char **argv) {
111 return parse_single_color(&(config->current_bar->colors.focused_separator), "focused_separator", argc, argv); 103 return parse_single_color(&(config->current_bar->colors.focused_separator),
104 "focused_separator", argc, argv);
112} 105}
113 106
114struct cmd_results *bar_colors_cmd_statusline(int argc, char **argv) { 107struct cmd_results *bar_colors_cmd_statusline(int argc, char **argv) {
115 return parse_single_color(&(config->current_bar->colors.statusline), "statusline", argc, argv); 108 return parse_single_color(&(config->current_bar->colors.statusline),
109 "statusline", argc, argv);
116} 110}
117 111
118struct cmd_results *bar_colors_cmd_focused_statusline(int argc, char **argv) { 112struct cmd_results *bar_colors_cmd_focused_statusline(int argc, char **argv) {
119 return parse_single_color(&(config->current_bar->colors.focused_separator), "focused_separator", argc, argv); 113 return parse_single_color(&(config->current_bar->colors.focused_separator),
114 "focused_separator", argc, argv);
120} 115}
121 116
122struct cmd_results *bar_colors_cmd_urgent_workspace(int argc, char **argv) { 117struct cmd_results *bar_colors_cmd_urgent_workspace(int argc, char **argv) {
diff --git a/sway/commands/bar/context_button.c b/sway/commands/bar/context_button.c
index 6d7d7aec..3b76885a 100644
--- a/sway/commands/bar/context_button.c
+++ b/sway/commands/bar/context_button.c
@@ -3,24 +3,6 @@
3#include "log.h" 3#include "log.h"
4 4
5struct cmd_results *bar_cmd_context_button(int argc, char **argv) { 5struct cmd_results *bar_cmd_context_button(int argc, char **argv) {
6 const char *cmd_name = "context_button"; 6 // TODO TRAY
7#ifndef ENABLE_TRAY 7 return cmd_results_new(CMD_INVALID, "context_button", "TODO TRAY");
8 return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command "
9 "%s called, but sway was compiled without tray support",
10 cmd_name, cmd_name);
11#else
12 struct cmd_results *error = NULL;
13 if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) {
14 return error;
15 }
16
17 if (!config->current_bar) {
18 return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined.");
19 }
20
21 // User should be able to prefix with 0x or whatever they want
22 config->current_bar->context_button = strtoul(argv[0], NULL, 0);
23
24 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
25#endif
26} 8}
diff --git a/sway/commands/bar/font.c b/sway/commands/bar/font.c
index c586c5bc..80b7a593 100644
--- a/sway/commands/bar/font.c
+++ b/sway/commands/bar/font.c
@@ -1,3 +1,4 @@
1#define _POSIX_C_SOURCE 200809L
1#include <string.h> 2#include <string.h>
2#include "sway/commands.h" 3#include "sway/commands.h"
3#include "log.h" 4#include "log.h"
@@ -8,19 +9,13 @@ struct cmd_results *bar_cmd_font(int argc, char **argv) {
8 if ((error = checkarg(argc, "font", EXPECTED_AT_LEAST, 1))) { 9 if ((error = checkarg(argc, "font", EXPECTED_AT_LEAST, 1))) {
9 return error; 10 return error;
10 } 11 }
11
12 if (!config->current_bar) { 12 if (!config->current_bar) {
13 return cmd_results_new(CMD_FAILURE, "font", "No bar defined."); 13 return cmd_results_new(CMD_FAILURE, "font", "No bar defined.");
14 } 14 }
15
16 char *font = join_args(argv, argc); 15 char *font = join_args(argv, argc);
17 free(config->current_bar->font); 16 free(config->current_bar->font);
18 if (strlen(font) > 6 && strncmp("pango:", font, 6) == 0) { 17 config->current_bar->font = strdup(font);
19 config->current_bar->font = font; 18 wlr_log(L_DEBUG, "Settings font '%s' for bar: %s",
20 } else { 19 config->current_bar->font, config->current_bar->id);
21 config->current_bar->font = font;
22 }
23
24 sway_log(L_DEBUG, "Settings font '%s' for bar: %s", config->current_bar->font, config->current_bar->id);
25 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 20 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
26} 21}
diff --git a/sway/commands/bar/height.c b/sway/commands/bar/height.c
index eb576ab3..3160caed 100644
--- a/sway/commands/bar/height.c
+++ b/sway/commands/bar/height.c
@@ -8,14 +8,13 @@ struct cmd_results *bar_cmd_height(int argc, char **argv) {
8 if ((error = checkarg(argc, "height", EXPECTED_EQUAL_TO, 1))) { 8 if ((error = checkarg(argc, "height", EXPECTED_EQUAL_TO, 1))) {
9 return error; 9 return error;
10 } 10 }
11
12 int height = atoi(argv[0]); 11 int height = atoi(argv[0]);
13 if (height < 0) { 12 if (height < 0) {
14 return cmd_results_new(CMD_INVALID, "height", 13 return cmd_results_new(CMD_INVALID, "height",
15 "Invalid height value: %s", argv[0]); 14 "Invalid height value: %s", argv[0]);
16 } 15 }
17
18 config->current_bar->height = height; 16 config->current_bar->height = height;
19 sway_log(L_DEBUG, "Setting bar height to %d on bar: %s", height, config->current_bar->id); 17 wlr_log(L_DEBUG, "Setting bar height to %d on bar: %s",
18 height, config->current_bar->id);
20 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 19 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
21} 20}
diff --git a/sway/commands/bar/hidden_state.c b/sway/commands/bar/hidden_state.c
index 0b49aa6b..6641f184 100644
--- a/sway/commands/bar/hidden_state.c
+++ b/sway/commands/bar/hidden_state.c
@@ -6,7 +6,8 @@
6#include "sway/ipc-server.h" 6#include "sway/ipc-server.h"
7#include "log.h" 7#include "log.h"
8 8
9static struct cmd_results *bar_set_hidden_state(struct bar_config *bar, const char *hidden_state) { 9static struct cmd_results *bar_set_hidden_state(struct bar_config *bar,
10 const char *hidden_state) {
10 char *old_state = bar->hidden_state; 11 char *old_state = bar->hidden_state;
11 if (strcasecmp("toggle", hidden_state) == 0 && !config->reading) { 12 if (strcasecmp("toggle", hidden_state) == 0 && !config->reading) {
12 if (strcasecmp("hide", bar->hidden_state) == 0) { 13 if (strcasecmp("hide", bar->hidden_state) == 0) {
@@ -19,16 +20,16 @@ static struct cmd_results *bar_set_hidden_state(struct bar_config *bar, const ch
19 } else if (strcasecmp("show", hidden_state) == 0) { 20 } else if (strcasecmp("show", hidden_state) == 0) {
20 bar->hidden_state = strdup("show"); 21 bar->hidden_state = strdup("show");
21 } else { 22 } else {
22 return cmd_results_new(CMD_INVALID, "hidden_state", "Invalid value %s", hidden_state); 23 return cmd_results_new(CMD_INVALID, "hidden_state",
24 "Invalid value %s", hidden_state);
23 } 25 }
24
25 if (strcmp(old_state, bar->hidden_state) != 0) { 26 if (strcmp(old_state, bar->hidden_state) != 0) {
26 if (!config->reading) { 27 if (!config->reading) {
27 ipc_event_barconfig_update(bar); 28 ipc_event_barconfig_update(bar);
28 } 29 }
29 sway_log(L_DEBUG, "Setting hidden_state: '%s' for bar: %s", bar->hidden_state, bar->id); 30 wlr_log(L_DEBUG, "Setting hidden_state: '%s' for bar: %s",
31 bar->hidden_state, bar->id);
30 } 32 }
31
32 // free old mode 33 // free old mode
33 free(old_state); 34 free(old_state);
34 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 35 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
@@ -42,13 +43,12 @@ struct cmd_results *bar_cmd_hidden_state(int argc, char **argv) {
42 if ((error = checkarg(argc, "hidden_state", EXPECTED_LESS_THAN, 3))) { 43 if ((error = checkarg(argc, "hidden_state", EXPECTED_LESS_THAN, 3))) {
43 return error; 44 return error;
44 } 45 }
45
46 if (config->reading && argc > 1) { 46 if (config->reading && argc > 1) {
47 return cmd_results_new(CMD_INVALID, "hidden_state", "Unexpected value %s in config mode", argv[1]); 47 return cmd_results_new(CMD_INVALID, "hidden_state",
48 "Unexpected value %s in config mode", argv[1]);
48 } 49 }
49 50
50 const char *state = argv[0]; 51 const char *state = argv[0];
51
52 if (config->reading) { 52 if (config->reading) {
53 return bar_set_hidden_state(config->current_bar, state); 53 return bar_set_hidden_state(config->current_bar, state);
54 } 54 }
@@ -57,10 +57,8 @@ struct cmd_results *bar_cmd_hidden_state(int argc, char **argv) {
57 if (argc == 2) { 57 if (argc == 2) {
58 id = argv[1]; 58 id = argv[1];
59 } 59 }
60
61 int i;
62 struct bar_config *bar; 60 struct bar_config *bar;
63 for (i = 0; i < config->bars->length; ++i) { 61 for (int i = 0; i < config->bars->length; ++i) {
64 bar = config->bars->items[i]; 62 bar = config->bars->items[i];
65 if (id && strcmp(id, bar->id) == 0) { 63 if (id && strcmp(id, bar->id) == 0) {
66 return bar_set_hidden_state(bar, state); 64 return bar_set_hidden_state(bar, state);
@@ -71,9 +69,5 @@ struct cmd_results *bar_cmd_hidden_state(int argc, char **argv) {
71 return error; 69 return error;
72 } 70 }
73 } 71 }
74
75 // active bar modifiers might have changed.
76 update_active_bar_modifiers();
77
78 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 72 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
79} 73}
diff --git a/sway/commands/bar/icon_theme.c b/sway/commands/bar/icon_theme.c
index cbfc0be5..44cd3076 100644
--- a/sway/commands/bar/icon_theme.c
+++ b/sway/commands/bar/icon_theme.c
@@ -3,23 +3,6 @@
3#include "sway/commands.h" 3#include "sway/commands.h"
4 4
5struct cmd_results *bar_cmd_icon_theme(int argc, char **argv) { 5struct cmd_results *bar_cmd_icon_theme(int argc, char **argv) {
6 const char *cmd_name = "tray_output"; 6 // TODO TRAY
7#ifndef ENABLE_TRAY 7 return cmd_results_new(CMD_INVALID, "icon_theme", "TODO TRAY");
8 return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command "
9 "%s called, but sway was compiled without tray support",
10 cmd_name, cmd_name);
11#else
12 struct cmd_results *error = NULL;
13 if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) {
14 return error;
15 }
16
17 if (!config->current_bar) {
18 return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined.");
19 }
20
21 config->current_bar->icon_theme = strdup(argv[0]);
22
23 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
24#endif
25} 8}
diff --git a/sway/commands/bar/id.c b/sway/commands/bar/id.c
index 1221ebf6..c1e56f03 100644
--- a/sway/commands/bar/id.c
+++ b/sway/commands/bar/id.c
@@ -11,10 +11,8 @@ struct cmd_results *bar_cmd_id(int argc, char **argv) {
11 11
12 const char *name = argv[0]; 12 const char *name = argv[0];
13 const char *oldname = config->current_bar->id; 13 const char *oldname = config->current_bar->id;
14
15 // check if id is used by a previously defined bar 14 // check if id is used by a previously defined bar
16 int i; 15 for (int i = 0; i < config->bars->length; ++i) {
17 for (i = 0; i < config->bars->length; ++i) {
18 struct bar_config *find = config->bars->items[i]; 16 struct bar_config *find = config->bars->items[i];
19 if (strcmp(name, find->id) == 0 && config->current_bar != find) { 17 if (strcmp(name, find->id) == 0 && config->current_bar != find) {
20 return cmd_results_new(CMD_FAILURE, "id", 18 return cmd_results_new(CMD_FAILURE, "id",
@@ -23,11 +21,10 @@ struct cmd_results *bar_cmd_id(int argc, char **argv) {
23 } 21 }
24 } 22 }
25 23
26 sway_log(L_DEBUG, "Renaming bar: '%s' to '%s'", oldname, name); 24 wlr_log(L_DEBUG, "Renaming bar: '%s' to '%s'", oldname, name);
27 25
28 // free old bar id 26 // free old bar id
29 free(config->current_bar->id); 27 free(config->current_bar->id);
30
31 config->current_bar->id = strdup(name); 28 config->current_bar->id = strdup(name);
32 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 29 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
33} 30}
diff --git a/sway/commands/bar/mode.c b/sway/commands/bar/mode.c
index 36816b93..34bb0a4f 100644
--- a/sway/commands/bar/mode.c
+++ b/sway/commands/bar/mode.c
@@ -27,11 +27,8 @@ static struct cmd_results *bar_set_mode(struct bar_config *bar, const char *mode
27 if (strcmp(old_mode, bar->mode) != 0) { 27 if (strcmp(old_mode, bar->mode) != 0) {
28 if (!config->reading) { 28 if (!config->reading) {
29 ipc_event_barconfig_update(bar); 29 ipc_event_barconfig_update(bar);
30
31 // active bar modifiers might have changed.
32 update_active_bar_modifiers();
33 } 30 }
34 sway_log(L_DEBUG, "Setting mode: '%s' for bar: %s", bar->mode, bar->id); 31 wlr_log(L_DEBUG, "Setting mode: '%s' for bar: %s", bar->mode, bar->id);
35 } 32 }
36 33
37 // free old mode 34 // free old mode
@@ -47,13 +44,12 @@ struct cmd_results *bar_cmd_mode(int argc, char **argv) {
47 if ((error = checkarg(argc, "mode", EXPECTED_LESS_THAN, 3))) { 44 if ((error = checkarg(argc, "mode", EXPECTED_LESS_THAN, 3))) {
48 return error; 45 return error;
49 } 46 }
50
51 if (config->reading && argc > 1) { 47 if (config->reading && argc > 1) {
52 return cmd_results_new(CMD_INVALID, "mode", "Unexpected value %s in config mode", argv[1]); 48 return cmd_results_new(CMD_INVALID,
49 "mode", "Unexpected value %s in config mode", argv[1]);
53 } 50 }
54 51
55 const char *mode = argv[0]; 52 const char *mode = argv[0];
56
57 if (config->reading) { 53 if (config->reading) {
58 return bar_set_mode(config->current_bar, mode); 54 return bar_set_mode(config->current_bar, mode);
59 } 55 }
@@ -63,19 +59,16 @@ struct cmd_results *bar_cmd_mode(int argc, char **argv) {
63 id = argv[1]; 59 id = argv[1];
64 } 60 }
65 61
66 int i;
67 struct bar_config *bar; 62 struct bar_config *bar;
68 for (i = 0; i < config->bars->length; ++i) { 63 for (int i = 0; i < config->bars->length; ++i) {
69 bar = config->bars->items[i]; 64 bar = config->bars->items[i];
70 if (id && strcmp(id, bar->id) == 0) { 65 if (id && strcmp(id, bar->id) == 0) {
71 return bar_set_mode(bar, mode); 66 return bar_set_mode(bar, mode);
72 } 67 }
73
74 error = bar_set_mode(bar, mode); 68 error = bar_set_mode(bar, mode);
75 if (error) { 69 if (error) {
76 return error; 70 return error;
77 } 71 }
78 } 72 }
79
80 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 73 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
81} 74}
diff --git a/sway/commands/bar/modifier.c b/sway/commands/bar/modifier.c
index 153d87e6..7ba4b125 100644
--- a/sway/commands/bar/modifier.c
+++ b/sway/commands/bar/modifier.c
@@ -15,7 +15,6 @@ struct cmd_results *bar_cmd_modifier(int argc, char **argv) {
15 } 15 }
16 16
17 uint32_t mod = 0; 17 uint32_t mod = 0;
18
19 list_t *split = split_string(argv[0], "+"); 18 list_t *split = split_string(argv[0], "+");
20 for (int i = 0; i < split->length; ++i) { 19 for (int i = 0; i < split->length; ++i) {
21 uint32_t tmp_mod; 20 uint32_t tmp_mod;
@@ -24,12 +23,13 @@ struct cmd_results *bar_cmd_modifier(int argc, char **argv) {
24 continue; 23 continue;
25 } else { 24 } else {
26 free_flat_list(split); 25 free_flat_list(split);
27 return cmd_results_new(CMD_INVALID, "modifier", "Unknown modifier '%s'", split->items[i]); 26 return cmd_results_new(CMD_INVALID, "modifier",
27 "Unknown modifier '%s'", split->items[i]);
28 } 28 }
29 } 29 }
30 free_flat_list(split); 30 free_flat_list(split);
31
32 config->current_bar->modifier = mod; 31 config->current_bar->modifier = mod;
33 sway_log(L_DEBUG, "Show/Hide the bar when pressing '%s' in hide mode.", argv[0]); 32 wlr_log(L_DEBUG,
33 "Show/Hide the bar when pressing '%s' in hide mode.", argv[0]);
34 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 34 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
35} 35}
diff --git a/sway/commands/bar/output.c b/sway/commands/bar/output.c
index a5710bc0..f7ca0aa4 100644
--- a/sway/commands/bar/output.c
+++ b/sway/commands/bar/output.c
@@ -1,4 +1,5 @@
1#define _XOPEN_SOURCE 500 1#define _XOPEN_SOURCE 500
2#include <stdbool.h>
2#include <string.h> 3#include <string.h>
3#include "sway/commands.h" 4#include "sway/commands.h"
4#include "list.h" 5#include "list.h"
@@ -9,7 +10,6 @@ struct cmd_results *bar_cmd_output(int argc, char **argv) {
9 if ((error = checkarg(argc, "output", EXPECTED_EQUAL_TO, 1))) { 10 if ((error = checkarg(argc, "output", EXPECTED_EQUAL_TO, 1))) {
10 return error; 11 return error;
11 } 12 }
12
13 if (!config->current_bar) { 13 if (!config->current_bar) {
14 return cmd_results_new(CMD_FAILURE, "output", "No bar defined."); 14 return cmd_results_new(CMD_FAILURE, "output", "No bar defined.");
15 } 15 }
@@ -21,21 +21,20 @@ struct cmd_results *bar_cmd_output(int argc, char **argv) {
21 config->current_bar->outputs = outputs; 21 config->current_bar->outputs = outputs;
22 } 22 }
23 23
24 int i; 24 bool add_output = true;
25 int add_output = 1;
26 if (strcmp("*", output) == 0) { 25 if (strcmp("*", output) == 0) {
27 // remove all previous defined outputs and replace with '*' 26 // remove all previous defined outputs and replace with '*'
28 for (i = 0; i < outputs->length; ++i) { 27 for (int i = 0; i < outputs->length; ++i) {
29 free(outputs->items[i]); 28 free(outputs->items[i]);
30 list_del(outputs, i); 29 list_del(outputs, i);
31 } 30 }
32 } else { 31 } else {
33 // only add output if not already defined with either the same 32 // only add output if not already defined with either the same
34 // name or as '*' 33 // name or as '*'
35 for (i = 0; i < outputs->length; ++i) { 34 for (int i = 0; i < outputs->length; ++i) {
36 const char *find = outputs->items[i]; 35 const char *find = outputs->items[i];
37 if (strcmp("*", find) == 0 || strcmp(output, find) == 0) { 36 if (strcmp("*", find) == 0 || strcmp(output, find) == 0) {
38 add_output = 0; 37 add_output = false;
39 break; 38 break;
40 } 39 }
41 } 40 }
@@ -43,8 +42,8 @@ struct cmd_results *bar_cmd_output(int argc, char **argv) {
43 42
44 if (add_output) { 43 if (add_output) {
45 list_add(outputs, strdup(output)); 44 list_add(outputs, strdup(output));
46 sway_log(L_DEBUG, "Adding bar: '%s' to output '%s'", config->current_bar->id, output); 45 wlr_log(L_DEBUG, "Adding bar: '%s' to output '%s'",
46 config->current_bar->id, output);
47 } 47 }
48
49 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 48 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
50} 49}
diff --git a/sway/commands/bar/pango_markup.c b/sway/commands/bar/pango_markup.c
index f69e882f..480af724 100644
--- a/sway/commands/bar/pango_markup.c
+++ b/sway/commands/bar/pango_markup.c
@@ -8,19 +8,20 @@ struct cmd_results *bar_cmd_pango_markup(int argc, char **argv) {
8 if ((error = checkarg(argc, "pango_markup", EXPECTED_EQUAL_TO, 1))) { 8 if ((error = checkarg(argc, "pango_markup", EXPECTED_EQUAL_TO, 1))) {
9 return error; 9 return error;
10 } 10 }
11
12 if (!config->current_bar) { 11 if (!config->current_bar) {
13 return cmd_results_new(CMD_FAILURE, "pango_markup", "No bar defined."); 12 return cmd_results_new(CMD_FAILURE, "pango_markup", "No bar defined.");
14 } 13 }
15
16 if (strcasecmp("enabled", argv[0]) == 0) { 14 if (strcasecmp("enabled", argv[0]) == 0) {
17 config->current_bar->pango_markup = true; 15 config->current_bar->pango_markup = true;
18 sway_log(L_DEBUG, "Enabling pango markup for bar: %s", config->current_bar->id); 16 wlr_log(L_DEBUG, "Enabling pango markup for bar: %s",
17 config->current_bar->id);
19 } else if (strcasecmp("disabled", argv[0]) == 0) { 18 } else if (strcasecmp("disabled", argv[0]) == 0) {
20 config->current_bar->pango_markup = false; 19 config->current_bar->pango_markup = false;
21 sway_log(L_DEBUG, "Disabling pango markup for bar: %s", config->current_bar->id); 20 wlr_log(L_DEBUG, "Disabling pango markup for bar: %s",
21 config->current_bar->id);
22 } else { 22 } else {
23 error = cmd_results_new(CMD_INVALID, "pango_markup", "Invalid value %s", argv[0]); 23 error = cmd_results_new(CMD_INVALID, "pango_markup",
24 "Invalid value %s", argv[0]);
24 return error; 25 return error;
25 } 26 }
26 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 27 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
diff --git a/sway/commands/bar/position.c b/sway/commands/bar/position.c
index 50de58e2..9c580483 100644
--- a/sway/commands/bar/position.c
+++ b/sway/commands/bar/position.c
@@ -1,3 +1,4 @@
1#define _POSIX_C_SOURCE 200809L
1#include <string.h> 2#include <string.h>
2#include <strings.h> 3#include <strings.h>
3#include "sway/commands.h" 4#include "sway/commands.h"
@@ -8,26 +9,18 @@ struct cmd_results *bar_cmd_position(int argc, char **argv) {
8 if ((error = checkarg(argc, "position", EXPECTED_EQUAL_TO, 1))) { 9 if ((error = checkarg(argc, "position", EXPECTED_EQUAL_TO, 1))) {
9 return error; 10 return error;
10 } 11 }
11
12 if (!config->current_bar) { 12 if (!config->current_bar) {
13 return cmd_results_new(CMD_FAILURE, "position", "No bar defined."); 13 return cmd_results_new(CMD_FAILURE, "position", "No bar defined.");
14 } 14 }
15 15 char *valid[] = { "top", "bottom", "left", "right" };
16 if (strcasecmp("top", argv[0]) == 0) { 16 for (size_t i = 0; i < sizeof(valid) / sizeof(valid[0]); ++i) {
17 config->current_bar->position = DESKTOP_SHELL_PANEL_POSITION_TOP; 17 if (strcasecmp(valid[i], argv[0]) == 0) {
18 } else if (strcasecmp("bottom", argv[0]) == 0) { 18 wlr_log(L_DEBUG, "Setting bar position '%s' for bar: %s",
19 config->current_bar->position = DESKTOP_SHELL_PANEL_POSITION_BOTTOM; 19 argv[0], config->current_bar->id);
20 } else if (strcasecmp("left", argv[0]) == 0) { 20 config->current_bar->position = strdup(argv[0]);
21 sway_log(L_INFO, "Warning: swaybar currently only supports top and bottom positioning. YMMV"); 21 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
22 config->current_bar->position = DESKTOP_SHELL_PANEL_POSITION_LEFT; 22 }
23 } else if (strcasecmp("right", argv[0]) == 0) {
24 sway_log(L_INFO, "Warning: swaybar currently only supports top and bottom positioning. YMMV");
25 config->current_bar->position = DESKTOP_SHELL_PANEL_POSITION_RIGHT;
26 } else {
27 error = cmd_results_new(CMD_INVALID, "position", "Invalid value %s", argv[0]);
28 return error;
29 } 23 }
30 24 return cmd_results_new(CMD_INVALID,
31 sway_log(L_DEBUG, "Setting bar position '%s' for bar: %s", argv[0], config->current_bar->id); 25 "position", "Invalid value %s", argv[0]);
32 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
33} 26}
diff --git a/sway/commands/bar/secondary_button.c b/sway/commands/bar/secondary_button.c
index 745045c5..449124cb 100644
--- a/sway/commands/bar/secondary_button.c
+++ b/sway/commands/bar/secondary_button.c
@@ -3,24 +3,6 @@
3#include "log.h" 3#include "log.h"
4 4
5struct cmd_results *bar_cmd_secondary_button(int argc, char **argv) { 5struct cmd_results *bar_cmd_secondary_button(int argc, char **argv) {
6 const char *cmd_name = "secondary_button"; 6 // TODO TRAY
7#ifndef ENABLE_TRAY 7 return cmd_results_new(CMD_INVALID, "secondary_button", "TODO TRAY");
8 return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command "
9 "%s called, but sway was compiled without tray support",
10 cmd_name, cmd_name);
11#else
12 struct cmd_results *error = NULL;
13 if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) {
14 return error;
15 }
16
17 if (!config->current_bar) {
18 return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined.");
19 }
20
21 // User should be able to prefix with 0x or whatever they want
22 config->current_bar->secondary_button = strtoul(argv[0], NULL, 0);
23
24 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
25#endif
26} 8}
diff --git a/sway/commands/bar/separator_symbol.c b/sway/commands/bar/separator_symbol.c
index 2766d8a2..1e08df6d 100644
--- a/sway/commands/bar/separator_symbol.c
+++ b/sway/commands/bar/separator_symbol.c
@@ -8,14 +8,13 @@ struct cmd_results *bar_cmd_separator_symbol(int argc, char **argv) {
8 if ((error = checkarg(argc, "separator_symbol", EXPECTED_EQUAL_TO, 1))) { 8 if ((error = checkarg(argc, "separator_symbol", EXPECTED_EQUAL_TO, 1))) {
9 return error; 9 return error;
10 } 10 }
11
12 if (!config->current_bar) { 11 if (!config->current_bar) {
13 return cmd_results_new(CMD_FAILURE, "separator_symbol", "No bar defined."); 12 return cmd_results_new(CMD_FAILURE,
13 "separator_symbol", "No bar defined.");
14 } 14 }
15
16 free(config->current_bar->separator_symbol); 15 free(config->current_bar->separator_symbol);
17 config->current_bar->separator_symbol = strdup(argv[0]); 16 config->current_bar->separator_symbol = strdup(argv[0]);
18 sway_log(L_DEBUG, "Settings separator_symbol '%s' for bar: %s", config->current_bar->separator_symbol, config->current_bar->id); 17 wlr_log(L_DEBUG, "Settings separator_symbol '%s' for bar: %s",
19 18 config->current_bar->separator_symbol, config->current_bar->id);
20 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 19 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
21} 20}
diff --git a/sway/commands/bar/status_command.c b/sway/commands/bar/status_command.c
index b227ac47..5e199cde 100644
--- a/sway/commands/bar/status_command.c
+++ b/sway/commands/bar/status_command.c
@@ -8,14 +8,13 @@ struct cmd_results *bar_cmd_status_command(int argc, char **argv) {
8 if ((error = checkarg(argc, "status_command", EXPECTED_AT_LEAST, 1))) { 8 if ((error = checkarg(argc, "status_command", EXPECTED_AT_LEAST, 1))) {
9 return error; 9 return error;
10 } 10 }
11
12 if (!config->current_bar) { 11 if (!config->current_bar) {
13 return cmd_results_new(CMD_FAILURE, "status_command", "No bar defined."); 12 return cmd_results_new(CMD_FAILURE,
13 "status_command", "No bar defined.");
14 } 14 }
15
16 free(config->current_bar->status_command); 15 free(config->current_bar->status_command);
17 config->current_bar->status_command = join_args(argv, argc); 16 config->current_bar->status_command = join_args(argv, argc);
18 sway_log(L_DEBUG, "Feeding bar with status command: %s", config->current_bar->status_command); 17 wlr_log(L_DEBUG, "Feeding bar with status command: %s",
19 18 config->current_bar->status_command);
20 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 19 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
21} 20}
diff --git a/sway/commands/bar/strip_workspace_numbers.c b/sway/commands/bar/strip_workspace_numbers.c
index 9ac32482..4f24a356 100644
--- a/sway/commands/bar/strip_workspace_numbers.c
+++ b/sway/commands/bar/strip_workspace_numbers.c
@@ -5,23 +5,25 @@
5 5
6struct cmd_results *bar_cmd_strip_workspace_numbers(int argc, char **argv) { 6struct cmd_results *bar_cmd_strip_workspace_numbers(int argc, char **argv) {
7 struct cmd_results *error = NULL; 7 struct cmd_results *error = NULL;
8 if ((error = checkarg(argc, "strip_workspace_numbers", EXPECTED_EQUAL_TO, 1))) { 8 if ((error = checkarg(argc,
9 "strip_workspace_numbers", EXPECTED_EQUAL_TO, 1))) {
9 return error; 10 return error;
10 } 11 }
11
12 if (!config->current_bar) { 12 if (!config->current_bar) {
13 return cmd_results_new(CMD_FAILURE, "strip_workspace_numbers", "No bar defined."); 13 return cmd_results_new(CMD_FAILURE,
14 "strip_workspace_numbers", "No bar defined.");
14 } 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 sway_log(L_DEBUG, "Stripping workspace numbers on bar: %s", config->current_bar->id); 18 wlr_log(L_DEBUG, "Stripping workspace numbers on bar: %s",
19 config->current_bar->id);
19 } else if (strcasecmp("no", argv[0]) == 0) { 20 } else if (strcasecmp("no", argv[0]) == 0) {
20 config->current_bar->strip_workspace_numbers = false; 21 config->current_bar->strip_workspace_numbers = false;
21 sway_log(L_DEBUG, "Enabling workspace numbers on bar: %s", config->current_bar->id); 22 wlr_log(L_DEBUG, "Enabling workspace numbers on bar: %s",
23 config->current_bar->id);
22 } else { 24 } else {
23 error = cmd_results_new(CMD_INVALID, "strip_workspace_numbers", "Invalid value %s", argv[0]); 25 return cmd_results_new(CMD_INVALID,
24 return error; 26 "strip_workspace_numbers", "Invalid value %s", argv[0]);
25 } 27 }
26 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 28 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
27} 29}
diff --git a/sway/commands/bar/swaybar_command.c b/sway/commands/bar/swaybar_command.c
index 452e2df5..520cdd11 100644
--- a/sway/commands/bar/swaybar_command.c
+++ b/sway/commands/bar/swaybar_command.c
@@ -8,14 +8,13 @@ struct cmd_results *bar_cmd_swaybar_command(int argc, char **argv) {
8 if ((error = checkarg(argc, "swaybar_command", EXPECTED_AT_LEAST, 1))) { 8 if ((error = checkarg(argc, "swaybar_command", EXPECTED_AT_LEAST, 1))) {
9 return error; 9 return error;
10 } 10 }
11
12 if (!config->current_bar) { 11 if (!config->current_bar) {
13 return cmd_results_new(CMD_FAILURE, "swaybar_command", "No bar defined."); 12 return cmd_results_new(CMD_FAILURE,
13 "swaybar_command", "No bar defined.");
14 } 14 }
15
16 free(config->current_bar->swaybar_command); 15 free(config->current_bar->swaybar_command);
17 config->current_bar->swaybar_command = join_args(argv, argc); 16 config->current_bar->swaybar_command = join_args(argv, argc);
18 sway_log(L_DEBUG, "Using custom swaybar command: %s", config->current_bar->swaybar_command); 17 wlr_log(L_DEBUG, "Using custom swaybar command: %s",
19 18 config->current_bar->swaybar_command);
20 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 19 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
21} 20}
diff --git a/sway/commands/bar/tray_output.c b/sway/commands/bar/tray_output.c
index 012304a9..6ab16731 100644
--- a/sway/commands/bar/tray_output.c
+++ b/sway/commands/bar/tray_output.c
@@ -3,27 +3,6 @@
3#include "sway/commands.h" 3#include "sway/commands.h"
4 4
5struct cmd_results *bar_cmd_tray_output(int argc, char **argv) { 5struct cmd_results *bar_cmd_tray_output(int argc, char **argv) {
6 const char *cmd_name = "tray_output"; 6 // TODO TRAY
7#ifndef ENABLE_TRAY 7 return cmd_results_new(CMD_INVALID, "tray_output", "TODO TRAY");
8 return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command "
9 "%s called, but sway was compiled without tray support",
10 cmd_name, cmd_name);
11#else
12 struct cmd_results *error = NULL;
13 if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) {
14 return error;
15 }
16
17 if (!config->current_bar) {
18 return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined.");
19 }
20
21 if (strcmp(argv[0], "all") == 0) {
22 // Default behaviour
23 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
24 }
25 config->current_bar->tray_output = strdup(argv[0]);
26
27 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
28#endif
29} 8}
diff --git a/sway/commands/bar/tray_padding.c b/sway/commands/bar/tray_padding.c
index ac0572ce..91c56f19 100644
--- a/sway/commands/bar/tray_padding.c
+++ b/sway/commands/bar/tray_padding.c
@@ -4,31 +4,6 @@
4#include "log.h" 4#include "log.h"
5 5
6struct cmd_results *bar_cmd_tray_padding(int argc, char **argv) { 6struct cmd_results *bar_cmd_tray_padding(int argc, char **argv) {
7 const char *cmd_name = "tray_padding"; 7 // TODO TRAY
8#ifndef ENABLE_TRAY 8 return cmd_results_new(CMD_INVALID, "tray_padding", "TODO TRAY");
9 return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command"
10 "%s called, but sway was compiled without tray support",
11 cmd_name, cmd_name);
12#else
13 struct cmd_results *error = NULL;
14 if ((error = checkarg(argc, cmd_name, EXPECTED_AT_LEAST, 1))) {
15 return error;
16 }
17
18 if (!config->current_bar) {
19 return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined.");
20 }
21
22 if (argc == 1 || (argc == 2 && strcasecmp("px", argv[1]) == 0)) {
23 char *inv;
24 uint32_t padding = strtoul(argv[0], &inv, 10);
25 if (*inv == '\0' || strcasecmp(inv, "px") == 0) {
26 config->current_bar->tray_padding = padding;
27 sway_log(L_DEBUG, "Enabling tray padding of %d px on bar: %s", padding, config->current_bar->id);
28 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
29 }
30 }
31 return cmd_results_new(CMD_FAILURE, cmd_name,
32 "Expected 'tray_padding <padding>[px]'");
33#endif
34} 9}
diff --git a/sway/commands/bar/workspace_buttons.c b/sway/commands/bar/workspace_buttons.c
index 67dd2d31..6edc3a0d 100644
--- a/sway/commands/bar/workspace_buttons.c
+++ b/sway/commands/bar/workspace_buttons.c
@@ -8,20 +8,21 @@ struct cmd_results *bar_cmd_workspace_buttons(int argc, char **argv) {
8 if ((error = checkarg(argc, "workspace_buttons", EXPECTED_EQUAL_TO, 1))) { 8 if ((error = checkarg(argc, "workspace_buttons", EXPECTED_EQUAL_TO, 1))) {
9 return error; 9 return error;
10 } 10 }
11
12 if (!config->current_bar) { 11 if (!config->current_bar) {
13 return cmd_results_new(CMD_FAILURE, "workspace_buttons", "No bar defined."); 12 return cmd_results_new(CMD_FAILURE,
13 "workspace_buttons", "No bar defined.");
14 } 14 }
15
16 if (strcasecmp("yes", argv[0]) == 0) { 15 if (strcasecmp("yes", argv[0]) == 0) {
17 config->current_bar->workspace_buttons = true; 16 config->current_bar->workspace_buttons = true;
18 sway_log(L_DEBUG, "Enabling workspace buttons on bar: %s", config->current_bar->id); 17 wlr_log(L_DEBUG, "Enabling workspace buttons on bar: %s",
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 sway_log(L_DEBUG, "Disabling workspace buttons on bar: %s", config->current_bar->id); 21 wlr_log(L_DEBUG, "Disabling workspace buttons on bar: %s",
22 config->current_bar->id);
22 } else { 23 } else {
23 error = cmd_results_new(CMD_INVALID, "workspace_buttons", "Invalid value %s", argv[0]); 24 return cmd_results_new(CMD_INVALID, "workspace_buttons",
24 return error; 25 "Invalid value %s", argv[0]);
25 } 26 }
26 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 27 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
27} 28}
diff --git a/sway/commands/bar/wrap_scroll.c b/sway/commands/bar/wrap_scroll.c
index 4ed1f12a..7386f82c 100644
--- a/sway/commands/bar/wrap_scroll.c
+++ b/sway/commands/bar/wrap_scroll.c
@@ -8,20 +8,20 @@ struct cmd_results *bar_cmd_wrap_scroll(int argc, char **argv) {
8 if ((error = checkarg(argc, "wrap_scroll", EXPECTED_EQUAL_TO, 1))) { 8 if ((error = checkarg(argc, "wrap_scroll", EXPECTED_EQUAL_TO, 1))) {
9 return error; 9 return error;
10 } 10 }
11
12 if (!config->current_bar) { 11 if (!config->current_bar) {
13 return cmd_results_new(CMD_FAILURE, "wrap_scroll", "No bar defined."); 12 return cmd_results_new(CMD_FAILURE, "wrap_scroll", "No bar defined.");
14 } 13 }
15
16 if (strcasecmp("yes", argv[0]) == 0) { 14 if (strcasecmp("yes", argv[0]) == 0) {
17 config->current_bar->wrap_scroll = true; 15 config->current_bar->wrap_scroll = true;
18 sway_log(L_DEBUG, "Enabling wrap scroll on bar: %s", config->current_bar->id); 16 wlr_log(L_DEBUG, "Enabling wrap scroll on bar: %s",
17 config->current_bar->id);
19 } else if (strcasecmp("no", argv[0]) == 0) { 18 } else if (strcasecmp("no", argv[0]) == 0) {
20 config->current_bar->wrap_scroll = false; 19 config->current_bar->wrap_scroll = false;
21 sway_log(L_DEBUG, "Disabling wrap scroll on bar: %s", config->current_bar->id); 20 wlr_log(L_DEBUG, "Disabling wrap scroll on bar: %s",
21 config->current_bar->id);
22 } else { 22 } else {
23 error = cmd_results_new(CMD_INVALID, "wrap_scroll", "Invalid value %s", argv[0]); 23 return cmd_results_new(CMD_INVALID,
24 return error; 24 "wrap_scroll", "Invalid value %s", argv[0]);
25 } 25 }
26 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 26 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
27} 27}
diff --git a/sway/commands/bind.c b/sway/commands/bind.c
index d9ea37b7..cbabb07b 100644
--- a/sway/commands/bind.c
+++ b/sway/commands/bind.c
@@ -1,9 +1,13 @@
1#ifdef __linux__
2#include <linux/input-event-codes.h>
3#elif __FreeBSD__
4#include <dev/evdev/input-event-codes.h>
5#endif
1#include <xkbcommon/xkbcommon.h> 6#include <xkbcommon/xkbcommon.h>
2#include <xkbcommon/xkbcommon-names.h> 7#include <xkbcommon/xkbcommon-names.h>
3#include <strings.h> 8#include <strings.h>
4#include "sway/commands.h" 9#include "sway/commands.h"
5#include "sway/config.h" 10#include "sway/config.h"
6#include "sway/input_state.h"
7#include "list.h" 11#include "list.h"
8#include "log.h" 12#include "log.h"
9#include "stringop.h" 13#include "stringop.h"
@@ -11,13 +15,67 @@
11 15
12int binding_order = 0; 16int binding_order = 0;
13 17
18void free_sway_binding(struct sway_binding *binding) {
19 if (!binding) {
20 return;
21 }
22
23 if (binding->keys) {
24 free_flat_list(binding->keys);
25 }
26 free(binding->command);
27 free(binding);
28}
29
30/**
31 * Returns true if the bindings have the same key and modifier combinations.
32 * Note that keyboard layout is not considered, so the bindings might actually
33 * not be equivalent on some layouts.
34 */
35bool binding_key_compare(struct sway_binding *binding_a,
36 struct sway_binding *binding_b) {
37 if (binding_a->release != binding_b->release) {
38 return false;
39 }
40
41 if (binding_a->bindcode != binding_b->bindcode) {
42 return false;
43 }
44
45 if (binding_a->modifiers ^ binding_b->modifiers) {
46 return false;
47 }
48
49 if (binding_a->keys->length != binding_b->keys->length) {
50 return false;
51 }
52
53 int keys_len = binding_a->keys->length;
54 for (int i = 0; i < keys_len; ++i) {
55 uint32_t key_a = *(uint32_t*)binding_a->keys->items[i];
56 bool found = false;
57 for (int j = 0; j < keys_len; ++j) {
58 uint32_t key_b = *(uint32_t*)binding_b->keys->items[j];
59 if (key_b == key_a) {
60 found = true;
61 break;
62 }
63 }
64 if (!found) {
65 return false;
66 }
67 }
68
69 return true;
70}
71
14struct cmd_results *cmd_bindsym(int argc, char **argv) { 72struct cmd_results *cmd_bindsym(int argc, char **argv) {
15 struct cmd_results *error = NULL; 73 struct cmd_results *error = NULL;
16 if ((error = checkarg(argc, "bindsym", EXPECTED_MORE_THAN, 1))) { 74 if ((error = checkarg(argc, "bindsym", EXPECTED_MORE_THAN, 1))) {
17 return error; 75 return error;
18 } 76 }
19 77
20 struct sway_binding *binding = malloc(sizeof(struct sway_binding)); 78 struct sway_binding *binding = calloc(1, sizeof(struct sway_binding));
21 if (!binding) { 79 if (!binding) {
22 return cmd_results_new(CMD_FAILURE, "bindsym", 80 return cmd_results_new(CMD_FAILURE, "bindsym",
23 "Unable to allocate binding"); 81 "Unable to allocate binding");
@@ -58,7 +116,7 @@ struct cmd_results *cmd_bindsym(int argc, char **argv) {
58 // Check for mouse binding 116 // Check for mouse binding
59 if (strncasecmp(split->items[i], "button", strlen("button")) == 0 && 117 if (strncasecmp(split->items[i], "button", strlen("button")) == 0 &&
60 strlen(split->items[i]) == strlen("button0")) { 118 strlen(split->items[i]) == strlen("button0")) {
61 sym = ((char *)split->items[i])[strlen("button")] - '1' + M_LEFT_CLICK; 119 sym = ((char *)split->items[i])[strlen("button")] - '1' + BTN_LEFT;
62 } 120 }
63 if (!sym) { 121 if (!sym) {
64 struct cmd_results *ret = cmd_results_new(CMD_INVALID, "bindsym", 122 struct cmd_results *ret = cmd_results_new(CMD_INVALID, "bindsym",
@@ -67,7 +125,7 @@ struct cmd_results *cmd_bindsym(int argc, char **argv) {
67 free_flat_list(split); 125 free_flat_list(split);
68 return ret; 126 return ret;
69 } 127 }
70 xkb_keysym_t *key = malloc(sizeof(xkb_keysym_t)); 128 xkb_keysym_t *key = calloc(1, sizeof(xkb_keysym_t));
71 if (!key) { 129 if (!key) {
72 free_sway_binding(binding); 130 free_sway_binding(binding);
73 free_flat_list(split); 131 free_flat_list(split);
@@ -78,20 +136,29 @@ struct cmd_results *cmd_bindsym(int argc, char **argv) {
78 list_add(binding->keys, key); 136 list_add(binding->keys, key);
79 } 137 }
80 free_flat_list(split); 138 free_flat_list(split);
139 binding->order = binding_order++;
81 140
82 struct sway_mode *mode = config->current_mode; 141 list_t *mode_bindings = config->current_mode->keysym_bindings;
83 int i = list_seq_find(mode->bindings, sway_binding_cmp_keys, binding); 142
84 if (i > -1) { 143 // overwrite the binding if it already exists
85 sway_log(L_DEBUG, "bindsym - '%s' already exists, overwriting", argv[0]); 144 bool overwritten = false;
86 struct sway_binding *dup = mode->bindings->items[i]; 145 for (int i = 0; i < mode_bindings->length; ++i) {
87 free_sway_binding(dup); 146 struct sway_binding *config_binding = mode_bindings->items[i];
88 list_del(mode->bindings, i); 147 if (binding_key_compare(binding, config_binding)) {
148 wlr_log(L_DEBUG, "overwriting old binding with command '%s'",
149 config_binding->command);
150 free_sway_binding(config_binding);
151 mode_bindings->items[i] = binding;
152 overwritten = true;
153 }
89 } 154 }
90 binding->order = binding_order++;
91 list_add(mode->bindings, binding);
92 list_qsort(mode->bindings, sway_binding_cmp_qsort);
93 155
94 sway_log(L_DEBUG, "bindsym - Bound %s to command %s", argv[0], binding->command); 156 if (!overwritten) {
157 list_add(mode_bindings, binding);
158 }
159
160 wlr_log(L_DEBUG, "bindsym - Bound %s to command %s",
161 argv[0], binding->command);
95 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 162 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
96} 163}
97 164
@@ -101,7 +168,7 @@ struct cmd_results *cmd_bindcode(int argc, char **argv) {
101 return error; 168 return error;
102 } 169 }
103 170
104 struct sway_binding *binding = malloc(sizeof(struct sway_binding)); 171 struct sway_binding *binding = calloc(1, sizeof(struct sway_binding));
105 if (!binding) { 172 if (!binding) {
106 return cmd_results_new(CMD_FAILURE, "bindsym", 173 return cmd_results_new(CMD_FAILURE, "bindsym",
107 "Unable to allocate binding"); 174 "Unable to allocate binding");
@@ -138,33 +205,41 @@ struct cmd_results *cmd_bindcode(int argc, char **argv) {
138 // parse keycode 205 // parse keycode
139 xkb_keycode_t keycode = (int)strtol(split->items[i], NULL, 10); 206 xkb_keycode_t keycode = (int)strtol(split->items[i], NULL, 10);
140 if (!xkb_keycode_is_legal_ext(keycode)) { 207 if (!xkb_keycode_is_legal_ext(keycode)) {
141 error = cmd_results_new(CMD_INVALID, "bindcode", "Invalid keycode '%s'", (char *)split->items[i]); 208 error =
209 cmd_results_new(CMD_INVALID, "bindcode",
210 "Invalid keycode '%s'", (char *)split->items[i]);
142 free_sway_binding(binding); 211 free_sway_binding(binding);
143 list_free(split); 212 list_free(split);
144 return error; 213 return error;
145 } 214 }
146 xkb_keycode_t *key = malloc(sizeof(xkb_keycode_t)); 215 xkb_keycode_t *key = calloc(1, sizeof(xkb_keycode_t));
147 *key = keycode - 8; 216 *key = keycode - 8;
148 list_add(binding->keys, key); 217 list_add(binding->keys, key);
149 } 218 }
150 free_flat_list(split); 219 free_flat_list(split);
151 220
152 struct sway_mode *mode = config->current_mode; 221 binding->order = binding_order++;
153 int i = list_seq_find(mode->bindings, sway_binding_cmp_keys, binding); 222
154 if (i > -1) { 223 list_t *mode_bindings = config->current_mode->keycode_bindings;
155 struct sway_binding *dup = mode->bindings->items[i]; 224
156 if (dup->bindcode) { 225 // overwrite the binding if it already exists
157 sway_log(L_DEBUG, "bindcode - '%s' already exists, overwriting", argv[0]); 226 bool overwritten = false;
158 } else { 227 for (int i = 0; i < mode_bindings->length; ++i) {
159 sway_log(L_DEBUG, "bindcode - '%s' already exists as bindsym, overwriting", argv[0]); 228 struct sway_binding *config_binding = mode_bindings->items[i];
229 if (binding_key_compare(binding, config_binding)) {
230 wlr_log(L_DEBUG, "overwriting old binding with command '%s'",
231 config_binding->command);
232 free_sway_binding(config_binding);
233 mode_bindings->items[i] = binding;
234 overwritten = true;
160 } 235 }
161 free_sway_binding(dup);
162 list_del(mode->bindings, i);
163 } 236 }
164 binding->order = binding_order++;
165 list_add(mode->bindings, binding);
166 list_qsort(mode->bindings, sway_binding_cmp_qsort);
167 237
168 sway_log(L_DEBUG, "bindcode - Bound %s to command %s", argv[0], binding->command); 238 if (!overwritten) {
239 list_add(mode_bindings, binding);
240 }
241
242 wlr_log(L_DEBUG, "bindcode - Bound %s to command %s",
243 argv[0], binding->command);
169 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 244 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
170} 245}
diff --git a/sway/commands/border.c b/sway/commands/border.c
deleted file mode 100644
index c888622e..00000000
--- a/sway/commands/border.c
+++ /dev/null
@@ -1,65 +0,0 @@
1#include <errno.h>
2#include <stdlib.h>
3#include <string.h>
4#include <strings.h>
5#include "sway/commands.h"
6#include "sway/container.h"
7#include "sway/focus.h"
8
9struct cmd_results *cmd_border(int argc, char **argv) {
10 struct cmd_results *error = NULL;
11 if (!config->active) {
12 return cmd_results_new(CMD_FAILURE, "border", "Can only be used when sway is running.");
13 }
14 if ((error = checkarg(argc, "border", EXPECTED_AT_LEAST, 1))) {
15 return error;
16 }
17
18 if (argc > 2) {
19 return cmd_results_new(CMD_INVALID, "border",
20 "Expected 'border <normal|pixel|none|toggle> [<n>]");
21 }
22
23 swayc_t *view = current_container;
24 enum swayc_border_types border = view->border_type;
25 int thickness = view->border_thickness;
26
27 if (strcasecmp(argv[0], "none") == 0) {
28 border = B_NONE;
29 } else if (strcasecmp(argv[0], "normal") == 0) {
30 border = B_NORMAL;
31 } else if (strcasecmp(argv[0], "pixel") == 0) {
32 border = B_PIXEL;
33 } else if (strcasecmp(argv[0], "toggle") == 0) {
34 switch (border) {
35 case B_NONE:
36 border = B_PIXEL;
37 break;
38 case B_NORMAL:
39 border = B_NONE;
40 break;
41 case B_PIXEL:
42 border = B_NORMAL;
43 break;
44 }
45 } else {
46 return cmd_results_new(CMD_INVALID, "border",
47 "Expected 'border <normal|pixel|none|toggle>");
48 }
49
50 if (argc == 2 && (border == B_NORMAL || border == B_PIXEL)) {
51 thickness = (int)strtol(argv[1], NULL, 10);
52 if (errno == ERANGE || thickness < 0) {
53 errno = 0;
54 return cmd_results_new(CMD_INVALID, "border", "Number is out of range.");
55 }
56 }
57
58 if (view) {
59 view->border_type = border;
60 view->border_thickness = thickness;
61 update_geometry(view);
62 }
63
64 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
65}
diff --git a/sway/commands/client.c b/sway/commands/client.c
deleted file mode 100644
index f3d879cd..00000000
--- a/sway/commands/client.c
+++ /dev/null
@@ -1,72 +0,0 @@
1#include <stdlib.h>
2#include <string.h>
3#include "sway/commands.h"
4
5static struct cmd_results *parse_border_color(struct border_colors *border_colors, const char *cmd_name, int argc, char **argv) {
6 struct cmd_results *error = NULL;
7 if (argc < 3 || argc > 5) {
8 return cmd_results_new(CMD_INVALID, cmd_name, "Requires between three and five color values");
9 }
10
11 uint32_t *colors[5] = {
12 &border_colors->border,
13 &border_colors->background,
14 &border_colors->text,
15 &border_colors->indicator,
16 &border_colors->child_border
17 };
18 int i;
19 for (i = 0; i < argc; i++) {
20 char buffer[10];
21 error = add_color(cmd_name, buffer, argv[i]);
22 if (error) {
23 return error;
24 }
25 *colors[i] = strtoul(buffer + 1, NULL, 16);
26 }
27
28 if (argc < 5) {
29 border_colors->child_border = border_colors->background;
30 }
31
32 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
33}
34
35struct cmd_results *cmd_client_focused(int argc, char **argv) {
36 return parse_border_color(&config->border_colors.focused, "client.focused", argc, argv);
37}
38
39struct cmd_results *cmd_client_focused_inactive(int argc, char **argv) {
40 return parse_border_color(&config->border_colors.focused_inactive, "client.focused_inactive", argc, argv);
41}
42
43struct cmd_results *cmd_client_unfocused(int argc, char **argv) {
44 return parse_border_color(&config->border_colors.unfocused, "client.unfocused", argc, argv);
45}
46
47struct cmd_results *cmd_client_urgent(int argc, char **argv) {
48 return parse_border_color(&config->border_colors.urgent, "client.urgent", argc, argv);
49}
50
51struct cmd_results *cmd_client_placeholder(int argc, char **argv) {
52 return parse_border_color(&config->border_colors.placeholder, "client.placeholder", argc, argv);
53}
54
55struct cmd_results *cmd_client_background(int argc, char **argv) {
56 char buffer[10];
57 struct cmd_results *error = NULL;
58 uint32_t background;
59
60 if (argc != 1) {
61 return cmd_results_new(CMD_INVALID, "client.background", "Requires exactly one color value");
62 }
63
64 error = add_color("client.background", buffer, argv[0]);
65 if (error) {
66 return error;
67 }
68
69 background = strtoul(buffer+1, NULL, 16);
70 config->border_colors.background = background;
71 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
72}
diff --git a/sway/commands/clipboard.c b/sway/commands/clipboard.c
deleted file mode 100644
index 95514e78..00000000
--- a/sway/commands/clipboard.c
+++ /dev/null
@@ -1,38 +0,0 @@
1#include <wlc/wlc.h>
2#include <unistd.h>
3#include <string.h>
4#include "sway/commands.h"
5#include "stringop.h"
6
7static void send_clipboard(void *data, const char *type, int fd) {
8 if (strcmp(type, "text/plain") != 0
9 && strcmp(type, "text/plain;charset=utf-8") != 0) {
10 close(fd);
11 return;
12 }
13
14 const char *str = data;
15 write(fd, str, strlen(str));
16 close(fd);
17}
18
19struct cmd_results *cmd_clipboard(int argc, char **argv) {
20 static char *current_data = NULL;
21
22 struct cmd_results *error = NULL;
23 if ((error = checkarg(argc, "clipboard", EXPECTED_AT_LEAST, 1))) {
24 return error;
25 }
26
27 static const char *types[2] = {
28 "text/plain",
29 "text/plain;charset=utf-8"
30 };
31
32 char *str = join_args(argv, argc);
33 wlc_set_selection(str, types, 2, &send_clipboard);
34
35 free(current_data);
36 current_data = str;
37 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
38}
diff --git a/sway/commands/commands.c b/sway/commands/commands.c
deleted file mode 100644
index 0c64970c..00000000
--- a/sway/commands/commands.c
+++ /dev/null
@@ -1,26 +0,0 @@
1#include <stdbool.h>
2#include <string.h>
3#include "sway/commands.h"
4#include "sway/config.h"
5#include "list.h"
6#include "log.h"
7
8struct cmd_results *cmd_commands(int argc, char **argv) {
9 struct cmd_results *error = NULL;
10 if ((error = checkarg(argc, "commands", EXPECTED_EQUAL_TO, 1))) {
11 return error;
12 }
13 if ((error = check_security_config())) {
14 return error;
15 }
16
17 if (strcmp(argv[0], "{") != 0) {
18 return cmd_results_new(CMD_FAILURE, "commands", "Expected block declaration");
19 }
20
21 if (!config->reading) {
22 return cmd_results_new(CMD_FAILURE, "commands", "Can only be used in config file.");
23 }
24
25 return cmd_results_new(CMD_BLOCK_COMMANDS, NULL, NULL);
26}
diff --git a/sway/commands/debuglog.c b/sway/commands/debuglog.c
deleted file mode 100644
index 658d6165..00000000
--- a/sway/commands/debuglog.c
+++ /dev/null
@@ -1,27 +0,0 @@
1#include <string.h>
2#include <strings.h>
3#include "sway/commands.h"
4#include "log.h"
5
6struct cmd_results *cmd_debuglog(int argc, char **argv) {
7 struct cmd_results *error = NULL;
8 if ((error = checkarg(argc, "debuglog", EXPECTED_EQUAL_TO, 1))) {
9 return error;
10 } else if (strcasecmp(argv[0], "toggle") == 0) {
11 if (config->reading) {
12 return cmd_results_new(CMD_FAILURE, "debuglog toggle", "Can't be used in config file.");
13 }
14 if (toggle_debug_logging()) {
15 sway_log(L_DEBUG, "Debuglog turned on.");
16 }
17 } else if (strcasecmp(argv[0], "on") == 0) {
18 set_log_level(L_DEBUG);
19 sway_log(L_DEBUG, "Debuglog turned on.");
20 } else if (strcasecmp(argv[0], "off") == 0) {
21 reset_log_level();
22 } else {
23 return cmd_results_new(CMD_FAILURE, "debuglog", "Expected 'debuglog on|off|toggle'");
24 }
25 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
26}
27
diff --git a/sway/commands/default_border.c b/sway/commands/default_border.c
deleted file mode 100644
index 8fbe8d19..00000000
--- a/sway/commands/default_border.c
+++ /dev/null
@@ -1,44 +0,0 @@
1#include <errno.h>
2#include <string.h>
3#include <strings.h>
4#include "sway/commands.h"
5#include "sway/container.h"
6
7struct cmd_results *cmd_default_border(int argc, char **argv) {
8 struct cmd_results *error = NULL;
9 if ((error = checkarg(argc, "default_border", EXPECTED_AT_LEAST, 1))) {
10 return error;
11 }
12
13 if (argc > 2) {
14 return cmd_results_new(CMD_INVALID, "default_border",
15 "Expected 'default_border <normal|none|pixel> [<n>]");
16 }
17
18 enum swayc_border_types border = config->border;
19 int thickness = config->border_thickness;
20
21 if (strcasecmp(argv[0], "none") == 0) {
22 border = B_NONE;
23 } else if (strcasecmp(argv[0], "normal") == 0) {
24 border = B_NORMAL;
25 } else if (strcasecmp(argv[0], "pixel") == 0) {
26 border = B_PIXEL;
27 } else {
28 return cmd_results_new(CMD_INVALID, "default_border",
29 "Expected 'default_border <normal|none|pixel> [<n>]");
30 }
31
32 if (argc == 2 && (border == B_NORMAL || border == B_PIXEL)) {
33 thickness = (int)strtol(argv[1], NULL, 10);
34 if (errno == ERANGE || thickness < 0) {
35 errno = 0;
36 return cmd_results_new(CMD_INVALID, "default_border", "Number is out out of range.");
37 }
38 }
39
40 config->border = border;
41 config->border_thickness = thickness;
42
43 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
44}
diff --git a/sway/commands/default_floating_border.c b/sway/commands/default_floating_border.c
deleted file mode 100644
index fb48c1c0..00000000
--- a/sway/commands/default_floating_border.c
+++ /dev/null
@@ -1,45 +0,0 @@
1#include <errno.h>
2#include <string.h>
3#include <strings.h>
4#include "sway/commands.h"
5#include "sway/container.h"
6
7struct cmd_results *cmd_default_floating_border(int argc, char **argv) {
8 struct cmd_results *error = NULL;
9 if ((error = checkarg(argc, "default_floating_border", EXPECTED_AT_LEAST, 1))) {
10 return error;
11 }
12
13 if (argc > 2) {
14 return cmd_results_new(CMD_INVALID, "default_floating_border",
15 "Expected 'default_floating_border <normal|none|pixel> [<n>]");
16 }
17
18 enum swayc_border_types border = config->floating_border;
19 int thickness = config->floating_border_thickness;
20
21 if (strcasecmp(argv[0], "none") == 0) {
22 border = B_NONE;
23 } else if (strcasecmp(argv[0], "normal") == 0) {
24 border = B_NORMAL;
25 } else if (strcasecmp(argv[0], "pixel") == 0) {
26 border = B_PIXEL;
27 } else {
28 return cmd_results_new(CMD_INVALID, "default_floating_border",
29 "Expected 'default_floating_border <normal|none|pixel> [<n>]");
30 }
31
32 if (argc == 2 && (border == B_NORMAL || border == B_PIXEL)) {
33 thickness = (int)strtol(argv[1], NULL, 10);
34 if (errno == ERANGE || thickness < 0) {
35 errno = 0;
36 return cmd_results_new(CMD_INVALID, "default_floating_border",
37 "Number is out out of range.");
38 }
39 }
40
41 config->floating_border = border;
42 config->floating_border_thickness = thickness;
43
44 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
45}
diff --git a/sway/commands/orientation.c b/sway/commands/default_orientation.c
index e54b60ee..a5347ce2 100644
--- a/sway/commands/orientation.c
+++ b/sway/commands/default_orientation.c
@@ -2,10 +2,9 @@
2#include <strings.h> 2#include <strings.h>
3#include "sway/commands.h" 3#include "sway/commands.h"
4 4
5struct cmd_results *cmd_orientation(int argc, char **argv) { 5struct cmd_results *cmd_default_orientation(int argc, char **argv) {
6 struct cmd_results *error = NULL; 6 struct cmd_results *error = NULL;
7 if (!config->reading) return cmd_results_new(CMD_FAILURE, "orientation", "Can only be used in config file."); 7 if ((error = checkarg(argc, "default_orientation", EXPECTED_EQUAL_TO, 1))) {
8 if ((error = checkarg(argc, "orientation", EXPECTED_EQUAL_TO, 1))) {
9 return error; 8 return error;
10 } 9 }
11 if (strcasecmp(argv[0], "horizontal") == 0) { 10 if (strcasecmp(argv[0], "horizontal") == 0) {
@@ -15,7 +14,8 @@ struct cmd_results *cmd_orientation(int argc, char **argv) {
15 } else if (strcasecmp(argv[0], "auto") == 0) { 14 } else if (strcasecmp(argv[0], "auto") == 0) {
16 // Do nothing 15 // Do nothing
17 } else { 16 } else {
18 return cmd_results_new(CMD_INVALID, "orientation", "Expected 'orientation <horizontal|vertical|auto>'"); 17 return cmd_results_new(CMD_INVALID, "default_orientation",
18 "Expected 'orientation <horizontal|vertical|auto>'");
19 } 19 }
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/exec.c b/sway/commands/exec.c
index 58ef5f94..363d5bef 100644
--- a/sway/commands/exec.c
+++ b/sway/commands/exec.c
@@ -1,5 +1,6 @@
1#include <string.h> 1#include <string.h>
2#include "sway/commands.h" 2#include "sway/commands.h"
3#include "sway/config.h"
3#include "log.h" 4#include "log.h"
4#include "stringop.h" 5#include "stringop.h"
5 6
@@ -7,10 +8,9 @@ struct cmd_results *cmd_exec(int argc, char **argv) {
7 if (!config->active) return cmd_results_new(CMD_DEFER, "exec", NULL); 8 if (!config->active) return cmd_results_new(CMD_DEFER, "exec", NULL);
8 if (config->reloading) { 9 if (config->reloading) {
9 char *args = join_args(argv, argc); 10 char *args = join_args(argv, argc);
10 sway_log(L_DEBUG, "Ignoring 'exec %s' due to reload", args); 11 wlr_log(L_DEBUG, "Ignoring 'exec %s' due to reload", args);
11 free(args); 12 free(args);
12 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 13 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
13 } 14 }
14 return cmd_exec_always(argc, argv); 15 return cmd_exec_always(argc, argv);
15} 16}
16
diff --git a/sway/commands/exec_always.c b/sway/commands/exec_always.c
index ab2d8622..954950e7 100644
--- a/sway/commands/exec_always.c
+++ b/sway/commands/exec_always.c
@@ -1,9 +1,13 @@
1#define _XOPEN_SOURCE 500 1#define _XOPEN_SOURCE 500
2#include <stdlib.h>
3#include <stdint.h>
2#include <string.h> 4#include <string.h>
3#include <sys/wait.h> 5#include <sys/wait.h>
4#include <unistd.h> 6#include <unistd.h>
5#include "sway/commands.h" 7#include "sway/commands.h"
6#include "sway/config.h" 8#include "sway/config.h"
9#include "sway/tree/container.h"
10#include "sway/tree/workspace.h"
7#include "log.h" 11#include "log.h"
8#include "stringop.h" 12#include "stringop.h"
9 13
@@ -16,7 +20,7 @@ struct cmd_results *cmd_exec_always(int argc, char **argv) {
16 20
17 char *tmp = NULL; 21 char *tmp = NULL;
18 if (strcmp((char*)*argv, "--no-startup-id") == 0) { 22 if (strcmp((char*)*argv, "--no-startup-id") == 0) {
19 sway_log(L_INFO, "exec switch '--no-startup-id' not supported, ignored."); 23 wlr_log(L_INFO, "exec switch '--no-startup-id' not supported, ignored.");
20 if ((error = checkarg(argc - 1, "exec_always", EXPECTED_MORE_THAN, 0))) { 24 if ((error = checkarg(argc - 1, "exec_always", EXPECTED_MORE_THAN, 0))) {
21 return error; 25 return error;
22 } 26 }
@@ -31,11 +35,11 @@ struct cmd_results *cmd_exec_always(int argc, char **argv) {
31 strncpy(cmd, tmp, sizeof(cmd)); 35 strncpy(cmd, tmp, sizeof(cmd));
32 cmd[sizeof(cmd) - 1] = 0; 36 cmd[sizeof(cmd) - 1] = 0;
33 free(tmp); 37 free(tmp);
34 sway_log(L_DEBUG, "Executing %s", cmd); 38 wlr_log(L_DEBUG, "Executing %s", cmd);
35 39
36 int fd[2]; 40 int fd[2];
37 if (pipe(fd) != 0) { 41 if (pipe(fd) != 0) {
38 sway_log(L_ERROR, "Unable to create pipe for fork"); 42 wlr_log(L_ERROR, "Unable to create pipe for fork");
39 } 43 }
40 44
41 pid_t pid; 45 pid_t pid;
@@ -49,7 +53,7 @@ struct cmd_results *cmd_exec_always(int argc, char **argv) {
49 setsid(); 53 setsid();
50 if ((*child = fork()) == 0) { 54 if ((*child = fork()) == 0) {
51 execl("/bin/sh", "/bin/sh", "-c", cmd, (void *)NULL); 55 execl("/bin/sh", "/bin/sh", "-c", cmd, (void *)NULL);
52 /* Not reached */ 56 // Not reached
53 } 57 }
54 close(fd[0]); 58 close(fd[0]);
55 ssize_t s = 0; 59 ssize_t s = 0;
@@ -70,13 +74,9 @@ struct cmd_results *cmd_exec_always(int argc, char **argv) {
70 close(fd[0]); 74 close(fd[0]);
71 // cleanup child process 75 // cleanup child process
72 wait(0); 76 wait(0);
73 swayc_t *ws = swayc_active_workspace(); 77 if (*child > 0) {
74 if (*child > 0 && ws) { 78 wlr_log(L_DEBUG, "Child process created with pid %d", *child);
75 sway_log(L_DEBUG, "Child process created with pid %d for workspace %s", *child, ws->name); 79 // TODO: add PID to active workspace
76 struct pid_workspace *pw = malloc(sizeof(struct pid_workspace));
77 pw->pid = child;
78 pw->workspace = strdup(ws->name);
79 pid_workspace_add(pw);
80 } else { 80 } else {
81 free(child); 81 free(child);
82 } 82 }
diff --git a/sway/commands/exit.c b/sway/commands/exit.c
index f192f86a..d5353c20 100644
--- a/sway/commands/exit.c
+++ b/sway/commands/exit.c
@@ -1,17 +1,14 @@
1#include <stddef.h>
1#include "sway/commands.h" 2#include "sway/commands.h"
2#include "sway/container.h" 3#include "sway/config.h"
3 4
4void sway_terminate(int exit_code); 5void sway_terminate(int exit_code);
5 6
6struct cmd_results *cmd_exit(int argc, char **argv) { 7struct cmd_results *cmd_exit(int argc, char **argv) {
7 struct cmd_results *error = NULL; 8 struct cmd_results *error = NULL;
8 if (config->reading) return cmd_results_new(CMD_FAILURE, "exit", "Can't be used in config file.");
9 if ((error = checkarg(argc, "exit", EXPECTED_EQUAL_TO, 0))) { 9 if ((error = checkarg(argc, "exit", EXPECTED_EQUAL_TO, 0))) {
10 return error; 10 return error;
11 } 11 }
12 // Close all views 12 sway_terminate(0);
13 close_views(&root_container);
14 sway_terminate(EXIT_SUCCESS);
15 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 13 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
16} 14}
17
diff --git a/sway/commands/floating.c b/sway/commands/floating.c
deleted file mode 100644
index ccfde532..00000000
--- a/sway/commands/floating.c
+++ /dev/null
@@ -1,81 +0,0 @@
1#include <string.h>
2#include <strings.h>
3#include "sway/commands.h"
4#include "sway/container.h"
5#include "sway/ipc-server.h"
6#include "sway/layout.h"
7#include "list.h"
8#include "log.h"
9
10struct cmd_results *cmd_floating(int argc, char **argv) {
11 struct cmd_results *error = NULL;
12 if (config->reading) return cmd_results_new(CMD_FAILURE, "floating", "Can't be used in config file.");
13 if ((error = checkarg(argc, "floating", EXPECTED_EQUAL_TO, 1))) {
14 return error;
15 }
16 swayc_t *view = current_container;
17 bool wants_floating;
18 if (strcasecmp(argv[0], "enable") == 0) {
19 wants_floating = true;
20 } else if (strcasecmp(argv[0], "disable") == 0) {
21 wants_floating = false;
22 } else if (strcasecmp(argv[0], "toggle") == 0) {
23 wants_floating = !view->is_floating;
24 } else {
25 return cmd_results_new(CMD_FAILURE, "floating",
26 "Expected 'floating <enable|disable|toggle>");
27 }
28
29 // Prevent running floating commands on things like workspaces
30 if (view->type != C_VIEW) {
31 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
32 }
33
34 // Change from nonfloating to floating
35 if (!view->is_floating && wants_floating) {
36 // Remove view from its current location
37 destroy_container(remove_child(view));
38
39 // and move it into workspace floating
40 add_floating(swayc_active_workspace(), view);
41 view->x = (swayc_active_workspace()->width - view->width)/2;
42 view->y = (swayc_active_workspace()->height - view->height)/2;
43 if (view->desired_width != -1) {
44 view->width = view->desired_width;
45 }
46 if (view->desired_height != -1) {
47 view->height = view->desired_height;
48 }
49 arrange_windows(swayc_active_workspace(), -1, -1);
50
51 } else if (view->is_floating && !wants_floating) {
52 // Delete the view from the floating list and unset its is_floating flag
53 remove_child(view);
54 view->is_floating = false;
55 // Get the properly focused container, and add in the view there
56 swayc_t *focused = container_under_pointer();
57 // If focused is null, it's because the currently focused container is a workspace
58 if (focused == NULL || focused->is_floating) {
59 focused = swayc_active_workspace();
60 }
61 set_focused_container(focused);
62
63 sway_log(L_DEBUG, "Non-floating focused container is %p", focused);
64
65 // Case of focused workspace, just create as child of it
66 if (focused->type == C_WORKSPACE) {
67 add_child(focused, view);
68 }
69 // Regular case, create as sibling of current container
70 else {
71 add_sibling(focused, view);
72 }
73 // Refocus on the view once its been put back into the layout
74 view->width = view->height = 0;
75 arrange_windows(swayc_active_workspace(), -1, -1);
76 remove_view_from_scratchpad(view);
77 ipc_event_window(view, "floating");
78 }
79 set_focused_container(view);
80 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
81}
diff --git a/sway/commands/floating_maximum_size.c b/sway/commands/floating_maximum_size.c
deleted file mode 100644
index 5bca4d7c..00000000
--- a/sway/commands/floating_maximum_size.c
+++ /dev/null
@@ -1,38 +0,0 @@
1#include <stdlib.h>
2#include <string.h>
3#include "sway/commands.h"
4#include "log.h"
5
6struct cmd_results *cmd_floating_maximum_size(int argc, char **argv) {
7 struct cmd_results *error = NULL;
8 int32_t width;
9 int32_t height;
10 char *ptr;
11
12 if ((error = checkarg(argc, "floating_maximum_size", EXPECTED_EQUAL_TO, 3))) {
13 return error;
14 }
15 width = strtol(argv[0], &ptr, 10);
16 height = strtol(argv[2], &ptr, 10);
17
18 if (width < -1) {
19 sway_log(L_DEBUG, "floating_maximum_size invalid width value: '%s'", argv[0]);
20
21 } else {
22 config->floating_maximum_width = width;
23
24 }
25
26 if (height < -1) {
27 sway_log(L_DEBUG, "floating_maximum_size invalid height value: '%s'", argv[2]);
28 }
29 else {
30 config->floating_maximum_height = height;
31
32 }
33
34 sway_log(L_DEBUG, "New floating_maximum_size: '%d' x '%d'", config->floating_maximum_width,
35 config->floating_maximum_height);
36
37 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
38}
diff --git a/sway/commands/floating_minimum_size.c b/sway/commands/floating_minimum_size.c
deleted file mode 100644
index ba72454c..00000000
--- a/sway/commands/floating_minimum_size.c
+++ /dev/null
@@ -1,38 +0,0 @@
1#include <stdlib.h>
2#include <string.h>
3#include "sway/commands.h"
4#include "log.h"
5
6struct cmd_results *cmd_floating_minimum_size(int argc, char **argv) {
7 struct cmd_results *error = NULL;
8 int32_t width;
9 int32_t height;
10 char *ptr;
11
12 if ((error = checkarg(argc, "floating_minimum_size", EXPECTED_EQUAL_TO, 3))) {
13 return error;
14 }
15 width = strtol(argv[0], &ptr, 10);
16 height = strtol(argv[2], &ptr, 10);
17
18 if (width <= 0) {
19 sway_log(L_DEBUG, "floating_minimum_size invalid width value: '%s'", argv[0]);
20
21 } else {
22 config->floating_minimum_width = width;
23
24 }
25
26 if (height <= 0) {
27 sway_log(L_DEBUG, "floating_minimum_size invalid height value: '%s'", argv[2]);
28 }
29 else {
30 config->floating_minimum_height = height;
31
32 }
33
34 sway_log(L_DEBUG, "New floating_minimum_size: '%d' x '%d'", config->floating_minimum_width,
35 config->floating_minimum_height);
36
37 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
38}
diff --git a/sway/commands/floating_mod.c b/sway/commands/floating_mod.c
deleted file mode 100644
index b8e81ab9..00000000
--- a/sway/commands/floating_mod.c
+++ /dev/null
@@ -1,42 +0,0 @@
1#include <string.h>
2#include <strings.h>
3#include "sway/commands.h"
4#include "sway/input_state.h"
5#include "list.h"
6#include "log.h"
7#include "stringop.h"
8#include "util.h"
9
10struct cmd_results *cmd_floating_mod(int argc, char **argv) {
11 struct cmd_results *error = NULL;
12 if ((error = checkarg(argc, "floating_modifier", EXPECTED_AT_LEAST, 1))) {
13 return error;
14 }
15 int i;
16 list_t *split = split_string(argv[0], "+");
17 config->floating_mod = 0;
18
19 // set modifier keys
20 for (i = 0; i < split->length; ++i) {
21 config->floating_mod |= get_modifier_mask_by_name(split->items[i]);
22 }
23 free_flat_list(split);
24 if (!config->floating_mod) {
25 error = cmd_results_new(CMD_INVALID, "floating_modifier", "Unknown keys %s", argv[0]);
26 return error;
27 }
28
29 if (argc >= 2) {
30 if (strcasecmp("inverse", argv[1]) == 0) {
31 config->dragging_key = M_RIGHT_CLICK;
32 config->resizing_key = M_LEFT_CLICK;
33 } else if (strcasecmp("normal", argv[1]) == 0) {
34 config->dragging_key = M_LEFT_CLICK;
35 config->resizing_key = M_RIGHT_CLICK;
36 } else {
37 error = cmd_results_new(CMD_INVALID, "floating_modifier", "Invalid definition %s", argv[1]);
38 return error;
39 }
40 }
41 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
42}
diff --git a/sway/commands/floating_scroll.c b/sway/commands/floating_scroll.c
deleted file mode 100644
index 8c50c5bd..00000000
--- a/sway/commands/floating_scroll.c
+++ /dev/null
@@ -1,46 +0,0 @@
1#define _XOPEN_SOURCE 500
2#include <string.h>
3#include <strings.h>
4#include "sway/commands.h"
5#include "log.h"
6#include "stringop.h"
7
8struct cmd_results *cmd_floating_scroll(int argc, char **argv) {
9 struct cmd_results *error = NULL;
10 if ((error = checkarg(argc, "floating_scroll", EXPECTED_AT_LEAST, 1))) {
11 return error;
12 }
13 if (!strcasecmp("up", argv[0])) {
14 free(config->floating_scroll_up_cmd);
15 if (argc < 2) {
16 config->floating_scroll_up_cmd = strdup("");
17 } else {
18 config->floating_scroll_up_cmd = join_args(argv + 1, argc - 1);
19 }
20 } else if (!strcasecmp("down", argv[0])) {
21 free(config->floating_scroll_down_cmd);
22 if (argc < 2) {
23 config->floating_scroll_down_cmd = strdup("");
24 } else {
25 config->floating_scroll_down_cmd = join_args(argv + 1, argc - 1);
26 }
27 } else if (!strcasecmp("left", argv[0])) {
28 free(config->floating_scroll_left_cmd);
29 if (argc < 2) {
30 config->floating_scroll_left_cmd = strdup("");
31 } else {
32 config->floating_scroll_left_cmd = join_args(argv + 1, argc - 1);
33 }
34 } else if (!strcasecmp("right", argv[0])) {
35 free(config->floating_scroll_right_cmd);
36 if (argc < 2) {
37 config->floating_scroll_right_cmd = strdup("");
38 } else {
39 config->floating_scroll_right_cmd = join_args(argv + 1, argc - 1);
40 }
41 } else {
42 error = cmd_results_new(CMD_INVALID, "floating_scroll", "Unknown command: '%s'", argv[0]);
43 return error;
44 }
45 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
46}
diff --git a/sway/commands/focus.c b/sway/commands/focus.c
index defaba29..74d9d535 100644
--- a/sway/commands/focus.c
+++ b/sway/commands/focus.c
@@ -1,99 +1,57 @@
1#include <string.h>
2#include <strings.h> 1#include <strings.h>
3#include <wlc/wlc.h> 2#include <wlr/util/log.h>
3#include "log.h"
4#include "sway/input/input-manager.h"
5#include "sway/input/seat.h"
6#include "sway/tree/view.h"
4#include "sway/commands.h" 7#include "sway/commands.h"
5#include "sway/container.h" 8
6#include "sway/focus.h" 9static bool parse_movement_direction(const char *name,
7#include "sway/input_state.h" 10 enum movement_direction *out) {
8#include "sway/output.h" 11 if (strcasecmp(name, "left") == 0) {
9#include "sway/workspace.h" 12 *out = MOVE_LEFT;
13 } else if (strcasecmp(name, "right") == 0) {
14 *out = MOVE_RIGHT;
15 } else if (strcasecmp(name, "up") == 0) {
16 *out = MOVE_UP;
17 } else if (strcasecmp(name, "down") == 0) {
18 *out = MOVE_DOWN;
19 } else if (strcasecmp(name, "parent") == 0) {
20 *out = MOVE_PARENT;
21 } else if (strcasecmp(name, "child") == 0) {
22 *out = MOVE_CHILD;
23 } else {
24 return false;
25 }
26
27 return true;
28}
10 29
11struct cmd_results *cmd_focus(int argc, char **argv) { 30struct cmd_results *cmd_focus(int argc, char **argv) {
12 if (config->reading) return cmd_results_new(CMD_FAILURE, "focus", "Can't be used in config file."); 31 struct sway_container *con = config->handler_context.current_container;
13 struct cmd_results *error = NULL; 32 struct sway_seat *seat = config->handler_context.seat;
14 if (argc > 0 && strcasecmp(argv[0], "output") == 0) { 33 if (con->type < C_WORKSPACE) {
15 swayc_t *output = NULL; 34 return cmd_results_new(CMD_FAILURE, "focus",
16 struct wlc_point abs_pos; 35 "Command 'focus' cannot be used above the workspace level");
17 get_absolute_center_position(get_focused_container(&root_container), &abs_pos); 36 }
18 if ((error = checkarg(argc, "focus", EXPECTED_EQUAL_TO, 2))) { 37
19 return error; 38 if (argc == 0) {
20 } else if (!(output = output_by_name(argv[1], &abs_pos))) { 39 seat_set_focus(seat, con);
21 return cmd_results_new(CMD_FAILURE, "focus output",
22 "Can't find output with name/at direction '%s' @ (%i,%i)", argv[1], abs_pos.x, abs_pos.y);
23 } else if (!workspace_switch(swayc_active_workspace_for(output))) {
24 return cmd_results_new(CMD_FAILURE, "focus output",
25 "Switching to workspace on output '%s' was blocked", argv[1]);
26 } else if (config->mouse_warping) {
27 swayc_t *focused = get_focused_view(output);
28 if (focused && focused->type == C_VIEW) {
29 center_pointer_on(focused);
30 }
31 }
32 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
33 } else if (argc == 0) {
34 set_focused_container(current_container);
35 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 40 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
36 } else if ((error = checkarg(argc, "focus", EXPECTED_EQUAL_TO, 1))) {
37 return error;
38 } 41 }
39 static int floating_toggled_index = 0; 42
40 static int tiled_toggled_index = 0; 43 // TODO mode_toggle
41 if (strcasecmp(argv[0], "left") == 0) { 44 enum movement_direction direction = 0;
42 move_focus(MOVE_LEFT); 45 if (!parse_movement_direction(argv[0], &direction)) {
43 } else if (strcasecmp(argv[0], "right") == 0) {
44 move_focus(MOVE_RIGHT);
45 } else if (strcasecmp(argv[0], "up") == 0) {
46 move_focus(MOVE_UP);
47 } else if (strcasecmp(argv[0], "down") == 0) {
48 move_focus(MOVE_DOWN);
49 } else if (strcasecmp(argv[0], "parent") == 0) {
50 move_focus(MOVE_PARENT);
51 } else if (strcasecmp(argv[0], "child") == 0) {
52 move_focus(MOVE_CHILD);
53 } else if (strcasecmp(argv[0], "next") == 0) {
54 move_focus(MOVE_NEXT);
55 } else if (strcasecmp(argv[0], "prev") == 0) {
56 move_focus(MOVE_PREV);
57 } else if (strcasecmp(argv[0], "mode_toggle") == 0) {
58 int i;
59 swayc_t *workspace = swayc_active_workspace();
60 swayc_t *focused = get_focused_view(workspace);
61 if (focused->is_floating) {
62 if (workspace->children->length > 0) {
63 for (i = 0;i < workspace->floating->length; i++) {
64 if (workspace->floating->items[i] == focused) {
65 floating_toggled_index = i;
66 break;
67 }
68 }
69 if (workspace->children->length > tiled_toggled_index) {
70 set_focused_container(get_focused_view(workspace->children->items[tiled_toggled_index]));
71 } else {
72 set_focused_container(get_focused_view(workspace->children->items[0]));
73 tiled_toggled_index = 0;
74 }
75 }
76 } else {
77 if (workspace->floating->length > 0) {
78 for (i = 0;i < workspace->children->length; i++) {
79 if (workspace->children->items[i] == focused) {
80 tiled_toggled_index = i;
81 break;
82 }
83 }
84 if (workspace->floating->length > floating_toggled_index) {
85 swayc_t *floating = workspace->floating->items[floating_toggled_index];
86 set_focused_container(get_focused_view(floating));
87 } else {
88 swayc_t *floating = workspace->floating->items[workspace->floating->length - 1];
89 set_focused_container(get_focused_view(floating));
90 tiled_toggled_index = workspace->floating->length - 1;
91 }
92 }
93 }
94 } else {
95 return cmd_results_new(CMD_INVALID, "focus", 46 return cmd_results_new(CMD_INVALID, "focus",
96 "Expected 'focus <direction|parent|child|mode_toggle>' or 'focus output <direction|name>'"); 47 "Expected 'focus <direction|parent|child|mode_toggle>' or 'focus output <direction|name>'");
97 } 48 }
49
50 struct sway_container *next_focus = container_get_in_direction(
51 con, seat, direction);
52 if (next_focus) {
53 seat_set_focus(seat, next_focus);
54 }
55
98 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 56 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
99} 57}
diff --git a/sway/commands/focus_follows_mouse.c b/sway/commands/focus_follows_mouse.c
index 7c9c2b13..661e7852 100644
--- a/sway/commands/focus_follows_mouse.c
+++ b/sway/commands/focus_follows_mouse.c
@@ -7,7 +7,6 @@ struct cmd_results *cmd_focus_follows_mouse(int argc, char **argv) {
7 if ((error = checkarg(argc, "focus_follows_mouse", EXPECTED_EQUAL_TO, 1))) { 7 if ((error = checkarg(argc, "focus_follows_mouse", EXPECTED_EQUAL_TO, 1))) {
8 return error; 8 return error;
9 } 9 }
10
11 config->focus_follows_mouse = !strcasecmp(argv[0], "yes"); 10 config->focus_follows_mouse = !strcasecmp(argv[0], "yes");
12 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 11 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
13} 12}
diff --git a/sway/commands/font.c b/sway/commands/font.c
deleted file mode 100644
index 32994f8a..00000000
--- a/sway/commands/font.c
+++ /dev/null
@@ -1,27 +0,0 @@
1#define _XOPEN_SOURCE 500
2#include <string.h>
3#include "sway/border.h"
4#include "sway/commands.h"
5#include "log.h"
6#include "stringop.h"
7
8struct cmd_results *cmd_font(int argc, char **argv) {
9 struct cmd_results *error = NULL;
10 if ((error = checkarg(argc, "font", EXPECTED_AT_LEAST, 1))) {
11 return error;
12 }
13
14 char *font = join_args(argv, argc);
15 free(config->font);
16 if (strlen(font) > 6 && strncmp("pango:", font, 6) == 0) {
17 config->font = strdup(font + 6);
18 free(font);
19 } else {
20 config->font = font;
21 }
22
23 config->font_height = get_font_text_height(config->font);
24
25 sway_log(L_DEBUG, "Settings font %s", config->font);
26 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
27}
diff --git a/sway/commands/for_window.c b/sway/commands/for_window.c
deleted file mode 100644
index d1fd1641..00000000
--- a/sway/commands/for_window.c
+++ /dev/null
@@ -1,41 +0,0 @@
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#include "stringop.h"
8
9struct cmd_results *cmd_for_window(int argc, char **argv) {
10 struct cmd_results *error = NULL;
11 if ((error = checkarg(argc, "for_window", EXPECTED_AT_LEAST, 2))) {
12 return error;
13 }
14 // add command to a criteria/command pair that is run against views when they appear.
15 char *criteria = argv[0], *cmdlist = join_args(argv + 1, argc - 1);
16
17 struct criteria *crit = malloc(sizeof(struct criteria));
18 if (!crit) {
19 return cmd_results_new(CMD_FAILURE, "for_window", "Unable to allocate criteria");
20 }
21 crit->crit_raw = strdup(criteria);
22 crit->cmdlist = cmdlist;
23 crit->tokens = create_list();
24 char *err_str = extract_crit_tokens(crit->tokens, crit->crit_raw);
25
26 if (err_str) {
27 error = cmd_results_new(CMD_INVALID, "for_window", err_str);
28 free(err_str);
29 free_criteria(crit);
30 } else if (crit->tokens->length == 0) {
31 error = cmd_results_new(CMD_INVALID, "for_window", "Found no name/value pairs in criteria");
32 free_criteria(crit);
33 } else if (list_seq_find(config->criteria, criteria_cmp, crit) != -1) {
34 sway_log(L_DEBUG, "for_window: Duplicate, skipping.");
35 free_criteria(crit);
36 } else {
37 sway_log(L_DEBUG, "for_window: '%s' -> '%s' added", crit->crit_raw, crit->cmdlist);
38 list_add(config->criteria, crit);
39 }
40 return error ? error : cmd_results_new(CMD_SUCCESS, NULL, NULL);
41}
diff --git a/sway/commands/force_focus_wrapping.c b/sway/commands/force_focus_wrapping.c
deleted file mode 100644
index f19dd163..00000000
--- a/sway/commands/force_focus_wrapping.c
+++ /dev/null
@@ -1,13 +0,0 @@
1#include <string.h>
2#include <strings.h>
3#include "sway/commands.h"
4
5struct cmd_results *cmd_force_focus_wrapping(int argc, char **argv) {
6 struct cmd_results *error = NULL;
7 if ((error = checkarg(argc, "force_focus_wrapping", EXPECTED_EQUAL_TO, 1))) {
8 return error;
9 }
10
11 config->force_focus_wrapping = !strcasecmp(argv[0], "yes");
12 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
13}
diff --git a/sway/commands/fullscreen.c b/sway/commands/fullscreen.c
deleted file mode 100644
index bfff82f9..00000000
--- a/sway/commands/fullscreen.c
+++ /dev/null
@@ -1,60 +0,0 @@
1#include <stdbool.h>
2#include <string.h>
3#include <wlc/wlc.h>
4#include "sway/commands.h"
5#include "sway/container.h"
6#include "sway/focus.h"
7#include "sway/ipc-server.h"
8#include "sway/layout.h"
9
10struct cmd_results *cmd_fullscreen(int argc, char **argv) {
11 struct cmd_results *error = NULL;
12 if (config->reading) return cmd_results_new(CMD_FAILURE, "fullscreen", "Can't be used in config file.");
13 if (!config->active) return cmd_results_new(CMD_FAILURE, "fullscreen", "Can only be used when sway is running.");
14 if ((error = checkarg(argc, "fullscreen", EXPECTED_AT_LEAST, 0))) {
15 return error;
16 }
17 swayc_t *container = current_container;
18 if(container->type != C_VIEW){
19 return cmd_results_new(CMD_INVALID, "fullscreen", "Only views can fullscreen");
20 }
21 swayc_t *workspace = swayc_parent_by_type(container, C_WORKSPACE);
22 bool current = swayc_is_fullscreen(container);
23 wlc_view_set_state(container->handle, WLC_BIT_FULLSCREEN, !current);
24
25 if (container->is_floating) {
26 if (current) {
27 // set dimensions back to what they were before we fullscreened this
28 container->x = container->cached_geometry.origin.x;
29 container->y = container->cached_geometry.origin.y;
30 container->width = container->cached_geometry.size.w;
31 container->height = container->cached_geometry.size.h;
32 } else {
33 // cache dimensions so we can reset them after we "unfullscreen" this
34 struct wlc_geometry geo = {
35 .origin = {
36 .x = container->x,
37 .y = container->y
38 },
39 .size = {
40 .w = container->width,
41 .h = container->height
42 }
43 };
44 container->cached_geometry = geo;
45 }
46 }
47
48 // Resize workspace if going from fullscreen -> notfullscreen
49 // otherwise just resize container
50 if (!current) {
51 arrange_windows(workspace, -1, -1);
52 workspace->fullscreen = container;
53 } else {
54 arrange_windows(container, -1, -1);
55 workspace->fullscreen = NULL;
56 }
57 ipc_event_window(container, "fullscreen_mode");
58
59 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
60}
diff --git a/sway/commands/gaps.c b/sway/commands/gaps.c
deleted file mode 100644
index 0a48592d..00000000
--- a/sway/commands/gaps.c
+++ /dev/null
@@ -1,166 +0,0 @@
1#include <ctype.h>
2#include <errno.h>
3#include <stdlib.h>
4#include <string.h>
5#include <strings.h>
6#include "sway/commands.h"
7#include "sway/container.h"
8#include "sway/focus.h"
9#include "sway/layout.h"
10
11struct cmd_results *cmd_gaps(int argc, char **argv) {
12 struct cmd_results *error = NULL;
13 if ((error = checkarg(argc, "gaps", EXPECTED_AT_LEAST, 1))) {
14 return error;
15 }
16 const char *expected_syntax =
17 "Expected 'gaps edge_gaps <on|off|toggle>' or "
18 "'gaps <inner|outer> <current|all|workspace> <set|plus|minus n>'";
19 const char *amount_str = argv[0];
20 // gaps amount
21 if (argc >= 1 && isdigit(*amount_str)) {
22 int amount = (int)strtol(amount_str, NULL, 10);
23 if (errno == ERANGE) {
24 errno = 0;
25 return cmd_results_new(CMD_INVALID, "gaps", "Number is out out of range.");
26 }
27 config->gaps_inner = config->gaps_outer = amount;
28 arrange_windows(&root_container, -1, -1);
29 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
30 }
31 // gaps inner|outer n
32 else if (argc >= 2 && isdigit((amount_str = argv[1])[0])) {
33 int amount = (int)strtol(amount_str, NULL, 10);
34 if (errno == ERANGE) {
35 errno = 0;
36 return cmd_results_new(CMD_INVALID, "gaps", "Number is out out of range.");
37 }
38 const char *target_str = argv[0];
39 if (strcasecmp(target_str, "inner") == 0) {
40 config->gaps_inner = amount;
41 } else if (strcasecmp(target_str, "outer") == 0) {
42 config->gaps_outer = amount;
43 }
44 arrange_windows(&root_container, -1, -1);
45 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
46 } else if (argc == 2 && strcasecmp(argv[0], "edge_gaps") == 0) {
47 // gaps edge_gaps <on|off|toggle>
48 if (strcasecmp(argv[1], "toggle") == 0) {
49 if (config->reading) {
50 return cmd_results_new(CMD_FAILURE, "gaps edge_gaps toggle",
51 "Can't be used in config file.");
52 }
53 config->edge_gaps = !config->edge_gaps;
54 } else {
55 config->edge_gaps =
56 (strcasecmp(argv[1], "yes") == 0 || strcasecmp(argv[1], "on") == 0);
57 }
58 arrange_windows(&root_container, -1, -1);
59 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
60 }
61 // gaps inner|outer current|all set|plus|minus n
62 if (argc < 4 || config->reading) {
63 return cmd_results_new(CMD_INVALID, "gaps", expected_syntax);
64 }
65 // gaps inner|outer ...
66 const char *inout_str = argv[0];
67 enum {INNER, OUTER} inout;
68 if (strcasecmp(inout_str, "inner") == 0) {
69 inout = INNER;
70 } else if (strcasecmp(inout_str, "outer") == 0) {
71 inout = OUTER;
72 } else {
73 return cmd_results_new(CMD_INVALID, "gaps", expected_syntax);
74 }
75
76 // gaps ... current|all ...
77 const char *target_str = argv[1];
78 enum {CURRENT, WORKSPACE, ALL} target;
79 if (strcasecmp(target_str, "current") == 0) {
80 target = CURRENT;
81 } else if (strcasecmp(target_str, "all") == 0) {
82 target = ALL;
83 } else if (strcasecmp(target_str, "workspace") == 0) {
84 if (inout == OUTER) {
85 target = CURRENT;
86 } else {
87 // Set gap for views in workspace
88 target = WORKSPACE;
89 }
90 } else {
91 return cmd_results_new(CMD_INVALID, "gaps", expected_syntax);
92 }
93
94 // gaps ... n
95 amount_str = argv[3];
96 int amount = (int)strtol(amount_str, NULL, 10);
97 if (errno == ERANGE) {
98 errno = 0;
99 return cmd_results_new(CMD_INVALID, "gaps", "Number is out out of range.");
100 }
101
102 // gaps ... set|plus|minus ...
103 const char *method_str = argv[2];
104 enum {SET, ADD} method;
105 if (strcasecmp(method_str, "set") == 0) {
106 method = SET;
107 } else if (strcasecmp(method_str, "plus") == 0) {
108 method = ADD;
109 } else if (strcasecmp(method_str, "minus") == 0) {
110 method = ADD;
111 amount *= -1;
112 } else {
113 return cmd_results_new(CMD_INVALID, "gaps", expected_syntax);
114 }
115
116 if (target == CURRENT) {
117 swayc_t *cont;
118 if (inout == OUTER) {
119 if ((cont = swayc_active_workspace()) == NULL) {
120 return cmd_results_new(CMD_FAILURE, "gaps", "There's no active workspace.");
121 }
122 } else {
123 if ((cont = get_focused_view(&root_container))->type != C_VIEW) {
124 return cmd_results_new(CMD_FAILURE, "gaps", "Currently focused item is not a view.");
125 }
126 }
127 cont->gaps = swayc_gap(cont);
128 if (method == SET) {
129 cont->gaps = amount;
130 } else if ((cont->gaps += amount) < 0) {
131 cont->gaps = 0;
132 }
133 arrange_windows(cont->parent, -1, -1);
134 } else if (inout == OUTER) {
135 //resize all workspace.
136 int i,j;
137 for (i = 0; i < root_container.children->length; ++i) {
138 swayc_t *op = root_container.children->items[i];
139 for (j = 0; j < op->children->length; ++j) {
140 swayc_t *ws = op->children->items[j];
141 if (method == SET) {
142 ws->gaps = amount;
143 } else if ((ws->gaps += amount) < 0) {
144 ws->gaps = 0;
145 }
146 }
147 }
148 arrange_windows(&root_container, -1, -1);
149 } else {
150 // Resize gaps for all views in workspace
151 swayc_t *top;
152 if (target == WORKSPACE) {
153 if ((top = swayc_active_workspace()) == NULL) {
154 return cmd_results_new(CMD_FAILURE, "gaps", "There's currently no active workspace.");
155 }
156 } else {
157 top = &root_container;
158 }
159 int top_gap = top->gaps;
160 container_map(top, method == SET ? set_gaps : add_gaps, &amount);
161 top->gaps = top_gap;
162 arrange_windows(top, -1, -1);
163 }
164
165 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
166}
diff --git a/sway/commands/hide_edge_borders.c b/sway/commands/hide_edge_borders.c
deleted file mode 100644
index ee2a2644..00000000
--- a/sway/commands/hide_edge_borders.c
+++ /dev/null
@@ -1,27 +0,0 @@
1#include <string.h>
2#include <strings.h>
3#include "sway/commands.h"
4
5struct cmd_results *cmd_hide_edge_borders(int argc, char **argv) {
6 struct cmd_results *error = NULL;
7 if ((error = checkarg(argc, "hide_edge_borders", EXPECTED_EQUAL_TO, 1))) {
8 return error;
9 }
10
11 if (strcasecmp(argv[0], "none") == 0) {
12 config->hide_edge_borders = E_NONE;
13 } else if (strcasecmp(argv[0], "vertical") == 0) {
14 config->hide_edge_borders = E_VERTICAL;
15 } else if (strcasecmp(argv[0], "horizontal") == 0) {
16 config->hide_edge_borders = E_HORIZONTAL;
17 } else if (strcasecmp(argv[0], "both") == 0) {
18 config->hide_edge_borders = E_BOTH;
19 } else if (strcasecmp(argv[0], "smart") == 0) {
20 config->hide_edge_borders = E_SMART;
21 } else {
22 return cmd_results_new(CMD_INVALID, "hide_edge_borders",
23 "Expected 'hide_edge_borders <none|vertical|horizontal|both>'");
24 }
25
26 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
27}
diff --git a/sway/commands/input.c b/sway/commands/input.c
index ad53d272..fa9cf05a 100644
--- a/sway/commands/input.c
+++ b/sway/commands/input.c
@@ -1,7 +1,7 @@
1#include <string.h> 1#include <string.h>
2#include <strings.h> 2#include <strings.h>
3#include "sway/commands.h" 3#include "sway/commands.h"
4#include "sway/input.h" 4#include "sway/input/input-manager.h"
5#include "log.h" 5#include "log.h"
6 6
7struct cmd_results *cmd_input(int argc, char **argv) { 7struct cmd_results *cmd_input(int argc, char **argv) {
@@ -11,45 +11,73 @@ struct cmd_results *cmd_input(int argc, char **argv) {
11 } 11 }
12 12
13 if (config->reading && strcmp("{", argv[1]) == 0) { 13 if (config->reading && strcmp("{", argv[1]) == 0) {
14 current_input_config = new_input_config(argv[0]); 14 free_input_config(config->handler_context.input_config);
15 sway_log(L_DEBUG, "entering input block: %s", current_input_config->identifier); 15 config->handler_context.input_config = new_input_config(argv[0]);
16 if (!config->handler_context.input_config) {
17 return cmd_results_new(CMD_FAILURE, NULL, "Couldn't allocate config");
18 }
19 wlr_log(L_DEBUG, "entering input block: %s", argv[0]);
16 return cmd_results_new(CMD_BLOCK_INPUT, NULL, NULL); 20 return cmd_results_new(CMD_BLOCK_INPUT, NULL, NULL);
17 } 21 }
18 22
19 if (argc > 2) { 23 if ((error = checkarg(argc, "input", EXPECTED_AT_LEAST, 3))) {
20 int argc_new = argc-2; 24 return error;
21 char **argv_new = argv+2; 25 }
22 26
23 struct cmd_results *res; 27 bool has_context = (config->handler_context.input_config != NULL);
24 current_input_config = new_input_config(argv[0]); 28 if (!has_context) {
25 if (strcasecmp("accel_profile", argv[1]) == 0) { 29 // caller did not give a context so create one just for this command
26 res = input_cmd_accel_profile(argc_new, argv_new); 30 config->handler_context.input_config = new_input_config(argv[0]);
27 } else if (strcasecmp("click_method", argv[1]) == 0) { 31 if (!config->handler_context.input_config) {
28 res = input_cmd_click_method(argc_new, argv_new); 32 return cmd_results_new(CMD_FAILURE, NULL, "Couldn't allocate config");
29 } else if (strcasecmp("drag_lock", argv[1]) == 0) {
30 res = input_cmd_drag_lock(argc_new, argv_new);
31 } else if (strcasecmp("dwt", argv[1]) == 0) {
32 res = input_cmd_dwt(argc_new, argv_new);
33 } else if (strcasecmp("events", argv[1]) == 0) {
34 res = input_cmd_events(argc_new, argv_new);
35 } else if (strcasecmp("left_handed", argv[1]) == 0) {
36 res = input_cmd_left_handed(argc_new, argv_new);
37 } else if (strcasecmp("middle_emulation", argv[1]) == 0) {
38 res = input_cmd_middle_emulation(argc_new, argv_new);
39 } else if (strcasecmp("natural_scroll", argv[1]) == 0) {
40 res = input_cmd_natural_scroll(argc_new, argv_new);
41 } else if (strcasecmp("pointer_accel", argv[1]) == 0) {
42 res = input_cmd_pointer_accel(argc_new, argv_new);
43 } else if (strcasecmp("scroll_method", argv[1]) == 0) {
44 res = input_cmd_scroll_method(argc_new, argv_new);
45 } else if (strcasecmp("tap", argv[1]) == 0) {
46 res = input_cmd_tap(argc_new, argv_new);
47 } else {
48 res = cmd_results_new(CMD_INVALID, "input <device>", "Unknown command %s", argv[1]);
49 } 33 }
50 current_input_config = NULL;
51 return res;
52 } 34 }
53 35
54 return cmd_results_new(CMD_BLOCK_INPUT, NULL, NULL); 36 int argc_new = argc-2;
37 char **argv_new = argv+2;
38
39 struct cmd_results *res;
40 if (strcasecmp("accel_profile", argv[1]) == 0) {
41 res = input_cmd_accel_profile(argc_new, argv_new);
42 } else if (strcasecmp("click_method", argv[1]) == 0) {
43 res = input_cmd_click_method(argc_new, argv_new);
44 } else if (strcasecmp("drag_lock", argv[1]) == 0) {
45 res = input_cmd_drag_lock(argc_new, argv_new);
46 } else if (strcasecmp("dwt", argv[1]) == 0) {
47 res = input_cmd_dwt(argc_new, argv_new);
48 } else if (strcasecmp("events", argv[1]) == 0) {
49 res = input_cmd_events(argc_new, argv_new);
50 } else if (strcasecmp("left_handed", argv[1]) == 0) {
51 res = input_cmd_left_handed(argc_new, argv_new);
52 } else if (strcasecmp("middle_emulation", argv[1]) == 0) {
53 res = input_cmd_middle_emulation(argc_new, argv_new);
54 } else if (strcasecmp("natural_scroll", argv[1]) == 0) {
55 res = input_cmd_natural_scroll(argc_new, argv_new);
56 } else if (strcasecmp("pointer_accel", argv[1]) == 0) {
57 res = input_cmd_pointer_accel(argc_new, argv_new);
58 } else if (strcasecmp("scroll_method", argv[1]) == 0) {
59 res = input_cmd_scroll_method(argc_new, argv_new);
60 } else if (strcasecmp("tap", argv[1]) == 0) {
61 res = input_cmd_tap(argc_new, argv_new);
62 } else if (strcasecmp("xkb_layout", argv[1]) == 0) {
63 res = input_cmd_xkb_layout(argc_new, argv_new);
64 } else if (strcasecmp("xkb_model", argv[1]) == 0) {
65 res = input_cmd_xkb_model(argc_new, argv_new);
66 } else if (strcasecmp("xkb_options", argv[1]) == 0) {
67 res = input_cmd_xkb_options(argc_new, argv_new);
68 } else if (strcasecmp("xkb_rules", argv[1]) == 0) {
69 res = input_cmd_xkb_rules(argc_new, argv_new);
70 } else if (strcasecmp("xkb_variant", argv[1]) == 0) {
71 res = input_cmd_xkb_variant(argc_new, argv_new);
72 } else {
73 res = cmd_results_new(CMD_INVALID, "input <device>", "Unknown command %s", argv[1]);
74 }
75
76 if (!has_context) {
77 // clean up the context we created earlier
78 free_input_config(config->handler_context.input_config);
79 config->handler_context.input_config = NULL;
80 }
81
82 return res;
55} 83}
diff --git a/sway/commands/input/accel_profile.c b/sway/commands/input/accel_profile.c
index 8288c1ad..37d6e133 100644
--- a/sway/commands/input/accel_profile.c
+++ b/sway/commands/input/accel_profile.c
@@ -1,17 +1,22 @@
1#include <string.h> 1#include <string.h>
2#include <strings.h> 2#include <strings.h>
3#include "sway/config.h"
3#include "sway/commands.h" 4#include "sway/commands.h"
4#include "sway/input.h" 5#include "sway/input/input-manager.h"
5 6
6struct cmd_results *input_cmd_accel_profile(int argc, char **argv) { 7struct cmd_results *input_cmd_accel_profile(int argc, char **argv) {
7 struct cmd_results *error = NULL; 8 struct cmd_results *error = NULL;
8 if ((error = checkarg(argc, "accel_profile", EXPECTED_AT_LEAST, 1))) { 9 if ((error = checkarg(argc, "accel_profile", EXPECTED_AT_LEAST, 1))) {
9 return error; 10 return error;
10 } 11 }
12 struct input_config *current_input_config =
13 config->handler_context.input_config;
11 if (!current_input_config) { 14 if (!current_input_config) {
12 return cmd_results_new(CMD_FAILURE, "accel_profile", "No input device defined."); 15 return cmd_results_new(CMD_FAILURE, "accel_profile",
16 "No input device defined.");
13 } 17 }
14 struct input_config *new_config = new_input_config(current_input_config->identifier); 18 struct input_config *new_config =
19 new_input_config(current_input_config->identifier);
15 20
16 if (strcasecmp(argv[0], "adaptive") == 0) { 21 if (strcasecmp(argv[0], "adaptive") == 0) {
17 new_config->accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; 22 new_config->accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE;
@@ -22,6 +27,6 @@ struct cmd_results *input_cmd_accel_profile(int argc, char **argv) {
22 "Expected 'accel_profile <adaptive|flat>'"); 27 "Expected 'accel_profile <adaptive|flat>'");
23 } 28 }
24 29
25 input_cmd_apply(new_config); 30 apply_input_config(new_config);
26 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 31 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
27} 32}
diff --git a/sway/commands/input/click_method.c b/sway/commands/input/click_method.c
index 5e9d3dcb..8f1f0aa7 100644
--- a/sway/commands/input/click_method.c
+++ b/sway/commands/input/click_method.c
@@ -1,19 +1,23 @@
1#include <string.h> 1#include <string.h>
2#include <strings.h> 2#include <strings.h>
3#include "sway/commands.h" 3#include "sway/commands.h"
4#include "sway/input.h" 4#include "sway/config.h"
5#include "sway/input/input-manager.h"
5#include "log.h" 6#include "log.h"
6 7
7struct cmd_results *input_cmd_click_method(int argc, char **argv) { 8struct cmd_results *input_cmd_click_method(int argc, char **argv) {
8 sway_log(L_DEBUG, "click_method for device: %d %s", current_input_config==NULL, current_input_config->identifier);
9 struct cmd_results *error = NULL; 9 struct cmd_results *error = NULL;
10 if ((error = checkarg(argc, "click_method", EXPECTED_AT_LEAST, 1))) { 10 if ((error = checkarg(argc, "click_method", EXPECTED_AT_LEAST, 1))) {
11 return error; 11 return error;
12 } 12 }
13 struct input_config *current_input_config =
14 config->handler_context.input_config;
13 if (!current_input_config) { 15 if (!current_input_config) {
14 return cmd_results_new(CMD_FAILURE, "click_method", "No input device defined."); 16 return cmd_results_new(CMD_FAILURE, "click_method",
17 "No input device defined.");
15 } 18 }
16 struct input_config *new_config = new_input_config(current_input_config->identifier); 19 struct input_config *new_config =
20 new_input_config(current_input_config->identifier);
17 21
18 if (strcasecmp(argv[0], "none") == 0) { 22 if (strcasecmp(argv[0], "none") == 0) {
19 new_config->click_method = LIBINPUT_CONFIG_CLICK_METHOD_NONE; 23 new_config->click_method = LIBINPUT_CONFIG_CLICK_METHOD_NONE;
@@ -22,9 +26,10 @@ struct cmd_results *input_cmd_click_method(int argc, char **argv) {
22 } else if (strcasecmp(argv[0], "clickfinger") == 0) { 26 } else if (strcasecmp(argv[0], "clickfinger") == 0) {
23 new_config->click_method = LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER; 27 new_config->click_method = LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER;
24 } else { 28 } else {
25 return cmd_results_new(CMD_INVALID, "click_method", "Expected 'click_method <none|button_areas|clickfinger'"); 29 return cmd_results_new(CMD_INVALID, "click_method",
30 "Expected 'click_method <none|button_areas|clickfinger'");
26 } 31 }
27 32
28 input_cmd_apply(new_config); 33 apply_input_config(new_config);
29 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 34 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
30} 35}
diff --git a/sway/commands/input/drag_lock.c b/sway/commands/input/drag_lock.c
index f5a7beb4..8273a7d4 100644
--- a/sway/commands/input/drag_lock.c
+++ b/sway/commands/input/drag_lock.c
@@ -1,26 +1,32 @@
1#include <string.h> 1#include <string.h>
2#include <strings.h> 2#include <strings.h>
3#include "sway/config.h"
3#include "sway/commands.h" 4#include "sway/commands.h"
4#include "sway/input.h" 5#include "sway/input/input-manager.h"
5 6
6struct cmd_results *input_cmd_drag_lock(int argc, char **argv) { 7struct cmd_results *input_cmd_drag_lock(int argc, char **argv) {
7 struct cmd_results *error = NULL; 8 struct cmd_results *error = NULL;
8 if ((error = checkarg(argc, "drag_lock", EXPECTED_AT_LEAST, 1))) { 9 if ((error = checkarg(argc, "drag_lock", EXPECTED_AT_LEAST, 1))) {
9 return error; 10 return error;
10 } 11 }
12 struct input_config *current_input_config =
13 config->handler_context.input_config;
11 if (!current_input_config) { 14 if (!current_input_config) {
12 return cmd_results_new(CMD_FAILURE, "drag_lock", "No input device defined."); 15 return cmd_results_new(CMD_FAILURE,
16 "drag_lock", "No input device defined.");
13 } 17 }
14 struct input_config *new_config = new_input_config(current_input_config->identifier); 18 struct input_config *new_config =
19 new_input_config(current_input_config->identifier);
15 20
16 if (strcasecmp(argv[0], "enabled") == 0) { 21 if (strcasecmp(argv[0], "enabled") == 0) {
17 new_config->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_ENABLED; 22 new_config->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_ENABLED;
18 } else if (strcasecmp(argv[0], "disabled") == 0) { 23 } else if (strcasecmp(argv[0], "disabled") == 0) {
19 new_config->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_DISABLED; 24 new_config->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_DISABLED;
20 } else { 25 } else {
21 return cmd_results_new(CMD_INVALID, "drag_lock", "Expected 'drag_lock <enabled|disabled>'"); 26 return cmd_results_new(CMD_INVALID, "drag_lock",
27 "Expected 'drag_lock <enabled|disabled>'");
22 } 28 }
23 29
24 input_cmd_apply(new_config); 30 apply_input_config(new_config);
25 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 31 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
26} 32}
diff --git a/sway/commands/input/dwt.c b/sway/commands/input/dwt.c
index 557b2207..995a2f47 100644
--- a/sway/commands/input/dwt.c
+++ b/sway/commands/input/dwt.c
@@ -1,26 +1,31 @@
1#include <string.h> 1#include <string.h>
2#include <strings.h> 2#include <strings.h>
3#include "sway/config.h"
3#include "sway/commands.h" 4#include "sway/commands.h"
4#include "sway/input.h" 5#include "sway/input/input-manager.h"
5 6
6struct cmd_results *input_cmd_dwt(int argc, char **argv) { 7struct cmd_results *input_cmd_dwt(int argc, char **argv) {
7 struct cmd_results *error = NULL; 8 struct cmd_results *error = NULL;
8 if ((error = checkarg(argc, "dwt", EXPECTED_AT_LEAST, 1))) { 9 if ((error = checkarg(argc, "dwt", EXPECTED_AT_LEAST, 1))) {
9 return error; 10 return error;
10 } 11 }
12 struct input_config *current_input_config =
13 config->handler_context.input_config;
11 if (!current_input_config) { 14 if (!current_input_config) {
12 return cmd_results_new(CMD_FAILURE, "dwt", "No input device defined."); 15 return cmd_results_new(CMD_FAILURE, "dwt", "No input device defined.");
13 } 16 }
14 struct input_config *new_config = new_input_config(current_input_config->identifier); 17 struct input_config *new_config =
18 new_input_config(current_input_config->identifier);
15 19
16 if (strcasecmp(argv[0], "enabled") == 0) { 20 if (strcasecmp(argv[0], "enabled") == 0) {
17 new_config->dwt = LIBINPUT_CONFIG_DWT_ENABLED; 21 new_config->dwt = LIBINPUT_CONFIG_DWT_ENABLED;
18 } else if (strcasecmp(argv[0], "disabled") == 0) { 22 } else if (strcasecmp(argv[0], "disabled") == 0) {
19 new_config->dwt = LIBINPUT_CONFIG_DWT_DISABLED; 23 new_config->dwt = LIBINPUT_CONFIG_DWT_DISABLED;
20 } else { 24 } else {
21 return cmd_results_new(CMD_INVALID, "dwt", "Expected 'dwt <enabled|disabled>'"); 25 return cmd_results_new(CMD_INVALID, "dwt",
26 "Expected 'dwt <enabled|disabled>'");
22 } 27 }
23 28
24 input_cmd_apply(new_config); 29 apply_input_config(new_config);
25 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 30 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
26} 31}
diff --git a/sway/commands/input/events.c b/sway/commands/input/events.c
index 9d54287a..2217f5ce 100644
--- a/sway/commands/input/events.c
+++ b/sway/commands/input/events.c
@@ -1,30 +1,38 @@
1#include <string.h> 1#include <string.h>
2#include <strings.h> 2#include <strings.h>
3#include "sway/config.h"
3#include "sway/commands.h" 4#include "sway/commands.h"
4#include "sway/input.h" 5#include "sway/input/input-manager.h"
5#include "log.h" 6#include "log.h"
6 7
7struct cmd_results *input_cmd_events(int argc, char **argv) { 8struct cmd_results *input_cmd_events(int argc, char **argv) {
8 sway_log(L_DEBUG, "events for device: %s", current_input_config->identifier);
9 struct cmd_results *error = NULL; 9 struct cmd_results *error = NULL;
10 if ((error = checkarg(argc, "events", EXPECTED_AT_LEAST, 1))) { 10 if ((error = checkarg(argc, "events", EXPECTED_AT_LEAST, 1))) {
11 return error; 11 return error;
12 } 12 }
13 struct input_config *current_input_config =
14 config->handler_context.input_config;
13 if (!current_input_config) { 15 if (!current_input_config) {
14 return cmd_results_new(CMD_FAILURE, "events", "No input device defined."); 16 return cmd_results_new(CMD_FAILURE, "events",
17 "No input device defined.");
15 } 18 }
16 struct input_config *new_config = new_input_config(current_input_config->identifier); 19 wlr_log(L_DEBUG, "events for device: %s",
20 current_input_config->identifier);
21 struct input_config *new_config =
22 new_input_config(current_input_config->identifier);
17 23
18 if (strcasecmp(argv[0], "enabled") == 0) { 24 if (strcasecmp(argv[0], "enabled") == 0) {
19 new_config->send_events = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; 25 new_config->send_events = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED;
20 } else if (strcasecmp(argv[0], "disabled") == 0) { 26 } else if (strcasecmp(argv[0], "disabled") == 0) {
21 new_config->send_events = LIBINPUT_CONFIG_SEND_EVENTS_DISABLED; 27 new_config->send_events = LIBINPUT_CONFIG_SEND_EVENTS_DISABLED;
22 } else if (strcasecmp(argv[0], "disabled_on_external_mouse") == 0) { 28 } else if (strcasecmp(argv[0], "disabled_on_external_mouse") == 0) {
23 new_config->send_events = LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE; 29 new_config->send_events =
30 LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE;
24 } else { 31 } else {
25 return cmd_results_new(CMD_INVALID, "events", "Expected 'events <enabled|disabled|disabled_on_external_mouse>'"); 32 return cmd_results_new(CMD_INVALID, "events",
33 "Expected 'events <enabled|disabled|disabled_on_external_mouse>'");
26 } 34 }
27 35
28 input_cmd_apply(new_config); 36 apply_input_config(new_config);
29 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 37 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
30} 38}
diff --git a/sway/commands/input/left_handed.c b/sway/commands/input/left_handed.c
index 6c913e70..94b8e03e 100644
--- a/sway/commands/input/left_handed.c
+++ b/sway/commands/input/left_handed.c
@@ -1,26 +1,32 @@
1#include <string.h> 1#include <string.h>
2#include <strings.h> 2#include <strings.h>
3#include "sway/config.h"
3#include "sway/commands.h" 4#include "sway/commands.h"
4#include "sway/input.h" 5#include "sway/input/input-manager.h"
5 6
6struct cmd_results *input_cmd_left_handed(int argc, char **argv) { 7struct cmd_results *input_cmd_left_handed(int argc, char **argv) {
7 struct cmd_results *error = NULL; 8 struct cmd_results *error = NULL;
8 if ((error = checkarg(argc, "left_handed", EXPECTED_AT_LEAST, 1))) { 9 if ((error = checkarg(argc, "left_handed", EXPECTED_AT_LEAST, 1))) {
9 return error; 10 return error;
10 } 11 }
12 struct input_config *current_input_config =
13 config->handler_context.input_config;
11 if (!current_input_config) { 14 if (!current_input_config) {
12 return cmd_results_new(CMD_FAILURE, "left_handed", "No input device defined."); 15 return cmd_results_new(CMD_FAILURE, "left_handed",
16 "No input device defined.");
13 } 17 }
14 struct input_config *new_config = new_input_config(current_input_config->identifier); 18 struct input_config *new_config =
19 new_input_config(current_input_config->identifier);
15 20
16 if (strcasecmp(argv[0], "enabled") == 0) { 21 if (strcasecmp(argv[0], "enabled") == 0) {
17 new_config->left_handed = 1; 22 new_config->left_handed = 1;
18 } else if (strcasecmp(argv[0], "disabled") == 0) { 23 } else if (strcasecmp(argv[0], "disabled") == 0) {
19 new_config->left_handed = 0; 24 new_config->left_handed = 0;
20 } else { 25 } else {
21 return cmd_results_new(CMD_INVALID, "left_handed", "Expected 'left_handed <enabled|disabled>'"); 26 return cmd_results_new(CMD_INVALID, "left_handed",
27 "Expected 'left_handed <enabled|disabled>'");
22 } 28 }
23 29
24 input_cmd_apply(new_config); 30 apply_input_config(new_config);
25 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 31 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
26} 32}
diff --git a/sway/commands/input/map_to_output.c b/sway/commands/input/map_to_output.c
new file mode 100644
index 00000000..60e4608e
--- /dev/null
+++ b/sway/commands/input/map_to_output.c
@@ -0,0 +1,27 @@
1#define _POSIX_C_SOURCE 200809L
2#include <string.h>
3#include <strings.h>
4#include "sway/config.h"
5#include "sway/commands.h"
6#include "sway/input/input-manager.h"
7#include "log.h"
8
9struct cmd_results *input_cmd_map_to_output(int argc, char **argv) {
10 struct cmd_results *error = NULL;
11 if ((error = checkarg(argc, "map_to_output", EXPECTED_EQUAL_TO, 1))) {
12 return error;
13 }
14 struct input_config *current_input_config =
15 config->handler_context.input_config;
16 if (!current_input_config) {
17 return cmd_results_new(CMD_FAILURE, "map_to_output",
18 "No input device defined.");
19 }
20 struct input_config *new_config =
21 new_input_config(current_input_config->identifier);
22
23 new_config->mapped_output = strdup(argv[0]);
24 apply_input_config(new_config);
25
26 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
27}
diff --git a/sway/commands/input/middle_emulation.c b/sway/commands/input/middle_emulation.c
index 33cdd7d6..a551fd51 100644
--- a/sway/commands/input/middle_emulation.c
+++ b/sway/commands/input/middle_emulation.c
@@ -1,26 +1,33 @@
1#include <string.h> 1#include <string.h>
2#include <strings.h> 2#include <strings.h>
3#include "sway/config.h"
3#include "sway/commands.h" 4#include "sway/commands.h"
4#include "sway/input.h" 5#include "sway/input/input-manager.h"
5 6
6struct cmd_results *input_cmd_middle_emulation(int argc, char **argv) { 7struct cmd_results *input_cmd_middle_emulation(int argc, char **argv) {
7 struct cmd_results *error = NULL; 8 struct cmd_results *error = NULL;
8 if ((error = checkarg(argc, "middle_emulation", EXPECTED_AT_LEAST, 1))) { 9 if ((error = checkarg(argc, "middle_emulation", EXPECTED_AT_LEAST, 1))) {
9 return error; 10 return error;
10 } 11 }
12 struct input_config *current_input_config =
13 config->handler_context.input_config;
11 if (!current_input_config) { 14 if (!current_input_config) {
12 return cmd_results_new(CMD_FAILURE, "middle_emulation", "No input device defined."); 15 return cmd_results_new(CMD_FAILURE, "middle_emulation",
16 "No input device defined.");
13 } 17 }
14 struct input_config *new_config = new_input_config(current_input_config->identifier); 18 struct input_config *new_config =
19 new_input_config(current_input_config->identifier);
15 20
16 if (strcasecmp(argv[0], "enabled") == 0) { 21 if (strcasecmp(argv[0], "enabled") == 0) {
17 new_config->middle_emulation = LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED; 22 new_config->middle_emulation = LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED;
18 } else if (strcasecmp(argv[0], "disabled") == 0) { 23 } else if (strcasecmp(argv[0], "disabled") == 0) {
19 new_config->middle_emulation = LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED; 24 new_config->middle_emulation =
25 LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED;
20 } else { 26 } else {
21 return cmd_results_new(CMD_INVALID, "middle_emulation", "Expected 'middle_emulation <enabled|disabled>'"); 27 return cmd_results_new(CMD_INVALID, "middle_emulation",
28 "Expected 'middle_emulation <enabled|disabled>'");
22 } 29 }
23 30
24 input_cmd_apply(new_config); 31 apply_input_config(new_config);
25 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 32 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
26} 33}
diff --git a/sway/commands/input/natural_scroll.c b/sway/commands/input/natural_scroll.c
index 7bc8b8d0..c4e19b78 100644
--- a/sway/commands/input/natural_scroll.c
+++ b/sway/commands/input/natural_scroll.c
@@ -1,26 +1,32 @@
1#include <string.h> 1#include <string.h>
2#include <strings.h> 2#include <strings.h>
3#include "sway/config.h"
3#include "sway/commands.h" 4#include "sway/commands.h"
4#include "sway/input.h" 5#include "sway/input/input-manager.h"
5 6
6struct cmd_results *input_cmd_natural_scroll(int argc, char **argv) { 7struct cmd_results *input_cmd_natural_scroll(int argc, char **argv) {
7 struct cmd_results *error = NULL; 8 struct cmd_results *error = NULL;
8 if ((error = checkarg(argc, "natural_scroll", EXPECTED_AT_LEAST, 1))) { 9 if ((error = checkarg(argc, "natural_scroll", EXPECTED_AT_LEAST, 1))) {
9 return error; 10 return error;
10 } 11 }
12 struct input_config *current_input_config =
13 config->handler_context.input_config;
11 if (!current_input_config) { 14 if (!current_input_config) {
12 return cmd_results_new(CMD_FAILURE, "natural_scoll", "No input device defined."); 15 return cmd_results_new(CMD_FAILURE, "natural_scoll",
16 "No input device defined.");
13 } 17 }
14 struct input_config *new_config = new_input_config(current_input_config->identifier); 18 struct input_config *new_config =
19 new_input_config(current_input_config->identifier);
15 20
16 if (strcasecmp(argv[0], "enabled") == 0) { 21 if (strcasecmp(argv[0], "enabled") == 0) {
17 new_config->natural_scroll = 1; 22 new_config->natural_scroll = 1;
18 } else if (strcasecmp(argv[0], "disabled") == 0) { 23 } else if (strcasecmp(argv[0], "disabled") == 0) {
19 new_config->natural_scroll = 0; 24 new_config->natural_scroll = 0;
20 } else { 25 } else {
21 return cmd_results_new(CMD_INVALID, "natural_scroll", "Expected 'natural_scroll <enabled|disabled>'"); 26 return cmd_results_new(CMD_INVALID, "natural_scroll",
27 "Expected 'natural_scroll <enabled|disabled>'");
22 } 28 }
23 29
24 input_cmd_apply(new_config); 30 apply_input_config(new_config);
25 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 31 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
26} 32}
diff --git a/sway/commands/input/pointer_accel.c b/sway/commands/input/pointer_accel.c
index 94f595d1..171063aa 100644
--- a/sway/commands/input/pointer_accel.c
+++ b/sway/commands/input/pointer_accel.c
@@ -1,24 +1,30 @@
1#include <stdlib.h> 1#include <stdlib.h>
2#include <string.h> 2#include <string.h>
3#include "sway/config.h"
3#include "sway/commands.h" 4#include "sway/commands.h"
4#include "sway/input.h" 5#include "sway/input/input-manager.h"
5 6
6struct cmd_results *input_cmd_pointer_accel(int argc, char **argv) { 7struct cmd_results *input_cmd_pointer_accel(int argc, char **argv) {
7 struct cmd_results *error = NULL; 8 struct cmd_results *error = NULL;
8 if ((error = checkarg(argc, "pointer_accel", EXPECTED_AT_LEAST, 1))) { 9 if ((error = checkarg(argc, "pointer_accel", EXPECTED_AT_LEAST, 1))) {
9 return error; 10 return error;
10 } 11 }
12 struct input_config *current_input_config =
13 config->handler_context.input_config;
11 if (!current_input_config) { 14 if (!current_input_config) {
12 return cmd_results_new(CMD_FAILURE, "pointer_accel", "No input device defined."); 15 return cmd_results_new(CMD_FAILURE,
16 "pointer_accel", "No input device defined.");
13 } 17 }
14 struct input_config *new_config = new_input_config(current_input_config->identifier); 18 struct input_config *new_config =
19 new_input_config(current_input_config->identifier);
15 20
16 float pointer_accel = atof(argv[0]); 21 float pointer_accel = atof(argv[0]);
17 if (pointer_accel < -1 || pointer_accel > 1) { 22 if (pointer_accel < -1 || pointer_accel > 1) {
18 return cmd_results_new(CMD_INVALID, "pointer_accel", "Input out of range [-1, 1]"); 23 return cmd_results_new(CMD_INVALID, "pointer_accel",
24 "Input out of range [-1, 1]");
19 } 25 }
20 new_config->pointer_accel = pointer_accel; 26 new_config->pointer_accel = pointer_accel;
21 27
22 input_cmd_apply(new_config); 28 apply_input_config(new_config);
23 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 29 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
24} 30}
diff --git a/sway/commands/input/scroll_method.c b/sway/commands/input/scroll_method.c
index 5c6c3d7a..0a1c57ac 100644
--- a/sway/commands/input/scroll_method.c
+++ b/sway/commands/input/scroll_method.c
@@ -1,17 +1,22 @@
1#include <string.h> 1#include <string.h>
2#include <strings.h> 2#include <strings.h>
3#include "sway/config.h"
3#include "sway/commands.h" 4#include "sway/commands.h"
4#include "sway/input.h" 5#include "sway/input/input-manager.h"
5 6
6struct cmd_results *input_cmd_scroll_method(int argc, char **argv) { 7struct cmd_results *input_cmd_scroll_method(int argc, char **argv) {
7 struct cmd_results *error = NULL; 8 struct cmd_results *error = NULL;
8 if ((error = checkarg(argc, "scroll_method", EXPECTED_AT_LEAST, 1))) { 9 if ((error = checkarg(argc, "scroll_method", EXPECTED_AT_LEAST, 1))) {
9 return error; 10 return error;
10 } 11 }
12 struct input_config *current_input_config =
13 config->handler_context.input_config;
11 if (!current_input_config) { 14 if (!current_input_config) {
12 return cmd_results_new(CMD_FAILURE, "scroll_method", "No input device defined."); 15 return cmd_results_new(CMD_FAILURE, "scroll_method",
16 "No input device defined.");
13 } 17 }
14 struct input_config *new_config = new_input_config(current_input_config->identifier); 18 struct input_config *new_config =
19 new_input_config(current_input_config->identifier);
15 20
16 if (strcasecmp(argv[0], "none") == 0) { 21 if (strcasecmp(argv[0], "none") == 0) {
17 new_config->scroll_method = LIBINPUT_CONFIG_SCROLL_NO_SCROLL; 22 new_config->scroll_method = LIBINPUT_CONFIG_SCROLL_NO_SCROLL;
@@ -22,9 +27,10 @@ struct cmd_results *input_cmd_scroll_method(int argc, char **argv) {
22 } else if (strcasecmp(argv[0], "on_button_down") == 0) { 27 } else if (strcasecmp(argv[0], "on_button_down") == 0) {
23 new_config->scroll_method = LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; 28 new_config->scroll_method = LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN;
24 } else { 29 } else {
25 return cmd_results_new(CMD_INVALID, "scroll_method", "Expected 'scroll_method <none|two_finger|edge|on_button_down>'"); 30 return cmd_results_new(CMD_INVALID, "scroll_method",
31 "Expected 'scroll_method <none|two_finger|edge|on_button_down>'");
26 } 32 }
27 33
28 input_cmd_apply(new_config); 34 apply_input_config(new_config);
29 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 35 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
30} 36}
diff --git a/sway/commands/input/tap.c b/sway/commands/input/tap.c
index 9e3ca2af..e7f03058 100644
--- a/sway/commands/input/tap.c
+++ b/sway/commands/input/tap.c
@@ -1,29 +1,34 @@
1#include <string.h> 1#include <string.h>
2#include <strings.h> 2#include <strings.h>
3#include "sway/config.h"
3#include "sway/commands.h" 4#include "sway/commands.h"
4#include "sway/input.h" 5#include "sway/input/input-manager.h"
5#include "log.h" 6#include "log.h"
6 7
7struct cmd_results *input_cmd_tap(int argc, char **argv) { 8struct cmd_results *input_cmd_tap(int argc, char **argv) {
8 sway_log(L_DEBUG, "tap for device: %s", current_input_config->identifier);
9 struct cmd_results *error = NULL; 9 struct cmd_results *error = NULL;
10 if ((error = checkarg(argc, "tap", EXPECTED_AT_LEAST, 1))) { 10 if ((error = checkarg(argc, "tap", EXPECTED_AT_LEAST, 1))) {
11 return error; 11 return error;
12 } 12 }
13 struct input_config *current_input_config =
14 config->handler_context.input_config;
13 if (!current_input_config) { 15 if (!current_input_config) {
14 return cmd_results_new(CMD_FAILURE, "tap", "No input device defined."); 16 return cmd_results_new(CMD_FAILURE, "tap", "No input device defined.");
15 } 17 }
16 struct input_config *new_config = new_input_config(current_input_config->identifier); 18 struct input_config *new_config =
19 new_input_config(current_input_config->identifier);
17 20
18 if (strcasecmp(argv[0], "enabled") == 0) { 21 if (strcasecmp(argv[0], "enabled") == 0) {
19 new_config->tap = LIBINPUT_CONFIG_TAP_ENABLED; 22 new_config->tap = LIBINPUT_CONFIG_TAP_ENABLED;
20 } else if (strcasecmp(argv[0], "disabled") == 0) { 23 } else if (strcasecmp(argv[0], "disabled") == 0) {
21 new_config->tap = LIBINPUT_CONFIG_TAP_DISABLED; 24 new_config->tap = LIBINPUT_CONFIG_TAP_DISABLED;
22 } else { 25 } else {
23 return cmd_results_new(CMD_INVALID, "tap", "Expected 'tap <enabled|disabled>'"); 26 return cmd_results_new(CMD_INVALID, "tap",
27 "Expected 'tap <enabled|disabled>'");
24 } 28 }
25 29
26 sway_log(L_DEBUG, "apply-tap for device: %s", current_input_config->identifier); 30 wlr_log(L_DEBUG, "apply-tap for device: %s",
27 input_cmd_apply(new_config); 31 current_input_config->identifier);
32 apply_input_config(new_config);
28 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 33 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
29} 34}
diff --git a/sway/commands/input/xkb_layout.c b/sway/commands/input/xkb_layout.c
new file mode 100644
index 00000000..867e65d3
--- /dev/null
+++ b/sway/commands/input/xkb_layout.c
@@ -0,0 +1,26 @@
1#define _XOPEN_SOURCE 700
2#include "sway/config.h"
3#include "sway/commands.h"
4#include "sway/input/input-manager.h"
5#include "log.h"
6
7struct cmd_results *input_cmd_xkb_layout(int argc, char **argv) {
8 struct cmd_results *error = NULL;
9 if ((error = checkarg(argc, "xkb_layout", EXPECTED_EQUAL_TO, 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, "xkb_layout", "No input device defined.");
16 }
17 struct input_config *new_config =
18 new_input_config(current_input_config->identifier);
19
20 new_config->xkb_layout = strdup(argv[0]);
21
22 wlr_log(L_DEBUG, "apply-xkb_layout for device: %s layout: %s",
23 current_input_config->identifier, new_config->xkb_layout);
24 apply_input_config(new_config);
25 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
26}
diff --git a/sway/commands/input/xkb_model.c b/sway/commands/input/xkb_model.c
new file mode 100644
index 00000000..e8c8e04e
--- /dev/null
+++ b/sway/commands/input/xkb_model.c
@@ -0,0 +1,26 @@
1#define _XOPEN_SOURCE 700
2#include "sway/config.h"
3#include "sway/commands.h"
4#include "sway/input/input-manager.h"
5#include "log.h"
6
7struct cmd_results *input_cmd_xkb_model(int argc, char **argv) {
8 struct cmd_results *error = NULL;
9 if ((error = checkarg(argc, "xkb_model", EXPECTED_EQUAL_TO, 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, "xkb_model", "No input device defined.");
16 }
17 struct input_config *new_config =
18 new_input_config(current_input_config->identifier);
19
20 new_config->xkb_model = strdup(argv[0]);
21
22 wlr_log(L_DEBUG, "apply-xkb_model for device: %s model: %s",
23 current_input_config->identifier, new_config->xkb_model);
24 apply_input_config(new_config);
25 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
26}
diff --git a/sway/commands/input/xkb_options.c b/sway/commands/input/xkb_options.c
new file mode 100644
index 00000000..e9ddd6e3
--- /dev/null
+++ b/sway/commands/input/xkb_options.c
@@ -0,0 +1,26 @@
1#define _XOPEN_SOURCE 700
2#include "sway/config.h"
3#include "sway/commands.h"
4#include "sway/input/input-manager.h"
5#include "log.h"
6
7struct cmd_results *input_cmd_xkb_options(int argc, char **argv) {
8 struct cmd_results *error = NULL;
9 if ((error = checkarg(argc, "xkb_options", EXPECTED_EQUAL_TO, 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, "xkb_options", "No input device defined.");
16 }
17 struct input_config *new_config =
18 new_input_config(current_input_config->identifier);
19
20 new_config->xkb_options = strdup(argv[0]);
21
22 wlr_log(L_DEBUG, "apply-xkb_options for device: %s options: %s",
23 current_input_config->identifier, new_config->xkb_options);
24 apply_input_config(new_config);
25 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
26}
diff --git a/sway/commands/input/xkb_rules.c b/sway/commands/input/xkb_rules.c
new file mode 100644
index 00000000..926d0ac1
--- /dev/null
+++ b/sway/commands/input/xkb_rules.c
@@ -0,0 +1,26 @@
1#define _XOPEN_SOURCE 700
2#include "sway/config.h"
3#include "sway/commands.h"
4#include "sway/input/input-manager.h"
5#include "log.h"
6
7struct cmd_results *input_cmd_xkb_rules(int argc, char **argv) {
8 struct cmd_results *error = NULL;
9 if ((error = checkarg(argc, "xkb_rules", EXPECTED_EQUAL_TO, 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, "xkb_rules", "No input device defined.");
16 }
17 struct input_config *new_config =
18 new_input_config(current_input_config->identifier);
19
20 new_config->xkb_rules = strdup(argv[0]);
21
22 wlr_log(L_DEBUG, "apply-xkb_rules for device: %s rules: %s",
23 current_input_config->identifier, new_config->xkb_rules);
24 apply_input_config(new_config);
25 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
26}
diff --git a/sway/commands/input/xkb_variant.c b/sway/commands/input/xkb_variant.c
new file mode 100644
index 00000000..0e3ffd41
--- /dev/null
+++ b/sway/commands/input/xkb_variant.c
@@ -0,0 +1,26 @@
1#define _XOPEN_SOURCE 700
2#include "sway/config.h"
3#include "sway/commands.h"
4#include "sway/input/input-manager.h"
5#include "log.h"
6
7struct cmd_results *input_cmd_xkb_variant(int argc, char **argv) {
8 struct cmd_results *error = NULL;
9 if ((error = checkarg(argc, "xkb_variant", EXPECTED_EQUAL_TO, 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, "xkb_variant", "No input device defined.");
16 }
17 struct input_config *new_config =
18 new_input_config(current_input_config->identifier);
19
20 new_config->xkb_variant = strdup(argv[0]);
21
22 wlr_log(L_DEBUG, "apply-xkb_variant for device: %s variant: %s",
23 current_input_config->identifier, new_config->xkb_variant);
24 apply_input_config(new_config);
25 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
26}
diff --git a/sway/commands/ipc.c b/sway/commands/ipc.c
deleted file mode 100644
index 0c678961..00000000
--- a/sway/commands/ipc.c
+++ /dev/null
@@ -1,162 +0,0 @@
1#define _XOPEN_SOURCE 500
2#include <stdio.h>
3#include <string.h>
4#include "sway/security.h"
5#include "sway/commands.h"
6#include "sway/config.h"
7#include "ipc.h"
8#include "log.h"
9#include "util.h"
10
11static struct ipc_policy *current_policy = NULL;
12
13struct cmd_results *cmd_ipc(int argc, char **argv) {
14 struct cmd_results *error = NULL;
15 if ((error = checkarg(argc, "ipc", EXPECTED_EQUAL_TO, 2))) {
16 return error;
17 }
18 if ((error = check_security_config())) {
19 return error;
20 }
21
22 char *program = NULL;
23
24 if (!strcmp(argv[0], "*")) {
25 program = strdup(argv[0]);
26 } else if (!(program = resolve_path(argv[0]))) {
27 return cmd_results_new(
28 CMD_INVALID, "ipc", "Unable to resolve IPC Policy target.");
29 }
30 if (config->reading && strcmp("{", argv[1]) != 0) {
31 return cmd_results_new(CMD_INVALID, "ipc",
32 "Expected '{' at start of IPC config definition.");
33 }
34
35 if (!config->reading) {
36 return cmd_results_new(CMD_FAILURE, "ipc", "Can only be used in config file.");
37 }
38
39 current_policy = alloc_ipc_policy(program);
40 list_add(config->ipc_policies, current_policy);
41
42 free(program);
43 return cmd_results_new(CMD_BLOCK_IPC, NULL, NULL);
44}
45
46struct cmd_results *cmd_ipc_events(int argc, char **argv) {
47 struct cmd_results *error = NULL;
48 if ((error = checkarg(argc, "events", EXPECTED_EQUAL_TO, 1))) {
49 return error;
50 }
51
52 if (config->reading && strcmp("{", argv[0]) != 0) {
53 return cmd_results_new(CMD_INVALID, "events",
54 "Expected '{' at start of IPC event config definition.");
55 }
56
57 if (!config->reading) {
58 return cmd_results_new(CMD_FAILURE, "events", "Can only be used in config file.");
59 }
60
61 return cmd_results_new(CMD_BLOCK_IPC_EVENTS, NULL, NULL);
62}
63
64struct cmd_results *cmd_ipc_cmd(int argc, char **argv) {
65 struct cmd_results *error = NULL;
66 if ((error = checkarg(argc, "ipc", EXPECTED_EQUAL_TO, 1))) {
67 return error;
68 }
69
70 bool enabled;
71 if (strcmp(argv[0], "enabled") == 0) {
72 enabled = true;
73 } else if (strcmp(argv[0], "disabled") == 0) {
74 enabled = false;
75 } else {
76 return cmd_results_new(CMD_INVALID, argv[-1],
77 "Argument must be one of 'enabled' or 'disabled'");
78 }
79
80 struct {
81 char *name;
82 enum ipc_feature type;
83 } types[] = {
84 { "*", IPC_FEATURE_ALL_COMMANDS },
85 { "command", IPC_FEATURE_COMMAND },
86 { "workspaces", IPC_FEATURE_GET_WORKSPACES },
87 { "outputs", IPC_FEATURE_GET_OUTPUTS },
88 { "tree", IPC_FEATURE_GET_TREE },
89 { "marks", IPC_FEATURE_GET_MARKS },
90 { "bar-config", IPC_FEATURE_GET_BAR_CONFIG },
91 { "inputs", IPC_FEATURE_GET_INPUTS },
92 { "clipboard", IPC_FEATURE_GET_CLIPBOARD },
93 };
94
95 uint32_t type = 0;
96
97 for (size_t i = 0; i < sizeof(types) / sizeof(types[0]); ++i) {
98 if (strcmp(types[i].name, argv[-1]) == 0) {
99 type = types[i].type;
100 break;
101 }
102 }
103
104 if (enabled) {
105 current_policy->features |= type;
106 sway_log(L_DEBUG, "Enabled IPC %s feature", argv[-1]);
107 } else {
108 current_policy->features &= ~type;
109 sway_log(L_DEBUG, "Disabled IPC %s feature", argv[-1]);
110 }
111
112 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
113}
114
115struct cmd_results *cmd_ipc_event_cmd(int argc, char **argv) {
116 struct cmd_results *error = NULL;
117 if ((error = checkarg(argc, "ipc", EXPECTED_EQUAL_TO, 1))) {
118 return error;
119 }
120
121 bool enabled;
122 if (strcmp(argv[0], "enabled") == 0) {
123 enabled = true;
124 } else if (strcmp(argv[0], "disabled") == 0) {
125 enabled = false;
126 } else {
127 return cmd_results_new(CMD_INVALID, argv[-1],
128 "Argument must be one of 'enabled' or 'disabled'");
129 }
130
131 struct {
132 char *name;
133 enum ipc_feature type;
134 } types[] = {
135 { "*", IPC_FEATURE_ALL_EVENTS },
136 { "workspace", IPC_FEATURE_EVENT_WORKSPACE },
137 { "output", IPC_FEATURE_EVENT_OUTPUT },
138 { "mode", IPC_FEATURE_EVENT_MODE },
139 { "window", IPC_FEATURE_EVENT_WINDOW },
140 { "binding", IPC_FEATURE_EVENT_BINDING },
141 { "input", IPC_FEATURE_EVENT_INPUT },
142 };
143
144 uint32_t type = 0;
145
146 for (size_t i = 0; i < sizeof(types) / sizeof(types[0]); ++i) {
147 if (strcmp(types[i].name, argv[-1]) == 0) {
148 type = types[i].type;
149 break;
150 }
151 }
152
153 if (enabled) {
154 current_policy->features |= type;
155 sway_log(L_DEBUG, "Enabled IPC %s event", argv[-1]);
156 } else {
157 current_policy->features &= ~type;
158 sway_log(L_DEBUG, "Disabled IPC %s event", argv[-1]);
159 }
160
161 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
162}
diff --git a/sway/commands/kill.c b/sway/commands/kill.c
index 742e2b86..f3fa52f1 100644
--- a/sway/commands/kill.c
+++ b/sway/commands/kill.c
@@ -1,12 +1,16 @@
1#include <wlr/util/log.h>
2#include "log.h"
3#include "sway/input/input-manager.h"
4#include "sway/input/seat.h"
5#include "sway/tree/view.h"
6#include "sway/tree/container.h"
1#include "sway/commands.h" 7#include "sway/commands.h"
2#include "sway/container.h"
3#include "sway/focus.h"
4 8
5struct cmd_results *cmd_kill(int argc, char **argv) { 9struct cmd_results *cmd_kill(int argc, char **argv) {
6 if (config->reading) return cmd_results_new(CMD_FAILURE, "kill", "Can't be used in config file."); 10 struct sway_container *con =
7 if (!config->active) return cmd_results_new(CMD_FAILURE, "kill", "Can only be used when sway is running."); 11 config->handler_context.current_container;
12
13 container_close(con);
8 14
9 swayc_t *container = current_container;
10 close_views(container);
11 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 15 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
12} 16}
diff --git a/sway/commands/layout.c b/sway/commands/layout.c
index 57a86565..4c49a627 100644
--- a/sway/commands/layout.c
+++ b/sway/commands/layout.c
@@ -1,196 +1,55 @@
1#include <string.h> 1#include <string.h>
2#include <strings.h> 2#include <strings.h>
3#include "sway/commands.h" 3#include "sway/commands.h"
4#include "sway/container.h" 4#include "sway/tree/container.h"
5#include "sway/layout.h" 5#include "sway/tree/layout.h"
6 6#include "log.h"
7/**
8 * handle "layout auto" command group
9 */
10static struct cmd_results *cmd_layout_auto(swayc_t *container, int argc, char **argv);
11 7
12struct cmd_results *cmd_layout(int argc, char **argv) { 8struct cmd_results *cmd_layout(int argc, char **argv) {
13 struct cmd_results *error = NULL; 9 struct cmd_results *error = NULL;
14 if (config->reading) return cmd_results_new(CMD_FAILURE, "layout", "Can't be used in config file.");
15 if (!config->active) return cmd_results_new(CMD_FAILURE, "layout", "Can only be used when sway is running.");
16 if ((error = checkarg(argc, "layout", EXPECTED_MORE_THAN, 0))) { 10 if ((error = checkarg(argc, "layout", EXPECTED_MORE_THAN, 0))) {
17 return error; 11 return error;
18 } 12 }
19 swayc_t *parent = current_container; 13 struct sway_container *parent = config->handler_context.current_container;
14
15 // TODO: floating
16 /*
20 if (parent->is_floating) { 17 if (parent->is_floating) {
21 return cmd_results_new(CMD_FAILURE, "layout", "Unable to change layout of floating windows"); 18 return cmd_results_new(CMD_FAILURE, "layout", "Unable to change layout of floating windows");
22 } 19 }
20 */
23 21
24 while (parent->type == C_VIEW) { 22 while (parent->type == C_VIEW) {
25 parent = parent->parent; 23 parent = parent->parent;
26 } 24 }
27 25
28 enum swayc_layouts old_layout = parent->layout; 26 // TODO: stacks and tabs
29 27
30 if (strcasecmp(argv[0], "default") == 0) { 28 if (strcasecmp(argv[0], "default") == 0) {
31 swayc_change_layout(parent, parent->prev_layout); 29 container_set_layout(parent, parent->prev_layout);
32 if (parent->layout == L_NONE) { 30 if (parent->layout == L_NONE) {
33 swayc_t *output = swayc_parent_by_type(parent, C_OUTPUT); 31 container_set_layout(parent, container_get_default_layout(parent));
34 swayc_change_layout(parent, default_layout(output));
35 } 32 }
36 } else { 33 } else {
37 if (parent->layout != L_TABBED && parent->layout != L_STACKED) { 34 if (parent->layout != L_TABBED && parent->layout != L_STACKED) {
38 parent->prev_layout = parent->layout; 35 parent->prev_layout = parent->layout;
39 } 36 }
40 37
41 if (strcasecmp(argv[0], "tabbed") == 0) { 38 if (strcasecmp(argv[0], "splith") == 0) {
42 if (parent->type != C_CONTAINER && !swayc_is_empty_workspace(parent)){ 39 container_set_layout(parent, L_HORIZ);
43 parent = new_container(parent, L_TABBED);
44 }
45
46 swayc_change_layout(parent, L_TABBED);
47 } else if (strcasecmp(argv[0], "stacking") == 0) {
48 if (parent->type != C_CONTAINER && !swayc_is_empty_workspace(parent)) {
49 parent = new_container(parent, L_STACKED);
50 }
51
52 swayc_change_layout(parent, L_STACKED);
53 } else if (strcasecmp(argv[0], "splith") == 0) {
54 swayc_change_layout(parent, L_HORIZ);
55 } else if (strcasecmp(argv[0], "splitv") == 0) { 40 } else if (strcasecmp(argv[0], "splitv") == 0) {
56 swayc_change_layout(parent, L_VERT); 41 container_set_layout(parent, L_VERT);
57 } else if (strcasecmp(argv[0], "toggle") == 0 && argc == 2 && strcasecmp(argv[1], "split") == 0) { 42 } else if (strcasecmp(argv[0], "toggle") == 0 && argc == 2 && strcasecmp(argv[1], "split") == 0) {
58 if (parent->layout == L_HORIZ && (parent->workspace_layout == L_NONE 43 if (parent->layout == L_HORIZ && (parent->workspace_layout == L_NONE
59 || parent->workspace_layout == L_HORIZ)) { 44 || parent->workspace_layout == L_HORIZ)) {
60 swayc_change_layout(parent, L_VERT); 45 container_set_layout(parent, L_VERT);
61 } else { 46 } else {
62 swayc_change_layout(parent, L_HORIZ); 47 container_set_layout(parent, L_HORIZ);
63 } 48 }
64 } else if (strcasecmp(argv[0], "auto") == 0) {
65 return cmd_layout_auto(parent, argc, argv);
66 } 49 }
67 } 50 }
68 51
69 update_layout_geometry(parent, old_layout);
70 update_geometry(parent);
71
72 arrange_windows(parent, parent->width, parent->height); 52 arrange_windows(parent, parent->width, parent->height);
73 53
74 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 54 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
75} 55}
76
77static struct cmd_results *cmd_layout_auto(swayc_t *container, int argc, char **argv) {
78 // called after checking that argv[0] is auto, so just continue parsing from there
79 struct cmd_results *error = NULL;
80 const char *cmd_name = "layout auto";
81 const char *set_inc_cmd_name = "layout auto [master|ncol] [set|inc]";
82 const char *err_msg = "Allowed arguments are <right|left|top|bottom|next|prev|master|ncol>";
83
84 bool need_layout_update = false;
85 enum swayc_layouts old_layout = container->layout;
86 enum swayc_layouts layout = old_layout;
87
88 if ((error = checkarg(argc, "layout auto", EXPECTED_MORE_THAN, 1))) {
89 return error;
90 }
91
92 if (strcasecmp(argv[1], "left") == 0) {
93 layout = L_AUTO_LEFT;
94 } else if (strcasecmp(argv[1], "right") == 0) {
95 layout = L_AUTO_RIGHT;
96 } else if (strcasecmp(argv[1], "top") == 0) {
97 layout = L_AUTO_TOP;
98 } else if (strcasecmp(argv[1], "bottom") == 0) {
99 layout = L_AUTO_BOTTOM;
100 } else if (strcasecmp(argv[1], "next") == 0) {
101 if (is_auto_layout(container->layout) && container->layout < L_AUTO_LAST) {
102 layout = container->layout + 1;
103 } else {
104 layout = L_AUTO_FIRST;
105 }
106 } else if (strcasecmp(argv[1], "prev") == 0) {
107 if (is_auto_layout(container->layout) && container->layout > L_AUTO_FIRST) {
108 layout = container->layout - 1;
109 } else {
110 layout = L_AUTO_LAST;
111 }
112 } else {
113 bool is_nmaster;
114 bool is_set;
115 if (strcasecmp(argv[1], "master") == 0) {
116 is_nmaster = true;
117 } else if (strcasecmp(argv[1], "ncol") == 0) {
118 is_nmaster = false;
119 } else {
120 return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command. %s",
121 cmd_name, err_msg);
122 }
123 if ((error = checkarg(argc, "auto <master|ncol>", EXPECTED_EQUAL_TO, 4))) {
124 return error;
125 }
126 if (strcasecmp(argv[2], "set") == 0) {
127 is_set = true;
128 } else if (strcasecmp(argv[2], "inc") == 0) {
129 is_set = false;
130 } else {
131 return cmd_results_new(CMD_INVALID, set_inc_cmd_name, "Invalid %s command. %s, "
132 "Argument must be on of <set|inc>",
133 set_inc_cmd_name);
134 }
135 char *end;
136 int n = (int)strtol(argv[3], &end, 10);
137 if (*end) {
138 return cmd_results_new(CMD_INVALID, set_inc_cmd_name, "Invalid %s command "
139 "(argument must be an integer)", set_inc_cmd_name);
140 }
141 if (is_auto_layout(container->layout)) {
142 int inc = 0; /* difference between current master/ncol and requested value */
143 if (is_nmaster) {
144 if (is_set) {
145 if (n < 0) {
146 return cmd_results_new(CMD_INVALID, set_inc_cmd_name, "Invalid %s command "
147 "(master must be >= 0)", set_inc_cmd_name);
148 }
149 inc = n - (int)container->nb_master;
150 } else { /* inc command */
151 if ((int)container->nb_master + n >= 0) {
152 inc = n;
153 }
154 }
155 if (inc) {
156 for (int i = container->nb_master;
157 i >= 0 && i < container->children->length
158 && i != (int)container->nb_master + inc;) {
159 ((swayc_t *)container->children->items[i])->height = -1;
160 ((swayc_t *)container->children->items[i])->width = -1;
161 i += inc > 0 ? 1 : -1;
162 }
163 container->nb_master += inc;
164 need_layout_update = true;
165 }
166 } else { /* ncol modification */
167 if (is_set) {
168 if (n <= 0) {
169 return cmd_results_new(CMD_INVALID, set_inc_cmd_name, "Invalid %s command "
170 "(ncol must be > 0)", set_inc_cmd_name);
171 }
172 inc = n - (int)container->nb_slave_groups;
173 } else { /* inc command */
174 if ((int)container->nb_slave_groups + n > 0) {
175 inc = n;
176 }
177 }
178 if (inc) {
179 container->nb_slave_groups += inc;
180 need_layout_update = true;
181 }
182 }
183 }
184 }
185
186 if (layout != old_layout) {
187 swayc_change_layout(container, layout);
188 update_layout_geometry(container, old_layout);
189 need_layout_update = true;
190 }
191 if (need_layout_update) {
192 update_geometry(container);
193 arrange_windows(container, container->width, container->height);
194 }
195 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
196}
diff --git a/sway/commands/log_colors.c b/sway/commands/log_colors.c
deleted file mode 100644
index 815d1942..00000000
--- a/sway/commands/log_colors.c
+++ /dev/null
@@ -1,22 +0,0 @@
1#include <string.h>
2#include <strings.h>
3#include "sway/commands.h"
4#include "log.h"
5
6struct cmd_results *cmd_log_colors(int argc, char **argv) {
7 struct cmd_results *error = NULL;
8 if (!config->reading) return cmd_results_new(CMD_FAILURE, "log_colors", "Can only be used in config file.");
9 if ((error = checkarg(argc, "log_colors", EXPECTED_EQUAL_TO, 1))) {
10 return error;
11 }
12 if (strcasecmp(argv[0], "no") == 0) {
13 sway_log_colors(0);
14 } else if (strcasecmp(argv[0], "yes") == 0) {
15 sway_log_colors(1);
16 } else {
17 error = cmd_results_new(CMD_FAILURE, "log_colors",
18 "Invalid log_colors command (expected `yes` or `no`, got '%s')", argv[0]);
19 return error;
20 }
21 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
22}
diff --git a/sway/commands/mark.c b/sway/commands/mark.c
deleted file mode 100644
index c1d959df..00000000
--- a/sway/commands/mark.c
+++ /dev/null
@@ -1,87 +0,0 @@
1#include <string.h>
2#include <strings.h>
3#include <stdbool.h>
4#include "sway/commands.h"
5#include "list.h"
6#include "stringop.h"
7
8static void find_marks_callback(swayc_t *container, void *_mark) {
9 char *mark = (char *)_mark;
10
11 int index;
12 if (container->marks && ((index = list_seq_find(container->marks, (int (*)(const void *, const void *))strcmp, mark)) != -1)) {
13 list_del(container->marks, index);
14 }
15}
16
17struct cmd_results *cmd_mark(int argc, char **argv) {
18 struct cmd_results *error = NULL;
19 if (config->reading) return cmd_results_new(CMD_FAILURE, "mark", "Can't be used in config file.");
20 if ((error = checkarg(argc, "mark", EXPECTED_AT_LEAST, 1))) {
21 return error;
22 }
23
24 swayc_t *view = current_container;
25 bool add = false;
26 bool toggle = false;
27
28 if (strcmp(argv[0], "--add") == 0) {
29 --argc; ++argv;
30 add = true;
31 } else if (strcmp(argv[0], "--replace") == 0) {
32 --argc; ++argv;
33 }
34
35 if (argc && strcmp(argv[0], "--toggle") == 0) {
36 --argc; ++argv;
37 toggle = true;
38 }
39
40 if (argc) {
41 char *mark = join_args(argv, argc);
42
43 // Remove all existing marks of this type
44 container_map(&root_container, find_marks_callback, mark);
45
46 if (view->marks) {
47 if (add) {
48 int index;
49 if ((index = list_seq_find(view->marks, (int (*)(const void *, const void *))strcmp, mark)) != -1) {
50 if (toggle) {
51 free(view->marks->items[index]);
52 list_del(view->marks, index);
53
54 if (0 == view->marks->length) {
55 list_free(view->marks);
56 view->marks = NULL;
57 }
58 }
59 free(mark);
60 } else {
61 list_add(view->marks, mark);
62 }
63 } else {
64 if (toggle && list_seq_find(view->marks, (int (*)(const void *, const void *))strcmp, mark) != -1) {
65 // Delete the list
66 list_foreach(view->marks, free);
67 list_free(view->marks);
68 view->marks = NULL;
69 } else {
70 // Delete and replace with a new list
71 list_foreach(view->marks, free);
72 list_free(view->marks);
73
74 view->marks = create_list();
75 list_add(view->marks, mark);
76 }
77 }
78 } else {
79 view->marks = create_list();
80 list_add(view->marks, mark);
81 }
82 } else {
83 return cmd_results_new(CMD_FAILURE, "mark",
84 "Expected 'mark [--add|--replace] [--toggle] <mark>'");
85 }
86 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
87}
diff --git a/sway/commands/mode.c b/sway/commands/mode.c
index d2985c54..c30a8bac 100644
--- a/sway/commands/mode.c
+++ b/sway/commands/mode.c
@@ -15,43 +15,45 @@ struct cmd_results *cmd_mode(int argc, char **argv) {
15 } 15 }
16 16
17 const char *mode_name = argv[0]; 17 const char *mode_name = argv[0];
18 bool mode_make = (argc == 2 && strcmp(argv[1], "{") == 0); 18 bool new_mode = (argc == 2 && strcmp(argv[1], "{") == 0);
19 if (mode_make) { 19 if (new_mode && !config->reading) {
20 if (!config->reading) 20 return cmd_results_new(CMD_FAILURE,
21 return cmd_results_new(CMD_FAILURE, "mode", "Can only be used in config file."); 21 "mode", "Can only be used in config file.");
22 } 22 }
23 struct sway_mode *mode = NULL; 23 struct sway_mode *mode = NULL;
24 // Find mode 24 // Find mode
25 int i, len = config->modes->length; 25 for (int i = 0; i < config->modes->length; ++i) {
26 for (i = 0; i < len; ++i) { 26 struct sway_mode *test = config->modes->items[i];
27 struct sway_mode *find = config->modes->items[i]; 27 if (strcasecmp(test->name, mode_name) == 0) {
28 if (strcasecmp(find->name, mode_name) == 0) { 28 mode = test;
29 mode = find;
30 break; 29 break;
31 } 30 }
32 } 31 }
33 // Create mode if it doesn't exist 32 // Create mode if it doesn't exist
34 if (!mode && mode_make) { 33 if (!mode && new_mode) {
35 mode = malloc(sizeof(struct sway_mode)); 34 mode = calloc(1, sizeof(struct sway_mode));
36 if (!mode) { 35 if (!mode) {
37 return cmd_results_new(CMD_FAILURE, "mode", "Unable to allocate mode"); 36 return cmd_results_new(CMD_FAILURE,
37 "mode", "Unable to allocate mode");
38 } 38 }
39 mode->name = strdup(mode_name); 39 mode->name = strdup(mode_name);
40 mode->bindings = create_list(); 40 mode->keysym_bindings = create_list();
41 mode->keycode_bindings = create_list();
41 list_add(config->modes, mode); 42 list_add(config->modes, mode);
42 } 43 }
43 if (!mode) { 44 if (!mode) {
44 error = cmd_results_new(CMD_INVALID, "mode", "Unknown mode `%s'", mode_name); 45 error = cmd_results_new(CMD_INVALID,
46 "mode", "Unknown mode `%s'", mode_name);
45 return error; 47 return error;
46 } 48 }
47 if ((config->reading && mode_make) || (!config->reading && !mode_make)) { 49 if ((config->reading && new_mode) || (!config->reading && !new_mode)) {
48 sway_log(L_DEBUG, "Switching to mode `%s'",mode->name); 50 wlr_log(L_DEBUG, "Switching to mode `%s'",mode->name);
49 } 51 }
50 // Set current mode 52 // Set current mode
51 config->current_mode = mode; 53 config->current_mode = mode;
52 if (!mode_make) { 54 if (!new_mode) {
53 // trigger IPC mode event 55 // trigger IPC mode event
54 ipc_event_mode(config->current_mode->name); 56 ipc_event_mode(config->current_mode->name);
55 } 57 }
56 return cmd_results_new(mode_make ? CMD_BLOCK_MODE : CMD_SUCCESS, NULL, NULL); 58 return cmd_results_new(new_mode ? CMD_BLOCK_MODE : CMD_SUCCESS, NULL, NULL);
57} 59}
diff --git a/sway/commands/mouse_warping.c b/sway/commands/mouse_warping.c
index 5596d483..eef32ce7 100644
--- a/sway/commands/mouse_warping.c
+++ b/sway/commands/mouse_warping.c
@@ -11,7 +11,9 @@ struct cmd_results *cmd_mouse_warping(int argc, char **argv) {
11 } else if (strcasecmp(argv[0], "none") == 0) { 11 } else if (strcasecmp(argv[0], "none") == 0) {
12 config->mouse_warping = false; 12 config->mouse_warping = false;
13 } else { 13 } else {
14 return cmd_results_new(CMD_FAILURE, "mouse_warping", "Expected 'mouse_warping output|none'"); 14 return cmd_results_new(CMD_FAILURE, "mouse_warping",
15 "Expected 'mouse_warping output|none'");
15 } 16 }
16 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 17 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
17} 18}
19
diff --git a/sway/commands/move.c b/sway/commands/move.c
index 8d89f2ef..a5273ba4 100644
--- a/sway/commands/move.c
+++ b/sway/commands/move.c
@@ -1,186 +1,198 @@
1#define _XOPEN_SOURCE 500
1#include <string.h> 2#include <string.h>
2#include <strings.h> 3#include <strings.h>
3#include <wlc/wlc.h> 4#include <wlr/types/wlr_output.h>
5#include <wlr/types/wlr_output_layout.h>
6#include <wlr/util/log.h>
4#include "sway/commands.h" 7#include "sway/commands.h"
5#include "sway/container.h" 8#include "sway/input/seat.h"
6#include "sway/layout.h"
7#include "sway/output.h" 9#include "sway/output.h"
8#include "sway/workspace.h" 10#include "sway/tree/container.h"
9#include "list.h" 11#include "sway/tree/layout.h"
12#include "sway/tree/workspace.h"
10#include "stringop.h" 13#include "stringop.h"
14#include "list.h"
15
16static const char* expected_syntax =
17 "Expected 'move <left|right|up|down> <[px] px>' or "
18 "'move <container|window> to workspace <name>' or "
19 "'move <container|window|workspace> to output <name|direction>' or "
20 "'move position mouse'";
21
22static struct sway_container *output_in_direction(const char *direction,
23 struct wlr_output *reference, int ref_ox, int ref_oy) {
24 int ref_lx = ref_ox + reference->lx,
25 ref_ly = ref_oy + reference->ly;
26 struct {
27 char *name;
28 enum wlr_direction direction;
29 } names[] = {
30 { "up", WLR_DIRECTION_UP },
31 { "down", WLR_DIRECTION_DOWN },
32 { "left", WLR_DIRECTION_LEFT },
33 { "right", WLR_DIRECTION_RIGHT },
34 };
35 for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); ++i) {
36 if (strcasecmp(names[i].name, direction) == 0) {
37 struct wlr_output *adjacent = wlr_output_layout_adjacent_output(
38 root_container.sway_root->output_layout,
39 names[i].direction, reference, ref_lx, ref_ly);
40 if (adjacent) {
41 struct sway_output *sway_output = adjacent->data;
42 return sway_output->swayc;
43 }
44 break;
45 }
46 }
47 return output_by_name(direction);
48}
49
50static struct cmd_results *cmd_move_container(struct sway_container *current,
51 int argc, char **argv) {
52 struct cmd_results *error = NULL;
53 if ((error = checkarg(argc, "move container/window",
54 EXPECTED_AT_LEAST, 4))) {
55 return error;
56 } else if (strcasecmp(argv[1], "to") == 0
57 && strcasecmp(argv[2], "workspace") == 0) {
58 // move container to workspace x
59 if (current->type == C_WORKSPACE) {
60 // TODO: Wrap children in a container and move that
61 return cmd_results_new(CMD_FAILURE, "move", "Unimplemented");
62 } else if (current->type != C_CONTAINER && current->type != C_VIEW) {
63 return cmd_results_new(CMD_FAILURE, "move",
64 "Can only move containers and views.");
65 }
66 struct sway_container *ws;
67 char *ws_name = NULL;
68 if (argc == 5 && strcasecmp(argv[3], "number") == 0) {
69 // move "container to workspace number x"
70 ws_name = strdup(argv[4]);
71 ws = workspace_by_number(ws_name);
72 } else {
73 ws_name = join_args(argv + 3, argc - 3);
74 ws = workspace_by_name(ws_name);
75 }
76
77 if (config->auto_back_and_forth && prev_workspace_name) {
78 // auto back and forth move
79 struct sway_container *curr_ws = container_parent(current, C_WORKSPACE);
80 if (curr_ws->name && strcmp(curr_ws->name, ws_name) == 0) {
81 // if target workspace is the current one
82 free(ws_name);
83 ws_name = strdup(prev_workspace_name);
84 ws = workspace_by_name(ws_name);
85 }
86 }
87
88 if (!ws) {
89 ws = workspace_create(NULL, ws_name);
90 }
91 free(ws_name);
92 struct sway_container *old_parent = current->parent;
93 struct sway_container *focus = seat_get_focus_inactive(
94 config->handler_context.seat, ws);
95 container_move_to(current, focus);
96 seat_set_focus(config->handler_context.seat, old_parent);
97 container_reap_empty(old_parent);
98 container_reap_empty(focus->parent);
99 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
100 } else if (strcasecmp(argv[1], "to") == 0
101 && strcasecmp(argv[2], "output") == 0) {
102 if (current->type == C_WORKSPACE) {
103 // TODO: Wrap children in a container and move that
104 return cmd_results_new(CMD_FAILURE, "move", "Unimplemented");
105 } else if (current->type != C_CONTAINER
106 && current->type != C_VIEW) {
107 return cmd_results_new(CMD_FAILURE, "move",
108 "Can only move containers and views.");
109 }
110 struct sway_container *source = container_parent(current, C_OUTPUT);
111 struct sway_container *destination = output_in_direction(argv[3],
112 source->sway_output->wlr_output, current->x, current->y);
113 if (!destination) {
114 return cmd_results_new(CMD_FAILURE, "move workspace",
115 "Can't find output with name/direction '%s'", argv[3]);
116 }
117 struct sway_container *focus = seat_get_focus_inactive(
118 config->handler_context.seat, destination);
119 if (!focus) {
120 // We've never been to this output before
121 focus = destination->children->items[0];
122 }
123 struct sway_container *old_parent = current->parent;
124 container_move_to(current, focus);
125 seat_set_focus(config->handler_context.seat, old_parent);
126 container_reap_empty(old_parent);
127 container_reap_empty(focus->parent);
128 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
129 }
130 return cmd_results_new(CMD_INVALID, "move", expected_syntax);
131}
132
133static struct cmd_results *cmd_move_workspace(struct sway_container *current,
134 int argc, char **argv) {
135 struct cmd_results *error = NULL;
136 if ((error = checkarg(argc, "move workspace", EXPECTED_EQUAL_TO, 4))) {
137 return error;
138 } else if (strcasecmp(argv[1], "to") != 0
139 || strcasecmp(argv[2], "output") != 0) {
140 return cmd_results_new(CMD_INVALID, "move", expected_syntax);
141 }
142 struct sway_container *source = container_parent(current, C_OUTPUT);
143 int center_x = current->width / 2 + current->x,
144 center_y = current->height / 2 + current->y;
145 struct sway_container *destination = output_in_direction(argv[3],
146 source->sway_output->wlr_output, center_x, center_y);
147 if (!destination) {
148 return cmd_results_new(CMD_FAILURE, "move workspace",
149 "Can't find output with name/direction '%s'", argv[3]);
150 }
151 if (current->type != C_WORKSPACE) {
152 current = container_parent(current, C_WORKSPACE);
153 }
154 container_move_to(current, destination);
155 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
156}
11 157
12struct cmd_results *cmd_move(int argc, char **argv) { 158struct cmd_results *cmd_move(int argc, char **argv) {
13 struct cmd_results *error = NULL; 159 struct cmd_results *error = NULL;
14 int move_amt = 10; 160 int move_amt = 10;
15
16 if (config->reading) return cmd_results_new(CMD_FAILURE, "move", "Can't be used in config file.");
17 if ((error = checkarg(argc, "move", EXPECTED_AT_LEAST, 1))) { 161 if ((error = checkarg(argc, "move", EXPECTED_AT_LEAST, 1))) {
18 return error; 162 return error;
19 } 163 }
20 const char* expected_syntax = "Expected 'move <left|right|up|down|next|prev|first> <[px] px>' or " 164 struct sway_container *current = config->handler_context.current_container;
21 "'move <container|window> to workspace <name>' or "
22 "'move <container|window|workspace> to output <name|direction>' or "
23 "'move position mouse'";
24 swayc_t *view = current_container;
25 165
26 if (argc == 2 || (argc == 3 && strcasecmp(argv[2], "px") == 0 )) { 166 if (argc == 2 || (argc == 3 && strcasecmp(argv[2], "px") == 0)) {
27 char *inv; 167 char *inv;
28 move_amt = (int)strtol(argv[1], &inv, 10); 168 move_amt = (int)strtol(argv[1], &inv, 10);
29 if (*inv != '\0' && strcasecmp(inv, "px") != 0) { 169 if (*inv != '\0' && strcasecmp(inv, "px") != 0) {
30 move_amt = 10; 170 return cmd_results_new(CMD_FAILURE, "move",
171 "Invalid distance specified");
31 } 172 }
32 } 173 }
33 174
34 if (strcasecmp(argv[0], "left") == 0) { 175 if (strcasecmp(argv[0], "left") == 0) {
35 move_container(view, MOVE_LEFT, move_amt); 176 container_move(current, MOVE_LEFT, move_amt);
36 } else if (strcasecmp(argv[0], "right") == 0) { 177 } else if (strcasecmp(argv[0], "right") == 0) {
37 move_container(view, MOVE_RIGHT, move_amt); 178 container_move(current, MOVE_RIGHT, move_amt);
38 } else if (strcasecmp(argv[0], "up") == 0) { 179 } else if (strcasecmp(argv[0], "up") == 0) {
39 move_container(view, MOVE_UP, move_amt); 180 container_move(current, MOVE_UP, move_amt);
40 } else if (strcasecmp(argv[0], "down") == 0) { 181 } else if (strcasecmp(argv[0], "down") == 0) {
41 move_container(view, MOVE_DOWN, move_amt); 182 container_move(current, MOVE_DOWN, move_amt);
42 } else if (strcasecmp(argv[0], "next") == 0) { 183 } else if (strcasecmp(argv[0], "container") == 0
43 move_container(view, MOVE_NEXT, move_amt); 184 || strcasecmp(argv[0], "window") == 0) {
44 } else if (strcasecmp(argv[0], "prev") == 0) { 185 return cmd_move_container(current, argc, argv);
45 move_container(view, MOVE_PREV, move_amt);
46 } else if (strcasecmp(argv[0], "first") == 0) {
47 move_container(view, MOVE_FIRST, move_amt);
48 } else if (strcasecmp(argv[0], "container") == 0 || strcasecmp(argv[0], "window") == 0) {
49 // "move container ...
50 if ((error = checkarg(argc, "move container/window", EXPECTED_AT_LEAST, 4))) {
51 return error;
52 } else if (strcasecmp(argv[1], "to") == 0 && strcasecmp(argv[2], "workspace") == 0) {
53 // move container to workspace x
54 if (view->type == C_WORKSPACE) {
55 if (!view->children || view->children->length == 0) {
56 return cmd_results_new(CMD_FAILURE, "move", "Cannot move an empty workspace");
57 }
58 view = new_container(view, view->workspace_layout);
59 } if (view->type != C_CONTAINER && view->type != C_VIEW) {
60 return cmd_results_new(CMD_FAILURE, "move", "Can only move containers and views.");
61 }
62
63 swayc_t *ws;
64 const char *num_name = NULL;
65 char *ws_name = NULL;
66 if (argc == 5 && strcasecmp(argv[3], "number") == 0) {
67 // move "container to workspace number x"
68 num_name = argv[4];
69 ws = workspace_by_number(num_name);
70 } else {
71 ws_name = join_args(argv + 3, argc - 3);
72 ws = workspace_by_name(ws_name);
73 }
74
75 if (ws == NULL) {
76 ws = workspace_create(ws_name ? ws_name : num_name);
77 }
78 if (ws_name) {
79 free(ws_name);
80 }
81 move_container_to(view, get_focused_container(ws));
82 } else if (strcasecmp(argv[1], "to") == 0 && strcasecmp(argv[2], "output") == 0) {
83 // move container to output x
84 swayc_t *output = NULL;
85 struct wlc_point abs_pos;
86 get_absolute_center_position(view, &abs_pos);
87 if (view->type == C_WORKSPACE) {
88 if (!view->children || view->children->length == 0) {
89 return cmd_results_new(CMD_FAILURE, "move", "Cannot move an empty workspace");
90 }
91 view = new_container(view, view->workspace_layout);
92 } else if (view->type != C_CONTAINER && view->type != C_VIEW) {
93 return cmd_results_new(CMD_FAILURE, "move", "Can only move containers and views.");
94 } else if (!(output = output_by_name(argv[3], &abs_pos))) {
95 return cmd_results_new(CMD_FAILURE, "move",
96 "Can't find output with name/direction '%s' @ (%i,%i)", argv[3], abs_pos.x, abs_pos.y);
97 }
98
99 swayc_t *container = get_focused_container(output);
100 if (container->is_floating) {
101 move_container_to(view, container->parent);
102 } else {
103 move_container_to(view, container);
104 }
105 } else {
106 return cmd_results_new(CMD_INVALID, "move", expected_syntax);
107 }
108 } else if (strcasecmp(argv[0], "workspace") == 0) { 186 } else if (strcasecmp(argv[0], "workspace") == 0) {
109 // move workspace (to output x) 187 return cmd_move_workspace(current, argc, argv);
110 swayc_t *output = NULL; 188 } else if (strcasecmp(argv[0], "scratchpad") == 0
111 struct wlc_point abs_pos; 189 || (strcasecmp(argv[0], "to") == 0
112 get_absolute_center_position(view, &abs_pos); 190 && strcasecmp(argv[1], "scratchpad") == 0)) {
113 if ((error = checkarg(argc, "move workspace", EXPECTED_EQUAL_TO, 4))) { 191 // TODO: scratchpad
114 return error; 192 return cmd_results_new(CMD_FAILURE, "move", "Unimplemented");
115 } else if (strcasecmp(argv[1], "to") != 0 || strcasecmp(argv[2], "output") != 0) {
116 return cmd_results_new(CMD_INVALID, "move", expected_syntax);
117 } else if (!(output = output_by_name(argv[3], &abs_pos))) {
118 return cmd_results_new(CMD_FAILURE, "move workspace",
119 "Can't find output with name/direction '%s' @ (%i,%i)", argv[3], abs_pos.x, abs_pos.y);
120 }
121 if (view->type == C_WORKSPACE) {
122 // This probably means we're moving an empty workspace, but
123 // that's fine.
124 move_workspace_to(view, output);
125 } else {
126 swayc_t *workspace = swayc_parent_by_type(view, C_WORKSPACE);
127 move_workspace_to(workspace, output);
128 }
129 } else if (strcasecmp(argv[0], "scratchpad") == 0 || (strcasecmp(argv[0], "to") == 0 && strcasecmp(argv[1], "scratchpad") == 0)) {
130 // move scratchpad ...
131 if (view->type != C_CONTAINER && view->type != C_VIEW) {
132 return cmd_results_new(CMD_FAILURE, "move scratchpad", "Can only move containers and views.");
133 }
134 swayc_t *view = current_container;
135 int i;
136 for (i = 0; i < scratchpad->length; i++) {
137 if (scratchpad->items[i] == view) {
138 hide_view_in_scratchpad(view);
139 sp_view = NULL;
140 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
141 }
142 }
143 list_add(scratchpad, view);
144 if (!view->is_floating) {
145 destroy_container(remove_child(view));
146 } else {
147 remove_child(view);
148 }
149 wlc_view_set_mask(view->handle, 0);
150 arrange_windows(swayc_active_workspace(), -1, -1);
151 swayc_t *focused = container_under_pointer();
152 if (focused == NULL) {
153 focused = swayc_active_workspace();
154 }
155 set_focused_container(focused);
156 } else if (strcasecmp(argv[0], "position") == 0) { 193 } else if (strcasecmp(argv[0], "position") == 0) {
157 if ((error = checkarg(argc, "move workspace", EXPECTED_EQUAL_TO, 2))) { 194 // TODO: floating
158 return error; 195 return cmd_results_new(CMD_FAILURE, "move", "Unimplemented");
159 }
160 if (strcasecmp(argv[1], "mouse")) {
161 return cmd_results_new(CMD_INVALID, "move", expected_syntax);
162 }
163
164 if (view->is_floating) {
165 swayc_t *output = swayc_parent_by_type(view, C_OUTPUT);
166 struct wlc_geometry g;
167 wlc_view_get_visible_geometry(view->handle, &g);
168 const struct wlc_size *size = wlc_output_get_resolution(output->handle);
169
170 double x_pos, y_pos;
171 wlc_pointer_get_position_v2(&x_pos, &y_pos);
172
173 int32_t x = x_pos - g.size.w / 2;
174 int32_t y = y_pos - g.size.h / 2;
175
176 uint32_t w = size->w - g.size.w;
177 uint32_t h = size->h - g.size.h;
178
179 view->x = g.origin.x = MIN((int32_t)w, MAX(x, 0));
180 view->y = g.origin.y = MIN((int32_t)h, MAX(y, 0));
181
182 wlc_view_set_geometry(view->handle, 0, &g);
183 }
184 } else { 196 } else {
185 return cmd_results_new(CMD_INVALID, "move", expected_syntax); 197 return cmd_results_new(CMD_INVALID, "move", expected_syntax);
186 } 198 }
diff --git a/sway/commands/new_float.c b/sway/commands/new_float.c
deleted file mode 100644
index d0f96093..00000000
--- a/sway/commands/new_float.c
+++ /dev/null
@@ -1,8 +0,0 @@
1#include "log.h"
2#include "sway/commands.h"
3
4struct cmd_results *cmd_new_float(int argc, char **argv) {
5 sway_log(L_INFO, "`new_float` is deprecated and will be removed in the future. "
6 "Please use `default_floating_border` instead.");
7 return cmd_default_floating_border(argc, argv);
8}
diff --git a/sway/commands/new_window.c b/sway/commands/new_window.c
deleted file mode 100644
index 574a4527..00000000
--- a/sway/commands/new_window.c
+++ /dev/null
@@ -1,8 +0,0 @@
1#include "log.h"
2#include "sway/commands.h"
3
4struct cmd_results *cmd_new_window(int argc, char **argv) {
5 sway_log(L_INFO, "`new_window` is deprecated and will be removed in the future. "
6 "Please use `default_border` instead.");
7 return cmd_default_border(argc, argv);
8}
diff --git a/sway/commands/no_focus.c b/sway/commands/no_focus.c
deleted file mode 100644
index b3b88e5a..00000000
--- a/sway/commands/no_focus.c
+++ /dev/null
@@ -1,41 +0,0 @@
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#include "stringop.h"
8
9struct cmd_results *cmd_no_focus(int argc, char **argv) {
10 struct cmd_results *error = NULL;
11 if ((error = checkarg(argc, "no_focus", EXPECTED_EQUAL_TO, 1))) {
12 return error;
13 }
14 // add command to a criteria/command pair that is run against views when they appear.
15 char *criteria = argv[0];
16
17 struct criteria *crit = malloc(sizeof(struct criteria));
18 if (!crit) {
19 return cmd_results_new(CMD_FAILURE, "no_focus", "Unable to allocate criteria");
20 }
21 crit->crit_raw = strdup(criteria);
22 crit->tokens = create_list();
23 crit->cmdlist = NULL;
24 char *err_str = extract_crit_tokens(crit->tokens, crit->crit_raw);
25
26 if (err_str) {
27 error = cmd_results_new(CMD_INVALID, "no_focus", err_str);
28 free(err_str);
29 free_criteria(crit);
30 } else if (crit->tokens->length == 0) {
31 error = cmd_results_new(CMD_INVALID, "no_focus", "Found no name/value pairs in criteria");
32 free_criteria(crit);
33 } else if (list_seq_find(config->no_focus, criteria_cmp, crit) != -1) {
34 sway_log(L_DEBUG, "no_focus: Duplicate, skipping.");
35 free_criteria(crit);
36 } else {
37 sway_log(L_DEBUG, "no_focus: '%s' added", crit->crit_raw);
38 list_add(config->no_focus, crit);
39 }
40 return error ? error : cmd_results_new(CMD_SUCCESS, NULL, NULL);
41}
diff --git a/sway/commands/opacity.c b/sway/commands/opacity.c
new file mode 100644
index 00000000..68fd9f42
--- /dev/null
+++ b/sway/commands/opacity.c
@@ -0,0 +1,36 @@
1#include <assert.h>
2#include <stdlib.h>
3#include "sway/commands.h"
4#include "sway/tree/view.h"
5#include "log.h"
6
7static bool parse_opacity(const char *opacity, float *val) {
8 char *err;
9 *val = strtof(opacity, &err);
10 if (*val < 0 || *val > 1 || *err) {
11 return false;
12 }
13 return true;
14}
15
16struct cmd_results *cmd_opacity(int argc, char **argv) {
17 struct cmd_results *error = NULL;
18 if ((error = checkarg(argc, "layout", EXPECTED_EQUAL_TO, 1))) {
19 return error;
20 }
21
22 struct sway_container *con =
23 config->handler_context.current_container;
24
25 float opacity = 0.0f;
26
27 if (!parse_opacity(argv[0], &opacity)) {
28 return cmd_results_new(CMD_INVALID, "opacity <value>",
29 "Invalid value (expected 0..1): %s", argv[0]);
30 }
31
32 con->alpha = opacity;
33 container_damage_whole(con);
34
35 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
36}
diff --git a/sway/commands/output.c b/sway/commands/output.c
index 911391d2..f7e3372c 100644
--- a/sway/commands/output.c
+++ b/sway/commands/output.c
@@ -17,196 +17,299 @@ static char *bg_options[] = {
17 "center", 17 "center",
18 "fill", 18 "fill",
19 "fit", 19 "fit",
20 "tile" 20 "tile",
21}; 21};
22 22
23struct cmd_results *cmd_output(int argc, char **argv) { 23static struct cmd_results *cmd_output_mode(struct output_config *output,
24 struct cmd_results *error = NULL; 24 int *i, int argc, char **argv) {
25 if ((error = checkarg(argc, "output", EXPECTED_AT_LEAST, 1))) { 25 if (++*i >= argc) {
26 return error; 26 return cmd_results_new(CMD_INVALID, "output", "Missing mode argument.");
27 } 27 }
28 const char *name = argv[0];
29 28
30 struct output_config *output = calloc(1, sizeof(struct output_config)); 29 char *end;
31 if (!output) { 30 output->width = strtol(argv[*i], &end, 10);
32 return cmd_results_new(CMD_FAILURE, "output", "Unable to allocate output config"); 31 if (*end) {
32 // Format is 1234x4321
33 if (*end != 'x') {
34 return cmd_results_new(CMD_INVALID, "output",
35 "Invalid mode width.");
36 }
37 ++end;
38 output->height = strtol(end, &end, 10);
39 if (*end) {
40 if (*end != '@') {
41 return cmd_results_new(CMD_INVALID, "output",
42 "Invalid mode height.");
43 }
44 ++end;
45 output->refresh_rate = strtof(end, &end);
46 if (strcasecmp("Hz", end) != 0) {
47 return cmd_results_new(CMD_INVALID, "output",
48 "Invalid mode refresh rate.");
49 }
50 }
51 } else {
52 // Format is 1234 4321
53 if (++*i >= argc) {
54 return cmd_results_new(CMD_INVALID, "output",
55 "Missing mode argument (height).");
56 }
57 output->height = strtol(argv[*i], &end, 10);
58 if (*end) {
59 return cmd_results_new(CMD_INVALID, "output",
60 "Invalid mode height.");
61 }
33 } 62 }
34 output->x = output->y = output->width = output->height = -1;
35 output->name = strdup(name);
36 output->enabled = -1;
37 output->scale = 1;
38 63
39 // TODO: atoi doesn't handle invalid numbers 64 return NULL;
65}
40 66
41 int i; 67static struct cmd_results *cmd_output_position(struct output_config *output,
42 for (i = 1; i < argc; ++i) { 68 int *i, int argc, char **argv) {
43 const char *command = argv[i]; 69 if (++*i >= argc) {
70 return cmd_results_new(CMD_INVALID, "output",
71 "Missing position argument.");
72 }
44 73
45 if (strcasecmp(command, "disable") == 0) { 74 char *end;
46 output->enabled = 0; 75 output->x = strtol(argv[*i], &end, 10);
47 } else if (strcasecmp(command, "resolution") == 0 || strcasecmp(command, "res") == 0) { 76 if (*end) {
48 if (++i >= argc) { 77 // Format is 1234,4321
49 error = cmd_results_new(CMD_INVALID, "output", "Missing resolution argument."); 78 if (*end != ',') {
50 goto fail; 79 return cmd_results_new(CMD_INVALID, "output",
51 } 80 "Invalid position x.");
52 char *res = argv[i]; 81 }
53 char *x = strchr(res, 'x'); 82 ++end;
54 int width = -1, height = -1; 83 output->y = strtol(end, &end, 10);
55 if (x != NULL) { 84 if (*end) {
56 // Format is 1234x4321 85 return cmd_results_new(CMD_INVALID, "output",
57 *x = '\0'; 86 "Invalid position y.");
58 width = atoi(res); 87 }
59 height = atoi(x + 1); 88 } else {
60 *x = 'x'; 89 // Format is 1234 4321 (legacy)
61 } else { 90 if (++*i >= argc) {
62 // Format is 1234 4321 91 return cmd_results_new(CMD_INVALID, "output",
63 width = atoi(res); 92 "Missing position argument (y).");
64 if (++i >= argc) { 93 }
65 error = cmd_results_new(CMD_INVALID, "output", "Missing resolution argument (height)."); 94 output->y = strtol(argv[*i], &end, 10);
66 goto fail; 95 if (*end) {
96 return cmd_results_new(CMD_INVALID, "output",
97 "Invalid position y.");
98 }
99 }
100
101 return NULL;
102}
103
104static struct cmd_results *cmd_output_scale(struct output_config *output,
105 int *i, int argc, char **argv) {
106 if (++*i >= argc) {
107 return cmd_results_new(CMD_INVALID, "output",
108 "Missing scale argument.");
109 }
110
111 char *end;
112 output->scale = strtof(argv[*i], &end);
113 if (*end) {
114 return cmd_results_new(CMD_INVALID, "output", "Invalid scale.");
115 }
116
117 return NULL;
118}
119
120static struct cmd_results *cmd_output_transform(struct output_config *output,
121 int *i, int argc, char **argv) {
122 if (++*i >= argc) {
123 return cmd_results_new(CMD_INVALID, "output",
124 "Missing transform argument.");
125 }
126
127 char *value = argv[*i];
128 if (strcmp(value, "normal") == 0) {
129 output->transform = WL_OUTPUT_TRANSFORM_NORMAL;
130 } else if (strcmp(value, "90") == 0) {
131 output->transform = WL_OUTPUT_TRANSFORM_90;
132 } else if (strcmp(value, "180") == 0) {
133 output->transform = WL_OUTPUT_TRANSFORM_180;
134 } else if (strcmp(value, "270") == 0) {
135 output->transform = WL_OUTPUT_TRANSFORM_270;
136 } else if (strcmp(value, "flipped") == 0) {
137 output->transform = WL_OUTPUT_TRANSFORM_FLIPPED;
138 } else if (strcmp(value, "flipped-90") == 0) {
139 output->transform = WL_OUTPUT_TRANSFORM_FLIPPED_90;
140 } else if (strcmp(value, "flipped-180") == 0) {
141 output->transform = WL_OUTPUT_TRANSFORM_FLIPPED_180;
142 } else if (strcmp(value, "flipped-270") == 0) {
143 output->transform = WL_OUTPUT_TRANSFORM_FLIPPED_270;
144 } else {
145 return cmd_results_new(CMD_INVALID, "output",
146 "Invalid output transform.");
147 }
148
149 return NULL;
150}
151
152static struct cmd_results *cmd_output_background(struct output_config *output,
153 int *i, int argc, char **argv) {
154 if (++*i >= argc) {
155 return cmd_results_new(CMD_INVALID, "output",
156 "Missing background file or color specification.");
157 }
158 const char *background = argv[*i];
159 if (*i + 1 >= argc) {
160 return cmd_results_new(CMD_INVALID, "output",
161 "Missing background scaling mode or `solid_color`.");
162 }
163 const char *background_option = argv[*i];
164
165 if (strcasecmp(background_option, "solid_color") == 0) {
166 output->background = strdup(background);
167 output->background_option = strdup("solid_color");
168 } else {
169 bool valid = false;
170 char *mode;
171 size_t j;
172 for (j = 0; j < (size_t)(argc - *i); ++j) {
173 mode = argv[*i + j];
174 size_t n = sizeof(bg_options) / sizeof(char *);
175 for (size_t k = 0; k < n; ++k) {
176 if (strcasecmp(mode, bg_options[k]) == 0) {
177 valid = true;
178 break;
67 } 179 }
68 res = argv[i];
69 height = atoi(res);
70 } 180 }
71 output->width = width; 181 if (valid) {
72 output->height = height; 182 break;
73 } else if (strcasecmp(command, "position") == 0 || strcasecmp(command, "pos") == 0) {
74 if (++i >= argc) {
75 error = cmd_results_new(CMD_INVALID, "output", "Missing position argument.");
76 goto fail;
77 } 183 }
78 char *res = argv[i]; 184 }
79 char *c = strchr(res, ','); 185 if (!valid) {
80 int x = -1, y = -1; 186 return cmd_results_new(CMD_INVALID, "output",
81 if (c != NULL) { 187 "Missing background scaling mode.");
82 // Format is 1234,4321 188 }
83 *c = '\0'; 189
84 x = atoi(res); 190 wordexp_t p;
85 y = atoi(c + 1); 191 char *src = join_args(argv + *i, j);
86 *c = ','; 192 if (wordexp(src, &p, 0) != 0 || p.we_wordv[0] == NULL) {
87 } else { 193 return cmd_results_new(CMD_INVALID, "output",
88 // Format is 1234 4321 194 "Invalid syntax (%s).", src);
89 x = atoi(res); 195 }
90 if (++i >= argc) { 196 free(src);
91 error = cmd_results_new(CMD_INVALID, "output", "Missing position argument (y)."); 197 src = p.we_wordv[0];
92 goto fail; 198 if (config->reading && *src != '/') {
199 char *conf = strdup(config->current_config);
200 if (conf) {
201 char *conf_path = dirname(conf);
202 src = malloc(strlen(conf_path) + strlen(src) + 2);
203 if (src) {
204 sprintf(src, "%s/%s", conf_path, p.we_wordv[0]);
205 } else {
206 wlr_log(L_ERROR,
207 "Unable to allocate background source");
93 } 208 }
94 res = argv[i]; 209 free(conf);
95 y = atoi(res);
96 }
97 output->x = x;
98 output->y = y;
99 } else if (strcasecmp(command, "scale") == 0) {
100 if (++i >= argc) {
101 error = cmd_results_new(CMD_INVALID, "output", "Missing scale parameter.");
102 goto fail;
103 }
104 output->scale = atoi(argv[i]);
105 } else if (strcasecmp(command, "background") == 0 || strcasecmp(command, "bg") == 0) {
106 wordexp_t p;
107 if (++i >= argc) {
108 error = cmd_results_new(CMD_INVALID, "output", "Missing background file or color specification.");
109 goto fail;
110 }
111 if (i + 1 >= argc) {
112 error = cmd_results_new(CMD_INVALID, "output", "Missing background scaling mode or `solid_color`.");
113 goto fail;
114 }
115 if (strcasecmp(argv[i + 1], "solid_color") == 0) {
116 output->background = strdup(argv[argc - 2]);
117 output->background_option = strdup("solid_color");
118 } else { 210 } else {
119 // argv[i+j]=bg_option 211 wlr_log(L_ERROR, "Unable to allocate background source");
120 bool valid = false; 212 }
121 char *mode; 213 }
122 size_t j; 214 if (!src || access(src, F_OK) == -1) {
123 for (j = 0; j < (size_t) (argc - i); ++j) { 215 wordfree(&p);
124 mode = argv[i + j]; 216 return cmd_results_new(CMD_INVALID, "output",
125 for (size_t k = 0; k < sizeof(bg_options) / sizeof(char *); ++k) { 217 "Background file unreadable (%s).", src);
126 if (strcasecmp(mode, bg_options[k]) == 0) { 218 }
127 valid = true;
128 break;
129 }
130 }
131 if (valid) {
132 break;
133 }
134 }
135 if (!valid) {
136 error = cmd_results_new(CMD_INVALID, "output", "Missing background scaling mode.");
137 goto fail;
138 }
139 219
140 char *src = join_args(argv + i, j); 220 output->background = strdup(src);
141 if (wordexp(src, &p, 0) != 0 || p.we_wordv[0] == NULL) { 221 output->background_option = strdup(mode);
142 error = cmd_results_new(CMD_INVALID, "output", "Invalid syntax (%s)", src); 222 if (src != p.we_wordv[0]) {
143 goto fail; 223 free(src);
144 } 224 }
145 free(src); 225 wordfree(&p);
146 src = p.we_wordv[0];
147 if (config->reading && *src != '/') {
148 char *conf = strdup(config->current_config);
149 if (conf) {
150 char *conf_path = dirname(conf);
151 src = malloc(strlen(conf_path) + strlen(src) + 2);
152 if (src) {
153 sprintf(src, "%s/%s", conf_path, p.we_wordv[0]);
154 } else {
155 sway_log(L_ERROR, "Unable to allocate background source");
156 }
157 free(conf);
158 } else {
159 sway_log(L_ERROR, "Unable to allocate background source");
160 }
161 }
162 if (!src || access(src, F_OK) == -1) {
163 error = cmd_results_new(CMD_INVALID, "output", "Background file unreadable (%s)", src);
164 wordfree(&p);
165 goto fail;
166 }
167 226
168 output->background = strdup(src); 227 *i += j;
169 output->background_option = strdup(mode); 228 }
170 if (src != p.we_wordv[0]) {
171 free(src);
172 }
173 wordfree(&p);
174 229
175 i += j; 230 return NULL;
176 } 231}
232
233struct cmd_results *cmd_output(int argc, char **argv) {
234 struct cmd_results *error = checkarg(argc, "output", EXPECTED_AT_LEAST, 1);
235 if (error != NULL) {
236 return error;
237 }
238
239 struct output_config *output = new_output_config(argv[0]);
240 if (!output) {
241 wlr_log(L_ERROR, "Failed to allocate output config");
242 return NULL;
243 }
244
245 for (int i = 1; i < argc; ++i) {
246 const char *command = argv[i];
247
248 if (strcasecmp(command, "enable") == 0) {
249 output->enabled = 1;
250 } else if (strcasecmp(command, "disable") == 0) {
251 output->enabled = 0;
252 } else if (strcasecmp(command, "mode") == 0 ||
253 strcasecmp(command, "resolution") == 0 ||
254 strcasecmp(command, "res") == 0) {
255 error = cmd_output_mode(output, &i, argc, argv);
256 } else if (strcasecmp(command, "position") == 0 ||
257 strcasecmp(command, "pos") == 0) {
258 error = cmd_output_position(output, &i, argc, argv);
259 } else if (strcasecmp(command, "scale") == 0) {
260 error = cmd_output_scale(output, &i, argc, argv);
261 } else if (strcasecmp(command, "transform") == 0) {
262 error = cmd_output_transform(output, &i, argc, argv);
263 } else if (strcasecmp(command, "background") == 0 ||
264 strcasecmp(command, "bg") == 0) {
265 error = cmd_output_background(output, &i, argc, argv);
266 } else {
267 error = cmd_results_new(CMD_INVALID, "output",
268 "Invalid output subcommand: %s.", command);
269 }
270
271 if (error != NULL) {
272 goto fail;
177 } 273 }
178 } 274 }
179 275
180 i = list_seq_find(config->output_configs, output_name_cmp, name); 276 int i = list_seq_find(config->output_configs, output_name_cmp, output->name);
181 if (i >= 0) { 277 if (i >= 0) {
182 // merge existing config 278 // Merge existing config
183 struct output_config *oc = config->output_configs->items[i]; 279 struct output_config *current = config->output_configs->items[i];
184 merge_output_config(oc, output); 280 merge_output_config(current, output);
185 free_output_config(output); 281 free_output_config(output);
186 output = oc; 282 output = current;
187 } else { 283 } else {
188 list_add(config->output_configs, output); 284 list_add(config->output_configs, output);
189 } 285 }
190 286
191 sway_log(L_DEBUG, "Config stored for output %s (enabled:%d) (%d x %d @ %d, %d scale %d) (bg %s %s)", 287 wlr_log(L_DEBUG, "Config stored for output %s (enabled: %d) (%dx%d@%fHz "
192 output->name, output->enabled, output->width, 288 "position %d,%d scale %f transform %d) (bg %s %s)",
193 output->height, output->x, output->y, output->scale, 289 output->name, output->enabled, output->width, output->height,
194 output->background, output->background_option); 290 output->refresh_rate, output->x, output->y, output->scale,
291 output->transform, output->background, output->background_option);
195 292
196 if (output->name) { 293 // Try to find the output container and apply configuration now. If
197 // Try to find the output container and apply configuration now. If 294 // this is during startup then there will be no container and config
198 // this is during startup then there will be no container and config 295 // will be applied during normal "new output" event from wlroots.
199 // will be applied during normal "new output" event from wlc. 296 char identifier[128];
200 swayc_t *cont = NULL; 297 bool all = strcmp(output->name, "*") == 0;
201 for (int i = 0; i < root_container.children->length; ++i) { 298 for (int i = 0; i < root_container.children->length; ++i) {
202 cont = root_container.children->items[i]; 299 struct sway_container *cont = root_container.children->items[i];
203 if (cont->name && ((strcmp(cont->name, output->name) == 0) || (strcmp(output->name, "*") == 0))) { 300 if (cont->type != C_OUTPUT) {
204 apply_output_config(output, cont); 301 continue;
302 }
205 303
206 if (strcmp(output->name, "*") != 0) { 304 output_get_identifier(identifier, sizeof(identifier), cont->sway_output);
207 // stop looking if the output config isn't applicable to all outputs 305 if (all || strcmp(cont->name, output->name) == 0 ||
208 break; 306 strcmp(identifier, output->name) == 0) {
209 } 307 apply_output_config(output, cont);
308
309 if (!all) {
310 // Stop looking if the output config isn't applicable to all
311 // outputs
312 break;
210 } 313 }
211 } 314 }
212 } 315 }
diff --git a/sway/commands/permit.c b/sway/commands/permit.c
deleted file mode 100644
index 7a5e06f7..00000000
--- a/sway/commands/permit.c
+++ /dev/null
@@ -1,108 +0,0 @@
1#define _XOPEN_SOURCE 500
2#include <string.h>
3#include "sway/commands.h"
4#include "sway/config.h"
5#include "sway/security.h"
6#include "util.h"
7#include "log.h"
8
9static enum secure_feature get_features(int argc, char **argv,
10 struct cmd_results **error) {
11 enum secure_feature features = 0;
12
13 struct {
14 char *name;
15 enum secure_feature feature;
16 } feature_names[] = {
17 { "lock", FEATURE_LOCK },
18 { "panel", FEATURE_PANEL },
19 { "background", FEATURE_BACKGROUND },
20 { "screenshot", FEATURE_SCREENSHOT },
21 { "fullscreen", FEATURE_FULLSCREEN },
22 { "keyboard", FEATURE_KEYBOARD },
23 { "mouse", FEATURE_MOUSE },
24 };
25
26 for (int i = 1; i < argc; ++i) {
27 size_t j;
28 for (j = 0; j < sizeof(feature_names) / sizeof(feature_names[0]); ++j) {
29 if (strcmp(feature_names[j].name, argv[i]) == 0) {
30 break;
31 }
32 }
33 if (j == sizeof(feature_names) / sizeof(feature_names[0])) {
34 *error = cmd_results_new(CMD_INVALID,
35 "permit", "Invalid feature grant %s", argv[i]);
36 return 0;
37 }
38 features |= feature_names[j].feature;
39 }
40 return features;
41}
42
43struct cmd_results *cmd_permit(int argc, char **argv) {
44 struct cmd_results *error = NULL;
45 if ((error = checkarg(argc, "permit", EXPECTED_MORE_THAN, 1))) {
46 return error;
47 }
48 if ((error = check_security_config())) {
49 return error;
50 }
51
52 bool assign_perms = true;
53 char *program = NULL;
54
55 if (!strcmp(argv[0], "*")) {
56 program = strdup(argv[0]);
57 } else {
58 program = resolve_path(argv[0]);
59 }
60 if (!program) {
61 sway_assert(program, "Unable to resolve IPC permit target '%s'."
62 " will issue empty policy", argv[0]);
63 assign_perms = false;
64 program = strdup(argv[0]);
65 }
66
67 struct feature_policy *policy = get_feature_policy(program);
68 if (policy && assign_perms) {
69 policy->features |= get_features(argc, argv, &error);
70 sway_log(L_DEBUG, "Permissions granted to %s for features %d",
71 policy->program, policy->features);
72 }
73
74 free(program);
75 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
76}
77
78struct cmd_results *cmd_reject(int argc, char **argv) {
79 struct cmd_results *error = NULL;
80 if ((error = checkarg(argc, "reject", EXPECTED_MORE_THAN, 1))) {
81 return error;
82 }
83 if ((error = check_security_config())) {
84 return error;
85 }
86
87 char *program = NULL;
88 if (!strcmp(argv[0], "*")) {
89 program = strdup(argv[0]);
90 } else {
91 program = resolve_path(argv[0]);
92 }
93 if (!program) {
94 // Punt
95 sway_log(L_INFO, "Unable to resolve IPC reject target '%s'."
96 " Will use provided path", argv[0]);
97 program = strdup(argv[0]);
98 }
99
100 struct feature_policy *policy = get_feature_policy(program);
101 policy->features &= ~get_features(argc, argv, &error);
102
103 sway_log(L_DEBUG, "Permissions granted to %s for features %d",
104 policy->program, policy->features);
105
106 free(program);
107 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
108}
diff --git a/sway/commands/reload.c b/sway/commands/reload.c
index 01fcc5ba..5bca6cde 100644
--- a/sway/commands/reload.c
+++ b/sway/commands/reload.c
@@ -1,10 +1,9 @@
1#include "sway/commands.h" 1#include "sway/commands.h"
2#include "sway/config.h" 2#include "sway/config.h"
3#include "sway/layout.h" 3#include "sway/tree/layout.h"
4 4
5struct cmd_results *cmd_reload(int argc, char **argv) { 5struct cmd_results *cmd_reload(int argc, char **argv) {
6 struct cmd_results *error = NULL; 6 struct cmd_results *error = NULL;
7 if (config->reading) return cmd_results_new(CMD_FAILURE, "reload", "Can't be used in config file.");
8 if ((error = checkarg(argc, "reload", EXPECTED_EQUAL_TO, 0))) { 7 if ((error = checkarg(argc, "reload", EXPECTED_EQUAL_TO, 0))) {
9 return error; 8 return error;
10 } 9 }
@@ -13,7 +12,6 @@ struct cmd_results *cmd_reload(int argc, char **argv) {
13 } 12 }
14 13
15 load_swaybars(); 14 load_swaybars();
16
17 arrange_windows(&root_container, -1, -1); 15 arrange_windows(&root_container, -1, -1);
18 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 16 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
19} 17}
diff --git a/sway/commands/resize.c b/sway/commands/resize.c
index ef52bb07..93c1fe7d 100644
--- a/sway/commands/resize.c
+++ b/sway/commands/resize.c
@@ -4,372 +4,281 @@
4#include <stdlib.h> 4#include <stdlib.h>
5#include <string.h> 5#include <string.h>
6#include <strings.h> 6#include <strings.h>
7#include <wlc/wlc.h> 7#include <wlr/util/log.h>
8#include "sway/commands.h" 8#include "sway/commands.h"
9#include "sway/layout.h" 9#include "sway/tree/layout.h"
10#include "sway/focus.h"
11#include "sway/input_state.h"
12#include "sway/handlers.h"
13#include "log.h" 10#include "log.h"
14 11
15enum resize_dim_types { 12static const int MIN_SANE_W = 100, MIN_SANE_H = 60;
16 RESIZE_DIM_PX,
17 RESIZE_DIM_PPT,
18 RESIZE_DIM_DEFAULT,
19};
20
21static bool set_size_floating(int new_dimension, bool use_width) {
22 swayc_t *view = current_container;
23 if (view) {
24 if (use_width) {
25 int current_width = view->width;
26 view->desired_width = new_dimension;
27 floating_view_sane_size(view);
28
29 int new_x = view->x + (int)(((view->desired_width - current_width) / 2) * -1);
30 view->width = view->desired_width;
31 view->x = new_x;
32 13
33 update_geometry(view); 14enum resize_unit {
34 } else { 15 RESIZE_UNIT_PX,
35 int current_height = view->height; 16 RESIZE_UNIT_PPT,
36 view->desired_height = new_dimension; 17 RESIZE_UNIT_DEFAULT,
37 floating_view_sane_size(view); 18 RESIZE_UNIT_INVALID,
38 19};
39 int new_y = view->y + (int)(((view->desired_height - current_height) / 2) * -1);
40 view->height = view->desired_height;
41 view->y = new_y;
42 20
43 update_geometry(view); 21enum resize_axis {
44 } 22 RESIZE_AXIS_HORIZONTAL,
23 RESIZE_AXIS_VERTICAL,
24 RESIZE_AXIS_INVALID,
25};
45 26
46 return true; 27static enum resize_unit parse_resize_unit(const char *unit) {
28 if (strcasecmp(unit, "px") == 0) {
29 return RESIZE_UNIT_PX;
47 } 30 }
48 31 if (strcasecmp(unit, "ppt") == 0) {
49 return false; 32 return RESIZE_UNIT_PPT;
33 }
34 if (strcasecmp(unit, "default") == 0) {
35 return RESIZE_UNIT_DEFAULT;
36 }
37 return RESIZE_UNIT_INVALID;
50} 38}
51 39
52static bool resize_floating(int amount, bool use_width) { 40static enum resize_axis parse_resize_axis(const char *axis) {
53 swayc_t *view = current_container; 41 if (strcasecmp(axis, "width") == 0 || strcasecmp(axis, "horizontal") == 0) {
54 42 return RESIZE_AXIS_HORIZONTAL;
55 if (view) { 43 }
56 if (use_width) { 44 if (strcasecmp(axis, "height") == 0 || strcasecmp(axis, "vertical") == 0) {
57 return set_size_floating(view->width + amount, true); 45 return RESIZE_AXIS_VERTICAL;
58 } else {
59 return set_size_floating(view->height + amount, false);
60 }
61 } 46 }
47 return RESIZE_AXIS_INVALID;
48}
62 49
63 return false; 50static int parallel_coord(struct sway_container *c, enum resize_axis a) {
51 return a == RESIZE_AXIS_HORIZONTAL ? c->x : c->y;
64} 52}
65 53
66static bool resize_tiled(int amount, bool use_width) { 54static int parallel_size(struct sway_container *c, enum resize_axis a) {
67 swayc_t *container = current_container; 55 return a == RESIZE_AXIS_HORIZONTAL ? c->width : c->height;
68 swayc_t *parent = container->parent; 56}
69 int idx_focused = 0;
70 bool use_major = false;
71 size_t nb_before = 0;
72 size_t nb_after = 0;
73
74 // 1. Identify a container ancestor that will allow the focused child to grow in the requested
75 // direction.
76 while (container->parent) {
77 parent = container->parent;
78 if ((parent->children && parent->children->length > 1)
79 && (is_auto_layout(parent->layout)
80 || (use_width ? parent->layout == L_HORIZ : parent->layout == L_VERT))) {
81 // check if container has siblings that can provide/absorb the space needed for
82 // the resize operation.
83 use_major = use_width
84 ? parent->layout == L_AUTO_LEFT || parent->layout == L_AUTO_RIGHT
85 : parent->layout == L_AUTO_TOP || parent->layout == L_AUTO_BOTTOM;
86 // Note: use_major will be false for L_HORIZ and L_VERT
87
88 idx_focused = index_child(container);
89 if (idx_focused < 0) {
90 sway_log(L_ERROR, "Something weird is happening, child container not "
91 "present in its parent's children list.");
92 continue;
93 }
94 if (use_major) {
95 nb_before = auto_group_index(parent, idx_focused);
96 nb_after = auto_group_count(parent) - nb_before - 1;
97 } else {
98 nb_before = idx_focused - auto_group_start_index(parent, idx_focused);
99 nb_after = auto_group_end_index(parent, idx_focused) - idx_focused - 1;
100 sway_log(L_DEBUG, "+++ focused: %d, start: %d, end: %d, before: %d, after: %d",
101 idx_focused,
102 (int)auto_group_start_index(parent, idx_focused),
103 (int)auto_group_end_index(parent, idx_focused),
104 (int)nb_before, (int)nb_after);
105 57
106 } 58static void resize_tiled(int amount, enum resize_axis axis) {
107 if (nb_before || nb_after) { 59 struct sway_container *parent = config->handler_context.current_container;
108 break; 60 struct sway_container *focused = parent;
109 } 61 if (!parent) {
110 } 62 return;
111 container = parent; /* continue up the tree to the next ancestor */
112 }
113 if (parent == &root_container) {
114 return true;
115 } 63 }
116 sway_log(L_DEBUG, "Found the proper parent: %p. It has %zu before conts, " 64
117 "and %zu after conts", parent, nb_before, nb_after); 65 enum sway_container_layout parallel_layout =
118 // 2. Ensure that the resize operation will not make one of the resized containers drop 66 axis == RESIZE_AXIS_HORIZONTAL ? L_HORIZ : L_VERT;
119 // below the "sane" size threshold. 67 int minor_weight = 0;
120 bool valid = true; 68 int major_weight = 0;
121 swayc_t *focused = parent->children->items[idx_focused]; 69 while (parent->parent) {
122 int start = use_major ? 0 : auto_group_start_index(parent, idx_focused); 70 struct sway_container *next = parent->parent;
123 int end = use_major ? parent->children->length : auto_group_end_index(parent, idx_focused); 71 if (next->layout == parallel_layout) {
124 sway_log(L_DEBUG, "Check children of container %p [%d,%d[", container, start, end); 72 for (int i = 0; i < next->children->length; i++) {
125 for (int i = start; i < end; ) { 73 struct sway_container *sibling = next->children->items[i];
126 swayc_t *sibling = parent->children->items[i]; 74
127 double pixels = amount; 75 int sibling_pos = parallel_coord(sibling, axis);
128 bool is_before = use_width ? sibling->x < focused->x : sibling->y < focused->y; 76 int focused_pos = parallel_coord(focused, axis);
129 bool is_after = use_width ? sibling->x > focused->x : sibling->y > focused->y; 77 int parent_pos = parallel_coord(parent, axis);
130 if (is_before || is_after) { 78
131 pixels = -pixels; 79 if (sibling_pos != focused_pos) {
132 pixels /= is_before ? nb_before : nb_after; 80 if (sibling_pos < parent_pos) {
133 if (nb_after != 0 && nb_before != 0) { 81 minor_weight++;
134 pixels /= 2; 82 } else if (sibling_pos > parent_pos) {
135 } 83 major_weight++;
136 }
137 sway_log(L_DEBUG, "Check container %p: width %g vs %d, height %g vs %d", sibling, sibling->width + pixels, min_sane_w, sibling->height + pixels, min_sane_h);
138 if (use_width ?
139 sibling->width + pixels < min_sane_w :
140 sibling->height + pixels < min_sane_h) {
141 valid = false;
142 sway_log(L_DEBUG, "Container size no longer sane");
143 break;
144 }
145 i = use_major ? auto_group_end_index(parent, i) : (i + 1);
146 sway_log(L_DEBUG, "+++++ check %i", i);
147 }
148 // 3. Apply the size change
149 if (valid) {
150 for (int i = start; i < end; ) {
151 int next_i = use_major ? auto_group_end_index(parent, i) : (i + 1);
152 swayc_t *sibling = parent->children->items[i];
153 double pixels = amount;
154 bool is_before = use_width ? sibling->x < focused->x : sibling->y < focused->y;
155 bool is_after = use_width ? sibling->x > focused->x : sibling->y > focused->y;
156 if (is_before || is_after) {
157 pixels = -pixels;
158 pixels /= is_before ? nb_before : nb_after;
159 if (nb_after != 0 && nb_before != 0) {
160 pixels /= 2;
161 }
162 sway_log(L_DEBUG, "%p: %s", sibling, is_before ? "before" : "after");
163 if (use_major) {
164 for (int j = i; j < next_i; ++j) {
165 recursive_resize(parent->children->items[j], pixels,
166 use_width ?
167 (is_before ? WLC_RESIZE_EDGE_RIGHT : WLC_RESIZE_EDGE_LEFT) :
168 (is_before ? WLC_RESIZE_EDGE_BOTTOM : WLC_RESIZE_EDGE_TOP));
169 }
170 } else {
171 recursive_resize(sibling, pixels,
172 use_width ?
173 (is_before ? WLC_RESIZE_EDGE_RIGHT : WLC_RESIZE_EDGE_LEFT) :
174 (is_before ? WLC_RESIZE_EDGE_BOTTOM : WLC_RESIZE_EDGE_TOP));
175 }
176 } else {
177 if (use_major) {
178 for (int j = i; j < next_i; ++j) {
179 recursive_resize(parent->children->items[j], pixels / 2,
180 use_width ? WLC_RESIZE_EDGE_LEFT : WLC_RESIZE_EDGE_TOP);
181 recursive_resize(parent->children->items[j], pixels / 2,
182 use_width ? WLC_RESIZE_EDGE_RIGHT : WLC_RESIZE_EDGE_BOTTOM);
183 } 84 }
184 } else {
185 recursive_resize(sibling, pixels / 2,
186 use_width ? WLC_RESIZE_EDGE_LEFT : WLC_RESIZE_EDGE_TOP);
187 recursive_resize(sibling, pixels / 2,
188 use_width ? WLC_RESIZE_EDGE_RIGHT : WLC_RESIZE_EDGE_BOTTOM);
189 } 85 }
190 } 86 }
191 i = next_i; 87 if (major_weight || minor_weight) {
88 break;
89 }
192 } 90 }
193 // Recursive resize does not handle positions, let arrange_windows 91 parent = next;
194 // take care of that.
195 arrange_windows(swayc_active_workspace(), -1, -1);
196 } 92 }
197 return true;
198}
199
200static bool set_size_tiled(int amount, bool use_width) {
201 int desired;
202 swayc_t *focused = current_container;
203 93
204 if (use_width) { 94 if (parent->type == C_ROOT) {
205 desired = amount - focused->width; 95 return;
206 } else {
207 desired = amount - focused->height;
208 } 96 }
209 97
210 return resize_tiled(desired, use_width); 98 wlr_log(L_DEBUG,
211} 99 "Found the proper parent: %p. It has %d l conts, and %d r conts",
212 100 parent->parent, minor_weight, major_weight);
213static bool set_size(int dimension, bool use_width) {
214 swayc_t *focused = current_container;
215 101
216 if (focused) { 102 int min_sane = axis == RESIZE_AXIS_HORIZONTAL ? MIN_SANE_W : MIN_SANE_H;
217 if (focused->is_floating) {
218 return set_size_floating(dimension, use_width);
219 } else {
220 return set_size_tiled(dimension, use_width);
221 }
222 }
223 103
224 return false; 104 //TODO: Ensure rounding is done in such a way that there are NO pixel leaks
225} 105 // ^ ?????
226 106
227static bool resize(int dimension, bool use_width, enum resize_dim_types dim_type) { 107 for (int i = 0; i < parent->parent->children->length; i++) {
228 swayc_t *focused = current_container; 108 struct sway_container *sibling = parent->parent->children->items[i];
229 109
230 // translate "10 ppt" (10%) to appropriate # of pixels in case we need it 110 int sibling_pos = parallel_coord(sibling, axis);
231 float ppt_dim = (float)dimension / 100; 111 int focused_pos = parallel_coord(focused, axis);
112 int parent_pos = parallel_coord(parent, axis);
232 113
233 if (use_width) { 114 int sibling_size = parallel_size(sibling, axis);
234 ppt_dim = focused->width * ppt_dim; 115 int parent_size = parallel_size(parent, axis);
235 } else {
236 ppt_dim = focused->height * ppt_dim;
237 }
238 116
239 if (focused) { 117 if (sibling_pos != focused_pos) {
240 if (focused->is_floating) { 118 if (sibling_pos < parent_pos) {
241 // floating view resize dimensions should default to px, so only 119 double pixels = -amount / minor_weight;
242 // use ppt if specified 120 if (major_weight && (sibling_size + pixels / 2) < min_sane) {
243 if (dim_type == RESIZE_DIM_PPT) { 121 return; // Too small
244 dimension = (int)ppt_dim; 122 } else if ((sibling_size + pixels) < min_sane) {
123 return; // Too small
124 }
125 } else if (sibling_pos > parent_pos) {
126 double pixels = -amount / major_weight;
127 if (minor_weight && (sibling_size + pixels / 2) < min_sane) {
128 return; // Too small
129 } else if ((sibling_size + pixels) < min_sane) {
130 return; // Too small
131 }
245 } 132 }
246
247 return resize_floating(dimension, use_width);
248 } else { 133 } else {
249 // tiled view resize dimensions should default to ppt, so only use 134 double pixels = amount;
250 // px if specified 135 if (parent_size + pixels < min_sane) {
251 if (dim_type != RESIZE_DIM_PX) { 136 return; // Too small
252 dimension = (int)ppt_dim;
253 } 137 }
254
255 return resize_tiled(dimension, use_width);
256 } 138 }
257 } 139 }
258 140
259 return false; 141 enum resize_edge minor_edge = axis == RESIZE_AXIS_HORIZONTAL ?
260} 142 RESIZE_EDGE_LEFT : RESIZE_EDGE_TOP;
143 enum resize_edge major_edge = axis == RESIZE_AXIS_HORIZONTAL ?
144 RESIZE_EDGE_RIGHT : RESIZE_EDGE_BOTTOM;
261 145
262static struct cmd_results *cmd_resize_set(int argc, char **argv) { 146 for (int i = 0; i < parent->parent->children->length; i++) {
263 struct cmd_results *error = NULL; 147 struct sway_container *sibling = parent->parent->children->items[i];
264 if ((error = checkarg(argc, "resize set", EXPECTED_AT_LEAST, 2))) {
265 return error;
266 }
267 148
268 if (strcasecmp(argv[0], "width") == 0 || strcasecmp(argv[0], "height") == 0) { 149 int sibling_pos = parallel_coord(sibling, axis);
269 // handle `reset set width 100 px height 100 px` syntax, also allows 150 int focused_pos = parallel_coord(focused, axis);
270 // specifying only one dimension for a `resize set` 151 int parent_pos = parallel_coord(parent, axis);
271 int cmd_num = 0;
272 int dim;
273
274 while ((cmd_num + 1) < argc) {
275 dim = (int)strtol(argv[cmd_num + 1], NULL, 10);
276 if (errno == ERANGE || dim == 0) {
277 errno = 0;
278 return cmd_results_new(CMD_INVALID, "resize set",
279 "Expected 'resize set <width|height> <amount> [px] [<width|height> <amount> [px]]'");
280 }
281 152
282 if (strcasecmp(argv[cmd_num], "width") == 0) { 153 if (sibling_pos != focused_pos) {
283 set_size(dim, true); 154 if (sibling_pos < parent_pos) {
284 } else if (strcasecmp(argv[cmd_num], "height") == 0) { 155 double pixels = -1 * amount;
285 set_size(dim, false); 156 pixels /= minor_weight;
286 } else { 157 if (major_weight) {
287 return cmd_results_new(CMD_INVALID, "resize set", 158 container_recursive_resize(sibling, pixels / 2, major_edge);
288 "Expected 'resize set <width|height> <amount> [px] [<width|height> <amount> [px]]'"); 159 } else {
160 container_recursive_resize(sibling, pixels, major_edge);
161 }
162 } else if (sibling_pos > parent_pos) {
163 double pixels = -1 * amount;
164 pixels /= major_weight;
165 if (minor_weight) {
166 container_recursive_resize(sibling, pixels / 2, minor_edge);
167 } else {
168 container_recursive_resize(sibling, pixels, minor_edge);
169 }
289 } 170 }
290 171 } else {
291 cmd_num += 2; 172 if (major_weight != 0 && minor_weight != 0) {
292 173 double pixels = amount;
293 if (cmd_num < argc && strcasecmp(argv[cmd_num], "px") == 0) { 174 pixels /= 2;
294 // if this was `resize set width 400 px height 300 px`, disregard the `px` arg 175 container_recursive_resize(parent, pixels, minor_edge);
295 cmd_num++; 176 container_recursive_resize(parent, pixels, major_edge);
177 } else if (major_weight) {
178 container_recursive_resize(parent, amount, major_edge);
179 } else if (minor_weight) {
180 container_recursive_resize(parent, amount, minor_edge);
296 } 181 }
297 } 182 }
298 } else { 183 }
299 // handle `reset set 100 px 100 px` syntax
300 int width = (int)strtol(argv[0], NULL, 10);
301 if (errno == ERANGE || width == 0) {
302 errno = 0;
303 return cmd_results_new(CMD_INVALID, "resize set",
304 "Expected 'resize set <width> [px] <height> [px]'");
305 }
306 184
307 int height_arg = 1; 185 arrange_windows(parent->parent, -1, -1);
308 if (strcasecmp(argv[1], "px") == 0) { 186}
309 height_arg = 2;
310 }
311 187
312 int height = (int)strtol(argv[height_arg], NULL, 10); 188static void resize(int amount, enum resize_axis axis, enum resize_unit unit) {
313 if (errno == ERANGE || height == 0) { 189 struct sway_container *current = config->handler_context.current_container;
314 errno = 0; 190 if (unit == RESIZE_UNIT_DEFAULT) {
315 return cmd_results_new(CMD_INVALID, "resize set", 191 // Default for tiling; TODO floating should be px
316 "Expected 'resize set <width> [px] <height> [px]'"); 192 unit = RESIZE_UNIT_PPT;
317 } 193 }
318 194
319 set_size(width, true); 195 if (unit == RESIZE_UNIT_PPT) {
320 set_size(height, false); 196 float pct = amount / 100.0f;
197 switch (axis) {
198 case RESIZE_AXIS_HORIZONTAL:
199 amount = (float)current->width * pct;
200 break;
201 case RESIZE_AXIS_VERTICAL:
202 amount = (float)current->height * pct;
203 break;
204 default:
205 sway_assert(0, "invalid resize axis");
206 return;
207 }
321 } 208 }
322 209
323 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 210 return resize_tiled(amount, axis);
324} 211}
325 212
326struct cmd_results *cmd_resize(int argc, char **argv) { 213struct cmd_results *cmd_resize(int argc, char **argv) {
327 struct cmd_results *error = NULL; 214 struct sway_container *current = config->handler_context.current_container;
328 if (config->reading) return cmd_results_new(CMD_FAILURE, "resize", "Can't be used in config file."); 215 if (!current) {
329 if (!config->active) return cmd_results_new(CMD_FAILURE, "resize", "Can only be used when sway is running."); 216 return cmd_results_new(CMD_INVALID, "resize", "Cannot resize nothing");
330 217 }
218 if (current->type != C_VIEW && current->type != C_CONTAINER) {
219 return cmd_results_new(CMD_INVALID, "resize",
220 "Can only resize views/containers");
221 }
331 if (strcasecmp(argv[0], "set") == 0) { 222 if (strcasecmp(argv[0], "set") == 0) {
332 return cmd_resize_set(argc - 1, &argv[1]); 223 // TODO
224 //return cmd_resize_set(argc - 1, &argv[1]);
225 return cmd_results_new(CMD_INVALID, "resize", "resize set unimplemented");
333 } 226 }
334 227 struct cmd_results *error;
335 if ((error = checkarg(argc, "resize", EXPECTED_AT_LEAST, 2))) { 228 if ((error = checkarg(argc, "resize", EXPECTED_AT_LEAST, 2))) {
336 return error; 229 return error;
337 } 230 }
338 231
339 int dim_arg = argc - 1; 232 // TODO: resize grow|shrink left|right|up|down
340 233
341 enum resize_dim_types dim_type = RESIZE_DIM_DEFAULT; 234 const char *usage = "Expected 'resize <shrink|grow> "
342 if (strcasecmp(argv[dim_arg], "ppt") == 0) { 235 "<width|height> [<amount>] [px|ppt]'";
343 dim_type = RESIZE_DIM_PPT; 236
344 dim_arg--; 237 int multiplier = 0;
345 } else if (strcasecmp(argv[dim_arg], "px") == 0) { 238 if (strcasecmp(*argv, "grow") == 0) {
346 dim_type = RESIZE_DIM_PX; 239 multiplier = 1;
347 dim_arg--; 240 } else if (strcasecmp(*argv, "shrink") == 0) {
241 multiplier = -1;
242 } else {
243 return cmd_results_new(CMD_INVALID, "resize", usage);
348 } 244 }
245 --argc; ++argv;
349 246
350 int amount = (int)strtol(argv[dim_arg], NULL, 10); 247 enum resize_axis axis = parse_resize_axis(*argv);
351 if (errno == ERANGE || amount == 0) { 248 if (axis == RESIZE_AXIS_INVALID) {
352 errno = 0; 249 return cmd_results_new(CMD_INVALID, "resize", usage);
353 amount = 10; // this is the default resize dimension used by i3 for both px and ppt 250 }
354 sway_log(L_DEBUG, "Tried to get resize dimension out of '%s' but failed; setting dimension to default %d", 251 --argc; ++argv;
355 argv[dim_arg], amount); 252
253 int amount = 10; // Default amount
254 enum resize_unit unit = RESIZE_UNIT_DEFAULT;
255
256 if (argc) {
257 char *err;
258 amount = (int)strtol(*argv, &err, 10);
259 if (*err) {
260 // e.g. `resize grow width 10px`
261 unit = parse_resize_unit(err);
262 if (unit == RESIZE_UNIT_INVALID) {
263 return cmd_results_new(CMD_INVALID, "resize", usage);
264 }
265 }
266 --argc; ++argv;
356 } 267 }
357 268
358 bool use_width = false; 269 if (argc) {
359 if (strcasecmp(argv[1], "width") == 0) { 270 unit = parse_resize_unit(*argv);
360 use_width = true; 271 if (unit == RESIZE_UNIT_INVALID) {
361 } else if (strcasecmp(argv[1], "height") != 0) { 272 return cmd_results_new(CMD_INVALID, "resize", usage);
362 return cmd_results_new(CMD_INVALID, "resize", 273 }
363 "Expected 'resize <shrink|grow> <width|height> [<amount>] [px|ppt]'"); 274 --argc; ++argv;
364 } 275 }
365 276
366 if (strcasecmp(argv[0], "shrink") == 0) { 277 if (argc) {
367 amount *= -1; 278 // Provied too many args, the bastard
368 } else if (strcasecmp(argv[0], "grow") != 0) { 279 return cmd_results_new(CMD_INVALID, "resize", usage);
369 return cmd_results_new(CMD_INVALID, "resize",
370 "Expected 'resize <shrink|grow> <width|height> [<amount>] [px|ppt]'");
371 } 280 }
372 281
373 resize(amount, use_width, dim_type); 282 resize(amount * multiplier, axis, unit);
374 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 283 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
375} 284}
diff --git a/sway/commands/scratchpad.c b/sway/commands/scratchpad.c
deleted file mode 100644
index 6c5c92df..00000000
--- a/sway/commands/scratchpad.c
+++ /dev/null
@@ -1,72 +0,0 @@
1#include <string.h>
2#include <strings.h>
3#include <wlc/wlc.h>
4#include "sway/commands.h"
5#include "sway/container.h"
6#include "sway/focus.h"
7#include "sway/layout.h"
8
9static swayc_t *fetch_view_from_scratchpad() {
10 sp_index = (sp_index + 1) % scratchpad->length;
11 swayc_t *view = scratchpad->items[sp_index];
12
13 if (wlc_view_get_output(view->handle) != swayc_active_output()->handle) {
14 wlc_view_set_output(view->handle, swayc_active_output()->handle);
15 }
16 if (!view->is_floating) {
17 view->width = swayc_active_workspace()->width * 0.5;
18 view->height = swayc_active_workspace()->height * 0.75;
19 view->x = (swayc_active_workspace()->width - view->width)/2;
20 view->y = (swayc_active_workspace()->height - view->height)/2;
21 }
22 if (swayc_active_workspace()->width < view->x + 20 || view->x + view->width < 20) {
23 view->x = (swayc_active_workspace()->width - view->width)/2;
24 }
25 if (swayc_active_workspace()->height < view->y + 20 || view->y + view->height < 20) {
26 view->y = (swayc_active_workspace()->height - view->height)/2;
27 }
28
29 add_floating(swayc_active_workspace(), view);
30 wlc_view_set_mask(view->handle, VISIBLE);
31 view->visible = true;
32 arrange_windows(swayc_active_workspace(), -1, -1);
33 set_focused_container(view);
34 return view;
35}
36
37struct cmd_results *cmd_scratchpad(int argc, char **argv) {
38 struct cmd_results *error = NULL;
39 if (config->reading) return cmd_results_new(CMD_FAILURE, "scratchpad", "Can't be used in config file.");
40 if (!config->active) return cmd_results_new(CMD_FAILURE, "scratchpad", "Can only be used when sway is running.");
41 if ((error = checkarg(argc, "scratchpad", EXPECTED_EQUAL_TO, 1))) {
42 return error;
43 }
44
45 if (strcasecmp(argv[0], "show") == 0 && scratchpad->length > 0) {
46 if (!sp_view) {
47 if (current_container) {
48 // Haxor the scratchpad index if criteria'd
49 for (int i = 0; i < scratchpad->length; ++i) {
50 if (scratchpad->items[i] == current_container) {
51 sp_index = (i - 1) % scratchpad->length;
52 }
53 }
54 }
55 sp_view = fetch_view_from_scratchpad();
56 } else {
57 if (swayc_active_workspace() != sp_view->parent) {
58 hide_view_in_scratchpad(sp_view);
59 if (sp_index == 0) {
60 sp_index = scratchpad->length;
61 }
62 sp_index--;
63 sp_view = fetch_view_from_scratchpad();
64 } else {
65 hide_view_in_scratchpad(sp_view);
66 sp_view = NULL;
67 }
68 }
69 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
70 }
71 return cmd_results_new(CMD_FAILURE, "scratchpad", "Expected 'scratchpad show' when scratchpad is not empty.");
72}
diff --git a/sway/commands/seamless_mouse.c b/sway/commands/seamless_mouse.c
deleted file mode 100644
index 7760e88d..00000000
--- a/sway/commands/seamless_mouse.c
+++ /dev/null
@@ -1,13 +0,0 @@
1#include <string.h>
2#include <strings.h>
3#include "sway/commands.h"
4
5struct cmd_results *cmd_seamless_mouse(int argc, char **argv) {
6 struct cmd_results *error = NULL;
7 if ((error = checkarg(argc, "seamless_mouse", EXPECTED_EQUAL_TO, 1))) {
8 return error;
9 }
10
11 config->seamless_mouse = (strcasecmp(argv[0], "on") == 0 || strcasecmp(argv[0], "yes") == 0);
12 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
13}
diff --git a/sway/commands/seat.c b/sway/commands/seat.c
new file mode 100644
index 00000000..5916015f
--- /dev/null
+++ b/sway/commands/seat.c
@@ -0,0 +1,61 @@
1#include <string.h>
2#include <strings.h>
3#include "sway/commands.h"
4#include "sway/input/input-manager.h"
5#include "log.h"
6
7struct cmd_results *cmd_seat(int argc, char **argv) {
8 struct cmd_results *error = NULL;
9 if ((error = checkarg(argc, "seat", EXPECTED_AT_LEAST, 2))) {
10 return error;
11 }
12
13 if (config->reading && strcmp("{", argv[1]) == 0) {
14 free_seat_config(config->handler_context.seat_config);
15 config->handler_context.seat_config = new_seat_config(argv[0]);
16 if (!config->handler_context.seat_config) {
17 return cmd_results_new(CMD_FAILURE, NULL,
18 "Couldn't allocate config");
19 }
20 wlr_log(L_DEBUG, "entering seat block: %s", argv[0]);
21 return cmd_results_new(CMD_BLOCK_SEAT, NULL, NULL);
22 }
23
24 if ((error = checkarg(argc, "seat", EXPECTED_AT_LEAST, 3))) {
25 return error;
26 }
27
28 bool has_context = (config->handler_context.seat_config != NULL);
29 if (!has_context) {
30 config->handler_context.seat_config = new_seat_config(argv[0]);
31 if (!config->handler_context.seat_config) {
32 return cmd_results_new(CMD_FAILURE, NULL,
33 "Couldn't allocate config");
34 }
35 }
36
37 int argc_new = argc-2;
38 char **argv_new = argv+2;
39
40 struct cmd_results *res;
41 if (strcasecmp("attach", argv[1]) == 0) {
42 res = seat_cmd_attach(argc_new, argv_new);
43 } else if (strcasecmp("cursor", argv[1]) == 0) {
44 res = seat_cmd_cursor(argc_new, argv_new);
45 } else if (strcasecmp("fallback", argv[1]) == 0) {
46 res = seat_cmd_fallback(argc_new, argv_new);
47 } else {
48 res =
49 cmd_results_new(CMD_INVALID,
50 "seat <name>", "Unknown command %s",
51 argv[1]);
52 }
53
54 if (!has_context) {
55 // clean up the context we created earlier
56 free_seat_config(config->handler_context.seat_config);
57 config->handler_context.seat_config = NULL;
58 }
59
60 return res;
61}
diff --git a/sway/commands/seat/attach.c b/sway/commands/seat/attach.c
new file mode 100644
index 00000000..3e771c00
--- /dev/null
+++ b/sway/commands/seat/attach.c
@@ -0,0 +1,28 @@
1#define _XOPEN_SOURCE 700
2#include <string.h>
3#include <strings.h>
4#include "sway/input/input-manager.h"
5#include "sway/commands.h"
6#include "sway/config.h"
7#include "log.h"
8#include "stringop.h"
9
10struct cmd_results *seat_cmd_attach(int argc, char **argv) {
11 struct cmd_results *error = NULL;
12 if ((error = checkarg(argc, "attach", EXPECTED_AT_LEAST, 1))) {
13 return error;
14 }
15 struct seat_config *current_seat_config =
16 config->handler_context.seat_config;
17 if (!current_seat_config) {
18 return cmd_results_new(CMD_FAILURE, "attach", "No seat defined");
19 }
20
21 struct seat_config *new_config = new_seat_config(current_seat_config->name);
22 struct seat_attachment_config *new_attachment = seat_attachment_config_new();
23 new_attachment->identifier = strdup(argv[0]);
24 list_add(new_config->attachments, new_attachment);
25
26 apply_seat_config(new_config);
27 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
28}
diff --git a/sway/commands/seat/cursor.c b/sway/commands/seat/cursor.c
new file mode 100644
index 00000000..5dad97f1
--- /dev/null
+++ b/sway/commands/seat/cursor.c
@@ -0,0 +1,89 @@
1#define _XOPEN_SOURCE 700
2#ifdef __linux__
3#include <linux/input-event-codes.h>
4#elif __FreeBSD__
5#include <dev/evdev/input-event-codes.h>
6#endif
7
8#include <strings.h>
9#include <wlr/types/wlr_cursor.h>
10#include "sway/commands.h"
11#include "sway/input/cursor.h"
12
13static struct cmd_results *press_or_release(struct sway_cursor *cursor,
14 char *action, char *button_str, uint32_t time);
15
16static const char *expected_syntax = "Expected 'cursor <move> <x> <y>' or "
17 "'cursor <set> <x> <y>' or "
18 "'curor <press|release> <left|right|1|2|3...>'";
19
20struct cmd_results *seat_cmd_cursor(int argc, char **argv) {
21 struct cmd_results *error = NULL;
22 if ((error = checkarg(argc, "cursor", EXPECTED_AT_LEAST, 2))) {
23 return error;
24 }
25 struct sway_seat *seat = config->handler_context.seat;
26 if (!seat) {
27 return cmd_results_new(CMD_FAILURE, "cursor", "No seat defined");
28 }
29
30 struct sway_cursor *cursor = seat->cursor;
31
32 struct timespec now;
33 clock_gettime(CLOCK_MONOTONIC, &now);
34 uint32_t time = now.tv_nsec / 1000;
35
36 if (strcasecmp(argv[0], "move") == 0) {
37 if (argc < 3) {
38 return cmd_results_new(CMD_INVALID, "cursor", expected_syntax);
39 }
40 int delta_x = strtol(argv[1], NULL, 10);
41 int delta_y = strtol(argv[2], NULL, 10);
42 wlr_cursor_move(cursor->cursor, NULL, delta_x, delta_y);
43 cursor_send_pointer_motion(cursor, time);
44 } else if (strcasecmp(argv[0], "set") == 0) {
45 if (argc < 3) {
46 return cmd_results_new(CMD_INVALID, "cursor", expected_syntax);
47 }
48 // map absolute coords (0..1,0..1) to root container coords
49 float x = strtof(argv[1], NULL) / root_container.width;
50 float y = strtof(argv[2], NULL) / root_container.height;
51 wlr_cursor_warp_absolute(cursor->cursor, NULL, x, y);
52 cursor_send_pointer_motion(cursor, time);
53 } else {
54 if (argc < 2) {
55 return cmd_results_new(CMD_INVALID, "cursor", expected_syntax);
56 }
57 if ((error = press_or_release(cursor, argv[0], argv[1], time))) {
58 return error;
59 }
60 }
61
62 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
63}
64
65static struct cmd_results *press_or_release(struct sway_cursor *cursor,
66 char *action, char *button_str, uint32_t time) {
67 enum wlr_button_state state;
68 uint32_t button;
69 if (strcasecmp(action, "press") == 0) {
70 state = WLR_BUTTON_PRESSED;
71 } else if (strcasecmp(action, "release") == 0) {
72 state = WLR_BUTTON_RELEASED;
73 } else {
74 return cmd_results_new(CMD_INVALID, "cursor", expected_syntax);
75 }
76
77 if (strcasecmp(button_str, "left") == 0) {
78 button = BTN_LEFT;
79 } else if (strcasecmp(button_str, "right") == 0) {
80 button = BTN_RIGHT;
81 } else {
82 button = strtol(button_str, NULL, 10);
83 if (button == 0) {
84 return cmd_results_new(CMD_INVALID, "cursor", expected_syntax);
85 }
86 }
87 dispatch_cursor_button(cursor, time, button, state);
88 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
89}
diff --git a/sway/commands/seat/fallback.c b/sway/commands/seat/fallback.c
new file mode 100644
index 00000000..56feaab5
--- /dev/null
+++ b/sway/commands/seat/fallback.c
@@ -0,0 +1,32 @@
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 *seat_cmd_fallback(int argc, char **argv) {
8 struct cmd_results *error = NULL;
9 if ((error = checkarg(argc, "fallback", EXPECTED_AT_LEAST, 1))) {
10 return error;
11 }
12 struct seat_config *current_seat_config =
13 config->handler_context.seat_config;
14 if (!current_seat_config) {
15 return cmd_results_new(CMD_FAILURE, "fallback", "No seat defined");
16 }
17 struct seat_config *new_config =
18 new_seat_config(current_seat_config->name);
19
20 if (strcasecmp(argv[0], "true") == 0) {
21 new_config->fallback = 1;
22 } else if (strcasecmp(argv[0], "false") == 0) {
23 new_config->fallback = 0;
24 } else {
25 free_seat_config(new_config);
26 return cmd_results_new(CMD_INVALID, "fallback",
27 "Expected 'fallback <true|false>'");
28 }
29
30 apply_seat_config(new_config);
31 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
32}
diff --git a/sway/commands/set.c b/sway/commands/set.c
index 46fc6d38..84e9b792 100644
--- a/sway/commands/set.c
+++ b/sway/commands/set.c
@@ -5,6 +5,7 @@
5#include "sway/commands.h" 5#include "sway/commands.h"
6#include "sway/config.h" 6#include "sway/config.h"
7#include "list.h" 7#include "list.h"
8#include "log.h"
8#include "stringop.h" 9#include "stringop.h"
9 10
10// sort in order of longest->shortest 11// sort in order of longest->shortest
@@ -14,16 +15,24 @@ static int compare_set_qsort(const void *_l, const void *_r) {
14 return strlen(r->name) - strlen(l->name); 15 return strlen(r->name) - strlen(l->name);
15} 16}
16 17
18void free_sway_variable(struct sway_variable *var) {
19 if (!var) {
20 return;
21 }
22 free(var->name);
23 free(var->value);
24 free(var);
25}
26
17struct cmd_results *cmd_set(int argc, char **argv) { 27struct cmd_results *cmd_set(int argc, char **argv) {
18 char *tmp; 28 char *tmp;
19 struct cmd_results *error = NULL; 29 struct cmd_results *error = NULL;
20 if (!config->reading) return cmd_results_new(CMD_FAILURE, "set", "Can only be used in config file.");
21 if ((error = checkarg(argc, "set", EXPECTED_AT_LEAST, 2))) { 30 if ((error = checkarg(argc, "set", EXPECTED_AT_LEAST, 2))) {
22 return error; 31 return error;
23 } 32 }
24 33
25 if (argv[0][0] != '$') { 34 if (argv[0][0] != '$') {
26 sway_log(L_INFO, "Warning: variable '%s' doesn't start with $", argv[0]); 35 wlr_log(L_INFO, "Warning: variable '%s' doesn't start with $", argv[0]);
27 36
28 size_t size = snprintf(NULL, 0, "$%s", argv[0]); 37 size_t size = snprintf(NULL, 0, "$%s", argv[0]);
29 tmp = malloc(size + 1); 38 tmp = malloc(size + 1);
diff --git a/sway/commands/show_marks.c b/sway/commands/show_marks.c
deleted file mode 100644
index ed56d9e5..00000000
--- a/sway/commands/show_marks.c
+++ /dev/null
@@ -1,13 +0,0 @@
1#include <string.h>
2#include <strings.h>
3#include "sway/commands.h"
4
5struct cmd_results *cmd_show_marks(int argc, char **argv) {
6 struct cmd_results *error = NULL;
7 if ((error = checkarg(argc, "show_marks", EXPECTED_EQUAL_TO, 1))) {
8 return error;
9 }
10
11 config->show_marks = !strcasecmp(argv[0], "on");
12 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
13}
diff --git a/sway/commands/smart_gaps.c b/sway/commands/smart_gaps.c
deleted file mode 100644
index 815fc501..00000000
--- a/sway/commands/smart_gaps.c
+++ /dev/null
@@ -1,20 +0,0 @@
1#include <string.h>
2#include <strings.h>
3#include "sway/commands.h"
4
5struct cmd_results *cmd_smart_gaps(int argc, char **argv) {
6 struct cmd_results *error = NULL;
7 if ((error = checkarg(argc, "smart_gaps", EXPECTED_EQUAL_TO, 1))) {
8 return error;
9 }
10
11 if (strcasecmp(argv[0], "on") == 0) {
12 config->smart_gaps = true;
13 } else if (strcasecmp(argv[0], "off") == 0) {
14 config->smart_gaps = false;
15 } else {
16 return cmd_results_new(CMD_INVALID, "smart_gaps", "Expected 'smart_gaps <on|off>'");
17 }
18
19 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
20}
diff --git a/sway/commands/split.c b/sway/commands/split.c
index e3045a4f..130ed31f 100644
--- a/sway/commands/split.c
+++ b/sway/commands/split.c
@@ -1,76 +1,41 @@
1#include <string.h> 1#include <string.h>
2#include <strings.h> 2#include <strings.h>
3#include <wlc/wlc-render.h>
4#include "sway/border.h"
5#include "sway/commands.h" 3#include "sway/commands.h"
6#include "sway/container.h" 4#include "sway/tree/container.h"
7#include "sway/focus.h" 5#include "sway/tree/layout.h"
8#include "sway/layout.h" 6#include "sway/tree/view.h"
7#include "sway/input/input-manager.h"
8#include "sway/input/seat.h"
9#include "log.h" 9#include "log.h"
10 10
11static struct cmd_results *_do_split(int argc, char **argv, int layout) { 11static struct cmd_results *do_split(int layout) {
12 char *name = layout == L_VERT ? "splitv" : 12 struct sway_container *con = config->handler_context.current_container;
13 layout == L_HORIZ ? "splith" : "split"; 13 struct sway_container *parent = container_split(con, layout);
14 struct cmd_results *error = NULL; 14 container_create_notify(parent);
15 if (config->reading) return cmd_results_new(CMD_FAILURE, name, "Can't be used in config file."); 15 arrange_windows(parent, -1, -1);
16 if (!config->active) return cmd_results_new(CMD_FAILURE, name, "Can only be used when sway is running.");
17 if ((error = checkarg(argc, name, EXPECTED_EQUAL_TO, 0))) {
18 return error;
19 }
20 swayc_t *focused = current_container;
21
22 // Case of floating window, don't split
23 if (focused->is_floating) {
24 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
25 }
26 /* Case that focus is on an workspace with 0/1 children.change its layout */
27 if (focused->type == C_WORKSPACE && focused->children->length <= 1) {
28 sway_log(L_DEBUG, "changing workspace layout");
29 swayc_change_layout(focused, layout);
30 } else if (focused->type != C_WORKSPACE && focused->parent->children->length == 1) {
31 /* Case of no siblings. change parent layout */
32 sway_log(L_DEBUG, "changing container layout");
33 swayc_change_layout(focused->parent, layout);
34 } else {
35 /* regular case where new split container is build around focused container
36 * or in case of workspace, container inherits its children */
37 sway_log(L_DEBUG, "Adding new container around current focused container");
38 sway_log(L_INFO, "FOCUSED SIZE: %.f %.f", focused->width, focused->height);
39 swayc_t *parent = new_container(focused, layout);
40 set_focused_container(focused);
41 arrange_windows(parent, -1, -1);
42 }
43
44 // update container every time
45 // if it is tabbed/stacked then the title must change
46 // if the indicator color is different then the border must change
47 update_container_border(focused);
48 swayc_t *output = swayc_parent_by_type(focused, C_OUTPUT);
49 // schedule render to make changes take effect right away,
50 // otherwise we would have to wait for the view to render,
51 // which is unpredictable.
52 wlc_output_schedule_render(output->handle);
53 16
54 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 17 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
55} 18}
56 19
57struct cmd_results *cmd_split(int argc, char **argv) { 20struct cmd_results *cmd_split(int argc, char **argv) {
58 struct cmd_results *error = NULL; 21 struct cmd_results *error = NULL;
59 if (config->reading) return cmd_results_new(CMD_FAILURE, "split", "Can't be used in config file.");
60 if (!config->active) return cmd_results_new(CMD_FAILURE, "split", "Can only be used when sway is running.");
61 if ((error = checkarg(argc, "split", EXPECTED_EQUAL_TO, 1))) { 22 if ((error = checkarg(argc, "split", EXPECTED_EQUAL_TO, 1))) {
62 return error; 23 return error;
63 } 24 }
64 if (strcasecmp(argv[0], "v") == 0 || strcasecmp(argv[0], "vertical") == 0) { 25 if (strcasecmp(argv[0], "v") == 0 || strcasecmp(argv[0], "vertical") == 0) {
65 _do_split(argc - 1, argv + 1, L_VERT); 26 do_split(L_VERT);
66 } else if (strcasecmp(argv[0], "h") == 0 || strcasecmp(argv[0], "horizontal") == 0) { 27 } else if (strcasecmp(argv[0], "h") == 0 ||
67 _do_split(argc - 1, argv + 1, L_HORIZ); 28 strcasecmp(argv[0], "horizontal") == 0) {
68 } else if (strcasecmp(argv[0], "t") == 0 || strcasecmp(argv[0], "toggle") == 0) { 29 do_split(L_HORIZ);
69 swayc_t *focused = current_container; 30 } else if (strcasecmp(argv[0], "t") == 0 ||
31 strcasecmp(argv[0], "toggle") == 0) {
32 struct sway_container *focused =
33 config->handler_context.current_container;
34
70 if (focused->parent->layout == L_VERT) { 35 if (focused->parent->layout == L_VERT) {
71 _do_split(argc - 1, argv + 1, L_HORIZ); 36 do_split(L_HORIZ);
72 } else { 37 } else {
73 _do_split(argc - 1, argv + 1, L_VERT); 38 do_split(L_VERT);
74 } 39 }
75 } else { 40 } else {
76 error = cmd_results_new(CMD_FAILURE, "split", 41 error = cmd_results_new(CMD_FAILURE, "split",
@@ -81,18 +46,32 @@ struct cmd_results *cmd_split(int argc, char **argv) {
81} 46}
82 47
83struct cmd_results *cmd_splitv(int argc, char **argv) { 48struct cmd_results *cmd_splitv(int argc, char **argv) {
84 return _do_split(argc, argv, L_VERT); 49 struct cmd_results *error = NULL;
50 if ((error = checkarg(argc, "splitv", EXPECTED_EQUAL_TO, 0))) {
51 return error;
52 }
53 return do_split(L_VERT);
85} 54}
86 55
87struct cmd_results *cmd_splith(int argc, char **argv) { 56struct cmd_results *cmd_splith(int argc, char **argv) {
88 return _do_split(argc, argv, L_HORIZ); 57 struct cmd_results *error = NULL;
58 if ((error = checkarg(argc, "splitv", EXPECTED_EQUAL_TO, 0))) {
59 return error;
60 }
61 return do_split(L_HORIZ);
89} 62}
90 63
91struct cmd_results *cmd_splitt(int argc, char **argv) { 64struct cmd_results *cmd_splitt(int argc, char **argv) {
92 swayc_t *focused = current_container; 65 struct cmd_results *error = NULL;
93 if (focused->parent->layout == L_VERT) { 66 if ((error = checkarg(argc, "splitv", EXPECTED_EQUAL_TO, 0))) {
94 return _do_split(argc, argv, L_HORIZ); 67 return error;
68 }
69
70 struct sway_container *con = config->handler_context.current_container;
71
72 if (con->parent->layout == L_VERT) {
73 return do_split(L_HORIZ);
95 } else { 74 } else {
96 return _do_split(argc, argv, L_VERT); 75 return do_split(L_VERT);
97 } 76 }
98} 77}
diff --git a/sway/commands/sticky.c b/sway/commands/sticky.c
deleted file mode 100644
index 4899e061..00000000
--- a/sway/commands/sticky.c
+++ /dev/null
@@ -1,25 +0,0 @@
1#include <string.h>
2#include "sway/commands.h"
3#include "sway/focus.h"
4
5struct cmd_results *cmd_sticky(int argc, char **argv) {
6 struct cmd_results *error = NULL;
7 if (config->reading) return cmd_results_new(CMD_FAILURE, "sticky", "Can't be used in config file.");
8 if (!config->active) return cmd_results_new(CMD_FAILURE, "sticky", "Can only be used when sway is running.");
9 if ((error = checkarg(argc, "sticky", EXPECTED_EQUAL_TO, 1))) {
10 return error;
11 }
12 char *action = argv[0];
13 swayc_t *cont = get_focused_view(&root_container);
14 if (strcmp(action, "toggle") == 0) {
15 cont->sticky = !cont->sticky;
16 } else if (strcmp(action, "enable") == 0) {
17 cont->sticky = true;
18 } else if (strcmp(action, "disable") == 0) {
19 cont->sticky = false;
20 } else {
21 return cmd_results_new(CMD_FAILURE, "sticky",
22 "Expected 'sticky enable|disable|toggle'");
23 }
24 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
25}
diff --git a/sway/commands/swaybg_command.c b/sway/commands/swaybg_command.c
new file mode 100644
index 00000000..770d4821
--- /dev/null
+++ b/sway/commands/swaybg_command.c
@@ -0,0 +1,20 @@
1#include <string.h>
2#include "sway/commands.h"
3#include "log.h"
4#include "stringop.h"
5
6struct cmd_results *cmd_swaybg_command(int argc, char **argv) {
7 struct cmd_results *error = NULL;
8 if ((error = checkarg(argc, "swaybg_command", EXPECTED_AT_LEAST, 1))) {
9 return error;
10 }
11
12 if (config->swaybg_command) {
13 free(config->swaybg_command);
14 }
15 config->swaybg_command = join_args(argv, argc);
16 wlr_log(L_DEBUG, "Using custom swaybg command: %s",
17 config->swaybg_command);
18
19 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
20}
diff --git a/sway/commands/unmark.c b/sway/commands/unmark.c
deleted file mode 100644
index ac213261..00000000
--- a/sway/commands/unmark.c
+++ /dev/null
@@ -1,31 +0,0 @@
1#include <string.h>
2#include <strings.h>
3#include "sway/commands.h"
4#include "list.h"
5#include "stringop.h"
6
7struct cmd_results *cmd_unmark(int argc, char **argv) {
8 swayc_t *view = current_container;
9
10 if (view->marks) {
11 if (argc) {
12 char *mark = join_args(argv, argc);
13 int index;
14 if ((index = list_seq_find(view->marks, (int (*)(const void *, const void *))strcmp, mark)) != -1) {
15 free(view->marks->items[index]);
16 list_del(view->marks, index);
17
18 if (view->marks->length == 0) {
19 list_free(view->marks);
20 view->marks = NULL;
21 }
22 }
23 free(mark);
24 } else {
25 list_foreach(view->marks, free);
26 list_free(view->marks);
27 view->marks = NULL;
28 }
29 }
30 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
31}
diff --git a/sway/commands/workspace.c b/sway/commands/workspace.c
index a7839746..958b3222 100644
--- a/sway/commands/workspace.c
+++ b/sway/commands/workspace.c
@@ -3,8 +3,8 @@
3#include <strings.h> 3#include <strings.h>
4#include "sway/commands.h" 4#include "sway/commands.h"
5#include "sway/config.h" 5#include "sway/config.h"
6#include "sway/input_state.h" 6#include "sway/input/seat.h"
7#include "sway/workspace.h" 7#include "sway/tree/workspace.h"
8#include "list.h" 8#include "list.h"
9#include "log.h" 9#include "log.h"
10#include "stringop.h" 10#include "stringop.h"
@@ -17,6 +17,17 @@ struct cmd_results *cmd_workspace(int argc, char **argv) {
17 17
18 int output_location = -1; 18 int output_location = -1;
19 19
20 struct sway_container *current_container = config->handler_context.current_container;
21 struct sway_container *old_workspace = NULL, *old_output = NULL;
22 if (current_container) {
23 if (current_container->type == C_WORKSPACE) {
24 old_workspace = current_container;
25 } else {
26 old_workspace = container_parent(current_container, C_WORKSPACE);
27 }
28 old_output = container_parent(current_container, C_OUTPUT);
29 }
30
20 for (int i = 0; i < argc; ++i) { 31 for (int i = 0; i < argc; ++i) {
21 if (strcasecmp(argv[i], "output") == 0) { 32 if (strcasecmp(argv[i], "output") == 0) {
22 output_location = i; 33 output_location = i;
@@ -40,52 +51,51 @@ struct cmd_results *cmd_workspace(int argc, char **argv) {
40 free(old); // workspaces can only be assigned to a single output 51 free(old); // workspaces can only be assigned to a single output
41 list_del(config->workspace_outputs, i); 52 list_del(config->workspace_outputs, i);
42 } 53 }
43 sway_log(L_DEBUG, "Assigning workspace %s to output %s", wso->workspace, wso->output); 54 wlr_log(L_DEBUG, "Assigning workspace %s to output %s", wso->workspace, wso->output);
44 list_add(config->workspace_outputs, wso); 55 list_add(config->workspace_outputs, wso);
45 } else { 56 } else {
46 if (config->reading || !config->active) { 57 if (config->reading || !config->active) {
47 return cmd_results_new(CMD_DEFER, "workspace", NULL); 58 return cmd_results_new(CMD_DEFER, "workspace", NULL);
48 } 59 }
49 swayc_t *ws = NULL; 60 struct sway_container *ws = NULL;
50 if (strcasecmp(argv[0], "number") == 0) { 61 if (strcasecmp(argv[0], "number") == 0) {
51 if (!(ws = workspace_by_number(argv[1]))) { 62 if (!(ws = workspace_by_number(argv[1]))) {
52 char *name = join_args(argv + 1, argc - 1); 63 char *name = join_args(argv + 1, argc - 1);
53 ws = workspace_create(name); 64 ws = workspace_create(NULL, name);
54 free(name); 65 free(name);
55 } 66 }
56 } else if (strcasecmp(argv[0], "next") == 0) { 67 } else if (strcasecmp(argv[0], "next") == 0) {
57 ws = workspace_next(); 68 ws = workspace_next(old_workspace);
58 } else if (strcasecmp(argv[0], "prev") == 0) { 69 } else if (strcasecmp(argv[0], "prev") == 0) {
59 ws = workspace_prev(); 70 ws = workspace_prev(old_workspace);
60 } else if (strcasecmp(argv[0], "next_on_output") == 0) { 71 } else if (strcasecmp(argv[0], "next_on_output") == 0) {
61 ws = workspace_output_next(); 72 ws = workspace_output_next(old_output);
62 } else if (strcasecmp(argv[0], "prev_on_output") == 0) { 73 } else if (strcasecmp(argv[0], "prev_on_output") == 0) {
63 ws = workspace_output_prev(); 74 ws = workspace_output_prev(old_output);
64 } else if (strcasecmp(argv[0], "back_and_forth") == 0) { 75 } else if (strcasecmp(argv[0], "back_and_forth") == 0) {
65 // if auto_back_and_forth is enabled, workspace_switch will swap 76 // if auto_back_and_forth is enabled, workspace_switch will swap
66 // the workspaces. If we created prev_workspace here, workspace_switch 77 // the workspaces. If we created prev_workspace here, workspace_switch
67 // would put us back on original workspace. 78 // would put us back on original workspace.
68 if (config->auto_back_and_forth) { 79 if (config->auto_back_and_forth) {
69 ws = swayc_active_workspace(); 80 ws = old_workspace;
70 } else if (prev_workspace_name && !(ws = workspace_by_name(prev_workspace_name))) { 81 } else if (prev_workspace_name
71 ws = workspace_create(prev_workspace_name); 82 && !(ws = workspace_by_name(prev_workspace_name))) {
83 ws = workspace_create(NULL, prev_workspace_name);
72 } 84 }
73 } else { 85 } else {
74 char *name = join_args(argv, argc); 86 char *name = join_args(argv, argc);
75 if (!(ws = workspace_by_name(name))) { 87 if (!(ws = workspace_by_name(name))) {
76 ws = workspace_create(name); 88 ws = workspace_create(NULL, name);
77 } 89 }
78 free(name); 90 free(name);
79 } 91 }
80 swayc_t *old_output = swayc_active_output();
81 workspace_switch(ws); 92 workspace_switch(ws);
82 swayc_t *new_output = swayc_active_output(); 93 current_container =
94 seat_get_focus(config->handler_context.seat);
95 struct sway_container *new_output = container_parent(current_container, C_OUTPUT);
83 96
84 if (config->mouse_warping && old_output != new_output) { 97 if (config->mouse_warping && old_output != new_output) {
85 swayc_t *focused = get_focused_view(ws); 98 // TODO: Warp mouse
86 if (focused && focused->type == C_VIEW) {
87 center_pointer_on(focused);
88 }
89 } 99 }
90 } 100 }
91 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 101 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
diff --git a/sway/commands/workspace_layout.c b/sway/commands/workspace_layout.c
deleted file mode 100644
index 9ac84be2..00000000
--- a/sway/commands/workspace_layout.c
+++ /dev/null
@@ -1,40 +0,0 @@
1#include <string.h>
2#include <strings.h>
3#include "sway/commands.h"
4
5struct cmd_results *cmd_workspace_layout(int argc, char **argv) {
6 struct cmd_results *error = NULL;
7 if ((error = checkarg(argc, "workspace_layout", EXPECTED_AT_LEAST, 1))) {
8 return error;
9 }
10
11 if (strcasecmp(argv[0], "default") == 0) {
12 config->default_layout = L_NONE;
13 } else if (strcasecmp(argv[0], "stacking") == 0) {
14 config->default_layout = L_STACKED;
15 } else if (strcasecmp(argv[0], "tabbed") == 0) {
16 config->default_layout = L_TABBED;
17 } else if (strcasecmp(argv[0], "auto") == 0) {
18 if (argc == 1) {
19 config->default_layout = L_AUTO_FIRST;
20 } else {
21 if ((error = checkarg(argc, "workspace_layout auto", EXPECTED_EQUAL_TO, 2))) {
22 return error;
23 }
24 if (strcasecmp(argv[1], "left") == 0) {
25 config->default_layout = L_AUTO_LEFT;
26 } else if (strcasecmp(argv[1], "right") == 0) {
27 config->default_layout = L_AUTO_RIGHT;
28 } else if (strcasecmp(argv[1], "top") == 0) {
29 config->default_layout = L_AUTO_TOP;
30 } else if (strcasecmp(argv[1], "bottom") == 0) {
31 config->default_layout = L_AUTO_BOTTOM;
32 } else {
33 return cmd_results_new(CMD_INVALID, "workspace_layout auto", "Expected 'workspace_layout auto <left|right|top|bottom>'");
34 }
35 }
36 } else {
37 return cmd_results_new(CMD_INVALID, "workspace_layout", "Expected 'workspace_layout <default|stacking|tabbed|auto|auto left|auto right|auto top|auto bottom>'");
38 }
39 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
40}
diff --git a/sway/commands/workspace_auto_back_and_forth.c b/sway/commands/ws_auto_back_and_forth.c
index d58ae5c8..2485db35 100644
--- a/sway/commands/workspace_auto_back_and_forth.c
+++ b/sway/commands/ws_auto_back_and_forth.c
@@ -7,12 +7,6 @@ struct cmd_results *cmd_ws_auto_back_and_forth(int argc, char **argv) {
7 if ((error = checkarg(argc, "workspace_auto_back_and_forth", EXPECTED_EQUAL_TO, 1))) { 7 if ((error = checkarg(argc, "workspace_auto_back_and_forth", EXPECTED_EQUAL_TO, 1))) {
8 return error; 8 return error;
9 } 9 }
10 if (strcasecmp(argv[0], "yes") == 0) { 10 config->auto_back_and_forth = !strcasecmp(argv[0], "yes");
11 config->auto_back_and_forth = true;
12 } else if (strcasecmp(argv[0], "no") == 0) {
13 config->auto_back_and_forth = false;
14 } else {
15 return cmd_results_new(CMD_INVALID, "workspace_auto_back_and_forth", "Expected 'workspace_auto_back_and_forth <yes|no>'");
16 }
17 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 11 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
18} 12}
diff --git a/sway/config.c b/sway/config.c
index a33b8edc..90b833ab 100644
--- a/sway/config.c
+++ b/sway/config.c
@@ -12,17 +12,19 @@
12#include <signal.h> 12#include <signal.h>
13#include <libinput.h> 13#include <libinput.h>
14#include <limits.h> 14#include <limits.h>
15#include <float.h>
16#include <dirent.h> 15#include <dirent.h>
17#include <strings.h> 16#include <strings.h>
18#include "wayland-desktop-shell-server-protocol.h" 17#ifdef __linux__
18#include <linux/input-event-codes.h>
19#elif __FreeBSD__
20#include <dev/evdev/input-event-codes.h>
21#endif
22#include <wlr/types/wlr_output.h>
23#include "sway/input/input-manager.h"
24#include "sway/input/seat.h"
19#include "sway/commands.h" 25#include "sway/commands.h"
20#include "sway/config.h" 26#include "sway/config.h"
21#include "sway/layout.h" 27#include "sway/tree/layout.h"
22#include "sway/input_state.h"
23#include "sway/criteria.h"
24#include "sway/input.h"
25#include "sway/border.h"
26#include "readline.h" 28#include "readline.h"
27#include "stringop.h" 29#include "stringop.h"
28#include "list.h" 30#include "list.h"
@@ -30,269 +32,102 @@
30 32
31struct sway_config *config = NULL; 33struct sway_config *config = NULL;
32 34
33static void terminate_swaybar(pid_t pid);
34
35static void free_variable(struct sway_variable *var) {
36 if (!var) {
37 return;
38 }
39 free(var->name);
40 free(var->value);
41 free(var);
42}
43
44static void free_binding(struct sway_binding *bind) {
45 if (!bind) {
46 return;
47 }
48 free_flat_list(bind->keys);
49 free(bind->command);
50 free(bind);
51}
52
53static void free_mode(struct sway_mode *mode) { 35static void free_mode(struct sway_mode *mode) {
54 if (!mode) {
55 return;
56 }
57 free(mode->name);
58 int i;
59 for (i = 0; mode->bindings && i < mode->bindings->length; ++i) {
60 free_binding(mode->bindings->items[i]);
61 }
62 list_free(mode->bindings);
63 free(mode);
64}
65
66static void free_bar(struct bar_config *bar) {
67 if (!bar) {
68 return;
69 }
70 free(bar->mode);
71 free(bar->hidden_state);
72#ifdef ENABLE_TRAY
73 free(bar->tray_output);
74 free(bar->icon_theme);
75#endif
76 free(bar->status_command);
77 free(bar->font);
78 free(bar->separator_symbol);
79 int i; 36 int i;
80 for (i = 0; bar->bindings && i < bar->bindings->length; ++i) {
81 free_sway_mouse_binding(bar->bindings->items[i]);
82 }
83 list_free(bar->bindings);
84
85 if (bar->outputs) {
86 free_flat_list(bar->outputs);
87 }
88
89 if (bar->pid != 0) {
90 terminate_swaybar(bar->pid);
91 }
92
93 free(bar->colors.background);
94 free(bar->colors.statusline);
95 free(bar->colors.separator);
96 free(bar->colors.focused_background);
97 free(bar->colors.focused_statusline);
98 free(bar->colors.focused_separator);
99 free(bar->colors.focused_workspace_border);
100 free(bar->colors.focused_workspace_bg);
101 free(bar->colors.focused_workspace_text);
102 free(bar->colors.active_workspace_border);
103 free(bar->colors.active_workspace_bg);
104 free(bar->colors.active_workspace_text);
105 free(bar->colors.inactive_workspace_border);
106 free(bar->colors.inactive_workspace_bg);
107 free(bar->colors.inactive_workspace_text);
108 free(bar->colors.urgent_workspace_border);
109 free(bar->colors.urgent_workspace_bg);
110 free(bar->colors.urgent_workspace_text);
111 free(bar->colors.binding_mode_border);
112 free(bar->colors.binding_mode_bg);
113 free(bar->colors.binding_mode_text);
114
115 free(bar);
116}
117 37
118void free_input_config(struct input_config *ic) { 38 if (!mode) {
119 if (!ic) {
120 return;
121 }
122 free(ic->identifier);
123 free(ic);
124}
125
126void free_output_config(struct output_config *oc) {
127 if (!oc) {
128 return;
129 }
130 free(oc->name);
131 free(oc->background);
132 free(oc->background_option);
133 free(oc);
134}
135
136static void free_workspace_output(struct workspace_output *wo) {
137 if (!wo) {
138 return; 39 return;
139 } 40 }
140 free(wo->output); 41 free(mode->name);
141 free(wo->workspace); 42 if (mode->keysym_bindings) {
142 free(wo); 43 for (i = 0; i < mode->keysym_bindings->length; i++) {
143} 44 free_sway_binding(mode->keysym_bindings->items[i]);
144
145static void pid_workspace_cleanup() {
146 struct timespec ts;
147 struct pid_workspace *pw = NULL;
148
149 clock_gettime(CLOCK_MONOTONIC, &ts);
150
151 // work backwards through list and remove any entries
152 // older than PID_WORKSPACE_TIMEOUT
153 for (int i = config->pid_workspaces->length - 1; i > -1; i--) {
154 pw = config->pid_workspaces->items[i];
155
156 if (difftime(ts.tv_sec, *pw->time_added) >= PID_WORKSPACE_TIMEOUT) {
157 free_pid_workspace(config->pid_workspaces->items[i]);
158 list_del(config->pid_workspaces, i);
159 } 45 }
46 list_free(mode->keysym_bindings);
160 } 47 }
161} 48 if (mode->keycode_bindings) {
162 49 for (i = 0; i < mode->keycode_bindings->length; i++) {
163// de-dupe pid_workspaces to ensure pid uniqueness 50 free_sway_binding(mode->keycode_bindings->items[i]);
164void pid_workspace_add(struct pid_workspace *pw) {
165 struct pid_workspace *list_pw = NULL;
166 struct timespec ts;
167 time_t *now = malloc(sizeof(time_t));
168 if (!now) {
169 sway_log(L_ERROR, "Allocating time for pid_workspace failed");
170 return;
171 }
172
173 pid_workspace_cleanup();
174
175 // add current time to pw
176 clock_gettime(CLOCK_MONOTONIC, &ts);
177 *now = ts.tv_sec;
178
179 pw->time_added = now;
180
181 // work backwards through list and delete any entries that
182 // have the same pid as that in our new pid_workspace
183 for (int i = config->pid_workspaces->length - 1; i > -1; i--) {
184 list_pw = config->pid_workspaces->items[i];
185
186 if (pw->pid == list_pw->pid) {
187 free_pid_workspace(config->pid_workspaces->items[i]);
188 list_del(config->pid_workspaces, i);
189 } 51 }
52 list_free(mode->keycode_bindings);
190 } 53 }
191 54 free(mode);
192 list_add(config->pid_workspaces, pw);
193}
194
195void free_pid_workspace(struct pid_workspace *pw) {
196 if (!pw) {
197 return;
198 }
199 free(pw->pid);
200 free(pw->workspace);
201 free(pw->time_added);
202 free(pw);
203}
204
205void free_command_policy(struct command_policy *policy) {
206 if (!policy) {
207 return;
208 }
209 free(policy->command);
210 free(policy);
211}
212
213void free_feature_policy(struct feature_policy *policy) {
214 if (!policy) {
215 return;
216 }
217 free(policy->program);
218 free(policy);
219} 55}
220 56
221void free_config(struct sway_config *config) { 57void free_config(struct sway_config *config) {
58 config_clear_handler_context(config);
59
222 if (!config) { 60 if (!config) {
223 return; 61 return;
224 } 62 }
225 int i;
226 for (i = 0; config->symbols && i < config->symbols->length; ++i) {
227 free_variable(config->symbols->items[i]);
228 }
229 list_free(config->symbols);
230 63
231 for (i = 0; config->modes && i < config->modes->length; ++i) { 64 // TODO: handle all currently unhandled lists as we add implementations
232 free_mode(config->modes->items[i]); 65 if (config->symbols) {
66 for (int i = 0; i < config->symbols->length; ++i) {
67 free_sway_variable(config->symbols->items[i]);
68 }
69 list_free(config->symbols);
233 } 70 }
234 list_free(config->modes); 71 if (config->modes) {
235 72 for (int i = 0; i < config->modes->length; ++i) {
236 for (i = 0; config->bars && i < config->bars->length; ++i) { 73 free_mode(config->modes->items[i]);
237 free_bar(config->bars->items[i]); 74 }
75 list_free(config->modes);
238 } 76 }
239 list_free(config->bars); 77 if (config->bars) {
240 78 for (int i = 0; i < config->bars->length; ++i) {
241 free_flat_list(config->cmd_queue); 79 free_bar_config(config->bars->items[i]);
242 80 }
243 for (i = 0; config->workspace_outputs && i < config->workspace_outputs->length; ++i) { 81 list_free(config->bars);
244 free_workspace_output(config->workspace_outputs->items[i]);
245 } 82 }
83 list_free(config->cmd_queue);
246 list_free(config->workspace_outputs); 84 list_free(config->workspace_outputs);
247
248 for (i = 0; config->pid_workspaces && i < config->pid_workspaces->length; ++i) {
249 free_pid_workspace(config->pid_workspaces->items[i]);
250 }
251 list_free(config->pid_workspaces); 85 list_free(config->pid_workspaces);
252 86 list_free(config->output_configs);
253 for (i = 0; config->criteria && i < config->criteria->length; ++i) { 87 if (config->input_configs) {
254 free_criteria(config->criteria->items[i]); 88 for (int i = 0; i < config->input_configs->length; i++) {
89 free_input_config(config->input_configs->items[i]);
90 }
91 list_free(config->input_configs);
255 } 92 }
256 list_free(config->criteria); 93 if (config->seat_configs) {
257 94 for (int i = 0; i < config->seat_configs->length; i++) {
258 for (i = 0; config->no_focus && i < config->no_focus->length; ++i) { 95 free_seat_config(config->seat_configs->items[i]);
259 free_criteria(config->no_focus->items[i]); 96 }
97 list_free(config->seat_configs);
260 } 98 }
99 list_free(config->criteria);
261 list_free(config->no_focus); 100 list_free(config->no_focus);
262 101 list_free(config->active_bar_modifiers);
263 for (i = 0; config->input_configs && i < config->input_configs->length; ++i) { 102 list_free(config->config_chain);
264 free_input_config(config->input_configs->items[i]);
265 }
266 list_free(config->input_configs);
267
268 for (i = 0; config->output_configs && i < config->output_configs->length; ++i) {
269 free_output_config(config->output_configs->items[i]);
270 }
271 list_free(config->output_configs);
272
273 for (i = 0; config->command_policies && i < config->command_policies->length; ++i) {
274 free_command_policy(config->command_policies->items[i]);
275 }
276 list_free(config->command_policies); 103 list_free(config->command_policies);
277
278 for (i = 0; config->feature_policies && i < config->feature_policies->length; ++i) {
279 free_feature_policy(config->feature_policies->items[i]);
280 }
281 list_free(config->feature_policies); 104 list_free(config->feature_policies);
282 105 list_free(config->ipc_policies);
283 list_free(config->active_bar_modifiers); 106 free(config->current_bar);
284 free_flat_list(config->config_chain);
285 free(config->font);
286 free(config->floating_scroll_up_cmd); 107 free(config->floating_scroll_up_cmd);
287 free(config->floating_scroll_down_cmd); 108 free(config->floating_scroll_down_cmd);
288 free(config->floating_scroll_left_cmd); 109 free(config->floating_scroll_left_cmd);
289 free(config->floating_scroll_right_cmd); 110 free(config->floating_scroll_right_cmd);
111 free(config->font);
112 free((char *)config->current_config);
290 free(config); 113 free(config);
291} 114}
292 115
293 116static void destroy_removed_seats(struct sway_config *old_config,
294static bool file_exists(const char *path) { 117 struct sway_config *new_config) {
295 return path && access(path, R_OK) != -1; 118 struct seat_config *seat_config;
119 struct sway_seat *seat;
120 int i;
121 for (i = 0; i < old_config->seat_configs->length; i++) {
122 seat_config = old_config->seat_configs->items[i];
123 /* Also destroy seats that aren't present in new config */
124 if (new_config && list_seq_find(new_config->seat_configs,
125 seat_name_cmp, seat_config->name) < 0) {
126 seat = input_manager_get_seat(input_manager,
127 seat_config->name);
128 seat_destroy(seat);
129 }
130 }
296} 131}
297 132
298static void config_defaults(struct sway_config *config) { 133static void config_defaults(struct sway_config *config) {
@@ -304,19 +139,22 @@ static void config_defaults(struct sway_config *config) {
304 if (!(config->criteria = create_list())) goto cleanup; 139 if (!(config->criteria = create_list())) goto cleanup;
305 if (!(config->no_focus = create_list())) goto cleanup; 140 if (!(config->no_focus = create_list())) goto cleanup;
306 if (!(config->input_configs = create_list())) goto cleanup; 141 if (!(config->input_configs = create_list())) goto cleanup;
142 if (!(config->seat_configs = create_list())) goto cleanup;
307 if (!(config->output_configs = create_list())) goto cleanup; 143 if (!(config->output_configs = create_list())) goto cleanup;
308 144
309 if (!(config->cmd_queue = create_list())) goto cleanup; 145 if (!(config->cmd_queue = create_list())) goto cleanup;
310 146
311 if (!(config->current_mode = malloc(sizeof(struct sway_mode)))) goto cleanup; 147 if (!(config->current_mode = malloc(sizeof(struct sway_mode))))
148 goto cleanup;
312 if (!(config->current_mode->name = malloc(sizeof("default")))) goto cleanup; 149 if (!(config->current_mode->name = malloc(sizeof("default")))) goto cleanup;
313 strcpy(config->current_mode->name, "default"); 150 strcpy(config->current_mode->name, "default");
314 if (!(config->current_mode->bindings = create_list())) goto cleanup; 151 if (!(config->current_mode->keysym_bindings = create_list())) goto cleanup;
152 if (!(config->current_mode->keycode_bindings = create_list())) goto cleanup;
315 list_add(config->modes, config->current_mode); 153 list_add(config->modes, config->current_mode);
316 154
317 config->floating_mod = 0; 155 config->floating_mod = 0;
318 config->dragging_key = M_LEFT_CLICK; 156 config->dragging_key = BTN_LEFT;
319 config->resizing_key = M_RIGHT_CLICK; 157 config->resizing_key = BTN_RIGHT;
320 if (!(config->floating_scroll_up_cmd = strdup(""))) goto cleanup; 158 if (!(config->floating_scroll_up_cmd = strdup(""))) goto cleanup;
321 if (!(config->floating_scroll_down_cmd = strdup(""))) goto cleanup; 159 if (!(config->floating_scroll_down_cmd = strdup(""))) goto cleanup;
322 if (!(config->floating_scroll_left_cmd = strdup(""))) goto cleanup; 160 if (!(config->floating_scroll_left_cmd = strdup(""))) goto cleanup;
@@ -324,7 +162,8 @@ static void config_defaults(struct sway_config *config) {
324 config->default_layout = L_NONE; 162 config->default_layout = L_NONE;
325 config->default_orientation = L_NONE; 163 config->default_orientation = L_NONE;
326 if (!(config->font = strdup("monospace 10"))) goto cleanup; 164 if (!(config->font = strdup("monospace 10"))) goto cleanup;
327 config->font_height = get_font_text_height(config->font); 165 // TODO: border
166 //config->font_height = get_font_text_height(config->font);
328 167
329 // floating view 168 // floating view
330 config->floating_maximum_width = 0; 169 config->floating_maximum_width = 0;
@@ -339,7 +178,6 @@ static void config_defaults(struct sway_config *config) {
339 config->active = false; 178 config->active = false;
340 config->failed = false; 179 config->failed = false;
341 config->auto_back_and_forth = false; 180 config->auto_back_and_forth = false;
342 config->seamless_mouse = true;
343 config->reading = false; 181 config->reading = false;
344 config->show_marks = true; 182 config->show_marks = true;
345 183
@@ -403,29 +241,8 @@ cleanup:
403 sway_abort("Unable to allocate config structures"); 241 sway_abort("Unable to allocate config structures");
404} 242}
405 243
406static int compare_modifiers(const void *left, const void *right) { 244static bool file_exists(const char *path) {
407 uint32_t a = *(uint32_t *)left; 245 return path && access(path, R_OK) != -1;
408 uint32_t b = *(uint32_t *)right;
409
410 return a - b;
411}
412
413void update_active_bar_modifiers() {
414 if (config->active_bar_modifiers->length > 0) {
415 list_free(config->active_bar_modifiers);
416 config->active_bar_modifiers = create_list();
417 }
418
419 struct bar_config *bar;
420 int i;
421 for (i = 0; i < config->bars->length; ++i) {
422 bar = config->bars->items[i];
423 if (strcmp(bar->mode, "hide") == 0 && strcmp(bar->hidden_state, "hide") == 0) {
424 if (list_seq_find(config->active_bar_modifiers, compare_modifiers, &bar->modifier) < 0) {
425 list_add(config->active_bar_modifiers, &bar->modifier);
426 }
427 }
428 }
429} 246}
430 247
431static char *get_config_path(void) { 248static char *get_config_path(void) {
@@ -442,12 +259,12 @@ static char *get_config_path(void) {
442 char *home = getenv("HOME"); 259 char *home = getenv("HOME");
443 char *config_home = malloc(strlen(home) + strlen("/.config") + 1); 260 char *config_home = malloc(strlen(home) + strlen("/.config") + 1);
444 if (!config_home) { 261 if (!config_home) {
445 sway_log(L_ERROR, "Unable to allocate $HOME/.config"); 262 wlr_log(L_ERROR, "Unable to allocate $HOME/.config");
446 } else { 263 } else {
447 strcpy(config_home, home); 264 strcpy(config_home, home);
448 strcat(config_home, "/.config"); 265 strcat(config_home, "/.config");
449 setenv("XDG_CONFIG_HOME", config_home, 1); 266 setenv("XDG_CONFIG_HOME", config_home, 1);
450 sway_log(L_DEBUG, "Set XDG_CONFIG_HOME to %s", config_home); 267 wlr_log(L_DEBUG, "Set XDG_CONFIG_HOME to %s", config_home);
451 free(config_home); 268 free(config_home);
452 } 269 }
453 } 270 }
@@ -463,6 +280,7 @@ static char *get_config_path(void) {
463 if (file_exists(path)) { 280 if (file_exists(path)) {
464 return path; 281 return path;
465 } 282 }
283 free(path);
466 } 284 }
467 } 285 }
468 286
@@ -472,7 +290,7 @@ static char *get_config_path(void) {
472const char *current_config_path; 290const char *current_config_path;
473 291
474static bool load_config(const char *path, struct sway_config *config) { 292static bool load_config(const char *path, struct sway_config *config) {
475 sway_log(L_INFO, "Loading config from %s", path); 293 wlr_log(L_INFO, "Loading config from %s", path);
476 current_config_path = path; 294 current_config_path = path;
477 295
478 struct stat sb; 296 struct stat sb;
@@ -481,13 +299,13 @@ static bool load_config(const char *path, struct sway_config *config) {
481 } 299 }
482 300
483 if (path == NULL) { 301 if (path == NULL) {
484 sway_log(L_ERROR, "Unable to find a config file!"); 302 wlr_log(L_ERROR, "Unable to find a config file!");
485 return false; 303 return false;
486 } 304 }
487 305
488 FILE *f = fopen(path, "r"); 306 FILE *f = fopen(path, "r");
489 if (!f) { 307 if (!f) {
490 sway_log(L_ERROR, "Unable to open %s for reading", path); 308 wlr_log(L_ERROR, "Unable to open %s for reading", path);
491 return false; 309 return false;
492 } 310 }
493 311
@@ -495,20 +313,14 @@ static bool load_config(const char *path, struct sway_config *config) {
495 fclose(f); 313 fclose(f);
496 314
497 if (!config_load_success) { 315 if (!config_load_success) {
498 sway_log(L_ERROR, "Error(s) loading config!"); 316 wlr_log(L_ERROR, "Error(s) loading config!");
499 } 317 }
500 318
501 current_config_path = NULL; 319 current_config_path = NULL;
502 return true; 320 return true;
503} 321}
504 322
505static int qstrcmp(const void* a, const void* b) {
506 return strcmp(*((char**) a), *((char**) b));
507}
508
509bool load_main_config(const char *file, bool is_active) { 323bool load_main_config(const char *file, bool is_active) {
510 input_init();
511
512 char *path; 324 char *path;
513 if (file != NULL) { 325 if (file != NULL) {
514 path = strdup(file); 326 path = strdup(file);
@@ -524,7 +336,7 @@ bool load_main_config(const char *file, bool is_active) {
524 336
525 config_defaults(config); 337 config_defaults(config);
526 if (is_active) { 338 if (is_active) {
527 sway_log(L_DEBUG, "Performing configuration file reload"); 339 wlr_log(L_DEBUG, "Performing configuration file reload");
528 config->reloading = true; 340 config->reloading = true;
529 config->active = true; 341 config->active = true;
530 } 342 }
@@ -535,11 +347,14 @@ bool load_main_config(const char *file, bool is_active) {
535 config->reading = true; 347 config->reading = true;
536 348
537 // Read security configs 349 // Read security configs
350 // TODO: Security
538 bool success = true; 351 bool success = true;
352 /*
539 DIR *dir = opendir(SYSCONFDIR "/sway/security.d"); 353 DIR *dir = opendir(SYSCONFDIR "/sway/security.d");
540 if (!dir) { 354 if (!dir) {
541 sway_log(L_ERROR, "%s does not exist, sway will have no security configuration" 355 wlr_log(L_ERROR,
542 " and will probably be broken", SYSCONFDIR "/sway/security.d"); 356 "%s does not exist, sway will have no security configuration"
357 " and will probably be broken", SYSCONFDIR "/sway/security.d");
543 } else { 358 } else {
544 list_t *secconfigs = create_list(); 359 list_t *secconfigs = create_list();
545 char *base = SYSCONFDIR "/sway/security.d/"; 360 char *base = SYSCONFDIR "/sway/security.d/";
@@ -563,8 +378,12 @@ bool load_main_config(const char *file, bool is_active) {
563 list_qsort(secconfigs, qstrcmp); 378 list_qsort(secconfigs, qstrcmp);
564 for (int i = 0; i < secconfigs->length; ++i) { 379 for (int i = 0; i < secconfigs->length; ++i) {
565 char *_path = secconfigs->items[i]; 380 char *_path = secconfigs->items[i];
566 if (stat(_path, &s) || s.st_uid != 0 || s.st_gid != 0 || (((s.st_mode & 0777) != 0644) && (s.st_mode & 0777) != 0444)) { 381 if (stat(_path, &s) || s.st_uid != 0 || s.st_gid != 0 ||
567 sway_log(L_ERROR, "Refusing to load %s - it must be owned by root and mode 644 or 444", _path); 382 (((s.st_mode & 0777) != 0644) &&
383 (s.st_mode & 0777) != 0444)) {
384 wlr_log(L_ERROR,
385 "Refusing to load %s - it must be owned by root "
386 "and mode 644 or 444", _path);
568 success = false; 387 success = false;
569 } else { 388 } else {
570 success = success && load_config(_path, config); 389 success = success && load_config(_path, config);
@@ -573,6 +392,7 @@ bool load_main_config(const char *file, bool is_active) {
573 392
574 free_flat_list(secconfigs); 393 free_flat_list(secconfigs);
575 } 394 }
395 */
576 396
577 success = success && load_config(path, config); 397 success = success && load_config(path, config);
578 398
@@ -581,18 +401,15 @@ bool load_main_config(const char *file, bool is_active) {
581 } 401 }
582 402
583 if (old_config) { 403 if (old_config) {
404 destroy_removed_seats(old_config, config);
584 free_config(old_config); 405 free_config(old_config);
585 } 406 }
586 config->reading = false; 407 config->reading = false;
587
588 if (success) {
589 update_active_bar_modifiers();
590 }
591
592 return success; 408 return success;
593} 409}
594 410
595static bool load_include_config(const char *path, const char *parent_dir, struct sway_config *config) { 411static bool load_include_config(const char *path, const char *parent_dir,
412 struct sway_config *config) {
596 // save parent config 413 // save parent config
597 const char *parent_config = config->current_config; 414 const char *parent_config = config->current_config;
598 415
@@ -602,7 +419,8 @@ static bool load_include_config(const char *path, const char *parent_dir, struct
602 len = len + strlen(parent_dir) + 2; 419 len = len + strlen(parent_dir) + 2;
603 full_path = malloc(len * sizeof(char)); 420 full_path = malloc(len * sizeof(char));
604 if (!full_path) { 421 if (!full_path) {
605 sway_log(L_ERROR, "Unable to allocate full path to included config"); 422 wlr_log(L_ERROR,
423 "Unable to allocate full path to included config");
606 return false; 424 return false;
607 } 425 }
608 snprintf(full_path, len, "%s/%s", parent_dir, path); 426 snprintf(full_path, len, "%s/%s", parent_dir, path);
@@ -612,7 +430,7 @@ static bool load_include_config(const char *path, const char *parent_dir, struct
612 free(full_path); 430 free(full_path);
613 431
614 if (real_path == NULL) { 432 if (real_path == NULL) {
615 sway_log(L_DEBUG, "%s not found.", path); 433 wlr_log(L_DEBUG, "%s not found.", path);
616 return false; 434 return false;
617 } 435 }
618 436
@@ -621,7 +439,9 @@ static bool load_include_config(const char *path, const char *parent_dir, struct
621 for (j = 0; j < config->config_chain->length; ++j) { 439 for (j = 0; j < config->config_chain->length; ++j) {
622 char *old_path = config->config_chain->items[j]; 440 char *old_path = config->config_chain->items[j];
623 if (strcmp(real_path, old_path) == 0) { 441 if (strcmp(real_path, old_path) == 0) {
624 sway_log(L_DEBUG, "%s already included once, won't be included again.", real_path); 442 wlr_log(L_DEBUG,
443 "%s already included once, won't be included again.",
444 real_path);
625 free(real_path); 445 free(real_path);
626 return false; 446 return false;
627 } 447 }
@@ -673,7 +493,7 @@ bool load_include_configs(const char *path, struct sway_config *config) {
673 // restore wd 493 // restore wd
674 if (chdir(wd) < 0) { 494 if (chdir(wd) < 0) {
675 free(wd); 495 free(wd);
676 sway_log(L_ERROR, "failed to restore working directory"); 496 wlr_log(L_ERROR, "failed to restore working directory");
677 return false; 497 return false;
678 } 498 }
679 499
@@ -681,13 +501,11 @@ bool load_include_configs(const char *path, struct sway_config *config) {
681 return true; 501 return true;
682} 502}
683 503
684struct cmd_results *check_security_config() { 504void config_clear_handler_context(struct sway_config *config) {
685 if (!current_config_path || strncmp(SYSCONFDIR "/sway/security.d/", current_config_path, 505 free_input_config(config->handler_context.input_config);
686 strlen(SYSCONFDIR "/sway/security.d/")) != 0) { 506 free_seat_config(config->handler_context.seat_config);
687 return cmd_results_new(CMD_INVALID, "permit", 507
688 "This command is only permitted to run from " SYSCONFDIR "/sway/security.d/*"); 508 memset(&config->handler_context, 0, sizeof(config->handler_context));
689 }
690 return NULL;
691} 509}
692 510
693bool read_config(FILE *file, struct sway_config *config) { 511bool read_config(FILE *file, struct sway_config *config) {
@@ -717,13 +535,13 @@ bool read_config(FILE *file, struct sway_config *config) {
717 switch(res->status) { 535 switch(res->status) {
718 case CMD_FAILURE: 536 case CMD_FAILURE:
719 case CMD_INVALID: 537 case CMD_INVALID:
720 sway_log(L_ERROR, "Error on line %i '%s': %s (%s)", line_number, line, 538 wlr_log(L_ERROR, "Error on line %i '%s': %s (%s)", line_number,
721 res->error, config->current_config); 539 line, res->error, config->current_config);
722 success = false; 540 success = false;
723 break; 541 break;
724 542
725 case CMD_DEFER: 543 case CMD_DEFER:
726 sway_log(L_DEBUG, "Defferring command `%s'", line); 544 wlr_log(L_DEBUG, "Deferring command `%s'", line);
727 list_add(config->cmd_queue, strdup(line)); 545 list_add(config->cmd_queue, strdup(line));
728 break; 546 break;
729 547
@@ -731,7 +549,7 @@ bool read_config(FILE *file, struct sway_config *config) {
731 if (block == CMD_BLOCK_END) { 549 if (block == CMD_BLOCK_END) {
732 block = CMD_BLOCK_MODE; 550 block = CMD_BLOCK_MODE;
733 } else { 551 } else {
734 sway_log(L_ERROR, "Invalid block '%s'", line); 552 wlr_log(L_ERROR, "Invalid block '%s'", line);
735 } 553 }
736 break; 554 break;
737 555
@@ -739,7 +557,15 @@ bool read_config(FILE *file, struct sway_config *config) {
739 if (block == CMD_BLOCK_END) { 557 if (block == CMD_BLOCK_END) {
740 block = CMD_BLOCK_INPUT; 558 block = CMD_BLOCK_INPUT;
741 } else { 559 } else {
742 sway_log(L_ERROR, "Invalid block '%s'", line); 560 wlr_log(L_ERROR, "Invalid block '%s'", line);
561 }
562 break;
563
564 case CMD_BLOCK_SEAT:
565 if (block == CMD_BLOCK_END) {
566 block = CMD_BLOCK_SEAT;
567 } else {
568 wlr_log(L_ERROR, "Invalid block '%s'", line);
743 } 569 }
744 break; 570 break;
745 571
@@ -747,7 +573,7 @@ bool read_config(FILE *file, struct sway_config *config) {
747 if (block == CMD_BLOCK_END) { 573 if (block == CMD_BLOCK_END) {
748 block = CMD_BLOCK_BAR; 574 block = CMD_BLOCK_BAR;
749 } else { 575 } else {
750 sway_log(L_ERROR, "Invalid block '%s'", line); 576 wlr_log(L_ERROR, "Invalid block '%s'", line);
751 } 577 }
752 break; 578 break;
753 579
@@ -755,7 +581,7 @@ bool read_config(FILE *file, struct sway_config *config) {
755 if (block == CMD_BLOCK_BAR) { 581 if (block == CMD_BLOCK_BAR) {
756 block = CMD_BLOCK_BAR_COLORS; 582 block = CMD_BLOCK_BAR_COLORS;
757 } else { 583 } else {
758 sway_log(L_ERROR, "Invalid block '%s'", line); 584 wlr_log(L_ERROR, "Invalid block '%s'", line);
759 } 585 }
760 break; 586 break;
761 587
@@ -763,7 +589,7 @@ bool read_config(FILE *file, struct sway_config *config) {
763 if (block == CMD_BLOCK_END) { 589 if (block == CMD_BLOCK_END) {
764 block = CMD_BLOCK_COMMANDS; 590 block = CMD_BLOCK_COMMANDS;
765 } else { 591 } else {
766 sway_log(L_ERROR, "Invalid block '%s'", line); 592 wlr_log(L_ERROR, "Invalid block '%s'", line);
767 } 593 }
768 break; 594 break;
769 595
@@ -771,7 +597,7 @@ bool read_config(FILE *file, struct sway_config *config) {
771 if (block == CMD_BLOCK_END) { 597 if (block == CMD_BLOCK_END) {
772 block = CMD_BLOCK_IPC; 598 block = CMD_BLOCK_IPC;
773 } else { 599 } else {
774 sway_log(L_ERROR, "Invalid block '%s'", line); 600 wlr_log(L_ERROR, "Invalid block '%s'", line);
775 } 601 }
776 break; 602 break;
777 603
@@ -779,56 +605,61 @@ bool read_config(FILE *file, struct sway_config *config) {
779 if (block == CMD_BLOCK_IPC) { 605 if (block == CMD_BLOCK_IPC) {
780 block = CMD_BLOCK_IPC_EVENTS; 606 block = CMD_BLOCK_IPC_EVENTS;
781 } else { 607 } else {
782 sway_log(L_ERROR, "Invalid block '%s'", line); 608 wlr_log(L_ERROR, "Invalid block '%s'", line);
783 } 609 }
784 break; 610 break;
785 611
786 case CMD_BLOCK_END: 612 case CMD_BLOCK_END:
787 switch(block) { 613 switch(block) {
788 case CMD_BLOCK_MODE: 614 case CMD_BLOCK_MODE:
789 sway_log(L_DEBUG, "End of mode block"); 615 wlr_log(L_DEBUG, "End of mode block");
790 config->current_mode = config->modes->items[0]; 616 config->current_mode = config->modes->items[0];
791 block = CMD_BLOCK_END; 617 block = CMD_BLOCK_END;
792 break; 618 break;
793 619
794 case CMD_BLOCK_INPUT: 620 case CMD_BLOCK_INPUT:
795 sway_log(L_DEBUG, "End of input block"); 621 wlr_log(L_DEBUG, "End of input block");
796 current_input_config = NULL; 622 block = CMD_BLOCK_END;
623 break;
624
625 case CMD_BLOCK_SEAT:
626 wlr_log(L_DEBUG, "End of seat block");
797 block = CMD_BLOCK_END; 627 block = CMD_BLOCK_END;
798 break; 628 break;
799 629
800 case CMD_BLOCK_BAR: 630 case CMD_BLOCK_BAR:
801 sway_log(L_DEBUG, "End of bar block"); 631 wlr_log(L_DEBUG, "End of bar block");
802 config->current_bar = NULL; 632 config->current_bar = NULL;
803 block = CMD_BLOCK_END; 633 block = CMD_BLOCK_END;
804 break; 634 break;
805 635
806 case CMD_BLOCK_BAR_COLORS: 636 case CMD_BLOCK_BAR_COLORS:
807 sway_log(L_DEBUG, "End of bar colors block"); 637 wlr_log(L_DEBUG, "End of bar colors block");
808 block = CMD_BLOCK_BAR; 638 block = CMD_BLOCK_BAR;
809 break; 639 break;
810 640
811 case CMD_BLOCK_COMMANDS: 641 case CMD_BLOCK_COMMANDS:
812 sway_log(L_DEBUG, "End of commands block"); 642 wlr_log(L_DEBUG, "End of commands block");
813 block = CMD_BLOCK_END; 643 block = CMD_BLOCK_END;
814 break; 644 break;
815 645
816 case CMD_BLOCK_IPC: 646 case CMD_BLOCK_IPC:
817 sway_log(L_DEBUG, "End of IPC block"); 647 wlr_log(L_DEBUG, "End of IPC block");
818 block = CMD_BLOCK_END; 648 block = CMD_BLOCK_END;
819 break; 649 break;
820 650
821 case CMD_BLOCK_IPC_EVENTS: 651 case CMD_BLOCK_IPC_EVENTS:
822 sway_log(L_DEBUG, "End of IPC events block"); 652 wlr_log(L_DEBUG, "End of IPC events block");
823 block = CMD_BLOCK_IPC; 653 block = CMD_BLOCK_IPC;
824 break; 654 break;
825 655
826 case CMD_BLOCK_END: 656 case CMD_BLOCK_END:
827 sway_log(L_ERROR, "Unmatched }"); 657 wlr_log(L_ERROR, "Unmatched }");
828 break; 658 break;
829 659
830 default:; 660 default:;
831 } 661 }
662 config_clear_handler_context(config);
832 default:; 663 default:;
833 } 664 }
834 free(line); 665 free(line);
@@ -838,365 +669,6 @@ bool read_config(FILE *file, struct sway_config *config) {
838 return success; 669 return success;
839} 670}
840 671
841int input_identifier_cmp(const void *item, const void *data) {
842 const struct input_config *ic = item;
843 const char *identifier = data;
844 return strcmp(ic->identifier, identifier);
845}
846
847int output_name_cmp(const void *item, const void *data) {
848 const struct output_config *output = item;
849 const char *name = data;
850
851 return strcmp(output->name, name);
852}
853
854void merge_input_config(struct input_config *dst, struct input_config *src) {
855 if (src->identifier) {
856 if (dst->identifier) {
857 free(dst->identifier);
858 }
859 dst->identifier = strdup(src->identifier);
860 }
861 if (src->accel_profile != INT_MIN) {
862 dst->accel_profile = src->accel_profile;
863 }
864 if (src->click_method != INT_MIN) {
865 dst->click_method = src->click_method;
866 }
867 if (src->drag_lock != INT_MIN) {
868 dst->drag_lock = src->drag_lock;
869 }
870 if (src->dwt != INT_MIN) {
871 dst->dwt = src->dwt;
872 }
873 if (src->middle_emulation != INT_MIN) {
874 dst->middle_emulation = src->middle_emulation;
875 }
876 if (src->natural_scroll != INT_MIN) {
877 dst->natural_scroll = src->natural_scroll;
878 }
879 if (src->pointer_accel != FLT_MIN) {
880 dst->pointer_accel = src->pointer_accel;
881 }
882 if (src->scroll_method != INT_MIN) {
883 dst->scroll_method = src->scroll_method;
884 }
885 if (src->send_events != INT_MIN) {
886 dst->send_events = src->send_events;
887 }
888 if (src->tap != INT_MIN) {
889 dst->tap = src->tap;
890 }
891}
892
893void merge_output_config(struct output_config *dst, struct output_config *src) {
894 if (src->name) {
895 if (dst->name) {
896 free(dst->name);
897 }
898 dst->name = strdup(src->name);
899 }
900 if (src->enabled != -1) {
901 dst->enabled = src->enabled;
902 }
903 if (src->width != -1) {
904 dst->width = src->width;
905 }
906 if (src->height != -1) {
907 dst->height = src->height;
908 }
909 if (src->x != -1) {
910 dst->x = src->x;
911 }
912 if (src->y != -1) {
913 dst->y = src->y;
914 }
915 if (src->scale != -1) {
916 dst->scale = src->scale;
917 }
918 if (src->background) {
919 if (dst->background) {
920 free(dst->background);
921 }
922 dst->background = strdup(src->background);
923 }
924 if (src->background_option) {
925 if (dst->background_option) {
926 free(dst->background_option);
927 }
928 dst->background_option = strdup(src->background_option);
929 }
930}
931
932static void invoke_swaybar(struct bar_config *bar) {
933 // Pipe to communicate errors
934 int filedes[2];
935 if (pipe(filedes) == -1) {
936 sway_log(L_ERROR, "Pipe setup failed! Cannot fork into bar");
937 return;
938 }
939
940 bar->pid = fork();
941 if (bar->pid == 0) {
942 close(filedes[0]);
943 if (!bar->swaybar_command) {
944 char *const cmd[] = {
945 "swaybar",
946 "-b",
947 bar->id,
948 NULL,
949 };
950
951 close(filedes[1]);
952 execvp(cmd[0], cmd);
953 _exit(EXIT_SUCCESS);
954 } else {
955 // run custom swaybar
956 int len = strlen(bar->swaybar_command) + strlen(bar->id) + 5;
957 char *command = malloc(len * sizeof(char));
958 if (!command) {
959 const char msg[] = "Unable to allocate swaybar command string";
960 int len = sizeof(msg);
961 if (write(filedes[1], &len, sizeof(int))) {};
962 if (write(filedes[1], msg, len)) {};
963 close(filedes[1]);
964 _exit(EXIT_FAILURE);
965 }
966 snprintf(command, len, "%s -b %s", bar->swaybar_command, bar->id);
967
968 char *const cmd[] = {
969 "sh",
970 "-c",
971 command,
972 NULL,
973 };
974
975 close(filedes[1]);
976 execvp(cmd[0], cmd);
977 free(command);
978 _exit(EXIT_SUCCESS);
979 }
980 }
981 close(filedes[0]);
982 int len;
983 if(read(filedes[1], &len, sizeof(int)) == sizeof(int)) {
984 char *buf = malloc(len);
985 if(!buf) {
986 sway_log(L_ERROR, "Cannot allocate error string");
987 return;
988 }
989 if(read(filedes[1], buf, len)) {
990 sway_log(L_ERROR, "%s", buf);
991 }
992 free(buf);
993 }
994 close(filedes[1]);
995}
996
997static void terminate_swaybar(pid_t pid) {
998 int ret = kill(pid, SIGTERM);
999 if (ret != 0) {
1000 sway_log(L_ERROR, "Unable to terminate swaybar [pid: %d]", pid);
1001 } else {
1002 int status;
1003 waitpid(pid, &status, 0);
1004 }
1005}
1006
1007void terminate_swaybg(pid_t pid) {
1008 int ret = kill(pid, SIGTERM);
1009 if (ret != 0) {
1010 sway_log(L_ERROR, "Unable to terminate swaybg [pid: %d]", pid);
1011 } else {
1012 int status;
1013 waitpid(pid, &status, 0);
1014 }
1015}
1016
1017static bool active_output(const char *name) {
1018 int i;
1019 swayc_t *cont = NULL;
1020 for (i = 0; i < root_container.children->length; ++i) {
1021 cont = root_container.children->items[i];
1022 if (cont->type == C_OUTPUT && strcasecmp(name, cont->name) == 0) {
1023 return true;
1024 }
1025 }
1026
1027 return false;
1028}
1029
1030void load_swaybars() {
1031 // Check for bars
1032 list_t *bars = create_list();
1033 struct bar_config *bar = NULL;
1034 int i;
1035 for (i = 0; i < config->bars->length; ++i) {
1036 bar = config->bars->items[i];
1037 bool apply = false;
1038 if (bar->outputs) {
1039 int j;
1040 for (j = 0; j < bar->outputs->length; ++j) {
1041 char *o = bar->outputs->items[j];
1042 if (!strcmp(o, "*") || active_output(o)) {
1043 apply = true;
1044 break;
1045 }
1046 }
1047 } else {
1048 apply = true;
1049 }
1050 if (apply) {
1051 list_add(bars, bar);
1052 }
1053 }
1054
1055 for (i = 0; i < bars->length; ++i) {
1056 bar = bars->items[i];
1057 if (bar->pid != 0) {
1058 terminate_swaybar(bar->pid);
1059 }
1060 sway_log(L_DEBUG, "Invoking swaybar for bar id '%s'", bar->id);
1061 invoke_swaybar(bar);
1062 }
1063
1064 list_free(bars);
1065}
1066
1067void apply_input_config(struct input_config *ic, struct libinput_device *dev) {
1068 if (!ic) {
1069 return;
1070 }
1071
1072 sway_log(L_DEBUG, "apply_input_config(%s)", ic->identifier);
1073
1074 if (ic->accel_profile != INT_MIN) {
1075 sway_log(L_DEBUG, "apply_input_config(%s) accel_set_profile(%d)", ic->identifier, ic->accel_profile);
1076 libinput_device_config_accel_set_profile(dev, ic->accel_profile);
1077 }
1078 if (ic->click_method != INT_MIN) {
1079 sway_log(L_DEBUG, "apply_input_config(%s) click_set_method(%d)", ic->identifier, ic->click_method);
1080 libinput_device_config_click_set_method(dev, ic->click_method);
1081 }
1082 if (ic->drag_lock != INT_MIN) {
1083 sway_log(L_DEBUG, "apply_input_config(%s) tap_set_drag_lock_enabled(%d)", ic->identifier, ic->click_method);
1084 libinput_device_config_tap_set_drag_lock_enabled(dev, ic->drag_lock);
1085 }
1086 if (ic->dwt != INT_MIN) {
1087 sway_log(L_DEBUG, "apply_input_config(%s) dwt_set_enabled(%d)", ic->identifier, ic->dwt);
1088 libinput_device_config_dwt_set_enabled(dev, ic->dwt);
1089 }
1090 if (ic->left_handed != INT_MIN) {
1091 sway_log(L_DEBUG, "apply_input_config(%s) left_handed_set_enabled(%d)", ic->identifier, ic->left_handed);
1092 libinput_device_config_left_handed_set(dev, ic->left_handed);
1093 }
1094 if (ic->middle_emulation != INT_MIN) {
1095 sway_log(L_DEBUG, "apply_input_config(%s) middle_emulation_set_enabled(%d)", ic->identifier, ic->middle_emulation);
1096 libinput_device_config_middle_emulation_set_enabled(dev, ic->middle_emulation);
1097 }
1098 if (ic->natural_scroll != INT_MIN) {
1099 sway_log(L_DEBUG, "apply_input_config(%s) natural_scroll_set_enabled(%d)", ic->identifier, ic->natural_scroll);
1100 libinput_device_config_scroll_set_natural_scroll_enabled(dev, ic->natural_scroll);
1101 }
1102 if (ic->pointer_accel != FLT_MIN) {
1103 sway_log(L_DEBUG, "apply_input_config(%s) accel_set_speed(%f)", ic->identifier, ic->pointer_accel);
1104 libinput_device_config_accel_set_speed(dev, ic->pointer_accel);
1105 }
1106 if (ic->scroll_method != INT_MIN) {
1107 sway_log(L_DEBUG, "apply_input_config(%s) scroll_set_method(%d)", ic->identifier, ic->scroll_method);
1108 libinput_device_config_scroll_set_method(dev, ic->scroll_method);
1109 }
1110 if (ic->send_events != INT_MIN) {
1111 sway_log(L_DEBUG, "apply_input_config(%s) send_events_set_mode(%d)", ic->identifier, ic->send_events);
1112 libinput_device_config_send_events_set_mode(dev, ic->send_events);
1113 }
1114 if (ic->tap != INT_MIN) {
1115 sway_log(L_DEBUG, "apply_input_config(%s) tap_set_enabled(%d)", ic->identifier, ic->tap);
1116 libinput_device_config_tap_set_enabled(dev, ic->tap);
1117 }
1118}
1119
1120void apply_output_config(struct output_config *oc, swayc_t *output) {
1121 if (oc && oc->enabled == 0) {
1122 destroy_output(output);
1123 return;
1124 }
1125
1126 if (oc && oc->width > 0 && oc->height > 0) {
1127 output->width = oc->width;
1128 output->height = oc->height;
1129
1130 sway_log(L_DEBUG, "Set %s size to %ix%i (%d)", oc->name, oc->width, oc->height, oc->scale);
1131 struct wlc_size new_size = { .w = oc->width, .h = oc->height };
1132 wlc_output_set_resolution(output->handle, &new_size, (uint32_t)oc->scale);
1133 } else if (oc) {
1134 const struct wlc_size *new_size = wlc_output_get_resolution(output->handle);
1135 wlc_output_set_resolution(output->handle, new_size, (uint32_t)oc->scale);
1136 }
1137
1138 // Find position for it
1139 if (oc && oc->x != -1 && oc->y != -1) {
1140 sway_log(L_DEBUG, "Set %s position to %d, %d", oc->name, oc->x, oc->y);
1141 output->x = oc->x;
1142 output->y = oc->y;
1143 } else {
1144 int x = 0;
1145 for (int i = 0; i < root_container.children->length; ++i) {
1146 swayc_t *c = root_container.children->items[i];
1147 if (c->type == C_OUTPUT) {
1148 if (c->width + c->x > x) {
1149 x = c->width + c->x;
1150 }
1151 }
1152 }
1153 output->x = x;
1154 }
1155
1156 if (!oc || !oc->background) {
1157 // Look for a * config for background
1158 int i = list_seq_find(config->output_configs, output_name_cmp, "*");
1159 if (i >= 0) {
1160 oc = config->output_configs->items[i];
1161 } else {
1162 oc = NULL;
1163 }
1164 }
1165
1166 int output_i;
1167 for (output_i = 0; output_i < root_container.children->length; ++output_i) {
1168 if (root_container.children->items[output_i] == output) {
1169 break;
1170 }
1171 }
1172
1173 if (oc && oc->background) {
1174 if (output->bg_pid != 0) {
1175 terminate_swaybg(output->bg_pid);
1176 }
1177
1178 sway_log(L_DEBUG, "Setting background for output %d to %s", output_i, oc->background);
1179
1180 size_t bufsize = 12;
1181 char output_id[bufsize];
1182 snprintf(output_id, bufsize, "%d", output_i);
1183 output_id[bufsize-1] = 0;
1184
1185 char *const cmd[] = {
1186 "swaybg",
1187 output_id,
1188 oc->background,
1189 oc->background_option,
1190 NULL,
1191 };
1192
1193 output->bg_pid = fork();
1194 if (output->bg_pid == 0) {
1195 execvp(cmd[0], cmd);
1196 }
1197 }
1198}
1199
1200char *do_var_replacement(char *str) { 672char *do_var_replacement(char *str) {
1201 int i; 673 int i;
1202 char *find = str; 674 char *find = str;
@@ -1216,8 +688,9 @@ char *do_var_replacement(char *str) {
1216 int vvlen = strlen(var->value); 688 int vvlen = strlen(var->value);
1217 char *newstr = malloc(strlen(str) - vnlen + vvlen + 1); 689 char *newstr = malloc(strlen(str) - vnlen + vvlen + 1);
1218 if (!newstr) { 690 if (!newstr) {
1219 sway_log(L_ERROR, 691 wlr_log(L_ERROR,
1220 "Unable to allocate replacement during variable expansion"); 692 "Unable to allocate replacement "
693 "during variable expansion");
1221 break; 694 break;
1222 } 695 }
1223 char *newptr = newstr; 696 char *newptr = newstr;
@@ -1247,197 +720,3 @@ int workspace_output_cmp_workspace(const void *a, const void *b) {
1247 const struct workspace_output *wsa = a, *wsb = b; 720 const struct workspace_output *wsa = a, *wsb = b;
1248 return lenient_strcmp(wsa->workspace, wsb->workspace); 721 return lenient_strcmp(wsa->workspace, wsb->workspace);
1249} 722}
1250
1251int sway_binding_cmp_keys(const void *a, const void *b) {
1252 const struct sway_binding *binda = a, *bindb = b;
1253
1254 // Count keys pressed for this binding. important so we check long before
1255 // short ones. for example mod+a+b before mod+a
1256 unsigned int moda = 0, modb = 0, i;
1257
1258 // Count how any modifiers are pressed
1259 for (i = 0; i < 8 * sizeof(binda->modifiers); ++i) {
1260 moda += (binda->modifiers & 1 << i) != 0;
1261 modb += (bindb->modifiers & 1 << i) != 0;
1262 }
1263 if (bindb->keys->length + modb != binda->keys->length + moda) {
1264 return (bindb->keys->length + modb) - (binda->keys->length + moda);
1265 }
1266
1267 // Otherwise compare keys
1268 if (binda->modifiers > bindb->modifiers) {
1269 return 1;
1270 } else if (binda->modifiers < bindb->modifiers) {
1271 return -1;
1272 }
1273 struct wlc_modifiers no_mods = { 0, 0 };
1274 for (int i = 0; i < binda->keys->length; i++) {
1275 xkb_keysym_t ka = *(xkb_keysym_t *)binda->keys->items[i],
1276 kb = *(xkb_keysym_t *)bindb->keys->items[i];
1277 if (binda->bindcode) {
1278 uint32_t *keycode = binda->keys->items[i];
1279 ka = wlc_keyboard_get_keysym_for_key(*keycode, &no_mods);
1280 }
1281
1282 if (bindb->bindcode) {
1283 uint32_t *keycode = bindb->keys->items[i];
1284 kb = wlc_keyboard_get_keysym_for_key(*keycode, &no_mods);
1285 }
1286
1287 if (ka > kb) {
1288 return 1;
1289 } else if (ka < kb) {
1290 return -1;
1291 }
1292 }
1293
1294 return 0;
1295}
1296
1297int sway_binding_cmp(const void *a, const void *b) {
1298 int cmp = 0;
1299 if ((cmp = sway_binding_cmp_keys(a, b)) != 0) {
1300 return cmp;
1301 }
1302 const struct sway_binding *binda = a, *bindb = b;
1303 return lenient_strcmp(binda->command, bindb->command);
1304}
1305
1306int sway_binding_cmp_qsort(const void *a, const void *b) {
1307 return sway_binding_cmp(*(void **)a, *(void **)b);
1308}
1309
1310void free_sway_binding(struct sway_binding *binding) {
1311 if (binding->keys) {
1312 for (int i = 0; i < binding->keys->length; i++) {
1313 free(binding->keys->items[i]);
1314 }
1315 list_free(binding->keys);
1316 }
1317 if (binding->command) {
1318 free(binding->command);
1319 }
1320 free(binding);
1321}
1322
1323int sway_mouse_binding_cmp_buttons(const void *a, const void *b) {
1324 const struct sway_mouse_binding *binda = a, *bindb = b;
1325 if (binda->button > bindb->button) {
1326 return 1;
1327 }
1328 if (binda->button < bindb->button) {
1329 return -1;
1330 }
1331 return 0;
1332}
1333
1334int sway_mouse_binding_cmp(const void *a, const void *b) {
1335 int cmp = 0;
1336 if ((cmp = sway_binding_cmp_keys(a, b)) != 0) {
1337 return cmp;
1338 }
1339 const struct sway_mouse_binding *binda = a, *bindb = b;
1340 return lenient_strcmp(binda->command, bindb->command);
1341}
1342
1343int sway_mouse_binding_cmp_qsort(const void *a, const void *b) {
1344 return sway_mouse_binding_cmp(*(void **)a, *(void **)b);
1345}
1346
1347void free_sway_mouse_binding(struct sway_mouse_binding *binding) {
1348 if (binding->command) {
1349 free(binding->command);
1350 }
1351 free(binding);
1352}
1353
1354struct sway_binding *sway_binding_dup(struct sway_binding *sb) {
1355 struct sway_binding *new_sb = malloc(sizeof(struct sway_binding));
1356 if (!new_sb) {
1357 return NULL;
1358 }
1359
1360 new_sb->order = sb->order;
1361 new_sb->modifiers = sb->modifiers;
1362 new_sb->command = strdup(sb->command);
1363
1364 new_sb->keys = create_list();
1365 int i;
1366 for (i = 0; i < sb->keys->length; ++i) {
1367 xkb_keysym_t *key = malloc(sizeof(xkb_keysym_t));
1368 if (!key) {
1369 free_sway_binding(new_sb);
1370 return NULL;
1371 }
1372 *key = *(xkb_keysym_t *)sb->keys->items[i];
1373 list_add(new_sb->keys, key);
1374 }
1375
1376 return new_sb;
1377}
1378
1379struct bar_config *default_bar_config(void) {
1380 struct bar_config *bar = NULL;
1381 bar = malloc(sizeof(struct bar_config));
1382 if (!bar) {
1383 return NULL;
1384 }
1385 if (!(bar->mode = strdup("dock"))) goto cleanup;
1386 if (!(bar->hidden_state = strdup("hide"))) goto cleanup;
1387 bar->modifier = WLC_BIT_MOD_LOGO;
1388 bar->outputs = NULL;
1389 bar->position = DESKTOP_SHELL_PANEL_POSITION_BOTTOM;
1390 if (!(bar->bindings = create_list())) goto cleanup;
1391 if (!(bar->status_command = strdup("while :; do date +'%Y-%m-%d %l:%M:%S %p'; sleep 1; done"))) goto cleanup;
1392 bar->pango_markup = false;
1393 bar->swaybar_command = NULL;
1394 bar->font = NULL;
1395 bar->height = -1;
1396 bar->workspace_buttons = true;
1397 bar->wrap_scroll = false;
1398 bar->separator_symbol = NULL;
1399 bar->strip_workspace_numbers = false;
1400 bar->binding_mode_indicator = true;
1401#ifdef ENABLE_TRAY
1402 bar->tray_output = NULL;
1403 bar->icon_theme = NULL;
1404 bar->tray_padding = 2;
1405 bar->activate_button = 0x110; /* BTN_LEFT */
1406 bar->context_button = 0x111; /* BTN_RIGHT */
1407 bar->secondary_button = 0x112; /* BTN_MIDDLE */
1408#endif
1409 bar->verbose = false;
1410 bar->pid = 0;
1411 // set default colors
1412 if (!(bar->colors.background = strndup("#000000ff", 9))) goto cleanup;
1413 if (!(bar->colors.statusline = strndup("#ffffffff", 9))) goto cleanup;
1414 if (!(bar->colors.separator = strndup("#666666ff", 9))) goto cleanup;
1415 if (!(bar->colors.focused_workspace_border = strndup("#4c7899ff", 9))) goto cleanup;
1416 if (!(bar->colors.focused_workspace_bg = strndup("#285577ff", 9))) goto cleanup;
1417 if (!(bar->colors.focused_workspace_text = strndup("#ffffffff", 9))) goto cleanup;
1418 if (!(bar->colors.active_workspace_border = strndup("#333333ff", 9))) goto cleanup;
1419 if (!(bar->colors.active_workspace_bg = strndup("#5f676aff", 9))) goto cleanup;
1420 if (!(bar->colors.active_workspace_text = strndup("#ffffffff", 9))) goto cleanup;
1421 if (!(bar->colors.inactive_workspace_border = strndup("#333333ff", 9))) goto cleanup;
1422 if (!(bar->colors.inactive_workspace_bg = strndup("#222222ff", 9))) goto cleanup;
1423 if (!(bar->colors.inactive_workspace_text = strndup("#888888ff", 9))) goto cleanup;
1424 if (!(bar->colors.urgent_workspace_border = strndup("#2f343aff", 9))) goto cleanup;
1425 if (!(bar->colors.urgent_workspace_bg = strndup("#900000ff", 9))) goto cleanup;
1426 if (!(bar->colors.urgent_workspace_text = strndup("#ffffffff", 9))) goto cleanup;
1427 // if the following colors stay undefined, they fall back to background,
1428 // statusline, separator and urgent_workspace_*.
1429 bar->colors.focused_background = NULL;
1430 bar->colors.focused_statusline = NULL;
1431 bar->colors.focused_separator = NULL;
1432 bar->colors.binding_mode_border = NULL;
1433 bar->colors.binding_mode_bg = NULL;
1434 bar->colors.binding_mode_text = NULL;
1435
1436 list_add(config->bars, bar);
1437
1438 return bar;
1439
1440cleanup:
1441 free_bar(bar);
1442 return NULL;
1443}
diff --git a/sway/config/bar.c b/sway/config/bar.c
new file mode 100644
index 00000000..2913f059
--- /dev/null
+++ b/sway/config/bar.c
@@ -0,0 +1,240 @@
1#define _POSIX_C_SOURCE 200809L
2#define _XOPEN_SOURCE 700
3#include <stdio.h>
4#include <stdbool.h>
5#include <stdlib.h>
6#include <unistd.h>
7#include <wordexp.h>
8#include <sys/types.h>
9#include <sys/wait.h>
10#include <sys/stat.h>
11#include <signal.h>
12#include <strings.h>
13#include "sway/config.h"
14#include "stringop.h"
15#include "list.h"
16#include "log.h"
17
18static void terminate_swaybar(pid_t pid) {
19 wlr_log(L_DEBUG, "Terminating swaybar %d", pid);
20 int ret = kill(pid, SIGTERM);
21 if (ret != 0) {
22 wlr_log_errno(L_ERROR, "Unable to terminate swaybar %d", pid);
23 } else {
24 int status;
25 waitpid(pid, &status, 0);
26 }
27}
28
29void free_bar_config(struct bar_config *bar) {
30 if (!bar) {
31 return;
32 }
33 free(bar->mode);
34 free(bar->position);
35 free(bar->hidden_state);
36 free(bar->status_command);
37 free(bar->font);
38 free(bar->separator_symbol);
39 // TODO: Free mouse bindings
40 list_free(bar->bindings);
41 if (bar->outputs) {
42 free_flat_list(bar->outputs);
43 }
44 if (bar->pid != 0) {
45 terminate_swaybar(bar->pid);
46 }
47 free(bar->colors.background);
48 free(bar->colors.statusline);
49 free(bar->colors.separator);
50 free(bar->colors.focused_background);
51 free(bar->colors.focused_statusline);
52 free(bar->colors.focused_separator);
53 free(bar->colors.focused_workspace_border);
54 free(bar->colors.focused_workspace_bg);
55 free(bar->colors.focused_workspace_text);
56 free(bar->colors.active_workspace_border);
57 free(bar->colors.active_workspace_bg);
58 free(bar->colors.active_workspace_text);
59 free(bar->colors.inactive_workspace_border);
60 free(bar->colors.inactive_workspace_bg);
61 free(bar->colors.inactive_workspace_text);
62 free(bar->colors.urgent_workspace_border);
63 free(bar->colors.urgent_workspace_bg);
64 free(bar->colors.urgent_workspace_text);
65 free(bar->colors.binding_mode_border);
66 free(bar->colors.binding_mode_bg);
67 free(bar->colors.binding_mode_text);
68 free(bar);
69}
70
71struct bar_config *default_bar_config(void) {
72 struct bar_config *bar = NULL;
73 bar = malloc(sizeof(struct bar_config));
74 if (!bar) {
75 return NULL;
76 }
77 if (!(bar->mode = strdup("dock"))) goto cleanup;
78 if (!(bar->hidden_state = strdup("hide"))) goto cleanup;
79 bar->outputs = NULL;
80 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;
84 bar->swaybar_command = NULL;
85 bar->font = NULL;
86 bar->height = -1;
87 bar->workspace_buttons = true;
88 bar->wrap_scroll = false;
89 bar->separator_symbol = NULL;
90 bar->strip_workspace_numbers = false;
91 bar->binding_mode_indicator = true;
92 bar->verbose = false;
93 bar->pid = 0;
94 // set default colors
95 if (!(bar->colors.background = strndup("#000000ff", 9))) {
96 goto cleanup;
97 }
98 if (!(bar->colors.statusline = strndup("#ffffffff", 9))) {
99 goto cleanup;
100 }
101 if (!(bar->colors.separator = strndup("#666666ff", 9))) {
102 goto cleanup;
103 }
104 if (!(bar->colors.focused_workspace_border = strndup("#4c7899ff", 9))) {
105 goto cleanup;
106 }
107 if (!(bar->colors.focused_workspace_bg = strndup("#285577ff", 9))) {
108 goto cleanup;
109 }
110 if (!(bar->colors.focused_workspace_text = strndup("#ffffffff", 9))) {
111 goto cleanup;
112 }
113 if (!(bar->colors.active_workspace_border = strndup("#333333ff", 9))) {
114 goto cleanup;
115 }
116 if (!(bar->colors.active_workspace_bg = strndup("#5f676aff", 9))) {
117 goto cleanup;
118 }
119 if (!(bar->colors.active_workspace_text = strndup("#ffffffff", 9))) {
120 goto cleanup;
121 }
122 if (!(bar->colors.inactive_workspace_border = strndup("#333333ff", 9))) {
123 goto cleanup;
124 }
125 if (!(bar->colors.inactive_workspace_bg = strndup("#222222ff", 9))) {
126 goto cleanup;
127 }
128 if (!(bar->colors.inactive_workspace_text = strndup("#888888ff", 9))) {
129 goto cleanup;
130 }
131 if (!(bar->colors.urgent_workspace_border = strndup("#2f343aff", 9))) {
132 goto cleanup;
133 }
134 if (!(bar->colors.urgent_workspace_bg = strndup("#900000ff", 9))) {
135 goto cleanup;
136 }
137 if (!(bar->colors.urgent_workspace_text = strndup("#ffffffff", 9))) {
138 goto cleanup;
139 }
140 // if the following colors stay undefined, they fall back to background,
141 // statusline, separator and urgent_workspace_*.
142 bar->colors.focused_background = NULL;
143 bar->colors.focused_statusline = NULL;
144 bar->colors.focused_separator = NULL;
145 bar->colors.binding_mode_border = NULL;
146 bar->colors.binding_mode_bg = NULL;
147 bar->colors.binding_mode_text = NULL;
148
149 list_add(config->bars, bar);
150 return bar;
151cleanup:
152 free_bar_config(bar);
153 return NULL;
154}
155
156void invoke_swaybar(struct bar_config *bar) {
157 // Pipe to communicate errors
158 int filedes[2];
159 if (pipe(filedes) == -1) {
160 wlr_log(L_ERROR, "Pipe setup failed! Cannot fork into bar");
161 return;
162 }
163
164 bar->pid = fork();
165 if (bar->pid == 0) {
166 close(filedes[0]);
167
168 // run custom swaybar
169 size_t len = snprintf(NULL, 0, "%s -b %s",
170 bar->swaybar_command ? bar->swaybar_command : "swaybar",
171 bar->id);
172 char *command = malloc(len + 1);
173 if (!command) {
174 const char msg[] = "Unable to allocate swaybar command string";
175 size_t len = sizeof(msg);
176 if (write(filedes[1], &len, sizeof(int))) {};
177 if (write(filedes[1], msg, len)) {};
178 close(filedes[1]);
179 exit(1);
180 }
181 snprintf(command, len + 1, "%s -b %s",
182 bar->swaybar_command ? bar->swaybar_command : "swaybar",
183 bar->id);
184 char *const cmd[] = { "sh", "-c", command, NULL, };
185 close(filedes[1]);
186 execvp(cmd[0], cmd);
187 exit(1);
188 }
189 wlr_log(L_DEBUG, "Spawned swaybar %d", bar->pid);
190 close(filedes[0]);
191 ssize_t len;
192 if (read(filedes[1], &len, sizeof(int)) == sizeof(int)) {
193 char *buf = malloc(len);
194 if(!buf) {
195 wlr_log(L_ERROR, "Cannot allocate error string");
196 return;
197 }
198 if (read(filedes[1], buf, len)) {
199 wlr_log(L_ERROR, "%s", buf);
200 }
201 free(buf);
202 }
203 close(filedes[1]);
204}
205
206static bool active_output(const char *name) {
207 struct sway_container *cont = NULL;
208 for (int i = 0; i < root_container.children->length; ++i) {
209 cont = root_container.children->items[i];
210 if (cont->type == C_OUTPUT && strcasecmp(name, cont->name) == 0) {
211 return true;
212 }
213 }
214 return false;
215}
216
217void load_swaybars() {
218 for (int i = 0; i < config->bars->length; ++i) {
219 struct bar_config *bar = config->bars->items[i];
220 bool apply = false;
221 if (bar->outputs) {
222 for (int j = 0; j < bar->outputs->length; ++j) {
223 char *o = bar->outputs->items[j];
224 if (!strcmp(o, "*") || active_output(o)) {
225 apply = true;
226 break;
227 }
228 }
229 } else {
230 apply = true;
231 }
232 if (apply) {
233 if (bar->pid != 0) {
234 terminate_swaybar(bar->pid);
235 }
236 wlr_log(L_DEBUG, "Invoking swaybar for bar id '%s'", bar->id);
237 invoke_swaybar(bar);
238 }
239 }
240}
diff --git a/sway/config/input.c b/sway/config/input.c
new file mode 100644
index 00000000..5e657c43
--- /dev/null
+++ b/sway/config/input.c
@@ -0,0 +1,119 @@
1#define _XOPEN_SOURCE 700
2#include <stdlib.h>
3#include <limits.h>
4#include <float.h>
5#include "sway/config.h"
6#include "log.h"
7
8struct input_config *new_input_config(const char* identifier) {
9 struct input_config *input = calloc(1, sizeof(struct input_config));
10 if (!input) {
11 wlr_log(L_DEBUG, "Unable to allocate input config");
12 return NULL;
13 }
14 wlr_log(L_DEBUG, "new_input_config(%s)", identifier);
15 if (!(input->identifier = strdup(identifier))) {
16 free(input);
17 wlr_log(L_DEBUG, "Unable to allocate input config");
18 return NULL;
19 }
20
21 input->tap = INT_MIN;
22 input->drag_lock = INT_MIN;
23 input->dwt = INT_MIN;
24 input->send_events = INT_MIN;
25 input->click_method = INT_MIN;
26 input->middle_emulation = INT_MIN;
27 input->natural_scroll = INT_MIN;
28 input->accel_profile = INT_MIN;
29 input->pointer_accel = FLT_MIN;
30 input->scroll_method = INT_MIN;
31 input->left_handed = INT_MIN;
32
33 return input;
34}
35
36void merge_input_config(struct input_config *dst, struct input_config *src) {
37 if (src->identifier) {
38 free(dst->identifier);
39 dst->identifier = strdup(src->identifier);
40 }
41 if (src->accel_profile != INT_MIN) {
42 dst->accel_profile = src->accel_profile;
43 }
44 if (src->click_method != INT_MIN) {
45 dst->click_method = src->click_method;
46 }
47 if (src->drag_lock != INT_MIN) {
48 dst->drag_lock = src->drag_lock;
49 }
50 if (src->dwt != INT_MIN) {
51 dst->dwt = src->dwt;
52 }
53 if (src->middle_emulation != INT_MIN) {
54 dst->middle_emulation = src->middle_emulation;
55 }
56 if (src->natural_scroll != INT_MIN) {
57 dst->natural_scroll = src->natural_scroll;
58 }
59 if (src->pointer_accel != FLT_MIN) {
60 dst->pointer_accel = src->pointer_accel;
61 }
62 if (src->scroll_method != INT_MIN) {
63 dst->scroll_method = src->scroll_method;
64 }
65 if (src->send_events != INT_MIN) {
66 dst->send_events = src->send_events;
67 }
68 if (src->tap != INT_MIN) {
69 dst->tap = src->tap;
70 }
71 if (src->xkb_layout) {
72 free(dst->xkb_layout);
73 dst->xkb_layout = strdup(src->xkb_layout);
74 }
75 if (src->xkb_model) {
76 free(dst->xkb_model);
77 dst->xkb_model = strdup(src->xkb_model);
78 }
79 if (src->xkb_options) {
80 free(dst->xkb_options);
81 dst->xkb_options = strdup(src->xkb_options);
82 }
83 if (src->xkb_rules) {
84 free(dst->xkb_rules);
85 dst->xkb_rules = strdup(src->xkb_rules);
86 }
87 if (src->xkb_variant) {
88 free(dst->xkb_variant);
89 dst->xkb_variant = strdup(src->xkb_variant);
90 }
91 if (src->mapped_output) {
92 free(dst->mapped_output);
93 dst->mapped_output = strdup(src->mapped_output);
94 }
95}
96
97struct input_config *copy_input_config(struct input_config *ic) {
98 struct input_config *copy = calloc(1, sizeof(struct input_config));
99 if (copy == NULL) {
100 wlr_log(L_ERROR, "could not allocate input config");
101 return NULL;
102 }
103 merge_input_config(copy, ic);
104 return copy;
105}
106
107void free_input_config(struct input_config *ic) {
108 if (!ic) {
109 return;
110 }
111 free(ic->identifier);
112 free(ic);
113}
114
115int input_identifier_cmp(const void *item, const void *data) {
116 const struct input_config *ic = item;
117 const char *identifier = data;
118 return strcmp(ic->identifier, identifier);
119}
diff --git a/sway/config/output.c b/sway/config/output.c
new file mode 100644
index 00000000..1c298d37
--- /dev/null
+++ b/sway/config/output.c
@@ -0,0 +1,213 @@
1#define _XOPEN_SOURCE 700
2#include <assert.h>
3#include <stdbool.h>
4#include <string.h>
5#include <signal.h>
6#include <sys/wait.h>
7#include <unistd.h>
8#include <wlr/types/wlr_output.h>
9#include <wlr/types/wlr_output_layout.h>
10#include "sway/config.h"
11#include "sway/output.h"
12#include "log.h"
13
14int output_name_cmp(const void *item, const void *data) {
15 const struct output_config *output = item;
16 const char *name = data;
17
18 return strcmp(output->name, name);
19}
20
21void output_get_identifier(char *identifier, size_t len,
22 struct sway_output *output) {
23 struct wlr_output *wlr_output = output->wlr_output;
24 snprintf(identifier, len, "%s %s %s", wlr_output->make, wlr_output->model,
25 wlr_output->serial);
26}
27
28struct output_config *new_output_config(const char *name) {
29 struct output_config *oc = calloc(1, sizeof(struct output_config));
30 if (oc == NULL) {
31 return NULL;
32 }
33 oc->name = strdup(name);
34 if (oc->name == NULL) {
35 free(oc);
36 return NULL;
37 }
38 oc->enabled = -1;
39 oc->width = oc->height = -1;
40 oc->refresh_rate = -1;
41 oc->x = oc->y = -1;
42 oc->scale = -1;
43 oc->transform = -1;
44 return oc;
45}
46
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) {
53 dst->enabled = src->enabled;
54 }
55 if (src->width != -1) {
56 dst->width = src->width;
57 }
58 if (src->height != -1) {
59 dst->height = src->height;
60 }
61 if (src->x != -1) {
62 dst->x = src->x;
63 }
64 if (src->y != -1) {
65 dst->y = src->y;
66 }
67 if (src->scale != -1) {
68 dst->scale = src->scale;
69 }
70 if (src->refresh_rate != -1) {
71 dst->refresh_rate = src->refresh_rate;
72 }
73 if (src->transform != -1) {
74 dst->transform = src->transform;
75 }
76 if (src->background) {
77 free(dst->background);
78 dst->background = strdup(src->background);
79 }
80 if (src->background_option) {
81 free(dst->background_option);
82 dst->background_option = strdup(src->background_option);
83 }
84}
85
86static void set_mode(struct wlr_output *output, int width, int height,
87 float refresh_rate) {
88 int mhz = (int)(refresh_rate * 1000);
89 if (wl_list_empty(&output->modes)) {
90 wlr_log(L_DEBUG, "Assigning custom mode to %s", output->name);
91 wlr_output_set_custom_mode(output, width, height, mhz);
92 return;
93 }
94
95 struct wlr_output_mode *mode, *best = NULL;
96 wl_list_for_each(mode, &output->modes, link) {
97 if (mode->width == width && mode->height == height) {
98 if (mode->refresh == mhz) {
99 best = mode;
100 break;
101 }
102 best = mode;
103 }
104 }
105 if (!best) {
106 wlr_log(L_ERROR, "Configured mode for %s not available", output->name);
107 } else {
108 wlr_log(L_DEBUG, "Assigning configured mode to %s", output->name);
109 wlr_output_set_mode(output, best);
110 }
111}
112
113void terminate_swaybg(pid_t pid) {
114 int ret = kill(pid, SIGTERM);
115 if (ret != 0) {
116 wlr_log(L_ERROR, "Unable to terminate swaybg [pid: %d]", pid);
117 } else {
118 int status;
119 waitpid(pid, &status, 0);
120 }
121}
122
123void apply_output_config(struct output_config *oc, struct sway_container *output) {
124 assert(output->type == C_OUTPUT);
125
126 struct wlr_output_layout *output_layout =
127 root_container.sway_root->output_layout;
128 struct wlr_output *wlr_output = output->sway_output->wlr_output;
129
130 if (oc && oc->enabled == 0) {
131 container_destroy(output);
132 wlr_output_layout_remove(root_container.sway_root->output_layout,
133 wlr_output);
134 return;
135 }
136
137 if (oc && oc->width > 0 && oc->height > 0) {
138 wlr_log(L_DEBUG, "Set %s mode to %dx%d (%f GHz)", oc->name, oc->width,
139 oc->height, oc->refresh_rate);
140 set_mode(wlr_output, oc->width, oc->height, oc->refresh_rate);
141 }
142 if (oc && oc->scale > 0) {
143 wlr_log(L_DEBUG, "Set %s scale to %f", oc->name, oc->scale);
144 wlr_output_set_scale(wlr_output, oc->scale);
145 }
146 if (oc && oc->transform >= 0) {
147 wlr_log(L_DEBUG, "Set %s transform to %d", oc->name, oc->transform);
148 wlr_output_set_transform(wlr_output, oc->transform);
149 }
150
151 // Find position for it
152 if (oc && (oc->x != -1 || oc->y != -1)) {
153 wlr_log(L_DEBUG, "Set %s position to %d, %d", oc->name, oc->x, oc->y);
154 wlr_output_layout_add(output_layout, wlr_output, oc->x, oc->y);
155 } else {
156 wlr_output_layout_add_auto(output_layout, wlr_output);
157 }
158
159 if (!oc || !oc->background) {
160 // Look for a * config for background
161 int i = list_seq_find(config->output_configs, output_name_cmp, "*");
162 if (i >= 0) {
163 oc = config->output_configs->items[i];
164 } else {
165 oc = NULL;
166 }
167 }
168
169 int output_i;
170 for (output_i = 0; output_i < root_container.children->length; ++output_i) {
171 if (root_container.children->items[output_i] == output) {
172 break;
173 }
174 }
175
176 if (oc && oc->background) {
177 if (output->sway_output->bg_pid != 0) {
178 terminate_swaybg(output->sway_output->bg_pid);
179 }
180
181 wlr_log(L_DEBUG, "Setting background for output %d to %s",
182 output_i, oc->background);
183
184 size_t len = snprintf(NULL, 0, "%s %d %s %s",
185 config->swaybg_command ? config->swaybg_command : "swaybg",
186 output_i, oc->background, oc->background_option);
187 char *command = malloc(len + 1);
188 if (!command) {
189 wlr_log(L_DEBUG, "Unable to allocate swaybg command");
190 return;
191 }
192 snprintf(command, len + 1, "%s %d %s %s",
193 config->swaybg_command ? config->swaybg_command : "swaybg",
194 output_i, oc->background, oc->background_option);
195 wlr_log(L_DEBUG, "-> %s", command);
196
197 char *const cmd[] = { "sh", "-c", command, NULL };
198 output->sway_output->bg_pid = fork();
199 if (output->sway_output->bg_pid == 0) {
200 execvp(cmd[0], cmd);
201 }
202 }
203}
204
205void free_output_config(struct output_config *oc) {
206 if (!oc) {
207 return;
208 }
209 free(oc->name);
210 free(oc->background);
211 free(oc->background_option);
212 free(oc);
213}
diff --git a/sway/config/seat.c b/sway/config/seat.c
new file mode 100644
index 00000000..bd8b45c8
--- /dev/null
+++ b/sway/config/seat.c
@@ -0,0 +1,146 @@
1#define _XOPEN_SOURCE 700
2#include <stdlib.h>
3#include <string.h>
4#include "sway/config.h"
5#include "log.h"
6
7struct seat_config *new_seat_config(const char* name) {
8 struct seat_config *seat = calloc(1, sizeof(struct seat_config));
9 if (!seat) {
10 wlr_log(L_DEBUG, "Unable to allocate seat config");
11 return NULL;
12 }
13
14 wlr_log(L_DEBUG, "new_seat_config(%s)", name);
15 seat->name = strdup(name);
16 if (!sway_assert(seat->name, "could not allocate name for seat")) {
17 free(seat);
18 return NULL;
19 }
20
21 seat->fallback = -1;
22 seat->attachments = create_list();
23 if (!sway_assert(seat->attachments,
24 "could not allocate seat attachments list")) {
25 free(seat->name);
26 free(seat);
27 return NULL;
28 }
29
30 return seat;
31}
32
33struct seat_attachment_config *seat_attachment_config_new() {
34 struct seat_attachment_config *attachment =
35 calloc(1, sizeof(struct seat_attachment_config));
36 if (!attachment) {
37 wlr_log(L_DEBUG, "cannot allocate attachment config");
38 return NULL;
39 }
40 return attachment;
41}
42
43static void seat_attachment_config_free(
44 struct seat_attachment_config *attachment) {
45 free(attachment->identifier);
46 free(attachment);
47 return;
48}
49
50static struct seat_attachment_config *seat_attachment_config_copy(
51 struct seat_attachment_config *attachment) {
52 struct seat_attachment_config *copy = seat_attachment_config_new();
53 if (!copy) {
54 return NULL;
55 }
56
57 copy->identifier = strdup(attachment->identifier);
58
59 return copy;
60}
61
62static void merge_seat_attachment_config(struct seat_attachment_config *dest,
63 struct seat_attachment_config *source) {
64 // nothing to merge yet, but there will be some day
65}
66
67void merge_seat_config(struct seat_config *dest, struct seat_config *source) {
68 if (source->name) {
69 free(dest->name);
70 dest->name = strdup(source->name);
71 }
72
73 if (source->fallback != -1) {
74 dest->fallback = source->fallback;
75 }
76
77 for (int i = 0; i < source->attachments->length; ++i) {
78 struct seat_attachment_config *source_attachment =
79 source->attachments->items[i];
80 bool found = false;
81 for (int j = 0; j < dest->attachments->length; ++j) {
82 struct seat_attachment_config *dest_attachment =
83 dest->attachments->items[j];
84 if (strcmp(source_attachment->identifier,
85 dest_attachment->identifier) == 0) {
86 merge_seat_attachment_config(dest_attachment,
87 source_attachment);
88 found = true;
89 }
90 }
91
92 if (!found) {
93 struct seat_attachment_config *copy =
94 seat_attachment_config_copy(source_attachment);
95 if (copy) {
96 list_add(dest->attachments, copy);
97 }
98 }
99 }
100}
101
102struct seat_config *copy_seat_config(struct seat_config *seat) {
103 struct seat_config *copy = new_seat_config(seat->name);
104 if (copy == NULL) {
105 return NULL;
106 }
107
108 merge_seat_config(copy, seat);
109
110 return copy;
111}
112
113void free_seat_config(struct seat_config *seat) {
114 if (!seat) {
115 return;
116 }
117
118 free(seat->name);
119 for (int i = 0; i < seat->attachments->length; ++i) {
120 struct seat_attachment_config *attachment =
121 seat->attachments->items[i];
122 seat_attachment_config_free(attachment);
123 }
124
125 list_free(seat->attachments);
126 free(seat);
127}
128
129int seat_name_cmp(const void *item, const void *data) {
130 const struct seat_config *sc = item;
131 const char *name = data;
132 return strcmp(sc->name, name);
133}
134
135struct seat_attachment_config *seat_config_get_attachment(
136 struct seat_config *seat_config, char *identifier) {
137 for (int i = 0; i < seat_config->attachments->length; ++i) {
138 struct seat_attachment_config *attachment =
139 seat_config->attachments->items[i];
140 if (strcmp(attachment->identifier, identifier) == 0) {
141 return attachment;
142 }
143 }
144
145 return NULL;
146}
diff --git a/sway/container.c b/sway/container.c
deleted file mode 100644
index 829fde69..00000000
--- a/sway/container.c
+++ /dev/null
@@ -1,1016 +0,0 @@
1#define _XOPEN_SOURCE 500
2#include <ctype.h>
3#include <stdlib.h>
4#include <stdbool.h>
5#include <strings.h>
6#include <string.h>
7#include "sway/config.h"
8#include "sway/container.h"
9#include "sway/workspace.h"
10#include "sway/focus.h"
11#include "sway/border.h"
12#include "sway/layout.h"
13#include "sway/input_state.h"
14#include "sway/ipc-server.h"
15#include "sway/output.h"
16#include "log.h"
17#include "stringop.h"
18
19#define ASSERT_NONNULL(PTR) \
20 sway_assert (PTR, #PTR "must be non-null")
21
22static swayc_t *new_swayc(enum swayc_types type) {
23 // next id starts at 1 because 0 is assigned to root_container in layout.c
24 static size_t next_id = 1;
25 swayc_t *c = calloc(1, sizeof(swayc_t));
26 if (!c) {
27 return NULL;
28 }
29 c->id = next_id++;
30 c->handle = -1;
31 c->gaps = -1;
32 c->layout = L_NONE;
33 c->workspace_layout = L_NONE;
34 c->type = type;
35 c->nb_master = 1;
36 c->nb_slave_groups = 1;
37 if (type != C_VIEW) {
38 c->children = create_list();
39 }
40 return c;
41}
42
43static void free_swayc(swayc_t *cont) {
44 if (!ASSERT_NONNULL(cont)) {
45 return;
46 }
47 if (cont->children) {
48 // remove children until there are no more, free_swayc calls
49 // remove_child, which removes child from this container
50 while (cont->children->length) {
51 free_swayc(cont->children->items[0]);
52 }
53 list_free(cont->children);
54 }
55 if (cont->unmanaged) {
56 list_free(cont->unmanaged);
57 }
58 if (cont->floating) {
59 while (cont->floating->length) {
60 free_swayc(cont->floating->items[0]);
61 }
62 list_free(cont->floating);
63 }
64 if (cont->marks) {
65 list_foreach(cont->marks, free);
66 list_free(cont->marks);
67 }
68 if (cont->parent) {
69 remove_child(cont);
70 }
71 if (cont->name) {
72 free(cont->name);
73 }
74 if (cont->class) {
75 free(cont->class);
76 }
77 if (cont->instance) {
78 free(cont->instance);
79 }
80 if (cont->app_id) {
81 free(cont->app_id);
82 }
83 if (cont->bg_pid != 0) {
84 terminate_swaybg(cont->bg_pid);
85 }
86 if (cont->border) {
87 if (cont->border->buffer) {
88 free(cont->border->buffer);
89 }
90 free(cont->border);
91 }
92 free(cont);
93}
94
95static void update_root_geometry() {
96 int width = 0;
97 int height = 0;
98 swayc_t *child;
99 int child_width;
100 int child_height;
101
102 for (int i = 0; i < root_container.children->length; ++i) {
103 child = root_container.children->items[i];
104 child_width = child->width + child->x;
105 child_height = child->height + child->y;
106 if (child_width > width) {
107 width = child_width;
108 }
109
110 if (child_height > height) {
111 height = child_height;
112 }
113 }
114
115 root_container.width = width;
116 root_container.height = height;
117}
118
119// New containers
120
121swayc_t *new_output(wlc_handle handle) {
122 struct wlc_size size;
123 output_get_scaled_size(handle, &size);
124 const char *name = wlc_output_get_name(handle);
125 // Find current outputs to see if this already exists
126 {
127 int i, len = root_container.children->length;
128 for (i = 0; i < len; ++i) {
129 swayc_t *op = root_container.children->items[i];
130 const char *op_name = op->name;
131 if (op_name && name && strcmp(op_name, name) == 0) {
132 sway_log(L_DEBUG, "restoring output %" PRIuPTR ":%s", handle, op_name);
133 return op;
134 }
135 }
136 }
137
138 sway_log(L_DEBUG, "New output %" PRIuPTR ":%s", handle, name);
139
140 struct output_config *oc = NULL, *all = NULL;
141 int i;
142 for (i = 0; i < config->output_configs->length; ++i) {
143 struct output_config *cur = config->output_configs->items[i];
144 if (strcasecmp(name, cur->name) == 0) {
145 sway_log(L_DEBUG, "Matched output config for %s", name);
146 oc = cur;
147 }
148 if (strcasecmp("*", cur->name) == 0) {
149 sway_log(L_DEBUG, "Matched wildcard output config for %s", name);
150 all = cur;
151 }
152
153 if (oc && all) {
154 break;
155 }
156 }
157
158 if (!oc) {
159 oc = all;
160 }
161
162 if (oc && !oc->enabled) {
163 return NULL;
164 }
165
166 swayc_t *output = new_swayc(C_OUTPUT);
167 output->handle = handle;
168 output->name = name ? strdup(name) : NULL;
169 output->width = size.w;
170 output->height = size.h;
171 output->unmanaged = create_list();
172 output->bg_pid = 0;
173
174 apply_output_config(oc, output);
175 add_child(&root_container, output);
176 load_swaybars();
177
178 // Create workspace
179 char *ws_name = NULL;
180 swayc_t *ws = NULL;
181
182 if (name) {
183 for (i = 0; i < config->workspace_outputs->length; ++i) {
184 struct workspace_output *wso = config->workspace_outputs->items[i];
185 if (strcasecmp(wso->output, name) == 0) {
186 sway_log(L_DEBUG, "Matched workspace to output: %s for %s", wso->workspace, wso->output);
187 // Check if any other workspaces are using this name
188 if ((ws = workspace_by_name(wso->workspace))) {
189 // if yes, move those to this output, because they should be here
190 move_workspace_to(ws, output);
191 } else if (!ws_name) {
192 // set a workspace name in case we need to create a default one
193 ws_name = strdup(wso->workspace);
194 }
195 }
196 }
197 }
198
199 if (output->children->length == 0) {
200 if (!ws_name) {
201 ws_name = workspace_next_name(output->name);
202 }
203 // create and initialize default workspace
204 sway_log(L_DEBUG, "Creating default workspace %s", ws_name);
205 ws = new_workspace(output, ws_name);
206 ws->is_focused = true;
207 } else {
208 sort_workspaces(output);
209 set_focused_container(output->children->items[0]);
210 }
211
212 free(ws_name);
213 update_root_geometry();
214 return output;
215}
216
217swayc_t *new_workspace(swayc_t *output, const char *name) {
218 if (!ASSERT_NONNULL(output)) {
219 return NULL;
220 }
221 sway_log(L_DEBUG, "Added workspace %s for output %u", name, (unsigned int)output->handle);
222 swayc_t *workspace = new_swayc(C_WORKSPACE);
223
224 workspace->prev_layout = L_NONE;
225 workspace->layout = default_layout(output);
226 workspace->workspace_layout = default_layout(output);
227
228 workspace->x = output->x;
229 workspace->y = output->y;
230 workspace->width = output->width;
231 workspace->height = output->height;
232 workspace->name = !name ? NULL : strdup(name);
233 workspace->visible = false;
234 workspace->floating = create_list();
235
236 add_child(output, workspace);
237 sort_workspaces(output);
238
239 return workspace;
240}
241
242swayc_t *new_container(swayc_t *child, enum swayc_layouts layout) {
243 if (!ASSERT_NONNULL(child)
244 && !sway_assert(!child->is_floating, "cannot create container around floating window")) {
245 return NULL;
246 }
247 swayc_t *cont = new_swayc(C_CONTAINER);
248
249 sway_log(L_DEBUG, "creating container %p around %p", cont, child);
250
251 cont->prev_layout = L_NONE;
252 cont->layout = layout;
253 cont->width = child->width;
254 cont->height = child->height;
255 cont->x = child->x;
256 cont->y = child->y;
257 cont->visible = child->visible;
258 cont->cached_geometry = child->cached_geometry;
259 cont->gaps = child->gaps;
260
261 /* Container inherits all of workspaces children, layout and whatnot */
262 if (child->type == C_WORKSPACE) {
263 swayc_t *workspace = child;
264 // reorder focus
265 cont->focused = workspace->focused;
266 workspace->focused = cont;
267 // set all children focu to container
268 int i;
269 for (i = 0; i < workspace->children->length; ++i) {
270 ((swayc_t *)workspace->children->items[i])->parent = cont;
271 }
272 // Swap children
273 list_t *tmp_list = workspace->children;
274 workspace->children = cont->children;
275 cont->children = tmp_list;
276 // add container to workspace chidren
277 add_child(workspace, cont);
278 // give them proper layouts
279 cont->layout = workspace->workspace_layout;
280 cont->prev_layout = workspace->prev_layout;
281 /* TODO: might break shit in move_container!!! workspace->layout = layout; */
282 set_focused_container_for(workspace, get_focused_view(workspace));
283 } else { // Or is built around container
284 swayc_t *parent = replace_child(child, cont);
285 if (parent) {
286 add_child(cont, child);
287 }
288 }
289 return cont;
290}
291
292swayc_t *new_view(swayc_t *sibling, wlc_handle handle) {
293 if (!ASSERT_NONNULL(sibling)) {
294 return NULL;
295 }
296 const char *title = wlc_view_get_title(handle);
297 swayc_t *view = new_swayc(C_VIEW);
298 sway_log(L_DEBUG, "Adding new view %" PRIuPTR ":%s to container %p %d",
299 handle, title, sibling, sibling ? sibling->type : 0);
300 // Setup values
301 view->handle = handle;
302 view->name = title ? strdup(title) : NULL;
303 const char *class = wlc_view_get_class(handle);
304 view->class = class ? strdup(class) : NULL;
305 const char *instance = wlc_view_get_instance(handle);
306 view->instance = instance ? strdup(instance) : NULL;
307 const char *app_id = wlc_view_get_app_id(handle);
308 view->app_id = app_id ? strdup(app_id) : NULL;
309 view->visible = true;
310 view->is_focused = true;
311 view->sticky = false;
312 view->width = 0;
313 view->height = 0;
314 view->desired_width = -1;
315 view->desired_height = -1;
316 // setup border
317 view->border_type = config->border;
318 view->border_thickness = config->border_thickness;
319
320 view->is_floating = false;
321
322 if (sibling->type == C_WORKSPACE) {
323 // Case of focused workspace, just create as child of it
324 add_child(sibling, view);
325 } else {
326 // Regular case, create as sibling of current container
327 add_sibling(sibling, view);
328 }
329 return view;
330}
331
332swayc_t *new_floating_view(wlc_handle handle) {
333 if (swayc_active_workspace() == NULL) {
334 return NULL;
335 }
336 const char *title = wlc_view_get_title(handle);
337 swayc_t *view = new_swayc(C_VIEW);
338 sway_log(L_DEBUG, "Adding new view %" PRIuPTR ":%x:%s as a floating view",
339 handle, wlc_view_get_type(handle), title);
340 // Setup values
341 view->handle = handle;
342 view->name = title ? strdup(title) : NULL;
343 const char *class = wlc_view_get_class(handle);
344 view->class = class ? strdup(class) : NULL;
345 const char *instance = wlc_view_get_instance(handle);
346 view->instance = instance ? strdup(instance) : NULL;
347 const char *app_id = wlc_view_get_app_id(handle);
348 view->app_id = app_id ? strdup(app_id) : NULL;
349 view->visible = true;
350 view->sticky = false;
351
352 // Set the geometry of the floating view
353 const struct wlc_geometry *geometry = wlc_view_get_geometry(handle);
354
355 // give it requested geometry, but place in center if possible
356 // in top left otherwise
357 if (geometry->size.w != 0) {
358 view->x = (swayc_active_workspace()->width - geometry->size.w) / 2;
359 } else {
360 view->x = 0;
361 }
362 if (geometry->size.h != 0) {
363 view->y = (swayc_active_workspace()->height - geometry->size.h) / 2;
364 } else {
365 view->y = 0;
366 }
367
368 view->width = geometry->size.w;
369 view->height = geometry->size.h;
370
371 view->desired_width = view->width;
372 view->desired_height = view->height;
373
374 // setup border
375 view->border_type = config->floating_border;
376 view->border_thickness = config->floating_border_thickness;
377
378 view->is_floating = true;
379
380 // Case of focused workspace, just create as child of it
381 list_add(swayc_active_workspace()->floating, view);
382 view->parent = swayc_active_workspace();
383 if (swayc_active_workspace()->focused == NULL) {
384 set_focused_container_for(swayc_active_workspace(), view);
385 }
386 return view;
387}
388
389void floating_view_sane_size(swayc_t *view) {
390 // floating_minimum is used as sane value.
391 // floating_maximum has priority in case of conflict
392 // TODO: implement total_outputs_dimensions()
393 if (config->floating_minimum_height != -1 &&
394 view->desired_height < config->floating_minimum_height) {
395 view->desired_height = config->floating_minimum_height;
396 }
397 if (config->floating_minimum_width != -1 &&
398 view->desired_width < config->floating_minimum_width) {
399 view->desired_width = config->floating_minimum_width;
400 }
401
402 // if 0 do not resize, only enforce max value
403 if (config->floating_maximum_height == 0) {
404 // Missing total_outputs_dimensions() using swayc_active_workspace()
405 config->floating_maximum_height = swayc_active_workspace()->height;
406
407 } else if (config->floating_maximum_height != -1 &&
408 view->desired_height > config->floating_maximum_height) {
409 view->desired_height = config->floating_maximum_height;
410 }
411
412 // if 0 do not resize, only enforce max value
413 if (config->floating_maximum_width == 0) {
414 // Missing total_outputs_dimensions() using swayc_active_workspace()
415 config->floating_maximum_width = swayc_active_workspace()->width;
416
417 } else if (config->floating_maximum_width != -1 &&
418 view->desired_width > config->floating_maximum_width) {
419 view->desired_width = config->floating_maximum_width;
420 }
421
422 sway_log(L_DEBUG, "Sane values for view to %d x %d @ %.f, %.f",
423 view->desired_width, view->desired_height, view->x, view->y);
424
425 return;
426}
427
428
429// Destroy container
430
431swayc_t *destroy_output(swayc_t *output) {
432 if (!ASSERT_NONNULL(output)) {
433 return NULL;
434 }
435 if (output->children->length > 0) {
436 // TODO save workspaces when there are no outputs.
437 // TODO also check if there will ever be no outputs except for exiting
438 // program
439 if (root_container.children->length > 1) {
440 int p = root_container.children->items[0] == output;
441 // Move workspace from this output to another output
442 while (output->children->length) {
443 swayc_t *child = output->children->items[0];
444 remove_child(child);
445 add_child(root_container.children->items[p], child);
446 }
447 sort_workspaces(root_container.children->items[p]);
448 update_visibility(root_container.children->items[p]);
449 arrange_windows(root_container.children->items[p], -1, -1);
450 }
451 }
452 sway_log(L_DEBUG, "OUTPUT: Destroying output '%" PRIuPTR "'", output->handle);
453 free_swayc(output);
454 update_root_geometry();
455 return &root_container;
456}
457
458swayc_t *destroy_workspace(swayc_t *workspace) {
459 if (!ASSERT_NONNULL(workspace)) {
460 return NULL;
461 }
462
463 // Do not destroy this if it's the last workspace on this output
464 swayc_t *output = swayc_parent_by_type(workspace, C_OUTPUT);
465 if (output && output->children->length == 1) {
466 return NULL;
467 }
468
469 swayc_t *parent = workspace->parent;
470 // destroy the WS if there are no children
471 if (workspace->children->length == 0 && workspace->floating->length == 0) {
472 sway_log(L_DEBUG, "destroying workspace '%s'", workspace->name);
473 ipc_event_workspace(workspace, NULL, "empty");
474 } else {
475 // Move children to a different workspace on this output
476 swayc_t *new_workspace = NULL;
477 int i;
478 for(i = 0; i < output->children->length; i++) {
479 if(output->children->items[i] != workspace) {
480 break;
481 }
482 }
483 new_workspace = output->children->items[i];
484
485 sway_log(L_DEBUG, "moving children to different workspace '%s' -> '%s'",
486 workspace->name, new_workspace->name);
487
488 for(i = 0; i < workspace->children->length; i++) {
489 move_container_to(workspace->children->items[i], new_workspace);
490 }
491
492 for(i = 0; i < workspace->floating->length; i++) {
493 move_container_to(workspace->floating->items[i], new_workspace);
494 }
495 }
496
497 free_swayc(workspace);
498 return parent;
499}
500
501swayc_t *destroy_container(swayc_t *container) {
502 if (!ASSERT_NONNULL(container)) {
503 return NULL;
504 }
505 while (container->children->length == 0 && container->type == C_CONTAINER) {
506 sway_log(L_DEBUG, "Container: Destroying container '%p'", container);
507 swayc_t *parent = container->parent;
508 free_swayc(container);
509 container = parent;
510 }
511 return container;
512}
513
514swayc_t *destroy_view(swayc_t *view) {
515 if (!ASSERT_NONNULL(view)) {
516 return NULL;
517 }
518 sway_log(L_DEBUG, "Destroying view '%p'", view);
519 swayc_t *parent = view->parent;
520 free_swayc(view);
521
522 // Destroy empty containers
523 if (parent && parent->type == C_CONTAINER) {
524 return destroy_container(parent);
525 }
526 return parent;
527}
528
529// Container lookup
530
531
532swayc_t *swayc_by_test(swayc_t *container, bool (*test)(swayc_t *view, void *data), void *data) {
533 if (!container->children) {
534 return NULL;
535 }
536 // Special case for checking floating stuff
537 int i;
538 if (container->type == C_WORKSPACE) {
539 for (i = 0; i < container->floating->length; ++i) {
540 swayc_t *child = container->floating->items[i];
541 if (test(child, data)) {
542 return child;
543 }
544 }
545 }
546 for (i = 0; i < container->children->length; ++i) {
547 swayc_t *child = container->children->items[i];
548 if (test(child, data)) {
549 return child;
550 } else {
551 swayc_t *res = swayc_by_test(child, test, data);
552 if (res) {
553 return res;
554 }
555 }
556 }
557 return NULL;
558}
559
560static bool test_name(swayc_t *view, void *data) {
561 if (!view || !view->name) {
562 return false;
563 }
564 return strcmp(view->name, data) == 0;
565}
566
567swayc_t *swayc_by_name(const char *name) {
568 return swayc_by_test(&root_container, test_name, (void *)name);
569}
570
571swayc_t *swayc_parent_by_type(swayc_t *container, enum swayc_types type) {
572 if (!ASSERT_NONNULL(container)) {
573 return NULL;
574 }
575 if (!sway_assert(type < C_TYPES && type >= C_ROOT, "invalid type")) {
576 return NULL;
577 }
578 do {
579 container = container->parent;
580 } while (container && container->type != type);
581 return container;
582}
583
584swayc_t *swayc_parent_by_layout(swayc_t *container, enum swayc_layouts layout) {
585 if (!ASSERT_NONNULL(container)) {
586 return NULL;
587 }
588 if (!sway_assert(layout < L_LAYOUTS && layout >= L_NONE, "invalid layout")) {
589 return NULL;
590 }
591 do {
592 container = container->parent;
593 } while (container && container->layout != layout);
594 return container;
595}
596
597swayc_t *swayc_focus_by_type(swayc_t *container, enum swayc_types type) {
598 if (!ASSERT_NONNULL(container)) {
599 return NULL;
600 }
601 if (!sway_assert(type < C_TYPES && type >= C_ROOT, "invalid type")) {
602 return NULL;
603 }
604 do {
605 container = container->focused;
606 } while (container && container->type != type);
607 return container;
608}
609
610swayc_t *swayc_focus_by_layout(swayc_t *container, enum swayc_layouts layout) {
611 if (!ASSERT_NONNULL(container)) {
612 return NULL;
613 }
614 if (!sway_assert(layout < L_LAYOUTS && layout >= L_NONE, "invalid layout")) {
615 return NULL;
616 }
617 do {
618 container = container->focused;
619 } while (container && container->layout != layout);
620 return container;
621}
622
623
624static swayc_t *_swayc_by_handle_helper(wlc_handle handle, swayc_t *parent) {
625 if (!parent || !parent->children) {
626 return NULL;
627 }
628 int i, len;
629 swayc_t **child;
630 if (parent->type == C_WORKSPACE) {
631 len = parent->floating->length;
632 child = (swayc_t **)parent->floating->items;
633 for (i = 0; i < len; ++i, ++child) {
634 if ((*child)->handle == handle) {
635 return *child;
636 }
637 }
638 }
639
640 len = parent->children->length;
641 child = (swayc_t**)parent->children->items;
642 for (i = 0; i < len; ++i, ++child) {
643 if ((*child)->handle == handle) {
644 return *child;
645 } else {
646 swayc_t *res;
647 if ((res = _swayc_by_handle_helper(handle, *child))) {
648 return res;
649 }
650 }
651 }
652 return NULL;
653}
654
655swayc_t *swayc_by_handle(wlc_handle handle) {
656 return _swayc_by_handle_helper(handle, &root_container);
657}
658
659swayc_t *swayc_active_output(void) {
660 return root_container.focused;
661}
662
663swayc_t *swayc_active_workspace(void) {
664 return root_container.focused ? root_container.focused->focused : NULL;
665}
666
667swayc_t *swayc_active_workspace_for(swayc_t *cont) {
668 if (!cont) {
669 return NULL;
670 }
671 switch (cont->type) {
672 case C_ROOT:
673 cont = cont->focused;
674 /* Fallthrough */
675
676 case C_OUTPUT:
677 cont = cont ? cont->focused : NULL;
678 /* Fallthrough */
679
680 case C_WORKSPACE:
681 return cont;
682
683 default:
684 return swayc_parent_by_type(cont, C_WORKSPACE);
685 }
686}
687
688static bool pointer_test(swayc_t *view, void *_origin) {
689 const struct wlc_point *origin = _origin;
690 // Determine the output that the view is under
691 swayc_t *parent = swayc_parent_by_type(view, C_OUTPUT);
692 if (origin->x >= view->x && origin->y >= view->y
693 && origin->x < view->x + view->width && origin->y < view->y + view->height
694 && view->visible && parent == root_container.focused) {
695 return true;
696 }
697 return false;
698}
699
700swayc_t *container_under_pointer(void) {
701 // root.output->workspace
702 if (!root_container.focused) {
703 return NULL;
704 }
705 swayc_t *lookup = root_container.focused;
706 // Case of empty workspace
707 if (lookup->children && !lookup->unmanaged) {
708 return NULL;
709 }
710 double x, y;
711 wlc_pointer_get_position_v2(&x, &y);
712 struct wlc_point origin = { .x = x, .y = y };
713
714 while (lookup && lookup->type != C_VIEW) {
715 int i;
716 int len;
717 for (int _i = 0; lookup->unmanaged && _i < lookup->unmanaged->length; ++_i) {
718 wlc_handle *handle = lookup->unmanaged->items[_i];
719 const struct wlc_geometry *geo = wlc_view_get_geometry(*handle);
720 if (origin.x >= geo->origin.x && origin.y >= geo->origin.y
721 && origin.x < geo->origin.x + (int)geo->size.w
722 && origin.y < geo->origin.y + (int)geo->size.h) {
723 // Hack: we force focus upon unmanaged views here
724 wlc_view_focus(*handle);
725 return NULL;
726 }
727 }
728 // if tabbed/stacked go directly to focused container, otherwise search
729 // children
730 if (lookup->layout == L_TABBED || lookup->layout == L_STACKED) {
731 lookup = lookup->focused;
732 continue;
733 }
734 // if workspace, search floating
735 if (lookup->type == C_WORKSPACE) {
736 i = len = lookup->floating->length;
737 bool got_floating = false;
738 while (--i > -1) {
739 if (pointer_test(lookup->floating->items[i], &origin)) {
740 lookup = lookup->floating->items[i];
741 got_floating = true;
742 break;
743 }
744 }
745 if (got_floating) {
746 continue;
747 }
748 }
749 // search children
750 len = lookup->children->length;
751 for (i = 0; i < len; ++i) {
752 if (pointer_test(lookup->children->items[i], &origin)) {
753 lookup = lookup->children->items[i];
754 break;
755 }
756 }
757 // when border and titles are done, this could happen
758 if (i == len) {
759 break;
760 }
761 }
762 return lookup;
763}
764
765swayc_t *container_find(swayc_t *container, bool (*f)(swayc_t *, const void *), const void *data) {
766 if (container->children == NULL || container->children->length == 0) {
767 return NULL;
768 }
769
770 swayc_t *con;
771 if (container->type == C_WORKSPACE) {
772 for (int i = 0; i < container->floating->length; ++i) {
773 con = container->floating->items[i];
774 if (f(con, data)) {
775 return con;
776 }
777 con = container_find(con, f, data);
778 if (con != NULL) {
779 return con;
780 }
781 }
782 }
783
784 for (int i = 0; i < container->children->length; ++i) {
785 con = container->children->items[i];
786 if (f(con, data)) {
787 return con;
788 }
789
790 con = container_find(con, f, data);
791 if (con != NULL) {
792 return con;
793 }
794 }
795
796 return NULL;
797}
798
799// Container information
800
801bool swayc_is_fullscreen(swayc_t *view) {
802 return view && view->type == C_VIEW && (wlc_view_get_state(view->handle) & WLC_BIT_FULLSCREEN);
803}
804
805bool swayc_is_active(swayc_t *view) {
806 return view && view->type == C_VIEW && (wlc_view_get_state(view->handle) & WLC_BIT_ACTIVATED);
807}
808
809bool swayc_is_parent_of(swayc_t *parent, swayc_t *child) {
810 while (child != &root_container) {
811 child = child->parent;
812 if (child == parent) {
813 return true;
814 }
815 }
816 return false;
817}
818
819bool swayc_is_child_of(swayc_t *child, swayc_t *parent) {
820 return swayc_is_parent_of(parent, child);
821}
822
823bool swayc_is_empty_workspace(swayc_t *container) {
824 return container->type == C_WORKSPACE && container->children->length == 0;
825}
826
827int swayc_gap(swayc_t *container) {
828 if (container->type == C_VIEW || container->type == C_CONTAINER) {
829 return container->gaps >= 0 ? container->gaps : config->gaps_inner;
830 } else if (container->type == C_WORKSPACE) {
831 int base = container->gaps >= 0 ? container->gaps : config->gaps_outer;
832 if (config->edge_gaps && !(config->smart_gaps && container->children->length == 1)) {
833 // the inner gap is created via a margin around each window which
834 // is half the gap size, so the workspace also needs half a gap
835 // size to make the outermost gap the same size (excluding the
836 // actual "outer gap" size which is handled independently)
837 return base + config->gaps_inner / 2;
838 } else if (config->smart_gaps && container->children->length == 1) {
839 return 0;
840 } else {
841 return base;
842 }
843 } else {
844 return 0;
845 }
846}
847
848// Mapping
849
850void container_map(swayc_t *container, void (*f)(swayc_t *view, void *data), void *data) {
851 if (container) {
852 int i;
853 if (container->children) {
854 for (i = 0; i < container->children->length; ++i) {
855 swayc_t *child = container->children->items[i];
856 container_map(child, f, data);
857 }
858 }
859 if (container->floating) {
860 for (i = 0; i < container->floating->length; ++i) {
861 swayc_t *child = container->floating->items[i];
862 container_map(child, f, data);
863 }
864 }
865 f(container, data);
866 }
867}
868
869void update_visibility_output(swayc_t *container, wlc_handle output) {
870 // Inherit visibility
871 swayc_t *parent = container->parent;
872 container->visible = parent->visible;
873 // special cases where visibility depends on focus
874 if (parent->type == C_OUTPUT || parent->layout == L_TABBED ||
875 parent->layout == L_STACKED) {
876 container->visible = parent->focused == container && parent->visible;
877 }
878 // Set visibility and output for view
879 if (container->type == C_VIEW) {
880 wlc_view_set_output(container->handle, output);
881 wlc_view_set_mask(container->handle, container->visible ? VISIBLE : 0);
882 }
883 // Update visibility for children
884 else {
885 if (container->children) {
886 int i, len = container->children->length;
887 for (i = 0; i < len; ++i) {
888 update_visibility_output(container->children->items[i], output);
889 }
890 }
891 if (container->floating) {
892 int i, len = container->floating->length;
893 for (i = 0; i < len; ++i) {
894 update_visibility_output(container->floating->items[i], output);
895 }
896 }
897 }
898}
899
900void update_visibility(swayc_t *container) {
901 if (!container) return;
902 switch (container->type) {
903 case C_ROOT:
904 container->visible = true;
905 if (container->children) {
906 int i, len = container->children->length;
907 for (i = 0; i < len; ++i) {
908 update_visibility(container->children->items[i]);
909 }
910 }
911 return;
912
913 case C_OUTPUT:
914 container->visible = true;
915 if (container->children) {
916 int i, len = container->children->length;
917 for (i = 0; i < len; ++i) {
918 update_visibility_output(container->children->items[i], container->handle);
919 }
920 }
921 return;
922
923 default:
924 {
925 swayc_t *op = swayc_parent_by_type(container, C_OUTPUT);
926 update_visibility_output(container, op->handle);
927 }
928 }
929}
930
931void set_gaps(swayc_t *view, void *_data) {
932 int *data = _data;
933 if (!ASSERT_NONNULL(view)) {
934 return;
935 }
936 if (view->type == C_WORKSPACE || view->type == C_VIEW) {
937 view->gaps = *data;
938 }
939}
940
941void add_gaps(swayc_t *view, void *_data) {
942 int *data = _data;
943 if (!ASSERT_NONNULL(view)) {
944 return;
945 }
946 if (view->type == C_WORKSPACE || view->type == C_VIEW) {
947 if ((view->gaps += *data) < 0) {
948 view->gaps = 0;
949 }
950 }
951}
952
953static void close_view(swayc_t *container, void *data) {
954 if (container->type == C_VIEW) {
955 wlc_view_close(container->handle);
956 }
957}
958
959void close_views(swayc_t *container) {
960 container_map(container, close_view, NULL);
961}
962
963swayc_t *swayc_tabbed_stacked_ancestor(swayc_t *view) {
964 swayc_t *parent = NULL;
965 if (!ASSERT_NONNULL(view)) {
966 return NULL;
967 }
968 while (view->type != C_WORKSPACE && view->parent && view->parent->type != C_WORKSPACE) {
969 view = view->parent;
970 if (view->layout == L_TABBED || view->layout == L_STACKED) {
971 parent = view;
972 }
973 }
974
975 return parent;
976}
977
978swayc_t *swayc_tabbed_stacked_parent(swayc_t *con) {
979 if (!ASSERT_NONNULL(con)) {
980 return NULL;
981 }
982 if (con->parent && (con->parent->layout == L_TABBED || con->parent->layout == L_STACKED)) {
983 return con->parent;
984 }
985 return NULL;
986}
987
988swayc_t *swayc_change_layout(swayc_t *container, enum swayc_layouts layout) {
989 // if layout change modifies the auto layout's major axis, swap width and height
990 // to preserve current ratios.
991 if (is_auto_layout(layout) && is_auto_layout(container->layout)) {
992 enum swayc_layouts prev_major =
993 container->layout == L_AUTO_LEFT || container->layout == L_AUTO_RIGHT
994 ? L_HORIZ : L_VERT;
995 enum swayc_layouts new_major =
996 layout == L_AUTO_LEFT || layout == L_AUTO_RIGHT
997 ? L_HORIZ : L_VERT;
998 if (new_major != prev_major) {
999 for (int i = 0; i < container->children->length; ++i) {
1000 swayc_t *child = container->children->items[i];
1001 double h = child->height;
1002 child->height = child->width;
1003 child->width = h;
1004 }
1005 }
1006 }
1007 if (container->type == C_WORKSPACE) {
1008 container->workspace_layout = layout;
1009 if (layout == L_HORIZ || layout == L_VERT || is_auto_layout(layout)) {
1010 container->layout = layout;
1011 }
1012 } else {
1013 container->layout = layout;
1014 }
1015 return container;
1016}
diff --git a/sway/criteria.c b/sway/criteria.c
index e8978ebe..22e9a49b 100644
--- a/sway/criteria.c
+++ b/sway/criteria.c
@@ -4,13 +4,15 @@
4#include <stdbool.h> 4#include <stdbool.h>
5#include <pcre.h> 5#include <pcre.h>
6#include "sway/criteria.h" 6#include "sway/criteria.h"
7#include "sway/container.h" 7#include "sway/tree/container.h"
8#include "sway/config.h" 8#include "sway/config.h"
9#include "sway/tree/view.h"
9#include "stringop.h" 10#include "stringop.h"
10#include "list.h" 11#include "list.h"
11#include "log.h" 12#include "log.h"
12 13
13enum criteria_type { // *must* keep in sync with criteria_strings[] 14enum criteria_type { // *must* keep in sync with criteria_strings[]
15 CRIT_APP_ID,
14 CRIT_CLASS, 16 CRIT_CLASS,
15 CRIT_CON_ID, 17 CRIT_CON_ID,
16 CRIT_CON_MARK, 18 CRIT_CON_MARK,
@@ -27,6 +29,7 @@ enum criteria_type { // *must* keep in sync with criteria_strings[]
27}; 29};
28 30
29static const char * const criteria_strings[CRIT_LAST] = { 31static const char * const criteria_strings[CRIT_LAST] = {
32 [CRIT_APP_ID] = "app_id",
30 [CRIT_CLASS] = "class", 33 [CRIT_CLASS] = "class",
31 [CRIT_CON_ID] = "con_id", 34 [CRIT_CON_ID] = "con_id",
32 [CRIT_CON_MARK] = "con_mark", 35 [CRIT_CON_MARK] = "con_mark",
@@ -100,8 +103,9 @@ static int countchr(char *str, char c) {
100// of buf. 103// of buf.
101// 104//
102// Returns error string or NULL if successful. 105// Returns error string or NULL if successful.
103static char *crit_tokens(int *argc, char ***buf, const char * const criteria_str) { 106static char *crit_tokens(int *argc, char ***buf,
104 sway_log(L_DEBUG, "Parsing criteria: '%s'", criteria_str); 107 const char * const criteria_str) {
108 wlr_log(L_DEBUG, "Parsing criteria: '%s'", criteria_str);
105 char *base = criteria_from(criteria_str); 109 char *base = criteria_from(criteria_str);
106 char *head = base; 110 char *head = base;
107 char *namep = head; // start of criteria name 111 char *namep = head; // start of criteria name
@@ -247,13 +251,13 @@ char *extract_crit_tokens(list_t *tokens, const char * const criteria) {
247 free_crit_token(token); 251 free_crit_token(token);
248 goto ect_cleanup; 252 goto ect_cleanup;
249 } else if (token->type == CRIT_URGENT || crit_is_focused(value)) { 253 } else if (token->type == CRIT_URGENT || crit_is_focused(value)) {
250 sway_log(L_DEBUG, "%s -> \"%s\"", name, value); 254 wlr_log(L_DEBUG, "%s -> \"%s\"", name, value);
251 list_add(tokens, token); 255 list_add(tokens, token);
252 } else if((error = generate_regex(&token->regex, value))) { 256 } else if((error = generate_regex(&token->regex, value))) {
253 free_crit_token(token); 257 free_crit_token(token);
254 goto ect_cleanup; 258 goto ect_cleanup;
255 } else { 259 } else {
256 sway_log(L_DEBUG, "%s -> /%s/", name, value); 260 wlr_log(L_DEBUG, "%s -> /%s/", name, value);
257 list_add(tokens, token); 261 list_add(tokens, token);
258 } 262 }
259 } 263 }
@@ -268,8 +272,8 @@ static int regex_cmp(const char *item, const pcre *regex) {
268} 272}
269 273
270// test a single view if it matches list of criteria tokens (all of them). 274// test a single view if it matches list of criteria tokens (all of them).
271static bool criteria_test(swayc_t *cont, list_t *tokens) { 275static bool criteria_test(struct sway_container *cont, list_t *tokens) {
272 if (cont->type != C_VIEW) { 276 if (cont->type != C_CONTAINER && cont->type != C_VIEW) {
273 return false; 277 return false;
274 } 278 }
275 int matches = 0; 279 int matches = 0;
@@ -277,94 +281,85 @@ static bool criteria_test(swayc_t *cont, list_t *tokens) {
277 struct crit_token *crit = tokens->items[i]; 281 struct crit_token *crit = tokens->items[i];
278 switch (crit->type) { 282 switch (crit->type) {
279 case CRIT_CLASS: 283 case CRIT_CLASS:
280 if (!cont->class) { 284 {
281 // ignore 285 const char *class = view_get_class(cont->sway_view);
282 } else if (crit_is_focused(crit->raw)) { 286 if (!class) {
283 swayc_t *focused = get_focused_view(&root_container); 287 break;
284 if (focused->class && strcmp(cont->class, focused->class) == 0) { 288 }
289 if (crit->regex && regex_cmp(class, crit->regex) == 0) {
285 matches++; 290 matches++;
286 } 291 }
287 } else if (crit->regex && regex_cmp(cont->class, crit->regex) == 0) { 292 break;
288 matches++;
289 } 293 }
290 break; 294 case CRIT_CON_ID:
291 case CRIT_CON_ID: { 295 {
292 char *endptr; 296 char *endptr;
293 size_t crit_id = strtoul(crit->raw, &endptr, 10); 297 size_t crit_id = strtoul(crit->raw, &endptr, 10);
294 298
295 if (*endptr == 0 && cont->id == crit_id) { 299 if (*endptr == 0 && cont->id == crit_id) {
296 ++matches;
297 }
298 break;
299 }
300 case CRIT_CON_MARK:
301 if (crit->regex && cont->marks && (list_seq_find(cont->marks, (int (*)(const void *, const void *))regex_cmp, crit->regex) != -1)) {
302 // Make sure it isn't matching the NUL string
303 if ((strcmp(crit->raw, "") == 0) == (list_seq_find(cont->marks, (int (*)(const void *, const void *))strcmp, "") != -1)) {
304 ++matches; 300 ++matches;
305 } 301 }
302 break;
306 } 303 }
304 case CRIT_CON_MARK:
305 // TODO
307 break; 306 break;
308 case CRIT_FLOATING: 307 case CRIT_FLOATING:
309 if (cont->is_floating) { 308 // TODO
310 matches++;
311 }
312 break; 309 break;
313 case CRIT_ID: 310 case CRIT_ID:
314 if (!cont->app_id) { 311 // TODO
315 // ignore
316 } else if (crit->regex && regex_cmp(cont->app_id, crit->regex) == 0) {
317 matches++;
318 }
319 break; 312 break;
313 case CRIT_APP_ID:
314 {
315 const char *app_id = view_get_app_id(cont->sway_view);
316 if (!app_id) {
317 break;
318 }
319
320 if (crit->regex && regex_cmp(app_id, crit->regex) == 0) {
321 matches++;
322 }
323 break;
324 }
320 case CRIT_INSTANCE: 325 case CRIT_INSTANCE:
321 if (!cont->instance) { 326 {
322 // ignore 327 const char *instance = view_get_instance(cont->sway_view);
323 } else if (crit_is_focused(crit->raw)) { 328 if (!instance) {
324 swayc_t *focused = get_focused_view(&root_container); 329 break;
325 if (focused->instance && strcmp(cont->instance, focused->instance) == 0) { 330 }
331
332 if (crit->regex && regex_cmp(instance, crit->regex) == 0) {
326 matches++; 333 matches++;
327 } 334 }
328 } else if (crit->regex && regex_cmp(cont->instance, crit->regex) == 0) { 335 break;
329 matches++;
330 } 336 }
331 break;
332 case CRIT_TILING: 337 case CRIT_TILING:
333 if (!cont->is_floating) { 338 // TODO
334 matches++;
335 }
336 break; 339 break;
337 case CRIT_TITLE: 340 case CRIT_TITLE:
338 if (!cont->name) { 341 {
339 // ignore 342 const char *title = view_get_title(cont->sway_view);
340 } else if (crit_is_focused(crit->raw)) { 343 if (!title) {
341 swayc_t *focused = get_focused_view(&root_container); 344 break;
342 if (focused->name && strcmp(cont->name, focused->name) == 0) { 345 }
346
347 if (crit->regex && regex_cmp(title, crit->regex) == 0) {
343 matches++; 348 matches++;
344 } 349 }
345 } else if (crit->regex && regex_cmp(cont->name, crit->regex) == 0) { 350 break;
346 matches++;
347 } 351 }
348 break; 352 case CRIT_URGENT:
349 case CRIT_URGENT: // "latest" or "oldest" 353 // TODO "latest" or "oldest"
350 break; 354 break;
351 case CRIT_WINDOW_ROLE: 355 case CRIT_WINDOW_ROLE:
356 // TODO
352 break; 357 break;
353 case CRIT_WINDOW_TYPE: 358 case CRIT_WINDOW_TYPE:
354 // TODO wlc indeed exposes this information 359 // TODO
355 break; 360 break;
356 case CRIT_WORKSPACE: ; 361 case CRIT_WORKSPACE:
357 swayc_t *cont_ws = swayc_parent_by_type(cont, C_WORKSPACE); 362 // TODO
358 if (!cont_ws || !cont_ws->name) {
359 // ignore
360 } else if (crit_is_focused(crit->raw)) {
361 swayc_t *focused_ws = swayc_active_workspace();
362 if (focused_ws->name && strcmp(cont_ws->name, focused_ws->name) == 0) {
363 matches++;
364 }
365 } else if (crit->regex && regex_cmp(cont_ws->name, crit->regex) == 0) {
366 matches++;
367 }
368 break; 363 break;
369 default: 364 default:
370 sway_abort("Invalid criteria type (%i)", crit->type); 365 sway_abort("Invalid criteria type (%i)", crit->type);
@@ -403,7 +398,7 @@ void free_criteria(struct criteria *crit) {
403 free(crit); 398 free(crit);
404} 399}
405 400
406bool criteria_any(swayc_t *cont, list_t *criteria) { 401bool criteria_any(struct sway_container *cont, list_t *criteria) {
407 for (int i = 0; i < criteria->length; i++) { 402 for (int i = 0; i < criteria->length; i++) {
408 struct criteria *bc = criteria->items[i]; 403 struct criteria *bc = criteria->items[i];
409 if (criteria_test(cont, bc->tokens)) { 404 if (criteria_test(cont, bc->tokens)) {
@@ -413,7 +408,7 @@ bool criteria_any(swayc_t *cont, list_t *criteria) {
413 return false; 408 return false;
414} 409}
415 410
416list_t *criteria_for(swayc_t *cont) { 411list_t *criteria_for(struct sway_container *cont) {
417 list_t *criteria = config->criteria, *matches = create_list(); 412 list_t *criteria = config->criteria, *matches = create_list();
418 for (int i = 0; i < criteria->length; i++) { 413 for (int i = 0; i < criteria->length; i++) {
419 struct criteria *bc = criteria->items[i]; 414 struct criteria *bc = criteria->items[i];
@@ -429,23 +424,22 @@ struct list_tokens {
429 list_t *tokens; 424 list_t *tokens;
430}; 425};
431 426
432static void container_match_add(swayc_t *container, struct list_tokens *list_tokens) { 427static void container_match_add(struct sway_container *container,
428 struct list_tokens *list_tokens) {
433 if (criteria_test(container, list_tokens->tokens)) { 429 if (criteria_test(container, list_tokens->tokens)) {
434 list_add(list_tokens->list, container); 430 list_add(list_tokens->list, container);
435 } 431 }
436} 432}
437 433
438list_t *container_for(list_t *tokens) { 434list_t *container_for_crit_tokens(list_t *tokens) {
439 struct list_tokens list_tokens = (struct list_tokens){create_list(), tokens}; 435 struct list_tokens list_tokens =
436 (struct list_tokens){create_list(), tokens};
440 437
441 container_map(&root_container, (void (*)(swayc_t *, void *))container_match_add, &list_tokens); 438 container_for_each_descendant_dfs(&root_container,
442 439 (void (*)(struct sway_container *, void *))container_match_add,
443 for (int i = 0; i < scratchpad->length; ++i) { 440 &list_tokens);
444 swayc_t *c = scratchpad->items[i];
445 if (criteria_test(c, tokens)) {
446 list_add(list_tokens.list, c);
447 }
448 }
449 441
442 // TODO look in the scratchpad
443
450 return list_tokens.list; 444 return list_tokens.list;
451} 445}
diff --git a/sway/debug-tree.c b/sway/debug-tree.c
new file mode 100644
index 00000000..ae0a1869
--- /dev/null
+++ b/sway/debug-tree.c
@@ -0,0 +1,119 @@
1#include <pango/pangocairo.h>
2#include <wlr/backend.h>
3#include <wlr/render/wlr_texture.h>
4#include <wlr/util/log.h>
5#include "config.h"
6#include "sway/input/input-manager.h"
7#include "sway/input/seat.h"
8#include "sway/server.h"
9#include "sway/tree/container.h"
10#include "sway/tree/layout.h"
11#include "cairo.h"
12#include "config.h"
13#include "pango.h"
14
15static const char *layout_to_str(enum sway_container_layout layout) {
16 switch (layout) {
17 case L_HORIZ:
18 return "L_HORIZ";
19 case L_VERT:
20 return "L_VERT";
21 case L_STACKED:
22 return "L_STACKED";
23 case L_TABBED:
24 return "L_TABBED";
25 case L_FLOATING:
26 return "L_FLOATING";
27 case L_NONE:
28 default:
29 return "L_NONE";
30 }
31}
32
33static int draw_container(cairo_t *cairo, struct sway_container *container,
34 struct sway_container *focus, int x, int y) {
35 int text_width, text_height;
36 get_text_size(cairo, "monospace", &text_width, &text_height,
37 1, false, "%s id:%zd '%s' %s %.fx%.f@%.f,%.f",
38 container_type_to_str(container->type), container->id, container->name,
39 layout_to_str(container->layout),
40 container->width, container->height, container->x, container->y);
41 cairo_save(cairo);
42 cairo_rectangle(cairo, x + 2, y, text_width - 2, text_height);
43 cairo_set_source_u32(cairo, 0xFFFFFFE0);
44 cairo_fill(cairo);
45 int height = text_height;
46 if (container->children) {
47 for (int i = 0; i < container->children->length; ++i) {
48 struct sway_container *child = container->children->items[i];
49 if (child->parent == container) {
50 cairo_set_source_u32(cairo, 0x000000FF);
51 } else {
52 cairo_set_source_u32(cairo, 0xFF0000FF);
53 }
54 height += draw_container(cairo, child, focus, x + 10, y + height);
55 }
56 }
57 cairo_set_source_u32(cairo, 0xFFFFFFE0);
58 cairo_rectangle(cairo, x, y, 2, height);
59 cairo_fill(cairo);
60 cairo_restore(cairo);
61 cairo_move_to(cairo, x, y);
62 if (focus == container) {
63 cairo_set_source_u32(cairo, 0x0000FFFF);
64 }
65 pango_printf(cairo, "monospace", 1, false, "%s id:%zd '%s' %s %.fx%.f@%.f,%.f",
66 container_type_to_str(container->type), container->id, container->name,
67 layout_to_str(container->layout),
68 container->width, container->height, container->x, container->y);
69 return height;
70}
71
72bool enable_debug_tree = false;
73
74void update_debug_tree() {
75 if (!enable_debug_tree) {
76 return;
77 }
78
79 int width = 640, height = 480;
80 for (int i = 0; i < root_container.children->length; ++i) {
81 struct sway_container *container = root_container.children->items[i];
82 if (container->width > width) {
83 width = container->width;
84 }
85 if (container->height > height) {
86 height = container->height;
87 }
88 }
89 cairo_surface_t *surface =
90 cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
91 cairo_t *cairo = cairo_create(surface);
92 PangoContext *pango = pango_cairo_create_context(cairo);
93
94 struct sway_seat *seat = NULL;
95 wl_list_for_each(seat, &input_manager->seats, link) {
96 break;
97 }
98
99 struct sway_container *focus = NULL;
100 if (seat != NULL) {
101 focus = seat_get_focus(seat);
102 }
103 cairo_set_source_u32(cairo, 0x000000FF);
104 draw_container(cairo, &root_container, focus, 0, 0);
105
106 cairo_surface_flush(surface);
107 struct wlr_renderer *renderer = wlr_backend_get_renderer(server.backend);
108 if (root_container.sway_root->debug_tree) {
109 wlr_texture_destroy(root_container.sway_root->debug_tree);
110 }
111 unsigned char *data = cairo_image_surface_get_data(surface);
112 int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
113 struct wlr_texture *texture = wlr_texture_from_pixels(renderer,
114 WL_SHM_FORMAT_ARGB8888, stride, width, height, data);
115 root_container.sway_root->debug_tree = texture;
116 cairo_surface_destroy(surface);
117 g_object_unref(pango);
118 cairo_destroy(cairo);
119}
diff --git a/sway/debug_log.c b/sway/debug_log.c
deleted file mode 100644
index d1eafae8..00000000
--- a/sway/debug_log.c
+++ /dev/null
@@ -1,103 +0,0 @@
1#include "log.h"
2#include "sway.h"
3#include <stdarg.h>
4#include <stdio.h>
5#include <stdlib.h>
6#include <libgen.h>
7#include <fcntl.h>
8#include <unistd.h>
9#include <signal.h>
10#include <errno.h>
11#include <string.h>
12#include <stringop.h>
13#include "sway/workspace.h"
14
15/* XXX:DEBUG:XXX */
16static void container_log(const swayc_t *c, int depth) {
17 fprintf(stderr, "focus:%c",
18 c == get_focused_view(&root_container) ? 'K':
19 c == get_focused_container(&root_container) ? 'F' : // Focused
20 c == swayc_active_workspace() ? 'W' : // active workspace
21 c == &root_container ? 'R' : // root
22 'X');// not any others
23 for (int i = 6; i > depth; i--) { fprintf(stderr, " "); }
24 fprintf(stderr,"|(%p)",c);
25 fprintf(stderr,"(p:%-8p)",c->parent);
26 fprintf(stderr,"(f:%-8p)",c->focused);
27 fprintf(stderr,"(h:%2" PRIuPTR ")",c->handle);
28 fprintf(stderr,"Type:%-4s|",
29 c->type == C_ROOT ? "root" :
30 c->type == C_OUTPUT ? "op" :
31 c->type == C_WORKSPACE ? "ws" :
32 c->type == C_CONTAINER ? "cont" :
33 c->type == C_VIEW ? "view" : "?");
34 fprintf(stderr,"layout:%-5s|",
35 c->layout == L_NONE ? "-" :
36 c->layout == L_HORIZ ? "Horiz":
37 c->layout == L_VERT ? "Vert":
38 c->layout == L_STACKED ? "Stack":
39 c->layout == L_TABBED ? "Tab":
40 c->layout == L_FLOATING ? "Float":
41 c->layout == L_AUTO_LEFT ? "A_lft":
42 c->layout == L_AUTO_RIGHT ? "A_rgt":
43 c->layout == L_AUTO_TOP ? "A_top":
44 c->layout == L_AUTO_BOTTOM ? "A_bot":
45 "Unknown");
46 fprintf(stderr, "w:%4.f|h:%4.f|", c->width, c->height);
47 fprintf(stderr, "x:%4.f|y:%4.f|", c->x, c->y);
48 fprintf(stderr, "g:%3d|",c->gaps);
49 fprintf(stderr, "vis:%c|", c->visible?'t':'f');
50 fprintf(stderr, "children:%2d|",c->children?c->children->length:0);
51 fprintf(stderr, "name:%.16s\n", c->name);
52}
53void layout_log(const swayc_t *c, int depth) {
54 if (L_DEBUG > get_log_level()) return;
55 int i, d;
56 int e = c->children ? c->children->length : 0;
57 container_log(c, depth);
58 if (e) {
59 for (i = 0; i < e; ++i) {
60 fputc('|',stderr);
61 for (d = 0; d < depth; ++d) fputc('-', stderr);
62 layout_log(c->children->items[i], depth + 1);
63 }
64 }
65 if (c->type == C_WORKSPACE) {
66 e = c->floating?c->floating->length:0;
67 if (e) {
68 for (i = 0; i < e; ++i) {
69 fputc('|',stderr);
70 for (d = 0; d < depth; ++d) fputc('=', stderr);
71 layout_log(c->floating->items[i], depth + 1);
72 }
73 }
74 }
75}
76
77const char *swayc_type_string(enum swayc_types type) {
78 return type == C_ROOT ? "ROOT" :
79 type == C_OUTPUT ? "OUTPUT" :
80 type == C_WORKSPACE ? "WORKSPACE" :
81 type == C_CONTAINER ? "CONTAINER" :
82 type == C_VIEW ? "VIEW" :
83 "UNKNOWN";
84}
85
86// Like sway_log, but also appends some info about given container to log output.
87void swayc_log(log_importance_t verbosity, swayc_t *cont, const char* format, ...) {
88 sway_assert(cont, "swayc_log: no container ...");
89 va_list args;
90 va_start(args, format);
91 char *txt = malloc(128);
92 vsprintf(txt, format, args);
93 va_end(args);
94
95 char *debug_txt = malloc(32);
96 snprintf(debug_txt, 32, "%s '%s'", swayc_type_string(cont->type), cont->name);
97
98 sway_log(verbosity, "%s (%s)", txt, debug_txt);
99 free(txt);
100 free(debug_txt);
101}
102
103/* XXX:DEBUG:XXX */
diff --git a/sway/desktop/desktop.c b/sway/desktop/desktop.c
new file mode 100644
index 00000000..66f33151
--- /dev/null
+++ b/sway/desktop/desktop.c
@@ -0,0 +1,14 @@
1#include "sway/tree/container.h"
2#include "sway/desktop.h"
3#include "sway/output.h"
4
5void desktop_damage_surface(struct wlr_surface *surface, double lx, double ly,
6 bool whole) {
7 for (int i = 0; i < root_container.children->length; ++i) {
8 struct sway_container *cont = root_container.children->items[i];
9 if (cont->type == C_OUTPUT) {
10 output_damage_surface(cont->sway_output, lx - cont->x, ly - cont->y,
11 surface, whole);
12 }
13 }
14}
diff --git a/sway/desktop/layer_shell.c b/sway/desktop/layer_shell.c
new file mode 100644
index 00000000..f841e5f1
--- /dev/null
+++ b/sway/desktop/layer_shell.c
@@ -0,0 +1,347 @@
1#include <stdbool.h>
2#include <stdlib.h>
3#include <string.h>
4#include <wayland-server.h>
5#include <wlr/types/wlr_box.h>
6#include <wlr/types/wlr_layer_shell.h>
7#include <wlr/types/wlr_output_damage.h>
8#include <wlr/types/wlr_output.h>
9#include <wlr/util/log.h>
10#include "sway/input/input-manager.h"
11#include "sway/input/seat.h"
12#include "sway/layers.h"
13#include "sway/output.h"
14#include "sway/server.h"
15#include "sway/tree/layout.h"
16
17static void apply_exclusive(struct wlr_box *usable_area,
18 uint32_t anchor, int32_t exclusive,
19 int32_t margin_top, int32_t margin_right,
20 int32_t margin_bottom, int32_t margin_left) {
21 if (exclusive <= 0) {
22 return;
23 }
24 struct {
25 uint32_t anchors;
26 int *positive_axis;
27 int *negative_axis;
28 int margin;
29 } edges[] = {
30 {
31 .anchors =
32 ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
33 ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
34 ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP,
35 .positive_axis = &usable_area->y,
36 .negative_axis = &usable_area->height,
37 .margin = margin_top,
38 },
39 {
40 .anchors =
41 ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
42 ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
43 ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
44 .positive_axis = NULL,
45 .negative_axis = &usable_area->height,
46 .margin = margin_bottom,
47 },
48 {
49 .anchors =
50 ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
51 ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
52 ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
53 .positive_axis = &usable_area->x,
54 .negative_axis = &usable_area->width,
55 .margin = margin_left,
56 },
57 {
58 .anchors =
59 ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
60 ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
61 ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
62 .positive_axis = NULL,
63 .negative_axis = &usable_area->width,
64 .margin = margin_right,
65 },
66 };
67 for (size_t i = 0; i < sizeof(edges) / sizeof(edges[0]); ++i) {
68 if ((anchor & edges[i].anchors) == edges[i].anchors) {
69 if (edges[i].positive_axis) {
70 *edges[i].positive_axis += exclusive + edges[i].margin;
71 }
72 if (edges[i].negative_axis) {
73 *edges[i].negative_axis -= exclusive + edges[i].margin;
74 }
75 }
76 }
77}
78
79static void arrange_layer(struct sway_output *output, struct wl_list *list,
80 struct wlr_box *usable_area, bool exclusive) {
81 struct sway_layer_surface *sway_layer;
82 struct wlr_box full_area = { 0 };
83 wlr_output_effective_resolution(output->wlr_output,
84 &full_area.width, &full_area.height);
85 wl_list_for_each(sway_layer, list, link) {
86 struct wlr_layer_surface *layer = sway_layer->layer_surface;
87 struct wlr_layer_surface_state *state = &layer->current;
88 if (exclusive != (state->exclusive_zone > 0)) {
89 continue;
90 }
91 struct wlr_box bounds;
92 if (state->exclusive_zone == -1) {
93 bounds = full_area;
94 } else {
95 bounds = *usable_area;
96 }
97 struct wlr_box box = {
98 .width = state->desired_width,
99 .height = state->desired_height
100 };
101 // Horizontal axis
102 const uint32_t both_horiz = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
103 | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
104 if ((state->anchor & both_horiz) && box.width == 0) {
105 box.x = bounds.x;
106 box.width = bounds.width;
107 } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) {
108 box.x = bounds.x;
109 } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) {
110 box.x = bounds.x + (bounds.width - box.width);
111 } else {
112 box.x = bounds.x + ((bounds.width / 2) - (box.width / 2));
113 }
114 // Vertical axis
115 const uint32_t both_vert = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP
116 | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
117 if ((state->anchor & both_vert) && box.height == 0) {
118 box.y = bounds.y;
119 box.height = bounds.height;
120 } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) {
121 box.y = bounds.y;
122 } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) {
123 box.y = bounds.y + (bounds.height - box.height);
124 } else {
125 box.y = bounds.y + ((bounds.height / 2) - (box.height / 2));
126 }
127 // Margin
128 if ((state->anchor & both_horiz) == both_horiz) {
129 box.x += state->margin.left;
130 box.width -= state->margin.left + state->margin.right;
131 } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) {
132 box.x += state->margin.left;
133 } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) {
134 box.x -= state->margin.right;
135 }
136 if ((state->anchor & both_vert) == both_vert) {
137 box.y += state->margin.top;
138 box.height -= state->margin.top + state->margin.bottom;
139 } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) {
140 box.y += state->margin.top;
141 } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) {
142 box.y -= state->margin.bottom;
143 }
144 if (box.width < 0 || box.height < 0) {
145 // TODO: Bubble up a protocol error?
146 wlr_layer_surface_close(layer);
147 continue;
148 }
149 // Apply
150 sway_layer->geo = box;
151 apply_exclusive(usable_area, state->anchor, state->exclusive_zone,
152 state->margin.top, state->margin.right,
153 state->margin.bottom, state->margin.left);
154 wlr_layer_surface_configure(layer, box.width, box.height);
155 }
156}
157
158void arrange_layers(struct sway_output *output) {
159 struct wlr_box usable_area = { 0 };
160 wlr_output_effective_resolution(output->wlr_output,
161 &usable_area.width, &usable_area.height);
162
163 // Arrange exclusive surfaces from top->bottom
164 arrange_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY],
165 &usable_area, true);
166 arrange_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP],
167 &usable_area, true);
168 arrange_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM],
169 &usable_area, true);
170 arrange_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND],
171 &usable_area, true);
172
173 if (memcmp(&usable_area, &output->usable_area,
174 sizeof(struct wlr_box)) != 0) {
175 wlr_log(L_DEBUG, "Usable area changed, rearranging output");
176 memcpy(&output->usable_area, &usable_area, sizeof(struct wlr_box));
177 arrange_windows(output->swayc, -1, -1);
178 }
179
180 // Arrange non-exlusive surfaces from top->bottom
181 usable_area.x = usable_area.y = 0;
182 wlr_output_effective_resolution(output->wlr_output,
183 &usable_area.width, &usable_area.height);
184 arrange_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY],
185 &usable_area, false);
186 arrange_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP],
187 &usable_area, false);
188 arrange_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM],
189 &usable_area, false);
190 arrange_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND],
191 &usable_area, false);
192
193 // Find topmost keyboard interactive layer, if such a layer exists
194 uint32_t layers_above_shell[] = {
195 ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY,
196 ZWLR_LAYER_SHELL_V1_LAYER_TOP,
197 };
198 size_t nlayers = sizeof(layers_above_shell) / sizeof(layers_above_shell[0]);
199 struct sway_layer_surface *layer, *topmost = NULL;
200 for (size_t i = 0; i < nlayers; ++i) {
201 wl_list_for_each_reverse(layer,
202 &output->layers[layers_above_shell[i]], link) {
203 if (layer->layer_surface->current.keyboard_interactive) {
204 topmost = layer;
205 break;
206 }
207 }
208 if (topmost != NULL) {
209 break;
210 }
211 }
212
213 struct sway_seat *seat;
214 wl_list_for_each(seat, &input_manager->seats, link) {
215 seat_set_focus_layer(seat, topmost ? topmost->layer_surface : NULL);
216 }
217}
218
219static void handle_output_destroy(struct wl_listener *listener, void *data) {
220 struct sway_layer_surface *sway_layer =
221 wl_container_of(listener, sway_layer, output_destroy);
222 wl_list_remove(&sway_layer->output_destroy.link);
223 sway_layer->layer_surface->output = NULL;
224 wlr_layer_surface_close(sway_layer->layer_surface);
225}
226
227static void handle_surface_commit(struct wl_listener *listener, void *data) {
228 struct sway_layer_surface *layer =
229 wl_container_of(listener, layer, surface_commit);
230 struct wlr_layer_surface *layer_surface = layer->layer_surface;
231 struct wlr_output *wlr_output = layer_surface->output;
232 if (wlr_output == NULL) {
233 return;
234 }
235
236 struct sway_output *output = wlr_output->data;
237 struct wlr_box old_geo = layer->geo;
238 arrange_layers(output);
239 if (memcmp(&old_geo, &layer->geo, sizeof(struct wlr_box)) != 0) {
240 output_damage_surface(output, old_geo.x, old_geo.y,
241 layer_surface->surface, true);
242 output_damage_surface(output, layer->geo.x, layer->geo.y,
243 layer_surface->surface, true);
244 } else {
245 output_damage_surface(output, layer->geo.x, layer->geo.y,
246 layer_surface->surface, false);
247 }
248}
249
250static void unmap(struct sway_layer_surface *sway_layer) {
251 struct wlr_output *wlr_output = sway_layer->layer_surface->output;
252 if (wlr_output == NULL) {
253 return;
254 }
255 struct sway_output *output = wlr_output->data;
256 output_damage_surface(output, sway_layer->geo.x, sway_layer->geo.y,
257 sway_layer->layer_surface->surface, true);
258}
259
260static void handle_destroy(struct wl_listener *listener, void *data) {
261 struct sway_layer_surface *sway_layer =
262 wl_container_of(listener, sway_layer, destroy);
263 wlr_log(L_DEBUG, "Layer surface destroyed (%s)",
264 sway_layer->layer_surface->namespace);
265 if (sway_layer->layer_surface->mapped) {
266 unmap(sway_layer);
267 }
268 wl_list_remove(&sway_layer->link);
269 wl_list_remove(&sway_layer->destroy.link);
270 wl_list_remove(&sway_layer->map.link);
271 wl_list_remove(&sway_layer->unmap.link);
272 wl_list_remove(&sway_layer->surface_commit.link);
273 if (sway_layer->layer_surface->output != NULL) {
274 struct sway_output *output = sway_layer->layer_surface->output->data;
275 arrange_layers(output);
276
277 wl_list_remove(&sway_layer->output_destroy.link);
278 }
279 free(sway_layer);
280}
281
282static void handle_map(struct wl_listener *listener, void *data) {
283 struct sway_layer_surface *sway_layer = wl_container_of(listener,
284 sway_layer, map);
285 struct sway_output *output = sway_layer->layer_surface->output->data;
286 output_damage_surface(output, sway_layer->geo.x, sway_layer->geo.y,
287 sway_layer->layer_surface->surface, true);
288 // TODO: send enter to subsurfaces and popups
289 wlr_surface_send_enter(sway_layer->layer_surface->surface,
290 sway_layer->layer_surface->output);
291}
292
293static void handle_unmap(struct wl_listener *listener, void *data) {
294 struct sway_layer_surface *sway_layer = wl_container_of(
295 listener, sway_layer, unmap);
296 unmap(sway_layer);
297}
298
299void handle_layer_shell_surface(struct wl_listener *listener, void *data) {
300 struct wlr_layer_surface *layer_surface = data;
301 struct sway_server *server =
302 wl_container_of(listener, server, layer_shell_surface);
303 wlr_log(L_DEBUG, "new layer surface: namespace %s layer %d anchor %d "
304 "size %dx%d margin %d,%d,%d,%d",
305 layer_surface->namespace, layer_surface->layer, layer_surface->layer,
306 layer_surface->client_pending.desired_width,
307 layer_surface->client_pending.desired_height,
308 layer_surface->client_pending.margin.top,
309 layer_surface->client_pending.margin.right,
310 layer_surface->client_pending.margin.bottom,
311 layer_surface->client_pending.margin.left);
312
313 struct sway_layer_surface *sway_layer =
314 calloc(1, sizeof(struct sway_layer_surface));
315 if (!sway_layer) {
316 return;
317 }
318
319 sway_layer->surface_commit.notify = handle_surface_commit;
320 wl_signal_add(&layer_surface->surface->events.commit,
321 &sway_layer->surface_commit);
322
323 sway_layer->output_destroy.notify = handle_output_destroy;
324 wl_signal_add(&layer_surface->output->events.destroy,
325 &sway_layer->output_destroy);
326
327 sway_layer->destroy.notify = handle_destroy;
328 wl_signal_add(&layer_surface->events.destroy, &sway_layer->destroy);
329 sway_layer->map.notify = handle_map;
330 wl_signal_add(&layer_surface->events.map, &sway_layer->map);
331 sway_layer->unmap.notify = handle_unmap;
332 wl_signal_add(&layer_surface->events.unmap, &sway_layer->unmap);
333 // TODO: Listen for subsurfaces
334
335 sway_layer->layer_surface = layer_surface;
336 layer_surface->data = sway_layer;
337
338 struct sway_output *output = layer_surface->output->data;
339 wl_list_insert(&output->layers[layer_surface->layer], &sway_layer->link);
340
341 // Temporarily set the layer's current state to client_pending
342 // So that we can easily arrange it
343 struct wlr_layer_surface_state old_state = layer_surface->current;
344 layer_surface->current = layer_surface->client_pending;
345 arrange_layers(output);
346 layer_surface->current = old_state;
347}
diff --git a/sway/desktop/output.c b/sway/desktop/output.c
new file mode 100644
index 00000000..1b3143d0
--- /dev/null
+++ b/sway/desktop/output.c
@@ -0,0 +1,579 @@
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_matrix.h>
10#include <wlr/types/wlr_output_damage.h>
11#include <wlr/types/wlr_output_layout.h>
12#include <wlr/types/wlr_output.h>
13#include <wlr/types/wlr_surface.h>
14#include <wlr/types/wlr_wl_shell.h>
15#include <wlr/util/region.h>
16#include "log.h"
17#include "sway/input/input-manager.h"
18#include "sway/input/seat.h"
19#include "sway/layers.h"
20#include "sway/output.h"
21#include "sway/server.h"
22#include "sway/tree/container.h"
23#include "sway/tree/layout.h"
24#include "sway/tree/view.h"
25
26struct sway_container *output_by_name(const char *name) {
27 for (int i = 0; i < root_container.children->length; ++i) {
28 struct sway_container *output = root_container.children->items[i];
29 if (strcasecmp(output->name, name) == 0) {
30 return output;
31 }
32 }
33 return NULL;
34}
35
36/**
37 * Rotate a child's position relative to a parent. The parent size is (pw, ph),
38 * the child position is (*sx, *sy) and its size is (sw, sh).
39 */
40static void rotate_child_position(double *sx, double *sy, double sw, double sh,
41 double pw, double ph, float rotation) {
42 if (rotation == 0.0f) {
43 return;
44 }
45
46 // Coordinates relative to the center of the subsurface
47 double ox = *sx - pw/2 + sw/2,
48 oy = *sy - ph/2 + sh/2;
49 // Rotated coordinates
50 double rx = cos(-rotation)*ox - sin(-rotation)*oy,
51 ry = cos(-rotation)*oy + sin(-rotation)*ox;
52 *sx = rx + pw/2 - sw/2;
53 *sy = ry + ph/2 - sh/2;
54}
55
56/**
57 * Contains a surface's root geometry information. For instance, when rendering
58 * a popup, this will contain the parent view's position and size.
59 */
60struct root_geometry {
61 double x, y;
62 int width, height;
63 float rotation;
64};
65
66static bool get_surface_box(struct root_geometry *geo,
67 struct sway_output *output, struct wlr_surface *surface, int sx, int sy,
68 struct wlr_box *surface_box) {
69 if (!wlr_surface_has_buffer(surface)) {
70 return false;
71 }
72
73 int sw = surface->current->width;
74 int sh = surface->current->height;
75
76 double _sx = sx, _sy = sy;
77 rotate_child_position(&_sx, &_sy, sw, sh, geo->width, geo->height,
78 geo->rotation);
79
80 struct wlr_box box = {
81 .x = geo->x + _sx,
82 .y = geo->y + _sy,
83 .width = sw,
84 .height = sh,
85 };
86 if (surface_box != NULL) {
87 memcpy(surface_box, &box, sizeof(struct wlr_box));
88 }
89
90 struct wlr_box rotated_box;
91 wlr_box_rotated_bounds(&box, geo->rotation, &rotated_box);
92
93 struct wlr_box output_box = {
94 .width = output->swayc->width,
95 .height = output->swayc->height,
96 };
97
98 struct wlr_box intersection;
99 return wlr_box_intersection(&output_box, &rotated_box, &intersection);
100}
101
102static void surface_for_each_surface(struct wlr_surface *surface,
103 double ox, double oy, struct root_geometry *geo,
104 wlr_surface_iterator_func_t iterator, void *user_data) {
105 geo->x = ox;
106 geo->y = oy;
107 geo->width = surface->current->width;
108 geo->height = surface->current->height;
109 geo->rotation = 0;
110
111 wlr_surface_for_each_surface(surface, iterator, user_data);
112}
113
114static void output_view_for_each_surface(struct sway_view *view,
115 struct root_geometry *geo, wlr_surface_iterator_func_t iterator,
116 void *user_data) {
117 geo->x = view->swayc->x;
118 geo->y = view->swayc->y;
119 geo->width = view->surface->current->width;
120 geo->height = view->surface->current->height;
121 geo->rotation = 0; // TODO
122
123 view_for_each_surface(view, iterator, user_data);
124}
125
126static void layer_for_each_surface(struct wl_list *layer_surfaces,
127 struct root_geometry *geo, wlr_surface_iterator_func_t iterator,
128 void *user_data) {
129 struct sway_layer_surface *layer_surface;
130 wl_list_for_each(layer_surface, layer_surfaces, link) {
131 struct wlr_layer_surface *wlr_layer_surface =
132 layer_surface->layer_surface;
133 surface_for_each_surface(wlr_layer_surface->surface,
134 layer_surface->geo.x, layer_surface->geo.y, geo, iterator,
135 user_data);
136 }
137}
138
139static void unmanaged_for_each_surface(struct wl_list *unmanaged,
140 struct sway_output *output, struct root_geometry *geo,
141 wlr_surface_iterator_func_t iterator, void *user_data) {
142 struct sway_xwayland_unmanaged *unmanaged_surface;
143 wl_list_for_each(unmanaged_surface, unmanaged, link) {
144 struct wlr_xwayland_surface *xsurface =
145 unmanaged_surface->wlr_xwayland_surface;
146 double ox = unmanaged_surface->lx - output->swayc->x;
147 double oy = unmanaged_surface->ly - output->swayc->y;
148
149 surface_for_each_surface(xsurface->surface, ox, oy, geo,
150 iterator, user_data);
151 }
152}
153
154static void scale_box(struct wlr_box *box, float scale) {
155 box->x *= scale;
156 box->y *= scale;
157 box->width *= scale;
158 box->height *= scale;
159}
160
161struct render_data {
162 struct root_geometry root_geo;
163 struct sway_output *output;
164 float alpha;
165};
166
167static void render_surface_iterator(struct wlr_surface *surface, int sx, int sy,
168 void *_data) {
169 struct render_data *data = _data;
170 struct wlr_output *wlr_output = data->output->wlr_output;
171 float rotation = data->root_geo.rotation;
172 float alpha = data->alpha;
173
174 if (!wlr_surface_has_buffer(surface)) {
175 return;
176 }
177
178 struct wlr_box box;
179 bool intersects = get_surface_box(&data->root_geo, data->output, surface,
180 sx, sy, &box);
181 if (!intersects) {
182 return;
183 }
184
185 struct wlr_renderer *renderer =
186 wlr_backend_get_renderer(wlr_output->backend);
187 if (!sway_assert(renderer != NULL,
188 "expected the output backend to have a renderer")) {
189 return;
190 }
191
192 scale_box(&box, wlr_output->scale);
193
194 float matrix[9];
195 enum wl_output_transform transform =
196 wlr_output_transform_invert(surface->current->transform);
197 wlr_matrix_project_box(matrix, &box, transform, rotation,
198 wlr_output->transform_matrix);
199
200 wlr_render_texture_with_matrix(renderer, surface->texture,
201 matrix, alpha);
202}
203
204static void render_layer(struct sway_output *output,
205 struct wl_list *layer_surfaces) {
206 struct render_data data = { .output = output, .alpha = 1.0f };
207 layer_for_each_surface(layer_surfaces, &data.root_geo,
208 render_surface_iterator, &data);
209}
210
211static void render_unmanaged(struct sway_output *output,
212 struct wl_list *unmanaged) {
213 struct render_data data = { .output = output, .alpha = 1.0f };
214 unmanaged_for_each_surface(unmanaged, output, &data.root_geo,
215 render_surface_iterator, &data);
216}
217
218static void render_container_iterator(struct sway_container *con,
219 void *_data) {
220 struct sway_output *output = _data;
221 if (!sway_assert(con->type == C_VIEW, "expected a view")) {
222 return;
223 }
224 struct render_data data = { .output = output, .alpha = con->alpha };
225 output_view_for_each_surface(con->sway_view, &data.root_geo,
226 render_surface_iterator, &data);
227}
228
229static void render_container(struct sway_output *output,
230 struct sway_container *con) {
231 container_descendants(con, C_VIEW, render_container_iterator, output);
232}
233
234static struct sway_container *output_get_active_workspace(
235 struct sway_output *output) {
236 struct sway_seat *seat = input_manager_current_seat(input_manager);
237 struct sway_container *focus =
238 seat_get_focus_inactive(seat, output->swayc);
239 if (!focus) {
240 // We've never been to this output before
241 focus = output->swayc->children->items[0];
242 }
243 struct sway_container *workspace = focus;
244 if (workspace->type != C_WORKSPACE) {
245 workspace = container_parent(workspace, C_WORKSPACE);
246 }
247 return workspace;
248}
249
250static void render_output(struct sway_output *output, struct timespec *when,
251 pixman_region32_t *damage) {
252 struct wlr_output *wlr_output = output->wlr_output;
253
254 struct wlr_renderer *renderer =
255 wlr_backend_get_renderer(wlr_output->backend);
256 if (!sway_assert(renderer != NULL,
257 "expected the output backend to have a renderer")) {
258 return;
259 }
260
261 wlr_renderer_begin(renderer, wlr_output->width, wlr_output->height);
262
263 if (!pixman_region32_not_empty(damage)) {
264 // Output isn't damaged but needs buffer swap
265 goto renderer_end;
266 }
267
268 // TODO: don't damage the whole output
269 int width, height;
270 wlr_output_transformed_resolution(wlr_output, &width, &height);
271 pixman_region32_union_rect(damage, damage, 0, 0, width, height);
272
273 float clear_color[] = {0.25f, 0.25f, 0.25f, 1.0f};
274 wlr_renderer_clear(renderer, clear_color);
275
276 render_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]);
277 render_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]);
278
279 struct sway_container *workspace = output_get_active_workspace(output);
280 render_container(output, workspace);
281
282 render_unmanaged(output, &root_container.sway_root->xwayland_unmanaged);
283
284 // TODO: consider revising this when fullscreen windows are supported
285 render_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]);
286 render_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]);
287
288renderer_end:
289 if (root_container.sway_root->debug_tree) {
290 wlr_render_texture(renderer, root_container.sway_root->debug_tree,
291 wlr_output->transform_matrix, 0, 0, 1);
292 }
293
294 wlr_renderer_end(renderer);
295 if (!wlr_output_damage_swap_buffers(output->damage, when, damage)) {
296 return;
297 }
298 output->last_frame = *when;
299}
300
301struct send_frame_done_data {
302 struct root_geometry root_geo;
303 struct sway_output *output;
304 struct timespec *when;
305};
306
307static void send_frame_done_iterator(struct wlr_surface *surface,
308 int sx, int sy, void *_data) {
309 struct send_frame_done_data *data = _data;
310
311 bool intersects = get_surface_box(&data->root_geo, data->output, surface,
312 sx, sy, NULL);
313 if (intersects) {
314 wlr_surface_send_frame_done(surface, data->when);
315 }
316}
317
318static void send_frame_done_layer(struct send_frame_done_data *data,
319 struct wl_list *layer_surfaces) {
320 layer_for_each_surface(layer_surfaces, &data->root_geo,
321 send_frame_done_iterator, data);
322}
323
324static void send_frame_done_unmanaged(struct send_frame_done_data *data,
325 struct wl_list *unmanaged) {
326 unmanaged_for_each_surface(unmanaged, data->output, &data->root_geo,
327 send_frame_done_iterator, data);
328}
329
330static void send_frame_done_container_iterator(struct sway_container *con,
331 void *_data) {
332 struct send_frame_done_data *data = _data;
333 if (!sway_assert(con->type == C_VIEW, "expected a view")) {
334 return;
335 }
336 output_view_for_each_surface(con->sway_view, &data->root_geo,
337 send_frame_done_iterator, data);
338}
339
340static void send_frame_done_container(struct send_frame_done_data *data,
341 struct sway_container *con) {
342 container_descendants(con, C_VIEW,
343 send_frame_done_container_iterator, data);
344}
345
346static void send_frame_done(struct sway_output *output, struct timespec *when) {
347 struct send_frame_done_data data = {
348 .output = output,
349 .when = when,
350 };
351
352 send_frame_done_layer(&data,
353 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]);
354 send_frame_done_layer(&data,
355 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]);
356
357 struct sway_container *workspace = output_get_active_workspace(output);
358 send_frame_done_container(&data, workspace);
359
360 send_frame_done_unmanaged(&data,
361 &root_container.sway_root->xwayland_unmanaged);
362
363 send_frame_done_layer(&data,
364 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]);
365 send_frame_done_layer(&data,
366 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]);
367}
368
369static void damage_handle_frame(struct wl_listener *listener, void *data) {
370 struct sway_output *output =
371 wl_container_of(listener, output, damage_frame);
372
373 if (!output->wlr_output->enabled) {
374 return;
375 }
376
377 struct timespec now;
378 clock_gettime(CLOCK_MONOTONIC, &now);
379
380 bool needs_swap;
381 pixman_region32_t damage;
382 pixman_region32_init(&damage);
383 if (!wlr_output_damage_make_current(output->damage, &needs_swap, &damage)) {
384 return;
385 }
386
387 if (needs_swap) {
388 render_output(output, &now, &damage);
389 }
390
391 pixman_region32_fini(&damage);
392
393 // Send frame done to all visible surfaces
394 send_frame_done(output, &now);
395}
396
397void output_damage_whole(struct sway_output *output) {
398 wlr_output_damage_add_whole(output->damage);
399}
400
401struct damage_data {
402 struct root_geometry root_geo;
403 struct sway_output *output;
404 bool whole;
405};
406
407static void damage_surface_iterator(struct wlr_surface *surface, int sx, int sy,
408 void *_data) {
409 struct damage_data *data = _data;
410 struct sway_output *output = data->output;
411 float rotation = data->root_geo.rotation;
412 bool whole = data->whole;
413
414 struct wlr_box box;
415 bool intersects = get_surface_box(&data->root_geo, data->output, surface,
416 sx, sy, &box);
417 if (!intersects) {
418 return;
419 }
420
421 scale_box(&box, output->wlr_output->scale);
422
423 if (whole) {
424 wlr_box_rotated_bounds(&box, rotation, &box);
425 wlr_output_damage_add_box(output->damage, &box);
426 } else {
427 int center_x = box.x + box.width/2;
428 int center_y = box.y + box.height/2;
429
430 pixman_region32_t damage;
431 pixman_region32_init(&damage);
432 pixman_region32_copy(&damage, &surface->current->surface_damage);
433 wlr_region_scale(&damage, &damage, output->wlr_output->scale);
434 if (ceil(output->wlr_output->scale) > surface->current->scale) {
435 // When scaling up a surface, it'll become blurry so we need to
436 // expand the damage region
437 wlr_region_expand(&damage, &damage,
438 ceil(output->wlr_output->scale) - surface->current->scale);
439 }
440 pixman_region32_translate(&damage, box.x, box.y);
441 wlr_region_rotated_bounds(&damage, &damage, rotation,
442 center_x, center_y);
443 wlr_output_damage_add(output->damage, &damage);
444 pixman_region32_fini(&damage);
445 }
446}
447
448void output_damage_surface(struct sway_output *output, double ox, double oy,
449 struct wlr_surface *surface, bool whole) {
450 struct damage_data data = {
451 .output = output,
452 .whole = whole,
453 };
454
455 surface_for_each_surface(surface, ox, oy, &data.root_geo,
456 damage_surface_iterator, &data);
457}
458
459void output_damage_view(struct sway_output *output, struct sway_view *view,
460 bool whole) {
461 if (!sway_assert(view->swayc != NULL, "expected a view in the tree")) {
462 return;
463 }
464
465 struct damage_data data = {
466 .output = output,
467 .whole = whole,
468 };
469
470 output_view_for_each_surface(view, &data.root_geo,
471 damage_surface_iterator, &data);
472}
473
474static void output_damage_whole_container_iterator(struct sway_container *con,
475 void *data) {
476 struct sway_output *output = data;
477
478 if (!sway_assert(con->type == C_VIEW, "expected a view")) {
479 return;
480 }
481
482 output_damage_view(output, con->sway_view, true);
483}
484
485void output_damage_whole_container(struct sway_output *output,
486 struct sway_container *con) {
487 float scale = output->wlr_output->scale;
488 struct wlr_box box = {
489 .x = con->x * scale,
490 .y = con->y * scale,
491 .width = con->width * scale,
492 .height = con->height * scale,
493 };
494 wlr_output_damage_add_box(output->damage, &box);
495
496 container_descendants(con, C_VIEW, output_damage_whole_container_iterator,
497 output);
498}
499
500static void damage_handle_destroy(struct wl_listener *listener, void *data) {
501 struct sway_output *output =
502 wl_container_of(listener, output, damage_destroy);
503 container_destroy(output->swayc);
504}
505
506static void handle_destroy(struct wl_listener *listener, void *data) {
507 struct sway_output *output = wl_container_of(listener, output, destroy);
508 container_destroy(output->swayc);
509}
510
511static void handle_mode(struct wl_listener *listener, void *data) {
512 struct sway_output *output = wl_container_of(listener, output, mode);
513 arrange_layers(output);
514 arrange_windows(output->swayc, -1, -1);
515}
516
517static void handle_transform(struct wl_listener *listener, void *data) {
518 struct sway_output *output = wl_container_of(listener, output, transform);
519 arrange_layers(output);
520 arrange_windows(output->swayc, -1, -1);
521}
522
523static void handle_scale(struct wl_listener *listener, void *data) {
524 struct sway_output *output = wl_container_of(listener, output, scale);
525 arrange_layers(output);
526 arrange_windows(output->swayc, -1, -1);
527}
528
529void handle_new_output(struct wl_listener *listener, void *data) {
530 struct sway_server *server = wl_container_of(listener, server, new_output);
531 struct wlr_output *wlr_output = data;
532 wlr_log(L_DEBUG, "New output %p: %s", wlr_output, wlr_output->name);
533
534 struct sway_output *output = calloc(1, sizeof(struct sway_output));
535 if (!output) {
536 return;
537 }
538 output->wlr_output = wlr_output;
539 wlr_output->data = output;
540 output->server = server;
541
542 if (!wl_list_empty(&wlr_output->modes)) {
543 struct wlr_output_mode *mode =
544 wl_container_of(wlr_output->modes.prev, mode, link);
545 wlr_output_set_mode(wlr_output, mode);
546 }
547
548 output->damage = wlr_output_damage_create(wlr_output);
549
550 output->swayc = output_create(output);
551 if (!output->swayc) {
552 free(output);
553 return;
554 }
555
556 size_t len = sizeof(output->layers) / sizeof(output->layers[0]);
557 for (size_t i = 0; i < len; ++i) {
558 wl_list_init(&output->layers[i]);
559 }
560
561 input_manager_configure_xcursor(input_manager);
562
563 wl_signal_add(&wlr_output->events.destroy, &output->destroy);
564 output->destroy.notify = handle_destroy;
565 wl_signal_add(&wlr_output->events.mode, &output->mode);
566 output->mode.notify = handle_mode;
567 wl_signal_add(&wlr_output->events.transform, &output->transform);
568 output->transform.notify = handle_transform;
569 wl_signal_add(&wlr_output->events.scale, &output->scale);
570 output->scale.notify = handle_scale;
571
572 wl_signal_add(&output->damage->events.frame, &output->damage_frame);
573 output->damage_frame.notify = damage_handle_frame;
574 wl_signal_add(&output->damage->events.destroy, &output->damage_destroy);
575 output->damage_destroy.notify = damage_handle_destroy;
576
577 arrange_layers(output);
578 arrange_windows(&root_container, -1, -1);
579}
diff --git a/sway/desktop/wl_shell.c b/sway/desktop/wl_shell.c
new file mode 100644
index 00000000..b63c220c
--- /dev/null
+++ b/sway/desktop/wl_shell.c
@@ -0,0 +1,131 @@
1#define _POSIX_C_SOURCE 199309L
2#include <stdbool.h>
3#include <stdlib.h>
4#include <wayland-server.h>
5#include <wlr/types/wlr_wl_shell.h>
6#include "sway/tree/container.h"
7#include "sway/tree/layout.h"
8#include "sway/server.h"
9#include "sway/tree/view.h"
10#include "sway/input/seat.h"
11#include "sway/input/input-manager.h"
12#include "log.h"
13
14static struct sway_wl_shell_view *wl_shell_view_from_view(
15 struct sway_view *view) {
16 if (!sway_assert(view->type == SWAY_VIEW_WL_SHELL,
17 "Expected wl_shell view")) {
18 return NULL;
19 }
20 return (struct sway_wl_shell_view *)view;
21}
22
23static const char *get_prop(struct sway_view *view, enum sway_view_prop prop) {
24 if (wl_shell_view_from_view(view) == NULL) {
25 return NULL;
26 }
27 switch (prop) {
28 case VIEW_PROP_TITLE:
29 return view->wlr_wl_shell_surface->title;
30 case VIEW_PROP_CLASS:
31 return view->wlr_wl_shell_surface->class;
32 default:
33 return NULL;
34 }
35}
36
37static void configure(struct sway_view *view, double ox, double oy, int width,
38 int height) {
39 struct sway_wl_shell_view *wl_shell_view = wl_shell_view_from_view(view);
40 if (wl_shell_view == NULL) {
41 return;
42 }
43 view_update_position(view, ox, oy);
44 wl_shell_view->pending_width = width;
45 wl_shell_view->pending_height = height;
46 wlr_wl_shell_surface_configure(view->wlr_wl_shell_surface, 0, width, height);
47}
48
49static void _close(struct sway_view *view) {
50 if (wl_shell_view_from_view(view) == NULL) {
51 return;
52 }
53
54 wl_client_destroy(view->wlr_wl_shell_surface->client);
55}
56
57static void destroy(struct sway_view *view) {
58 struct sway_wl_shell_view *wl_shell_view = wl_shell_view_from_view(view);
59 if (wl_shell_view == NULL) {
60 return;
61 }
62 wl_list_remove(&wl_shell_view->commit.link);
63 wl_list_remove(&wl_shell_view->destroy.link);
64 free(wl_shell_view);
65}
66
67static const struct sway_view_impl view_impl = {
68 .get_prop = get_prop,
69 .configure = configure,
70 .close = _close,
71 .destroy = destroy,
72};
73
74static void handle_commit(struct wl_listener *listener, void *data) {
75 struct sway_wl_shell_view *wl_shell_view =
76 wl_container_of(listener, wl_shell_view, commit);
77 struct sway_view *view = &wl_shell_view->view;
78 // NOTE: We intentionally discard the view's desired width here
79 // TODO: Let floating views do whatever
80 view_update_size(view, wl_shell_view->pending_width,
81 wl_shell_view->pending_height);
82 view_damage(view, false);
83}
84
85static void handle_destroy(struct wl_listener *listener, void *data) {
86 struct sway_wl_shell_view *wl_shell_view =
87 wl_container_of(listener, wl_shell_view, destroy);
88 view_destroy(&wl_shell_view->view);
89}
90
91void handle_wl_shell_surface(struct wl_listener *listener, void *data) {
92 struct sway_server *server = wl_container_of(listener, server,
93 wl_shell_surface);
94 struct wlr_wl_shell_surface *shell_surface = data;
95
96 if (shell_surface->state == WLR_WL_SHELL_SURFACE_STATE_POPUP) {
97 // popups don't get views
98 wlr_log(L_DEBUG, "New wl_shell popup");
99 return;
100 }
101
102 // TODO: make transient windows floating
103
104 wlr_log(L_DEBUG, "New wl_shell toplevel title='%s' app_id='%s'",
105 shell_surface->title, shell_surface->class);
106 wlr_wl_shell_surface_ping(shell_surface);
107
108 struct sway_wl_shell_view *wl_shell_view =
109 calloc(1, sizeof(struct sway_wl_shell_view));
110 if (!sway_assert(wl_shell_view, "Failed to allocate view")) {
111 return;
112 }
113
114 view_init(&wl_shell_view->view, SWAY_VIEW_WL_SHELL, &view_impl);
115 wl_shell_view->view.wlr_wl_shell_surface = shell_surface;
116
117 // TODO:
118 // - Wire up listeners
119 // - Look up pid and open on appropriate workspace
120 // - Set new view to maximized so it behaves nicely
121 // - Criteria
122
123 wl_shell_view->commit.notify = handle_commit;
124 wl_signal_add(&shell_surface->surface->events.commit,
125 &wl_shell_view->commit);
126
127 wl_shell_view->destroy.notify = handle_destroy;
128 wl_signal_add(&shell_surface->events.destroy, &wl_shell_view->destroy);
129
130 view_map(&wl_shell_view->view, shell_surface->surface);
131}
diff --git a/sway/desktop/xdg_shell_v6.c b/sway/desktop/xdg_shell_v6.c
new file mode 100644
index 00000000..e4703040
--- /dev/null
+++ b/sway/desktop/xdg_shell_v6.c
@@ -0,0 +1,249 @@
1#define _POSIX_C_SOURCE 199309L
2#include <stdbool.h>
3#include <stdlib.h>
4#include <wayland-server.h>
5#include <wlr/types/wlr_xdg_shell_v6.h>
6#include "sway/tree/container.h"
7#include "sway/tree/layout.h"
8#include "sway/server.h"
9#include "sway/tree/view.h"
10#include "sway/input/seat.h"
11#include "sway/input/input-manager.h"
12#include "log.h"
13
14static const struct sway_view_child_impl popup_impl;
15
16static void popup_destroy(struct sway_view_child *child) {
17 if (!sway_assert(child->impl == &popup_impl,
18 "Expected an xdg_shell_v6 popup")) {
19 return;
20 }
21 struct sway_xdg_popup_v6 *popup = (struct sway_xdg_popup_v6 *)child;
22 wl_list_remove(&popup->new_popup.link);
23 wl_list_remove(&popup->unmap.link);
24 wl_list_remove(&popup->destroy.link);
25 free(popup);
26}
27
28static const struct sway_view_child_impl popup_impl = {
29 .destroy = popup_destroy,
30};
31
32static struct sway_xdg_popup_v6 *popup_create(
33 struct wlr_xdg_popup_v6 *wlr_popup, struct sway_view *view);
34
35static void popup_handle_new_popup(struct wl_listener *listener, void *data) {
36 struct sway_xdg_popup_v6 *popup =
37 wl_container_of(listener, popup, new_popup);
38 struct wlr_xdg_popup_v6 *wlr_popup = data;
39 popup_create(wlr_popup, popup->child.view);
40}
41
42static void popup_handle_unmap(struct wl_listener *listener, void *data) {
43 struct sway_xdg_popup_v6 *popup = wl_container_of(listener, popup, unmap);
44 view_child_destroy(&popup->child);
45}
46
47static void popup_handle_destroy(struct wl_listener *listener, void *data) {
48 struct sway_xdg_popup_v6 *popup = wl_container_of(listener, popup, destroy);
49 view_child_destroy(&popup->child);
50}
51
52static struct sway_xdg_popup_v6 *popup_create(
53 struct wlr_xdg_popup_v6 *wlr_popup, struct sway_view *view) {
54 struct wlr_xdg_surface_v6 *xdg_surface = wlr_popup->base;
55
56 struct sway_xdg_popup_v6 *popup =
57 calloc(1, sizeof(struct sway_xdg_popup_v6));
58 if (popup == NULL) {
59 return NULL;
60 }
61 view_child_init(&popup->child, &popup_impl, view, xdg_surface->surface);
62
63 wl_signal_add(&xdg_surface->events.new_popup, &popup->new_popup);
64 popup->new_popup.notify = popup_handle_new_popup;
65 wl_signal_add(&xdg_surface->events.unmap, &popup->unmap);
66 popup->unmap.notify = popup_handle_unmap;
67 wl_signal_add(&xdg_surface->events.destroy, &popup->destroy);
68 popup->destroy.notify = popup_handle_destroy;
69
70 return popup;
71}
72
73
74static struct sway_xdg_shell_v6_view *xdg_shell_v6_view_from_view(
75 struct sway_view *view) {
76 if (!sway_assert(view->type == SWAY_VIEW_XDG_SHELL_V6,
77 "Expected xdg_shell_v6 view")) {
78 return NULL;
79 }
80 return (struct sway_xdg_shell_v6_view *)view;
81}
82
83static const char *get_prop(struct sway_view *view, enum sway_view_prop prop) {
84 if (xdg_shell_v6_view_from_view(view) == NULL) {
85 return NULL;
86 }
87 switch (prop) {
88 case VIEW_PROP_TITLE:
89 return view->wlr_xdg_surface_v6->toplevel->title;
90 case VIEW_PROP_APP_ID:
91 return view->wlr_xdg_surface_v6->toplevel->app_id;
92 default:
93 return NULL;
94 }
95}
96
97static void configure(struct sway_view *view, double ox, double oy, int width,
98 int height) {
99 struct sway_xdg_shell_v6_view *xdg_shell_v6_view =
100 xdg_shell_v6_view_from_view(view);
101 if (xdg_shell_v6_view == NULL) {
102 return;
103 }
104
105 view_update_position(view, ox, oy);
106 xdg_shell_v6_view->pending_width = width;
107 xdg_shell_v6_view->pending_height = height;
108 wlr_xdg_toplevel_v6_set_size(view->wlr_xdg_surface_v6, width, height);
109}
110
111static void set_activated(struct sway_view *view, bool activated) {
112 if (xdg_shell_v6_view_from_view(view) == NULL) {
113 return;
114 }
115 struct wlr_xdg_surface_v6 *surface = view->wlr_xdg_surface_v6;
116 if (surface->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL) {
117 wlr_xdg_toplevel_v6_set_activated(surface, activated);
118 }
119}
120
121static void for_each_surface(struct sway_view *view,
122 wlr_surface_iterator_func_t iterator, void *user_data) {
123 if (xdg_shell_v6_view_from_view(view) == NULL) {
124 return;
125 }
126 wlr_xdg_surface_v6_for_each_surface(view->wlr_xdg_surface_v6, iterator,
127 user_data);
128}
129
130static void _close(struct sway_view *view) {
131 if (xdg_shell_v6_view_from_view(view) == NULL) {
132 return;
133 }
134 struct wlr_xdg_surface_v6 *surface = view->wlr_xdg_surface_v6;
135 if (surface->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL) {
136 wlr_xdg_surface_v6_send_close(surface);
137 }
138}
139
140static void destroy(struct sway_view *view) {
141 struct sway_xdg_shell_v6_view *xdg_shell_v6_view =
142 xdg_shell_v6_view_from_view(view);
143 if (xdg_shell_v6_view == NULL) {
144 return;
145 }
146 wl_list_remove(&xdg_shell_v6_view->destroy.link);
147 wl_list_remove(&xdg_shell_v6_view->map.link);
148 wl_list_remove(&xdg_shell_v6_view->unmap.link);
149 free(xdg_shell_v6_view);
150}
151
152static const struct sway_view_impl view_impl = {
153 .get_prop = get_prop,
154 .configure = configure,
155 .set_activated = set_activated,
156 .for_each_surface = for_each_surface,
157 .close = _close,
158 .destroy = destroy,
159};
160
161static void handle_commit(struct wl_listener *listener, void *data) {
162 struct sway_xdg_shell_v6_view *xdg_shell_v6_view =
163 wl_container_of(listener, xdg_shell_v6_view, commit);
164 struct sway_view *view = &xdg_shell_v6_view->view;
165 // NOTE: We intentionally discard the view's desired width here
166 // TODO: Store this for restoration when moving to floating plane
167 // TODO: Let floating views do whatever
168 view_update_size(view, xdg_shell_v6_view->pending_width,
169 xdg_shell_v6_view->pending_height);
170 view_damage(view, false);
171}
172
173static void handle_new_popup(struct wl_listener *listener, void *data) {
174 struct sway_xdg_shell_v6_view *xdg_shell_v6_view =
175 wl_container_of(listener, xdg_shell_v6_view, new_popup);
176 struct wlr_xdg_popup_v6 *wlr_popup = data;
177 popup_create(wlr_popup, &xdg_shell_v6_view->view);
178}
179
180static void handle_unmap(struct wl_listener *listener, void *data) {
181 struct sway_xdg_shell_v6_view *xdg_shell_v6_view =
182 wl_container_of(listener, xdg_shell_v6_view, unmap);
183
184 view_unmap(&xdg_shell_v6_view->view);
185
186 wl_list_remove(&xdg_shell_v6_view->commit.link);
187 wl_list_remove(&xdg_shell_v6_view->new_popup.link);
188}
189
190static void handle_map(struct wl_listener *listener, void *data) {
191 struct sway_xdg_shell_v6_view *xdg_shell_v6_view =
192 wl_container_of(listener, xdg_shell_v6_view, map);
193 struct sway_view *view = &xdg_shell_v6_view->view;
194 struct wlr_xdg_surface_v6 *xdg_surface = view->wlr_xdg_surface_v6;
195
196 view_map(view, view->wlr_xdg_surface_v6->surface);
197
198 xdg_shell_v6_view->commit.notify = handle_commit;
199 wl_signal_add(&xdg_surface->surface->events.commit,
200 &xdg_shell_v6_view->commit);
201
202 xdg_shell_v6_view->new_popup.notify = handle_new_popup;
203 wl_signal_add(&xdg_surface->events.new_popup,
204 &xdg_shell_v6_view->new_popup);
205}
206
207static void handle_destroy(struct wl_listener *listener, void *data) {
208 struct sway_xdg_shell_v6_view *xdg_shell_v6_view =
209 wl_container_of(listener, xdg_shell_v6_view, destroy);
210 view_destroy(&xdg_shell_v6_view->view);
211}
212
213void handle_xdg_shell_v6_surface(struct wl_listener *listener, void *data) {
214 struct sway_server *server = wl_container_of(listener, server,
215 xdg_shell_v6_surface);
216 struct wlr_xdg_surface_v6 *xdg_surface = data;
217
218 if (xdg_surface->role == WLR_XDG_SURFACE_V6_ROLE_POPUP) {
219 wlr_log(L_DEBUG, "New xdg_shell_v6 popup");
220 return;
221 }
222
223 wlr_log(L_DEBUG, "New xdg_shell_v6 toplevel title='%s' app_id='%s'",
224 xdg_surface->toplevel->title, xdg_surface->toplevel->app_id);
225 wlr_xdg_surface_v6_ping(xdg_surface);
226 wlr_xdg_toplevel_v6_set_maximized(xdg_surface, true);
227
228 struct sway_xdg_shell_v6_view *xdg_shell_v6_view =
229 calloc(1, sizeof(struct sway_xdg_shell_v6_view));
230 if (!sway_assert(xdg_shell_v6_view, "Failed to allocate view")) {
231 return;
232 }
233
234 view_init(&xdg_shell_v6_view->view, SWAY_VIEW_XDG_SHELL_V6, &view_impl);
235 xdg_shell_v6_view->view.wlr_xdg_surface_v6 = xdg_surface;
236
237 // TODO:
238 // - Look up pid and open on appropriate workspace
239 // - Criteria
240
241 xdg_shell_v6_view->map.notify = handle_map;
242 wl_signal_add(&xdg_surface->events.map, &xdg_shell_v6_view->map);
243
244 xdg_shell_v6_view->unmap.notify = handle_unmap;
245 wl_signal_add(&xdg_surface->events.unmap, &xdg_shell_v6_view->unmap);
246
247 xdg_shell_v6_view->destroy.notify = handle_destroy;
248 wl_signal_add(&xdg_surface->events.destroy, &xdg_shell_v6_view->destroy);
249}
diff --git a/sway/desktop/xwayland.c b/sway/desktop/xwayland.c
new file mode 100644
index 00000000..413dbf8b
--- /dev/null
+++ b/sway/desktop/xwayland.c
@@ -0,0 +1,310 @@
1#define _POSIX_C_SOURCE 199309L
2#include <stdbool.h>
3#include <stdlib.h>
4#include <wayland-server.h>
5#include <wlr/types/wlr_output_layout.h>
6#include <wlr/types/wlr_output.h>
7#include <wlr/xwayland.h>
8#include "log.h"
9#include "sway/desktop.h"
10#include "sway/input/input-manager.h"
11#include "sway/input/seat.h"
12#include "sway/output.h"
13#include "sway/server.h"
14#include "sway/tree/container.h"
15#include "sway/tree/layout.h"
16#include "sway/tree/view.h"
17
18static void unmanaged_handle_request_configure(struct wl_listener *listener,
19 void *data) {
20 struct sway_xwayland_unmanaged *surface =
21 wl_container_of(listener, surface, request_configure);
22 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface;
23 struct wlr_xwayland_surface_configure_event *ev = data;
24 wlr_xwayland_surface_configure(xsurface, ev->x, ev->y,
25 ev->width, ev->height);
26}
27
28static void unmanaged_handle_commit(struct wl_listener *listener, void *data) {
29 struct sway_xwayland_unmanaged *surface =
30 wl_container_of(listener, surface, commit);
31 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface;
32
33 if (xsurface->x != surface->lx || xsurface->y != surface->ly) {
34 // Surface has moved
35 desktop_damage_surface(xsurface->surface, surface->lx, surface->ly,
36 true);
37 surface->lx = xsurface->x;
38 surface->ly = xsurface->y;
39 desktop_damage_surface(xsurface->surface, surface->lx, surface->ly,
40 true);
41 } else {
42 desktop_damage_surface(xsurface->surface, xsurface->x, xsurface->y,
43 false);
44 }
45}
46
47static void unmanaged_handle_map(struct wl_listener *listener, void *data) {
48 struct sway_xwayland_unmanaged *surface =
49 wl_container_of(listener, surface, map);
50 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface;
51
52 wl_list_insert(&root_container.sway_root->xwayland_unmanaged,
53 &surface->link);
54
55 wl_signal_add(&xsurface->surface->events.commit, &surface->commit);
56 surface->commit.notify = unmanaged_handle_commit;
57
58 surface->lx = xsurface->x;
59 surface->ly = xsurface->y;
60 desktop_damage_surface(xsurface->surface, surface->lx, surface->ly, true);
61
62 if (!wlr_xwayland_surface_is_unmanaged(xsurface)) {
63 struct sway_seat *seat = input_manager_current_seat(input_manager);
64 struct wlr_xwayland *xwayland = seat->input->server->xwayland;
65 wlr_xwayland_set_seat(xwayland, seat->wlr_seat);
66 seat_set_focus_surface(seat, xsurface->surface);
67 }
68
69 // TODO: we don't send surface enter/leave events to xwayland unmanaged
70 // surfaces, but xwayland doesn't support HiDPI anyway
71}
72
73static void unmanaged_handle_unmap(struct wl_listener *listener, void *data) {
74 struct sway_xwayland_unmanaged *surface =
75 wl_container_of(listener, surface, unmap);
76 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface;
77 desktop_damage_surface(xsurface->surface, xsurface->x, xsurface->y, true);
78 wl_list_remove(&surface->link);
79 wl_list_remove(&surface->commit.link);
80}
81
82static void unmanaged_handle_destroy(struct wl_listener *listener, void *data) {
83 struct sway_xwayland_unmanaged *surface =
84 wl_container_of(listener, surface, destroy);
85 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface;
86 if (xsurface->mapped) {
87 unmanaged_handle_unmap(&surface->unmap, xsurface);
88 }
89 wl_list_remove(&surface->map.link);
90 wl_list_remove(&surface->unmap.link);
91 wl_list_remove(&surface->destroy.link);
92 free(surface);
93}
94
95static struct sway_xwayland_unmanaged *create_unmanaged(
96 struct wlr_xwayland_surface *xsurface) {
97 struct sway_xwayland_unmanaged *surface =
98 calloc(1, sizeof(struct sway_xwayland_unmanaged));
99 if (surface == NULL) {
100 wlr_log(L_ERROR, "Allocation failed");
101 return NULL;
102 }
103
104 surface->wlr_xwayland_surface = xsurface;
105
106 wl_signal_add(&xsurface->events.request_configure,
107 &surface->request_configure);
108 surface->request_configure.notify = unmanaged_handle_request_configure;
109 wl_signal_add(&xsurface->events.map, &surface->map);
110 surface->map.notify = unmanaged_handle_map;
111 wl_signal_add(&xsurface->events.unmap, &surface->unmap);
112 surface->unmap.notify = unmanaged_handle_unmap;
113 wl_signal_add(&xsurface->events.destroy, &surface->destroy);
114 surface->destroy.notify = unmanaged_handle_destroy;
115
116 unmanaged_handle_map(&surface->map, xsurface);
117
118 return surface;
119}
120
121
122static struct sway_xwayland_view *xwayland_view_from_view(
123 struct sway_view *view) {
124 if (!sway_assert(view->type == SWAY_VIEW_XWAYLAND,
125 "Expected xwayland view")) {
126 return NULL;
127 }
128 return (struct sway_xwayland_view *)view;
129}
130
131static const char *get_prop(struct sway_view *view, enum sway_view_prop prop) {
132 if (xwayland_view_from_view(view) == NULL) {
133 return NULL;
134 }
135 switch (prop) {
136 case VIEW_PROP_TITLE:
137 return view->wlr_xwayland_surface->title;
138 case VIEW_PROP_CLASS:
139 return view->wlr_xwayland_surface->class;
140 default:
141 return NULL;
142 }
143}
144
145static void configure(struct sway_view *view, double ox, double oy, int width,
146 int height) {
147 struct sway_xwayland_view *xwayland_view = xwayland_view_from_view(view);
148 if (xwayland_view == NULL) {
149 return;
150 }
151 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
152
153 struct sway_container *output = container_parent(view->swayc, C_OUTPUT);
154 if (!sway_assert(output, "view must be within tree to set position")) {
155 return;
156 }
157 struct sway_container *root = container_parent(output, C_ROOT);
158 if (!sway_assert(root, "output must be within tree to set position")) {
159 return;
160 }
161 struct wlr_output_layout *layout = root->sway_root->output_layout;
162 struct wlr_output_layout_output *loutput =
163 wlr_output_layout_get(layout, output->sway_output->wlr_output);
164 if (!sway_assert(loutput, "output must be within layout to set position")) {
165 return;
166 }
167
168 view_update_position(view, ox, oy);
169
170 xwayland_view->pending_width = width;
171 xwayland_view->pending_height = height;
172 wlr_xwayland_surface_configure(xsurface, ox + loutput->x, oy + loutput->y,
173 width, height);
174}
175
176static void set_activated(struct sway_view *view, bool activated) {
177 if (xwayland_view_from_view(view) == NULL) {
178 return;
179 }
180 struct wlr_xwayland_surface *surface = view->wlr_xwayland_surface;
181 wlr_xwayland_surface_activate(surface, activated);
182}
183
184static void _close(struct sway_view *view) {
185 if (xwayland_view_from_view(view) == NULL) {
186 return;
187 }
188 wlr_xwayland_surface_close(view->wlr_xwayland_surface);
189}
190
191static void destroy(struct sway_view *view) {
192 struct sway_xwayland_view *xwayland_view = xwayland_view_from_view(view);
193 if (xwayland_view == NULL) {
194 return;
195 }
196 wl_list_remove(&xwayland_view->destroy.link);
197 wl_list_remove(&xwayland_view->request_configure.link);
198 wl_list_remove(&xwayland_view->map.link);
199 wl_list_remove(&xwayland_view->unmap.link);
200 free(xwayland_view);
201}
202
203static const struct sway_view_impl view_impl = {
204 .get_prop = get_prop,
205 .configure = configure,
206 .set_activated = set_activated,
207 .close = _close,
208 .destroy = destroy,
209};
210
211static void handle_commit(struct wl_listener *listener, void *data) {
212 struct sway_xwayland_view *xwayland_view =
213 wl_container_of(listener, xwayland_view, commit);
214 struct sway_view *view = &xwayland_view->view;
215 // NOTE: We intentionally discard the view's desired width here
216 // TODO: Let floating views do whatever
217 view_update_size(view, xwayland_view->pending_width,
218 xwayland_view->pending_height);
219 view_damage(view, false);
220}
221
222static void handle_unmap(struct wl_listener *listener, void *data) {
223 struct sway_xwayland_view *xwayland_view =
224 wl_container_of(listener, xwayland_view, unmap);
225 wl_list_remove(&xwayland_view->commit.link);
226 view_unmap(&xwayland_view->view);
227}
228
229static void handle_map(struct wl_listener *listener, void *data) {
230 struct sway_xwayland_view *xwayland_view =
231 wl_container_of(listener, xwayland_view, map);
232 struct wlr_xwayland_surface *xsurface = data;
233 struct sway_view *view = &xwayland_view->view;
234
235 // Wire up the commit listener here, because xwayland map/unmap can change
236 // the underlying wlr_surface
237 wl_signal_add(&xsurface->surface->events.commit, &xwayland_view->commit);
238 xwayland_view->commit.notify = handle_commit;
239
240 // Put it back into the tree
241 wlr_xwayland_surface_set_maximized(xsurface, true);
242 view_map(view, xsurface->surface);
243}
244
245static void handle_destroy(struct wl_listener *listener, void *data) {
246 struct sway_xwayland_view *xwayland_view =
247 wl_container_of(listener, xwayland_view, destroy);
248 struct sway_view *view = &xwayland_view->view;
249 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
250 if (xsurface->mapped) {
251 handle_unmap(&xwayland_view->unmap, xsurface);
252 }
253 view_destroy(&xwayland_view->view);
254}
255
256static void handle_request_configure(struct wl_listener *listener, void *data) {
257 struct sway_xwayland_view *xwayland_view =
258 wl_container_of(listener, xwayland_view, request_configure);
259 struct wlr_xwayland_surface_configure_event *ev = data;
260 struct sway_view *view = &xwayland_view->view;
261 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
262 // TODO: floating windows are allowed to move around like this, but make
263 // sure tiling windows always stay in place.
264 wlr_xwayland_surface_configure(xsurface, ev->x, ev->y,
265 ev->width, ev->height);
266}
267
268void handle_xwayland_surface(struct wl_listener *listener, void *data) {
269 struct sway_server *server = wl_container_of(listener, server,
270 xwayland_surface);
271 struct wlr_xwayland_surface *xsurface = data;
272
273 if (wlr_xwayland_surface_is_unmanaged(xsurface) ||
274 xsurface->override_redirect) {
275 wlr_log(L_DEBUG, "New xwayland unmanaged surface");
276 create_unmanaged(xsurface);
277 return;
278 }
279
280 wlr_log(L_DEBUG, "New xwayland surface title='%s' class='%s'",
281 xsurface->title, xsurface->class);
282
283 struct sway_xwayland_view *xwayland_view =
284 calloc(1, sizeof(struct sway_xwayland_view));
285 if (!sway_assert(xwayland_view, "Failed to allocate view")) {
286 return;
287 }
288
289 view_init(&xwayland_view->view, SWAY_VIEW_XWAYLAND, &view_impl);
290 xwayland_view->view.wlr_xwayland_surface = xsurface;
291
292 // TODO:
293 // - Look up pid and open on appropriate workspace
294 // - Criteria
295
296 wl_signal_add(&xsurface->events.destroy, &xwayland_view->destroy);
297 xwayland_view->destroy.notify = handle_destroy;
298
299 wl_signal_add(&xsurface->events.request_configure,
300 &xwayland_view->request_configure);
301 xwayland_view->request_configure.notify = handle_request_configure;
302
303 wl_signal_add(&xsurface->events.unmap, &xwayland_view->unmap);
304 xwayland_view->unmap.notify = handle_unmap;
305
306 wl_signal_add(&xsurface->events.map, &xwayland_view->map);
307 xwayland_view->map.notify = handle_map;
308
309 handle_map(&xwayland_view->map, xsurface);
310}
diff --git a/sway/extensions.c b/sway/extensions.c
deleted file mode 100644
index 91746561..00000000
--- a/sway/extensions.c
+++ /dev/null
@@ -1,407 +0,0 @@
1#include <stdlib.h>
2#include <wlc/wlc.h>
3#include <wlc/wlc-wayland.h>
4#include <wlc/wlc-render.h>
5#include "wayland-desktop-shell-server-protocol.h"
6#include "wayland-swaylock-server-protocol.h"
7#include "wayland-gamma-control-server-protocol.h"
8#include "wayland-server-decoration-server-protocol.h"
9#include "sway/layout.h"
10#include "sway/input_state.h"
11#include "sway/extensions.h"
12#include "sway/security.h"
13#include "sway/ipc-server.h"
14#include "log.h"
15
16struct desktop_shell_state desktop_shell;
17struct decoration_state decoration_state;
18
19static struct panel_config *find_or_create_panel_config(struct wl_resource *resource) {
20 for (int i = 0; i < desktop_shell.panels->length; i++) {
21 struct panel_config *conf = desktop_shell.panels->items[i];
22 if (conf->wl_resource == resource) {
23 sway_log(L_DEBUG, "Found existing panel config for resource %p", resource);
24 return conf;
25 }
26 }
27 sway_log(L_DEBUG, "Creating panel config for resource %p", resource);
28 struct panel_config *config = calloc(1, sizeof(struct panel_config));
29 if (!config) {
30 sway_log(L_ERROR, "Unable to create panel config");
31 return NULL;
32 }
33 list_add(desktop_shell.panels, config);
34 config->wl_resource = resource;
35 return config;
36}
37
38void background_surface_destructor(struct wl_resource *resource) {
39 sway_log(L_DEBUG, "Background surface killed");
40 int i;
41 for (i = 0; i < desktop_shell.backgrounds->length; ++i) {
42 struct background_config *config = desktop_shell.backgrounds->items[i];
43 if (config->wl_surface_res == resource) {
44 list_del(desktop_shell.backgrounds, i);
45 break;
46 }
47 }
48}
49
50void panel_surface_destructor(struct wl_resource *resource) {
51 sway_log(L_DEBUG, "Panel surface killed");
52 int i;
53 for (i = 0; i < desktop_shell.panels->length; ++i) {
54 struct panel_config *config = desktop_shell.panels->items[i];
55 if (config->wl_surface_res == resource) {
56 list_del(desktop_shell.panels, i);
57 arrange_windows(&root_container, -1, -1);
58 break;
59 }
60 }
61}
62
63void lock_surface_destructor(struct wl_resource *resource) {
64 sway_log(L_DEBUG, "Lock surface killed");
65 int i;
66 for (i = 0; i < desktop_shell.lock_surfaces->length; ++i) {
67 struct wl_resource *surface = desktop_shell.lock_surfaces->items[i];
68 if (surface == resource) {
69 list_del(desktop_shell.lock_surfaces, i);
70 arrange_windows(&root_container, -1, -1);
71 break;
72 }
73 }
74 if (desktop_shell.lock_surfaces->length == 0) {
75 sway_log(L_DEBUG, "Desktop shell unlocked");
76 desktop_shell.is_locked = false;
77
78 // We need to now give focus back to the focus which we internally
79 // track, since when we lock sway we don't actually change our internal
80 // focus tracking.
81 swayc_t *focus = get_focused_container(swayc_active_workspace());
82 set_focused_container(focus);
83 wlc_view_focus(focus->handle);
84 }
85}
86
87static void set_background(struct wl_client *client, struct wl_resource *resource,
88 struct wl_resource *_output, struct wl_resource *surface) {
89 pid_t pid;
90 wl_client_get_credentials(client, &pid, NULL, NULL);
91 if (!(get_feature_policy_mask(pid) & FEATURE_BACKGROUND)) {
92 sway_log(L_INFO, "Denying background feature to %d", pid);
93 return;
94 }
95 wlc_handle output = wlc_handle_from_wl_output_resource(_output);
96 if (!output) {
97 return;
98 }
99 sway_log(L_DEBUG, "Setting surface %p as background for output %d", surface, (int)output);
100 struct background_config *config = malloc(sizeof(struct background_config));
101 if (!config) {
102 sway_log(L_ERROR, "Unable to allocate background config");
103 return;
104 }
105 config->client = client;
106 config->output = output;
107 config->surface = wlc_resource_from_wl_surface_resource(surface);
108 config->wl_surface_res = surface;
109 list_add(desktop_shell.backgrounds, config);
110 wl_resource_set_destructor(surface, background_surface_destructor);
111 arrange_windows(swayc_by_handle(output), -1, -1);
112 wlc_output_schedule_render(config->output);
113}
114
115static void set_panel(struct wl_client *client, struct wl_resource *resource,
116 struct wl_resource *_output, struct wl_resource *surface) {
117 pid_t pid;
118 wl_client_get_credentials(client, &pid, NULL, NULL);
119 if (!(get_feature_policy_mask(pid) & FEATURE_PANEL)) {
120 sway_log(L_INFO, "Denying panel feature to %d", pid);
121 return;
122 }
123 wlc_handle output = wlc_handle_from_wl_output_resource(_output);
124 if (!output) {
125 return;
126 }
127 sway_log(L_DEBUG, "Setting surface %p as panel for output %d (wl_resource: %p)", surface, (int)output, resource);
128 struct panel_config *config = find_or_create_panel_config(resource);
129 config->output = output;
130 config->client = client;
131 config->surface = wlc_resource_from_wl_surface_resource(surface);
132 config->wl_surface_res = surface;
133 wl_resource_set_destructor(surface, panel_surface_destructor);
134 arrange_windows(&root_container, -1, -1);
135 wlc_output_schedule_render(config->output);
136}
137
138static void desktop_set_lock_surface(struct wl_client *client, struct wl_resource *resource, struct wl_resource *surface) {
139 sway_log(L_ERROR, "desktop_set_lock_surface is not currently supported");
140}
141
142static void desktop_unlock(struct wl_client *client, struct wl_resource *resource) {
143 sway_log(L_ERROR, "desktop_unlock is not currently supported");
144}
145
146static void set_grab_surface(struct wl_client *client, struct wl_resource *resource, struct wl_resource *surface) {
147 sway_log(L_ERROR, "desktop_set_grab_surface is not currently supported");
148}
149
150static void desktop_ready(struct wl_client *client, struct wl_resource *resource) {
151 // nop
152}
153
154static void set_panel_position(struct wl_client *client, struct wl_resource *resource, uint32_t position) {
155 pid_t pid;
156 wl_client_get_credentials(client, &pid, NULL, NULL);
157 if (!(get_feature_policy_mask(pid) & FEATURE_PANEL)) {
158 sway_log(L_INFO, "Denying panel feature to %d", pid);
159 return;
160 }
161 struct panel_config *config = find_or_create_panel_config(resource);
162 sway_log(L_DEBUG, "Panel position for wl_resource %p changed %d => %d", resource, config->panel_position, position);
163 config->panel_position = position;
164 arrange_windows(&root_container, -1, -1);
165}
166
167static struct desktop_shell_interface desktop_shell_implementation = {
168 .set_background = set_background,
169 .set_panel = set_panel,
170 .set_lock_surface = desktop_set_lock_surface,
171 .unlock = desktop_unlock,
172 .set_grab_surface = set_grab_surface,
173 .desktop_ready = desktop_ready,
174 .set_panel_position = set_panel_position
175};
176
177static void desktop_shell_bind(struct wl_client *client, void *data,
178 uint32_t version, uint32_t id) {
179 if (version > 3) {
180 // Unsupported version
181 return;
182 }
183
184 struct wl_resource *resource = wl_resource_create(client, &desktop_shell_interface, version, id);
185 if (!resource) {
186 wl_client_post_no_memory(client);
187 }
188
189 wl_resource_set_implementation(resource, &desktop_shell_implementation, NULL, NULL);
190}
191
192static void set_lock_surface(struct wl_client *client, struct wl_resource *resource,
193 struct wl_resource *_output, struct wl_resource *surface) {
194 pid_t pid;
195 wl_client_get_credentials(client, &pid, NULL, NULL);
196 if (!(get_feature_policy_mask(pid) & FEATURE_LOCK)) {
197 sway_log(L_INFO, "Denying lock feature to %d", pid);
198 return;
199 }
200 swayc_t *output = swayc_by_handle(wlc_handle_from_wl_output_resource(_output));
201 swayc_t *view = swayc_by_handle(wlc_handle_from_wl_surface_resource(surface));
202 sway_log(L_DEBUG, "Setting lock surface to %p", view);
203 if (view && output) {
204 swayc_t *workspace = output->focused;
205 if (!swayc_is_child_of(view, workspace)) {
206 move_container_to(view, workspace);
207 }
208 // make the view floating so it doesn't rearrange other siblings.
209 if (!view->is_floating) {
210 destroy_container(remove_child(view));
211 add_floating(workspace, view);
212 }
213 wlc_view_set_state(view->handle, WLC_BIT_FULLSCREEN, true);
214 wlc_view_bring_to_front(view->handle);
215 wlc_view_focus(view->handle);
216 desktop_shell.is_locked = true;
217 input_init();
218 arrange_windows(workspace, -1, -1);
219 list_add(desktop_shell.lock_surfaces, surface);
220 wl_resource_set_destructor(surface, lock_surface_destructor);
221 } else {
222 sway_log(L_ERROR, "Attempted to set lock surface to non-view");
223 }
224}
225
226static void unlock(struct wl_client *client, struct wl_resource *resource) {
227 sway_log(L_ERROR, "unlock is not currently supported");
228 // This isn't really necessary, we just unlock when the client exits.
229}
230
231static struct lock_interface swaylock_implementation = {
232 .set_lock_surface = set_lock_surface,
233 .unlock = unlock
234};
235
236static void swaylock_bind(struct wl_client *client, void *data,
237 uint32_t version, uint32_t id) {
238 if (version > 1) {
239 // Unsupported version
240 return;
241 }
242
243 struct wl_resource *resource = wl_resource_create(client, &lock_interface, version, id);
244 if (!resource) {
245 wl_client_post_no_memory(client);
246 }
247
248 wl_resource_set_implementation(resource, &swaylock_implementation, NULL, NULL);
249}
250
251static void gamma_control_destroy(struct wl_client *client, struct wl_resource *res) {
252 wl_resource_destroy(res);
253}
254
255static void gamma_control_set_gamma(struct wl_client *client,
256 struct wl_resource *res, struct wl_array *red,
257 struct wl_array *green, struct wl_array *blue) {
258 if (red->size != green->size || red->size != blue->size) {
259 wl_resource_post_error(res, GAMMA_CONTROL_ERROR_INVALID_GAMMA,
260 "The gamma ramps don't have the same size");
261 return;
262 }
263 uint16_t *r = (uint16_t *)red->data;
264 uint16_t *g = (uint16_t *)green->data;
265 uint16_t *b = (uint16_t *)blue->data;
266 wlc_handle output = wlc_handle_from_wl_output_resource(
267 wl_resource_get_user_data(res));
268 if (!output) {
269 return;
270 }
271 sway_log(L_DEBUG, "Setting gamma for output");
272 wlc_output_set_gamma(output, red->size / sizeof(uint16_t), r, g, b);
273}
274
275static void gamma_control_reset_gamma(struct wl_client *client,
276 struct wl_resource *resource) {
277 // This space intentionally left blank
278}
279
280static struct gamma_control_interface gamma_control_implementation = {
281 .destroy = gamma_control_destroy,
282 .set_gamma = gamma_control_set_gamma,
283 .reset_gamma = gamma_control_reset_gamma
284};
285
286static void gamma_control_manager_destroy(struct wl_client *client,
287 struct wl_resource *res) {
288 wl_resource_destroy(res);
289}
290
291static void gamma_control_manager_get(struct wl_client *client,
292 struct wl_resource *res, uint32_t id, struct wl_resource *_output) {
293 struct wl_resource *manager_res = wl_resource_create(client,
294 &gamma_control_interface, wl_resource_get_version(res), id);
295 wlc_handle output = wlc_handle_from_wl_output_resource(_output);
296 if (!output) {
297 return;
298 }
299 wl_resource_set_implementation(manager_res, &gamma_control_implementation,
300 _output, NULL);
301 gamma_control_send_gamma_size(manager_res, wlc_output_get_gamma_size(output));
302}
303
304static struct gamma_control_manager_interface gamma_manager_implementation = {
305 .destroy = gamma_control_manager_destroy,
306 .get_gamma_control = gamma_control_manager_get
307};
308
309static void gamma_control_manager_bind(struct wl_client *client, void *data,
310 uint32_t version, uint32_t id) {
311 if (version > 1) {
312 // Unsupported version
313 return;
314 }
315 struct wl_resource *resource = wl_resource_create(client,
316 &gamma_control_manager_interface, version, id);
317 if (!resource) {
318 wl_client_post_no_memory(client);
319 }
320 wl_resource_set_implementation(resource, &gamma_manager_implementation, NULL, NULL);
321}
322
323static void server_decoration_release(struct wl_client *client,
324 struct wl_resource *resource) {
325 wl_resource_destroy(resource);
326}
327
328void server_decoration_enable_csd(wlc_handle handle) {
329 swayc_t *view = swayc_by_handle(handle);
330 if (!view) {
331 sway_log(L_DEBUG, "view invalid");
332 return;
333 }
334 sway_log(L_DEBUG, "%s requested client side decorations", view->name);
335 view->border_type = B_NONE;
336 update_geometry(view);
337}
338
339static void server_decoration_request_mode(struct wl_client *client,
340 struct wl_resource *resource, uint32_t mode) {
341 sway_log(L_DEBUG, "Client requested server decoration mode %d", mode);
342 if (mode == ORG_KDE_KWIN_SERVER_DECORATION_MODE_SERVER) {
343 return;
344 }
345 struct wl_resource *surface = wl_resource_get_user_data(resource);
346 if (!surface) {
347 sway_log(L_DEBUG, "surface invalid");
348 return;
349 }
350 wlc_handle handle = wlc_handle_from_wl_surface_resource(surface);
351 if (!handle) {
352 list_add(decoration_state.csd_resources, surface);
353 return;
354 }
355 server_decoration_enable_csd(handle);
356}
357
358static struct org_kde_kwin_server_decoration_interface server_decoration_implementation = {
359 .release = server_decoration_release,
360 .request_mode = server_decoration_request_mode,
361};
362
363static void server_decoration_manager_create(struct wl_client *client,
364 struct wl_resource *resource, uint32_t id, struct wl_resource *surface) {
365 sway_log(L_DEBUG, "Client requested server decoration manager");
366 struct wl_resource *manager = wl_resource_create(client,
367 &org_kde_kwin_server_decoration_interface, 1, id);
368 if (!manager) {
369 wl_client_post_no_memory(client);
370 }
371 wl_resource_set_implementation(manager, &server_decoration_implementation, surface, NULL);
372}
373
374// Jesus christ KDE, these names are whack as hell
375static struct org_kde_kwin_server_decoration_manager_interface server_decoration_manager_implementation = {
376 .create = server_decoration_manager_create,
377};
378
379static void server_decoration_manager_bind(struct wl_client *client, void *data,
380 uint32_t version, uint32_t id) {
381 if (version > 1) {
382 // Unsupported version
383 return;
384 }
385 struct wl_resource *resource = wl_resource_create(client,
386 &org_kde_kwin_server_decoration_manager_interface, version, id);
387 if (!resource) {
388 wl_client_post_no_memory(client);
389 }
390 wl_resource_set_implementation(resource, &server_decoration_manager_implementation, NULL, NULL);
391 org_kde_kwin_server_decoration_manager_send_default_mode(resource,
392 ORG_KDE_KWIN_SERVER_DECORATION_MODE_SERVER);
393}
394
395void register_extensions(void) {
396 wl_global_create(wlc_get_wl_display(), &desktop_shell_interface, 3, NULL, desktop_shell_bind);
397 desktop_shell.backgrounds = create_list();
398 desktop_shell.panels = create_list();
399 desktop_shell.lock_surfaces = create_list();
400 desktop_shell.is_locked = false;
401 decoration_state.csd_resources = create_list();
402 wl_global_create(wlc_get_wl_display(), &lock_interface, 1, NULL, swaylock_bind);
403 wl_global_create(wlc_get_wl_display(), &gamma_control_manager_interface, 1,
404 NULL, gamma_control_manager_bind);
405 wl_global_create(wlc_get_wl_display(), &org_kde_kwin_server_decoration_manager_interface ,
406 1, NULL, server_decoration_manager_bind);
407}
diff --git a/sway/focus.c b/sway/focus.c
deleted file mode 100644
index e9b032f8..00000000
--- a/sway/focus.c
+++ /dev/null
@@ -1,277 +0,0 @@
1#include "stdbool.h"
2#include <wlc/wlc.h>
3#include "sway/focus.h"
4#include "sway/workspace.h"
5#include "sway/layout.h"
6#include "sway/config.h"
7#include "sway/extensions.h"
8#include "sway/input_state.h"
9#include "sway/ipc-server.h"
10#include "sway/border.h"
11#include "log.h"
12
13bool locked_container_focus = false;
14bool suspend_workspace_cleanup = false;
15
16// switches parent focus to c. will switch it accordingly
17static void update_focus(swayc_t *c) {
18 // Handle if focus switches
19 swayc_t *parent = c->parent;
20 if (!parent) return;
21 if (parent->focused != c) {
22 // Get previous focus
23 swayc_t *prev = parent->focused;
24 // Set new focus
25 parent->focused = c;
26
27 switch (c->type) {
28 // Shouldn't happen
29 case C_ROOT: return;
30
31 // Case where output changes
32 case C_OUTPUT:
33 wlc_output_focus(c->handle);
34 break;
35
36 // Case where workspace changes
37 case C_WORKSPACE:
38 if (prev) {
39 ipc_event_workspace(prev, c, "focus");
40
41 // if the old workspace has no children, destroy it
42 if(prev->children->length == 0 && prev->floating->length == 0 && !suspend_workspace_cleanup) {
43 destroy_workspace(prev);
44 } else {
45 // update visibility of old workspace
46 update_visibility(prev);
47 }
48 }
49 // Update visibility of newly focused workspace
50 update_visibility(c);
51 break;
52
53 default:
54 case C_VIEW:
55 case C_CONTAINER:
56 break;
57 }
58 }
59}
60
61bool move_focus(enum movement_direction direction) {
62 swayc_t *old_view = get_focused_container(&root_container);
63 swayc_t *new_view = get_swayc_in_direction(old_view, direction);
64 if (!new_view) {
65 return false;
66 } else if (new_view->type == C_ROOT) {
67 sway_log(L_DEBUG, "Not setting focus above the workspace level");
68 return false;
69 } else if (new_view->type == C_OUTPUT) {
70 return set_focused_container(swayc_active_workspace_for(new_view));
71 } else if (direction == MOVE_PARENT || direction == MOVE_CHILD) {
72 return set_focused_container(new_view);
73 } else if (config->mouse_warping) {
74 swayc_t *old_op = old_view->type == C_OUTPUT ?
75 old_view : swayc_parent_by_type(old_view, C_OUTPUT);
76 swayc_t *focused = get_focused_view(new_view);
77 if (set_focused_container(focused)) {
78 if (old_op != swayc_active_output() && focused && focused->type == C_VIEW) {
79 center_pointer_on(focused);
80 }
81 return true;
82 }
83 } else {
84 return set_focused_container(get_focused_view(new_view));
85 }
86 return false;
87}
88
89swayc_t *get_focused_container(swayc_t *parent) {
90 if (!parent) {
91 return swayc_active_workspace();
92 }
93 while (!parent->is_focused && parent->focused) {
94 parent = parent->focused;
95 }
96 return parent;
97}
98
99bool set_focused_container(swayc_t *c) {
100 if (locked_container_focus || !c || !c->parent) {
101 return false;
102 }
103
104 // current ("old") workspace for sending workspace change event later
105 swayc_t *old_ws = swayc_active_workspace();
106 // keep track of child count so we can determine if it gets destroyed
107 int old_ws_child_count = 0;
108 if (old_ws) {
109 old_ws_child_count = old_ws->children->length + old_ws->floating->length;
110 }
111
112 // current ("old") focused container
113 swayc_t *old_focus = get_focused_container(&root_container);
114 // if old_focus is a workspace, then it's the same workspace as
115 // old_ws, and we'll need to null its pointer too, since it will
116 // be destroyed in the update_focus() call
117 bool old_focus_was_ws = (old_focus->type == C_WORKSPACE);
118
119 // workspace of new focused container
120 swayc_t *workspace = swayc_active_workspace_for(c);
121
122 if (swayc_is_fullscreen(get_focused_container(workspace))) {
123 // if switching to a workspace with a fullscreen view,
124 // focus on the fullscreen view
125 c = get_focused_container(workspace);
126 }
127
128 swayc_log(L_DEBUG, c, "Setting focus to %p:%" PRIuPTR, c, c->handle);
129
130 if (c->type == C_VIEW) {
131 // dispatch a window event
132 ipc_event_window(c, "focus");
133 }
134
135 // update the global pointer
136 current_focus = c;
137
138 // update container focus from here to root, making necessary changes along
139 // the way
140 swayc_t *p = c;
141 if (p->type != C_OUTPUT && p->type != C_ROOT) {
142 p->is_focused = true;
143 }
144 while (p != &root_container) {
145 update_focus(p);
146 p = p->parent;
147 p->is_focused = false;
148 }
149
150 if (old_focus_was_ws && old_ws_child_count == 0) {
151 // this workspace was destroyed in update_focus(), so null the pointers
152 old_focus = NULL;
153 old_ws = NULL;
154 }
155
156 if (!(wlc_view_get_type(p->handle) & WLC_BIT_POPUP)) {
157 if (old_focus) {
158 if (old_focus->type == C_VIEW) {
159 wlc_view_set_state(old_focus->handle, WLC_BIT_ACTIVATED, false);
160 }
161 update_container_border(old_focus);
162 }
163 if (c->type == C_VIEW) {
164 wlc_view_set_state(c->handle, WLC_BIT_ACTIVATED, true);
165 }
166 if (!desktop_shell.is_locked) {
167 // If the system is locked, we do everything _but_ actually setting
168 // focus. This includes making our internals think that this view is
169 // focused.
170 wlc_view_focus(c->handle);
171 }
172 if (c->parent->layout != L_TABBED && c->parent->layout != L_STACKED) {
173 update_container_border(c);
174 }
175
176 swayc_t *parent = swayc_tabbed_stacked_ancestor(c);
177 if (parent != NULL) {
178 arrange_backgrounds();
179 arrange_windows(parent, -1, -1);
180 }
181 }
182
183 if (old_ws != workspace) {
184 // old_ws might be NULL here but that's ok
185 ipc_event_workspace(old_ws, workspace, "focus");
186 }
187
188 return true;
189}
190
191bool set_focused_container_for(swayc_t *a, swayc_t *c) {
192 if (locked_container_focus || !c) {
193 return false;
194 }
195 swayc_t *find = c;
196 while (find != a && (find = find->parent)) {
197 if (find == &root_container) {
198 return false;
199 }
200 }
201
202 // Get workspace for c, get that workspaces current focused container.
203 swayc_t *workspace = swayc_active_workspace_for(c);
204 swayc_t *focused = get_focused_view(workspace);
205 // if the workspace we are changing focus to has a fullscreen view return
206 if (swayc_is_fullscreen(focused) && c != focused) {
207 return false;
208 }
209
210 // Check if we are changing a parent container that will see change
211 bool effective = true;
212 while (find != &root_container) {
213 if (find->parent->focused != find) {
214 effective = false;
215 }
216 find = find->parent;
217 }
218 if (effective) {
219 // Go to set_focused_container
220 return set_focused_container(c);
221 }
222
223 sway_log(L_DEBUG, "Setting focus for %p:%" PRIuPTR " to %p:%" PRIuPTR,
224 a, a->handle, c, c->handle);
225
226 c->is_focused = true;
227 swayc_t *p = c;
228 while (p != a) {
229 update_focus(p);
230 p = p->parent;
231 p->is_focused = false;
232 }
233 return true;
234}
235
236swayc_t *get_focused_view(swayc_t *parent) {
237 swayc_t *c = parent;
238 while (c && c->type != C_VIEW) {
239 if (c->type == C_WORKSPACE && c->focused == NULL) {
240 return c;
241 }
242 c = c->focused;
243 }
244 if (c == NULL) {
245 c = swayc_active_workspace_for(parent);
246 }
247 return c;
248}
249
250swayc_t *get_focused_float(swayc_t *ws) {
251 if(!sway_assert(ws->type == C_WORKSPACE, "must be of workspace type")) {
252 ws = swayc_active_workspace();
253 }
254 if (ws->floating->length) {
255 return ws->floating->items[ws->floating->length - 1];
256 }
257 return NULL;
258}
259
260swayc_t *get_focused_view_include_floating(swayc_t *parent) {
261 swayc_t *c = parent;
262 swayc_t *f = NULL;
263
264 while (c && c->type != C_VIEW) {
265 if (c->type == C_WORKSPACE && c->focused == NULL) {
266 return ((f = get_focused_float(c))) ? f : c;
267 }
268
269 c = c->focused;
270 }
271
272 if (c == NULL) {
273 c = swayc_active_workspace_for(parent);
274 }
275
276 return c;
277}
diff --git a/sway/handlers.c b/sway/handlers.c
deleted file mode 100644
index 33e75d6b..00000000
--- a/sway/handlers.c
+++ /dev/null
@@ -1,1143 +0,0 @@
1#define _XOPEN_SOURCE 500
2#include <xkbcommon/xkbcommon.h>
3#include <strings.h>
4#include <stdlib.h>
5#include <stdbool.h>
6#include <libinput.h>
7#include <math.h>
8#include <wlc/wlc.h>
9#include <wlc/wlc-render.h>
10#include <wlc/wlc-wayland.h>
11#include <ctype.h>
12#include "sway/handlers.h"
13#include "sway/border.h"
14#include "sway/layout.h"
15#include "sway/config.h"
16#include "sway/commands.h"
17#include "sway/workspace.h"
18#include "sway/container.h"
19#include "sway/output.h"
20#include "sway/focus.h"
21#include "sway/input_state.h"
22#include "sway/extensions.h"
23#include "sway/criteria.h"
24#include "sway/ipc-server.h"
25#include "sway/input.h"
26#include "sway/security.h"
27#include "list.h"
28#include "stringop.h"
29#include "log.h"
30
31// Event should be sent to client
32#define EVENT_PASSTHROUGH false
33
34// Event handled by sway and should not be sent to client
35#define EVENT_HANDLED true
36
37static struct panel_config *if_panel_find_config(struct wl_client *client) {
38 int i;
39 for (i = 0; i < desktop_shell.panels->length; i++) {
40 struct panel_config *config = desktop_shell.panels->items[i];
41 if (config->client == client) {
42 return config;
43 }
44 }
45 return NULL;
46}
47
48static struct background_config *if_background_find_config(struct wl_client *client) {
49 int i;
50 for (i = 0; i < desktop_shell.backgrounds->length; i++) {
51 struct background_config *config = desktop_shell.backgrounds->items[i];
52 if (config->client == client) {
53 return config;
54 }
55 }
56 return NULL;
57}
58
59static struct wlc_geometry compute_panel_geometry(struct panel_config *config) {
60 struct wlc_size resolution;
61 output_get_scaled_size(config->output, &resolution);
62 const struct wlc_geometry *old = wlc_view_get_geometry(config->handle);
63 struct wlc_geometry new;
64
65 switch (config->panel_position) {
66 case DESKTOP_SHELL_PANEL_POSITION_TOP:
67 new.origin.x = 0;
68 new.origin.y = 0;
69 new.size.w = resolution.w;
70 new.size.h = old->size.h;
71 break;
72 case DESKTOP_SHELL_PANEL_POSITION_BOTTOM:
73 new.origin.x = 0;
74 new.origin.y = resolution.h - old->size.h;
75 new.size.w = resolution.w;
76 new.size.h = old->size.h;
77 break;
78 case DESKTOP_SHELL_PANEL_POSITION_LEFT:
79 new.origin.x = 0;
80 new.origin.y = 0;
81 new.size.w = old->size.w;
82 new.size.h = resolution.h;
83 break;
84 case DESKTOP_SHELL_PANEL_POSITION_RIGHT:
85 new.origin.x = resolution.w - old->size.w;
86 new.origin.y = 0;
87 new.size.w = old->size.w;
88 new.size.h = resolution.h;
89 break;
90 }
91
92 return new;
93}
94
95static void update_panel_geometry(struct panel_config *config) {
96 struct wlc_geometry geometry = compute_panel_geometry(config);
97 wlc_view_set_geometry(config->handle, 0, &geometry);
98}
99
100static void update_panel_geometries(wlc_handle output) {
101 for (int i = 0; i < desktop_shell.panels->length; i++) {
102 struct panel_config *config = desktop_shell.panels->items[i];
103 if (config->output == output) {
104 update_panel_geometry(config);
105 }
106 }
107}
108
109static void update_background_geometry(struct background_config *config) {
110 struct wlc_geometry geometry = wlc_geometry_zero;
111 output_get_scaled_size(config->output, &geometry.size);
112 wlc_view_set_geometry(config->handle, 0, &geometry);
113}
114
115static void update_background_geometries(wlc_handle output) {
116 for (int i = 0; i < desktop_shell.backgrounds->length; i++) {
117 struct background_config *config = desktop_shell.backgrounds->items[i];
118 if (config->output == output) {
119 update_background_geometry(config);
120 }
121 }
122}
123
124/* Handles */
125
126static bool handle_input_created(struct libinput_device *device) {
127 const char *identifier = libinput_dev_unique_id(device);
128 if (!identifier) {
129 sway_log(L_ERROR, "Unable to allocate unique name for input device %p",
130 device);
131 return true;
132 }
133 sway_log(L_INFO, "Found input device (%s)", identifier);
134
135 list_add(input_devices, device);
136
137 struct input_config *ic = NULL;
138 int i;
139 for (i = 0; i < config->input_configs->length; ++i) {
140 struct input_config *cur = config->input_configs->items[i];
141 if (strcasecmp(identifier, cur->identifier) == 0) {
142 sway_log(L_DEBUG, "Matched input config for %s",
143 identifier);
144 ic = cur;
145 break;
146 }
147 if (strcasecmp("*", cur->identifier) == 0) {
148 sway_log(L_DEBUG, "Matched wildcard input config for %s",
149 identifier);
150 ic = cur;
151 break;
152 }
153 }
154
155 apply_input_config(ic, device);
156 return true;
157}
158
159static void handle_input_destroyed(struct libinput_device *device) {
160 int i;
161 list_t *list = input_devices;
162 for (i = 0; i < list->length; ++i) {
163 if(((struct libinput_device *)list->items[i]) == device) {
164 list_del(list, i);
165 break;
166 }
167 }
168}
169
170static bool handle_output_created(wlc_handle output) {
171 swayc_t *op = new_output(output);
172
173 // Visibility mask to be able to make view invisible
174 wlc_output_set_mask(output, VISIBLE);
175
176 if (!op) {
177 return false;
178 }
179
180 // Switch to workspace if we need to
181 if (swayc_active_workspace() == NULL) {
182 swayc_t *ws = op->children->items[0];
183 workspace_switch(ws);
184 }
185
186 // Fixes issues with backgrounds and wlc
187 wlc_handle prev = wlc_get_focused_output();
188 wlc_output_focus(output);
189 wlc_output_focus(prev);
190 return true;
191}
192
193static void handle_output_destroyed(wlc_handle output) {
194 int i;
195 list_t *list = root_container.children;
196 for (i = 0; i < list->length; ++i) {
197 if (((swayc_t *)list->items[i])->handle == output) {
198 break;
199 }
200 }
201 if (i < list->length) {
202 destroy_output(list->items[i]);
203 } else {
204 return;
205 }
206 if (list->length > 0) {
207 // switch to other outputs active workspace
208 workspace_switch(((swayc_t *)root_container.children->items[0])->focused);
209 }
210}
211
212static void handle_output_post_render(wlc_handle output) {
213 ipc_get_pixels(output);
214}
215
216static void handle_view_pre_render(wlc_handle view) {
217 render_view_borders(view);
218}
219
220static void handle_output_resolution_change(wlc_handle output, const struct wlc_size *from, const struct wlc_size *to) {
221 sway_log(L_DEBUG, "Output %u resolution changed to %d x %d", (unsigned int)output, to->w, to->h);
222
223 swayc_t *c = swayc_by_handle(output);
224 if (!c) {
225 return;
226 }
227 c->width = to->w;
228 c->height = to->h;
229
230 update_panel_geometries(output);
231 update_background_geometries(output);
232
233 arrange_windows(&root_container, -1, -1);
234}
235
236static void handle_output_focused(wlc_handle output, bool focus) {
237 swayc_t *c = swayc_by_handle(output);
238 // if for some reason this output doesn't exist, create it.
239 if (!c) {
240 handle_output_created(output);
241 }
242 if (focus) {
243 set_focused_container(get_focused_container(c));
244 }
245}
246
247static void ws_cleanup() {
248 swayc_t *op, *ws;
249 int i = 0, j;
250 if (!root_container.children)
251 return;
252 while (i < root_container.children->length) {
253 op = root_container.children->items[i++];
254 if (!op->children)
255 continue;
256 j = 0;
257 while (j < op->children->length) {
258 ws = op->children->items[j++];
259 if (ws->children->length == 0 && ws->floating->length == 0 && ws != op->focused) {
260 if (destroy_workspace(ws)) {
261 j--;
262 }
263 }
264 }
265 }
266}
267
268static void positioner_place_window(wlc_handle handle) {
269 const struct wlc_geometry *anchor = wlc_view_positioner_get_anchor_rect(handle);
270 const struct wlc_size *sr = wlc_view_positioner_get_size(handle);
271 // a positioner is required to have a non-null anchor and a non-negative size
272 if (!anchor || !sr ||
273 sr->w <= 0 || sr->h <= 0 ||
274 anchor->size.w <= 0 || anchor->size.h <= 0) {
275 return;
276 }
277 const struct wlc_point offset = *wlc_view_positioner_get_offset(handle);
278 enum wlc_positioner_anchor_bit anchors = wlc_view_positioner_get_anchor(handle);
279 enum wlc_positioner_gravity_bit gravity = wlc_view_positioner_get_gravity(handle);
280 struct wlc_geometry geo = {
281 .origin = offset,
282 .size = *sr
283 };
284
285 if (anchors & WLC_BIT_ANCHOR_TOP) {
286 geo.origin.y += anchor->origin.y;
287 } else if (anchors & WLC_BIT_ANCHOR_BOTTOM) {
288 geo.origin.y += anchor->origin.y + anchor->size.h;
289 } else {
290 geo.origin.y += anchor->origin.y + anchor->size.h / 2;
291 }
292 if (anchors & WLC_BIT_ANCHOR_LEFT) {
293 geo.origin.x += anchor->origin.x;
294 } else if (anchors & WLC_BIT_ANCHOR_RIGHT) {
295 geo.origin.x += anchor->origin.x + anchor->size.w;
296 } else {
297 geo.origin.x += anchor->origin.x + anchor->size.w / 2;
298 }
299
300 if (gravity & WLC_BIT_GRAVITY_TOP) {
301 geo.origin.y -= geo.size.h;
302 } else if (gravity & WLC_BIT_GRAVITY_BOTTOM) {
303 /* default */
304 } else {
305 geo.origin.y -= geo.size.h / 2;
306 }
307 if (gravity & WLC_BIT_GRAVITY_LEFT) {
308 geo.origin.x -= geo.size.w;
309 } else if (gravity & WLC_BIT_GRAVITY_RIGHT) {
310 /* default */
311 } else {
312 geo.origin.x -= geo.size.w / 2;
313 }
314
315 sway_log(L_DEBUG, "xdg-positioner: placing window %" PRIuPTR " "
316 "sized (%u,%u) offset by (%d,%d), "
317 "anchor rectangle sized (%u,%u) at (%d,%d), "
318 "anchor edges: %s %s, gravity: %s %s",
319 handle,
320 sr->w, sr->h, offset.x, offset.y,
321 anchor->size.w, anchor->size.h, anchor->origin.x, anchor->origin.y,
322 anchors & WLC_BIT_ANCHOR_TOP ? "top" :
323 (anchors & WLC_BIT_ANCHOR_BOTTOM ? "bottom" : "middle"),
324 anchors & WLC_BIT_ANCHOR_LEFT ? "left" :
325 (anchors & WLC_BIT_ANCHOR_RIGHT ? "right" : "center"),
326 gravity & WLC_BIT_GRAVITY_TOP ? "top" :
327 (gravity & WLC_BIT_GRAVITY_BOTTOM ? "bottom" : "middle"),
328 gravity & WLC_BIT_GRAVITY_LEFT ? "left" :
329 (gravity & WLC_BIT_GRAVITY_RIGHT ? "right" : "center"));
330
331 wlc_handle parent = wlc_view_get_parent(handle);
332 if (parent) {
333 const struct wlc_geometry *pg = wlc_view_get_geometry(parent);
334 geo.origin.x += pg->origin.x;
335 geo.origin.y += pg->origin.y;
336 }
337 wlc_view_set_geometry(handle, 0, &geo);
338}
339
340static bool handle_view_created(wlc_handle handle) {
341 // if view is child of another view, the use that as focused container
342 wlc_handle parent = wlc_view_get_parent(handle);
343 swayc_t *focused = NULL;
344 swayc_t *newview = NULL;
345 swayc_t *current_ws = swayc_active_workspace();
346 bool return_to_workspace = false;
347 struct wl_client *client = wlc_view_get_wl_client(handle);
348 struct wl_resource *resource = wlc_surface_get_wl_resource(
349 wlc_view_get_surface(handle));
350 pid_t pid;
351 struct panel_config *panel_config = NULL;
352 struct background_config *background_config = NULL;
353
354 panel_config = if_panel_find_config(client);
355 if (panel_config) {
356 panel_config->handle = handle;
357 update_panel_geometry(panel_config);
358 wlc_view_set_mask(handle, VISIBLE);
359 wlc_view_set_output(handle, panel_config->output);
360 wlc_view_bring_to_front(handle);
361 arrange_windows(&root_container, -1, -1);
362 return true;
363 }
364
365 background_config = if_background_find_config(client);
366 if (background_config) {
367 background_config->handle = handle;
368 update_background_geometry(background_config);
369 wlc_view_set_mask(handle, VISIBLE);
370 wlc_view_set_output(handle, background_config->output);
371 wlc_view_send_to_back(handle);
372 return true;
373 }
374
375 // Get parent container, to add view in
376 if (parent) {
377 focused = swayc_by_handle(parent);
378 }
379
380 if (client) {
381 pid = wlc_view_get_pid(handle);
382
383 if (pid) {
384 // using newview as a temp storage location here,
385 // rather than adding yet another workspace var
386 newview = workspace_for_pid(pid);
387 if (newview) {
388 focused = get_focused_container(newview);
389 return_to_workspace = true;
390 }
391 newview = NULL;
392 }
393 }
394
395 swayc_t *prev_focus = get_focused_container(&root_container);
396
397 if (!focused || focused->type == C_OUTPUT) {
398 focused = prev_focus;
399 // Move focus from floating view
400 if (focused->is_floating) {
401 // To workspace if there are no children
402 if (focused->parent->children->length == 0) {
403 focused = focused->parent;
404 }
405 // TODO find a better way of doing this
406 // Or to focused container
407 else {
408 focused = get_focused_container(focused->parent->children->items[0]);
409 }
410 }
411 }
412
413 positioner_place_window(handle);
414
415 sway_log(L_DEBUG, "handle:%" PRIuPTR " type:%x state:%x parent:%" PRIuPTR " "
416 "mask:%d (x:%d y:%d w:%d h:%d) title:%s "
417 "class:%s appid:%s",
418 handle, wlc_view_get_type(handle), wlc_view_get_state(handle), parent,
419 wlc_view_get_mask(handle), wlc_view_get_geometry(handle)->origin.x,
420 wlc_view_get_geometry(handle)->origin.y,wlc_view_get_geometry(handle)->size.w,
421 wlc_view_get_geometry(handle)->size.h, wlc_view_get_title(handle),
422 wlc_view_get_class(handle), wlc_view_get_app_id(handle));
423
424 // TODO properly figure out how each window should be handled.
425 switch (wlc_view_get_type(handle)) {
426 // regular view created regularly
427 case 0:
428 if (parent) {
429 newview = new_floating_view(handle);
430 } else {
431 newview = new_view(focused, handle);
432 wlc_view_set_state(handle, WLC_BIT_MAXIMIZED, true);
433 }
434 break;
435
436 // Dmenu keeps viewfocus, but others with this flag don't, for now simulate
437 // dmenu
438 case WLC_BIT_OVERRIDE_REDIRECT:
439 wlc_view_focus(handle);
440 wlc_view_set_state(handle, WLC_BIT_ACTIVATED, true);
441 wlc_view_bring_to_front(handle);
442 break;
443
444 // Firefox popups have this flag set.
445 case WLC_BIT_OVERRIDE_REDIRECT|WLC_BIT_UNMANAGED:
446 wlc_view_bring_to_front(handle);
447 locked_container_focus = true;
448 break;
449
450 // Modals, get focus, popups do not
451 case WLC_BIT_MODAL:
452 wlc_view_focus(handle);
453 wlc_view_bring_to_front(handle);
454 newview = new_floating_view(handle);
455 /* fallthrough */
456 case WLC_BIT_POPUP:
457 wlc_view_bring_to_front(handle);
458 break;
459 }
460
461 // Prevent current ws from being destroyed, if empty
462 suspend_workspace_cleanup = true;
463
464 if (newview) {
465 ipc_event_window(newview, "new");
466 set_focused_container(newview);
467 wlc_view_set_mask(handle, VISIBLE);
468 swayc_t *output = swayc_parent_by_type(newview, C_OUTPUT);
469 arrange_windows(output, -1, -1);
470 // check if it matches for_window in config and execute if so
471 list_t *criteria = criteria_for(newview);
472 for (int i = 0; i < criteria->length; i++) {
473 struct criteria *crit = criteria->items[i];
474 sway_log(L_DEBUG, "for_window '%s' matches new view %p, cmd: '%s'",
475 crit->crit_raw, newview, crit->cmdlist);
476 struct cmd_results *res = handle_command(crit->cmdlist, CONTEXT_CRITERIA);
477 if (res->status != CMD_SUCCESS) {
478 sway_log(L_ERROR, "Command '%s' failed: %s", res->input, res->error);
479 }
480 free_cmd_results(res);
481 // view must be focused for commands to affect it, so always
482 // refocus in-between command lists
483 set_focused_container(newview);
484 }
485 swayc_t *workspace = swayc_parent_by_type(focused, C_WORKSPACE);
486 if (workspace && workspace->fullscreen) {
487 set_focused_container(workspace->fullscreen);
488 }
489 for (int i = 0; i < decoration_state.csd_resources->length; ++i) {
490 struct wl_resource *res = decoration_state.csd_resources->items[i];
491 if (res == resource) {
492 list_del(decoration_state.csd_resources, i);
493 server_decoration_enable_csd(handle);
494 break;
495 }
496 }
497 } else {
498 swayc_t *output = swayc_parent_by_type(focused, C_OUTPUT);
499 wlc_handle *h = malloc(sizeof(wlc_handle));
500 if (!h) {
501 sway_log(L_ERROR, "Unable to allocate window handle, view handler bailing out");
502 return true;
503 }
504 *h = handle;
505 sway_log(L_DEBUG, "Adding unmanaged window %p to %p", h, output->unmanaged);
506 list_add(output->unmanaged, h);
507 wlc_view_set_mask(handle, VISIBLE);
508 }
509
510 if (return_to_workspace && current_ws != swayc_active_workspace()) {
511 // we were on one workspace, switched to another to add this view,
512 // now let's return to where we were
513 workspace_switch(current_ws);
514 set_focused_container(get_focused_container(current_ws));
515 }
516 if (prev_focus && prev_focus->type == C_VIEW
517 && newview && criteria_any(newview, config->no_focus)) {
518 // Restore focus
519 swayc_t *ws = swayc_parent_by_type(newview, C_WORKSPACE);
520 if (!ws || ws != newview->parent
521 || ws->children->length + ws->floating->length != 1) {
522 sway_log(L_DEBUG, "no_focus: restoring focus to %s", prev_focus->name);
523 set_focused_container(prev_focus);
524 }
525 }
526
527 suspend_workspace_cleanup = false;
528 ws_cleanup();
529 return true;
530}
531
532static void handle_view_destroyed(wlc_handle handle) {
533 sway_log(L_DEBUG, "Destroying window %" PRIuPTR, handle);
534 swayc_t *view = swayc_by_handle(handle);
535
536 // destroy views by type
537 switch (wlc_view_get_type(handle)) {
538 // regular view created regularly
539 case 0:
540 case WLC_BIT_MODAL:
541 case WLC_BIT_POPUP:
542 break;
543 // DMENU has this flag, and takes view_focus, but other things with this
544 // flag don't
545 case WLC_BIT_OVERRIDE_REDIRECT:
546 break;
547 case WLC_BIT_OVERRIDE_REDIRECT|WLC_BIT_UNMANAGED:
548 locked_container_focus = false;
549 break;
550 }
551
552 if (view) {
553 bool fullscreen = swayc_is_fullscreen(view);
554 remove_view_from_scratchpad(view);
555 swayc_t *parent = destroy_view(view), *iter = NULL;
556 if (parent) {
557 ipc_event_window(parent, "close");
558
559 // Destroy empty workspaces
560 if (parent->type == C_WORKSPACE &&
561 parent->children->length == 0 &&
562 parent->floating->length == 0 &&
563 swayc_active_workspace() != parent &&
564 !parent->visible) {
565 parent = destroy_workspace(parent);
566 }
567
568 if (fullscreen) {
569 iter = parent;
570 while (iter) {
571 if (iter->fullscreen) {
572 iter->fullscreen = NULL;
573 break;
574 }
575 iter = iter->parent;
576 }
577 }
578
579
580 arrange_windows(iter ? iter : parent, -1, -1);
581 }
582 } else {
583 // Is it unmanaged?
584 int i;
585 for (i = 0; i < root_container.children->length; ++i) {
586 swayc_t *output = root_container.children->items[i];
587 int j;
588 for (j = 0; j < output->unmanaged->length; ++j) {
589 wlc_handle *_handle = output->unmanaged->items[j];
590 if (*_handle == handle) {
591 list_del(output->unmanaged, j);
592 free(_handle);
593 break;
594 }
595 }
596 }
597 // Is it in the scratchpad?
598 for (i = 0; i < scratchpad->length; ++i) {
599 swayc_t *item = scratchpad->items[i];
600 if (item->handle == handle) {
601 list_del(scratchpad, i);
602 destroy_view(item);
603 break;
604 }
605 }
606 }
607 set_focused_container(get_focused_view(&root_container));
608}
609
610static void handle_view_focus(wlc_handle view, bool focus) {
611 return;
612}
613
614static void handle_view_geometry_request(wlc_handle handle, const struct wlc_geometry *geometry) {
615 sway_log(L_DEBUG, "geometry request for %" PRIuPTR " %dx%d @ %d,%d", handle,
616 geometry->size.w, geometry->size.h, geometry->origin.x, geometry->origin.y);
617 // If the view is floating, then apply the geometry.
618 // Otherwise save the desired width/height for the view.
619 // This will not do anything for the time being as WLC improperly sends geometry requests
620 swayc_t *view = swayc_by_handle(handle);
621 if (view) {
622 view->desired_width = geometry->size.w;
623 view->desired_height = geometry->size.h;
624
625 if (view->is_floating) {
626 floating_view_sane_size(view);
627 view->width = view->desired_width;
628 view->height = view->desired_height;
629 view->x = geometry->origin.x;
630 view->y = geometry->origin.y;
631 update_geometry(view);
632 }
633 } else {
634 wlc_view_set_geometry(handle, 0, geometry);
635 }
636}
637
638static void handle_view_state_request(wlc_handle view, enum wlc_view_state_bit state, bool toggle) {
639 swayc_t *c = swayc_by_handle(view);
640 pid_t pid = wlc_view_get_pid(view);
641 switch (state) {
642 case WLC_BIT_FULLSCREEN:
643 if (!(get_feature_policy_mask(pid) & FEATURE_FULLSCREEN)) {
644 sway_log(L_INFO, "Denying fullscreen to %d (%s)", pid, c->name);
645 break;
646 }
647 // i3 just lets it become fullscreen
648 wlc_view_set_state(view, state, toggle);
649 if (c) {
650 sway_log(L_DEBUG, "setting view %" PRIuPTR " %s, fullscreen %d", view, c->name, toggle);
651 arrange_windows(c->parent, -1, -1);
652 // Set it as focused window for that workspace if its going fullscreen
653 swayc_t *ws = swayc_parent_by_type(c, C_WORKSPACE);
654 if (toggle) {
655 // Set ws focus to c
656 set_focused_container_for(ws, c);
657 ws->fullscreen = c;
658 } else {
659 ws->fullscreen = NULL;
660 }
661 }
662 break;
663 case WLC_BIT_MAXIMIZED:
664 case WLC_BIT_RESIZING:
665 case WLC_BIT_MOVING:
666 break;
667 case WLC_BIT_ACTIVATED:
668 sway_log(L_DEBUG, "View %p requested to be activated", c);
669 break;
670 }
671 return;
672}
673
674static void handle_view_properties_updated(wlc_handle view, uint32_t mask) {
675 if (mask == WLC_BIT_PROPERTY_TITLE) {
676 swayc_t *c = swayc_by_handle(view);
677 if (!c) {
678 return;
679 }
680
681 // update window title
682 const char *new_name = wlc_view_get_title(view);
683
684 if (new_name) {
685 if (!c->name || strcmp(c->name, new_name) != 0) {
686 free(c->name);
687 c->name = strdup(new_name);
688 swayc_t *p = swayc_tabbed_stacked_ancestor(c);
689 if (p) {
690 // TODO: we only got the topmost tabbed/stacked container, update borders of all containers on the path
691 update_container_border(get_focused_view(p));
692 } else if (c->border_type == B_NORMAL) {
693 update_container_border(c);
694 }
695 ipc_event_window(c, "title");
696 }
697 }
698 }
699}
700
701static void handle_binding_command(struct sway_binding *binding) {
702 struct sway_binding *binding_copy = binding;
703 bool reload = false;
704 // if this is a reload command we need to make a duplicate of the
705 // binding since it will be gone after the reload has completed.
706 if (strcasecmp(binding->command, "reload") == 0) {
707 binding_copy = sway_binding_dup(binding);
708 if (!binding_copy) {
709 sway_log(L_ERROR, "Unable to duplicate binding during reload");
710 return;
711 }
712 reload = true;
713 }
714
715 struct cmd_results *res = handle_command(binding->command, CONTEXT_BINDING);
716 if (res->status != CMD_SUCCESS) {
717 sway_log(L_ERROR, "Command '%s' failed: %s", res->input, res->error);
718 }
719 ipc_event_binding_keyboard(binding_copy);
720
721 if (reload) { // free the binding if we made a copy
722 free_sway_binding(binding_copy);
723 }
724
725 free_cmd_results(res);
726}
727
728static bool handle_bindsym(struct sway_binding *binding, uint32_t keysym, uint32_t keycode) {
729 int i;
730 for (i = 0; i < binding->keys->length; ++i) {
731 if (binding->bindcode) {
732 xkb_keycode_t *key = binding->keys->items[i];
733 if (keycode == *key) {
734 handle_binding_command(binding);
735 return true;
736 }
737 } else {
738 xkb_keysym_t *key = binding->keys->items[i];
739 if (keysym == *key) {
740 handle_binding_command(binding);
741 return true;
742 }
743 }
744 }
745
746 return false;
747}
748
749static bool valid_bindsym(struct sway_binding *binding) {
750 bool match = false;
751 int i;
752 for (i = 0; i < binding->keys->length; ++i) {
753 if (binding->bindcode) {
754 xkb_keycode_t *key = binding->keys->items[i];
755 if ((match = check_key(0, *key)) == false) {
756 break;
757 }
758 } else {
759 xkb_keysym_t *key = binding->keys->items[i];
760 if ((match = check_key(*key, 0)) == false) {
761 break;
762 }
763 }
764 }
765
766 return match;
767}
768
769static bool handle_bindsym_release(struct sway_binding *binding) {
770 if (binding->keys->length == 1) {
771 xkb_keysym_t *key = binding->keys->items[0];
772 if (check_released_key(*key)) {
773 handle_binding_command(binding);
774 return true;
775 }
776 }
777
778 return false;
779}
780
781static bool handle_key(wlc_handle view, uint32_t time, const struct wlc_modifiers *modifiers,
782 uint32_t key, enum wlc_key_state state) {
783
784 if (desktop_shell.is_locked) {
785 return EVENT_PASSTHROUGH;
786 }
787
788 // reset pointer mode on keypress
789 if (state == WLC_KEY_STATE_PRESSED && pointer_state.mode) {
790 pointer_mode_reset();
791 }
792
793 struct sway_mode *mode = config->current_mode;
794
795 struct wlc_modifiers no_mods = { 0, 0 };
796 uint32_t sym = tolower(wlc_keyboard_get_keysym_for_key(key, &no_mods));
797
798 int i;
799
800 if (state == WLC_KEY_STATE_PRESSED) {
801 press_key(sym, key);
802 } else { // WLC_KEY_STATE_RELEASED
803 release_key(sym, key);
804 }
805
806 // handle bar modifiers pressed/released
807 uint32_t modifier;
808 for (i = 0; i < config->active_bar_modifiers->length; ++i) {
809 modifier = *(uint32_t *)config->active_bar_modifiers->items[i];
810
811 switch (modifier_state_changed(modifiers->mods, modifier)) {
812 case MOD_STATE_PRESSED:
813 ipc_event_modifier(modifier, "pressed");
814 break;
815 case MOD_STATE_RELEASED:
816 ipc_event_modifier(modifier, "released");
817 break;
818 }
819 }
820 // update modifiers state
821 modifiers_state_update(modifiers->mods);
822
823 // handle bindings
824 list_t *candidates = create_list();
825 for (i = 0; i < mode->bindings->length; ++i) {
826 struct sway_binding *binding = mode->bindings->items[i];
827 if ((modifiers->mods ^ binding->modifiers) == 0) {
828 switch (state) {
829 case WLC_KEY_STATE_PRESSED:
830 if (!binding->release && valid_bindsym(binding)) {
831 list_add(candidates, binding);
832 }
833 break;
834 case WLC_KEY_STATE_RELEASED:
835 if (binding->release && handle_bindsym_release(binding)) {
836 list_free(candidates);
837 return EVENT_HANDLED;
838 }
839 break;
840 }
841 }
842 }
843
844 for (i = 0; i < candidates->length; ++i) {
845 struct sway_binding *binding = candidates->items[i];
846 if (state == WLC_KEY_STATE_PRESSED) {
847 if (!binding->release && handle_bindsym(binding, sym, key)) {
848 list_free(candidates);
849 return EVENT_HANDLED;
850 }
851 }
852 }
853
854 list_free(candidates);
855
856 swayc_t *focused = get_focused_container(&root_container);
857 if (focused->type == C_VIEW) {
858 pid_t pid = wlc_view_get_pid(focused->handle);
859 if (!(get_feature_policy_mask(pid) & FEATURE_KEYBOARD)) {
860 return EVENT_HANDLED;
861 }
862 }
863 return EVENT_PASSTHROUGH;
864}
865
866static bool handle_pointer_motion(wlc_handle handle, uint32_t time, double x, double y) {
867 if (desktop_shell.is_locked) {
868 return EVENT_PASSTHROUGH;
869 }
870
871 double new_x = x;
872 double new_y = y;
873 // Switch to adjacent output if touching output edge.
874 //
875 // Since this doesn't currently support moving windows between outputs we
876 // don't do the switch if the pointer is in a mode.
877 if (config->seamless_mouse && !pointer_state.mode &&
878 !pointer_state.left.held && !pointer_state.right.held && !pointer_state.scroll.held) {
879
880 swayc_t *output = swayc_active_output(), *adjacent = NULL;
881 struct wlc_point abs_pos = { .x = x + output->x, .y = y + output->y };
882 if (x <= 0) { // Left edge
883 if ((adjacent = swayc_adjacent_output(output, MOVE_LEFT, &abs_pos, false))) {
884 if (workspace_switch(swayc_active_workspace_for(adjacent))) {
885 new_x = adjacent->width;
886 // adjust for differently aligned outputs (well, this is
887 // only correct when the two outputs have the same
888 // resolution or the same dpi I guess, it should take
889 // physical attributes into account)
890 new_y += (output->y - adjacent->y);
891 }
892 }
893 } else if (x >= output->width) { // Right edge
894 if ((adjacent = swayc_adjacent_output(output, MOVE_RIGHT, &abs_pos, false))) {
895 if (workspace_switch(swayc_active_workspace_for(adjacent))) {
896 new_x = 0;
897 new_y += (output->y - adjacent->y);
898 }
899 }
900 } else if (y <= 0) { // Top edge
901 if ((adjacent = swayc_adjacent_output(output, MOVE_UP, &abs_pos, false))) {
902 if (workspace_switch(swayc_active_workspace_for(adjacent))) {
903 new_y = adjacent->height;
904 new_x += (output->x - adjacent->x);
905 }
906 }
907 } else if (y >= output->height) { // Bottom edge
908 if ((adjacent = swayc_adjacent_output(output, MOVE_DOWN, &abs_pos, false))) {
909 if (workspace_switch(swayc_active_workspace_for(adjacent))) {
910 new_y = 0;
911 new_x += (output->x - adjacent->x);
912 }
913 }
914 }
915 }
916
917 pointer_position_set(new_x, new_y, false);
918
919 swayc_t *focused = get_focused_container(&root_container);
920 if (focused->type == C_VIEW) {
921 pid_t pid = wlc_view_get_pid(focused->handle);
922 if (!(get_feature_policy_mask(pid) & FEATURE_MOUSE)) {
923 return EVENT_HANDLED;
924 }
925 }
926
927 return EVENT_PASSTHROUGH;
928}
929
930static bool swayc_border_check(swayc_t *c, const void *_origin) {
931 const struct wlc_point *origin = _origin;
932 const struct wlc_geometry title_bar = c->title_bar_geometry;
933
934 if (c->border_type != B_NORMAL) {
935 return false;
936 }
937
938 if (origin->x >= title_bar.origin.x && origin->y >= title_bar.origin.y
939 && origin->x < title_bar.origin.x + (int32_t)title_bar.size.w
940 && origin->y < title_bar.origin.y + (int32_t)title_bar.size.h) {
941 return true;
942 }
943 return false;
944}
945
946static bool handle_pointer_button(wlc_handle view, uint32_t time, const struct wlc_modifiers *modifiers,
947 uint32_t button, enum wlc_button_state state, const struct wlc_point *origin) {
948
949 // Update view pointer is on
950 pointer_state.view = container_under_pointer();
951
952 struct sway_mode *mode = config->current_mode;
953 // handle bindings
954 for (int i = 0; i < mode->bindings->length; ++i) {
955 struct sway_binding *binding = mode->bindings->items[i];
956 if ((modifiers->mods ^ binding->modifiers) == 0) {
957 switch (state) {
958 case WLC_BUTTON_STATE_PRESSED:
959 if (!binding->release && handle_bindsym(binding, button, 0)) {
960 return EVENT_HANDLED;
961 }
962 break;
963 case WLC_BUTTON_STATE_RELEASED:
964 if (binding->release && handle_bindsym(binding, button, 0)) {
965 return EVENT_HANDLED;
966 }
967 break;
968 }
969 }
970 }
971
972 // Update pointer_state
973 switch (button) {
974 case M_LEFT_CLICK:
975 if (state == WLC_BUTTON_STATE_PRESSED) {
976 pointer_state.left.held = true;
977 pointer_state.left.x = origin->x;
978 pointer_state.left.y = origin->y;
979 pointer_state.left.view = pointer_state.view;
980 } else {
981 pointer_state.left.held = false;
982 }
983 break;
984
985 case M_RIGHT_CLICK:
986 if (state == WLC_BUTTON_STATE_PRESSED) {
987 pointer_state.right.held = true;
988 pointer_state.right.x = origin->x;
989 pointer_state.right.y = origin->y;
990 pointer_state.right.view = pointer_state.view;
991 } else {
992 pointer_state.right.held = false;
993 }
994 break;
995
996 case M_SCROLL_CLICK:
997 if (state == WLC_BUTTON_STATE_PRESSED) {
998 pointer_state.scroll.held = true;
999 pointer_state.scroll.x = origin->x;
1000 pointer_state.scroll.y = origin->y;
1001 pointer_state.scroll.view = pointer_state.view;
1002 } else {
1003 pointer_state.scroll.held = false;
1004 }
1005 break;
1006
1007 //TODO scrolling behavior
1008 case M_SCROLL_UP:
1009 case M_SCROLL_DOWN:
1010 break;
1011 }
1012
1013 // get focused window and check if to change focus on mouse click
1014 swayc_t *focused = get_focused_container(&root_container);
1015
1016 // don't change focus or mode if fullscreen
1017 if (swayc_is_fullscreen(focused)) {
1018 if (focused->type == C_VIEW) {
1019 pid_t pid = wlc_view_get_pid(focused->handle);
1020 if (!(get_feature_policy_mask(pid) & FEATURE_MOUSE)) {
1021 return EVENT_HANDLED;
1022 }
1023 }
1024 return EVENT_PASSTHROUGH;
1025 }
1026
1027 // set pointer mode only if floating mod has been set
1028 if (config->floating_mod) {
1029 pointer_mode_set(button, !(modifiers->mods ^ config->floating_mod));
1030 }
1031
1032 // Check whether to change focus
1033 swayc_t *pointer = pointer_state.view;
1034 if (pointer) {
1035 swayc_t *ws = swayc_parent_by_type(focused, C_WORKSPACE);
1036 if (ws != NULL) {
1037 swayc_t *find = container_find(ws, &swayc_border_check, origin);
1038 if (find != NULL) {
1039 set_focused_container(find);
1040 return EVENT_HANDLED;
1041 }
1042 }
1043
1044 if (focused != pointer) {
1045 set_focused_container(pointer_state.view);
1046 }
1047 // Send to front if floating
1048 if (pointer->is_floating) {
1049 int i;
1050 for (i = 0; i < pointer->parent->floating->length; i++) {
1051 if (pointer->parent->floating->items[i] == pointer) {
1052 list_del(pointer->parent->floating, i);
1053 list_add(pointer->parent->floating, pointer);
1054 break;
1055 }
1056 }
1057 wlc_view_bring_to_front(pointer->handle);
1058 }
1059 }
1060
1061 // Return if mode has been set
1062 if (pointer_state.mode) {
1063 return EVENT_HANDLED;
1064 }
1065
1066 if (focused->type == C_VIEW) {
1067 pid_t pid = wlc_view_get_pid(focused->handle);
1068 if (!(get_feature_policy_mask(pid) & FEATURE_MOUSE)) {
1069 return EVENT_HANDLED;
1070 }
1071 }
1072
1073 // Always send mouse release
1074 if (state == WLC_BUTTON_STATE_RELEASED) {
1075 return EVENT_PASSTHROUGH;
1076 }
1077
1078 // Finally send click
1079 return EVENT_PASSTHROUGH;
1080}
1081
1082bool handle_pointer_scroll(wlc_handle view, uint32_t time, const struct wlc_modifiers* modifiers,
1083 uint8_t axis_bits, double _amount[2]) {
1084 if (!(modifiers->mods ^ config->floating_mod)) {
1085 int x_amount = (int)_amount[0];
1086 int y_amount = (int)_amount[1];
1087
1088 if (x_amount > 0 && strcmp(config->floating_scroll_up_cmd, "")) {
1089 handle_command(config->floating_scroll_up_cmd, CONTEXT_BINDING);
1090 return EVENT_HANDLED;
1091 } else if (x_amount < 0 && strcmp(config->floating_scroll_down_cmd, "")) {
1092 handle_command(config->floating_scroll_down_cmd, CONTEXT_BINDING);
1093 return EVENT_HANDLED;
1094 }
1095
1096 if (y_amount > 0 && strcmp(config->floating_scroll_right_cmd, "")) {
1097 handle_command(config->floating_scroll_right_cmd, CONTEXT_BINDING);
1098 return EVENT_HANDLED;
1099 } else if (y_amount < 0 && strcmp(config->floating_scroll_left_cmd, "")) {
1100 handle_command(config->floating_scroll_left_cmd, CONTEXT_BINDING);
1101 return EVENT_HANDLED;
1102 }
1103 }
1104 return EVENT_PASSTHROUGH;
1105}
1106
1107static void handle_wlc_ready(void) {
1108 sway_log(L_DEBUG, "Compositor is ready, executing cmds in queue");
1109 // Execute commands until there are none left
1110 config->active = true;
1111 while (config->cmd_queue->length) {
1112 char *line = config->cmd_queue->items[0];
1113 struct cmd_results *res = handle_command(line, CONTEXT_CONFIG);
1114 if (res->status != CMD_SUCCESS) {
1115 sway_log(L_ERROR, "Error on line '%s': %s", line, res->error);
1116 }
1117 free_cmd_results(res);
1118 free(line);
1119 list_del(config->cmd_queue, 0);
1120 }
1121}
1122
1123void register_wlc_handlers() {
1124 wlc_set_output_created_cb(handle_output_created);
1125 wlc_set_output_destroyed_cb(handle_output_destroyed);
1126 wlc_set_output_resolution_cb(handle_output_resolution_change);
1127 wlc_set_output_focus_cb(handle_output_focused);
1128 wlc_set_output_render_post_cb(handle_output_post_render);
1129 wlc_set_view_created_cb(handle_view_created);
1130 wlc_set_view_destroyed_cb(handle_view_destroyed);
1131 wlc_set_view_focus_cb(handle_view_focus);
1132 wlc_set_view_render_pre_cb(handle_view_pre_render);
1133 wlc_set_view_request_geometry_cb(handle_view_geometry_request);
1134 wlc_set_view_request_state_cb(handle_view_state_request);
1135 wlc_set_view_properties_updated_cb(handle_view_properties_updated);
1136 wlc_set_keyboard_key_cb(handle_key);
1137 wlc_set_pointer_motion_cb_v2(handle_pointer_motion);
1138 wlc_set_pointer_button_cb(handle_pointer_button);
1139 wlc_set_pointer_scroll_cb(handle_pointer_scroll);
1140 wlc_set_compositor_ready_cb(handle_wlc_ready);
1141 wlc_set_input_created_cb(handle_input_created);
1142 wlc_set_input_destroyed_cb(handle_input_destroyed);
1143}
diff --git a/sway/input.c b/sway/input.c
deleted file mode 100644
index 6263f79f..00000000
--- a/sway/input.c
+++ /dev/null
@@ -1,69 +0,0 @@
1#define _XOPEN_SOURCE 700
2#include <ctype.h>
3#include <float.h>
4#include <limits.h>
5#include <stdio.h>
6#include <string.h>
7#include <libinput.h>
8#include "sway/config.h"
9#include "sway/input.h"
10#include "list.h"
11#include "log.h"
12
13struct input_config *new_input_config(const char* identifier) {
14 struct input_config *input = calloc(1, sizeof(struct input_config));
15 if (!input) {
16 sway_log(L_DEBUG, "Unable to allocate input config");
17 return NULL;
18 }
19 sway_log(L_DEBUG, "new_input_config(%s)", identifier);
20 if (!(input->identifier = strdup(identifier))) {
21 free(input);
22 sway_log(L_DEBUG, "Unable to allocate input config");
23 return NULL;
24 }
25
26 input->tap = INT_MIN;
27 input->drag_lock = INT_MIN;
28 input->dwt = INT_MIN;
29 input->send_events = INT_MIN;
30 input->click_method = INT_MIN;
31 input->middle_emulation = INT_MIN;
32 input->natural_scroll = INT_MIN;
33 input->accel_profile = INT_MIN;
34 input->pointer_accel = FLT_MIN;
35 input->scroll_method = INT_MIN;
36 input->left_handed = INT_MIN;
37
38 return input;
39}
40
41char *libinput_dev_unique_id(struct libinput_device *device) {
42 int vendor = libinput_device_get_id_vendor(device);
43 int product = libinput_device_get_id_product(device);
44 char *name = strdup(libinput_device_get_name(device));
45
46 char *p = name;
47 for (; *p; ++p) {
48 if (*p == ' ') {
49 *p = '_';
50 }
51 }
52
53 sway_log(L_DEBUG, "rewritten name %s", name);
54
55 int len = strlen(name) + sizeof(char) * 6;
56 char *identifier = malloc(len);
57 if (!identifier) {
58 sway_log(L_ERROR, "Unable to allocate unique input device name");
59 return NULL;
60 }
61
62 const char *fmt = "%d:%d:%s";
63 snprintf(identifier, len, fmt, vendor, product, name);
64 free(name);
65 return identifier;
66}
67
68list_t *input_devices = NULL;
69struct input_config *current_input_config = NULL;
diff --git a/sway/input/cursor.c b/sway/input/cursor.c
new file mode 100644
index 00000000..15a61cbf
--- /dev/null
+++ b/sway/input/cursor.c
@@ -0,0 +1,384 @@
1#define _XOPEN_SOURCE 700
2#ifdef __linux__
3#include <linux/input-event-codes.h>
4#elif __FreeBSD__
5#include <dev/evdev/input-event-codes.h>
6#endif
7#include <wlr/types/wlr_cursor.h>
8#include <wlr/types/wlr_xcursor_manager.h>
9#include "list.h"
10#include "log.h"
11#include "sway/input/cursor.h"
12#include "sway/layers.h"
13#include "sway/output.h"
14#include "sway/tree/view.h"
15#include "wlr-layer-shell-unstable-v1-protocol.h"
16
17static struct wlr_surface *layer_surface_at(struct sway_output *output,
18 struct wl_list *layer, double ox, double oy, double *sx, double *sy) {
19 struct sway_layer_surface *sway_layer;
20 wl_list_for_each_reverse(sway_layer, layer, link) {
21 struct wlr_surface *wlr_surface =
22 sway_layer->layer_surface->surface;
23 double _sx = ox - sway_layer->geo.x;
24 double _sy = oy - sway_layer->geo.y;
25 // TODO: Test popups/subsurfaces
26 if (wlr_surface_point_accepts_input(wlr_surface, _sx, _sy)) {
27 *sx = _sx;
28 *sy = _sy;
29 return wlr_surface;
30 }
31 }
32 return NULL;
33}
34
35/**
36 * Returns the container at the cursor's position. If there is a surface at that
37 * location, it is stored in **surface (it may not be a view).
38 */
39static struct sway_container *container_at_cursor(struct sway_cursor *cursor,
40 struct wlr_surface **surface, double *sx, double *sy) {
41 // check for unmanaged views first
42 struct wl_list *unmanaged = &root_container.sway_root->xwayland_unmanaged;
43 struct sway_xwayland_unmanaged *unmanaged_surface;
44 wl_list_for_each_reverse(unmanaged_surface, unmanaged, link) {
45 struct wlr_xwayland_surface *xsurface =
46 unmanaged_surface->wlr_xwayland_surface;
47
48 double _sx = cursor->cursor->x - unmanaged_surface->lx;
49 double _sy = cursor->cursor->y - unmanaged_surface->ly;
50 if (wlr_surface_point_accepts_input(xsurface->surface, _sx, _sy)) {
51 *surface = xsurface->surface;
52 *sx = _sx;
53 *sy = _sy;
54 return NULL;
55 }
56 }
57
58 // find the output the cursor is on
59 struct wlr_output_layout *output_layout =
60 root_container.sway_root->output_layout;
61 struct wlr_output *wlr_output =
62 wlr_output_layout_output_at(output_layout,
63 cursor->cursor->x, cursor->cursor->y);
64 if (wlr_output == NULL) {
65 return NULL;
66 }
67 struct sway_output *output = wlr_output->data;
68 double ox = cursor->cursor->x, oy = cursor->cursor->y;
69 wlr_output_layout_output_coords(output_layout, wlr_output, &ox, &oy);
70
71 // find the focused workspace on the output for this seat
72 struct sway_container *ws =
73 seat_get_focus_inactive(cursor->seat, output->swayc);
74 if (ws && ws->type != C_WORKSPACE) {
75 ws = container_parent(ws, C_WORKSPACE);
76 }
77 if (!ws) {
78 return output->swayc;
79 }
80
81 if ((*surface = layer_surface_at(output,
82 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY],
83 ox, oy, sx, sy))) {
84 return ws;
85 }
86 if ((*surface = layer_surface_at(output,
87 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP],
88 ox, oy, sx, sy))) {
89 return ws;
90 }
91
92 struct sway_container *c;
93 if ((c = container_at(ws, cursor->cursor->x, cursor->cursor->y,
94 surface, sx, sy))) {
95 return c;
96 }
97
98 if ((*surface = layer_surface_at(output,
99 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM],
100 ox, oy, sx, sy))) {
101 return ws;
102 }
103 if ((*surface = layer_surface_at(output,
104 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND],
105 ox, oy, sx, sy))) {
106 return ws;
107 }
108
109 c = seat_get_focus_inactive(cursor->seat, output->swayc);
110 if (c) {
111 return c;
112 }
113 if (!c && output->swayc->children->length) {
114 c = output->swayc->children->items[0];
115 return c;
116 }
117
118 return output->swayc;
119}
120
121void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time) {
122 struct wlr_seat *seat = cursor->seat->wlr_seat;
123 struct wlr_surface *surface = NULL;
124 double sx, sy;
125 struct sway_container *c = container_at_cursor(cursor, &surface, &sx, &sy);
126 if (c && config->focus_follows_mouse) {
127 seat_set_focus_warp(cursor->seat, c, false);
128 }
129
130 // reset cursor if switching between clients
131 struct wl_client *client = NULL;
132 if (surface != NULL) {
133 client = wl_resource_get_client(surface->resource);
134 }
135 if (client != cursor->image_client) {
136 wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager,
137 "left_ptr", cursor->cursor);
138 cursor->image_client = client;
139 }
140
141 // send pointer enter/leave
142 if (surface != NULL) {
143 if (seat_is_input_allowed(cursor->seat, surface)) {
144 wlr_seat_pointer_notify_enter(seat, surface, sx, sy);
145 wlr_seat_pointer_notify_motion(seat, time, sx, sy);
146 }
147 } else {
148 wlr_seat_pointer_clear_focus(seat);
149 }
150}
151
152static void handle_cursor_motion(struct wl_listener *listener, void *data) {
153 struct sway_cursor *cursor = wl_container_of(listener, cursor, motion);
154 struct wlr_event_pointer_motion *event = data;
155 wlr_cursor_move(cursor->cursor, event->device,
156 event->delta_x, event->delta_y);
157 cursor_send_pointer_motion(cursor, event->time_msec);
158}
159
160static void handle_cursor_motion_absolute(
161 struct wl_listener *listener, void *data) {
162 struct sway_cursor *cursor =
163 wl_container_of(listener, cursor, motion_absolute);
164 struct wlr_event_pointer_motion_absolute *event = data;
165 wlr_cursor_warp_absolute(cursor->cursor, event->device, event->x, event->y);
166 cursor_send_pointer_motion(cursor, event->time_msec);
167}
168
169void dispatch_cursor_button(struct sway_cursor *cursor,
170 uint32_t time_msec, uint32_t button, enum wlr_button_state state) {
171 struct wlr_surface *surface = NULL;
172 double sx, sy;
173 struct sway_container *cont =
174 container_at_cursor(cursor, &surface, &sx, &sy);
175 if (surface && wlr_surface_is_layer_surface(surface)) {
176 struct wlr_layer_surface *layer =
177 wlr_layer_surface_from_wlr_surface(surface);
178 if (layer->current.keyboard_interactive) {
179 seat_set_focus_layer(cursor->seat, layer);
180 return;
181 }
182 }
183 // Avoid moving keyboard focus from a surface that accepts it to one
184 // that does not unless the change would move us to a new workspace.
185 //
186 // This prevents, for example, losing focus when clicking on swaybar.
187 if (surface && cont && cont->type != C_VIEW) {
188 struct sway_container *new_ws = cont;
189 if (new_ws && new_ws->type != C_WORKSPACE) {
190 new_ws = container_parent(new_ws, C_WORKSPACE);
191 }
192 struct sway_container *old_ws = seat_get_focus(cursor->seat);
193 if (old_ws && old_ws->type != C_WORKSPACE) {
194 old_ws = container_parent(old_ws, C_WORKSPACE);
195 }
196 if (new_ws != old_ws) {
197 seat_set_focus(cursor->seat, cont);
198 }
199 } else {
200 seat_set_focus(cursor->seat, cont);
201 }
202
203 wlr_seat_pointer_notify_button(cursor->seat->wlr_seat,
204 time_msec, button, state);
205}
206
207static void handle_cursor_button(struct wl_listener *listener, void *data) {
208 struct sway_cursor *cursor = wl_container_of(listener, cursor, button);
209 struct wlr_event_pointer_button *event = data;
210 dispatch_cursor_button(cursor,
211 event->time_msec, event->button, event->state);
212}
213
214static void handle_cursor_axis(struct wl_listener *listener, void *data) {
215 struct sway_cursor *cursor = wl_container_of(listener, cursor, axis);
216 struct wlr_event_pointer_axis *event = data;
217 wlr_seat_pointer_notify_axis(cursor->seat->wlr_seat, event->time_msec,
218 event->orientation, event->delta);
219}
220
221static void handle_touch_down(struct wl_listener *listener, void *data) {
222 struct sway_cursor *cursor = wl_container_of(listener, cursor, touch_down);
223 struct wlr_event_touch_down *event = data;
224 wlr_log(L_DEBUG, "TODO: handle touch down event: %p", event);
225}
226
227static void handle_touch_up(struct wl_listener *listener, void *data) {
228 struct sway_cursor *cursor = wl_container_of(listener, cursor, touch_up);
229 struct wlr_event_touch_up *event = data;
230 wlr_log(L_DEBUG, "TODO: handle touch up event: %p", event);
231}
232
233static void handle_touch_motion(struct wl_listener *listener, void *data) {
234 struct sway_cursor *cursor =
235 wl_container_of(listener, cursor, touch_motion);
236 struct wlr_event_touch_motion *event = data;
237 wlr_log(L_DEBUG, "TODO: handle touch motion event: %p", event);
238}
239
240static void handle_tool_axis(struct wl_listener *listener, void *data) {
241 struct sway_cursor *cursor = wl_container_of(listener, cursor, tool_axis);
242 struct wlr_event_tablet_tool_axis *event = data;
243
244 if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_X) &&
245 (event->updated_axes & WLR_TABLET_TOOL_AXIS_Y)) {
246 wlr_cursor_warp_absolute(cursor->cursor, event->device,
247 event->x, event->y);
248 cursor_send_pointer_motion(cursor, event->time_msec);
249 } else if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_X)) {
250 wlr_cursor_warp_absolute(cursor->cursor, event->device, event->x, -1);
251 cursor_send_pointer_motion(cursor, event->time_msec);
252 } else if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_Y)) {
253 wlr_cursor_warp_absolute(cursor->cursor, event->device, -1, event->y);
254 cursor_send_pointer_motion(cursor, event->time_msec);
255 }
256}
257
258static void handle_tool_tip(struct wl_listener *listener, void *data) {
259 struct sway_cursor *cursor = wl_container_of(listener, cursor, tool_tip);
260 struct wlr_event_tablet_tool_tip *event = data;
261 dispatch_cursor_button(cursor, event->time_msec,
262 BTN_LEFT, event->state == WLR_TABLET_TOOL_TIP_DOWN ?
263 WLR_BUTTON_PRESSED : WLR_BUTTON_RELEASED);
264}
265
266static void handle_tool_button(struct wl_listener *listener, void *data) {
267 struct sway_cursor *cursor = wl_container_of(listener, cursor, tool_button);
268 struct wlr_event_tablet_tool_button *event = data;
269 // TODO: the user may want to configure which tool buttons are mapped to
270 // which simulated pointer buttons
271 switch (event->state) {
272 case WLR_BUTTON_PRESSED:
273 if (cursor->tool_buttons == 0) {
274 dispatch_cursor_button(cursor,
275 event->time_msec, BTN_RIGHT, event->state);
276 }
277 cursor->tool_buttons++;
278 break;
279 case WLR_BUTTON_RELEASED:
280 if (cursor->tool_buttons == 1) {
281 dispatch_cursor_button(cursor,
282 event->time_msec, BTN_RIGHT, event->state);
283 }
284 cursor->tool_buttons--;
285 break;
286 }
287}
288
289static void handle_request_set_cursor(struct wl_listener *listener,
290 void *data) {
291 struct sway_cursor *cursor =
292 wl_container_of(listener, cursor, request_set_cursor);
293 struct wlr_seat_pointer_request_set_cursor_event *event = data;
294
295 struct wl_client *focused_client = NULL;
296 struct wlr_surface *focused_surface =
297 cursor->seat->wlr_seat->pointer_state.focused_surface;
298 if (focused_surface != NULL) {
299 focused_client = wl_resource_get_client(focused_surface->resource);
300 }
301
302 // TODO: check cursor mode
303 if (focused_client == NULL ||
304 event->seat_client->client != focused_client) {
305 wlr_log(L_DEBUG, "denying request to set cursor from unfocused client");
306 return;
307 }
308
309 wlr_cursor_set_surface(cursor->cursor, event->surface, event->hotspot_x,
310 event->hotspot_y);
311 cursor->image_client = focused_client;
312}
313
314void sway_cursor_destroy(struct sway_cursor *cursor) {
315 if (!cursor) {
316 return;
317 }
318
319 wlr_xcursor_manager_destroy(cursor->xcursor_manager);
320 wlr_cursor_destroy(cursor->cursor);
321 free(cursor);
322}
323
324struct sway_cursor *sway_cursor_create(struct sway_seat *seat) {
325 struct sway_cursor *cursor = calloc(1, sizeof(struct sway_cursor));
326 if (!sway_assert(cursor, "could not allocate sway cursor")) {
327 return NULL;
328 }
329
330 struct wlr_cursor *wlr_cursor = wlr_cursor_create();
331 if (!sway_assert(wlr_cursor, "could not allocate wlr cursor")) {
332 free(cursor);
333 return NULL;
334 }
335
336 cursor->seat = seat;
337 wlr_cursor_attach_output_layout(wlr_cursor,
338 root_container.sway_root->output_layout);
339
340 // input events
341 wl_signal_add(&wlr_cursor->events.motion, &cursor->motion);
342 cursor->motion.notify = handle_cursor_motion;
343
344 wl_signal_add(&wlr_cursor->events.motion_absolute,
345 &cursor->motion_absolute);
346 cursor->motion_absolute.notify = handle_cursor_motion_absolute;
347
348 wl_signal_add(&wlr_cursor->events.button, &cursor->button);
349 cursor->button.notify = handle_cursor_button;
350
351 wl_signal_add(&wlr_cursor->events.axis, &cursor->axis);
352 cursor->axis.notify = handle_cursor_axis;
353
354 wl_signal_add(&wlr_cursor->events.touch_down, &cursor->touch_down);
355 cursor->touch_down.notify = handle_touch_down;
356
357 wl_signal_add(&wlr_cursor->events.touch_up, &cursor->touch_up);
358 cursor->touch_up.notify = handle_touch_up;
359
360 wl_signal_add(&wlr_cursor->events.touch_motion,
361 &cursor->touch_motion);
362 cursor->touch_motion.notify = handle_touch_motion;
363
364 // TODO: tablet protocol support
365 // Note: We should emulate pointer events for clients that don't support the
366 // tablet protocol when the time comes
367 wl_signal_add(&wlr_cursor->events.tablet_tool_axis,
368 &cursor->tool_axis);
369 cursor->tool_axis.notify = handle_tool_axis;
370
371 wl_signal_add(&wlr_cursor->events.tablet_tool_tip, &cursor->tool_tip);
372 cursor->tool_tip.notify = handle_tool_tip;
373
374 wl_signal_add(&wlr_cursor->events.tablet_tool_button, &cursor->tool_button);
375 cursor->tool_button.notify = handle_tool_button;
376
377 wl_signal_add(&seat->wlr_seat->events.request_set_cursor,
378 &cursor->request_set_cursor);
379 cursor->request_set_cursor.notify = handle_request_set_cursor;
380
381 cursor->cursor = wlr_cursor;
382
383 return cursor;
384}
diff --git a/sway/input/input-manager.c b/sway/input/input-manager.c
new file mode 100644
index 00000000..ae55d2a1
--- /dev/null
+++ b/sway/input/input-manager.c
@@ -0,0 +1,445 @@
1#define _XOPEN_SOURCE 700
2#include <ctype.h>
3#include <float.h>
4#include <limits.h>
5#include <stdio.h>
6#include <string.h>
7#include <libinput.h>
8#include <math.h>
9#include <wlr/backend/libinput.h>
10#include <wlr/types/wlr_input_inhibitor.h>
11#include "sway/config.h"
12#include "sway/input/input-manager.h"
13#include "sway/input/seat.h"
14#include "sway/server.h"
15#include "stringop.h"
16#include "list.h"
17#include "log.h"
18
19static const char *default_seat = "seat0";
20
21// TODO make me not global
22struct sway_input_manager *input_manager;
23
24struct input_config *current_input_config = NULL;
25struct seat_config *current_seat_config = NULL;
26
27struct sway_seat *input_manager_current_seat(struct sway_input_manager *input) {
28 struct sway_seat *seat = config->handler_context.seat;
29 if (!seat) {
30 seat = input_manager_get_default_seat(input_manager);
31 }
32 return seat;
33}
34
35struct sway_seat *input_manager_get_seat(
36 struct sway_input_manager *input, const char *seat_name) {
37 struct sway_seat *seat = NULL;
38 wl_list_for_each(seat, &input->seats, link) {
39 if (strcmp(seat->wlr_seat->name, seat_name) == 0) {
40 return seat;
41 }
42 }
43
44 return seat_create(input, seat_name);
45}
46
47static char *get_device_identifier(struct wlr_input_device *device) {
48 int vendor = device->vendor;
49 int product = device->product;
50 char *name = strdup(device->name);
51 name = strip_whitespace(name);
52
53 char *p = name;
54 for (; *p; ++p) {
55 if (*p == ' ') {
56 *p = '_';
57 }
58 }
59
60 const char *fmt = "%d:%d:%s";
61 int len = snprintf(NULL, 0, fmt, vendor, product, name) + 1;
62 char *identifier = malloc(len);
63 if (!identifier) {
64 wlr_log(L_ERROR, "Unable to allocate unique input device name");
65 return NULL;
66 }
67
68 snprintf(identifier, len, fmt, vendor, product, name);
69 free(name);
70 return identifier;
71}
72
73static struct sway_input_device *input_sway_device_from_wlr(
74 struct sway_input_manager *input, struct wlr_input_device *device) {
75 struct sway_input_device *input_device = NULL;
76 wl_list_for_each(input_device, &input->devices, link) {
77 if (input_device->wlr_device == device) {
78 return input_device;
79 }
80 }
81 return NULL;
82}
83
84static bool input_has_seat_configuration(struct sway_input_manager *input) {
85 struct sway_seat *seat = NULL;
86 wl_list_for_each(seat, &input->seats, link) {
87 struct seat_config *seat_config = seat_get_config(seat);
88 if (seat_config) {
89 return true;
90 }
91 }
92
93 return false;
94}
95
96static void input_manager_libinput_config_pointer(
97 struct sway_input_device *input_device) {
98 struct wlr_input_device *wlr_device = input_device->wlr_device;
99 struct input_config *ic = input_device_get_config(input_device);
100 struct libinput_device *libinput_device;
101
102 if (!ic || !wlr_input_device_is_libinput(wlr_device)) {
103 return;
104 }
105
106 libinput_device = wlr_libinput_get_device_handle(wlr_device);
107 wlr_log(L_DEBUG, "input_manager_libinput_config_pointer(%s)",
108 ic->identifier);
109
110 if (ic->accel_profile != INT_MIN) {
111 wlr_log(L_DEBUG, "libinput_config_pointer(%s) accel_set_profile(%d)",
112 ic->identifier, ic->accel_profile);
113 libinput_device_config_accel_set_profile(libinput_device,
114 ic->accel_profile);
115 }
116 if (ic->click_method != INT_MIN) {
117 wlr_log(L_DEBUG, "libinput_config_pointer(%s) click_set_method(%d)",
118 ic->identifier, ic->click_method);
119 libinput_device_config_click_set_method(libinput_device,
120 ic->click_method);
121 }
122 if (ic->drag_lock != INT_MIN) {
123 wlr_log(L_DEBUG,
124 "libinput_config_pointer(%s) tap_set_drag_lock_enabled(%d)",
125 ic->identifier, ic->click_method);
126 libinput_device_config_tap_set_drag_lock_enabled(libinput_device,
127 ic->drag_lock);
128 }
129 if (ic->dwt != INT_MIN) {
130 wlr_log(L_DEBUG, "libinput_config_pointer(%s) dwt_set_enabled(%d)",
131 ic->identifier, ic->dwt);
132 libinput_device_config_dwt_set_enabled(libinput_device, ic->dwt);
133 }
134 if (ic->left_handed != INT_MIN) {
135 wlr_log(L_DEBUG,
136 "libinput_config_pointer(%s) left_handed_set_enabled(%d)",
137 ic->identifier, ic->left_handed);
138 libinput_device_config_left_handed_set(libinput_device,
139 ic->left_handed);
140 }
141 if (ic->middle_emulation != INT_MIN) {
142 wlr_log(L_DEBUG,
143 "libinput_config_pointer(%s) middle_emulation_set_enabled(%d)",
144 ic->identifier, ic->middle_emulation);
145 libinput_device_config_middle_emulation_set_enabled(libinput_device,
146 ic->middle_emulation);
147 }
148 if (ic->natural_scroll != INT_MIN) {
149 wlr_log(L_DEBUG,
150 "libinput_config_pointer(%s) natural_scroll_set_enabled(%d)",
151 ic->identifier, ic->natural_scroll);
152 libinput_device_config_scroll_set_natural_scroll_enabled(
153 libinput_device, ic->natural_scroll);
154 }
155 if (ic->pointer_accel != FLT_MIN) {
156 wlr_log(L_DEBUG, "libinput_config_pointer(%s) accel_set_speed(%f)",
157 ic->identifier, ic->pointer_accel);
158 libinput_device_config_accel_set_speed(libinput_device,
159 ic->pointer_accel);
160 }
161 if (ic->scroll_method != INT_MIN) {
162 wlr_log(L_DEBUG, "libinput_config_pointer(%s) scroll_set_method(%d)",
163 ic->identifier, ic->scroll_method);
164 libinput_device_config_scroll_set_method(libinput_device,
165 ic->scroll_method);
166 }
167 if (ic->send_events != INT_MIN) {
168 wlr_log(L_DEBUG, "libinput_config_pointer(%s) send_events_set_mode(%d)",
169 ic->identifier, ic->send_events);
170 libinput_device_config_send_events_set_mode(libinput_device,
171 ic->send_events);
172 }
173 if (ic->tap != INT_MIN) {
174 wlr_log(L_DEBUG, "libinput_config_pointer(%s) tap_set_enabled(%d)",
175 ic->identifier, ic->tap);
176 libinput_device_config_tap_set_enabled(libinput_device, ic->tap);
177 }
178}
179
180static void handle_device_destroy(struct wl_listener *listener, void *data) {
181 struct wlr_input_device *device = data;
182
183 struct sway_input_device *input_device =
184 input_sway_device_from_wlr(input_manager, device);
185
186 if (!sway_assert(input_device, "could not find sway device")) {
187 return;
188 }
189
190 wlr_log(L_DEBUG, "removing device: '%s'",
191 input_device->identifier);
192
193 struct sway_seat *seat = NULL;
194 wl_list_for_each(seat, &input_manager->seats, link) {
195 seat_remove_device(seat, input_device);
196 }
197
198 wl_list_remove(&input_device->link);
199 wl_list_remove(&input_device->device_destroy.link);
200 free(input_device->identifier);
201 free(input_device);
202}
203
204static void handle_new_input(struct wl_listener *listener, void *data) {
205 struct sway_input_manager *input =
206 wl_container_of(listener, input, new_input);
207 struct wlr_input_device *device = data;
208
209 struct sway_input_device *input_device =
210 calloc(1, sizeof(struct sway_input_device));
211 if (!sway_assert(input_device, "could not allocate input device")) {
212 return;
213 }
214
215 input_device->wlr_device = device;
216 input_device->identifier = get_device_identifier(device);
217 wl_list_insert(&input->devices, &input_device->link);
218
219 wlr_log(L_DEBUG, "adding device: '%s'",
220 input_device->identifier);
221
222 if (input_device->wlr_device->type == WLR_INPUT_DEVICE_POINTER) {
223 input_manager_libinput_config_pointer(input_device);
224 }
225
226 struct sway_seat *seat = NULL;
227 if (!input_has_seat_configuration(input)) {
228 wlr_log(L_DEBUG, "no seat configuration, using default seat");
229 seat = input_manager_get_seat(input, default_seat);
230 seat_add_device(seat, input_device);
231 return;
232 }
233
234 bool added = false;
235 wl_list_for_each(seat, &input->seats, link) {
236 struct seat_config *seat_config = seat_get_config(seat);
237 bool has_attachment = seat_config &&
238 (seat_config_get_attachment(seat_config, input_device->identifier) ||
239 seat_config_get_attachment(seat_config, "*"));
240
241 if (has_attachment) {
242 seat_add_device(seat, input_device);
243 added = true;
244 }
245 }
246
247 if (!added) {
248 wl_list_for_each(seat, &input->seats, link) {
249 struct seat_config *seat_config = seat_get_config(seat);
250 if (seat_config && seat_config->fallback == 1) {
251 seat_add_device(seat, input_device);
252 added = true;
253 }
254 }
255 }
256
257 if (!added) {
258 wlr_log(L_DEBUG,
259 "device '%s' is not configured on any seats",
260 input_device->identifier);
261 }
262
263 wl_signal_add(&device->events.destroy, &input_device->device_destroy);
264 input_device->device_destroy.notify = handle_device_destroy;
265}
266
267static void handle_inhibit_activate(struct wl_listener *listener, void *data) {
268 struct sway_input_manager *input_manager = wl_container_of(
269 listener, input_manager, inhibit_activate);
270 struct sway_seat *seat;
271 wl_list_for_each(seat, &input_manager->seats, link) {
272 seat_set_exclusive_client(seat, input_manager->inhibit->active_client);
273 }
274}
275
276static void handle_inhibit_deactivate(struct wl_listener *listener, void *data) {
277 struct sway_input_manager *input_manager = wl_container_of(
278 listener, input_manager, inhibit_deactivate);
279 struct sway_seat *seat;
280 wl_list_for_each(seat, &input_manager->seats, link) {
281 seat_set_exclusive_client(seat, NULL);
282 struct sway_container *previous = seat_get_focus(seat);
283 if (previous) {
284 wlr_log(L_DEBUG, "Returning focus to %p %s '%s'", previous,
285 container_type_to_str(previous->type), previous->name);
286 // Hack to get seat to re-focus the return value of get_focus
287 seat_set_focus(seat, previous->parent);
288 seat_set_focus(seat, previous);
289 }
290 }
291}
292
293struct sway_input_manager *input_manager_create(
294 struct sway_server *server) {
295 struct sway_input_manager *input =
296 calloc(1, sizeof(struct sway_input_manager));
297 if (!input) {
298 return NULL;
299 }
300 input->server = server;
301
302 wl_list_init(&input->devices);
303 wl_list_init(&input->seats);
304
305 // create the default seat
306 input_manager_get_seat(input, default_seat);
307
308 input->new_input.notify = handle_new_input;
309 wl_signal_add(&server->backend->events.new_input, &input->new_input);
310
311 input->inhibit = wlr_input_inhibit_manager_create(server->wl_display);
312 input->inhibit_activate.notify = handle_inhibit_activate;
313 wl_signal_add(&input->inhibit->events.activate,
314 &input->inhibit_activate);
315 input->inhibit_deactivate.notify = handle_inhibit_deactivate;
316 wl_signal_add(&input->inhibit->events.deactivate,
317 &input->inhibit_deactivate);
318
319 return input;
320}
321
322bool input_manager_has_focus(struct sway_input_manager *input,
323 struct sway_container *container) {
324 struct sway_seat *seat = NULL;
325 wl_list_for_each(seat, &input->seats, link) {
326 if (seat_get_focus(seat) == container) {
327 return true;
328 }
329 }
330
331 return false;
332}
333
334void input_manager_set_focus(struct sway_input_manager *input,
335 struct sway_container *container) {
336 struct sway_seat *seat;
337 wl_list_for_each(seat, &input->seats, link) {
338 seat_set_focus(seat, container);
339 }
340}
341
342void input_manager_apply_input_config(struct sway_input_manager *input,
343 struct input_config *input_config) {
344 struct sway_input_device *input_device = NULL;
345 wl_list_for_each(input_device, &input->devices, link) {
346 if (strcmp(input_device->identifier, input_config->identifier) == 0) {
347 if (input_device->wlr_device->type == WLR_INPUT_DEVICE_POINTER) {
348 input_manager_libinput_config_pointer(input_device);
349 }
350
351 struct sway_seat *seat = NULL;
352 wl_list_for_each(seat, &input->seats, link) {
353 seat_configure_device(seat, input_device);
354 }
355 }
356 }
357}
358
359void input_manager_apply_seat_config(struct sway_input_manager *input,
360 struct seat_config *seat_config) {
361 wlr_log(L_DEBUG, "applying new seat config for seat %s",
362 seat_config->name);
363 struct sway_seat *seat = input_manager_get_seat(input, seat_config->name);
364 if (!seat) {
365 return;
366 }
367
368 seat_apply_config(seat, seat_config);
369
370 // for every device, try to add it to a seat and if no seat has it
371 // attached, add it to the fallback seats.
372 struct sway_input_device *input_device = NULL;
373 wl_list_for_each(input_device, &input->devices, link) {
374 list_t *seat_list = create_list();
375 struct sway_seat *seat = NULL;
376 wl_list_for_each(seat, &input->seats, link) {
377 struct seat_config *seat_config = seat_get_config(seat);
378 if (!seat_config) {
379 continue;
380 }
381 if (seat_config_get_attachment(seat_config, "*") ||
382 seat_config_get_attachment(seat_config,
383 input_device->identifier)) {
384 list_add(seat_list, seat);
385 }
386 }
387
388 if (seat_list->length) {
389 wl_list_for_each(seat, &input->seats, link) {
390 bool attached = false;
391 for (int i = 0; i < seat_list->length; ++i) {
392 if (seat == seat_list->items[i]) {
393 attached = true;
394 break;
395 }
396 }
397 if (attached) {
398 seat_add_device(seat, input_device);
399 } else {
400 seat_remove_device(seat, input_device);
401 }
402 }
403 } else {
404 wl_list_for_each(seat, &input->seats, link) {
405 struct seat_config *seat_config = seat_get_config(seat);
406 if (seat_config && seat_config->fallback == 1) {
407 seat_add_device(seat, input_device);
408 } else {
409 seat_remove_device(seat, input_device);
410 }
411 }
412 }
413 list_free(seat_list);
414 }
415}
416
417void input_manager_configure_xcursor(struct sway_input_manager *input) {
418 struct sway_seat *seat = NULL;
419 wl_list_for_each(seat, &input->seats, link) {
420 seat_configure_xcursor(seat);
421 }
422}
423
424struct sway_seat *input_manager_get_default_seat(
425 struct sway_input_manager *input) {
426 struct sway_seat *seat = NULL;
427 wl_list_for_each(seat, &input->seats, link) {
428 if (strcmp(seat->wlr_seat->name, "seat0") == 0) {
429 return seat;
430 }
431 }
432 return seat;
433}
434
435struct input_config *input_device_get_config(struct sway_input_device *device) {
436 struct input_config *input_config = NULL;
437 for (int i = 0; i < config->input_configs->length; ++i) {
438 input_config = config->input_configs->items[i];
439 if (strcmp(input_config->identifier, device->identifier) == 0) {
440 return input_config;
441 }
442 }
443
444 return NULL;
445}
diff --git a/sway/input/keyboard.c b/sway/input/keyboard.c
new file mode 100644
index 00000000..dbb0c359
--- /dev/null
+++ b/sway/input/keyboard.c
@@ -0,0 +1,504 @@
1#include <assert.h>
2#include <wlr/backend/multi.h>
3#include <wlr/backend/session.h>
4#include "sway/input/seat.h"
5#include "sway/input/keyboard.h"
6#include "sway/input/input-manager.h"
7#include "sway/commands.h"
8#include "log.h"
9
10static bool keysym_is_modifier(xkb_keysym_t keysym) {
11 switch (keysym) {
12 case XKB_KEY_Shift_L: case XKB_KEY_Shift_R:
13 case XKB_KEY_Control_L: case XKB_KEY_Control_R:
14 case XKB_KEY_Caps_Lock:
15 case XKB_KEY_Shift_Lock:
16 case XKB_KEY_Meta_L: case XKB_KEY_Meta_R:
17 case XKB_KEY_Alt_L: case XKB_KEY_Alt_R:
18 case XKB_KEY_Super_L: case XKB_KEY_Super_R:
19 case XKB_KEY_Hyper_L: case XKB_KEY_Hyper_R:
20 return true;
21 default:
22 return false;
23 }
24}
25
26static size_t pressed_keysyms_length(xkb_keysym_t *pressed_keysyms) {
27 size_t n = 0;
28 for (size_t i = 0; i < SWAY_KEYBOARD_PRESSED_KEYSYMS_CAP; ++i) {
29 if (pressed_keysyms[i] != XKB_KEY_NoSymbol) {
30 ++n;
31 }
32 }
33 return n;
34}
35
36static ssize_t pressed_keysyms_index(xkb_keysym_t *pressed_keysyms,
37 xkb_keysym_t keysym) {
38 for (size_t i = 0; i < SWAY_KEYBOARD_PRESSED_KEYSYMS_CAP; ++i) {
39 if (pressed_keysyms[i] == keysym) {
40 return i;
41 }
42 }
43 return -1;
44}
45
46static void pressed_keysyms_add(xkb_keysym_t *pressed_keysyms,
47 xkb_keysym_t keysym) {
48 ssize_t i = pressed_keysyms_index(pressed_keysyms, keysym);
49 if (i < 0) {
50 i = pressed_keysyms_index(pressed_keysyms, XKB_KEY_NoSymbol);
51 if (i >= 0) {
52 pressed_keysyms[i] = keysym;
53 }
54 }
55}
56
57static void pressed_keysyms_remove(xkb_keysym_t *pressed_keysyms,
58 xkb_keysym_t keysym) {
59 ssize_t i = pressed_keysyms_index(pressed_keysyms, keysym);
60 if (i >= 0) {
61 pressed_keysyms[i] = XKB_KEY_NoSymbol;
62 }
63}
64
65static void pressed_keysyms_update(xkb_keysym_t *pressed_keysyms,
66 const xkb_keysym_t *keysyms, size_t keysyms_len,
67 enum wlr_key_state state) {
68 for (size_t i = 0; i < keysyms_len; ++i) {
69 if (keysym_is_modifier(keysyms[i])) {
70 continue;
71 }
72 if (state == WLR_KEY_PRESSED) {
73 pressed_keysyms_add(pressed_keysyms, keysyms[i]);
74 } else { // WLR_KEY_RELEASED
75 pressed_keysyms_remove(pressed_keysyms, keysyms[i]);
76 }
77 }
78}
79
80static bool binding_matches_key_state(struct sway_binding *binding,
81 enum wlr_key_state key_state) {
82 if (key_state == WLR_KEY_PRESSED && !binding->release) {
83 return true;
84 }
85 if (key_state == WLR_KEY_RELEASED && binding->release) {
86 return true;
87 }
88
89 return false;
90}
91
92static void keyboard_execute_command(struct sway_keyboard *keyboard,
93 struct sway_binding *binding) {
94 wlr_log(L_DEBUG, "running command for binding: %s",
95 binding->command);
96 config_clear_handler_context(config);
97 config->handler_context.seat = keyboard->seat_device->sway_seat;
98 struct cmd_results *results = execute_command(binding->command, NULL);
99 if (results->status != CMD_SUCCESS) {
100 wlr_log(L_DEBUG, "could not run command for binding: %s (%s)",
101 binding->command, results->error);
102 }
103 free_cmd_results(results);
104}
105
106/**
107 * Execute a built-in, hardcoded compositor binding. These are triggered from a
108 * single keysym.
109 *
110 * Returns true if the keysym was handled by a binding and false if the event
111 * should be propagated to clients.
112 */
113static bool keyboard_execute_compositor_binding(struct sway_keyboard *keyboard,
114 xkb_keysym_t *pressed_keysyms, uint32_t modifiers, size_t keysyms_len) {
115 for (size_t i = 0; i < keysyms_len; ++i) {
116 xkb_keysym_t keysym = pressed_keysyms[i];
117 if (keysym >= XKB_KEY_XF86Switch_VT_1 &&
118 keysym <= XKB_KEY_XF86Switch_VT_12) {
119 if (wlr_backend_is_multi(server.backend)) {
120 struct wlr_session *session =
121 wlr_multi_get_session(server.backend);
122 if (session) {
123 unsigned vt = keysym - XKB_KEY_XF86Switch_VT_1 + 1;
124 wlr_session_change_vt(session, vt);
125 }
126 }
127 return true;
128 }
129 }
130
131 return false;
132}
133
134/**
135 * Execute keyboard bindings bound with `bindysm` for the given keyboard state.
136 *
137 * Returns true if the keysym was handled by a binding and false if the event
138 * should be propagated to clients.
139 */
140static bool keyboard_execute_bindsym(struct sway_keyboard *keyboard,
141 xkb_keysym_t *pressed_keysyms, uint32_t modifiers,
142 enum wlr_key_state key_state) {
143 // configured bindings
144 int n = pressed_keysyms_length(pressed_keysyms);
145 list_t *keysym_bindings = config->current_mode->keysym_bindings;
146 for (int i = 0; i < keysym_bindings->length; ++i) {
147 struct sway_binding *binding = keysym_bindings->items[i];
148 if (!binding_matches_key_state(binding, key_state) ||
149 modifiers ^ binding->modifiers ||
150 n != binding->keys->length) {
151 continue;
152 }
153
154 bool match = true;
155 for (int j = 0; j < binding->keys->length; ++j) {
156 match =
157 pressed_keysyms_index(pressed_keysyms,
158 *(int*)binding->keys->items[j]) >= 0;
159
160 if (!match) {
161 break;
162 }
163 }
164
165 if (match) {
166 keyboard_execute_command(keyboard, binding);
167 return true;
168 }
169 }
170
171 return false;
172}
173
174static bool binding_matches_keycodes(struct wlr_keyboard *keyboard,
175 struct sway_binding *binding, struct wlr_event_keyboard_key *event) {
176 assert(binding->bindcode);
177
178 uint32_t keycode = event->keycode + 8;
179
180 if (!binding_matches_key_state(binding, event->state)) {
181 return false;
182 }
183
184 uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard);
185 if (modifiers ^ binding->modifiers) {
186 return false;
187 }
188
189 // on release, the released key must be in the binding
190 if (event->state == WLR_KEY_RELEASED) {
191 bool found = false;
192 for (int i = 0; i < binding->keys->length; ++i) {
193 uint32_t binding_keycode = *(uint32_t*)binding->keys->items[i] + 8;
194 if (binding_keycode == keycode) {
195 found = true;
196 break;
197 }
198 }
199 if (!found) {
200 return false;
201 }
202 }
203
204 // every keycode in the binding must be present in the pressed keys on the
205 // keyboard
206 for (int i = 0; i < binding->keys->length; ++i) {
207 uint32_t binding_keycode = *(uint32_t*)binding->keys->items[i] + 8;
208 if (event->state == WLR_KEY_RELEASED && keycode == binding_keycode) {
209 continue;
210 }
211
212 bool found = false;
213 for (size_t j = 0; j < keyboard->num_keycodes; ++j) {
214 xkb_keycode_t keycode = keyboard->keycodes[j] + 8;
215 if (keycode == binding_keycode) {
216 found = true;
217 break;
218 }
219 }
220
221 if (!found) {
222 return false;
223 }
224 }
225
226 // every keycode pressed on the keyboard must be present within the binding
227 // keys (unless it is a modifier)
228 for (size_t i = 0; i < keyboard->num_keycodes; ++i) {
229 xkb_keycode_t keycode = keyboard->keycodes[i] + 8;
230 bool found = false;
231 for (int j = 0; j < binding->keys->length; ++j) {
232 uint32_t binding_keycode = *(uint32_t*)binding->keys->items[j] + 8;
233 if (binding_keycode == keycode) {
234 found = true;
235 break;
236 }
237 }
238
239 if (!found) {
240 if (!binding->modifiers) {
241 return false;
242 }
243
244 // check if it is a modifier, which we know matched from the check
245 // above
246 const xkb_keysym_t *keysyms;
247 int num_keysyms =
248 xkb_state_key_get_syms(keyboard->xkb_state,
249 keycode, &keysyms);
250 if (num_keysyms != 1 || !keysym_is_modifier(keysyms[0])) {
251 return false;
252 }
253 }
254 }
255
256 return true;
257}
258
259/**
260 * Execute keyboard bindings bound with `bindcode` for the given keyboard state.
261 *
262 * Returns true if the keysym was handled by a binding and false if the event
263 * should be propagated to clients.
264 */
265static bool keyboard_execute_bindcode(struct sway_keyboard *keyboard,
266 struct wlr_event_keyboard_key *event) {
267 struct wlr_keyboard *wlr_keyboard =
268 keyboard->seat_device->input_device->wlr_device->keyboard;
269 list_t *keycode_bindings = config->current_mode->keycode_bindings;
270 for (int i = 0; i < keycode_bindings->length; ++i) {
271 struct sway_binding *binding = keycode_bindings->items[i];
272 if (binding_matches_keycodes(wlr_keyboard, binding, event)) {
273 keyboard_execute_command(keyboard, binding);
274 return true;
275 }
276 }
277
278 return false;
279}
280
281/**
282 * Get keysyms and modifiers from the keyboard as xkb sees them.
283 *
284 * This uses the xkb keysyms translation based on pressed modifiers and clears
285 * the consumed modifiers from the list of modifiers passed to keybind
286 * detection.
287 *
288 * On US layout, pressing Alt+Shift+2 will trigger Alt+@.
289 */
290static size_t keyboard_keysyms_translated(struct sway_keyboard *keyboard,
291 xkb_keycode_t keycode, const xkb_keysym_t **keysyms,
292 uint32_t *modifiers) {
293 struct wlr_input_device *device =
294 keyboard->seat_device->input_device->wlr_device;
295 *modifiers = wlr_keyboard_get_modifiers(device->keyboard);
296 xkb_mod_mask_t consumed = xkb_state_key_get_consumed_mods2(
297 device->keyboard->xkb_state, keycode, XKB_CONSUMED_MODE_XKB);
298 *modifiers = *modifiers & ~consumed;
299
300 return xkb_state_key_get_syms(device->keyboard->xkb_state,
301 keycode, keysyms);
302}
303
304/**
305 * Get keysyms and modifiers from the keyboard as if modifiers didn't change
306 * keysyms.
307 *
308 * This avoids the xkb keysym translation based on modifiers considered pressed
309 * in the state.
310 *
311 * This will trigger keybinds such as Alt+Shift+2.
312 */
313static size_t keyboard_keysyms_raw(struct sway_keyboard *keyboard,
314 xkb_keycode_t keycode, const xkb_keysym_t **keysyms,
315 uint32_t *modifiers) {
316 struct wlr_input_device *device =
317 keyboard->seat_device->input_device->wlr_device;
318 *modifiers = wlr_keyboard_get_modifiers(device->keyboard);
319
320 xkb_layout_index_t layout_index = xkb_state_key_get_layout(
321 device->keyboard->xkb_state, keycode);
322 return xkb_keymap_key_get_syms_by_level(device->keyboard->keymap,
323 keycode, layout_index, 0, keysyms);
324}
325
326static void handle_keyboard_key(struct wl_listener *listener, void *data) {
327 struct sway_keyboard *keyboard =
328 wl_container_of(listener, keyboard, keyboard_key);
329 struct wlr_seat *wlr_seat = keyboard->seat_device->sway_seat->wlr_seat;
330 struct wlr_input_device *wlr_device =
331 keyboard->seat_device->input_device->wlr_device;
332 struct wlr_event_keyboard_key *event = data;
333
334 xkb_keycode_t keycode = event->keycode + 8;
335 bool handled = false;
336
337 // handle keycodes
338 handled = keyboard_execute_bindcode(keyboard, event);
339
340 // handle translated keysyms
341 if (!handled && event->state == WLR_KEY_RELEASED) {
342 handled = keyboard_execute_bindsym(keyboard,
343 keyboard->pressed_keysyms_translated,
344 keyboard->modifiers_translated,
345 event->state);
346 }
347 const xkb_keysym_t *translated_keysyms;
348 size_t translated_keysyms_len =
349 keyboard_keysyms_translated(keyboard, keycode, &translated_keysyms,
350 &keyboard->modifiers_translated);
351 pressed_keysyms_update(keyboard->pressed_keysyms_translated,
352 translated_keysyms, translated_keysyms_len, event->state);
353 if (!handled && event->state == WLR_KEY_PRESSED) {
354 handled = keyboard_execute_bindsym(keyboard,
355 keyboard->pressed_keysyms_translated,
356 keyboard->modifiers_translated,
357 event->state);
358 }
359
360 // Handle raw keysyms
361 if (!handled && event->state == WLR_KEY_RELEASED) {
362 handled = keyboard_execute_bindsym(keyboard,
363 keyboard->pressed_keysyms_raw, keyboard->modifiers_raw,
364 event->state);
365 }
366 const xkb_keysym_t *raw_keysyms;
367 size_t raw_keysyms_len =
368 keyboard_keysyms_raw(keyboard, keycode, &raw_keysyms, &keyboard->modifiers_raw);
369 pressed_keysyms_update(keyboard->pressed_keysyms_raw, raw_keysyms,
370 raw_keysyms_len, event->state);
371 if (!handled && event->state == WLR_KEY_PRESSED) {
372 handled = keyboard_execute_bindsym(keyboard,
373 keyboard->pressed_keysyms_raw, keyboard->modifiers_raw,
374 event->state);
375 }
376
377 // Compositor bindings
378 if (!handled && event->state == WLR_KEY_PRESSED) {
379 handled =
380 keyboard_execute_compositor_binding(keyboard,
381 keyboard->pressed_keysyms_translated,
382 keyboard->modifiers_translated,
383 translated_keysyms_len);
384 }
385 if (!handled && event->state == WLR_KEY_PRESSED) {
386 handled =
387 keyboard_execute_compositor_binding(keyboard,
388 keyboard->pressed_keysyms_raw, keyboard->modifiers_raw,
389 raw_keysyms_len);
390 }
391
392 if (!handled || event->state == WLR_KEY_RELEASED) {
393 wlr_seat_set_keyboard(wlr_seat, wlr_device);
394 wlr_seat_keyboard_notify_key(wlr_seat, event->time_msec,
395 event->keycode, event->state);
396 }
397}
398
399static void handle_keyboard_modifiers(struct wl_listener *listener,
400 void *data) {
401 struct sway_keyboard *keyboard =
402 wl_container_of(listener, keyboard, keyboard_modifiers);
403 struct wlr_seat *wlr_seat = keyboard->seat_device->sway_seat->wlr_seat;
404 struct wlr_input_device *wlr_device =
405 keyboard->seat_device->input_device->wlr_device;
406 wlr_seat_set_keyboard(wlr_seat, wlr_device);
407 wlr_seat_keyboard_notify_modifiers(wlr_seat, &wlr_device->keyboard->modifiers);
408}
409
410struct sway_keyboard *sway_keyboard_create(struct sway_seat *seat,
411 struct sway_seat_device *device) {
412 struct sway_keyboard *keyboard =
413 calloc(1, sizeof(struct sway_keyboard));
414 if (!sway_assert(keyboard, "could not allocate sway keyboard")) {
415 return NULL;
416 }
417
418 keyboard->seat_device = device;
419 device->keyboard = keyboard;
420
421 wl_list_init(&keyboard->keyboard_key.link);
422 wl_list_init(&keyboard->keyboard_modifiers.link);
423
424 return keyboard;
425}
426
427void sway_keyboard_configure(struct sway_keyboard *keyboard) {
428 struct xkb_rule_names rules;
429 memset(&rules, 0, sizeof(rules));
430 struct input_config *input_config =
431 input_device_get_config(keyboard->seat_device->input_device);
432 struct wlr_input_device *wlr_device =
433 keyboard->seat_device->input_device->wlr_device;
434
435 if (input_config && input_config->xkb_layout) {
436 rules.layout = input_config->xkb_layout;
437 } else {
438 rules.layout = getenv("XKB_DEFAULT_LAYOUT");
439 }
440 if (input_config && input_config->xkb_model) {
441 rules.model = input_config->xkb_model;
442 } else {
443 rules.model = getenv("XKB_DEFAULT_MODEL");
444 }
445
446 if (input_config && input_config->xkb_options) {
447 rules.options = input_config->xkb_options;
448 } else {
449 rules.options = getenv("XKB_DEFAULT_OPTIONS");
450 }
451
452 if (input_config && input_config->xkb_rules) {
453 rules.rules = input_config->xkb_rules;
454 } else {
455 rules.rules = getenv("XKB_DEFAULT_RULES");
456 }
457
458 if (input_config && input_config->xkb_variant) {
459 rules.variant = input_config->xkb_variant;
460 } else {
461 rules.variant = getenv("XKB_DEFAULT_VARIANT");
462 }
463
464 struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
465 if (!sway_assert(context, "cannot create XKB context")) {
466 return;
467 }
468
469 struct xkb_keymap *keymap =
470 xkb_keymap_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS);
471
472 if (!keymap) {
473 wlr_log(L_DEBUG, "cannot configure keyboard: keymap does not exist");
474 xkb_context_unref(context);
475 return;
476 }
477
478 xkb_keymap_unref(keyboard->keymap);
479 keyboard->keymap = keymap;
480 wlr_keyboard_set_keymap(wlr_device->keyboard, keyboard->keymap);
481
482 wlr_keyboard_set_repeat_info(wlr_device->keyboard, 25, 600);
483 xkb_context_unref(context);
484 struct wlr_seat *seat = keyboard->seat_device->sway_seat->wlr_seat;
485 wlr_seat_set_keyboard(seat, wlr_device);
486
487 wl_list_remove(&keyboard->keyboard_key.link);
488 wl_signal_add(&wlr_device->keyboard->events.key, &keyboard->keyboard_key);
489 keyboard->keyboard_key.notify = handle_keyboard_key;
490
491 wl_list_remove(&keyboard->keyboard_modifiers.link);
492 wl_signal_add( &wlr_device->keyboard->events.modifiers,
493 &keyboard->keyboard_modifiers);
494 keyboard->keyboard_modifiers.notify = handle_keyboard_modifiers;
495}
496
497void sway_keyboard_destroy(struct sway_keyboard *keyboard) {
498 if (!keyboard) {
499 return;
500 }
501 wl_list_remove(&keyboard->keyboard_key.link);
502 wl_list_remove(&keyboard->keyboard_modifiers.link);
503 free(keyboard);
504}
diff --git a/sway/input/seat.c b/sway/input/seat.c
new file mode 100644
index 00000000..467e5087
--- /dev/null
+++ b/sway/input/seat.c
@@ -0,0 +1,675 @@
1#define _XOPEN_SOURCE 700
2#define _POSIX_C_SOURCE 199309L
3#include <assert.h>
4#include <strings.h>
5#include <time.h>
6#include <wlr/types/wlr_cursor.h>
7#include <wlr/types/wlr_output_layout.h>
8#include <wlr/types/wlr_xcursor_manager.h>
9#include "sway/debug.h"
10#include "sway/tree/container.h"
11#include "sway/tree/workspace.h"
12#include "sway/input/seat.h"
13#include "sway/input/cursor.h"
14#include "sway/input/input-manager.h"
15#include "sway/input/keyboard.h"
16#include "sway/ipc-server.h"
17#include "sway/layers.h"
18#include "sway/output.h"
19#include "sway/tree/container.h"
20#include "sway/tree/view.h"
21#include "log.h"
22
23static void seat_device_destroy(struct sway_seat_device *seat_device) {
24 if (!seat_device) {
25 return;
26 }
27
28 sway_keyboard_destroy(seat_device->keyboard);
29 wlr_cursor_detach_input_device(seat_device->sway_seat->cursor->cursor,
30 seat_device->input_device->wlr_device);
31 wl_list_remove(&seat_device->link);
32 free(seat_device);
33}
34
35void seat_destroy(struct sway_seat *seat) {
36 struct sway_seat_device *seat_device, *next;
37 wl_list_for_each_safe(seat_device, next, &seat->devices, link) {
38 seat_device_destroy(seat_device);
39 }
40 sway_cursor_destroy(seat->cursor);
41 wl_list_remove(&seat->link);
42 wlr_seat_destroy(seat->wlr_seat);
43}
44
45static struct sway_seat_container *seat_container_from_container(
46 struct sway_seat *seat, struct sway_container *con);
47
48static void seat_container_destroy(struct sway_seat_container *seat_con) {
49 struct sway_container *con = seat_con->container;
50 struct sway_container *child = NULL;
51
52 if (con->children != NULL) {
53 for (int i = 0; i < con->children->length; ++i) {
54 child = con->children->items[i];
55 struct sway_seat_container *seat_child =
56 seat_container_from_container(seat_con->seat, child);
57 seat_container_destroy(seat_child);
58 }
59 }
60
61 wl_list_remove(&seat_con->destroy.link);
62 wl_list_remove(&seat_con->link);
63 free(seat_con);
64}
65
66static void seat_send_focus(struct sway_seat *seat,
67 struct sway_container *con) {
68 if (con->type != C_VIEW) {
69 return;
70 }
71 struct sway_view *view = con->sway_view;
72 if (view->type == SWAY_VIEW_XWAYLAND) {
73 struct wlr_xwayland *xwayland =
74 seat->input->server->xwayland;
75 wlr_xwayland_set_seat(xwayland, seat->wlr_seat);
76 }
77 view_set_activated(view, true);
78 struct wlr_keyboard *keyboard =
79 wlr_seat_get_keyboard(seat->wlr_seat);
80 if (keyboard) {
81 wlr_seat_keyboard_notify_enter(seat->wlr_seat,
82 view->surface, keyboard->keycodes,
83 keyboard->num_keycodes, &keyboard->modifiers);
84 } else {
85 wlr_seat_keyboard_notify_enter(
86 seat->wlr_seat, view->surface, NULL, 0, NULL);
87 }
88}
89
90static struct sway_container *seat_get_focus_by_type(struct sway_seat *seat,
91 struct sway_container *container, enum sway_container_type type) {
92 if (container->type == C_VIEW || container->children->length == 0) {
93 return container;
94 }
95
96 struct sway_seat_container *current = NULL;
97 wl_list_for_each(current, &seat->focus_stack, link) {
98 if (current->container->type != type && type != C_TYPES) {
99 continue;
100 }
101
102 if (container_has_child(container, current->container)) {
103 return current->container;
104 }
105 }
106
107 return NULL;
108}
109
110void seat_focus_inactive_children_for_each(struct sway_seat *seat,
111 struct sway_container *container,
112 void (*f)(struct sway_container *container, void *data), void *data) {
113 struct sway_seat_container *current = NULL;
114 wl_list_for_each(current, &seat->focus_stack, link) {
115 if (current->container->parent == NULL) {
116 continue;
117 }
118 if (current->container->parent == container) {
119 f(current->container, data);
120 }
121 }
122}
123
124struct sway_container *seat_get_focus_inactive_view(struct sway_seat *seat,
125 struct sway_container *container) {
126 return seat_get_focus_by_type(seat, container, C_VIEW);
127}
128
129static void handle_seat_container_destroy(struct wl_listener *listener,
130 void *data) {
131 struct sway_seat_container *seat_con =
132 wl_container_of(listener, seat_con, destroy);
133 struct sway_seat *seat = seat_con->seat;
134 struct sway_container *con = seat_con->container;
135 struct sway_container *parent = con->parent;
136 struct sway_container *focus = seat_get_focus(seat);
137
138 bool set_focus =
139 focus != NULL &&
140 (focus == con || container_has_child(con, focus)) &&
141 con->type != C_WORKSPACE;
142
143 seat_container_destroy(seat_con);
144
145 if (set_focus) {
146 struct sway_container *next_focus = NULL;
147 while (next_focus == NULL) {
148 next_focus = seat_get_focus_by_type(seat, parent, C_VIEW);
149
150 if (next_focus == NULL && parent->type == C_WORKSPACE) {
151 next_focus = parent;
152 break;
153 }
154
155 parent = parent->parent;
156 }
157
158 // the structure change might have caused it to move up to the top of
159 // the focus stack without sending focus notifications to the view
160 if (seat_get_focus(seat) == next_focus) {
161 seat_send_focus(seat, next_focus);
162 } else {
163 seat_set_focus(seat, next_focus);
164 }
165 }
166}
167
168static struct sway_seat_container *seat_container_from_container(
169 struct sway_seat *seat, struct sway_container *con) {
170 if (con->type == C_ROOT || con->type == C_OUTPUT) {
171 // these don't get seat containers ever
172 return NULL;
173 }
174
175 struct sway_seat_container *seat_con = NULL;
176 wl_list_for_each(seat_con, &seat->focus_stack, link) {
177 if (seat_con->container == con) {
178 return seat_con;
179 }
180 }
181
182 seat_con = calloc(1, sizeof(struct sway_seat_container));
183 if (seat_con == NULL) {
184 wlr_log(L_ERROR, "could not allocate seat container");
185 return NULL;
186 }
187
188 seat_con->container = con;
189 seat_con->seat = seat;
190 wl_list_insert(seat->focus_stack.prev, &seat_con->link);
191 wl_signal_add(&con->events.destroy, &seat_con->destroy);
192 seat_con->destroy.notify = handle_seat_container_destroy;
193
194 return seat_con;
195}
196
197static void handle_new_container(struct wl_listener *listener, void *data) {
198 struct sway_seat *seat = wl_container_of(listener, seat, new_container);
199 struct sway_container *con = data;
200 seat_container_from_container(seat, con);
201}
202
203static void collect_focus_iter(struct sway_container *con, void *data) {
204 struct sway_seat *seat = data;
205 if (con->type > C_WORKSPACE) {
206 return;
207 }
208 struct sway_seat_container *seat_con =
209 seat_container_from_container(seat, con);
210 if (!seat_con) {
211 return;
212 }
213 wl_list_remove(&seat_con->link);
214 wl_list_insert(&seat->focus_stack, &seat_con->link);
215}
216
217struct sway_seat *seat_create(struct sway_input_manager *input,
218 const char *seat_name) {
219 struct sway_seat *seat = calloc(1, sizeof(struct sway_seat));
220 if (!seat) {
221 return NULL;
222 }
223
224 seat->wlr_seat = wlr_seat_create(input->server->wl_display, seat_name);
225 if (!sway_assert(seat->wlr_seat, "could not allocate seat")) {
226 free(seat);
227 return NULL;
228 }
229
230 seat->cursor = sway_cursor_create(seat);
231 if (!seat->cursor) {
232 wlr_seat_destroy(seat->wlr_seat);
233 free(seat);
234 return NULL;
235 }
236
237 // init the focus stack
238 wl_list_init(&seat->focus_stack);
239
240 container_for_each_descendant_dfs(&root_container,
241 collect_focus_iter, seat);
242
243 wl_signal_add(&root_container.sway_root->events.new_container,
244 &seat->new_container);
245 seat->new_container.notify = handle_new_container;
246
247 seat->input = input;
248 wl_list_init(&seat->devices);
249
250 wlr_seat_set_capabilities(seat->wlr_seat,
251 WL_SEAT_CAPABILITY_KEYBOARD |
252 WL_SEAT_CAPABILITY_POINTER |
253 WL_SEAT_CAPABILITY_TOUCH);
254
255 seat_configure_xcursor(seat);
256
257 wl_list_insert(&input->seats, &seat->link);
258
259 return seat;
260}
261
262static void seat_apply_input_config(struct sway_seat *seat,
263 struct sway_seat_device *sway_device) {
264 struct input_config *ic = input_device_get_config(
265 sway_device->input_device);
266 if (!ic) {
267 return;
268 }
269 wlr_log(L_DEBUG, "Applying input config to %s",
270 sway_device->input_device->identifier);
271 if (ic->mapped_output) {
272 struct sway_container *output = NULL;
273 for (int i = 0; i < root_container.children->length; ++i) {
274 struct sway_container *_output = root_container.children->items[i];
275 if (strcasecmp(_output->name, ic->mapped_output) == 0) {
276 output = _output;
277 break;
278 }
279 }
280 if (output) {
281 wlr_cursor_map_input_to_output(seat->cursor->cursor,
282 sway_device->input_device->wlr_device,
283 output->sway_output->wlr_output);
284 wlr_log(L_DEBUG, "Mapped to output %s", output->name);
285 }
286 }
287}
288
289static void seat_configure_pointer(struct sway_seat *seat,
290 struct sway_seat_device *sway_device) {
291 wlr_cursor_attach_input_device(seat->cursor->cursor,
292 sway_device->input_device->wlr_device);
293 seat_apply_input_config(seat, sway_device);
294}
295
296static void seat_configure_keyboard(struct sway_seat *seat,
297 struct sway_seat_device *seat_device) {
298 if (!seat_device->keyboard) {
299 sway_keyboard_create(seat, seat_device);
300 }
301 struct wlr_keyboard *wlr_keyboard =
302 seat_device->input_device->wlr_device->keyboard;
303 sway_keyboard_configure(seat_device->keyboard);
304 wlr_seat_set_keyboard(seat->wlr_seat,
305 seat_device->input_device->wlr_device);
306 struct sway_container *focus = seat_get_focus(seat);
307 if (focus && focus->type == C_VIEW) {
308 // force notify reenter to pick up the new configuration
309 wlr_seat_keyboard_clear_focus(seat->wlr_seat);
310 wlr_seat_keyboard_notify_enter(seat->wlr_seat,
311 focus->sway_view->surface, wlr_keyboard->keycodes,
312 wlr_keyboard->num_keycodes, &wlr_keyboard->modifiers);
313 }
314}
315
316static void seat_configure_tablet_tool(struct sway_seat *seat,
317 struct sway_seat_device *sway_device) {
318 wlr_cursor_attach_input_device(seat->cursor->cursor,
319 sway_device->input_device->wlr_device);
320 seat_apply_input_config(seat, sway_device);
321}
322
323static struct sway_seat_device *seat_get_device(struct sway_seat *seat,
324 struct sway_input_device *input_device) {
325 struct sway_seat_device *seat_device = NULL;
326 wl_list_for_each(seat_device, &seat->devices, link) {
327 if (seat_device->input_device == input_device) {
328 return seat_device;
329 }
330 }
331
332 return NULL;
333}
334
335void seat_configure_device(struct sway_seat *seat,
336 struct sway_input_device *input_device) {
337 struct sway_seat_device *seat_device =
338 seat_get_device(seat, input_device);
339 if (!seat_device) {
340 return;
341 }
342
343 switch (input_device->wlr_device->type) {
344 case WLR_INPUT_DEVICE_POINTER:
345 seat_configure_pointer(seat, seat_device);
346 break;
347 case WLR_INPUT_DEVICE_KEYBOARD:
348 seat_configure_keyboard(seat, seat_device);
349 break;
350 case WLR_INPUT_DEVICE_TABLET_TOOL:
351 seat_configure_tablet_tool(seat, seat_device);
352 break;
353 case WLR_INPUT_DEVICE_TABLET_PAD:
354 case WLR_INPUT_DEVICE_TOUCH:
355 wlr_log(L_DEBUG, "TODO: configure other devices");
356 break;
357 }
358}
359
360void seat_add_device(struct sway_seat *seat,
361 struct sway_input_device *input_device) {
362 if (seat_get_device(seat, input_device)) {
363 seat_configure_device(seat, input_device);
364 return;
365 }
366
367 struct sway_seat_device *seat_device =
368 calloc(1, sizeof(struct sway_seat_device));
369 if (!seat_device) {
370 wlr_log(L_DEBUG, "could not allocate seat device");
371 return;
372 }
373
374 wlr_log(L_DEBUG, "adding device %s to seat %s",
375 input_device->identifier, seat->wlr_seat->name);
376
377 seat_device->sway_seat = seat;
378 seat_device->input_device = input_device;
379 wl_list_insert(&seat->devices, &seat_device->link);
380
381 seat_configure_device(seat, input_device);
382}
383
384void seat_remove_device(struct sway_seat *seat,
385 struct sway_input_device *input_device) {
386 struct sway_seat_device *seat_device =
387 seat_get_device(seat, input_device);
388
389 if (!seat_device) {
390 return;
391 }
392
393 wlr_log(L_DEBUG, "removing device %s from seat %s",
394 input_device->identifier, seat->wlr_seat->name);
395
396 seat_device_destroy(seat_device);
397}
398
399void seat_configure_xcursor(struct sway_seat *seat) {
400 // TODO configure theme and size
401 const char *cursor_theme = NULL;
402
403 if (!seat->cursor->xcursor_manager) {
404 seat->cursor->xcursor_manager =
405 wlr_xcursor_manager_create(cursor_theme, 24);
406 if (sway_assert(seat->cursor->xcursor_manager,
407 "Cannot create XCursor manager for theme %s",
408 cursor_theme)) {
409 return;
410 }
411 }
412
413 for (int i = 0; i < root_container.children->length; ++i) {
414 struct sway_container *output_container =
415 root_container.children->items[i];
416 struct wlr_output *output =
417 output_container->sway_output->wlr_output;
418 bool result =
419 wlr_xcursor_manager_load(seat->cursor->xcursor_manager,
420 output->scale);
421
422 sway_assert(!result,
423 "Cannot load xcursor theme for output '%s' with scale %f",
424 // TODO: Fractional scaling
425 output->name, (double)output->scale);
426 }
427
428 wlr_xcursor_manager_set_cursor_image(seat->cursor->xcursor_manager,
429 "left_ptr", seat->cursor->cursor);
430 wlr_cursor_warp(seat->cursor->cursor, NULL, seat->cursor->cursor->x,
431 seat->cursor->cursor->y);
432}
433
434bool seat_is_input_allowed(struct sway_seat *seat,
435 struct wlr_surface *surface) {
436 struct wl_client *client = wl_resource_get_client(surface->resource);
437 return !seat->exclusive_client || seat->exclusive_client == client;
438}
439
440void seat_set_focus_warp(struct sway_seat *seat,
441 struct sway_container *container, bool warp) {
442 if (seat->focused_layer) {
443 return;
444 }
445
446 struct sway_container *last_focus = seat_get_focus(seat);
447 if (container && last_focus == container) {
448 return;
449 }
450
451 if (container) {
452 struct sway_seat_container *seat_con =
453 seat_container_from_container(seat, container);
454 if (seat_con == NULL) {
455 return;
456 }
457
458 // put all the anscestors of this container on top of the focus stack
459 struct sway_seat_container *parent =
460 seat_container_from_container(seat,
461 seat_con->container->parent);
462 while (parent) {
463 wl_list_remove(&parent->link);
464 wl_list_insert(&seat->focus_stack, &parent->link);
465
466 parent =
467 seat_container_from_container(seat,
468 parent->container->parent);
469 }
470
471 wl_list_remove(&seat_con->link);
472 wl_list_insert(&seat->focus_stack, &seat_con->link);
473
474 if (container->type == C_VIEW && !seat_is_input_allowed(
475 seat, container->sway_view->surface)) {
476 wlr_log(L_DEBUG, "Refusing to set focus, input is inhibited");
477 return;
478 }
479
480 if (container->type == C_VIEW) {
481 seat_send_focus(seat, container);
482 }
483 }
484
485 if (last_focus) {
486 struct sway_container *last_ws = last_focus;
487 if (last_ws && last_ws->type != C_WORKSPACE) {
488 last_ws = container_parent(last_ws, C_WORKSPACE);
489 }
490 if (last_ws) {
491 ipc_event_workspace(last_ws, container, "focus");
492 if (!workspace_is_visible(last_ws)
493 && last_ws->children->length == 0) {
494 container_destroy(last_ws);
495 }
496 }
497
498 if (config->mouse_warping && warp) {
499 struct sway_container *last_output = last_focus;
500 if (last_output && last_output->type != C_OUTPUT) {
501 last_output = container_parent(last_output, C_OUTPUT);
502 }
503 struct sway_container *new_output = container;
504 if (new_output && new_output->type != C_OUTPUT) {
505 new_output = container_parent(new_output, C_OUTPUT);
506 }
507 if (new_output && last_output && new_output != last_output) {
508 double x = new_output->x + container->x +
509 container->width / 2.0;
510 double y = new_output->y + container->y +
511 container->height / 2.0;
512 struct wlr_output *wlr_output =
513 new_output->sway_output->wlr_output;
514 if (!wlr_output_layout_contains_point(
515 root_container.sway_root->output_layout,
516 wlr_output, seat->cursor->cursor->x,
517 seat->cursor->cursor->y)) {
518 wlr_cursor_warp(seat->cursor->cursor, NULL, x, y);
519 struct timespec now;
520 clock_gettime(CLOCK_MONOTONIC, &now);
521 cursor_send_pointer_motion(seat->cursor, now.tv_nsec / 1000);
522 }
523 }
524 }
525 }
526
527 if (last_focus && last_focus->type == C_VIEW &&
528 !input_manager_has_focus(seat->input, last_focus)) {
529 struct sway_view *view = last_focus->sway_view;
530 view_set_activated(view, false);
531 }
532
533 seat->has_focus = (container != NULL);
534
535 update_debug_tree();
536}
537
538void seat_set_focus(struct sway_seat *seat,
539 struct sway_container *container) {
540 seat_set_focus_warp(seat, container, true);
541}
542
543void seat_set_focus_surface(struct sway_seat *seat,
544 struct wlr_surface *surface) {
545 if (seat->focused_layer != NULL) {
546 return;
547 }
548 if (seat->has_focus) {
549 struct sway_container *focus = seat_get_focus(seat);
550 if (focus->type == C_VIEW) {
551 wlr_seat_keyboard_clear_focus(seat->wlr_seat);
552 view_set_activated(focus->sway_view, false);
553 }
554 seat->has_focus = false;
555 }
556 struct wlr_keyboard *keyboard =
557 wlr_seat_get_keyboard(seat->wlr_seat);
558 if (keyboard) {
559 wlr_seat_keyboard_notify_enter(seat->wlr_seat, surface,
560 keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers);
561 } else {
562 wlr_seat_keyboard_notify_enter(seat->wlr_seat, surface, NULL, 0, NULL);
563 }
564}
565
566void seat_set_focus_layer(struct sway_seat *seat,
567 struct wlr_layer_surface *layer) {
568 if (!layer && seat->focused_layer) {
569 seat->focused_layer = NULL;
570 struct sway_container *previous = seat_get_focus(seat);
571 if (previous) {
572 wlr_log(L_DEBUG, "Returning focus to %p %s '%s'", previous,
573 container_type_to_str(previous->type), previous->name);
574 // Hack to get seat to re-focus the return value of get_focus
575 seat_set_focus(seat, previous->parent);
576 seat_set_focus(seat, previous);
577 }
578 return;
579 } else if (!layer || seat->focused_layer == layer) {
580 return;
581 }
582 seat_set_focus_surface(seat, layer->surface);
583 if (layer->layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) {
584 seat->focused_layer = layer;
585 }
586}
587
588void seat_set_exclusive_client(struct sway_seat *seat,
589 struct wl_client *client) {
590 if (!client) {
591 seat->exclusive_client = client;
592 // Triggers a refocus of the topmost surface layer if necessary
593 // TODO: Make layer surface focus per-output based on cursor position
594 for (int i = 0; i < root_container.children->length; ++i) {
595 struct sway_container *output = root_container.children->items[i];
596 if (!sway_assert(output->type == C_OUTPUT,
597 "root container has non-output child")) {
598 continue;
599 }
600 arrange_layers(output->sway_output);
601 }
602 return;
603 }
604 if (seat->focused_layer) {
605 if (wl_resource_get_client(seat->focused_layer->resource) != client) {
606 seat_set_focus_layer(seat, NULL);
607 }
608 }
609 if (seat->has_focus) {
610 struct sway_container *focus = seat_get_focus(seat);
611 if (focus->type == C_VIEW && wl_resource_get_client(
612 focus->sway_view->surface->resource) != client) {
613 seat_set_focus(seat, NULL);
614 }
615 }
616 if (seat->wlr_seat->pointer_state.focused_client) {
617 if (seat->wlr_seat->pointer_state.focused_client->client != client) {
618 wlr_seat_pointer_clear_focus(seat->wlr_seat);
619 }
620 }
621 struct timespec now;
622 clock_gettime(CLOCK_MONOTONIC, &now);
623 struct wlr_touch_point *point;
624 wl_list_for_each(point, &seat->wlr_seat->touch_state.touch_points, link) {
625 if (point->client->client != client) {
626 wlr_seat_touch_point_clear_focus(seat->wlr_seat,
627 now.tv_nsec / 1000, point->touch_id);
628 }
629 }
630 seat->exclusive_client = client;
631}
632
633struct sway_container *seat_get_focus_inactive(struct sway_seat *seat,
634 struct sway_container *container) {
635 return seat_get_focus_by_type(seat, container, C_TYPES);
636}
637
638struct sway_container *sway_seat_get_focus(struct sway_seat *seat) {
639 if (!seat->has_focus) {
640 return NULL;
641 }
642 return seat_get_focus_inactive(seat, &root_container);
643}
644
645struct sway_container *seat_get_focus(struct sway_seat *seat) {
646 if (!seat->has_focus) {
647 return NULL;
648 }
649 return seat_get_focus_inactive(seat, &root_container);
650}
651
652void seat_apply_config(struct sway_seat *seat,
653 struct seat_config *seat_config) {
654 struct sway_seat_device *seat_device = NULL;
655
656 if (!seat_config) {
657 return;
658 }
659
660 wl_list_for_each(seat_device, &seat->devices, link) {
661 seat_configure_device(seat, seat_device->input_device);
662 }
663}
664
665struct seat_config *seat_get_config(struct sway_seat *seat) {
666 struct seat_config *seat_config = NULL;
667 for (int i = 0; i < config->seat_configs->length; ++i ) {
668 seat_config = config->seat_configs->items[i];
669 if (strcmp(seat->wlr_seat->name, seat_config->name) == 0) {
670 return seat_config;
671 }
672 }
673
674 return NULL;
675}
diff --git a/sway/input_state.c b/sway/input_state.c
deleted file mode 100644
index 04aafd37..00000000
--- a/sway/input_state.c
+++ /dev/null
@@ -1,490 +0,0 @@
1#include <string.h>
2#include <stdbool.h>
3#include <ctype.h>
4#include "sway/input_state.h"
5#include "sway/config.h"
6#include "log.h"
7
8#define KEY_STATE_MAX_LENGTH 64
9
10struct key_state {
11 /*
12 * Aims to store state regardless of modifiers.
13 * If you press a key, then hold shift, then release the key, we'll
14 * get two different key syms, but the same key code. This handles
15 * that scenario and makes sure we can use the right bindings.
16 */
17 uint32_t key_sym;
18 uint32_t alt_sym;
19 uint32_t key_code;
20};
21
22static struct key_state key_state_array[KEY_STATE_MAX_LENGTH];
23
24static struct key_state last_released;
25
26static uint32_t modifiers_state;
27
28void input_init(void) {
29 int i;
30 for (i = 0; i < KEY_STATE_MAX_LENGTH; ++i) {
31 struct key_state none = { 0, 0, 0 };
32 key_state_array[i] = none;
33 }
34
35 struct key_state none = { 0, 0, 0 };
36 last_released = none;
37
38 modifiers_state = 0;
39}
40
41uint32_t modifier_state_changed(uint32_t new_state, uint32_t mod) {
42 if ((new_state & mod) != 0) { // pressed
43 if ((modifiers_state & mod) != 0) { // already pressed
44 return MOD_STATE_UNCHANGED;
45 } else { // pressed
46 return MOD_STATE_PRESSED;
47 }
48 } else { // not pressed
49 if ((modifiers_state & mod) != 0) { // released
50 return MOD_STATE_RELEASED;
51 } else { // already released
52 return MOD_STATE_UNCHANGED;
53 }
54 }
55}
56
57void modifiers_state_update(uint32_t new_state) {
58 modifiers_state = new_state;
59}
60
61static uint8_t find_key(uint32_t key_sym, uint32_t key_code, bool update) {
62 int i;
63 for (i = 0; i < KEY_STATE_MAX_LENGTH; ++i) {
64 if (0 == key_sym && 0 == key_code && key_state_array[i].key_sym == 0) {
65 break;
66 }
67 if (key_sym != 0 && (key_state_array[i].key_sym == key_sym
68 || key_state_array[i].alt_sym == key_sym)) {
69 break;
70 }
71 if (update && key_state_array[i].key_code == key_code) {
72 key_state_array[i].alt_sym = key_sym;
73 break;
74 }
75 if (key_sym == 0 && key_code != 0 && key_state_array[i].key_code == key_code) {
76 break;
77 }
78 }
79 return i;
80}
81
82bool check_key(uint32_t key_sym, uint32_t key_code) {
83 return find_key(key_sym, key_code, false) < KEY_STATE_MAX_LENGTH;
84}
85
86bool check_released_key(uint32_t key_sym) {
87 return (key_sym != 0
88 && (last_released.key_sym == key_sym
89 || last_released.alt_sym == key_sym));
90}
91
92void press_key(uint32_t key_sym, uint32_t key_code) {
93 if (key_code == 0) {
94 return;
95 }
96 // Check if key exists
97 if (!check_key(key_sym, key_code)) {
98 // Check that we don't exceed buffer length
99 int insert = find_key(0, 0, true);
100 if (insert < KEY_STATE_MAX_LENGTH) {
101 key_state_array[insert].key_sym = key_sym;
102 key_state_array[insert].key_code = key_code;
103 }
104 }
105}
106
107void release_key(uint32_t key_sym, uint32_t key_code) {
108 uint8_t index = find_key(key_sym, key_code, true);
109 if (index < KEY_STATE_MAX_LENGTH) {
110 last_released.key_sym = key_state_array[index].key_sym;
111 last_released.alt_sym = key_state_array[index].alt_sym;
112 last_released.key_code = key_state_array[index].key_code;
113 struct key_state none = { 0, 0, 0 };
114 key_state_array[index] = none;
115 }
116}
117
118// Pointer state and mode
119
120struct pointer_state pointer_state;
121
122static struct mode_state {
123 // initial view state
124 double x, y, w, h;
125 swayc_t *ptr;
126 // Containers used for resizing horizontally
127 struct {
128 double w;
129 swayc_t *ptr;
130 struct {
131 double w;
132 swayc_t *ptr;
133 } parent;
134 } horiz;
135 // Containers used for resizing vertically
136 struct {
137 double h;
138 swayc_t *ptr;
139 struct {
140 double h;
141 swayc_t *ptr;
142 } parent;
143 } vert;
144} initial;
145
146static struct {
147 bool left;
148 bool top;
149} lock;
150
151// initial set/unset
152
153static void set_initial_view(swayc_t *view) {
154 initial.ptr = view;
155 initial.x = view->x;
156 initial.y = view->y;
157 initial.w = view->width;
158 initial.h = view->height;
159}
160
161static void set_initial_sibling(void) {
162 bool reset = true;
163 swayc_t *ws = swayc_active_workspace_for(initial.ptr);
164 if ((initial.horiz.ptr = get_swayc_in_direction_under(initial.ptr,
165 lock.left ? MOVE_RIGHT: MOVE_LEFT, ws))) {
166 initial.horiz.w = initial.horiz.ptr->width;
167 initial.horiz.parent.ptr = get_swayc_in_direction_under(initial.horiz.ptr,
168 lock.left ? MOVE_LEFT : MOVE_RIGHT, ws);
169 initial.horiz.parent.w = initial.horiz.parent.ptr->width;
170 reset = false;
171 }
172 if ((initial.vert.ptr = get_swayc_in_direction_under(initial.ptr,
173 lock.top ? MOVE_DOWN: MOVE_UP, ws))) {
174 initial.vert.h = initial.vert.ptr->height;
175 initial.vert.parent.ptr = get_swayc_in_direction_under(initial.vert.ptr,
176 lock.top ? MOVE_UP : MOVE_DOWN, ws);
177 initial.vert.parent.h = initial.vert.parent.ptr->height;
178 reset = false;
179 }
180 // If nothing will change just undo the mode
181 if (reset) {
182 pointer_state.mode = 0;
183 }
184}
185
186static void reset_initial_view(void) {
187 initial.ptr->x = initial.x;
188 initial.ptr->y = initial.y;
189 initial.ptr->width = initial.w;
190 initial.ptr->height = initial.h;
191 arrange_windows(initial.ptr, -1, -1);
192 pointer_state.mode = 0;
193}
194
195static void reset_initial_sibling(void) {
196 initial.horiz.ptr->width = initial.horiz.w;
197 initial.horiz.parent.ptr->width = initial.horiz.parent.w;
198 initial.vert.ptr->height = initial.vert.h;
199 initial.vert.parent.ptr->height = initial.vert.parent.h;
200 arrange_windows(initial.horiz.ptr->parent, -1, -1);
201 arrange_windows(initial.vert.ptr->parent, -1, -1);
202 pointer_state.mode = 0;
203}
204
205void pointer_position_set(double new_x, double new_y, bool force_focus) {
206 double x, y;
207 wlc_pointer_get_position_v2(&x, &y);
208 pointer_state.delta.x = new_x - x;
209 pointer_state.delta.y = new_y - y;
210
211 wlc_pointer_set_position_v2(new_x, new_y);
212
213 // Update view under pointer
214 swayc_t *prev_view = pointer_state.view;
215 pointer_state.view = container_under_pointer();
216
217 // If pointer is in a mode, update it
218 if (pointer_state.mode) {
219 pointer_mode_update();
220 // Otherwise change focus if config is set
221 } else if (force_focus || (prev_view != pointer_state.view && config->focus_follows_mouse)) {
222 if (pointer_state.view && pointer_state.view->type == C_VIEW) {
223 set_focused_container(pointer_state.view);
224 }
225 }
226}
227
228void center_pointer_on(swayc_t *view) {
229 pointer_position_set(view->x + view->width/2, view->y + view->height/2, true);
230}
231
232// Mode set left/right click
233
234static void pointer_mode_set_dragging(void) {
235 switch (config->dragging_key) {
236 case M_LEFT_CLICK:
237 set_initial_view(pointer_state.left.view);
238 break;
239 case M_RIGHT_CLICK:
240 set_initial_view(pointer_state.right.view);
241 break;
242 }
243
244 if (initial.ptr->is_floating) {
245 pointer_state.mode = M_DRAGGING | M_FLOATING;
246 } else {
247 pointer_state.mode = M_DRAGGING | M_TILING;
248 // unset mode if we can't drag tile
249 if (initial.ptr->parent->type == C_WORKSPACE &&
250 initial.ptr->parent->children->length == 1) {
251 pointer_state.mode = 0;
252 }
253 }
254}
255
256static void pointer_mode_set_resizing(void) {
257 switch (config->resizing_key) {
258 case M_LEFT_CLICK:
259 set_initial_view(pointer_state.left.view);
260 break;
261 case M_RIGHT_CLICK:
262 set_initial_view(pointer_state.right.view);
263 break;
264 }
265 // Setup locking information
266 int midway_x = initial.ptr->x + initial.ptr->width/2;
267 int midway_y = initial.ptr->y + initial.ptr->height/2;
268
269 double x, y;
270 wlc_pointer_get_position_v2(&x, &y);
271 lock.left = x > midway_x;
272 lock.top = y > midway_y;
273
274 if (initial.ptr->is_floating) {
275 pointer_state.mode = M_RESIZING | M_FLOATING;
276 } else {
277 pointer_state.mode = M_RESIZING | M_TILING;
278 set_initial_sibling();
279 }
280}
281
282// Mode set/update/reset
283
284void pointer_mode_set(uint32_t button, bool condition) {
285 // switch on drag/resize mode
286 switch (pointer_state.mode & (M_DRAGGING | M_RESIZING)) {
287 case M_DRAGGING:
288 // end drag mode when 'dragging' click is unpressed
289 if (config->dragging_key == M_LEFT_CLICK && !pointer_state.left.held) {
290 pointer_state.mode = 0;
291 } else if (config->dragging_key == M_RIGHT_CLICK && !pointer_state.right.held) {
292 pointer_state.mode = 0;
293 }
294 break;
295
296 case M_RESIZING:
297 // end resize mode when 'resizing' click is unpressed
298 if (config->resizing_key == M_LEFT_CLICK && !pointer_state.left.held) {
299 pointer_state.mode = 0;
300 } else if (config->resizing_key == M_RIGHT_CLICK && !pointer_state.right.held) {
301 pointer_state.mode = 0;
302 }
303 break;
304
305 // No mode case
306 default:
307 // return if failed condition, or no view
308 if (!condition || !pointer_state.view) {
309 break;
310 }
311
312 // Set mode depending on current button press
313 switch (button) {
314 // Start left-click mode
315 case M_LEFT_CLICK:
316 // if button release don't do anything
317 if (pointer_state.left.held) {
318 if (config->dragging_key == M_LEFT_CLICK) {
319 pointer_mode_set_dragging();
320 } else if (config->resizing_key == M_LEFT_CLICK) {
321 pointer_mode_set_resizing();
322 }
323 }
324 break;
325
326 // Start right-click mode
327 case M_RIGHT_CLICK:
328 // if button release don't do anyhting
329 if (pointer_state.right.held) {
330 if (config->dragging_key == M_RIGHT_CLICK) {
331 pointer_mode_set_dragging();
332 } else if (config->resizing_key == M_RIGHT_CLICK) {
333 pointer_mode_set_resizing();
334 }
335 }
336 break;
337 }
338 }
339}
340
341void pointer_mode_update(void) {
342 if (initial.ptr->type != C_VIEW) {
343 pointer_state.mode = 0;
344 return;
345 }
346 double x, y;
347 wlc_pointer_get_position_v2(&x, &y);
348 int dx = x;
349 int dy = y;
350
351 switch (pointer_state.mode) {
352 case M_FLOATING | M_DRAGGING:
353 // Update position
354 switch (config->dragging_key) {
355 case M_LEFT_CLICK:
356 dx -= pointer_state.left.x;
357 dy -= pointer_state.left.y;
358 break;
359 case M_RIGHT_CLICK:
360 dx -= pointer_state.right.x;
361 dy -= pointer_state.right.y;
362 break;
363 }
364
365 if (initial.x + dx != initial.ptr->x) {
366 initial.ptr->x = initial.x + dx;
367 }
368 if (initial.y + dy != initial.ptr->y) {
369 initial.ptr->y = initial.y + dy;
370 }
371 update_geometry(initial.ptr);
372 break;
373
374 case M_FLOATING | M_RESIZING:
375 switch (config->resizing_key) {
376 case M_LEFT_CLICK:
377 dx -= pointer_state.left.x;
378 dy -= pointer_state.left.y;
379 initial.ptr = pointer_state.left.view;
380 break;
381 case M_RIGHT_CLICK:
382 dx -= pointer_state.right.x;
383 dy -= pointer_state.right.y;
384 initial.ptr = pointer_state.right.view;
385 break;
386 }
387
388 if (lock.left) {
389 if (initial.w + dx > min_sane_w) {
390 initial.ptr->width = initial.w + dx;
391 }
392 } else { // lock.right
393 if (initial.w - dx > min_sane_w) {
394 initial.ptr->width = initial.w - dx;
395 initial.ptr->x = initial.x + dx;
396 }
397 }
398 if (lock.top) {
399 if (initial.h + dy > min_sane_h) {
400 initial.ptr->height = initial.h + dy;
401 }
402 } else { // lock.bottom
403 if (initial.h - dy > min_sane_h) {
404 initial.ptr->height = initial.h - dy;
405 initial.ptr->y = initial.y + dy;
406 }
407 }
408 update_geometry(initial.ptr);
409 break;
410
411 case M_TILING | M_DRAGGING:
412 // swap current view under pointer with dragged view
413 if (pointer_state.view && pointer_state.view->type == C_VIEW
414 && pointer_state.view != initial.ptr
415 && !pointer_state.view->is_floating) {
416 // Swap them around
417 swap_container(pointer_state.view, initial.ptr);
418 swap_geometry(pointer_state.view, initial.ptr);
419 update_geometry(pointer_state.view);
420 update_geometry(initial.ptr);
421 // Set focus back to initial view
422 set_focused_container(initial.ptr);
423 // Arrange the windows
424 arrange_windows(&root_container, -1, -1);
425 }
426 break;
427
428 case M_TILING | M_RESIZING:
429 switch (config->resizing_key) {
430 case M_LEFT_CLICK:
431 dx -= pointer_state.left.x;
432 dy -= pointer_state.left.y;
433 break;
434 case M_RIGHT_CLICK:
435 dx -= pointer_state.right.x;
436 dy -= pointer_state.right.y;
437 break;
438 }
439
440 // resize if we can
441 if (initial.horiz.ptr) {
442 if (lock.left) {
443 // Check whether its fine to resize
444 if (initial.w + dx > min_sane_w && initial.horiz.w - dx > min_sane_w) {
445 initial.horiz.ptr->width = initial.horiz.w - dx;
446 initial.horiz.parent.ptr->width = initial.horiz.parent.w + dx;
447 }
448 } else { // lock.right
449 if (initial.w - dx > min_sane_w && initial.horiz.w + dx > min_sane_w) {
450 initial.horiz.ptr->width = initial.horiz.w + dx;
451 initial.horiz.parent.ptr->width = initial.horiz.parent.w - dx;
452 }
453 }
454 arrange_windows(initial.horiz.ptr->parent, -1, -1);
455 }
456 if (initial.vert.ptr) {
457 if (lock.top) {
458 if (initial.h + dy > min_sane_h && initial.vert.h - dy > min_sane_h) {
459 initial.vert.ptr->height = initial.vert.h - dy;
460 initial.vert.parent.ptr->height = initial.vert.parent.h + dy;
461 }
462 } else { // lock.bottom
463 if (initial.h - dy > min_sane_h && initial.vert.h + dy > min_sane_h) {
464 initial.vert.ptr->height = initial.vert.h + dy;
465 initial.vert.parent.ptr->height = initial.vert.parent.h - dy;
466 }
467 }
468 arrange_windows(initial.vert.ptr->parent, -1, -1);
469 }
470 default:
471 return;
472 }
473}
474
475void pointer_mode_reset(void) {
476 switch (pointer_state.mode) {
477 case M_FLOATING | M_RESIZING:
478 case M_FLOATING | M_DRAGGING:
479 reset_initial_view();
480 break;
481
482 case M_TILING | M_RESIZING:
483 (void) reset_initial_sibling;
484 break;
485
486 case M_TILING | M_DRAGGING:
487 default:
488 break;
489 }
490}
diff --git a/sway/ipc-json.c b/sway/ipc-json.c
index 6ab63c75..6158fc29 100644
--- a/sway/ipc-json.c
+++ b/sway/ipc-json.c
@@ -1,247 +1,213 @@
1#include <json-c/json.h> 1#include <json-c/json.h>
2#include <stdio.h>
2#include <ctype.h> 3#include <ctype.h>
3#include <string.h> 4#include "log.h"
4#include <stdint.h>
5#include <libinput.h>
6#include "sway/container.h"
7#include "sway/input.h"
8#include "sway/ipc-json.h" 5#include "sway/ipc-json.h"
9#include "util.h" 6#include "sway/tree/container.h"
10 7#include "sway/output.h"
11static json_object *ipc_json_create_rect(swayc_t *c) { 8#include "sway/input/input-manager.h"
12 json_object *rect = json_object_new_object(); 9#include "sway/input/seat.h"
10#include <wlr/types/wlr_box.h>
11#include <wlr/types/wlr_output.h>
12#include "wlr-layer-shell-unstable-v1-protocol.h"
13
14static const char *ipc_json_layout_description(enum sway_container_layout l) {
15 switch (l) {
16 case L_VERT:
17 return "splitv";
18 case L_HORIZ:
19 return "splith";
20 case L_TABBED:
21 return "tabbed";
22 case L_STACKED:
23 return "stacked";
24 case L_FLOATING:
25 return "floating";
26 case L_NONE:
27 break;
28 }
29 return "none";
30}
13 31
14 json_object_object_add(rect, "x", json_object_new_int((int32_t)c->x)); 32json_object *ipc_json_get_version() {
15 json_object_object_add(rect, "y", json_object_new_int((int32_t)c->y)); 33 int major = 0, minor = 0, patch = 0;
34 json_object *version = json_object_new_object();
16 35
17 struct wlc_size size; 36 sscanf(SWAY_VERSION, "%u.%u.%u", &major, &minor, &patch);
18 if (c->type == C_OUTPUT) {
19 size = *wlc_output_get_resolution(c->handle);
20 } else {
21 size.w = c->width;
22 size.h = c->height;
23 }
24 37
25 json_object_object_add(rect, "width", json_object_new_int((int32_t)size.w)); 38 json_object_object_add(version, "human_readable", json_object_new_string(SWAY_VERSION));
26 json_object_object_add(rect, "height", json_object_new_int((int32_t)size.h)); 39 json_object_object_add(version, "variant", json_object_new_string("sway"));
40 json_object_object_add(version, "major", json_object_new_int(major));
41 json_object_object_add(version, "minor", json_object_new_int(minor));
42 json_object_object_add(version, "patch", json_object_new_int(patch));
27 43
28 return rect; 44 return version;
29} 45}
30 46
31static json_object *ipc_json_create_rect_from_geometry(struct wlc_geometry g) { 47static json_object *ipc_json_create_rect(struct sway_container *c) {
32 json_object *rect = json_object_new_object(); 48 json_object *rect = json_object_new_object();
33 49
34 json_object_object_add(rect, "x", json_object_new_int(g.origin.x)); 50 json_object_object_add(rect, "x", json_object_new_int((int32_t)c->x));
35 json_object_object_add(rect, "y", json_object_new_int(g.origin.y)); 51 json_object_object_add(rect, "y", json_object_new_int((int32_t)c->y));
36 json_object_object_add(rect, "width", json_object_new_int(g.size.w)); 52 json_object_object_add(rect, "width", json_object_new_int((int32_t)c->width));
37 json_object_object_add(rect, "height", json_object_new_int(g.size.h)); 53 json_object_object_add(rect, "height", json_object_new_int((int32_t)c->height));
38 54
39 return rect; 55 return rect;
40} 56}
41 57
42static const char *ipc_json_border_description(swayc_t *c) { 58static void ipc_json_describe_root(struct sway_container *root, json_object *object) {
43 const char *border; 59 json_object_object_add(object, "type", json_object_new_string("root"));
44 60 json_object_object_add(object, "layout", json_object_new_string("splith"));
45 switch (c->border_type) {
46 case B_PIXEL:
47 border = "1pixel";
48 break;
49
50 case B_NORMAL:
51 border = "normal";
52 break;
53
54 case B_NONE: // fallthrough
55 default:
56 border = "none";
57 break;
58 }
59
60 return border;
61} 61}
62 62
63static const char *ipc_json_layout_description(enum swayc_layouts l) { 63static const char *ipc_json_get_output_transform(enum wl_output_transform transform) {
64 const char *layout; 64 switch (transform) {
65 65 case WL_OUTPUT_TRANSFORM_NORMAL:
66 switch (l) { 66 return "normal";
67 case L_VERT: 67 case WL_OUTPUT_TRANSFORM_90:
68 layout = "splitv"; 68 return "90";
69 break; 69 case WL_OUTPUT_TRANSFORM_180:
70 70 return "180";
71 case L_HORIZ: 71 case WL_OUTPUT_TRANSFORM_270:
72 layout = "splith"; 72 return "270";
73 break; 73 case WL_OUTPUT_TRANSFORM_FLIPPED:
74 74 return "flipped";
75 case L_TABBED: 75 case WL_OUTPUT_TRANSFORM_FLIPPED_90:
76 layout = "tabbed"; 76 return "flipped-90";
77 break; 77 case WL_OUTPUT_TRANSFORM_FLIPPED_180:
78 78 return "flipped-180";
79 case L_STACKED: 79 case WL_OUTPUT_TRANSFORM_FLIPPED_270:
80 layout = "stacked"; 80 return "flipped-270";
81 break;
82
83 case L_FLOATING:
84 layout = "floating";
85 break;
86
87 case L_NONE: // fallthrough
88 case L_LAYOUTS: // fallthrough; this should never happen, I'm just trying to silence compiler warnings
89 default:
90 layout = "null";
91 break;
92 } 81 }
93 82 return NULL;
94 return layout;
95} 83}
96 84
97static float ipc_json_child_percentage(swayc_t *c) { 85static void ipc_json_describe_output(struct sway_container *container, json_object *object) {
98 float percent = 0; 86 struct wlr_output *wlr_output = container->sway_output->wlr_output;
99 swayc_t *parent = c->parent; 87 json_object_object_add(object, "type",
100 88 json_object_new_string("output"));
101 if (parent) { 89 json_object_object_add(object, "active",
102 switch (parent->layout) { 90 json_object_new_boolean(true));
103 case L_VERT: 91 json_object_object_add(object, "primary",
104 percent = c->height / parent->height; 92 json_object_new_boolean(false));
105 break; 93 json_object_object_add(object, "layout",
106 94 json_object_new_string("output"));
107 case L_HORIZ: 95 json_object_object_add(object, "make",
108 percent = c->width / parent->width; 96 json_object_new_string(wlr_output->make));
109 break; 97 json_object_object_add(object, "model",
110 98 json_object_new_string(wlr_output->model));
111 case L_STACKED: // fallthrough 99 json_object_object_add(object, "serial",
112 case L_TABBED: // fallthrough 100 json_object_new_string(wlr_output->serial));
113 percent = 1.0; 101 json_object_object_add(object, "scale",
114 break; 102 json_object_new_double(wlr_output->scale));
115 103 json_object_object_add(object, "refresh",
116 default: 104 json_object_new_int(wlr_output->refresh));
117 break; 105 json_object_object_add(object, "transform",
106 json_object_new_string(
107 ipc_json_get_output_transform(wlr_output->transform)));
108
109 struct sway_seat *seat = input_manager_get_default_seat(input_manager);
110 const char *ws = NULL;
111 if (seat) {
112 struct sway_container *focus =
113 seat_get_focus_inactive(seat, container);
114 if (focus && focus->type != C_WORKSPACE) {
115 focus = container_parent(focus, C_WORKSPACE);
116 }
117 if (focus) {
118 ws = focus->name;
118 } 119 }
119 } 120 }
121 json_object_object_add(object, "current_workspace",
122 json_object_new_string(ws));
123
124 json_object *modes_array = json_object_new_array();
125 struct wlr_output_mode *mode;
126 wl_list_for_each(mode, &wlr_output->modes, link) {
127 json_object *mode_object = json_object_new_object();
128 json_object_object_add(mode_object, "width",
129 json_object_new_int(mode->width));
130 json_object_object_add(mode_object, "height",
131 json_object_new_int(mode->height));
132 json_object_object_add(mode_object, "refresh",
133 json_object_new_int(mode->refresh));
134 json_object_array_add(modes_array, mode_object);
135 }
120 136
121 return percent; 137 json_object_object_add(object, "modes", modes_array);
122}
123
124static void ipc_json_describe_output(swayc_t *output, json_object *object) {
125 uint32_t scale = wlc_output_get_scale(output->handle);
126 json_object_object_add(object, "active", json_object_new_boolean(true));
127 json_object_object_add(object, "primary", json_object_new_boolean(false));
128 json_object_object_add(object, "layout", json_object_new_string("output")); 138 json_object_object_add(object, "layout", json_object_new_string("output"));
129 json_object_object_add(object, "type", json_object_new_string("output"));
130 json_object_object_add(object, "current_workspace",
131 (output->focused) ? json_object_new_string(output->focused->name) : NULL);
132 json_object_object_add(object, "scale", json_object_new_int(scale));
133} 139}
134 140
135static void ipc_json_describe_workspace(swayc_t *workspace, json_object *object) { 141static void ipc_json_describe_workspace(struct sway_container *workspace,
136 int num = (isdigit(workspace->name[0])) ? atoi(workspace->name) : -1; 142 json_object *object) {
137 const char *layout = ipc_json_layout_description(workspace->workspace_layout); 143 int num = isdigit(workspace->name[0]) ? atoi(workspace->name) : -1;
138 144
139 json_object_object_add(object, "num", json_object_new_int(num)); 145 json_object_object_add(object, "num", json_object_new_int(num));
140 json_object_object_add(object, "output", (workspace->parent) ? json_object_new_string(workspace->parent->name) : NULL); 146 json_object_object_add(object, "output", workspace->parent ?
147 json_object_new_string(workspace->parent->name) : NULL);
141 json_object_object_add(object, "type", json_object_new_string("workspace")); 148 json_object_object_add(object, "type", json_object_new_string("workspace"));
142 json_object_object_add(object, "layout", (strcmp(layout, "null") == 0) ? NULL : json_object_new_string(layout)); 149 json_object_object_add(object, "urgent", json_object_new_boolean(false));
143}
144 150
145// window is in the scratchpad ? changed : none 151 const char *layout = ipc_json_layout_description(workspace->workspace_layout);
146static const char *ipc_json_get_scratchpad_state(swayc_t *c) { 152 json_object_object_add(object, "layout", json_object_new_string(layout));
147 int i;
148 for (i = 0; i < scratchpad->length; i++) {
149 if (scratchpad->items[i] == c) {
150 return "changed";
151 }
152 }
153 return "none"; // we ignore the fresh value
154} 153}
155 154
156static void ipc_json_describe_view(swayc_t *c, json_object *object) { 155static void ipc_json_describe_view(struct sway_container *c, json_object *object) {
157 json_object *props = json_object_new_object(); 156 json_object_object_add(object, "name",
158 json_object_object_add(object, "type", json_object_new_string((c->is_floating) ? "floating_con" : "con")); 157 c->name ? json_object_new_string(c->name) : NULL);
159 158 json_object_object_add(object, "type", json_object_new_string("con"));
160 wlc_handle parent = wlc_view_get_parent(c->handle);
161 json_object_object_add(object, "scratchpad_state",
162 json_object_new_string(ipc_json_get_scratchpad_state(c)));
163
164 json_object_object_add(object, "name", (c->name) ? json_object_new_string(c->name) : NULL);
165
166 json_object_object_add(props, "class", c->class ? json_object_new_string(c->class) :
167 c->app_id ? json_object_new_string(c->app_id) : NULL);
168 json_object_object_add(props, "instance", c->instance ? json_object_new_string(c->instance) :
169 c->app_id ? json_object_new_string(c->app_id) : NULL);
170 json_object_object_add(props, "title", (c->name) ? json_object_new_string(c->name) : NULL);
171 json_object_object_add(props, "transient_for", parent ? json_object_new_int(parent) : NULL);
172 json_object_object_add(object, "window_properties", props);
173
174 json_object_object_add(object, "fullscreen_mode",
175 json_object_new_int(swayc_is_fullscreen(c) ? 1 : 0));
176 json_object_object_add(object, "sticky", json_object_new_boolean(c->sticky));
177 json_object_object_add(object, "floating", json_object_new_string(
178 c->is_floating ? "auto_on" : "auto_off")); // we can't state the cause
179
180 json_object_object_add(object, "app_id", c->app_id ? json_object_new_string(c->app_id) : NULL);
181 159
182 if (c->parent) { 160 if (c->parent) {
183 const char *layout = (c->parent->type == C_CONTAINER) ? 161 enum sway_container_layout layout = (c->parent->type == C_CONTAINER) ?
184 ipc_json_layout_description(c->parent->layout) : "none"; 162 c->parent->layout : c->layout;
185 const char *last_layout = (c->parent->type == C_CONTAINER) ? 163
186 ipc_json_layout_description(c->parent->prev_layout) : "none";
187 json_object_object_add(object, "layout", 164 json_object_object_add(object, "layout",
188 (strcmp(layout, "null") == 0) ? NULL : json_object_new_string(layout)); 165 json_object_new_string(ipc_json_layout_description(layout)));
189 json_object_object_add(object, "last_split_layout",
190 (strcmp(last_layout, "null") == 0) ? NULL : json_object_new_string(last_layout));
191 json_object_object_add(object, "workspace_layout",
192 json_object_new_string(ipc_json_layout_description(swayc_parent_by_type(c, C_WORKSPACE)->workspace_layout)));
193 } 166 }
194} 167}
195 168
196static void ipc_json_describe_root(swayc_t *c, json_object *object) { 169static void focus_inactive_children_iterator(struct sway_container *c, void *data) {
197 json_object_object_add(object, "type", json_object_new_string("root")); 170 json_object *focus = data;
198 json_object_object_add(object, "layout", json_object_new_string("splith")); 171 json_object_array_add(focus, json_object_new_int(c->id));
199} 172}
200 173
201json_object *ipc_json_describe_container(swayc_t *c) { 174json_object *ipc_json_describe_container(struct sway_container *c) {
202 float percent = ipc_json_child_percentage(c);
203
204 if (!(sway_assert(c, "Container must not be null."))) { 175 if (!(sway_assert(c, "Container must not be null."))) {
205 return NULL; 176 return NULL;
206 } 177 }
207 178
179 struct sway_seat *seat = input_manager_get_default_seat(input_manager);
180 bool focused = seat_get_focus(seat) == c;
181
208 json_object *object = json_object_new_object(); 182 json_object *object = json_object_new_object();
209 183
210 json_object_object_add(object, "id", json_object_new_int((int)c->id)); 184 json_object_object_add(object, "id", json_object_new_int((int)c->id));
211 json_object_object_add(object, "name", (c->name) ? json_object_new_string(c->name) : NULL); 185 json_object_object_add(object, "name",
186 c->name ? json_object_new_string(c->name) : NULL);
212 json_object_object_add(object, "rect", ipc_json_create_rect(c)); 187 json_object_object_add(object, "rect", ipc_json_create_rect(c));
213 json_object_object_add(object, "visible", json_object_new_boolean(c->visible)); 188 json_object_object_add(object, "focused",
214 json_object_object_add(object, "focused", json_object_new_boolean(c == current_focus)); 189 json_object_new_boolean(focused));
215 190
216 json_object_object_add(object, "border", json_object_new_string(ipc_json_border_description(c))); 191 json_object *focus = json_object_new_array();
217 json_object_object_add(object, "window_rect", ipc_json_create_rect_from_geometry(c->actual_geometry)); 192 seat_focus_inactive_children_for_each(seat, c,
218 json_object_object_add(object, "deco_rect", ipc_json_create_rect_from_geometry(c->title_bar_geometry)); 193 focus_inactive_children_iterator, focus);
219 json_object_object_add(object, "geometry", ipc_json_create_rect_from_geometry(c->cached_geometry)); 194 json_object_object_add(object, "focus", focus);
220 json_object_object_add(object, "percent", (percent > 0) ? json_object_new_double(percent) : NULL);
221 json_object_object_add(object, "window", json_object_new_int(c->handle)); // for the sake of i3 compat
222 // TODO: make urgency actually work once Sway supports it
223 json_object_object_add(object, "urgent", json_object_new_boolean(false));
224 json_object_object_add(object, "current_border_width", json_object_new_int(c->border_thickness));
225 195
226 switch (c->type) { 196 switch (c->type) {
227 case C_ROOT: 197 case C_ROOT:
228 ipc_json_describe_root(c, object); 198 ipc_json_describe_root(c, object);
229 break; 199 break;
230
231 case C_OUTPUT: 200 case C_OUTPUT:
232 ipc_json_describe_output(c, object); 201 ipc_json_describe_output(c, object);
233 break; 202 break;
234 203 case C_CONTAINER:
235 case C_CONTAINER: // fallthrough
236 case C_VIEW: 204 case C_VIEW:
237 ipc_json_describe_view(c, object); 205 ipc_json_describe_view(c, object);
238 break; 206 break;
239
240 case C_WORKSPACE: 207 case C_WORKSPACE:
241 ipc_json_describe_workspace(c, object); 208 ipc_json_describe_workspace(c, object);
242 break; 209 break;
243 210 case C_TYPES:
244 case C_TYPES: // fallthrough; this should never happen, I'm just trying to silence compiler warnings
245 default: 211 default:
246 break; 212 break;
247 } 213 }
@@ -249,80 +215,56 @@ json_object *ipc_json_describe_container(swayc_t *c) {
249 return object; 215 return object;
250} 216}
251 217
252json_object *ipc_json_describe_input(struct libinput_device *device) { 218json_object *ipc_json_describe_container_recursive(struct sway_container *c) {
253 char* identifier = libinput_dev_unique_id(device); 219 json_object *object = ipc_json_describe_container(c);
254 int vendor = libinput_device_get_id_vendor(device); 220 int i;
255 int product = libinput_device_get_id_product(device);
256 const char *name = libinput_device_get_name(device);
257 double width = -1, height = -1;
258 int has_size = libinput_device_get_size(device, &width, &height);
259
260 json_object *device_object = json_object_new_object();
261 json_object_object_add(device_object,"identifier",
262 identifier ? json_object_new_string(identifier) : NULL);
263 json_object_object_add(device_object,
264 "vendor", json_object_new_int(vendor));
265 json_object_object_add(device_object,
266 "product", json_object_new_int(product));
267 json_object_object_add(device_object,
268 "name", json_object_new_string(name));
269 if (has_size == 0) {
270 json_object *size_object = json_object_new_object();
271 json_object_object_add(size_object,
272 "width", json_object_new_double(width));
273 json_object_object_add(size_object,
274 "height", json_object_new_double(height));
275 } else {
276 json_object_object_add(device_object, "size", NULL);
277 }
278 221
279 struct { 222 json_object *children = json_object_new_array();
280 enum libinput_device_capability cap; 223 if (c->type != C_VIEW && c->children) {
281 const char *name; 224 for (i = 0; i < c->children->length; ++i) {
282 // If anyone feels like implementing device-specific IPC output, 225 json_object_array_add(children, ipc_json_describe_container_recursive(c->children->items[i]));
283 // be my guest
284 json_object *(*describe)(struct libinput_device *);
285 } caps[] = {
286 { LIBINPUT_DEVICE_CAP_KEYBOARD, "keyboard", NULL },
287 { LIBINPUT_DEVICE_CAP_POINTER, "pointer", NULL },
288 { LIBINPUT_DEVICE_CAP_TOUCH, "touch", NULL },
289 { LIBINPUT_DEVICE_CAP_TABLET_TOOL, "tablet_tool", NULL },
290 { LIBINPUT_DEVICE_CAP_TABLET_PAD, "tablet_pad", NULL },
291 { LIBINPUT_DEVICE_CAP_GESTURE, "gesture", NULL },
292#ifdef LIBINPUT_DEVICE_CAP_SWITCH // libinput 1.7.0+
293 { LIBINPUT_DEVICE_CAP_SWITCH, "switch", NULL },
294#endif
295 };
296
297 json_object *_caps = json_object_new_array();
298 for (size_t i = 0; i < sizeof(caps) / sizeof(caps[0]); ++i) {
299 if (libinput_device_has_capability(device, caps[i].cap)) {
300 json_object_array_add(_caps, json_object_new_string(caps[i].name));
301 if (caps[i].describe) {
302 json_object *desc = caps[i].describe(device);
303 json_object_object_add(device_object, caps[i].name, desc);
304 }
305 } 226 }
306 } 227 }
307 json_object_object_add(device_object, "capabilities", _caps); 228 json_object_object_add(object, "nodes", children);
308 229
309 free(identifier); 230 return object;
310 return device_object;
311} 231}
312 232
313json_object *ipc_json_get_version() { 233static const char *describe_device_type(struct sway_input_device *device) {
314 int major = 0, minor = 0, patch = 0; 234 switch (device->wlr_device->type) {
315 json_object *version = json_object_new_object(); 235 case WLR_INPUT_DEVICE_POINTER:
236 return "pointer";
237 case WLR_INPUT_DEVICE_KEYBOARD:
238 return "keyboard";
239 case WLR_INPUT_DEVICE_TOUCH:
240 return "touch";
241 case WLR_INPUT_DEVICE_TABLET_TOOL:
242 return "tablet_tool";
243 case WLR_INPUT_DEVICE_TABLET_PAD:
244 return "tablet_pad";
245 }
246 return "unknown";
247}
316 248
317 sscanf(SWAY_VERSION, "%u.%u.%u", &major, &minor, &patch); 249json_object *ipc_json_describe_input(struct sway_input_device *device) {
250 if (!(sway_assert(device, "Device must not be null"))) {
251 return NULL;
252 }
318 253
319 json_object_object_add(version, "human_readable", json_object_new_string(SWAY_VERSION)); 254 json_object *object = json_object_new_object();
320 json_object_object_add(version, "variant", json_object_new_string("sway"));
321 json_object_object_add(version, "major", json_object_new_int(major));
322 json_object_object_add(version, "minor", json_object_new_int(minor));
323 json_object_object_add(version, "patch", json_object_new_int(patch));
324 255
325 return version; 256 json_object_object_add(object, "identifier",
257 json_object_new_string(device->identifier));
258 json_object_object_add(object, "name",
259 json_object_new_string(device->wlr_device->name));
260 json_object_object_add(object, "vendor",
261 json_object_new_int(device->wlr_device->vendor));
262 json_object_object_add(object, "product",
263 json_object_new_int(device->wlr_device->product));
264 json_object_object_add(object, "type",
265 json_object_new_string(describe_device_type(device)));
266
267 return object;
326} 268}
327 269
328json_object *ipc_json_describe_bar_config(struct bar_config *bar) { 270json_object *ipc_json_describe_bar_config(struct bar_config *bar) {
@@ -332,107 +274,116 @@ json_object *ipc_json_describe_bar_config(struct bar_config *bar) {
332 274
333 json_object *json = json_object_new_object(); 275 json_object *json = json_object_new_object();
334 json_object_object_add(json, "id", json_object_new_string(bar->id)); 276 json_object_object_add(json, "id", json_object_new_string(bar->id));
335#ifdef ENABLE_TRAY
336 if (bar->tray_output) {
337 json_object_object_add(json, "tray_output", json_object_new_string(bar->tray_output));
338 } else {
339 json_object_object_add(json, "tray_output", NULL);
340 }
341 if (bar->icon_theme) {
342 json_object_object_add(json, "icon_theme", json_object_new_string(bar->icon_theme));
343 } else {
344 json_object_object_add(json, "icon_theme", NULL);
345 }
346 json_object_object_add(json, "tray_padding", json_object_new_int(bar->tray_padding));
347 json_object_object_add(json, "activate_button", json_object_new_int(bar->activate_button));
348 json_object_object_add(json, "context_button", json_object_new_int(bar->context_button));
349 json_object_object_add(json, "secondary_button", json_object_new_int(bar->secondary_button));
350#endif
351 json_object_object_add(json, "mode", json_object_new_string(bar->mode)); 277 json_object_object_add(json, "mode", json_object_new_string(bar->mode));
352 json_object_object_add(json, "hidden_state", json_object_new_string(bar->hidden_state)); 278 json_object_object_add(json, "hidden_state",
353 json_object_object_add(json, "modifier", json_object_new_string(get_modifier_name_by_mask(bar->modifier))); 279 json_object_new_string(bar->hidden_state));
354 switch (bar->position) { 280 json_object_object_add(json, "position",
355 case DESKTOP_SHELL_PANEL_POSITION_TOP: 281 json_object_new_string(bar->position));
356 json_object_object_add(json, "position", json_object_new_string("top")); 282 json_object_object_add(json, "status_command",
357 break; 283 json_object_new_string(bar->status_command));
358 case DESKTOP_SHELL_PANEL_POSITION_BOTTOM: 284 json_object_object_add(json, "font",
359 json_object_object_add(json, "position", json_object_new_string("bottom")); 285 json_object_new_string((bar->font) ? bar->font : config->font));
360 break;
361 case DESKTOP_SHELL_PANEL_POSITION_LEFT:
362 json_object_object_add(json, "position", json_object_new_string("left"));
363 break;
364 case DESKTOP_SHELL_PANEL_POSITION_RIGHT:
365 json_object_object_add(json, "position", json_object_new_string("right"));
366 break;
367 }
368 json_object_object_add(json, "status_command", json_object_new_string(bar->status_command));
369 json_object_object_add(json, "font", json_object_new_string((bar->font) ? bar->font : config->font));
370 if (bar->separator_symbol) { 286 if (bar->separator_symbol) {
371 json_object_object_add(json, "separator_symbol", json_object_new_string(bar->separator_symbol)); 287 json_object_object_add(json, "separator_symbol",
288 json_object_new_string(bar->separator_symbol));
372 } 289 }
373 json_object_object_add(json, "bar_height", json_object_new_int(bar->height)); 290 json_object_object_add(json, "bar_height",
374 json_object_object_add(json, "wrap_scroll", json_object_new_boolean(bar->wrap_scroll)); 291 json_object_new_int(bar->height));
375 json_object_object_add(json, "workspace_buttons", json_object_new_boolean(bar->workspace_buttons)); 292 json_object_object_add(json, "wrap_scroll",
376 json_object_object_add(json, "strip_workspace_numbers", json_object_new_boolean(bar->strip_workspace_numbers)); 293 json_object_new_boolean(bar->wrap_scroll));
377 json_object_object_add(json, "binding_mode_indicator", json_object_new_boolean(bar->binding_mode_indicator)); 294 json_object_object_add(json, "workspace_buttons",
378 json_object_object_add(json, "verbose", json_object_new_boolean(bar->verbose)); 295 json_object_new_boolean(bar->workspace_buttons));
379 json_object_object_add(json, "pango_markup", json_object_new_boolean(bar->pango_markup)); 296 json_object_object_add(json, "strip_workspace_numbers",
297 json_object_new_boolean(bar->strip_workspace_numbers));
298 json_object_object_add(json, "binding_mode_indicator",
299 json_object_new_boolean(bar->binding_mode_indicator));
300 json_object_object_add(json, "verbose",
301 json_object_new_boolean(bar->verbose));
302 json_object_object_add(json, "pango_markup",
303 json_object_new_boolean(bar->pango_markup));
380 304
381 json_object *colors = json_object_new_object(); 305 json_object *colors = json_object_new_object();
382 json_object_object_add(colors, "background", json_object_new_string(bar->colors.background)); 306 json_object_object_add(colors, "background",
383 json_object_object_add(colors, "statusline", json_object_new_string(bar->colors.statusline)); 307 json_object_new_string(bar->colors.background));
384 json_object_object_add(colors, "separator", json_object_new_string(bar->colors.separator)); 308 json_object_object_add(colors, "statusline",
309 json_object_new_string(bar->colors.statusline));
310 json_object_object_add(colors, "separator",
311 json_object_new_string(bar->colors.separator));
385 312
386 if (bar->colors.focused_background) { 313 if (bar->colors.focused_background) {
387 json_object_object_add(colors, "focused_background", json_object_new_string(bar->colors.focused_background)); 314 json_object_object_add(colors, "focused_background",
315 json_object_new_string(bar->colors.focused_background));
388 } else { 316 } else {
389 json_object_object_add(colors, "focused_background", json_object_new_string(bar->colors.background)); 317 json_object_object_add(colors, "focused_background",
318 json_object_new_string(bar->colors.background));
390 } 319 }
391 320
392 if (bar->colors.focused_statusline) { 321 if (bar->colors.focused_statusline) {
393 json_object_object_add(colors, "focused_statusline", json_object_new_string(bar->colors.focused_statusline)); 322 json_object_object_add(colors, "focused_statusline",
323 json_object_new_string(bar->colors.focused_statusline));
394 } else { 324 } else {
395 json_object_object_add(colors, "focused_statusline", json_object_new_string(bar->colors.statusline)); 325 json_object_object_add(colors, "focused_statusline",
326 json_object_new_string(bar->colors.statusline));
396 } 327 }
397 328
398 if (bar->colors.focused_separator) { 329 if (bar->colors.focused_separator) {
399 json_object_object_add(colors, "focused_separator", json_object_new_string(bar->colors.focused_separator)); 330 json_object_object_add(colors, "focused_separator",
331 json_object_new_string(bar->colors.focused_separator));
400 } else { 332 } else {
401 json_object_object_add(colors, "focused_separator", json_object_new_string(bar->colors.separator)); 333 json_object_object_add(colors, "focused_separator",
334 json_object_new_string(bar->colors.separator));
402 } 335 }
403 336
404 json_object_object_add(colors, "focused_workspace_border", json_object_new_string(bar->colors.focused_workspace_border)); 337 json_object_object_add(colors, "focused_workspace_border",
405 json_object_object_add(colors, "focused_workspace_bg", json_object_new_string(bar->colors.focused_workspace_bg)); 338 json_object_new_string(bar->colors.focused_workspace_border));
406 json_object_object_add(colors, "focused_workspace_text", json_object_new_string(bar->colors.focused_workspace_text)); 339 json_object_object_add(colors, "focused_workspace_bg",
407 340 json_object_new_string(bar->colors.focused_workspace_bg));
408 json_object_object_add(colors, "inactive_workspace_border", json_object_new_string(bar->colors.inactive_workspace_border)); 341 json_object_object_add(colors, "focused_workspace_text",
409 json_object_object_add(colors, "inactive_workspace_bg", json_object_new_string(bar->colors.inactive_workspace_bg)); 342 json_object_new_string(bar->colors.focused_workspace_text));
410 json_object_object_add(colors, "inactive_workspace_text", json_object_new_string(bar->colors.inactive_workspace_text)); 343
411 344 json_object_object_add(colors, "inactive_workspace_border",
412 json_object_object_add(colors, "active_workspace_border", json_object_new_string(bar->colors.active_workspace_border)); 345 json_object_new_string(bar->colors.inactive_workspace_border));
413 json_object_object_add(colors, "active_workspace_bg", json_object_new_string(bar->colors.active_workspace_bg)); 346 json_object_object_add(colors, "inactive_workspace_bg",
414 json_object_object_add(colors, "active_workspace_text", json_object_new_string(bar->colors.active_workspace_text)); 347 json_object_new_string(bar->colors.inactive_workspace_bg));
415 348 json_object_object_add(colors, "inactive_workspace_text",
416 json_object_object_add(colors, "urgent_workspace_border", json_object_new_string(bar->colors.urgent_workspace_border)); 349 json_object_new_string(bar->colors.inactive_workspace_text));
417 json_object_object_add(colors, "urgent_workspace_bg", json_object_new_string(bar->colors.urgent_workspace_bg)); 350
418 json_object_object_add(colors, "urgent_workspace_text", json_object_new_string(bar->colors.urgent_workspace_text)); 351 json_object_object_add(colors, "active_workspace_border",
352 json_object_new_string(bar->colors.active_workspace_border));
353 json_object_object_add(colors, "active_workspace_bg",
354 json_object_new_string(bar->colors.active_workspace_bg));
355 json_object_object_add(colors, "active_workspace_text",
356 json_object_new_string(bar->colors.active_workspace_text));
357
358 json_object_object_add(colors, "urgent_workspace_border",
359 json_object_new_string(bar->colors.urgent_workspace_border));
360 json_object_object_add(colors, "urgent_workspace_bg",
361 json_object_new_string(bar->colors.urgent_workspace_bg));
362 json_object_object_add(colors, "urgent_workspace_text",
363 json_object_new_string(bar->colors.urgent_workspace_text));
419 364
420 if (bar->colors.binding_mode_border) { 365 if (bar->colors.binding_mode_border) {
421 json_object_object_add(colors, "binding_mode_border", json_object_new_string(bar->colors.binding_mode_border)); 366 json_object_object_add(colors, "binding_mode_border",
367 json_object_new_string(bar->colors.binding_mode_border));
422 } else { 368 } else {
423 json_object_object_add(colors, "binding_mode_border", json_object_new_string(bar->colors.urgent_workspace_border)); 369 json_object_object_add(colors, "binding_mode_border",
370 json_object_new_string(bar->colors.urgent_workspace_border));
424 } 371 }
425 372
426 if (bar->colors.binding_mode_bg) { 373 if (bar->colors.binding_mode_bg) {
427 json_object_object_add(colors, "binding_mode_bg", json_object_new_string(bar->colors.binding_mode_bg)); 374 json_object_object_add(colors, "binding_mode_bg",
375 json_object_new_string(bar->colors.binding_mode_bg));
428 } else { 376 } else {
429 json_object_object_add(colors, "binding_mode_bg", json_object_new_string(bar->colors.urgent_workspace_bg)); 377 json_object_object_add(colors, "binding_mode_bg",
378 json_object_new_string(bar->colors.urgent_workspace_bg));
430 } 379 }
431 380
432 if (bar->colors.binding_mode_text) { 381 if (bar->colors.binding_mode_text) {
433 json_object_object_add(colors, "binding_mode_text", json_object_new_string(bar->colors.binding_mode_text)); 382 json_object_object_add(colors, "binding_mode_text",
383 json_object_new_string(bar->colors.binding_mode_text));
434 } else { 384 } else {
435 json_object_object_add(colors, "binding_mode_text", json_object_new_string(bar->colors.urgent_workspace_text)); 385 json_object_object_add(colors, "binding_mode_text",
386 json_object_new_string(bar->colors.urgent_workspace_text));
436 } 387 }
437 388
438 json_object_object_add(json, "colors", colors); 389 json_object_object_add(json, "colors", colors);
@@ -440,75 +391,11 @@ json_object *ipc_json_describe_bar_config(struct bar_config *bar) {
440 // Add outputs if defined 391 // Add outputs if defined
441 if (bar->outputs && bar->outputs->length > 0) { 392 if (bar->outputs && bar->outputs->length > 0) {
442 json_object *outputs = json_object_new_array(); 393 json_object *outputs = json_object_new_array();
443 int i; 394 for (int i = 0; i < bar->outputs->length; ++i) {
444 for (i = 0; i < bar->outputs->length; ++i) {
445 const char *name = bar->outputs->items[i]; 395 const char *name = bar->outputs->items[i];
446 json_object_array_add(outputs, json_object_new_string(name)); 396 json_object_array_add(outputs, json_object_new_string(name));
447 } 397 }
448 json_object_object_add(json, "outputs", outputs); 398 json_object_object_add(json, "outputs", outputs);
449 } 399 }
450
451 return json; 400 return json;
452} 401}
453
454json_object *ipc_json_describe_container_recursive(swayc_t *c) {
455 json_object *object = ipc_json_describe_container(c);
456 int i;
457
458 json_object *floating = json_object_new_array();
459 if (c->type != C_VIEW && c->floating) {
460 for (i = 0; i < c->floating->length; ++i) {
461 swayc_t *item = c->floating->items[i];
462 json_object_array_add(floating, ipc_json_describe_container_recursive(item));
463 }
464 }
465 json_object_object_add(object, "floating_nodes", floating);
466
467 json_object *children = json_object_new_array();
468 if (c->type != C_VIEW && c->children) {
469 for (i = 0; i < c->children->length; ++i) {
470 json_object_array_add(children, ipc_json_describe_container_recursive(c->children->items[i]));
471 }
472 }
473 json_object_object_add(object, "nodes", children);
474
475 json_object *focus = json_object_new_array();
476 if (c->type != C_VIEW) {
477 if (c->focused) {
478 json_object_array_add(focus, json_object_new_double(c->focused->id));
479 }
480 if (c->floating) {
481 for (i = 0; i < c->floating->length; ++i) {
482 swayc_t *item = c->floating->items[i];
483 if (item == c->focused) {
484 continue;
485 }
486
487 json_object_array_add(focus, json_object_new_double(item->id));
488 }
489 }
490 if (c->children) {
491 for (i = 0; i < c->children->length; ++i) {
492 swayc_t *item = c->children->items[i];
493 if (item == c->focused) {
494 continue;
495 }
496
497 json_object_array_add(focus, json_object_new_double(item->id));
498 }
499 }
500 }
501 json_object_object_add(object, "focus", focus);
502
503 if (c->type == C_ROOT) {
504 json_object *scratchpad_json = json_object_new_array();
505 if (scratchpad->length > 0) {
506 for (i = 0; i < scratchpad->length; ++i) {
507 json_object_array_add(scratchpad_json, ipc_json_describe_container_recursive(scratchpad->items[i]));
508 }
509 }
510 json_object_object_add(object, "scratchpad", scratchpad_json);
511 }
512
513 return object;
514}
diff --git a/sway/ipc-server.c b/sway/ipc-server.c
index b560b930..045802e1 100644
--- a/sway/ipc-server.c
+++ b/sway/ipc-server.c
@@ -1,51 +1,41 @@
1// See https://i3wm.org/docs/ipc.html for protocol information 1// See https://i3wm.org/docs/ipc.html for protocol information
2
3#ifndef __FreeBSD__ 2#ifndef __FreeBSD__
4// Any value will hide SOCK_CLOEXEC on FreeBSD (__BSD_VISIBLE=0) 3// Any value will hide SOCK_CLOEXEC on FreeBSD (__BSD_VISIBLE=0)
5#define _XOPEN_SOURCE 700 4#define _XOPEN_SOURCE 700
6#endif 5#endif
7 6#include <assert.h>
8#include <errno.h> 7#include <errno.h>
8#include <fcntl.h>
9#include <json-c/json.h>
10#include <stdbool.h>
11#include <stdint.h>
12#include <stdlib.h>
9#include <string.h> 13#include <string.h>
10#include <sys/socket.h> 14#include <sys/socket.h>
15#include <sys/ioctl.h>
11#include <sys/un.h> 16#include <sys/un.h>
12#include <stdbool.h>
13#include <wlc/wlc-render.h>
14#include <unistd.h> 17#include <unistd.h>
15#include <stdlib.h> 18#include <wayland-server.h>
16#include <sys/ioctl.h> 19#include "sway/commands.h"
17#include <fcntl.h>
18#include <json-c/json.h>
19#include <list.h>
20#include <libinput.h>
21#ifdef __linux__
22struct ucred {
23 pid_t pid;
24 uid_t uid;
25 gid_t gid;
26};
27#endif
28#include "sway/ipc-json.h" 20#include "sway/ipc-json.h"
29#include "sway/ipc-server.h" 21#include "sway/ipc-server.h"
30#include "sway/security.h" 22#include "sway/server.h"
31#include "sway/config.h" 23#include "sway/input/input-manager.h"
32#include "sway/commands.h" 24#include "sway/input/seat.h"
33#include "sway/input.h"
34#include "stringop.h"
35#include "log.h"
36#include "list.h" 25#include "list.h"
37#include "util.h" 26#include "log.h"
38 27
39static int ipc_socket = -1; 28static int ipc_socket = -1;
40static struct wlc_event_source *ipc_event_source = NULL; 29static struct wl_event_source *ipc_event_source = NULL;
41static struct sockaddr_un *ipc_sockaddr = NULL; 30static struct sockaddr_un *ipc_sockaddr = NULL;
42static list_t *ipc_client_list = NULL; 31static list_t *ipc_client_list = NULL;
43 32
44static const char ipc_magic[] = {'i', '3', '-', 'i', 'p', 'c'}; 33static const char ipc_magic[] = {'i', '3', '-', 'i', 'p', 'c'};
45 34
46struct ipc_client { 35struct ipc_client {
47 struct wlc_event_source *event_source; 36 struct wl_event_source *event_source;
48 struct wlc_event_source *writable_event_source; 37 struct wl_event_source *writable_event_source;
38 struct sway_server *server;
49 int fd; 39 int fd;
50 uint32_t payload_length; 40 uint32_t payload_length;
51 uint32_t security_policy; 41 uint32_t security_policy;
@@ -56,27 +46,6 @@ struct ipc_client {
56 char *write_buffer; 46 char *write_buffer;
57}; 47};
58 48
59static list_t *ipc_get_pixel_requests = NULL;
60
61struct get_pixels_request {
62 struct ipc_client *client;
63 wlc_handle output;
64 struct wlc_geometry geo;
65};
66
67struct get_clipboard_request {
68 struct ipc_client *client;
69 json_object *json;
70 int fd;
71 struct wlc_event_source *fd_event_source;
72 struct wlc_event_source *timer_event_source;
73 char *type;
74 unsigned int *pending;
75 char *buf;
76 size_t buf_size;
77 size_t buf_position;
78};
79
80struct sockaddr_un *ipc_user_sockaddr(void); 49struct sockaddr_un *ipc_user_sockaddr(void);
81int ipc_handle_connection(int fd, uint32_t mask, void *data); 50int ipc_handle_connection(int fd, uint32_t mask, void *data);
82int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data); 51int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data);
@@ -84,11 +53,8 @@ int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data);
84void ipc_client_disconnect(struct ipc_client *client); 53void ipc_client_disconnect(struct ipc_client *client);
85void ipc_client_handle_command(struct ipc_client *client); 54void ipc_client_handle_command(struct ipc_client *client);
86bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t payload_length); 55bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t payload_length);
87void ipc_get_workspaces_callback(swayc_t *workspace, void *data);
88void ipc_get_outputs_callback(swayc_t *container, void *data);
89static void ipc_get_marks_callback(swayc_t *container, void *data);
90 56
91void ipc_init(void) { 57void ipc_init(struct sway_server *server) {
92 ipc_socket = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); 58 ipc_socket = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
93 if (ipc_socket == -1) { 59 if (ipc_socket == -1) {
94 sway_abort("Unable to create IPC socket"); 60 sway_abort("Unable to create IPC socket");
@@ -116,14 +82,14 @@ void ipc_init(void) {
116 setenv("SWAYSOCK", ipc_sockaddr->sun_path, 1); 82 setenv("SWAYSOCK", ipc_sockaddr->sun_path, 1);
117 83
118 ipc_client_list = create_list(); 84 ipc_client_list = create_list();
119 ipc_get_pixel_requests = create_list();
120 85
121 ipc_event_source = wlc_event_loop_add_fd(ipc_socket, WLC_EVENT_READABLE, ipc_handle_connection, NULL); 86 ipc_event_source = wl_event_loop_add_fd(server->wl_event_loop, ipc_socket,
87 WL_EVENT_READABLE, ipc_handle_connection, server);
122} 88}
123 89
124void ipc_terminate(void) { 90void ipc_terminate(void) {
125 if (ipc_event_source) { 91 if (ipc_event_source) {
126 wlc_event_source_remove(ipc_event_source); 92 wl_event_source_remove(ipc_event_source);
127 } 93 }
128 close(ipc_socket); 94 close(ipc_socket);
129 unlink(ipc_sockaddr->sun_path); 95 unlink(ipc_sockaddr->sun_path);
@@ -157,100 +123,82 @@ struct sockaddr_un *ipc_user_sockaddr(void) {
157 return ipc_sockaddr; 123 return ipc_sockaddr;
158} 124}
159 125
160static pid_t get_client_pid(int client_fd) {
161// FreeBSD supports getting uid/gid, but not pid
162#ifdef __linux__
163 struct ucred ucred;
164 socklen_t len = sizeof(struct ucred);
165
166 if (getsockopt(client_fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1) {
167 return -1;
168 }
169
170 return ucred.pid;
171#else
172 return -1;
173#endif
174}
175
176int ipc_handle_connection(int fd, uint32_t mask, void *data) { 126int ipc_handle_connection(int fd, uint32_t mask, void *data) {
177 (void) fd; (void) data; 127 (void) fd;
178 sway_log(L_DEBUG, "Event on IPC listening socket"); 128 struct sway_server *server = data;
179 assert(mask == WLC_EVENT_READABLE); 129 wlr_log(L_DEBUG, "Event on IPC listening socket");
130 assert(mask == WL_EVENT_READABLE);
180 131
181 int client_fd = accept(ipc_socket, NULL, NULL); 132 int client_fd = accept(ipc_socket, NULL, NULL);
182 if (client_fd == -1) { 133 if (client_fd == -1) {
183 sway_log_errno(L_ERROR, "Unable to accept IPC client connection"); 134 wlr_log_errno(L_ERROR, "Unable to accept IPC client connection");
184 return 0; 135 return 0;
185 } 136 }
186 137
187 int flags; 138 int flags;
188 if ((flags = fcntl(client_fd, F_GETFD)) == -1 139 if ((flags = fcntl(client_fd, F_GETFD)) == -1
189 || fcntl(client_fd, F_SETFD, flags|FD_CLOEXEC) == -1) { 140 || fcntl(client_fd, F_SETFD, flags|FD_CLOEXEC) == -1) {
190 sway_log_errno(L_ERROR, "Unable to set CLOEXEC on IPC client socket"); 141 wlr_log_errno(L_ERROR, "Unable to set CLOEXEC on IPC client socket");
191 close(client_fd); 142 close(client_fd);
192 return 0; 143 return 0;
193 } 144 }
194 if ((flags = fcntl(client_fd, F_GETFL)) == -1 145 if ((flags = fcntl(client_fd, F_GETFL)) == -1
195 || fcntl(client_fd, F_SETFL, flags|O_NONBLOCK) == -1) { 146 || fcntl(client_fd, F_SETFL, flags|O_NONBLOCK) == -1) {
196 sway_log_errno(L_ERROR, "Unable to set NONBLOCK on IPC client socket"); 147 wlr_log_errno(L_ERROR, "Unable to set NONBLOCK on IPC client socket");
197 close(client_fd); 148 close(client_fd);
198 return 0; 149 return 0;
199 } 150 }
200 151
201 struct ipc_client* client = malloc(sizeof(struct ipc_client)); 152 struct ipc_client *client = malloc(sizeof(struct ipc_client));
202 if (!client) { 153 if (!client) {
203 sway_log(L_ERROR, "Unable to allocate ipc client"); 154 wlr_log(L_ERROR, "Unable to allocate ipc client");
204 close(client_fd); 155 close(client_fd);
205 return 0; 156 return 0;
206 } 157 }
158 client->server = server;
207 client->payload_length = 0; 159 client->payload_length = 0;
208 client->fd = client_fd; 160 client->fd = client_fd;
209 client->subscribed_events = 0; 161 client->subscribed_events = 0;
210 client->event_source = wlc_event_loop_add_fd(client_fd, WLC_EVENT_READABLE, ipc_client_handle_readable, client); 162 client->event_source = wl_event_loop_add_fd(server->wl_event_loop,
163 client_fd, WL_EVENT_READABLE, ipc_client_handle_readable, client);
211 client->writable_event_source = NULL; 164 client->writable_event_source = NULL;
212 165
213 client->write_buffer_size = 128; 166 client->write_buffer_size = 128;
214 client->write_buffer_len = 0; 167 client->write_buffer_len = 0;
215 client->write_buffer = malloc(client->write_buffer_size); 168 client->write_buffer = malloc(client->write_buffer_size);
216 if (!client->write_buffer) { 169 if (!client->write_buffer) {
217 sway_log(L_ERROR, "Unable to allocate ipc client write buffer"); 170 wlr_log(L_ERROR, "Unable to allocate ipc client write buffer");
218 close(client_fd); 171 close(client_fd);
219 return 0; 172 return 0;
220 } 173 }
221 174
222 pid_t pid = get_client_pid(client->fd); 175 wlr_log(L_DEBUG, "New client: fd %d", client_fd);
223 client->security_policy = get_ipc_policy_mask(pid);
224
225 sway_log(L_DEBUG, "New client: fd %d, pid %d", client_fd, pid);
226
227 list_add(ipc_client_list, client); 176 list_add(ipc_client_list, client);
228
229 return 0; 177 return 0;
230} 178}
231 179
232static const int ipc_header_size = sizeof(ipc_magic)+8; 180static const int ipc_header_size = sizeof(ipc_magic) + 8;
233 181
234int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data) { 182int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data) {
235 struct ipc_client *client = data; 183 struct ipc_client *client = data;
236 184
237 if (mask & WLC_EVENT_ERROR) { 185 if (mask & WL_EVENT_ERROR) {
238 sway_log(L_ERROR, "IPC Client socket error, removing client"); 186 wlr_log(L_ERROR, "IPC Client socket error, removing client");
239 ipc_client_disconnect(client); 187 ipc_client_disconnect(client);
240 return 0; 188 return 0;
241 } 189 }
242 190
243 if (mask & WLC_EVENT_HANGUP) { 191 if (mask & WL_EVENT_HANGUP) {
244 sway_log(L_DEBUG, "Client %d hung up", client->fd); 192 wlr_log(L_DEBUG, "Client %d hung up", client->fd);
245 ipc_client_disconnect(client); 193 ipc_client_disconnect(client);
246 return 0; 194 return 0;
247 } 195 }
248 196
249 sway_log(L_DEBUG, "Client %d readable", client->fd); 197 wlr_log(L_DEBUG, "Client %d readable", client->fd);
250 198
251 int read_available; 199 int read_available;
252 if (ioctl(client_fd, FIONREAD, &read_available) == -1) { 200 if (ioctl(client_fd, FIONREAD, &read_available) == -1) {
253 sway_log_errno(L_INFO, "Unable to read IPC socket buffer size"); 201 wlr_log_errno(L_INFO, "Unable to read IPC socket buffer size");
254 ipc_client_disconnect(client); 202 ipc_client_disconnect(client);
255 return 0; 203 return 0;
256 } 204 }
@@ -272,13 +220,13 @@ int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data) {
272 // Should be fully available, because read_available >= ipc_header_size 220 // Should be fully available, because read_available >= ipc_header_size
273 ssize_t received = recv(client_fd, buf, ipc_header_size, 0); 221 ssize_t received = recv(client_fd, buf, ipc_header_size, 0);
274 if (received == -1) { 222 if (received == -1) {
275 sway_log_errno(L_INFO, "Unable to receive header from IPC client"); 223 wlr_log_errno(L_INFO, "Unable to receive header from IPC client");
276 ipc_client_disconnect(client); 224 ipc_client_disconnect(client);
277 return 0; 225 return 0;
278 } 226 }
279 227
280 if (memcmp(buf, ipc_magic, sizeof(ipc_magic)) != 0) { 228 if (memcmp(buf, ipc_magic, sizeof(ipc_magic)) != 0) {
281 sway_log(L_DEBUG, "IPC header check failed"); 229 wlr_log(L_DEBUG, "IPC header check failed");
282 ipc_client_disconnect(client); 230 ipc_client_disconnect(client);
283 return 0; 231 return 0;
284 } 232 }
@@ -293,17 +241,110 @@ int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data) {
293 return 0; 241 return 0;
294} 242}
295 243
244static bool ipc_has_event_listeners(enum ipc_command_type event) {
245 for (int i = 0; i < ipc_client_list->length; i++) {
246 struct ipc_client *client = ipc_client_list->items[i];
247 if ((client->subscribed_events & event_mask(event)) == 0) {
248 return true;
249 }
250 }
251 return false;
252}
253
254static void ipc_send_event(const char *json_string, enum ipc_command_type event) {
255 struct ipc_client *client;
256 for (int i = 0; i < ipc_client_list->length; i++) {
257 client = ipc_client_list->items[i];
258 if ((client->subscribed_events & event_mask(event)) == 0) {
259 continue;
260 }
261 client->current_command = event;
262 if (!ipc_send_reply(client, json_string, (uint32_t) strlen(json_string))) {
263 wlr_log_errno(L_INFO, "Unable to send reply to IPC client");
264 ipc_client_disconnect(client);
265 }
266 }
267}
268
269void ipc_event_workspace(struct sway_container *old,
270 struct sway_container *new, const char *change) {
271 if (!ipc_has_event_listeners(IPC_EVENT_WORKSPACE)) {
272 return;
273 }
274 wlr_log(L_DEBUG, "Sending workspace::%s event", change);
275 json_object *obj = json_object_new_object();
276 json_object_object_add(obj, "change", json_object_new_string(change));
277 if (strcmp("focus", change) == 0) {
278 if (old) {
279 json_object_object_add(obj, "old",
280 ipc_json_describe_container_recursive(old));
281 } else {
282 json_object_object_add(obj, "old", NULL);
283 }
284 }
285
286 if (new) {
287 json_object_object_add(obj, "current",
288 ipc_json_describe_container_recursive(new));
289 } else {
290 json_object_object_add(obj, "current", NULL);
291 }
292
293 const char *json_string = json_object_to_json_string(obj);
294 ipc_send_event(json_string, IPC_EVENT_WORKSPACE);
295 json_object_put(obj);
296}
297
298void ipc_event_window(struct sway_container *window, const char *change) {
299 if (!ipc_has_event_listeners(IPC_EVENT_WINDOW)) {
300 return;
301 }
302 wlr_log(L_DEBUG, "Sending window::%s event", change);
303 json_object *obj = json_object_new_object();
304 json_object_object_add(obj, "change", json_object_new_string(change));
305 json_object_object_add(obj, "container", ipc_json_describe_container_recursive(window));
306
307 const char *json_string = json_object_to_json_string(obj);
308 ipc_send_event(json_string, IPC_EVENT_WINDOW);
309 json_object_put(obj);
310}
311
312void ipc_event_barconfig_update(struct bar_config *bar) {
313 if (!ipc_has_event_listeners(IPC_EVENT_BARCONFIG_UPDATE)) {
314 return;
315 }
316 wlr_log(L_DEBUG, "Sending barconfig_update event");
317 json_object *json = ipc_json_describe_bar_config(bar);
318
319 const char *json_string = json_object_to_json_string(json);
320 ipc_send_event(json_string, IPC_EVENT_BARCONFIG_UPDATE);
321 json_object_put(json);
322}
323
324void ipc_event_mode(const char *mode) {
325 if (!ipc_has_event_listeners(IPC_EVENT_MODE)) {
326 return;
327 }
328 wlr_log(L_DEBUG, "Sending mode::%s event", mode);
329 json_object *obj = json_object_new_object();
330 json_object_object_add(obj, "change", json_object_new_string(mode));
331
332 const char *json_string = json_object_to_json_string(obj);
333 ipc_send_event(json_string, IPC_EVENT_MODE);
334 json_object_put(obj);
335}
336
296int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data) { 337int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data) {
297 struct ipc_client *client = data; 338 struct ipc_client *client = data;
298 339
299 if (mask & WLC_EVENT_ERROR) { 340 if (mask & WL_EVENT_ERROR) {
300 sway_log(L_ERROR, "IPC Client socket error, removing client"); 341 wlr_log(L_ERROR, "IPC Client socket error, removing client");
301 ipc_client_disconnect(client); 342 ipc_client_disconnect(client);
302 return 0; 343 return 0;
303 } 344 }
304 345
305 if (mask & WLC_EVENT_HANGUP) { 346 if (mask & WL_EVENT_HANGUP) {
306 sway_log(L_DEBUG, "Client %d hung up", client->fd); 347 wlr_log(L_DEBUG, "Client %d hung up", client->fd);
307 ipc_client_disconnect(client); 348 ipc_client_disconnect(client);
308 return 0; 349 return 0;
309 } 350 }
@@ -312,14 +353,14 @@ int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data) {
312 return 0; 353 return 0;
313 } 354 }
314 355
315 sway_log(L_DEBUG, "Client %d writable", client->fd); 356 wlr_log(L_DEBUG, "Client %d writable", client->fd);
316 357
317 ssize_t written = write(client->fd, client->write_buffer, client->write_buffer_len); 358 ssize_t written = write(client->fd, client->write_buffer, client->write_buffer_len);
318 359
319 if (written == -1 && errno == EAGAIN) { 360 if (written == -1 && errno == EAGAIN) {
320 return 0; 361 return 0;
321 } else if (written == -1) { 362 } else if (written == -1) {
322 sway_log_errno(L_INFO, "Unable to send data from queue to IPC client"); 363 wlr_log_errno(L_INFO, "Unable to send data from queue to IPC client");
323 ipc_client_disconnect(client); 364 ipc_client_disconnect(client);
324 return 0; 365 return 0;
325 } 366 }
@@ -328,7 +369,7 @@ int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data) {
328 client->write_buffer_len -= written; 369 client->write_buffer_len -= written;
329 370
330 if (client->write_buffer_len == 0 && client->writable_event_source) { 371 if (client->write_buffer_len == 0 && client->writable_event_source) {
331 wlc_event_source_remove(client->writable_event_source); 372 wl_event_source_remove(client->writable_event_source);
332 client->writable_event_source = NULL; 373 client->writable_event_source = NULL;
333 } 374 }
334 375
@@ -344,332 +385,48 @@ void ipc_client_disconnect(struct ipc_client *client) {
344 shutdown(client->fd, SHUT_RDWR); 385 shutdown(client->fd, SHUT_RDWR);
345 } 386 }
346 387
347 sway_log(L_INFO, "IPC Client %d disconnected", client->fd); 388 wlr_log(L_INFO, "IPC Client %d disconnected", client->fd);
348 wlc_event_source_remove(client->event_source); 389 wl_event_source_remove(client->event_source);
349 if (client->writable_event_source) { 390 if (client->writable_event_source) {
350 wlc_event_source_remove(client->writable_event_source); 391 wl_event_source_remove(client->writable_event_source);
351 } 392 }
352 int i = 0; 393 int i = 0;
353 while (i < ipc_client_list->length && ipc_client_list->items[i] != client) i++; 394 while (i < ipc_client_list->length && ipc_client_list->items[i] != client) {
395 i++;
396 }
354 list_del(ipc_client_list, i); 397 list_del(ipc_client_list, i);
355 free(client->write_buffer); 398 free(client->write_buffer);
356 close(client->fd); 399 close(client->fd);
357 free(client); 400 free(client);
358} 401}
359 402
360bool output_by_name_test(swayc_t *view, void *data) { 403static void ipc_get_workspaces_callback(struct sway_container *workspace,
361 char *name = (char *)data; 404 void *data) {
362 if (view->type != C_OUTPUT) { 405 if (workspace->type != C_WORKSPACE) {
363 return false;
364 }
365 return !strcmp(name, view->name);
366}
367
368void ipc_get_pixels(wlc_handle output) {
369 if (ipc_get_pixel_requests->length == 0) {
370 return; 406 return;
371 } 407 }
372 408 json_object *workspace_json = ipc_json_describe_container(workspace);
373 list_t *unhandled = create_list(); 409 // override the default focused indicator because
374 410 // it's set differently for the get_workspaces reply
375 struct get_pixels_request *req; 411 struct sway_seat *seat =
376 int i; 412 input_manager_get_default_seat(input_manager);
377 for (i = 0; i < ipc_get_pixel_requests->length; ++i) { 413 struct sway_container *focused_ws = seat_get_focus(seat);
378 req = ipc_get_pixel_requests->items[i]; 414 if (focused_ws != NULL && focused_ws->type != C_WORKSPACE) {
379 if (req->output != output) { 415 focused_ws = container_parent(focused_ws, C_WORKSPACE);
380 list_add(unhandled, req); 416 }
381 continue; 417 bool focused = workspace == focused_ws;
382 } 418 json_object_object_del(workspace_json, "focused");
383 419 json_object_object_add(workspace_json, "focused",
384 const struct wlc_size *size = &req->geo.size; 420 json_object_new_boolean(focused));
385 struct wlc_geometry g_out; 421 json_object_array_add((json_object *)data, workspace_json);
386 char response_header[9]; 422
387 memset(response_header, 0, sizeof(response_header)); 423 focused_ws = seat_get_focus_inactive(seat, workspace->parent);
388 char *data = malloc(sizeof(response_header) + size->w * size->h * 4); 424 if (focused_ws->type != C_WORKSPACE) {
389 if (!data) { 425 focused_ws = container_parent(focused_ws, C_WORKSPACE);
390 sway_log(L_ERROR, "Unable to allocate pixels for get_pixels"); 426 }
391 ipc_client_disconnect(req->client); 427 bool visible = workspace == focused_ws;
392 free(req); 428 json_object_object_add(workspace_json, "visible",
393 continue; 429 json_object_new_boolean(visible));
394 }
395 wlc_pixels_read(WLC_RGBA8888, &req->geo, &g_out, data + sizeof(response_header));
396
397 response_header[0] = 1;
398 uint32_t *_size = (uint32_t *)(response_header + 1);
399 _size[0] = g_out.size.w;
400 _size[1] = g_out.size.h;
401 size_t len = sizeof(response_header) + (g_out.size.w * g_out.size.h * 4);
402 memcpy(data, response_header, sizeof(response_header));
403 ipc_send_reply(req->client, data, len);
404 free(data);
405 // free the request since it has been handled
406 free(req);
407 }
408
409 // free old list of pixel requests and set new list to all unhandled
410 // requests (request for another output).
411 list_free(ipc_get_pixel_requests);
412 ipc_get_pixel_requests = unhandled;
413}
414
415static bool is_text_target(const char *target) {
416 return (strncmp(target, "text/", 5) == 0
417 || strcmp(target, "UTF8_STRING") == 0
418 || strcmp(target, "STRING") == 0
419 || strcmp(target, "TEXT") == 0
420 || strcmp(target, "COMPOUND_TEXT") == 0);
421}
422
423static void release_clipboard_request(struct get_clipboard_request *req) {
424 if (--(*req->pending) == 0) {
425 const char *str = json_object_to_json_string(req->json);
426 ipc_send_reply(req->client, str, (uint32_t)strlen(str));
427 json_object_put(req->json);
428 }
429
430 free(req->type);
431 free(req->buf);
432 wlc_event_source_remove(req->fd_event_source);
433 wlc_event_source_remove(req->timer_event_source);
434 close(req->fd);
435 free(req);
436}
437
438static int ipc_selection_data_cb(int fd, uint32_t mask, void *data) {
439 assert(data);
440 struct get_clipboard_request *req = (struct get_clipboard_request *)data;
441
442 if (mask & WLC_EVENT_ERROR) {
443 sway_log(L_ERROR, "Selection data fd error");
444 goto error;
445 }
446
447 if (mask & WLC_EVENT_READABLE) {
448 static const unsigned int max_size = 8192 * 1024;
449 int amt = 0;
450
451 do {
452 int size = req->buf_size - req->buf_position;
453 int amt = read(fd, req->buf + req->buf_position, size - 1);
454 if (amt < 0) {
455 if (errno == EAGAIN) {
456 return 0;
457 }
458
459 sway_log_errno(L_INFO, "Failed to read from clipboard data fd");
460 goto release;
461 }
462
463 req->buf_position += amt;
464 if (req->buf_position >= req->buf_size - 1) {
465 if (req->buf_size >= max_size) {
466 sway_log(L_ERROR, "get_clipbard: selection data too large");
467 goto error;
468 }
469 char *next = realloc(req->buf, req->buf_size *= 2);
470 if (!next) {
471 sway_log_errno(L_ERROR, "get_clipboard: realloc data buffer failed");
472 goto error;
473 }
474
475 req->buf = next;
476 }
477 } while(amt != 0);
478
479 req->buf[req->buf_position] = '\0';
480
481 json_object *obj = json_object_new_object();
482 json_object_object_add(obj, "success", json_object_new_boolean(true));
483 if (is_text_target(req->type)) {
484 json_object_object_add(obj, "content", json_object_new_string(req->buf));
485 json_object_object_add(req->json, req->type, obj);
486 } else {
487 size_t outlen;
488 char *b64 = b64_encode(req->buf, req->buf_position, &outlen);
489 json_object_object_add(obj, "content", json_object_new_string(b64));
490 free(b64);
491
492 char *type = malloc(strlen(req->type) + 8);
493 strcat(type, ";base64");
494 json_object_object_add(req->json, type, obj);
495 free(type);
496 }
497 }
498
499 goto release;
500
501error:;
502 json_object *obj = json_object_new_object();
503 json_object_object_add(obj, "success", json_object_new_boolean(false));
504 json_object_object_add(obj, "error",
505 json_object_new_string("Failed to retrieve data"));
506 json_object_object_add(req->json, req->type, obj);
507
508release:
509 release_clipboard_request(req);
510 return 0;
511}
512
513static int ipc_selection_timer_cb(void *data) {
514 assert(data);
515 struct get_clipboard_request *req = (struct get_clipboard_request *)data;
516
517 sway_log(L_INFO, "get_clipbard: timeout for type %s", req->type);
518 json_object *obj = json_object_new_object();
519 json_object_object_add(obj, "success", json_object_new_boolean(false));
520 json_object_object_add(obj, "error", json_object_new_string("Timeout"));
521 json_object_object_add(req->json, req->type, obj);
522
523 release_clipboard_request(req);
524 return 0;
525}
526
527// greedy wildcard (only "*") matching
528bool mime_type_matches(const char *mime_type, const char *pattern) {
529 const char *wildcard = NULL;
530 while (*mime_type && *pattern) {
531 if (*pattern == '*' && !wildcard) {
532 wildcard = pattern;
533 ++pattern;
534 }
535
536 if (*mime_type != *pattern) {
537 if (!wildcard)
538 return false;
539
540 pattern = wildcard;
541 ++mime_type;
542 continue;
543 }
544
545 ++mime_type;
546 ++pattern;
547 }
548
549 while (*pattern == '*') {
550 ++pattern;
551 }
552
553 return (*mime_type == *pattern);
554}
555
556void ipc_get_clipboard(struct ipc_client *client, char *buf) {
557 size_t size;
558 const char **types = wlc_get_selection_types(&size);
559 if (client->payload_length == 0) {
560 json_object *obj = json_object_new_array();
561 for (size_t i = 0; i < size; ++i) {
562 json_object_array_add(obj, json_object_new_string(types[i]));
563 }
564
565 const char *str = json_object_to_json_string(obj);
566 ipc_send_reply(client, str, strlen(str));
567 json_object_put(obj);
568 return;
569 }
570
571 unescape_string(buf);
572 strip_quotes(buf);
573 list_t *requested = split_string(buf, " ");
574 json_object *json = json_object_new_object();
575 unsigned int *pending = malloc(sizeof(unsigned int));
576 *pending = 0;
577
578 for (size_t l = 0; l < (size_t) requested->length; ++l) {
579 const char *pattern = requested->items[l];
580 bool found = false;
581 for (size_t i = 0; i < size; ++i) {
582 if (!mime_type_matches(types[i], pattern)) {
583 continue;
584 }
585
586 found = true;
587
588 struct get_clipboard_request *req = malloc(sizeof(*req));
589 if (!req) {
590 sway_log(L_ERROR, "get_clipboard: request malloc failed");
591 goto data_error;
592 }
593
594 int pipes[2];
595 if (pipe(pipes) == -1) {
596 sway_log_errno(L_ERROR, "get_clipboard: pipe call failed");
597 free(req);
598 goto data_error;
599 }
600
601 fcntl(pipes[0], F_SETFD, FD_CLOEXEC | O_NONBLOCK);
602 fcntl(pipes[1], F_SETFD, FD_CLOEXEC | O_NONBLOCK);
603
604 if (!wlc_get_selection_data(types[i], pipes[1])) {
605 close(pipes[0]);
606 close(pipes[1]);
607 free(req);
608 sway_log(L_ERROR, "get_clipboard: failed to retrieve "
609 "selection data");
610 goto data_error;
611 }
612
613 if (!(req->buf = malloc(512))) {
614 close(pipes[0]);
615 close(pipes[1]);
616 free(req);
617 sway_log_errno(L_ERROR, "get_clipboard: buf malloc failed");
618 goto data_error;
619 }
620
621 (*pending)++;
622
623 req->client = client;
624 req->type = strdup(types[i]);
625 req->json = json;
626 req->pending = pending;
627 req->buf_position = 0;
628 req->buf_size = 512;
629 req->fd = pipes[0];
630 req->timer_event_source = wlc_event_loop_add_timer(ipc_selection_timer_cb, req);
631 req->fd_event_source = wlc_event_loop_add_fd(pipes[0],
632 WLC_EVENT_READABLE | WLC_EVENT_ERROR | WLC_EVENT_HANGUP,
633 &ipc_selection_data_cb, req);
634
635 wlc_event_source_timer_update(req->timer_event_source, 30000);
636
637 // NOTE: remove this goto to enable retrieving multiple
638 // targets at once. The whole implementation is already
639 // made for it. The only reason it was disabled
640 // at the time of writing is that neither wlc's xselection
641 // implementation nor (apparently) gtk on wayland supports
642 // multiple send requests at the same time which makes
643 // every request except the last one fail (and therefore
644 // return empty data)
645 goto cleanup;
646 }
647
648 if (!found) {
649 sway_log(L_INFO, "Invalid clipboard type %s requested", pattern);
650 }
651 }
652
653 if (*pending == 0) {
654 static const char *error_empty = "{ \"success\": false, \"error\": "
655 "\"No matching types found\" }";
656 ipc_send_reply(client, error_empty, (uint32_t)strlen(error_empty));
657 free(json);
658 free(pending);
659 }
660
661 goto cleanup;
662
663data_error:;
664 static const char *error_json = "{ \"success\": false, \"error\": "
665 "\"Failed to create clipboard data request\" }";
666 ipc_send_reply(client, error_json, (uint32_t)strlen(error_json));
667 free(json);
668 free(pending);
669
670cleanup:
671 list_free(requested);
672 free(types);
673} 430}
674 431
675void ipc_client_handle_command(struct ipc_client *client) { 432void ipc_client_handle_command(struct ipc_client *client) {
@@ -679,7 +436,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
679 436
680 char *buf = malloc(client->payload_length + 1); 437 char *buf = malloc(client->payload_length + 1);
681 if (!buf) { 438 if (!buf) {
682 sway_log_errno(L_INFO, "Unable to allocate IPC payload"); 439 wlr_log_errno(L_INFO, "Unable to allocate IPC payload");
683 ipc_client_disconnect(client); 440 ipc_client_disconnect(client);
684 return; 441 return;
685 } 442 }
@@ -688,7 +445,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
688 ssize_t received = recv(client->fd, buf, client->payload_length, 0); 445 ssize_t received = recv(client->fd, buf, client->payload_length, 0);
689 if (received == -1) 446 if (received == -1)
690 { 447 {
691 sway_log_errno(L_INFO, "Unable to receive payload from IPC client"); 448 wlr_log_errno(L_INFO, "Unable to receive payload from IPC client");
692 ipc_client_disconnect(client); 449 ipc_client_disconnect(client);
693 free(buf); 450 free(buf);
694 return; 451 return;
@@ -701,10 +458,8 @@ void ipc_client_handle_command(struct ipc_client *client) {
701 switch (client->current_command) { 458 switch (client->current_command) {
702 case IPC_COMMAND: 459 case IPC_COMMAND:
703 { 460 {
704 if (!(client->security_policy & IPC_FEATURE_COMMAND)) { 461 config_clear_handler_context(config);
705 goto exit_denied; 462 struct cmd_results *results = execute_command(buf, NULL);
706 }
707 struct cmd_results *results = handle_command(buf, CONTEXT_IPC);
708 const char *json = cmd_results_to_json(results); 463 const char *json = cmd_results_to_json(results);
709 char reply[256]; 464 char reply[256];
710 int length = snprintf(reply, sizeof(reply), "%s", json); 465 int length = snprintf(reply, sizeof(reply), "%s", json);
@@ -713,18 +468,45 @@ void ipc_client_handle_command(struct ipc_client *client) {
713 goto exit_cleanup; 468 goto exit_cleanup;
714 } 469 }
715 470
471 case IPC_GET_OUTPUTS:
472 {
473 json_object *outputs = json_object_new_array();
474 for (int i = 0; i < root_container.children->length; ++i) {
475 struct sway_container *container = root_container.children->items[i];
476 if (container->type == C_OUTPUT) {
477 json_object_array_add(outputs,
478 ipc_json_describe_container(container));
479 }
480 }
481 const char *json_string = json_object_to_json_string(outputs);
482 ipc_send_reply(client, json_string, (uint32_t) strlen(json_string));
483 json_object_put(outputs); // free
484 goto exit_cleanup;
485 }
486
487 case IPC_GET_WORKSPACES:
488 {
489 json_object *workspaces = json_object_new_array();
490 container_for_each_descendant_dfs(&root_container,
491 ipc_get_workspaces_callback, workspaces);
492 const char *json_string = json_object_to_json_string(workspaces);
493 ipc_send_reply(client, json_string, (uint32_t) strlen(json_string));
494 json_object_put(workspaces); // free
495 goto exit_cleanup;
496 }
497
716 case IPC_SUBSCRIBE: 498 case IPC_SUBSCRIBE:
717 { 499 {
718 // TODO: Check if they're permitted to use these events 500 // TODO: Check if they're permitted to use these events
719 struct json_object *request = json_tokener_parse(buf); 501 struct json_object *request = json_tokener_parse(buf);
720 if (request == NULL) { 502 if (request == NULL) {
721 ipc_send_reply(client, "{\"success\": false}", 18); 503 ipc_send_reply(client, "{\"success\": false}", 18);
722 sway_log_errno(L_INFO, "Failed to read request"); 504 wlr_log_errno(L_INFO, "Failed to read request");
723 goto exit_cleanup; 505 goto exit_cleanup;
724 } 506 }
725 507
726 // parse requested event types 508 // parse requested event types
727 for (int i = 0; i < json_object_array_length(request); i++) { 509 for (size_t i = 0; i < json_object_array_length(request); i++) {
728 const char *event_type = json_object_get_string(json_object_array_get_idx(request, i)); 510 const char *event_type = json_object_get_string(json_object_array_get_idx(request, i));
729 if (strcmp(event_type, "workspace") == 0) { 511 if (strcmp(event_type, "workspace") == 0) {
730 client->subscribed_events |= event_mask(IPC_EVENT_WORKSPACE); 512 client->subscribed_events |= event_mask(IPC_EVENT_WORKSPACE);
@@ -741,86 +523,39 @@ void ipc_client_handle_command(struct ipc_client *client) {
741 } else { 523 } else {
742 ipc_send_reply(client, "{\"success\": false}", 18); 524 ipc_send_reply(client, "{\"success\": false}", 18);
743 json_object_put(request); 525 json_object_put(request);
744 sway_log_errno(L_INFO, "Failed to parse request"); 526 wlr_log_errno(L_INFO, "Failed to parse request");
745 goto exit_cleanup; 527 goto exit_cleanup;
746 } 528 }
747 } 529 }
748 530
749 json_object_put(request); 531 json_object_put(request);
750
751 ipc_send_reply(client, "{\"success\": true}", 17); 532 ipc_send_reply(client, "{\"success\": true}", 17);
752 goto exit_cleanup; 533 goto exit_cleanup;
753 } 534 }
754 535
755 case IPC_GET_WORKSPACES:
756 {
757 if (!(client->security_policy & IPC_FEATURE_GET_WORKSPACES)) {
758 goto exit_denied;
759 }
760 json_object *workspaces = json_object_new_array();
761 container_map(&root_container, ipc_get_workspaces_callback, workspaces);
762 const char *json_string = json_object_to_json_string(workspaces);
763 ipc_send_reply(client, json_string, (uint32_t) strlen(json_string));
764 json_object_put(workspaces); // free
765 goto exit_cleanup;
766 }
767
768 case IPC_GET_INPUTS: 536 case IPC_GET_INPUTS:
769 { 537 {
770 if (!(client->security_policy & IPC_FEATURE_GET_INPUTS)) {
771 goto exit_denied;
772 }
773 json_object *inputs = json_object_new_array(); 538 json_object *inputs = json_object_new_array();
774 if (input_devices) { 539 struct sway_input_device *device = NULL;
775 for(int i = 0; i<input_devices->length; i++) { 540 wl_list_for_each(device, &input_manager->devices, link) {
776 struct libinput_device *device = input_devices->items[i]; 541 json_object_array_add(inputs, ipc_json_describe_input(device));
777 json_object_array_add(inputs, ipc_json_describe_input(device));
778 }
779 } 542 }
780 const char *json_string = json_object_to_json_string(inputs); 543 const char *json_string = json_object_to_json_string(inputs);
781 ipc_send_reply(client, json_string, (uint32_t) strlen(json_string)); 544 ipc_send_reply(client, json_string, (uint32_t)strlen(json_string));
782 json_object_put(inputs); 545 json_object_put(inputs); // free
783 goto exit_cleanup;
784 }
785
786 case IPC_GET_OUTPUTS:
787 {
788 if (!(client->security_policy & IPC_FEATURE_GET_OUTPUTS)) {
789 goto exit_denied;
790 }
791 json_object *outputs = json_object_new_array();
792 container_map(&root_container, ipc_get_outputs_callback, outputs);
793 const char *json_string = json_object_to_json_string(outputs);
794 ipc_send_reply(client, json_string, (uint32_t) strlen(json_string));
795 json_object_put(outputs); // free
796 goto exit_cleanup; 546 goto exit_cleanup;
797 } 547 }
798 548
799 case IPC_GET_TREE: 549 case IPC_GET_TREE:
800 { 550 {
801 if (!(client->security_policy & IPC_FEATURE_GET_TREE)) { 551 json_object *tree =
802 goto exit_denied; 552 ipc_json_describe_container_recursive(&root_container);
803 }
804 json_object *tree = ipc_json_describe_container_recursive(&root_container);
805 const char *json_string = json_object_to_json_string(tree); 553 const char *json_string = json_object_to_json_string(tree);
806 ipc_send_reply(client, json_string, (uint32_t) strlen(json_string)); 554 ipc_send_reply(client, json_string, (uint32_t) strlen(json_string));
807 json_object_put(tree); 555 json_object_put(tree);
808 goto exit_cleanup; 556 goto exit_cleanup;
809 } 557 }
810 558
811 case IPC_GET_MARKS:
812 {
813 if (!(client->security_policy & IPC_FEATURE_GET_MARKS)) {
814 goto exit_denied;
815 }
816 json_object *marks = json_object_new_array();
817 container_map(&root_container, ipc_get_marks_callback, marks);
818 const char *json_string = json_object_to_json_string(marks);
819 ipc_send_reply(client, json_string, (uint32_t) strlen(json_string));
820 json_object_put(marks);
821 goto exit_cleanup;
822 }
823
824 case IPC_GET_VERSION: 559 case IPC_GET_VERSION:
825 { 560 {
826 json_object *version = ipc_json_get_version(); 561 json_object *version = ipc_json_get_version();
@@ -830,62 +565,12 @@ void ipc_client_handle_command(struct ipc_client *client) {
830 goto exit_cleanup; 565 goto exit_cleanup;
831 } 566 }
832 567
833 case IPC_SWAY_GET_PIXELS:
834 {
835 char response_header[9];
836 memset(response_header, 0, sizeof(response_header));
837
838 json_object *obj = json_tokener_parse(buf);
839 json_object *o, *x, *y, *w, *h;
840
841 json_object_object_get_ex(obj, "output", &o);
842 json_object_object_get_ex(obj, "x", &x);
843 json_object_object_get_ex(obj, "y", &y);
844 json_object_object_get_ex(obj, "w", &w);
845 json_object_object_get_ex(obj, "h", &h);
846
847 struct wlc_geometry g = {
848 .origin = {
849 .x = json_object_get_int(x),
850 .y = json_object_get_int(y)
851 },
852 .size = {
853 .w = json_object_get_int(w),
854 .h = json_object_get_int(h)
855 }
856 };
857
858 swayc_t *output = swayc_by_test(&root_container, output_by_name_test, (void *)json_object_get_string(o));
859 json_object_put(obj);
860
861 if (!output) {
862 sway_log(L_ERROR, "IPC GET_PIXELS request with unknown output name");
863 ipc_send_reply(client, response_header, sizeof(response_header));
864 goto exit_cleanup;
865 }
866 struct get_pixels_request *req = malloc(sizeof(struct get_pixels_request));
867 if (!req) {
868 sway_log(L_ERROR, "Unable to allocate get_pixels request");
869 goto exit_cleanup;
870 }
871 req->client = client;
872 req->output = output->handle;
873 req->geo = g;
874 list_add(ipc_get_pixel_requests, req);
875 wlc_output_schedule_render(output->handle);
876 goto exit_cleanup;
877 }
878
879 case IPC_GET_BAR_CONFIG: 568 case IPC_GET_BAR_CONFIG:
880 { 569 {
881 if (!(client->security_policy & IPC_FEATURE_GET_BAR_CONFIG)) {
882 goto exit_denied;
883 }
884 if (!buf[0]) { 570 if (!buf[0]) {
885 // Send list of configured bar IDs 571 // Send list of configured bar IDs
886 json_object *bars = json_object_new_array(); 572 json_object *bars = json_object_new_array();
887 int i; 573 for (int i = 0; i < config->bars->length; ++i) {
888 for (i = 0; i < config->bars->length; ++i) {
889 struct bar_config *bar = config->bars->items[i]; 574 struct bar_config *bar = config->bars->items[i];
890 json_object_array_add(bars, json_object_new_string(bar->id)); 575 json_object_array_add(bars, json_object_new_string(bar->id));
891 } 576 }
@@ -895,8 +580,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
895 } else { 580 } else {
896 // Send particular bar's details 581 // Send particular bar's details
897 struct bar_config *bar = NULL; 582 struct bar_config *bar = NULL;
898 int i; 583 for (int i = 0; i < config->bars->length; ++i) {
899 for (i = 0; i < config->bars->length; ++i) {
900 bar = config->bars->items[i]; 584 bar = config->bars->items[i];
901 if (strcmp(buf, bar->id) == 0) { 585 if (strcmp(buf, bar->id) == 0) {
902 break; 586 break;
@@ -916,24 +600,13 @@ void ipc_client_handle_command(struct ipc_client *client) {
916 goto exit_cleanup; 600 goto exit_cleanup;
917 } 601 }
918 602
919 case IPC_GET_CLIPBOARD:
920 {
921 if (!(client->security_policy & IPC_FEATURE_GET_CLIPBOARD)) {
922 goto exit_denied;
923 }
924
925 ipc_get_clipboard(client, buf);
926 goto exit_cleanup;
927 }
928
929 default: 603 default:
930 sway_log(L_INFO, "Unknown IPC command type %i", client->current_command); 604 wlr_log(L_INFO, "Unknown IPC command type %i", client->current_command);
931 goto exit_cleanup; 605 goto exit_cleanup;
932 } 606 }
933 607
934exit_denied:
935 ipc_send_reply(client, error_denied, (uint32_t)strlen(error_denied)); 608 ipc_send_reply(client, error_denied, (uint32_t)strlen(error_denied));
936 sway_log(L_DEBUG, "Denied IPC client access to %i", client->current_command); 609 wlr_log(L_DEBUG, "Denied IPC client access to %i", client->current_command);
937 610
938exit_cleanup: 611exit_cleanup:
939 client->payload_length = 0; 612 client->payload_length = 0;
@@ -956,16 +629,15 @@ bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t pay
956 client->write_buffer_size *= 2; 629 client->write_buffer_size *= 2;
957 } 630 }
958 631
959 // TODO: reduce the limit back to 4 MB when screenshooter is implemented 632 if (client->write_buffer_size > 4e6) { // 4 MB
960 if (client->write_buffer_size > (1 << 28)) { // 256 MB 633 wlr_log(L_ERROR, "Client write buffer too big, disconnecting client");
961 sway_log(L_ERROR, "Client write buffer too big, disconnecting client");
962 ipc_client_disconnect(client); 634 ipc_client_disconnect(client);
963 return false; 635 return false;
964 } 636 }
965 637
966 char *new_buffer = realloc(client->write_buffer, client->write_buffer_size); 638 char *new_buffer = realloc(client->write_buffer, client->write_buffer_size);
967 if (!new_buffer) { 639 if (!new_buffer) {
968 sway_log(L_ERROR, "Unable to reallocate ipc client write buffer"); 640 wlr_log(L_ERROR, "Unable to reallocate ipc client write buffer");
969 ipc_client_disconnect(client); 641 ipc_client_disconnect(client);
970 return false; 642 return false;
971 } 643 }
@@ -977,212 +649,11 @@ bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t pay
977 client->write_buffer_len += payload_length; 649 client->write_buffer_len += payload_length;
978 650
979 if (!client->writable_event_source) { 651 if (!client->writable_event_source) {
980 client->writable_event_source = wlc_event_loop_add_fd(client->fd, WLC_EVENT_WRITABLE, ipc_client_handle_writable, client); 652 client->writable_event_source = wl_event_loop_add_fd(
653 server.wl_event_loop, client->fd, WL_EVENT_WRITABLE,
654 ipc_client_handle_writable, client);
981 } 655 }
982 656
983 sway_log(L_DEBUG, "Added IPC reply to client %d queue: %s", client->fd, payload); 657 wlr_log(L_DEBUG, "Added IPC reply to client %d queue: %s", client->fd, payload);
984
985 return true; 658 return true;
986} 659}
987
988void ipc_get_workspaces_callback(swayc_t *workspace, void *data) {
989 if (workspace->type == C_WORKSPACE) {
990 json_object *workspace_json = ipc_json_describe_container(workspace);
991 // override the default focused indicator because
992 // it's set differently for the get_workspaces reply
993 bool focused = root_container.focused == workspace->parent && workspace->parent->focused == workspace;
994 json_object_object_del(workspace_json, "focused");
995 json_object_object_add(workspace_json, "focused", json_object_new_boolean(focused));
996 json_object_array_add((json_object *)data, workspace_json);
997 }
998}
999
1000void ipc_get_outputs_callback(swayc_t *container, void *data) {
1001 if (container->type == C_OUTPUT) {
1002 json_object_array_add((json_object *)data, ipc_json_describe_container(container));
1003 }
1004}
1005
1006static void ipc_get_marks_callback(swayc_t *container, void *data) {
1007 json_object *object = (json_object *)data;
1008 if (container->marks) {
1009 for (int i = 0; i < container->marks->length; ++i) {
1010 char *mark = (char *)container->marks->items[i];
1011 json_object_array_add(object, json_object_new_string(mark));
1012 }
1013 }
1014}
1015
1016void ipc_send_event(const char *json_string, enum ipc_command_type event) {
1017 static struct {
1018 enum ipc_command_type event;
1019 enum ipc_feature feature;
1020 } security_mappings[] = {
1021 { IPC_EVENT_WORKSPACE, IPC_FEATURE_EVENT_WORKSPACE },
1022 { IPC_EVENT_OUTPUT, IPC_FEATURE_EVENT_OUTPUT },
1023 { IPC_EVENT_MODE, IPC_FEATURE_EVENT_MODE },
1024 { IPC_EVENT_WINDOW, IPC_FEATURE_EVENT_WINDOW },
1025 { IPC_EVENT_BINDING, IPC_FEATURE_EVENT_BINDING },
1026 { IPC_EVENT_INPUT, IPC_FEATURE_EVENT_INPUT }
1027 };
1028
1029 uint32_t security_mask = 0;
1030 for (size_t i = 0; i < sizeof(security_mappings) / sizeof(security_mappings[0]); ++i) {
1031 if (security_mappings[i].event == event) {
1032 security_mask = security_mappings[i].feature;
1033 break;
1034 }
1035 }
1036
1037 int i;
1038 struct ipc_client *client;
1039 for (i = 0; i < ipc_client_list->length; i++) {
1040 client = ipc_client_list->items[i];
1041 if (!(client->security_policy & security_mask)) {
1042 continue;
1043 }
1044 if ((client->subscribed_events & event_mask(event)) == 0) {
1045 continue;
1046 }
1047 client->current_command = event;
1048 if (!ipc_send_reply(client, json_string, (uint32_t) strlen(json_string))) {
1049 sway_log_errno(L_INFO, "Unable to send reply to IPC client");
1050 ipc_client_disconnect(client);
1051 }
1052 }
1053}
1054
1055void ipc_event_workspace(swayc_t *old, swayc_t *new, const char *change) {
1056 sway_log(L_DEBUG, "Sending workspace::%s event", change);
1057 json_object *obj = json_object_new_object();
1058 json_object_object_add(obj, "change", json_object_new_string(change));
1059 if (strcmp("focus", change) == 0) {
1060 if (old) {
1061 json_object_object_add(obj, "old", ipc_json_describe_container_recursive(old));
1062 } else {
1063 json_object_object_add(obj, "old", NULL);
1064 }
1065 }
1066
1067 if (new) {
1068 json_object_object_add(obj, "current", ipc_json_describe_container_recursive(new));
1069 } else {
1070 json_object_object_add(obj, "current", NULL);
1071 }
1072
1073 const char *json_string = json_object_to_json_string(obj);
1074 ipc_send_event(json_string, IPC_EVENT_WORKSPACE);
1075
1076 json_object_put(obj); // free
1077}
1078
1079void ipc_event_window(swayc_t *window, const char *change) {
1080 sway_log(L_DEBUG, "Sending window::%s event", change);
1081 json_object *obj = json_object_new_object();
1082 json_object_object_add(obj, "change", json_object_new_string(change));
1083 json_object_object_add(obj, "container", ipc_json_describe_container_recursive(window));
1084
1085 const char *json_string = json_object_to_json_string(obj);
1086 ipc_send_event(json_string, IPC_EVENT_WINDOW);
1087
1088 json_object_put(obj); // free
1089}
1090
1091void ipc_event_barconfig_update(struct bar_config *bar) {
1092 sway_log(L_DEBUG, "Sending barconfig_update event");
1093 json_object *json = ipc_json_describe_bar_config(bar);
1094 const char *json_string = json_object_to_json_string(json);
1095 ipc_send_event(json_string, IPC_EVENT_BARCONFIG_UPDATE);
1096
1097 json_object_put(json); // free
1098}
1099
1100void ipc_event_mode(const char *mode) {
1101 sway_log(L_DEBUG, "Sending mode::%s event", mode);
1102 json_object *obj = json_object_new_object();
1103 json_object_object_add(obj, "change", json_object_new_string(mode));
1104
1105 const char *json_string = json_object_to_json_string(obj);
1106 ipc_send_event(json_string, IPC_EVENT_MODE);
1107
1108 json_object_put(obj); // free
1109}
1110
1111void ipc_event_modifier(uint32_t modifier, const char *state) {
1112 sway_log(L_DEBUG, "Sending modifier::%s event", state);
1113 json_object *obj = json_object_new_object();
1114 json_object_object_add(obj, "change", json_object_new_string(state));
1115
1116 const char *modifier_name = get_modifier_name_by_mask(modifier);
1117 json_object_object_add(obj, "modifier", json_object_new_string(modifier_name));
1118
1119 const char *json_string = json_object_to_json_string(obj);
1120 ipc_send_event(json_string, IPC_EVENT_MODIFIER);
1121
1122 json_object_put(obj); // free
1123}
1124
1125static void ipc_event_binding(json_object *sb_obj) {
1126 sway_log(L_DEBUG, "Sending binding::run event");
1127 json_object *obj = json_object_new_object();
1128 json_object_object_add(obj, "change", json_object_new_string("run"));
1129 json_object_object_add(obj, "binding", sb_obj);
1130
1131 const char *json_string = json_object_to_json_string(obj);
1132 ipc_send_event(json_string, IPC_EVENT_BINDING);
1133
1134 json_object_put(obj); // free
1135}
1136
1137void ipc_event_binding_keyboard(struct sway_binding *sb) {
1138 json_object *sb_obj = json_object_new_object();
1139 json_object_object_add(sb_obj, "command", json_object_new_string(sb->command));
1140
1141 const char *names[10];
1142
1143 int len = get_modifier_names(names, sb->modifiers);
1144 int i;
1145 json_object *modifiers = json_object_new_array();
1146 for (i = 0; i < len; ++i) {
1147 json_object_array_add(modifiers, json_object_new_string(names[i]));
1148 }
1149
1150 json_object_object_add(sb_obj, "event_state_mask", modifiers);
1151
1152 json_object *input_codes = json_object_new_array();
1153 int input_code = 0;
1154 json_object *symbols = json_object_new_array();
1155 json_object *symbol = NULL;
1156
1157 if (sb->bindcode) { // bindcode: populate input_codes
1158 uint32_t keycode;
1159 for (i = 0; i < sb->keys->length; ++i) {
1160 keycode = *(uint32_t *)sb->keys->items[i];
1161 json_object_array_add(input_codes, json_object_new_int(keycode));
1162 if (i == 0) {
1163 input_code = keycode;
1164 }
1165 }
1166 } else { // bindsym: populate symbols
1167 uint32_t keysym;
1168 char buffer[64];
1169 for (i = 0; i < sb->keys->length; ++i) {
1170 keysym = *(uint32_t *)sb->keys->items[i];
1171 if (xkb_keysym_get_name(keysym, buffer, 64) > 0) {
1172 json_object *str = json_object_new_string(buffer);
1173 json_object_array_add(symbols, str);
1174 if (i == 0) {
1175 symbol = str;
1176 }
1177 }
1178 }
1179 }
1180
1181 json_object_object_add(sb_obj, "input_codes", input_codes);
1182 json_object_object_add(sb_obj, "input_code", json_object_new_int(input_code));
1183 json_object_object_add(sb_obj, "symbols", symbols);
1184 json_object_object_add(sb_obj, "symbol", symbol);
1185 json_object_object_add(sb_obj, "input_type", json_object_new_string("keyboard"));
1186
1187 ipc_event_binding(sb_obj);
1188}
diff --git a/sway/layout.c b/sway/layout.c
deleted file mode 100644
index 69291daf..00000000
--- a/sway/layout.c
+++ /dev/null
@@ -1,1770 +0,0 @@
1#define _XOPEN_SOURCE 500
2#include <stdlib.h>
3#include <stdbool.h>
4#include <math.h>
5#include <wlc/wlc.h>
6#include "sway/extensions.h"
7#include "sway/config.h"
8#include "sway/container.h"
9#include "sway/workspace.h"
10#include "sway/focus.h"
11#include "sway/output.h"
12#include "sway/ipc-server.h"
13#include "sway/border.h"
14#include "sway/layout.h"
15#include "list.h"
16#include "log.h"
17
18swayc_t root_container;
19swayc_t *current_focus;
20list_t *scratchpad;
21
22int min_sane_h = 60;
23int min_sane_w = 100;
24
25void init_layout(void) {
26 root_container.id = 0; // normally assigned in new_swayc()
27 root_container.type = C_ROOT;
28 root_container.layout = L_NONE;
29 root_container.name = strdup("root");
30 root_container.children = create_list();
31 root_container.handle = -1;
32 root_container.visible = true;
33 current_focus = &root_container;
34 scratchpad = create_list();
35}
36
37int index_child(const swayc_t *child) {
38 swayc_t *parent = child->parent;
39 int i, len;
40 if (!child->is_floating) {
41 len = parent->children->length;
42 for (i = 0; i < len; ++i) {
43 if (parent->children->items[i] == child) {
44 break;
45 }
46 }
47 } else {
48 len = parent->floating->length;
49 for (i = 0; i < len; ++i) {
50 if (parent->floating->items[i] == child) {
51 break;
52 }
53 }
54 }
55 if (!sway_assert(i < len, "Stray container")) {
56 return -1;
57 }
58 return i;
59}
60
61void add_child(swayc_t *parent, swayc_t *child) {
62 sway_log(L_DEBUG, "Adding %p (%d, %fx%f) to %p (%d, %fx%f)", child, child->type,
63 child->width, child->height, parent, parent->type, parent->width, parent->height);
64 list_add(parent->children, child);
65 child->parent = parent;
66 // set focus for this container
67 if (!parent->focused) {
68 parent->focused = child;
69 }
70 if (parent->type == C_WORKSPACE && child->type == C_VIEW && (parent->workspace_layout == L_TABBED || parent->workspace_layout == L_STACKED)) {
71 child = new_container(child, parent->workspace_layout);
72 }
73}
74
75static double *get_height(swayc_t *cont) {
76 return &cont->height;
77}
78
79static double *get_width(swayc_t *cont) {
80 return &cont->width;
81}
82
83void insert_child(swayc_t *parent, swayc_t *child, int index) {
84 if (index > parent->children->length) {
85 index = parent->children->length;
86 }
87 if (index < 0) {
88 index = 0;
89 }
90 list_insert(parent->children, index, child);
91 child->parent = parent;
92 if (!parent->focused) {
93 parent->focused = child;
94 }
95 if (parent->type == C_WORKSPACE && child->type == C_VIEW && (parent->workspace_layout == L_TABBED || parent->workspace_layout == L_STACKED)) {
96 child = new_container(child, parent->workspace_layout);
97 }
98 if (is_auto_layout(parent->layout)) {
99 /* go through each group, adjust the size of the first child of each group */
100 double *(*get_maj_dim)(swayc_t *cont);
101 double *(*get_min_dim)(swayc_t *cont);
102 if (parent->layout == L_AUTO_LEFT || parent->layout == L_AUTO_RIGHT) {
103 get_maj_dim = get_width;
104 get_min_dim = get_height;
105 } else {
106 get_maj_dim = get_height;
107 get_min_dim = get_width;
108 }
109 for (int i = index; i < parent->children->length;) {
110 int start = auto_group_start_index(parent, i);
111 int end = auto_group_end_index(parent, i);
112 swayc_t *first = parent->children->items[start];
113 if (start + 1 < parent->children->length) {
114 /* preserve the group's dimension along major axis */
115 *get_maj_dim(first) = *get_maj_dim(parent->children->items[start + 1]);
116 } else {
117 /* new group, let the apply_layout handle it */
118 first->height = first->width = 0;
119 break;
120 }
121 double remaining = *get_min_dim(parent);
122 for (int j = end - 1; j > start; --j) {
123 swayc_t *sibling = parent->children->items[j];
124 if (sibling == child) {
125 /* the inserted child won't yet have its minor
126 dimension set */
127 remaining -= *get_min_dim(parent) / (end - start);
128 } else {
129 remaining -= *get_min_dim(sibling);
130 }
131 }
132 *get_min_dim(first) = remaining;
133 i = end;
134 }
135 }
136}
137
138void add_floating(swayc_t *ws, swayc_t *child) {
139 sway_log(L_DEBUG, "Adding %p (%d, %fx%f) to %p (%d, %fx%f)", child, child->type,
140 child->width, child->height, ws, ws->type, ws->width, ws->height);
141 if (!sway_assert(ws->type == C_WORKSPACE, "Must be of workspace type")) {
142 return;
143 }
144 list_add(ws->floating, child);
145 child->parent = ws;
146 child->is_floating = true;
147 if (!ws->focused) {
148 ws->focused = child;
149 }
150 ipc_event_window(child, "floating");
151}
152
153swayc_t *add_sibling(swayc_t *fixed, swayc_t *active) {
154 swayc_t *parent = fixed->parent;
155 if (fixed->is_floating) {
156 if (active->is_floating) {
157 int i = index_child(fixed);
158 list_insert(parent->floating, i + 1, active);
159 } else {
160 list_add(parent->children, active);
161 }
162 } else {
163 if (active->is_floating) {
164 list_add(parent->floating, active);
165 } else {
166 int i = index_child(fixed);
167 if (is_auto_layout(parent->layout)) {
168 list_add(parent->children, active);
169 } else {
170 list_insert(parent->children, i + 1, active);
171 }
172 }
173 }
174 active->parent = parent;
175 // focus new child
176 parent->focused = active;
177 return active->parent;
178}
179
180swayc_t *replace_child(swayc_t *child, swayc_t *new_child) {
181 swayc_t *parent = child->parent;
182 if (parent == NULL) {
183 return NULL;
184 }
185 int i = index_child(child);
186 if (child->is_floating) {
187 parent->floating->items[i] = new_child;
188 } else {
189 parent->children->items[i] = new_child;
190 }
191 // Set parent and focus for new_child
192 new_child->parent = child->parent;
193 if (child->parent->focused == child) {
194 child->parent->focused = new_child;
195 }
196 child->parent = NULL;
197
198 // Set geometry for new child
199 new_child->x = child->x;
200 new_child->y = child->y;
201 new_child->width = child->width;
202 new_child->height = child->height;
203
204 // reset geometry for child
205 child->width = 0;
206 child->height = 0;
207
208 // deactivate child
209 if (child->type == C_VIEW) {
210 wlc_view_set_state(child->handle, WLC_BIT_ACTIVATED, false);
211 }
212 return parent;
213}
214
215swayc_t *remove_child(swayc_t *child) {
216 int i;
217 swayc_t *parent = child->parent;
218 if (child->is_floating) {
219 // Special case for floating views
220 for (i = 0; i < parent->floating->length; ++i) {
221 if (parent->floating->items[i] == child) {
222 list_del(parent->floating, i);
223 break;
224 }
225 }
226 i = 0;
227 } else {
228 for (i = 0; i < parent->children->length; ++i) {
229 if (parent->children->items[i] == child) {
230 list_del(parent->children, i);
231 break;
232 }
233 }
234 if (is_auto_layout(parent->layout) && parent->children->length) {
235 /* go through each group, adjust the size of the last child of each group */
236 double *(*get_maj_dim)(swayc_t *cont);
237 double *(*get_min_dim)(swayc_t *cont);
238 if (parent->layout == L_AUTO_LEFT || parent->layout == L_AUTO_RIGHT) {
239 get_maj_dim = get_width;
240 get_min_dim = get_height;
241 } else {
242 get_maj_dim = get_height;
243 get_min_dim = get_width;
244 }
245 for (int j = parent->children->length - 1; j >= i;) {
246 int start = auto_group_start_index(parent, j);
247 int end = auto_group_end_index(parent, j);
248 swayc_t *first = parent->children->items[start];
249 if (i == start) {
250 /* removed element was first child in the current group,
251 use its size along the major axis */
252 *get_maj_dim(first) = *get_maj_dim(child);
253 } else if (start > i) {
254 /* preserve the group's dimension along major axis */
255 *get_maj_dim(first) = *get_maj_dim(parent->children->items[start - 1]);
256 }
257 if (end != parent->children->length) {
258 double remaining = *get_min_dim(parent);
259 for (int k = start; k < end - 1; ++k) {
260 swayc_t *sibling = parent->children->items[k];
261 remaining -= *get_min_dim(sibling);
262 }
263 /* last element of the group gets remaining size, elements
264 that don't change groups keep their ratio */
265 *get_min_dim((swayc_t *) parent->children->items[end - 1]) = remaining;
266 } /* else last group, let apply_layout handle it */
267 j = start - 1;
268 }
269 }
270 }
271 // Set focused to new container
272 if (parent->focused == child) {
273 if (parent->children->length > 0) {
274 parent->focused = parent->children->items[i ? i-1:0];
275 } else if (parent->floating && parent->floating->length) {
276 parent->focused = parent->floating->items[parent->floating->length - 1];
277 } else {
278 parent->focused = NULL;
279 }
280 }
281 child->parent = NULL;
282 // deactivate view
283 if (child->type == C_VIEW) {
284 wlc_view_set_state(child->handle, WLC_BIT_ACTIVATED, false);
285 }
286 return parent;
287}
288
289void swap_container(swayc_t *a, swayc_t *b) {
290 if (!sway_assert(a&&b, "parameters must be non null") ||
291 !sway_assert(a->parent && b->parent, "containers must have parents")) {
292 return;
293 }
294 size_t a_index = index_child(a);
295 size_t b_index = index_child(b);
296 swayc_t *a_parent = a->parent;
297 swayc_t *b_parent = b->parent;
298 // Swap the pointers
299 if (a->is_floating) {
300 a_parent->floating->items[a_index] = b;
301 } else {
302 a_parent->children->items[a_index] = b;
303 }
304 if (b->is_floating) {
305 b_parent->floating->items[b_index] = a;
306 } else {
307 b_parent->children->items[b_index] = a;
308 }
309 a->parent = b_parent;
310 b->parent = a_parent;
311 if (a_parent->focused == a) {
312 a_parent->focused = b;
313 }
314 // don't want to double switch
315 if (b_parent->focused == b && a_parent != b_parent) {
316 b_parent->focused = a;
317 }
318}
319
320void swap_geometry(swayc_t *a, swayc_t *b) {
321 double x = a->x;
322 double y = a->y;
323 double w = a->width;
324 double h = a->height;
325 a->x = b->x;
326 a->y = b->y;
327 a->width = b->width;
328 a->height = b->height;
329 b->x = x;
330 b->y = y;
331 b->width = w;
332 b->height = h;
333}
334
335static void swap_children(swayc_t *container, int a, int b) {
336 if (a >= 0 && b >= 0 && a < container->children->length
337 && b < container->children->length
338 && a != b) {
339 swayc_t *pa = (swayc_t *)container->children->items[a];
340 swayc_t *pb = (swayc_t *)container->children->items[b];
341 container->children->items[a] = container->children->items[b];
342 container->children->items[b] = pa;
343 if (is_auto_layout(container->layout)) {
344 size_t ga = auto_group_index(container, a);
345 size_t gb = auto_group_index(container, b);
346 if (ga != gb) {
347 swap_geometry(pa, pb);
348 }
349 }
350 }
351}
352
353void move_container(swayc_t *container, enum movement_direction dir, int move_amt) {
354 enum swayc_layouts layout = L_NONE;
355 swayc_t *parent = container->parent;
356 if (container->is_floating) {
357 swayc_t *output = swayc_parent_by_type(container, C_OUTPUT);
358 switch(dir) {
359 case MOVE_LEFT:
360 container->x = MAX(0, container->x - move_amt);
361 break;
362 case MOVE_RIGHT:
363 container->x = MIN(output->width - container->width, container->x + move_amt);
364 break;
365 case MOVE_UP:
366 container->y = MAX(0, container->y - move_amt);
367 break;
368 case MOVE_DOWN:
369 container->y = MIN(output->height - container->height, container->y + move_amt);
370 break;
371 default:
372 break;
373 }
374 update_geometry(container);
375 return;
376 }
377 if (container->type != C_VIEW && container->type != C_CONTAINER) {
378 return;
379 }
380 if (dir == MOVE_UP || dir == MOVE_DOWN) {
381 layout = L_VERT;
382 } else if (dir == MOVE_LEFT || dir == MOVE_RIGHT) {
383 layout = L_HORIZ;
384 } else if (dir == MOVE_FIRST) {
385 // swap first child in auto layout with currently focused child
386 if (is_auto_layout(parent->layout)) {
387 int focused_idx = index_child(container);
388 swayc_t *first = parent->children->items[0];
389 if (focused_idx > 0) {
390 list_swap(parent->children, 0, focused_idx);
391 swap_geometry(first, container);
392 }
393 arrange_windows(parent->parent, -1, -1);
394 ipc_event_window(container, "move");
395 set_focused_container_for(parent->parent, container);
396 }
397 return;
398 } else if (! (dir == MOVE_NEXT || dir == MOVE_PREV)) {
399 return;
400 }
401 swayc_t *child = container;
402 bool ascended = false;
403
404 // View is wrapped in intermediate container which is needed for displaying
405 // the titlebar. Moving only the view outside of its parent container would just
406 // wrap it again under worspace. There would effectively be no movement,
407 // just a change of wrapping container.
408 if (child->type == C_VIEW &&
409 parent->type == C_CONTAINER &&
410 parent->children->length == 1 &&
411 parent->parent->type == C_WORKSPACE) {
412 child = parent;
413 parent = parent->parent;
414 }
415
416 while (true) {
417 sway_log(L_DEBUG, "container:%p, parent:%p, child %p,",
418 container,parent,child);
419 if (parent->layout == layout
420 || (layout == L_NONE && (parent->type == C_CONTAINER || parent->type == C_WORKSPACE)) /* accept any layout for next/prev direction */
421 || (parent->layout == L_TABBED && layout == L_HORIZ)
422 || (parent->layout == L_STACKED && layout == L_VERT)
423 || is_auto_layout(parent->layout)) {
424 int diff;
425 // If it has ascended (parent has moved up), no container is removed
426 // so insert it at index, or index+1.
427 // if it has not, the moved container is removed, so it needs to be
428 // inserted at index-1, or index+1
429 if (ascended) {
430 diff = dir == MOVE_LEFT || dir == MOVE_UP || dir == MOVE_PREV ? 0 : 1;
431 } else {
432 diff = dir == MOVE_LEFT || dir == MOVE_UP || dir == MOVE_PREV ? -1 : 1;
433 }
434 int idx = index_child(child);
435 int desired = idx + diff;
436 if (dir == MOVE_NEXT || dir == MOVE_PREV) {
437 // Next/Prev always wrap.
438 if (desired < 0) {
439 desired += parent->children->length;
440 } else if (desired >= parent->children->length) {
441 desired = 0;
442 }
443 }
444 // when it has ascended, legal insertion position is 0:len
445 // when it has not, legal insertion position is 0:len-1
446 if (desired >= 0 && desired - ascended < parent->children->length) {
447 if (!ascended) {
448 child = parent->children->items[desired];
449 // Move container into sibling container
450 if (child->type == C_CONTAINER) {
451 parent = child;
452 // Insert it in first/last if matching layout, otherwise
453 // insert it next to focused container
454 if (parent->layout == layout
455 || (parent->layout == L_TABBED && layout == L_HORIZ)
456 || (parent->layout == L_STACKED && layout == L_VERT)
457 || is_auto_layout(parent->layout)) {
458 desired = (diff < 0) * parent->children->length;
459 } else {
460 desired = index_child(child->focused) + 1;
461 }
462 //reset geometry
463 container->width = container->height = 0;
464 }
465 }
466 if (container->parent == parent) {
467 swap_children(parent, idx, desired);
468 } else {
469 swayc_t *old_parent = remove_child(container);
470 insert_child(parent, container, desired);
471 destroy_container(old_parent);
472 sway_log(L_DEBUG,"Moving to %p %d", parent, desired);
473 }
474 break;
475 }
476 }
477 // Change parent layout if we need to
478 if (parent->children->length == 1 && parent->layout != layout && layout != L_NONE) {
479 /* swayc_change_layout(parent, layout); */
480 parent->layout = layout;
481 continue;
482 }
483 if (parent->type == C_WORKSPACE) {
484 // If moving to an adjacent output we need a starting position (since this
485 // output might border to multiple outputs).
486 struct wlc_point abs_pos;
487 get_absolute_center_position(container, &abs_pos);
488
489 swayc_t *output = swayc_adjacent_output(parent->parent, dir, &abs_pos, true);
490
491 if (output) {
492 sway_log(L_DEBUG, "Moving between outputs");
493 swayc_t *old_parent = remove_child(container);
494 destroy_container(old_parent);
495
496 swayc_t *dest = output->focused;
497 switch (dir) {
498 case MOVE_LEFT:
499 case MOVE_UP:
500 // reset container geometry
501 container->width = container->height = 0;
502 add_child(dest, container);
503 break;
504 case MOVE_RIGHT:
505 case MOVE_DOWN:
506 // reset container geometry
507 container->width = container->height = 0;
508 insert_child(dest, container, 0);
509 break;
510 default:
511 break;
512 }
513 // arrange new workspace
514 arrange_windows(dest, -1, -1);
515 set_focused_container(container);
516 break;
517 }
518
519 // We simply cannot move any further.
520 if (parent->layout == layout) {
521 break;
522 }
523 // Create container around workspace to insert child into
524 parent = new_container(parent, layout);
525 // Previous line set the resulting container's layout to
526 // workspace_layout. It should have been just layout.
527 parent->layout = parent->parent->layout;
528 }
529 ascended = true;
530 child = parent;
531 parent = child->parent;
532 }
533 arrange_windows(parent->parent, -1, -1);
534 ipc_event_window(container, "move");
535 set_focused_container_for(parent->parent, container);
536}
537
538void move_container_to(swayc_t* container, swayc_t* destination) {
539 if (container == destination || swayc_is_parent_of(container, destination)) {
540 return;
541 }
542 swayc_t *parent = remove_child(container);
543 // Send to new destination
544 if (container->is_floating) {
545 swayc_t *ws = swayc_active_workspace_for(destination);
546 add_floating(ws, container);
547
548 // If the workspace only has one child after adding one, it
549 // means that the workspace was just initialized.
550 if (ws->children->length + ws->floating->length == 1) {
551 ipc_event_workspace(NULL, ws, "init");
552 }
553 } else if (destination->type == C_WORKSPACE) {
554 // reset container geometry
555 container->width = container->height = 0;
556 add_child(destination, container);
557
558 // If the workspace only has one child after adding one, it
559 // means that the workspace was just initialized.
560 if (destination->children->length + destination->floating->length == 1) {
561 ipc_event_workspace(NULL, destination, "init");
562 }
563 } else {
564 // reset container geometry
565 container->width = container->height = 0;
566 add_sibling(destination, container);
567 }
568 // Destroy old container if we need to
569 parent = destroy_container(parent);
570 // Refocus
571 swayc_t *op1 = swayc_parent_by_type(destination, C_OUTPUT);
572 swayc_t *op2 = swayc_parent_by_type(parent, C_OUTPUT);
573 set_focused_container(get_focused_view(op1));
574 arrange_windows(op1, -1, -1);
575 update_visibility(op1);
576 if (op1 != op2) {
577 set_focused_container(get_focused_view(op2));
578 arrange_windows(op2, -1, -1);
579 update_visibility(op2);
580 }
581}
582
583void move_workspace_to(swayc_t* workspace, swayc_t* destination) {
584 if (workspace == destination || swayc_is_parent_of(workspace, destination)) {
585 return;
586 }
587 swayc_t *src_op = remove_child(workspace);
588 // reset container geometry
589 workspace->width = workspace->height = 0;
590 add_child(destination, workspace);
591 sort_workspaces(destination);
592 // Refocus destination (change to new workspace)
593 set_focused_container(get_focused_view(workspace));
594 arrange_windows(destination, -1, -1);
595 update_visibility(destination);
596
597 // make sure source output has a workspace
598 if (src_op->children->length == 0) {
599 char *ws_name = workspace_next_name(src_op->name);
600 swayc_t *ws = new_workspace(src_op, ws_name);
601 ws->is_focused = true;
602 free(ws_name);
603 }
604 set_focused_container(get_focused_view(src_op));
605 update_visibility(src_op);
606}
607
608static void adjust_border_geometry(swayc_t *c, struct wlc_geometry *g,
609 const struct wlc_size *res, int left, int right, int top, int bottom) {
610
611 g->size.w += left + right;
612 if (g->origin.x - left < 0) {
613 g->size.w += g->origin.x - left;
614 } else if (g->origin.x + g->size.w - right > res->w) {
615 g->size.w = res->w - g->origin.x + right;
616 }
617
618 g->size.h += top + bottom;
619 if (g->origin.y - top < 0) {
620 g->size.h += g->origin.y - top;
621 } else if (g->origin.y + g->size.h - top > res->h) {
622 g->size.h = res->h - g->origin.y + top;
623 }
624
625 g->origin.x = MIN((uint32_t)MAX(g->origin.x - left, 0), res->w);
626 g->origin.y = MIN((uint32_t)MAX(g->origin.y - top, 0), res->h);
627
628}
629
630static void update_border_geometry_floating(swayc_t *c, struct wlc_geometry *geometry) {
631 struct wlc_geometry g = *geometry;
632 c->actual_geometry = g;
633
634 swayc_t *output = swayc_parent_by_type(c, C_OUTPUT);
635 struct wlc_size res;
636 output_get_scaled_size(output->handle, &res);
637
638 switch (c->border_type) {
639 case B_NONE:
640 break;
641 case B_PIXEL:
642 adjust_border_geometry(c, &g, &res, c->border_thickness,
643 c->border_thickness, c->border_thickness, c->border_thickness);
644 break;
645 case B_NORMAL:
646 {
647 int title_bar_height = config->font_height + 4; // borders + padding
648
649 adjust_border_geometry(c, &g, &res, c->border_thickness,
650 c->border_thickness, title_bar_height, c->border_thickness);
651
652 struct wlc_geometry title_bar = {
653 .origin = {
654 .x = c->actual_geometry.origin.x - c->border_thickness,
655 .y = c->actual_geometry.origin.y - title_bar_height
656 },
657 .size = {
658 .w = c->actual_geometry.size.w + (2 * c->border_thickness),
659 .h = title_bar_height
660 }
661 };
662 c->title_bar_geometry = title_bar;
663 break;
664 }
665 }
666
667 c->border_geometry = g;
668 *geometry = c->actual_geometry;
669
670 update_container_border(c);
671}
672
673void update_layout_geometry(swayc_t *parent, enum swayc_layouts prev_layout) {
674 switch (parent->layout) {
675 case L_TABBED:
676 case L_STACKED:
677 if (prev_layout != L_TABBED && prev_layout != L_STACKED) {
678 // cache current geometry for all non-float children
679 int i;
680 for (i = 0; i < parent->children->length; ++i) {
681 swayc_t *child = parent->children->items[i];
682 child->cached_geometry.origin.x = child->x;
683 child->cached_geometry.origin.y = child->y;
684 child->cached_geometry.size.w = child->width;
685 child->cached_geometry.size.h = child->height;
686 }
687 }
688 break;
689 default:
690 if (prev_layout == L_TABBED || prev_layout == L_STACKED) {
691 // recover cached geometry for all non-float children
692 int i;
693 for (i = 0; i < parent->children->length; ++i) {
694 swayc_t *child = parent->children->items[i];
695 // only recoverer cached geometry if non-zero
696 if (!wlc_geometry_equals(&child->cached_geometry, &wlc_geometry_zero)) {
697 child->x = child->cached_geometry.origin.x;
698 child->y = child->cached_geometry.origin.y;
699 child->width = child->cached_geometry.size.w;
700 child->height = child->cached_geometry.size.h;
701 }
702 }
703 }
704 break;
705 }
706}
707
708static int update_gap_geometry(swayc_t *container, struct wlc_geometry *g) {
709 swayc_t *ws = swayc_parent_by_type(container, C_WORKSPACE);
710 swayc_t *op = ws->parent;
711 int gap = container->is_floating ? 0 : swayc_gap(container);
712 if (gap % 2 != 0) {
713 // because gaps are implemented as "half sized margins" it's currently
714 // not possible to align views properly with odd sized gaps.
715 gap -= 1;
716 }
717
718 g->origin.x = container->x + gap/2 < op->width ? container->x + gap/2 : op->width-1;
719 g->origin.y = container->y + gap/2 < op->height ? container->y + gap/2 : op->height-1;
720 g->size.w = container->width > gap ? container->width - gap : 1;
721 g->size.h = container->height > gap ? container->height - gap : 1;
722
723 if ((!config->edge_gaps && gap > 0) || (config->smart_gaps && ws->children->length == 1)) {
724 // Remove gap against the workspace edges. Because a pixel is not
725 // divisable, depending on gap size and the number of siblings our view
726 // might be at the workspace edge without being exactly so (thus test
727 // with gap, and align correctly).
728 if (container->x - gap <= ws->x) {
729 g->origin.x = ws->x;
730 g->size.w = container->width - gap/2;
731 }
732 if (container->y - gap <= ws->y) {
733 g->origin.y = ws->y;
734 g->size.h = container->height - gap/2;
735 }
736 if (container->x + container->width + gap >= ws->x + ws->width) {
737 g->size.w = ws->x + ws->width - g->origin.x;
738 }
739 if (container->y + container->height + gap >= ws->y + ws->height) {
740 g->size.h = ws->y + ws->height - g->origin.y;
741 }
742 }
743
744 return gap;
745}
746
747void update_geometry(swayc_t *container) {
748 if (container->type != C_VIEW && container->type != C_CONTAINER) {
749 return;
750 }
751
752 swayc_t *workspace = swayc_parent_by_type(container, C_WORKSPACE);
753 swayc_t *op = workspace->parent;
754 swayc_t *parent = container->parent;
755
756 struct wlc_geometry geometry = {
757 .origin = {
758 .x = container->x < op->width ? container->x : op->width-1,
759 .y = container->y < op->height ? container->y : op->height-1
760 },
761 .size = {
762 .w = container->width,
763 .h = container->height,
764 }
765 };
766
767 int gap = 0;
768
769 // apply inner gaps to non-tabbed/stacked containers
770 swayc_t *p = swayc_tabbed_stacked_ancestor(container);
771 if (p == NULL) {
772 gap = update_gap_geometry(container, &geometry);
773 }
774
775 swayc_t *output = swayc_parent_by_type(container, C_OUTPUT);
776 struct wlc_size size;
777 output_get_scaled_size(output->handle, &size);
778
779 if (swayc_is_fullscreen(container)) {
780 geometry.origin.x = 0;
781 geometry.origin.y = 0;
782 geometry.size.w = size.w;
783 geometry.size.h = size.h;
784 if (op->focused == workspace) {
785 wlc_view_bring_to_front(container->handle);
786 }
787
788 container->border_geometry = wlc_geometry_zero;
789 container->title_bar_geometry = wlc_geometry_zero;
790 border_clear(container->border);
791 } else if (container->is_floating) { // allocate border for floating window
792 update_border_geometry_floating(container, &geometry);
793 } else if (!container->is_floating) { // allocate border for titled window
794 container->border_geometry = geometry;
795
796 int border_top = container->border_thickness;
797 int border_bottom = container->border_thickness;
798 int border_left = container->border_thickness;
799 int border_right = container->border_thickness;
800
801 // handle hide_edge_borders
802 if (config->hide_edge_borders != E_NONE && (gap <= 0 || (config->smart_gaps && workspace->children->length == 1))) {
803 if (config->hide_edge_borders == E_VERTICAL || config->hide_edge_borders == E_BOTH) {
804 if (geometry.origin.x == workspace->x) {
805 border_left = 0;
806 }
807
808 if (geometry.origin.x + geometry.size.w == workspace->x + workspace->width) {
809 border_right = 0;
810 }
811 }
812
813 if (config->hide_edge_borders == E_HORIZONTAL || config->hide_edge_borders == E_BOTH) {
814 if (geometry.origin.y == workspace->y || should_hide_top_border(container, geometry.origin.y)) {
815 border_top = 0;
816 }
817
818 if (geometry.origin.y + geometry.size.h == workspace->y + workspace->height) {
819 border_bottom = 0;
820 }
821 }
822
823 if (config->hide_edge_borders == E_SMART && workspace->children->length == 1) {
824 border_top = 0;
825 border_bottom = 0;
826 border_left = 0;
827 border_right = 0;
828 }
829 }
830
831 int title_bar_height = config->font_height + 4; //borders + padding
832
833 if (parent->layout == L_TABBED && parent->children->length > 1) {
834 int i, x = 0, w, l, r;
835 l = parent->children->length;
836 w = geometry.size.w / l;
837 r = geometry.size.w % l;
838 for (i = 0; i < parent->children->length; ++i) {
839 swayc_t *view = parent->children->items[i];
840 if (view == container) {
841 x = w * i;
842 if (i == l - 1) {
843 w += r;
844 }
845 break;
846 }
847 }
848
849 struct wlc_geometry title_bar = {
850 .origin = {
851 .x = container->border_geometry.origin.x + x,
852 .y = container->border_geometry.origin.y
853 },
854 .size = {
855 .w = w,
856 .h = title_bar_height
857 }
858 };
859 geometry.origin.x += border_left;
860 geometry.origin.y += title_bar.size.h;
861 geometry.size.w -= (border_left + border_right);
862 geometry.size.h -= (border_bottom + title_bar.size.h);
863 container->title_bar_geometry = title_bar;
864 } else if (parent->layout == L_STACKED && parent->children->length > 1) {
865 int i, y = 0;
866 for (i = 0; i < parent->children->length; ++i) {
867 swayc_t *view = parent->children->items[i];
868 if (view == container) {
869 y = title_bar_height * i;
870 }
871 }
872
873 struct wlc_geometry title_bar = {
874 .origin = {
875 .x = container->border_geometry.origin.x,
876 .y = container->border_geometry.origin.y + y
877 },
878 .size = {
879 .w = container->border_geometry.size.w,
880 .h = title_bar_height
881 }
882 };
883 title_bar_height = title_bar_height * parent->children->length;
884 geometry.origin.x += border_left;
885 geometry.origin.y += title_bar_height;
886 geometry.size.w -= (border_left + border_right);
887 geometry.size.h -= (border_bottom + title_bar_height);
888 container->title_bar_geometry = title_bar;
889 } else {
890 switch (container->border_type) {
891 case B_NONE:
892 break;
893 case B_PIXEL:
894 geometry.origin.x += border_left;
895 geometry.origin.y += border_top;
896 geometry.size.w -= (border_left + border_right);
897 geometry.size.h -= (border_top + border_bottom);
898 break;
899 case B_NORMAL:
900 {
901 struct wlc_geometry title_bar = {
902 .origin = {
903 .x = container->border_geometry.origin.x,
904 .y = container->border_geometry.origin.y
905 },
906 .size = {
907 .w = container->border_geometry.size.w,
908 .h = title_bar_height
909 }
910 };
911 geometry.origin.x += border_left;
912 geometry.origin.y += title_bar.size.h;
913 geometry.size.w -= (border_left + border_right);
914 geometry.size.h -= (border_bottom + title_bar.size.h);
915 container->title_bar_geometry = title_bar;
916 break;
917 }
918 }
919 }
920
921 container->actual_geometry = geometry;
922
923 if (container->type == C_VIEW) {
924 update_container_border(container);
925 }
926 }
927
928 if (container->type == C_VIEW) {
929 wlc_view_set_geometry(container->handle, 0, &geometry);
930 }
931}
932
933/**
934 * Layout application prototypes
935 */
936static void apply_horiz_layout(swayc_t *container, const double x,
937 const double y, const double width,
938 const double height, const int start,
939 const int end);
940static void apply_vert_layout(swayc_t *container, const double x,
941 const double y, const double width,
942 const double height, const int start,
943 const int end);
944static void apply_tabbed_or_stacked_layout(swayc_t *container, double x,
945 double y, double width,
946 double height);
947
948static void apply_auto_layout(swayc_t *container, const double x, const double y,
949 const double width, const double height,
950 enum swayc_layouts group_layout,
951 bool master_first);
952
953static void arrange_windows_r(swayc_t *container, double width, double height) {
954 int i;
955 if (width == -1 || height == -1) {
956 swayc_log(L_DEBUG, container, "Arranging layout for %p", container);
957 width = container->width;
958 height = container->height;
959 }
960 // pixels are indivisible. if we don't round the pixels, then the view
961 // calculations will be off (e.g. 50.5 + 50.5 = 101, but in reality it's
962 // 50 + 50 = 100). doing it here cascades properly to all width/height/x/y.
963 width = floor(width);
964 height = floor(height);
965
966 sway_log(L_DEBUG, "Arranging layout for %p %s %fx%f+%f,%f", container,
967 container->name, container->width, container->height, container->x,
968 container->y);
969
970 double x = 0, y = 0;
971 switch (container->type) {
972 case C_ROOT:
973 for (i = 0; i < container->children->length; ++i) {
974 swayc_t *output = container->children->items[i];
975 sway_log(L_DEBUG, "Arranging output '%s' at %f,%f", output->name, output->x, output->y);
976 arrange_windows_r(output, -1, -1);
977 }
978 return;
979 case C_OUTPUT:
980 {
981 struct wlc_size resolution;
982 output_get_scaled_size(container->handle, &resolution);
983 width = resolution.w; height = resolution.h;
984 // output must have correct size due to e.g. seamless mouse,
985 // but a workspace might be smaller depending on panels.
986 container->width = width;
987 container->height = height;
988 }
989 // arrange all workspaces:
990 for (i = 0; i < container->children->length; ++i) {
991 swayc_t *child = container->children->items[i];
992 arrange_windows_r(child, -1, -1);
993 }
994 // Bring all unmanaged views to the front
995 for (i = 0; i < container->unmanaged->length; ++i) {
996 wlc_handle *handle = container->unmanaged->items[i];
997 wlc_view_bring_to_front(*handle);
998 }
999 return;
1000 case C_WORKSPACE:
1001 {
1002 swayc_t *output = swayc_parent_by_type(container, C_OUTPUT);
1003 width = output->width, height = output->height;
1004 for (i = 0; i < desktop_shell.panels->length; ++i) {
1005 struct panel_config *config = desktop_shell.panels->items[i];
1006 if (config->output == output->handle) {
1007 struct wlc_size size = *wlc_surface_get_size(config->surface);
1008 sway_log(L_DEBUG, "-> Found panel for this workspace: %ux%u, position: %u", size.w, size.h, config->panel_position);
1009 switch (config->panel_position) {
1010 case DESKTOP_SHELL_PANEL_POSITION_TOP:
1011 y += size.h; height -= size.h;
1012 break;
1013 case DESKTOP_SHELL_PANEL_POSITION_BOTTOM:
1014 height -= size.h;
1015 break;
1016 case DESKTOP_SHELL_PANEL_POSITION_LEFT:
1017 x += size.w; width -= size.w;
1018 break;
1019 case DESKTOP_SHELL_PANEL_POSITION_RIGHT:
1020 width -= size.w;
1021 break;
1022 }
1023 }
1024 }
1025 int gap = swayc_gap(container);
1026 x = container->x = x + gap;
1027 y = container->y = y + gap;
1028 width = container->width = width - gap * 2;
1029 height = container->height = height - gap * 2;
1030 sway_log(L_DEBUG, "Arranging workspace '%s' at %f, %f", container->name, container->x, container->y);
1031 }
1032 // children are properly handled below
1033 break;
1034 case C_VIEW:
1035 {
1036 container->width = width;
1037 container->height = height;
1038 update_geometry(container);
1039 sway_log(L_DEBUG, "Set view to %.f x %.f @ %.f, %.f", container->width,
1040 container->height, container->x, container->y);
1041 }
1042 return;
1043 default:
1044 container->width = width;
1045 container->height = height;
1046 x = container->x;
1047 y = container->y;
1048
1049 // add gaps to top level tapped/stacked container
1050 if (container->parent->type == C_WORKSPACE &&
1051 (container->layout == L_TABBED || container->layout == L_STACKED)) {
1052 update_geometry(container);
1053 width = container->border_geometry.size.w;
1054 height = container->border_geometry.size.h;
1055 x = container->border_geometry.origin.x;
1056 y = container->border_geometry.origin.y;
1057 }
1058
1059 // update container size if it's a direct child in a tabbed/stacked layout
1060 // if parent is a workspace, its actual_geometry won't be initialized
1061 if (swayc_tabbed_stacked_parent(container) != NULL &&
1062 container->parent->type != C_WORKSPACE) {
1063 // Use parent actual_geometry as a base for calculating
1064 // container geometry
1065 container->width = container->parent->actual_geometry.size.w;
1066 container->height = container->parent->actual_geometry.size.h;
1067 container->x = container->parent->actual_geometry.origin.x;
1068 container->y = container->parent->actual_geometry.origin.y;
1069
1070 update_geometry(container);
1071 width = container->width = container->actual_geometry.size.w;
1072 height = container->height = container->actual_geometry.size.h;
1073 x = container->x = container->actual_geometry.origin.x;
1074 y = container->y = container->actual_geometry.origin.y;
1075 }
1076
1077 break;
1078 }
1079
1080 switch (container->layout) {
1081 case L_HORIZ:
1082 default:
1083 apply_horiz_layout(container, x, y, width, height, 0,
1084 container->children->length);
1085 break;
1086 case L_VERT:
1087 apply_vert_layout(container, x, y, width, height, 0,
1088 container->children->length);
1089 break;
1090 case L_TABBED:
1091 case L_STACKED:
1092 apply_tabbed_or_stacked_layout(container, x, y, width, height);
1093 break;
1094 case L_AUTO_LEFT:
1095 apply_auto_layout(container, x, y, width, height, L_VERT, true);
1096 break;
1097 case L_AUTO_RIGHT:
1098 apply_auto_layout(container, x, y, width, height, L_VERT, false);
1099 break;
1100 case L_AUTO_TOP:
1101 apply_auto_layout(container, x, y, width, height, L_HORIZ, true);
1102 break;
1103 case L_AUTO_BOTTOM:
1104 apply_auto_layout(container, x, y, width, height, L_HORIZ, false);
1105 break;
1106 }
1107
1108 // Arrage floating layouts for workspaces last
1109 if (container->type == C_WORKSPACE) {
1110 for (int i = 0; i < container->floating->length; ++i) {
1111 swayc_t *view = container->floating->items[i];
1112 if (view->type == C_VIEW) {
1113 update_geometry(view);
1114 sway_log(L_DEBUG, "Set floating view to %.f x %.f @ %.f, %.f",
1115 view->width, view->height, view->x, view->y);
1116 if (swayc_is_fullscreen(view)) {
1117 wlc_view_bring_to_front(view->handle);
1118 } else if (!container->focused ||
1119 !swayc_is_fullscreen(container->focused)) {
1120 wlc_view_bring_to_front(view->handle);
1121 }
1122 }
1123 }
1124 }
1125}
1126
1127void apply_horiz_layout(swayc_t *container, const double x, const double y,
1128 const double width, const double height,
1129 const int start, const int end) {
1130 double scale = 0;
1131 // Calculate total width
1132 for (int i = start; i < end; ++i) {
1133 double *old_width = &((swayc_t *)container->children->items[i])->width;
1134 if (*old_width <= 0) {
1135 if (end - start > 1) {
1136 *old_width = width / (end - start - 1);
1137 } else {
1138 *old_width = width;
1139 }
1140 }
1141 scale += *old_width;
1142 }
1143 scale = width / scale;
1144
1145 // Resize windows
1146 double child_x = x;
1147 if (scale > 0.1) {
1148 sway_log(L_DEBUG, "Arranging %p horizontally", container);
1149 swayc_t *focused = NULL;
1150 for (int i = start; i < end; ++i) {
1151 swayc_t *child = container->children->items[i];
1152 sway_log(L_DEBUG,
1153 "Calculating arrangement for %p:%d (will scale %f by %f)", child,
1154 child->type, width, scale);
1155 child->x = child_x;
1156 child->y = y;
1157
1158 if (child == container->focused) {
1159 focused = child;
1160 }
1161
1162 if (i == end - 1) {
1163 double remaining_width = x + width - child_x;
1164 arrange_windows_r(child, remaining_width, height);
1165 } else {
1166 arrange_windows_r(child, child->width * scale, height);
1167 }
1168 child_x += child->width;
1169 }
1170
1171 // update focused view border last because it may
1172 // depend on the title bar geometry of its siblings.
1173 if (focused && container->children->length > 1) {
1174 update_container_border(focused);
1175 }
1176 }
1177}
1178
1179void apply_vert_layout(swayc_t *container, const double x, const double y,
1180 const double width, const double height, const int start,
1181 const int end) {
1182 int i;
1183 double scale = 0;
1184 // Calculate total height
1185 for (i = start; i < end; ++i) {
1186 double *old_height = &((swayc_t *)container->children->items[i])->height;
1187 if (*old_height <= 0) {
1188 if (end - start > 1) {
1189 *old_height = height / (end - start - 1);
1190 } else {
1191 *old_height = height;
1192 }
1193 }
1194 scale += *old_height;
1195 }
1196 scale = height / scale;
1197
1198 // Resize
1199 double child_y = y;
1200 if (scale > 0.1) {
1201 sway_log(L_DEBUG, "Arranging %p vertically", container);
1202 swayc_t *focused = NULL;
1203 for (i = start; i < end; ++i) {
1204 swayc_t *child = container->children->items[i];
1205 sway_log(L_DEBUG,
1206 "Calculating arrangement for %p:%d (will scale %f by %f)", child,
1207 child->type, height, scale);
1208 child->x = x;
1209 child->y = child_y;
1210
1211 if (child == container->focused) {
1212 focused = child;
1213 }
1214
1215 if (i == end - 1) {
1216 double remaining_height = y + height - child_y;
1217 arrange_windows_r(child, width, remaining_height);
1218 } else {
1219 arrange_windows_r(child, width, child->height * scale);
1220 }
1221 child_y += child->height;
1222 }
1223
1224 // update focused view border last because it may
1225 // depend on the title bar geometry of its siblings.
1226 if (focused && container->children->length > 1) {
1227 update_container_border(focused);
1228 }
1229 }
1230}
1231
1232void apply_tabbed_or_stacked_layout(swayc_t *container, double x, double y,
1233 double width, double height) {
1234 int i;
1235 swayc_t *focused = NULL;
1236 for (i = 0; i < container->children->length; ++i) {
1237 swayc_t *child = container->children->items[i];
1238 child->x = x;
1239 child->y = y;
1240 if (child == container->focused) {
1241 focused = child;
1242 } else {
1243 arrange_windows_r(child, width, height);
1244 }
1245 }
1246
1247 if (focused) {
1248 arrange_windows_r(focused, width, height);
1249 }
1250}
1251
1252void apply_auto_layout(swayc_t *container, const double x, const double y,
1253 const double width, const double height,
1254 enum swayc_layouts group_layout,
1255 bool master_first) {
1256 // Auto layout "container" in width x height @ x, y
1257 // using "group_layout" for each of the groups in the container.
1258 // There is one "master" group, plus container->nb_slave_groups.
1259 // Each group is layed out side by side following the "major" axis.
1260 // The direction of the layout used for groups is the "minor" axis.
1261 // Example:
1262 //
1263 // ---- major axis -->
1264 // +---------+-----------+
1265 // | | | |
1266 // | master | slave 1 | |
1267 // | +-----------+ | minor axis (direction of group_layout)
1268 // | | | |
1269 // | | slave 2 | V
1270 // +---------+-----------+
1271 //
1272 // container with three children (one master and two slaves) and
1273 // a single slave group (containing slave 1 and 2). The master
1274 // group and slave group are layed out using L_VERT.
1275
1276 size_t nb_groups = auto_group_count(container);
1277
1278 // the target dimension of the container along the "major" axis, each
1279 // group in the container will be layed out using "group_layout" along
1280 // the "minor" axis.
1281 double dim_maj;
1282 double pos_maj;
1283
1284 // x and y coords for the next group to be laid out.
1285 const double *group_x, *group_y;
1286
1287 // pos of the next group to layout along the major axis
1288 double pos;
1289
1290 // size of the next group along the major axis.
1291 double group_dim;
1292
1293 // height and width of next group to be laid out.
1294 const double *group_h, *group_w;
1295
1296 switch (group_layout) {
1297 default:
1298 sway_log(L_DEBUG, "Unknown layout type (%d) used in %s()",
1299 group_layout, __func__);
1300 /* fall through */
1301 case L_VERT:
1302 dim_maj = width;
1303 pos_maj = x;
1304
1305 group_x = &pos;
1306 group_y = &y;
1307 group_w = &group_dim;
1308 group_h = &height;
1309 break;
1310 case L_HORIZ:
1311 dim_maj = height;
1312 pos_maj = y;
1313
1314 group_x = &x;
1315 group_y = &pos;
1316 group_w = &width;
1317 group_h = &group_dim;
1318 break;
1319 }
1320
1321 /* Determine the dimension of each of the groups in the layout.
1322 * Dimension will be width for a VERT layout and height for a HORIZ
1323 * layout. */
1324 double old_group_dim[nb_groups];
1325 double old_dim = 0;
1326 for (size_t group = 0; group < nb_groups; ++group) {
1327 int idx;
1328 if (auto_group_bounds(container, group, &idx, NULL)) {
1329 swayc_t *child = container->children->items[idx];
1330 double *dim = group_layout == L_HORIZ ? &child->height : &child->width;
1331 if (*dim <= 0) {
1332 // New child with uninitialized dimension
1333 *dim = dim_maj;
1334 if (nb_groups > 1) {
1335 // child gets a dimension proportional to existing groups,
1336 // it will be later scaled based on to the available size
1337 // in the major axis.
1338 *dim /= (nb_groups - 1);
1339 }
1340 }
1341 old_dim += *dim;
1342 old_group_dim[group] = *dim;
1343 }
1344 }
1345 double scale = dim_maj / old_dim;
1346
1347 /* Apply layout to each group */
1348 pos = pos_maj;
1349
1350 for (size_t group = 0; group < nb_groups; ++group) {
1351 int start, end; // index of first (inclusive) and last (exclusive) child in the group
1352 if (auto_group_bounds(container, group, &start, &end)) {
1353 // adjusted size of the group
1354 group_dim = old_group_dim[group] * scale;
1355 if (group == nb_groups - 1) {
1356 group_dim = pos_maj + dim_maj - pos; // remaining width
1357 }
1358 sway_log(L_DEBUG, "Arranging container %p column %zu, children [%d,%d[ (%fx%f+%f,%f)",
1359 container, group, start, end, *group_w, *group_h, *group_x, *group_y);
1360 switch (group_layout) {
1361 default:
1362 case L_VERT:
1363 apply_vert_layout(container, *group_x, *group_y, *group_w, *group_h, start, end);
1364 break;
1365 case L_HORIZ:
1366 apply_horiz_layout(container, *group_x, *group_y, *group_w, *group_h, start, end);
1367 break;
1368 }
1369
1370 /* update position for next group */
1371 pos += group_dim;
1372 }
1373 }
1374}
1375
1376void arrange_windows(swayc_t *container, double width, double height) {
1377 update_visibility(container);
1378 arrange_windows_r(container, width, height);
1379 layout_log(&root_container, 0);
1380}
1381
1382void arrange_backgrounds(void) {
1383 struct background_config *bg;
1384 for (int i = 0; i < desktop_shell.backgrounds->length; ++i) {
1385 bg = desktop_shell.backgrounds->items[i];
1386 wlc_view_send_to_back(bg->handle);
1387 }
1388}
1389
1390/**
1391 * Get swayc in the direction of newly entered output.
1392 */
1393static swayc_t *get_swayc_in_output_direction(swayc_t *output, enum movement_direction dir) {
1394 if (!output) {
1395 return NULL;
1396 }
1397
1398 swayc_t *ws = swayc_focus_by_type(output, C_WORKSPACE);
1399 if (ws && ws->children->length > 0) {
1400 switch (dir) {
1401 case MOVE_LEFT:
1402 // get most right child of new output
1403 return ws->children->items[ws->children->length-1];
1404 case MOVE_RIGHT:
1405 // get most left child of new output
1406 return ws->children->items[0];
1407 case MOVE_UP:
1408 case MOVE_DOWN:
1409 {
1410 swayc_t *focused_view = swayc_focus_by_type(ws, C_VIEW);
1411 if (focused_view && focused_view->parent) {
1412 swayc_t *parent = focused_view->parent;
1413 if (parent->layout == L_VERT) {
1414 if (dir == MOVE_UP) {
1415 // get child furthest down on new output
1416 return parent->children->items[parent->children->length-1];
1417 } else if (dir == MOVE_DOWN) {
1418 // get child furthest up on new output
1419 return parent->children->items[0];
1420 }
1421 }
1422 return focused_view;
1423 }
1424 break;
1425 }
1426 default:
1427 break;
1428 }
1429 }
1430
1431 return output;
1432}
1433
1434swayc_t *get_swayc_in_direction_under(swayc_t *container, enum movement_direction dir, swayc_t *limit) {
1435 if (dir == MOVE_CHILD) {
1436 return container->focused;
1437 }
1438
1439 swayc_t *parent = container->parent;
1440 if (dir == MOVE_PARENT) {
1441 if (parent->type == C_OUTPUT) {
1442 return NULL;
1443 } else {
1444 return parent;
1445 }
1446 }
1447
1448 if (dir == MOVE_PREV || dir == MOVE_NEXT) {
1449 int focused_idx = index_child(container);
1450 if (focused_idx == -1) {
1451 return NULL;
1452 } else {
1453 int desired = (focused_idx + (dir == MOVE_NEXT ? 1 : -1)) %
1454 parent->children->length;
1455 if (desired < 0) {
1456 desired += parent->children->length;
1457 }
1458 return parent->children->items[desired];
1459 }
1460 }
1461
1462 // If moving to an adjacent output we need a starting position (since this
1463 // output might border to multiple outputs).
1464 struct wlc_point abs_pos;
1465 get_absolute_center_position(container, &abs_pos);
1466
1467 if (container->type == C_VIEW && swayc_is_fullscreen(container)) {
1468 sway_log(L_DEBUG, "Moving from fullscreen view, skipping to output");
1469 container = swayc_parent_by_type(container, C_OUTPUT);
1470 get_absolute_center_position(container, &abs_pos);
1471 swayc_t *output = swayc_adjacent_output(container, dir, &abs_pos, true);
1472 return get_swayc_in_output_direction(output, dir);
1473 }
1474
1475 if (container->type == C_WORKSPACE && container->fullscreen) {
1476 sway_log(L_DEBUG, "Moving to fullscreen view");
1477 return container->fullscreen;
1478 }
1479
1480 swayc_t *wrap_candidate = NULL;
1481 while (true) {
1482 // Test if we can even make a difference here
1483 bool can_move = false;
1484 int desired;
1485 int idx = index_child(container);
1486 if (parent->type == C_ROOT) {
1487 swayc_t *output = swayc_adjacent_output(container, dir, &abs_pos, true);
1488 if (!output || output == container) {
1489 return wrap_candidate;
1490 }
1491 sway_log(L_DEBUG, "Moving between outputs");
1492 return get_swayc_in_output_direction(output, dir);
1493 } else {
1494 if (is_auto_layout(parent->layout)) {
1495 bool is_major = parent->layout == L_AUTO_LEFT || parent->layout == L_AUTO_RIGHT
1496 ? dir == MOVE_LEFT || dir == MOVE_RIGHT
1497 : dir == MOVE_DOWN || dir == MOVE_UP;
1498 size_t gidx = auto_group_index(parent, idx);
1499 if (is_major) {
1500 size_t desired_grp = gidx + (dir == MOVE_RIGHT || dir == MOVE_DOWN ? 1 : -1);
1501 can_move = auto_group_bounds(parent, desired_grp, &desired, NULL);
1502 } else {
1503 desired = idx + (dir == MOVE_RIGHT || dir == MOVE_DOWN ? 1 : -1);
1504 int start, end;
1505 can_move = auto_group_bounds(parent, gidx, &start, &end)
1506 && desired >= start && desired < end;
1507 }
1508 } else {
1509 if (dir == MOVE_LEFT || dir == MOVE_RIGHT) {
1510 if (parent->layout == L_HORIZ || parent->layout == L_TABBED) {
1511 can_move = true;
1512 desired = idx + (dir == MOVE_LEFT ? -1 : 1);
1513 }
1514 } else {
1515 if (parent->layout == L_VERT || parent->layout == L_STACKED) {
1516 can_move = true;
1517 desired = idx + (dir == MOVE_UP ? -1 : 1);
1518 }
1519 }
1520 }
1521 }
1522
1523 if (can_move) {
1524 if (container->is_floating) {
1525 if (desired < 0) {
1526 wrap_candidate = parent->floating->items[parent->floating->length-1];
1527 } else if (desired >= parent->floating->length){
1528 wrap_candidate = parent->floating->items[0];
1529 } else {
1530 wrap_candidate = parent->floating->items[desired];
1531 }
1532 if (wrap_candidate) {
1533 wlc_view_bring_to_front(wrap_candidate->handle);
1534 }
1535 return wrap_candidate;
1536 } else if (desired < 0 || desired >= parent->children->length) {
1537 can_move = false;
1538 int len = parent->children->length;
1539 if (!wrap_candidate && len > 1) {
1540 if (desired < 0) {
1541 wrap_candidate = parent->children->items[len-1];
1542 } else {
1543 wrap_candidate = parent->children->items[0];
1544 }
1545 if (config->force_focus_wrapping) {
1546 return wrap_candidate;
1547 }
1548 }
1549 } else {
1550 sway_log(L_DEBUG, "%s cont %d-%p dir %i sibling %d: %p", __func__,
1551 idx, container, dir, desired, parent->children->items[desired]);
1552 return parent->children->items[desired];
1553 }
1554 }
1555 if (!can_move) {
1556 container = parent;
1557 parent = parent->parent;
1558 if (!parent || container == limit) {
1559 // wrapping is the last chance
1560 return wrap_candidate;
1561 }
1562 }
1563 }
1564}
1565
1566swayc_t *get_swayc_in_direction(swayc_t *container, enum movement_direction dir) {
1567 return get_swayc_in_direction_under(container, dir, NULL);
1568}
1569
1570void recursive_resize(swayc_t *container, double amount, enum wlc_resize_edge edge) {
1571 int i;
1572 bool layout_match = true;
1573 sway_log(L_DEBUG, "Resizing %p with amount: %f", container, amount);
1574 if (edge == WLC_RESIZE_EDGE_LEFT || edge == WLC_RESIZE_EDGE_RIGHT) {
1575 container->width += amount;
1576 layout_match = container->layout == L_HORIZ;
1577 } else if (edge == WLC_RESIZE_EDGE_TOP || edge == WLC_RESIZE_EDGE_BOTTOM) {
1578 container->height += amount;
1579 layout_match = container->layout == L_VERT;
1580 }
1581 if (container->type == C_VIEW) {
1582 update_geometry(container);
1583 return;
1584 }
1585 if (layout_match) {
1586 for (i = 0; i < container->children->length; i++) {
1587 recursive_resize(container->children->items[i], amount/container->children->length, edge);
1588 }
1589 } else {
1590 for (i = 0; i < container->children->length; i++) {
1591 recursive_resize(container->children->items[i], amount, edge);
1592 }
1593 }
1594}
1595
1596enum swayc_layouts default_layout(swayc_t *output) {
1597 if (config->default_layout != L_NONE) {
1598 return config->default_layout;
1599 } else if (config->default_orientation != L_NONE) {
1600 return config->default_orientation;
1601 } else if (output->width >= output->height) {
1602 return L_HORIZ;
1603 } else {
1604 return L_VERT;
1605 }
1606}
1607
1608bool is_auto_layout(enum swayc_layouts layout) {
1609 return (layout >= L_AUTO_FIRST) && (layout <= L_AUTO_LAST);
1610}
1611
1612/**
1613 * Return the number of master elements in a container
1614 */
1615static inline size_t auto_master_count(const swayc_t *container) {
1616 sway_assert(container->children->length >= 0, "Container %p has (negative) children %d",
1617 container, container->children->length);
1618 return MIN(container->nb_master, (size_t)container->children->length);
1619}
1620
1621/**
1622 * Return the number of children in the slave groups. This corresponds to the children
1623 * that are not members of the master group.
1624 */
1625static inline size_t auto_slave_count(const swayc_t *container) {
1626 return container->children->length - auto_master_count(container);
1627}
1628
1629/**
1630 * Return the number of slave groups in the container.
1631 */
1632size_t auto_slave_group_count(const swayc_t *container) {
1633 return MIN(container->nb_slave_groups, auto_slave_count(container));
1634}
1635
1636/**
1637 * Return the combined number of master and slave groups in the container.
1638 */
1639size_t auto_group_count(const swayc_t *container) {
1640 return auto_slave_group_count(container)
1641 + (container->children->length && container->nb_master ? 1 : 0);
1642}
1643
1644/**
1645 * given the index of a container's child, return the index of the first child of the group
1646 * which index is a member of.
1647 */
1648int auto_group_start_index(const swayc_t *container, int index) {
1649 if (index < 0 || ! is_auto_layout(container->layout)
1650 || (size_t)index < container->nb_master) {
1651 return 0;
1652 } else {
1653 size_t nb_slaves = auto_slave_count(container);
1654 size_t nb_slave_grp = auto_slave_group_count(container);
1655 size_t grp_sz = nb_slaves / nb_slave_grp;
1656 size_t remainder = nb_slaves % nb_slave_grp;
1657 int idx2 = (nb_slave_grp - remainder) * grp_sz + container->nb_master;
1658 int start_idx;
1659 if (index < idx2) {
1660 start_idx = ((index - container->nb_master) / grp_sz) * grp_sz + container->nb_master;
1661 } else {
1662 start_idx = idx2 + ((index - idx2) / (grp_sz + 1)) * (grp_sz + 1);
1663 }
1664 return MIN(start_idx, container->children->length);
1665 }
1666}
1667
1668/**
1669 * given the index of a container's child, return the index of the first child of the group
1670 * that follows the one which index is a member of.
1671 * This makes the function usable to walk through the groups in a container.
1672 */
1673int auto_group_end_index(const swayc_t *container, int index) {
1674 if (index < 0 || ! is_auto_layout(container->layout)) {
1675 return container->children->length;
1676 } else {
1677 int nxt_idx;
1678 if ((size_t)index < container->nb_master) {
1679 nxt_idx = auto_master_count(container);
1680 } else {
1681 size_t nb_slaves = auto_slave_count(container);
1682 size_t nb_slave_grp = auto_slave_group_count(container);
1683 size_t grp_sz = nb_slaves / nb_slave_grp;
1684 size_t remainder = nb_slaves % nb_slave_grp;
1685 int idx2 = (nb_slave_grp - remainder) * grp_sz + container->nb_master;
1686 if (index < idx2) {
1687 nxt_idx = ((index - container->nb_master) / grp_sz + 1) * grp_sz + container->nb_master;
1688 } else {
1689 nxt_idx = idx2 + ((index - idx2) / (grp_sz + 1) + 1) * (grp_sz + 1);
1690 }
1691 }
1692 return MIN(nxt_idx, container->children->length);
1693 }
1694}
1695
1696/**
1697 * return the index of the Group containing <index>th child of <container>.
1698 * The index is the order of the group along the container's major axis (starting at 0).
1699 */
1700size_t auto_group_index(const swayc_t *container, int index) {
1701 if (index < 0) {
1702 return 0;
1703 }
1704 bool master_first = (container->layout == L_AUTO_LEFT || container->layout == L_AUTO_TOP);
1705 size_t nb_slaves = auto_slave_count(container);
1706 if ((size_t)index < container->nb_master) {
1707 if (master_first || nb_slaves <= 0) {
1708 return 0;
1709 } else {
1710 return auto_slave_group_count(container);
1711 }
1712 } else {
1713 size_t nb_slave_grp = auto_slave_group_count(container);
1714 size_t grp_sz = nb_slaves / nb_slave_grp;
1715 size_t remainder = nb_slaves % nb_slave_grp;
1716 int idx2 = (nb_slave_grp - remainder) * grp_sz + container->nb_master;
1717 size_t grp_idx;
1718 if (index < idx2) {
1719 grp_idx = (index - container->nb_master) / grp_sz;
1720 } else {
1721 grp_idx = (nb_slave_grp - remainder) + (index - idx2) / (grp_sz + 1) ;
1722 }
1723 return grp_idx + (master_first && container-> nb_master ? 1 : 0);
1724 }
1725}
1726
1727/**
1728 * Return the first index (inclusive) and last index (exclusive) of the elements of a group in
1729 * an auto layout.
1730 * If the bounds of the given group can be calculated, they are returned in the start/end
1731 * parameters (int pointers) and the return value will be true.
1732 * The indexes are passed by reference and can be NULL.
1733 */
1734bool auto_group_bounds(const swayc_t *container, size_t group_index, int *start, int *end) {
1735 size_t nb_grp = auto_group_count(container);
1736 if (group_index >= nb_grp) {
1737 return false;
1738 }
1739 bool master_first = (container->layout == L_AUTO_LEFT || container->layout == L_AUTO_TOP);
1740 size_t nb_master = auto_master_count(container);
1741 size_t nb_slave_grp = auto_slave_group_count(container);
1742 int g_start, g_end;
1743 if (nb_master && (master_first ? group_index == 0 : group_index == nb_grp - 1)) {
1744 g_start = 0;
1745 g_end = nb_master;
1746 } else {
1747 size_t nb_slaves = auto_slave_count(container);
1748 size_t grp_sz = nb_slaves / nb_slave_grp;
1749 size_t remainder = nb_slaves % nb_slave_grp;
1750 size_t g0 = master_first && container->nb_master ? 1 : 0;
1751 size_t g1 = g0 + nb_slave_grp - remainder;
1752 if (group_index < g1) {
1753 g_start = container->nb_master + (group_index - g0) * grp_sz;
1754 g_end = g_start + grp_sz;
1755 } else {
1756 size_t g2 = group_index - g1;
1757 g_start = container->nb_master
1758 + (nb_slave_grp - remainder) * grp_sz
1759 + g2 * (grp_sz + 1);
1760 g_end = g_start + grp_sz + 1;
1761 }
1762 }
1763 if (start) {
1764 *start = g_start;
1765 }
1766 if (end) {
1767 *end = g_end;
1768 }
1769 return true;
1770}
diff --git a/sway/main.c b/sway/main.c
index cc9147b8..efb674b6 100644
--- a/sway/main.c
+++ b/sway/main.c
@@ -1,58 +1,46 @@
1#define _XOPEN_SOURCE 700 1#define _XOPEN_SOURCE 700
2#define _POSIX_C_SOURCE 200112L 2#define _POSIX_C_SOURCE 200112L
3#include <stdio.h> 3#include <getopt.h>
4#include <stdlib.h> 4#include <signal.h>
5#include <stdbool.h> 5#include <stdbool.h>
6#include <wlc/wlc.h> 6#include <stdlib.h>
7#include <sys/wait.h> 7#include <stdio.h>
8#include <sys/types.h> 8#include <string.h>
9#include <sys/stat.h> 9#include <sys/stat.h>
10#include <sys/types.h>
11#include <sys/wait.h>
10#include <sys/un.h> 12#include <sys/un.h>
11#include <signal.h>
12#include <unistd.h> 13#include <unistd.h>
13#include <getopt.h>
14#ifdef __linux__ 14#ifdef __linux__
15#include <sys/capability.h> 15#include <sys/capability.h>
16#include <sys/prctl.h> 16#include <sys/prctl.h>
17#endif 17#endif
18#include "sway/extensions.h" 18#include <wlr/util/log.h>
19#include "sway/layout.h"
20#include "sway/config.h" 19#include "sway/config.h"
21#include "sway/security.h" 20#include "sway/debug.h"
22#include "sway/handlers.h" 21#include "sway/server.h"
23#include "sway/input.h" 22#include "sway/tree/layout.h"
24#include "sway/ipc-server.h" 23#include "sway/ipc-server.h"
25#include "ipc-client.h" 24#include "ipc-client.h"
26#include "readline.h" 25#include "readline.h"
27#include "stringop.h" 26#include "stringop.h"
28#include "sway.h"
29#include "log.h"
30#include "util.h" 27#include "util.h"
31 28
32static bool terminate_request = false; 29static bool terminate_request = false;
33static int exit_value = 0; 30static int exit_value = 0;
31struct sway_server server;
34 32
35void sway_terminate(int exit_code) { 33void sway_terminate(int exit_code) {
36 terminate_request = true; 34 terminate_request = true;
37 exit_value = exit_code; 35 exit_value = exit_code;
38 wlc_terminate(); 36 wl_display_terminate(server.wl_display);
39} 37}
40 38
41void sig_handler(int signal) { 39void sig_handler(int signal) {
42 close_views(&root_container); 40 //close_views(&root_container);
43 sway_terminate(EXIT_SUCCESS); 41 sway_terminate(EXIT_SUCCESS);
44} 42}
45 43
46static void wlc_log_handler(enum wlc_log_type type, const char *str) {
47 if (type == WLC_LOG_ERROR) {
48 sway_log(L_ERROR, "[wlc] %s", str);
49 } else if (type == WLC_LOG_WARN) {
50 sway_log(L_INFO, "[wlc] %s", str);
51 } else {
52 sway_log(L_DEBUG, "[wlc] %s", str);
53 }
54}
55
56void detect_raspi() { 44void detect_raspi() {
57 bool raspi = false; 45 bool raspi = false;
58 FILE *f = fopen("/sys/firmware/devicetree/base/model", "r"); 46 FILE *f = fopen("/sys/firmware/devicetree/base/model", "r");
@@ -98,23 +86,16 @@ void detect_proprietary() {
98 if (!f) { 86 if (!f) {
99 return; 87 return;
100 } 88 }
101 bool nvidia = false, nvidia_modeset = false, nvidia_uvm = false, nvidia_drm = false;
102 while (!feof(f)) { 89 while (!feof(f)) {
103 char *line; 90 char *line;
104 if (!(line = read_line(f))) { 91 if (!(line = read_line(f))) {
105 break; 92 break;
106 } 93 }
107 if (strstr(line, "nvidia")) { 94 if (strstr(line, "nvidia")) {
108 nvidia = true; 95 fprintf(stderr, "\x1B[1;31mWarning: Proprietary Nvidia drivers are "
109 } 96 "NOT supported. Use Nouveau.\x1B[0m\n");
110 if (strstr(line, "nvidia_modeset")) { 97 free(line);
111 nvidia_modeset = true; 98 break;
112 }
113 if (strstr(line, "nvidia_uvm")) {
114 nvidia_uvm = true;
115 }
116 if (strstr(line, "nvidia_drm")) {
117 nvidia_drm = true;
118 } 99 }
119 if (strstr(line, "fglrx")) { 100 if (strstr(line, "fglrx")) {
120 fprintf(stderr, "\x1B[1;31mWarning: Proprietary AMD drivers do " 101 fprintf(stderr, "\x1B[1;31mWarning: Proprietary AMD drivers do "
@@ -125,52 +106,6 @@ void detect_proprietary() {
125 free(line); 106 free(line);
126 } 107 }
127 fclose(f); 108 fclose(f);
128 if (nvidia) {
129 fprintf(stderr, "\x1B[1;31mWarning: Proprietary nvidia driver support "
130 "is considered experimental. Nouveau is strongly recommended."
131 "\x1B[0m\n");
132 if (!nvidia_modeset || !nvidia_uvm || !nvidia_drm) {
133 fprintf(stderr, "\x1B[1;31mWarning: You do not have all of the "
134 "necessary kernel modules loaded for nvidia support. "
135 "You need nvidia, nvidia_modeset, nvidia_uvm, and nvidia_drm."
136 "\x1B[0m\n");
137 }
138#ifdef __linux__
139 f = fopen("/sys/module/nvidia_drm/parameters/modeset", "r");
140 if (f) {
141 char *line = read_line(f);
142 if (line && strstr(line, "Y")) {
143 // nvidia-drm.modeset is set to 0
144 fprintf(stderr, "\x1B[1;31mWarning: You must load "
145 "nvidia-drm with the modeset option on to use "
146 "the proprietary driver. Consider adding "
147 "nvidia-drm.modeset=1 to your kernel command line "
148 "parameters.\x1B[0m\n");
149 }
150 fclose(f);
151 free(line);
152 } else {
153 // nvidia-drm.modeset is not set
154 fprintf(stderr, "\x1B[1;31mWarning: You must load "
155 "nvidia-drm with the modeset option on to use "
156 "the proprietary driver. Consider adding "
157 "nvidia-drm.modeset=1 to your kernel command line "
158 "parameters.\x1B[0m\n");
159 }
160#else
161 f = fopen("/proc/cmdline", "r");
162 if (f) {
163 char *line = read_line(f);
164 if (line && !strstr(line, "nvidia-drm.modeset=1")) {
165 fprintf(stderr, "\x1B[1;31mWarning: You must add "
166 "nvidia-drm.modeset=1 to your kernel command line to use "
167 "the proprietary driver.\x1B[0m\n");
168 }
169 fclose(f);
170 free(line);
171 }
172#endif
173 }
174} 109}
175 110
176void run_as_ipc_client(char *command, char *socket_path) { 111void run_as_ipc_client(char *command, char *socket_path) {
@@ -184,27 +119,15 @@ void run_as_ipc_client(char *command, char *socket_path) {
184static void log_env() { 119static void log_env() {
185 const char *log_vars[] = { 120 const char *log_vars[] = {
186 "PATH", 121 "PATH",
187 "LD_LOAD_PATH", 122 "LD_LIBRARY_PATH",
188 "LD_PRELOAD_PATH", 123 "LD_PRELOAD_PATH",
189 "LD_LIBRARY_PATH", 124 "LD_LIBRARY_PATH",
190 "SWAY_CURSOR_THEME", 125 "SWAY_CURSOR_THEME",
191 "SWAY_CURSOR_SIZE", 126 "SWAY_CURSOR_SIZE",
192 "SWAYSOCK", 127 "SWAYSOCK"
193 "WLC_DRM_DEVICE",
194 "WLC_SHM",
195 "WLC_OUTPUTS",
196 "WLC_XWAYLAND",
197 "WLC_LIBINPUT",
198 "WLC_REPEAT_DELAY",
199 "WLC_REPEAT_RATE",
200 "XKB_DEFAULT_RULES",
201 "XKB_DEFAULT_MODEL",
202 "XKB_DEFAULT_LAYOUT",
203 "XKB_DEFAULT_VARIANT",
204 "XKB_DEFAULT_OPTIONS",
205 }; 128 };
206 for (size_t i = 0; i < sizeof(log_vars) / sizeof(char *); ++i) { 129 for (size_t i = 0; i < sizeof(log_vars) / sizeof(char *); ++i) {
207 sway_log(L_INFO, "%s=%s", log_vars[i], getenv(log_vars[i])); 130 wlr_log(L_INFO, "%s=%s", log_vars[i], getenv(log_vars[i]));
208 } 131 }
209} 132}
210 133
@@ -219,14 +142,14 @@ static void log_distro() {
219 for (size_t i = 0; i < sizeof(paths) / sizeof(char *); ++i) { 142 for (size_t i = 0; i < sizeof(paths) / sizeof(char *); ++i) {
220 FILE *f = fopen(paths[i], "r"); 143 FILE *f = fopen(paths[i], "r");
221 if (f) { 144 if (f) {
222 sway_log(L_INFO, "Contents of %s:", paths[i]); 145 wlr_log(L_INFO, "Contents of %s:", paths[i]);
223 while (!feof(f)) { 146 while (!feof(f)) {
224 char *line; 147 char *line;
225 if (!(line = read_line(f))) { 148 if (!(line = read_line(f))) {
226 break; 149 break;
227 } 150 }
228 if (*line) { 151 if (*line) {
229 sway_log(L_INFO, "%s", line); 152 wlr_log(L_INFO, "%s", line);
230 } 153 }
231 free(line); 154 free(line);
232 } 155 }
@@ -236,9 +159,10 @@ static void log_distro() {
236} 159}
237 160
238static void log_kernel() { 161static void log_kernel() {
162 return;
239 FILE *f = popen("uname -a", "r"); 163 FILE *f = popen("uname -a", "r");
240 if (!f) { 164 if (!f) {
241 sway_log(L_INFO, "Unable to determine kernel version"); 165 wlr_log(L_INFO, "Unable to determine kernel version");
242 return; 166 return;
243 } 167 }
244 while (!feof(f)) { 168 while (!feof(f)) {
@@ -247,7 +171,7 @@ static void log_kernel() {
247 break; 171 break;
248 } 172 }
249 if (*line) { 173 if (*line) {
250 sway_log(L_INFO, "%s", line); 174 wlr_log(L_INFO, "%s", line);
251 } 175 }
252 free(line); 176 free(line);
253 } 177 }
@@ -258,14 +182,14 @@ static void security_sanity_check() {
258 // TODO: Notify users visually if this has issues 182 // TODO: Notify users visually if this has issues
259 struct stat s; 183 struct stat s;
260 if (stat("/proc", &s)) { 184 if (stat("/proc", &s)) {
261 sway_log(L_ERROR, 185 wlr_log(L_ERROR,
262 "!! DANGER !! /proc is not available - sway CANNOT enforce security rules!"); 186 "!! DANGER !! /proc is not available - sway CANNOT enforce security rules!");
263 } 187 }
264#ifdef __linux__ 188#ifdef __linux__
265 cap_flag_value_t v; 189 cap_flag_value_t v;
266 cap_t cap = cap_get_proc(); 190 cap_t cap = cap_get_proc();
267 if (!cap || cap_get_flag(cap, CAP_SYS_PTRACE, CAP_PERMITTED, &v) != 0 || v != CAP_SET) { 191 if (!cap || cap_get_flag(cap, CAP_SYS_PTRACE, CAP_PERMITTED, &v) != 0 || v != CAP_SET) {
268 sway_log(L_ERROR, 192 wlr_log(L_ERROR,
269 "!! DANGER !! Sway does not have CAP_SYS_PTRACE and cannot enforce security rules for processes running as other users."); 193 "!! DANGER !! Sway does not have CAP_SYS_PTRACE and cannot enforce security rules for processes running as other users.");
270 } 194 }
271 if (cap) { 195 if (cap) {
@@ -281,13 +205,13 @@ static void executable_sanity_check() {
281 stat(exe, &sb); 205 stat(exe, &sb);
282 // We assume that cap_get_file returning NULL implies ENODATA 206 // We assume that cap_get_file returning NULL implies ENODATA
283 if (sb.st_mode & (S_ISUID|S_ISGID) && cap_get_file(exe)) { 207 if (sb.st_mode & (S_ISUID|S_ISGID) && cap_get_file(exe)) {
284 sway_log(L_ERROR, 208 wlr_log(L_ERROR,
285 "sway executable has both the s(g)uid bit AND file caps set."); 209 "sway executable has both the s(g)uid bit AND file caps set.");
286 sway_log(L_ERROR, 210 wlr_log(L_ERROR,
287 "This is strongly discouraged (and completely broken)."); 211 "This is strongly discouraged (and completely broken).");
288 sway_log(L_ERROR, 212 wlr_log(L_ERROR,
289 "Please clear one of them (either the suid bit, or the file caps)."); 213 "Please clear one of them (either the suid bit, or the file caps).");
290 sway_log(L_ERROR, 214 wlr_log(L_ERROR,
291 "If unsure, strip the file caps."); 215 "If unsure, strip the file caps.");
292 exit(EXIT_FAILURE); 216 exit(EXIT_FAILURE);
293 } 217 }
@@ -295,6 +219,37 @@ static void executable_sanity_check() {
295#endif 219#endif
296} 220}
297 221
222static void drop_permissions(bool keep_caps) {
223 if (getuid() != geteuid() || getgid() != getegid()) {
224 if (setgid(getgid()) != 0) {
225 wlr_log(L_ERROR, "Unable to drop root");
226 exit(EXIT_FAILURE);
227 }
228 if (setuid(getuid()) != 0) {
229 wlr_log(L_ERROR, "Unable to drop root");
230 exit(EXIT_FAILURE);
231 }
232 }
233 if (setuid(0) != -1) {
234 wlr_log(L_ERROR, "Root privileges can be restored.");
235 exit(EXIT_FAILURE);
236 }
237#ifdef __linux__
238 if (keep_caps) {
239 // Drop every cap except CAP_SYS_PTRACE
240 cap_t caps = cap_init();
241 cap_value_t keep = CAP_SYS_PTRACE;
242 wlr_log(L_INFO, "Dropping extra capabilities");
243 if (cap_set_flag(caps, CAP_PERMITTED, 1, &keep, CAP_SET) ||
244 cap_set_flag(caps, CAP_EFFECTIVE, 1, &keep, CAP_SET) ||
245 cap_set_proc(caps)) {
246 wlr_log(L_ERROR, "Failed to drop extra capabilities");
247 exit(EXIT_FAILURE);
248 }
249 }
250#endif
251}
252
298int main(int argc, char **argv) { 253int main(int argc, char **argv) {
299 static int verbose = 0, debug = 0, validate = 0; 254 static int verbose = 0, debug = 0, validate = 0;
300 255
@@ -334,7 +289,7 @@ int main(int argc, char **argv) {
334 int c; 289 int c;
335 while (1) { 290 while (1) {
336 int option_index = 0; 291 int option_index = 0;
337 c = getopt_long(argc, argv, "hCdvVc:", long_options, &option_index); 292 c = getopt_long(argc, argv, "hCdDvVc:", long_options, &option_index);
338 if (c == -1) { 293 if (c == -1) {
339 break; 294 break;
340 } 295 }
@@ -352,6 +307,9 @@ int main(int argc, char **argv) {
352 case 'd': // debug 307 case 'd': // debug
353 debug = 1; 308 debug = 1;
354 break; 309 break;
310 case 'D': // extended debug options
311 enable_debug_tree = true;
312 break;
355 case 'v': // version 313 case 'v': // version
356 fprintf(stdout, "sway version " SWAY_VERSION "\n"); 314 fprintf(stdout, "sway version " SWAY_VERSION "\n");
357 exit(EXIT_SUCCESS); 315 exit(EXIT_SUCCESS);
@@ -374,37 +332,24 @@ int main(int argc, char **argv) {
374 } 332 }
375 } 333 }
376 334
377 // we need to setup logging before wlc_init in case it fails. 335 // TODO: switch logging over to wlroots?
378 if (debug) { 336 if (debug) {
379 init_log(L_DEBUG); 337 wlr_log_init(L_DEBUG, NULL);
380 } else if (verbose || validate) { 338 } else if (verbose || validate) {
381 init_log(L_INFO); 339 wlr_log_init(L_INFO, NULL);
382 } else { 340 } else {
383 init_log(L_ERROR); 341 wlr_log_init(L_ERROR, NULL);
384 } 342 }
385 343
386 if (optind < argc) { // Behave as IPC client 344 if (optind < argc) { // Behave as IPC client
387 if(optind != 1) { 345 if(optind != 1) {
388 sway_log(L_ERROR, "Don't use options with the IPC client"); 346 wlr_log(L_ERROR, "Don't use options with the IPC client");
389 exit(EXIT_FAILURE);
390 }
391 if (getuid() != geteuid() || getgid() != getegid()) {
392 if (setgid(getgid()) != 0) {
393 sway_log(L_ERROR, "Unable to drop root");
394 exit(EXIT_FAILURE);
395 }
396 if (setuid(getuid()) != 0) {
397 sway_log(L_ERROR, "Unable to drop root");
398 exit(EXIT_FAILURE);
399 }
400 }
401 if (setuid(0) != -1) {
402 sway_log(L_ERROR, "Root privileges can be restored.");
403 exit(EXIT_FAILURE); 347 exit(EXIT_FAILURE);
404 } 348 }
349 drop_permissions(false);
405 char *socket_path = getenv("SWAYSOCK"); 350 char *socket_path = getenv("SWAYSOCK");
406 if (!socket_path) { 351 if (!socket_path) {
407 sway_log(L_ERROR, "Unable to retrieve socket path"); 352 wlr_log(L_ERROR, "Unable to retrieve socket path");
408 exit(EXIT_FAILURE); 353 exit(EXIT_FAILURE);
409 } 354 }
410 char *command = join_args(argv + optind, argc - optind); 355 char *command = join_args(argv + optind, argc - optind);
@@ -413,49 +358,25 @@ int main(int argc, char **argv) {
413 } 358 }
414 359
415 executable_sanity_check(); 360 executable_sanity_check();
416#ifdef __linux__
417 bool suid = false; 361 bool suid = false;
362#ifdef __linux__
418 if (getuid() != geteuid() || getgid() != getegid()) { 363 if (getuid() != geteuid() || getgid() != getegid()) {
419 // Retain capabilities after setuid() 364 // Retain capabilities after setuid()
420 if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0)) { 365 if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0)) {
421 sway_log(L_ERROR, "Cannot keep caps after setuid()"); 366 wlr_log(L_ERROR, "Cannot keep caps after setuid()");
422 exit(EXIT_FAILURE); 367 exit(EXIT_FAILURE);
423 } 368 }
424 suid = true; 369 suid = true;
425 } 370 }
426#endif 371#endif
427 372
428 wlc_log_set_handler(wlc_log_handler);
429 log_kernel(); 373 log_kernel();
430 log_distro(); 374 log_distro();
431 log_env();
432 detect_proprietary(); 375 detect_proprietary();
433 detect_raspi(); 376 detect_raspi();
434 377
435 input_devices = create_list();
436
437 /* Changing code earlier than this point requires detailed review */
438 /* (That code runs as root on systems without logind, and wlc_init drops to
439 * another user.) */
440 register_wlc_handlers();
441 if (!wlc_init()) {
442 return 1;
443 }
444 register_extensions();
445
446#ifdef __linux__ 378#ifdef __linux__
447 if (suid) { 379 drop_permissions(suid);
448 // Drop every cap except CAP_SYS_PTRACE
449 cap_t caps = cap_init();
450 cap_value_t keep = CAP_SYS_PTRACE;
451 sway_log(L_INFO, "Dropping extra capabilities");
452 if (cap_set_flag(caps, CAP_PERMITTED, 1, &keep, CAP_SET) ||
453 cap_set_flag(caps, CAP_EFFECTIVE, 1, &keep, CAP_SET) ||
454 cap_set_proc(caps)) {
455 sway_log(L_ERROR, "Failed to drop extra capabilities");
456 exit(EXIT_FAILURE);
457 }
458 }
459#endif 380#endif
460 // handle SIGTERM signals 381 // handle SIGTERM signals
461 signal(SIGTERM, sig_handler); 382 signal(SIGTERM, sig_handler);
@@ -463,11 +384,16 @@ int main(int argc, char **argv) {
463 // prevent ipc from crashing sway 384 // prevent ipc from crashing sway
464 signal(SIGPIPE, SIG_IGN); 385 signal(SIGPIPE, SIG_IGN);
465 386
466 sway_log(L_INFO, "Starting sway version " SWAY_VERSION "\n"); 387 wlr_log(L_INFO, "Starting sway version " SWAY_VERSION);
388
389 layout_init();
467 390
468 init_layout(); 391 if (!server_init(&server)) {
392 return 1;
393 }
469 394
470 ipc_init(); 395 ipc_init(&server);
396 log_env();
471 397
472 if (validate) { 398 if (validate) {
473 bool valid = load_main_config(config_path, false); 399 bool valid = load_main_config(config_path, false);
@@ -484,11 +410,17 @@ int main(int argc, char **argv) {
484 410
485 security_sanity_check(); 411 security_sanity_check();
486 412
413 // TODO: wait for server to be ready
414 // TODO: consume config->cmd_queue
415 config->active = true;
416
487 if (!terminate_request) { 417 if (!terminate_request) {
488 wlc_run(); 418 server_run(&server);
489 } 419 }
490 420
491 list_free(input_devices); 421 wlr_log(L_INFO, "Shutting down sway");
422
423 server_fini(&server);
492 424
493 ipc_terminate(); 425 ipc_terminate();
494 426
diff --git a/sway/meson.build b/sway/meson.build
new file mode 100644
index 00000000..9e55e335
--- /dev/null
+++ b/sway/meson.build
@@ -0,0 +1,131 @@
1sway_sources = files(
2 'main.c',
3 'server.c',
4 'commands.c',
5 'config.c',
6 'criteria.c',
7 'debug-tree.c',
8 'ipc-json.c',
9 'ipc-server.c',
10 'security.c',
11
12 'desktop/desktop.c',
13 'desktop/output.c',
14 'desktop/layer_shell.c',
15 'desktop/wl_shell.c',
16 'desktop/xdg_shell_v6.c',
17 'desktop/xwayland.c',
18
19 'input/input-manager.c',
20 'input/seat.c',
21 'input/cursor.c',
22 'input/keyboard.c',
23
24 'config/bar.c',
25 'config/output.c',
26 'config/seat.c',
27 'config/input.c',
28
29 'commands/bar.c',
30 'commands/bind.c',
31 'commands/default_orientation.c',
32 'commands/exit.c',
33 'commands/exec.c',
34 'commands/exec_always.c',
35 'commands/focus.c',
36 'commands/focus_follows_mouse.c',
37 'commands/kill.c',
38 'commands/opacity.c',
39 'commands/include.c',
40 'commands/input.c',
41 'commands/layout.c',
42 'commands/mode.c',
43 'commands/mouse_warping.c',
44 'commands/move.c',
45 'commands/output.c',
46 'commands/reload.c',
47 'commands/resize.c',
48 'commands/seat.c',
49 'commands/seat/attach.c',
50 'commands/seat/cursor.c',
51 'commands/seat/fallback.c',
52 'commands/set.c',
53 'commands/split.c',
54 'commands/swaybg_command.c',
55 'commands/workspace.c',
56 'commands/ws_auto_back_and_forth.c',
57
58 'commands/bar/activate_button.c',
59 'commands/bar/binding_mode_indicator.c',
60 'commands/bar/bindsym.c',
61 'commands/bar/colors.c',
62 'commands/bar/context_button.c',
63 'commands/bar/font.c',
64 'commands/bar/height.c',
65 'commands/bar/hidden_state.c',
66 'commands/bar/icon_theme.c',
67 'commands/bar/id.c',
68 'commands/bar/mode.c',
69 'commands/bar/modifier.c',
70 'commands/bar/output.c',
71 'commands/bar/pango_markup.c',
72 'commands/bar/position.c',
73 'commands/bar/secondary_button.c',
74 'commands/bar/separator_symbol.c',
75 'commands/bar/status_command.c',
76 'commands/bar/strip_workspace_numbers.c',
77 'commands/bar/swaybar_command.c',
78 'commands/bar/tray_output.c',
79 'commands/bar/tray_padding.c',
80 'commands/bar/workspace_buttons.c',
81 'commands/bar/wrap_scroll.c',
82
83 'commands/input/accel_profile.c',
84 'commands/input/click_method.c',
85 'commands/input/drag_lock.c',
86 'commands/input/dwt.c',
87 'commands/input/events.c',
88 'commands/input/left_handed.c',
89 'commands/input/map_to_output.c',
90 'commands/input/middle_emulation.c',
91 'commands/input/natural_scroll.c',
92 'commands/input/pointer_accel.c',
93 'commands/input/scroll_method.c',
94 'commands/input/tap.c',
95 'commands/input/xkb_layout.c',
96 'commands/input/xkb_model.c',
97 'commands/input/xkb_options.c',
98 'commands/input/xkb_rules.c',
99 'commands/input/xkb_variant.c',
100
101 'tree/container.c',
102 'tree/layout.c',
103 'tree/view.c',
104 'tree/workspace.c',
105 'tree/output.c',
106)
107
108sway_deps = [
109 cairo,
110 gdk_pixbuf,
111 jsonc,
112 libcap,
113 libinput,
114 math,
115 pango,
116 pcre,
117 pixman,
118 server_protos,
119 wayland_server,
120 wlroots,
121 xkbcommon,
122]
123
124executable(
125 'sway',
126 sway_sources,
127 include_directories: [sway_inc],
128 dependencies: sway_deps,
129 link_with: [lib_sway_common],
130 install: true
131)
diff --git a/sway/output.c b/sway/output.c
deleted file mode 100644
index c0f29c5a..00000000
--- a/sway/output.c
+++ /dev/null
@@ -1,277 +0,0 @@
1#include <strings.h>
2#include <ctype.h>
3#include <stdlib.h>
4#include "sway/output.h"
5#include "log.h"
6#include "list.h"
7
8void output_get_scaled_size(wlc_handle handle, struct wlc_size *size) {
9 *size = *wlc_output_get_resolution(handle);
10 uint32_t scale = wlc_output_get_scale(handle);
11 size->w /= scale;
12 size->h /= scale;
13}
14
15swayc_t *output_by_name(const char* name, const struct wlc_point *abs_pos) {
16 swayc_t *output = NULL;
17 // If there is no output directly next to the current one, use
18 // swayc_opposite_output to wrap.
19 if (strcasecmp(name, "left") == 0) {
20 output = swayc_adjacent_output(NULL, MOVE_LEFT, abs_pos, true);
21 if (!output) {
22 output = swayc_opposite_output(MOVE_RIGHT, abs_pos);
23 }
24 } else if (strcasecmp(name, "right") == 0) {
25 output = swayc_adjacent_output(NULL, MOVE_RIGHT, abs_pos, true);
26 if (!output) {
27 output = swayc_opposite_output(MOVE_LEFT, abs_pos);
28 }
29 } else if (strcasecmp(name, "up") == 0) {
30 output = swayc_adjacent_output(NULL, MOVE_UP, abs_pos, true);
31 if (!output) {
32 output = swayc_opposite_output(MOVE_DOWN, abs_pos);
33 }
34 } else if (strcasecmp(name, "down") == 0) {
35 output = swayc_adjacent_output(NULL, MOVE_DOWN, abs_pos, true);
36 if (!output) {
37 output = swayc_opposite_output(MOVE_UP, abs_pos);
38 }
39 } else {
40 for(int i = 0; i < root_container.children->length; ++i) {
41 swayc_t *c = root_container.children->items[i];
42 if (c->type == C_OUTPUT && strcasecmp(c->name, name) == 0) {
43 return c;
44 }
45 }
46 }
47 return output;
48}
49
50swayc_t *swayc_opposite_output(enum movement_direction dir,
51 const struct wlc_point *abs_pos) {
52
53 // Search through all the outputs and pick the output whose edge covers the
54 // given position, and is at leftmost/rightmost/upmost/downmost side of the
55 // screen (decided by the direction given).
56 swayc_t *opposite = NULL;
57 char *dir_text = NULL;
58 switch(dir) {
59 case MOVE_LEFT:
60 case MOVE_RIGHT: ;
61 for (int i = 0; i < root_container.children->length; ++i) {
62 swayc_t *c = root_container.children->items[i];
63 if (abs_pos->y >= c->y && abs_pos->y <= c->y + c->height) {
64 if (!opposite) {
65 opposite = c;
66 } else if ((dir == MOVE_LEFT && c->x < opposite->x)
67 || (dir == MOVE_RIGHT && c->x > opposite->x)) {
68 opposite = c;
69 }
70 }
71 }
72 dir_text = dir == MOVE_LEFT ? "leftmost" : "rightmost";
73 break;
74 case MOVE_UP:
75 case MOVE_DOWN: ;
76 for (int i = 0; i < root_container.children->length; ++i) {
77 swayc_t *c = root_container.children->items[i];
78 if (abs_pos->x >= c->x && abs_pos->x <= c->x + c->width) {
79 if (!opposite) {
80 opposite = c;
81 } else if ((dir == MOVE_UP && c->y < opposite->y)
82 || (dir == MOVE_DOWN && c->y > opposite->y)) {
83 opposite = c;
84 }
85 }
86 }
87 dir_text = dir == MOVE_UP ? "upmost" : "downmost";
88 break;
89 default:
90 sway_abort("Function called with invalid argument.");
91 break;
92 }
93 if (opposite) {
94 sway_log(L_DEBUG, "%s (%.0fx%.0f+%.0f+%.0f) is %s from y-position %i",
95 opposite->name, opposite->width, opposite->height, opposite->x, opposite->y,
96 dir_text, abs_pos->y);
97 }
98 return opposite;
99}
100
101// Position is where on the edge (as absolute position) the adjacent output should be searched for.
102swayc_t *swayc_adjacent_output(swayc_t *output, enum movement_direction dir,
103 const struct wlc_point *abs_pos, bool pick_closest) {
104
105 if (!output) {
106 output = swayc_active_output();
107 }
108 // In order to find adjacent outputs we need to test that the outputs are
109 // aligned on one axis (decided by the direction given) and that the given
110 // position is within the edge of the adjacent output. If no such output
111 // exists we pick the adjacent output within the edge that is closest to
112 // the given position, if any.
113 swayc_t *adjacent = NULL;
114 char *dir_text = NULL;
115 switch(dir) {
116 case MOVE_LEFT:
117 case MOVE_RIGHT: ;
118 double delta_y = 0;
119 for(int i = 0; i < root_container.children->length; ++i) {
120 swayc_t *c = root_container.children->items[i];
121 if (c == output || c->type != C_OUTPUT) {
122 continue;
123 }
124 bool x_aligned = dir == MOVE_LEFT ?
125 c->x + c->width == output->x :
126 c->x == output->x + output->width;
127 if (!x_aligned) {
128 continue;
129 }
130 if (abs_pos->y >= c->y && abs_pos->y <= c->y + c->height) {
131 delta_y = 0;
132 adjacent = c;
133 break;
134 } else if (pick_closest) {
135 // track closest adjacent output
136 double top_y = c->y, bottom_y = c->y + c->height;
137 if (top_y >= output->y && top_y <= output->y + output->height) {
138 double delta = top_y - abs_pos->y;
139 if (delta < 0) delta = -delta;
140 if (delta < delta_y || !adjacent) {
141 delta_y = delta;
142 adjacent = c;
143 }
144 }
145 // we check both points and pick the closest
146 if (bottom_y >= output->y && bottom_y <= output->y + output->height) {
147 double delta = bottom_y - abs_pos->y;
148 if (delta < 0) delta = -delta;
149 if (delta < delta_y || !adjacent) {
150 delta_y = delta;
151 adjacent = c;
152 }
153 }
154 }
155 }
156 dir_text = dir == MOVE_LEFT ? "left of" : "right of";
157 if (adjacent && delta_y == 0) {
158 sway_log(L_DEBUG, "%s (%.0fx%.0f+%.0f+%.0f) is %s current output %s (y-position %i)",
159 adjacent->name, adjacent->width, adjacent->height, adjacent->x, adjacent->y,
160 dir_text, output->name, abs_pos->y);
161 } else if (adjacent) {
162 // so we end up picking the closest adjacent output because
163 // there is no directly adjacent to the given position
164 sway_log(L_DEBUG, "%s (%.0fx%.0f+%.0f+%.0f) is %s current output %s (y-position %i, delta: %.0f)",
165 adjacent->name, adjacent->width, adjacent->height, adjacent->x, adjacent->y,
166 dir_text, output->name, abs_pos->y, delta_y);
167 }
168 break;
169 case MOVE_UP:
170 case MOVE_DOWN: ;
171 double delta_x = 0;
172 for(int i = 0; i < root_container.children->length; ++i) {
173 swayc_t *c = root_container.children->items[i];
174 if (c == output || c->type != C_OUTPUT) {
175 continue;
176 }
177 bool y_aligned = dir == MOVE_UP ?
178 c->y + c->height == output->y :
179 c->y == output->y + output->height;
180 if (!y_aligned) {
181 continue;
182 }
183 if (abs_pos->x >= c->x && abs_pos->x <= c->x + c->width) {
184 delta_x = 0;
185 adjacent = c;
186 break;
187 } else if (pick_closest) {
188 // track closest adjacent output
189 double left_x = c->x, right_x = c->x + c->width;
190 if (left_x >= output->x && left_x <= output->x + output->width) {
191 double delta = left_x - abs_pos->x;
192 if (delta < 0) delta = -delta;
193 if (delta < delta_x || !adjacent) {
194 delta_x = delta;
195 adjacent = c;
196 }
197 }
198 // we check both points and pick the closest
199 if (right_x >= output->x && right_x <= output->x + output->width) {
200 double delta = right_x - abs_pos->x;
201 if (delta < 0) delta = -delta;
202 if (delta < delta_x || !adjacent) {
203 delta_x = delta;
204 adjacent = c;
205 }
206 }
207 }
208 }
209 dir_text = dir == MOVE_UP ? "above" : "below";
210 if (adjacent && delta_x == 0) {
211 sway_log(L_DEBUG, "%s (%.0fx%.0f+%.0f+%.0f) is %s current output %s (x-position %i)",
212 adjacent->name, adjacent->width, adjacent->height, adjacent->x, adjacent->y,
213 dir_text, output->name, abs_pos->x);
214 } else if (adjacent) {
215 // so we end up picking the closest adjacent output because
216 // there is no directly adjacent to the given position
217 sway_log(L_DEBUG, "%s (%.0fx%.0f+%.0f+%.0f) is %s current output %s (x-position %i, delta: %.0f)",
218 adjacent->name, adjacent->width, adjacent->height, adjacent->x, adjacent->y,
219 dir_text, output->name, abs_pos->x, delta_x);
220 }
221 break;
222 default:
223 sway_abort("Function called with invalid argument.");
224 break;
225 }
226 return adjacent;
227}
228
229void get_absolute_position(swayc_t *container, struct wlc_point *point) {
230 if (!container || !point)
231 sway_abort("Need container and wlc_point (was %p, %p).", container, point);
232
233 if (container->type == C_OUTPUT) {
234 // Coordinates are already absolute.
235 point->x = container->x;
236 point->y = container->y;
237 } else {
238 swayc_t *output = swayc_parent_by_type(container, C_OUTPUT);
239 if (container->type == C_WORKSPACE) {
240 // Workspace coordinates are actually wrong/arbitrary, but should
241 // be same as output.
242 point->x = output->x;
243 point->y = output->y;
244 } else {
245 point->x = output->x + container->x;
246 point->y = output->y + container->y;
247 }
248 }
249}
250
251void get_absolute_center_position(swayc_t *container, struct wlc_point *point) {
252 get_absolute_position(container, point);
253 point->x += container->width/2;
254 point->y += container->height/2;
255}
256
257static int sort_workspace_cmp_qsort(const void *_a, const void *_b) {
258 swayc_t *a = *(void **)_a;
259 swayc_t *b = *(void **)_b;
260 int retval = 0;
261
262 if (isdigit(a->name[0]) && isdigit(b->name[0])) {
263 int a_num = strtol(a->name, NULL, 10);
264 int b_num = strtol(b->name, NULL, 10);
265 retval = (a_num < b_num) ? -1 : (a_num > b_num);
266 } else if (isdigit(a->name[0])) {
267 retval = -1;
268 } else if (isdigit(b->name[0])) {
269 retval = 1;
270 }
271
272 return retval;
273}
274
275void sort_workspaces(swayc_t *output) {
276 list_stable_sort(output->children, sort_workspace_cmp_qsort);
277}
diff --git a/sway/security.c b/sway/security.c
index fcd70f9d..cc0d3f66 100644
--- a/sway/security.c
+++ b/sway/security.c
@@ -1,102 +1,7 @@
1#define _XOPEN_SOURCE 700 1#define _XOPEN_SOURCE 700
2#include <sys/types.h> 2#include <stdlib.h>
3#include <sys/stat.h>
4#include <string.h> 3#include <string.h>
5#include <unistd.h>
6#include <stdio.h>
7#include "sway/config.h"
8#include "sway/security.h" 4#include "sway/security.h"
9#include "log.h"
10
11static bool validate_ipc_target(const char *program) {
12 struct stat sb;
13
14 sway_log(L_DEBUG, "Validating IPC target '%s'", program);
15
16 if (!strcmp(program, "*")) {
17 return true;
18 }
19 if (lstat(program, &sb) == -1) {
20 return false;
21 }
22 if (!S_ISREG(sb.st_mode)) {
23 sway_log(L_ERROR,
24 "IPC target '%s' MUST be/point at an existing regular file",
25 program);
26 return false;
27 }
28 if (sb.st_uid != 0) {
29#ifdef NDEBUG
30 sway_log(L_ERROR, "IPC target '%s' MUST be owned by root", program);
31 return false;
32#else
33 sway_log(L_INFO, "IPC target '%s' MUST be owned by root (waived for debug build)", program);
34 return true;
35#endif
36 }
37 if (sb.st_mode & S_IWOTH) {
38 sway_log(L_ERROR, "IPC target '%s' MUST NOT be world writable", program);
39 return false;
40 }
41
42 return true;
43}
44
45struct feature_policy *alloc_feature_policy(const char *program) {
46 uint32_t default_policy = 0;
47
48 if (!validate_ipc_target(program)) {
49 return NULL;
50 }
51 for (int i = 0; i < config->feature_policies->length; ++i) {
52 struct feature_policy *policy = config->feature_policies->items[i];
53 if (strcmp(policy->program, "*") == 0) {
54 default_policy = policy->features;
55 break;
56 }
57 }
58
59 struct feature_policy *policy = malloc(sizeof(struct feature_policy));
60 if (!policy) {
61 return NULL;
62 }
63 policy->program = strdup(program);
64 if (!policy->program) {
65 free(policy);
66 return NULL;
67 }
68 policy->features = default_policy;
69
70 return policy;
71}
72
73struct ipc_policy *alloc_ipc_policy(const char *program) {
74 uint32_t default_policy = 0;
75
76 if (!validate_ipc_target(program)) {
77 return NULL;
78 }
79 for (int i = 0; i < config->ipc_policies->length; ++i) {
80 struct ipc_policy *policy = config->ipc_policies->items[i];
81 if (strcmp(policy->program, "*") == 0) {
82 default_policy = policy->features;
83 break;
84 }
85 }
86
87 struct ipc_policy *policy = malloc(sizeof(struct ipc_policy));
88 if (!policy) {
89 return NULL;
90 }
91 policy->program = strdup(program);
92 if (!policy->program) {
93 free(policy);
94 return NULL;
95 }
96 policy->features = default_policy;
97
98 return policy;
99}
100 5
101struct command_policy *alloc_command_policy(const char *command) { 6struct command_policy *alloc_command_policy(const char *command) {
102 struct command_policy *policy = malloc(sizeof(struct command_policy)); 7 struct command_policy *policy = malloc(sizeof(struct command_policy));
@@ -111,118 +16,3 @@ struct command_policy *alloc_command_policy(const char *command) {
111 policy->context = 0; 16 policy->context = 0;
112 return policy; 17 return policy;
113} 18}
114
115static const char *get_pid_exe(pid_t pid) {
116#ifdef __FreeBSD__
117 const char *fmt = "/proc/%d/file";
118#else
119 const char *fmt = "/proc/%d/exe";
120#endif
121 int pathlen = snprintf(NULL, 0, fmt, pid);
122 char *path = malloc(pathlen + 1);
123 if (path) {
124 snprintf(path, pathlen + 1, fmt, pid);
125 }
126
127 static char link[2048];
128
129 ssize_t len = !path ? -1 : readlink(path, link, sizeof(link));
130 if (len < 0) {
131 sway_log(L_INFO,
132 "WARNING: unable to read %s for security check. Using default policy.",
133 path);
134 strcpy(link, "*");
135 } else {
136 link[len] = '\0';
137 }
138 free(path);
139
140 return link;
141}
142
143struct feature_policy *get_feature_policy(const char *name) {
144 struct feature_policy *policy = NULL;
145
146 for (int i = 0; i < config->feature_policies->length; ++i) {
147 struct feature_policy *p = config->feature_policies->items[i];
148 if (strcmp(p->program, name) == 0) {
149 policy = p;
150 break;
151 }
152 }
153 if (!policy) {
154 policy = alloc_feature_policy(name);
155 sway_assert(policy, "Unable to allocate security policy");
156 if (policy) {
157 list_add(config->feature_policies, policy);
158 }
159 }
160 return policy;
161}
162
163uint32_t get_feature_policy_mask(pid_t pid) {
164 uint32_t default_policy = 0;
165 const char *link = get_pid_exe(pid);
166
167 for (int i = 0; i < config->feature_policies->length; ++i) {
168 struct feature_policy *policy = config->feature_policies->items[i];
169 if (strcmp(policy->program, "*") == 0) {
170 default_policy = policy->features;
171 }
172 if (strcmp(policy->program, link) == 0) {
173 return policy->features;
174 }
175 }
176
177 return default_policy;
178}
179
180uint32_t get_ipc_policy_mask(pid_t pid) {
181 uint32_t default_policy = 0;
182 const char *link = get_pid_exe(pid);
183
184 for (int i = 0; i < config->ipc_policies->length; ++i) {
185 struct ipc_policy *policy = config->ipc_policies->items[i];
186 if (strcmp(policy->program, "*") == 0) {
187 default_policy = policy->features;
188 }
189 if (strcmp(policy->program, link) == 0) {
190 return policy->features;
191 }
192 }
193
194 return default_policy;
195}
196
197uint32_t get_command_policy_mask(const char *cmd) {
198 uint32_t default_policy = 0;
199
200 for (int i = 0; i < config->command_policies->length; ++i) {
201 struct command_policy *policy = config->command_policies->items[i];
202 if (strcmp(policy->command, "*") == 0) {
203 default_policy = policy->context;
204 }
205 if (strcmp(policy->command, cmd) == 0) {
206 return policy->context;
207 }
208 }
209
210 return default_policy;
211}
212
213const char *command_policy_str(enum command_context context) {
214 switch (context) {
215 case CONTEXT_ALL:
216 return "all";
217 case CONTEXT_CONFIG:
218 return "config";
219 case CONTEXT_BINDING:
220 return "binding";
221 case CONTEXT_IPC:
222 return "IPC";
223 case CONTEXT_CRITERIA:
224 return "criteria";
225 default:
226 return "unknown";
227 }
228}
diff --git a/sway/server.c b/sway/server.c
new file mode 100644
index 00000000..c1125f14
--- /dev/null
+++ b/sway/server.c
@@ -0,0 +1,141 @@
1#define _POSIX_C_SOURCE 200112L
2#include <assert.h>
3#include <stdbool.h>
4#include <stdlib.h>
5#include <wayland-server.h>
6#include <wlr/backend.h>
7#include <wlr/backend/session.h>
8#include <wlr/render/wlr_renderer.h>
9#include <wlr/types/wlr_compositor.h>
10#include <wlr/types/wlr_gamma_control.h>
11#include <wlr/types/wlr_linux_dmabuf.h>
12#include <wlr/types/wlr_layer_shell.h>
13#include <wlr/types/wlr_primary_selection.h>
14#include <wlr/types/wlr_screenshooter.h>
15#include <wlr/types/wlr_server_decoration.h>
16#include <wlr/types/wlr_xcursor_manager.h>
17#include <wlr/types/wlr_xdg_output.h>
18#include <wlr/types/wlr_wl_shell.h>
19#include <wlr/util/log.h>
20// TODO WLR: make Xwayland optional
21#include <wlr/xwayland.h>
22#include "sway/commands.h"
23#include "sway/config.h"
24#include "sway/input/input-manager.h"
25#include "sway/server.h"
26#include "sway/tree/layout.h"
27
28static void server_ready(struct wl_listener *listener, void *data) {
29 wlr_log(L_DEBUG, "Compositor is ready, executing cmds in queue");
30 // Execute commands until there are none left
31 config->active = true;
32 while (config->cmd_queue->length) {
33 char *line = config->cmd_queue->items[0];
34 struct cmd_results *res = execute_command(line, NULL);
35 if (res->status != CMD_SUCCESS) {
36 wlr_log(L_ERROR, "Error on line '%s': %s", line, res->error);
37 }
38 free_cmd_results(res);
39 free(line);
40 list_del(config->cmd_queue, 0);
41 }
42}
43
44bool server_init(struct sway_server *server) {
45 wlr_log(L_DEBUG, "Initializing Wayland server");
46
47 server->wl_display = wl_display_create();
48 server->wl_event_loop = wl_display_get_event_loop(server->wl_display);
49 server->backend = wlr_backend_autocreate(server->wl_display);
50
51 struct wlr_renderer *renderer = wlr_backend_get_renderer(server->backend);
52 assert(renderer);
53
54 wl_display_init_shm(server->wl_display);
55
56 server->compositor = wlr_compositor_create(server->wl_display, renderer);
57 server->data_device_manager =
58 wlr_data_device_manager_create(server->wl_display);
59
60 wlr_screenshooter_create(server->wl_display);
61 wlr_gamma_control_manager_create(server->wl_display);
62 wlr_primary_selection_device_manager_create(server->wl_display);
63
64 server->new_output.notify = handle_new_output;
65 wl_signal_add(&server->backend->events.new_output, &server->new_output);
66
67 wlr_xdg_output_manager_create(server->wl_display,
68 root_container.sway_root->output_layout);
69
70 server->layer_shell = wlr_layer_shell_create(server->wl_display);
71 wl_signal_add(&server->layer_shell->events.new_surface,
72 &server->layer_shell_surface);
73 server->layer_shell_surface.notify = handle_layer_shell_surface;
74
75 server->xdg_shell_v6 = wlr_xdg_shell_v6_create(server->wl_display);
76 wl_signal_add(&server->xdg_shell_v6->events.new_surface,
77 &server->xdg_shell_v6_surface);
78 server->xdg_shell_v6_surface.notify = handle_xdg_shell_v6_surface;
79
80 server->wl_shell = wlr_wl_shell_create(server->wl_display);
81 wl_signal_add(&server->wl_shell->events.new_surface,
82 &server->wl_shell_surface);
83 server->wl_shell_surface.notify = handle_wl_shell_surface;
84
85 // TODO make xwayland optional
86 server->xwayland =
87 wlr_xwayland_create(server->wl_display, server->compositor);
88 wl_signal_add(&server->xwayland->events.new_surface,
89 &server->xwayland_surface);
90 server->xwayland_surface.notify = handle_xwayland_surface;
91 wl_signal_add(&server->xwayland->events.ready,
92 &server->xwayland_ready);
93 // TODO: call server_ready now if xwayland is not enabled
94 server->xwayland_ready.notify = server_ready;
95
96 // TODO: configurable cursor theme and size
97 server->xcursor_manager = wlr_xcursor_manager_create(NULL, 24);
98 wlr_xcursor_manager_load(server->xcursor_manager, 1);
99 struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor(
100 server->xcursor_manager, "left_ptr", 1);
101 if (xcursor != NULL) {
102 struct wlr_xcursor_image *image = xcursor->images[0];
103 wlr_xwayland_set_cursor(server->xwayland, image->buffer,
104 image->width * 4, image->width, image->height, image->hotspot_x,
105 image->hotspot_y);
106 }
107
108 // TODO: Integration with sway borders
109 struct wlr_server_decoration_manager *deco_manager =
110 wlr_server_decoration_manager_create(server->wl_display);
111 wlr_server_decoration_manager_set_default_mode(
112 deco_manager, WLR_SERVER_DECORATION_MANAGER_MODE_SERVER);
113
114 wlr_linux_dmabuf_create(server->wl_display, renderer);
115
116 server->socket = wl_display_add_socket_auto(server->wl_display);
117 if (!server->socket) {
118 wlr_log(L_ERROR, "Unable to open wayland socket");
119 wlr_backend_destroy(server->backend);
120 return false;
121 }
122
123 input_manager = input_manager_create(server);
124 return true;
125}
126
127void server_fini(struct sway_server *server) {
128 // TODO
129}
130
131void server_run(struct sway_server *server) {
132 wlr_log(L_INFO, "Running compositor on wayland display '%s'",
133 server->socket);
134 setenv("WAYLAND_DISPLAY", server->socket, true);
135 if (!wlr_backend_start(server->backend)) {
136 wlr_log(L_ERROR, "Failed to start backend");
137 wlr_backend_destroy(server->backend);
138 return;
139 }
140 wl_display_run(server->wl_display);
141}
diff --git a/sway/sway-input.5.txt b/sway/sway-input.5.txt
index f0c8f87c..05725360 100644
--- a/sway/sway-input.5.txt
+++ b/sway/sway-input.5.txt
@@ -1,5 +1,5 @@
1///// 1/////
2vim:set ts=4 sw=4 tw=82 noet: 2vim:set ft=asciidoc ts=4 sw=4 tw=82 noet:
3///// 3/////
4sway-input (5) 4sway-input (5)
5============== 5==============
@@ -11,12 +11,57 @@ sway-input - input configuration file and commands
11Description 11Description
12----------- 12-----------
13 13
14Sway allows for configuration of libinput devices within the sway configuration file. 14Sway allows for configuration of devices within the sway configuration file.
15sway-input commands must be used inside an _input { }_ block in the config. 15sway-input commands must be used inside an _input { }_ block in the config.
16To obtain a list of available device identifiers, run **swaymsg -t get_inputs**. 16To obtain a list of available device identifiers, run **swaymsg -t get_inputs**.
17 17
18Commands 18Input Commands
19-------- 19--------------
20
21Keyboard Configuration
22~~~~~~~~~~~~~~~~~~~~~~
23
24For more information on these xkb configuration options, see
25**xkeyboard-config**(7).
26
27**input** <identifier> xkb_layout <layout_name>::
28 Sets the layout of the keyboard like _us_ or _de_.
29
30**input** <identifier> xkb_model <model_name>::
31 Sets the model of the keyboard. This has an influence for some extra keys your
32 keyboard might have.
33
34**input** <identifier> xkb_options <options>::
35 Sets extra xkb configuration options for the keyboard.
36
37**input** <identifier> xkb_rules <rules>::
38 Sets files of rules to be used for keyboard mapping composition.
39
40**input** <identifier> xkb_variant <variant>::
41 Sets the variant of the keyboard like _dvorak_ or _colemak_.
42
43Mapping Configuration
44---------------------
45
46**input** <identifier> map_to_output <identifier>::
47 Maps inputs from this device to the specified output. Only meaningful if the
48 device is a pointer, touch, or drawing tablet device.
49
50**input** <identifier> map_to_region <WxH\@X,Y>::
51 Maps inputs from this device to the specified region of the global output
52 layout. Only meaningful if the device is a pointer, touch, or drawing tablet
53 device.
54
55**input** <identifier> map_region <WxH\@X,Y>::
56 Ignores inputs from this device that do not occur within the specified region.
57 Can be in millimeters (e.g. 10mmx20mm\@10mm,20mm) or in terms of 0..1 (e.g.
58 0.5x0.5\@0,0). Not all devices support millimeters. Only meaningful if the
59 device is not a keyboard an provides events in absolute terms (such as a
60 drawing tablet or touch screen - most pointers provide events relative to the
61 previous frame).
62
63Libinput Configuration
64~~~~~~~~~~~~~~~~~~~~~~
20 65
21**input** <identifier> accel_profile <adaptive|flat>:: 66**input** <identifier> accel_profile <adaptive|flat>::
22 Sets the pointer acceleration profile for the specified input device. 67 Sets the pointer acceleration profile for the specified input device.
@@ -53,6 +98,27 @@ Commands
53**input** <identifier> tap <enabled|disabled>:: 98**input** <identifier> tap <enabled|disabled>::
54 Enables or disables tap for specified input device. 99 Enables or disables tap for specified input device.
55 100
101Seat Configuration
102------------------
103
104Configure options for multiseat mode. sway-seat commands must be used inside a
105_seat { }_ block in the config.
106
107A _seat_ is a collection of input devices that act independently of each other.
108Seats are identified by name and the default seat is _seat0_ if no seats are
109configured. Each seat has an independent keyboard focus and a separate cursor that
110is controlled by the pointer devices of the seat. This is useful for multiple
111people using the desktop at the same time with their own devices (each sitting in
112their own "seat").
113
114**seat** <name> attach <input_identifier>::
115 Attach an input device to this seat by its input identifier. A special value
116 of _*_ will attach all devices to the seat.
117
118**seat** <name> fallback <true|false>::
119 Set this seat as the fallback seat. A fallback seat will attach any device not
120 explicitly attached to another seat (similar to a "default" seat).
121
56See Also 122See Also
57-------- 123--------
58 124
diff --git a/sway/sway.1.txt b/sway/sway.1.txt
index 14ab9f49..17fc13da 100644
--- a/sway/sway.1.txt
+++ b/sway/sway.1.txt
@@ -1,5 +1,5 @@
1///// 1/////
2vim:set ts=4 sw=4 tw=82 noet: 2vim:set ft=asciidoc ts=4 sw=4 tw=82 noet:
3///// 3/////
4:quotes.~: 4:quotes.~:
5 5
@@ -93,27 +93,6 @@ The following environment variables have an effect on sway:
93*SWAYSOCK*:: 93*SWAYSOCK*::
94 Specifies the path to the sway IPC socket. 94 Specifies the path to the sway IPC socket.
95 95
96*WLC_DRM_DEVICE*::
97 Specifies the device to use in DRM mode.
98
99*WLC_SHM*::
100 Set 1 to force EGL clients to use shared memory.
101
102*WLC_OUTPUTS*::
103 Number of fake outputs to use when running in X11 mode.
104
105*WLC_XWAYLAND*::
106 Set to 0 to disable Xwayland support.
107
108*WLC_LIBINPUT*::
109 Set to 1 to force libinput (even in X11 mode).
110
111*WLC_REPEAT_DELAY*::
112 Configures the keyboard repeat delay.
113
114*WLC_REPEAT_RATE*::
115 Configures the keyboard repeat rate.
116
117*XKB_DEFAULT_RULES*, *XKB_DEFAULT_MODEL*, *XKB_DEFAULT_LAYOUT*, *XKB_DEFAULT_VARIANT*, *XKB_DEFAULT_OPTIONS*:: 96*XKB_DEFAULT_RULES*, *XKB_DEFAULT_MODEL*, *XKB_DEFAULT_LAYOUT*, *XKB_DEFAULT_VARIANT*, *XKB_DEFAULT_OPTIONS*::
118 Configures the xkb keyboard settings. See xkeyboard-config(7). 97 Configures the xkb keyboard settings. See xkeyboard-config(7).
119 98
diff --git a/sway/sway.5.txt b/sway/sway.5.txt
index 750254c8..03975349 100644
--- a/sway/sway.5.txt
+++ b/sway/sway.5.txt
@@ -43,6 +43,9 @@ The following commands may only be used in the configuration file.
43 Sets variable $name to _value_. You can use the new variable in the arguments 43 Sets variable $name to _value_. You can use the new variable in the arguments
44 of future commands. 44 of future commands.
45 45
46**swaybg_command** <command>::
47 Executes custom bg command, default is _swaybg_.
48
46The following commands cannot be used directly in the configuration file. 49The following commands cannot be used directly in the configuration file.
47They are expected to be used with **bindsym** or at runtime through **swaymsg**(1). 50They are expected to be used with **bindsym** or at runtime through **swaymsg**(1).
48 51
@@ -126,22 +129,21 @@ They are expected to be used with **bindsym** or at runtime through **swaymsg**(
126 129
127**resize** <shrink|grow> <width|height> [<amount>] [px|ppt]:: 130**resize** <shrink|grow> <width|height> [<amount>] [px|ppt]::
128 Resizes the currently focused container or view by _amount_. _amount_ is 131 Resizes the currently focused container or view by _amount_. _amount_ is
129 optional: the default value is 10 (either px or ppt depending on the view 132 optional: the default value is 10 (either px or ppt depending on the view type).
130 type). The [px|ppt] parameter is optional. _px_ specifies that _amount_ refers 133 The [px|ppt] parameter is optional. _px_ specifies that _amount_ refers to pixels;
131 to pixels; _ppt_ specifies that _amount_ refers to percentage points of the 134 _ppt_ specifies that _amount_ refers to percentage points of the current
132 current dimension. Floating views use px dimensions by default (but can use 135 size. Floating views use px by default (but can use ppt if specified); tiled
133 ppt if specified); tiled views use ppt dimensions by default (but can use px 136 views use ppt by default (but can use px if specified).
134 if specified).
135 137
136**resize set** <width> [px] <height> [px]:: 138**resize set** <width> [px] <height> [px]::
137 Sets the width and height of the currently focused container to _width_ pixels 139 Sets the width and height of the currently focused container to _width_ pixels
138 and _height_ pixels. The [px] parameters are optional and have no effect. This 140 and _height_ pixels. The [px] parameters are optional and have no effect. This
139 command only accepts pixel dimensions. 141 command only accepts a size in pixels.
140 142
141**resize set** <width|height> <amount> [px] [<width|height> <amount> [px]]:: 143**resize set** <width|height> <amount> [px] [<width|height> <amount> [px]]::
142 Sets the _width_ and/or _height_ of the currently focused container to 144 Sets the _width_ and/or _height_ of the currently focused container to
143 _amount_. The [px] parameters are optional and have no effect. This command 145 _amount_. The [px] parameters are optional and have no effect. This command
144 only accepts pixel dimensions. 146 only accepts a size in pixels.
145 147
146**scratchpad show**:: 148**scratchpad show**::
147 Shows a window from the scratchpad. Repeatedly using this command will cycle 149 Shows a window from the scratchpad. Repeatedly using this command will cycle
@@ -254,14 +256,14 @@ The default colors are:
254 *restart* is executed. 256 *restart* is executed.
255 257
256**floating_maximum_size** <width> x <height>:: 258**floating_maximum_size** <width> x <height>::
257 Specifies the maximum dimensions of floating windows. 259 Specifies the maximum size of floating windows.
258 Uses the container dimensions as default. 260 Uses the container size as default.
259 -1 x -1 will remove any restriction on dimensions. 261 -1 x -1 will remove any restriction on size.
260 0 x 0 has the same behavior as not setting any value. 262 0 x 0 has the same behavior as not setting any value.
261 If in conflict, this option has precedence over floating_minimum_size. 263 If in conflict, this option has precedence over floating_minimum_size.
262 264
263**floating_minimum_size** <width> x <height>:: 265**floating_minimum_size** <width> x <height>::
264 Specifies the minimum dimensions of floating windows. 266 Specifies the minimum size of floating windows.
265 Default parameters are 75 x 50. 267 Default parameters are 75 x 50.
266 -1 and 0 are invalid parameters, default will be used instead. 268 -1 and 0 are invalid parameters, default will be used instead.
267 269
@@ -313,7 +315,7 @@ The default colors are:
313**hide_edge_borders** <none|vertical|horizontal|both|smart>:: 315**hide_edge_borders** <none|vertical|horizontal|both|smart>::
314 Hide window borders adjacent to the screen edges. Default is _none_. 316 Hide window borders adjacent to the screen edges. Default is _none_.
315 317
316**input** <input device> <block of commands>:: 318**input** <input_device> <block of commands>::
317 Append _{_ to this command, the following lines will be commands to configure 319 Append _{_ to this command, the following lines will be commands to configure
318 the named input device, and _}_ on its own line will close the block. 320 the named input device, and _}_ on its own line will close the block.
319 + 321 +
@@ -321,6 +323,18 @@ The default colors are:
321 + 323 +
322 See **sway-input**(5) for details. 324 See **sway-input**(5) for details.
323 325
326**seat** <seat_name> <block of commands>::
327 Append _{_ to this command, the following lines will be commands to configure
328 the named seat, and _}_ on its own line will close the block.
329 See **sway-input**(5) for details.
330
331**seat** <seat_name> cursor <move|set> <x> <y>::
332 Move cursor relatively to current position or set to absolute screen position.
333 A value of 0 causes the axis to be ignored in both commands.
334
335**seat** <seat_name> cursor <press|release> <left|right|1|2|3...>::
336 Simulate press of mouse button specified by left, right, or numerical code.
337
324**kill**:: 338**kill**::
325 Kills (force-closes) the currently-focused container and all of its children. 339 Kills (force-closes) the currently-focused container and all of its children.
326 340
@@ -349,35 +363,44 @@ The default colors are:
349 Prevents windows matching <criteria> from being focused automatically when 363 Prevents windows matching <criteria> from being focused automatically when
350 they're created. This does not apply to the first window in a workspace. 364 they're created. This does not apply to the first window in a workspace.
351 365
352**output** <name> <resolution|res> <WIDTHxHEIGHT>:: 366**output** <name> mode|resolution|res <WIDTHxHEIGHT>[@<RATE>[Hz]]::
353 Configures the specified output to use the given resolution. 367 Configures the specified output to use the given mode. Modes are a combination
368 of width and height (in pixels) and a refresh rate that your display can be
369 configured to use. For a list of available modes, use swaymsg -t get_outputs.
370 +
371 Examples:
372 +
373 output HDMI-A-1 mode 1920x1080
354 + 374 +
355 _Note_: sway does not currently support setting the output mode. Your output's native 375 output HDMI-A-1 mode 1920x1080@60Hz
356 resolution will be used and the screen will be scaled from the resolution
357 specified to your native resolution.
358 376
359**output** <name> <position|pos> <X,Y>:: 377**output** <name> position|pos <X,Y>::
360 Configures the specified output to be arranged at the given position. 378 Configures the specified output to be arranged at the given position.
361 379
362**output** <name> <scale> <I>:: 380**output** <name> scale <I>::
363 Configures the specified output to be scaled up by the specified integer 381 Configures the specified output to be scaled up by the specified integer
364 scaling factor (usually 2 for HiDPI screens). 382 scaling factor (usually 2 for HiDPI screens). Fractional scaling is supported.
365 383
366**output** <name> <background|bg> <file> <mode>:: 384**output** <name> background|bg <file> <mode>::
367 Sets the wallpaper for the given output to the specified file, using the given 385 Sets the wallpaper for the given output to the specified file, using the given
368 scaling mode (one of "stretch", "fill", "fit", "center", "tile"). 386 scaling mode (one of "stretch", "fill", "fit", "center", "tile").
369 387
370**output** <name> <background|bg> <color> solid_color:: 388**output** <name> background|bg <color> solid_color::
371 Sets the background of the given output to the specified color. _color_ should 389 Sets the background of the given output to the specified color. _color_ should
372 be specified as an _#rrggbb_ (no alpha) color. 390 be specified as an _#rrggbb_ (no alpha) color.
373 391
392**output** <name> transform <transform>::
393 Sets the background transform to the given value. Can be one of "90", "180",
394 "270" for rotation; or "flipped", "flipped-90", "flipped-180", "flipped-270"
395 to apply a rotation and flip, or "normal" to apply no transform.
396
374**output** <name> disable:: 397**output** <name> disable::
375 Disables the specified output. 398 Disables the specified output.
376 399
377**NOTES FOR THE OUTPUT COMMAND**:: 400**NOTES FOR THE OUTPUT COMMAND**::
378 You may combine output commands into one, like so: 401 You may combine output commands into one, like so:
379 + 402 +
380 output HDMI-A-1 res 1920x1080 pos 1920,0 bg ~/wallpaper.png stretch 403 output HDMI-A-1 mode 1920x1080 pos 1920,0 bg ~/wallpaper.png stretch
381 + 404 +
382 You can get a list of output names like so: 405 You can get a list of output names like so:
383 + 406 +
@@ -397,6 +420,10 @@ The default colors are:
397 However, any mark that starts with an underscore will not be drawn even if the 420 However, any mark that starts with an underscore will not be drawn even if the
398 option is on. The default option is _on_. 421 option is on. The default option is _on_.
399 422
423**opacity** <value>::
424 Set the opacity of the window between 0 (completely transparent) and 1
425 (completely opaque).
426
400**unmark** <identifier>:: 427**unmark** <identifier>::
401 **Unmark** will remove _identifier_ from the list of current marks on a window. If 428 **Unmark** will remove _identifier_ from the list of current marks on a window. If
402 no _identifier_ is specified, then **unmark** will remove all marks. 429 no _identifier_ is specified, then **unmark** will remove all marks.
diff --git a/sway/tree/container.c b/sway/tree/container.c
new file mode 100644
index 00000000..ea1c93bb
--- /dev/null
+++ b/sway/tree/container.c
@@ -0,0 +1,534 @@
1#define _POSIX_C_SOURCE 200809L
2#include <assert.h>
3#include <stdint.h>
4#include <stdlib.h>
5#include <string.h>
6#include <strings.h>
7#include <wayland-server.h>
8#include <wlr/types/wlr_output_layout.h>
9#include <wlr/types/wlr_wl_shell.h>
10#include "sway/config.h"
11#include "sway/input/input-manager.h"
12#include "sway/input/seat.h"
13#include "sway/ipc-server.h"
14#include "sway/output.h"
15#include "sway/server.h"
16#include "sway/tree/layout.h"
17#include "sway/tree/view.h"
18#include "sway/tree/workspace.h"
19#include "log.h"
20
21static list_t *bfs_queue;
22
23static list_t *get_bfs_queue() {
24 if (!bfs_queue) {
25 bfs_queue = create_list();
26 if (!bfs_queue) {
27 wlr_log(L_ERROR, "could not allocate list for bfs queue");
28 return NULL;
29 }
30 }
31 bfs_queue->length = 0;
32
33 return bfs_queue;
34}
35
36const char *container_type_to_str(enum sway_container_type type) {
37 switch (type) {
38 case C_ROOT:
39 return "C_ROOT";
40 case C_OUTPUT:
41 return "C_OUTPUT";
42 case C_WORKSPACE:
43 return "C_WORKSPACE";
44 case C_CONTAINER:
45 return "C_CONTAINER";
46 case C_VIEW:
47 return "C_VIEW";
48 default:
49 return "C_UNKNOWN";
50 }
51}
52
53void container_create_notify(struct sway_container *container) {
54 // TODO send ipc event type based on the container type
55 wl_signal_emit(&root_container.sway_root->events.new_container, container);
56
57 if (container->type == C_VIEW || container->type == C_CONTAINER) {
58 ipc_event_window(container, "new");
59 }
60}
61
62static void container_close_notify(struct sway_container *container) {
63 if (container == NULL) {
64 return;
65 }
66 // TODO send ipc event type based on the container type
67 if (container->type == C_VIEW || container->type == C_WORKSPACE) {
68 ipc_event_window(container, "close");
69 }
70}
71
72struct sway_container *container_create(enum sway_container_type type) {
73 // next id starts at 1 because 0 is assigned to root_container in layout.c
74 static size_t next_id = 1;
75 struct sway_container *c = calloc(1, sizeof(struct sway_container));
76 if (!c) {
77 return NULL;
78 }
79 c->id = next_id++;
80 c->layout = L_NONE;
81 c->workspace_layout = L_NONE;
82 c->type = type;
83 c->alpha = 1.0f;
84
85 if (type != C_VIEW) {
86 c->children = create_list();
87 }
88
89 wl_signal_init(&c->events.destroy);
90 wl_signal_init(&c->events.reparent);
91
92 return c;
93}
94
95static void _container_destroy(struct sway_container *cont) {
96 if (cont == NULL) {
97 return;
98 }
99
100 wl_signal_emit(&cont->events.destroy, cont);
101 container_close_notify(cont);
102
103 struct sway_container *parent = cont->parent;
104 if (cont->children != NULL && cont->children->length) {
105 // remove children until there are no more, container_destroy calls
106 // container_remove_child, which removes child from this container
107 while (cont->children != NULL) {
108 struct sway_container *child = cont->children->items[0];
109 container_remove_child(child);
110 _container_destroy(child);
111 }
112 }
113 if (cont->marks) {
114 list_foreach(cont->marks, free);
115 list_free(cont->marks);
116 }
117 if (parent) {
118 parent = container_remove_child(cont);
119 }
120 if (cont->name) {
121 free(cont->name);
122 }
123 list_free(cont->children);
124 cont->children = NULL;
125 free(cont);
126}
127
128static struct sway_container *container_output_destroy(
129 struct sway_container *output) {
130 if (!sway_assert(output, "cannot destroy null output")) {
131 return NULL;
132 }
133
134 if (output->children->length > 0) {
135 // TODO save workspaces when there are no outputs.
136 // TODO also check if there will ever be no outputs except for exiting
137 // program
138 if (root_container.children->length > 1) {
139 int p = root_container.children->items[0] == output;
140 // Move workspace from this output to another output
141 while (output->children->length) {
142 struct sway_container *child = output->children->items[0];
143 container_remove_child(child);
144 container_add_child(root_container.children->items[p], child);
145 }
146 container_sort_workspaces(root_container.children->items[p]);
147 arrange_windows(root_container.children->items[p],
148 -1, -1);
149 }
150 }
151
152 wl_list_remove(&output->sway_output->destroy.link);
153 wl_list_remove(&output->sway_output->mode.link);
154 wl_list_remove(&output->sway_output->transform.link);
155 wl_list_remove(&output->sway_output->scale.link);
156
157 wl_list_remove(&output->sway_output->damage_destroy.link);
158 wl_list_remove(&output->sway_output->damage_frame.link);
159
160 wlr_log(L_DEBUG, "OUTPUT: Destroying output '%s'", output->name);
161 _container_destroy(output);
162 return &root_container;
163}
164
165static struct sway_container *container_workspace_destroy(
166 struct sway_container *workspace) {
167 if (!sway_assert(workspace, "cannot destroy null workspace")) {
168 return NULL;
169 }
170
171 // Do not destroy this if it's the last workspace on this output
172 struct sway_container *output = container_parent(workspace, C_OUTPUT);
173 if (output && output->children->length == 1) {
174 return NULL;
175 }
176
177 struct sway_container *parent = workspace->parent;
178 if (workspace->children->length == 0) {
179 // destroy the WS if there are no children (TODO check for floating)
180 wlr_log(L_DEBUG, "destroying workspace '%s'", workspace->name);
181 ipc_event_workspace(workspace, NULL, "empty");
182 } else {
183 // Move children to a different workspace on this output
184 struct sway_container *new_workspace = NULL;
185 // TODO move floating
186 for (int i = 0; i < output->children->length; i++) {
187 if (output->children->items[i] != workspace) {
188 new_workspace = output->children->items[i];
189 break;
190 }
191 }
192
193 wlr_log(L_DEBUG, "moving children to different workspace '%s' -> '%s'",
194 workspace->name, new_workspace->name);
195 for (int i = 0; i < workspace->children->length; i++) {
196 container_move_to(workspace->children->items[i], new_workspace);
197 }
198 }
199
200 _container_destroy(workspace);
201
202 output_damage_whole(output->sway_output);
203
204 return parent;
205}
206
207static void container_root_finish(struct sway_container *con) {
208 wlr_log(L_ERROR, "TODO: destroy the root container");
209}
210
211bool container_reap_empty(struct sway_container *con) {
212 switch (con->type) {
213 case C_ROOT:
214 case C_OUTPUT:
215 // dont reap these
216 break;
217 case C_WORKSPACE:
218 if (!workspace_is_visible(con) && con->children->length == 0) {
219 wlr_log(L_DEBUG, "Destroying workspace via reaper");
220 container_workspace_destroy(con);
221 return true;
222 }
223 break;
224 case C_CONTAINER:
225 if (con->children->length == 0) {
226 _container_destroy(con);
227 return true;
228 }
229 case C_VIEW:
230 break;
231 case C_TYPES:
232 sway_assert(false, "container_reap_empty called on an invalid "
233 "container");
234 break;
235 }
236
237 return false;
238}
239
240struct sway_container *container_reap_empty_recursive(
241 struct sway_container *con) {
242 while (con) {
243 struct sway_container *next = con->parent;
244 if (!container_reap_empty(con)) {
245 break;
246 }
247 con = next;
248 }
249 return con;
250}
251
252struct sway_container *container_flatten(struct sway_container *container) {
253 while (container->type == C_CONTAINER && container->children->length == 1) {
254 struct sway_container *child = container->children->items[0];
255 struct sway_container *parent = container->parent;
256 container_replace_child(container, child);
257 container_destroy(container);
258 container = parent;
259 }
260 return container;
261}
262
263struct sway_container *container_destroy(struct sway_container *con) {
264 if (con == NULL) {
265 return NULL;
266 }
267
268 struct sway_container *parent = con->parent;
269
270 switch (con->type) {
271 case C_ROOT:
272 container_root_finish(con);
273 break;
274 case C_OUTPUT:
275 // dont try to reap the root after this
276 container_output_destroy(con);
277 break;
278 case C_WORKSPACE:
279 // dont try to reap the output after this
280 container_workspace_destroy(con);
281 break;
282 case C_CONTAINER:
283 if (con->children->length) {
284 for (int i = 0; i < con->children->length; ++i) {
285 struct sway_container *child = con->children->items[0];
286 container_remove_child(child);
287 container_add_child(parent, child);
288 }
289 }
290 _container_destroy(con);
291 break;
292 case C_VIEW:
293 _container_destroy(con);
294 break;
295 case C_TYPES:
296 wlr_log(L_ERROR, "container_destroy called on an invalid "
297 "container");
298 break;
299 }
300
301 return container_reap_empty_recursive(parent);
302}
303
304static void container_close_func(struct sway_container *container, void *data) {
305 if (container->type == C_VIEW) {
306 view_close(container->sway_view);
307 }
308}
309
310struct sway_container *container_close(struct sway_container *con) {
311 if (!sway_assert(con != NULL,
312 "container_close called with a NULL container")) {
313 return NULL;
314 }
315
316 struct sway_container *parent = con->parent;
317
318 if (con->type == C_VIEW) {
319 view_close(con->sway_view);
320 } else {
321 container_for_each_descendant_dfs(con, container_close_func, NULL);
322 }
323
324 return parent;
325}
326
327struct sway_container *container_view_create(struct sway_container *sibling,
328 struct sway_view *sway_view) {
329 if (!sway_assert(sibling,
330 "container_view_create called with NULL sibling/parent")) {
331 return NULL;
332 }
333 const char *title = view_get_title(sway_view);
334 struct sway_container *swayc = container_create(C_VIEW);
335 wlr_log(L_DEBUG, "Adding new view %p:%s to container %p %d %s",
336 swayc, title, sibling, sibling ? sibling->type : 0, sibling->name);
337 // Setup values
338 swayc->sway_view = sway_view;
339 swayc->name = title ? strdup(title) : NULL;
340 swayc->width = 0;
341 swayc->height = 0;
342
343 if (sibling->type == C_WORKSPACE) {
344 // Case of focused workspace, just create as child of it
345 container_add_child(sibling, swayc);
346 } else {
347 // Regular case, create as sibling of current container
348 container_add_sibling(sibling, swayc);
349 }
350 container_create_notify(swayc);
351 return swayc;
352}
353
354void container_descendants(struct sway_container *root,
355 enum sway_container_type type,
356 void (*func)(struct sway_container *item, void *data), void *data) {
357 for (int i = 0; i < root->children->length; ++i) {
358 struct sway_container *item = root->children->items[i];
359 if (item->type == type) {
360 func(item, data);
361 }
362 if (item->children && item->children->length) {
363 container_descendants(item, type, func, data);
364 }
365 }
366}
367
368struct sway_container *container_find(struct sway_container *container,
369 bool (*test)(struct sway_container *view, void *data), void *data) {
370 if (!container->children) {
371 return NULL;
372 }
373 // TODO: floating windows
374 for (int i = 0; i < container->children->length; ++i) {
375 struct sway_container *child = container->children->items[i];
376 if (test(child, data)) {
377 return child;
378 } else {
379 struct sway_container *res = container_find(child, test, data);
380 if (res) {
381 return res;
382 }
383 }
384 }
385 return NULL;
386}
387
388struct sway_container *container_parent(struct sway_container *container,
389 enum sway_container_type type) {
390 if (!sway_assert(container, "container is NULL")) {
391 return NULL;
392 }
393 if (!sway_assert(type < C_TYPES && type >= C_ROOT, "invalid type")) {
394 return NULL;
395 }
396 do {
397 container = container->parent;
398 } while (container && container->type != type);
399 return container;
400}
401
402struct sway_container *container_at(struct sway_container *parent,
403 double lx, double ly,
404 struct wlr_surface **surface, double *sx, double *sy) {
405 list_t *queue = get_bfs_queue();
406 if (!queue) {
407 return NULL;
408 }
409
410 list_add(queue, parent);
411
412 struct sway_container *swayc = NULL;
413 while (queue->length) {
414 swayc = queue->items[0];
415 list_del(queue, 0);
416 if (swayc->type == C_VIEW) {
417 struct sway_view *sview = swayc->sway_view;
418 struct sway_container *soutput = container_parent(swayc, C_OUTPUT);
419 struct wlr_box *output_box =
420 wlr_output_layout_get_box(
421 root_container.sway_root->output_layout,
422 soutput->sway_output->wlr_output);
423 double ox = lx - output_box->x;
424 double oy = ly - output_box->y;
425 double view_sx = ox - swayc->x;
426 double view_sy = oy - swayc->y;
427
428 double _sx, _sy;
429 struct wlr_surface *_surface;
430 switch (sview->type) {
431 case SWAY_VIEW_XWAYLAND:
432 _surface = wlr_surface_surface_at(sview->surface,
433 view_sx, view_sy, &_sx, &_sy);
434 break;
435 case SWAY_VIEW_WL_SHELL:
436 _surface = wlr_wl_shell_surface_surface_at(
437 sview->wlr_wl_shell_surface,
438 view_sx, view_sy, &_sx, &_sy);
439 break;
440 case SWAY_VIEW_XDG_SHELL_V6:
441 // the top left corner of the sway container is the
442 // coordinate of the top left corner of the window geometry
443 view_sx += sview->wlr_xdg_surface_v6->geometry.x;
444 view_sy += sview->wlr_xdg_surface_v6->geometry.y;
445
446 _surface = wlr_xdg_surface_v6_surface_at(
447 sview->wlr_xdg_surface_v6,
448 view_sx, view_sy, &_sx, &_sy);
449 break;
450 }
451 if (_surface) {
452 *sx = _sx;
453 *sy = _sy;
454 *surface = _surface;
455 return swayc;
456 }
457 } else {
458 list_cat(queue, swayc->children);
459 }
460 }
461
462 return NULL;
463}
464
465void container_for_each_descendant_dfs(struct sway_container *container,
466 void (*f)(struct sway_container *container, void *data),
467 void *data) {
468 if (container) {
469 if (container->children) {
470 for (int i = 0; i < container->children->length; ++i) {
471 struct sway_container *child =
472 container->children->items[i];
473 container_for_each_descendant_dfs(child, f, data);
474 }
475 }
476 f(container, data);
477 }
478}
479
480void container_for_each_descendant_bfs(struct sway_container *con,
481 void (*f)(struct sway_container *con, void *data), void *data) {
482 list_t *queue = get_bfs_queue();
483 if (!queue) {
484 return;
485 }
486
487 if (queue == NULL) {
488 wlr_log(L_ERROR, "could not allocate list");
489 return;
490 }
491
492 list_add(queue, con);
493
494 struct sway_container *current = NULL;
495 while (queue->length) {
496 current = queue->items[0];
497 list_del(queue, 0);
498 f(current, data);
499 // TODO floating containers
500 list_cat(queue, current->children);
501 }
502}
503
504bool container_has_anscestor(struct sway_container *descendant,
505 struct sway_container *anscestor) {
506 while (descendant->type != C_ROOT) {
507 descendant = descendant->parent;
508 if (descendant == anscestor) {
509 return true;
510 }
511 }
512 return false;
513}
514
515static bool find_child_func(struct sway_container *con, void *data) {
516 struct sway_container *child = data;
517 return con == child;
518}
519
520bool container_has_child(struct sway_container *con,
521 struct sway_container *child) {
522 if (con == NULL || con->type == C_VIEW || con->children->length == 0) {
523 return false;
524 }
525 return container_find(con, find_child_func, child);
526}
527
528void container_damage_whole(struct sway_container *con) {
529 struct sway_container *output = con;
530 if (output->type != C_OUTPUT) {
531 output = container_parent(output, C_OUTPUT);
532 }
533 output_damage_whole_container(output->sway_output, con);
534}
diff --git a/sway/tree/layout.c b/sway/tree/layout.c
new file mode 100644
index 00000000..ae76ca26
--- /dev/null
+++ b/sway/tree/layout.c
@@ -0,0 +1,1030 @@
1#define _POSIX_C_SOURCE 200809L
2#include <ctype.h>
3#include <math.h>
4#include <stdbool.h>
5#include <stdlib.h>
6#include <string.h>
7#include <wlr/types/wlr_output.h>
8#include <wlr/types/wlr_output_layout.h>
9#include "sway/debug.h"
10#include "sway/tree/container.h"
11#include "sway/tree/layout.h"
12#include "sway/output.h"
13#include "sway/tree/workspace.h"
14#include "sway/tree/view.h"
15#include "sway/input/seat.h"
16#include "sway/ipc-server.h"
17#include "list.h"
18#include "log.h"
19
20struct sway_container root_container;
21
22static void output_layout_handle_change(struct wl_listener *listener,
23 void *data) {
24 struct wlr_output_layout *output_layout =
25 root_container.sway_root->output_layout;
26 const struct wlr_box *layout_box =
27 wlr_output_layout_get_box(output_layout, NULL);
28 root_container.x = layout_box->x;
29 root_container.y = layout_box->y;
30 root_container.width = layout_box->width;
31 root_container.height = layout_box->height;
32
33 arrange_windows(&root_container, layout_box->width, layout_box->height);
34}
35
36struct sway_container *container_set_layout(struct sway_container *container,
37 enum sway_container_layout layout) {
38 if (container->type == C_WORKSPACE) {
39 container->workspace_layout = layout;
40 if (layout == L_HORIZ || layout == L_VERT) {
41 container->layout = layout;
42 }
43 } else {
44 container->layout = layout;
45 }
46 return container;
47}
48
49void layout_init(void) {
50 root_container.id = 0; // normally assigned in new_swayc()
51 root_container.type = C_ROOT;
52 root_container.layout = L_NONE;
53 root_container.name = strdup("root");
54 root_container.children = create_list();
55 wl_signal_init(&root_container.events.destroy);
56
57 root_container.sway_root = calloc(1, sizeof(*root_container.sway_root));
58 root_container.sway_root->output_layout = wlr_output_layout_create();
59 wl_list_init(&root_container.sway_root->xwayland_unmanaged);
60 wl_signal_init(&root_container.sway_root->events.new_container);
61
62 root_container.sway_root->output_layout_change.notify =
63 output_layout_handle_change;
64 wl_signal_add(&root_container.sway_root->output_layout->events.change,
65 &root_container.sway_root->output_layout_change);
66}
67
68static int index_child(const struct sway_container *child) {
69 // TODO handle floating
70 struct sway_container *parent = child->parent;
71 int i, len;
72 len = parent->children->length;
73 for (i = 0; i < len; ++i) {
74 if (parent->children->items[i] == child) {
75 break;
76 }
77 }
78
79 if (!sway_assert(i < len, "Stray container")) {
80 return -1;
81 }
82 return i;
83}
84
85void container_insert_child(struct sway_container *parent,
86 struct sway_container *child, int i) {
87 struct sway_container *old_parent = child->parent;
88 if (old_parent) {
89 container_remove_child(child);
90 }
91 wlr_log(L_DEBUG, "Inserting id:%zd at index %d", child->id, i);
92 list_insert(parent->children, i, child);
93 child->parent = parent;
94 wl_signal_emit(&child->events.reparent, old_parent);
95}
96
97struct sway_container *container_add_sibling(struct sway_container *fixed,
98 struct sway_container *active) {
99 // TODO handle floating
100 struct sway_container *old_parent = NULL;
101 if (active->parent) {
102 old_parent = active->parent;
103 container_remove_child(active);
104 }
105 struct sway_container *parent = fixed->parent;
106 int i = index_child(fixed);
107 list_insert(parent->children, i + 1, active);
108 active->parent = parent;
109 wl_signal_emit(&active->events.reparent, old_parent);
110 return active->parent;
111}
112
113void container_add_child(struct sway_container *parent,
114 struct sway_container *child) {
115 wlr_log(L_DEBUG, "Adding %p (%d, %fx%f) to %p (%d, %fx%f)",
116 child, child->type, child->width, child->height,
117 parent, parent->type, parent->width, parent->height);
118 list_add(parent->children, child);
119 child->parent = parent;
120}
121
122struct sway_container *container_remove_child(struct sway_container *child) {
123 struct sway_container *parent = child->parent;
124 for (int i = 0; i < parent->children->length; ++i) {
125 if (parent->children->items[i] == child) {
126 list_del(parent->children, i);
127 break;
128 }
129 }
130 child->parent = NULL;
131 return parent;
132}
133
134void container_move_to(struct sway_container *container,
135 struct sway_container *destination) {
136 if (container == destination
137 || container_has_anscestor(container, destination)) {
138 return;
139 }
140 struct sway_container *old_parent = container_remove_child(container);
141 container->width = container->height = 0;
142 struct sway_container *new_parent;
143 if (destination->type == C_VIEW) {
144 new_parent = container_add_sibling(destination, container);
145 } else {
146 new_parent = destination;
147 container_add_child(destination, container);
148 }
149 wl_signal_emit(&container->events.reparent, old_parent);
150 if (container->type == C_WORKSPACE) {
151 struct sway_seat *seat = input_manager_get_default_seat(
152 input_manager);
153 if (old_parent->children->length == 0) {
154 char *ws_name = workspace_next_name(old_parent->name);
155 struct sway_container *ws =
156 workspace_create(old_parent, ws_name);
157 free(ws_name);
158 seat_set_focus(seat, ws);
159 }
160 container_sort_workspaces(new_parent);
161 seat_set_focus(seat, new_parent);
162 }
163 if (old_parent) {
164 arrange_windows(old_parent, -1, -1);
165 }
166 arrange_windows(new_parent, -1, -1);
167}
168
169static bool sway_dir_to_wlr(enum movement_direction dir,
170 enum wlr_direction *out) {
171 switch (dir) {
172 case MOVE_UP:
173 *out = WLR_DIRECTION_UP;
174 break;
175 case MOVE_DOWN:
176 *out = WLR_DIRECTION_DOWN;
177 break;
178 case MOVE_LEFT:
179 *out = WLR_DIRECTION_LEFT;
180 break;
181 case MOVE_RIGHT:
182 *out = WLR_DIRECTION_RIGHT;
183 break;
184 default:
185 return false;
186 }
187
188 return true;
189}
190
191static bool is_parallel(enum sway_container_layout layout,
192 enum movement_direction dir) {
193 switch (layout) {
194 case L_TABBED:
195 case L_STACKED:
196 case L_HORIZ:
197 return dir == MOVE_LEFT || dir == MOVE_RIGHT;
198 case L_VERT:
199 return dir == MOVE_UP || dir == MOVE_DOWN;
200 default:
201 return false;
202 }
203}
204
205static enum movement_direction invert_movement(enum movement_direction dir) {
206 switch (dir) {
207 case MOVE_LEFT:
208 return MOVE_RIGHT;
209 case MOVE_RIGHT:
210 return MOVE_LEFT;
211 case MOVE_UP:
212 return MOVE_DOWN;
213 case MOVE_DOWN:
214 return MOVE_UP;
215 default:
216 sway_assert(0, "This function expects left|right|up|down");
217 return MOVE_LEFT;
218 }
219}
220
221static int move_offs(enum movement_direction move_dir) {
222 return move_dir == MOVE_LEFT || move_dir == MOVE_UP ? -1 : 1;
223}
224
225/* Gets the index of the most extreme member based on the movement offset */
226static int container_limit(struct sway_container *container,
227 enum movement_direction move_dir) {
228 return move_offs(move_dir) < 0 ? 0 : container->children->length;
229}
230
231/* Takes one child, sets it aside, wraps the rest of the children in a new
232 * container, switches the layout of the workspace, and drops the child back in.
233 * In other words, rejigger it. */
234static void workspace_rejigger(struct sway_container *ws,
235 struct sway_container *child, enum movement_direction move_dir) {
236 struct sway_container *original_parent = child->parent;
237 struct sway_container *new_parent =
238 container_split(ws, ws->layout);
239
240 container_remove_child(child);
241 for (int i = 0; i < ws->children->length; ++i) {
242 struct sway_container *_child = ws->children->items[i];
243 container_move_to(new_parent, _child);
244 }
245
246 int index = move_offs(move_dir);
247 container_insert_child(ws, child, index < 0 ? 0 : 1);
248 container_set_layout(ws,
249 move_dir == MOVE_LEFT || move_dir == MOVE_RIGHT ? L_HORIZ : L_VERT);
250
251 container_flatten(ws);
252 container_reap_empty_recursive(original_parent);
253 wl_signal_emit(&child->events.reparent, original_parent);
254 container_create_notify(new_parent);
255 arrange_windows(ws, -1, -1);
256}
257
258void container_move(struct sway_container *container,
259 enum movement_direction move_dir, int move_amt) {
260 if (!sway_assert(
261 container->type != C_CONTAINER || container->type != C_VIEW,
262 "Can only move containers and views")) {
263 return;
264 }
265 int offs = move_offs(move_dir);
266
267 struct sway_container *sibling = NULL;
268 struct sway_container *current = container;
269 struct sway_container *parent = current->parent;
270
271 if (parent != container_flatten(parent)) {
272 // Special case: we were the last one in this container, so flatten it
273 // and leave
274 update_debug_tree();
275 return;
276 }
277
278 while (!sibling) {
279 if (current->type == C_ROOT) {
280 return;
281 }
282
283 parent = current->parent;
284 wlr_log(L_DEBUG, "Visiting %p %s '%s'", current,
285 container_type_to_str(current->type), current->name);
286
287 int index = index_child(current);
288
289 switch (current->type) {
290 case C_OUTPUT: {
291 enum wlr_direction wlr_dir;
292 sway_dir_to_wlr(move_dir, &wlr_dir);
293 double ref_lx = current->x + current->width / 2;
294 double ref_ly = current->y + current->height / 2;
295 struct wlr_output *next = wlr_output_layout_adjacent_output(
296 root_container.sway_root->output_layout, wlr_dir,
297 current->sway_output->wlr_output, ref_lx, ref_ly);
298 if (!next) {
299 wlr_log(L_DEBUG, "Hit edge of output, nowhere else to go");
300 return;
301 }
302 struct sway_output *next_output = next->data;
303 current = next_output->swayc;
304 wlr_log(L_DEBUG, "Selected next output (%s)", current->name);
305 // Select workspace and get outta here
306 current = seat_get_focus_inactive(
307 config->handler_context.seat, current);
308 if (current->type != C_WORKSPACE) {
309 current = container_parent(current, C_WORKSPACE);
310 }
311 sibling = current;
312 break;
313 }
314 case C_WORKSPACE:
315 if (!is_parallel(current->layout, move_dir)) {
316 if (current->children->length > 2) {
317 wlr_log(L_DEBUG, "Rejiggering the workspace (%d kiddos)",
318 current->children->length);
319 workspace_rejigger(current, container, move_dir);
320 } else if (current->children->length == 2) {
321 wlr_log(L_DEBUG, "Changing workspace layout");
322 container_set_layout(current,
323 move_dir == MOVE_LEFT || move_dir == MOVE_RIGHT ?
324 L_HORIZ : L_VERT);
325 container_insert_child(current, container, offs < 0 ? 0 : 1);
326 arrange_windows(current, -1, -1);
327 }
328 return;
329 } else {
330 wlr_log(L_DEBUG, "Selecting output");
331 current = current->parent;
332 }
333 break;
334 case C_CONTAINER:
335 case C_VIEW:
336 if (is_parallel(parent->layout, move_dir)) {
337 if ((index == parent->children->length - 1 && offs > 0)
338 || (index == 0 && offs < 0)) {
339 if (current->parent == container->parent) {
340 wlr_log(L_DEBUG, "Hit limit, selecting parent");
341 current = current->parent;
342 } else {
343 wlr_log(L_DEBUG, "Hit limit, "
344 "promoting descendant to sibling");
345 // Special case
346 struct sway_container *old_parent = container->parent;
347 container_insert_child(current->parent, container,
348 index + (offs < 0 ? 0 : 1));
349 container->width = container->height = 0;
350 arrange_windows(current->parent, -1, -1);
351 arrange_windows(old_parent, -1, -1);
352 return;
353 }
354 } else {
355 sibling = parent->children->items[index + offs];
356 wlr_log(L_DEBUG, "Selecting sibling id:%zd", sibling->id);
357 }
358 } else {
359 wlr_log(L_DEBUG, "Moving up to find a parallel container");
360 current = current->parent;
361 }
362 break;
363 default:
364 sway_assert(0, "Not expecting to see container of type %s here",
365 container_type_to_str(current->type));
366 return;
367 }
368 }
369
370 // Part two: move stuff around
371 int index = index_child(container);
372 struct sway_container *old_parent = container->parent;
373
374 while (sibling) {
375 switch (sibling->type) {
376 case C_VIEW:
377 if (sibling->parent == container->parent) {
378 wlr_log(L_DEBUG, "Swapping siblings");
379 sibling->parent->children->items[index + offs] = container;
380 sibling->parent->children->items[index] = sibling;
381 arrange_windows(sibling->parent, -1, -1);
382 } else {
383 wlr_log(L_DEBUG, "Promoting to sibling of cousin");
384 container_insert_child(sibling->parent, container,
385 index_child(sibling) + (offs > 0 ? 0 : 1));
386 container->width = container->height = 0;
387 arrange_windows(sibling->parent, -1, -1);
388 arrange_windows(old_parent, -1, -1);
389 }
390 sibling = NULL;
391 break;
392 case C_WORKSPACE: // Note: only in the case of moving between outputs
393 case C_CONTAINER:
394 if (is_parallel(sibling->layout, move_dir)) {
395 int limit = container_limit(sibling, invert_movement(move_dir));
396 wlr_log(L_DEBUG, "limit: %d", limit);
397 wlr_log(L_DEBUG,
398 "Reparenting container (parallel) to index %d "
399 "(move dir: %d)", limit, move_dir);
400 container_insert_child(sibling, container, limit);
401 container->width = container->height = 0;
402 arrange_windows(sibling, -1, -1);
403 arrange_windows(old_parent, -1, -1);
404 sibling = NULL;
405 } else {
406 wlr_log(L_DEBUG, "Reparenting container (perpendicular)");
407 container_remove_child(container);
408 struct sway_container *focus_inactive = seat_get_focus_inactive(
409 config->handler_context.seat, sibling);
410 if (focus_inactive) {
411 while (focus_inactive->parent != sibling) {
412 focus_inactive = focus_inactive->parent;
413 }
414 wlr_log(L_DEBUG, "Focus inactive: id:%zd",
415 focus_inactive->id);
416 sibling = focus_inactive;
417 continue;
418 } else if (sibling->children->length) {
419 wlr_log(L_DEBUG, "No focus-inactive, adding arbitrarily");
420 container_add_sibling(sibling->children->items[0], container);
421 } else {
422 wlr_log(L_DEBUG, "No kiddos, adding container alone");
423 container_add_child(sibling, container);
424 }
425 container->width = container->height = 0;
426 arrange_windows(sibling, -1, -1);
427 arrange_windows(old_parent, -1, -1);
428 sibling = NULL;
429 }
430 break;
431 default:
432 sway_assert(0, "Not expecting to see container of type %s here",
433 container_type_to_str(sibling->type));
434 return;
435 }
436 }
437
438 if (old_parent) {
439 seat_set_focus(config->handler_context.seat, old_parent);
440 seat_set_focus(config->handler_context.seat, container);
441 }
442
443 struct sway_container *last_ws = old_parent;
444 struct sway_container *next_ws = container->parent;
445 if (last_ws && last_ws->type != C_WORKSPACE) {
446 last_ws = container_parent(last_ws, C_WORKSPACE);
447 }
448 if (next_ws && next_ws->type != C_WORKSPACE) {
449 next_ws = container_parent(next_ws, C_WORKSPACE);
450 }
451 if (last_ws && next_ws && last_ws != next_ws) {
452 ipc_event_workspace(last_ws, container, "focus");
453 }
454}
455
456enum sway_container_layout container_get_default_layout(
457 struct sway_container *con) {
458 if (con->type != C_OUTPUT) {
459 con = container_parent(con, C_OUTPUT);
460 }
461
462 if (!sway_assert(con != NULL,
463 "container_get_default_layout must be called on an attached"
464 " container below the root container")) {
465 return 0;
466 }
467
468 if (config->default_layout != L_NONE) {
469 return config->default_layout;
470 } else if (config->default_orientation != L_NONE) {
471 return config->default_orientation;
472 } else if (con->width >= con->height) {
473 return L_HORIZ;
474 } else {
475 return L_VERT;
476 }
477}
478
479static int sort_workspace_cmp_qsort(const void *_a, const void *_b) {
480 struct sway_container *a = *(void **)_a;
481 struct sway_container *b = *(void **)_b;
482 int retval = 0;
483
484 if (isdigit(a->name[0]) && isdigit(b->name[0])) {
485 int a_num = strtol(a->name, NULL, 10);
486 int b_num = strtol(b->name, NULL, 10);
487 retval = (a_num < b_num) ? -1 : (a_num > b_num);
488 } else if (isdigit(a->name[0])) {
489 retval = -1;
490 } else if (isdigit(b->name[0])) {
491 retval = 1;
492 }
493
494 return retval;
495}
496
497void container_sort_workspaces(struct sway_container *output) {
498 list_stable_sort(output->children, sort_workspace_cmp_qsort);
499}
500
501static void apply_horiz_layout(struct sway_container *container, const double x,
502 const double y, const double width,
503 const double height, const int start,
504 const int end);
505
506static void apply_vert_layout(struct sway_container *container, const double x,
507 const double y, const double width,
508 const double height, const int start,
509 const int end);
510
511void arrange_windows(struct sway_container *container,
512 double width, double height) {
513 if (config->reloading) {
514 return;
515 }
516 int i;
517 if (width == -1 || height == -1) {
518 width = container->width;
519 height = container->height;
520 }
521 // pixels are indivisible. if we don't round the pixels, then the view
522 // calculations will be off (e.g. 50.5 + 50.5 = 101, but in reality it's
523 // 50 + 50 = 100). doing it here cascades properly to all width/height/x/y.
524 width = floor(width);
525 height = floor(height);
526
527 wlr_log(L_DEBUG, "Arranging layout for %p %s %fx%f+%f,%f", container,
528 container->name, container->width, container->height, container->x,
529 container->y);
530
531 double x = 0, y = 0;
532 switch (container->type) {
533 case C_ROOT:
534 for (i = 0; i < container->children->length; ++i) {
535 struct sway_container *output = container->children->items[i];
536 const struct wlr_box *output_box = wlr_output_layout_get_box(
537 container->sway_root->output_layout,
538 output->sway_output->wlr_output);
539 output->x = output_box->x;
540 output->y = output_box->y;
541 output->width = output_box->width;
542 output->height = output_box->height;
543 wlr_log(L_DEBUG, "Arranging output '%s' at %f,%f",
544 output->name, output->x, output->y);
545 arrange_windows(output, output_box->width, output_box->height);
546 }
547 return;
548 case C_OUTPUT:
549 // arrange all workspaces:
550 for (i = 0; i < container->children->length; ++i) {
551 struct sway_container *child = container->children->items[i];
552 arrange_windows(child, -1, -1);
553 }
554 return;
555 case C_WORKSPACE:
556 {
557 struct sway_container *output =
558 container_parent(container, C_OUTPUT);
559 struct wlr_box *area = &output->sway_output->usable_area;
560 wlr_log(L_DEBUG, "Usable area for ws: %dx%d@%d,%d",
561 area->width, area->height, area->x, area->y);
562 container->width = width = area->width;
563 container->height = height = area->height;
564 container->x = x = area->x;
565 container->y = y = area->y;
566 wlr_log(L_DEBUG, "Arranging workspace '%s' at %f, %f",
567 container->name, container->x, container->y);
568 }
569 // children are properly handled below
570 break;
571 case C_VIEW:
572 {
573 container->width = width;
574 container->height = height;
575 view_configure(container->sway_view, container->x, container->y,
576 container->width, container->height);
577 wlr_log(L_DEBUG, "Set view to %.f x %.f @ %.f, %.f",
578 container->width, container->height,
579 container->x, container->y);
580 }
581 return;
582 default:
583 container->width = width;
584 container->height = height;
585 x = container->x;
586 y = container->y;
587 break;
588 }
589
590 switch (container->layout) {
591 case L_HORIZ:
592 apply_horiz_layout(container, x, y, width, height, 0,
593 container->children->length);
594 break;
595 case L_VERT:
596 apply_vert_layout(container, x, y, width, height, 0,
597 container->children->length);
598 break;
599 default:
600 wlr_log(L_DEBUG, "TODO: arrange layout type %d", container->layout);
601 apply_horiz_layout(container, x, y, width, height, 0,
602 container->children->length);
603 break;
604 }
605 container_damage_whole(container);
606 // TODO: Make this less shitty
607 update_debug_tree();
608}
609
610static void apply_horiz_layout(struct sway_container *container,
611 const double x, const double y,
612 const double width, const double height,
613 const int start, const int end) {
614 double scale = 0;
615 // Calculate total width
616 for (int i = start; i < end; ++i) {
617 double *old_width =
618 &((struct sway_container *)container->children->items[i])->width;
619 if (*old_width <= 0) {
620 if (end - start > 1) {
621 *old_width = width / (end - start - 1);
622 } else {
623 *old_width = width;
624 }
625 }
626 scale += *old_width;
627 }
628 scale = width / scale;
629
630 // Resize windows
631 double child_x = x;
632 if (scale > 0.1) {
633 wlr_log(L_DEBUG, "Arranging %p horizontally", container);
634 for (int i = start; i < end; ++i) {
635 struct sway_container *child = container->children->items[i];
636 wlr_log(L_DEBUG,
637 "Calculating arrangement for %p:%d (will scale %f by %f)",
638 child, child->type, width, scale);
639
640 if (child->type == C_VIEW) {
641 view_configure(child->sway_view, child_x, y, child->width,
642 child->height);
643 } else {
644 child->x = child_x;
645 child->y = y;
646 }
647
648 if (i == end - 1) {
649 double remaining_width = x + width - child_x;
650 arrange_windows(child, remaining_width, height);
651 } else {
652 arrange_windows(child, child->width * scale, height);
653 }
654 child_x += child->width;
655 }
656
657 // update focused view border last because it may
658 // depend on the title bar geometry of its siblings.
659 /* TODO WLR
660 if (focused && container->children->length > 1) {
661 update_container_border(focused);
662 }
663 */
664 }
665}
666
667void apply_vert_layout(struct sway_container *container,
668 const double x, const double y,
669 const double width, const double height, const int start,
670 const int end) {
671 int i;
672 double scale = 0;
673 // Calculate total height
674 for (i = start; i < end; ++i) {
675 double *old_height =
676 &((struct sway_container *)container->children->items[i])->height;
677 if (*old_height <= 0) {
678 if (end - start > 1) {
679 *old_height = height / (end - start - 1);
680 } else {
681 *old_height = height;
682 }
683 }
684 scale += *old_height;
685 }
686 scale = height / scale;
687
688 // Resize
689 double child_y = y;
690 if (scale > 0.1) {
691 wlr_log(L_DEBUG, "Arranging %p vertically", container);
692 for (i = start; i < end; ++i) {
693 struct sway_container *child = container->children->items[i];
694 wlr_log(L_DEBUG,
695 "Calculating arrangement for %p:%d (will scale %f by %f)",
696 child, child->type, height, scale);
697 if (child->type == C_VIEW) {
698 view_configure(child->sway_view, x, child_y, child->width,
699 child->height);
700 } else {
701 child->x = x;
702 child->y = child_y;
703 }
704
705 if (i == end - 1) {
706 double remaining_height = y + height - child_y;
707 arrange_windows(child, width, remaining_height);
708 } else {
709 arrange_windows(child, width, child->height * scale);
710 }
711 child_y += child->height;
712 }
713
714 // update focused view border last because it may
715 // depend on the title bar geometry of its siblings.
716 /* TODO WLR
717 if (focused && container->children->length > 1) {
718 update_container_border(focused);
719 }
720 */
721 }
722}
723
724/**
725 * Get swayc in the direction of newly entered output.
726 */
727static struct sway_container *get_swayc_in_output_direction(
728 struct sway_container *output, enum movement_direction dir,
729 struct sway_seat *seat) {
730 if (!output) {
731 return NULL;
732 }
733
734 struct sway_container *ws = seat_get_focus_inactive(seat, output);
735 if (ws->type != C_WORKSPACE) {
736 ws = container_parent(ws, C_WORKSPACE);
737 }
738
739 if (ws == NULL) {
740 wlr_log(L_ERROR, "got an output without a workspace");
741 return NULL;
742 }
743
744 if (ws->children->length > 0) {
745 switch (dir) {
746 case MOVE_LEFT:
747 // get most right child of new output
748 return ws->children->items[ws->children->length-1];
749 case MOVE_RIGHT:
750 // get most left child of new output
751 return ws->children->items[0];
752 case MOVE_UP:
753 case MOVE_DOWN: {
754 struct sway_container *focused =
755 seat_get_focus_inactive(seat, ws);
756 if (focused && focused->parent) {
757 struct sway_container *parent = focused->parent;
758 if (parent->layout == L_VERT) {
759 if (dir == MOVE_UP) {
760 // get child furthest down on new output
761 int idx = parent->children->length - 1;
762 return parent->children->items[idx];
763 } else if (dir == MOVE_DOWN) {
764 // get child furthest up on new output
765 return parent->children->items[0];
766 }
767 }
768 return focused;
769 }
770 break;
771 }
772 default:
773 break;
774 }
775 }
776
777 return ws;
778}
779
780static void get_layout_center_position(struct sway_container *container,
781 int *x, int *y) {
782 // FIXME view coords are inconsistently referred to in layout/output systems
783 if (container->type == C_OUTPUT) {
784 *x = container->x + container->width/2;
785 *y = container->y + container->height/2;
786 } else {
787 struct sway_container *output = container_parent(container, C_OUTPUT);
788 if (container->type == C_WORKSPACE) {
789 // Workspace coordinates are actually wrong/arbitrary, but should
790 // be same as output.
791 *x = output->x;
792 *y = output->y;
793 } else {
794 *x = output->x + container->x;
795 *y = output->y + container->y;
796 }
797 }
798}
799
800static struct sway_container *sway_output_from_wlr(struct wlr_output *output) {
801 if (output == NULL) {
802 return NULL;
803 }
804 for (int i = 0; i < root_container.children->length; ++i) {
805 struct sway_container *o = root_container.children->items[i];
806 if (o->type == C_OUTPUT && o->sway_output->wlr_output == output) {
807 return o;
808 }
809 }
810 return NULL;
811}
812
813struct sway_container *container_get_in_direction(
814 struct sway_container *container, struct sway_seat *seat,
815 enum movement_direction dir) {
816 if (dir == MOVE_CHILD) {
817 return seat_get_focus_inactive(seat, container);
818 }
819
820 struct sway_container *parent = container->parent;
821 if (dir == MOVE_PARENT) {
822 if (parent->type == C_OUTPUT) {
823 return NULL;
824 } else {
825 return parent;
826 }
827 }
828
829 // TODO WLR fullscreen
830 /*
831 if (container->type == C_VIEW && swayc_is_fullscreen(container)) {
832 wlr_log(L_DEBUG, "Moving from fullscreen view, skipping to output");
833 container = container_parent(container, C_OUTPUT);
834 get_layout_center_position(container, &abs_pos);
835 struct sway_container *output =
836 swayc_adjacent_output(container, dir, &abs_pos, true);
837 return get_swayc_in_output_direction(output, dir);
838 }
839 if (container->type == C_WORKSPACE && container->fullscreen) {
840 sway_log(L_DEBUG, "Moving to fullscreen view");
841 return container->fullscreen;
842 }
843 */
844
845 struct sway_container *wrap_candidate = NULL;
846 while (true) {
847 bool can_move = false;
848 int desired;
849 int idx = index_child(container);
850 if (parent->type == C_ROOT) {
851 enum wlr_direction wlr_dir = 0;
852 if (!sway_assert(sway_dir_to_wlr(dir, &wlr_dir),
853 "got invalid direction: %d", dir)) {
854 return NULL;
855 }
856 int lx, ly;
857 get_layout_center_position(container, &lx, &ly);
858 struct wlr_output_layout *layout =
859 root_container.sway_root->output_layout;
860 struct wlr_output *wlr_adjacent =
861 wlr_output_layout_adjacent_output(layout, wlr_dir,
862 container->sway_output->wlr_output, lx, ly);
863 struct sway_container *adjacent =
864 sway_output_from_wlr(wlr_adjacent);
865
866 if (!adjacent || adjacent == container) {
867 return wrap_candidate;
868 }
869 struct sway_container *next =
870 get_swayc_in_output_direction(adjacent, dir, seat);
871 if (next == NULL) {
872 return NULL;
873 }
874 if (next->children && next->children->length) {
875 // TODO consider floating children as well
876 return seat_get_focus_inactive_view(seat, next);
877 } else {
878 return next;
879 }
880 } else {
881 if (dir == MOVE_LEFT || dir == MOVE_RIGHT) {
882 if (parent->layout == L_HORIZ || parent->layout == L_TABBED) {
883 can_move = true;
884 desired = idx + (dir == MOVE_LEFT ? -1 : 1);
885 }
886 } else {
887 if (parent->layout == L_VERT || parent->layout == L_STACKED) {
888 can_move = true;
889 desired = idx + (dir == MOVE_UP ? -1 : 1);
890 }
891 }
892 }
893
894 if (can_move) {
895 // TODO handle floating
896 if (desired < 0 || desired >= parent->children->length) {
897 can_move = false;
898 int len = parent->children->length;
899 if (!wrap_candidate && len > 1) {
900 if (desired < 0) {
901 wrap_candidate = parent->children->items[len-1];
902 } else {
903 wrap_candidate = parent->children->items[0];
904 }
905 if (config->force_focus_wrapping) {
906 return wrap_candidate;
907 }
908 }
909 } else {
910 struct sway_container *desired_con = parent->children->items[desired];
911 wlr_log(L_DEBUG,
912 "cont %d-%p dir %i sibling %d: %p", idx,
913 container, dir, desired, desired_con);
914 struct sway_container *next = seat_get_focus_inactive_view(seat, desired_con);
915 return next;
916 }
917 }
918
919 if (!can_move) {
920 container = parent;
921 parent = parent->parent;
922 if (!parent) {
923 // wrapping is the last chance
924 return wrap_candidate;
925 }
926 }
927 }
928}
929
930struct sway_container *container_replace_child(struct sway_container *child,
931 struct sway_container *new_child) {
932 struct sway_container *parent = child->parent;
933 if (parent == NULL) {
934 return NULL;
935 }
936 int i = index_child(child);
937
938 // TODO floating
939 if (new_child->parent) {
940 container_remove_child(new_child);
941 }
942 parent->children->items[i] = new_child;
943 new_child->parent = parent;
944 child->parent = NULL;
945
946 // Set geometry for new child
947 new_child->x = child->x;
948 new_child->y = child->y;
949 new_child->width = child->width;
950 new_child->height = child->height;
951
952 // reset geometry for child
953 child->width = 0;
954 child->height = 0;
955
956 return parent;
957}
958
959struct sway_container *container_split(struct sway_container *child,
960 enum sway_container_layout layout) {
961 // TODO floating: cannot split a floating container
962 if (!sway_assert(child, "child cannot be null")) {
963 return NULL;
964 }
965 if (child->type == C_WORKSPACE && child->children->length == 0) {
966 // Special case: this just behaves like splitt
967 child->prev_layout = child->layout;
968 child->layout = layout;
969 arrange_windows(child, -1, -1);
970 return child;
971 }
972
973 struct sway_container *cont = container_create(C_CONTAINER);
974
975 wlr_log(L_DEBUG, "creating container %p around %p", cont, child);
976
977 cont->prev_layout = L_NONE;
978 cont->width = child->width;
979 cont->height = child->height;
980 cont->x = child->x;
981 cont->y = child->y;
982
983 if (child->type == C_WORKSPACE) {
984 struct sway_seat *seat = input_manager_get_default_seat(input_manager);
985 struct sway_container *workspace = child;
986 bool set_focus = (seat_get_focus(seat) == workspace);
987
988 while (workspace->children->length) {
989 struct sway_container *ws_child = workspace->children->items[0];
990 container_remove_child(ws_child);
991 container_add_child(cont, ws_child);
992 }
993
994 container_add_child(workspace, cont);
995 enum sway_container_layout old_layout = workspace->layout;
996 container_set_layout(workspace, layout);
997 cont->layout = old_layout;
998
999 if (set_focus) {
1000 seat_set_focus(seat, cont);
1001 }
1002 } else {
1003 cont->layout = layout;
1004 container_replace_child(child, cont);
1005 container_add_child(cont, child);
1006 }
1007
1008 return cont;
1009}
1010
1011void container_recursive_resize(struct sway_container *container,
1012 double amount, enum resize_edge edge) {
1013 bool layout_match = true;
1014 wlr_log(L_DEBUG, "Resizing %p with amount: %f", container, amount);
1015 if (edge == RESIZE_EDGE_LEFT || edge == RESIZE_EDGE_RIGHT) {
1016 container->width += amount;
1017 layout_match = container->layout == L_HORIZ;
1018 } else if (edge == RESIZE_EDGE_TOP || edge == RESIZE_EDGE_BOTTOM) {
1019 container->height += amount;
1020 layout_match = container->layout == L_VERT;
1021 }
1022 if (container->children) {
1023 for (int i = 0; i < container->children->length; i++) {
1024 struct sway_container *child = container->children->items[i];
1025 double amt = layout_match ?
1026 amount / container->children->length : amount;
1027 container_recursive_resize(child, amt, edge);
1028 }
1029 }
1030}
diff --git a/sway/tree/output.c b/sway/tree/output.c
new file mode 100644
index 00000000..6c7044a2
--- /dev/null
+++ b/sway/tree/output.c
@@ -0,0 +1,73 @@
1#define _POSIX_C_SOURCE 200809L
2#include <string.h>
3#include <strings.h>
4#include "sway/output.h"
5#include "sway/tree/output.h"
6#include "sway/tree/workspace.h"
7#include "log.h"
8
9struct sway_container *output_create(
10 struct sway_output *sway_output) {
11 struct wlr_box size;
12 wlr_output_effective_resolution(sway_output->wlr_output, &size.width,
13 &size.height);
14
15 const char *name = sway_output->wlr_output->name;
16 char identifier[128];
17 output_get_identifier(identifier, sizeof(identifier), sway_output);
18
19 struct output_config *oc = NULL, *all = NULL;
20 for (int i = 0; i < config->output_configs->length; ++i) {
21 struct output_config *cur = config->output_configs->items[i];
22
23 if (strcasecmp(name, cur->name) == 0 ||
24 strcasecmp(identifier, cur->name) == 0) {
25 wlr_log(L_DEBUG, "Matched output config for %s", name);
26 oc = cur;
27 }
28 if (strcasecmp("*", cur->name) == 0) {
29 wlr_log(L_DEBUG, "Matched wildcard output config for %s", name);
30 all = cur;
31 }
32
33 if (oc && all) {
34 break;
35 }
36 }
37 if (!oc) {
38 oc = all;
39 }
40
41 if (oc && !oc->enabled) {
42 return NULL;
43 }
44
45 struct sway_container *output = container_create(C_OUTPUT);
46 output->sway_output = sway_output;
47 output->name = strdup(name);
48 if (output->name == NULL) {
49 container_destroy(output);
50 return NULL;
51 }
52
53 apply_output_config(oc, output);
54 container_add_child(&root_container, output);
55 load_swaybars();
56
57 // Create workspace
58 char *ws_name = workspace_next_name(output->name);
59 wlr_log(L_DEBUG, "Creating default workspace %s", ws_name);
60 struct sway_container *ws = workspace_create(output, ws_name);
61 // Set each seat's focus if not already set
62 struct sway_seat *seat = NULL;
63 wl_list_for_each(seat, &input_manager->seats, link) {
64 if (!seat->has_focus) {
65 seat_set_focus(seat, ws);
66 }
67 }
68
69 free(ws_name);
70 container_create_notify(output);
71 return output;
72}
73
diff --git a/sway/tree/view.c b/sway/tree/view.c
new file mode 100644
index 00000000..99b44720
--- /dev/null
+++ b/sway/tree/view.c
@@ -0,0 +1,329 @@
1#include <stdlib.h>
2#include <wayland-server.h>
3#include <wlr/types/wlr_output_layout.h>
4#include "log.h"
5#include "sway/output.h"
6#include "sway/tree/container.h"
7#include "sway/tree/layout.h"
8#include "sway/tree/view.h"
9
10void view_init(struct sway_view *view, enum sway_view_type type,
11 const struct sway_view_impl *impl) {
12 view->type = type;
13 view->impl = impl;
14 wl_signal_init(&view->events.unmap);
15}
16
17void view_destroy(struct sway_view *view) {
18 if (view == NULL) {
19 return;
20 }
21
22 if (view->surface != NULL) {
23 view_unmap(view);
24 }
25
26 container_destroy(view->swayc);
27
28 if (view->impl->destroy) {
29 view->impl->destroy(view);
30 } else {
31 free(view);
32 }
33}
34
35const char *view_get_title(struct sway_view *view) {
36 if (view->impl->get_prop) {
37 return view->impl->get_prop(view, VIEW_PROP_TITLE);
38 }
39 return NULL;
40}
41
42const char *view_get_app_id(struct sway_view *view) {
43 if (view->impl->get_prop) {
44 return view->impl->get_prop(view, VIEW_PROP_APP_ID);
45 }
46 return NULL;
47}
48
49const char *view_get_class(struct sway_view *view) {
50 if (view->impl->get_prop) {
51 return view->impl->get_prop(view, VIEW_PROP_CLASS);
52 }
53 return NULL;
54}
55
56const char *view_get_instance(struct sway_view *view) {
57 if (view->impl->get_prop) {
58 return view->impl->get_prop(view, VIEW_PROP_INSTANCE);
59 }
60 return NULL;
61}
62
63void view_configure(struct sway_view *view, double ox, double oy, int width,
64 int height) {
65 if (view->impl->configure) {
66 view->impl->configure(view, ox, oy, width, height);
67 }
68}
69
70void view_set_activated(struct sway_view *view, bool activated) {
71 if (view->impl->set_activated) {
72 view->impl->set_activated(view, activated);
73 }
74}
75
76void view_close(struct sway_view *view) {
77 if (view->impl->close) {
78 view->impl->close(view);
79 }
80}
81
82void view_damage(struct sway_view *view, bool whole) {
83 for (int i = 0; i < root_container.children->length; ++i) {
84 struct sway_container *cont = root_container.children->items[i];
85 if (cont->type == C_OUTPUT) {
86 output_damage_view(cont->sway_output, view, whole);
87 }
88 }
89}
90
91static void view_get_layout_box(struct sway_view *view, struct wlr_box *box) {
92 struct sway_container *output = container_parent(view->swayc, C_OUTPUT);
93
94 box->x = output->x + view->swayc->x;
95 box->y = output->y + view->swayc->y;
96 box->width = view->width;
97 box->height = view->height;
98}
99
100void view_for_each_surface(struct sway_view *view,
101 wlr_surface_iterator_func_t iterator, void *user_data) {
102 if (view->impl->for_each_surface) {
103 view->impl->for_each_surface(view, iterator, user_data);
104 } else {
105 wlr_surface_for_each_surface(view->surface, iterator, user_data);
106 }
107}
108
109static void view_subsurface_create(struct sway_view *view,
110 struct wlr_subsurface *subsurface);
111
112static void view_init_subsurfaces(struct sway_view *view,
113 struct wlr_surface *surface);
114
115static void view_handle_surface_new_subsurface(struct wl_listener *listener,
116 void *data) {
117 struct sway_view *view =
118 wl_container_of(listener, view, surface_new_subsurface);
119 struct wlr_subsurface *subsurface = data;
120 view_subsurface_create(view, subsurface);
121}
122
123static void surface_send_enter_iterator(struct wlr_surface *surface,
124 int x, int y, void *data) {
125 struct wlr_output *wlr_output = data;
126 wlr_surface_send_enter(surface, wlr_output);
127}
128
129static void surface_send_leave_iterator(struct wlr_surface *surface,
130 int x, int y, void *data) {
131 struct wlr_output *wlr_output = data;
132 wlr_surface_send_leave(surface, wlr_output);
133}
134
135static void view_handle_container_reparent(struct wl_listener *listener,
136 void *data) {
137 struct sway_view *view =
138 wl_container_of(listener, view, container_reparent);
139 struct sway_container *old_parent = data;
140
141 struct sway_container *old_output = old_parent;
142 if (old_output != NULL && old_output->type != C_OUTPUT) {
143 old_output = container_parent(old_output, C_OUTPUT);
144 }
145
146 struct sway_container *new_output = view->swayc->parent;
147 if (new_output != NULL && new_output->type != C_OUTPUT) {
148 new_output = container_parent(new_output, C_OUTPUT);
149 }
150
151 if (old_output == new_output) {
152 return;
153 }
154
155 if (old_output != NULL) {
156 view_for_each_surface(view, surface_send_leave_iterator,
157 old_output->sway_output->wlr_output);
158 }
159 if (new_output != NULL) {
160 view_for_each_surface(view, surface_send_enter_iterator,
161 new_output->sway_output->wlr_output);
162 }
163}
164
165void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) {
166 if (!sway_assert(view->surface == NULL, "cannot map mapped view")) {
167 return;
168 }
169
170 struct sway_seat *seat = input_manager_current_seat(input_manager);
171 struct sway_container *focus = seat_get_focus_inactive(seat,
172 &root_container);
173 struct sway_container *cont = container_view_create(focus, view);
174
175 view->surface = wlr_surface;
176 view->swayc = cont;
177
178 view_init_subsurfaces(view, wlr_surface);
179 wl_signal_add(&wlr_surface->events.new_subsurface,
180 &view->surface_new_subsurface);
181 view->surface_new_subsurface.notify = view_handle_surface_new_subsurface;
182
183 wl_signal_add(&view->swayc->events.reparent, &view->container_reparent);
184 view->container_reparent.notify = view_handle_container_reparent;
185
186 arrange_windows(cont->parent, -1, -1);
187 input_manager_set_focus(input_manager, cont);
188
189 view_damage(view, true);
190 view_handle_container_reparent(&view->container_reparent, NULL);
191}
192
193void view_unmap(struct sway_view *view) {
194 if (!sway_assert(view->surface != NULL, "cannot unmap unmapped view")) {
195 return;
196 }
197
198 wl_signal_emit(&view->events.unmap, view);
199
200 view_damage(view, true);
201
202 wl_list_remove(&view->surface_new_subsurface.link);
203 wl_list_remove(&view->container_reparent.link);
204
205 struct sway_container *parent = container_destroy(view->swayc);
206
207 view->swayc = NULL;
208 view->surface = NULL;
209
210 arrange_windows(parent, -1, -1);
211}
212
213void view_update_position(struct sway_view *view, double ox, double oy) {
214 if (view->swayc->x == ox && view->swayc->y == oy) {
215 return;
216 }
217
218 view_damage(view, true);
219 view->swayc->x = ox;
220 view->swayc->y = oy;
221 view_damage(view, true);
222}
223
224void view_update_size(struct sway_view *view, int width, int height) {
225 if (view->width == width && view->height == height) {
226 return;
227 }
228
229 view_damage(view, true);
230 view->width = width;
231 view->height = height;
232 view_damage(view, true);
233}
234
235
236static void view_subsurface_create(struct sway_view *view,
237 struct wlr_subsurface *subsurface) {
238 struct sway_view_child *child = calloc(1, sizeof(struct sway_view_child));
239 if (child == NULL) {
240 wlr_log(L_ERROR, "Allocation failed");
241 return;
242 }
243 view_child_init(child, NULL, view, subsurface->surface);
244}
245
246static void view_child_handle_surface_commit(struct wl_listener *listener,
247 void *data) {
248 struct sway_view_child *child =
249 wl_container_of(listener, child, surface_commit);
250 // TODO: only accumulate damage from the child
251 view_damage(child->view, false);
252}
253
254static void view_child_handle_surface_new_subsurface(
255 struct wl_listener *listener, void *data) {
256 struct sway_view_child *child =
257 wl_container_of(listener, child, surface_new_subsurface);
258 struct wlr_subsurface *subsurface = data;
259 view_subsurface_create(child->view, subsurface);
260}
261
262static void view_child_handle_surface_destroy(struct wl_listener *listener,
263 void *data) {
264 struct sway_view_child *child =
265 wl_container_of(listener, child, surface_destroy);
266 view_child_destroy(child);
267}
268
269static void view_child_handle_view_unmap(struct wl_listener *listener,
270 void *data) {
271 struct sway_view_child *child =
272 wl_container_of(listener, child, view_unmap);
273 view_child_destroy(child);
274}
275
276static void view_init_subsurfaces(struct sway_view *view,
277 struct wlr_surface *surface) {
278 struct wlr_subsurface *subsurface;
279 wl_list_for_each(subsurface, &surface->subsurface_list, parent_link) {
280 view_subsurface_create(view, subsurface);
281 }
282}
283
284void view_child_init(struct sway_view_child *child,
285 const struct sway_view_child_impl *impl, struct sway_view *view,
286 struct wlr_surface *surface) {
287 child->impl = impl;
288 child->view = view;
289 child->surface = surface;
290
291 wl_signal_add(&surface->events.commit, &child->surface_commit);
292 child->surface_commit.notify = view_child_handle_surface_commit;
293 wl_signal_add(&surface->events.new_subsurface,
294 &child->surface_new_subsurface);
295 child->surface_new_subsurface.notify =
296 view_child_handle_surface_new_subsurface;
297 wl_signal_add(&surface->events.destroy, &child->surface_destroy);
298 child->surface_destroy.notify = view_child_handle_surface_destroy;
299 wl_signal_add(&view->events.unmap, &child->view_unmap);
300 child->view_unmap.notify = view_child_handle_view_unmap;
301
302 struct sway_container *output = child->view->swayc->parent;
303 if (output != NULL) {
304 if (output->type != C_OUTPUT) {
305 output = container_parent(output, C_OUTPUT);
306 }
307 wlr_surface_send_enter(child->surface, output->sway_output->wlr_output);
308 }
309
310 view_init_subsurfaces(child->view, surface);
311
312 // TODO: only damage the whole child
313 view_damage(child->view, true);
314}
315
316void view_child_destroy(struct sway_view_child *child) {
317 // TODO: only damage the whole child
318 view_damage(child->view, true);
319
320 wl_list_remove(&child->surface_commit.link);
321 wl_list_remove(&child->surface_destroy.link);
322 wl_list_remove(&child->view_unmap.link);
323
324 if (child->impl && child->impl->destroy) {
325 child->impl->destroy(child);
326 } else {
327 free(child);
328 }
329}
diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c
new file mode 100644
index 00000000..316f01e4
--- /dev/null
+++ b/sway/tree/workspace.c
@@ -0,0 +1,401 @@
1#define _XOPEN_SOURCE 500
2#include <ctype.h>
3#include <limits.h>
4#include <stdbool.h>
5#include <stdlib.h>
6#include <stdio.h>
7#include <strings.h>
8#include "stringop.h"
9#include "sway/input/input-manager.h"
10#include "sway/input/seat.h"
11#include "sway/ipc-server.h"
12#include "sway/tree/container.h"
13#include "sway/tree/workspace.h"
14#include "log.h"
15#include "util.h"
16
17static struct sway_container *get_workspace_initial_output(const char *name) {
18 struct sway_container *parent;
19 // Search for workspace<->output pair
20 int e = config->workspace_outputs->length;
21 for (int i = 0; i < config->workspace_outputs->length; ++i) {
22 struct workspace_output *wso = config->workspace_outputs->items[i];
23 if (strcasecmp(wso->workspace, name) == 0) {
24 // Find output to use if it exists
25 e = root_container.children->length;
26 for (i = 0; i < e; ++i) {
27 parent = root_container.children->items[i];
28 if (strcmp(parent->name, wso->output) == 0) {
29 return parent;
30 }
31 }
32 break;
33 }
34 }
35 // Otherwise put it on the focused output
36 struct sway_seat *seat = input_manager_current_seat(input_manager);
37 struct sway_container *focus =
38 seat_get_focus_inactive(seat, &root_container);
39 parent = focus;
40 parent = container_parent(parent, C_OUTPUT);
41 return parent;
42}
43
44struct sway_container *workspace_create(struct sway_container *output,
45 const char *name) {
46 if (output == NULL) {
47 output = get_workspace_initial_output(name);
48 }
49
50 wlr_log(L_DEBUG, "Added workspace %s for output %s", name, output->name);
51 struct sway_container *workspace = container_create(C_WORKSPACE);
52
53 workspace->x = output->x;
54 workspace->y = output->y;
55 workspace->width = output->width;
56 workspace->height = output->height;
57 workspace->name = !name ? NULL : strdup(name);
58 workspace->prev_layout = L_NONE;
59 workspace->layout = container_get_default_layout(output);
60 workspace->workspace_layout = workspace->layout;
61
62 container_add_child(output, workspace);
63 container_sort_workspaces(output);
64 container_create_notify(workspace);
65
66 return workspace;
67}
68
69char *prev_workspace_name = NULL;
70struct workspace_by_number_data {
71 int len;
72 const char *cset;
73 const char *name;
74};
75
76void next_name_map(struct sway_container *ws, void *data) {
77 int *count = data;
78 ++count;
79}
80
81static bool workspace_valid_on_output(const char *output_name,
82 const char *ws_name) {
83 int i;
84 for (i = 0; i < config->workspace_outputs->length; ++i) {
85 struct workspace_output *wso = config->workspace_outputs->items[i];
86 if (strcasecmp(wso->workspace, ws_name) == 0) {
87 if (strcasecmp(wso->output, output_name) != 0) {
88 return false;
89 }
90 }
91 }
92
93 return true;
94}
95
96char *workspace_next_name(const char *output_name) {
97 wlr_log(L_DEBUG, "Workspace: Generating new workspace name for output %s",
98 output_name);
99 int l = 1;
100 // Scan all workspace bindings to find the next available workspace name,
101 // if none are found/available then default to a number
102 struct sway_mode *mode = config->current_mode;
103
104 // TODO: iterate over keycode bindings too
105 int order = INT_MAX;
106 char *target = NULL;
107 for (int i = 0; i < mode->keysym_bindings->length; ++i) {
108 struct sway_binding *binding = mode->keysym_bindings->items[i];
109 char *cmdlist = strdup(binding->command);
110 char *dup = cmdlist;
111 char *name = NULL;
112
113 // workspace n
114 char *cmd = argsep(&cmdlist, " ");
115 if (cmdlist) {
116 name = argsep(&cmdlist, ",;");
117 }
118
119 if (strcmp("workspace", cmd) == 0 && name) {
120 wlr_log(L_DEBUG, "Got valid workspace command for target: '%s'", name);
121 char *_target = strdup(name);
122 strip_quotes(_target);
123 while (isspace(*_target)) {
124 memmove(_target, _target+1, strlen(_target+1));
125 }
126
127 // Make sure that the command references an actual workspace
128 // not a command about workspaces
129 if (strcmp(_target, "next") == 0 ||
130 strcmp(_target, "prev") == 0 ||
131 strcmp(_target, "next_on_output") == 0 ||
132 strcmp(_target, "prev_on_output") == 0 ||
133 strcmp(_target, "number") == 0 ||
134 strcmp(_target, "back_and_forth") == 0 ||
135 strcmp(_target, "current") == 0)
136 {
137 free(_target);
138 free(dup);
139 continue;
140 }
141
142 // If the command is workspace number <name>, isolate the name
143 if (strncmp(_target, "number ", strlen("number ")) == 0) {
144 size_t length = strlen(_target) - strlen("number ") + 1;
145 char *temp = malloc(length);
146 strncpy(temp, _target + strlen("number "), length - 1);
147 temp[length - 1] = '\0';
148 free(_target);
149 _target = temp;
150 wlr_log(L_DEBUG, "Isolated name from workspace number: '%s'", _target);
151
152 // Make sure the workspace number doesn't already exist
153 if (workspace_by_number(_target)) {
154 free(_target);
155 free(dup);
156 continue;
157 }
158 }
159
160 // Make sure that the workspace doesn't already exist
161 if (workspace_by_name(_target)) {
162 free(_target);
163 free(dup);
164 continue;
165 }
166
167 // make sure that the workspace can appear on the given
168 // output
169 if (!workspace_valid_on_output(output_name, _target)) {
170 free(_target);
171 free(dup);
172 continue;
173 }
174
175 if (binding->order < order) {
176 order = binding->order;
177 free(target);
178 target = _target;
179 wlr_log(L_DEBUG, "Workspace: Found free name %s", _target);
180 }
181 }
182 free(dup);
183 }
184 if (target != NULL) {
185 return target;
186 }
187 // As a fall back, get the current number of active workspaces
188 // and return that + 1 for the next workspace's name
189 int ws_num = root_container.children->length;
190 if (ws_num >= 10) {
191 l = 2;
192 } else if (ws_num >= 100) {
193 l = 3;
194 }
195 char *name = malloc(l + 1);
196 if (!name) {
197 wlr_log(L_ERROR, "Could not allocate workspace name");
198 return NULL;
199 }
200 sprintf(name, "%d", ws_num++);
201 return name;
202}
203
204static bool _workspace_by_number(struct sway_container *view, void *data) {
205 if (view->type != C_WORKSPACE) {
206 return false;
207 }
208 struct workspace_by_number_data *wbnd = data;
209 int a = strspn(view->name, wbnd->cset);
210 return a == wbnd->len && strncmp(view->name, wbnd->name, a) == 0;
211}
212
213struct sway_container *workspace_by_number(const char* name) {
214 struct workspace_by_number_data wbnd = {0, "1234567890", name};
215 wbnd.len = strspn(name, wbnd.cset);
216 if (wbnd.len <= 0) {
217 return NULL;
218 }
219 return container_find(&root_container,
220 _workspace_by_number, (void *) &wbnd);
221}
222
223static bool _workspace_by_name(struct sway_container *view, void *data) {
224 return (view->type == C_WORKSPACE) &&
225 (strcasecmp(view->name, (char *) data) == 0);
226}
227
228struct sway_container *workspace_by_name(const char *name) {
229 struct sway_seat *seat = input_manager_current_seat(input_manager);
230 struct sway_container *current_workspace = NULL, *current_output = NULL;
231 struct sway_container *focus = seat_get_focus(seat);
232 if (focus) {
233 current_workspace = container_parent(focus, C_WORKSPACE);
234 current_output = container_parent(focus, C_OUTPUT);
235 }
236 if (strcmp(name, "prev") == 0) {
237 return workspace_prev(current_workspace);
238 } else if (strcmp(name, "prev_on_output") == 0) {
239 return workspace_output_prev(current_output);
240 } else if (strcmp(name, "next") == 0) {
241 return workspace_next(current_workspace);
242 } else if (strcmp(name, "next_on_output") == 0) {
243 return workspace_output_next(current_output);
244 } else if (strcmp(name, "current") == 0) {
245 return current_workspace;
246 } else {
247 return container_find(&root_container, _workspace_by_name,
248 (void *)name);
249 }
250}
251
252/**
253 * Get the previous or next workspace on the specified output. Wraps around at
254 * the end and beginning. If next is false, the previous workspace is returned,
255 * otherwise the next one is returned.
256 */
257struct sway_container *workspace_output_prev_next_impl(
258 struct sway_container *output, bool next) {
259 if (!sway_assert(output->type == C_OUTPUT,
260 "Argument must be an output, is %d", output->type)) {
261 return NULL;
262 }
263
264 struct sway_seat *seat = input_manager_current_seat(input_manager);
265 struct sway_container *focus = seat_get_focus_inactive(seat, output);
266 struct sway_container *workspace = (focus->type == C_WORKSPACE ?
267 focus :
268 container_parent(focus, C_WORKSPACE));
269
270 int i;
271 for (i = 0; i < output->children->length; i++) {
272 if (output->children->items[i] == workspace) {
273 return output->children->items[
274 wrap(i + (next ? 1 : -1), output->children->length)];
275 }
276 }
277
278 // Doesn't happen, at worst the for loop returns the previously active
279 // workspace
280 return NULL;
281}
282
283/**
284 * Get the previous or next workspace. If the first/last workspace on an output
285 * is active, proceed to the previous/next output's previous/next workspace. If
286 * next is false, the previous workspace is returned, otherwise the next one is
287 * returned.
288 */
289struct sway_container *workspace_prev_next_impl(
290 struct sway_container *workspace, bool next) {
291 if (!sway_assert(workspace->type == C_WORKSPACE,
292 "Argument must be a workspace, is %d", workspace->type)) {
293 return NULL;
294 }
295
296 struct sway_container *current_output = workspace->parent;
297 int offset = next ? 1 : -1;
298 int start = next ? 0 : 1;
299 int end;
300 if (next) {
301 end = current_output->children->length - 1;
302 } else {
303 end = current_output->children->length;
304 }
305 int i;
306 for (i = start; i < end; i++) {
307 if (current_output->children->items[i] == workspace) {
308 return current_output->children->items[i + offset];
309 }
310 }
311
312 // Given workspace is the first/last on the output, jump to the
313 // previous/next output
314 int num_outputs = root_container.children->length;
315 for (i = 0; i < num_outputs; i++) {
316 if (root_container.children->items[i] == current_output) {
317 struct sway_container *next_output = root_container.children->items[
318 wrap(i + offset, num_outputs)];
319 return workspace_output_prev_next_impl(next_output, next);
320 }
321 }
322
323 // Doesn't happen, at worst the for loop returns the previously active
324 // workspace on the active output
325 return NULL;
326}
327
328struct sway_container *workspace_output_next(struct sway_container *current) {
329 return workspace_output_prev_next_impl(current, true);
330}
331
332struct sway_container *workspace_next(struct sway_container *current) {
333 return workspace_prev_next_impl(current, true);
334}
335
336struct sway_container *workspace_output_prev(struct sway_container *current) {
337 return workspace_output_prev_next_impl(current, false);
338}
339
340struct sway_container *workspace_prev(struct sway_container *current) {
341 return workspace_prev_next_impl(current, false);
342}
343
344bool workspace_switch(struct sway_container *workspace) {
345 if (!workspace) {
346 return false;
347 }
348 struct sway_seat *seat = input_manager_current_seat(input_manager);
349 struct sway_container *focus =
350 seat_get_focus_inactive(seat, &root_container);
351 if (!seat || !focus) {
352 return false;
353 }
354 struct sway_container *active_ws = focus;
355 if (active_ws->type != C_WORKSPACE) {
356 active_ws = container_parent(focus, C_WORKSPACE);
357 }
358
359 if (config->auto_back_and_forth
360 && active_ws == workspace
361 && prev_workspace_name) {
362 struct sway_container *new_ws = workspace_by_name(prev_workspace_name);
363 workspace = new_ws ?
364 new_ws :
365 workspace_create(NULL, prev_workspace_name);
366 }
367
368 if (!prev_workspace_name || (strcmp(prev_workspace_name, active_ws->name)
369 && active_ws != workspace)) {
370 free(prev_workspace_name);
371 prev_workspace_name = malloc(strlen(active_ws->name) + 1);
372 if (!prev_workspace_name) {
373 wlr_log(L_ERROR, "Unable to allocate previous workspace name");
374 return false;
375 }
376 strcpy(prev_workspace_name, active_ws->name);
377 }
378
379 // TODO: Deal with sticky containers
380
381 wlr_log(L_DEBUG, "Switching to workspace %p:%s",
382 workspace, workspace->name);
383 struct sway_container *next = seat_get_focus_inactive(seat, workspace);
384 if (next == NULL) {
385 next = workspace;
386 }
387 seat_set_focus(seat, next);
388 struct sway_container *output = container_parent(workspace, C_OUTPUT);
389 arrange_windows(output, -1, -1);
390 return true;
391}
392
393bool workspace_is_visible(struct sway_container *ws) {
394 struct sway_container *output = container_parent(ws, C_OUTPUT);
395 struct sway_seat *seat = input_manager_current_seat(input_manager);
396 struct sway_container *focus = seat_get_focus_inactive(seat, output);
397 if (focus->type != C_WORKSPACE) {
398 focus = container_parent(focus, C_WORKSPACE);
399 }
400 return focus == ws;
401}
diff --git a/sway/workspace.c b/sway/workspace.c
deleted file mode 100644
index e0367190..00000000
--- a/sway/workspace.c
+++ /dev/null
@@ -1,374 +0,0 @@
1#define _XOPEN_SOURCE 500
2#include <stdlib.h>
3#include <stdbool.h>
4#include <limits.h>
5#include <ctype.h>
6#include <wlc/wlc.h>
7#include <string.h>
8#include <strings.h>
9#include <sys/types.h>
10#include "sway/ipc-server.h"
11#include "sway/extensions.h"
12#include "sway/workspace.h"
13#include "sway/layout.h"
14#include "sway/container.h"
15#include "sway/handlers.h"
16#include "sway/config.h"
17#include "sway/focus.h"
18#include "stringop.h"
19#include "util.h"
20#include "list.h"
21#include "log.h"
22#include "ipc.h"
23
24char *prev_workspace_name = NULL;
25struct workspace_by_number_data {
26 int len;
27 const char *cset;
28 const char *name;
29};
30
31static bool workspace_valid_on_output(const char *output_name, const char *ws_name) {
32 int i;
33 for (i = 0; i < config->workspace_outputs->length; ++i) {
34 struct workspace_output *wso = config->workspace_outputs->items[i];
35 if (strcasecmp(wso->workspace, ws_name) == 0) {
36 if (strcasecmp(wso->output, output_name) != 0) {
37 return false;
38 }
39 }
40 }
41
42 return true;
43}
44
45char *workspace_next_name(const char *output_name) {
46 sway_log(L_DEBUG, "Workspace: Generating new workspace name for output %s", output_name);
47 int i;
48 int l = 1;
49 // Scan all workspace bindings to find the next available workspace name,
50 // if none are found/available then default to a number
51 struct sway_mode *mode = config->current_mode;
52
53 int order = INT_MAX;
54 char *target = NULL;
55 for (i = 0; i < mode->bindings->length; ++i) {
56 struct sway_binding *binding = mode->bindings->items[i];
57 char *cmdlist = strdup(binding->command);
58 char *dup = cmdlist;
59 char *name = NULL;
60
61 // workspace n
62 char *cmd = argsep(&cmdlist, " ");
63 if (cmdlist) {
64 name = argsep(&cmdlist, ",;");
65 }
66
67 if (strcmp("workspace", cmd) == 0 && name) {
68 sway_log(L_DEBUG, "Got valid workspace command for target: '%s'", name);
69 char *_target = strdup(name);
70 strip_quotes(_target);
71 while (isspace(*_target))
72 _target++;
73
74 // Make sure that the command references an actual workspace
75 // not a command about workspaces
76 if (strcmp(_target, "next") == 0 ||
77 strcmp(_target, "prev") == 0 ||
78 strcmp(_target, "next_on_output") == 0 ||
79 strcmp(_target, "prev_on_output") == 0 ||
80 strcmp(_target, "number") == 0 ||
81 strcmp(_target, "back_and_forth") == 0 ||
82 strcmp(_target, "current") == 0)
83 {
84 free(_target);
85 free(dup);
86 continue;
87 }
88
89 // Make sure that the workspace doesn't already exist
90 if (workspace_by_name(_target)) {
91 free(_target);
92 free(dup);
93 continue;
94 }
95
96 // make sure that the workspace can appear on the given
97 // output
98 if (!workspace_valid_on_output(output_name, _target)) {
99 free(_target);
100 free(dup);
101 continue;
102 }
103
104 if (binding->order < order) {
105 order = binding->order;
106 free(target);
107 target = _target;
108 sway_log(L_DEBUG, "Workspace: Found free name %s", _target);
109 }
110 }
111 free(dup);
112 }
113 if (target != NULL) {
114 return target;
115 }
116 // As a fall back, get the current number of active workspaces
117 // and return that + 1 for the next workspace's name
118 int ws_num = root_container.children->length;
119 if (ws_num >= 10) {
120 l = 2;
121 } else if (ws_num >= 100) {
122 l = 3;
123 }
124 char *name = malloc(l + 1);
125 if (!name) {
126 sway_log(L_ERROR, "Could not allocate workspace name");
127 return NULL;
128 }
129 sprintf(name, "%d", ws_num++);
130 return name;
131}
132
133swayc_t *workspace_create(const char* name) {
134 swayc_t *parent;
135 // Search for workspace<->output pair
136 int i, e = config->workspace_outputs->length;
137 for (i = 0; i < e; ++i) {
138 struct workspace_output *wso = config->workspace_outputs->items[i];
139 if (strcasecmp(wso->workspace, name) == 0)
140 {
141 // Find output to use if it exists
142 e = root_container.children->length;
143 for (i = 0; i < e; ++i) {
144 parent = root_container.children->items[i];
145 if (strcmp(parent->name, wso->output) == 0) {
146 return new_workspace(parent, name);
147 }
148 }
149 break;
150 }
151 }
152 // Otherwise create a new one
153 parent = get_focused_container(&root_container);
154 parent = swayc_parent_by_type(parent, C_OUTPUT);
155 return new_workspace(parent, name);
156}
157
158static bool _workspace_by_name(swayc_t *view, void *data) {
159 return (view->type == C_WORKSPACE) &&
160 (strcasecmp(view->name, (char *) data) == 0);
161}
162
163swayc_t *workspace_by_name(const char* name) {
164 if (strcmp(name, "prev") == 0) {
165 return workspace_prev();
166 }
167 else if (strcmp(name, "prev_on_output") == 0) {
168 return workspace_output_prev();
169 }
170 else if (strcmp(name, "next") == 0) {
171 return workspace_next();
172 }
173 else if (strcmp(name, "next_on_output") == 0) {
174 return workspace_output_next();
175 }
176 else if (strcmp(name, "current") == 0) {
177 return swayc_active_workspace();
178 }
179 else {
180 return swayc_by_test(&root_container, _workspace_by_name, (void *) name);
181 }
182}
183
184static bool _workspace_by_number(swayc_t *view, void *data) {
185 if (view->type != C_WORKSPACE) {
186 return false;
187 }
188 struct workspace_by_number_data *wbnd = data;
189 int a = strspn(view->name, wbnd->cset);
190 return a == wbnd->len && strncmp(view->name, wbnd->name, a) == 0;
191}
192swayc_t *workspace_by_number(const char* name) {
193 struct workspace_by_number_data wbnd = {0, "1234567890", name};
194 wbnd.len = strspn(name, wbnd.cset);
195 if (wbnd.len <= 0) {
196 return NULL;
197 }
198 return swayc_by_test(&root_container, _workspace_by_number, (void *) &wbnd);
199}
200
201/**
202 * Get the previous or next workspace on the specified output.
203 * Wraps around at the end and beginning.
204 * If next is false, the previous workspace is returned, otherwise the next one is returned.
205 */
206swayc_t *workspace_output_prev_next_impl(swayc_t *output, bool next) {
207 if (!sway_assert(output->type == C_OUTPUT, "Argument must be an output, is %d", output->type)) {
208 return NULL;
209 }
210
211 int i;
212 for (i = 0; i < output->children->length; i++) {
213 if (output->children->items[i] == output->focused) {
214 return output->children->items[wrap(i + (next ? 1 : -1), output->children->length)];
215 }
216 }
217
218 // Doesn't happen, at worst the for loop returns the previously active workspace
219 return NULL;
220}
221
222/**
223 * Get the previous or next workspace. If the first/last workspace on an output is active,
224 * proceed to the previous/next output's previous/next workspace.
225 * If next is false, the previous workspace is returned, otherwise the next one is returned.
226 */
227swayc_t *workspace_prev_next_impl(swayc_t *workspace, bool next) {
228 if (!sway_assert(workspace->type == C_WORKSPACE, "Argument must be a workspace, is %d", workspace->type)) {
229 return NULL;
230 }
231
232 swayc_t *current_output = workspace->parent;
233 int offset = next ? 1 : -1;
234 int start = next ? 0 : 1;
235 int end = next ? (current_output->children->length) - 1 : current_output->children->length;
236 int i;
237 for (i = start; i < end; i++) {
238 if (current_output->children->items[i] == workspace) {
239 return current_output->children->items[i + offset];
240 }
241 }
242
243 // Given workspace is the first/last on the output, jump to the previous/next output
244 int num_outputs = root_container.children->length;
245 for (i = 0; i < num_outputs; i++) {
246 if (root_container.children->items[i] == current_output) {
247 swayc_t *next_output = root_container.children->items[wrap(i + offset, num_outputs)];
248 return workspace_output_prev_next_impl(next_output, next);
249 }
250 }
251
252 // Doesn't happen, at worst the for loop returns the previously active workspace on the active output
253 return NULL;
254}
255
256swayc_t *workspace_output_next() {
257 return workspace_output_prev_next_impl(swayc_active_output(), true);
258}
259
260swayc_t *workspace_next() {
261 return workspace_prev_next_impl(swayc_active_workspace(), true);
262}
263
264swayc_t *workspace_output_prev() {
265 return workspace_output_prev_next_impl(swayc_active_output(), false);
266}
267
268swayc_t *workspace_prev() {
269 return workspace_prev_next_impl(swayc_active_workspace(), false);
270}
271
272bool workspace_switch(swayc_t *workspace) {
273 if (!workspace) {
274 return false;
275 }
276 swayc_t *active_ws = swayc_active_workspace();
277 if (config->auto_back_and_forth && active_ws == workspace && prev_workspace_name) {
278 swayc_t *new_ws = workspace_by_name(prev_workspace_name);
279 workspace = new_ws ? new_ws : workspace_create(prev_workspace_name);
280 }
281
282 if (!prev_workspace_name
283 || (strcmp(prev_workspace_name, active_ws->name)
284 && active_ws != workspace)) {
285 free(prev_workspace_name);
286 prev_workspace_name = malloc(strlen(active_ws->name) + 1);
287 if (!prev_workspace_name) {
288 sway_log(L_ERROR, "Unable to allocate previous workspace name");
289 return false;
290 }
291 strcpy(prev_workspace_name, active_ws->name);
292 }
293
294 // move sticky containers
295 if (swayc_parent_by_type(active_ws, C_OUTPUT) == swayc_parent_by_type(workspace, C_OUTPUT)) {
296 // don't change list while traversing it, use intermediate list instead
297 list_t *stickies = create_list();
298 for (int i = 0; i < active_ws->floating->length; i++) {
299 swayc_t *cont = active_ws->floating->items[i];
300 if (cont->sticky) {
301 list_add(stickies, cont);
302 }
303 }
304 for (int i = 0; i < stickies->length; i++) {
305 swayc_t *cont = stickies->items[i];
306 sway_log(L_DEBUG, "Moving sticky container %p to %p:%s",
307 cont, workspace, workspace->name);
308 swayc_t *parent = remove_child(cont);
309 add_floating(workspace, cont);
310 // Destroy old container if we need to
311 destroy_container(parent);
312 }
313 list_free(stickies);
314 }
315 sway_log(L_DEBUG, "Switching to workspace %p:%s", workspace, workspace->name);
316 if (!set_focused_container(get_focused_view(workspace))) {
317 return false;
318 }
319 swayc_t *output = swayc_parent_by_type(workspace, C_OUTPUT);
320 arrange_backgrounds();
321 arrange_windows(output, -1, -1);
322 return true;
323}
324
325swayc_t *workspace_for_pid(pid_t pid) {
326 int i;
327 swayc_t *ws = NULL;
328 struct pid_workspace *pw = NULL;
329
330 sway_log(L_DEBUG, "looking for workspace for pid %d", pid);
331
332 // leaving this here as it's useful for debugging
333 // sway_log(L_DEBUG, "all pid_workspaces");
334 // for (int k = 0; k < config->pid_workspaces->length; k++) {
335 // pw = config->pid_workspaces->items[k];
336 // sway_log(L_DEBUG, "pid %d workspace %s time_added %li", *pw->pid, pw->workspace, *pw->time_added);
337 // }
338
339 do {
340 for (i = 0; i < config->pid_workspaces->length; i++) {
341 pw = config->pid_workspaces->items[i];
342 pid_t *pw_pid = pw->pid;
343
344 if (pid == *pw_pid) {
345 sway_log(L_DEBUG, "found pid_workspace for pid %d, workspace %s", pid, pw->workspace);
346 break; // out of for loop
347 }
348
349 pw = NULL;
350 }
351
352 if (pw) {
353 break; // out of do-while loop
354 }
355
356 pid = get_parent_pid(pid);
357 // no sense in looking for matches for pid 0.
358 // also, if pid == getpid(), that is the compositor's
359 // pid, which definitely isn't helpful
360 } while (pid > 0 && pid != getpid());
361
362 if (pw) {
363 ws = workspace_by_name(pw->workspace);
364
365 if (!ws) {
366 sway_log(L_DEBUG, "Creating workspace %s for pid %d because it disappeared", pw->workspace, pid);
367 ws = workspace_create(pw->workspace);
368 }
369
370 list_del(config->pid_workspaces, i);
371 }
372
373 return ws;
374}