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}