├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── config.h └── crud.c /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: evrdrm 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # Debug files 32 | *.dSYM/ 33 | ktrace.out 34 | *.core 35 | crud 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Rose 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC ?= gcc 2 | PREFIX ?= /usr/local 3 | CFLAGS ?= -Wall -pedantic 4 | CFLAGS += `pkg-config --cflags x11 xext` -std=gnu99 5 | LDFLAGS += `pkg-config --libs x11 xext` 6 | 7 | all: 8 | $(CC) crud.c -o crud $(LDFLAGS) $(CFLAGS) 9 | 10 | debug: 11 | $(CC) crud.c -o crud $(LDFLAGS) $(CFLAGS) -g 12 | 13 | install: 14 | install -D -m755 crud $(DESTDIR)$(PREFIX)/bin/crud 15 | 16 | uninstall: 17 | rm -f $(DESTDIR)$(PREFIX)/bin/crud 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # crud 2 | crud (C Rectangular oUtline Drawer) is a tool which lets you select a region on the screen and prints information about that region to stdout. 3 | 4 | It allows changing the selection border size and the color of the selection at compile time through its configuration file, `config.h`. It tries hard not to do anything unnecessary, consequently, the source code is very small and readable. Its output is very much like [slop](https://github.com/naelstrof/slop), however `crud` is much smaller. 5 | 6 | Like slop, it draws the selection using a window rather than XDrawRectangle, thus we can be sure the selection never leaves artifacts or shows up on a screenshot. 7 | 8 | # Inactive? 9 | crud is finished and usable, any first party updates to it are on a bugfix or quality-of-life basis. You are free to submit PRs and they will be merged if appropriate. 10 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | #define FOREGROUND_COLOR 0x888888 // color to use for the selection border, RGB 2 | #define BORDER 4 // width of the selection border, in pixels 3 | #define PERSISTENT_GRABBING 0 // if nonzero, crud will retry grabbing the cursor repeatedly if it initially fails 4 | -------------------------------------------------------------------------------- /crud.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "config.h" 12 | 13 | enum Cursors { 14 | Normal = 0, 15 | Crosshair = 1 16 | }; 17 | 18 | typedef struct { 19 | int x; 20 | int y; 21 | int width; 22 | int height; 23 | } Selection; 24 | 25 | typedef struct { 26 | Selection selection; 27 | Window window; 28 | } WindowSelection; 29 | 30 | // static values, these are initialized in setup. 31 | static Cursor cursor[Crosshair + 1]; 32 | static Display* display = NULL; 33 | static int screen = 0; 34 | static Window root; 35 | 36 | void setup() { 37 | display = XOpenDisplay(NULL); 38 | 39 | if (!display) { 40 | printf("Failed to open the display.\n"); 41 | exit(1); 42 | } 43 | 44 | cursor[Normal] = XCreateFontCursor(display, XC_left_ptr); 45 | cursor[Crosshair] = XCreateFontCursor(display, XC_crosshair); 46 | 47 | screen = DefaultScreen(display); 48 | root = RootWindow(display, screen); 49 | } 50 | 51 | void teardown() { 52 | XFreeCursor(display, cursor[0]); 53 | XFreeCursor(display, cursor[1]); 54 | 55 | if (display != NULL) 56 | XCloseDisplay(display); 57 | } 58 | 59 | void set_rects_from_selection(XRectangle* rects, Selection dimensions) { 60 | rects[0].x = dimensions.x - BORDER; 61 | rects[0].y = dimensions.y - BORDER; 62 | rects[0].width = BORDER; 63 | rects[0].height = dimensions.height + BORDER * 2; 64 | 65 | rects[1].x = dimensions.x; 66 | rects[1].y = dimensions.y - BORDER; 67 | rects[1].width = dimensions.width + BORDER; 68 | rects[1].height = BORDER; 69 | 70 | rects[2].x = dimensions.x + dimensions.width; 71 | rects[2].y = dimensions.y - BORDER; 72 | rects[2].width = BORDER; 73 | rects[2].height = dimensions.height + BORDER * 2; 74 | 75 | rects[3].x = dimensions.x; 76 | rects[3].y = dimensions.y + dimensions.height; 77 | rects[3].width = dimensions.width + BORDER; 78 | rects[3].height = BORDER; 79 | } 80 | 81 | void switch_cursor(Cursor* cursor) { 82 | int mask = PointerMotionMask | ButtonPressMask | ButtonReleaseMask; 83 | int error; 84 | 85 | do { 86 | error = XGrabPointer(display, root, True, 87 | mask, GrabModeAsync, GrabModeAsync, 88 | None, *cursor, CurrentTime); 89 | sleep(0); 90 | } while (PERSISTENT_GRABBING && error); 91 | 92 | if (error) 93 | exit(1); 94 | } 95 | 96 | WindowSelection make_selection(Selection dimensions) { 97 | XSetWindowAttributes attributes; 98 | unsigned long value_mask = CWBackPixel | CWOverrideRedirect | CWEventMask; 99 | Screen* sscreen = ScreenOfDisplay(display, DefaultScreen(display)); 100 | 101 | attributes.background_pixel = FOREGROUND_COLOR; 102 | attributes.override_redirect = true; 103 | attributes.event_mask = StructureNotifyMask; 104 | 105 | Window window = 106 | XCreateWindow(display, root, 0, 0, WidthOfScreen(sscreen), HeightOfScreen(sscreen), 107 | 0, CopyFromParent, InputOutput, CopyFromParent, value_mask, &attributes); 108 | 109 | XRectangle rects[4]; 110 | XRectangle rect; 111 | 112 | set_rects_from_selection(rects, dimensions); 113 | XShapeCombineRectangles(display, window, ShapeBounding, 0, 0, rects, 4, ShapeSet, 0); 114 | 115 | rect.x = rect.y = rect.width = rect.height = 0; 116 | XShapeCombineRectangles(display, window, ShapeInput, 0, 0, &rect, 1, ShapeSet, 0); 117 | 118 | XMapWindow(display, window); 119 | 120 | return (WindowSelection) { 121 | .selection = dimensions, 122 | .window = window 123 | }; 124 | } 125 | 126 | int destroy_check(Display* display, XEvent* ev, XPointer win) { 127 | return ev->type == DestroyNotify && ev->xdestroywindow.window == *((Window*)win); 128 | } 129 | 130 | void destroy_selection(WindowSelection* sel) { 131 | XEvent ev; 132 | 133 | XSetWindowBackground(display, sel->window, 0); 134 | XClearWindow(display, sel->window); 135 | XDestroyWindow(display, sel->window); 136 | XIfEvent(display, &ev, &destroy_check, (XPointer)&sel->window); 137 | } 138 | 139 | void set_selection(WindowSelection* sel, Selection dimensions) { 140 | XRectangle rects[4]; 141 | set_rects_from_selection(rects, dimensions); 142 | sel->selection = dimensions; 143 | XShapeCombineRectangles(display, sel->window, ShapeBounding, 0, 0, rects, 4, ShapeSet, 0); 144 | } 145 | 146 | int main(int argc, char **argv) { 147 | #ifdef __OpenBSD__ 148 | if (pledge("stdio rpath unix prot_exec", NULL) == -1) 149 | err(1, "pledge"); 150 | #endif 151 | 152 | setup(); 153 | 154 | // make a selection and make it as small as possible 155 | WindowSelection ws = make_selection((Selection) { 156 | .x = 0, 157 | .y = 0, 158 | .width = 0, 159 | .height = 0 160 | }); 161 | 162 | bool done = false; 163 | bool button_state = false; 164 | int x = 0, y = 0, width = 0, height = 0; 165 | int start_x = 0, start_y = 0; 166 | XEvent event; 167 | 168 | switch_cursor(&cursor[1]); 169 | 170 | XGrabKeyboard(display, root, GrabModeSync, GrabModeAsync, True, CurrentTime); 171 | 172 | while (!done) { 173 | XNextEvent(display, &event); 174 | switch (event.type) { 175 | case KeyPress: 176 | if (XLookupKeysym(&event.xkey, 0) == XK_Escape) return 1; 177 | break; 178 | 179 | case ButtonPress: 180 | if (event.xbutton.button == Button3) 181 | return 1; 182 | button_state = true; 183 | x = start_x = event.xbutton.x_root; 184 | y = start_y = event.xbutton.y_root; 185 | width = height = 0; 186 | break; 187 | 188 | case MotionNotify: 189 | if (button_state) { 190 | set_selection(&ws, (Selection) { 191 | .x = x, 192 | .y = y, 193 | .width = width, 194 | .height = height 195 | }); 196 | 197 | x = event.xbutton.x_root; 198 | y = event.xbutton.y_root; 199 | 200 | if (x > start_x) { 201 | width = x - start_x; 202 | x = start_x; 203 | } 204 | 205 | else { 206 | width = start_x - x; 207 | } 208 | 209 | if (y > start_y) { 210 | height = y - start_y; 211 | y = start_y; 212 | } 213 | 214 | else { 215 | height = start_y - y; 216 | } 217 | 218 | set_selection(&ws, (Selection) { 219 | .x = x, 220 | .y = y, 221 | .width = width, 222 | .height = height 223 | }); 224 | } 225 | 226 | break; 227 | 228 | case ButtonRelease: 229 | if (event.xbutton.x_root == start_x && event.xbutton.y_root == start_y) { 230 | done = true; 231 | button_state = false; 232 | 233 | Window hroot, hovered; 234 | int rx, ry, hx, hy; 235 | unsigned int m; 236 | 237 | if (XQueryPointer(display, root, &hroot, &hovered, &rx, &ry, &hx, &hy, &m)) { 238 | XWindowAttributes attrs; 239 | XGetWindowAttributes(display, hovered, &attrs); 240 | // update with the selected window's attributes 241 | set_selection(&ws, (Selection) { 242 | .x = attrs.x, 243 | .y = attrs.y, 244 | .width = attrs.width, 245 | .height = attrs.height 246 | }); 247 | } 248 | } 249 | 250 | else { 251 | done = true; 252 | button_state = false; 253 | } 254 | break; 255 | 256 | default: 257 | break; 258 | } 259 | } 260 | 261 | destroy_selection(&ws); 262 | 263 | XUngrabKeyboard(display, CurrentTime); 264 | XUngrabPointer(display, CurrentTime); 265 | XSync(display, 1); 266 | 267 | Selection* result = &ws.selection; 268 | 269 | printf("W=%d\nH=%d\nX=%d\nY=%d\n", result->width, result->height, result->x, result->y); 270 | printf("G=%dx%d+%d+%d\n", result->width, result->height, result->x, result->y); 271 | 272 | XFlush(display); 273 | 274 | teardown(); 275 | 276 | return 0; 277 | } 278 | --------------------------------------------------------------------------------