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 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}