1 /**
2 * @file client.c
3 * @author Joe Wingbermuehle
4 * @date 2004-2006
5 *
6 * @brief Client window functions.
7 *
8 */
9
10 #include "jwm.h"
11 #include "client.h"
12 #include "clientlist.h"
13 #include "icon.h"
14 #include "group.h"
15 #include "tray.h"
16 #include "confirm.h"
17 #include "cursor.h"
18 #include "taskbar.h"
19 #include "screen.h"
20 #include "pager.h"
21 #include "color.h"
22 #include "place.h"
23 #include "event.h"
24 #include "settings.h"
25 #include "timing.h"
26 #include "grab.h"
27 #include "desktop.h"
28
29 static ClientNode *activeClient;
30
31 unsigned int clientCount;
32
33 static void LoadFocus(void);
34 static void RestackTransients(const ClientNode *np);
35 static void MinimizeTransients(ClientNode *np, char lower);
36 static void RestoreTransients(ClientNode *np, char raise);
37 static void KillClientHandler(ClientNode *np);
38 static void UnmapClient(ClientNode *np);
39
40 /** Load windows that are already mapped. */
StartupClients(void)41 void StartupClients(void)
42 {
43
44 XWindowAttributes attr;
45 Window rootReturn, parentReturn, *childrenReturn;
46 unsigned int childrenCount;
47 unsigned int x;
48
49 clientCount = 0;
50 activeClient = NULL;
51 currentDesktop = 0;
52
53 /* Clear out the client lists. */
54 for(x = 0; x < LAYER_COUNT; x++) {
55 nodes[x] = NULL;
56 nodeTail[x] = NULL;
57 }
58
59 /* Query client windows. */
60 JXQueryTree(display, rootWindow, &rootReturn, &parentReturn,
61 &childrenReturn, &childrenCount);
62
63 /* Add each client. */
64 for(x = 0; x < childrenCount; x++) {
65 if(JXGetWindowAttributes(display, childrenReturn[x], &attr)) {
66 if(attr.override_redirect == False && attr.map_state == IsViewable) {
67 AddClientWindow(childrenReturn[x], 1, 1);
68 }
69 }
70 }
71
72 JXFree(childrenReturn);
73
74 LoadFocus();
75
76 RequireTaskUpdate();
77 RequirePagerUpdate();
78
79 }
80
81 /** Release client windows. */
ShutdownClients(void)82 void ShutdownClients(void)
83 {
84
85 int x;
86
87 for(x = 0; x < LAYER_COUNT; x++) {
88 while(nodeTail[x]) {
89 RemoveClient(nodeTail[x]);
90 }
91 }
92
93 }
94
95 /** Set the focus to the window currently under the mouse pointer. */
LoadFocus(void)96 void LoadFocus(void)
97 {
98
99 ClientNode *np;
100 Window rootReturn, childReturn;
101 int rootx, rooty;
102 int winx, winy;
103 unsigned int mask;
104
105 JXQueryPointer(display, rootWindow, &rootReturn, &childReturn,
106 &rootx, &rooty, &winx, &winy, &mask);
107
108 np = FindClient(childReturn);
109 if(np) {
110 FocusClient(np);
111 }
112
113 }
114
115 /** Add a window to management. */
AddClientWindow(Window w,char alreadyMapped,char notOwner)116 ClientNode *AddClientWindow(Window w, char alreadyMapped, char notOwner)
117 {
118
119 XWindowAttributes attr;
120 ClientNode *np;
121
122 Assert(w != None);
123
124 /* Get window attributes. */
125 if(JXGetWindowAttributes(display, w, &attr) == 0) {
126 return NULL;
127 }
128
129 /* Determine if we should care about this window. */
130 if(attr.override_redirect == True) {
131 return NULL;
132 }
133 if(attr.class == InputOnly) {
134 return NULL;
135 }
136
137 /* Prepare a client node for this window. */
138 np = Allocate(sizeof(ClientNode));
139 memset(np, 0, sizeof(ClientNode));
140
141 np->window = w;
142 np->parent = None;
143 np->owner = None;
144 np->state.desktop = currentDesktop;
145
146 np->x = attr.x;
147 np->y = attr.y;
148 np->width = attr.width;
149 np->height = attr.height;
150 np->cmap = attr.colormap;
151 np->state.status = STAT_NONE;
152 np->state.maxFlags = MAX_NONE;
153 np->state.layer = LAYER_NORMAL;
154 np->state.defaultLayer = LAYER_NORMAL;
155
156 np->state.border = BORDER_DEFAULT;
157 np->borderAction = BA_NONE;
158
159 ReadClientInfo(np, alreadyMapped);
160
161 if(!notOwner) {
162 np->state.border = BORDER_OUTLINE | BORDER_TITLE | BORDER_MOVE;
163 np->state.status |= STAT_WMDIALOG | STAT_STICKY;
164 np->state.layer = LAYER_ABOVE;
165 np->state.defaultLayer = LAYER_ABOVE;
166 }
167
168 ApplyGroups(np);
169 if(np->icon == NULL) {
170 LoadIcon(np);
171 }
172
173 /* We now know the layer, so insert */
174 np->prev = NULL;
175 np->next = nodes[np->state.layer];
176 if(np->next) {
177 np->next->prev = np;
178 } else {
179 nodeTail[np->state.layer] = np;
180 }
181 nodes[np->state.layer] = np;
182
183 SetDefaultCursor(np->window);
184
185 if(notOwner) {
186 XSetWindowAttributes sattr;
187 JXAddToSaveSet(display, np->window);
188 sattr.event_mask
189 = EnterWindowMask
190 | ColormapChangeMask
191 | PropertyChangeMask
192 | KeyReleaseMask
193 | StructureNotifyMask;
194 sattr.do_not_propagate_mask = ButtonPressMask
195 | ButtonReleaseMask
196 | PointerMotionMask
197 | KeyPressMask
198 | KeyReleaseMask;
199 JXChangeWindowAttributes(display, np->window,
200 CWEventMask | CWDontPropagate, &sattr);
201 }
202 JXGrabButton(display, AnyButton, AnyModifier, np->window, True,
203 ButtonPressMask, GrabModeSync, GrabModeAsync, None, None);
204
205 PlaceClient(np, alreadyMapped);
206 ReparentClient(np);
207 XSaveContext(display, np->window, clientContext, (void*)np);
208
209 if(np->state.status & STAT_MAPPED) {
210 JXMapWindow(display, np->window);
211 }
212
213 clientCount += 1;
214
215 if(!alreadyMapped) {
216 RaiseClient(np);
217 }
218
219 if(np->state.status & STAT_OPACITY) {
220 SetOpacity(np, np->state.opacity, 1);
221 } else {
222 SetOpacity(np, settings.inactiveClientOpacity, 1);
223 }
224 if(np->state.status & STAT_STICKY) {
225 SetCardinalAtom(np->window, ATOM_NET_WM_DESKTOP, ~0UL);
226 } else {
227 SetCardinalAtom(np->window, ATOM_NET_WM_DESKTOP, np->state.desktop);
228 }
229
230 /* Shade the client if requested. */
231 if(np->state.status & STAT_SHADED) {
232 np->state.status &= ~STAT_SHADED;
233 ShadeClient(np);
234 }
235
236 /* Minimize the client if requested. */
237 if(np->state.status & STAT_MINIMIZED) {
238 np->state.status &= ~STAT_MINIMIZED;
239 MinimizeClient(np, 0);
240 }
241
242 /* Maximize the client if requested. */
243 if(np->state.maxFlags) {
244 const MaxFlags flags = np->state.maxFlags;
245 np->state.maxFlags = MAX_NONE;
246 MaximizeClient(np, flags);
247 }
248
249 if(np->state.status & STAT_URGENT) {
250 RegisterCallback(URGENCY_DELAY, SignalUrgent, np);
251 }
252
253 /* Update task bars. */
254 AddClientToTaskBar(np);
255
256 /* Make sure we're still in sync */
257 WriteState(np);
258 SendConfigureEvent(np);
259
260 /* Hide the client if we're not on the right desktop. */
261 if(np->state.desktop != currentDesktop
262 && !(np->state.status & STAT_STICKY)) {
263 HideClient(np);
264 }
265
266 ReadClientStrut(np);
267
268 /* Focus transients if their parent has focus. */
269 if(np->owner != None) {
270 if(activeClient && np->owner == activeClient->window) {
271 FocusClient(np);
272 }
273 }
274
275 /* Make the client fullscreen if requested. */
276 if(np->state.status & STAT_FULLSCREEN) {
277 np->state.status &= ~STAT_FULLSCREEN;
278 SetClientFullScreen(np, 1);
279 }
280 ResetBorder(np);
281
282 return np;
283 }
284
285 /** Minimize a client window and all of its transients. */
MinimizeClient(ClientNode * np,char lower)286 void MinimizeClient(ClientNode *np, char lower)
287 {
288 Assert(np);
289 MinimizeTransients(np, lower);
290 RequireRestack();
291 RequireTaskUpdate();
292 }
293
294 /** Minimize all transients as well as the specified client. */
MinimizeTransients(ClientNode * np,char lower)295 void MinimizeTransients(ClientNode *np, char lower)
296 {
297
298 ClientNode *tp;
299 int x;
300
301 Assert(np);
302
303 /* Unmap the window and update its state. */
304 if(np->state.status & (STAT_MAPPED | STAT_SHADED)) {
305 UnmapClient(np);
306 if(np->parent != None) {
307 JXUnmapWindow(display, np->parent);
308 }
309 }
310 np->state.status |= STAT_MINIMIZED;
311
312 /* Minimize transient windows. */
313 for(x = 0; x < LAYER_COUNT; x++) {
314 tp = nodes[x];
315 while(tp) {
316 ClientNode *next = tp->next;
317 if(tp->owner == np->window
318 && (tp->state.status & (STAT_MAPPED | STAT_SHADED))
319 && !(tp->state.status & STAT_MINIMIZED)) {
320 MinimizeTransients(tp, lower);
321 }
322 tp = next;
323 }
324 }
325
326 /* Focus the next window. */
327 if(np->state.status & STAT_ACTIVE) {
328 FocusNextStacked(np);
329 }
330
331 if(lower) {
332 /* Move this client to the end of the layer list. */
333 if(nodeTail[np->state.layer] != np) {
334 if(np->prev) {
335 np->prev->next = np->next;
336 } else {
337 nodes[np->state.layer] = np->next;
338 }
339 np->next->prev = np->prev;
340 tp = nodeTail[np->state.layer];
341 nodeTail[np->state.layer] = np;
342 tp->next = np;
343 np->prev = tp;
344 np->next = NULL;
345 }
346 }
347
348 WriteState(np);
349
350 }
351
352 /** Shade a client. */
ShadeClient(ClientNode * np)353 void ShadeClient(ClientNode *np)
354 {
355
356 Assert(np);
357
358 if((np->state.status & (STAT_SHADED | STAT_FULLSCREEN)) ||
359 !(np->state.border & BORDER_SHADE)) {
360 return;
361 }
362
363 UnmapClient(np);
364 np->state.status |= STAT_SHADED;
365
366 WriteState(np);
367 ResetBorder(np);
368 RequirePagerUpdate();
369
370 }
371
372 /** Unshade a client. */
UnshadeClient(ClientNode * np)373 void UnshadeClient(ClientNode *np)
374 {
375
376 Assert(np);
377
378 if(!(np->state.status & STAT_SHADED)) {
379 return;
380 }
381
382 if(!(np->state.status & (STAT_MINIMIZED | STAT_SDESKTOP))) {
383 JXMapWindow(display, np->window);
384 np->state.status |= STAT_MAPPED;
385 }
386 np->state.status &= ~STAT_SHADED;
387
388 WriteState(np);
389 ResetBorder(np);
390 RefocusClient();
391 RequirePagerUpdate();
392
393 }
394
395 /** Set a client's state to withdrawn. */
SetClientWithdrawn(ClientNode * np)396 void SetClientWithdrawn(ClientNode *np)
397 {
398
399 Assert(np);
400
401 if(activeClient == np) {
402 activeClient = NULL;
403 np->state.status &= ~STAT_ACTIVE;
404 FocusNextStacked(np);
405 }
406
407 if(np->state.status & STAT_MAPPED) {
408 UnmapClient(np);
409 if(np->parent != None) {
410 JXUnmapWindow(display, np->parent);
411 }
412 } else if(np->state.status & STAT_SHADED) {
413 if(!(np->state.status & STAT_MINIMIZED)) {
414 if(np->parent != None) {
415 JXUnmapWindow(display, np->parent);
416 }
417 }
418 }
419
420 np->state.status &= ~STAT_SHADED;
421 np->state.status &= ~STAT_MINIMIZED;
422 np->state.status &= ~STAT_SDESKTOP;
423
424 WriteState(np);
425 RequireTaskUpdate();
426 RequirePagerUpdate();
427
428 }
429
430 /** Restore a window with its transients (helper method). */
RestoreTransients(ClientNode * np,char raise)431 void RestoreTransients(ClientNode *np, char raise)
432 {
433
434 ClientNode *tp;
435 int x;
436
437 Assert(np);
438
439 /* Make sure this window is on the current desktop. */
440 SetClientDesktop(np, currentDesktop);
441
442 /* Restore this window. */
443 if(!(np->state.status & STAT_MAPPED)) {
444 if(np->state.status & STAT_SHADED) {
445 if(np->parent != None) {
446 JXMapWindow(display, np->parent);
447 }
448 } else {
449 JXMapWindow(display, np->window);
450 if(np->parent != None) {
451 JXMapWindow(display, np->parent);
452 }
453 np->state.status |= STAT_MAPPED;
454 }
455 }
456 np->state.status &= ~STAT_MINIMIZED;
457 np->state.status &= ~STAT_SDESKTOP;
458
459 /* Restore transient windows. */
460 for(x = 0; x < LAYER_COUNT; x++) {
461 for(tp = nodes[x]; tp; tp = tp->next) {
462 if(tp->owner == np->window && (tp->state.status & STAT_MINIMIZED)) {
463 RestoreTransients(tp, raise);
464 }
465 }
466 }
467
468 if(raise) {
469 FocusClient(np);
470 RaiseClient(np);
471 }
472 WriteState(np);
473
474 }
475
476 /** Restore a client window and its transients. */
RestoreClient(ClientNode * np,char raise)477 void RestoreClient(ClientNode *np, char raise)
478 {
479 if((np->state.status & STAT_FIXED) && !(np->state.status & STAT_STICKY)) {
480 ChangeDesktop(np->state.desktop);
481 }
482 RestoreTransients(np, raise);
483 RequireRestack();
484 RequireTaskUpdate();
485 }
486
487 /** Set the client layer. This will affect transients. */
SetClientLayer(ClientNode * np,unsigned int layer)488 void SetClientLayer(ClientNode *np, unsigned int layer)
489 {
490
491 ClientNode *tp, *next;
492
493 Assert(np);
494 Assert(layer <= LAST_LAYER);
495
496 if(np->state.layer != layer) {
497 int x;
498
499 /* Loop through all clients so we get transients. */
500 for(x = FIRST_LAYER; x <= LAST_LAYER; x++) {
501 tp = nodes[x];
502 while(tp) {
503 next = tp->next;
504 if(tp == np || tp->owner == np->window) {
505
506 /* Remove from the old node list */
507 if(next) {
508 next->prev = tp->prev;
509 } else {
510 nodeTail[tp->state.layer] = tp->prev;
511 }
512 if(tp->prev) {
513 tp->prev->next = next;
514 } else {
515 nodes[tp->state.layer] = next;
516 }
517
518 /* Insert into the new node list */
519 tp->prev = NULL;
520 tp->next = nodes[layer];
521 if(nodes[layer]) {
522 nodes[layer]->prev = tp;
523 } else {
524 nodeTail[layer] = tp;
525 }
526 nodes[layer] = tp;
527
528 /* Set the new layer */
529 tp->state.layer = layer;
530 WriteState(tp);
531
532 }
533 tp = next;
534 }
535 }
536
537 RequireRestack();
538
539 }
540
541 }
542
543 /** Set a client's sticky status. This will update transients. */
SetClientSticky(ClientNode * np,char isSticky)544 void SetClientSticky(ClientNode *np, char isSticky)
545 {
546
547 ClientNode *tp;
548 int x;
549 char old;
550
551 Assert(np);
552
553 /* Get the old sticky status. */
554 if(np->state.status & STAT_STICKY) {
555 old = 1;
556 } else {
557 old = 0;
558 }
559
560 if(isSticky && !old) {
561
562 /* Change from non-sticky to sticky. */
563
564 for(x = 0; x < LAYER_COUNT; x++) {
565 for(tp = nodes[x]; tp; tp = tp->next) {
566 if(tp == np || tp->owner == np->window) {
567 tp->state.status |= STAT_STICKY;
568 SetCardinalAtom(tp->window, ATOM_NET_WM_DESKTOP, ~0UL);
569 WriteState(tp);
570 }
571 }
572 }
573
574 } else if(!isSticky && old) {
575
576 /* Change from sticky to non-sticky. */
577
578 for(x = 0; x < LAYER_COUNT; x++) {
579 for(tp = nodes[x]; tp; tp = tp->next) {
580 if(tp == np || tp->owner == np->window) {
581 tp->state.status &= ~STAT_STICKY;
582 WriteState(tp);
583 }
584 }
585 }
586
587 /* Since this client is no longer sticky, we need to assign
588 * a desktop. Here we use the current desktop.
589 * Note that SetClientDesktop updates transients (which is good).
590 */
591 SetClientDesktop(np, currentDesktop);
592
593 }
594
595 }
596
597 /** Set a client's desktop. This will update transients. */
SetClientDesktop(ClientNode * np,unsigned int desktop)598 void SetClientDesktop(ClientNode *np, unsigned int desktop)
599 {
600
601 ClientNode *tp;
602
603 Assert(np);
604
605 if(JUNLIKELY(desktop >= settings.desktopCount)) {
606 return;
607 }
608
609 if(!(np->state.status & STAT_STICKY)) {
610 int x;
611 for(x = 0; x < LAYER_COUNT; x++) {
612 for(tp = nodes[x]; tp; tp = tp->next) {
613 if(tp == np || tp->owner == np->window) {
614
615 tp->state.desktop = desktop;
616
617 if(desktop == currentDesktop) {
618 ShowClient(tp);
619 } else {
620 HideClient(tp);
621 }
622
623 SetCardinalAtom(tp->window, ATOM_NET_WM_DESKTOP,
624 tp->state.desktop);
625 }
626 }
627 }
628 RequirePagerUpdate();
629 RequireTaskUpdate();
630 }
631
632 }
633
634 /** Hide a client. This will not update transients. */
HideClient(ClientNode * np)635 void HideClient(ClientNode *np)
636 {
637 if(!(np->state.status & STAT_HIDDEN)) {
638 if(activeClient == np) {
639 activeClient = NULL;
640 }
641 np->state.status |= STAT_HIDDEN;
642 if(np->state.status & (STAT_MAPPED | STAT_SHADED)) {
643 if(np->parent != None) {
644 JXUnmapWindow(display, np->parent);
645 } else {
646 JXUnmapWindow(display, np->window);
647 }
648 }
649 }
650 }
651
652 /** Show a hidden client. This will not update transients. */
ShowClient(ClientNode * np)653 void ShowClient(ClientNode *np)
654 {
655 if(np->state.status & STAT_HIDDEN) {
656 np->state.status &= ~STAT_HIDDEN;
657 if(np->state.status & (STAT_MAPPED | STAT_SHADED)) {
658 if(!(np->state.status & STAT_MINIMIZED)) {
659 if(np->parent != None) {
660 JXMapWindow(display, np->parent);
661 } else {
662 JXMapWindow(display, np->window);
663 }
664 if(np->state.status & STAT_ACTIVE) {
665 FocusClient(np);
666 }
667 }
668 }
669 }
670 }
671
672 /** Maximize a client window. */
MaximizeClient(ClientNode * np,MaxFlags flags)673 void MaximizeClient(ClientNode *np, MaxFlags flags)
674 {
675
676 /* Return if we don't have a client. */
677 if(np == NULL) {
678 return;
679 }
680
681 /* Don't allow maximization of full-screen clients. */
682 if(np->state.status & STAT_FULLSCREEN) {
683 return;
684 }
685 if(!(np->state.border & BORDER_MAX)) {
686 return;
687 }
688
689 if(np->state.status & STAT_SHADED) {
690 UnshadeClient(np);
691 }
692
693 if(np->state.status & STAT_MINIMIZED) {
694 RestoreClient(np, 1);
695 }
696
697 RaiseClient(np);
698 FocusClient(np);
699 if(np->state.maxFlags) {
700 /* Undo existing maximization. */
701 np->x = np->oldx;
702 np->y = np->oldy;
703 np->width = np->oldWidth;
704 np->height = np->oldHeight;
705 np->state.maxFlags = MAX_NONE;
706 }
707 if(flags != MAX_NONE) {
708 /* Maximize if requested. */
709 PlaceMaximizedClient(np, flags);
710 }
711
712 WriteState(np);
713 ResetBorder(np);
714 DrawBorder(np);
715 SendConfigureEvent(np);
716 RequirePagerUpdate();
717
718 }
719
720 /** Maximize a client using its default maximize settings. */
MaximizeClientDefault(ClientNode * np)721 void MaximizeClientDefault(ClientNode *np)
722 {
723
724 MaxFlags flags = MAX_NONE;
725
726 Assert(np);
727
728 if(np->state.maxFlags == MAX_NONE) {
729 if(np->state.border & BORDER_MAX_H) {
730 flags |= MAX_HORIZ;
731 }
732 if(np->state.border & BORDER_MAX_V) {
733 flags |= MAX_VERT;
734 }
735 }
736
737 MaximizeClient(np, flags);
738
739 }
740
741 /** Set a client's full screen state. */
SetClientFullScreen(ClientNode * np,char fullScreen)742 void SetClientFullScreen(ClientNode *np, char fullScreen)
743 {
744
745 XEvent event;
746 int north, south, east, west;
747 BoundingBox box;
748 const ScreenType *sp;
749
750 Assert(np);
751
752 /* Make sure there's something to do. */
753 if(!fullScreen == !(np->state.status & STAT_FULLSCREEN)) {
754 return;
755 }
756 if(!(np->state.border & BORDER_FULLSCREEN)) {
757 return;
758 }
759
760 if(np->state.status & STAT_SHADED) {
761 UnshadeClient(np);
762 }
763
764 if(fullScreen) {
765
766 np->state.status |= STAT_FULLSCREEN;
767
768 if(!(np->state.maxFlags)) {
769 np->oldx = np->x;
770 np->oldy = np->y;
771 np->oldWidth = np->width;
772 np->oldHeight = np->height;
773 }
774
775 sp = GetCurrentScreen(np->x, np->y);
776 GetScreenBounds(sp, &box);
777
778 GetBorderSize(&np->state, &north, &south, &east, &west);
779 box.x += west;
780 box.y += north;
781 box.width -= east + west;
782 box.height -= north + south;
783
784 np->x = box.x;
785 np->y = box.y;
786 np->width = box.width;
787 np->height = box.height;
788 ResetBorder(np);
789
790 } else {
791
792 np->state.status &= ~STAT_FULLSCREEN;
793
794 np->x = np->oldx;
795 np->y = np->oldy;
796 np->width = np->oldWidth;
797 np->height = np->oldHeight;
798 ConstrainSize(np);
799 ConstrainPosition(np);
800
801 if(np->state.maxFlags != MAX_NONE) {
802 PlaceMaximizedClient(np, np->state.maxFlags);
803 }
804
805 ResetBorder(np);
806
807 event.type = MapRequest;
808 event.xmaprequest.send_event = True;
809 event.xmaprequest.display = display;
810 event.xmaprequest.parent = np->parent;
811 event.xmaprequest.window = np->window;
812 JXSendEvent(display, rootWindow, False,
813 SubstructureRedirectMask, &event);
814
815 }
816
817 WriteState(np);
818 SendConfigureEvent(np);
819 RequireRestack();
820
821 }
822
823 /** Set the active client. */
FocusClient(ClientNode * np)824 void FocusClient(ClientNode *np)
825 {
826 if(np->state.status & STAT_HIDDEN) {
827 return;
828 }
829 if(!(np->state.status & (STAT_CANFOCUS | STAT_TAKEFOCUS))) {
830 return;
831 }
832
833 if(activeClient != np || !(np->state.status & STAT_ACTIVE)) {
834 if(activeClient) {
835 activeClient->state.status &= ~STAT_ACTIVE;
836 if(!(activeClient->state.status & STAT_OPACITY)) {
837 SetOpacity(activeClient, settings.inactiveClientOpacity, 0);
838 }
839 DrawBorder(activeClient);
840 WriteNetState(activeClient);
841 }
842 np->state.status |= STAT_ACTIVE;
843 activeClient = np;
844 if(!(np->state.status & STAT_OPACITY)) {
845 SetOpacity(np, settings.activeClientOpacity, 0);
846 }
847
848 DrawBorder(np);
849 RequirePagerUpdate();
850 RequireTaskUpdate();
851 }
852
853 if(np->state.status & STAT_MAPPED) {
854 UpdateClientColormap(np);
855 SetWindowAtom(rootWindow, ATOM_NET_ACTIVE_WINDOW, np->window);
856 WriteNetState(np);
857 if(np->state.status & STAT_CANFOCUS) {
858 JXSetInputFocus(display, np->window, RevertToParent, eventTime);
859 }
860 if(np->state.status & STAT_TAKEFOCUS) {
861 SendClientMessage(np->window, ATOM_WM_PROTOCOLS, ATOM_WM_TAKE_FOCUS);
862 }
863 } else {
864 JXSetInputFocus(display, rootWindow, RevertToParent, eventTime);
865 }
866
867 }
868
869
870 /** Refocus the active client (if there is one). */
RefocusClient(void)871 void RefocusClient(void)
872 {
873 if(activeClient) {
874 FocusClient(activeClient);
875 }
876 }
877
878 /** Send a delete message to a client. */
DeleteClient(ClientNode * np)879 void DeleteClient(ClientNode *np)
880 {
881 Assert(np);
882 ReadWMProtocols(np->window, &np->state);
883 if(np->state.status & STAT_DELETE) {
884 SendClientMessage(np->window, ATOM_WM_PROTOCOLS, ATOM_WM_DELETE_WINDOW);
885 } else {
886 KillClient(np);
887 }
888 }
889
890 /** Callback to kill a client after a confirm dialog. */
KillClientHandler(ClientNode * np)891 void KillClientHandler(ClientNode *np)
892 {
893 if(np == activeClient) {
894 FocusNextStacked(np);
895 }
896
897 JXKillClient(display, np->window);
898 }
899
900 /** Kill a client window. */
KillClient(ClientNode * np)901 void KillClient(ClientNode *np)
902 {
903 Assert(np);
904 ShowConfirmDialog(np, KillClientHandler,
905 _("Kill this window?"),
906 _("This may cause data to be lost!"),
907 NULL);
908 }
909
910 /** Place transients on top of the owner. */
RestackTransients(const ClientNode * np)911 void RestackTransients(const ClientNode *np)
912 {
913 ClientNode *tp;
914 unsigned int layer;
915
916 /* Place any transient windows on top of the owner */
917 for(layer = 0; layer < LAYER_COUNT; layer++) {
918 for(tp = nodes[layer]; tp; tp = tp->next) {
919 if(tp->owner == np->window && tp->prev) {
920
921 ClientNode *next = tp->next;
922
923 tp->prev->next = tp->next;
924 if(tp->next) {
925 tp->next->prev = tp->prev;
926 } else {
927 nodeTail[tp->state.layer] = tp->prev;
928 }
929 tp->next = nodes[tp->state.layer];
930 nodes[tp->state.layer]->prev = tp;
931 tp->prev = NULL;
932 nodes[tp->state.layer] = tp;
933
934 tp = next;
935
936 }
937
938 /* tp will be tp->next if the above code is executed. */
939 /* Thus, if it is NULL, we are done with this layer. */
940 if(!tp) {
941 break;
942 }
943 }
944 }
945 }
946
947 /** Raise the client. This will affect transients. */
RaiseClient(ClientNode * np)948 void RaiseClient(ClientNode *np)
949 {
950
951 Assert(np);
952
953 if(nodes[np->state.layer] != np) {
954
955 /* Raise the window */
956 Assert(np->prev);
957 np->prev->next = np->next;
958 if(np->next) {
959 np->next->prev = np->prev;
960 } else {
961 nodeTail[np->state.layer] = np->prev;
962 }
963 np->next = nodes[np->state.layer];
964 nodes[np->state.layer]->prev = np;
965 np->prev = NULL;
966 nodes[np->state.layer] = np;
967
968 }
969
970 RestackTransients(np);
971 RequireRestack();
972
973 }
974
975 /** Restack a client window. This will not affect transients. */
RestackClient(ClientNode * np,Window above,int detail)976 void RestackClient(ClientNode *np, Window above, int detail)
977 {
978
979 ClientNode *tp;
980 char inserted = 0;
981
982 /* Remove from the window list. */
983 if(np->prev) {
984 np->prev->next = np->next;
985 } else {
986 nodes[np->state.layer] = np->next;
987 }
988 if(np->next) {
989 np->next->prev = np->prev;
990 } else {
991 nodeTail[np->state.layer] = np->prev;
992 }
993
994 /* Insert back into the window list. */
995 if(above != None && above != np->window) {
996
997 /* Insert relative to some other window. */
998 char found = 0;
999 for(tp = nodes[np->state.layer]; tp; tp = tp->next) {
1000 if(tp == np) {
1001 found = 1;
1002 } else if(tp->window == above) {
1003 char insert_before = 0;
1004 inserted = 1;
1005 switch(detail) {
1006 case Above:
1007 case TopIf:
1008 insert_before = 1;
1009 break;
1010 case Below:
1011 case BottomIf:
1012 insert_before = 0;
1013 break;
1014 case Opposite:
1015 insert_before = !found;
1016 break;
1017 }
1018 if(insert_before) {
1019
1020 /* Insert before this window. */
1021 np->prev = tp->prev;
1022 np->next = tp;
1023 if(tp->prev) {
1024 tp->prev->next = np;
1025 } else {
1026 nodes[np->state.layer] = np;
1027 }
1028 tp->prev = np;
1029
1030 } else {
1031
1032 /* Insert after this window. */
1033 np->prev = tp;
1034 np->next = tp->next;
1035 if(tp->next) {
1036 tp->next->prev = np;
1037 } else {
1038 nodeTail[np->state.layer] = np;
1039 }
1040 tp->next = np;
1041
1042 }
1043 break;
1044 }
1045 }
1046 }
1047 if(!inserted) {
1048
1049 /* Insert absolute for the layer. */
1050 if(detail == Below || detail == BottomIf) {
1051
1052 /* Insert to the bottom of the stack. */
1053 np->next = NULL;
1054 np->prev = nodeTail[np->state.layer];
1055 if(nodeTail[np->state.layer]) {
1056 nodeTail[np->state.layer]->next = np;
1057 } else {
1058 nodes[np->state.layer] = np;
1059 }
1060 nodeTail[np->state.layer] = np;
1061
1062 } else {
1063
1064 /* Insert at the top of the stack. */
1065 np->next = nodes[np->state.layer];
1066 np->prev = NULL;
1067 if(nodes[np->state.layer]) {
1068 nodes[np->state.layer]->prev = np;
1069 } else {
1070 nodeTail[np->state.layer] = np;
1071 }
1072 nodes[np->state.layer] = np;
1073
1074 }
1075 }
1076
1077 RestackTransients(np);
1078 RequireRestack();
1079
1080 }
1081
1082 /** Restack the clients according the way we want them. */
RestackClients(void)1083 void RestackClients(void)
1084 {
1085
1086 TrayType *tp;
1087 ClientNode *np;
1088 unsigned int layer, index;
1089 int trayCount;
1090 Window *stack;
1091 Window fw;
1092
1093 if(JUNLIKELY(shouldExit)) {
1094 return;
1095 }
1096
1097 /* Allocate memory for restacking. */
1098 trayCount = GetTrayCount();
1099 stack = AllocateStack((clientCount + trayCount) * sizeof(Window));
1100
1101 /* Prepare the stacking array. */
1102 fw = None;
1103 index = 0;
1104 if(activeClient && (activeClient->state.status & STAT_FULLSCREEN)) {
1105 fw = activeClient->window;
1106 for(np = nodes[activeClient->state.layer]; np; np = np->next) {
1107 if(np->owner == fw) {
1108 if(np->parent != None) {
1109 stack[index] = np->parent;
1110 } else {
1111 stack[index] = np->window;
1112 }
1113 index += 1;
1114 }
1115 }
1116 if(activeClient->parent != None) {
1117 stack[index] = activeClient->parent;
1118 } else {
1119 stack[index] = activeClient->window;
1120 }
1121 index += 1;
1122 }
1123 layer = LAST_LAYER;
1124 for(;;) {
1125
1126 for(np = nodes[layer]; np; np = np->next) {
1127 if( (np->state.status & (STAT_MAPPED | STAT_SHADED))
1128 && !(np->state.status & STAT_HIDDEN)) {
1129 if(fw != None && (np->window == fw || np->owner == fw)) {
1130 continue;
1131 }
1132 if(np->parent != None) {
1133 stack[index] = np->parent;
1134 } else {
1135 stack[index] = np->window;
1136 }
1137 index += 1;
1138 }
1139 }
1140
1141 for(tp = GetTrays(); tp; tp = tp->next) {
1142 if(layer == tp->layer) {
1143 stack[index] = tp->window;
1144 index += 1;
1145 }
1146 }
1147
1148 if(layer == FIRST_LAYER) {
1149 break;
1150 }
1151 layer -= 1;
1152
1153 }
1154
1155 JXRestackWindows(display, stack, index);
1156
1157 ReleaseStack(stack);
1158 UpdateNetClientList();
1159 RequirePagerUpdate();
1160
1161 }
1162
1163 /** Send a client message to a window. */
SendClientMessage(Window w,AtomType type,AtomType message)1164 void SendClientMessage(Window w, AtomType type, AtomType message)
1165 {
1166
1167 XEvent event;
1168 int status;
1169
1170 memset(&event, 0, sizeof(event));
1171 event.xclient.type = ClientMessage;
1172 event.xclient.window = w;
1173 event.xclient.message_type = atoms[type];
1174 event.xclient.format = 32;
1175 event.xclient.data.l[0] = atoms[message];
1176 event.xclient.data.l[1] = eventTime;
1177
1178 status = JXSendEvent(display, w, False, 0, &event);
1179 if(JUNLIKELY(status == False)) {
1180 Debug("SendClientMessage failed");
1181 }
1182
1183 }
1184
1185 /** Remove a client window from management. */
RemoveClient(ClientNode * np)1186 void RemoveClient(ClientNode *np)
1187 {
1188
1189 ColormapNode *cp;
1190
1191 Assert(np);
1192 Assert(np->window != None);
1193
1194 /* Remove this client from the client list */
1195 if(np->next) {
1196 np->next->prev = np->prev;
1197 } else {
1198 nodeTail[np->state.layer] = np->prev;
1199 }
1200 if(np->prev) {
1201 np->prev->next = np->next;
1202 } else {
1203 nodes[np->state.layer] = np->next;
1204 }
1205 clientCount -= 1;
1206 XDeleteContext(display, np->window, clientContext);
1207 if(np->parent != None) {
1208 XDeleteContext(display, np->parent, frameContext);
1209 }
1210
1211 if(np->state.status & STAT_URGENT) {
1212 UnregisterCallback(SignalUrgent, np);
1213 }
1214
1215 /* Make sure this client isn't active */
1216 if(activeClient == np && !shouldExit) {
1217 FocusNextStacked(np);
1218 }
1219 if(activeClient == np) {
1220
1221 /* Must be the last client. */
1222 SetWindowAtom(rootWindow, ATOM_NET_ACTIVE_WINDOW, None);
1223 activeClient = NULL;
1224 JXSetInputFocus(display, rootWindow, RevertToParent, eventTime);
1225
1226 }
1227
1228 /* If the window manager is exiting (ie, not the client), then
1229 * reparent etc. */
1230 if(shouldExit && !(np->state.status & STAT_WMDIALOG)) {
1231 if(np->state.maxFlags) {
1232 np->x = np->oldx;
1233 np->y = np->oldy;
1234 np->width = np->oldWidth;
1235 np->height = np->oldHeight;
1236 JXMoveResizeWindow(display, np->window,
1237 np->x, np->y, np->width, np->height);
1238 }
1239 GravitateClient(np, 1);
1240 if((np->state.status & STAT_HIDDEN)
1241 || (!(np->state.status & STAT_MAPPED)
1242 && (np->state.status & (STAT_MINIMIZED | STAT_SHADED)))) {
1243 JXMapWindow(display, np->window);
1244 }
1245 JXUngrabButton(display, AnyButton, AnyModifier, np->window);
1246 JXReparentWindow(display, np->window, rootWindow, np->x, np->y);
1247 JXRemoveFromSaveSet(display, np->window);
1248 }
1249
1250 /* Destroy the parent */
1251 if(np->parent) {
1252 JXDestroyWindow(display, np->parent);
1253 }
1254
1255 if(np->name) {
1256 Release(np->name);
1257 }
1258 if(np->instanceName) {
1259 JXFree(np->instanceName);
1260 }
1261 if(np->className) {
1262 JXFree(np->className);
1263 }
1264
1265 RemoveClientFromTaskBar(np);
1266 RemoveClientStrut(np);
1267
1268 while(np->colormaps) {
1269 cp = np->colormaps->next;
1270 Release(np->colormaps);
1271 np->colormaps = cp;
1272 }
1273
1274 DestroyIcon(np->icon);
1275
1276 Release(np);
1277
1278 RequireRestack();
1279
1280 }
1281
1282 /** Get the active client (possibly NULL). */
GetActiveClient(void)1283 ClientNode *GetActiveClient(void)
1284 {
1285 return activeClient;
1286 }
1287
1288 /** Find a client by parent or window. */
FindClient(Window w)1289 ClientNode *FindClient(Window w)
1290 {
1291 ClientNode *np;
1292 np = FindClientByWindow(w);
1293 if(!np) {
1294 np = FindClientByParent(w);
1295 }
1296 return np;
1297 }
1298
1299 /** Find a client by window. */
FindClientByWindow(Window w)1300 ClientNode *FindClientByWindow(Window w)
1301 {
1302 ClientNode *np;
1303 if(!XFindContext(display, w, clientContext, (void*)&np)) {
1304 return np;
1305 } else {
1306 return NULL;
1307 }
1308 }
1309
1310 /** Find a client by its frame window. */
FindClientByParent(Window p)1311 ClientNode *FindClientByParent(Window p)
1312 {
1313 ClientNode *np;
1314 if(!XFindContext(display, p, frameContext, (void*)&np)) {
1315 return np;
1316 } else {
1317 return NULL;
1318 }
1319 }
1320
1321 /** Reparent a client window. */
ReparentClient(ClientNode * np)1322 void ReparentClient(ClientNode *np)
1323 {
1324 XSetWindowAttributes attr;
1325 XEvent event;
1326 int attrMask;
1327 int x, y, width, height;
1328 int north, south, east, west;
1329
1330 if((np->state.border & (BORDER_TITLE | BORDER_OUTLINE)) == 0) {
1331
1332 if(np->parent == None) {
1333 return;
1334 }
1335
1336 JXReparentWindow(display, np->window, rootWindow, np->x, np->y);
1337 XDeleteContext(display, np->parent, frameContext);
1338 JXDestroyWindow(display, np->parent);
1339 np->parent = None;
1340
1341 } else {
1342
1343 if(np->parent != None) {
1344 return;
1345 }
1346
1347 attrMask = 0;
1348
1349 /* We can't use PointerMotionHint mask here since the exact location
1350 * of the mouse on the frame is important. */
1351 attrMask |= CWEventMask;
1352 attr.event_mask
1353 = ButtonPressMask
1354 | ButtonReleaseMask
1355 | ExposureMask
1356 | PointerMotionMask
1357 | SubstructureRedirectMask
1358 | SubstructureNotifyMask
1359 | EnterWindowMask
1360 | LeaveWindowMask
1361 | KeyPressMask
1362 | KeyReleaseMask;
1363
1364 attrMask |= CWDontPropagate;
1365 attr.do_not_propagate_mask = ButtonPressMask | ButtonReleaseMask;
1366
1367 attrMask |= CWBackPixel;
1368 attr.background_pixel = colors[COLOR_TITLE_BG2];
1369
1370 attrMask |= CWBorderPixel;
1371 attr.border_pixel = 0;
1372
1373 x = np->x;
1374 y = np->y;
1375 width = np->width;
1376 height = np->height;
1377 GetBorderSize(&np->state, &north, &south, &east, &west);
1378 x -= west;
1379 y -= north;
1380 width += east + west;
1381 height += north + south;
1382
1383 /* Create the frame window. */
1384 np->parent = JXCreateWindow(display, rootWindow, x, y, width, height,
1385 0, rootDepth, InputOutput,
1386 rootVisual, attrMask, &attr);
1387 XSaveContext(display, np->parent, frameContext, (void*)np);
1388
1389 JXSetWindowBorderWidth(display, np->window, 0);
1390
1391 /* Reparent the client window. */
1392 JXReparentWindow(display, np->window, np->parent, west, north);
1393
1394 if(np->state.status & STAT_MAPPED) {
1395 JXMapWindow(display, np->parent);
1396 }
1397 }
1398
1399 JXSync(display, False);
1400 JXCheckTypedWindowEvent(display, np->window, UnmapNotify, &event);
1401
1402 }
1403
1404 /** Send a configure event to a client window. */
SendConfigureEvent(ClientNode * np)1405 void SendConfigureEvent(ClientNode *np)
1406 {
1407
1408 XConfigureEvent event;
1409 const ScreenType *sp;
1410
1411 Assert(np);
1412
1413 memset(&event, 0, sizeof(event));
1414 event.display = display;
1415 event.type = ConfigureNotify;
1416 event.event = np->window;
1417 event.window = np->window;
1418 if(np->state.status & STAT_FULLSCREEN) {
1419 sp = GetCurrentScreen(np->x, np->y);
1420 event.x = sp->x;
1421 event.y = sp->y;
1422 event.width = sp->width;
1423 event.height = sp->height;
1424 } else {
1425 event.x = np->x;
1426 event.y = np->y;
1427 event.width = np->width;
1428 event.height = np->height;
1429 }
1430
1431 JXSendEvent(display, np->window, False, StructureNotifyMask,
1432 (XEvent*)&event);
1433
1434 }
1435
1436 /** Update a window's colormap.
1437 * A call to this function indicates that the colormap(s) for the given
1438 * client changed. This will change the active colormap(s) if the given
1439 * client is active.
1440 */
UpdateClientColormap(ClientNode * np)1441 void UpdateClientColormap(ClientNode *np)
1442 {
1443
1444 Assert(np);
1445
1446 if(np == activeClient) {
1447
1448 ColormapNode *cp = np->colormaps;
1449 char wasInstalled = 0;
1450 while(cp) {
1451 XWindowAttributes attr;
1452 if(JXGetWindowAttributes(display, cp->window, &attr)) {
1453 if(attr.colormap != None) {
1454 if(attr.colormap == np->cmap) {
1455 wasInstalled = 1;
1456 }
1457 JXInstallColormap(display, attr.colormap);
1458 }
1459 }
1460 cp = cp->next;
1461 }
1462
1463 if(!wasInstalled && np->cmap != None) {
1464 JXInstallColormap(display, np->cmap);
1465 }
1466
1467 }
1468
1469 }
1470
1471 /** Update callback for clients with the urgency hint set. */
SignalUrgent(const TimeType * now,int x,int y,Window w,void * data)1472 void SignalUrgent(const TimeType *now, int x, int y, Window w, void *data)
1473 {
1474
1475 ClientNode *np = (ClientNode*)data;
1476
1477 /* Redraw borders. */
1478 if(np->state.status & STAT_FLASH) {
1479 np->state.status &= ~STAT_FLASH;
1480 } else if(!(np->state.status & STAT_NOTURGENT)) {
1481 np->state.status |= STAT_FLASH;
1482 }
1483 DrawBorder(np);
1484 RequireTaskUpdate();
1485 RequirePagerUpdate();
1486
1487 }
1488
1489 /** Unmap a client window and consume the UnmapNotify event. */
UnmapClient(ClientNode * np)1490 void UnmapClient(ClientNode *np)
1491 {
1492 if(np->state.status & STAT_MAPPED) {
1493 np->state.status &= ~STAT_MAPPED;
1494 JXUnmapWindow(display, np->window);
1495 }
1496 }
1497
1498