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