summaryrefslogtreecommitdiffstats
path: root/sway/tree/workspace.c
diff options
context:
space:
mode:
Diffstat (limited to 'sway/tree/workspace.c')
-rw-r--r--sway/tree/workspace.c401
1 files changed, 401 insertions, 0 deletions
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}