1 #include "config.h"
2 #include <assert.h>
3 #include <ctype.h>
4 #include <libgen.h>
5 #include <limits.h>
6 #include <signal.h>
7 #include <stdarg.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <time.h>
12 #include <unistd.h>
13 #include <sys/types.h>
14 #include <sys/time.h>
15 #include <X11/Xproto.h>
16 #include <X11/Xatom.h>
17 #include <X11/Xlib.h>
18 #include <X11/Xutil.h>
19 #include <X11/Xresource.h>
20 #include <X11/keysym.h>
21 #include <X11/XKBlib.h>
22
23 #include "WinMgr.h"
24 #define GUI_EVENT_NAMES
25 #include "guievent.h"
26 #include "logevent.h"
27
28 /// _SET would be nice to have
29 #define _NET_WM_STATE_REMOVE 0
30 #define _NET_WM_STATE_ADD 1
31 #define _NET_WM_STATE_TOGGLE 2
32
33 #define _NET_WM_ORIENTATION_HORZ 0
34 #define _NET_WM_ORIENTATION_VERT 1
35 #define _NET_WM_TOPLEFT 0
36 #define _NET_WM_TOPRIGHT 1
37 #define _NET_WM_BOTTOMRIGHT 2
38 #define _NET_WM_BOTTOMLEFT 3
39
40 #define KEY_MODMASK(x) ((x) & (ControlMask | ShiftMask | Mod1Mask))
41
42 #define COUNT(a) (int(sizeof a / sizeof(*a)))
43 #define CTRL(k) ((k) & 0x1F)
44 #define TEST(p) if (p); else fail( #p, __func__, __LINE__ )
45
46 static struct Layout {
47 long orient;
48 long columns;
49 long rows;
50 long corner;
51 } layout = { 0L, 4L, 3L, _NET_WM_TOPLEFT, };
52
53 typedef unsigned long Pixel;
54 const char* ApplicationName = "testnetwmhints";
55
tell(const char * msg,...)56 static void tell(const char* msg, ...) {
57 FILE* output = stdout;
58 timeval now;
59 gettimeofday(&now, NULL);
60 struct tm *loc = localtime(&now.tv_sec);
61
62 fprintf(output, "%02d.%03u: ",
63 /* loc->tm_hour, loc->tm_min, */
64 loc->tm_sec, (unsigned)(now.tv_usec / 1000));
65
66 va_list ap;
67 va_start(ap, msg);
68 vfprintf(output, msg, ap);
69 va_end(ap);
70 fflush(output);
71 }
72
fail(const char * expr,const char * func,const int line)73 static void fail(const char* expr, const char* func, const int line) {
74 tell("%s: %3d: ( %s ) FAILED!\n", func, line, expr);
75 }
76
77 class TDisplay {
78 Display *dpy;
79 public:
TDisplay()80 TDisplay() : dpy(0) { }
display()81 Display* display() { return dpy ? dpy : dpy = XOpenDisplay(0); }
~TDisplay()82 ~TDisplay() { if (dpy) { XCloseDisplay(dpy); dpy = 0; } }
operator Display*()83 operator Display*() { return display(); }
84 // operator _XPrivDisplay() { return _XPrivDisplay(display()); }
85 };
86
87 static TDisplay display;
88 static Colormap colormap;
89 static Window root = None;
90 static Window window = None;
91 static Window unmapped = None;
92 static Window unviewed = None;
93 ///static GC gc;
94
95 static long workspaceCount = 4;
96 static long activeWorkspace = 0;
97 static long windowWorkspace = 0;
98 //static long state[10] = { 0, 0 };
99 ///static bool sticky = false;
100
101 ///static bool fullscreen = true;
102
103 class TAtom {
104 public:
105 const char* const name;
106 private:
107 Atom atom;
108 public:
TAtom(const char * name)109 explicit TAtom(const char* name) : name(name), atom(None) { }
operator unsigned()110 operator unsigned() { return atom ? atom :
111 atom = XInternAtom(display, name, False); }
112 };
113
114 static TAtom _XA_WM_CLIENT_LEADER("WM_CLIENT_LEADER");
115 static TAtom _XA_WM_DELETE_WINDOW("WM_DELETE_WINDOW");
116 static TAtom _XA_WM_PROTOCOLS("WM_PROTOCOLS");
117 static TAtom _XA_WM_TAKE_FOCUS("WM_TAKE_FOCUS");
118 static TAtom _XA_WM_STATE("WM_STATE");
119 static TAtom _XA_WIN_TRAY("WIN_TRAY");
120 static TAtom _XA_WIN_LAYER("_WIN_LAYER");
121 static TAtom _XA_ICEWM_GUI_EVENT("ICEWM_GUI_EVENT");
122 static TAtom _XA_NET_WM_DESKTOP("_NET_WM_DESKTOP");
123 static TAtom _XA_NET_CURRENT_DESKTOP("_NET_CURRENT_DESKTOP");
124 static TAtom _XA_NET_NUMBER_OF_DESKTOPS("_NET_NUMBER_OF_DESKTOPS");
125 static TAtom _XA_NET_DESKTOP_NAMES("_NET_DESKTOP_NAMES");
126 static TAtom _XA_NET_WM_ACTION_ABOVE("_NET_WM_ACTION_ABOVE");
127 static TAtom _XA_NET_WM_ACTION_BELOW("_NET_WM_ACTION_BELOW");
128 static TAtom _XA_NET_WM_ACTION_CHANGE_DESKTOP("_NET_WM_ACTION_CHANGE_DESKTOP");
129 static TAtom _XA_NET_WM_ACTION_CLOSE("_NET_WM_ACTION_CLOSE");
130 static TAtom _XA_NET_WM_ACTION_FULLSCREEN("_NET_WM_ACTION_FULLSCREEN");
131 static TAtom _XA_NET_WM_ACTION_HIDE("_NET_WM_ACTION_HIDE");
132 static TAtom _XA_NET_WM_ACTION_MAXIMIZE_HORZ("_NET_WM_ACTION_MAXIMIZE_HORZ");
133 static TAtom _XA_NET_WM_ACTION_MAXIMIZE_VERT("_NET_WM_ACTION_MAXIMIZE_VERT");
134 static TAtom _XA_NET_WM_ACTION_MINIMIZE("_NET_WM_ACTION_MINIMIZE");
135 static TAtom _XA_NET_WM_ACTION_MOVE("_NET_WM_ACTION_MOVE");
136 static TAtom _XA_NET_WM_ACTION_RESIZE("_NET_WM_ACTION_RESIZE");
137 static TAtom _XA_NET_WM_ACTION_SHADE("_NET_WM_ACTION_SHADE");
138 static TAtom _XA_NET_WM_ACTION_STICK("_NET_WM_ACTION_STICK");
139 static TAtom _XA_NET_WM_ALLOWED_ACTIONS("_NET_WM_ALLOWED_ACTIONS");
140 static TAtom _XA_NET_WM_STATE("_NET_WM_STATE");
141 static TAtom _XA_NET_WM_STATE_ABOVE("_NET_WM_STATE_ABOVE");
142 static TAtom _XA_NET_WM_STATE_BELOW("_NET_WM_STATE_BELOW");
143 static TAtom _XA_NET_WM_STATE_DEMANDS_ATTENTION("_NET_WM_STATE_DEMANDS_ATTENTION");
144 static TAtom _XA_NET_WM_STATE_FOCUSED("_NET_WM_STATE_FOCUSED");
145 static TAtom _XA_NET_WM_STATE_FULLSCREEN("_NET_WM_STATE_FULLSCREEN");
146 static TAtom _XA_NET_WM_STATE_HIDDEN("_NET_WM_STATE_HIDDEN");
147 static TAtom _XA_NET_WM_STATE_MAXIMIZED_HORZ("_NET_WM_STATE_MAXIMIZED_HORZ");
148 static TAtom _XA_NET_WM_STATE_MAXIMIZED_VERT("_NET_WM_STATE_MAXIMIZED_VERT");
149 static TAtom _XA_NET_WM_STATE_MODAL("_NET_WM_STATE_MODAL");
150 static TAtom _XA_NET_WM_STATE_SHADED("_NET_WM_STATE_SHADED");
151 static TAtom _XA_NET_WM_STATE_SKIP_PAGER("_NET_WM_STATE_SKIP_PAGER");
152 static TAtom _XA_NET_WM_STATE_SKIP_TASKBAR("_NET_WM_STATE_SKIP_TASKBAR");
153 static TAtom _XA_NET_WM_STATE_STICKY("_NET_WM_STATE_STICKY");
154 static TAtom _XA_NET_WM_MOVERESIZE("_NET_WM_MOVERESIZE");
155 static TAtom _XA_NET_WM_PID("_NET_WM_PID");
156 static TAtom _XA_NET_WM_PING("_NET_WM_PING");
157 static TAtom _XA_NET_ACTIVE_WINDOW("_NET_ACTIVE_WINDOW");
158 static TAtom _XA_NET_DESKTOP_LAYOUT("_NET_DESKTOP_LAYOUT");
159 static TAtom _XA_NET_REQUEST_FRAME_EXTENTS("_NET_REQUEST_FRAME_EXTENTS");
160 static TAtom _XA_NET_FRAME_EXTENTS("_NET_FRAME_EXTENTS");
161 static TAtom _XA_NET_WM_WINDOW_TYPE("_NET_WM_WINDOW_TYPE");
162 static TAtom _XA_NET_WM_WINDOW_TYPE_COMBO("_NET_WM_WINDOW_TYPE_COMBO");
163 static TAtom _XA_NET_WM_WINDOW_TYPE_DESKTOP("_NET_WM_WINDOW_TYPE_DESKTOP");
164 static TAtom _XA_NET_WM_WINDOW_TYPE_DIALOG("_NET_WM_WINDOW_TYPE_DIALOG");
165 static TAtom _XA_NET_WM_WINDOW_TYPE_DND("_NET_WM_WINDOW_TYPE_DND");
166 static TAtom _XA_NET_WM_WINDOW_TYPE_DOCK("_NET_WM_WINDOW_TYPE_DOCK");
167 static TAtom _XA_NET_WM_WINDOW_TYPE_DROPDOWN_MENU("_NET_WM_WINDOW_TYPE_DROPDOWN_MENU");
168 static TAtom _XA_NET_WM_WINDOW_TYPE_MENU("_NET_WM_WINDOW_TYPE_MENU");
169 static TAtom _XA_NET_WM_WINDOW_TYPE_NORMAL("_NET_WM_WINDOW_TYPE_NORMAL");
170 static TAtom _XA_NET_WM_WINDOW_TYPE_NOTIFICATION("_NET_WM_WINDOW_TYPE_NOTIFICATION");
171 static TAtom _XA_NET_WM_WINDOW_TYPE_POPUP_MENU("_NET_WM_WINDOW_TYPE_POPUP_MENU");
172 static TAtom _XA_NET_WM_WINDOW_TYPE_SPLASH("_NET_WM_WINDOW_TYPE_SPLASH");
173 static TAtom _XA_NET_WM_WINDOW_TYPE_TOOLBAR("_NET_WM_WINDOW_TYPE_TOOLBAR");
174 static TAtom _XA_NET_WM_WINDOW_TYPE_TOOLTIP("_NET_WM_WINDOW_TYPE_TOOLTIP");
175 static TAtom _XA_NET_WM_WINDOW_TYPE_UTILITY("_NET_WM_WINDOW_TYPE_UTILITY");
176
atomName(Atom atom)177 static const char* atomName(Atom atom) {
178 static const size_t count = 1024;
179 static char* atoms[count];
180 if (atom <= 0) return "invalid_atom_zero";
181 if (atom < count && atoms[atom] != 0)
182 return atoms[atom];
183 char* name = XGetAtomName(display, atom);
184 if (atom < count) atoms[atom] = name;
185 return name;
186 }
187
changeWorkspace(Window w,long workspace)188 void changeWorkspace(Window w, long workspace) {
189 XClientMessageEvent xev = {};
190
191 xev.type = ClientMessage;
192 xev.window = w;
193 xev.message_type = _XA_NET_WM_DESKTOP;
194 xev.format = 32;
195 xev.data.l[0] = workspace;
196 xev.data.l[1] = CurrentTime; //xev.data.l[1] = timeStamp;
197 XSendEvent(display, root, False, SubstructureNotifyMask, (XEvent *) &xev);
198 tell("changeWorkspace %ld\n", workspace);
199 }
200
moveResize(Window w,int x,int y,int what)201 void moveResize(Window w, int x, int y, int what) {
202 XClientMessageEvent xev = {};
203
204 xev.type = ClientMessage;
205 xev.window = w;
206 xev.message_type = _XA_NET_WM_MOVERESIZE;
207 xev.format = 32;
208 xev.data.l[0] = x;
209 xev.data.l[1] = y; //xev.data.l[1] = timeStamp;
210 xev.data.l[2] = what;
211 XSendEvent(display, root, False, SubstructureNotifyMask, (XEvent *) &xev);
212 }
213
toggleState(Window w,TAtom & toggle_state)214 void toggleState(Window w, TAtom& toggle_state) {
215 XClientMessageEvent xev = {};
216
217 tell("toggle state %s\n", toggle_state.name);
218
219 xev.type = ClientMessage;
220 xev.window = w;
221 xev.message_type = _XA_NET_WM_STATE;
222 xev.format = 32;
223 xev.data.l[0] = _NET_WM_STATE_TOGGLE;
224 xev.data.l[1] = (long)toggle_state;
225
226 ///xev.data.l[4] = CurrentTime; //xev.data.l[1] = timeStamp;
227
228 XSendEvent(display, root, False, SubstructureNotifyMask, (XEvent *) &xev);
229 }
230
toggleUrgency(Display * d,Window w)231 void toggleUrgency(Display* d, Window w) {
232 XWMHints* h = XGetWMHints(d, w);
233 if (!h)
234 h = XAllocWMHints();
235 h->flags = XUrgencyHint;
236 XSetWMHints(d, w, h);
237 XFree(h);
238 }
239
setLayer(Window w,long layer)240 void setLayer(Window w, long layer) {
241 XClientMessageEvent xev = {};
242
243 xev.type = ClientMessage;
244 xev.window = w;
245 xev.message_type = _XA_WIN_LAYER;
246 xev.format = 32;
247 xev.data.l[0] = layer;
248 xev.data.l[1] = CurrentTime; //xev.data.l[1] = timeStamp;
249 XSendEvent(display, root, False, SubstructureNotifyMask, (XEvent *) &xev);
250 }
251
setProperty(Window w,Atom prop,Atom type,Atom * data,size_t count)252 void setProperty(Window w, Atom prop, Atom type, Atom* data, size_t count) {
253 XChangeProperty(display, w, prop, type, 32, PropModeReplace,
254 (unsigned char *) data, int(count));
255 }
256
setLayout(Window w)257 void setLayout(Window w) {
258 tell("setLayout %ld, %ld, %ld, %ld\n",
259 layout.orient, layout.columns, layout.rows, layout.corner);
260 setProperty(w, _XA_NET_DESKTOP_LAYOUT, XA_CARDINAL, (Atom*)&layout, 4);
261 }
262
263 #if 0
264 void setTrayHint(Window w, long tray_opt) {
265 XClientMessageEvent xev = {};
266
267 xev.type = ClientMessage;
268 xev.window = w;
269 xev.message_type = _XA_WIN_TRAY;
270 xev.format = 32;
271 xev.data.l[0] = tray_opt;
272 xev.data.l[1] = CurrentTime; //xev.data.l[1] = timeStamp;
273 XSendEvent(display, root, False, SubstructureNotifyMask, (XEvent *) &xev);
274 }
275 #endif
276
requestExtents(Window w)277 void requestExtents(Window w) {
278 XClientMessageEvent xev = {};
279
280 xev.type = ClientMessage;
281 xev.window = w;
282 xev.message_type = _XA_NET_REQUEST_FRAME_EXTENTS;
283 xev.format = 32;
284 tell("%-8s 0x%lX, 0x%lx\n", "extents", root, w);
285 XSendEvent(display, root, False, SubstructureNotifyMask, (XEvent *) &xev);
286 }
287
getProperty(Window w,Atom p,Atom t,long * c,long limit=1L)288 static bool getProperty(Window w, Atom p, Atom t, long* c, long limit = 1L) {
289 Atom r_type;
290 int r_format;
291 unsigned long count;
292 unsigned long bytes_remain;
293 unsigned char* prop = nullptr;
294
295 if (XGetWindowProperty(display, w, p,
296 0L, limit, False, t,
297 &r_type, &r_format,
298 &count, &bytes_remain,
299 &prop) == Success && prop)
300 {
301 TEST(r_type == t && r_format == 32 && count == 1);
302 *c = ((long *)prop)[0];
303 XFree(prop);
304 return true;
305 } else {
306 return false;
307 }
308 }
309
updateWorkspaceCount()310 static void updateWorkspaceCount() {
311 getProperty(root, _XA_NET_NUMBER_OF_DESKTOPS, XA_CARDINAL, &workspaceCount);
312 }
313
updateActiveWorkspace()314 static void updateActiveWorkspace() {
315 getProperty(root, _XA_NET_CURRENT_DESKTOP, XA_CARDINAL, &activeWorkspace);
316 }
317
updateWindowWorkspace()318 static void updateWindowWorkspace() {
319 getProperty(window, _XA_NET_WM_DESKTOP, XA_CARDINAL, &windowWorkspace);
320 }
321
xfail(Display * display,XErrorEvent * xev)322 static int xfail(Display* display, XErrorEvent* xev) {
323
324 char message[80], req[80], number[80];
325
326 snprintf(number, sizeof number, "%d", xev->request_code);
327 XGetErrorDatabaseText(display, "XRequest", number, "",
328 req, sizeof(req));
329 if (!req[0])
330 snprintf(req, sizeof req, "[request_code=%d]", xev->request_code);
331
332 if (XGetErrorText(display,
333 xev->error_code,
334 message, sizeof(message)) !=
335 Success)
336 *message = '\0';
337
338 tell("X error %s(0x%lX): %s\n", req, xev->resourceid, message);
339 return 0;
340 }
341
sigcatch(int signo)342 static void sigcatch(int signo) {
343 tell("Received signal %d: \"%s\".\n", signo, strsignal(signo));
344 if (signo == SIGALRM) {
345 Display* d = XOpenDisplay(nullptr);
346 toggleUrgency(d, window);
347 XCloseDisplay(d);
348 }
349 }
350
help(char * name)351 static void help(char* name) {
352 printf("Usage: %s [options]\n"
353 "\t-d display\n"
354 "\t-e : handle errors\n"
355 "\t-p : enable pinging\n"
356 "\t-s : synchronize\n"
357 , name);
358 exit(1);
359 }
360
getColor(const char * name)361 static Pixel getColor(const char* name) {
362 int screen = XDefaultScreen(display);
363 colormap = XDefaultColormap(display, screen);
364 XColor color = {}, exact;
365 Pixel pixel;
366 if (XLookupColor(display, colormap, name, &exact, &color) &&
367 XAllocColor(display, colormap, &color))
368 pixel = color.pixel;
369 else if (XParseColor(display, colormap, name, &color) &&
370 XAllocColor(display, colormap, &color))
371 pixel = color.pixel;
372 else
373 pixel = XWhitePixel(display, screen);
374 tell("color %s = 0x%lX\n", name, pixel);
375 return pixel;
376 }
377
test_run(char * progname,bool pinging)378 static void test_run(char* progname, bool pinging) {
379 int screen = XDefaultScreen(display);
380 root = XRootWindow(display, screen);
381 colormap = XDefaultColormap(display, screen);
382 // Pixel black = XBlackPixel(display, screen);
383 Pixel white = XWhitePixel(display, screen);
384 Pixel blue = getColor("blue");
385 Pixel green = getColor("green");
386 Pixel yellow = getColor("yellow");
387
388 window = XCreateWindow(display, root,
389 0,
390 0,
391 64, 64,
392 0,
393 CopyFromParent, InputOutput, CopyFromParent,
394 None, None);
395
396 XSetWindowBackground(display, window, blue);
397
398 Atom protocols[] = {
399 _XA_WM_DELETE_WINDOW, _XA_WM_TAKE_FOCUS,
400 _XA_NET_WM_PING,
401 };
402 XSetWMProtocols(display, window, protocols, COUNT(protocols) );
403
404 XClassHint classHint = { (char *)"window", (char *)"testnet" };
405 XSetClassHint(display, window, &classHint);
406 XStoreName(display, window, basename(progname));
407
408 char hostname[HOST_NAME_MAX + 1] = {};
409 gethostname(hostname, HOST_NAME_MAX);
410 XTextProperty hname = {
411 (unsigned char *) hostname,
412 XA_STRING,
413 8,
414 strnlen(hostname, HOST_NAME_MAX),
415 };
416 XSetWMClientMachine(display, window, &hname);
417
418 XID pid = getpid();
419 setProperty(window, _XA_NET_WM_PID, XA_CARDINAL, &pid, 1);
420
421 long imask = ExposureMask | StructureNotifyMask |
422 ButtonPressMask | ButtonReleaseMask |
423 KeyPressMask | KeyReleaseMask |
424 PropertyChangeMask | SubstructureNotifyMask;
425 XSelectInput(display, window, imask);
426
427 XSelectInput(display, root, PropertyChangeMask);
428
429 Window leader = XCreateWindow(display, root, 0, 0, 10, 10, 0, CopyFromParent,
430 InputOnly, CopyFromParent, None, None);
431 setProperty(window, _XA_WM_CLIENT_LEADER, XA_WINDOW, &leader, 1);
432
433 XMapRaised(display, window);
434
435 unmapped = XCreateSimpleWindow(display, root, 0, 0, 100, 100, 20,
436 white, yellow);
437 XClassHint unclassHint = { (char *)"unmapped", (char *)"testnet" };
438 XSetClassHint(display, unmapped, &unclassHint);
439 XStoreName(display, unmapped, "unmapped");
440 setProperty(unmapped, _XA_NET_WM_PID, XA_CARDINAL, &pid, 1);
441
442 setProperty(unmapped, _XA_WM_CLIENT_LEADER, XA_WINDOW, &leader, 1);
443
444 unviewed = XCreateSimpleWindow(display, root, 0, 0, 100, 100, 20,
445 white, green);
446 XClassHint vwclassHint = { (char *)"unviewed", (char *)"testnet" };
447 XSetClassHint(display, unviewed, &vwclassHint);
448 XStoreName(display, unviewed, "unviewed");
449 setProperty(unviewed, _XA_NET_WM_PID, XA_CARDINAL, &pid, 1);
450
451 setProperty(unviewed, _XA_WM_CLIENT_LEADER, XA_WINDOW, &leader, 1);
452
453 Atom wmstate[] = {
454 // _XA_NET_WM_STATE_ABOVE,
455 // _XA_NET_WM_STATE_BELOW,
456 _XA_NET_WM_STATE_DEMANDS_ATTENTION,
457 // _XA_NET_WM_STATE_FOCUSED,
458 // _XA_NET_WM_STATE_FULLSCREEN,
459 // _XA_NET_WM_STATE_HIDDEN,
460 // _XA_NET_WM_STATE_MAXIMIZED_HORZ,
461 // _XA_NET_WM_STATE_MAXIMIZED_VERT,
462 _XA_NET_WM_STATE_MODAL,
463 // _XA_NET_WM_STATE_SHADED,
464 // _XA_NET_WM_STATE_SKIP_PAGER,
465 // _XA_NET_WM_STATE_SKIP_TASKBAR,
466 _XA_NET_WM_STATE_STICKY,
467 };
468 setProperty(unmapped, _XA_NET_WM_STATE, XA_ATOM, wmstate, COUNT(wmstate));
469 setProperty(unviewed, _XA_NET_WM_STATE, XA_ATOM, wmstate, COUNT(wmstate));
470
471 Atom wintype[] = {
472 // _XA_NET_WM_WINDOW_TYPE_COMBO,
473 // _XA_NET_WM_WINDOW_TYPE_DESKTOP,
474 _XA_NET_WM_WINDOW_TYPE_DIALOG,
475 // _XA_NET_WM_WINDOW_TYPE_DND,
476 // _XA_NET_WM_WINDOW_TYPE_DOCK,
477 // _XA_NET_WM_WINDOW_TYPE_DROPDOWN_MENU,
478 // _XA_NET_WM_WINDOW_TYPE_MENU,
479 // _XA_NET_WM_WINDOW_TYPE_NORMAL,
480 // _XA_NET_WM_WINDOW_TYPE_NOTIFICATION,
481 // _XA_NET_WM_WINDOW_TYPE_POPUP_MENU,
482 // _XA_NET_WM_WINDOW_TYPE_SPLASH,
483 // _XA_NET_WM_WINDOW_TYPE_TOOLBAR,
484 // _XA_NET_WM_WINDOW_TYPE_TOOLTIP,
485 // _XA_NET_WM_WINDOW_TYPE_UTILITY,
486 };
487 setProperty(unmapped, _XA_NET_WM_WINDOW_TYPE, XA_ATOM,
488 wintype, COUNT(wintype));
489 setProperty(unviewed, _XA_NET_WM_WINDOW_TYPE, XA_ATOM,
490 wintype, COUNT(wintype));
491
492 Atom actions[] = {
493 _XA_NET_WM_ACTION_ABOVE,
494 _XA_NET_WM_ACTION_BELOW,
495 _XA_NET_WM_ACTION_CHANGE_DESKTOP,
496 _XA_NET_WM_ACTION_CLOSE,
497 _XA_NET_WM_ACTION_FULLSCREEN,
498 _XA_NET_WM_ACTION_HIDE,
499 _XA_NET_WM_ACTION_MAXIMIZE_HORZ,
500 _XA_NET_WM_ACTION_MAXIMIZE_VERT,
501 _XA_NET_WM_ACTION_MINIMIZE,
502 _XA_NET_WM_ACTION_MOVE,
503 _XA_NET_WM_ACTION_RESIZE,
504 _XA_NET_WM_ACTION_SHADE,
505 _XA_NET_WM_ACTION_STICK,
506 };
507 setProperty(unmapped, _XA_NET_WM_ALLOWED_ACTIONS, XA_ATOM,
508 actions, COUNT(actions));
509 setProperty(unviewed, _XA_NET_WM_ALLOWED_ACTIONS, XA_ATOM,
510 actions, COUNT(actions));
511
512 XSelectInput(display, unmapped, imask);
513 XSelectInput(display, unviewed, imask);
514
515 updateWorkspaceCount();
516 updateActiveWorkspace();
517
518 XEvent xev = {};
519 /// XButtonEvent &button = xev.xbutton;
520 XPropertyEvent& property = xev.xproperty;
521 XKeyEvent& key = xev.xkey;
522 XClientMessageEvent& client = xev.xclient;
523
524 while (XNextEvent(display, &xev) == Success) switch (xev.type) {
525
526 case ClientMessage: {
527 if (client.message_type == _XA_WM_PROTOCOLS && client.window == window) {
528 TEST(client.format == 32);
529 if (client.data.l[0] == _XA_WM_DELETE_WINDOW) {
530 tell("WM_DELETE_WINDOW\n");
531 break;
532 }
533 else
534 if (client.data.l[0] == _XA_WM_TAKE_FOCUS) {
535 tell("WM_TAKE_FOCUS\n");
536 XSetInputFocus(display, window, None, CurrentTime);
537 break;
538 }
539 else
540 if (client.data.l[0] == _XA_NET_WM_PING) {
541 long* l = client.data.l;
542 tell("_NET_WM_PING %ld, 0x%lX, 0x%lX, 0x%lX\n",
543 l[1], l[2], l[3], l[4]);
544 if (pinging) {
545 client.window = root;
546 XSendEvent(display, root, False,
547 SubstructureRedirectMask|SubstructureNotifyMask,
548 &xev);
549 XFlush(display);
550 tell("\tSent pong.\n");
551 }
552 else {
553 tell("\tNo pong sent.\n");
554 }
555 break;
556 }
557 }
558 tell("client message %lu, %d, %lu, %ld\n",
559 client.message_type, client.format,
560 client.window, client.data.l[0]);
561 } break;
562
563 case KeyPress: {
564 unsigned k = XkbKeycodeToKeysym(display, key.keycode, 0,
565 (key.state & ShiftMask) != 0);
566 unsigned m = KEY_MODMASK(key.state);
567 if (m == ControlMask && k < '~' && isalpha(k))
568 k = CTRL(k);
569
570 if (k == 'q' || k == XK_Escape)
571 return;
572 else if (k == '?' || k == 'h') {
573 printf("0 : layer desktop\n"
574 "1 : layer below\n"
575 "2 : layer normal\n"
576 "3 : layer on top\n"
577 "4 : layer dock\n"
578 "5 : layer above dock\n"
579 "? : help\n"
580 "A : urgent alarm 5 seconds\n"
581 "M : toggle state modal\n"
582 "a : toggle state above\n"
583 "b : toggle state below\n"
584 "d : toggle state urgent\n"
585 "f : toggle fullscreen\n"
586 "s : toggle sticky\n"
587 "t : toggle skip taskbar\n"
588 "m : move resize 8\n"
589 "r : move resize 4\n"
590 "u : map the unmapped\n"
591 "v : map the unviewed\n"
592 "x : extents unmapped\n"
593 "X : extents window\n"
594 "^X : extents root\n"
595 "Left : goto previous workspace\n"
596 "Right : goto next workspace\n"
597 "Shift+Left : move to previous workspace\n"
598 "Shift+Right : move to next workspace\n"
599 "^B : layout bottom left\n"
600 "^C : control layout dimensions\n"
601 "^H : horizontal layout orientation\n"
602 "^L : layout top left\n"
603 "^R : layout bottom right\n"
604 "^T : layout top right\n"
605 "^V : vertical layout orientation\n"
606 "\n");
607 }
608 else if (k == XK_Left && m == 0)
609 changeWorkspace(root,
610 (workspaceCount +
611 activeWorkspace - 1) % workspaceCount);
612 else if (k == XK_Right && m == 0)
613 changeWorkspace(root,
614 (activeWorkspace + 1) % workspaceCount);
615 else if (k == XK_Left && m == ShiftMask)
616 changeWorkspace(window,
617 (workspaceCount +
618 windowWorkspace - 1) % workspaceCount);
619 else if (k == XK_Right && m == ShiftMask)
620 changeWorkspace(window,
621 (windowWorkspace + 1) % workspaceCount);
622 else if (k == 'f')
623 toggleState(window, _XA_NET_WM_STATE_FULLSCREEN);
624 else if (k == 'a')
625 toggleState(window, _XA_NET_WM_STATE_ABOVE);
626 else if (k == 'b')
627 toggleState(window, _XA_NET_WM_STATE_BELOW);
628 else if (k == 'd')
629 toggleState(window, _XA_NET_WM_STATE_DEMANDS_ATTENTION);
630 else if (k == 'M')
631 toggleState(window, _XA_NET_WM_STATE_MODAL);
632 else if (k == 's')
633 toggleState(window, _XA_NET_WM_STATE_STICKY);
634 else if (k == 't')
635 toggleState(window, _XA_NET_WM_STATE_SKIP_TASKBAR);
636 else if (k == 'A') {
637 int sec = 5; tell("alarm %d seconds\n", sec); alarm(sec);
638 }
639 else if (k == '0')
640 setLayer(window, WinLayerDesktop);
641 else if (k == '1')
642 setLayer(window, WinLayerBelow);
643 else if (k == '2')
644 setLayer(window, WinLayerNormal);
645 else if (k == '3')
646 setLayer(window, WinLayerOnTop);
647 else if (k == '4')
648 setLayer(window, WinLayerDock);
649 else if (k == '5')
650 setLayer(window, WinLayerAboveDock);
651 else if (k == 'm') {
652 tell("%d %d\n", key.x_root, key.y_root);
653 moveResize(window, key.x_root, key.y_root, 8); // move
654 } else if (k == 'r') {
655 tell("%d %d\n", key.x_root, key.y_root);
656 moveResize(window, key.x_root, key.y_root, 4); // _|
657 }
658 else if (k == 'u') {
659 XMapRaised(display, unmapped);
660 }
661 else if (k == 'v') {
662 XMapRaised(display, unviewed);
663 }
664 else if (k == 'x' || k == 'X' || k == CTRL('X')) {
665 Window w = m == ShiftMask ? window :
666 m == ControlMask ? root : unmapped;
667 const char *s = m == ShiftMask ? "window" :
668 m == ControlMask ? "root" : "unmapped";
669 tell("x = '%c' for %s\n", k, s);
670 requestExtents(w);
671 }
672 else if (k == CTRL('V')) {
673 layout.orient = _NET_WM_ORIENTATION_VERT;
674 setLayout(root);
675 }
676 else if (k == CTRL('H')) {
677 layout.orient = _NET_WM_ORIENTATION_HORZ;
678 setLayout(root);
679 }
680 else if (k == CTRL('L')) {
681 layout.corner = _NET_WM_TOPLEFT;
682 setLayout(root);
683 }
684 else if (k == CTRL('T')) {
685 layout.corner = _NET_WM_TOPRIGHT;
686 setLayout(root);
687 }
688 else if (k == CTRL('B')) {
689 layout.corner = _NET_WM_BOTTOMLEFT;
690 setLayout(root);
691 }
692 else if (k == CTRL('R')) {
693 layout.corner = _NET_WM_BOTTOMRIGHT;
694 setLayout(root);
695 }
696 else if (k == CTRL('C')) {
697 char buf[100];
698 printf("Desktop Layout Columns (%ld): ", layout.columns);
699 fflush(stdout);
700 if (fgets(buf, sizeof buf, stdin))
701 sscanf(buf, " %ld", &layout.columns);
702 printf("Desktop Layout Rows (%ld): ", layout.rows);
703 fflush(stdout);
704 if (fgets(buf, sizeof buf, stdin))
705 sscanf(buf, " %ld", &layout.rows);
706 setLayout(root);
707 }
708 else if (k != XK_Shift_L && k != XK_Shift_R &&
709 k != XK_Control_L && k != XK_Control_R)
710 {
711 tell("Unrecognized key %d\n", k);
712 }
713 } break;
714
715 case PropertyNotify: {
716 if (property.state == PropertyDelete)
717 break;
718
719 Atom r_type;
720 int r_format;
721 unsigned long count;
722 unsigned long bytes_remain;
723 unsigned char *prop(0);
724
725 if (property.window == root &&
726 property.atom == _XA_NET_CURRENT_DESKTOP) {
727 updateWorkspaceCount();
728 updateActiveWorkspace();
729 tell("active=%ld of %ld\n", activeWorkspace, workspaceCount);
730 }
731 else if (property.window == root &&
732 property.atom == _XA_NET_DESKTOP_NAMES) {
733 }
734 else if (property.window == window &&
735 property.atom == _XA_NET_WM_DESKTOP) {
736 updateWindowWorkspace();
737 }
738 else if (property.window == root &&
739 property.atom == _XA_NET_ACTIVE_WINDOW) {
740 long win = None;
741 getProperty(root, _XA_NET_ACTIVE_WINDOW, XA_WINDOW, &win);
742 tell("active window = 0x%lX\n", win);
743 }
744 else if (property.atom == _XA_NET_WM_STATE) {
745 if (XGetWindowProperty(display, property.window,
746 _XA_NET_WM_STATE,
747 0, 20, False, AnyPropertyType,
748 &r_type, &r_format,
749 &count, &bytes_remain,
750 &prop) == Success && prop)
751 {
752 TEST(r_type == XA_ATOM && r_format == 32);
753 tell("net wm state %s: ",
754 property.window == window ? "window" :
755 property.window == unmapped ? "unmapped" :
756 property.window == unviewed ? "unviewed" :
757 "unknown");
758 for (unsigned long i = 0; i < count; ++i) {
759 printf("%s%s", i ? ", " : "",
760 atomName(((long *)prop)[i]));
761 }
762 printf("%s\n", count == 0 ? "_" : "");
763 XFree(prop);
764 }
765 }
766 else if (property.atom == _XA_WM_STATE) {
767 if (XGetWindowProperty(display, property.window,
768 _XA_WM_STATE,
769 0, 2, False, AnyPropertyType,
770 &r_type, &r_format,
771 &count, &bytes_remain,
772 &prop) == Success && prop)
773 {
774 TEST(r_type == _XA_WM_STATE && r_format == 32 && count == 2);
775 tell("wm state %s:",
776 property.window == window ? "window" :
777 property.window == unmapped ? "unmapped" :
778 property.window == unviewed ? "unviewed" :
779 "unknown");
780 for (unsigned i = 0; i < count; ++i) {
781 printf("%s%ld", i ? ", " : " ", ((long *)prop)[i]);
782 }
783 printf("%s\n", count == 0 ? "_" : "");
784 XFree(prop);
785 }
786 }
787 else if (property.atom == _XA_WIN_LAYER) {
788 if (XGetWindowProperty(display, property.window,
789 _XA_WIN_LAYER,
790 0, 1, False, AnyPropertyType,
791 &r_type, &r_format,
792 &count, &bytes_remain,
793 &prop) == Success && prop)
794 {
795 TEST(r_type == XA_CARDINAL && r_format == 32 && count == 1);
796 long layer = ((long *)prop)[0];
797 tell("win layer %s: %ld\n",
798 property.window == window ? "window" :
799 property.window == unmapped ? "unmapped" :
800 property.window == unviewed ? "unviewed" :
801 "unknown", layer);
802 XFree(prop);
803 }
804 }
805 else if (property.atom == _XA_ICEWM_GUI_EVENT) {
806 if (XGetWindowProperty(display, property.window,
807 _XA_ICEWM_GUI_EVENT,
808 0, 1, False, AnyPropertyType,
809 &r_type, &r_format,
810 &count, &bytes_remain,
811 &prop) == Success && prop)
812 {
813 TEST(r_type == _XA_ICEWM_GUI_EVENT && r_format == 8 && count == 1);
814 if (*prop < COUNT(gui_event_names))
815 tell("IceWM GUI event %s\n", gui_event_names[*prop]);
816 else
817 tell("IceWM GUI event %d\n", *prop);
818 }
819 }
820 else if (property.atom == _XA_WIN_TRAY) {
821 if (XGetWindowProperty(display, property.window,
822 _XA_WIN_TRAY,
823 0, 1, False, AnyPropertyType,
824 &r_type, &r_format,
825 &count, &bytes_remain,
826 &prop) == Success && prop)
827 {
828 TEST(r_type == XA_CARDINAL && r_format == 32 && count == 1);
829 long tray = ((long *)prop)[0];
830 tell("tray option=%d\n", tray);
831 }
832 }
833 else {
834 Atom atom = property.atom;
835 Window w = property.window;
836 const char *s = w == window ? "window" :
837 w == unmapped ? "unmapped" :
838 w == unviewed ? "unviewed" :
839 w == root ? "root" : "other!";
840 Atom type(None);
841 int format(0);
842 unsigned long nitems(0);
843 unsigned long after(0);
844 unsigned char* prop(0);
845 if (XGetWindowProperty(display, w, atom,
846 0, 4, False, AnyPropertyType,
847 &type, &format, &nitems,
848 &after, &prop) == Success && prop) {
849 tell("%-8s %-26s %2d %s\n", s, atomName(atom), format,
850 atomName(type));
851 if (atom == _XA_NET_FRAME_EXTENTS) {
852 TEST(type == XA_CARDINAL && format == 32 && nitems == 4);
853 long* data = (long *) prop;
854 int n = int(nitems);
855 for (int i = 0; i < n; ++i) {
856 printf("%s%ld", i ? ", " : "\t\t", data[i]);
857 }
858 if (n)
859 printf("\n");
860 }
861 else if (atom == XA_WINDOW) {
862 TEST(type == XA_WINDOW && format == 32 && nitems == 1);
863 long* data = (long *) prop;
864 int n = int(nitems);
865 for (int i = 0; i < n; ++i) {
866 printf("%s%ld", i ? ", " : "\t\t", data[i]);
867 }
868 if (n)
869 printf("\n");
870 }
871 else if (atom == XA_CARDINAL) {
872 TEST(type == XA_CARDINAL && format == 32 && nitems == 1);
873 long* data = (long *) prop;
874 int n = int(nitems);
875 for (int i = 0; i < n; ++i) {
876 printf("%s%ld", i ? ", " : "\t\t", data[i]);
877 }
878 if (n)
879 printf("\n");
880 }
881 XFree(prop);
882 }
883 else {
884 tell("%-8s %s %s\n", s, atomName(atom),
885 "Cannot get window property!");
886 }
887 }
888 } break;
889
890 default:
891 tell("%s\n", eventName(xev.type));
892 break;
893 }
894 }
895
main(int argc,char ** argv)896 int main(int argc, char **argv) {
897
898 signal(SIGTERM, sigcatch);
899 signal(SIGALRM, sigcatch);
900
901 bool pinging = false;
902
903 for (int opt; (opt = getopt(argc, argv, "d:epsh?")) > 0; ) {
904 switch (opt) {
905 case 'd':
906 setenv("DISPLAY", optarg, True);
907 break;
908
909 case 'e':
910 XSetErrorHandler(xfail);
911 break;
912
913 case 'p':
914 pinging ^= true;
915 break;
916
917 case 's':
918 XSynchronize(display, True);
919 break;
920
921 default:
922 help(*argv);
923 }
924 }
925 if (optind < argc) {
926 printf("%s: bad arg '%s'.\n", *argv, argv[optind]);
927 help(*argv);
928 } else {
929 test_run(*argv, pinging);
930 }
931 return 0;
932 }
933
934 // vim: set sw=4 ts=4 et:
935