master ohmu/wmutils / src / core / chwm.c
ohmu  ·  2026-07-25
  1/* See LICENSE file for copyright and license details. */
  2#include <xcb/xcb.h>
  3#include <xcb/xcb_aux.h>
  4#include <stdlib.h>
  5#include <stdio.h>
  6#include <string.h>
  7#include <err.h>
  8#include "arg.h"
  9#include "util.h"
 10
 11static xcb_connection_t *conn;
 12static void usage(char *name);
 13static void set_border(int, int, xcb_window_t);
 14static void set2border(xcb_window_t, int, int, int, int);
 15static void monitor_and_update(xcb_window_t, int, int, int, int);
 16
 17static void
 18usage(char *name)
 19{
 20	fprintf(stderr, "usage: %s <-s size> <-c color> | <-I icolor> <-O ocolor> <-i isize> <-o osize> [-w] <wid> [wid...]\n", name);
 21	fprintf(stderr, "  -w: watch mode - continuously update borders on window resize\n");
 22	exit(1);
 23}
 24
 25static void
 26set_border(int width, int color, xcb_window_t win)
 27{
 28	uint32_t values[3];
 29	uint16_t curr_width;
 30	int mask;
 31	xcb_get_geometry_reply_t *geom;
 32
 33	if (width != -1) {
 34		geom = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL);
 35		if (!geom)
 36			return;
 37
 38		curr_width = geom->border_width;
 39		values[0] = geom->x + curr_width - width;
 40		values[1] = geom->y + curr_width - width;
 41		values[2] = width;
 42		mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_BORDER_WIDTH;
 43		xcb_configure_window(conn, win, mask, values);
 44		xcb_aux_sync(conn);
 45		free(geom);
 46	}
 47
 48	if (color != -1) {
 49		values[0] = color;
 50		mask = XCB_CW_BORDER_PIXEL;
 51		xcb_change_window_attributes(conn, win, mask, values);
 52		xcb_aux_sync(conn);
 53	}
 54}
 55
 56static void
 57set2border(xcb_window_t win, int oc, int os, int ic, int is)
 58{
 59	if (os < 0 || oc < 0 || is < 0 || ic < 0)
 60		return;
 61
 62	uint32_t values[3];
 63	uint16_t curr_width;
 64	int mask;
 65	short w, h, b, o;
 66	
 67	xcb_get_geometry_reply_t *geom = xcb_get_geometry_reply(conn,
 68			xcb_get_geometry(conn, win), NULL);
 69	
 70	if (geom == NULL)
 71		return;
 72
 73	b = (unsigned short)(is + os);
 74	curr_width = geom->border_width;
 75	
 76	/* Set border width first if needed */
 77	if (curr_width != b) {
 78		values[0] = geom->x + curr_width - b;
 79		values[1] = geom->y + curr_width - b;
 80		values[2] = b;
 81		mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_BORDER_WIDTH;
 82		xcb_configure_window(conn, win, mask, values);
 83		xcb_flush(conn);
 84		
 85		/* Re-get geometry after border width change */
 86		free(geom);
 87		geom = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL);
 88		if (geom == NULL)
 89			return;
 90	}
 91
 92	w = (short)geom->width;
 93	h = (short)geom->height;
 94	o = (unsigned short)os;
 95
 96	xcb_rectangle_t inner[] = {
 97		{w, 0, b - o, h + b - o},
 98		{w + b + o, 0, b - o, h + b - o},
 99		{0, h, w + b - o, b - o},
100		{0, h + b + o, w + b - o, b - o},
101		{w + b + o, b + h + o, b, b}
102	};
103
104	xcb_rectangle_t outer[] = {
105		{w + b - o, 0, o, h + b * 2},
106		{w + b, 0, o, h + b * 2},
107		{0, h + b - o, w + b * 2, o},
108		{0, h + b, w + b * 2, o},
109		{1, 1, 1, 1}
110	};
111
112	xcb_pixmap_t pmap = xcb_generate_id(conn);
113	xcb_create_pixmap(conn, geom->depth, pmap, win,
114			w + (b * 2),
115			h + (b * 2));
116
117	xcb_gcontext_t gc = xcb_generate_id(conn);
118	xcb_create_gc(conn, gc, pmap, 0, NULL);
119
120	values[0] = oc;
121	xcb_change_gc(conn, gc, XCB_GC_FOREGROUND, values);
122	xcb_poly_fill_rectangle(conn, pmap, gc, 5, outer);
123
124	values[0] = ic;
125	xcb_change_gc(conn, gc, XCB_GC_FOREGROUND, values);
126	xcb_poly_fill_rectangle(conn, pmap, gc, 5, inner);
127
128	values[0] = pmap;
129	xcb_change_window_attributes(conn, win, XCB_CW_BORDER_PIXMAP, values);
130
131	xcb_flush(conn);
132	xcb_free_pixmap(conn, pmap);
133	xcb_free_gc(conn, gc);
134	free(geom);
135}
136
137static void
138monitor_and_update(xcb_window_t win, int oc, int os, int ic, int is)
139{
140	uint32_t mask = XCB_CW_EVENT_MASK;
141	uint32_t values[] = { XCB_EVENT_MASK_STRUCTURE_NOTIFY };
142	
143	xcb_change_window_attributes(conn, win, mask, values);
144	xcb_flush(conn);
145
146	set2border(win, oc, os, ic, is);
147
148	xcb_generic_event_t *ev;
149	uint16_t last_w = 0, last_h = 0;
150
151	while ((ev = xcb_wait_for_event(conn))) {
152		switch (ev->response_type & ~0x80) {
153			case XCB_CONFIGURE_NOTIFY: {
154				xcb_configure_notify_event_t *e = (xcb_configure_notify_event_t *)ev;
155				if (e->window == win) {
156					if (e->width != last_w || e->height != last_h) {
157						last_w = e->width;
158						last_h = e->height;
159						set2border(win, oc, os, ic, is);
160					}
161				}
162				break;
163			}
164			case XCB_DESTROY_NOTIFY: {
165				xcb_destroy_notify_event_t *e = (xcb_destroy_notify_event_t *)ev;
166				if (e->window == win) {
167					free(ev);
168					return;
169				}
170				break;
171			}
172		}
173		free(ev);
174	}
175}
176
177int
178main(int argc, char **argv)
179{
180	char *argv0;
181	int color, width;
182	int oc, os, ic, is;
183	int use_double_border = 0;
184	int watch_mode = 0;
185
186	color = width = -1;
187	oc = os = ic = is = -1;
188
189	if (argc < 2)
190		usage(argv[0]);
191
192	ARGBEGIN {
193		case 's':
194			width = strtoul(EARGF(usage(argv0)), NULL, 10);
195			break;
196		case 'c':
197			color = strtoul(EARGF(usage(argv0)), NULL, 16);
198			break;
199		case 'I':
200			ic = strtoul(EARGF(usage(argv0)), NULL, 16);
201			use_double_border = 1;
202			break;
203		case 'O':
204			oc = strtoul(EARGF(usage(argv0)), NULL, 16);
205			use_double_border = 1;
206			break;
207		case 'i':
208			is = strtoul(EARGF(usage(argv0)), NULL, 10);
209			use_double_border = 1;
210			break;
211		case 'o':
212			os = strtoul(EARGF(usage(argv0)), NULL, 10);
213			use_double_border = 1;
214			break;
215		case 'w':
216			watch_mode = 1;
217			break;
218		default:
219			usage(argv0);
220	} ARGEND
221
222	init_xcb(&conn);
223
224	if (watch_mode && use_double_border && *argv) {
225		/* Watch mode: only works with one window */
226		xcb_window_t win = strtoul(*argv, NULL, 16);
227		monitor_and_update(win, oc, os, ic, is);
228	} else {
229		/* Normal mode: set borders once */
230		while (*argv) {
231			xcb_window_t win = strtoul(*argv++, NULL, 16);
232			if (use_double_border)
233				set2border(win, oc, os, ic, is);
234			else
235				set_border(width, color, win);
236		}
237	}
238
239	xcb_aux_sync(conn);
240	kill_xcb(&conn);
241
242	return 0;
243}
244