aboutsummaryrefslogtreecommitdiff
path: root/xdeadzone.c
blob: e3446f977288f8bffadcaad066c86148dc52662e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
/*
	xdeadzone - create a window and don't allow the mouse pointer to enter it.

	Operation is simple: set up our window, wait for events, and if we get
	an EnterNotify event, warp the pointer out of our window.
*/

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#ifndef VERSION
#  define VERSION "UnknownVersion"
#endif

#define NAME "XDeadZone"

/* define this to print some debug info at runtime. */
#define DEBUG

#ifdef DEBUG
#  define DBG(x) x
#else
#  define DBG(x)
#endif

enum { M_UNSET, M_ABS, M_NE, M_NW, M_SE, M_SW };

const char *exe_name;

void set_exe_name(const char *p) {
	exe_name = p;
	while(*p) {
		if(*p == '/') exe_name = p + 1;
		p++;
	}
}

void banner(void) {
	printf(NAME " " VERSION "\n");
}

void usage(const int ret) {
	banner();
	printf(
			"Usage:\n  %s "
			"<-b | -w | -i> <-n> [-nw | -ne | -sw | -se | -abs] [geometry]\n\n"
			"  <geometry> is WxH for all modes but -abs, or\n  WxH[+-]xpos[+-]ypos for -abs\n",
			exe_name);
	exit(ret);
}

void errmsg(const char *msg) {
	fprintf(stderr, "%s: %s\n", exe_name, msg);
	usage(1);
}

void check_mode(int mode) {
	if(mode != M_UNSET)
		errmsg("multiple modes given, only one of -abs -ne -nw -se -sw is allowed");
}

int streq(const char *s1, const char *s2) {
	return !strcmp(s1, s2);
}

