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