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