├── .gitignore ├── LICENSE ├── Makefile ├── README.asciidoc ├── Sourcedeps ├── VERSION ├── doc ├── xdo.1 └── xdo.1.txt ├── helpers.c ├── helpers.h ├── xdo.c └── xdo.h /.gitignore: -------------------------------------------------------------------------------- 1 | xdo 2 | *.o 3 | tags 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Bastien Dejean 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME := xdo 2 | VERCMD ?= git describe --tags 2> /dev/null 3 | VERSION := $(shell $(VERCMD) || cat VERSION) 4 | 5 | CPPFLAGS += -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" 6 | CFLAGS += -std=c99 -pedantic -Wall -Wextra 7 | LDLIBS := -lxcb -lxcb-util -lxcb-icccm -lxcb-ewmh -lxcb-xtest 8 | 9 | PREFIX ?= /usr/local 10 | BINPREFIX ?= $(PREFIX)/bin 11 | MANPREFIX ?= $(PREFIX)/share/man 12 | 13 | SRC := $(wildcard *.c) 14 | OBJ := $(SRC:.c=.o) 15 | 16 | all: $(NAME) 17 | 18 | debug: CFLAGS += -O0 -g 19 | debug: $(NAME) 20 | 21 | include Sourcedeps 22 | 23 | $(OBJ): Makefile 24 | 25 | $(NAME): $(OBJ) 26 | 27 | install: 28 | mkdir -p "$(DESTDIR)$(BINPREFIX)" 29 | cp -p $(NAME) "$(DESTDIR)$(BINPREFIX)" 30 | mkdir -p "$(DESTDIR)$(MANPREFIX)/man1" 31 | cp -p doc/$(NAME).1 "$(DESTDIR)$(MANPREFIX)/man1" 32 | 33 | uninstall: 34 | rm -f "$(DESTDIR)$(BINPREFIX)/$(NAME)" 35 | rm -f $(DESTDIR)$(MANPREFIX)/man1/$(NAME).1 36 | 37 | doc: 38 | a2x -v -d manpage -f manpage -a revnumber=$(VERSION) doc/$(NAME).1.txt 39 | 40 | clean: 41 | rm -f $(OBJ) $(NAME) 42 | 43 | .PHONY: all debug install uninstall doc clean 44 | -------------------------------------------------------------------------------- /README.asciidoc: -------------------------------------------------------------------------------- 1 | doc/xdo.1.txt -------------------------------------------------------------------------------- /Sourcedeps: -------------------------------------------------------------------------------- 1 | helpers.o: helpers.c helpers.h 2 | xdo.o: xdo.c helpers.h xdo.h 3 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.5.7 -------------------------------------------------------------------------------- /doc/xdo.1: -------------------------------------------------------------------------------- 1 | '\" t 2 | .\" Title: xdo 3 | .\" Author: [FIXME: author] [see http://docbook.sf.net/el/author] 4 | .\" Generator: DocBook XSL Stylesheets v1.79.1 5 | .\" Date: 09/18/2017 6 | .\" Manual: Xdo Manual 7 | .\" Source: Xdo 0.5.7 8 | .\" Language: English 9 | .\" 10 | .TH "XDO" "1" "09/18/2017" "Xdo 0\&.5\&.7" "Xdo Manual" 11 | .\" ----------------------------------------------------------------- 12 | .\" * Define some portability stuff 13 | .\" ----------------------------------------------------------------- 14 | .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 15 | .\" http://bugs.debian.org/507673 16 | .\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html 17 | .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 18 | .ie \n(.g .ds Aq \(aq 19 | .el .ds Aq ' 20 | .\" ----------------------------------------------------------------- 21 | .\" * set default formatting 22 | .\" ----------------------------------------------------------------- 23 | .\" disable hyphenation 24 | .nh 25 | .\" disable justification (adjust text to left margin only) 26 | .ad l 27 | .\" ----------------------------------------------------------------- 28 | .\" * MAIN CONTENT STARTS HERE * 29 | .\" ----------------------------------------------------------------- 30 | .SH "NAME" 31 | xdo \- Perform actions on windows 32 | .SH "SYNOPSIS" 33 | .sp 34 | \fBxdo\fR \fIACTION\fR [\fIOPTIONS\fR] [\fIWID\fR \&...] 35 | .SH "DESCRIPTION" 36 | .sp 37 | Apply the given action to the given windows\&. 38 | .sp 39 | If no window IDs and no options are given, the action applies to the focused window\&. 40 | .SH "ACTIONS" 41 | .PP 42 | \fBclose\fR 43 | .RS 4 44 | Close the window\&. 45 | .RE 46 | .PP 47 | \fBkill\fR 48 | .RS 4 49 | Kill the client\&. 50 | .RE 51 | .PP 52 | \fBhide\fR 53 | .RS 4 54 | Unmap the window\&. 55 | .RE 56 | .PP 57 | \fBshow\fR 58 | .RS 4 59 | Map the window\&. 60 | .RE 61 | .PP 62 | \fBraise\fR 63 | .RS 4 64 | Raise the window\&. 65 | .RE 66 | .PP 67 | \fBlower\fR 68 | .RS 4 69 | Lower the window\&. 70 | .RE 71 | .PP 72 | \fBbelow\fR 73 | .RS 4 74 | Put the window below the target (see 75 | \fB\-t\fR)\&. 76 | .RE 77 | .PP 78 | \fBabove\fR 79 | .RS 4 80 | Put the window above the target (see 81 | \fB\-t\fR)\&. 82 | .RE 83 | .PP 84 | \fBmove\fR 85 | .RS 4 86 | Move the window\&. 87 | .RE 88 | .PP 89 | \fBresize\fR 90 | .RS 4 91 | Resize the window\&. 92 | .RE 93 | .PP 94 | \fBactivate\fR 95 | .RS 4 96 | Activate the window\&. 97 | .RE 98 | .PP 99 | \fBid\fR 100 | .RS 4 101 | Print the window\(cqs ID\&. 102 | .RE 103 | .PP 104 | \fBpid\fR 105 | .RS 4 106 | Print the window\(cqs pid\&. 107 | .RE 108 | .PP 109 | \fBkey_press\fR, \fBkey_release\fR 110 | .RS 4 111 | Simulate a key press/release event\&. 112 | .RE 113 | .PP 114 | \fBbutton_press\fR, \fBbutton_release\fR 115 | .RS 4 116 | Simulate a button press/release event\&. 117 | .RE 118 | .PP 119 | \fBpointer_motion\fR 120 | .RS 4 121 | Simulate a pointer motion event\&. 122 | .RE 123 | .PP 124 | \fB\-h\fR 125 | .RS 4 126 | Print the synopsis and exit\&. 127 | .RE 128 | .PP 129 | \fB\-v\fR 130 | .RS 4 131 | Print the version and exit\&. 132 | .RE 133 | .SH "OPTIONS" 134 | .sp 135 | When options are provided, the action applies to all the children of the root window that match the comparisons implied by the options in relation to the focused window\&. 136 | .PP 137 | \fB\-r\fR 138 | .RS 4 139 | Distinct ID\&. 140 | .RE 141 | .PP 142 | \fB\-c\fR 143 | .RS 4 144 | Same class\&. 145 | .RE 146 | .PP 147 | \fB\-C\fR 148 | .RS 4 149 | Distinct class\&. 150 | .RE 151 | .PP 152 | \fB\-d\fR 153 | .RS 4 154 | Same desktop\&. 155 | .RE 156 | .PP 157 | \fB\-D\fR 158 | .RS 4 159 | Distinct desktop\&. 160 | .RE 161 | .PP 162 | \fB\-n\fR \fIINSTANCE_NAME\fR 163 | .RS 4 164 | The window has the given instance name\&. 165 | .RE 166 | .PP 167 | \fB\-N\fR \fICLASS_NAME\fR 168 | .RS 4 169 | The window has the given class name\&. 170 | .RE 171 | .PP 172 | \fB\-a\fR \fIWM_NAME\fR 173 | .RS 4 174 | The window has the given wm name\&. 175 | .RE 176 | .PP 177 | \fB\-t\fR \fIWID\fR 178 | .RS 4 179 | The target window for the 180 | \fBbelow\fR 181 | and 182 | \fBabove\fR 183 | actions\&. 184 | .RE 185 | .PP 186 | \fB\-p\fR \fIPID\fR 187 | .RS 4 188 | The window has the given pid\&. 189 | .RE 190 | .PP 191 | \fB\-k\fR \fICODE\fR 192 | .RS 4 193 | Use the given code for the 194 | \fBkey_press\fR, 195 | \fBkey_release\fR, 196 | \fBbutton_press\fR 197 | and 198 | \fBbutton_release\fR 199 | actions\&. 200 | .RE 201 | .PP 202 | \fB\-x\fR \fI[\(+-]PIXELS\fR 203 | .RS 4 204 | Window x coordinate (or delta) for the 205 | \fBmove\fR 206 | and 207 | \fBpointer_motion\fR 208 | action\&. 209 | .RE 210 | .PP 211 | \fB\-y\fR \fI[\(+-]PIXELS\fR 212 | .RS 4 213 | Window y coordinate (or delta) for the 214 | \fBmove\fR 215 | and 216 | \fBpointer_motion\fR 217 | action\&. 218 | .RE 219 | .PP 220 | \fB\-w\fR \fI[\(+-]PIXELS\fR 221 | .RS 4 222 | Window width (or delta) for the 223 | \fBresize\fR 224 | action\&. 225 | .RE 226 | .PP 227 | \fB\-h\fR \fI[\(+-]PIXELS\fR 228 | .RS 4 229 | Window height (or delta) for the 230 | \fBresize\fR 231 | action\&. 232 | .RE 233 | .PP 234 | \fB\-m\fR 235 | .RS 4 236 | Wait for the existence of a matching window\&. 237 | .RE 238 | .PP 239 | \fB\-s\fR 240 | .RS 4 241 | Handle symbolic desktop numbers\&. 242 | .RE 243 | .SH "EXAMPLES" 244 | .sp 245 | Close the focused window: 246 | .sp 247 | .if n \{\ 248 | .RS 4 249 | .\} 250 | .nf 251 | xdo close 252 | .fi 253 | .if n \{\ 254 | .RE 255 | .\} 256 | .sp 257 | Close all the windows having the same class as the focused window: 258 | .sp 259 | .if n \{\ 260 | .RS 4 261 | .\} 262 | .nf 263 | xdo close \-c 264 | .fi 265 | .if n \{\ 266 | .RE 267 | .\} 268 | .sp 269 | Hide all the windows of the current desktop except the focused window: 270 | .sp 271 | .if n \{\ 272 | .RS 4 273 | .\} 274 | .nf 275 | xdo hide \-dr 276 | .fi 277 | .if n \{\ 278 | .RE 279 | .\} 280 | .sp 281 | Activate the window which ID is 0x00800109: 282 | .sp 283 | .if n \{\ 284 | .RS 4 285 | .\} 286 | .nf 287 | xdo activate 0x00800109 288 | .fi 289 | .if n \{\ 290 | .RE 291 | .\} 292 | .sp 293 | Send fake key press/release events with keycode 46 to the focused window: 294 | .sp 295 | .if n \{\ 296 | .RS 4 297 | .\} 298 | .nf 299 | xdo key_press \-k 46; sleep 0\&.2; xdo key_release \-k 46 300 | .fi 301 | .if n \{\ 302 | .RE 303 | .\} 304 | -------------------------------------------------------------------------------- /doc/xdo.1.txt: -------------------------------------------------------------------------------- 1 | :man source: Xdo 2 | :man version: {revnumber} 3 | :man manual: Xdo Manual 4 | 5 | xdo(1) 6 | ====== 7 | 8 | Name 9 | ---- 10 | 11 | xdo - Perform actions on windows 12 | 13 | Synopsis 14 | -------- 15 | 16 | *xdo* 'ACTION' ['OPTIONS'] ['WID' ...] 17 | 18 | Description 19 | ----------- 20 | 21 | Apply the given action to the given windows. 22 | 23 | If no window IDs and no options are given, the action applies to the focused window. 24 | 25 | Actions 26 | ------- 27 | 28 | *close*:: 29 | Close the window. 30 | 31 | *kill*:: 32 | Kill the client. 33 | 34 | *hide*:: 35 | Unmap the window. 36 | 37 | *show*:: 38 | Map the window. 39 | 40 | *raise*:: 41 | Raise the window. 42 | 43 | *lower*:: 44 | Lower the window. 45 | 46 | *below*:: 47 | Put the window below the target (see *-t*). 48 | 49 | *above*:: 50 | Put the window above the target (see *-t*). 51 | 52 | *move*:: 53 | Move the window. 54 | 55 | *resize*:: 56 | Resize the window. 57 | 58 | *activate*:: 59 | Activate the window. 60 | 61 | *id*:: 62 | Print the window's ID. 63 | 64 | *pid*:: 65 | Print the window's pid. 66 | 67 | *key_press*:: 68 | *key_release*:: 69 | Simulate a key press/release event. 70 | 71 | *button_press*:: 72 | *button_release*:: 73 | Simulate a button press/release event. 74 | 75 | *pointer_motion*:: 76 | Simulate a pointer motion event. 77 | 78 | *-h*:: 79 | Print the synopsis and exit. 80 | 81 | *-v*:: 82 | Print the version and exit. 83 | 84 | Options 85 | ------- 86 | 87 | When options are provided, the action applies to all the children of the root window that match the comparisons implied by the options in relation to the focused window. 88 | 89 | *-r*:: 90 | Distinct ID. 91 | 92 | *-c*:: 93 | Same class. 94 | 95 | *-C*:: 96 | Distinct class. 97 | 98 | *-d*:: 99 | Same desktop. 100 | 101 | *-D*:: 102 | Distinct desktop. 103 | 104 | *-n* 'INSTANCE_NAME':: 105 | The window has the given instance name. 106 | 107 | *-N* 'CLASS_NAME':: 108 | The window has the given class name. 109 | 110 | *-a* 'WM_NAME':: 111 | The window has the given wm name. 112 | 113 | *-t* 'WID':: 114 | The target window for the *below* and *above* actions. 115 | 116 | *-p* 'PID':: 117 | The window has the given pid. 118 | 119 | *-k* 'CODE':: 120 | Use the given code for the *key_press*, *key_release*, *button_press* and *button_release* actions. 121 | 122 | *-x* '[±]PIXELS':: 123 | Window x coordinate (or delta) for the *move* and *pointer_motion* action. 124 | 125 | *-y* '[±]PIXELS':: 126 | Window y coordinate (or delta) for the *move* and *pointer_motion* action. 127 | 128 | *-w* '[±]PIXELS':: 129 | Window width (or delta) for the *resize* action. 130 | 131 | *-h* '[±]PIXELS':: 132 | Window height (or delta) for the *resize* action. 133 | 134 | *-m*:: 135 | Wait for the existence of a matching window. 136 | 137 | *-s*:: 138 | Handle symbolic desktop numbers. 139 | 140 | Examples 141 | -------- 142 | 143 | Close the focused window: 144 | 145 | ---- 146 | xdo close 147 | ---- 148 | 149 | Close all the windows having the same class as the focused window: 150 | 151 | ---- 152 | xdo close -c 153 | ---- 154 | 155 | Hide all the windows of the current desktop except the focused window: 156 | 157 | ---- 158 | xdo hide -dr 159 | ---- 160 | 161 | Activate the window which ID is 0x00800109: 162 | 163 | ---- 164 | xdo activate 0x00800109 165 | ---- 166 | 167 | Send fake key press/release events with keycode 46 to the focused window: 168 | 169 | ---- 170 | xdo key_press -k 46; sleep 0.2; xdo key_release -k 46 171 | ---- 172 | 173 | //// 174 | vim: set ft=asciidoc: 175 | //// 176 | -------------------------------------------------------------------------------- /helpers.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "helpers.h" 5 | 6 | void warn(char *fmt, ...) 7 | { 8 | va_list ap; 9 | va_start(ap, fmt); 10 | vfprintf(stderr, fmt, ap); 11 | va_end(ap); 12 | } 13 | 14 | __attribute__((noreturn)) 15 | void err(char *fmt, ...) 16 | { 17 | va_list ap; 18 | va_start(ap, fmt); 19 | vfprintf(stderr, fmt, ap); 20 | va_end(ap); 21 | exit(EXIT_FAILURE); 22 | } 23 | -------------------------------------------------------------------------------- /helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef _HELPERS_H 2 | #define _HELPERS_H 3 | 4 | #define MAXLEN 256 5 | #define MIN(A, B) ((A) < (B) ? (A) : (B)) 6 | 7 | void warn(char *, ...); 8 | __attribute__((noreturn)) 9 | void err(char *, ...); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /xdo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "helpers.h" 14 | #include "xdo.h" 15 | 16 | int main(int argc, char *argv[]) 17 | { 18 | if (argc < 2) { 19 | err("No arguments given.\n"); 20 | } 21 | 22 | void (*action) (xcb_window_t win); 23 | 24 | if (strcmp(argv[1], "id") == 0) { 25 | action = window_id; 26 | } else if (strcmp(argv[1], "pid") == 0) { 27 | action = window_pid; 28 | } else if (strcmp(argv[1], "activate") == 0) { 29 | action = window_activate; 30 | } else if (strcmp(argv[1], "hide") == 0) { 31 | action = window_hide; 32 | } else if (strcmp(argv[1], "show") == 0) { 33 | action = window_show; 34 | } else if (strcmp(argv[1], "move") == 0) { 35 | action = window_move; 36 | } else if (strcmp(argv[1], "resize") == 0) { 37 | action = window_resize; 38 | } else if (strcmp(argv[1], "close") == 0) { 39 | action = window_close; 40 | } else if (strcmp(argv[1], "kill") == 0) { 41 | action = window_kill; 42 | } else if (strcmp(argv[1], "raise") == 0) { 43 | action = window_raise; 44 | } else if (strcmp(argv[1], "lower") == 0) { 45 | action = window_lower; 46 | } else if (strcmp(argv[1], "below") == 0) { 47 | action = window_below; 48 | } else if (strcmp(argv[1], "above") == 0) { 49 | action = window_above; 50 | } else if (strcmp(argv[1], "key_press") == 0) { 51 | action = key_press; 52 | } else if (strcmp(argv[1], "key_release") == 0) { 53 | action = key_release; 54 | } else if (strcmp(argv[1], "button_press") == 0) { 55 | action = button_press; 56 | } else if (strcmp(argv[1], "button_release") == 0) { 57 | action = button_release; 58 | } else if (strcmp(argv[1], "pointer_motion") == 0) { 59 | action = pointer_motion; 60 | } else if (strcmp(argv[1], "-h") == 0) { 61 | return usage(); 62 | } else if (strcmp(argv[1], "-v") == 0) { 63 | return version(); 64 | } else { 65 | err("Unknown action: '%s'.\n", argv[1]); 66 | } 67 | 68 | init(); 69 | argc--, argv++; 70 | int opt; 71 | while ((opt = getopt(argc, argv, "rcCdDsmn:N:a:p:k:t:x:y:h:w:")) != -1) { 72 | switch (opt) { 73 | case 'r': 74 | cfg.wid = VALUE_DIFFERENT; 75 | break; 76 | case 'c': 77 | cfg.class = VALUE_SAME; 78 | break; 79 | case 'C': 80 | cfg.class = VALUE_DIFFERENT; 81 | break; 82 | case 'd': 83 | cfg.desktop = VALUE_SAME; 84 | break; 85 | case 'D': 86 | cfg.desktop = VALUE_DIFFERENT; 87 | break; 88 | case 'n': 89 | cfg.instance_name = optarg; 90 | break; 91 | case 'N': 92 | cfg.class_name = optarg; 93 | break; 94 | case 'a': 95 | cfg.wm_name = optarg; 96 | break; 97 | case 'p': 98 | cfg.pid = atoi(optarg); 99 | break; 100 | case 't': 101 | cfg.target = strtol(optarg, NULL, 0); 102 | break; 103 | case 'k': 104 | cfg.evt_code = atoi(optarg); 105 | break; 106 | case 's': 107 | cfg.symb_desks = true; 108 | break; 109 | case 'm': 110 | cfg.wait_match = true; 111 | break; 112 | case 'x': 113 | cfg.x = optarg; 114 | break; 115 | case 'y': 116 | cfg.y = optarg; 117 | break; 118 | case 'w': 119 | cfg.width = optarg; 120 | break; 121 | case 'h': 122 | cfg.height = optarg; 123 | break; 124 | } 125 | } 126 | 127 | int num = argc - optind; 128 | char **args = argv + optind; 129 | 130 | setup(); 131 | 132 | struct sigaction sa; 133 | sa.sa_handler = &handle_signal; 134 | sa.sa_flags = SA_RESTART; 135 | sigfillset(&sa.sa_mask); 136 | sigaction(SIGTERM, &sa, NULL); 137 | sigaction(SIGINT, &sa, NULL); 138 | sigaction(SIGHUP, &sa, NULL); 139 | 140 | int hits = 0; 141 | xcb_window_t win = XCB_NONE; 142 | 143 | if (action == key_press || 144 | action == key_release || 145 | action == button_press || 146 | action == button_release || 147 | action == pointer_motion) { 148 | hits = 1; 149 | (*action)(win); 150 | goto end; 151 | } 152 | 153 | char class[MAXLEN] = {0}; 154 | uint32_t desktop = 0; 155 | if (cfg.wid != VALUE_IGNORE || cfg.class != VALUE_IGNORE) { 156 | get_active_window(&win); 157 | } 158 | if (cfg.class != VALUE_IGNORE) { 159 | get_class(win, class, sizeof(class)); 160 | } 161 | if (cfg.desktop != VALUE_IGNORE) { 162 | get_current_desktop(&desktop); 163 | } 164 | 165 | running = true; 166 | 167 | while (running) { 168 | if (num > 0) { 169 | char *end; 170 | for (int i = 0; i < num; i++) { 171 | errno = 0; 172 | long int w = strtol(args[i], &end, 0); 173 | if (errno != 0 || *end != '\0') { 174 | warn("Invalid window ID: '%s'.\n", args[i]); 175 | } else if (match(w, win, desktop, class)) { 176 | (*action)(w); 177 | hits++; 178 | } 179 | } 180 | } else { 181 | if (cfg.wid == VALUE_IGNORE && 182 | cfg.class == VALUE_IGNORE && 183 | cfg.desktop == VALUE_IGNORE && 184 | cfg.class_name == NULL && 185 | cfg.instance_name == NULL && 186 | cfg.wm_name == NULL && 187 | cfg.pid == 0) { 188 | xcb_window_t win; 189 | get_active_window(&win); 190 | if (win != XCB_NONE) { 191 | (*action)(win); 192 | hits++; 193 | } 194 | } else { 195 | apply(action, root, win, desktop, class, &hits); 196 | } 197 | } 198 | 199 | if (cfg.wait_match && hits == 0) { 200 | nanosleep(&wait_interval, NULL); 201 | } else { 202 | break; 203 | } 204 | } 205 | 206 | end: 207 | finish(); 208 | if (hits > 0) { 209 | return EXIT_SUCCESS; 210 | } else { 211 | return EXIT_FAILURE; 212 | } 213 | } 214 | 215 | void apply(void (*action)(xcb_window_t), xcb_window_t parent, xcb_window_t win, uint32_t desktop, char* class, int* hits) 216 | { 217 | xcb_query_tree_reply_t *qtr = xcb_query_tree_reply(dpy, xcb_query_tree(dpy, parent), NULL); 218 | if (qtr == NULL) { 219 | warn("Failed to query the window tree.\n"); 220 | return; 221 | } 222 | int len = xcb_query_tree_children_length(qtr); 223 | xcb_window_t *wins = xcb_query_tree_children(qtr); 224 | for (int i = 0; i < len; i++) { 225 | xcb_window_t w = wins[i]; 226 | if (match(w, win, desktop, class)) { 227 | (*action)(w); 228 | (*hits)++; 229 | } 230 | apply(action, w, win, desktop, class, hits); 231 | } 232 | free(qtr); 233 | } 234 | 235 | bool match(xcb_window_t w, xcb_window_t win, uint32_t desktop, char* class) 236 | { 237 | char c[MAXLEN] = {0}, i[MAXLEN] = {0}, n[MAXLEN] = {0}; 238 | uint32_t d, p; 239 | return (cfg.wid == VALUE_IGNORE || (cfg.wid == VALUE_DIFFERENT && w != win)) && 240 | (cfg.class == VALUE_IGNORE || 241 | (get_class(w, c, sizeof(c)) && 242 | ((cfg.class == VALUE_SAME && strcmp(class, c) == 0) || 243 | (cfg.class == VALUE_DIFFERENT && strcmp(class, c) != 0)))) && 244 | (cfg.class_name == NULL || (get_class(w, c, sizeof(c)) && 245 | strcmp(cfg.class_name, c) == 0)) && 246 | (cfg.instance_name == NULL || (get_instance(w, i, sizeof(i)) && 247 | strcmp(cfg.instance_name, i) == 0)) && 248 | (cfg.wm_name == NULL || (get_wm_name(w, n, sizeof(n)) && 249 | strcmp(cfg.wm_name, n) == 0)) && 250 | (cfg.pid == 0 || (get_pid(w, &p) && p == cfg.pid)) && 251 | (cfg.desktop == VALUE_IGNORE || 252 | (get_desktop(w, &d) && 253 | ((cfg.desktop == VALUE_SAME && DESKEQ(desktop, d)) || 254 | (cfg.desktop == VALUE_DIFFERENT && !DESKEQ(desktop, d))))); 255 | } 256 | 257 | void init(void) 258 | { 259 | cfg.class = cfg.desktop = cfg.wid = VALUE_IGNORE; 260 | cfg.class_name = cfg.instance_name = NULL; 261 | cfg.x = cfg.y = cfg.width = cfg.height = NULL; 262 | cfg.pid = 0; 263 | cfg.evt_code = XCB_NONE; 264 | cfg.symb_desks = cfg.wait_match = false; 265 | } 266 | 267 | int usage(void) 268 | { 269 | printf("xdo ACTION [OPTIONS] [WID ...]\n"); 270 | return EXIT_SUCCESS; 271 | } 272 | 273 | int version(void) 274 | { 275 | printf("%s\n", VERSION); 276 | return EXIT_SUCCESS; 277 | } 278 | 279 | void setup(void) 280 | { 281 | dpy = xcb_connect(NULL, &default_screen); 282 | if (xcb_connection_has_error(dpy)) { 283 | err("Can't open display.\n"); 284 | } 285 | xcb_screen_t *screen = xcb_setup_roots_iterator(xcb_get_setup(dpy)).data; 286 | if (screen == NULL) { 287 | err("Can't acquire screen.\n"); 288 | } 289 | root = screen->root; 290 | ewmh = malloc(sizeof(xcb_ewmh_connection_t)); 291 | if (xcb_ewmh_init_atoms_replies(ewmh, xcb_ewmh_init_atoms(dpy, ewmh), NULL) == 0) { 292 | err("Can't initialize EWMH atoms.\n"); 293 | } 294 | } 295 | 296 | void finish(void) 297 | { 298 | xcb_aux_sync(dpy); 299 | xcb_ewmh_connection_wipe(ewmh); 300 | free(ewmh); 301 | xcb_disconnect(dpy); 302 | } 303 | 304 | void handle_signal(int sig) 305 | { 306 | if (sig == SIGTERM || sig == SIGINT || sig == SIGHUP) { 307 | running = false; 308 | } 309 | } 310 | 311 | void get_active_window(xcb_window_t *win) 312 | { 313 | if (xcb_ewmh_get_active_window_reply(ewmh, xcb_ewmh_get_active_window(ewmh, default_screen), win, NULL) != 1) { 314 | err("Can't determine the active window.\n"); 315 | } 316 | } 317 | 318 | bool get_class(xcb_window_t win, char *class, size_t len) 319 | { 320 | xcb_icccm_get_wm_class_reply_t icr; 321 | if (xcb_icccm_get_wm_class_reply(dpy, xcb_icccm_get_wm_class(dpy, win), &icr, NULL) == 1) { 322 | strncpy(class, icr.class_name, len); 323 | return true; 324 | } else { 325 | return false; 326 | } 327 | } 328 | 329 | bool get_instance(xcb_window_t win, char *instance, size_t len) 330 | { 331 | xcb_icccm_get_wm_class_reply_t icr; 332 | if (xcb_icccm_get_wm_class_reply(dpy, xcb_icccm_get_wm_class(dpy, win), &icr, NULL) == 1) { 333 | strncpy(instance, icr.instance_name, len); 334 | return true; 335 | } else { 336 | return false; 337 | } 338 | } 339 | 340 | bool get_wm_name(xcb_window_t win, char *wm_name, size_t len) 341 | { 342 | xcb_icccm_get_text_property_reply_t itr; 343 | if (xcb_icccm_get_wm_name_reply(dpy, xcb_icccm_get_wm_name(dpy, win), &itr, NULL) == 1) { 344 | strncpy(wm_name, itr.name, MIN(len, itr.name_len)); 345 | return true; 346 | } else { 347 | return false; 348 | } 349 | } 350 | 351 | bool get_pid(xcb_window_t win, uint32_t *pid) 352 | { 353 | return (xcb_ewmh_get_wm_pid_reply(ewmh, xcb_ewmh_get_wm_pid(ewmh, win), pid, NULL) == 1); 354 | } 355 | 356 | bool get_desktop(xcb_window_t win, uint32_t *desktop) 357 | { 358 | return (xcb_ewmh_get_wm_desktop_reply(ewmh, xcb_ewmh_get_wm_desktop(ewmh, win), desktop, NULL) == 1 && (*desktop != ALL_DESKS || cfg.symb_desks)); 359 | } 360 | 361 | bool get_current_desktop(uint32_t *desktop) 362 | { 363 | return (xcb_ewmh_get_current_desktop_reply(ewmh, xcb_ewmh_get_current_desktop(ewmh, default_screen), desktop, NULL) == 1); 364 | } 365 | 366 | void window_close(xcb_window_t win) 367 | { 368 | xcb_ewmh_request_close_window(ewmh, default_screen, win, XCB_CURRENT_TIME, XCB_EWMH_CLIENT_SOURCE_TYPE_OTHER); 369 | } 370 | 371 | void window_kill(xcb_window_t win) 372 | { 373 | xcb_kill_client(dpy, win); 374 | } 375 | 376 | void window_hide(xcb_window_t win) 377 | { 378 | xcb_unmap_window(dpy, win); 379 | } 380 | 381 | void window_show(xcb_window_t win) 382 | { 383 | xcb_map_window(dpy, win); 384 | } 385 | 386 | void window_raise(xcb_window_t win) 387 | { 388 | uint32_t values[] = {XCB_STACK_MODE_ABOVE}; 389 | xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_STACK_MODE, values); 390 | } 391 | 392 | void window_lower(xcb_window_t win) 393 | { 394 | uint32_t values[] = {XCB_STACK_MODE_BELOW}; 395 | xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_STACK_MODE, values); 396 | } 397 | 398 | void window_stack(xcb_window_t win, uint32_t mode) 399 | { 400 | uint16_t mask = XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE; 401 | uint32_t values[] = {cfg.target, mode}; 402 | xcb_configure_window(dpy, win, mask, values); 403 | } 404 | 405 | void window_above(xcb_window_t win) 406 | { 407 | window_stack(win, XCB_STACK_MODE_ABOVE); 408 | } 409 | 410 | void window_below(xcb_window_t win) 411 | { 412 | window_stack(win, XCB_STACK_MODE_BELOW); 413 | } 414 | 415 | #define SETGEOM(v) \ 416 | if (cfg.v != NULL) { \ 417 | uint32_t v = atoi(cfg.v); \ 418 | if (ISRELA(cfg.v)) { \ 419 | values[i] += v; \ 420 | } else { \ 421 | values[i] = v; \ 422 | } \ 423 | } \ 424 | i++; 425 | 426 | 427 | void window_move(xcb_window_t win) 428 | { 429 | xcb_get_geometry_reply_t *geo = xcb_get_geometry_reply(dpy, xcb_get_geometry(dpy, win), NULL); 430 | if (geo == NULL) { 431 | return; 432 | } 433 | uint32_t values[2] = {geo->x, geo->y}; 434 | int i = 0; 435 | SETGEOM(x) 436 | SETGEOM(y) 437 | xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values); 438 | } 439 | 440 | void window_resize(xcb_window_t win) 441 | { 442 | xcb_get_geometry_reply_t *geo = xcb_get_geometry_reply(dpy, xcb_get_geometry(dpy, win), NULL); 443 | if (geo == NULL) { 444 | return; 445 | } 446 | uint32_t values[2] = {geo->width, geo->height}; 447 | int i = 0; 448 | SETGEOM(width) 449 | SETGEOM(height) 450 | xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values); 451 | } 452 | 453 | #undef SETGEOM 454 | 455 | void window_activate(xcb_window_t win) 456 | { 457 | xcb_ewmh_request_change_active_window(ewmh, default_screen, win, XCB_EWMH_CLIENT_SOURCE_TYPE_OTHER, XCB_CURRENT_TIME, XCB_NONE); 458 | } 459 | 460 | void window_id(xcb_window_t win) 461 | { 462 | printf("0x%08X\n", win); 463 | } 464 | 465 | void window_pid(xcb_window_t win) 466 | { 467 | uint32_t pid; 468 | if (get_pid(win, &pid)) { 469 | printf("%i\n", pid); 470 | } 471 | } 472 | 473 | void fake_input(xcb_window_t win, uint8_t evt, uint8_t code) 474 | { 475 | xcb_test_fake_input(dpy, evt, code, XCB_CURRENT_TIME, win, 0, 0, 0); 476 | } 477 | 478 | void fake_motion(xcb_window_t win, uint8_t rel, uint16_t x, uint16_t y) 479 | { 480 | xcb_test_fake_input(dpy, XCB_MOTION_NOTIFY, rel, XCB_CURRENT_TIME, win, x, y, 0); 481 | } 482 | 483 | void key_press(xcb_window_t win) 484 | { 485 | fake_input(win, XCB_KEY_PRESS, cfg.evt_code); 486 | } 487 | 488 | void key_release(xcb_window_t win) 489 | { 490 | fake_input(win, XCB_KEY_RELEASE, cfg.evt_code); 491 | } 492 | 493 | void button_press(xcb_window_t win) 494 | { 495 | fake_input(win, XCB_BUTTON_PRESS, cfg.evt_code); 496 | } 497 | 498 | void button_release(xcb_window_t win) 499 | { 500 | fake_input(win, XCB_BUTTON_RELEASE, cfg.evt_code); 501 | } 502 | 503 | void pointer_motion(xcb_window_t win) 504 | { 505 | uint16_t x = atoi(cfg.x); 506 | uint16_t y = atoi(cfg.y); 507 | fake_motion(win, ISRELA(cfg.x) || ISRELA(cfg.y), x, y); 508 | } 509 | -------------------------------------------------------------------------------- /xdo.h: -------------------------------------------------------------------------------- 1 | #ifndef _XDO_H 2 | #define _XDO_H 3 | 4 | #define ALL_DESKS 0xFFFFFFFF 5 | #define DESKEQ(d1,d2) (d1 == d2 || (cfg.symb_desks && (d1 == ALL_DESKS || d2 == ALL_DESKS))) 6 | #define ISRELA(s) (s[0] == '+' || s[0] == '-') 7 | 8 | typedef enum { 9 | VALUE_IGNORE, 10 | VALUE_SAME, 11 | VALUE_DIFFERENT 12 | } value_cmp_t; 13 | 14 | typedef struct { 15 | value_cmp_t wid; 16 | value_cmp_t class; 17 | value_cmp_t desktop; 18 | char *class_name; 19 | char *instance_name; 20 | char *wm_name; 21 | char *x; 22 | char *y; 23 | char *width; 24 | char *height; 25 | uint32_t pid; 26 | uint8_t evt_code; 27 | bool symb_desks; 28 | bool wait_match; 29 | long int target; 30 | } config_t; 31 | 32 | const struct timespec wait_interval = {0, 1e8}; // 100 ms 33 | 34 | xcb_connection_t *dpy; 35 | int default_screen; 36 | xcb_window_t root; 37 | xcb_ewmh_connection_t *ewmh; 38 | config_t cfg; 39 | bool running; 40 | 41 | void apply(void (*action)(xcb_window_t), xcb_window_t parent, xcb_window_t win, uint32_t desktop, char* class, int* hits); 42 | bool match(xcb_window_t w, xcb_window_t win, uint32_t desktop, char* class); 43 | void init(void); 44 | int usage(void); 45 | int version(void); 46 | void setup(void); 47 | void finish(void); 48 | void handle_signal(int sig); 49 | void get_active_window(xcb_window_t *win); 50 | bool get_class(xcb_window_t win, char *class, size_t len); 51 | bool get_instance(xcb_window_t win, char *instance, size_t len); 52 | bool get_wm_name(xcb_window_t win, char *wm_name, size_t len); 53 | bool get_pid(xcb_window_t win, uint32_t *pid); 54 | bool get_desktop(xcb_window_t win, uint32_t *desktop); 55 | bool get_current_desktop(uint32_t *desktop); 56 | void window_close(xcb_window_t win); 57 | void window_kill(xcb_window_t win); 58 | void window_hide(xcb_window_t win); 59 | void window_show(xcb_window_t win); 60 | void window_raise(xcb_window_t win); 61 | void window_lower(xcb_window_t win); 62 | void window_stack(xcb_window_t win, uint32_t mode); 63 | void window_above(xcb_window_t win); 64 | void window_below(xcb_window_t win); 65 | void window_move(xcb_window_t win); 66 | void window_resize(xcb_window_t win); 67 | void window_activate(xcb_window_t win); 68 | void window_id(xcb_window_t win); 69 | void window_pid(xcb_window_t win); 70 | void fake_input(xcb_window_t win, uint8_t evt, uint8_t code); 71 | void fake_motion(xcb_window_t win, uint8_t rel, uint16_t x, uint16_t y); 72 | void key_press(xcb_window_t win); 73 | void key_release(xcb_window_t win); 74 | void button_press(xcb_window_t win); 75 | void button_release(xcb_window_t win); 76 | void pointer_motion(xcb_window_t win); 77 | 78 | #endif 79 | --------------------------------------------------------------------------------