main wf/howl / src / ipc.c
  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}