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