├── .gitignore ├── CHANGES ├── LICENSE ├── README ├── makefile └── xorg-choose-window.c /.gitignore: -------------------------------------------------------------------------------- 1 | /xorg-choose-window 2 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | 0.2.0-next: 2 | 3 | 0.2.0: 4 | * optional blacklisting and whitelisting of windows by ID 5 | * choice of format for the printed window ID 6 | * standard command-line options and help text 7 | * draw text centred on windows 8 | * works for more types of windows 9 | * require fewer key-presses on average 10 | * changed and documented exit codes 11 | 12 | 0.1.2: 13 | * fix bug: filters out the wrong windows (those specifying _NET_WM_WINDOW_TYPE 14 | normal) 15 | 16 | 0.1.1: 17 | * only display the remaining characters to type on windows 18 | * improved window filtering 19 | * fix for launching from other keyboard-grabbing programs 20 | 21 | 0.1.0: 22 | * first release 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | xorg-choose-window 0.2.0-next by Joseph Lansdowne. 2 | 3 | A utility for choosing from visible windows in Xorg. Aimed at tiling window 4 | managers. For example, you might focus the chosen window, or close it, or swap 5 | it with the currently focused window. 6 | 7 | http://ikn.org.uk/tool/xorg-choose-window 8 | 9 | LICENSE 10 | 11 | Licensed under the Apache License, version 2.0; if this was not included, you 12 | can find it here: 13 | http://www.apache.org/licenses/LICENSE-2.0.txt 14 | 15 | DEPENDENCIES 16 | 17 | XCB, xcb-util-keysyms, xcb-util-wm: http://xcb.freedesktop.org/ 18 | argp: 19 | * part of glibc: https://www.gnu.org/software/libc/ 20 | * argp-standalone: http://www.freebsdsoftware.org/devel/argp-standalone.html 21 | 22 | INSTALLATION 23 | 24 | Build dependencies: 25 | * Make 26 | * a C compiler 27 | * pkg-config 28 | 29 | Run `make' then `make install'; to uninstall, run `make uninstall'. 30 | 31 | It should be necessary to run `make install' as root (DESTDIR is supported). 32 | 33 | The following files are installed to the following default locations: 34 | 35 | xorg-choose-window -> /usr/local/bin/xorg-choose-window 36 | 37 | USAGE 38 | 39 | Run `xorg-choose-window --help' for usage information. 40 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | PROG := xorg-choose-window 2 | PKGCONFIG_LIBS := xcb xcb-keysyms xcb-icccm xcb-ewmh 3 | CFLAGS += -Wall `pkg-config --cflags ${PKGCONFIG_LIBS}` 4 | LDLIBS += -lm `pkg-config --libs ${PKGCONFIG_LIBS}` 5 | INSTALL_PROGRAM := install 6 | 7 | prefix := /usr/local 8 | exec_prefix := $(prefix) 9 | bindir := $(exec_prefix)/bin 10 | 11 | .PHONY: all clean distclean install uninstall 12 | 13 | all: $(PROG) 14 | 15 | clean: 16 | - $(RM) $(PROG) 17 | 18 | distclean: clean 19 | 20 | install: 21 | mkdir -p "$(DESTDIR)$(bindir)" 22 | $(INSTALL_PROGRAM) $(PROG) "$(DESTDIR)$(bindir)" 23 | 24 | uninstall: 25 | $(RM) -r "$(DESTDIR)$(bindir)/$(PROG)" 26 | -------------------------------------------------------------------------------- /xorg-choose-window.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 4 | this file except in compliance with the License. You may obtain a copy of the 5 | License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | 33 | // TODO (fixes) 34 | // using sysexits.h which is glibc-specific? any others? 35 | // mask usages (x3): what should the order be? doc says just pass in one 36 | // structure for wsetup.overlay_*, wsetup.window 37 | 38 | // TODO (improvements) 39 | // larger default font 40 | // configurable font/text size/colours 41 | // manpage 42 | // - mention in readme 43 | // - move exit status info from --help 44 | // option to only partially cover the window 45 | 46 | 47 | // -- types 48 | 49 | /** 50 | * Part of a keysym lookup, used to translate between keysyms and characters. 51 | * 52 | * character: the ASCII character 53 | * keysym: the Xorg keysym to interpret the character as 54 | */ 55 | typedef struct keysyms_lookup_t { 56 | char character; 57 | xcb_keysym_t keysym; 58 | } keysyms_lookup_t; 59 | 60 | /** 61 | * A recursive structure holding data about windows, used to track the windows 62 | * we care about. Exactly one of `window` (paired with `overlay_*`) and 63 | * `children` is non-NULL. 64 | * 65 | * ?overlay_window: the window we created over the top of the tracked window 66 | * ?overlay_font_gc: for drawing the text on `overlay_window` 67 | * ?overlay_bg_gc: for drawing the background on `overlay_window` 68 | * ?overlay_rect: the on-screen area covered by `overlay_window` 69 | * ?window: the pre-existing tracked window 70 | * character: the character that must be typed to select `window`, or to descend 71 | * into `children` 72 | * ?children: the continuation of the structure 73 | * children_size: size of `children` (0 if `children` is NULL) 74 | */ 75 | typedef struct window_setup_t { 76 | xcb_window_t* overlay_window; 77 | xcb_gcontext_t* overlay_font_gc; 78 | xcb_gcontext_t* overlay_bg_gc; 79 | xcb_rectangle_t* overlay_rect; 80 | xcb_window_t* window; 81 | char character; 82 | struct window_setup_t* children; 83 | int children_size; 84 | } window_setup_t; 85 | 86 | /** 87 | * Data generated from initial user input to the program. 88 | * 89 | * ksl: keys available for use 90 | * blacklist: windows which should be ignored 91 | * whitelist: windows which should be included 92 | * format: FORMAT_DEC or FORMAT_HEX 93 | */ 94 | typedef struct xcw_input_t { 95 | keysyms_lookup_t* ksl; 96 | int ksl_size; 97 | xcb_window_t* blacklist; 98 | int blacklist_size; 99 | xcb_window_t* whitelist; 100 | int whitelist_size; 101 | short format; 102 | } xcw_input_t; 103 | 104 | 105 | /** 106 | * Collection of data needed throughout the runtime of the program. 107 | * 108 | * xcon: the connection to the X server 109 | * xroot: the root window 110 | * ewmh: the state for `xcb_ewmh` 111 | * ksymbols: cached key symbols 112 | * overlay_font: font used to render text on overlays 113 | * input: data generated from initial user input to the program 114 | * wsetups: array of setup structures 115 | */ 116 | typedef struct xcw_state_t { 117 | xcb_connection_t* xcon; 118 | xcb_window_t xroot; 119 | xcb_ewmh_connection_t ewmh; 120 | xcb_key_symbols_t* ksymbols; 121 | xcb_font_t overlay_font; 122 | xcw_input_t* input; 123 | window_setup_t* wsetups; 124 | int wsetups_size; 125 | } xcw_state_t; 126 | 127 | 128 | // -- constants 129 | 130 | /** 131 | * Text colour for overlay windows. 132 | */ 133 | int FG_COLOUR = 0xffffffff; 134 | /** 135 | * Name of the font used to render text on overlay windows. 136 | */ 137 | char* OVERLAY_FONT_NAME = "fixed"; 138 | /** 139 | * Background colour for overlay windows. 140 | */ 141 | int BG_COLOUR = 0xff333333; 142 | /** 143 | * Window class set on overlay windows. 144 | */ 145 | #define OVERLAY_WINDOW_CLASS "overlay\0xorg-choose-window" 146 | /** 147 | * Number of windows requested from _NET_CLIENT_LIST. 148 | */ 149 | int MAX_WINDOWS = 1024; 150 | /** 151 | * Printed version string (used internally by `argp`). 152 | */ 153 | const char* argp_program_version = "xorg-choose-window 0.2.0-next"; 154 | /* 155 | * Output format options. 156 | */ 157 | short FORMAT_DEC = 0; 158 | short FORMAT_HEX = 1; 159 | 160 | /** 161 | * Keysyms with an obvious 1-character representation. Only these characters 162 | * may be used as input. 163 | */ 164 | keysyms_lookup_t ALL_KEYSYMS_LOOKUP[] = { 165 | {'0', 0x0030}, 166 | {'1', 0x0031}, 167 | {'2', 0x0032}, 168 | {'3', 0x0033}, 169 | {'4', 0x0034}, 170 | {'5', 0x0035}, 171 | {'6', 0x0036}, 172 | {'7', 0x0037}, 173 | {'8', 0x0038}, 174 | {'9', 0x0039}, 175 | {'a', 0x0061}, 176 | {'b', 0x0062}, 177 | {'c', 0x0063}, 178 | {'d', 0x0064}, 179 | {'e', 0x0065}, 180 | {'f', 0x0066}, 181 | {'g', 0x0067}, 182 | {'h', 0x0068}, 183 | {'i', 0x0069}, 184 | {'j', 0x006a}, 185 | {'k', 0x006b}, 186 | {'l', 0x006c}, 187 | {'m', 0x006d}, 188 | {'n', 0x006e}, 189 | {'o', 0x006f}, 190 | {'p', 0x0070}, 191 | {'q', 0x0071}, 192 | {'r', 0x0072}, 193 | {'s', 0x0073}, 194 | {'t', 0x0074}, 195 | {'u', 0x0075}, 196 | {'v', 0x0076}, 197 | {'w', 0x0077}, 198 | {'x', 0x0078}, 199 | {'y', 0x0079}, 200 | {'z', 0x007a} 201 | }; 202 | /** 203 | * Size of `ALL_KEYSYMS_LOOKUP`. 204 | */ 205 | int ALL_KEYSYMS_LOOKUP_SIZE = ( 206 | sizeof(ALL_KEYSYMS_LOOKUP) / sizeof(*ALL_KEYSYMS_LOOKUP)); 207 | 208 | 209 | // -- utilities 210 | 211 | /** 212 | * Compute the smaller of two numbers. 213 | */ 214 | int min (int a, int b) { 215 | return a < b ? a : b; 216 | } 217 | 218 | 219 | /** 220 | * Compute the larger of two numbers. 221 | */ 222 | int max (int a, int b) { 223 | return a < b ? b : a; 224 | } 225 | 226 | 227 | /** 228 | * Print an error message to stderr and exit the process with the given status. 229 | * 230 | * code: exit code 231 | * fmt, va_list: arguments as taken by `vprintf` 232 | */ 233 | void xcw_vfail (int code, char *fmt, va_list args) { 234 | fprintf(stderr, "error: "); 235 | vfprintf(stderr, fmt, args); 236 | va_end(args); 237 | exit(code); 238 | } 239 | 240 | 241 | /** 242 | * Print an error message to stderr and exit the process with the given status. 243 | * 244 | * code: exit code 245 | * fmt, ...: arguments as taken by `*printf` 246 | */ 247 | void xcw_fail (int code, char *fmt, ...) { 248 | va_list args; 249 | va_start(args, fmt); 250 | xcw_vfail(code, fmt, args); 251 | /* xcw_vfail will call va_end and exit */ 252 | } 253 | 254 | 255 | /** 256 | * Print an error message to stderr and exit the process with a failure status. 257 | * 258 | * fmt, ...: arguments as taken by `*printf` 259 | */ 260 | void xcw_die (char *fmt, ...) { 261 | va_list args; 262 | va_start(args, fmt); 263 | xcw_vfail(EX_SOFTWARE, fmt, args); 264 | /* xcw_vfail will call va_end and exit */ 265 | } 266 | 267 | 268 | /** 269 | * Print a warning message to stderr. Takes arguments like `*printf`. 270 | */ 271 | void xcw_warn (char *fmt, ...) { 272 | va_list args; 273 | fprintf(stderr, "warning: "); 274 | va_start(args, fmt); 275 | vfprintf(stderr, fmt, args); 276 | va_end(args); 277 | } 278 | 279 | 280 | /** 281 | * Exit the process with a status indicating a window was chosen. 282 | */ 283 | void xcw_exit_match () { 284 | exit(0); 285 | } 286 | 287 | 288 | /** 289 | * Exit the process with a status indicating no window was chosen. 290 | */ 291 | void xcw_exit_no_match () { 292 | exit(0); 293 | } 294 | 295 | 296 | /** 297 | * Print the chosen window to stdout and exit the process. 298 | */ 299 | void choose_window (xcw_input_t* input, xcb_window_t window) { 300 | if (input->format == FORMAT_DEC) printf("%d\n", window); 301 | else if (input->format == FORMAT_HEX) printf("0x%x\n", window); 302 | xcw_exit_match(); 303 | } 304 | 305 | 306 | // -- xorg utilities 307 | 308 | /** 309 | * Perform the check for a checked, reply-less xcb request. 310 | * 311 | * cookie: corresponding to the request 312 | * msg: to print in case of error 313 | */ 314 | void xorg_check_request (xcb_connection_t* xcon, 315 | xcb_void_cookie_t cookie, char *msg) { 316 | xcb_generic_error_t *error = xcb_request_check(xcon, cookie); 317 | if (error) xcw_die("%s (%d)\n", msg, error->error_code); 318 | } 319 | 320 | 321 | /** 322 | * Move and resize a window. 323 | * 324 | * x, y: new absolute screen location 325 | */ 326 | void xorg_window_move_resize (xcb_connection_t* xcon, xcb_window_t window, 327 | int x, int y, int w, int h) { 328 | int mask = (XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | 329 | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT); 330 | uint32_t values[] = { x, y, w, h }; 331 | xcb_configure_window(xcon, window, mask, values); 332 | } 333 | 334 | 335 | /** 336 | * Determine whether a window has a property defined. 337 | * 338 | * prop: property ID 339 | */ 340 | int xorg_window_has_property (xcb_connection_t* xcon, 341 | xcb_window_t window, xcb_atom_t prop) { 342 | xcb_list_properties_cookie_t lpc = xcb_list_properties(xcon, window); 343 | xcb_list_properties_reply_t* lpr; 344 | if (!(lpr = xcb_list_properties_reply(xcon, lpc, NULL))) { 345 | xcw_die("list_properties"); 346 | } 347 | 348 | xcb_atom_t* props = xcb_list_properties_atoms(lpr); 349 | int result = 0; 350 | for (int i = 0; i < xcb_list_properties_atoms_length(lpr); i++) { 351 | if (props[i] == prop) { 352 | result = 1; 353 | break; 354 | } 355 | } 356 | 357 | free(lpr); 358 | return result; 359 | } 360 | 361 | 362 | /** 363 | * Construct a 2-byte character string from a 1-byte character string. 364 | */ 365 | xcb_char2b_t* xorg_str_to_2b (char* text, int text_size) { 366 | xcb_char2b_t* text2b = malloc(text_size * sizeof(xcb_char2b_t)); 367 | xcb_char2b_t c = { 0, 0 }; 368 | for (int i = 0; i < text_size; i++) { 369 | c.byte2 = text[i]; 370 | text2b[i] = c; 371 | } 372 | return text2b; 373 | } 374 | 375 | 376 | /** 377 | * Render text centred on a window. 378 | * 379 | * win_rect: rectangle covering window with ID `win` 380 | * gc: graphics context for rendering the text 381 | * text: text to render 382 | */ 383 | void xorg_draw_text_centred ( 384 | xcb_connection_t* xcon, xcb_window_t win, xcb_rectangle_t* win_rect, 385 | xcb_gcontext_t gc, char* text 386 | ) { 387 | int size = min(strlen(text), 255); 388 | 389 | // check expected rendered size 390 | xcb_char2b_t* text_2b = xorg_str_to_2b(text, size); 391 | xcb_query_text_extents_cookie_t qtec = ( 392 | xcb_query_text_extents(xcon, gc, size, text_2b)); 393 | free(text_2b); 394 | xcb_query_text_extents_reply_t* qter; 395 | if (!(qter = xcb_query_text_extents_reply(xcon, qtec, NULL))) { 396 | xcw_die("query_text_extents\n"); 397 | } 398 | 399 | int x = (win_rect->width - qter->overall_width) / 2; 400 | int y = (win_rect->height - qter->font_ascent - qter->font_descent) / 2; 401 | xcb_image_text_8(xcon, size, win, gc, x, y + qter->font_ascent, text); 402 | } 403 | 404 | 405 | /** 406 | * Determine whether a window is 'normal' and visible according to the base Xorg 407 | * specification. 408 | */ 409 | int xorg_window_normal (xcb_connection_t* xcon, xcb_window_t window) { 410 | xcb_get_window_attributes_cookie_t gwac = ( 411 | xcb_get_window_attributes(xcon, window)); 412 | xcb_get_window_attributes_reply_t* gwar; 413 | if (!(gwar = xcb_get_window_attributes_reply(xcon, gwac, NULL))) { 414 | xcw_die("get_window_attributes\n"); 415 | } 416 | 417 | return ( 418 | gwar->map_state == XCB_MAP_STATE_VIEWABLE && 419 | gwar->override_redirect == 0 420 | ); 421 | } 422 | 423 | 424 | /** 425 | * Determine whether a window is a persistent application window according EWMH. 426 | */ 427 | int ewmh_window_normal (xcw_state_t* state, xcb_window_t window) { 428 | xcb_get_property_cookie_t gpc = ( 429 | xcb_get_property(state->xcon, 0, window, 430 | state->ewmh._NET_WM_WINDOW_TYPE, XCB_ATOM_ATOM, 0, 1)); 431 | xcb_get_property_reply_t* gpr; 432 | if (!(gpr = xcb_get_property_reply(state->xcon, gpc, NULL))) { 433 | xcw_die("get_property _NET_WM_WINDOW_TYPE\n"); 434 | } 435 | uint32_t* window_type = (uint32_t*)xcb_get_property_value(gpr); 436 | int prop_len = xcb_get_property_value_length(gpr); 437 | free(gpr); 438 | 439 | // if reply length is 0, window type isn't defined, so treat it as normal 440 | return ( 441 | prop_len == 0 || 442 | window_type[0] == state->ewmh._NET_WM_WINDOW_TYPE_TOOLBAR || 443 | window_type[0] == state->ewmh._NET_WM_WINDOW_TYPE_MENU || 444 | window_type[0] == state->ewmh._NET_WM_WINDOW_TYPE_UTILITY || 445 | window_type[0] == state->ewmh._NET_WM_WINDOW_TYPE_SPLASH || 446 | window_type[0] == state->ewmh._NET_WM_WINDOW_TYPE_DIALOG || 447 | window_type[0] == state->ewmh._NET_WM_WINDOW_TYPE_NORMAL 448 | ); 449 | } 450 | 451 | 452 | /** 453 | * Get all windows from the X server. 454 | * 455 | * windows (output): window IDs 456 | * windows_size (output): size of `windows` 457 | */ 458 | void xorg_get_windows (xcw_state_t* state, 459 | xcb_window_t** windows, int* windows_size) { 460 | xcb_query_tree_cookie_t qtc = xcb_query_tree(state->xcon, state->xroot); 461 | xcb_query_tree_reply_t* qtr; 462 | if (!(qtr = xcb_query_tree_reply(state->xcon, qtc, NULL))) { 463 | xcw_die("query_tree\n"); 464 | } 465 | xcb_window_t* referenced_windows = xcb_query_tree_children(qtr); 466 | int size = xcb_query_tree_children_length(qtr); 467 | 468 | // copy for easier usage 469 | *windows = calloc(size, sizeof(xcb_window_t)); 470 | for (int i = 0; i < size; i++) (*windows)[i] = referenced_windows[i]; 471 | free(qtr); 472 | 473 | *windows_size = size; 474 | } 475 | 476 | 477 | /** 478 | * Get windows managed by the window manager. 479 | * 480 | * is_defined (output): whether the window manager defines the windows it 481 | * tracks; if 0, `windows` and `windows_size` will not be set 482 | * windows (output): window IDs 483 | * windows_size (output): size of `windows` 484 | */ 485 | void xorg_get_managed_windows (xcw_state_t* state, int* is_defined, 486 | xcb_window_t** windows, int* windows_size) { 487 | *is_defined = xorg_window_has_property( 488 | state->xcon, state->xroot, state->ewmh._NET_CLIENT_LIST); 489 | 490 | if (*is_defined) { 491 | xcb_get_property_cookie_t gpc = ( 492 | xcb_get_property(state->xcon, 0, state->xroot, 493 | state->ewmh._NET_CLIENT_LIST, XCB_ATOM_WINDOW, 0, 494 | MAX_WINDOWS)); 495 | xcb_get_property_reply_t* gpr; 496 | if (!(gpr = xcb_get_property_reply(state->xcon, gpc, NULL))) { 497 | xcw_die("get_property _NET_CLIENT_LIST\n"); 498 | } 499 | xcb_window_t* referenced_windows = ( 500 | (xcb_window_t*)xcb_get_property_value(gpr)); 501 | // each window ID is 4 bytes 502 | int size = xcb_get_property_value_length(gpr) / 4; 503 | 504 | // copy for easier usage 505 | *windows = calloc(size, sizeof(xcb_window_t)); 506 | for (int i = 0; i < size; i++) (*windows)[i] = referenced_windows[i]; 507 | free(gpr); 508 | 509 | *windows_size = size; 510 | } 511 | } 512 | 513 | 514 | /** 515 | * Determine whether a window is managed by the window manager. 516 | * 517 | * managed_windows: windows that are managed by the window manager 518 | */ 519 | int xorg_window_managed (xcb_window_t window, xcb_window_t* managed_windows, 520 | int managed_windows_size) { 521 | int found = 0; 522 | for (int i = 0; i < managed_windows_size; i++) { 523 | if (managed_windows[i] == window) { 524 | found = 1; 525 | break; 526 | } 527 | } 528 | return found; 529 | } 530 | 531 | 532 | /** 533 | * Determine whether a window is in the list of windows. 534 | */ 535 | int xorg_contains_window (xcb_window_t* windows, int windows_size, 536 | xcb_window_t window) { 537 | int found = 0; 538 | for (int i = 0; i < windows_size; i++) { 539 | if (windows[i] == window) { 540 | found = 1; 541 | break; 542 | } 543 | } 544 | return found; 545 | } 546 | 547 | 548 | /** 549 | * Initialise the connection to the X server. 550 | * 551 | * state (output): pointer to program state; `input` and `wsetups` are not 552 | * initialised 553 | */ 554 | void initialise_xorg (xcw_state_t** state) { 555 | int default_screen; // unused 556 | xcb_screen_t *screen; 557 | xcb_connection_t* xcon = xcb_connect(NULL, &default_screen); 558 | if (xcb_connection_has_error(xcon)) xcw_die("connect\n"); 559 | 560 | screen = xcb_setup_roots_iterator(xcb_get_setup(xcon)).data; 561 | if (screen == NULL) xcw_die("no screens\n"); 562 | xcb_window_t xroot = screen->root; 563 | 564 | xcb_ewmh_connection_t ewmh; 565 | xcb_intern_atom_cookie_t *ewmhc = xcb_ewmh_init_atoms(xcon, &ewmh); 566 | if (!xcb_ewmh_init_atoms_replies(&ewmh, ewmhc, NULL)) { 567 | xcw_die("ewmh init\n"); 568 | } 569 | 570 | xcb_key_symbols_t* ksymbols; 571 | ksymbols = xcb_key_symbols_alloc(xcon); 572 | if (ksymbols == NULL) xcw_die("key_symbols_alloc\n"); 573 | 574 | xcb_font_t overlay_font = xcb_generate_id(xcon); 575 | xcb_void_cookie_t ofc = xcb_open_font_checked( 576 | xcon, overlay_font, strlen(OVERLAY_FONT_NAME), OVERLAY_FONT_NAME); 577 | xorg_check_request(xcon, ofc, "open_font"); 578 | 579 | *state = malloc(sizeof(xcw_state_t)); 580 | xcw_state_t local_state = { 581 | xcon, xroot, ewmh, ksymbols, overlay_font, NULL, NULL, 0 582 | }; 583 | **state = local_state; 584 | } 585 | 586 | 587 | // -- input handling 588 | 589 | /** 590 | * Find an item in a keysym lookup given its character. 591 | * 592 | * ksl: keysym lookup to search 593 | * c: character to search for 594 | * 595 | * returns: found item, NULL if no matching items were found 596 | */ 597 | keysyms_lookup_t* keysyms_lookup_find_char ( 598 | keysyms_lookup_t* ksl, int ksl_size, char c 599 | ) { 600 | for (int i = 0; i < ksl_size; i++) { 601 | if (ksl[i].character == c) { 602 | return &(ksl[i]); 603 | } 604 | } 605 | return NULL; 606 | } 607 | 608 | 609 | /** 610 | * Find an item in a keysym lookup given its keysym. 611 | * 612 | * ksl: keysym lookup to search 613 | * ksym: keysym to search for 614 | * 615 | * returns: found item, NULL if no matching items were found 616 | */ 617 | keysyms_lookup_t* keysyms_lookup_find_keysym ( 618 | keysyms_lookup_t* ksl, int ksl_size, xcb_keysym_t ksym 619 | ) { 620 | for (int i = 0; i < ksl_size; i++) { 621 | if (ksl[i].keysym == ksym) { 622 | return &(ksl[i]); 623 | } 624 | } 625 | return NULL; 626 | } 627 | 628 | 629 | /** 630 | * Parse the `CHARACTERS` argument. May call `argp_error`. 631 | * 632 | * char_pool: value passed for the argument 633 | * input: result is placed in here 634 | */ 635 | void parse_arg_characters (char* char_pool, struct argp_state* state, 636 | xcw_input_t* input) { 637 | // we won't put more than `char_pool` items in `ksl` 638 | int pool_size = strlen(char_pool); 639 | input->ksl = calloc(pool_size, sizeof(keysyms_lookup_t)); 640 | int size = 0; 641 | 642 | // check validity of characters, compile lookup 643 | for (int i = 0; i < pool_size; i++) { 644 | char c = char_pool[i]; 645 | keysyms_lookup_t* ksl_item = keysyms_lookup_find_char( 646 | ALL_KEYSYMS_LOOKUP, ALL_KEYSYMS_LOOKUP_SIZE, c); 647 | if (ksl_item == NULL) { 648 | argp_error(state, "CHARACTERS argument: unknown character: %c", c); 649 | } 650 | // don't allow duplicates in lookup 651 | if (keysyms_lookup_find_char(input->ksl, size, c) == NULL) { 652 | input->ksl[size] = *ksl_item; 653 | size += 1; 654 | } 655 | } 656 | 657 | if (size < 2) { 658 | argp_error(state, 659 | "CHARACTERS argument: expected at least two characters"); 660 | } 661 | input->ksl = realloc(input->ksl, sizeof(keysyms_lookup_t) * size); 662 | input->ksl_size = size; 663 | } 664 | 665 | 666 | /** 667 | * Parse the `--blacklist` or `--whitelist` option. May call `argp_error`. 668 | * 669 | * window_id: value passed to the option 670 | * windows: result is added to this list, and its size updated 671 | */ 672 | void parse_arg_window_list (char* window_id, struct argp_state* state, 673 | xcb_window_t** windows, int* windows_size) { 674 | errno = 0; 675 | long int window = strtol(window_id, NULL, 0); 676 | // Xorg window IDs are 32-bit unsigned 677 | if (errno != 0 || window <= 0 || window > 0xffffffff) { 678 | argp_error(state, "invalid value for window ID: %s", window_id); 679 | } 680 | 681 | *windows = realloc(*windows, sizeof(xcb_window_t) * (*windows_size + 1)); 682 | (*windows)[*windows_size] = window; 683 | *windows_size += 1; 684 | } 685 | 686 | 687 | /** 688 | * Parse the `--format` option. May call `argp_error`. 689 | * 690 | * format: value passed to the option 691 | * input: result is placed in here 692 | */ 693 | void parse_arg_format (char* format, struct argp_state* state, 694 | xcw_input_t* input) { 695 | if (strcmp(format, "decimal") == 0) { 696 | input->format = FORMAT_DEC; 697 | } else if (strcmp(format, "hexadecimal") == 0) { 698 | input->format = FORMAT_HEX; 699 | } else { 700 | argp_error(state, "invalid value for output format: %s", format); 701 | } 702 | } 703 | 704 | 705 | /** 706 | * Argument parsing function for use with `argp`. 707 | * 708 | * state: `input` is `xcw_input_t*`, which points to an allocated instance that 709 | * gets populated by calls to this function 710 | */ 711 | error_t parse_arg (int key, char* value, struct argp_state* state) { 712 | xcw_input_t* input = (xcw_input_t*)(state->input); 713 | 714 | if (key == 'b') { 715 | parse_arg_window_list(value, state, &(input->blacklist), 716 | &(input->blacklist_size)); 717 | return 0; 718 | } else if (key == 'w') { 719 | parse_arg_window_list(value, state, &(input->whitelist), 720 | &(input->whitelist_size)); 721 | return 0; 722 | } else if (key == 'f') { 723 | parse_arg_format(value, state, input); 724 | return 0; 725 | } else if (key == ARGP_KEY_ARG) { 726 | if (state->arg_num == 0) { 727 | parse_arg_characters(value, state, input); 728 | return 0; 729 | } else { 730 | return ARGP_ERR_UNKNOWN; 731 | } 732 | } else { 733 | return ARGP_ERR_UNKNOWN; 734 | } 735 | } 736 | 737 | 738 | /** 739 | * Parse command-line arguments. 740 | * 741 | * argc, argv: as passed to `main` 742 | */ 743 | xcw_input_t* parse_args (int argc, char** argv) { 744 | struct argp_option options[] = { 745 | { "blacklist", 'b', "WINDOWID", 0, 746 | "IDs of windows to ignore (specify this option multiple times)" }, 747 | { "whitelist", 'w', "WINDOWID", 0, 748 | "IDs of windows to include (include all if none specified) \ 749 | (specify this option multiple times)" }, 750 | { "format", 'f', "FORMAT", 0, 751 | "Output format: 'decimal' or 'hexadecimal'" }, 752 | { 0 } 753 | }; 754 | 755 | struct argp parser = { 756 | options, parse_arg, "CHARACTERS", 757 | "\n\ 758 | Running the program draws a string of characters over each visible window. \ 759 | Typing one of those strings causes the program to print the corresponding \ 760 | window ID to standard output and exit. If any non-matching keys are pressed, \ 761 | the program exits without printing anything.\n\ 762 | \n\ 763 | CHARACTERS defines the characters available for use in the displayed strings; \ 764 | e.g. 'asdfjkl' is a good choice for a QWERTY keyboard layout. Allowed \ 765 | characters are the numbers 0-9 and the letters a-z.\n\ 766 | \n\ 767 | The program exits with status 0 on success, 64 on invalid arguments, and 70 if \ 768 | an unexpected error occurs.", 769 | NULL, NULL, NULL 770 | }; 771 | 772 | xcw_input_t input = { NULL, 0, NULL, 0 }; 773 | xcw_input_t* inputp = malloc(sizeof(xcw_input_t)); 774 | *inputp = input; 775 | argp_parse(&parser, argc, argv, 0, NULL, inputp); 776 | if (inputp->ksl == NULL) { 777 | xcw_fail(EX_USAGE, "missing CHARACTERS argument\n"); 778 | } 779 | return inputp; 780 | } 781 | 782 | 783 | /** 784 | * Acquire a Xorg keyboard grab on the root window. 785 | */ 786 | void initialise_input (xcw_state_t* state) { 787 | // wait a little for other programs to release the keyboard 788 | // since this program is likely to be launched from a hotkey daemon 789 | struct timespec ts = { 0, 1000000 }; // 1ms 790 | int status; 791 | for (int s = 0; s < 1000; s++) { // up to 1s total 792 | xcb_grab_keyboard_cookie_t gkc = xcb_grab_keyboard( 793 | state->xcon, 0, state->xroot, XCB_CURRENT_TIME, 794 | XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); 795 | xcb_grab_keyboard_reply_t* gkr; 796 | 797 | if ((gkr = xcb_grab_keyboard_reply(state->xcon, gkc, NULL))) { 798 | status = gkr->status; 799 | free(gkr); 800 | if (status == XCB_GRAB_STATUS_ALREADY_GRABBED) { 801 | nanosleep(&ts, NULL); 802 | continue; 803 | } else if (status == XCB_GRAB_STATUS_SUCCESS) { 804 | break; 805 | } else { 806 | xcw_die("grab_keyboard: %d\n", status); 807 | } 808 | } else { 809 | xcw_die("grab_keyboard\n"); 810 | } 811 | } 812 | 813 | if (status == XCB_GRAB_STATUS_ALREADY_GRABBED) { 814 | xcw_die("grab_keyboard: already grabbed\n"); 815 | } 816 | } 817 | 818 | 819 | // -- overlay windows 820 | 821 | /** 822 | * Create an overlay window. 823 | * 824 | * x, y: absolute screen location 825 | * w, h: window size 826 | */ 827 | xcb_window_t* overlay_create (xcw_state_t* state, int x, int y, int w, int h) { 828 | xcb_window_t* win = malloc(sizeof(xcb_window_t)); 829 | *win = xcb_generate_id(state->xcon); 830 | uint32_t mask = (XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | 831 | XCB_CW_SAVE_UNDER | XCB_CW_EVENT_MASK); 832 | uint32_t values[] = { 833 | BG_COLOUR, 1, 1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS 834 | }; 835 | xcb_void_cookie_t cwc = xcb_create_window_checked( 836 | state->xcon, XCB_COPY_FROM_PARENT, *win, state->xroot, 0, 0, 1, 1, 0, 837 | XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, mask, values); 838 | xorg_check_request(state->xcon, cwc, "create_window"); 839 | 840 | xcb_icccm_set_wm_class( 841 | state->xcon, *win, sizeof(OVERLAY_WINDOW_CLASS), OVERLAY_WINDOW_CLASS); 842 | xorg_window_move_resize(state->xcon, *win, x, y, w, h); 843 | xcb_void_cookie_t mwc = xcb_map_window_checked(state->xcon, *win); 844 | xorg_check_request(state->xcon, mwc, "map_window"); 845 | return win; 846 | } 847 | 848 | 849 | /** 850 | * Create a graphics context for drawing the background of an overlay window. 851 | * 852 | * win: the overlay window 853 | */ 854 | xcb_gcontext_t* overlay_get_bg_gc (xcb_connection_t* xcon, xcb_window_t win) { 855 | xcb_gcontext_t* gc = malloc(sizeof(xcb_gcontext_t)); 856 | *gc = xcb_generate_id(xcon); 857 | uint32_t mask = XCB_GC_FOREGROUND; 858 | uint32_t value_list[] = { BG_COLOUR }; 859 | xcb_void_cookie_t cgc = ( 860 | xcb_create_gc_checked(xcon, *gc, win, mask, value_list)); 861 | xorg_check_request(xcon, cgc, "create_gc"); 862 | return gc; 863 | } 864 | 865 | 866 | /** 867 | * Create a graphics context for drawing the text of an overlay window. 868 | * 869 | * win: the overlay window 870 | */ 871 | xcb_gcontext_t* overlay_get_font_gc (xcw_state_t* state, xcb_window_t win) { 872 | xcb_gcontext_t* gc = malloc(sizeof(xcb_gcontext_t)); 873 | *gc = xcb_generate_id(state->xcon); 874 | uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT; 875 | uint32_t value_list[] = { FG_COLOUR, BG_COLOUR, state->overlay_font }; 876 | xcb_void_cookie_t cgc = ( 877 | xcb_create_gc_checked(state->xcon, *gc, win, mask, value_list)); 878 | xorg_check_request(state->xcon, cgc, "create_gc"); 879 | return gc; 880 | } 881 | 882 | 883 | /** 884 | * Set the text on an overlay window. `xcb_flush` should be called after 885 | * calling this function. 886 | * 887 | * wsetup: containing the overlay window (if there is no overlay window, this 888 | * function does nothing) 889 | * text: text to render (null-terminated, must be at most 255 characters) 890 | */ 891 | void overlay_set_text (xcw_state_t* state, 892 | window_setup_t* wsetup, char* text) { 893 | if (wsetup->overlay_window == NULL) return; 894 | xcb_window_t win = *(wsetup->overlay_window); 895 | 896 | if (wsetup->overlay_bg_gc == NULL) { 897 | wsetup->overlay_bg_gc = overlay_get_bg_gc(state->xcon, win); 898 | } 899 | if (wsetup->overlay_font_gc == NULL) { 900 | wsetup->overlay_font_gc = overlay_get_font_gc(state, win); 901 | } 902 | 903 | xcb_poly_fill_rectangle(state->xcon, win, *(wsetup->overlay_bg_gc), 1, 904 | wsetup->overlay_rect); 905 | xorg_draw_text_centred(state->xcon, win, wsetup->overlay_rect, 906 | *(wsetup->overlay_font_gc), text); 907 | } 908 | 909 | 910 | /** 911 | * See `overlays_set_text`. `xcb_flush` should be called after calling * this 912 | * function. 913 | * 914 | * wsetups: array of setup structures containing overlay windows to render text 915 | * on (recursively) 916 | * text: prefix to text rendered to every overlay window (null-terminated) 917 | */ 918 | void _overlays_set_text (xcw_state_t* state, window_setup_t* wsetups, 919 | int wsetups_size, char* text) { 920 | int text_size = strlen(text); 921 | // there's no way we're every going to reach 255 characters with the 922 | // current setup; this is just in case extra static text gets added 923 | if (text_size + 1 > 255) { 924 | xcw_warn("refusing to render text longer than 255 characters\n"); 925 | 926 | } else for (int i = 0; i < wsetups_size; i++) { 927 | window_setup_t* wsetup = &(wsetups[i]); 928 | // next level down is 1 character longer, plus 1 for null 929 | char* new_text = calloc(text_size + 2, sizeof(char)); 930 | strcpy(new_text, text); 931 | new_text[text_size] = wsetup->character; 932 | new_text[text_size + 1] = '\0'; 933 | 934 | overlay_set_text(state, wsetup, new_text); 935 | if (wsetup->children != NULL) { 936 | _overlays_set_text(state, wsetup->children, wsetup->children_size, 937 | new_text); 938 | } 939 | 940 | free(new_text); 941 | } 942 | } 943 | 944 | 945 | /** 946 | * Update text on all overlay windows. 947 | */ 948 | void overlays_set_text (xcw_state_t* state) { 949 | char* text = ""; 950 | _overlays_set_text(state, state->wsetups, state->wsetups_size, text); 951 | xcb_flush(state->xcon); 952 | } 953 | 954 | 955 | // -- wsetup utilities 956 | 957 | 958 | /** 959 | * Create a bottom-level `wsetup_t`. 960 | * 961 | * window: the window to track 962 | * character: bottom-level character in the window label 963 | */ 964 | window_setup_t initialise_window_setup (xcw_state_t* state, xcb_window_t window, 965 | char character) { 966 | // an xcb_window_t is an xcb_drawable_t 967 | xcb_get_geometry_cookie_t ggc = xcb_get_geometry(state->xcon, window); 968 | xcb_get_geometry_reply_t* ggr; 969 | if (!(ggr = xcb_get_geometry_reply(state->xcon, ggc, NULL))) { 970 | xcw_die("get_geometry\n"); 971 | } 972 | 973 | xcb_rectangle_t rect = { 0, 0, ggr->width, ggr->height }; 974 | xcb_rectangle_t* rectp = malloc(sizeof(xcb_rectangle_t)); 975 | *rectp = rect; 976 | // guaranteed that ksl_size <= windows_size 977 | xcb_window_t* overlay_window = overlay_create( 978 | state, ggr->border_width + ggr->x, ggr->border_width + ggr->y, 979 | rect.width, rect.height); 980 | xcb_window_t* window_p = malloc(sizeof(xcb_window_t)); 981 | *window_p = window; 982 | 983 | window_setup_t wsetup = { 984 | overlay_window, NULL, NULL, rectp, window_p, character, NULL, 0 985 | }; 986 | return wsetup; 987 | } 988 | 989 | 990 | /** 991 | * See `initialise_window_tracking`. 992 | * 993 | * remain_depth: number of nested levels remaining (we choose a character in the 994 | * string to type at every level; if 0, we choose the last character) 995 | */ 996 | void _initialise_window_tracking (xcw_state_t* state, int remain_depth, 997 | xcb_window_t* windows, int windows_size, 998 | window_setup_t** wsetups, int* wsetups_size) { 999 | if (remain_depth == 0) { 1000 | *wsetups = calloc(windows_size, sizeof(window_setup_t)); 1001 | *wsetups_size = windows_size; 1002 | for (int i = 0; i < windows_size; i++) { 1003 | // guaranteed that ksl_size <= windows_size 1004 | (*wsetups)[i] = initialise_window_setup( 1005 | state, windows[i], state->input->ksl[i].character); 1006 | } 1007 | 1008 | } else { 1009 | // base number of windows 'used up' per iteration 1010 | int p = windows_size / state->input->ksl_size; 1011 | // number of iterations to use one extra window 1012 | int r = windows_size % state->input->ksl_size; 1013 | // required number of iterations to use all windows 1014 | int n = p > 0 ? state->input->ksl_size : r; 1015 | *wsetups = calloc(n, sizeof(window_setup_t)); 1016 | *wsetups_size = n; 1017 | xcb_window_t* remain_windows = windows; 1018 | 1019 | for (int i = 0; i < n; i++) { 1020 | window_setup_t* children = NULL; 1021 | int children_size; 1022 | int children_windows_size = i < r ? p + 1 : p; 1023 | 1024 | if (children_windows_size == 1) { 1025 | (*wsetups)[i] = initialise_window_setup( 1026 | state, *remain_windows, 1027 | state->input->ksl[i].character); 1028 | } else { 1029 | _initialise_window_tracking( 1030 | state, remain_depth - 1, 1031 | remain_windows, children_windows_size, 1032 | &children, &children_size); 1033 | window_setup_t wsetup = { 1034 | NULL, NULL, NULL, NULL, NULL, 1035 | state->input->ksl[i].character, children, children_size 1036 | }; 1037 | (*wsetups)[i] = wsetup; 1038 | } 1039 | 1040 | remain_windows += children_windows_size; 1041 | } 1042 | } 1043 | } 1044 | 1045 | 1046 | /** 1047 | * Construct data for tracked windows in a nested structure matching the 1048 | * characters that need to be typed to choose them. 1049 | * 1050 | * state: the result is stored in here 1051 | * windows: tracked windows 1052 | */ 1053 | void initialise_window_tracking (xcw_state_t* state, 1054 | xcb_window_t* windows, int windows_size) { 1055 | _initialise_window_tracking( 1056 | state, 1057 | // the length of each tracking string 1058 | (int)(log(max(windows_size - 1, 1)) / log(state->input->ksl_size)), 1059 | windows, windows_size, &(state->wsetups), &(state->wsetups_size)); 1060 | } 1061 | 1062 | 1063 | /** 1064 | * See `wsetup_debug_print`. 1065 | * 1066 | * depth: current depth in the structure, starting at 0, used for indentation 1067 | */ 1068 | void _wsetup_debug_print (window_setup_t* wsetup, int depth) { 1069 | printf("[wsetup] "); 1070 | for (int i = 0; i < depth; i++) printf(" "); 1071 | printf("%c", wsetup->character); 1072 | if (wsetup->window == NULL) printf("\n"); 1073 | else printf(" %x\n", *(wsetup->window)); 1074 | 1075 | if (wsetup->children != NULL) { 1076 | for (int i = 0; i < wsetup->children_size; i++) { 1077 | _wsetup_debug_print(&(wsetup->children[i]), depth + 1); 1078 | } 1079 | } 1080 | } 1081 | 1082 | 1083 | /** 1084 | * Print a setup structure to stdout. 1085 | */ 1086 | void wsetup_debug_print (window_setup_t* wsetup) { 1087 | _wsetup_debug_print(wsetup, 0); 1088 | } 1089 | 1090 | 1091 | /** 1092 | * Destroy all overlay windows in a setup structure and free the structure's 1093 | * used memory. 1094 | */ 1095 | void wsetup_free (xcb_connection_t* xcon, window_setup_t* wsetup) { 1096 | xcb_window_t* w = wsetup->overlay_window; 1097 | if (w != NULL) { 1098 | xcb_destroy_window_checked(xcon, *w); 1099 | free(wsetup->overlay_rect); 1100 | } 1101 | if (wsetup->overlay_bg_gc != NULL) { 1102 | xcb_free_gc(xcon, *(wsetup->overlay_bg_gc)); 1103 | free(wsetup->overlay_bg_gc); 1104 | } 1105 | if (wsetup->overlay_font_gc != NULL) { 1106 | xcb_free_gc(xcon, *(wsetup->overlay_font_gc)); 1107 | free(wsetup->overlay_font_gc); 1108 | } 1109 | 1110 | if (wsetup->children != NULL) { 1111 | for (int i = 0; i < wsetup->children_size; i++) { 1112 | window_setup_t* child = &(wsetup->children[i]); 1113 | wsetup_free(xcon, child); 1114 | } 1115 | free(wsetup->children); 1116 | } 1117 | 1118 | xcb_flush(xcon); 1119 | } 1120 | 1121 | 1122 | /** 1123 | * Choose the window in a setup structure or replace the current array of setup 1124 | * structures with its children. Updates text rendered on overlay windows. 1125 | * Exits the process if a window is chosen. 1126 | */ 1127 | void wsetup_choose (xcw_state_t* state, window_setup_t* wsetup) { 1128 | if (wsetup->window != NULL && wsetup->children_size == 0) { 1129 | choose_window(state->input, *(wsetup->window)); 1130 | } else { 1131 | state->wsetups = wsetup->children; 1132 | state->wsetups_size = wsetup->children_size; 1133 | overlays_set_text(state); 1134 | } 1135 | } 1136 | 1137 | 1138 | /** 1139 | * Reduce a setup structure by choosing an item. Frees removed parts of the 1140 | * structure. 1141 | * 1142 | * index: array index in `wsetups` to choose 1143 | */ 1144 | void wsetups_descend_by_index (xcw_state_t* state, int index) { 1145 | // state changes during iteration 1146 | window_setup_t* wsetups = state->wsetups; 1147 | int wsetups_size = state->wsetups_size; 1148 | for (int i = 0; i < wsetups_size; i++) { 1149 | if (i == index) wsetup_choose(state, &(wsetups[i])); 1150 | else wsetup_free(state->xcon, &(wsetups[i])); 1151 | } 1152 | } 1153 | 1154 | 1155 | /** 1156 | * Reduce a setup structure by choosing a character, then reduce recursively 1157 | * like `wsetups_descend_by_index`. Exits the process if the character doesn't 1158 | * correspond to any options. Frees removed parts of the structure. 1159 | * 1160 | * c: character to choose 1161 | */ 1162 | void wsetups_descend_by_char (xcw_state_t* state, char c) { 1163 | int index = -1; 1164 | for (int i = 0; i < state->wsetups_size; i++) { 1165 | if (state->wsetups[i].character == c) { 1166 | index = i; 1167 | break; 1168 | } 1169 | } 1170 | 1171 | if (index == -1) xcw_exit_no_match(); 1172 | else wsetups_descend_by_index(state, index); 1173 | } 1174 | 1175 | 1176 | // -- program 1177 | 1178 | /** 1179 | * Get the windows to track. 1180 | * 1181 | * windows (output): window IDs 1182 | * windows_size (output): size of `windows` 1183 | */ 1184 | void initialise_tracked_windows (xcw_state_t* state, 1185 | xcb_window_t** windows, int* windows_size) { 1186 | xcb_window_t* all_windows; 1187 | int all_windows_size; 1188 | xorg_get_windows(state, &all_windows, &all_windows_size); 1189 | int managed_windows_defined; 1190 | xcb_window_t* managed_windows; 1191 | int managed_windows_size; 1192 | xorg_get_managed_windows(state, &managed_windows_defined, 1193 | &managed_windows, &managed_windows_size); 1194 | 1195 | *windows = calloc(all_windows_size, sizeof(xcb_window_t)); 1196 | int size = 0; 1197 | for (int i = 0; i < all_windows_size; i++) { 1198 | if ( 1199 | // ignore if not managed by the window manager 1200 | !(managed_windows_defined && !xorg_window_managed( 1201 | all_windows[i], managed_windows, managed_windows_size 1202 | )) && 1203 | 1204 | // only include if whitelisted 1205 | (state->input->whitelist_size == 0 || xorg_contains_window( 1206 | state->input->whitelist, state->input->whitelist_size, 1207 | all_windows[i] 1208 | )) && 1209 | 1210 | // ignore if blacklisted 1211 | !xorg_contains_window( 1212 | state->input->blacklist, state->input->blacklist_size, 1213 | all_windows[i] 1214 | ) && 1215 | 1216 | xorg_window_normal(state->xcon, all_windows[i]) && 1217 | 1218 | ewmh_window_normal(state, all_windows[i]) 1219 | ) { 1220 | (*windows)[size] = all_windows[i]; 1221 | size += 1; 1222 | } 1223 | } 1224 | *windows = realloc(*windows, size * sizeof(xcb_window_t)); 1225 | *windows_size = size; 1226 | 1227 | if (managed_windows_defined) free(managed_windows); 1228 | free(all_windows); 1229 | } 1230 | 1231 | 1232 | /** 1233 | * Make adjustments to tracking windows based on a keypress event. Exits the 1234 | * process if this chooses a window. 1235 | */ 1236 | void handle_keypress (xcw_state_t* state, xcb_key_press_event_t* kp) { 1237 | xcb_keysym_t ksym = xcb_key_press_lookup_keysym(state->ksymbols, kp, 0); 1238 | keysyms_lookup_t* ksl_item = ( 1239 | keysyms_lookup_find_keysym(state->input->ksl, 1240 | state->input->ksl_size, ksym)); 1241 | 1242 | if (ksl_item == NULL) { 1243 | xcw_exit_no_match(); 1244 | } else { 1245 | wsetups_descend_by_char(state, ksl_item->character); 1246 | } 1247 | } 1248 | 1249 | 1250 | int main (int argc, char** argv) { 1251 | xcw_input_t* input = parse_args(argc, argv); 1252 | xcw_state_t* state; 1253 | initialise_xorg(&state); 1254 | state->input = input; 1255 | initialise_input(state); 1256 | 1257 | xcb_window_t* windows; 1258 | int windows_size; 1259 | initialise_tracked_windows(state, &windows, &windows_size); 1260 | initialise_window_tracking(state, windows, windows_size); 1261 | free(windows); 1262 | 1263 | if (state->wsetups_size == 0) { 1264 | xcw_exit_no_match(); 1265 | } else if (state->wsetups_size == 1) { 1266 | wsetup_choose(state, &(state->wsetups[0])); 1267 | } else { 1268 | overlays_set_text(state); 1269 | } 1270 | 1271 | xcb_generic_event_t *event; 1272 | while (1) if ((event = xcb_poll_for_event(state->xcon))) { 1273 | switch (event->response_type & ~0x80) { 1274 | case 0: { 1275 | xcb_generic_error_t* evterr = (xcb_generic_error_t*) event; 1276 | xcw_die("event loop error: %d\n", evterr->error_code); 1277 | break; 1278 | } 1279 | case XCB_EXPOSE: { 1280 | overlays_set_text(state); 1281 | break; 1282 | } 1283 | case XCB_KEY_PRESS: { 1284 | handle_keypress(state, (xcb_key_press_event_t*)event); 1285 | break; 1286 | } 1287 | } 1288 | 1289 | free(event); 1290 | } 1291 | 1292 | return 0; 1293 | } 1294 | --------------------------------------------------------------------------------