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