diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/yawc.c	Sun Aug 30 12:47:10 2026 -0500
@@ -0,0 +1,648 @@
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "nein_cursor.h"
+#include <swc.h>
+#include <wayland-server.h>
+#include <wayland-util.h>
+#include <xkbcommon/xkbcommon-keysyms.h>
+
+#include "config.h"
+#include "types.h"
+#include "util.h"
+#include "yawc.h"
+
+static void focus(struct client* c);
+static void apply_decor(struct client* c, bool active);
+static struct client* first_client(struct screen* s);
+static bool is_ws_client(const struct client* c, const struct screen* s);
+static void on_screen_destroy(void* data);
+static void on_win_destroy(void* data);
+static void on_win_entered(void* data);
+static void on_win_metadata_changed(void* data);
+static void run_autostart(void);
+static void setup(void);
+static void setup_binds(void);
+static void setup_cursor(void);
+static void sync_window_visibility(void);
+
+struct wm wm;
+const struct swc_manager manager = {
+	.new_screen = new_screen, .new_window = new_window, .new_device = new_device,
+};
+struct swc_window_handler window_handler = {
+	.destroy = on_win_destroy,
+	.title_changed = on_win_metadata_changed,
+	.app_id_changed = on_win_metadata_changed,
+	.entered = on_win_entered,
+};
+struct swc_screen_handler screen_handler = {
+	.destroy = on_screen_destroy,
+};
+
+static void focus(struct client* c)
+{
+	if (wm.sel_client) {
+		swc_window_set_border(
+			wm.sel_client->win,
+			cfg.border_col_normal, cfg.border_width,
+			cfg.border_col_normal_outer, cfg.border_width_outer
+		);
+	}
+
+	if (c) {
+		swc_window_set_border(
+			c->win,
+			cfg.border_col_active, cfg.border_width,
+			cfg.border_col_active_outer, cfg.border_width_outer
+		);
+	}
+
+	swc_window_focus(c ? c->win : NULL);
+	wm.sel_client = c;
+}
+
+static void apply_decor(struct client* c, bool active)
+{
+	(void)active;
+
+	if (!c)
+		return;
+	swc_window_set_decor(c->win, NULL);
+}
+
+static struct client* first_client(struct screen* s)
+{
+	struct client* c;
+
+	wl_list_for_each(c, &wm.clients, link) {
+		if (is_ws_client(c, s))
+			return c;
+	}
+
+	return NULL;
+}
+
+static bool is_ws_client(const struct client* c, const struct screen* s)
+{
+	return c && c->ws == wm.ws && (!s || c->scr == s);
+}
+
+static void on_screen_destroy(void* data)
+{
+	struct screen* s = data;
+
+	if (!s)
+		return;
+
+	wl_list_remove(&s->link);
+
+	if (wm.sel_screen == s) {
+		if (wl_list_empty(&wm.screens))
+			wm.sel_screen = NULL;
+		else
+			wm.sel_screen = wl_container_of(wm.screens.next, wm.sel_screen, link);
+	}
+
+	free(s);
+}
+
+static void on_win_destroy(void* data)
+{
+	struct client* c = data;
+	struct client* next;
+
+	if (!c)
+		return;
+
+	if (wm.grab.active && wm.grab.c == c) {
+		wm.grab.active = false;
+		wm.grab.c = NULL;
+	}
+
+	if (wm.sel_client == c) {
+		wm.sel_client = NULL;
+	}
+
+	wl_list_remove(&c->link);
+	free(c);
+
+	next = first_client(wm.sel_screen);
+	if (!next)
+		next = first_client(NULL);
+	focus(next);
+}
+
+static void on_win_entered(void* data)
+{
+	if (wm.grab.active)
+		return;
+
+	struct client* c = data;
+	if (!is_ws_client(c, NULL))
+		return;
+
+	focus(c);
+}
+
+static void on_win_metadata_changed(void* data)
+{
+	struct client* c = data;
+
+	if (!c)
+		return;
+
+	apply_decor(c, c == wm.sel_client);
+}
+static void setup_cursor(void)
+{
+	const struct nein_cursor_meta *arrow = &nein_cursor_metadata[NEIN_CURSOR_WHITEARROW];
+
+	swc_set_cursor_mode(SWC_CURSOR_MODE_COMPOSITOR);
+	swc_set_cursor_image(SWC_CURSOR_DEFAULT,
+		&nein_cursor_data[arrow->offset],
+		arrow->width, arrow->height,
+		arrow->hotspot_x, arrow->hotspot_y);
+}
+static void setup(void)
+{
+	/* display */
+	wm.dpy = wl_display_create();
+	if (!wm.dpy)
+		die(EXIT_FAILURE, "wl_display_create failed");
+
+	/* variables */
+	wl_list_init(&wm.screens);
+	wl_list_init(&wm.clients);
+	wm.sel_client = NULL;
+	wm.sel_screen = NULL;
+	wm.grab.active = false;
+	wm.grab.resize = false;
+	wm.grab.c = NULL;
+	wm.ws = 1;
+
+	/* event loop */
+	wm.ev_loop = wl_display_get_event_loop(wm.dpy);
+	if (!swc_initialize(wm.dpy, wm.ev_loop, &manager))
+		die(EXIT_FAILURE, "swc_initialize failed\n");
+
+	swc_repeat_rate = cfg.repeat_rate;
+	swc_repeat_delay = cfg.repeat_delay;
+	 setup_cursor();
+	setup_binds();
+
+	/* display socket */
+	const char* sock;
+	sock = wl_display_add_socket_auto(wm.dpy);
+	if (!sock)
+		die(EXIT_FAILURE, "wl_display_add_socket_auto failed\n");
+	setenv("WAYLAND_DISPLAY", sock, 1);
+	_log(stderr, "WAYLAND_DISPLAY=%s\n", sock);
+
+	run_autostart();
+
+	/* signals */
+	signal(SIGINT,  sig_handler);
+	signal(SIGTERM, sig_handler);
+	signal(SIGQUIT, sig_handler);
+}
+
+static void run_autostart(void)
+{
+	const char* shell = getenv("SHELL");
+
+	if (!shell || !shell[0])
+		shell = "/bin/sh";
+
+	for (size_t i = 0; i < LENGTH(autostart); i++) {
+		const char* cmd = autostart[i];
+		pid_t pid;
+
+		if (!cmd || !cmd[0])
+			continue;
+
+		pid = fork();
+		if (pid < 0) {
+			_log(stderr, "autostart fork failed for: %s\n", cmd);
+			continue;
+		}
+
+		if (pid == 0) {
+			setsid();
+			execl(shell, shell, "-c", cmd, (char*)NULL);
+			_exit(127);
+		}
+	}
+}
+
+static void setup_binds(void)
+{
+	for (size_t i = 0; i < LENGTH(binds); i++) {
+		const struct bind* b = &binds[i];
+		swc_add_binding(b->type, b->mods, b->ksym, b->fn, (void*)&b->arg);
+	}
+}
+
+static void sync_window_visibility(void)
+{
+	struct client* c;
+
+	wl_list_for_each(c, &wm.clients, link) {
+		if (c->ws == wm.ws)
+			swc_window_show(c->win);
+		else
+			swc_window_hide(c->win);
+	}
+}
+
+void focus_next(void* data, uint32_t time, uint32_t value, uint32_t state)
+{
+	(void)data;
+	(void)time;
+	(void)value;
+
+	struct client* c = NULL;
+
+	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
+		return;
+
+	if (wl_list_empty(&wm.clients))
+		return;
+
+	if (!wm.sel_client || !is_ws_client(wm.sel_client, wm.sel_screen)) {
+		c = first_client(wm.sel_screen);
+		if (!c)
+			c = first_client(NULL);
+		focus(c);
+		return;
+	}
+
+	struct wl_list* start = wm.sel_client->link.next;
+	struct wl_list* it = start;
+
+	do {
+		if (it == &wm.clients)
+			it = wm.clients.next;
+		if (it == &wm.clients)
+			break;
+
+		c = wl_container_of(it, c, link);
+		if (is_ws_client(c, wm.sel_screen)) {
+			focus(c);
+			return;
+		}
+		it = it->next;
+	} while (it != start);
+
+	c = first_client(wm.sel_screen);
+	if (!c)
+		c = first_client(NULL);
+	focus(c);
+}
+
+void focus_prev(void* data, uint32_t time, uint32_t value, uint32_t state)
+{
+	struct client* c = NULL;
+
+	(void)data;
+	(void)time;
+	(void)value;
+
+	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
+		return;
+
+	if (wl_list_empty(&wm.clients))
+		return;
+
+	if (!wm.sel_client || !is_ws_client(wm.sel_client, wm.sel_screen)) {
+		c = first_client(wm.sel_screen);
+		if (!c)
+			c = first_client(NULL);
+		focus(c);
+		return;
+	}
+
+	struct wl_list* start = wm.sel_client->link.prev;
+	struct wl_list* it = start;
+
+	do {
+		if (it == &wm.clients)
+			it = wm.clients.prev;
+		if (it == &wm.clients)
+			break;
+
+		c = wl_container_of(it, c, link);
+		if (is_ws_client(c, wm.sel_screen)) {
+			focus(c);
+			return;
+		}
+		it = it->prev;
+	} while (it != start);
+
+	c = first_client(wm.sel_screen);
+	if (!c)
+		c = first_client(NULL);
+	focus(c);
+}
+
+void kill_sel(void* data, uint32_t time, uint32_t value, uint32_t state)
+{
+	(void)data;
+	(void)time;
+	(void)value;
+
+	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
+		return;
+
+	if (!wm.sel_client)
+		return;
+
+	swc_window_close(wm.sel_client->win);
+}
+
+void fullscreen(void* data, uint32_t time, uint32_t value, uint32_t state)
+{
+	struct swc_rectangle geom;
+
+	(void)data;
+	(void)time;
+	(void)value;
+
+	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
+		return;
+
+	if (!wm.sel_client)
+		return;
+
+	if (wm.sel_client->fullscreen) {
+		wm.sel_client->fullscreen = false;
+		swc_window_set_stacked(wm.sel_client->win);
+		apply_decor(wm.sel_client, true);
+
+		if (wm.sel_client->w > 0 && wm.sel_client->h > 0) {
+			geom.x = wm.sel_client->x;
+			geom.y = wm.sel_client->y;
+			geom.width = wm.sel_client->w;
+			geom.height = wm.sel_client->h;
+			swc_window_set_geometry(wm.sel_client->win, &geom);
+		}
+		return;
+	}
+
+	if (!wm.sel_client->scr || !wm.sel_client->scr->scr)
+		return;
+
+	if (swc_window_get_geometry(wm.sel_client->win, &geom)) {
+		wm.sel_client->x = geom.x;
+		wm.sel_client->y = geom.y;
+		wm.sel_client->w = geom.width;
+		wm.sel_client->h = geom.height;
+	}
+
+	wm.sel_client->fullscreen = true;
+	apply_decor(wm.sel_client, true);
+	swc_window_set_fullscreen(wm.sel_client->win, wm.sel_client->scr->scr);
+}
+
+void mouse_move(void* data, uint32_t time, uint32_t value, uint32_t state)
+{
+	(void)data;
+	(void)time;
+	(void)value;
+
+	if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
+		if (!wm.sel_client)
+			return;
+
+		if (!wm.sel_client->floating) {
+			wm.sel_client->floating = true;
+			swc_window_set_stacked(wm.sel_client->win);
+		}
+
+		wm.grab.active = true;
+		wm.grab.resize = false;
+		wm.grab.c = wm.sel_client;
+
+		swc_window_begin_move(wm.grab.c->win);
+	}
+	else {
+		if (!wm.grab.active || wm.grab.resize || !wm.grab.c)
+			return;
+
+		swc_window_end_move(wm.grab.c->win);
+
+		wm.grab.active = false;
+		wm.grab.c = NULL;
+	}
+}
+
+void mouse_resize(void* data, uint32_t time, uint32_t value, uint32_t state)
+{
+	(void)data;
+	(void)time;
+	(void)value;
+
+	if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
+		if (!wm.sel_client)
+			return;
+
+		if (!wm.sel_client->floating) {
+			wm.sel_client->floating = true;
+			swc_window_set_stacked(wm.sel_client->win);
+		}
+
+		wm.grab.active = true;
+		wm.grab.resize = true;
+		wm.grab.c = wm.sel_client;
+
+		swc_window_begin_resize(
+			wm.grab.c->win,
+			SWC_WINDOW_EDGE_RIGHT | SWC_WINDOW_EDGE_BOTTOM
+		);
+	}
+	else {
+		if (!wm.grab.active || !wm.grab.resize || !wm.grab.c)
+			return;
+
+		swc_window_end_resize(wm.grab.c->win);
+
+		wm.grab.active = false;
+		wm.grab.c = NULL;
+	}
+}
+
+void new_screen(struct swc_screen* scr)
+{
+	struct screen* s;
+
+	s = malloc(sizeof(*s));
+	if (!s)
+		die(EXIT_FAILURE, "new screen calloc failed");
+
+	s->scr = scr;
+
+	s->x = 0;
+	s->y = 0;
+	s->w = 0;
+	s->h = 0;
+
+	wl_list_insert(&wm.screens, &s->link);
+
+	if (!wm.sel_screen)
+		wm.sel_screen = s;
+
+	swc_screen_set_handler(scr, &screen_handler, s);
+
+	_log(stderr, "new_screen=%p\n", (void*)scr);
+}
+
+void new_window(struct swc_window* win)
+{
+	struct client* c;
+
+	c = malloc(sizeof(*c));
+	if (!c)
+		die(EXIT_FAILURE, "malloc client failed");
+
+	win->motion_throttle_ms = 1000 / cfg.motion_throttle_hz;
+	win->min_width = 1;
+	win->min_height = 1;
+	win->max_width = 0;
+	win->max_height = 0;
+
+	c->win = win;
+	c->scr = wm.sel_screen;
+	c->mapped = 0;
+	c->floating = true;
+	c->fullscreen = 0;
+	c->ws = wm.ws;
+	c->x = 0;
+	c->y = 0;
+	c->w = 0;
+	c->h = 0;
+
+	wl_list_insert(&wm.clients, &c->link);
+	swc_window_set_handler(win, &window_handler, c);
+	swc_window_set_stacked(win);
+	apply_decor(c, false);
+	{
+		/* swc reports cursor coordinates in wl_fixed_t (24.8) units */
+		int32_t cx_fixed = 0;
+		int32_t cy_fixed = 0;
+
+		if (swc_cursor_position(&cx_fixed, &cy_fixed)) {
+			int32_t cx = cx_fixed / 256;
+			int32_t cy = cy_fixed / 256;
+
+			swc_window_set_position(
+				win,
+				cx - (int32_t)(cfg.spawn_w / 2),
+				cy - (int32_t)(cfg.spawn_h / 2)
+			);
+		}
+	}
+	swc_window_show(win);
+	focus(c);
+
+	_log(stderr, "new_window=%p\n", (void*)win);
+}
+
+void new_device(struct libinput_device* dev)
+{
+	(void)dev;
+}
+
+void quit(void* data, uint32_t time, uint32_t value, uint32_t state)
+{
+	(void)data;
+	(void)time;
+	(void)value;
+	(void)state;
+
+	wl_display_terminate(wm.dpy);
+}
+
+void spawn(void* data, uint32_t time, uint32_t value, uint32_t state)
+{
+	union arg* a = data;
+	char* const* cmd = (char* const*)a->v;
+
+	(void)time;
+	(void)value;
+
+	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
+		return;
+
+	if (fork() == 0) {
+		execvp(cmd[0], cmd);
+		_exit(127);
+	}
+}
+
+void workspace_goto(void* data, uint32_t time, uint32_t value, uint32_t state)
+{
+	union arg* a = data;
+	struct client* c;
+
+	(void)time;
+	(void)value;
+
+	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
+		return;
+
+	if (a->ui < 1 || a->ui > 9 || a->ui == wm.ws)
+		return;
+
+	wm.ws = a->ui;
+	sync_window_visibility();
+
+	c = first_client(wm.sel_screen);
+	if (!c)
+		c = first_client(NULL);
+	focus(c);
+}
+
+void workspace_moveto(void* data, uint32_t time, uint32_t value, uint32_t state)
+{
+	union arg* a = data;
+	struct client* c;
+	struct client* next;
+
+	(void)time;
+	(void)value;
+
+	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
+		return;
+
+	if (!wm.sel_client)
+		return;
+
+	if (a->ui < 1 || a->ui > 9)
+		return;
+
+	c = wm.sel_client;
+	if (c->ws == a->ui)
+		return;
+
+	c->ws = a->ui;
+	if (c->ws == wm.ws)
+		swc_window_show(c->win);
+	else
+		swc_window_hide(c->win);
+
+	next = first_client(wm.sel_screen);
+	if (!next)
+		next = first_client(NULL);
+	focus(next);
+}
+
+int main(void)
+{
+	setup();
+	wl_display_run(wm.dpy);
+	swc_finalize();
+	wl_display_destroy(wm.dpy);
+	return EXIT_SUCCESS;
+}