main wf/howl / src / howl.c
  1#include <stdio.h>
  2#include <stdlib.h>
  3#include <string.h>
  4#include <errno.h>
  5#include <signal.h>
  6#include <unistd.h>
  7#include <getopt.h>
  8#include <sys/socket.h>
  9#include <sys/un.h>
 10
 11#include <wayland-server.h>
 12#include <swc.h>
 13
 14#include "howl.h"
 15#include "types.h"
 16#include "ipc.h"
 17
 18static void cleanup(void);
 19static void new_screen(struct swc_screen *);
 20static void new_window(struct swc_window *);
 21static void on_win_destroy(void *);
 22static void on_win_entered(void *);
 23static void on_win_title(void *);
 24static void on_scr_destroy(void *);
 25static void on_scr_geometry(void *);
 26static bool is_ws_client(const struct client *, const struct screen *);
 27
 28extern status ipc_move(char **);
 29extern status ipc_move_absolute(char **);
 30extern status ipc_resize(char **);
 31extern status ipc_resize_absolute(char **);
 32extern status ipc_teleport(char **);
 33extern status ipc_center(char **);
 34extern status ipc_fullscreen(char **);
 35extern status ipc_hide(char **);
 36extern status ipc_show(char **);
 37extern status ipc_lower(char **);
 38extern status ipc_raise(char **);
 39extern status ipc_focus_prev(char **);
 40extern status ipc_focus_next(char **);
 41extern status ipc_close(char **);
 42extern status ipc_workspace(char **);
 43extern status ipc_move_workspace(char **);
 44extern status ipc_get_geometry(char **);
 45extern status ipc_get_pid(char **);
 46extern status ipc_get_title(char **);
 47extern status ipc_get_app_id(char **);
 48extern status ipc_get_focus(char **);
 49extern status ipc_get_workspace(char **);
 50extern status ipc_list_windows(char **);
 51extern status ipc_get_screen_geometry(char **);
 52extern status ipc_get_cursor_position(char **);
 53extern status ipc_bind(char **);
 54extern status ipc_unbind(char **);
 55extern status ipc_quit(char **);
 56extern status ipc_config(char **);
 57
 58struct wm wm;
 59struct config config;
 60struct decor *decor;
 61
 62static struct swc_manager mgr = {
 63	.new_screen = &new_screen,
 64	.new_window = &new_window
 65};
 66
 67static struct swc_window_handler win_handler = {
 68	.destroy = on_win_destroy,
 69	.entered = on_win_entered,
 70	.title_changed = on_win_title
 71};
 72
 73static struct swc_screen_handler scr_handler = {
 74	.destroy = on_scr_destroy,
 75	.usable_geometry_changed = on_scr_geometry
 76};
 77
 78static int ipc_fd;
 79
 80typedef status (*cmd_handler_t)(char **);
 81static const cmd_handler_t cmd_handler [cmd_last] = {
 82	[cmd_move]                = ipc_move,
 83	[cmd_move_absolute]       = ipc_move_absolute,
 84	[cmd_resize]              = ipc_resize,
 85	[cmd_resize_absolute]     = ipc_resize_absolute,
 86	[cmd_teleport]            = ipc_teleport,
 87	[cmd_center]              = ipc_center,
 88	[cmd_fullscreen]          = ipc_fullscreen,
 89	[cmd_hide]                = ipc_hide,
 90	[cmd_show]                = ipc_show,
 91	[cmd_lower]               = ipc_lower,
 92	[cmd_raise]               = ipc_raise,
 93	[cmd_focus_prev]          = ipc_focus_prev,
 94	[cmd_focus_next]          = ipc_focus_next,
 95	[cmd_close]               = ipc_close,
 96	[cmd_workspace]           = ipc_workspace,
 97	[cmd_move_workspace]      = ipc_move_workspace,
 98	[cmd_get_geometry]        = ipc_get_geometry,
 99	[cmd_get_pid]             = ipc_get_pid,
100	[cmd_get_title]           = ipc_get_title,
101	[cmd_get_app_id]          = ipc_get_app_id,
102	[cmd_get_focus]           = ipc_get_focus,
103	[cmd_get_workspace]       = ipc_get_workspace,
104	[cmd_list_windows]        = ipc_list_windows,
105	[cmd_get_screen_geometry] = ipc_get_screen_geometry,
106	[cmd_get_cursor_position] = ipc_get_cursor_position,
107	[cmd_bind]                = ipc_bind,
108	[cmd_unbind]              = ipc_unbind,
109	[cmd_quit]                = ipc_quit,
110	[cmd_config]              = ipc_config
111};
112
113static int
114ipc_handler(int fd, uint32_t mask, void *data) {
115	if (mask & WL_EVENT_HANGUP) {
116		_wrn("IPC socket connection closed prematurely: %s", strerror(errno));
117		return 0;
118	}
119
120	if (mask & WL_EVENT_ERROR) {
121		_wrn("IPC socket connection error: %s", strerror(errno));
122		return 0;
123	}
124
125	if (mask & WL_EVENT_READABLE) {
126		if (fd != ipc_fd)
127			return 0;
128
129		int afd = accept(ipc_fd, NULL, NULL);
130		if (afd == -1) {
131			_wrn("couldn't accept incoming connection on IPC socket: %s", strerror(errno));
132			goto end;
133		}
134
135		char buf[MAXSIZE];
136		ssize_t n;
137
138		if ((n = read(afd, buf, sizeof(buf) - 1)) < 0) {
139			_wrn("couldn't read from IPC socket: %s", strerror(errno));
140			goto end;
141		}
142
143		if (n == 0) {
144			_wrn("unexpected EOF on IPC socket: %s", strerror(errno));
145			goto end;
146		}
147
148		if (n > 0) {
149			int argc = 0;
150			char *argv[64], *tok = NULL;
151
152			buf[n] = '\0';
153			while (buf[n-1] == '\r' || buf[n-1] == '\n' || buf[n-1] == ' ')
154				buf[n-1] = '\0';
155
156			tok = strtok(buf, " ");
157			while (tok != NULL && argc < 63) {
158				argv[argc++] = tok;
159				tok = strtok(NULL, " ");
160			}
161			argv[argc] = NULL;
162
163			if (argc == 0)
164				goto end;
165
166			int cmd = atoi(argv[0]);
167			if (cmd < 0 || cmd > cmd_last) {
168				_wrn("IPC command index out of bounds");
169				goto end;
170			}
171
172			/* TODO: this is hard to debug */
173			if (!cmd_handler[cmd]) {
174				_wrn("no such command #%d", cmd);
175				goto end;
176			}
177
178			status s = cmd_handler[cmd](argv);
179			char answer[MAXSIZE];
180			snprintf(answer, sizeof(answer), "%d %s", s.ok, s.msg);
181
182			if (send(afd, answer, strlen(answer), 0) == -1)
183				_wrn("couldn't send answer to client");
184		}
185end:
186		close(afd);
187		return 0;
188	}
189
190	return 0; /* NOTREACHED */
191}
192
193static void
194setup_ipc(void) {
195	ipc_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
196	if (ipc_fd == -1)
197		_err(1, "couldn't open IPC socket: %s", strerror(errno));
198
199	struct sockaddr_un addr = {0};
200	addr.sun_family = AF_UNIX;
201	strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path) - 1);
202
203	unlink(SOCK_PATH);
204	if (bind(ipc_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
205		_err(1, "couldn't bind IPC socket: %s", strerror(errno));
206
207	if (listen(ipc_fd, 5) < 0)
208		_err(1, "couldn't listen on IPC socket: %s", strerror(errno));
209
210	wl_event_loop_add_fd(wm.loop, ipc_fd, WL_EVENT_READABLE | WL_EVENT_HANGUP | WL_EVENT_ERROR, ipc_handler, NULL);
211
212	_inf("set up IPC socket");
213}
214
215static void
216sig_handler(int s) {
217	cleanup();
218	if (wm.dpy) {
219		swc_finalize();
220		wl_display_terminate(wm.dpy);
221	}
222}
223
224static void
225setup(void) {
226	wm.dpy = wl_display_create();
227	if (!wm.dpy)
228		_err(1, "couldn't create Wayland display");
229
230	wl_list_init(&wm.screens);
231	wl_list_init(&wm.clients);
232	wm.cur         = NULL;
233	wm.scr         = NULL;
234	wm.grab.active = false;
235	wm.grab.resize = false;
236	wm.grab.client = NULL;
237	wm.ws          = 1;
238	wm.last_id     = 0;
239
240	config.mod      = SWC_MOD_LOGO;
241	config.if_color = 0xFF8777E5;
242	config.iu_color = 0xFF444444;
243	config.of_color = 0xFF202020;
244	config.ou_color = 0xFF202020;
245	config.ib_width = 3;
246	config.ob_width = 3;
247	config.tf       = "%t";
248
249	decor = calloc(1, sizeof(struct decor));
250	decor->enabled    = true;
251	decor->edge       = SWC_DECOR_EDGE_TOP;
252	decor->align      = SWC_DECOR_ALIGN_START;
253	decor->foreground = 0xFFFFFFFF;
254	decor->background = 0xFF000000;
255	decor->padding    = 0;
256	decor->offset_x   = 0;
257	decor->offset_y   = 0;
258	decor->fontname   = strdup("sans-serif:size=11");
259	if (!decor->fontname)
260		_err(1, "couldn't allocate default decoration font");
261
262	wm.loop = wl_display_get_event_loop(wm.dpy);
263	if (!swc_initialize(wm.dpy, wm.loop, &mgr))
264		_err(1, "couldn't initialize swc");
265
266	const char *sock = wl_display_add_socket_auto(wm.dpy);
267	if (!sock)
268		_err(1, "couldn't add Wayland display socket");
269
270	setenv("WAYLAND_DISPLAY", sock, 1);
271	_inf("set WAYLAND_DISPLAY=%s", sock);
272
273	setup_ipc();
274
275	signal(SIGINT,  sig_handler);
276	signal(SIGTERM, sig_handler);
277	signal(SIGQUIT, sig_handler);
278
279	wm.running = true;
280}
281
282void
283cleanup(void) {
284	/* ... */
285	free((void *)decor->active.top_left.data);
286	free((void *)decor->active.top.data);
287	free((void *)decor->active.top_right.data);
288	free((void *)decor->active.left.data);
289	free((void *)decor->active.right.data);
290	free((void *)decor->active.bottom_left.data);
291	free((void *)decor->active.bottom.data);
292	free((void *)decor->active.bottom_right.data);
293
294	free((void *)decor->inactive.top_left.data);
295	free((void *)decor->inactive.top.data);
296	free((void *)decor->inactive.top_right.data);
297	free((void *)decor->inactive.left.data);
298	free((void *)decor->inactive.right.data);
299	free((void *)decor->inactive.bottom_left.data);
300	free((void *)decor->inactive.bottom.data);
301	free((void *)decor->inactive.bottom_right.data);
302	free(decor->fontname);
303
304	free(decor);
305
306	close(ipc_fd);
307	unlink(SOCK_PATH);
308}
309
310static void
311load_config(char *conf_path) {
312	if (fork() == 0) {
313		setsid();
314		execl("/bin/sh", "sh", conf_path, NULL);
315	}
316}
317
318void
319bind_handler(void *data, uint32_t time, uint32_t value, uint32_t state) {
320	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
321		return;
322
323	char *const *cmd = (char *const *)data;
324	if (fork() == 0) {
325		setsid();
326		execvp(cmd[0], cmd);
327		_exit(127);
328	}
329}
330
331static const char *
332title_format(struct client *c, char *fmt) {
333	static char buf[MAXSIZE];
334	size_t o = 0;
335
336	if (!fmt || !c || !c->win) {
337		buf[0] = '\0';
338		return buf;
339	}
340
341	for (size_t i = 0; fmt[i] != '\0' && o + 1 < MAXSIZE; ++i) {
342		if (fmt[i] != '%') {
343			buf[o++] = fmt[i];
344			continue;
345		}
346
347		++i;
348		if (fmt[i] == '\0')
349			break;
350		char ch = fmt[i];
351
352		switch (ch) {
353			case '%':
354				if (o + 1 < MAXSIZE)
355					buf[o++] = '%';
356				break;
357			case 't':
358				/* FALLTHROUGH */
359			case 'a': {
360				const char *s = (ch == 't') ? c->win->title : c->win->app_id;
361				if (!s)
362					continue;
363				while (*s && o + 1 < MAXSIZE)
364					buf[o++] = *s++;
365				break;
366			}
367			case 'p': {
368				pid_t pid = swc_window_get_pid(c->win);
369				int n = snprintf(buf + o, MAXSIZE - o, "%d", pid);
370				if (n > 0)
371					o += (size_t)(n < (int)(MAXSIZE - o) ? n : (int)(MAXSIZE - o - 1));
372				break;
373			}
374			default:
375				if (o + 2 < MAXSIZE) {
376					buf[o++] = '%';
377					buf[o++] = ch;
378				}
379				break;
380		}
381	}
382
383	buf[o] = '\0';
384	return buf;
385}
386
387void
388decorate(struct client *c, bool focus) {
389	if (!c)
390		return;
391
392	if (c->fullscreen) {
393		swc_window_set_decor(c->win, NULL);
394		return;
395	}
396
397	const struct swc_decor_parts parts = {
398		.top_left = focus ? decor->active.top_left : decor->inactive.top_left,
399		.top = focus ? decor->active.top : decor->inactive.top,
400		.top_right = focus ? decor->active.top_right : decor->inactive.top_right,
401		.left = focus ? decor->active.left : decor->inactive.left,
402		.right = focus ? decor->active.right : decor->inactive.right,
403		.bottom_left = focus ? decor->active.bottom_left : decor->inactive.bottom_left,
404		.bottom = focus ? decor->active.bottom : decor->inactive.bottom,
405		.bottom_right = focus ? decor->active.bottom_right : decor->inactive.bottom_right
406	};
407
408	const char *title = title_format(c, config.tf);
409	struct swc_decor new = {
410		.color = decor->background,
411		.left = decor->edge_left,
412		.right = decor->edge_right,
413		.top = decor->edge_top,
414		.bottom = decor->edge_bottom,
415		.parts = &parts,
416		.title = {
417			.enabled = decor->enabled,
418			.edge = decor->edge,
419			.align = decor->align,
420			.string = title,
421			.color = decor->foreground,
422			.padding = decor->padding,
423			.offset_x = decor->offset_x,
424			.offset_y = decor->offset_y,
425			.font = decor->fontname
426		}
427	};
428
429	swc_window_set_decor(c->win, &new);
430}
431
432void
433undecorate(struct client *c) {
434	swc_window_set_decor(c->win, NULL);
435}
436
437static void
438focus(struct client *c) {
439	if (wm.cur) {
440		swc_window_set_border(
441			wm.cur->win,
442			config.iu_color, config.ib_width,
443			config.ou_color, config.ob_width
444		);
445		decorate(wm.cur, false);
446	}
447
448	if (c) {
449		swc_window_set_border(
450			c->win,
451			config.if_color, config.ib_width,
452			config.of_color, config.ob_width
453		);
454		decorate(c, true);
455	}
456
457	swc_window_focus(c ? c->win : NULL);
458	wm.cur = c;
459}
460
461static struct client *
462first_client(struct screen *s) {
463	struct client *c;
464
465	wl_list_for_each(c, &wm.clients, link) {
466		if (is_ws_client(c, s))
467			return c;
468	}
469
470	return NULL;
471}
472
473static bool
474is_ws_client(const struct client *c, const struct screen *s) {
475	return c && c->ws == wm.ws && (!s || c->scr == s);
476}
477
478static void
479sync_windows(void) {
480	struct client *c;
481
482	wl_list_for_each(c, &wm.clients, link) {
483		if (c->ws == wm.ws) {
484			c->visible = true;
485			swc_window_show(c->win);
486		} else {
487			c->visible = false;
488			swc_window_hide(c->win);
489		}
490	}
491}
492
493void
494ws_go_to(uint8_t ws) {
495	struct client *c;
496
497	if (ws < 1 || ws > 9 || ws == wm.ws)
498		return;
499
500	wm.ws = ws;
501	sync_windows();
502
503	c = first_client(wm.scr);
504	if (!c)
505		c = first_client(NULL);
506	focus(c);
507}
508
509void
510ws_move_to(uint8_t ws, struct client *c) {
511	struct client *next;
512
513	if (!c || ws < 1 || ws > 9)
514		return;
515	if (c->ws == ws)
516		return;
517
518	c->ws = ws;
519	if (c->ws == wm.ws) {
520		c->visible = true;
521		swc_window_show(c->win);
522	} else {
523		c->visible = false;
524		swc_window_hide(c->win);
525	}
526
527	next = first_client(wm.scr);
528	if (!next)
529		next = first_client(NULL);
530	focus(next);
531}
532
533void
534focus_prev(void) {
535	if (wl_list_empty(&wm.clients))
536		return;
537
538	struct client *c = NULL;
539	if (!wm.cur || !is_ws_client(wm.cur, wm.scr)) {
540		c = first_client(wm.scr);
541		if (!c)
542			c = first_client(NULL);
543		focus(c);
544		return;
545	}
546
547	struct wl_list *start = wm.cur->link.prev;
548	struct wl_list *it = start;
549
550	do {
551		if (it == &wm.clients)
552			it = wm.clients.prev;
553		if (it == &wm.clients)
554			break;
555
556		c = wl_container_of(it, c, link);
557		if (is_ws_client(c, wm.scr)) {
558			focus(c);
559			return;
560		}
561		it = it->prev;
562	} while (it != start);
563
564	c = first_client(wm.scr);
565	if (!c)
566		c = first_client(NULL);
567	focus(c);
568}
569
570void
571focus_next(void) {
572	if (wl_list_empty(&wm.clients))
573		return;
574
575	struct client *c = NULL;
576	if (!wm.cur || !is_ws_client(wm.cur, wm.scr)) {
577		c = first_client(wm.scr);
578		if (!c)
579			c = first_client(NULL);
580		focus(c);
581		return;
582	}
583
584	struct wl_list *start = wm.cur->link.next;
585	struct wl_list *it = start;
586
587	do {
588		if (it == &wm.clients)
589			it = wm.clients.next;
590		if (it == &wm.clients)
591			break;
592
593		c = wl_container_of(it, c, link);
594		if (is_ws_client(c, wm.scr)) {
595			focus(c);
596			return;
597		}
598		it = it->next;
599	} while (it != start);
600
601	c = first_client(wm.scr);
602	if (!c)
603		c = first_client(NULL);
604	focus(c);
605}
606
607static void
608new_screen(struct swc_screen *scr) {
609	struct screen *s;
610	s = malloc(sizeof(*s));
611	if (!s)
612		_err(1, "couldn't allocate screen");
613
614	s->scr    = scr;
615	s->x      = 0;
616	s->y      = 0;
617	s->width  = scr->usable_geometry.width;
618	s->height = scr->usable_geometry.height;
619
620	wl_list_insert(&wm.screens, &s->link);
621	if (!wm.scr)
622		wm.scr = s;
623	swc_screen_set_handler(scr, &scr_handler, s);
624	_inf("new_screen=%p", (void *)scr);
625}
626
627static void
628new_window(struct swc_window *win) {
629	struct client *c;
630	c = malloc(sizeof(*c));
631	if (!c)
632		_err(1, "couldn't allocate client");
633
634	win->motion_throttle_ms = 1000 / 85;
635	win->min_width          = 1;
636	win->min_height         = 1;
637	win->max_width          = 0;
638	win->max_height         = 0;
639
640	c->win        = win;
641	c->scr        = wm.scr;
642	c->visible    = true;
643	c->fullscreen = false;
644	c->ws         = wm.ws;
645	c->x          = 0;
646	c->y          = 0;
647	c->width      = 0;
648	c->height     = 0;
649	c->id         = wm.last_id++;
650
651	wl_list_insert(&wm.clients, &c->link);
652	swc_window_set_handler(win, &win_handler, c);
653	swc_window_set_stacked(win);
654	{
655		int32_t cx = 0;
656		int32_t cy = 0;
657
658		if (swc_cursor_position(&cx, &cy))
659			swc_window_set_position(win, cx / 256, cy / 256);
660	}
661	swc_window_show(win);
662	focus(c);
663	_inf("new_window=%p", (void *)win);
664}
665
666static void
667on_win_entered(void *data) {
668	if (wm.grab.active)
669		return;
670
671	struct client *c = data;
672	if (!is_ws_client(c, NULL))
673		return;
674
675	focus(c);
676}
677
678static void
679on_win_destroy(void *data) {
680	struct client *c = data, *next;
681
682	if (!c)
683		return;
684	if (wm.grab.active && wm.grab.client == c) {
685		wm.grab.active = false;
686		wm.grab.client = NULL;
687	}
688
689	wl_list_remove(&c->link);
690	free(c);
691
692	next = first_client(wm.scr);
693	if (!next)
694		next = first_client(NULL);
695	focus(next);
696}
697
698
699static void
700on_win_title(void *data) {
701	struct client *c = data;
702
703	decorate(c, (c == wm.cur));
704}
705
706static void
707on_scr_destroy(void *data) {
708	struct screen *s = data;
709	if (!s)
710		return;
711
712	wl_list_remove(&s->link);
713	if (wm.scr == s) {
714		if (wl_list_empty(&wm.screens))
715			wm.scr = NULL;
716		else
717			wm.scr = wl_container_of(wm.screens.next, wm.scr, link);
718	}
719	free(s);
720}
721
722static void
723on_scr_geometry(void *data) {
724	struct screen *s = data;
725	if (!s)
726		return;
727
728	int xoff = s->scr->usable_geometry.width - wm.scr->width;
729	int yoff = s->scr->usable_geometry.height - wm.scr->height;
730
731	wm.scr->width = s->scr->usable_geometry.width;
732	wm.scr->height = s->scr->usable_geometry.height;
733
734	if (!(wl_list_empty(&wm.clients))) {
735		struct client *c;
736		wl_list_for_each(c, &wm.clients, link) {
737			struct swc_rectangle geom;
738			if ((swc_window_get_geometry(c->win, &geom))) {
739				c->x = geom.x;
740				c->y = geom.y;
741			}
742			swc_window_set_position(c->win, c->x + xoff / 2, c->y + yoff / 2);
743		}
744	}
745}
746
747int
748main(int argc, char **argv) {
749	bool have_config = true;
750	char *conf_path = malloc(MAXSIZE * sizeof(char));
751	conf_path[0] = '\0';
752
753	int opt;
754	while ((opt = getopt(argc, argv, ":hvc:")) != -1) {
755		switch (opt) {
756			case 'h':
757				fprintf(stderr, "Usage: howl [-hv] [-c path]\n");
758				return 0;
759				break;
760			case 'v':
761				fprintf(stderr, "howl v%s\n", VERSION);
762				return 0;
763				break;
764			case 'c':
765				snprintf(conf_path, MAXSIZE * sizeof(char), "%s", optarg);
766				break;
767			case '?':
768				fprintf(stderr, "Unknown option: -%c", optopt);
769				return 1;
770				break;
771			case ':':
772				fprintf(stderr, "Missing argument for option -%c", optopt);
773				return 1;
774				break;
775		}
776	}
777
778	setup();
779
780	/* taken from berrywm, thanks! */
781	if (conf_path[0] == '\0') {
782		char *xdg_home = getenv("XDG_CONFIG_HOME");
783		if (xdg_home != NULL) {
784			snprintf(conf_path, MAXSIZE * sizeof(char), "%s/%s/%s", xdg_home, "howl", "autostart");
785			have_config = true;
786		} else {
787			char *home = getenv("HOME");
788			if (home == NULL) {
789				_wrn("couldn't find $HOME, autostart won't be loaded!");
790				have_config = false;
791			}
792			snprintf(conf_path, MAXSIZE * sizeof(char), "%s/%s/%s/%s", home, ".config", "howl", "autostart");
793		}
794	}
795
796	if (have_config) {
797		signal(SIGCHLD, SIG_IGN);
798		load_config(conf_path);
799	}
800
801	wl_display_run(wm.dpy);
802	if (!wm.running)
803		cleanup();
804
805	free(conf_path);
806	return 0;
807}