selk | Keyboard-only rectangle selector for X |
| download: https://git.y1.nz/archives/selk.tar.gz | |
| README | Files | Log | Refs | LICENSE |
draw.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <X11/Xlib.h>
5 #include <X11/Xutil.h>
6 #include <X11/extensions/Xrandr.h>
7
8 #include "config.h"
9 #include "draw.h"
10
11 void
12 setup_draw(DrawContext * ctx, X11 * x11)
13 {
14 unsigned long mask = 0;
15 XSetWindowAttributes window_attributes;
16
17 XColor color;
18 int color_alloc_ret = XAllocNamedColor(
19 x11->display,
20 DefaultColormap(
21 x11->display,
22 DefaultScreen(x11->display)
23 ),
24 line_color_name,
25 (XColor []) {0},
26 &color
27 );
28 if (!color_alloc_ret)
29 fatal("failed to allocate color.");
30
31 window_attributes.override_redirect = True;
32 mask |= CWOverrideRedirect;
33 window_attributes.background_pixel = color.pixel;
34 mask |= CWBackPixel;
35
36 window_attributes.event_mask = StructureNotifyMask;
37 mask |= CWEventMask;
38
39 for (int i = 0; i < WINDOW_COUNT; i++) {
40 x11->window[i] = XCreateWindow(
41 x11->display,
42 x11->root.window,
43 x11->root.x, x11->root.y,
44 x11->root.w, x11->root.h,
45 0,
46 CopyFromParent,
47 InputOutput,
48 CopyFromParent,
49 mask,
50 &window_attributes
51 );
52 char name[] = "selx0";
53 name[ARRLEN(name) - 2] += i;
54 XClassHint classhint = { name, "selx" };
55 XSetClassHint(x11->display, x11->window[i], &classhint);
56 }
57 }
58
59 void
60 setup_keyboard(X11 * x11)
61 {
62 /* when launched via dwm keybinding, it fails the grab since
63 * dwm has it grabbed already. listen for FocusChangeMask and
64 * keep retrying. */
65 int keyboard_grab_result;
66 XSelectInput(x11->display, x11->root.window, FocusChangeMask);
67 do {
68 keyboard_grab_result = XGrabKeyboard(
69 x11->display, x11->root.window, 0,
70 GrabModeAsync, GrabModeAsync, CurrentTime
71 );
72 XEvent junk;
73 XNextEvent(x11->display, &junk);
74 } while (keyboard_grab_result == AlreadyGrabbed);
75 XSelectInput(x11->display, x11->root.window, 0x0);
76
77 if (keyboard_grab_result != GrabSuccess)
78 fatal("failed to grab keyboard");
79 }
80
81 void
82 setup_display(X11 * x11)
83 {
84 x11->display = XOpenDisplay(NULL);
85 if (x11->display == NULL)
86 fatal("failed to open X display");
87
88 XWindowAttributes attributes;
89 x11->root.window = DefaultRootWindow(x11->display);
90
91 int get_attributes_ret = XGetWindowAttributes(
92 x11->display,
93 x11->root.window,
94 &attributes
95 );
96 if (get_attributes_ret == 0)
97 fatal("XGetWindowAttributes() failed");
98
99 x11->root.x = attributes.x;
100 x11->root.y = attributes.y;
101 x11->root.w = attributes.width;
102 x11->root.h = attributes.height;
103 }
104
105 void
106 select_window(DrawContext * ctx, X11 * x11, XEvent event)
107 {
108 XWindowAttributes window_attributes;
109 int b;
110
111 ctx->target.window = event.xkey.subwindow;
112 if (ctx->target.window == None)
113 ctx->target.window = event.xkey.root;
114
115 {
116 int get_attributes_result = XGetWindowAttributes(
117 x11->display,
118 ctx->target.window,
119 &window_attributes
120 );
121
122 if (get_attributes_result == 0)
123 fatal("XGetWindowAttributes() failed");
124 }
125
126 b = window_attributes.border_width;
127 ctx->start.x = window_attributes.x + b;
128 ctx->start.y = window_attributes.y + b;
129 ctx->end.x = ctx->start.x + window_attributes.width;
130 ctx->end.y = ctx->start.y + window_attributes.height;
131 }
132
133 void
134 select_monitor(DrawContext * ctx, X11 * x11)
135 {
136 ctx->target.monitor = -64;
137
138 int nmonitors;
139 XRRMonitorInfo *monitors = XRRGetMonitors(
140 x11->display, x11->root.window, True, &nmonitors
141 );
142
143 if (monitors == NULL)
144 fatal("XRRGetMonitors() failed");
145
146 Point * cur = &ctx->end;
147 for (SIZE_T i = 0; i < nmonitors; ++i) {
148 XRRMonitorInfo * m = monitors + i;
149 if (
150 cur->x >= m->x &&
151 cur->y >= m->y &&
152 cur->x < (m->x + m->width) &&
153 cur->y < (m->y + m->height)
154 ) {
155 ctx->target.monitor = i;
156 break;
157 }
158 }
159
160 if (ctx->target.monitor >= nmonitors)
161 fatal("no such monitor" "\n");
162
163 XRRMonitorInfo * m = monitors + ctx->target.monitor;
164 ctx->start.x = m->x;
165 ctx->start.y = m->y;
166 ctx->end.x = ctx->start.x + m->width;
167 ctx->end.y = ctx->start.y + m->height;
168 }
169
170 Rect
171 get_rectangle(DrawContext * ctx, X11 * x11)
172 {
173 int px[2] = { ctx->start.x, ctx->end.x },
174 py[2] = { ctx->start.y, ctx->end.y };
175
176 /* ensure positive orientations */
177 if (px[1] < px[0]) {
178 px[0] = ctx->end.x;
179 px[1] = ctx->start.x;
180 }
181 if (py[1] < py[0]) {
182 py[0] = ctx->end.y;
183 py[1] = ctx->start.y;
184 }
185
186 px[0] = CLAMP(x11->root.x, px[0], x11->root.x+x11->root.w);
187 px[1] = CLAMP(x11->root.x, px[1], x11->root.x+x11->root.w);
188 py[0] = CLAMP(x11->root.y, py[0], x11->root.y+x11->root.h);
189 py[1] = CLAMP(x11->root.y, py[1], x11->root.y+x11->root.h);
190
191 int x = px[0];
192 int y = py[0];
193 int w = DIFF(px[0], px[1]);
194 int h = DIFF(py[0], py[1]);
195
196 return (Rect) { x, y, w, h };
197 }
198
199 int
200 get_speed(DrawContext * ctx)
201 {
202 int mask = ctx->keys_down_mask;
203 if (mask & KEY_SLOW_MASK) return speed_slow;
204 if (mask & KEY_FAST_MASK) return speed_fast;
205 return speed_normal;
206 }
207
208 int
209 get_line_thickness(DrawContext * ctx)
210 {
211 int mask = ctx->keys_down_mask;
212 if (mask & KEY_SLOW_MASK) return border_thin;
213 if (mask & KEY_FAST_MASK) return border_thick;
214 return border_normal;
215 }
216
217 void
218 draw(DrawContext * ctx, X11 * x11)
219 {
220 Rect selection;
221 int x, y, w, h, b;
222 Rect * rects;
223
224 selection = get_rectangle(ctx, x11);
225 b = get_line_thickness(ctx);
226 x = selection.x;
227 y = selection.y;
228 w = selection.w;
229 h = selection.h;
230
231 if (ctx->mode == MODE_BOTH || ctx->mode == MODE_P1) {
232 w = x11->root.w;
233 h = x11->root.h;
234 }
235
236 rects = (Rect[]) {
237 { x-b, y-b, w+b*2, b }, /* top */
238 { x-b, y-b, b, h+b*2 }, /* left */
239 { x-b, y+h, w+b*2, b }, /* bottom */
240 { x+w, y-b, b, h+b*2 }, /* right */
241 { x+w/2-b, y+h/2-b, b*2, b*2 }, /* center or corner */
242 };
243
244 /* show the active edges thicker */
245 if (ctx->keys_down_mask & KEY_DOWN2_MASK) rects[2].h *= 2;
246 if (ctx->keys_down_mask & KEY_RIGHT2_MASK) rects[3].w *= 2;
247 if (ctx->keys_down_mask & KEY_LEFT2_MASK) {
248 rects[1].w += b; rects[1].x -= b;
249 }
250 if (ctx->keys_down_mask & KEY_UP2_MASK) {
251 rects[0].h += b; rects[0].y -= b;
252 }
253
254 for (int window_i = 0; window_i < WINDOW_COUNT; window_i++) {
255 int rect_i = window_i;
256 if (ctx->mode == MODE_BOTH || ctx->mode == MODE_P1)
257 rect_i %= 2;
258 Rect * rect = rects + rect_i;
259 XMoveResizeWindow(
260 x11->display, x11->window[window_i],
261 rect->x, rect->y,
262 rect->w, rect->h
263 );
264 }
265
266 XFlush(x11->display);
267 }
268
269 void
270 init_draw(DrawContext * ctx, X11 * x11) // , Point p)
271 {
272 ctx->start.x = 10;
273 ctx->start.y = 10;
274 ctx->end.x = 100;
275 ctx->end.y = 100;
276 ctx->mode = MODE_BOTH;
277 ctx->keys_down_mask = 0;
278
279 draw(ctx, x11);
280
281 for (int i = 0; i < ARRLEN(x11->window); i++)
282 XMapWindow(x11->display, x11->window[i]);
283
284 XFlush(x11->display);
285 }
286
287 void
288 set_up(DrawContext * ctx, X11 * x11)
289 {
290 setup_display(x11);
291 setup_keyboard(x11);
292 setup_draw(ctx, x11);
293 init_draw(ctx, x11);
294 }
295
296 void
297 clean_up(DrawContext * ctx, X11 * x11)
298 {
299 for (int i = 0; i < ARRLEN(x11->window); ++i)
300 XDestroyWindow(x11->display, x11->window[i]);
301
302 int destroyed = 0;
303 int unmapped = 0;
304 for (XEvent event; !(destroyed == WINDOW_COUNT && unmapped == WINDOW_COUNT);) {
305 XNextEvent(x11->display, &event);
306 for (int i = 0; i < ARRLEN(x11->window); i++) {
307 if(event.type == DestroyNotify)
308 destroyed += (event.xdestroywindow.window == x11->window[i]);
309 if(event.type == UnmapNotify)
310 unmapped += (event.xunmap.window == x11->window[i]);
311 }
312 }
313
314 XUngrabPointer(x11->display, CurrentTime);
315 XUngrabKeyboard(x11->display, CurrentTime);
316 }
317
318 int
319 handle_key_press(DrawContext * ctx, X11 * x11, XEvent event)
320 {
321 KeySym keysym = XKeycodeToKeysym(x11->display, event.xkey.keycode, 0);
322
323 if ((event.xkey.state & ControlMask) && keysym == XK_c)
324 fatal("Keyboard Interrupt" "\n");
325
326 switch (keysym) {
327 case KEY_MONITOR:
328 select_monitor(ctx, x11);
329 return STATUS_DONE;
330 case KEY_WINDOW:
331 select_window(ctx, x11, event);
332 return STATUS_DONE;
333 case KEY_CONFIRM:
334 if (ctx->mode != MODE_BOTH) return STATUS_DONE;
335 ctx->mode = MODE_P2;
336 break;
337 case KEY_CANCEL:
338 fatal("Cancelled" "\n");
339 break;
340 case KEY_SLOW: ctx->keys_down_mask |= KEY_SLOW_MASK; break;
341 case KEY_FAST: ctx->keys_down_mask |= KEY_FAST_MASK; break;
342 case KEY_LEFT: ctx->keys_down_mask |= KEY_LEFT_MASK; break;
343 case KEY_RIGHT: ctx->keys_down_mask |= KEY_RIGHT_MASK; break;
344 case KEY_UP: ctx->keys_down_mask |= KEY_UP_MASK; break;
345 case KEY_DOWN: ctx->keys_down_mask |= KEY_DOWN_MASK; break;
346 }
347
348 /* draw to update visual hinting */
349 draw(ctx, x11);
350 return STATUS_CONTINUE;
351 }
352
353 void
354 handle_key_release(DrawContext * ctx, X11 * x11, XEvent event)
355 {
356 KeySym keysym = XKeycodeToKeysym(
357 x11->display, event.xkey.keycode, 0
358 );
359
360 switch (keysym) {
361 case KEY_SLOW: ctx->keys_down_mask &= ~KEY_SLOW_MASK; break;
362 case KEY_FAST: ctx->keys_down_mask &= ~KEY_FAST_MASK; break;
363 case KEY_LEFT: ctx->keys_down_mask &= ~KEY_LEFT_MASK; break;
364 case KEY_RIGHT: ctx->keys_down_mask &= ~KEY_RIGHT_MASK; break;
365 case KEY_DOWN: ctx->keys_down_mask &= ~KEY_DOWN_MASK; break;
366 case KEY_UP: ctx->keys_down_mask &= ~KEY_UP_MASK; break;
367 }
368
369 /* draw to update visual hinting */
370 draw(ctx, x11);
371 }
372
373 int
374 handle_xevent(DrawContext * ctx, X11 * x11, XEvent event, int * queued)
375 {
376 switch (event.type) {
377 case KeyPress:
378 return handle_key_press(ctx, x11, event);
379 case KeyRelease:
380 handle_key_release(ctx, x11, event);
381 break;
382 case Expose:
383 draw(ctx, x11);
384 break;
385 case DestroyNotify:
386 fatal("received DestroyNotify" "\n");
387 }
388 return STATUS_CONTINUE;
389 }
390
391 void
392 do_movement(DrawContext * ctx, X11 * x11)
393 {
394 int mask = ctx->keys_down_mask;
395
396 int down = mask & KEY_DOWN_MASK;
397 int up = mask & KEY_UP_MASK;
398 int left = mask & KEY_LEFT_MASK;
399 int right = mask & KEY_RIGHT_MASK;
400
401 int speed = get_speed(ctx);
402 int delta_x = speed * ((right != 0) - (left != 0));
403 int delta_y = speed * ((down != 0) - (up != 0));
404
405 int min_x = x11->root.x;
406 int min_y = x11->root.y;
407 int max_x = x11->root.x + x11->root.w;
408 int max_y = x11->root.y + x11->root.h;
409
410 if (delta_x == 0 && delta_y == 0)
411 return;
412
413 if (ctx->mode == MODE_BOTH || ctx->mode == MODE_P1) {
414 ctx->start.x += delta_x;
415 ctx->start.y += delta_y;
416 }
417 if (ctx->mode == MODE_BOTH || ctx->mode == MODE_P2) {
418 ctx->end.x += delta_x;
419 ctx->end.y += delta_y;
420 }
421
422 /* clamp within screen */
423 ctx->start.x = CLAMP(min_x, ctx->start.x, max_x);
424 ctx->start.y = CLAMP(min_y, ctx->start.y, max_y);
425 ctx->end.x = CLAMP(min_x, ctx->end.x, max_x);
426 ctx->end.y = CLAMP(min_y, ctx->end.y, max_y);
427
428 draw(ctx, x11);
429 ctx->frames_moving++;
430 }
431
432 void
433 run_draw_loop(DrawContext * ctx, X11 * x11)
434 {
435 int queued = FALSE, event_set;
436 int status = STATUS_CONTINUE;
437 XEvent event;
438
439 while (status == STATUS_CONTINUE) {
440 event_set = XCheckMaskEvent(
441 x11->display,
442 ~0, /* accept all events */
443 &event
444 );
445
446 if (!event_set) {
447 usleep(sleep_ms * 1000);
448 if (ctx->keys_down_mask != 0) do_movement(ctx, x11);
449 else ctx->frames_moving = 0;
450 continue;
451 }
452
453 status = handle_xevent(ctx, x11, event, &queued);
454 }
455 }
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.