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}