1 module old;
2 import core.memory;
3 
4 import core.sys.posix.signal;
5 import core.sys.posix.sys.wait;
6 import core.sys.posix.unistd;
7 
8 import std.c.locale;
9 import std.c.string;
10 import std.c.stdlib;
11 
12 import std.stdio;
13 import std.string;
14 import std.algorithm;
15 import std.conv;
16 import std.process;
17 import std.traits;
18 
19 import deimos.X11.X;
20 import deimos.X11.Xlib;
21 import deimos.X11.keysymdef;
22 import deimos.X11.Xutil;
23 import deimos.X11.Xatom;
24 
25 import types;
26 import kernel;
27 import legacy;
28 import utils;
29 import config;
30 import cboxapp;
31 import window;
32 import helper.x11;
33 import gui.cursor;
34 import gui.font;
35 import theme.layout;
36 import theme.manager;
37 import gui.bar;
38 import monitor;
39 
40 void updatenumlockmask() 
41 {
42     XModifierKeymap *modmap;
43 
44     numlockmask = 0;
45     modmap = XGetModifierMapping(AppDisplay.instance().dpy);
46     foreach_reverse(i; 0..7) {
47         if(numlockmask == 0) {
48             break;
49         }
50         foreach_reverse(j; 0..modmap.max_keypermod-1) {
51             if(modmap.modifiermap[i * modmap.max_keypermod + j] ==
52                     XKeysymToKeycode(AppDisplay.instance().dpy, XK_Num_Lock)) {
53                 numlockmask = (1 << i);
54                 break;
55             }
56         }
57     }
58     XFreeModifiermap(modmap);
59 }
60 
61 void updateclientlist() 
62 {
63     
64     Client *c;
65     Monitor *m;
66 
67     XDeleteProperty(AppDisplay.instance().dpy, rootWin, netatom[NetClientList]);
68     for(m = mons; m; m = m.next)
69         for(c = m.clients; c; c = c.next)
70             XChangeProperty(AppDisplay.instance().dpy, rootWin, netatom[NetClientList],
71                             XA_WINDOW, 32, PropModeAppend,
72                             cast(ubyte *)&(c.win), 1);
73 }
74 
75 
76 bool getrootptr(int *x, int *y) 
77 {
78     int di;
79     uint dui;
80     Window dummy;
81 
82     return XQueryPointer(AppDisplay.instance().dpy, rootWin, &dummy, &dummy, x, y, &di, &di, &dui) != 0;
83 }
84 
85 void setfocus(Client *c) 
86 {
87     
88     if(!c.neverfocus) {
89         XSetInputFocus(AppDisplay.instance().dpy, c.win, RevertToPointerRoot, CurrentTime);
90         XChangeProperty(AppDisplay.instance().dpy, rootWin, netatom[NetActiveWindow],
91                         XA_WINDOW, 32, PropModeReplace,
92                         cast(ubyte *) &(c.win), 1);
93     }
94     sendevent(c, wmatom[WMTakeFocus]);
95 }
96 
97 
98 bool sendevent(Client *c, Atom proto) 
99 {
100     int n;
101     Atom *protocols;
102     bool exists = false;
103     XEvent ev;
104 
105     if(XGetWMProtocols(AppDisplay.instance().dpy, c.win, &protocols, &n)) {
106         while(!exists && n--)
107             exists = protocols[n] == proto;
108         XFree(protocols);
109     }
110     if(exists) {
111         ev.type = ClientMessage;
112         ev.xclient.window = c.win;
113         ev.xclient.message_type = wmatom[WMProtocols];
114         ev.xclient.format = 32;
115         ev.xclient.data.l[0] = proto;
116         ev.xclient.data.l[1] = CurrentTime;
117         XSendEvent(AppDisplay.instance().dpy, c.win, false, NoEventMask, &ev);
118     }
119     return exists;
120 }
121 
122 Client* nexttiled(Client *c) 
123 {
124     return c.range!"next".find!(a => !a.isfloating && ISVISIBLE(a)).front;
125 }
126 
127 void pop(Client *c) 
128 {
129     detach(c);
130     attach(c);
131     focus(c);
132     arrange(c.mon);
133 }
134 
135 void setfullscreen(Client *c, bool fullscreen) 
136 {
137     if(fullscreen) {
138         XChangeProperty(AppDisplay.instance().dpy, c.win, netatom[NetWMState], XA_ATOM, 32,
139                         PropModeReplace, cast(ubyte*)&netatom[NetWMFullscreen], 1);
140         c.isfullscreen = true;
141         c.oldstate = c.isfloating;
142         c.oldbw = c.bw;
143         c.bw = 0;
144         c.isfloating = true;
145         resizeclient(c, c.mon.mx, c.mon.my, c.mon.mw, c.mon.mh);
146         XRaiseWindow(AppDisplay.instance().dpy, c.win);
147     } else {
148         XChangeProperty(AppDisplay.instance().dpy, c.win, netatom[NetWMState], XA_ATOM, 32,
149                         PropModeReplace, cast(ubyte*)0, 0);
150         c.isfullscreen = false;
151         c.isfloating = c.oldstate;
152         c.bw = c.oldbw;
153         c.x = c.oldx;
154         c.y = c.oldy;
155         c.w = c.oldw;
156         c.h = c.oldh;
157         resizeclient(c, c.x, c.y, c.w, c.h);
158         arrange(c.mon);
159     }
160 }
161 
162 void resize(Client *c, int x, int y, int w, int h, bool interact) 
163 {
164     if(applysizehints(c, x, y, w, h, interact))
165         resizeclient(c, x, y, w, h);
166 }
167 
168 void resizeclient(Client *c, int x, int y, int w, int h) 
169 {
170     XWindowChanges wc;
171 
172     c.oldx = c.x;
173     c.x = wc.x = x;
174     c.oldy = c.y;
175     c.y = wc.y = y;
176     c.oldw = c.w;
177     c.w = wc.width = w;
178     c.oldh = c.h;
179     c.h = wc.height = h;
180     wc.border_width = c.bw;
181     XConfigureWindow(AppDisplay.instance().dpy, c.win, CWX|CWY|CWWidth|CWHeight|CWBorderWidth, &wc);
182     configure(c);
183     XSync(AppDisplay.instance().dpy, false);
184 }
185 
186 void sendmon(Client *c, Monitor *m) 
187 {
188     if(c.mon == m)
189         return;
190     unfocus(c, true);
191     detach(c);
192     detachstack(c);
193     c.mon = m;
194     c.tags = m.tagset[m.seltags]; /* assign tags of target monitor */
195     attach(c);
196     attachstack(c);
197     focus(null);
198     arrange(null);
199 }
200 
201 
202 bool applysizehints(Client *c, ref int x, ref int y, ref int w, ref int h, bool interact) 
203 {
204     bool baseismin;
205     Monitor *m = c.mon;
206 
207     /* set minimum possible */
208     w = max(1, w);
209     h = max(1, h);
210     if(interact) {
211         if(x > sw)
212             x = sw - WIDTH(c);
213         if(y > sh)
214             y = sh - HEIGHT(c);
215         if(x + w + 2 * c.bw < 0)
216             x = 0;
217         if(y + h + 2 * c.bw < 0)
218             y = 0;
219     } else {
220         if(x >= m.wx + m.ww)
221             x = m.wx + m.ww - WIDTH(c);
222         if(y >= m.wy + m.wh)
223             y = m.wy + m.wh - HEIGHT(c);
224         if(x + w + 2 * c.bw <= m.wx)
225             x = m.wx;
226         if(y + h + 2 * c.bw <= m.wy)
227             y = m.wy;
228     }
229     if(h < bh)
230         h = bh;
231     if(w < bh)
232         w = bh;
233     if(resizehints || c.isfloating || !c.mon.lt[c.mon.sellt].arrange) {
234         /* see last two sentences in ICCCM 4.1.2.3 */
235         baseismin = c.basew == c.minw && c.baseh == c.minh;
236         if(!baseismin) { /* temporarily remove base dimensions */
237             w -= c.basew;
238             h -= c.baseh;
239         }
240 import std.math :
241         nearbyint;
242         /* adjust for aspect limits */
243         if(c.mina > 0 && c.maxa > 0) {
244             if(c.maxa < float(w) / h)
245                 w = cast(int)(h * c.maxa + 0.5);
246             else if(c.mina < float(h) / w)
247                 h = cast(int)(w * c.mina + 0.5);
248         }
249         if(baseismin) { /* increment calculation requires this */
250             w -= c.basew;
251             h -= c.baseh;
252         }
253         /* adjust for increment value */
254         if(c.incw)
255             w -= w % c.incw;
256         if(c.inch)
257             h -= h % c.inch;
258         /* restore base dimensions */
259         w = max(w + c.basew, c.minw);
260         h = max(h + c.baseh, c.minh);
261         if(c.maxw)
262             w = min(w, c.maxw);
263         if(c.maxh)
264             h = min(h, c.maxh);
265     }
266     return x != c.x || y != c.y || w != c.w || h != c.h;
267 }
268 
269 void clientmessage(XEvent *e) 
270 {
271     XClientMessageEvent *cme = &e.xclient;
272     Client *c = wintoclient(cme.window);
273 
274     if(!c)
275         return;
276     if(cme.message_type == netatom[NetWMState]) {
277         if(cme.data.l[1] == netatom[NetWMFullscreen] || cme.data.l[2] == netatom[NetWMFullscreen]) {
278             setfullscreen(c, (cme.data.l[0] == 1 /* _NET_WM_STATE_ADD    */
279                               || (cme.data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */ && !c.isfullscreen)));
280         }
281     } else if(cme.message_type == netatom[NetActiveWindow]) {
282         if(!ISVISIBLE(c)) {
283             c.mon.seltags ^= 1;
284             c.mon.tagset[c.mon.seltags] = c.tags;
285         }
286         pop(c);
287     }
288 }
289 
290 void togglefloating(const Arg *arg) 
291 {
292     if(!selmon.sel)
293         return;
294     if(selmon.sel.isfullscreen) /* no support for fullscreen windows */
295         return;
296     selmon.sel.isfloating = !selmon.sel.isfloating || selmon.sel.isfixed;
297     if(selmon.sel.isfloating)
298         resize(selmon.sel, selmon.sel.x, selmon.sel.y,
299                selmon.sel.w, selmon.sel.h, false);
300     arrange(selmon);
301 }
302 
303 void tag(const Arg *arg) {
304     
305     if(selmon.sel && arg.ui & TAGMASK) {
306         selmon.sel.tags = arg.ui & TAGMASK;
307         focus(null);
308         arrange(selmon);
309     }
310 }
311 
312 void tagmon(const Arg *arg) {
313     
314     if(!selmon.sel || !mons.next)
315         return;
316     sendmon(selmon.sel, dirtomon(arg.i));
317 }
318 
319 void toggletag(const Arg *arg) {
320     
321     uint newtags;
322 
323     if(!selmon.sel)
324         return;
325     newtags = selmon.sel.tags ^ (arg.ui & TAGMASK);
326     if(newtags) {
327         selmon.sel.tags = newtags;
328         focus(null);
329         arrange(selmon);
330     }
331 }
332 
333 void toggleview(const Arg *arg) {
334     
335     uint newtagset = selmon.tagset[selmon.seltags] ^ (arg.ui & TAGMASK);
336 
337     if(newtagset) {
338         selmon.tagset[selmon.seltags] = newtagset;
339         focus(null);
340         arrange(selmon);
341     }
342 }
343 
344 void zoom(const Arg *arg) {
345     
346     Client *c = selmon.sel;
347 
348     if(!selmon.lt[selmon.sellt].arrange ||
349             (selmon.sel && selmon.sel.isfloating)) {
350         return;
351     }
352     if(c == nexttiled(selmon.clients)) {
353         if(c) {
354             c = nexttiled(c.next);
355         }
356         if(!c) {
357             return;
358         }
359     }
360     pop(c);
361 }
362 
363 
364 void view(const Arg *arg) {
365     
366     if((arg.ui & TAGMASK) == selmon.tagset[selmon.seltags]) {
367         return;
368     }
369     selmon.seltags ^= 1; /* toggle sel tagset */
370     if(arg.ui & TAGMASK) {
371         selmon.tagset[selmon.seltags] = arg.ui & TAGMASK;
372     }
373     focus(null);
374     arrange(selmon);
375 }
376 
377 void killclient(const Arg *arg) {
378     
379     if(!selmon.sel)
380         return;
381 
382     if(!sendevent(selmon.sel, wmatom[WMDelete])) {
383         XGrabServer(AppDisplay.instance().dpy);
384         XSetErrorHandler(&xerrordummy);
385         XSetCloseDownMode(AppDisplay.instance().dpy, CloseDownMode.DestroyAll);
386         XKillClient(AppDisplay.instance().dpy, selmon.sel.win);
387         XSync(AppDisplay.instance().dpy, false);
388         XSetErrorHandler(&xerror);
389         XUngrabServer(AppDisplay.instance().dpy);
390     }
391 }
392 
393 void incnmaster(const Arg *arg) {
394     
395     selmon.nmaster = max(selmon.nmaster + arg.i, 0);
396     arrange(selmon);
397 }
398 
399 void setmfact(const Arg *arg) {
400     
401     float f;
402 
403     if(!arg || !selmon.lt[selmon.sellt].arrange)
404         return;
405     f = arg.f < 1.0 ? arg.f + selmon.mfact : arg.f - 1.0;
406     if(f < 0.1 || f > 0.9)
407         return;
408     selmon.mfact = f;
409     arrange(selmon);
410 }
411 
412 
413 bool updategeom() 
414 {
415     
416     bool dirty = false;
417 
418     Bool isXineramaActive = false;
419 
420     version(XINERAMA) {
421         isXineramaActive = XineramaIsActive(AppDisplay.instance().dpy);
422     }
423  
424     if(isXineramaActive) {
425         version(XINERAMA) {
426             import std.range;
427             int nn;
428             Client *c;
429             XineramaScreenInfo *info = XineramaQueryScreens(AppDisplay.instance().dpy, &nn);
430             auto n = mons.range.walkLength;
431 
432             XineramaScreenInfo[] unique = new XineramaScreenInfo[nn];
433             if(!unique.length) {
434                 die("fatal: could not malloc() %u bytes\n", XineramaScreenInfo.sizeof * nn);
435             }
436            
437             /* only consider unique geometries as separate screens */
438             int j=0;
439             foreach(i; 0..nn) {
440                 if(isuniquegeom(&unique[j], j, &info[i])) {
441                     unique[j++] = info[i];
442                 }
443             }
444 
445             XFree(info);
446             nn = j;
447 
448             if(n <= nn) {
449                 foreach(i; 0..(nn-n)) { /* new monitors available */
450                     auto m = mons.range.find!"a.next is null".front;
451                     if(m) {
452                         m.next = createmon();
453                     } else {
454                         mons = createmon();
455                     }
456                 }
457                 foreach(i, m; iota(nn).lockstep(mons.range)) {
458                     if(i >= n ||
459                             (unique[i].x_org != m.mx || unique[i].y_org != m.my ||
460                              unique[i].width != m.mw || unique[i].height != m.mh)) {
461                         dirty = true;
462                         m.num = i;
463                         m.mx = m.wx = unique[i].x_org;
464                         m.my = m.wy = unique[i].y_org;
465                         m.mw = m.ww = unique[i].width;
466                         m.mh = m.wh = unique[i].height;
467                         updatebarpos(m);
468                     }
469                 }
470             } else { /* less monitors available nn < n */
471                 foreach(i; nn..n) {
472                     auto m = mons.range.find!"a.next is null".front;
473                     if(m) {
474                         while(m.clients) {
475                             dirty = true;
476                             c = m.clients;
477                             m.clients = c.next;
478                             detachstack(c);
479                             c.mon = mons;
480                             attach(c);
481                             attachstack(c);
482                         }
483                         if(m == selmon)
484                             selmon = mons;
485                         cleanupmon(m);
486                     }
487                 }
488             }
489             unique = null;
490         }
491     } else {
492         /* default monitor setup */
493         if(!mons) {
494             mons = createmon();
495         }
496         
497         if(mons.mw != sw || mons.mh != sh) {
498             dirty = true;
499             mons.mw = mons.ww = sw;
500             mons.mh = mons.wh = sh;
501             updatebarpos(mons);
502         }
503     }
504     if(dirty) {
505         selmon = mons;
506         selmon = wintomon(rootWin);
507     }
508     return dirty;
509 }
510 
511 void updatewmhints(Client *c) {
512     
513     XWMHints *wmh;
514     wmh = XGetWMHints(AppDisplay.instance().dpy, c.win);
515     if(wmh) {
516         if(c == selmon.sel && wmh.flags & XUrgencyHint) {
517             wmh.flags &= ~XUrgencyHint;
518             XSetWMHints(AppDisplay.instance().dpy, c.win, wmh);
519         } else
520             c.isurgent = (wmh.flags & XUrgencyHint) ? true : false;
521         if(wmh.flags & InputHint)
522             c.neverfocus = !wmh.input;
523         else
524             c.neverfocus = false;
525         XFree(wmh);
526     }
527 }
528 
529 void setclientstate(Client *c, long state) {
530     
531     long[] data = [ state, None ];
532 
533     XChangeProperty(AppDisplay.instance().dpy, c.win, wmatom[WMState], wmatom[WMState], 32,
534                     PropModeReplace, cast(ubyte *)data, 2);
535 }
536 
537 void configure(Client *c) {
538     
539     XConfigureEvent ce;
540 
541     ce.type = ConfigureNotify;
542     ce.display = AppDisplay.instance().dpy;
543     ce.event = c.win;
544     ce.window = c.win;
545     ce.x = c.x;
546     ce.y = c.y;
547     ce.width = c.w;
548     ce.height = c.h;
549     ce.border_width = c.bw;
550     ce.above = None;
551     ce.override_redirect = false;
552     XSendEvent(AppDisplay.instance().dpy, c.win, false, StructureNotifyMask, cast(XEvent *)&ce);
553 }
554 
555 
556 
557 
558 void updatesizehints(Client *c) {
559     
560     long msize;
561     XSizeHints size;
562 
563     if(!XGetWMNormalHints(AppDisplay.instance().dpy, c.win, &size, &msize)) {
564         /* size is uninitialized, ensure that size.flags aren't used */
565         size.flags = PSize;
566     }
567     if(size.flags & PBaseSize) {
568         c.basew = size.base_width;
569         c.baseh = size.base_height;
570     } else if(size.flags & PMinSize) {
571         c.basew = size.min_width;
572         c.baseh = size.min_height;
573     } else {
574         c.basew = c.baseh = 0;
575     }
576 
577     if(size.flags & PResizeInc) {
578         c.incw = size.width_inc;
579         c.inch = size.height_inc;
580     } else {
581         c.incw = c.inch = 0;
582     }
583     if(size.flags & PMaxSize) {
584         c.maxw = size.max_width;
585         c.maxh = size.max_height;
586     } else {
587         c.maxw = c.maxh = 0;
588     }
589     if(size.flags & PMinSize) {
590         c.minw = size.min_width;
591         c.minh = size.min_height;
592     } else if(size.flags & PBaseSize) {
593         c.minw = size.base_width;
594         c.minh = size.base_height;
595     } else {
596         c.minw = c.minh = 0;
597     }
598     if(size.flags & PAspect) {
599         c.mina = cast(float)size.min_aspect.y / size.min_aspect.x;
600         c.maxa = cast(float)size.max_aspect.x / size.max_aspect.y;
601     } else {
602         c.maxa = c.mina = 0.0;
603     }
604     c.isfixed = (c.maxw && c.minw && c.maxh && c.minh
605                  && c.maxw == c.minw && c.maxh == c.minh);
606 }
607 
608 void focus(Client *c) {
609     if(!c || !ISVISIBLE(c)) {
610         c = selmon.stack.range!"snext".find!(a => ISVISIBLE(a)).front;
611     }
612     /* was if(selmon.sel) */
613     if(selmon.sel && selmon.sel != c)
614         unfocus(selmon.sel, false);
615     if(c) {
616         if(c.mon != selmon)
617             selmon = c.mon;
618         if(c.isurgent)
619             clearurgent(c);
620         detachstack(c);
621         attachstack(c);
622         mouseEventHandler.grabbuttons(c, true);
623         XSetWindowBorder(AppDisplay.instance().dpy, c.win, ThemeManager.instance().getScheme(SchemeSel).border.rgb);
624         setfocus(c);
625     } else {
626         XSetInputFocus(AppDisplay.instance().dpy, rootWin, RevertToPointerRoot, CurrentTime);
627         XDeleteProperty(AppDisplay.instance().dpy, rootWin, netatom[NetActiveWindow]);
628     }
629     selmon.sel = c;
630     drawbars();
631 }
632 
633 void focusmon(const Arg *arg) {
634     
635     Monitor *m;
636 
637     if(mons && !mons.next) {
638         return;
639     }
640     m = dirtomon(arg.i);
641     if(m == selmon) {
642         return;
643     }
644     unfocus(selmon.sel, false); /* s/true/false/ fixes input focus issues
645 					in gedit and anjuta */
646     selmon = m;
647     focus(null);
648 }
649 
650 void focusstack(const Arg *arg) {
651     
652     Client *c = null, i;
653 
654     if(!selmon.sel)
655         return;
656     if(arg.i > 0) {
657         c = selmon.sel.range!"next".find!(a => ISVISIBLE(a)).front;
658         if(!c) {
659             c = selmon.clients.range!"next".find!(a => ISVISIBLE(a)).front;
660         }
661     } else {
662         for(i = selmon.clients; i != selmon.sel; i = i.next) {
663             if(ISVISIBLE(i)) {
664                 c = i;
665             }
666         }
667         if(!c) {
668             for(; i; i = i.next) {
669                 if(ISVISIBLE(i)) {
670                     c = i;
671                 }
672             }
673         }
674     }
675     if(c) {
676         focus(c);
677         restack(selmon);
678     }
679 }
680 
681 Client* wintoclient(Window w) {
682     
683     foreach(m; mons.range) {
684         auto c = m.clients.range!"next".find!(client => client.win == w).front;
685         if(c) {
686             return c;
687         }
688     }
689     return null;
690 }
691 
692 void unfocus(Client *c, bool setfocus) {
693     
694     if(!c)
695         return;
696     mouseEventHandler.grabbuttons(c, false);
697     XSetWindowBorder(AppDisplay.instance().dpy, c.win, ThemeManager.instance().getScheme(SchemeNorm).border.rgb);
698     if(setfocus) {
699         XSetInputFocus(AppDisplay.instance().dpy, rootWin, RevertToPointerRoot, CurrentTime);
700         XDeleteProperty(AppDisplay.instance().dpy, rootWin, netatom[NetActiveWindow]);
701     }
702 }
703 
704 
705 
706 void restack(Monitor *m) 
707 {
708     XEvent ev;
709     XWindowChanges wc;
710 
711     drawbar(m);
712     if(!m.sel)
713         return;
714     if(m.sel.isfloating || !m.lt[m.sellt].arrange)
715         XRaiseWindow(AppDisplay.instance().dpy, m.sel.win);
716     if(m.lt[m.sellt].arrange) {
717         wc.stack_mode = Below;
718         wc.sibling = m.barwin;
719         auto stacks = m.stack.range!"snext".filter!(a => !a.isfloating && ISVISIBLE(a));
720         foreach(c; stacks) {
721             XConfigureWindow(AppDisplay.instance().dpy, c.win, CWSibling|CWStackMode, &wc);
722             wc.sibling = c.win;
723         }
724     }
725     XSync(AppDisplay.instance().dpy, false);
726     while(XCheckMaskEvent(AppDisplay.instance().dpy, EnterWindowMask, &ev)) {}
727 }
728 
729 auto TEXTW(X)(auto ref in X x)
730 if(isSomeString!X) {
731     return drw.font.getexts_width(x) + drw.font.h;
732 }
733 
734 void cleanupmon(Monitor *mon) 
735 {
736     if(mon && mon == mons) {
737         mons = mons.next;
738     } else {
739         auto m = mons.range.find!(a => a.next == mon).front;
740         if(m) {
741             m.next = mon.next;
742         }
743     }
744     XUnmapWindow(AppDisplay.instance().dpy, mon.barwin);
745     XDestroyWindow(AppDisplay.instance().dpy, mon.barwin);
746     DGC.free(mon);
747 }
748 
749 void clearurgent(Client *c) {
750     
751     XWMHints *wmh;
752 
753     c.isurgent = false;
754     wmh = XGetWMHints(AppDisplay.instance().dpy, c.win);
755     if(wmh is null) {
756         return;
757     }
758     wmh.flags &= ~XUrgencyHint;
759     XSetWMHints(AppDisplay.instance().dpy, c.win, wmh);
760     XFree(wmh);
761 }
762 
763 void detachstack(Client *c) 
764 {
765     Client **tc;
766 
767 
768     for(tc = &c.mon.stack; *tc && *tc != c; tc = &(*tc).snext) {}
769     *tc = c.snext;
770 
771     if(c && c == c.mon.sel) {
772         auto t = c.mon.stack.range!"snext".find!(a=>ISVISIBLE(a)).front;
773         c.mon.sel = t;
774     }
775 }
776 
777 void detach(Client *c) 
778 {
779     Client **tc;
780     for(tc = &c.mon.clients; *tc && *tc != c; tc = &(*tc).next) {}
781     *tc = c.next;
782 }
783