git.y1.nz

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.