├── .gitignore ├── LICENSE ├── Makefile ├── README.md └── src ├── boox.c ├── config.h ├── parsing ├── parsing.c └── parsing.h └── xcb ├── connection.c ├── connection.h ├── selection.c └── selection.h /.gitignore: -------------------------------------------------------------------------------- 1 | boox 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC ?= cc 2 | 3 | SRC = src/boox.c src/xcb/connection.c src/xcb/selection.c src/parsing/parsing.c 4 | 5 | CFLAGS = -Wall -Wextra -pedantic 6 | DEPENDS = xcb xcb-cursor xcb-shape 7 | LDFLAGS = `pkg-config --cflags --libs $(DEPENDS)` 8 | 9 | PREFIX ?= /usr/local 10 | TARGET ?= boox 11 | 12 | all: 13 | $(CC) $(SRC) -o $(TARGET) $(CFLAGS) $(LDFLAGS) 14 | 15 | install: all 16 | install -D -m 755 $(TARGET) "$(DESTDIR)$(PREFIX)/bin/$(TARGET)" 17 | 18 | uninstall: 19 | rm -f "$(DESTDIR)$(PREFIX)/bin/$(TARGET)" 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # boox 2 | 3 | This project has been superseded by [bento](https://github.com/BanchouBoo/bento) 4 | 5 | ## details 6 | `boox` is a screen region and point/window selection tool with formattable output similar to [crud](https://github.com/ix/crud), [hacksaw](https://github.com/neXromancers/hacksaw), or [slop](https://github.com/naelstrof/slop). Like these other tools, `boox` draws the selection using a window. 7 | 8 | You can click on a window without dragging to select the geometry of that window, and the selection border will snap to windows you hover over. You can also right click to cancel the selection. 9 | 10 | Modifier keys change how selection works in various ways: 11 | - SHIFT: Keeps the selection size constant and instead moves the whole selection region around with your mouse 12 | - CONTROL: Resize from the center of the selection instead of the top left 13 | - ALT: Lock the selection to a specific axis (whichever you move in first), stacks with other modifiers 14 | 15 | If you run with `-p` you will select a point/window. 16 | 17 | ## flags/configuration 18 | Region selection output format can be configured with the `-f` flag or with the `BOOX_SELECTION_FORMAT` environment variable, to format output, use a string with `%x`, `%y`, `%w`, and `%h` to fill in the selection region values, you can also use `%i` for the window ID if the you selected a whole window by clicking it rather than dragging a selection, otherwise `%i` will just output 0. For example, to get the same output as crud you'd run `boox -f 'W=%w\nH=%h\nX=%x\nY=%y\nG=%wx%h+%x+%y'`. The default output is `%wx%h+%x+%y`. 19 | 20 | For point selection format you can still use `-f` or you can use the `BOOX_POINT_FORMAT` environment variable. All the formatting options are the same, though `%w` and `%h` will always be 0. The default output is `%x %y`. 21 | 22 | Border size can be configured with the `-b` flag or the `BOOX_BORDER_SIZE` environment variable, e.g. `boox -b 4` 23 | 24 | Border color can be configured with the `-c` flag or the `BOOX_BORDER_COLOR` environment variable, the value should be a hex color value **without** the `#`, e.g. `boox -c ff0000` 25 | 26 | Normally when the mouse pointer is already captured, boox will silently exit with an error, but you can use the `-w` flag to make it wait until it can capture the pointer 27 | 28 | You can restrict the selection to a specific window with the `-r` flag. Valid values are `root` (default), `current` (the current active window as determined by `_NET_ACTIVE_WINDOW`), and a specific window ID. A pattern you may find useful is `boox -r $(boox -p -f '%i')` to first select what window you want the selection to be restricted to then start the actual selection 29 | 30 | Some flags can also have their default values changed in `src/config.h` 31 | 32 | ## todo (maybe) 33 | - Aspect ratio selection mode 34 | - Grid selection mode, split the screen into a grid of rows and columns and snap the selection to grid cells, with optional padding between cells and at the edges of the screen 35 | - Start a selection with the initial values being that of a window 36 | - Keyboard control for selection, arrow keys/hjkl for moving the pointer, enter and/or space to start and finish the selection, alt to move by single pixel distances rather than jumping larger distances, shift and control work as they already do 37 | - Abstract platform backend to make it easier to implement other backends in the future 38 | - Wayland backend 39 | -------------------------------------------------------------------------------- /src/boox.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "config.h" 7 | #include "xcb/connection.h" 8 | #include "xcb/selection.h" 9 | #include "parsing/parsing.h" 10 | 11 | typedef enum { 12 | ALT_STATE_NONE, 13 | ALT_STATE_WAITING, 14 | ALT_STATE_HORIZONTAL, 15 | ALT_STATE_VERTICAL 16 | } alt_state_t; 17 | 18 | int running = 1; 19 | 20 | static void handle_events(void) { 21 | xcb_generic_event_t *ev; 22 | xcb_get_geometry_reply_t *geom; 23 | 24 | static xcb_window_t hovered_win = 0; 25 | static int selecting = 0; 26 | static point_t last_mouse_pos = { .x = 0, .y = 0 }; 27 | static alt_state_t alt_state = ALT_STATE_NONE; 28 | 29 | ev = xcb_wait_for_event(xcb_connection); 30 | if (!ev) { 31 | exit(EXIT_FAILURE); 32 | } 33 | 34 | switch (ev->response_type & ~0x80) { 35 | case XCB_BUTTON_PRESS: { 36 | xcb_button_press_event_t *e = ( xcb_button_press_event_t *)ev; 37 | if (e->detail == 1) { 38 | if (selection_mode == MODE_SELECT) { 39 | if (e->child && 40 | (e->state & XCB_MOD_MASK_CONTROL || 41 | e->state & XCB_MOD_MASK_SHIFT || 42 | alt_state == ALT_STATE_WAITING)) { 43 | 44 | geom = xcb_get_geometry_reply(xcb_connection, xcb_get_geometry(xcb_connection, e->child), NULL); 45 | selection = (rect_t) { 46 | .x = geom->x + geom->border_width, 47 | .y = geom->y + geom->border_width, 48 | .w = geom->width, 49 | .h = geom->height 50 | }; 51 | last_mouse_pos.x = selection.x + selection.w; 52 | last_mouse_pos.y = selection.y + selection.h; 53 | xcb_warp_pointer(xcb_connection, XCB_NONE, xcb_screen->root, 0, 0, 0, 0, selection.x + selection.w, selection.y + selection.h); 54 | flush(); 55 | } else { 56 | selection.x = e->root_x; 57 | selection.y = e->root_y; 58 | last_mouse_pos.x = e->root_x; 59 | last_mouse_pos.y = e->root_y; 60 | } 61 | selecting = 1; 62 | } 63 | } else if (e->detail == 3) { 64 | exit(EXIT_FAILURE); 65 | } 66 | } break; 67 | 68 | case XCB_KEY_PRESS: { 69 | xcb_key_press_event_t *e = ( xcb_key_press_event_t *)ev; 70 | // 64 == alt 71 | if (e->detail == 64) { 72 | alt_state = ALT_STATE_WAITING; 73 | } 74 | } break; 75 | 76 | case XCB_KEY_RELEASE: { 77 | xcb_key_release_event_t *e = ( xcb_key_release_event_t *)ev; 78 | // 64 == alt 79 | if (e->detail == 64) { 80 | alt_state = ALT_STATE_NONE; 81 | if (selecting) { 82 | // set last_mouse_pos to prevent jumping after the warp while selecting with control 83 | last_mouse_pos.x = selection.x + selection.w; 84 | last_mouse_pos.y = selection.y + selection.h; 85 | xcb_warp_pointer(xcb_connection, XCB_NONE, xcb_screen->root, 0, 0, 0, 0, last_mouse_pos.x, last_mouse_pos.y); 86 | flush(); 87 | } 88 | } 89 | } break; 90 | 91 | case XCB_MOTION_NOTIFY: { 92 | xcb_motion_notify_event_t *e = ( xcb_motion_notify_event_t *)ev; 93 | if (selecting) { 94 | const int16_t relative_x = (e->root_x - last_mouse_pos.x); 95 | const int16_t relative_y = (e->root_y - last_mouse_pos.y); 96 | 97 | if (alt_state == ALT_STATE_WAITING) { 98 | if (abs(relative_x) > abs(relative_y)) alt_state = ALT_STATE_HORIZONTAL; 99 | else if (abs(relative_y) > abs(relative_x)) alt_state = ALT_STATE_VERTICAL; 100 | } 101 | 102 | if (e->state & XCB_MOD_MASK_SHIFT) { 103 | if (alt_state == ALT_STATE_HORIZONTAL || alt_state == ALT_STATE_NONE) { 104 | selection.x += e->root_x - selection.x - selection.w; 105 | } 106 | if (alt_state == ALT_STATE_VERTICAL || alt_state == ALT_STATE_NONE) { 107 | selection.y += e->root_y - selection.y - selection.h; 108 | } 109 | } else if (e->state & XCB_MOD_MASK_CONTROL) { 110 | if (alt_state == ALT_STATE_HORIZONTAL || alt_state == ALT_STATE_NONE) { 111 | selection.x -= relative_x; 112 | selection.w += relative_x * 2; 113 | } 114 | if (alt_state == ALT_STATE_VERTICAL || alt_state == ALT_STATE_NONE) { 115 | selection.y -= relative_y; 116 | selection.h += relative_y * 2; 117 | } 118 | } else { 119 | if (alt_state == ALT_STATE_HORIZONTAL || alt_state == ALT_STATE_NONE) { 120 | selection.w = e->root_x - selection.x; 121 | } 122 | if (alt_state == ALT_STATE_VERTICAL || alt_state == ALT_STATE_NONE) { 123 | selection.h = e->root_y - selection.y; 124 | } 125 | } 126 | update_selection_window(selection); 127 | } else if (hovered_win != e->child) { 128 | hovered_win = e->child ? e->child : xcb_screen->root; 129 | geom = xcb_get_geometry_reply(xcb_connection, xcb_get_geometry(xcb_connection, hovered_win), NULL); 130 | update_selection_window((rect_t) { 131 | .x = geom->x + geom->border_width, 132 | .y = geom->y + geom->border_width, 133 | .w = geom->width, 134 | .h = geom->height 135 | }); 136 | } 137 | last_mouse_pos.x = e->root_x; 138 | last_mouse_pos.y = e->root_y; 139 | } break; 140 | 141 | case XCB_BUTTON_RELEASE: { 142 | xcb_button_release_event_t *e = ( xcb_button_release_event_t *)ev; 143 | if (e->detail != 1) 144 | break; 145 | 146 | if (selection_mode == MODE_POINT) { 147 | selection.x = e->root_x; 148 | selection.y = e->root_y; 149 | selected_window = get_child_window(e->child); 150 | selected_window = selected_window ? selected_window : e->child; 151 | 152 | xcb_ungrab_pointer(xcb_connection, XCB_CURRENT_TIME); 153 | running = 0; 154 | } else if (selecting) { 155 | if (selection.w == 0 && selection.h == 0) { 156 | geom = xcb_get_geometry_reply(xcb_connection, 157 | xcb_get_geometry(xcb_connection, e->child ? e->child : xcb_screen->root), NULL); 158 | selected_window = get_child_window(e->child); 159 | selected_window = selected_window ? selected_window : e->child; 160 | 161 | selection = (rect_t) { 162 | .x = geom->x + geom->border_width, 163 | .y = geom->y + geom->border_width, 164 | .w = geom->width, 165 | .h = geom->height 166 | }; 167 | } 168 | 169 | xcb_ungrab_pointer(xcb_connection, XCB_CURRENT_TIME); 170 | running = 0; 171 | } 172 | 173 | } break; 174 | } 175 | free(ev); 176 | } 177 | 178 | void print_help(int exit_code) { 179 | fprintf(stderr, "boox: [OPTIONS]\n"); 180 | 181 | fprintf(stderr, " -h print this help and exit\n\n"); 182 | 183 | fprintf(stderr, " -w if the pointer is already captured, wait for it to be uncaptured instead of failing\n"); 184 | fprintf(stderr, " -p select a point instead of region\n"); 185 | fprintf(stderr, " -f FORMAT set output format available values: %%x, %%y, %%w, and %%h for selection values, %%i for window ID (0 if not applicable or root)\n"); 186 | fprintf(stderr, " default selection format: '%s'\n", DEFAULT_SELECTION_OUTPUT_FORMAT); 187 | fprintf(stderr, " default point format: '%s'\n", DEFAULT_POINT_OUTPUT_FORMAT); 188 | fprintf(stderr, " -r WINDOW restrict selection to window valid values: root, current, or a window ID\n"); 189 | fprintf(stderr, " default: '%s'\n", "root"); 190 | fprintf(stderr, " -b SIZE set selection border size default: %d\n", DEFAULT_BORDER_SIZE); 191 | fprintf(stderr, " -c COLOR set selection border color default: %x\n", DEFAULT_BORDER_COLOR); 192 | 193 | exit(exit_code); 194 | } 195 | 196 | int main(int argc, char **argv) { 197 | char *format; 198 | char *selection_format_env = getenv("BOOX_SELECTION_FORMAT"); 199 | char *point_format_env = getenv("BOOX_POINT_FORMAT"); 200 | 201 | char *border_size_env = getenv("BOOX_BORDER_SIZE"); 202 | if (border_size_env) 203 | border_size = strtol(border_size_env, NULL, 10); 204 | else 205 | border_size = DEFAULT_BORDER_SIZE; 206 | 207 | char *border_color_env = getenv("BOOX_BORDER_COLOR"); 208 | if (border_color_env) { 209 | border_color = strtol(border_color_env, NULL, 16); 210 | } else 211 | border_color = DEFAULT_BORDER_COLOR; 212 | 213 | int wait_until_cursor_grabbable = 0; 214 | char *constraining_window_tag = "root"; 215 | 216 | char opt = 0; 217 | while ((opt = getopt(argc, argv, "hwpf:b:c:r:")) != -1) { 218 | switch (opt) { 219 | case 'h': { 220 | print_help(EXIT_SUCCESS); 221 | } break; 222 | case 'w': { 223 | wait_until_cursor_grabbable = 1; 224 | } break; 225 | case 'p': { 226 | selection_mode = MODE_POINT; 227 | } break; 228 | case 'f': { 229 | format = optarg; 230 | } break; 231 | case 'b': { 232 | border_size = strtol(optarg, NULL, 10); 233 | } break; 234 | case 'c': { 235 | border_color = strtol(optarg, NULL, 16); 236 | } break; 237 | case 'r': { 238 | constraining_window_tag = optarg; 239 | } break; 240 | case '?': print_help(EXIT_FAILURE); 241 | } 242 | } 243 | 244 | if (!format) { 245 | if (selection_mode == MODE_SELECT) 246 | format = selection_format_env ? selection_format_env : DEFAULT_SELECTION_OUTPUT_FORMAT; 247 | else if (selection_mode == MODE_POINT) 248 | format = point_format_env ? point_format_env : DEFAULT_POINT_OUTPUT_FORMAT; 249 | } 250 | 251 | if (!xcb_initialize(wait_until_cursor_grabbable, constraining_window_tag)) 252 | return EXIT_FAILURE; 253 | 254 | while (running) { 255 | handle_events(); 256 | } 257 | 258 | selection = fix_rect(selection); 259 | 260 | xcb_finalize(); 261 | 262 | formatted_print(format); 263 | 264 | return EXIT_SUCCESS; 265 | } 266 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | 4 | #define DEFAULT_BORDER_SIZE 4 5 | #define DEFAULT_BORDER_COLOR 0x420dab 6 | #define DEFAULT_SELECTION_OUTPUT_FORMAT "%wx%h+%x+%y" 7 | #define DEFAULT_POINT_OUTPUT_FORMAT "%x %y" 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /src/parsing/parsing.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "parsing.h" 5 | #include "../xcb/selection.h" 6 | 7 | char *format_output(char *src) 8 | { 9 | static char buffer[4096]; 10 | 11 | int offset = 0; 12 | while (*src) { 13 | char c = src[0]; 14 | if (c == '%') { 15 | char nc = src[1]; 16 | switch (nc) { 17 | case 'w': { 18 | offset += sprintf(buffer+offset, "%d", selection.w); 19 | src++; 20 | } break; 21 | case 'h': { 22 | offset += sprintf(buffer+offset, "%d", selection.h); 23 | src++; 24 | } break; 25 | case 'x': { 26 | offset += sprintf(buffer+offset, "%d", selection.x); 27 | src++; 28 | } break; 29 | case 'y': { 30 | offset += sprintf(buffer+offset, "%d", selection.y); 31 | src++; 32 | } break; 33 | case 'i': { 34 | offset += sprintf(buffer+offset, "%d", selected_window); 35 | src++; 36 | } break; 37 | default: { 38 | strncpy(buffer+offset, src, 1); 39 | offset++; 40 | } break; 41 | } 42 | } else if (c == '\\') { 43 | char nc = src[1]; 44 | switch (nc) { 45 | case 'n': { 46 | offset += sprintf(buffer+offset, "\n"); 47 | src++; 48 | } break; 49 | case 't': { 50 | offset += sprintf(buffer+offset, "\t"); 51 | src++; 52 | } break; 53 | default: { 54 | strncpy(buffer+offset, src, 1); 55 | offset++; 56 | } break; 57 | } 58 | } else { 59 | strncpy(buffer+offset, src, 1); 60 | offset++; 61 | } 62 | src++; 63 | } 64 | return buffer; 65 | } 66 | 67 | void formatted_print(char *src) 68 | { 69 | puts(format_output(src)); 70 | } 71 | -------------------------------------------------------------------------------- /src/parsing/parsing.h: -------------------------------------------------------------------------------- 1 | #ifndef PARSING_H 2 | #define PARSING_H 3 | 4 | char *format_output(char *src); 5 | void formatted_print(char *src); 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /src/xcb/connection.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "connection.h" 7 | #include "selection.h" 8 | 9 | xcb_connection_t *xcb_connection; 10 | xcb_screen_t *xcb_screen; 11 | 12 | int xcb_initialize(int wait_until_cursor_grabbable, char *constraining_window_tag) 13 | { 14 | int values[4]; 15 | int mask; 16 | 17 | if (xcb_connection_has_error(xcb_connection = xcb_connect(NULL, NULL))) 18 | return 0; 19 | 20 | xcb_screen = xcb_setup_roots_iterator(xcb_get_setup(xcb_connection)).data; 21 | 22 | xcb_cursor_context_t *ctx; 23 | xcb_cursor_t cursor = XCB_NONE; 24 | if (xcb_cursor_context_new(xcb_connection, xcb_screen, &ctx) >= 0) { 25 | cursor = xcb_cursor_load_cursor(ctx, "crosshair"); 26 | } 27 | 28 | xcb_grab_pointer_cookie_t grab_pointer_cookie; 29 | xcb_grab_pointer_reply_t *pointer_reply; 30 | 31 | if (strcmp(constraining_window_tag, "root") == 0) { 32 | constraining_window = xcb_screen->root; 33 | } else if (strcmp(constraining_window_tag, "current") == 0) { 34 | xcb_intern_atom_cookie_t focused_atom_cookie = xcb_intern_atom(xcb_connection, 1, 35 | 18, "_NET_ACTIVE_WINDOW"); 36 | xcb_intern_atom_reply_t *focused_atom = xcb_intern_atom_reply(xcb_connection, focused_atom_cookie, NULL); 37 | if (focused_atom) { 38 | xcb_get_property_cookie_t property_cookie = xcb_get_property(xcb_connection, 0, 39 | xcb_screen->root, focused_atom->atom, XCB_ATOM_WINDOW, 0, 1); 40 | xcb_get_property_reply_t *property = xcb_get_property_reply(xcb_connection, property_cookie, NULL); 41 | if (property) 42 | constraining_window = *(xcb_window_t*)xcb_get_property_value(property); 43 | } 44 | } else { 45 | constraining_window = (xcb_window_t)strtol(constraining_window_tag, NULL, 10); 46 | } 47 | constraining_window = constraining_window ? constraining_window : xcb_screen->root; 48 | 49 | do { 50 | grab_pointer_cookie = xcb_grab_pointer(xcb_connection, 0, 51 | xcb_screen->root, XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | 52 | XCB_EVENT_MASK_POINTER_MOTION, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, 53 | xcb_screen->root, cursor, XCB_CURRENT_TIME); 54 | 55 | pointer_reply = xcb_grab_pointer_reply(xcb_connection, 56 | grab_pointer_cookie, NULL); 57 | 58 | } while (wait_until_cursor_grabbable && pointer_reply->status != XCB_GRAB_STATUS_SUCCESS); 59 | 60 | if (pointer_reply && pointer_reply->status == XCB_GRAB_STATUS_ALREADY_GRABBED) 61 | return 0; 62 | 63 | // grab alt key (64) 64 | xcb_grab_key(xcb_connection, 0, xcb_screen->root, XCB_MOD_MASK_ANY, 64, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); 65 | 66 | xcb_free_cursor(xcb_connection, cursor); 67 | 68 | values[0] = XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY; 69 | mask = XCB_CW_EVENT_MASK; 70 | xcb_change_window_attributes_checked(xcb_connection, xcb_screen->root, mask, values); 71 | 72 | selection_window_initialize(); 73 | 74 | flush(); 75 | 76 | return 1; 77 | } 78 | 79 | void xcb_finalize(void) 80 | { 81 | if (xcb_connection) { 82 | xcb_kill_client(xcb_connection, selection_window); 83 | xcb_disconnect(xcb_connection); 84 | } 85 | } 86 | 87 | void flush() 88 | { 89 | xcb_flush(xcb_connection); 90 | } 91 | -------------------------------------------------------------------------------- /src/xcb/connection.h: -------------------------------------------------------------------------------- 1 | #ifndef CONNECTION_H 2 | #define CONNECTION_H 3 | 4 | #include 5 | 6 | extern xcb_connection_t *xcb_connection; 7 | extern xcb_screen_t *xcb_screen; 8 | 9 | int xcb_initialize(int wait_until_cursor_grabbable, char *constraining_window_tag); 10 | void xcb_finalize(void); 11 | void flush(void); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/xcb/selection.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "connection.h" 6 | #include "selection.h" 7 | #include "../config.h" 8 | 9 | mode_t selection_mode = MODE_SELECT; 10 | xcb_window_t selection_window; 11 | xcb_window_t constraining_window; 12 | rect_t selection; 13 | xcb_window_t selected_window; 14 | int border_size; 15 | int border_color; 16 | 17 | void selection_window_initialize(void) 18 | { 19 | int values[] = { border_color, 1, XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY}; 20 | int mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; 21 | 22 | selection_window = xcb_generate_id(xcb_connection); 23 | xcb_create_window(xcb_connection, XCB_COPY_FROM_PARENT, selection_window, xcb_screen->root, 0, 0, 24 | xcb_screen->width_in_pixels, xcb_screen->height_in_pixels, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, 25 | XCB_COPY_FROM_PARENT, mask, values); 26 | xcb_change_property(xcb_connection, XCB_PROP_MODE_REPLACE, selection_window, 27 | XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, 4, "boox"); 28 | xcb_change_property(xcb_connection, XCB_PROP_MODE_REPLACE, selection_window, 29 | XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, 10, "boox\0boox"); 30 | 31 | 32 | xcb_rectangle_t rects[4]; 33 | set_rects_from_selection(rects, (rect_t) { 34 | .x = -border_size, 35 | .y = -border_size, 36 | .w = 0, 37 | .h = 0 38 | }); 39 | // xcb_rectangle_t rect; 40 | 41 | xcb_shape_rectangles(xcb_connection, XCB_SHAPE_SO_SET, 42 | XCB_SHAPE_SK_BOUNDING, 0, selection_window, 0, 0, 4, rects); 43 | 44 | // TODO: I don't remember what this was for but everything works if I remove it? 45 | // xcb_shape_rectangles(xcb_connection, XCB_SHAPE_SO_SET, 46 | // XCB_SHAPE_SK_INPUT, 0, selection_window, 0, 0, 1, &rect); 47 | 48 | xcb_map_window(xcb_connection, selection_window); 49 | } 50 | 51 | void set_rects_from_selection(xcb_rectangle_t *rects, rect_t dimensions) 52 | { 53 | rects[0].x = dimensions.x - border_size; 54 | rects[0].y = dimensions.y - border_size; 55 | rects[0].width = border_size; 56 | rects[0].height = dimensions.h + border_size * 2; 57 | 58 | rects[1].x = dimensions.x; 59 | rects[1].y = dimensions.y - border_size; 60 | rects[1].width = dimensions.w + border_size; 61 | rects[1].height = border_size; 62 | 63 | rects[2].x = dimensions.x + dimensions.w; 64 | rects[2].y = dimensions.y - border_size; 65 | rects[2].width = border_size; 66 | rects[2].height = dimensions.h + border_size * 2; 67 | 68 | rects[3].x = dimensions.x; 69 | rects[3].y = dimensions.y + dimensions.h; 70 | rects[3].width = dimensions.w + border_size; 71 | rects[3].height = border_size; 72 | } 73 | 74 | void update_selection_window(rect_t dimensions) 75 | { 76 | xcb_rectangle_t rects[4]; 77 | set_rects_from_selection(rects, fix_rect(dimensions)); 78 | xcb_shape_rectangles(xcb_connection, XCB_SHAPE_SO_SET, 79 | XCB_SHAPE_SK_BOUNDING, 0, selection_window, 0, 0, 4, rects); 80 | flush(); 81 | } 82 | 83 | rect_t fix_rect(rect_t dimensions) 84 | { 85 | xcb_get_geometry_reply_t *geom = xcb_get_geometry_reply(xcb_connection, 86 | xcb_get_geometry(xcb_connection, constraining_window), NULL); 87 | 88 | #define min(a,b) (((a)<(b))?(a):(b)) 89 | #define max(a,b) (((a)>(b))?(a):(b)) 90 | #define clamp(a, low, high) min(max(a, low), high) 91 | 92 | int window_left_edge = geom->x; 93 | int window_right_edge = window_left_edge + geom->width; 94 | int window_top_edge = geom->y; 95 | int window_bottom_edge = window_top_edge + geom->height; 96 | 97 | int dimensions_left_edge = min(dimensions.x, dimensions.x + dimensions.w); 98 | int dimensions_right_edge = max(dimensions.x, dimensions.x + dimensions.w); 99 | int dimensions_top_edge = min(dimensions.y, dimensions.y + dimensions.h); 100 | int dimensions_bottom_edge = max(dimensions.y, dimensions.y + dimensions.h); 101 | 102 | int left_edge = clamp(dimensions_left_edge, window_left_edge, window_right_edge); 103 | int right_edge = clamp(dimensions_right_edge, window_left_edge, window_right_edge); 104 | int top_edge = clamp(dimensions_top_edge, window_top_edge, window_bottom_edge); 105 | int bottom_edge = clamp(dimensions_bottom_edge, window_top_edge, window_bottom_edge); 106 | 107 | dimensions.x = left_edge; 108 | dimensions.w = right_edge - left_edge; 109 | dimensions.y = top_edge; 110 | dimensions.h = bottom_edge - top_edge; 111 | 112 | return dimensions; 113 | } 114 | 115 | xcb_window_t get_child_window(xcb_window_t wid) { 116 | xcb_query_tree_cookie_t tree_cookie = xcb_query_tree(xcb_connection, wid); 117 | xcb_query_tree_reply_t *tree = xcb_query_tree_reply(xcb_connection, tree_cookie, NULL); 118 | if (!tree) 119 | return XCB_WINDOW_NONE; 120 | 121 | unsigned int nchildren = tree->children_len; 122 | if (!nchildren) 123 | return XCB_WINDOW_NONE; 124 | 125 | xcb_window_t *children = xcb_query_tree_children(tree); 126 | 127 | xcb_intern_atom_cookie_t wm_state_cookie = xcb_intern_atom(xcb_connection, 0, 8, "WM_STATE"); 128 | xcb_intern_atom_reply_t *wm_state = xcb_intern_atom_reply(xcb_connection, wm_state_cookie, NULL); 129 | 130 | int i; 131 | for (i = nchildren - 1; i >= 0; i--) { 132 | xcb_get_property_cookie_t property_cookie = xcb_get_property(xcb_connection, 0, 133 | children[i], wm_state->atom, XCB_GET_PROPERTY_TYPE_ANY, 0, 0); 134 | xcb_get_property_reply_t *property = xcb_get_property_reply(xcb_connection, property_cookie, NULL); 135 | 136 | if (property && property->type == XCB_NONE) 137 | continue; 138 | 139 | return children[i]; 140 | } 141 | 142 | for (i = nchildren - 1; i >= 0; i--) { 143 | if (children[i] != XCB_WINDOW_NONE) { 144 | wid = get_child_window(children[i]); 145 | if (wid != XCB_WINDOW_NONE) 146 | return wid; 147 | } 148 | } 149 | 150 | return wid; 151 | } 152 | -------------------------------------------------------------------------------- /src/xcb/selection.h: -------------------------------------------------------------------------------- 1 | #ifndef SELECTION_H 2 | #define SELECTION_H 3 | 4 | #include 5 | 6 | typedef struct { 7 | int x; 8 | int y; 9 | int w; 10 | int h; 11 | } rect_t; 12 | 13 | typedef struct { 14 | int x; 15 | int y; 16 | } point_t; 17 | 18 | typedef enum { 19 | MODE_SELECT, 20 | MODE_POINT 21 | } selection_mode_t; 22 | 23 | extern selection_mode_t selection_mode; 24 | extern xcb_window_t selection_window; 25 | extern xcb_window_t constraining_window; 26 | extern rect_t selection; 27 | extern xcb_window_t selected_window; 28 | extern int border_size; 29 | extern int border_color; 30 | 31 | void selection_window_initialize(void); 32 | void set_rects_from_selection(xcb_rectangle_t *rects, rect_t dimensions); 33 | void update_selection_window(rect_t dimensions); 34 | rect_t fix_rect(rect_t dimensions); 35 | xcb_window_t get_child_window(xcb_window_t wid); 36 | 37 | #endif 38 | --------------------------------------------------------------------------------