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