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}