master ohmu/wmutils / src / opt / mwmd.c
ohmu  ·  2026-07-25
  1/* mwmd - window manager daemon for move/resize */
  2// vibecoded, for educational purposes only
  3#include <xcb/xcb.h>
  4#include <xcb/xcb_aux.h>
  5#include <stdlib.h>
  6#include <stdio.h>
  7#include <err.h>
  8#include <string.h>
  9#include <signal.h>
 10#include <unistd.h>
 11
 12static xcb_connection_t *conn;
 13static xcb_screen_t *scr;
 14static int running = 1;
 15
 16static void
 17cleanup(void)
 18{
 19	if (conn)
 20		xcb_disconnect(conn);
 21}
 22
 23static void
 24signal_handler(int sig)
 25{
 26	(void)sig;
 27	running = 0;
 28}
 29
 30static void
 31init_xcb(void)
 32{
 33	conn = xcb_connect(NULL, NULL);
 34	if (xcb_connection_has_error(conn))
 35		errx(1, "cannot connect to X server");
 36}
 37
 38static void
 39get_screen(void)
 40{
 41	const xcb_setup_t *setup = xcb_get_setup(conn);
 42	xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
 43	scr = iter.data;
 44}
 45
 46static void
 47grab_keys(void)
 48{
 49	// Mod1 = Alt, Mod4 = Super
 50	uint32_t mods[] = { XCB_MOD_MASK_4 };
 51	
 52	for (int i = 0; i < 2; i++) {
 53		// Alt+Left Click = Move
 54		xcb_grab_button(conn, 0, scr->root,
 55				XCB_EVENT_MASK_BUTTON_PRESS
 56				| XCB_EVENT_MASK_BUTTON_RELEASE
 57				| XCB_EVENT_MASK_POINTER_MOTION,
 58				XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC,
 59				scr->root, XCB_NONE,
 60				XCB_BUTTON_INDEX_1, mods[i]);
 61		
 62		// Alt+Right Click = Resize
 63		xcb_grab_button(conn, 0, scr->root,
 64				XCB_EVENT_MASK_BUTTON_PRESS
 65				| XCB_EVENT_MASK_BUTTON_RELEASE
 66				| XCB_EVENT_MASK_POINTER_MOTION,
 67				XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC,
 68				scr->root, XCB_NONE,
 69				XCB_BUTTON_INDEX_3, mods[i]);
 70	}
 71	
 72	xcb_flush(conn);
 73}
 74
 75static xcb_window_t
 76get_window_at_pointer(void)
 77{
 78	xcb_query_pointer_reply_t *ptr;
 79	xcb_window_t child = XCB_NONE;
 80	
 81	ptr = xcb_query_pointer_reply(conn,
 82			xcb_query_pointer(conn, scr->root), NULL);
 83	if (!ptr)
 84		return XCB_NONE;
 85	
 86	child = ptr->child;
 87	free(ptr);
 88	return child;
 89}
 90
 91static void
 92move_window(xcb_window_t win)
 93{
 94	uint32_t values[2];
 95	xcb_get_geometry_reply_t *geom;
 96	xcb_generic_event_t *ev = NULL;
 97	int orig_x, orig_y;
 98	int start_x, start_y;
 99	int button_pressed = 1;
100
101	geom = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL);
102	if (!geom)
103		return;
104
105	orig_x = geom->x;
106	orig_y = geom->y;
107	
108	xcb_query_pointer_reply_t *ptr;
109	ptr = xcb_query_pointer_reply(conn,
110			xcb_query_pointer(conn, scr->root), NULL);
111	if (ptr) {
112		start_x = ptr->root_x;
113		start_y = ptr->root_y;
114		free(ptr);
115	}
116	
117	free(geom);
118
119	while ((ev = xcb_wait_for_event(conn))) {
120		switch (ev->response_type & ~0x80) {
121		case XCB_MOTION_NOTIFY: {
122			if (!button_pressed)
123				break;
124				
125			xcb_motion_notify_event_t *m = (xcb_motion_notify_event_t *)ev;
126			
127			int new_x = orig_x + (m->root_x - start_x);
128			int new_y = orig_y + (m->root_y - start_y);
129			
130			if (new_x < 0) new_x = 0;
131			if (new_y < 0) new_y = 0;
132			if (new_x + 10 > scr->width_in_pixels)
133				new_x = scr->width_in_pixels - 10;
134			if (new_y + 10 > scr->height_in_pixels)
135				new_y = scr->height_in_pixels - 10;
136			
137			values[0] = new_x;
138			values[1] = new_y;
139			
140			xcb_configure_window(conn, win,
141					XCB_CONFIG_WINDOW_X
142					| XCB_CONFIG_WINDOW_Y,
143					values);
144			xcb_flush(conn);
145			break;
146		}
147		case XCB_BUTTON_RELEASE: {
148			xcb_button_release_event_t *b = (xcb_button_release_event_t *)ev;
149			if (b->detail == XCB_BUTTON_INDEX_1 && button_pressed) {
150				button_pressed = 0;
151				free(ev);
152				return;
153			}
154			break;
155		}
156		}
157		free(ev);
158	}
159}
160
161static int
162get_corner(xcb_window_t win, int root_x, int root_y)
163{
164	xcb_get_geometry_reply_t *geom;
165	int corner = 3;
166	
167	geom = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL);
168	if (!geom)
169		return 3;
170	
171	int win_w = geom->width;
172	int win_h = geom->height;
173	int border = geom->border_width;
174	
175	int rx = root_x - geom->x - border;
176	int ry = root_y - geom->y - border;
177	
178	int dist_tl = rx*rx + ry*ry;
179	int dist_tr = (win_w - rx)*(win_w - rx) + ry*ry;
180	int dist_bl = rx*rx + (win_h - ry)*(win_h - ry);
181	int dist_br = (win_w - rx)*(win_w - rx) + (win_h - ry)*(win_h - ry);
182	
183	int min_dist = dist_tl;
184	corner = 0;
185	if (dist_tr < min_dist) { min_dist = dist_tr; corner = 1; }
186	if (dist_bl < min_dist) { min_dist = dist_bl; corner = 2; }
187	if (dist_br < min_dist) { min_dist = dist_br; corner = 3; }
188	
189	free(geom);
190	return corner;
191}
192
193static void
194resize_window(xcb_window_t win)
195{
196	uint32_t values[4];
197	xcb_get_geometry_reply_t *geom;
198	xcb_generic_event_t *ev = NULL;
199	int orig_x, orig_y, orig_w, orig_h;
200	int start_x, start_y;
201	int corner;
202	int button_pressed = 1;
203
204	geom = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL);
205	if (!geom)
206		return;
207
208	orig_x = geom->x;
209	orig_y = geom->y;
210	orig_w = geom->width;
211	orig_h = geom->height;
212	
213	xcb_query_pointer_reply_t *ptr;
214	ptr = xcb_query_pointer_reply(conn,
215			xcb_query_pointer(conn, scr->root), NULL);
216	if (ptr) {
217		start_x = ptr->root_x;
218		start_y = ptr->root_y;
219		corner = get_corner(win, ptr->root_x, ptr->root_y);
220		free(ptr);
221	}
222	
223	free(geom);
224
225	while ((ev = xcb_wait_for_event(conn))) {
226		switch (ev->response_type & ~0x80) {
227		case XCB_MOTION_NOTIFY: {
228			if (!button_pressed)
229				break;
230				
231			xcb_motion_notify_event_t *m = (xcb_motion_notify_event_t *)ev;
232			
233			int new_x = orig_x;
234			int new_y = orig_y;
235			int new_w = orig_w;
236			int new_h = orig_h;
237			
238			int dx = m->root_x - start_x;
239			int dy = m->root_y - start_y;
240			
241			switch (corner) {
242			case 0:
243				new_x = orig_x + dx;
244				new_y = orig_y + dy;
245				new_w = orig_w - dx;
246				new_h = orig_h - dy;
247				break;
248			case 1:
249				new_y = orig_y + dy;
250				new_w = orig_w + dx;
251				new_h = orig_h - dy;
252				break;
253			case 2:
254				new_x = orig_x + dx;
255				new_w = orig_w - dx;
256				new_h = orig_h + dy;
257				break;
258			case 3:
259			default:
260				new_w = orig_w + dx;
261				new_h = orig_h + dy;
262				break;
263			}
264			
265			if (new_w < 10) new_w = 10;
266			if (new_h < 10) new_h = 10;
267			if (new_x + new_w + 2 > scr->width_in_pixels)
268				new_w = scr->width_in_pixels - new_x - 2;
269			if (new_y + new_h + 2 > scr->height_in_pixels)
270				new_h = scr->height_in_pixels - new_y - 2;
271			if (new_x < 0) {
272				new_w += new_x;
273				new_x = 0;
274			}
275			if (new_y < 0) {
276				new_h += new_y;
277				new_y = 0;
278			}
279			if (new_w < 10) new_w = 10;
280			if (new_h < 10) new_h = 10;
281			
282			values[0] = new_x;
283			values[1] = new_y;
284			values[2] = new_w;
285			values[3] = new_h;
286			
287			xcb_configure_window(conn, win,
288					XCB_CONFIG_WINDOW_X
289					| XCB_CONFIG_WINDOW_Y
290					| XCB_CONFIG_WINDOW_WIDTH
291					| XCB_CONFIG_WINDOW_HEIGHT,
292					values);
293			xcb_flush(conn);
294			break;
295		}
296		case XCB_BUTTON_RELEASE: {
297			xcb_button_release_event_t *b = (xcb_button_release_event_t *)ev;
298			if ((b->detail == XCB_BUTTON_INDEX_1 || 
299			     b->detail == XCB_BUTTON_INDEX_3) && button_pressed) {
300				button_pressed = 0;
301				free(ev);
302				return;
303			}
304			break;
305		}
306		}
307		free(ev);
308	}
309}
310
311int
312main(void)
313{
314	xcb_generic_event_t *ev;
315	
316	init_xcb();
317	get_screen();
318	atexit(cleanup);
319	
320	signal(SIGINT, signal_handler);
321	signal(SIGTERM, signal_handler);
322	
323	grab_keys();
324	
325	printf("mwmd started. Use Alt+Click to move, Alt+RightClick to resize\n");
326	
327	while (running && (ev = xcb_wait_for_event(conn))) {
328		switch (ev->response_type & ~0x80) {
329		case XCB_BUTTON_PRESS: {
330			xcb_button_press_event_t *b = (xcb_button_press_event_t *)ev;
331			xcb_window_t win = b->child;
332			
333			if (win == XCB_NONE)
334				win = get_window_at_pointer();
335			
336			if (win == scr->root || win == XCB_NONE) {
337				free(ev);
338				continue;
339			}
340			
341			if (b->detail == XCB_BUTTON_INDEX_1) {
342				move_window(win);
343			} else if (b->detail == XCB_BUTTON_INDEX_3) {
344				resize_window(win);
345			}
346			break;
347		}
348		}
349		free(ev);
350	}
351	
352	cleanup();
353	return 0;
354}