1 /**
2  * @file event.c
3  * @author Joe Wingbermuehle
4  * @date 2004-2006
5  *
6  * @brief Functions to handle X11 events.
7  *
8  */
9 
10 #include "jwm.h"
11 #include "event.h"
12 
13 #include "client.h"
14 #include "clientlist.h"
15 #include "confirm.h"
16 #include "cursor.h"
17 #include "desktop.h"
18 #include "dock.h"
19 #include "icon.h"
20 #include "key.h"
21 #include "move.h"
22 #include "place.h"
23 #include "resize.h"
24 #include "root.h"
25 #include "swallow.h"
26 #include "taskbar.h"
27 #include "timing.h"
28 #include "winmenu.h"
29 #include "settings.h"
30 #include "tray.h"
31 #include "popup.h"
32 #include "pager.h"
33 #include "grab.h"
34 
35 #define MIN_TIME_DELTA 50
36 
37 Time eventTime = CurrentTime;
38 
39 typedef struct CallbackNode {
40    TimeType last;
41    int freq;
42    SignalCallback callback;
43    void *data;
44    struct CallbackNode *next;
45 } CallbackNode;
46 
47 static CallbackNode *callbacks = NULL;
48 
49 static char restack_pending = 0;
50 static char task_update_pending = 0;
51 static char pager_update_pending = 0;
52 
53 static void Signal(void);
54 static void DispatchBorderButtonEvent(const XButtonEvent *event,
55                                       ClientNode *np);
56 
57 static void HandleConfigureRequest(const XConfigureRequestEvent *event);
58 static char HandleConfigureNotify(const XConfigureEvent *event);
59 static char HandleExpose(const XExposeEvent *event);
60 static char HandlePropertyNotify(const XPropertyEvent *event);
61 static void HandleClientMessage(const XClientMessageEvent *event);
62 static void HandleColormapChange(const XColormapEvent *event);
63 static char HandleDestroyNotify(const XDestroyWindowEvent *event);
64 static void HandleMapRequest(const XMapEvent *event);
65 static void HandleUnmapNotify(const XUnmapEvent *event);
66 static void HandleButtonEvent(const XButtonEvent *event);
67 static void ToggleMaximized(ClientNode *np, MaxFlags flags);
68 static void HandleKeyPress(const XKeyEvent *event);
69 static void HandleKeyRelease(const XKeyEvent *event);
70 static void HandleEnterNotify(const XCrossingEvent *event);
71 static void HandleMotionNotify(const XMotionEvent *event);
72 static char HandleSelectionClear(const XSelectionClearEvent *event);
73 
74 static void HandleNetMoveResize(const XClientMessageEvent *event,
75                                 ClientNode *np);
76 static void HandleNetWMMoveResize(const XClientMessageEvent *evnet,
77                                   ClientNode *np);
78 static void HandleNetRestack(const XClientMessageEvent *event,
79                              ClientNode *np);
80 static void HandleNetWMState(const XClientMessageEvent *event,
81                              ClientNode *np);
82 static void HandleFrameExtentsRequest(const XClientMessageEvent *event);
83 static void UpdateState(ClientNode *np);
84 static void DiscardEnterEvents();
85 
86 #ifdef USE_SHAPE
87 static void HandleShapeEvent(const XShapeEvent *event);
88 #endif
89 
90 /** Wait for an event and process it. */
WaitForEvent(XEvent * event)91 char WaitForEvent(XEvent *event)
92 {
93    struct timeval timeout;
94    CallbackNode *cp;
95    fd_set fds;
96    long sleepTime;
97    int fd;
98    char handled;
99 
100 #ifdef ConnectionNumber
101    fd = ConnectionNumber(display);
102 #else
103    fd = JXConnectionNumber(display);
104 #endif
105 
106    /* Compute how long we should sleep. */
107    sleepTime = 10 * 1000;  /* 10 seconds. */
108    for(cp = callbacks; cp; cp = cp->next) {
109       if(cp->freq > 0 && cp->freq < sleepTime) {
110          sleepTime = cp->freq;
111       }
112    }
113 
114    do {
115 
116       while(JXPending(display) == 0) {
117          FD_ZERO(&fds);
118          FD_SET(fd, &fds);
119          timeout.tv_sec = sleepTime / 1000;
120          timeout.tv_usec = (sleepTime % 1000) * 1000;
121          if(select(fd + 1, &fds, NULL, NULL, &timeout) <= 0) {
122             Signal();
123          }
124          if(JUNLIKELY(shouldExit)) {
125             return 0;
126          }
127       }
128 
129       Signal();
130 
131       JXNextEvent(display, event);
132       UpdateTime(event);
133 
134       switch(event->type) {
135       case ConfigureRequest:
136          HandleConfigureRequest(&event->xconfigurerequest);
137          handled = 1;
138          break;
139       case MapRequest:
140          HandleMapRequest(&event->xmap);
141          handled = 1;
142          break;
143       case PropertyNotify:
144          handled = HandlePropertyNotify(&event->xproperty);
145          break;
146       case ClientMessage:
147          HandleClientMessage(&event->xclient);
148          handled = 1;
149          break;
150       case UnmapNotify:
151          HandleUnmapNotify(&event->xunmap);
152          handled = 1;
153          break;
154       case Expose:
155          handled = HandleExpose(&event->xexpose);
156          break;
157       case ColormapNotify:
158          HandleColormapChange(&event->xcolormap);
159          handled = 1;
160          break;
161       case DestroyNotify:
162          handled = HandleDestroyNotify(&event->xdestroywindow);
163          break;
164       case SelectionClear:
165          handled = HandleSelectionClear(&event->xselectionclear);
166          break;
167       case ResizeRequest:
168          handled = HandleDockResizeRequest(&event->xresizerequest);
169          break;
170       case MotionNotify:
171          SetMousePosition(event->xmotion.x_root, event->xmotion.y_root,
172                           event->xmotion.window);
173          handled = 0;
174          break;
175       case ButtonPress:
176       case ButtonRelease:
177          SetMousePosition(event->xbutton.x_root, event->xbutton.y_root,
178                           event->xbutton.window);
179          handled = 0;
180          break;
181       case EnterNotify:
182          SetMousePosition(event->xcrossing.x_root, event->xcrossing.y_root,
183                           event->xcrossing.window);
184          handled = 0;
185          break;
186       case LeaveNotify:
187          SetMousePosition(event->xcrossing.x_root, event->xcrossing.y_root,
188                           None);
189          handled = 0;
190          break;
191       case ReparentNotify:
192          HandleDockReparentNotify(&event->xreparent);
193          handled = 1;
194          break;
195       case ConfigureNotify:
196          handled = HandleConfigureNotify(&event->xconfigure);
197          break;
198       case CreateNotify:
199       case MapNotify:
200       case GraphicsExpose:
201       case NoExpose:
202          handled = 1;
203          break;
204       default:
205          if(0) {
206 #ifdef USE_SHAPE
207          } else if(haveShape && event->type == shapeEvent) {
208             HandleShapeEvent((XShapeEvent*)event);
209             handled = 1;
210 #endif
211          } else {
212             handled = 0;
213          }
214          break;
215       }
216 
217       if(!handled) {
218          handled = ProcessTrayEvent(event);
219       }
220       if(!handled) {
221          handled = ProcessDialogEvent(event);
222       }
223       if(!handled) {
224          handled = ProcessSwallowEvent(event);
225       }
226       if(!handled) {
227          handled = ProcessPopupEvent(event);
228       }
229 
230    } while(handled && JLIKELY(!shouldExit));
231 
232    return !handled;
233 
234 }
235 
236 /** Wake up components that need to run at certain times. */
Signal(void)237 void Signal(void)
238 {
239    static TimeType last = ZERO_TIME;
240 
241    CallbackNode *cp;
242    TimeType now;
243    Window w;
244    int x, y;
245 
246    if(restack_pending) {
247       RestackClients();
248       restack_pending = 0;
249    }
250    if(task_update_pending) {
251       UpdateTaskBar();
252       task_update_pending = 0;
253    }
254    if(pager_update_pending) {
255       UpdatePager();
256       pager_update_pending = 0;
257    }
258 
259    GetCurrentTime(&now);
260    if(GetTimeDifference(&now, &last) < MIN_TIME_DELTA) {
261       return;
262    }
263    last = now;
264 
265    GetMousePosition(&x, &y, &w);
266    for(cp = callbacks; cp; cp = cp->next) {
267       if(cp->freq == 0 || GetTimeDifference(&now, &cp->last) >= cp->freq) {
268          cp->last = now;
269          (cp->callback)(&now, x, y, w, cp->data);
270       }
271    }
272 }
273 
274 /** Process an event. */
ProcessEvent(XEvent * event)275 void ProcessEvent(XEvent *event)
276 {
277    switch(event->type) {
278    case ButtonPress:
279    case ButtonRelease:
280       HandleButtonEvent(&event->xbutton);
281       break;
282    case KeyPress:
283       HandleKeyPress(&event->xkey);
284       break;
285    case KeyRelease:
286       HandleKeyRelease(&event->xkey);
287       break;
288    case EnterNotify:
289       HandleEnterNotify(&event->xcrossing);
290       break;
291    case MotionNotify:
292       while(JXCheckTypedEvent(display, MotionNotify, event));
293       UpdateTime(event);
294       HandleMotionNotify(&event->xmotion);
295       break;
296    case LeaveNotify:
297    case DestroyNotify:
298    case Expose:
299    case ConfigureNotify:
300       break;
301    default:
302       Debug("Unknown event type: %d", event->type);
303       break;
304    }
305 }
306 
307 /** Discard button events for the specified windows. */
DiscardButtonEvents()308 void DiscardButtonEvents()
309 {
310    XEvent event;
311    JXSync(display, False);
312    while(JXCheckMaskEvent(display, ButtonPressMask | ButtonReleaseMask,
313 			  &event)) {
314       UpdateTime(&event);
315    }
316 }
317 
318 /** Discard motion events for the specified window. */
DiscardMotionEvents(XEvent * event,Window w)319 void DiscardMotionEvents(XEvent *event, Window w)
320 {
321    XEvent temp;
322    JXSync(display, False);
323    while(JXCheckTypedEvent(display, MotionNotify, &temp)) {
324       UpdateTime(&temp);
325       SetMousePosition(temp.xmotion.x_root, temp.xmotion.y_root,
326                        temp.xmotion.window);
327       if(temp.xmotion.window == w) {
328          *event = temp;
329       }
330    }
331 }
332 
333 /** Discard key events for the specified window. */
DiscardKeyEvents(XEvent * event,Window w)334 void DiscardKeyEvents(XEvent *event, Window w)
335 {
336    JXSync(display, False);
337    while(JXCheckTypedWindowEvent(display, w, KeyPress, event)) {
338       UpdateTime(event);
339    }
340 }
341 
342 /** Discard enter notify events. */
DiscardEnterEvents()343 void DiscardEnterEvents()
344 {
345    XEvent event;
346    JXSync(display, False);
347    while(JXCheckMaskEvent(display, EnterWindowMask, &event)) {
348       UpdateTime(&event);
349       SetMousePosition(event.xmotion.x_root, event.xmotion.y_root,
350                        event.xmotion.window);
351    }
352 }
353 
354 /** Process a selection clear event. */
HandleSelectionClear(const XSelectionClearEvent * event)355 char HandleSelectionClear(const XSelectionClearEvent *event)
356 {
357    if(event->selection == managerSelection) {
358       /* Lost WM selection. */
359       shouldExit = 1;
360       return 1;
361    }
362    return HandleDockSelectionClear(event);
363 }
364 
365 /** Process a button event. */
HandleButtonEvent(const XButtonEvent * event)366 void HandleButtonEvent(const XButtonEvent *event)
367 {
368 
369    ClientNode *np;
370    int north, south, east, west;
371 
372    np = FindClientByParent(event->window);
373    if(np) {
374       /* Click on the border. */
375       if(event->type == ButtonPress) {
376          FocusClient(np);
377          RaiseClient(np);
378       }
379       DispatchBorderButtonEvent(event, np);
380    } else if(event->window == rootWindow && event->type == ButtonPress) {
381       if(!ShowRootMenu(event->button, event->x, event->y, 0)) {
382          if(event->button == Button4) {
383             LeftDesktop();
384          } else if(event->button == Button5) {
385             RightDesktop();
386          }
387       }
388    } else {
389       const unsigned int mask = event->state & ~lockMask;
390       np = FindClientByWindow(event->window);
391       if(np) {
392          const char move_resize = (np->state.status & STAT_DRAG)
393             || ((mask == settings.moveMask)
394                && !(np->state.status & STAT_NODRAG));
395          switch(event->button) {
396          case Button1:
397          case Button2:
398             FocusClient(np);
399             if(settings.focusModel == FOCUS_SLOPPY
400                || settings.focusModel == FOCUS_CLICK) {
401                RaiseClient(np);
402             }
403             if(move_resize) {
404                GetBorderSize(&np->state, &north, &south, &east, &west);
405                MoveClient(np, event->x + west, event->y + north);
406             }
407             break;
408          case Button3:
409             if(move_resize) {
410                GetBorderSize(&np->state, &north, &south, &east, &west);
411                ResizeClient(np, BA_RESIZE | BA_RESIZE_E | BA_RESIZE_S,
412                             event->x + west, event->y + north);
413             } else {
414                FocusClient(np);
415                if(settings.focusModel == FOCUS_SLOPPY
416                   || settings.focusModel == FOCUS_CLICK) {
417                   RaiseClient(np);
418                }
419             }
420             break;
421          default:
422             break;
423          }
424          JXAllowEvents(display, ReplayPointer, eventTime);
425       }
426 
427    }
428 
429 }
430 
431 /** Toggle maximized state. */
ToggleMaximized(ClientNode * np,MaxFlags flags)432 void ToggleMaximized(ClientNode *np, MaxFlags flags)
433 {
434    if(np) {
435       if(np->state.maxFlags == flags) {
436          MaximizeClient(np, MAX_NONE);
437       } else {
438          MaximizeClient(np, flags);
439       }
440    }
441 }
442 
443 /** Process a key press event. */
HandleKeyPress(const XKeyEvent * event)444 void HandleKeyPress(const XKeyEvent *event)
445 {
446    ClientNode *np;
447    KeyType key;
448 
449    SetMousePosition(event->x_root, event->y_root, event->window);
450    key = GetKey(event);
451    np = GetActiveClient();
452    switch(key & 0xFF) {
453    case KEY_EXEC:
454       RunKeyCommand(event);
455       break;
456    case KEY_DESKTOP:
457       ChangeDesktop((key >> 8) - 1);
458       break;
459    case KEY_RDESKTOP:
460       RightDesktop();
461       break;
462    case KEY_LDESKTOP:
463       LeftDesktop();
464       break;
465    case KEY_UDESKTOP:
466       AboveDesktop();
467       break;
468    case KEY_DDESKTOP:
469       BelowDesktop();
470       break;
471    case KEY_SHOWDESK:
472       ShowDesktop();
473       break;
474    case KEY_SHOWTRAY:
475       ShowAllTrays();
476       break;
477    case KEY_NEXT:
478       StartWindowWalk();
479       FocusNext();
480       break;
481    case KEY_NEXTSTACK:
482       StartWindowStackWalk();
483       WalkWindowStack(1);
484       break;
485    case KEY_PREV:
486       StartWindowWalk();
487       FocusPrevious();
488       break;
489    case KEY_PREVSTACK:
490       StartWindowStackWalk();
491       WalkWindowStack(0);
492       break;
493    case KEY_CLOSE:
494       if(np) {
495          DeleteClient(np);
496       }
497       break;
498    case KEY_SHADE:
499       if(np) {
500          if(np->state.status & STAT_SHADED) {
501             UnshadeClient(np);
502          } else {
503             ShadeClient(np);
504          }
505       }
506       break;
507    case KEY_STICK:
508       if(np) {
509          if(np->state.status & STAT_STICKY) {
510             SetClientSticky(np, 0);
511          } else {
512             SetClientSticky(np, 1);
513          }
514       }
515       break;
516    case KEY_MOVE:
517       if(np) {
518          MoveClientKeyboard(np);
519       }
520       break;
521    case KEY_RESIZE:
522       if(np) {
523          ResizeClientKeyboard(np);
524       }
525       break;
526    case KEY_MIN:
527       if(np) {
528          MinimizeClient(np, 1);
529       }
530       break;
531    case KEY_MAX:
532       ToggleMaximized(np, MAX_HORIZ | MAX_VERT);
533       break;
534    case KEY_RESTORE:
535       if(np) {
536          if(np->state.maxFlags) {
537             MaximizeClient(np, MAX_NONE);
538          } else {
539             MinimizeClient(np, 1);
540          }
541       }
542       break;
543    case KEY_MAXTOP:
544       ToggleMaximized(np, MAX_TOP | MAX_HORIZ);
545       break;
546    case KEY_MAXBOTTOM:
547       ToggleMaximized(np, MAX_BOTTOM | MAX_HORIZ);
548       break;
549    case KEY_MAXLEFT:
550       ToggleMaximized(np, MAX_LEFT | MAX_VERT);
551       break;
552    case KEY_MAXRIGHT:
553       ToggleMaximized(np, MAX_RIGHT | MAX_VERT);
554       break;
555    case KEY_MAXV:
556       ToggleMaximized(np, MAX_VERT);
557       break;
558    case KEY_MAXH:
559       ToggleMaximized(np, MAX_HORIZ);
560       break;
561    case KEY_ROOT:
562       ShowKeyMenu(event);
563       break;
564    case KEY_WIN:
565       if(np) {
566          RaiseClient(np);
567          ShowWindowMenu(np, np->x, np->y, 1);
568       }
569       break;
570    case KEY_RESTART:
571       Restart();
572       break;
573    case KEY_EXIT:
574       Exit(1);
575       break;
576    case KEY_FULLSCREEN:
577       if(np) {
578          if(np->state.status & STAT_FULLSCREEN) {
579             SetClientFullScreen(np, 0);
580          } else {
581             SetClientFullScreen(np, 1);
582          }
583       }
584       break;
585    case KEY_SENDR:
586       if(np) {
587          const unsigned desktop = GetRightDesktop(np->state.desktop);
588          SetClientDesktop(np, desktop);
589          ChangeDesktop(desktop);
590       }
591       break;
592    case KEY_SENDL:
593       if(np) {
594          const unsigned desktop = GetLeftDesktop(np->state.desktop);
595          SetClientDesktop(np, desktop);
596          ChangeDesktop(desktop);
597       }
598       break;
599    case KEY_SENDU:
600       if(np) {
601          const unsigned desktop = GetAboveDesktop(np->state.desktop);
602          SetClientDesktop(np, desktop);
603          ChangeDesktop(desktop);
604       }
605       break;
606    case KEY_SENDD:
607       if(np) {
608          const unsigned desktop = GetBelowDesktop(np->state.desktop);
609          SetClientDesktop(np, desktop);
610          ChangeDesktop(desktop);
611       }
612       break;
613    default:
614       break;
615    }
616    DiscardEnterEvents();
617 }
618 
619 /** Handle a key release event. */
HandleKeyRelease(const XKeyEvent * event)620 void HandleKeyRelease(const XKeyEvent *event)
621 {
622    KeyType key;
623    key = GetKey(event) & 0xFF;
624    if(   key != KEY_NEXTSTACK && key != KEY_NEXT
625       && key != KEY_PREV      && key != KEY_PREVSTACK) {
626       StopWindowWalk();
627    }
628 }
629 
630 /** Process a configure request. */
HandleConfigureRequest(const XConfigureRequestEvent * event)631 void HandleConfigureRequest(const XConfigureRequestEvent *event)
632 {
633    ClientNode *np;
634 
635    if(HandleDockConfigureRequest(event)) {
636       return;
637    }
638 
639    np = FindClientByWindow(event->window);
640    if(np) {
641 
642       int deltax, deltay;
643       char changed = 0;
644       char resized = 0;
645 
646       GetGravityDelta(np, np->gravity, &deltax, &deltay);
647       if((event->value_mask & CWWidth) && (event->width != np->width)) {
648          switch(np->gravity) {
649          case EastGravity:
650          case NorthEastGravity:
651          case SouthEastGravity:
652             /* Right side should not move. */
653             np->x -= event->width - np->width;
654             break;
655          case WestGravity:
656          case NorthWestGravity:
657          case SouthWestGravity:
658             /* Left side should not move. */
659             break;
660          case CenterGravity:
661             /* Center of the window should not move. */
662             np->x -= (event->width - np->width) / 2;
663             break;
664          default:
665             break;
666          }
667          np->width = event->width;
668          changed = 1;
669          resized = 1;
670       }
671       if((event->value_mask & CWHeight) && (event->height != np->height)) {
672          switch(np->gravity) {
673          case NorthGravity:
674          case NorthEastGravity:
675          case NorthWestGravity:
676             /* Top should not move. */
677             break;
678          case SouthGravity:
679          case SouthEastGravity:
680          case SouthWestGravity:
681             /* Bottom should not move. */
682             np->y -= event->height - np->height;
683             break;
684          case CenterGravity:
685             /* Center of the window should not move. */
686             np->y -= (event->height - np->height) / 2;
687             break;
688          default:
689             break;
690          }
691          np->height = event->height;
692          changed = 1;
693          resized = 1;
694       }
695       if((event->value_mask & CWX) && (event->x - deltax != np->x)) {
696          np->x = event->x - deltax;
697          changed = 1;
698       }
699       if((event->value_mask & CWY) && (event->y - deltay != np->y)) {
700          np->y = event->y - deltay;
701          changed = 1;
702       }
703 
704       /* Update stacking. */
705       if((event->value_mask & CWStackMode)) {
706          Window above = None;
707          if(event->value_mask & CWSibling) {
708             above = event->above;
709          }
710          RestackClient(np, above, event->detail);
711       }
712 
713       /* Return early if there's nothing to do. */
714       if(!changed) {
715          /* Nothing changed; send a synthetic configure event. */
716          SendConfigureEvent(np);
717          return;
718       }
719 
720       /* Stop any move/resize that may be in progress. */
721       if(np->controller) {
722          (np->controller)(0);
723       }
724 
725       /* If the client is maximized, restore it first. */
726       if(np->state.maxFlags) {
727          MaximizeClient(np, MAX_NONE);
728       }
729 
730       if(np->state.border & BORDER_CONSTRAIN) {
731          resized = 1;
732       }
733       if(resized) {
734          /* The size changed so the parent will need to be redrawn. */
735          ConstrainSize(np);
736          ConstrainPosition(np);
737          ResetBorder(np);
738       } else {
739          /* Only the position changed; move the client. */
740          int north, south, east, west;
741          GetBorderSize(&np->state, &north, &south, &east, &west);
742 
743          if(np->parent != None) {
744             JXMoveWindow(display, np->parent, np->x - west, np->y - north);
745             SendConfigureEvent(np);
746          } else {
747             JXMoveWindow(display, np->window, np->x, np->y);
748          }
749       }
750 
751       RequirePagerUpdate();
752 
753    } else {
754 
755       /* We don't know about this window, just let the configure through. */
756 
757       XWindowChanges wc;
758       wc.stack_mode = event->detail;
759       wc.sibling = event->above;
760       wc.border_width = event->border_width;
761       wc.x = event->x;
762       wc.y = event->y;
763       wc.width = event->width;
764       wc.height = event->height;
765       JXConfigureWindow(display, event->window, event->value_mask, &wc);
766 
767    }
768 }
769 
770 /** Process a configure notify event. */
HandleConfigureNotify(const XConfigureEvent * event)771 char HandleConfigureNotify(const XConfigureEvent *event)
772 {
773    if(event->window != rootWindow) {
774       return 0;
775    }
776    if(rootWidth != event->width || rootHeight != event->height) {
777       rootWidth = event->width;
778       rootHeight = event->height;
779       shouldRestart = 1;
780       shouldExit = 1;
781    }
782    return 1;
783 }
784 
785 /** Process an enter notify event. */
HandleEnterNotify(const XCrossingEvent * event)786 void HandleEnterNotify(const XCrossingEvent *event)
787 {
788    ClientNode *np;
789    Cursor cur;
790    np = FindClient(event->window);
791    if(np) {
792       if(  !(np->state.status & STAT_ACTIVE)
793          && (settings.focusModel == FOCUS_SLOPPY
794             || settings.focusModel == FOCUS_SLOPPY_TITLE)) {
795          FocusClient(np);
796       }
797       if(np->parent == event->window) {
798          np->borderAction = GetBorderActionType(np, event->x, event->y);
799          cur = GetFrameCursor(np->borderAction);
800          JXDefineCursor(display, np->parent, cur);
801       } else if(np->borderAction != BA_NONE) {
802          SetDefaultCursor(np->parent);
803          np->borderAction = BA_NONE;
804       }
805    }
806 
807 }
808 
809 /** Handle an expose event. */
HandleExpose(const XExposeEvent * event)810 char HandleExpose(const XExposeEvent *event)
811 {
812    ClientNode *np;
813    np = FindClientByParent(event->window);
814    if(np) {
815       if(event->count == 0) {
816          DrawBorder(np);
817       }
818       return 1;
819    } else {
820       np = FindClientByWindow(event->window);
821       if(np) {
822          if(np->state.status & STAT_WMDIALOG) {
823 
824             /* Dialog expose events are handled elsewhere. */
825             return 0;
826 
827          } else {
828 
829             /* Ignore other expose events for client windows. */
830             return 1;
831 
832          }
833       }
834       return event->count ? 1 : 0;
835    }
836 }
837 
838 /** Handle a property notify event. */
HandlePropertyNotify(const XPropertyEvent * event)839 char HandlePropertyNotify(const XPropertyEvent *event)
840 {
841    ClientNode *np = FindClientByWindow(event->window);
842    if(np) {
843       char changed = 0;
844       switch(event->atom) {
845       case XA_WM_NAME:
846          ReadWMName(np);
847          changed = 1;
848          break;
849       case XA_WM_NORMAL_HINTS:
850          ReadWMNormalHints(np);
851          if(ConstrainSize(np)) {
852             ResetBorder(np);
853          }
854          changed = 1;
855          break;
856       case XA_WM_HINTS:
857          if(np->state.status & STAT_URGENT) {
858             UnregisterCallback(SignalUrgent, np);
859          }
860          ReadWMHints(np->window, &np->state, 1);
861          if(np->state.status & STAT_URGENT) {
862             RegisterCallback(URGENCY_DELAY, SignalUrgent, np);
863          }
864          WriteState(np);
865          break;
866       case XA_WM_TRANSIENT_FOR:
867          JXGetTransientForHint(display, np->window, &np->owner);
868          break;
869       case XA_WM_ICON_NAME:
870       case XA_WM_CLIENT_MACHINE:
871          break;
872       default:
873          if(event->atom == atoms[ATOM_WM_COLORMAP_WINDOWS]) {
874             ReadWMColormaps(np);
875             UpdateClientColormap(np);
876          } else if(event->atom == atoms[ATOM_WM_PROTOCOLS]) {
877             ReadWMProtocols(np->window, &np->state);
878          } else if(event->atom == atoms[ATOM_NET_WM_ICON]) {
879             LoadIcon(np);
880             changed = 1;
881          } else if(event->atom == atoms[ATOM_NET_WM_NAME]) {
882             ReadWMName(np);
883             changed = 1;
884          } else if(event->atom == atoms[ATOM_NET_WM_STRUT_PARTIAL]) {
885             ReadClientStrut(np);
886          } else if(event->atom == atoms[ATOM_NET_WM_STRUT]) {
887             ReadClientStrut(np);
888          } else if(event->atom == atoms[ATOM_MOTIF_WM_HINTS]) {
889             UpdateState(np);
890             WriteState(np);
891             ResetBorder(np);
892             changed = 1;
893          } else if(event->atom == atoms[ATOM_NET_WM_WINDOW_OPACITY]) {
894             ReadWMOpacity(np->window, &np->state.opacity);
895             if(np->parent != None) {
896                SetOpacity(np, np->state.opacity, 1);
897             }
898          }
899          break;
900       }
901 
902       if(changed) {
903          DrawBorder(np);
904          RequireTaskUpdate();
905          RequirePagerUpdate();
906       }
907       if(np->state.status & STAT_WMDIALOG) {
908          return 0;
909       } else {
910          return 1;
911       }
912    }
913 
914    return 1;
915 }
916 
917 /** Handle a client message. */
HandleClientMessage(const XClientMessageEvent * event)918 void HandleClientMessage(const XClientMessageEvent *event)
919 {
920 
921    ClientNode *np;
922 #ifdef DEBUG
923    char *atomName;
924 #endif
925 
926    np = FindClientByWindow(event->window);
927    if(np) {
928       if(event->message_type == atoms[ATOM_WM_CHANGE_STATE]) {
929 
930          if(np->controller) {
931             (np->controller)(0);
932          }
933 
934          switch(event->data.l[0]) {
935          case WithdrawnState:
936             SetClientWithdrawn(np);
937             break;
938          case IconicState:
939             MinimizeClient(np, 1);
940             break;
941          case NormalState:
942             RestoreClient(np, 1);
943             break;
944          default:
945             break;
946          }
947 
948       } else if(event->message_type == atoms[ATOM_NET_ACTIVE_WINDOW]) {
949 
950          RestoreClient(np, 1);
951          UnshadeClient(np);
952          FocusClient(np);
953 
954       } else if(event->message_type == atoms[ATOM_NET_WM_DESKTOP]) {
955 
956          if(event->data.l[0] == ~0L) {
957             SetClientSticky(np, 1);
958          } else {
959 
960             if(np->controller) {
961                (np->controller)(0);
962             }
963 
964             if(   event->data.l[0] >= 0
965                && event->data.l[0] < (long)settings.desktopCount) {
966                np->state.status &= ~STAT_STICKY;
967                SetClientDesktop(np, event->data.l[0]);
968             }
969          }
970 
971       } else if(event->message_type == atoms[ATOM_NET_CLOSE_WINDOW]) {
972 
973          DeleteClient(np);
974 
975       } else if(event->message_type == atoms[ATOM_NET_MOVERESIZE_WINDOW]) {
976 
977          HandleNetMoveResize(event, np);
978 
979       } else if(event->message_type == atoms[ATOM_NET_WM_MOVERESIZE]) {
980 
981          HandleNetWMMoveResize(event, np);
982 
983       } else if(event->message_type == atoms[ATOM_NET_RESTACK_WINDOW]) {
984 
985          HandleNetRestack(event, np);
986 
987       } else if(event->message_type == atoms[ATOM_NET_WM_STATE]) {
988 
989          HandleNetWMState(event, np);
990 
991       } else {
992 
993 #ifdef DEBUG
994          atomName = JXGetAtomName(display, event->message_type);
995          Debug("Unknown ClientMessage to client: %s", atomName);
996          JXFree(atomName);
997 #endif
998 
999       }
1000 
1001    } else if(event->window == rootWindow) {
1002 
1003       if(event->message_type == atoms[ATOM_JWM_RESTART]) {
1004          Restart();
1005       } else if(event->message_type == atoms[ATOM_JWM_EXIT]) {
1006          Exit(0);
1007       } else if(event->message_type == atoms[ATOM_JWM_RELOAD]) {
1008          ReloadMenu();
1009       } else if(event->message_type == atoms[ATOM_NET_CURRENT_DESKTOP]) {
1010          ChangeDesktop(event->data.l[0]);
1011       } else if(event->message_type == atoms[ATOM_NET_SHOWING_DESKTOP]) {
1012          ShowDesktop();
1013       } else {
1014 #ifdef DEBUG
1015          atomName = JXGetAtomName(display, event->message_type);
1016          Debug("Unknown ClientMessage to root: %s", atomName);
1017          JXFree(atomName);
1018 #endif
1019       }
1020 
1021    } else if(event->message_type == atoms[ATOM_NET_REQUEST_FRAME_EXTENTS]) {
1022 
1023       HandleFrameExtentsRequest(event);
1024 
1025    } else if(event->message_type == atoms[ATOM_NET_SYSTEM_TRAY_OPCODE]) {
1026 
1027       HandleDockEvent(event);
1028 
1029    } else {
1030 #ifdef DEBUG
1031          atomName = JXGetAtomName(display, event->message_type);
1032          Debug("ClientMessage to unknown window (0x%x): %s",
1033                event->window, atomName);
1034          JXFree(atomName);
1035 #endif
1036    }
1037 
1038 }
1039 
1040 /** Handle a _NET_MOVERESIZE_WINDOW request. */
HandleNetMoveResize(const XClientMessageEvent * event,ClientNode * np)1041 void HandleNetMoveResize(const XClientMessageEvent *event, ClientNode *np)
1042 {
1043 
1044    long flags;
1045    int gravity;
1046    int deltax, deltay;
1047 
1048    Assert(event);
1049    Assert(np);
1050 
1051    flags = event->data.l[0] >> 8;
1052    gravity = event->data.l[0] & 0xFF;
1053    if(gravity == 0) {
1054       gravity = np->gravity;
1055    }
1056    GetGravityDelta(np, gravity, &deltax, &deltay);
1057 
1058    if(flags & (1 << 2)) {
1059       const long width = event->data.l[3];
1060       switch(gravity) {
1061       case EastGravity:
1062       case NorthEastGravity:
1063       case SouthEastGravity:
1064          /* Right side should not move. */
1065          np->x -= width - np->width;
1066          break;
1067       case WestGravity:
1068       case NorthWestGravity:
1069       case SouthWestGravity:
1070          /* Left side should not move. */
1071          break;
1072       case CenterGravity:
1073          /* Center of the window should not move. */
1074          np->x -= (width - np->width) / 2;
1075          break;
1076       default:
1077          break;
1078       }
1079       np->width = width;
1080    }
1081    if(flags & (1 << 3)) {
1082       const long height = event->data.l[4];
1083       switch(gravity) {
1084       case NorthGravity:
1085       case NorthEastGravity:
1086       case NorthWestGravity:
1087          /* Top should not move. */
1088          break;
1089       case SouthGravity:
1090       case SouthEastGravity:
1091       case SouthWestGravity:
1092          /* Bottom should not move. */
1093          np->y -= height - np->height;
1094          break;
1095       case CenterGravity:
1096          /* Center of the window should not move. */
1097          np->y -= (height - np->height) / 2;
1098          break;
1099       default:
1100          break;
1101       }
1102       np->height = height;
1103    }
1104    if(flags & (1 << 0)) {
1105       np->x = event->data.l[1] - deltax;
1106    }
1107    if(flags & (1 << 1)) {
1108       np->y = event->data.l[2] - deltay;
1109    }
1110 
1111    /* Don't let maximized clients be moved or resized. */
1112    if(JUNLIKELY(np->state.status & STAT_FULLSCREEN)) {
1113       SetClientFullScreen(np, 0);
1114    }
1115    if(JUNLIKELY(np->state.maxFlags)) {
1116       MaximizeClient(np, MAX_NONE);
1117    }
1118 
1119    ConstrainSize(np);
1120    ResetBorder(np);
1121    SendConfigureEvent(np);
1122    RequirePagerUpdate();
1123 
1124 }
1125 
1126 /** Handle a _NET_WM_MOVERESIZE request. */
HandleNetWMMoveResize(const XClientMessageEvent * event,ClientNode * np)1127 void HandleNetWMMoveResize(const XClientMessageEvent *event, ClientNode *np)
1128 {
1129 
1130    long x = event->data.l[0] - np->x;
1131    long y = event->data.l[1] - np->y;
1132    const long direction = event->data.l[2];
1133    int deltax, deltay;
1134 
1135    GetGravityDelta(np, np->gravity, &deltax, &deltay);
1136    x -= deltax;
1137    y -= deltay;
1138 
1139    switch(direction) {
1140    case 0:  /* top-left */
1141       ResizeClient(np, BA_RESIZE | BA_RESIZE_N | BA_RESIZE_W, x, y);
1142       break;
1143    case 1:  /* top */
1144       ResizeClient(np, BA_RESIZE | BA_RESIZE_N, x, y);
1145       break;
1146    case 2:  /* top-right */
1147       ResizeClient(np, BA_RESIZE | BA_RESIZE_N | BA_RESIZE_E, x, y);
1148       break;
1149    case 3:  /* right */
1150       ResizeClient(np, BA_RESIZE | BA_RESIZE_E, x, y);
1151       break;
1152    case 4:  /* bottom-right */
1153       ResizeClient(np, BA_RESIZE | BA_RESIZE_S | BA_RESIZE_E, x, y);
1154       break;
1155    case 5:  /* bottom */
1156       ResizeClient(np, BA_RESIZE | BA_RESIZE_S, x, y);
1157       break;
1158    case 6:  /* bottom-left */
1159       ResizeClient(np, BA_RESIZE | BA_RESIZE_S | BA_RESIZE_W, x, y);
1160       break;
1161    case 7:  /* left */
1162       ResizeClient(np, BA_RESIZE | BA_RESIZE_W, x, y);
1163       break;
1164    case 8:  /* move */
1165       MoveClient(np, x, y);
1166       break;
1167    case 9:  /* resize-keyboard */
1168       ResizeClientKeyboard(np);
1169       break;
1170    case 10: /* move-keyboard */
1171       MoveClientKeyboard(np);
1172       break;
1173    case 11: /* cancel */
1174       if(np->controller) {
1175          (np->controller)(0);
1176       }
1177       break;
1178    default:
1179       break;
1180    }
1181 
1182 }
1183 
1184 /** Handle a _NET_RESTACK_WINDOW request. */
HandleNetRestack(const XClientMessageEvent * event,ClientNode * np)1185 void HandleNetRestack(const XClientMessageEvent *event, ClientNode *np)
1186 {
1187    const Window sibling = event->data.l[1];
1188    const int detail = event->data.l[2];
1189    RestackClient(np, sibling, detail);
1190 }
1191 
1192 /** Handle a _NET_WM_STATE request. */
HandleNetWMState(const XClientMessageEvent * event,ClientNode * np)1193 void HandleNetWMState(const XClientMessageEvent *event, ClientNode *np)
1194 {
1195 
1196    unsigned int x;
1197    MaxFlags maxFlags;
1198    char actionStick;
1199    char actionShade;
1200    char actionFullScreen;
1201    char actionMinimize;
1202    char actionNolist;
1203    char actionNopager;
1204    char actionBelow;
1205    char actionAbove;
1206 
1207    /* Up to two actions to be applied together. */
1208    maxFlags = MAX_NONE;
1209    actionStick = 0;
1210    actionShade = 0;
1211    actionFullScreen = 0;
1212    actionMinimize = 0;
1213    actionNolist = 0;
1214    actionNopager = 0;
1215    actionBelow = 0;
1216    actionAbove = 0;
1217 
1218    for(x = 1; x <= 2; x++) {
1219       if(event->data.l[x]
1220          == (long)atoms[ATOM_NET_WM_STATE_STICKY]) {
1221          actionStick = 1;
1222       } else if(event->data.l[x]
1223          == (long)atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT]) {
1224          maxFlags |= MAX_VERT;
1225       } else if(event->data.l[x]
1226          == (long)atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ]) {
1227          maxFlags |= MAX_HORIZ;
1228       } else if(event->data.l[x]
1229          == (long)atoms[ATOM_NET_WM_STATE_SHADED]) {
1230          actionShade = 1;
1231       } else if(event->data.l[x]
1232          == (long)atoms[ATOM_NET_WM_STATE_FULLSCREEN]) {
1233          actionFullScreen = 1;
1234       } else if(event->data.l[x]
1235          == (long)atoms[ATOM_NET_WM_STATE_HIDDEN]) {
1236          actionMinimize = 1;
1237       } else if(event->data.l[x]
1238          == (long)atoms[ATOM_NET_WM_STATE_SKIP_TASKBAR]) {
1239          actionNolist = 1;
1240       } else if(event->data.l[x]
1241          == (long)atoms[ATOM_NET_WM_STATE_SKIP_PAGER]) {
1242          actionNopager = 1;
1243       } else if(event->data.l[x]
1244          == (long)atoms[ATOM_NET_WM_STATE_BELOW]) {
1245          actionBelow = 1;
1246       } else if(event->data.l[x]
1247          == (long)atoms[ATOM_NET_WM_STATE_ABOVE]) {
1248          actionAbove = 1;
1249       }
1250    }
1251 
1252    switch(event->data.l[0]) {
1253    case 0: /* Remove */
1254       if(actionStick) {
1255          SetClientSticky(np, 0);
1256       }
1257       if(maxFlags != MAX_NONE && np->state.maxFlags) {
1258          MaximizeClient(np, np->state.maxFlags & ~maxFlags);
1259       }
1260       if(actionShade) {
1261          UnshadeClient(np);
1262       }
1263       if(actionFullScreen) {
1264          SetClientFullScreen(np, 0);
1265       }
1266       if(actionMinimize) {
1267          RestoreClient(np, 0);
1268       }
1269       if(actionNolist && !(np->state.status & STAT_ILIST)) {
1270          np->state.status &= ~STAT_NOLIST;
1271          RequireTaskUpdate();
1272       }
1273       if(actionNopager && !(np->state.status & STAT_IPAGER)) {
1274          np->state.status &= ~STAT_NOPAGER;
1275          RequirePagerUpdate();
1276       }
1277       if(actionBelow && np->state.layer == LAYER_BELOW) {
1278          SetClientLayer(np, np->state.defaultLayer);
1279       }
1280       if(actionAbove && np->state.layer == LAYER_ABOVE) {
1281          SetClientLayer(np, np->state.defaultLayer);
1282       }
1283       break;
1284    case 1: /* Add */
1285       if(actionStick) {
1286          SetClientSticky(np, 1);
1287       }
1288       if(maxFlags != MAX_NONE) {
1289          MaximizeClient(np, np->state.maxFlags | maxFlags);
1290       }
1291       if(actionShade) {
1292          ShadeClient(np);
1293       }
1294       if(actionFullScreen) {
1295          SetClientFullScreen(np, 1);
1296       }
1297       if(actionMinimize) {
1298          MinimizeClient(np, 1);
1299       }
1300       if(actionNolist && !(np->state.status & STAT_ILIST)) {
1301          np->state.status |= STAT_NOLIST;
1302          RequireTaskUpdate();
1303       }
1304       if(actionNopager && !(np->state.status & STAT_IPAGER)) {
1305          np->state.status |= STAT_NOPAGER;
1306          RequirePagerUpdate();
1307       }
1308       if(actionBelow) {
1309          SetClientLayer(np, LAYER_BELOW);
1310       }
1311       if(actionAbove) {
1312          SetClientLayer(np, LAYER_ABOVE);
1313       }
1314       break;
1315    case 2: /* Toggle */
1316       if(actionStick) {
1317          if(np->state.status & STAT_STICKY) {
1318             SetClientSticky(np, 0);
1319          } else {
1320             SetClientSticky(np, 1);
1321          }
1322       }
1323       if(maxFlags) {
1324          MaximizeClient(np, np->state.maxFlags ^ maxFlags);
1325       }
1326       if(actionShade) {
1327          if(np->state.status & STAT_SHADED) {
1328             UnshadeClient(np);
1329          } else {
1330             ShadeClient(np);
1331          }
1332       }
1333       if(actionFullScreen) {
1334          if(np->state.status & STAT_FULLSCREEN) {
1335             SetClientFullScreen(np, 0);
1336          } else {
1337             SetClientFullScreen(np, 1);
1338          }
1339       }
1340       if(actionBelow) {
1341          if(np->state.layer == LAYER_BELOW) {
1342             SetClientLayer(np, np->state.defaultLayer);
1343          } else {
1344             SetClientLayer(np, LAYER_BELOW);
1345          }
1346       }
1347       if(actionAbove) {
1348          if(np->state.layer == LAYER_ABOVE) {
1349             SetClientLayer(np, np->state.defaultLayer);
1350          } else {
1351             SetClientLayer(np, LAYER_ABOVE);
1352          }
1353       }
1354       /* Note that we don't handle toggling of hidden per EWMH
1355        * recommendations. */
1356       if(actionNolist && !(np->state.status & STAT_ILIST)) {
1357          np->state.status ^= STAT_NOLIST;
1358          RequireTaskUpdate();
1359       }
1360       if(actionNopager && !(np->state.status & STAT_IPAGER)) {
1361          np->state.status ^= STAT_NOPAGER;
1362          RequirePagerUpdate();
1363       }
1364       break;
1365    default:
1366       Debug("bad _NET_WM_STATE action: %ld", event->data.l[0]);
1367       break;
1368    }
1369 
1370    /* Update _NET_WM_STATE if needed.
1371     * The state update is handled elsewhere for the other actions.
1372     */
1373    if(actionNolist | actionNopager | actionAbove | actionBelow) {
1374       WriteState(np);
1375    }
1376 
1377 }
1378 
1379 /** Handle a _NET_REQUEST_FRAME_EXTENTS request. */
HandleFrameExtentsRequest(const XClientMessageEvent * event)1380 void HandleFrameExtentsRequest(const XClientMessageEvent *event)
1381 {
1382    ClientState state;
1383    state = ReadWindowState(event->window, 0);
1384    WriteFrameExtents(event->window, &state);
1385 }
1386 
1387 /** Handle a motion notify event. */
HandleMotionNotify(const XMotionEvent * event)1388 void HandleMotionNotify(const XMotionEvent *event)
1389 {
1390 
1391    ClientNode *np;
1392    Cursor cur;
1393 
1394    if(event->is_hint) {
1395       return;
1396    }
1397 
1398    np = FindClientByParent(event->window);
1399    if(np) {
1400       BorderActionType action;
1401       action = GetBorderActionType(np, event->x, event->y);
1402       if(np->borderAction != action) {
1403          np->borderAction = action;
1404          cur = GetFrameCursor(action);
1405          JXDefineCursor(display, np->parent, cur);
1406       }
1407    }
1408 
1409 }
1410 
1411 /** Handle a shape event. */
1412 #ifdef USE_SHAPE
HandleShapeEvent(const XShapeEvent * event)1413 void HandleShapeEvent(const XShapeEvent *event)
1414 {
1415    ClientNode *np;
1416    np = FindClientByWindow(event->window);
1417    if(np) {
1418       np->state.status |= STAT_SHAPED;
1419       ResetBorder(np);
1420    }
1421 }
1422 #endif /* USE_SHAPE */
1423 
1424 /** Handle a colormap event. */
HandleColormapChange(const XColormapEvent * event)1425 void HandleColormapChange(const XColormapEvent *event)
1426 {
1427    ClientNode *np;
1428    if(event->new == True) {
1429       np = FindClientByWindow(event->window);
1430       if(np) {
1431          np->cmap = event->colormap;
1432          UpdateClientColormap(np);
1433       }
1434    }
1435 }
1436 
1437 /** Handle a map request. */
HandleMapRequest(const XMapEvent * event)1438 void HandleMapRequest(const XMapEvent *event)
1439 {
1440    ClientNode *np;
1441    Assert(event);
1442    if(CheckSwallowMap(event->window)) {
1443       return;
1444    }
1445    np = FindClientByWindow(event->window);
1446    if(!np) {
1447       GrabServer();
1448       np = AddClientWindow(event->window, 0, 1);
1449       if(np) {
1450          if(!(np->state.status & STAT_NOFOCUS)) {
1451             FocusClient(np);
1452          }
1453       } else {
1454          JXMapWindow(display, event->window);
1455       }
1456       UngrabServer();
1457    } else {
1458       if(!(np->state.status & STAT_MAPPED)) {
1459          UpdateState(np);
1460          np->state.status |= STAT_MAPPED;
1461          XMapWindow(display, np->window);
1462          if(np->parent != None) {
1463             XMapWindow(display, np->parent);
1464          }
1465          if(!(np->state.status & STAT_STICKY)) {
1466             np->state.desktop = currentDesktop;
1467          }
1468          if(!(np->state.status & STAT_NOFOCUS)) {
1469             FocusClient(np);
1470             RaiseClient(np);
1471          }
1472          WriteState(np);
1473          RequireTaskUpdate();
1474          RequirePagerUpdate();
1475       }
1476    }
1477    RequireRestack();
1478 }
1479 
1480 /** Handle an unmap notify event. */
HandleUnmapNotify(const XUnmapEvent * event)1481 void HandleUnmapNotify(const XUnmapEvent *event)
1482 {
1483    ClientNode *np;
1484    XEvent e;
1485 
1486    Assert(event);
1487 
1488    if(event->window != event->event) {
1489       /* Allow ICCCM synthetic UnmapNotify events through. */
1490       if (event->event != rootWindow || !event->send_event) {
1491          return;
1492       }
1493    }
1494 
1495    np = FindClientByWindow(event->window);
1496    if(np) {
1497 
1498       /* Grab the server to prevent the client from destroying the
1499        * window after we check for a DestroyNotify. */
1500       GrabServer();
1501 
1502       if(np->controller) {
1503          (np->controller)(1);
1504       }
1505 
1506       if(JXCheckTypedWindowEvent(display, np->window, DestroyNotify, &e)) {
1507          UpdateTime(&e);
1508          RemoveClient(np);
1509       } else if((np->state.status & STAT_MAPPED) || event->send_event) {
1510          if(!(np->state.status & STAT_HIDDEN)) {
1511             np->state.status &= ~(STAT_MAPPED | STAT_MINIMIZED | STAT_SHADED);
1512             JXUngrabButton(display, AnyButton, AnyModifier, np->window);
1513             GravitateClient(np, 1);
1514             JXReparentWindow(display, np->window, rootWindow, np->x, np->y);
1515             WriteState(np);
1516             JXRemoveFromSaveSet(display, np->window);
1517             RemoveClient(np);
1518          }
1519       }
1520       UngrabServer();
1521 
1522    }
1523 }
1524 
1525 /** Handle a destroy notify event. */
HandleDestroyNotify(const XDestroyWindowEvent * event)1526 char HandleDestroyNotify(const XDestroyWindowEvent *event)
1527 {
1528    ClientNode *np;
1529    np = FindClientByWindow(event->window);
1530    if(np) {
1531       if(np->controller) {
1532          (np->controller)(1);
1533       }
1534       RemoveClient(np);
1535       return 1;
1536    } else {
1537       return HandleDockDestroy(event->window);
1538    }
1539 }
1540 
1541 /** Take the appropriate action for a click on a client border. */
DispatchBorderButtonEvent(const XButtonEvent * event,ClientNode * np)1542 void DispatchBorderButtonEvent(const XButtonEvent *event,
1543                                ClientNode *np)
1544 {
1545    static Time lastClickTime = 0;
1546    static int lastX = 0, lastY = 0;
1547    static char doubleClickActive = 0;
1548    BorderActionType action;
1549    int bsize;
1550 
1551    /* Middle click starts a move unless it's over the maximize button. */
1552    action = GetBorderActionType(np, event->x, event->y);
1553    if(event->button == Button2 && action != BA_MAXIMIZE) {
1554       MoveClient(np, event->x, event->y);
1555       return;
1556    }
1557 
1558    /* Determine the size of the border. */
1559    if(np->state.border & BORDER_OUTLINE) {
1560       bsize = settings.borderWidth;
1561    } else {
1562       bsize = 0;
1563    }
1564 
1565    /* Other buttons are context sensitive. */
1566    switch(action & 0x0F) {
1567    case BA_RESIZE:   /* Border */
1568       if(event->type == ButtonPress) {
1569          if(event->button == Button1) {
1570             ResizeClient(np, action, event->x, event->y);
1571          } else if(event->button == Button3) {
1572             const unsigned titleHeight = GetTitleHeight();
1573             const int x = np->x + event->x - bsize;
1574             const int y = np->y + event->y - titleHeight - bsize;
1575             ShowWindowMenu(np, x, y, 0);
1576          }
1577       }
1578       break;
1579    case BA_MOVE:     /* Title bar */
1580       if(event->button == Button1) {
1581          if(event->type == ButtonPress) {
1582             if(doubleClickActive
1583                && event->time != lastClickTime
1584                && event->time - lastClickTime <= settings.doubleClickSpeed
1585                && abs(event->x - lastX) <= settings.doubleClickDelta
1586                && abs(event->y - lastY) <= settings.doubleClickDelta) {
1587                MaximizeClientDefault(np);
1588                doubleClickActive = 0;
1589             } else {
1590                if(MoveClient(np, event->x, event->y)) {
1591                   doubleClickActive = 0;
1592                } else {
1593                   doubleClickActive = 1;
1594                   lastClickTime = event->time;
1595                   lastX = event->x;
1596                   lastY = event->y;
1597                }
1598             }
1599          }
1600       } else if(event->button == Button3) {
1601          const unsigned titleHeight = GetTitleHeight();
1602          const int x = np->x + event->x - bsize;
1603          const int y = np->y + event->y - titleHeight - bsize;
1604          ShowWindowMenu(np, x, y, 0);
1605       } else if(event->button == Button4) {
1606          ShadeClient(np);
1607       } else if(event->button == Button5) {
1608          UnshadeClient(np);
1609       }
1610       break;
1611    case BA_MENU:  /* Menu button */
1612       if(event->button == Button4) {
1613          ShadeClient(np);
1614       } else if(event->button == Button5) {
1615          UnshadeClient(np);
1616       } else if(event->type == ButtonPress) {
1617          const unsigned titleHeight = GetTitleHeight();
1618          const int x = np->x + event->x - bsize;
1619          const int y = np->y + event->y - titleHeight - bsize;
1620          ShowWindowMenu(np, x, y, 0);
1621       }
1622       break;
1623    case BA_CLOSE: /* Close button */
1624       if(event->type == ButtonRelease
1625          && (event->button == Button1 || event->button == Button3)) {
1626          DeleteClient(np);
1627       }
1628       break;
1629    case BA_MAXIMIZE: /* Maximize button */
1630       if(event->type == ButtonRelease) {
1631          switch(event->button) {
1632          case Button1:
1633             MaximizeClientDefault(np);
1634             break;
1635          case Button2:
1636             MaximizeClient(np, np->state.maxFlags ^ MAX_VERT);
1637             break;
1638          case Button3:
1639             MaximizeClient(np, np->state.maxFlags ^ MAX_HORIZ);
1640             break;
1641          default:
1642             break;
1643          }
1644       }
1645       break;
1646    case BA_MINIMIZE: /* Minimize button */
1647       if(event->type == ButtonRelease) {
1648          if(event->button == Button3) {
1649             if(np->state.status & STAT_SHADED) {
1650                UnshadeClient(np);
1651             } else {
1652                ShadeClient(np);
1653             }
1654          } else if(event->button == Button1) {
1655             MinimizeClient(np, 1);
1656          }
1657       }
1658       break;
1659    default:
1660       break;
1661    }
1662    DiscardEnterEvents();
1663 }
1664 
1665 /** Update window state information. */
UpdateState(ClientNode * np)1666 void UpdateState(ClientNode *np)
1667 {
1668    const char alreadyMapped = (np->state.status & STAT_MAPPED) ? 1 : 0;
1669    const char active = (np->state.status & STAT_ACTIVE) ? 1 : 0;
1670 
1671    /* Remove from the layer list. */
1672    if(np->prev != NULL) {
1673       np->prev->next = np->next;
1674    } else {
1675       Assert(nodes[np->state.layer] == np);
1676       nodes[np->state.layer] = np->next;
1677    }
1678    if(np->next != NULL) {
1679       np->next->prev = np->prev;
1680    } else {
1681       Assert(nodeTail[np->state.layer] == np);
1682       nodeTail[np->state.layer] = np->prev;
1683    }
1684 
1685    /* Read the state (and new layer). */
1686    if(np->state.status & STAT_URGENT) {
1687       UnregisterCallback(SignalUrgent, np);
1688    }
1689    np->state = ReadWindowState(np->window, alreadyMapped);
1690    if(np->state.status & STAT_URGENT) {
1691       RegisterCallback(URGENCY_DELAY, SignalUrgent, np);
1692    }
1693 
1694    /* We don't handle mapping the window, so restore its mapped state. */
1695    if(!alreadyMapped) {
1696       np->state.status &= ~STAT_MAPPED;
1697    }
1698 
1699    /* Add to the layer list. */
1700    np->prev = NULL;
1701    np->next = nodes[np->state.layer];
1702    if(np->next == NULL) {
1703       nodeTail[np->state.layer] = np;
1704    } else {
1705       np->next->prev = np;
1706    }
1707    nodes[np->state.layer] = np;
1708 
1709    if(active) {
1710       FocusClient(np);
1711    }
1712 
1713 }
1714 
1715 /** Update the last event time. */
UpdateTime(const XEvent * event)1716 void UpdateTime(const XEvent *event)
1717 {
1718    Time t = CurrentTime;
1719    Assert(event);
1720    switch(event->type) {
1721    case KeyPress:
1722    case KeyRelease:
1723       t = event->xkey.time;
1724       break;
1725    case ButtonPress:
1726    case ButtonRelease:
1727       t = event->xbutton.time;
1728       break;
1729    case MotionNotify:
1730       t = event->xmotion.time;
1731       break;
1732    case EnterNotify:
1733    case LeaveNotify:
1734       t = event->xcrossing.time;
1735       break;
1736    case PropertyNotify:
1737       t = event->xproperty.time;
1738       break;
1739    case SelectionClear:
1740       t = event->xselectionclear.time;
1741       break;
1742    case SelectionRequest:
1743       t = event->xselectionrequest.time;
1744       break;
1745    case SelectionNotify:
1746       t = event->xselection.time;
1747       break;
1748    default:
1749       break;
1750    }
1751    if(t != CurrentTime) {
1752       if(t > eventTime || t < eventTime - 60000) {
1753          eventTime = t;
1754       }
1755    }
1756 }
1757 
1758 /** Register a callback. */
RegisterCallback(int freq,SignalCallback callback,void * data)1759 void RegisterCallback(int freq, SignalCallback callback, void *data)
1760 {
1761    CallbackNode *cp;
1762    cp = Allocate(sizeof(CallbackNode));
1763    cp->last.seconds = 0;
1764    cp->last.ms = 0;
1765    cp->freq = freq;
1766    cp->callback = callback;
1767    cp->data = data;
1768    cp->next = callbacks;
1769    callbacks = cp;
1770 }
1771 
1772 /** Unregister a callback. */
UnregisterCallback(SignalCallback callback,void * data)1773 void UnregisterCallback(SignalCallback callback, void *data)
1774 {
1775    CallbackNode **cp;
1776    for(cp = &callbacks; *cp; cp = &(*cp)->next) {
1777       if((*cp)->callback == callback && (*cp)->data == data) {
1778          CallbackNode *temp = *cp;
1779          *cp = (*cp)->next;
1780          Release(temp);
1781          return;
1782       }
1783    }
1784    Assert(0);
1785 }
1786 
1787 /** Restack clients before waiting for an event. */
RequireRestack()1788 void RequireRestack()
1789 {
1790    restack_pending = 1;
1791 }
1792 
1793 /** Update the task bar before waiting for an event. */
RequireTaskUpdate()1794 void RequireTaskUpdate()
1795 {
1796    task_update_pending = 1;
1797 }
1798 
1799 /** Update the pager before waiting for an event. */
RequirePagerUpdate()1800 void RequirePagerUpdate()
1801 {
1802    pager_update_pending = 1;
1803 }
1804