1#include <stdio.h>
2#include <string.h>
3#include <stdlib.h>
4#include <inttypes.h>
5#include <limits.h>
6
7#include <wayland-server.h>
8#include <swc.h>
9#include <xkbcommon/xkbcommon.h>
10
11#include "howl.h"
12#include "types.h"
13#include "ipc.h"
14
15/* ugly mess but better than nothing */
16#define ARG_CLIENT(x) \
17 uint32_t id = wm.cur ? wm.cur->id : UINT32_MAX; \
18 if (x) id = fn_uint(x); \
19 struct client *tmp, *c = wm.cur; \
20 wl_list_for_each(tmp, &wm.clients, link) \
21 if (tmp->id == id) c = tmp; \
22 if (!c) return (status){ false, "Bad window\n" }
23
24extern struct wm wm;
25extern struct config config;
26
27struct bind {
28 uint32_t mod, key;
29};
30
31static int
32fn_int(char *s) {
33 return atoi(s);
34}
35
36static int
37fn_uint(char *s) {
38 return strtoul(s, NULL, 0);
39}
40
41static uint32_t
42fn_hex(char *s) {
43 if (s[0] == '#')
44 s++;
45 return strtoul(s, NULL, 16);
46}
47
48static struct bind
49fn_bind(char *s) {
50 uint32_t mods = 0, key = 0;
51 char *tok = strtok(s, "+");
52 while (tok != NULL) {
53 uint32_t tmp = 0;
54
55 if (strcmp(tok, "mod") == 0) tmp = config.mod;
56 else if (strcmp(tok, "alt") == 0) tmp = SWC_MOD_ALT;
57 else if (strcmp(tok, "win") == 0) tmp = SWC_MOD_LOGO;
58 else if (strcmp(tok, "ctrl") == 0) tmp = SWC_MOD_CTRL;
59 else if (strcmp(tok, "shift") == 0) tmp = SWC_MOD_SHIFT;
60 else if (strcmp(tok, "any") == 0) tmp = SWC_MOD_ANY;
61
62 /* if not a modifier, check if it's a key */
63 if (tmp == 0) {
64 tmp = xkb_keysym_from_name(tok, XKB_KEYSYM_CASE_INSENSITIVE);
65 if (tmp == 0) {
66 _wrn("no such key: %s", tok);
67 return (struct bind){ 0, 0 };
68 }
69 key = tmp;
70 } else {
71 mods |= tmp;
72 }
73
74 tok = strtok(NULL, "+");
75 }
76
77 return (struct bind){ mods, key };
78}
79
80extern void bind_handler(void *, uint32_t, uint32_t, uint32_t);
81extern void decorate(struct client *, bool);
82extern bool load_decor(char *);
83extern void ws_go_to(int8_t);
84extern void ws_move_to(int8_t, struct client *);
85extern void focus_prev(void);
86extern void focus_next(void);
87extern void cleanup(void);
88
89/*********/
90
91status
92ipc_move(char **arg) {
93 if (arg[1] == NULL || arg[2] == NULL)
94 return (status){ false, "" };
95
96 ARG_CLIENT(arg[3]);
97
98 struct swc_rectangle geom;
99 if (swc_window_get_geometry(c->win, &geom)) {
100 c->x = geom.x;
101 c->y = geom.y;
102 c->width = geom.width;
103 c->height = geom.height;
104 }
105
106 swc_window_set_position(c->win, c->x + fn_int(arg[1]), c->y + fn_int(arg[2]));
107
108 return (status){ true, "" };
109}
110
111status
112ipc_move_absolute(char **arg) {
113 if (arg[1] == NULL || arg[2] == NULL)
114 return (status){ false, "" };
115
116 ARG_CLIENT(arg[3]);
117
118 swc_window_set_position(c->win, fn_int(arg[1]), fn_int(arg[2]));
119
120 return (status){ true, "" };
121}
122
123status
124ipc_resize(char **arg) {
125 if (arg[1] == NULL || arg[2] == NULL)
126 return (status){ false, "" };
127
128 ARG_CLIENT(arg[3]);
129
130 struct swc_rectangle geom;
131 if (swc_window_get_geometry(c->win, &geom)) {
132 c->x = geom.x;
133 c->y = geom.y;
134 c->width = geom.width;
135 c->height = geom.height;
136 }
137
138 swc_window_set_size(c->win, c->width + fn_uint(arg[1]), c->height + fn_uint(arg[2]));
139
140 return (status){ true, "" };
141}
142
143status
144ipc_resize_absolute(char **arg) {
145 if (arg[1] == NULL || arg[2] == NULL)
146 return (status){ false, "" };
147
148 ARG_CLIENT(arg[3]);
149
150 swc_window_set_size(c->win, fn_uint(arg[1]), fn_uint(arg[2]));
151
152 return (status){ true, "" };
153}
154
155status
156ipc_teleport(char **arg) {
157 if (arg[1] == NULL || arg[2] == NULL ||
158 arg[3] == NULL || arg[4] == NULL)
159 return (status){ false, "" };
160
161 ARG_CLIENT(arg[5]);
162
163 struct swc_rectangle rect = {
164 .x = fn_int(arg[1]),
165 .y = fn_int(arg[2]),
166 .width = fn_uint(arg[3]),
167 .height = fn_uint(arg[4])
168 };
169
170 swc_window_set_geometry(c->win, &rect);
171
172 return (status){ true, "" };
173}
174
175status
176ipc_center(char **arg) {
177 ARG_CLIENT(arg[1]);
178
179 struct swc_rectangle geom;
180 if (swc_window_get_geometry(c->win, &geom)) {
181 c->x = geom.x;
182 c->y = geom.y;
183 c->width = geom.width;
184 c->height = geom.height;
185 }
186
187 swc_window_set_position(
188 c->win,
189 wm.scr->width / 2 - c->width / 2,
190 wm.scr->height / 2 - c->height / 2
191 );
192
193 return (status){ true, "" };
194}
195
196status
197ipc_fullscreen(char **arg) {
198 ARG_CLIENT(arg[1]);
199
200 struct swc_rectangle geom;
201
202 if (c->fullscreen) {
203 c->fullscreen = false;
204 swc_window_set_stacked(c->win);
205
206 if (c->width > 0 && c->height > 0) {
207 geom.x = c->x;
208 geom.y = c->y;
209 geom.width = c->width;
210 geom.height = c->height;
211 swc_window_set_geometry(c->win, &geom);
212 }
213 return (status){ true, "" };
214 }
215
216 if (!c->scr || !c->scr->scr)
217 return (status){ false, "" };
218
219 if (swc_window_get_geometry(c->win, &geom)) {
220 c->x = geom.x;
221 c->y = geom.y;
222 c->width = geom.width;
223 c->height = geom.height;
224 }
225
226 c->fullscreen = true;
227 swc_window_set_fullscreen(c->win, wm.scr->scr);
228
229 return (status){ true, "" };
230}
231
232status
233ipc_hide(char **arg) {
234 ARG_CLIENT(arg[1]);
235
236 swc_window_hide(c->win);
237 c->visible = false;
238
239 return (status){ true, "" };
240}
241
242status
243ipc_show(char **arg) {
244 ARG_CLIENT(arg[1]);
245
246 swc_window_show(c->win);
247 c->visible = true;
248
249 return (status){ true, "" };
250}
251
252/* TODO: this doesn't work until someone adds lower/raise upstream */
253status
254ipc_lower(char **arg) {
255 ARG_CLIENT(arg[1]);
256
257 wl_list_remove(&c->link);
258 wl_list_insert(wm.clients.prev, &c->link);
259 swc_window_stack(c->win, 99);
260
261 return (status){ true, "" };
262}
263
264status
265ipc_raise(char **arg) {
266 ARG_CLIENT(arg[1]);
267
268 wl_list_remove(&c->link);
269 wl_list_insert(&wm.clients, &c->link);
270 swc_window_stack(c->win, -99);
271
272 return (status){ true, "" };
273}
274
275status
276ipc_focus_prev(char **arg) {
277 focus_prev();
278
279 return (status){ true, "" };
280}
281
282status
283ipc_focus_next(char **arg) {
284 focus_next();
285
286 return (status){ true, "" };
287}
288
289status
290ipc_close(char **arg) {
291 ARG_CLIENT(arg[1]);
292
293 swc_window_close(c->win);
294
295 return (status){ true, "" };
296}
297
298status
299ipc_workspace(char **arg) {
300 if (arg[1] == NULL)
301 return (status){ false, "" };
302
303 ws_go_to(fn_int(arg[1]));
304
305 return (status){ true, "" };
306}
307
308status
309ipc_move_workspace(char **arg) {
310 if (arg[1] == NULL)
311 return (status){ false, "" };
312
313 ARG_CLIENT(arg[2]);
314
315 ws_move_to(fn_int(arg[1]), c);
316
317 return (status){ true, "" };
318}
319
320/*********/
321
322status
323ipc_get_geometry(char **arg) {
324 status s = {0};
325
326 ARG_CLIENT(arg[1]);
327
328 struct swc_rectangle geom;
329 if (swc_window_get_geometry(c->win, &geom)) {
330 snprintf(s.msg, sizeof(s.msg), "%d %d %" PRIu32 " %" PRIu32 "\n", geom.x, geom.y, geom.width, geom.height);
331 }
332
333 s.ok = true;
334 return s;
335}
336
337status
338ipc_get_pid(char **arg) {
339 status s = {0};
340
341 ARG_CLIENT(arg[1]);
342
343 pid_t pid = swc_window_get_pid(c->win);
344 snprintf(s.msg, sizeof(s.msg), "%d\n", pid);
345
346 s.ok = true;
347 return s;
348}
349
350status
351ipc_get_title(char **arg) {
352 status s = {0};
353
354 ARG_CLIENT(arg[1]);
355
356 snprintf(s.msg, sizeof(s.msg), "%s\n", c->win->title);
357
358 s.ok = true;
359 return s;
360}
361
362status
363ipc_get_app_id(char **arg) {
364 status s = {0};
365
366 ARG_CLIENT(arg[1]);
367
368 snprintf(s.msg, sizeof(s.msg), "%s\n", c->win->app_id);
369
370 s.ok = true;
371 return s;
372}
373
374status
375ipc_get_focus(char **arg) {
376 status s = {0};
377
378 if (wm.cur)
379 snprintf(s.msg, sizeof(s.msg), "%" PRIu32 "\n", wm.cur->id);
380 else
381 return (status){ false, "No window focused\n" };
382
383 s.ok = true;
384 return s;
385}
386
387status
388ipc_get_workspace(char **arg) {
389 status s = {0};
390
391 uint8_t ws = wm.ws;
392 if (arg[1]) {
393 ARG_CLIENT(arg[1]);
394 ws = c->ws;
395 }
396
397 snprintf(s.msg, sizeof(s.msg), "%" PRIu8 "\n", ws);
398
399 s.ok = true;
400 return s;
401}
402
403status
404ipc_get_screen_geometry(char **arg) {
405 status s = {0};
406
407 if (!wm.scr)
408 return (status){ false, "" };
409
410 snprintf(s.msg, sizeof(s.msg), "%" PRIu32 " %" PRIu32 "\n", wm.scr->width, wm.scr->height);
411
412 s.ok = true;
413 return s;
414}
415
416status
417ipc_get_cursor_position(char **arg) {
418 status s = {0};
419
420 int32_t cx = 0, cy = 0;
421 if (swc_cursor_position(&cx, &cy))
422 snprintf(s.msg, sizeof(s.msg), "%d %d\n", cx / 256, cy / 256);
423
424 s.ok = true;
425 return s;
426}
427
428status
429ipc_list_windows(char **arg) {
430 status s = {0};
431
432 enum {
433 LIST_MAPPED,
434 LIST_UNMAPPED,
435 LIST_ALL
436 } mode = LIST_MAPPED;
437
438 if (arg[1]) {
439 if ((strcmp(arg[1], "-m")) == 0)
440 mode = LIST_MAPPED;
441 else if ((strcmp(arg[1], "-u")) == 0)
442 mode = LIST_UNMAPPED;
443 else if ((strcmp(arg[1], "-a")) == 0)
444 mode = LIST_ALL;
445 }
446
447 if (!wl_list_empty(&wm.clients)) {
448 size_t o = 0;
449 struct client *c;
450
451 wl_list_for_each(c, &wm.clients, link) {
452 bool list = false;
453 switch (mode) {
454 case LIST_MAPPED: {
455 list = c->visible;
456 break;
457 }
458 case LIST_UNMAPPED: {
459 list = !c->visible;
460 break;
461 }
462 case LIST_ALL: {
463 list = true;
464 break;
465 }
466 }
467 if (!list)
468 continue;
469
470 o += snprintf(s.msg + o, sizeof(s.msg) - o, "%" PRIu32 "\n", c->id);
471 }
472 }
473
474 s.ok = true;
475 return s;
476}
477
478/*********/
479
480status
481ipc_bind(char **arg) {
482 if (arg[1] == NULL || arg[2] == NULL)
483 return (status){ false, "" };
484
485 struct bind tmp = fn_bind(arg[1]);
486 int count = 0;
487 for (char **p = arg + 2; *p != NULL; p++)
488 count++;
489
490 /* XXX: where is this freed? */
491 char **cmd = malloc((count + 1) * sizeof(char*));
492 if (!cmd)
493 return (status){ false, "" };
494
495 for (int i = 0; i < count; i++)
496 cmd[i] = strdup(arg[i + 2]);
497 cmd[count] = NULL;
498
499 swc_add_binding(
500 SWC_BINDING_KEY,
501 tmp.mod,
502 tmp.key,
503 bind_handler,
504 cmd
505 );
506
507 return (status){ true, "" };
508}
509
510status
511ipc_unbind(char **arg) {
512 if (arg[1] == NULL)
513 return (status){ false, "" };
514
515 struct bind tmp = fn_bind(arg[1]);
516 swc_remove_binding(
517 SWC_BINDING_KEY,
518 tmp.mod,
519 tmp.key
520 );
521
522 return (status){ true, "" };
523}
524
525status
526ipc_config(char **arg) {
527 if (arg[1] == NULL || arg[2] == NULL)
528 return (status){ false, "" };
529
530 enum cmd cmd = fn_int(arg[1]);
531 switch (cmd) {
532 case cmd_modkey: {
533 struct bind tmp = fn_bind(arg[2]);
534 config.mod = tmp.mod;
535 break;
536 }
537 case cmd_inner_focus_color:
538 config.if_color = fn_hex(arg[2]);
539 break;
540 case cmd_outer_focus_color:
541 config.of_color = fn_hex(arg[2]);
542 break;
543 case cmd_inner_unfocus_color:
544 config.iu_color = fn_hex(arg[2]);
545 break;
546 case cmd_outer_unfocus_color:
547 config.ou_color = fn_hex(arg[2]);
548 break;
549 case cmd_inner_border_width:
550 config.ib_width = fn_int(arg[2]);
551 break;
552 case cmd_outer_border_width:
553 config.ob_width = fn_int(arg[2]);
554 break;
555 case cmd_title_format:
556 config.tf = arg[2];
557 break;
558 case cmd_set_decor:
559 load_decor(arg[2]);
560 break;
561 default:
562 break;
563 }
564
565 struct client *c;
566 wl_list_for_each(c, &wm.clients, link) {
567 if (c == wm.cur) {
568 swc_window_set_border(
569 c->win,
570 config.if_color, config.ib_width,
571 config.of_color, config.ob_width
572 );
573 decorate(c, true);
574 } else {
575 swc_window_set_border(
576 c->win,
577 config.iu_color, config.ib_width,
578 config.ou_color, config.ob_width
579 );
580 decorate(c, false);
581 }
582 }
583
584 return (status){ true, "" };
585}
586
587status
588ipc_quit(char **arg) {
589 wm.running = false;
590 swc_finalize();
591 wl_display_terminate(wm.dpy);
592
593 return (status){ true, "" }; /* NOTREACHED */
594}