selk | Keyboard-only rectangle selector for X |
| download: https://git.y1.nz/archives/selk.tar.gz | |
| README | Files | Log | Refs | LICENSE |
commit 0a4af112e23c321c302e6eacfb520ea02356f21e parent 8ce6e3eb8f0ce8ee1b6c7726e8b8be2b7d346965 Author: Emma Weaver <emma@waeaves.com> Date: Sat, 13 Jun 2026 19:06:17 -0400 Changed controls to try to be more intuitive Diffstat:
| M | .gitignore | 2 | +- |
| M | Makefile | 6 | ++++++ |
| A | README | 27 | +++++++++++++++++++++++++++ |
| D | README.md | 6 | ------ |
| A | config.default.h | 27 | +++++++++++++++++++++++++++ |
| M | config.mk | 17 | +++++++---------- |
| M | draw.c | 171 | +++++++++++++++++++++++++++++++------------------------------------------------ |
| M | draw.h | 18 | ++++++++---------- |
| M | main.c | 19 | ++++++++++++------- |
| D | selk | 0 | |
| A | selk.1 | 40 | ++++++++++++++++++++++++++++++++++++++++ |
11 files changed, 194 insertions(+), 139 deletions(-)
diff --git a/.gitignore b/.gitignore @@ -1,4 +1,4 @@ -grabs +selk draw config.h diff --git a/Makefile b/Makefile @@ -6,6 +6,9 @@ selk: main.c draw.o config.mk draw.o: draw.c draw.h config.h config.mk cc -c draw.c ${CFLAGS} ${LDFLAGS} -o draw.o +config.h: + cp config.default.h config.h + clean: rm -f draw.o util.o selk @@ -13,6 +16,9 @@ install: selk mkdir -p $(DESTDIR)$(PREFIX)/bin cp -f selk $(DESTDIR)$(PREFIX)/bin chmod 755 $(DESTDIR)$(PREFIX)/bin/selk + mkdir -p $(MANPREFIX)/man1 + sed "s/VERSION/$(VERSION)/g" < selk.1 > $(MANPREFIX)/man1/selk.1 + chmod 644 $(MANPREFIX)/man1/selk.1 .PHONY: clean diff --git a/README b/README @@ -0,0 +1,27 @@ + + ? ...?. + ?$.$$########.. + ?...# SELK #####??? + ..$$###$$$...$? + .. .. + +Selk is a minimal, keyboard-only rectangle selector for x11. It's a +fork/rewrite of NRK's selx, with focus narrowed to the things I care about. + +Features: + - short/simple/minimal code + - fast/smooth keyboard controls (vim default) + - stupid-simple output format + controls + + * doesn't support multi-monitor setups. Just use a single monitor LOL + +--- + +TODO: + _ _ + / \ \ + _____@_____/____________ + + ...omg a tumbleweed! + bc there are no outstanding tasks currently :3 + diff --git a/README.md b/README.md @@ -1,6 +0,0 @@ -# selk - -Selk is a minimal, keyboard-only rectangle selector for x. - -It's a bad fork of NRK's selx. - diff --git a/config.default.h b/config.default.h @@ -0,0 +1,27 @@ +const char * line_color_name = "#ff44dd"; + +const int sleep_ms = 30; + +const int speed_slow = 2; +const int speed_normal = 12; +const int speed_fast = 24; + +const int border_thin = 2; +const int border_normal = 4; +const int border_thick = 8; + +/* CONTROLS */ +#define KEY_SLOW XK_Shift_L /* slow */ +#define KEY_FAST XK_Control_L /* fast */ + +/* movement */ +#define KEY_UP XK_k +#define KEY_DOWN XK_j +#define KEY_LEFT XK_h +#define KEY_RIGHT XK_l + +#define KEY_MONITOR XK_m +#define KEY_WINDOW XK_n + +#define KEY_CONFIRM XK_space +#define KEY_CANCEL XK_Escape diff --git a/config.mk b/config.mk @@ -1,25 +1,22 @@ -# grabs version -VERSION = 1.0 - # Customize below to fit your system # paths PREFIX = /usr/local -MANPREFIX = ${PREFIX}/share/man +MANPREFIX = $(PREFIX)/share/man # includes and libs INCS = -I/usr/X11R6/include -I/usr/include/freetype2 LIBS = -L/usr/X11R6/lib -lX11 -lfontconfig -lXft -lXrandr # flags -CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700L -DVERSION=\"${VERSION}\" -#CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS} -CFLAGS = ${INCS} -std=c99 -pedantic -Wall -Wno-deprecated-declarations -Os ${INCS} ${CPPFLAGS} -LDFLAGS = ${LIBS} +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700L -DVERSION=\"$(VERSION)\" +#CFLAGS = -g -std=c99 -pedantic -Wall -O0 $(INCS) $(CPPFLAGS) +CFLAGS = $(INCS) -std=c99 -pedantic -Wall -Wno-deprecated-declarations -Os $(INCS) $(CPPFLAGS) +LDFLAGS = $(LIBS) # Solaris -#CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\" -#LDFLAGS = ${LIBS} +#CFLAGS = -fast $(INCS) -DVERSION=\"$(VERSION)\" +#LDFLAGS = $(LIBS) # compiler and linker CC = cc diff --git a/draw.c b/draw.c @@ -9,7 +9,7 @@ #include "draw.h" void -setup_draw(DrawContext *ctx, X11 *x11) +setup_draw(DrawContext * ctx, X11 * x11) { unsigned long mask = 0; XSetWindowAttributes window_attributes; @@ -197,9 +197,21 @@ get_rectangle(DrawContext * ctx, X11 * x11) } int -is_fast(DrawContext * ctx) +get_speed(DrawContext * ctx) { - return !(ctx->keys_down_mask & KEY_SHIFT_MASK); + int mask = ctx->keys_down_mask; + if (mask & KEY_SLOW_MASK) return speed_slow; + if (mask & KEY_FAST_MASK) return speed_fast; + return speed_normal; +} + +int +get_line_thickness(DrawContext * ctx) +{ + int mask = ctx->keys_down_mask; + if (mask & KEY_SLOW_MASK) return border_thin; + if (mask & KEY_FAST_MASK) return border_thick; + return border_normal; } void @@ -209,48 +221,43 @@ draw(DrawContext * ctx, X11 * x11) int x, y, w, h, b; Rect * rects; - /* clamp the control points... */ - ctx->start.x = CLAMP(x11->root.x, ctx->start.x, x11->root.x+x11->root.w); - ctx->start.y = CLAMP(x11->root.y, ctx->start.y, x11->root.y+x11->root.h); - ctx->end.x = CLAMP(x11->root.x, ctx->end.x, x11->root.x+x11->root.w); - ctx->end.y = CLAMP(x11->root.y, ctx->end.y, x11->root.y+x11->root.h); - selection = get_rectangle(ctx, x11); + b = get_line_thickness(ctx); x = selection.x; y = selection.y; w = selection.w; h = selection.h; - b = border_width; + + if (ctx->mode == MODE_BOTH || ctx->mode == MODE_P1) { + w = x11->root.w; + h = x11->root.h; + } + rects = (Rect[]) { - { x-b, y-b, w+b, b }, /* top */ - { x-b, y, b, h+b }, /* left */ - { x, y+h, w+b, b }, /* bottom */ - { x+w, y-b, b, h+b }, /* right */ - { x+w/2-b/2,y+h/2-b/2,b,b }, /* center or corner */ + { x-b, y-b, w+b*2, b }, /* top */ + { x-b, y-b, b, h+b*2 }, /* left */ + { x-b, y+h, w+b*2, b }, /* bottom */ + { x+w, y-b, b, h+b*2 }, /* right */ + { x+w/2-b, y+h/2-b, b*2, b*2 }, /* center or corner */ }; /* show the active edges thicker */ if (ctx->keys_down_mask & KEY_DOWN2_MASK) rects[2].h *= 2; if (ctx->keys_down_mask & KEY_RIGHT2_MASK) rects[3].w *= 2; if (ctx->keys_down_mask & KEY_LEFT2_MASK) { - rects[1].w *= 2; rects[1].x -= b; + rects[1].w += b; rects[1].x -= b; } if (ctx->keys_down_mask & KEY_UP2_MASK) { - rects[0].h *= 2; rects[0].y -= b; + rects[0].h += b; rects[0].y -= b; } - /* make the indicator bigger when fast */ - if (is_fast(ctx)) { - rects[4].x -= b; - rects[4].y -= b; - rects[4].w += 2*b; - rects[4].h += 2*b; - } - - for (int i = 0; i < WINDOW_COUNT; i++) { - Rect * rect = rects + i; + for (int window_i = 0; window_i < WINDOW_COUNT; window_i++) { + int rect_i = window_i; + if (ctx->mode == MODE_BOTH || ctx->mode == MODE_P1) + rect_i %= 2; + Rect * rect = rects + rect_i; XMoveResizeWindow( - x11->display, x11->window[i], + x11->display, x11->window[window_i], rect->x, rect->y, rect->w, rect->h ); @@ -264,8 +271,10 @@ init_draw(DrawContext * ctx, X11 * x11) // , Point p) { ctx->start.x = 10; ctx->start.y = 10; - ctx->end.x = 410; - ctx->end.y = 410; + ctx->end.x = 100; + ctx->end.y = 100; + ctx->mode = MODE_BOTH; + ctx->keys_down_mask = 0; draw(ctx, x11); @@ -306,7 +315,7 @@ clean_up(DrawContext * ctx, X11 * x11) XUngrabKeyboard(x11->display, CurrentTime); } -void +int handle_key_press(DrawContext * ctx, X11 * x11, XEvent event) { KeySym keysym = XKeycodeToKeysym(x11->display, event.xkey.keycode, 0); @@ -317,35 +326,28 @@ handle_key_press(DrawContext * ctx, X11 * x11, XEvent event) switch (keysym) { case KEY_MONITOR: select_monitor(ctx, x11); - draw(ctx, x11); - break; + return STATUS_DONE; case KEY_WINDOW: select_window(ctx, x11, event); - draw(ctx, x11); - break; - case KEY_TAKE: - { - int shift = ctx->keys_down_mask & KEY_SHIFT_MASK; - ctx->state = shift ? STATE_RECORD : STATE_TAKE; - } + return STATUS_DONE; + case KEY_CONFIRM: + if (ctx->mode != MODE_BOTH) return STATUS_DONE; + ctx->mode = MODE_P2; break; case KEY_CANCEL: fatal("Cancelled" "\n"); break; - case KEY_SHIFT: ctx->keys_down_mask |= KEY_SHIFT_MASK; break; - case KEY_CONTROL: ctx->keys_down_mask |= KEY_CONTROL_MASK; break; + case KEY_SLOW: ctx->keys_down_mask |= KEY_SLOW_MASK; break; + case KEY_FAST: ctx->keys_down_mask |= KEY_FAST_MASK; break; case KEY_LEFT: ctx->keys_down_mask |= KEY_LEFT_MASK; break; case KEY_RIGHT: ctx->keys_down_mask |= KEY_RIGHT_MASK; break; case KEY_UP: ctx->keys_down_mask |= KEY_UP_MASK; break; case KEY_DOWN: ctx->keys_down_mask |= KEY_DOWN_MASK; break; - case KEY_DOWN_2: ctx->keys_down_mask |= KEY_DOWN2_MASK; break; - case KEY_UP_2: ctx->keys_down_mask |= KEY_UP2_MASK; break; - case KEY_LEFT_2: ctx->keys_down_mask |= KEY_LEFT2_MASK; break; - case KEY_RIGHT_2: ctx->keys_down_mask |= KEY_RIGHT2_MASK; break; } /* draw to update visual hinting */ draw(ctx, x11); + return STATUS_CONTINUE; } void @@ -356,55 +358,34 @@ handle_key_release(DrawContext * ctx, X11 * x11, XEvent event) ); switch (keysym) { - case KEY_SHIFT: ctx->keys_down_mask &= ~KEY_SHIFT_MASK; break; - case KEY_CONTROL: ctx->keys_down_mask &= ~KEY_CONTROL_MASK; break; + case KEY_SLOW: ctx->keys_down_mask &= ~KEY_SLOW_MASK; break; + case KEY_FAST: ctx->keys_down_mask &= ~KEY_FAST_MASK; break; case KEY_LEFT: ctx->keys_down_mask &= ~KEY_LEFT_MASK; break; case KEY_RIGHT: ctx->keys_down_mask &= ~KEY_RIGHT_MASK; break; case KEY_DOWN: ctx->keys_down_mask &= ~KEY_DOWN_MASK; break; case KEY_UP: ctx->keys_down_mask &= ~KEY_UP_MASK; break; - case KEY_DOWN_2: ctx->keys_down_mask &= ~KEY_DOWN2_MASK; break; - case KEY_UP_2: ctx->keys_down_mask &= ~KEY_UP2_MASK; break; - case KEY_LEFT_2: ctx->keys_down_mask &= ~KEY_LEFT2_MASK; break; - case KEY_RIGHT_2: ctx->keys_down_mask &= ~KEY_RIGHT2_MASK; break; } /* draw to update visual hinting */ draw(ctx, x11); } -void -handle_motion_notify(DrawContext * ctx, X11 * x11, XEvent event, int * queued) -{ - for ( int n = 0; n > 0 || (n = XPending(x11->display)) > 0; n--) { - XNextEvent(x11->display, &event); - if (event.type != MotionNotify) { - * queued = TRUE; - break; - } - } -} - -void +int handle_xevent(DrawContext * ctx, X11 * x11, XEvent event, int * queued) { switch (event.type) { case KeyPress: - handle_key_press(ctx, x11, event); - break; + return handle_key_press(ctx, x11, event); case KeyRelease: handle_key_release(ctx, x11, event); break; - case MotionNotify: - // TODO is this needed? - // handle_motion_notify(ctx, x11, event, queued); - break; case Expose: draw(ctx, x11); break; case DestroyNotify: fatal("received DestroyNotify" "\n"); - break; } + return STATUS_CONTINUE; } void @@ -412,22 +393,14 @@ do_movement(DrawContext * ctx, X11 * x11) { int mask = ctx->keys_down_mask; - Point start = ctx->start, end = ctx->end; - int down = mask & KEY_DOWN_MASK; int up = mask & KEY_UP_MASK; int left = mask & KEY_LEFT_MASK; int right = mask & KEY_RIGHT_MASK; - int down2 = mask & KEY_DOWN2_MASK; - int up2 = mask & KEY_UP2_MASK; - int left2 = mask & KEY_LEFT2_MASK; - int right2 = mask & KEY_RIGHT2_MASK; - - int speed = is_fast(ctx) ? movement_speed_fast : movement_speed_slow; + int speed = get_speed(ctx); int delta_x = speed * ((right != 0) - (left != 0)); int delta_y = speed * ((down != 0) - (up != 0)); - int any_specific = down2 || up2 || right2 || left2; int min_x = x11->root.x; int min_y = x11->root.y; @@ -437,31 +410,21 @@ do_movement(DrawContext * ctx, X11 * x11) if (delta_x == 0 && delta_y == 0) return; - if (!any_specific) { - /* translate the whole selection, */ - - /* (apply bounds so that the rect stops at screen's edge */ - if(delta_x > 0 && end.x + delta_x > max_x) - delta_x = max_x - end.x; - if(delta_y > 0 && end.y + delta_y > max_y) - delta_y = max_y - end.y; - if(delta_x < 0 && start.x + delta_x < min_x) - delta_x = min_x - start.x; - if(delta_y < 0 && start.y + delta_y < min_y) - delta_y = min_y - start.y; - + if (ctx->mode == MODE_BOTH || ctx->mode == MODE_P1) { ctx->start.x += delta_x; ctx->start.y += delta_y; + } + if (ctx->mode == MODE_BOTH || ctx->mode == MODE_P2) { ctx->end.x += delta_x; ctx->end.y += delta_y; - } else { - /* only move specific sides */ - if (down2) ctx->end.y += delta_y; - if (up2) ctx->start.y += delta_y; - if (right2) ctx->end.x += delta_x; - if (left2) ctx->start.x += delta_x; } + /* clamp within screen */ + ctx->start.x = CLAMP(min_x, ctx->start.x, max_x); + ctx->start.y = CLAMP(min_y, ctx->start.y, max_y); + ctx->end.x = CLAMP(min_x, ctx->end.x, max_x); + ctx->end.y = CLAMP(min_y, ctx->end.y, max_y); + draw(ctx, x11); ctx->frames_moving++; } @@ -470,12 +433,10 @@ void run_draw_loop(DrawContext * ctx, X11 * x11) { int queued = FALSE, event_set; + int status = STATUS_CONTINUE; XEvent event; - for (;;) { - if (ctx->state == STATE_TAKE || ctx->state == STATE_RECORD) - break; - + while (status == STATUS_CONTINUE) { event_set = XCheckMaskEvent( x11->display, ~0, /* accept all events */ @@ -489,6 +450,6 @@ run_draw_loop(DrawContext * ctx, X11 * x11) continue; } - handle_xevent(ctx, x11, event, &queued); + status = handle_xevent(ctx, x11, event, &queued); } } diff --git a/draw.h b/draw.h @@ -8,7 +8,7 @@ #define SIZEOF(...) ((SIZE_T)sizeof(__VA_ARGS__)) #define ARRLEN(...) (SIZEOF(__VA_ARGS__) / SIZEOF(0[__VA_ARGS__])) #define ARREND(V) ((V) + ARRLEN(V)) -#define CLAMP(lo, x, hi) ((x) < (lo) ? (lo) : ((x) > (hi) ? (hi) : x)) +#define CLAMP(lo, md, hi) ((md) < (lo) ? (lo) : ((md) > (hi) ? (hi) : (md))) #define TRUE 1 #define FALSE 0 @@ -20,12 +20,8 @@ //////////////// types -typedef enum { - STATE_NONE, - STATE_TAKE, /* take screenshot */ - STATE_RECORD /* take screen recording */ -} State; -enum { EVENT_CLICK, EVENT_MOTION, EVENT_SWAP }; +typedef enum { MODE_P1, MODE_P2, MODE_BOTH } Mode; +enum { STATUS_CONTINUE, STATUS_DONE }; enum { KEY_UP_MASK = 1 << 0, KEY_DOWN_MASK = 1 << 1, @@ -37,8 +33,8 @@ enum { KEY_LEFT2_MASK = 1 << 6, KEY_RIGHT2_MASK = 1 << 7, - KEY_CONTROL_MASK = 1 << 8, - KEY_SHIFT_MASK = 1 << 9 + KEY_FAST_MASK = 1 << 8, + KEY_SLOW_MASK = 1 << 9 }; typedef struct { int x, y; } Point; @@ -58,11 +54,13 @@ typedef struct { int frames_moving; Point start, end; - State state; + Mode mode; + union { Window window; int monitor; } target; + const char * color_name; const int border_width; } DrawContext; diff --git a/main.c b/main.c @@ -8,16 +8,21 @@ #include "draw.h" const char * usage = \ -"Selk 1.0 🄯 Emma 2026" "\n\n" \ +"Selk 1.1 🄯 Emma 2026" "\n\n" \ "Usage: selk [--help]" "\n\n" \ "Keyboard-based rectangle selector." "\n" \ "Controls:" "\n" \ -" [hjkl]: move selection" "\n" \ -" [shift + hjkl]: move selection slower" "\n" \ -" [wasd]: specify which sides to move" "\n" \ -" [m]: select current monitor" "\n" \ -" [n]: select current window" "\n" \ -" [enter]: print selection" "\n\n"; +" [hjkl]: move selection" "\n" \ +" [shift]: slower" "\n" \ +" [control]: faster" "\n" \ +" [space]: confirm" "\n" \ +" [m]: output monitor" "\n" \ +" [n]: output window" "\n" \ +" [escape]: cancel" "\n" \ +"Position the top-left corner, press space, position" "\n" \ +"the bottom corner, press space again. Or, at any" "\n" \ +"time press m or n." "\n" \ +"\n"; int main(int argc, const char * argv[]) diff --git a/selk b/selk Binary files differ. diff --git a/selk.1 b/selk.1 @@ -0,0 +1,40 @@ +.TH SELK 1 selk\-VERSION +.SH NAME +.B selk +\- keyboard-only rectangle selector program for X11 +.SH SYNOPSIS +.B selk +.RB [ --help ] +.SH OPTIONS +.TP +.B \-\-help +Print a short text that describes how to use the program. +Any call other than simply +.B selk +will result in this behavior. +.TP +.B (otherwise) +a graphical interface will appear for selecting a rectangle. +.SH CONTROLS +.PP +The program uses the following controls: +.PP + [hjkl]: move selection + [shift]: slower + [control]: faster + [space]: confirm + [m]: output monitor + [n]: output window + [escape]: cancel +.PP +Position the top-left corner, press space, position the bottom-right corner, +then press space again. Alternatively press m or n at any time. +.PP +After completion, the program outputs the rectangle in the form of four +integer values: +.B X Y W H. +.SH AUTHORS +Original code from NRK, refactored + updated by Emma Weaver. +.SH LICENSE +See the LICENSE file for the terms of redistribution. +
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.