int main(int argc, char **argv) {
	Display *d;
	Window w;
	XEvent ev;
	Atom dock_atom;
	XWindowAttributes attr;
	XSetWindowAttributes setattr;

	int x = -1, y = -1, gflags = -1, visible = 0, black = 0, mode = M_UNSET, normal_window = 0;
	unsigned int width, height;

	set_exe_name(argv[0]);

	while(*++argv) {
		const char *a = *argv;
		DBG(printf("arg: %s\n", a));

	  	if(streq(a, "--help")) {
			usage(0);
		} else if(streq(a, "--version")) {
			banner();
			exit(0);
		} else if(streq(a, "-b")) {
			visible = 1;
			black = 1;
		} else if(streq(a, "-w")) {
			visible = 1;
			black = 0;
		} else if(streq(a, "-i")) {
			visible = 0;
		} else if(streq(a, "-n")) {
			visible = 1;
			normal_window = 1;
		} else if(streq(a, "-abs")) {
			check_mode(mode);
			mode = M_ABS;
		} else if(streq(a, "-ne")) {
			check_mode(mode);
			mode = M_NE;
		} else if(streq(a, "-nw")) {
			check_mode(mode);
			mode = M_NW;
		} else if(streq(a, "-se")) {
			check_mode(mode);
			mode = M_SE;
		} else if(streq(a, "-sw")) {
			check_mode(mode);
			mode = M_SW;
		} else if(a[0] == '-') {
			fprintf(stderr, "%s: invalid option: %s (try --help)\n", exe_name, a);
			exit(1);
		} else { /* no dash, must be geometry */
			if(gflags == -1)
				gflags = XParseGeometry(a, &x, &y, &width, &height);
			else
				errmsg("multiple geometry arguments given (try --help)");
		}
	}

	if(gflags == -1)
		errmsg("missing required geometry argument (try --help)");
	else
		DBG(printf("XParseGeometry got %d %d %d %d\n", x, y, width, height));

	if(mode != M_ABS && (gflags & (XValue | YValue)))
		errmsg("bad geometry: X and Y position not allowed without -abs");

	switch(mode) {
		case M_ABS:
			if(!(gflags & (XValue | YValue)))
				errmsg("bad geometry: -abs requires X and Y position");
			break;
		case M_NW:
			x = 0; y = 0; break;
		case M_NE:
			x = -width; y = 0; break;
		case M_SW:
			x = 0; y = -height; break;
		case M_SE:
			x = -width; y = -height; break;
		default:
			errmsg("no mode given, one of -abs -ne -nw -se -sw is required");
	}

	if(width == 0 || height == 0)
		errmsg("bad geometry: width and height must be non-zero");

	if(!(d = XOpenDisplay(NULL)))
		errmsg("can't open X display");

	XGetWindowAttributes(d, DefaultRootWindow(d), &attr);
	if(mode == M_ABS) {
		if(gflags & XNegative) x = (attr.width  + x) - width;
		if(gflags & YNegative) y = (attr.height + y) - height;
	} else {
		if(x < 0) x = (attr.width  + x);
		if(y < 0) y = (attr.height + y);
	}

	DBG(printf("X size %d x %d, x %d, y %d, width %d, height %d\n",
				attr.width, attr.height, x, y, width, height));

	/* Create window with override_redirect enabled, to tell the window
		manager not to decorate it with a titlebar or resize frame.
		Also, we really only care about EnterNotify events.
		Set the window to solid white, to make it easy to see during testing.
		In actual use, the window won't be visible, it'll be in the dead zone,
		right?
	 */
	setattr.override_redirect = normal_window ? False : True;
	if(visible) {
		setattr.background_pixel = black ?
			BlackPixel(d, DefaultScreen(d)) :
			WhitePixel(d, DefaultScreen(d));
	}
	setattr.event_mask = EnterWindowMask;

	w = XCreateWindow(d,
			DefaultRootWindow(d),
			x, y, width, height, 0,
			CopyFromParent,
			InputOutput,
			CopyFromParent,
			(visible ? CWBackPixel : 0) | CWOverrideRedirect | CWEventMask,
			&setattr);

	DBG(printf("window created\n"));

	XStoreName(d, w, NAME);

	/* On most window managers, this makes the window stay on top and
		be present on all virtual desktops. Tested, works with WindowMaker, Fvwm2,
		KDE5, XFCE4, BlackBox, and FluxBox. On all but Fvwm2, it also
		disappears the titlebar and resize frame... but we already took
		care of that with override_redirect, above.
		ref: http://standards.freedesktop.org/wm-spec/wm-spec-latest.html
	 */
	if(!normal_window) {
		dock_atom = XInternAtom(d, "_NET_WM_WINDOW_TYPE_DOCK", False);
		XChangeProperty(d,
				w,
				XInternAtom(d, "_NET_WM_WINDOW_TYPE", False),
				XA_ATOM,
				32,
				PropModeReplace,
				(unsigned char *) &dock_atom,
				1);
	}

	XMapWindow(d, w);

	/* Have to do this after it's mapped, since the window manager might have
	  	ignored the x and y in the XCreateWindow(). */
	XMoveWindow(d, w, x, y);

	DBG(printf("awaiting EnterNotify events...\n"));
	while(1) {
		XNextEvent(d, &ev);
		if(ev.type == EnterNotify) {
			/* warp down if at top of screen */
			int new_y = (y == 0 ? y + height : y - 1);

			DBG(printf("Got EnterNotify, coords %d, %d, new Y is %d\n",
						ev.xcrossing.x_root, ev.xcrossing.y_root, new_y));
			XWarpPointer(d,
					None,
					DefaultRootWindow(d),
					0, 0, 0, 0,
					ev.xcrossing.x_root,
					new_y);
		}
	}

	/* We should never get here (the loop above never exits).
		The only way out is for us or the X server to be killed.  */
	XUnmapWindow(d, w);
	XDestroyWindow(d, w);
	XCloseDisplay(d);

	return 0;
}