├── .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 |
--------------------------------------------------------------------------------