comparison src/yawc.c @ 0:f0372d3356b4 default tip

initial import
author Yasuri <linuxoverwindows@charliesmp.net>
date Sun, 30 Aug 2026 12:47:10 -0500
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:f0372d3356b4
1 #include <signal.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <unistd.h>
6 #include "nein_cursor.h"
7 #include <swc.h>
8 #include <wayland-server.h>
9 #include <wayland-util.h>
10 #include <xkbcommon/xkbcommon-keysyms.h>
11
12 #include "config.h"
13 #include "types.h"
14 #include "util.h"
15 #include "yawc.h"
16
17 static void focus(struct client* c);
18 static void apply_decor(struct client* c, bool active);
19 static struct client* first_client(struct screen* s);
20 static bool is_ws_client(const struct client* c, const struct screen* s);
21 static void on_screen_destroy(void* data);
22 static void on_win_destroy(void* data);
23 static void on_win_entered(void* data);
24 static void on_win_metadata_changed(void* data);
25 static void run_autostart(void);
26 static void setup(void);
27 static void setup_binds(void);
28 static void setup_cursor(void);
29 static void sync_window_visibility(void);
30
31 struct wm wm;
32 const struct swc_manager manager = {
33 .new_screen = new_screen, .new_window = new_window, .new_device = new_device,
34 };
35 struct swc_window_handler window_handler = {
36 .destroy = on_win_destroy,
37 .title_changed = on_win_metadata_changed,
38 .app_id_changed = on_win_metadata_changed,
39 .entered = on_win_entered,
40 };
41 struct swc_screen_handler screen_handler = {
42 .destroy = on_screen_destroy,
43 };
44
45 static void focus(struct client* c)
46 {
47 if (wm.sel_client) {
48 swc_window_set_border(
49 wm.sel_client->win,
50 cfg.border_col_normal, cfg.border_width,
51 cfg.border_col_normal_outer, cfg.border_width_outer
52 );
53 }
54
55 if (c) {
56 swc_window_set_border(
57 c->win,
58 cfg.border_col_active, cfg.border_width,
59 cfg.border_col_active_outer, cfg.border_width_outer
60 );
61 }
62
63 swc_window_focus(c ? c->win : NULL);
64 wm.sel_client = c;
65 }
66
67 static void apply_decor(struct client* c, bool active)
68 {
69 (void)active;
70
71 if (!c)
72 return;
73 swc_window_set_decor(c->win, NULL);
74 }
75
76 static struct client* first_client(struct screen* s)
77 {
78 struct client* c;
79
80 wl_list_for_each(c, &wm.clients, link) {
81 if (is_ws_client(c, s))
82 return c;
83 }
84
85 return NULL;
86 }
87
88 static bool is_ws_client(const struct client* c, const struct screen* s)
89 {
90 return c && c->ws == wm.ws && (!s || c->scr == s);
91 }
92
93 static void on_screen_destroy(void* data)
94 {
95 struct screen* s = data;
96
97 if (!s)
98 return;
99
100 wl_list_remove(&s->link);
101
102 if (wm.sel_screen == s) {
103 if (wl_list_empty(&wm.screens))
104 wm.sel_screen = NULL;
105 else
106 wm.sel_screen = wl_container_of(wm.screens.next, wm.sel_screen, link);
107 }
108
109 free(s);
110 }
111
112 static void on_win_destroy(void* data)
113 {
114 struct client* c = data;
115 struct client* next;
116
117 if (!c)
118 return;
119
120 if (wm.grab.active && wm.grab.c == c) {
121 wm.grab.active = false;
122 wm.grab.c = NULL;
123 }
124
125 if (wm.sel_client == c) {
126 wm.sel_client = NULL;
127 }
128
129 wl_list_remove(&c->link);
130 free(c);
131
132 next = first_client(wm.sel_screen);
133 if (!next)
134 next = first_client(NULL);
135 focus(next);
136 }
137
138 static void on_win_entered(void* data)
139 {
140 if (wm.grab.active)
141 return;
142
143 struct client* c = data;
144 if (!is_ws_client(c, NULL))
145 return;
146
147 focus(c);
148 }
149
150 static void on_win_metadata_changed(void* data)
151 {
152 struct client* c = data;
153
154 if (!c)
155 return;
156
157 apply_decor(c, c == wm.sel_client);
158 }
159 static void setup_cursor(void)
160 {
161 const struct nein_cursor_meta *arrow = &nein_cursor_metadata[NEIN_CURSOR_WHITEARROW];
162
163 swc_set_cursor_mode(SWC_CURSOR_MODE_COMPOSITOR);
164 swc_set_cursor_image(SWC_CURSOR_DEFAULT,
165 &nein_cursor_data[arrow->offset],
166 arrow->width, arrow->height,
167 arrow->hotspot_x, arrow->hotspot_y);
168 }
169 static void setup(void)
170 {
171 /* display */
172 wm.dpy = wl_display_create();
173 if (!wm.dpy)
174 die(EXIT_FAILURE, "wl_display_create failed");
175
176 /* variables */
177 wl_list_init(&wm.screens);
178 wl_list_init(&wm.clients);
179 wm.sel_client = NULL;
180 wm.sel_screen = NULL;
181 wm.grab.active = false;
182 wm.grab.resize = false;
183 wm.grab.c = NULL;
184 wm.ws = 1;
185
186 /* event loop */
187 wm.ev_loop = wl_display_get_event_loop(wm.dpy);
188 if (!swc_initialize(wm.dpy, wm.ev_loop, &manager))
189 die(EXIT_FAILURE, "swc_initialize failed\n");
190
191 swc_repeat_rate = cfg.repeat_rate;
192 swc_repeat_delay = cfg.repeat_delay;
193 setup_cursor();
194 setup_binds();
195
196 /* display socket */
197 const char* sock;
198 sock = wl_display_add_socket_auto(wm.dpy);
199 if (!sock)
200 die(EXIT_FAILURE, "wl_display_add_socket_auto failed\n");
201 setenv("WAYLAND_DISPLAY", sock, 1);
202 _log(stderr, "WAYLAND_DISPLAY=%s\n", sock);
203
204 run_autostart();
205
206 /* signals */
207 signal(SIGINT, sig_handler);
208 signal(SIGTERM, sig_handler);
209 signal(SIGQUIT, sig_handler);
210 }
211
212 static void run_autostart(void)
213 {
214 const char* shell = getenv("SHELL");
215
216 if (!shell || !shell[0])
217 shell = "/bin/sh";
218
219 for (size_t i = 0; i < LENGTH(autostart); i++) {
220 const char* cmd = autostart[i];
221 pid_t pid;
222
223 if (!cmd || !cmd[0])
224 continue;
225
226 pid = fork();
227 if (pid < 0) {
228 _log(stderr, "autostart fork failed for: %s\n", cmd);
229 continue;
230 }
231
232 if (pid == 0) {
233 setsid();
234 execl(shell, shell, "-c", cmd, (char*)NULL);
235 _exit(127);
236 }
237 }
238 }
239
240 static void setup_binds(void)
241 {
242 for (size_t i = 0; i < LENGTH(binds); i++) {
243 const struct bind* b = &binds[i];
244 swc_add_binding(b->type, b->mods, b->ksym, b->fn, (void*)&b->arg);
245 }
246 }
247
248 static void sync_window_visibility(void)
249 {
250 struct client* c;
251
252 wl_list_for_each(c, &wm.clients, link) {
253 if (c->ws == wm.ws)
254 swc_window_show(c->win);
255 else
256 swc_window_hide(c->win);
257 }
258 }
259
260 void focus_next(void* data, uint32_t time, uint32_t value, uint32_t state)
261 {
262 (void)data;
263 (void)time;
264 (void)value;
265
266 struct client* c = NULL;
267
268 if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
269 return;
270
271 if (wl_list_empty(&wm.clients))
272 return;
273
274 if (!wm.sel_client || !is_ws_client(wm.sel_client, wm.sel_screen)) {
275 c = first_client(wm.sel_screen);
276 if (!c)
277 c = first_client(NULL);
278 focus(c);
279 return;
280 }
281
282 struct wl_list* start = wm.sel_client->link.next;
283 struct wl_list* it = start;
284
285 do {
286 if (it == &wm.clients)
287 it = wm.clients.next;
288 if (it == &wm.clients)
289 break;
290
291 c = wl_container_of(it, c, link);
292 if (is_ws_client(c, wm.sel_screen)) {
293 focus(c);
294 return;
295 }
296 it = it->next;
297 } while (it != start);
298
299 c = first_client(wm.sel_screen);
300 if (!c)
301 c = first_client(NULL);
302 focus(c);
303 }
304
305 void focus_prev(void* data, uint32_t time, uint32_t value, uint32_t state)
306 {
307 struct client* c = NULL;
308
309 (void)data;
310 (void)time;
311 (void)value;
312
313 if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
314 return;
315
316 if (wl_list_empty(&wm.clients))
317 return;
318
319 if (!wm.sel_client || !is_ws_client(wm.sel_client, wm.sel_screen)) {
320 c = first_client(wm.sel_screen);
321 if (!c)
322 c = first_client(NULL);
323 focus(c);
324 return;
325 }
326
327 struct wl_list* start = wm.sel_client->link.prev;
328 struct wl_list* it = start;
329
330 do {
331 if (it == &wm.clients)
332 it = wm.clients.prev;
333 if (it == &wm.clients)
334 break;
335
336 c = wl_container_of(it, c, link);
337 if (is_ws_client(c, wm.sel_screen)) {
338 focus(c);
339 return;
340 }
341 it = it->prev;
342 } while (it != start);
343
344 c = first_client(wm.sel_screen);
345 if (!c)
346 c = first_client(NULL);
347 focus(c);
348 }
349
350 void kill_sel(void* data, uint32_t time, uint32_t value, uint32_t state)
351 {
352 (void)data;
353 (void)time;
354 (void)value;
355
356 if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
357 return;
358
359 if (!wm.sel_client)
360 return;
361
362 swc_window_close(wm.sel_client->win);
363 }
364
365 void fullscreen(void* data, uint32_t time, uint32_t value, uint32_t state)
366 {
367 struct swc_rectangle geom;
368
369 (void)data;
370 (void)time;
371 (void)value;
372
373 if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
374 return;
375
376 if (!wm.sel_client)
377 return;
378
379 if (wm.sel_client->fullscreen) {
380 wm.sel_client->fullscreen = false;
381 swc_window_set_stacked(wm.sel_client->win);
382 apply_decor(wm.sel_client, true);
383
384 if (wm.sel_client->w > 0 && wm.sel_client->h > 0) {
385 geom.x = wm.sel_client->x;
386 geom.y = wm.sel_client->y;
387 geom.width = wm.sel_client->w;
388 geom.height = wm.sel_client->h;
389 swc_window_set_geometry(wm.sel_client->win, &geom);
390 }
391 return;
392 }
393
394 if (!wm.sel_client->scr || !wm.sel_client->scr->scr)
395 return;
396
397 if (swc_window_get_geometry(wm.sel_client->win, &geom)) {
398 wm.sel_client->x = geom.x;
399 wm.sel_client->y = geom.y;
400 wm.sel_client->w = geom.width;
401 wm.sel_client->h = geom.height;
402 }
403
404 wm.sel_client->fullscreen = true;
405 apply_decor(wm.sel_client, true);
406 swc_window_set_fullscreen(wm.sel_client->win, wm.sel_client->scr->scr);
407 }
408
409 void mouse_move(void* data, uint32_t time, uint32_t value, uint32_t state)
410 {
411 (void)data;
412 (void)time;
413 (void)value;
414
415 if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
416 if (!wm.sel_client)
417 return;
418
419 if (!wm.sel_client->floating) {
420 wm.sel_client->floating = true;
421 swc_window_set_stacked(wm.sel_client->win);
422 }
423
424 wm.grab.active = true;
425 wm.grab.resize = false;
426 wm.grab.c = wm.sel_client;
427
428 swc_window_begin_move(wm.grab.c->win);
429 }
430 else {
431 if (!wm.grab.active || wm.grab.resize || !wm.grab.c)
432 return;
433
434 swc_window_end_move(wm.grab.c->win);
435
436 wm.grab.active = false;
437 wm.grab.c = NULL;
438 }
439 }
440
441 void mouse_resize(void* data, uint32_t time, uint32_t value, uint32_t state)
442 {
443 (void)data;
444 (void)time;
445 (void)value;
446
447 if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
448 if (!wm.sel_client)
449 return;
450
451 if (!wm.sel_client->floating) {
452 wm.sel_client->floating = true;
453 swc_window_set_stacked(wm.sel_client->win);
454 }
455
456 wm.grab.active = true;
457 wm.grab.resize = true;
458 wm.grab.c = wm.sel_client;
459
460 swc_window_begin_resize(
461 wm.grab.c->win,
462 SWC_WINDOW_EDGE_RIGHT | SWC_WINDOW_EDGE_BOTTOM
463 );
464 }
465 else {
466 if (!wm.grab.active || !wm.grab.resize || !wm.grab.c)
467 return;
468
469 swc_window_end_resize(wm.grab.c->win);
470
471 wm.grab.active = false;
472 wm.grab.c = NULL;
473 }
474 }
475
476 void new_screen(struct swc_screen* scr)
477 {
478 struct screen* s;
479
480 s = malloc(sizeof(*s));
481 if (!s)
482 die(EXIT_FAILURE, "new screen calloc failed");
483
484 s->scr = scr;
485
486 s->x = 0;
487 s->y = 0;
488 s->w = 0;
489 s->h = 0;
490
491 wl_list_insert(&wm.screens, &s->link);
492
493 if (!wm.sel_screen)
494 wm.sel_screen = s;
495
496 swc_screen_set_handler(scr, &screen_handler, s);
497
498 _log(stderr, "new_screen=%p\n", (void*)scr);
499 }
500
501 void new_window(struct swc_window* win)
502 {
503 struct client* c;
504
505 c = malloc(sizeof(*c));
506 if (!c)
507 die(EXIT_FAILURE, "malloc client failed");
508
509 win->motion_throttle_ms = 1000 / cfg.motion_throttle_hz;
510 win->min_width = 1;
511 win->min_height = 1;
512 win->max_width = 0;
513 win->max_height = 0;
514
515 c->win = win;
516 c->scr = wm.sel_screen;
517 c->mapped = 0;
518 c->floating = true;
519 c->fullscreen = 0;
520 c->ws = wm.ws;
521 c->x = 0;
522 c->y = 0;
523 c->w = 0;
524 c->h = 0;
525
526 wl_list_insert(&wm.clients, &c->link);
527 swc_window_set_handler(win, &window_handler, c);
528 swc_window_set_stacked(win);
529 apply_decor(c, false);
530 {
531 /* swc reports cursor coordinates in wl_fixed_t (24.8) units */
532 int32_t cx_fixed = 0;
533 int32_t cy_fixed = 0;
534
535 if (swc_cursor_position(&cx_fixed, &cy_fixed)) {
536 int32_t cx = cx_fixed / 256;
537 int32_t cy = cy_fixed / 256;
538
539 swc_window_set_position(
540 win,
541 cx - (int32_t)(cfg.spawn_w / 2),
542 cy - (int32_t)(cfg.spawn_h / 2)
543 );
544 }
545 }
546 swc_window_show(win);
547 focus(c);
548
549 _log(stderr, "new_window=%p\n", (void*)win);
550 }
551
552 void new_device(struct libinput_device* dev)
553 {
554 (void)dev;
555 }
556
557 void quit(void* data, uint32_t time, uint32_t value, uint32_t state)
558 {
559 (void)data;
560 (void)time;
561 (void)value;
562 (void)state;
563
564 wl_display_terminate(wm.dpy);
565 }
566
567 void spawn(void* data, uint32_t time, uint32_t value, uint32_t state)
568 {
569 union arg* a = data;
570 char* const* cmd = (char* const*)a->v;
571
572 (void)time;
573 (void)value;
574
575 if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
576 return;
577
578 if (fork() == 0) {
579 execvp(cmd[0], cmd);
580 _exit(127);
581 }
582 }
583
584 void workspace_goto(void* data, uint32_t time, uint32_t value, uint32_t state)
585 {
586 union arg* a = data;
587 struct client* c;
588
589 (void)time;
590 (void)value;
591
592 if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
593 return;
594
595 if (a->ui < 1 || a->ui > 9 || a->ui == wm.ws)
596 return;
597
598 wm.ws = a->ui;
599 sync_window_visibility();
600
601 c = first_client(wm.sel_screen);
602 if (!c)
603 c = first_client(NULL);
604 focus(c);
605 }
606
607 void workspace_moveto(void* data, uint32_t time, uint32_t value, uint32_t state)
608 {
609 union arg* a = data;
610 struct client* c;
611 struct client* next;
612
613 (void)time;
614 (void)value;
615
616 if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
617 return;
618
619 if (!wm.sel_client)
620 return;
621
622 if (a->ui < 1 || a->ui > 9)
623 return;
624
625 c = wm.sel_client;
626 if (c->ws == a->ui)
627 return;
628
629 c->ws = a->ui;
630 if (c->ws == wm.ws)
631 swc_window_show(c->win);
632 else
633 swc_window_hide(c->win);
634
635 next = first_client(wm.sel_screen);
636 if (!next)
637 next = first_client(NULL);
638 focus(next);
639 }
640
641 int main(void)
642 {
643 setup();
644 wl_display_run(wm.dpy);
645 swc_finalize();
646 wl_display_destroy(wm.dpy);
647 return EXIT_SUCCESS;
648 }