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