1#include <signal.h>
2#include <stdio.h>
3#include <stdlib.h>
4#include <string.h>
5#include <unistd.h>
6#include <sys/wait.h>
7
8#include <swc.h>
9#include <wayland-server.h>
10#include <wayland-util.h>
11#include <xkbcommon/xkbcommon-keysyms.h>
12
13/* this is for lua-config.c !!! awwa!!awaw!!waw!! */
14extern struct config cfg;
15extern struct bind *binds;
16extern size_t nbinds;
17extern void load_config(void);
18
19#include "include/types.h"
20#include "include/util.h"
21#include "include/slgro.h"
22
23static void focus(struct client* c);
24static struct client* first_client(struct screen* s);
25static bool is_ws_client(const struct client* c, const struct screen* s);
26static void on_screen_destroy(void* data);
27static void on_win_destroy(void* data);
28static void on_win_entered(void* data);
29static void setup(void);
30static void setup_binds(void);
31static void sync_window_visibility(void);
32static void apply_decor(struct client* c, bool active);
33static const char* decorstring(struct client* c, char* buf, size_t size);
34
35#define WHEN_PRESSED(state) \
36 do { if ((state) != WL_KEYBOARD_KEY_STATE_PRESSED) return; } while (0)
37
38#define BIND_EVENT(time, value, state) \
39 (void)(time); (void)(value); \
40 WHEN_PRESSED(state);
41
42#define BIND_ACTION(data, time, value, state) \
43 BIND_EVENT(time, value, state); \
44 if (!wm.sel_client) return; \
45 union arg* a = (data)
46
47/* read geometry from client window; returns true if successful */
48static bool get_geometry(struct client *c, struct swc_rectangle *geom)
49{
50 if (!c || !c->win)
51 return false;
52
53 return swc_window_get_geometry(c->win, geom);
54}
55
56/* adjust geometry fields and write back */
57static void adjust_geom(struct client *c, int dx, int dy, int dw, int dh)
58{
59 struct swc_rectangle geom;
60 if (!get_geometry(c, &geom)) return;
61
62 geom.x += dx;
63 geom.y += dy;
64
65 int new_w = (int)geom.width + dw;
66 int new_h = (int)geom.height + dh;
67 geom.width = (new_w < 1) ? 1 : (uint32_t)new_w;
68 geom.height = (new_h < 1) ? 1 : (uint32_t)new_h;
69
70 swc_window_set_geometry(c->win, &geom);
71}
72
73static const char* decorstring(struct client* c, char* buf, size_t size)
74{
75 char path[64];
76 FILE* fp;
77 pid_t pid;
78 size_t len;
79
80 if (!c || !buf || size == 0)
81 return NULL;
82
83 pid = swc_window_get_pid(c->win);
84 if (pid <= 0)
85 return NULL;
86
87 snprintf(path, sizeof(path), "/proc/%lld/comm", (long long)pid);
88 fp = fopen(path, "r");
89 if (!fp)
90 return NULL;
91
92 if (!fgets(buf, (int)size, fp)) {
93 fclose(fp);
94 return NULL;
95 }
96
97 fclose(fp);
98 len = strcspn(buf, "\n");
99 buf[len] = '\0';
100 return buf[0] ? buf : NULL;
101}
102
103static void apply_decor(struct client* c, bool active)
104{
105 char process_name[256];
106 const char* title;
107 struct swc_decor decor;
108
109 if (!c)
110 return;
111
112 if (c->fullscreen) {
113 swc_window_set_decor(c->win, NULL);
114 return;
115 }
116
117 decor = cfg.decor;
118 title = decorstring(c, process_name, sizeof(process_name));
119 if (title)
120 decor.title.string = title;
121 swc_window_set_decor(c->win, &decor);
122}
123
124struct wm wm;
125const struct swc_manager manager = {
126 .new_screen = new_screen, .new_window = new_window, .new_device = new_device,
127};
128struct swc_window_handler window_handler = {
129 .destroy = on_win_destroy, .entered = on_win_entered,
130};
131struct swc_screen_handler screen_handler = {
132 .destroy = on_screen_destroy,
133};
134
135static struct client* get_focus_candidate(struct screen* s)
136{
137 struct client* c = first_client(s);
138 if (!c && s) c = first_client(NULL);
139 return c;
140}
141
142static void focus(struct client* c)
143{
144 if (wm.sel_client && wm.sel_client != c) {
145 swc_window_set_border(wm.sel_client->win, cfg.border_col_normal, cfg.border_width, 0, 0);
146 apply_decor(wm.sel_client, false);
147 }
148 if (c) {
149 swc_window_set_border(c->win, cfg.border_col_active, cfg.border_width, 0, 0);
150 apply_decor(c, true);
151 }
152
153 swc_window_focus(c ? c->win : NULL);
154 wm.sel_client = c;
155}
156
157static struct client* first_client(struct screen* s)
158{
159 struct client* c;
160
161 wl_list_for_each(c, &wm.clients, link) {
162 if (is_ws_client(c, s))
163 return c;
164 }
165
166 return NULL;
167}
168
169static bool is_ws_client(const struct client* c, const struct screen* s)
170{
171 return c && c->ws == wm.ws && (!s || c->scr == s);
172}
173
174static void on_screen_destroy(void* data)
175{
176 struct screen* s = data;
177
178 if (!s)
179 return;
180
181 wl_list_remove(&s->link);
182
183 if (wm.sel_screen == s) {
184 wm.sel_screen = wl_list_empty(&wm.screens)
185 ? NULL
186 : wl_container_of(wm.screens.next, wm.sel_screen, link);
187 }
188
189 free(s);
190}
191
192static void on_win_destroy(void* data)
193{
194 struct client* c = data;
195 if (!c) return;
196
197 if (wm.sel_client == c)
198 wm.sel_client = NULL;
199
200 wl_list_remove(&c->link);
201 free(c);
202
203 focus(get_focus_candidate(wm.sel_screen));
204}
205
206static void on_win_entered(void* data)
207{
208 struct client* c = data;
209 if (!is_ws_client(c, NULL))
210 return;
211
212 focus(c);
213}
214
215static void setup(void)
216{
217 /* display */
218 load_config();
219 wm.dpy = wl_display_create();
220 if (!wm.dpy)
221 die(EXIT_FAILURE, "wl_display_create failed");
222
223 /* variables */
224 wl_list_init(&wm.screens);
225 wl_list_init(&wm.clients);
226
227 wm.sel_client = NULL;
228 wm.sel_screen = NULL;
229 wm.ws = 1;
230
231 /* event loop */
232 wm.ev_loop = wl_display_get_event_loop(wm.dpy);
233 if (!swc_initialize(wm.dpy, wm.ev_loop, &manager))
234 die(EXIT_FAILURE, "swc_initialize failed\n");
235
236 setup_binds();
237
238 /* display socket */
239 const char* sock;
240 sock = wl_display_add_socket_auto(wm.dpy);
241 if (!sock)
242 die(EXIT_FAILURE, "wl_display_add_socket_auto failed\n");
243
244 setenv("WAYLAND_DISPLAY", sock, 1);
245 _log(stderr, "WAYLAND_DISPLAY=%s\n", sock);
246
247 /* signals */
248 signal(SIGINT, sig_handler);
249 signal(SIGTERM, sig_handler);
250 signal(SIGQUIT, sig_handler);
251}
252
253static void setup_binds(void)
254{
255 for (size_t i = 0; i < nbinds; i++) {
256 const struct bind* b = &binds[i];
257 swc_add_binding(b->type, b->mods, b->ksym, b->fn, (void*)&b->arg);
258 }
259}
260
261static void sync_window_visibility(void)
262{
263 struct client* c;
264
265 wl_list_for_each(c, &wm.clients, link) {
266 if (c->ws == wm.ws)
267 swc_window_show(c->win);
268 else
269 swc_window_hide(c->win);
270 }
271}
272
273/* focus next/prev helpers */
274static void focus_walk_direction(bool forward)
275{
276 if (wl_list_empty(&wm.clients)) return;
277
278 if (!is_ws_client(wm.sel_client, wm.sel_screen)) {
279 focus(get_focus_candidate(wm.sel_screen));
280 return;
281 }
282
283 struct wl_list *start =
284 forward ? wm.sel_client->link.next
285 : wm.sel_client->link.prev;
286
287 struct wl_list *it = start;
288 struct client *c = NULL;
289
290 do {
291 if (it == &wm.clients)
292 it = forward ? wm.clients.next : wm.clients.prev;
293
294 if (it == &wm.clients) break;
295
296 c = wl_container_of(it, c, link);
297 if (is_ws_client(c, wm.sel_screen)) {
298 focus(c);
299 return;
300 }
301
302 it = forward ? it->next : it->prev;
303 } while (it != start);
304
305 focus(get_focus_candidate(wm.sel_screen));
306}
307
308void focus_next(void* data, uint32_t time, uint32_t value, uint32_t state)
309{
310 (void)data; BIND_EVENT(time, value, state);
311 focus_walk_direction(true);
312}
313
314void kill_sel(void* data, uint32_t time, uint32_t value, uint32_t state)
315{
316 (void)data; BIND_EVENT(time, value, state);
317
318 if (wm.sel_client)
319 swc_window_close(wm.sel_client->win);
320}
321
322void fullscreen(void* data, uint32_t time, uint32_t value, uint32_t state)
323{
324 (void)data; BIND_EVENT(time, value, state);
325 if (!wm.sel_client) return;
326
327 struct client *c = wm.sel_client;
328 struct swc_rectangle geom;
329
330 if (c->fullscreen) {
331 c->fullscreen = false;
332 swc_window_set_stacked(c->win);
333 apply_decor(c, true);
334
335 if (c->w > 0 && c->h > 0) {
336 geom = (struct swc_rectangle){ c->x, c->y, c->w, c->h };
337 swc_window_set_geometry(c->win, &geom);
338 }
339 return;
340 }
341
342 if (!c->scr || !c->scr->scr) return;
343
344 if (get_geometry(c, &geom)) {
345 c->x = geom.x;
346 c->y = geom.y;
347 c->w = geom.width;
348 c->h = geom.height;
349 }
350
351 c->fullscreen = true;
352 apply_decor(c, true);
353 swc_window_set_fullscreen(c->win, c->scr->scr);
354}
355
356void kb_move_x(void* data, uint32_t time, uint32_t value, uint32_t state)
357{
358 BIND_ACTION(data, time, value, state);
359 adjust_geom(wm.sel_client, a->i, 0, 0, 0);
360}
361
362void kb_move_y(void* data, uint32_t time, uint32_t value, uint32_t state)
363{
364 BIND_ACTION(data, time, value, state);
365 adjust_geom(wm.sel_client, 0, a->i, 0, 0);
366}
367
368void kb_resize_width(void* data, uint32_t time, uint32_t value, uint32_t state)
369{
370 BIND_ACTION(data, time, value, state);
371 adjust_geom(wm.sel_client, 0, 0, a->i, 0);
372}
373
374void kb_resize_height(void* data, uint32_t time, uint32_t value, uint32_t state)
375{
376 BIND_ACTION(data, time, value, state);
377 adjust_geom(wm.sel_client, 0, 0, 0, a->i);
378}
379
380static void apply_grid_geometry(struct client *c, float x_frac, float w_frac)
381{
382 if (!c || !c->scr || !c->scr->scr) return;
383
384 struct swc_rectangle geom;
385 struct swc_rectangle *ug = &c->scr->scr->usable_geometry;
386
387 geom.x = ug->x + (ug->width * x_frac);
388 geom.y = ug->y;
389 geom.width = ug->width * w_frac;
390 geom.height = ug->height;
391
392 swc_window_set_geometry(c->win, &geom);
393}
394
395void center_window(void* data, uint32_t time, uint32_t value, uint32_t state)
396{
397 (void)data; BIND_EVENT(time, value, state);
398 if (!wm.sel_client->scr || !wm.sel_client->scr->scr) return;
399
400 struct swc_rectangle geom;
401 if (!get_geometry(wm.sel_client, &geom)) return;
402
403 struct swc_rectangle *ug = &wm.sel_client->scr->scr->usable_geometry;
404 geom.x = ug->x + (ug->width - geom.width) / 2;
405 geom.y = ug->y + (ug->height - geom.height) / 2;
406
407 swc_window_set_geometry(wm.sel_client->win, &geom);
408}
409
410void snap_left_half(void* data, uint32_t time, uint32_t value, uint32_t state)
411{
412 (void)data; BIND_EVENT(time, value, state);
413 apply_grid_geometry(wm.sel_client, 0.0f, 0.5f);
414}
415
416void snap_right_half(void* data, uint32_t time, uint32_t value, uint32_t state)
417{
418 (void)data; BIND_EVENT(time, value, state);
419 apply_grid_geometry(wm.sel_client, 0.5f, 0.5f);
420}
421
422void new_screen(struct swc_screen* scr)
423{
424 struct screen* s = calloc(1, sizeof(*s));
425 if (!s) die(EXIT_FAILURE, "new screen calloc failed");
426
427 s->scr = scr;
428 wl_list_insert(&wm.screens, &s->link);
429
430 if (!wm.sel_screen) wm.sel_screen = s;
431
432 swc_screen_set_handler(scr, &screen_handler, s);
433 _log(stderr, "new_screen=%p\n", (void*)scr);
434}
435
436void new_window(struct swc_window* win)
437{
438 struct client* c = calloc(1, sizeof(*c));
439 if (!c) die(EXIT_FAILURE, "calloc client failed");
440
441 win->motion_throttle_ms = 1000 / cfg.motion_throttle_hz;
442 win->min_width = 1;
443 win->min_height = 1;
444
445 c->win = win;
446 c->scr = wm.sel_screen;
447 c->ws = wm.ws;
448
449 wl_list_insert(&wm.clients, &c->link);
450 swc_window_set_handler(win, &window_handler, c);
451 swc_window_set_stacked(win);
452 apply_decor(c, false);
453
454 int32_t cx = 0, cy = 0;
455 if (swc_cursor_position(&cx, &cy))
456 swc_window_set_position(win, cx / 256, cy / 256);
457
458 swc_window_show(win);
459 focus(c);
460
461 _log(stderr, "new_window=%p\n", (void*)win);
462}
463
464void new_device(struct libinput_device* dev)
465{
466 (void)dev;
467}
468
469void quit(void* data, uint32_t time, uint32_t value, uint32_t state)
470{
471 (void)data; BIND_EVENT(time, value, state);
472 wl_display_terminate(wm.dpy);
473}
474
475void spawn(void* data, uint32_t time, uint32_t value, uint32_t state)
476{
477 BIND_EVENT(time, value, state);
478
479 pid_t pid = fork();
480 if (pid == 0) {
481 setsid();
482 if (fork() == 0) {
483 union arg* a = data;
484 char* const* cmd = (char* const*)a->v;
485
486 execvp(cmd[0], cmd);
487 _exit(127);
488 }
489 _exit(0);
490 } else if (pid > 0) {
491 waitpid(pid, NULL, 0);
492 }
493}
494
495void workspace_goto(void* data, uint32_t time, uint32_t value, uint32_t state)
496{
497 union arg* a = data;
498 BIND_EVENT(time, value, state);
499
500 if (a->ui < 1 || a->ui > 9 || a->ui == wm.ws) return;
501
502 wm.ws = a->ui;
503 sync_window_visibility();
504
505 focus(get_focus_candidate(wm.sel_screen));
506}
507
508void workspace_moveto(void* data, uint32_t time, uint32_t value, uint32_t state)
509{
510 BIND_ACTION(data, time, value, state);
511 if (a->ui < 1 || a->ui > 9 || wm.sel_client->ws == a->ui) return;
512
513 struct client* c = wm.sel_client;
514 c->ws = a->ui;
515
516 if (c->ws == wm.ws)
517 swc_window_show(c->win);
518 else
519 swc_window_hide(c->win);
520
521 focus(get_focus_candidate(wm.sel_screen));
522}
523
524int main(void)
525{
526 setup();
527 wl_display_run(wm.dpy);
528 swc_finalize();
529 wl_display_destroy(wm.dpy);
530 return EXIT_SUCCESS;
531}