1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2 // Window.cc for Blackbox - an X11 Window manager
3 // Copyright (c) 2001 - 2005 Sean 'Shaleh' Perry <shaleh@debian.org>
4 // Copyright (c) 1997 - 2000, 2002 - 2005
5 //         Bradley T Hughes <bhughes at trolltech.com>
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a
8 // copy of this software and associated documentation files (the "Software"),
9 // to deal in the Software without restriction, including without limitation
10 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
11 // and/or sell copies of the Software, and to permit persons to whom the
12 // Software is furnished to do so, subject to the following conditions:
13 //
14 // The above copyright notice and this permission notice shall be included in
15 // all copies or substantial portions of the Software.
16 //
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
20 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 // DEALINGS IN THE SOFTWARE.
24 
25 // make sure we get bt::textPropertyToString()
26 #include <X11/Xlib.h>
27 #include <X11/Xutil.h>
28 
29 #include "Window.hh"
30 #include "Screen.hh"
31 #include "WindowGroup.hh"
32 #include "Windowmenu.hh"
33 #include "Workspace.hh"
34 #include "blackbox.hh"
35 
36 #include <cstdlib>
37 
38 #include <Pen.hh>
39 #include <PixmapCache.hh>
40 #include <Unicode.hh>
41 
42 #include <X11/Xatom.h>
43 #ifdef SHAPE
44 #  include <X11/extensions/shape.h>
45 #endif
46 
47 #include <assert.h>
48 
49 /*
50   sometimes C++ is a pain in the ass... it gives us stuff like the
51   default copy constructor and assignment operator (which does member
52   my member copy/assignment), but what we don't get is a default
53   comparison operator... how fucked up is that?
54 
55   the language is designed to handle this:
56 
57   struct foo { int a; int b };
58   foo a = { 0 , 0 }, b = a;
59   foo c;
60   c = a;
61 
62   BUT, BUT, BUT, forget about doing this:
63 
64   a = findFoo();
65   if (c == a)
66      return; // nothing to do
67   c = a;
68 
69   ARGH!@#)(!*@#)(@#*$(!@#
70 */
71 bool operator==(const WMNormalHints &x, const WMNormalHints &y);
72 
73 
74 // Event mask used for managed client windows.
75 const unsigned long client_window_event_mask =
76   (PropertyChangeMask | StructureNotifyMask);
77 
78 
79 /*
80  * Returns the appropriate WindowType based on the _NET_WM_WINDOW_TYPE
81  */
window_type_from_atom(const bt::EWMH & ewmh,Atom atom)82 static WindowType window_type_from_atom(const bt::EWMH &ewmh, Atom atom) {
83   if (atom == ewmh.wmWindowTypeDialog())
84     return WindowTypeDialog;
85   if (atom == ewmh.wmWindowTypeDesktop())
86     return WindowTypeDesktop;
87   if (atom == ewmh.wmWindowTypeDock())
88     return WindowTypeDock;
89   if (atom == ewmh.wmWindowTypeMenu())
90     return WindowTypeMenu;
91   if (atom == ewmh.wmWindowTypeSplash())
92     return WindowTypeSplash;
93   if (atom == ewmh.wmWindowTypeToolbar())
94     return WindowTypeToolbar;
95   if (atom == ewmh.wmWindowTypeUtility())
96     return WindowTypeUtility;
97   return WindowTypeNormal;
98 }
99 
100 
101 /*
102  * Determine the appropriate decorations and functions based on the
103  * given properties and hints.
104  */
update_decorations(WindowDecorationFlags & decorations,WindowFunctionFlags & functions,bool transient,const EWMH & ewmh,const MotifHints & motifhints,const WMNormalHints & wmnormal,const WMProtocols & wmprotocols)105 static void update_decorations(WindowDecorationFlags &decorations,
106                                WindowFunctionFlags &functions,
107                                bool transient,
108                                const EWMH &ewmh,
109                                const MotifHints &motifhints,
110                                const WMNormalHints &wmnormal,
111                                const WMProtocols &wmprotocols) {
112   decorations = AllWindowDecorations;
113   functions   = AllWindowFunctions;
114 
115   // transients should be kept on the same workspace are their parents
116   if (transient)
117     functions &= ~WindowFunctionChangeWorkspace;
118 
119   // modify the window decorations/functions based on window type
120   switch (ewmh.window_type) {
121   case WindowTypeDialog:
122     decorations &= ~(WindowDecorationIconify |
123                      WindowDecorationMaximize);
124     functions   &= ~(WindowFunctionShade |
125                      WindowFunctionIconify |
126                      WindowFunctionMaximize |
127                      WindowFunctionChangeLayer |
128                      WindowFunctionFullScreen);
129     break;
130 
131   case WindowTypeDesktop:
132   case WindowTypeDock:
133   case WindowTypeSplash:
134     decorations = NoWindowDecorations;
135     functions   = NoWindowFunctions;
136     break;
137 
138   case WindowTypeToolbar:
139   case WindowTypeMenu:
140     decorations &= ~(WindowDecorationHandle |
141                      WindowDecorationGrip |
142                      WindowDecorationBorder |
143                      WindowDecorationIconify |
144                      WindowDecorationMaximize);
145     functions   &= ~(WindowFunctionResize |
146                      WindowFunctionShade |
147                      WindowFunctionIconify |
148                      WindowFunctionMaximize |
149                      WindowFunctionFullScreen);
150     break;
151 
152   case WindowTypeUtility:
153     decorations &= ~(WindowDecorationIconify |
154                      WindowDecorationMaximize);
155     functions   &= ~(WindowFunctionShade |
156                      WindowFunctionIconify |
157                      WindowFunctionMaximize);
158     break;
159 
160   default:
161     break;
162   }
163 
164   // mask away stuff turned off by Motif hints
165   decorations &= motifhints.decorations;
166   functions   &= motifhints.functions;
167 
168   // disable shade if we do not have a titlebar
169   if (!(decorations & WindowDecorationTitlebar))
170     functions &= ~WindowFunctionShade;
171 
172   // disable grips and maximize if we have a fixed size window
173   if ((wmnormal.flags & (PMinSize|PMaxSize)) == (PMinSize|PMaxSize)
174       && wmnormal.max_width <= wmnormal.min_width
175       && wmnormal.max_height <= wmnormal.min_height) {
176     decorations &= ~(WindowDecorationMaximize |
177                      WindowDecorationGrip);
178     functions   &= ~(WindowFunctionResize |
179                      WindowFunctionMaximize);
180   }
181 
182   // cannot close if client doesn't understand WM_DELETE_WINDOW
183   if (!wmprotocols.wm_delete_window) {
184     decorations &= ~WindowDecorationClose;
185     functions   &= ~WindowFunctionClose;
186   }
187 }
188 
189 
190 /*
191  * Calculate the frame margin based on the given decorations and
192  * style.
193  */
194 static
update_margin(WindowDecorationFlags decorations,const WindowStyle & style)195 bt::EWMH::Strut update_margin(WindowDecorationFlags decorations,
196                               const WindowStyle &style) {
197   bt::EWMH::Strut margin;
198 
199   const unsigned int bw = ((decorations & WindowDecorationBorder)
200                            ? style.frame_border_width
201                            : 0u);
202   margin.top = margin.bottom = margin.left = margin.right = bw;
203 
204   if (decorations & WindowDecorationTitlebar)
205     margin.top += style.title_height - bw;
206 
207   if (decorations & WindowDecorationHandle)
208     margin.bottom += style.handle_height - bw;
209 
210   return margin;
211 }
212 
213 
214 /*
215  * Add specified window to the appropriate window group, creating a
216  * new group if necessary.
217  */
update_window_group(Window window_group,Blackbox * blackbox,BlackboxWindow * win)218 static BWindowGroup *update_window_group(Window window_group,
219                                          Blackbox *blackbox,
220                                          BlackboxWindow *win) {
221   BWindowGroup *group = win->findWindowGroup();
222   if (!group) {
223     new BWindowGroup(blackbox, window_group);
224     group = win->findWindowGroup();
225     assert(group != 0);
226   }
227   group->addWindow(win);
228   return group;
229 }
230 
231 
232 /*
233  * Calculate the size of the frame window and constrain it to the
234  * size specified by the size hints of the client window.
235  *
236  * 'rect' refers to the geometry of the frame in pixels.
237  */
238 enum Corner {
239   TopLeft,
240   TopRight,
241   BottomLeft,
242   BottomRight
243 };
constrain(const bt::Rect & rect,const bt::EWMH::Strut & margin,const WMNormalHints & wmnormal,Corner corner)244 static bt::Rect constrain(const bt::Rect &rect,
245                           const bt::EWMH::Strut &margin,
246                           const WMNormalHints &wmnormal,
247                           Corner corner) {
248   bt::Rect r;
249 
250   // 'rect' represents the requested frame size, we need to strip
251   // 'margin' off and constrain the client size
252   r.setCoords(rect.left() + static_cast<signed>(margin.left),
253               rect.top() + static_cast<signed>(margin.top),
254               rect.right() - static_cast<signed>(margin.right),
255               rect.bottom() - static_cast<signed>(margin.bottom));
256 
257   unsigned int dw = r.width(), dh = r.height();
258 
259   const unsigned int base_width = (wmnormal.base_width
260                                    ? wmnormal.base_width
261                                    : wmnormal.min_width),
262                     base_height = (wmnormal.base_height
263                                    ? wmnormal.base_height
264                                    : wmnormal.min_height);
265 
266   // fit to min/max size
267   if (dw < wmnormal.min_width)
268     dw = wmnormal.min_width;
269   if (dh < wmnormal.min_height)
270     dh = wmnormal.min_height;
271 
272   if (dw > wmnormal.max_width)
273     dw = wmnormal.max_width;
274   if (dh > wmnormal.max_height)
275     dh = wmnormal.max_height;
276 
277   assert(dw >= base_width && dh >= base_height);
278 
279   // fit to size increments
280   if (wmnormal.flags & PResizeInc) {
281     dw = (((dw - base_width) / wmnormal.width_inc)
282           * wmnormal.width_inc) + base_width;
283     dh = (((dh - base_height) / wmnormal.height_inc)
284           * wmnormal.height_inc) + base_height;
285   }
286 
287   /*
288    * honor aspect ratios (based on twm which is based on uwm)
289    *
290    * The math looks like this:
291    *
292    * minAspectX    dwidth     maxAspectX
293    * ---------- <= ------- <= ----------
294    * minAspectY    dheight    maxAspectY
295    *
296    * If that is multiplied out, then the width and height are
297    * invalid in the following situations:
298    *
299    * minAspectX * dheight > minAspectY * dwidth
300    * maxAspectX * dheight < maxAspectY * dwidth
301    *
302    */
303   if (wmnormal.flags & PAspect) {
304     unsigned int delta;
305     const unsigned int min_asp_x = wmnormal.min_aspect_x,
306                        min_asp_y = wmnormal.min_aspect_y,
307                        max_asp_x = wmnormal.max_aspect_x,
308                        max_asp_y = wmnormal.max_aspect_y,
309                            w_inc = wmnormal.width_inc,
310                            h_inc = wmnormal.height_inc;
311     if (min_asp_x * dh > min_asp_y * dw) {
312       delta = ((min_asp_x * dh / min_asp_y - dw) * w_inc) / w_inc;
313       if (dw + delta <= wmnormal.max_width) {
314         dw += delta;
315       } else {
316         delta = ((dh - (dw * min_asp_y) / min_asp_x) * h_inc) / h_inc;
317         if (dh - delta >= wmnormal.min_height) dh -= delta;
318       }
319     }
320     if (max_asp_x * dh < max_asp_y * dw) {
321       delta = ((max_asp_y * dw / max_asp_x - dh) * h_inc) / h_inc;
322       if (dh + delta <= wmnormal.max_height) {
323         dh += delta;
324       } else {
325         delta = ((dw - (dh * max_asp_x) / max_asp_y) * w_inc) / w_inc;
326         if (dw - delta >= wmnormal.min_width) dw -= delta;
327       }
328     }
329   }
330 
331   r.setSize(dw, dh);
332 
333   // add 'margin' back onto 'r'
334   r.setCoords(r.left() - margin.left, r.top() - margin.top,
335               r.right() + margin.right, r.bottom() + margin.bottom);
336 
337   // move 'r' to the specified corner
338   int dx = rect.right() - r.right();
339   int dy = rect.bottom() - r.bottom();
340 
341   switch (corner) {
342   case TopLeft:
343     // nothing to do
344     break;
345 
346   case TopRight:
347     r.setPos(r.x() + dx, r.y());
348     break;
349 
350   case BottomLeft:
351     r.setPos(r.x(), r.y() + dy);
352     break;
353 
354   case BottomRight:
355     r.setPos(r.x() + dx, r.y() + dy);
356     break;
357   }
358 
359   return r;
360 }
361 
362 
363 /*
364  * Positions 'rect' according to the client window geometry and window
365  * gravity.
366  */
applyGravity(const bt::Rect & rect,const bt::EWMH::Strut & margin,int gravity)367 static bt::Rect applyGravity(const bt::Rect &rect,
368                              const bt::EWMH::Strut &margin,
369                              int gravity) {
370   bt::Rect r;
371 
372   // apply horizontal window gravity
373   switch (gravity) {
374   default:
375   case NorthWestGravity:
376   case SouthWestGravity:
377   case WestGravity:
378     r.setX(rect.x());
379     break;
380 
381   case NorthGravity:
382   case SouthGravity:
383   case CenterGravity:
384     r.setX(rect.x() - (margin.left + margin.right) / 2);
385     break;
386 
387   case NorthEastGravity:
388   case SouthEastGravity:
389   case EastGravity:
390     r.setX(rect.x() - (margin.left + margin.right) + 2);
391     break;
392 
393   case ForgetGravity:
394   case StaticGravity:
395     r.setX(rect.x() - margin.left);
396     break;
397   }
398 
399   // apply vertical window gravity
400   switch (gravity) {
401   default:
402   case NorthWestGravity:
403   case NorthEastGravity:
404   case NorthGravity:
405     r.setY(rect.y());
406     break;
407 
408   case CenterGravity:
409   case EastGravity:
410   case WestGravity:
411     r.setY(rect.y() - ((margin.top + margin.bottom) / 2));
412     break;
413 
414   case SouthWestGravity:
415   case SouthEastGravity:
416   case SouthGravity:
417     r.setY(rect.y() - (margin.bottom + margin.top) + 2);
418     break;
419 
420   case ForgetGravity:
421   case StaticGravity:
422     r.setY(rect.y() - margin.top);
423     break;
424   }
425 
426   r.setSize(rect.width() + margin.left + margin.right,
427             rect.height() + margin.top + margin.bottom);
428   return r;
429 }
430 
431 
432 /*
433  * The reverse of the applyGravity function.
434  *
435  * Positions 'rect' according to the frame window geometry and window
436  * gravity.
437  */
restoreGravity(const bt::Rect & rect,const bt::EWMH::Strut & margin,int gravity)438 static bt::Rect restoreGravity(const bt::Rect &rect,
439                                const bt::EWMH::Strut &margin,
440                                int gravity) {
441   bt::Rect r;
442 
443   // restore horizontal window gravity
444   switch (gravity) {
445   default:
446   case NorthWestGravity:
447   case SouthWestGravity:
448   case WestGravity:
449     r.setX(rect.x());
450     break;
451 
452   case NorthGravity:
453   case SouthGravity:
454   case CenterGravity:
455     r.setX(rect.x() + (margin.left + margin.right) / 2);
456     break;
457 
458   case NorthEastGravity:
459   case SouthEastGravity:
460   case EastGravity:
461     r.setX(rect.x() + (margin.left + margin.right) - 2);
462     break;
463 
464   case ForgetGravity:
465   case StaticGravity:
466     r.setX(rect.x() + margin.left);
467     break;
468   }
469 
470   // restore vertical window gravity
471   switch (gravity) {
472   default:
473   case NorthWestGravity:
474   case NorthEastGravity:
475   case NorthGravity:
476     r.setY(rect.y());
477     break;
478 
479   case CenterGravity:
480   case EastGravity:
481   case WestGravity:
482     r.setY(rect.y() + (margin.top + margin.bottom) / 2);
483     break;
484 
485   case SouthWestGravity:
486   case SouthEastGravity:
487   case SouthGravity:
488     r.setY(rect.y() + (margin.top + margin.bottom) - 2);
489     break;
490 
491   case ForgetGravity:
492   case StaticGravity:
493     r.setY(rect.y() + margin.top);
494     break;
495   }
496 
497   r.setSize(rect.width() - margin.left - margin.right,
498             rect.height() - margin.top - margin.bottom);
499   return r;
500 }
501 
502 
readWMName(Blackbox * blackbox,Window window)503 static bt::ustring readWMName(Blackbox *blackbox, Window window) {
504   bt::ustring name;
505 
506   if (!blackbox->ewmh().readWMName(window, name) || name.empty()) {
507     XTextProperty text_prop;
508 
509     if (XGetWMName(blackbox->XDisplay(), window, &text_prop)) {
510       name = bt::toUnicode(bt::textPropertyToString(blackbox->XDisplay(),
511                                                     text_prop));
512       XFree((char *) text_prop.value);
513     }
514   }
515 
516   if (name.empty())
517     name = bt::toUnicode("Unnamed");
518 
519   return name;
520 }
521 
522 
readWMIconName(Blackbox * blackbox,Window window)523 static bt::ustring readWMIconName(Blackbox *blackbox, Window window) {
524   bt::ustring name;
525 
526   if (!blackbox->ewmh().readWMIconName(window, name) || name.empty()) {
527     XTextProperty text_prop;
528     if (XGetWMIconName(blackbox->XDisplay(), window, &text_prop)) {
529       name = bt::toUnicode(bt::textPropertyToString(blackbox->XDisplay(),
530                                                     text_prop));
531       XFree((char *) text_prop.value);
532     }
533   }
534 
535   if (name.empty())
536     return bt::ustring();
537 
538   return name;
539 }
540 
541 
readEWMH(const bt::EWMH & bewmh,Window window,int currentWorkspace)542 static EWMH readEWMH(const bt::EWMH &bewmh,
543                      Window window,
544                      int currentWorkspace) {
545   EWMH ewmh;
546   ewmh.window_type  = WindowTypeNormal;
547   ewmh.workspace    = 0; // initialized properly below
548   ewmh.modal        = false;
549   ewmh.maxv         = false;
550   ewmh.maxh         = false;
551   ewmh.shaded       = false;
552   ewmh.skip_taskbar = false;
553   ewmh.skip_pager   = false;
554   ewmh.hidden       = false;
555   ewmh.fullscreen   = false;
556   ewmh.above        = false;
557   ewmh.below        = false;
558 
559   // note: wm_name and wm_icon_name are read separately
560 
561   bool ret;
562 
563   bt::EWMH::AtomList atoms;
564   ret = bewmh.readWMWindowType(window, atoms);
565   if (ret) {
566     bt::EWMH::AtomList::iterator it = atoms.begin(), end = atoms.end();
567     for (; it != end; ++it) {
568       if (bewmh.isSupportedWMWindowType(*it)) {
569         ewmh.window_type = ::window_type_from_atom(bewmh, *it);
570         break;
571       }
572     }
573   }
574 
575   atoms.clear();
576   ret = bewmh.readWMState(window, atoms);
577   if (ret) {
578     bt::EWMH::AtomList::iterator it = atoms.begin(), end = atoms.end();
579     for (; it != end; ++it) {
580       Atom state = *it;
581       if (state == bewmh.wmStateModal()) {
582         ewmh.modal = true;
583       } else if (state == bewmh.wmStateMaximizedVert()) {
584         ewmh.maxv = true;
585       } else if (state == bewmh.wmStateMaximizedHorz()) {
586         ewmh.maxh = true;
587       } else if (state == bewmh.wmStateShaded()) {
588         ewmh.shaded = true;
589       } else if (state == bewmh.wmStateSkipTaskbar()) {
590         ewmh.skip_taskbar = true;
591       } else if (state == bewmh.wmStateSkipPager()) {
592         ewmh.skip_pager = true;
593       } else if (state == bewmh.wmStateHidden()) {
594         /*
595           ignore _NET_WM_STATE_HIDDEN if present, the wm sets this
596           state, not the application
597         */
598       } else if (state == bewmh.wmStateFullscreen()) {
599         ewmh.fullscreen = true;
600       } else if (state == bewmh.wmStateAbove()) {
601         ewmh.above = true;
602       } else if (state == bewmh.wmStateBelow()) {
603         ewmh.below = true;
604       }
605     }
606   }
607 
608   switch (ewmh.window_type) {
609   case WindowTypeDesktop:
610   case WindowTypeDock:
611     // these types should occupy all workspaces by default
612     ewmh.workspace = bt::BSENTINEL;
613     break;
614 
615   default:
616     if (!bewmh.readWMDesktop(window, ewmh.workspace))
617       ewmh.workspace = currentWorkspace;
618     break;
619   } //switch
620 
621   return ewmh;
622 }
623 
624 
625 /*
626  * Returns the MotifWM hints for the specified window.
627  */
readMotifWMHints(Blackbox * blackbox,Window window)628 static MotifHints readMotifWMHints(Blackbox *blackbox, Window window) {
629   MotifHints motif;
630   motif.decorations = AllWindowDecorations;
631   motif.functions   = AllWindowFunctions;
632 
633   /*
634     this structure only contains 3 elements, even though the Motif 2.0
635     structure contains 5, because we only use the first 3
636   */
637   struct PropMotifhints {
638     unsigned long flags;
639     unsigned long functions;
640     unsigned long decorations;
641   };
642   static const unsigned int PROP_MWM_HINTS_ELEMENTS = 3u;
643   enum { // MWM flags
644     MWM_HINTS_FUNCTIONS   = 1<<0,
645     MWM_HINTS_DECORATIONS = 1<<1
646   };
647   enum { // MWM functions
648     MWM_FUNC_ALL      = 1<<0,
649     MWM_FUNC_RESIZE   = 1<<1,
650     MWM_FUNC_MOVE     = 1<<2,
651     MWM_FUNC_MINIMIZE = 1<<3,
652     MWM_FUNC_MAXIMIZE = 1<<4,
653     MWM_FUNC_CLOSE    = 1<<5
654   };
655   enum { // MWM decorations
656     MWM_DECOR_ALL      = 1<<0,
657     MWM_DECOR_BORDER   = 1<<1,
658     MWM_DECOR_RESIZEH  = 1<<2,
659     MWM_DECOR_TITLE    = 1<<3,
660     MWM_DECOR_MENU     = 1<<4,
661     MWM_DECOR_MINIMIZE = 1<<5,
662     MWM_DECOR_MAXIMIZE = 1<<6
663   };
664 
665   Atom atom_return;
666   PropMotifhints *prop = 0;
667   int format;
668   unsigned long num, len;
669   int ret = XGetWindowProperty(blackbox->XDisplay(), window,
670                                blackbox->motifWmHintsAtom(), 0,
671                                PROP_MWM_HINTS_ELEMENTS, False,
672                                blackbox->motifWmHintsAtom(), &atom_return,
673                                &format, &num, &len,
674                                (unsigned char **) &prop);
675 
676   if (ret != Success || !prop || num != PROP_MWM_HINTS_ELEMENTS) {
677     if (prop) XFree(prop);
678     return motif;
679   }
680 
681   if (prop->flags & MWM_HINTS_FUNCTIONS) {
682     if (prop->functions & MWM_FUNC_ALL) {
683       motif.functions = AllWindowFunctions;
684     } else {
685       // default to the functions that cannot be set through
686       // _MOTIF_WM_HINTS
687       motif.functions = (WindowFunctionShade
688                          | WindowFunctionChangeWorkspace
689                          | WindowFunctionChangeLayer
690                          | WindowFunctionFullScreen);
691 
692       if (prop->functions & MWM_FUNC_RESIZE)
693         motif.functions |= WindowFunctionResize;
694       if (prop->functions & MWM_FUNC_MOVE)
695         motif.functions |= WindowFunctionMove;
696       if (prop->functions & MWM_FUNC_MINIMIZE)
697         motif.functions |= WindowFunctionIconify;
698       if (prop->functions & MWM_FUNC_MAXIMIZE)
699         motif.functions |= WindowFunctionMaximize;
700       if (prop->functions & MWM_FUNC_CLOSE)
701         motif.functions |= WindowFunctionClose;
702     }
703   }
704 
705   if (prop->flags & MWM_HINTS_DECORATIONS) {
706     if (prop->decorations & MWM_DECOR_ALL) {
707       motif.decorations = AllWindowDecorations;
708     } else {
709       motif.decorations = NoWindowDecorations;
710 
711       if (prop->decorations & MWM_DECOR_BORDER)
712         motif.decorations |= WindowDecorationBorder;
713       if (prop->decorations & MWM_DECOR_RESIZEH)
714         motif.decorations |= WindowDecorationHandle;
715       if (prop->decorations & MWM_DECOR_TITLE)
716         motif.decorations |= WindowDecorationTitlebar;
717       if (prop->decorations & MWM_DECOR_MINIMIZE)
718         motif.decorations |= WindowDecorationIconify;
719       if (prop->decorations & MWM_DECOR_MAXIMIZE)
720         motif.decorations |= WindowDecorationMaximize;
721     }
722   }
723 
724   if (motif.decorations & WindowDecorationHandle) {
725     if (motif.functions & WindowFunctionResize)
726       motif.decorations |= WindowDecorationGrip;
727     else
728       motif.decorations &= ~WindowDecorationGrip;
729   }
730 
731   if (motif.decorations & WindowDecorationTitlebar) {
732     if (motif.functions & WindowFunctionClose)
733       motif.decorations |= WindowDecorationClose;
734     else
735       motif.decorations &= ~WindowDecorationClose;
736   }
737 
738   XFree(prop);
739 
740   return motif;
741 }
742 
743 
744 /*
745  * Returns the value of the WM_HINTS property.  If the property is not
746  * set, a set of default values is returned instead.
747  */
readWMHints(Blackbox * blackbox,Window window)748 static WMHints readWMHints(Blackbox *blackbox, Window window) {
749   WMHints wmh;
750   wmh.accept_focus = false;
751   wmh.window_group = None;
752   wmh.initial_state = NormalState;
753 
754   XWMHints *wmhint = XGetWMHints(blackbox->XDisplay(), window);
755   if (!wmhint) return wmh;
756 
757   if (wmhint->flags & InputHint)
758     wmh.accept_focus = (wmhint->input == True);
759   if (wmhint->flags & StateHint)
760     wmh.initial_state = wmhint->initial_state;
761   if (wmhint->flags & WindowGroupHint)
762     wmh.window_group = wmhint->window_group;
763 
764   XFree(wmhint);
765 
766   return wmh;
767 }
768 
769 
770 /*
771  * Returns the value of the WM_NORMAL_HINTS property.  If the property
772  * is not set, a set of default values is returned instead.
773  */
readWMNormalHints(Blackbox * blackbox,Window window,const bt::ScreenInfo & screenInfo)774 static WMNormalHints readWMNormalHints(Blackbox *blackbox,
775                                        Window window,
776                                        const bt::ScreenInfo &screenInfo) {
777   WMNormalHints wmnormal;
778   wmnormal.flags = 0;
779   wmnormal.min_width    = wmnormal.min_height   = 1u;
780   wmnormal.width_inc    = wmnormal.height_inc   = 1u;
781   wmnormal.min_aspect_x = wmnormal.min_aspect_y = 1u;
782   wmnormal.max_aspect_x = wmnormal.max_aspect_y = 1u;
783   wmnormal.base_width   = wmnormal.base_height  = 0u;
784   wmnormal.win_gravity  = NorthWestGravity;
785 
786   /*
787     use the full screen, not the strut modified size. otherwise when
788     the availableArea changes max_width/height will be incorrect and
789     lead to odd rendering bugs.
790   */
791   const bt::Rect &rect = screenInfo.rect();
792   wmnormal.max_width = rect.width();
793   wmnormal.max_height = rect.height();
794 
795   XSizeHints sizehint;
796   long unused;
797   if (! XGetWMNormalHints(blackbox->XDisplay(), window, &sizehint, &unused))
798     return wmnormal;
799 
800   wmnormal.flags = sizehint.flags;
801 
802   if (sizehint.flags & PMinSize) {
803     if (sizehint.min_width > 0)
804       wmnormal.min_width  = sizehint.min_width;
805     if (sizehint.min_height > 0)
806       wmnormal.min_height = sizehint.min_height;
807 
808     /*
809       if the minimum size is bigger then the screen, adjust the
810       maximum size
811     */
812     if (wmnormal.min_width > wmnormal.max_width)
813       wmnormal.max_width = wmnormal.min_width;
814     if (wmnormal.min_height > wmnormal.max_height)
815       wmnormal.max_height = wmnormal.min_height;
816   }
817 
818   if (sizehint.flags & PMaxSize) {
819     if (sizehint.max_width >= static_cast<signed>(wmnormal.min_width))
820       wmnormal.max_width  = sizehint.max_width;
821     else
822       wmnormal.max_width  = wmnormal.min_width;
823 
824     if (sizehint.max_height >= static_cast<signed>(wmnormal.min_height))
825       wmnormal.max_height = sizehint.max_height;
826     else
827       wmnormal.max_height = wmnormal.min_height;
828   }
829 
830   if (sizehint.flags & PResizeInc) {
831     wmnormal.width_inc  = sizehint.width_inc;
832     wmnormal.height_inc = sizehint.height_inc;
833   }
834 
835   if (sizehint.flags & PAspect) {
836     wmnormal.min_aspect_x = sizehint.min_aspect.x;
837     wmnormal.min_aspect_y = sizehint.min_aspect.y;
838     wmnormal.max_aspect_x = sizehint.max_aspect.x;
839     wmnormal.max_aspect_y = sizehint.max_aspect.y;
840   }
841 
842   if (sizehint.flags & PBaseSize) {
843     if (sizehint.base_width <= static_cast<signed>(wmnormal.min_width))
844       wmnormal.base_width  = sizehint.base_width;
845     if (sizehint.base_height <= static_cast<signed>(wmnormal.min_height))
846       wmnormal.base_height = sizehint.base_height;
847   }
848 
849   if (sizehint.flags & PWinGravity)
850     wmnormal.win_gravity = sizehint.win_gravity;
851 
852   return wmnormal;
853 }
854 
855 
856 /*
857  * Retrieve which Window Manager Protocols are supported by the client
858  * window.
859  */
readWMProtocols(Blackbox * blackbox,Window window)860 static WMProtocols readWMProtocols(Blackbox *blackbox,
861                                    Window window) {
862   WMProtocols protocols;
863   protocols.wm_delete_window = false;
864   protocols.wm_take_focus    = false;
865 
866   Atom *proto;
867   int num_return = 0;
868 
869   if (XGetWMProtocols(blackbox->XDisplay(), window,
870                       &proto, &num_return)) {
871     for (int i = 0; i < num_return; ++i) {
872       if (proto[i] == blackbox->wmDeleteWindowAtom()) {
873         protocols.wm_delete_window = true;
874       } else if (proto[i] == blackbox->wmTakeFocusAtom()) {
875         protocols.wm_take_focus = true;
876       }
877     }
878     XFree(proto);
879   }
880 
881   return protocols;
882 }
883 
884 
885 /*
886  * Reads the value of the WM_TRANSIENT_FOR property and returns a
887  * pointer to the transient parent for this window.  If the
888  * WM_TRANSIENT_FOR is missing or invalid, this function returns 0.
889  *
890  * 'client.wmhints' should be properly updated before calling this
891  * function.
892  *
893  * Note: a return value of ~0ul signifies a window that should be
894  * transient but has no discernible parent.
895  */
readTransientInfo(Blackbox * blackbox,Window window,const bt::ScreenInfo & screenInfo,const WMHints & wmhints)896 static Window readTransientInfo(Blackbox *blackbox,
897                                 Window window,
898                                 const bt::ScreenInfo &screenInfo,
899                                 const WMHints &wmhints) {
900   Window trans_for = None;
901 
902   if (!XGetTransientForHint(blackbox->XDisplay(), window, &trans_for)) {
903     // WM_TRANSIENT_FOR hint not set
904     return 0;
905   }
906 
907   if (trans_for == window) {
908     // wierd client... treat this window as a normal window
909     return 0;
910   }
911 
912   if (trans_for == None || trans_for == screenInfo.rootWindow()) {
913     /*
914       this is a violation of the ICCCM, yet the EWMH allows this as a
915       way to signify a group transient.
916     */
917     trans_for = wmhints.window_group;
918   }
919 
920   return trans_for;
921 }
922 
923 
readState(unsigned long & current_state,Blackbox * blackbox,Window window)924 static bool readState(unsigned long &current_state,
925                       Blackbox *blackbox,
926                       Window window) {
927   current_state = NormalState;
928 
929   Atom atom_return;
930   bool ret = false;
931   int foo;
932   unsigned long *state, ulfoo, nitems;
933 
934   if ((XGetWindowProperty(blackbox->XDisplay(), window,
935                           blackbox->wmStateAtom(),
936                           0l, 2l, False, blackbox->wmStateAtom(),
937                           &atom_return, &foo, &nitems, &ulfoo,
938                           (unsigned char **) &state) != Success) ||
939       (! state)) {
940     return false;
941   }
942 
943   if (nitems >= 1) {
944     current_state = static_cast<unsigned long>(state[0]);
945     ret = true;
946   }
947 
948   XFree((void *) state);
949 
950   return ret;
951 }
952 
953 
clearState(Blackbox * blackbox,Window window)954 static void clearState(Blackbox *blackbox, Window window) {
955   XDeleteProperty(blackbox->XDisplay(), window, blackbox->wmStateAtom());
956 
957   const bt::EWMH& ewmh = blackbox->ewmh();
958   ewmh.removeProperty(window, ewmh.wmDesktop());
959   ewmh.removeProperty(window, ewmh.wmState());
960   ewmh.removeProperty(window, ewmh.wmAllowedActions());
961   ewmh.removeProperty(window, ewmh.wmVisibleName());
962   ewmh.removeProperty(window, ewmh.wmVisibleIconName());
963 }
964 
965 
966 /*
967  * Initializes the class with default values/the window's set initial values.
968  */
BlackboxWindow(Blackbox * b,Window w,BScreen * s)969 BlackboxWindow::BlackboxWindow(Blackbox *b, Window w, BScreen *s) {
970   // fprintf(stderr, "BlackboxWindow size: %d bytes\n",
971   //         sizeof(BlackboxWindow));
972 
973 #ifdef    DEBUG
974   fprintf(stderr, "BlackboxWindow::BlackboxWindow(): creating 0x%lx\n", w);
975 #endif // DEBUG
976 
977   /*
978     set timer to zero... it is initialized properly later, so we check
979     if timer is zero in the destructor, and assume that the window is not
980     fully constructed if timer is zero...
981   */
982   timer = (bt::Timer*) 0;
983   blackbox = b;
984   client.window = w;
985   _screen = s;
986   lastButtonPressTime = 0;
987 
988   /*
989     the server needs to be grabbed here to prevent client's from sending
990     events while we are in the process of managing their window.
991     We hold the grab until after we are done moving the window around.
992   */
993 
994   blackbox->XGrabServer();
995 
996   // fetch client size and placement
997   XWindowAttributes wattrib;
998   if (! XGetWindowAttributes(blackbox->XDisplay(),
999                              client.window, &wattrib) ||
1000       ! wattrib.screen || wattrib.override_redirect) {
1001 #ifdef    DEBUG
1002     fprintf(stderr,
1003             "BlackboxWindow::BlackboxWindow(): XGetWindowAttributes failed\n");
1004 #endif // DEBUG
1005 
1006     blackbox->XUngrabServer();
1007     delete this;
1008     return;
1009   }
1010 
1011   // set the eventmask early in the game so that we make sure we get
1012   // all the events we are interested in
1013   XSetWindowAttributes attrib_set;
1014   attrib_set.event_mask = ::client_window_event_mask;
1015   attrib_set.do_not_propagate_mask = ButtonPressMask | ButtonReleaseMask |
1016                                      ButtonMotionMask;
1017   XChangeWindowAttributes(blackbox->XDisplay(), client.window,
1018                           CWEventMask|CWDontPropagate, &attrib_set);
1019 
1020   client.colormap = wattrib.colormap;
1021   window_number = bt::BSENTINEL;
1022   client.strut = 0;
1023   /*
1024     set the initial size and location of client window (relative to the
1025     _root window_). This position is the reference point used with the
1026     window's gravity to find the window's initial position.
1027   */
1028   client.rect.setRect(wattrib.x, wattrib.y, wattrib.width, wattrib.height);
1029   client.premax = client.rect;
1030   client.old_bw = wattrib.border_width;
1031   client.current_state = NormalState;
1032 
1033   frame.window = frame.plate = frame.title = frame.handle = None;
1034   frame.close_button = frame.iconify_button = frame.maximize_button = None;
1035   frame.right_grip = frame.left_grip = None;
1036   frame.utitle = frame.ftitle = frame.uhandle = frame.fhandle = None;
1037   frame.ulabel = frame.flabel = frame.ubutton = frame.fbutton = None;
1038   frame.pbutton = frame.ugrip = frame.fgrip = None;
1039 
1040   timer = new bt::Timer(blackbox, this);
1041   timer->setTimeout(blackbox->resource().autoRaiseDelay());
1042 
1043   client.title = ::readWMName(blackbox, client.window);
1044   client.icon_title = ::readWMIconName(blackbox, client.window);
1045 
1046   // get size, aspect, minimum/maximum size, ewmh and other hints set
1047   // by the client
1048   client.ewmh = ::readEWMH(blackbox->ewmh(), client.window,
1049                            _screen->currentWorkspace());
1050   client.motif = ::readMotifWMHints(blackbox, client.window);
1051   client.wmhints = ::readWMHints(blackbox, client.window);
1052   client.wmnormal = ::readWMNormalHints(blackbox, client.window,
1053                                         _screen->screenInfo());
1054   client.wmprotocols = ::readWMProtocols(blackbox, client.window);
1055   client.transient_for = ::readTransientInfo(blackbox, client.window,
1056                                              _screen->screenInfo(),
1057                                              client.wmhints);
1058 
1059   if (client.wmhints.window_group != None)
1060     (void) ::update_window_group(client.wmhints.window_group, blackbox, this);
1061 
1062   if (isTransient()) {
1063     // add ourselves to our transient_for
1064     BlackboxWindow *win = findTransientFor();
1065     if (win) {
1066       win->addTransient(this);
1067       client.ewmh.workspace = win->workspace();
1068       setLayer(win->layer());
1069     } else if (isGroupTransient()) {
1070       BWindowGroup *group = findWindowGroup();
1071       if (group)
1072         group->addTransient(this);
1073     } else {
1074       // broken client
1075       client.transient_for = 0;
1076     }
1077   }
1078 
1079   client.state.visible = false;
1080   client.state.iconic = false;
1081   client.state.moving = false;
1082   client.state.resizing = false;
1083   client.state.focused = false;
1084 
1085   switch (windowType()) {
1086   case WindowTypeDesktop:
1087     setLayer(StackingList::LayerDesktop);
1088     break;
1089 
1090   case WindowTypeDock:
1091     setLayer(StackingList::LayerAbove);
1092     // fallthrough intended
1093 
1094   default:
1095     if (client.ewmh.above)
1096       setLayer(StackingList::LayerAbove);
1097     else if (client.ewmh.below)
1098       setLayer(StackingList::LayerBelow);
1099     break;
1100   } // switch
1101 
1102   ::update_decorations(client.decorations,
1103                        client.functions,
1104                        isTransient(),
1105                        client.ewmh,
1106                        client.motif,
1107                        client.wmnormal,
1108                        client.wmprotocols);
1109 
1110   // sanity checks
1111   if (client.wmhints.initial_state == IconicState
1112       && !hasWindowFunction(WindowFunctionIconify))
1113     client.wmhints.initial_state = NormalState;
1114   if (isMaximized() && !hasWindowFunction(WindowFunctionMaximize))
1115     client.ewmh.maxv = client.ewmh.maxh = false;
1116   if (isFullScreen() && !hasWindowFunction(WindowFunctionFullScreen))
1117     client.ewmh.fullscreen = false;
1118 
1119   bt::EWMH::Strut strut;
1120   if (blackbox->ewmh().readWMStrut(client.window, &strut)) {
1121     client.strut = new bt::EWMH::Strut;
1122     *client.strut = strut;
1123     _screen->addStrut(client.strut);
1124   }
1125 
1126   /*
1127     if we just managed the group leader for an existing group, move
1128     all group transients to this window
1129   */
1130   {
1131     BWindowGroup *group = blackbox->findWindowGroup(client.window);
1132     if (group) {
1133       BlackboxWindowList transientList = group->transients();
1134       BlackboxWindowList::const_iterator it = transientList.begin();
1135       const BlackboxWindowList::const_iterator end = transientList.end();
1136       for (; it != end; ++it) {
1137         BlackboxWindow * const w1 = *it;
1138         if (w1->client.transient_for != client.window)
1139           continue;
1140         group->removeTransient(w1);
1141         addTransient(w1);
1142         w1->changeWorkspace(workspace());
1143         w1->changeLayer(layer());
1144       }
1145     }
1146   }
1147 
1148   frame.window = createToplevelWindow();
1149   blackbox->insertEventHandler(frame.window, this);
1150 
1151   frame.plate = createChildWindow(frame.window, NoEventMask);
1152   blackbox->insertEventHandler(frame.plate, this);
1153 
1154   if (client.decorations & WindowDecorationTitlebar)
1155     createTitlebar();
1156 
1157   if (client.decorations & WindowDecorationHandle)
1158     createHandle();
1159 
1160   // apply the size and gravity to the frame
1161   const WindowStyle &style = _screen->resource().windowStyle();
1162   frame.margin = ::update_margin(client.decorations, style);
1163   frame.rect = ::applyGravity(client.rect,
1164                               frame.margin,
1165                               client.wmnormal.win_gravity);
1166 
1167   associateClientWindow();
1168 
1169   blackbox->insertEventHandler(client.window, this);
1170   blackbox->insertWindow(client.window, this);
1171   blackbox->insertWindow(frame.plate, this);
1172 
1173   // preserve the window's initial state on first map, and its current
1174   // state across a restart
1175   if (!readState(client.current_state, blackbox, client.window))
1176     client.current_state = client.wmhints.initial_state;
1177 
1178   if (client.state.iconic) {
1179     // prepare the window to be iconified
1180     client.current_state = IconicState;
1181     client.state.iconic = False;
1182   } else if (workspace() != bt::BSENTINEL &&
1183              workspace() != _screen->currentWorkspace()) {
1184     client.current_state = WithdrawnState;
1185   }
1186 
1187   blackbox->XUngrabServer();
1188 
1189   grabButtons();
1190 
1191   XMapSubwindows(blackbox->XDisplay(), frame.window);
1192 
1193   if (isFullScreen()) {
1194     client.ewmh.fullscreen = false; // trick setFullScreen into working
1195     setFullScreen(true);
1196   } else {
1197     if (isMaximized()) {
1198       remaximize();
1199     } else {
1200       const unsigned long save_state = client.current_state;
1201 
1202       bt::Rect r = frame.rect;
1203       // trick configure into working
1204       frame.rect = bt::Rect();
1205       configure(r);
1206 
1207       if (isShaded()) {
1208         client.ewmh.shaded = false;
1209         setShaded(true);
1210       }
1211 
1212       if (isShaded() && save_state != IconicState) {
1213         /*
1214           At this point in the life of a window, current_state should
1215           only be set to IconicState if the window was an *icon*, not
1216           if it was shaded.
1217         */
1218         client.current_state = save_state;
1219       }
1220     }
1221   }
1222 }
1223 
1224 
~BlackboxWindow(void)1225 BlackboxWindow::~BlackboxWindow(void) {
1226 #ifdef    DEBUG
1227   fprintf(stderr, "BlackboxWindow::~BlackboxWindow: destroying 0x%lx\n",
1228           client.window);
1229 #endif // DEBUG
1230 
1231   if (! timer) // window not managed...
1232     return;
1233 
1234   if (client.state.moving || client.state.resizing) {
1235     _screen->hideGeometry();
1236     XUngrabPointer(blackbox->XDisplay(), blackbox->XTime());
1237   }
1238 
1239   delete timer;
1240 
1241   if (client.strut) {
1242     _screen->removeStrut(client.strut);
1243     delete client.strut;
1244   }
1245 
1246   BWindowGroup *group = findWindowGroup();
1247 
1248   if (isTransient()) {
1249     // remove ourselves from our transient_for
1250     BlackboxWindow *win = findTransientFor();
1251     if (win) {
1252       win->removeTransient(this);
1253     } else if (isGroupTransient()) {
1254       if (group)
1255         group->removeTransient(this);
1256     }
1257     client.transient_for = 0;
1258   }
1259 
1260   if (group)
1261     group->removeWindow(this);
1262 
1263   if (frame.title)
1264     destroyTitlebar();
1265 
1266   if (frame.handle)
1267     destroyHandle();
1268 
1269   blackbox->removeEventHandler(client.window);
1270   blackbox->removeWindow(client.window);
1271 
1272   blackbox->removeEventHandler(frame.plate);
1273   blackbox->removeWindow(frame.plate);
1274   XDestroyWindow(blackbox->XDisplay(), frame.plate);
1275 
1276   blackbox->removeEventHandler(frame.window);
1277   XDestroyWindow(blackbox->XDisplay(), frame.window);
1278 }
1279 
1280 
1281 /*
1282  * Creates a new top level window, with a given location, size, and border
1283  * width.
1284  * Returns: the newly created window
1285  */
createToplevelWindow(void)1286 Window BlackboxWindow::createToplevelWindow(void) {
1287   XSetWindowAttributes attrib_create;
1288   unsigned long create_mask = CWColormap | CWOverrideRedirect | CWEventMask;
1289 
1290   attrib_create.colormap = _screen->screenInfo().colormap();
1291   attrib_create.override_redirect = True;
1292   attrib_create.event_mask = EnterWindowMask | LeaveWindowMask;
1293 
1294   return XCreateWindow(blackbox->XDisplay(),
1295                        _screen->screenInfo().rootWindow(), 0, 0, 1, 1, 0,
1296                        _screen->screenInfo().depth(), InputOutput,
1297                        _screen->screenInfo().visual(),
1298                        create_mask, &attrib_create);
1299 }
1300 
1301 
1302 /*
1303  * Creates a child window, and optionally associates a given cursor with
1304  * the new window.
1305  */
createChildWindow(Window parent,unsigned long event_mask,Cursor cursor)1306 Window BlackboxWindow::createChildWindow(Window parent,
1307                                          unsigned long event_mask,
1308                                          Cursor cursor) {
1309   XSetWindowAttributes attrib_create;
1310   unsigned long create_mask = CWEventMask;
1311 
1312   attrib_create.event_mask = event_mask;
1313 
1314   if (cursor) {
1315     create_mask |= CWCursor;
1316     attrib_create.cursor = cursor;
1317   }
1318 
1319   return XCreateWindow(blackbox->XDisplay(), parent, 0, 0, 1, 1, 0,
1320                        _screen->screenInfo().depth(), InputOutput,
1321                        _screen->screenInfo().visual(),
1322                        create_mask, &attrib_create);
1323 }
1324 
1325 
1326 /*
1327  * Reparents the client window into the newly created frame.
1328  *
1329  * Note: the server must be grabbed before calling this function.
1330  */
associateClientWindow(void)1331 void BlackboxWindow::associateClientWindow(void) {
1332   XSetWindowBorderWidth(blackbox->XDisplay(), client.window, 0);
1333   XChangeSaveSet(blackbox->XDisplay(), client.window, SetModeInsert);
1334 
1335   XSelectInput(blackbox->XDisplay(), frame.plate,
1336                FocusChangeMask | SubstructureRedirectMask);
1337 
1338   XSelectInput(blackbox->XDisplay(), client.window,
1339                client_window_event_mask & ~StructureNotifyMask);
1340   XReparentWindow(blackbox->XDisplay(), client.window, frame.plate, 0, 0);
1341   XSelectInput(blackbox->XDisplay(), client.window, client_window_event_mask);
1342 
1343 #ifdef    SHAPE
1344   if (blackbox->hasShapeExtensions()) {
1345     XShapeSelectInput(blackbox->XDisplay(), client.window,
1346                       ShapeNotifyMask);
1347 
1348     Bool shaped = False;
1349     int foo;
1350     unsigned int ufoo;
1351 
1352     XShapeQueryExtents(blackbox->XDisplay(), client.window, &shaped,
1353                        &foo, &foo, &ufoo, &ufoo, &foo, &foo, &foo,
1354                        &ufoo, &ufoo);
1355     client.state.shaped = shaped;
1356   }
1357 #endif // SHAPE
1358 }
1359 
1360 
decorate(void)1361 void BlackboxWindow::decorate(void) {
1362   const WindowStyle &style = _screen->resource().windowStyle();
1363   if (client.decorations & WindowDecorationTitlebar) {
1364     // render focused button texture
1365     frame.fbutton =
1366       bt::PixmapCache::find(_screen->screenNumber(),
1367                             style.focus.button,
1368                             style.button_width,
1369                             style.button_width,
1370                             frame.fbutton);
1371 
1372     // render unfocused button texture
1373     frame.ubutton =
1374       bt::PixmapCache::find(_screen->screenNumber(),
1375                             style.unfocus.button,
1376                             style.button_width,
1377                             style.button_width,
1378                             frame.ubutton);
1379 
1380     // render pressed button texture
1381     frame.pbutton =
1382       bt::PixmapCache::find(_screen->screenNumber(),
1383                             style.pressed,
1384                             style.button_width,
1385                             style.button_width,
1386                             frame.pbutton);
1387 
1388     // render focused titlebar texture
1389     frame.ftitle =
1390       bt::PixmapCache::find(_screen->screenNumber(),
1391                             style.focus.title,
1392                             frame.rect.width(),
1393                             style.title_height,
1394                             frame.ftitle);
1395 
1396     // render unfocused titlebar texture
1397     frame.utitle =
1398       bt::PixmapCache::find(_screen->screenNumber(),
1399                             style.unfocus.title,
1400                             frame.rect.width(),
1401                             style.title_height,
1402                             frame.utitle);
1403 
1404     // render focused label texture
1405     frame.flabel =
1406       bt::PixmapCache::find(_screen->screenNumber(),
1407                             style.focus.label,
1408                             frame.label_w,
1409                             style.label_height,
1410                             frame.flabel);
1411 
1412     // render unfocused label texture
1413     frame.ulabel =
1414       bt::PixmapCache::find(_screen->screenNumber(),
1415                             style.unfocus.label,
1416                             frame.label_w,
1417                             style.label_height,
1418                             frame.ulabel);
1419   }
1420 
1421   if (client.decorations & WindowDecorationHandle) {
1422     frame.fhandle =
1423       bt::PixmapCache::find(_screen->screenNumber(),
1424                             style.focus.handle,
1425                             frame.rect.width(),
1426                             style.handle_height,
1427                             frame.fhandle);
1428 
1429     frame.uhandle =
1430       bt::PixmapCache::find(_screen->screenNumber(),
1431                             style.unfocus.handle,
1432                             frame.rect.width(),
1433                             style.handle_height,
1434                             frame.uhandle);
1435   }
1436 
1437   if (client.decorations & WindowDecorationGrip) {
1438     frame.fgrip =
1439       bt::PixmapCache::find(_screen->screenNumber(),
1440                             style.focus.grip,
1441                             style.grip_width,
1442                             style.handle_height,
1443                             frame.fgrip);
1444 
1445     frame.ugrip =
1446       bt::PixmapCache::find(_screen->screenNumber(),
1447                             style.unfocus.grip,
1448                             style.grip_width,
1449                             style.handle_height,
1450                             frame.ugrip);
1451   }
1452 }
1453 
1454 
createHandle(void)1455 void BlackboxWindow::createHandle(void) {
1456   frame.handle = createChildWindow(frame.window,
1457                                    ButtonPressMask | ButtonReleaseMask |
1458                                    ButtonMotionMask | ExposureMask);
1459   blackbox->insertEventHandler(frame.handle, this);
1460 
1461   if (client.decorations & WindowDecorationGrip)
1462     createGrips();
1463 }
1464 
1465 
destroyHandle(void)1466 void BlackboxWindow::destroyHandle(void) {
1467   if (frame.left_grip || frame.right_grip)
1468     destroyGrips();
1469 
1470   if (frame.fhandle) bt::PixmapCache::release(frame.fhandle);
1471   if (frame.uhandle) bt::PixmapCache::release(frame.uhandle);
1472 
1473   frame.fhandle = frame.uhandle = None;
1474 
1475   blackbox->removeEventHandler(frame.handle);
1476   XDestroyWindow(blackbox->XDisplay(), frame.handle);
1477   frame.handle = None;
1478 }
1479 
1480 
createGrips(void)1481 void BlackboxWindow::createGrips(void) {
1482   frame.left_grip =
1483     createChildWindow(frame.handle,
1484                       ButtonPressMask | ButtonReleaseMask |
1485                       ButtonMotionMask | ExposureMask,
1486                       blackbox->resource().cursors().resize_bottom_left);
1487   blackbox->insertEventHandler(frame.left_grip, this);
1488 
1489   frame.right_grip =
1490     createChildWindow(frame.handle,
1491                       ButtonPressMask | ButtonReleaseMask |
1492                       ButtonMotionMask | ExposureMask,
1493                       blackbox->resource().cursors().resize_bottom_right);
1494   blackbox->insertEventHandler(frame.right_grip, this);
1495 }
1496 
1497 
destroyGrips(void)1498 void BlackboxWindow::destroyGrips(void) {
1499   if (frame.fgrip) bt::PixmapCache::release(frame.fgrip);
1500   if (frame.ugrip) bt::PixmapCache::release(frame.ugrip);
1501 
1502   frame.fgrip = frame.ugrip = None;
1503 
1504   blackbox->removeEventHandler(frame.left_grip);
1505   blackbox->removeEventHandler(frame.right_grip);
1506 
1507   XDestroyWindow(blackbox->XDisplay(), frame.left_grip);
1508   XDestroyWindow(blackbox->XDisplay(), frame.right_grip);
1509   frame.left_grip = frame.right_grip = None;
1510 }
1511 
1512 
createTitlebar(void)1513 void BlackboxWindow::createTitlebar(void) {
1514   frame.title = createChildWindow(frame.window,
1515                                   ButtonPressMask | ButtonReleaseMask |
1516                                   ButtonMotionMask | ExposureMask);
1517   frame.label = createChildWindow(frame.title,
1518                                   ButtonPressMask | ButtonReleaseMask |
1519                                   ButtonMotionMask | ExposureMask);
1520   blackbox->insertEventHandler(frame.title, this);
1521   blackbox->insertEventHandler(frame.label, this);
1522 
1523   if (client.decorations & WindowDecorationIconify) createIconifyButton();
1524   if (client.decorations & WindowDecorationMaximize) createMaximizeButton();
1525   if (client.decorations & WindowDecorationClose) createCloseButton();
1526 }
1527 
1528 
destroyTitlebar(void)1529 void BlackboxWindow::destroyTitlebar(void) {
1530   if (frame.close_button)
1531     destroyCloseButton();
1532 
1533   if (frame.iconify_button)
1534     destroyIconifyButton();
1535 
1536   if (frame.maximize_button)
1537     destroyMaximizeButton();
1538 
1539   if (frame.fbutton) bt::PixmapCache::release(frame.fbutton);
1540   if (frame.ubutton) bt::PixmapCache::release(frame.ubutton);
1541   if (frame.pbutton) bt::PixmapCache::release(frame.pbutton);
1542   if (frame.ftitle)  bt::PixmapCache::release(frame.ftitle);
1543   if (frame.utitle)  bt::PixmapCache::release(frame.utitle);
1544   if (frame.flabel)  bt::PixmapCache::release(frame.flabel);
1545   if (frame.ulabel)  bt::PixmapCache::release(frame.ulabel);
1546 
1547   frame.fbutton = frame.ubutton = frame.pbutton =
1548    frame.ftitle = frame.utitle =
1549    frame.flabel = frame.ulabel = None;
1550 
1551   blackbox->removeEventHandler(frame.title);
1552   blackbox->removeEventHandler(frame.label);
1553 
1554   XDestroyWindow(blackbox->XDisplay(), frame.label);
1555   XDestroyWindow(blackbox->XDisplay(), frame.title);
1556   frame.title = frame.label = None;
1557 }
1558 
1559 
createCloseButton(void)1560 void BlackboxWindow::createCloseButton(void) {
1561   if (frame.title != None) {
1562     frame.close_button = createChildWindow(frame.title,
1563                                            ButtonPressMask |
1564                                            ButtonReleaseMask |
1565                                            ButtonMotionMask | ExposureMask);
1566     blackbox->insertEventHandler(frame.close_button, this);
1567   }
1568 }
1569 
1570 
destroyCloseButton(void)1571 void BlackboxWindow::destroyCloseButton(void) {
1572   blackbox->removeEventHandler(frame.close_button);
1573   XDestroyWindow(blackbox->XDisplay(), frame.close_button);
1574   frame.close_button = None;
1575 }
1576 
1577 
createIconifyButton(void)1578 void BlackboxWindow::createIconifyButton(void) {
1579   if (frame.title != None) {
1580     frame.iconify_button = createChildWindow(frame.title,
1581                                              ButtonPressMask |
1582                                              ButtonReleaseMask |
1583                                              ButtonMotionMask | ExposureMask);
1584     blackbox->insertEventHandler(frame.iconify_button, this);
1585   }
1586 }
1587 
1588 
destroyIconifyButton(void)1589 void BlackboxWindow::destroyIconifyButton(void) {
1590   blackbox->removeEventHandler(frame.iconify_button);
1591   XDestroyWindow(blackbox->XDisplay(), frame.iconify_button);
1592   frame.iconify_button = None;
1593 }
1594 
1595 
createMaximizeButton(void)1596 void BlackboxWindow::createMaximizeButton(void) {
1597   if (frame.title != None) {
1598     frame.maximize_button = createChildWindow(frame.title,
1599                                               ButtonPressMask |
1600                                               ButtonReleaseMask |
1601                                               ButtonMotionMask | ExposureMask);
1602     blackbox->insertEventHandler(frame.maximize_button, this);
1603   }
1604 }
1605 
1606 
destroyMaximizeButton(void)1607 void BlackboxWindow::destroyMaximizeButton(void) {
1608   blackbox->removeEventHandler(frame.maximize_button);
1609   XDestroyWindow(blackbox->XDisplay(), frame.maximize_button);
1610   frame.maximize_button = None;
1611 }
1612 
1613 
positionButtons(bool redecorate_label)1614 void BlackboxWindow::positionButtons(bool redecorate_label) {
1615   // we need to use signed ints here to detect windows that are too small
1616   const WindowStyle &style = _screen->resource().windowStyle();
1617   const int extra = style.title_margin == 0 ?
1618                     style.focus.button.borderWidth() : 0,
1619                bw = style.button_width + style.title_margin
1620                     - extra,
1621                by = style.title_margin +
1622                     style.focus.title.borderWidth();
1623   int lx = by, lw = frame.rect.width() - by;
1624 
1625   if (client.decorations & WindowDecorationIconify) {
1626     if (frame.iconify_button == None) createIconifyButton();
1627 
1628     XMoveResizeWindow(blackbox->XDisplay(), frame.iconify_button, by, by,
1629                       style.button_width, style.button_width);
1630     XMapWindow(blackbox->XDisplay(), frame.iconify_button);
1631 
1632     lx += bw;
1633     lw -= bw;
1634   } else if (frame.iconify_button) {
1635     destroyIconifyButton();
1636   }
1637 
1638   int bx = frame.rect.width() - bw
1639            - style.focus.title.borderWidth() - extra;
1640 
1641   if (client.decorations & WindowDecorationClose) {
1642     if (frame.close_button == None) createCloseButton();
1643 
1644     XMoveResizeWindow(blackbox->XDisplay(), frame.close_button, bx, by,
1645                       style.button_width, style.button_width);
1646     XMapWindow(blackbox->XDisplay(), frame.close_button);
1647 
1648     bx -= bw;
1649     lw -= bw;
1650   } else if (frame.close_button) {
1651     destroyCloseButton();
1652   }
1653 
1654   if (client.decorations & WindowDecorationMaximize) {
1655     if (frame.maximize_button == None) createMaximizeButton();
1656 
1657     XMoveResizeWindow(blackbox->XDisplay(), frame.maximize_button, bx, by,
1658                       style.button_width, style.button_width);
1659     XMapWindow(blackbox->XDisplay(), frame.maximize_button);
1660 
1661     bx -= bw;
1662     lw -= bw;
1663   } else if (frame.maximize_button) {
1664     destroyMaximizeButton();
1665   }
1666 
1667   if (lw > by) {
1668     frame.label_w = lw - by;
1669     XMoveResizeWindow(blackbox->XDisplay(), frame.label, lx, by,
1670                       frame.label_w, style.label_height);
1671     XMapWindow(blackbox->XDisplay(), frame.label);
1672 
1673     if (redecorate_label) {
1674       frame.flabel =
1675         bt::PixmapCache::find(_screen->screenNumber(),
1676                               style.focus.label,
1677                               frame.label_w, style.label_height,
1678                               frame.flabel);
1679       frame.ulabel =
1680         bt::PixmapCache::find(_screen->screenNumber(),
1681                               style.unfocus.label,
1682                               frame.label_w, style.label_height,
1683                               frame.ulabel);
1684     }
1685 
1686     const bt::ustring ellided =
1687       bt::ellideText(client.title, frame.label_w, bt::toUnicode("..."),
1688                      _screen->screenNumber(), style.font);
1689 
1690     if (ellided != client.visible_title) {
1691       client.visible_title = ellided;
1692       blackbox->ewmh().setWMVisibleName(client.window, client.visible_title);
1693     }
1694   } else {
1695     frame.label_w = 1;
1696     XUnmapWindow(blackbox->XDisplay(), frame.label);
1697   }
1698 
1699   redrawLabel();
1700   redrawAllButtons();
1701 }
1702 
1703 
reconfigure(void)1704 void BlackboxWindow::reconfigure(void) {
1705   const WindowStyle &style = _screen->resource().windowStyle();
1706   if (isMaximized()) {
1707     // update the frame margin in case the style has changed
1708     frame.margin = ::update_margin(client.decorations, style);
1709 
1710     // make sure maximized windows have the correct size after a style
1711     // change
1712     remaximize();
1713   } else {
1714     // get the client window geometry as if it was unmanaged
1715     bt::Rect r = frame.rect;
1716     if (client.ewmh.shaded) {
1717       r.setHeight(client.rect.height() + frame.margin.top
1718                   + frame.margin.bottom);
1719     }
1720     r = ::restoreGravity(r, frame.margin, client.wmnormal.win_gravity);
1721 
1722     // update the frame margin in case the style has changed
1723     frame.margin = ::update_margin(client.decorations, style);
1724 
1725     // get the frame window geometry from the client window geometry
1726     // calculated above
1727     r = ::applyGravity(r, frame.margin, client.wmnormal.win_gravity);
1728     if (client.ewmh.shaded) {
1729       frame.rect = r;
1730 
1731       positionWindows();
1732       decorate();
1733 
1734       // keep the window shaded
1735       frame.rect.setHeight(style.title_height);
1736       XResizeWindow(blackbox->XDisplay(), frame.window,
1737                     frame.rect.width(), frame.rect.height());
1738     } else {
1739       // trick configure into working
1740       frame.rect = bt::Rect();
1741       configure(r);
1742     }
1743   }
1744 
1745   ungrabButtons();
1746   grabButtons();
1747 }
1748 
1749 
grabButtons(void)1750 void BlackboxWindow::grabButtons(void) {
1751   if (blackbox->resource().focusModel() == ClickToFocusModel
1752       || blackbox->resource().clickRaise())
1753     // grab button 1 for changing focus/raising
1754     blackbox->grabButton(Button1, 0, frame.plate, True, ButtonPressMask,
1755                          GrabModeSync, GrabModeSync, frame.plate, None,
1756                          blackbox->resource().allowScrollLock());
1757 
1758   if (hasWindowFunction(WindowFunctionMove))
1759     blackbox->grabButton(Button1, Mod1Mask, frame.window, True,
1760                          ButtonReleaseMask | ButtonMotionMask, GrabModeAsync,
1761                          GrabModeAsync, frame.window,
1762                          blackbox->resource().cursors().move,
1763                          blackbox->resource().allowScrollLock());
1764   if (hasWindowFunction(WindowFunctionResize))
1765     blackbox->grabButton(Button3, Mod1Mask, frame.window, True,
1766                          ButtonReleaseMask | ButtonMotionMask, GrabModeAsync,
1767                          GrabModeAsync, frame.window,
1768                          None, blackbox->resource().allowScrollLock());
1769   // alt+middle lowers the window
1770   blackbox->grabButton(Button2, Mod1Mask, frame.window, True,
1771                        ButtonReleaseMask, GrabModeAsync, GrabModeAsync,
1772                        frame.window, None,
1773                        blackbox->resource().allowScrollLock());
1774 
1775   blackbox->grabButton(Button3, Mod4Mask, frame.window, True,
1776                        ButtonReleaseMask, GrabModeAsync, GrabModeAsync,
1777                        frame.window, None,
1778                        blackbox->resource().allowScrollLock());
1779 }
1780 
1781 
ungrabButtons(void)1782 void BlackboxWindow::ungrabButtons(void) {
1783   blackbox->ungrabButton(Button1, 0, frame.plate);
1784   blackbox->ungrabButton(Button1, Mod1Mask, frame.window);
1785   blackbox->ungrabButton(Button2, Mod1Mask, frame.window);
1786   blackbox->ungrabButton(Button3, Mod1Mask, frame.window);
1787   blackbox->ungrabButton(Button3, Mod4Mask, frame.window);
1788 }
1789 
1790 
positionWindows(void)1791 void BlackboxWindow::positionWindows(void) {
1792   const WindowStyle &style = _screen->resource().windowStyle();
1793   const unsigned int bw = (hasWindowDecoration(WindowDecorationBorder)
1794                            ? style.frame_border_width
1795                            : 0);
1796 
1797   XMoveResizeWindow(blackbox->XDisplay(), frame.plate,
1798                     frame.margin.left - bw,
1799                     frame.margin.top - bw,
1800                     client.rect.width(), client.rect.height());
1801   XSetWindowBorderWidth(blackbox->XDisplay(), frame.plate, bw);
1802   XMoveResizeWindow(blackbox->XDisplay(), client.window,
1803                     0, 0, client.rect.width(), client.rect.height());
1804   // ensure client.rect contains the real location
1805   client.rect.setPos(frame.rect.left() + frame.margin.left,
1806                      frame.rect.top() + frame.margin.top);
1807 
1808   if (client.decorations & WindowDecorationTitlebar) {
1809     if (frame.title == None) createTitlebar();
1810 
1811     XMoveResizeWindow(blackbox->XDisplay(), frame.title,
1812                       0, 0, frame.rect.width(), style.title_height);
1813 
1814     positionButtons();
1815     XMapSubwindows(blackbox->XDisplay(), frame.title);
1816     XMapWindow(blackbox->XDisplay(), frame.title);
1817   } else if (frame.title) {
1818     destroyTitlebar();
1819   }
1820 
1821   if (client.decorations & WindowDecorationHandle) {
1822     if (frame.handle == None) createHandle();
1823 
1824     // use client.rect here so the value is correct even if shaded
1825     XMoveResizeWindow(blackbox->XDisplay(), frame.handle,
1826                       0, client.rect.height() + frame.margin.top,
1827                       frame.rect.width(), style.handle_height);
1828 
1829     if (client.decorations & WindowDecorationGrip) {
1830       if (frame.left_grip == None || frame.right_grip == None) createGrips();
1831 
1832       XMoveResizeWindow(blackbox->XDisplay(), frame.left_grip, 0, 0,
1833                         style.grip_width, style.handle_height);
1834 
1835       const int nx = frame.rect.width() - style.grip_width;
1836       XMoveResizeWindow(blackbox->XDisplay(), frame.right_grip, nx, 0,
1837                         style.grip_width, style.handle_height);
1838 
1839       XMapSubwindows(blackbox->XDisplay(), frame.handle);
1840     } else {
1841       destroyGrips();
1842     }
1843 
1844     XMapWindow(blackbox->XDisplay(), frame.handle);
1845   } else if (frame.handle) {
1846     destroyHandle();
1847   }
1848 }
1849 
1850 
1851 /*
1852  * This function is responsible for updating both the client and the
1853  * frame rectangles.  According to the ICCCM a client message is not
1854  * sent for a resize, only a move.
1855  */
configure(int dx,int dy,unsigned int dw,unsigned int dh)1856 void BlackboxWindow::configure(int dx, int dy,
1857                                unsigned int dw, unsigned int dh) {
1858   bool send_event = ((frame.rect.x() != dx || frame.rect.y() != dy) &&
1859                      ! client.state.moving);
1860 
1861   if (dw != frame.rect.width() || dh != frame.rect.height()) {
1862     frame.rect.setRect(dx, dy, dw, dh);
1863 
1864     if (frame.rect.right() <= 0 || frame.rect.bottom() <= 0)
1865       frame.rect.setPos(0, 0);
1866 
1867     client.rect.setCoords(frame.rect.left() + frame.margin.left,
1868                           frame.rect.top() + frame.margin.top,
1869                           frame.rect.right() - frame.margin.right,
1870                           frame.rect.bottom() - frame.margin.bottom);
1871 
1872 #ifdef SHAPE
1873     if (client.state.shaped)
1874       configureShape();
1875 #endif // SHAPE
1876 
1877     XMoveResizeWindow(blackbox->XDisplay(), frame.window,
1878                       frame.rect.x(), frame.rect.y(),
1879                       frame.rect.width(), frame.rect.height());
1880 
1881     positionWindows();
1882     decorate();
1883     redrawWindowFrame();
1884   } else {
1885     frame.rect.setPos(dx, dy);
1886 
1887     XMoveWindow(blackbox->XDisplay(), frame.window,
1888                 frame.rect.x(), frame.rect.y());
1889     /*
1890       we may have been called just after an opaque window move, so
1891       even though the old coords match the new ones no ConfigureNotify
1892       has been sent yet.  There are likely other times when this will
1893       be relevant as well.
1894     */
1895     if (! client.state.moving) send_event = True;
1896   }
1897 
1898   if (send_event) {
1899     // if moving, the update and event will occur when the move finishes
1900     client.rect.setPos(frame.rect.left() + frame.margin.left,
1901                        frame.rect.top() + frame.margin.top);
1902 
1903     XEvent event;
1904     event.type = ConfigureNotify;
1905 
1906     event.xconfigure.display = blackbox->XDisplay();
1907     event.xconfigure.event = client.window;
1908     event.xconfigure.window = client.window;
1909     event.xconfigure.x = client.rect.x();
1910     event.xconfigure.y = client.rect.y();
1911     event.xconfigure.width = client.rect.width();
1912     event.xconfigure.height = client.rect.height();
1913     event.xconfigure.border_width = client.old_bw;
1914     event.xconfigure.above = frame.window;
1915     event.xconfigure.override_redirect = False;
1916 
1917     XSendEvent(blackbox->XDisplay(), client.window, False,
1918                StructureNotifyMask, &event);
1919   }
1920 }
1921 
1922 
1923 #ifdef SHAPE
configureShape(void)1924 void BlackboxWindow::configureShape(void) {
1925   XShapeCombineShape(blackbox->XDisplay(), frame.window, ShapeBounding,
1926                      frame.margin.left, frame.margin.top,
1927                      client.window, ShapeBounding, ShapeSet);
1928 
1929   int num = 0;
1930   XRectangle xrect[2];
1931 
1932   const WindowStyle &style = _screen->resource().windowStyle();
1933   if (client.decorations & WindowDecorationTitlebar) {
1934     xrect[0].x = xrect[0].y = 0;
1935     xrect[0].width = frame.rect.width();
1936     xrect[0].height = style.title_height;
1937     ++num;
1938   }
1939 
1940   if (client.decorations & WindowDecorationHandle) {
1941     xrect[1].x = 0;
1942     xrect[1].y = client.rect.height() + frame.margin.top;
1943     xrect[1].width = frame.rect.width();
1944     xrect[1].height = style.handle_height;
1945     ++num;
1946   }
1947 
1948   XShapeCombineRectangles(blackbox->XDisplay(), frame.window,
1949                           ShapeBounding, 0, 0, xrect, num,
1950                           ShapeUnion, Unsorted);
1951 }
1952 #endif // SHAPE
1953 
1954 
addTransient(BlackboxWindow * win)1955 void BlackboxWindow::addTransient(BlackboxWindow *win)
1956 { client.transientList.push_front(win); }
1957 
1958 
removeTransient(BlackboxWindow * win)1959 void BlackboxWindow::removeTransient(BlackboxWindow *win)
1960 { client.transientList.remove(win); }
1961 
1962 
findTransientFor(void) const1963 BlackboxWindow *BlackboxWindow::findTransientFor(void) const {
1964   BlackboxWindow *win = 0;
1965   if (isTransient()) {
1966     win = blackbox->findWindow(client.transient_for);
1967     if (win && win->_screen != _screen)
1968       win = 0;
1969   }
1970   return win;
1971 }
1972 
1973 
1974 /*
1975   walk up to either 1) a non-transient window 2) a group transient,
1976   watching out for a circular chain
1977 
1978   this function returns zero for non-transient windows
1979 */
findNonTransientParent(void) const1980 BlackboxWindow *BlackboxWindow::findNonTransientParent(void) const {
1981   BlackboxWindowList seen;
1982   seen.push_back(const_cast<BlackboxWindow *>(this));
1983 
1984   BlackboxWindow *w = findTransientFor();
1985   if (!w)
1986     return 0;
1987 
1988   while (w->isTransient() && !w->isGroupTransient()) {
1989     seen.push_back(w);
1990     BlackboxWindow * const tmp = w->findTransientFor();
1991     if (!tmp)
1992       break;
1993     if (std::find(seen.begin(), seen.end(), tmp) != seen.end()) {
1994       // circular transient chain
1995       break;
1996     }
1997     w = tmp;
1998   }
1999   return w;
2000 }
2001 
2002 
2003 /*
2004   Returns a list of all transients.  This is recursive, so it returns
2005   all transients of transients as well.
2006 */
buildFullTransientList(void) const2007 BlackboxWindowList BlackboxWindow::buildFullTransientList(void) const {
2008   BlackboxWindowList all = client.transientList;
2009   BlackboxWindowList::const_iterator it = client.transientList.begin(),
2010                                     end = client.transientList.end();
2011   for (; it != end; ++it) {
2012     BlackboxWindowList x = (*it)->buildFullTransientList();
2013     all.splice(all.end(), x);
2014   }
2015   return all;
2016 }
2017 
2018 
findWindowGroup(void) const2019 BWindowGroup *BlackboxWindow::findWindowGroup(void) const {
2020   BWindowGroup *group = 0;
2021   if (client.wmhints.window_group)
2022     group = blackbox->findWindowGroup(client.wmhints.window_group);
2023   return group;
2024 }
2025 
2026 
setWorkspace(unsigned int new_workspace)2027 void BlackboxWindow::setWorkspace(unsigned int new_workspace) {
2028   client.ewmh.workspace = new_workspace;
2029   blackbox->ewmh().setWMDesktop(client.window, client.ewmh.workspace);
2030 }
2031 
2032 
changeWorkspace(unsigned int new_workspace,ChangeWorkspaceOption how)2033 void BlackboxWindow::changeWorkspace(unsigned int new_workspace,
2034                                      ChangeWorkspaceOption how) {
2035   if (client.ewmh.workspace == new_workspace)
2036     return;
2037 
2038   if (isTransient()) {
2039     BlackboxWindow *win = findTransientFor();
2040     if (win) {
2041       if (win->workspace() != new_workspace) {
2042         win->changeWorkspace(new_workspace, how);
2043         return;
2044       }
2045     }
2046   } else {
2047     assert(hasWindowFunction(WindowFunctionChangeWorkspace));
2048   }
2049 
2050   Workspace *ws;
2051   if (workspace() != bt::BSENTINEL) {
2052     ws = _screen->findWorkspace(workspace());
2053     assert(ws != 0);
2054     ws->removeWindow(this);
2055   }
2056 
2057   if (new_workspace != bt::BSENTINEL) {
2058     ws = _screen->findWorkspace(new_workspace);
2059     assert(ws != 0);
2060     ws->addWindow(this);
2061   }
2062 
2063   switch (how) {
2064   case StayOnCurrentWorkspace:
2065     if (isVisible() && workspace() != bt::BSENTINEL
2066         && workspace() != _screen->currentWorkspace()) {
2067       hide();
2068     } else if (!isVisible()
2069                && (workspace() == bt::BSENTINEL
2070                    || workspace() == _screen->currentWorkspace())) {
2071       show();
2072     }
2073     break;
2074 
2075   case SwitchToNewWorkspace:
2076     /*
2077       we will change to the new workspace soon, so force this window
2078       to be visible
2079     */
2080     show();
2081     break;
2082   }
2083 
2084   // change workspace on all transients
2085   if (!client.transientList.empty()) {
2086     BlackboxWindowList::iterator it = client.transientList.begin(),
2087                                 end = client.transientList.end();
2088     for (; it != end; ++it)
2089       (*it)->changeWorkspace(new_workspace, how);
2090   }
2091 }
2092 
2093 
changeLayer(StackingList::Layer new_layer)2094 void BlackboxWindow::changeLayer(StackingList::Layer new_layer) {
2095   if (layer() == new_layer)
2096     return;
2097 
2098   bool restack = false;
2099   if (isTransient()) {
2100     BlackboxWindow *win = findTransientFor();
2101     if (win) {
2102       if (win->layer() != new_layer) {
2103         win->changeLayer(new_layer);
2104         return;
2105       } else {
2106         restack = true;
2107       }
2108     }
2109   } else {
2110     assert(hasWindowFunction(WindowFunctionChangeLayer));
2111     restack = true;
2112   }
2113 
2114   _screen->stackingList().changeLayer(this, new_layer);
2115 
2116   if (!client.transientList.empty()) {
2117     BlackboxWindowList::iterator it = client.transientList.begin();
2118     const BlackboxWindowList::iterator end = client.transientList.end();
2119     for (; it != end; ++it)
2120       (*it)->changeLayer(new_layer);
2121   }
2122 
2123   if (restack)
2124     _screen->restackWindows();
2125 }
2126 
2127 
setInputFocus(void)2128 bool BlackboxWindow::setInputFocus(void) {
2129   if (!isVisible())
2130     return false;
2131   if (client.state.focused)
2132     return true;
2133 
2134   switch (windowType()) {
2135   case WindowTypeDock:
2136     /*
2137       Many docks have auto-hide features similar to the toolbar and
2138       slit... we don't want these things to be moved to the center of
2139       the screen when switching to an empty workspace.
2140     */
2141     break;
2142 
2143   default:
2144     {  const bt::Rect &scr = _screen->screenInfo().rect();
2145       if (!frame.rect.intersects(scr)) {
2146         // client is outside the screen, move it to the center
2147         configure(scr.x() + (scr.width() - frame.rect.width()) / 2,
2148                   scr.y() + (scr.height() - frame.rect.height()) / 2,
2149                   frame.rect.width(), frame.rect.height());
2150       }
2151       break;
2152     }
2153   }
2154 
2155   /*
2156     pass focus to any modal transients, giving modal group transients
2157     higher priority
2158   */
2159   BWindowGroup *group = findWindowGroup();
2160   if (group && !group->transients().empty()) {
2161     BlackboxWindowList::const_iterator it = group->transients().begin(),
2162                                       end = group->transients().end();
2163     for (; it != end; ++it) {
2164       BlackboxWindow * const tmp = *it;
2165       if (!tmp->isVisible() || !tmp->isModal())
2166         continue;
2167       if (tmp == this) {
2168         // we are the newest modal group transient
2169         break;
2170       }
2171       if (isTransient()) {
2172         if (tmp == findNonTransientParent()) {
2173           // we are a transient of the modal group transient
2174           break;
2175         }
2176       }
2177       return tmp->setInputFocus();
2178     }
2179   }
2180 
2181   if (!client.transientList.empty()) {
2182     BlackboxWindowList::const_iterator it = client.transientList.begin(),
2183                                       end = client.transientList.end();
2184     for (; it != end; ++it) {
2185       BlackboxWindow * const tmp = *it;
2186       if (tmp->isVisible() && tmp->isModal())
2187         return tmp->setInputFocus();
2188     }
2189   }
2190 
2191   switch (windowType()) {
2192   case WindowTypeDock:
2193     return false;
2194   default:
2195     break;
2196   }
2197 
2198   XSetInputFocus(blackbox->XDisplay(), client.window,
2199                  RevertToPointerRoot, blackbox->XTime());
2200 
2201   if (client.wmprotocols.wm_take_focus) {
2202     XEvent ce;
2203     ce.xclient.type = ClientMessage;
2204     ce.xclient.message_type = blackbox->wmProtocolsAtom();
2205     ce.xclient.display = blackbox->XDisplay();
2206     ce.xclient.window = client.window;
2207     ce.xclient.format = 32;
2208     ce.xclient.data.l[0] = blackbox->wmTakeFocusAtom();
2209     ce.xclient.data.l[1] = blackbox->XTime();
2210     ce.xclient.data.l[2] = 0l;
2211     ce.xclient.data.l[3] = 0l;
2212     ce.xclient.data.l[4] = 0l;
2213     XSendEvent(blackbox->XDisplay(), client.window, False, NoEventMask, &ce);
2214   }
2215 
2216   return true;
2217 }
2218 
2219 
show(void)2220 void BlackboxWindow::show(void) {
2221   if (client.state.visible)
2222     return;
2223 
2224   if (client.state.iconic)
2225     _screen->removeIcon(this);
2226 
2227   client.state.iconic = false;
2228   client.state.visible = true;
2229   setState(isShaded() ? IconicState : NormalState);
2230 
2231   XMapWindow(blackbox->XDisplay(), client.window);
2232   XMapSubwindows(blackbox->XDisplay(), frame.window);
2233   XMapWindow(blackbox->XDisplay(), frame.window);
2234 
2235   if (!client.transientList.empty()) {
2236     BlackboxWindowList::iterator it = client.transientList.begin(),
2237                                 end = client.transientList.end();
2238     for (; it != end; ++it)
2239       (*it)->show();
2240   }
2241 
2242 #ifdef DEBUG
2243   int real_x, real_y;
2244   Window child;
2245   XTranslateCoordinates(blackbox->XDisplay(), client.window,
2246                         _screen->screenInfo().rootWindow(),
2247                         0, 0, &real_x, &real_y, &child);
2248   fprintf(stderr, "%s -- assumed: (%d, %d), real: (%d, %d)\n", title().c_str(),
2249           client.rect.left(), client.rect.top(), real_x, real_y);
2250   assert(client.rect.left() == real_x && client.rect.top() == real_y);
2251 #endif
2252 }
2253 
2254 
hide(void)2255 void BlackboxWindow::hide(void) {
2256   if (!client.state.visible)
2257     return;
2258 
2259   client.state.visible = false;
2260   setState(client.state.iconic ? IconicState : client.current_state);
2261 
2262   XUnmapWindow(blackbox->XDisplay(), frame.window);
2263 
2264   /*
2265    * we don't want this XUnmapWindow call to generate an UnmapNotify
2266    * event, so we need to clear the event mask on client.window for a
2267    * split second.  HOWEVER, since X11 is asynchronous, the window
2268    * could be destroyed in that split second, leaving us with a ghost
2269    * window... so, we need to do this while the X server is grabbed
2270    */
2271   blackbox->XGrabServer();
2272   XSelectInput(blackbox->XDisplay(), client.window,
2273                client_window_event_mask & ~StructureNotifyMask);
2274   XUnmapWindow(blackbox->XDisplay(), client.window);
2275   XSelectInput(blackbox->XDisplay(), client.window, client_window_event_mask);
2276   blackbox->XUngrabServer();
2277 }
2278 
2279 
close(void)2280 void BlackboxWindow::close(void) {
2281   assert(hasWindowFunction(WindowFunctionClose));
2282 
2283   XEvent ce;
2284   ce.xclient.type = ClientMessage;
2285   ce.xclient.message_type = blackbox->wmProtocolsAtom();
2286   ce.xclient.display = blackbox->XDisplay();
2287   ce.xclient.window = client.window;
2288   ce.xclient.format = 32;
2289   ce.xclient.data.l[0] = blackbox->wmDeleteWindowAtom();
2290   ce.xclient.data.l[1] = blackbox->XTime();
2291   ce.xclient.data.l[2] = 0l;
2292   ce.xclient.data.l[3] = 0l;
2293   ce.xclient.data.l[4] = 0l;
2294   XSendEvent(blackbox->XDisplay(), client.window, False, NoEventMask, &ce);
2295 }
2296 
2297 
activate(void)2298 void BlackboxWindow::activate(void) {
2299   if (workspace() != bt::BSENTINEL
2300       && workspace() != _screen->currentWorkspace())
2301     _screen->setCurrentWorkspace(workspace());
2302   if (client.state.iconic)
2303     show();
2304   if (client.ewmh.shaded)
2305     setShaded(false);
2306   if (setInputFocus())
2307     _screen->raiseWindow(this);
2308 }
2309 
2310 
iconify(void)2311 void BlackboxWindow::iconify(void) {
2312   if (client.state.iconic)
2313     return;
2314 
2315   if (isTransient()) {
2316     BlackboxWindow *win = findTransientFor();
2317     if (win) {
2318       if (!win->isIconic()) {
2319         win->iconify();
2320         return;
2321       }
2322     }
2323   } else {
2324     assert(hasWindowFunction(WindowFunctionIconify));
2325   }
2326 
2327   _screen->addIcon(this);
2328 
2329   client.state.iconic = true;
2330   hide();
2331 
2332   // iconify all transients
2333   if (!client.transientList.empty()) {
2334     BlackboxWindowList::iterator it = client.transientList.begin(),
2335                                 end = client.transientList.end();
2336     for (; it != end; ++it)
2337       (*it)->iconify();
2338   }
2339 }
2340 
2341 
maximize(unsigned int button)2342 void BlackboxWindow::maximize(unsigned int button) {
2343   assert(hasWindowFunction(WindowFunctionMaximize));
2344 
2345   // any maximize operation always unshades
2346   client.ewmh.shaded = false;
2347   frame.rect.setHeight(client.rect.height() + frame.margin.top
2348                        + frame.margin.bottom);
2349 
2350   if (isMaximized()) {
2351     // restore from maximized
2352     client.ewmh.maxh = client.ewmh.maxv = false;
2353 
2354     if (!isFullScreen()) {
2355       /*
2356         when a resize is begun, maximize(0) is called to clear any
2357         maximization flags currently set.  Otherwise it still thinks
2358         it is maximized.  so we do not need to call configure()
2359         because resizing will handle it
2360       */
2361       if (! client.state.resizing) {
2362         bt::Rect r = ::applyGravity(client.premax,
2363                                     frame.margin,
2364                                     client.wmnormal.win_gravity);
2365         r = ::constrain(r, frame.margin, client.wmnormal, TopLeft);
2366         // trick configure into working
2367         frame.rect = bt::Rect();
2368         configure(r);
2369       }
2370 
2371       redrawAllButtons(); // in case it is not called in configure()
2372     }
2373 
2374     updateEWMHState();
2375     updateEWMHAllowedActions();
2376     return;
2377   }
2378 
2379   switch (button) {
2380   case 1:
2381     client.ewmh.maxh = true;
2382     client.ewmh.maxv = true;
2383     break;
2384 
2385   case 2:
2386     client.ewmh.maxh = false;
2387     client.ewmh.maxv = true;
2388     break;
2389 
2390   case 3:
2391     client.ewmh.maxh = true;
2392     client.ewmh.maxv = false;
2393     break;
2394 
2395   default:
2396     assert(0);
2397     break;
2398   }
2399 
2400   if (!isFullScreen()) {
2401     // go go gadget-maximize!
2402     bt::Rect r = _screen->availableArea();
2403 
2404     if (!client.ewmh.maxh) {
2405       r.setX(frame.rect.x());
2406       r.setWidth(frame.rect.width());
2407     }
2408     if (!client.ewmh.maxv) {
2409       r.setY(frame.rect.y());
2410       r.setHeight(frame.rect.height());
2411     }
2412 
2413     // store the current frame geometry, so that we can restore it later
2414     client.premax = ::restoreGravity(frame.rect,
2415                                      frame.margin,
2416                                      client.wmnormal.win_gravity);
2417 
2418     r = ::constrain(r, frame.margin, client.wmnormal, TopLeft);
2419     // trick configure into working
2420     frame.rect = bt::Rect();
2421     configure(r);
2422   }
2423 
2424   updateEWMHState();
2425   updateEWMHAllowedActions();
2426 }
2427 
2428 
2429 /*
2430   re-maximizes the window to take into account availableArea changes.
2431 
2432   note that unlike maximize(), the shaded state is preserved.
2433 */
remaximize(void)2434 void BlackboxWindow::remaximize(void) {
2435   if (isShaded()) {
2436     bt::Rect r = _screen->availableArea();
2437 
2438     if (!client.ewmh.maxh) {
2439       r.setX(frame.rect.x());
2440       r.setWidth(frame.rect.width());
2441     }
2442     if (!client.ewmh.maxv) {
2443       r.setY(frame.rect.y());
2444       r.setHeight(frame.rect.height());
2445     }
2446 
2447     frame.rect = ::constrain(r, frame.margin, client.wmnormal, TopLeft);
2448 
2449     positionWindows();
2450     decorate();
2451 
2452     // set the frame rect to the shaded size
2453     const WindowStyle &style = _screen->resource().windowStyle();
2454     frame.rect.setHeight(style.title_height);
2455     XResizeWindow(blackbox->XDisplay(), frame.window,
2456                   frame.rect.width(), frame.rect.height());
2457     return;
2458   }
2459 
2460   unsigned int button = 0u;
2461   if (client.ewmh.maxv) {
2462     button = (client.ewmh.maxh) ? 1u : 2u;
2463   } else if (client.ewmh.maxh) {
2464     button = (client.ewmh.maxv) ? 1u : 3u;
2465   }
2466 
2467   // trick maximize() into working
2468   client.ewmh.maxh = client.ewmh.maxv = false;
2469   const bt::Rect tmp = client.premax;
2470   maximize(button);
2471   client.premax = tmp;
2472 }
2473 
2474 
setShaded(bool shaded)2475 void BlackboxWindow::setShaded(bool shaded) {
2476   assert(hasWindowFunction(WindowFunctionShade));
2477 
2478   if (client.ewmh.shaded == shaded)
2479     return;
2480 
2481   client.ewmh.shaded = shaded;
2482   if (!isShaded()) {
2483     if (isMaximized()) {
2484       remaximize();
2485     } else {
2486       // set the frame rect to the normal size
2487       frame.rect.setHeight(client.rect.height() + frame.margin.top +
2488                            frame.margin.bottom);
2489 
2490       XResizeWindow(blackbox->XDisplay(), frame.window,
2491                     frame.rect.width(), frame.rect.height());
2492     }
2493 
2494     setState(NormalState);
2495   } else {
2496     // set the frame rect to the shaded size
2497     const WindowStyle &style = _screen->resource().windowStyle();
2498     frame.rect.setHeight(style.title_height);
2499 
2500     XResizeWindow(blackbox->XDisplay(), frame.window,
2501                   frame.rect.width(), frame.rect.height());
2502 
2503     setState(IconicState);
2504   }
2505 }
2506 
2507 
setFullScreen(bool b)2508 void BlackboxWindow::setFullScreen(bool b) {
2509   assert(hasWindowFunction(WindowFunctionFullScreen));
2510 
2511   if (client.ewmh.fullscreen == b)
2512     return;
2513 
2514   // any fullscreen operation always unshades
2515   client.ewmh.shaded = false;
2516   frame.rect.setHeight(client.rect.height() + frame.margin.top
2517                        + frame.margin.bottom);
2518 
2519   bool refocus = isFocused();
2520   client.ewmh.fullscreen = b;
2521   if (isFullScreen()) {
2522     // go go gadget-fullscreen!
2523     if (!isMaximized())
2524       client.premax = ::restoreGravity(frame.rect,
2525                                        frame.margin,
2526                                        client.wmnormal.win_gravity);
2527 
2528     // modify decorations, functions and frame margin
2529     client.decorations = NoWindowDecorations;
2530     client.functions &= ~(WindowFunctionMove |
2531                           WindowFunctionResize |
2532                           WindowFunctionShade);
2533     const WindowStyle &style = _screen->resource().windowStyle();
2534     frame.margin = ::update_margin(client.decorations, style);
2535 
2536     /*
2537      * Note: we don't call ::constrain() here, because many
2538      * applications are broken in this respect.  Some specify a
2539      * max-size or aspect-ratio that simply doesn't cover the entire
2540      * screen.  Let's try to be smarter than such applications and
2541      * simply cover the entire screen.
2542      */
2543 
2544     // trick configure() into working
2545     frame.rect = bt::Rect();
2546     configure(_screen->screenInfo().rect());
2547 
2548     if (isVisible())
2549       changeLayer(StackingList::LayerFullScreen);
2550 
2551     updateEWMHState();
2552     updateEWMHAllowedActions();
2553   } else {
2554     // restore from fullscreen
2555     ::update_decorations(client.decorations,
2556                          client.functions,
2557                          isTransient(),
2558                          client.ewmh,
2559                          client.motif,
2560                          client.wmnormal,
2561                          client.wmprotocols);
2562     const WindowStyle &style = _screen->resource().windowStyle();
2563     frame.margin = ::update_margin(client.decorations, style);
2564 
2565     if (isVisible())
2566       changeLayer(StackingList::LayerNormal);
2567 
2568     if (isMaximized()) {
2569       remaximize();
2570     } else {
2571       bt::Rect r = ::applyGravity(client.premax,
2572                                   frame.margin,
2573                                   client.wmnormal.win_gravity);
2574       r = ::constrain(r, frame.margin, client.wmnormal, TopLeft);
2575 
2576       // trick configure into working
2577       frame.rect = bt::Rect();
2578       configure(r);
2579 
2580       updateEWMHState();
2581       updateEWMHAllowedActions();
2582     }
2583   }
2584 
2585   ungrabButtons();
2586   grabButtons();
2587 
2588   if (refocus)
2589     (void) setInputFocus();
2590 }
2591 
2592 
redrawWindowFrame(void) const2593 void BlackboxWindow::redrawWindowFrame(void) const {
2594   if (client.decorations & WindowDecorationTitlebar) {
2595     redrawTitle();
2596     redrawLabel();
2597     redrawAllButtons();
2598   }
2599 
2600   if (client.decorations & WindowDecorationBorder) {
2601     const WindowStyle &style = _screen->resource().windowStyle();
2602     const bt::Color &c = (isFocused()
2603                           ? style.focus.frame_border
2604                           : style.unfocus.frame_border);
2605     XSetWindowBorder(blackbox->XDisplay(), frame.plate,
2606                      c.pixel(_screen->screenNumber()));
2607   }
2608 
2609   if (client.decorations & WindowDecorationHandle) {
2610     redrawHandle();
2611 
2612     if (client.decorations & WindowDecorationGrip)
2613       redrawGrips();
2614   }
2615 }
2616 
2617 
setFocused(bool focused)2618 void BlackboxWindow::setFocused(bool focused) {
2619   if (focused == client.state.focused)
2620     return;
2621 
2622   client.state.focused = isVisible() ? focused : false;
2623 
2624   if (isVisible()) {
2625     redrawWindowFrame();
2626 
2627     if (client.state.focused) {
2628       XInstallColormap(blackbox->XDisplay(), client.colormap);
2629     } else {
2630       if (client.ewmh.fullscreen && layer() != StackingList::LayerBelow)
2631         changeLayer(StackingList::LayerBelow);
2632     }
2633   }
2634 }
2635 
2636 
setState(unsigned long new_state)2637 void BlackboxWindow::setState(unsigned long new_state) {
2638   client.current_state = new_state;
2639 
2640   unsigned long state[2];
2641   state[0] = client.current_state;
2642   state[1] = None;
2643   XChangeProperty(blackbox->XDisplay(), client.window,
2644                   blackbox->wmStateAtom(), blackbox->wmStateAtom(), 32,
2645                   PropModeReplace, (unsigned char *) state, 2);
2646 
2647   updateEWMHState();
2648   updateEWMHAllowedActions();
2649 }
2650 
2651 
updateEWMHState()2652 void BlackboxWindow::updateEWMHState() {
2653   const bt::EWMH& ewmh = blackbox->ewmh();
2654 
2655   // set _NET_WM_STATE
2656   bt::EWMH::AtomList atoms;
2657   if (isModal())
2658     atoms.push_back(ewmh.wmStateModal());
2659   if (isShaded())
2660     atoms.push_back(ewmh.wmStateShaded());
2661   if (isIconic())
2662     atoms.push_back(ewmh.wmStateHidden());
2663   if (isFullScreen())
2664     atoms.push_back(ewmh.wmStateFullscreen());
2665   if (client.ewmh.maxh)
2666     atoms.push_back(ewmh.wmStateMaximizedHorz());
2667   if (client.ewmh.maxv)
2668     atoms.push_back(ewmh.wmStateMaximizedVert());
2669   if (client.ewmh.skip_taskbar)
2670     atoms.push_back(ewmh.wmStateSkipTaskbar());
2671   if (client.ewmh.skip_pager)
2672     atoms.push_back(ewmh.wmStateSkipPager());
2673 
2674   switch (layer()) {
2675   case StackingList::LayerAbove:
2676     atoms.push_back(ewmh.wmStateAbove());
2677     break;
2678   case StackingList::LayerBelow:
2679     atoms.push_back(ewmh.wmStateBelow());
2680     break;
2681   default:
2682     break;
2683   }
2684 
2685   if (atoms.empty())
2686     ewmh.removeProperty(client.window, ewmh.wmState());
2687   else
2688     ewmh.setWMState(client.window, atoms);
2689 }
2690 
2691 
updateEWMHAllowedActions()2692 void BlackboxWindow::updateEWMHAllowedActions() {
2693   const bt::EWMH& ewmh = blackbox->ewmh();
2694 
2695   // set _NET_WM_ALLOWED_ACTIONS
2696   bt::EWMH::AtomList atoms;
2697   if (! client.state.iconic) {
2698     if (hasWindowFunction(WindowFunctionChangeWorkspace))
2699       atoms.push_back(ewmh.wmActionChangeDesktop());
2700 
2701     if (hasWindowFunction(WindowFunctionIconify))
2702       atoms.push_back(ewmh.wmActionMinimize());
2703 
2704     if (hasWindowFunction(WindowFunctionShade))
2705       atoms.push_back(ewmh.wmActionShade());
2706 
2707     if (hasWindowFunction(WindowFunctionMove))
2708       atoms.push_back(ewmh.wmActionMove());
2709 
2710     if (hasWindowFunction(WindowFunctionResize))
2711       atoms.push_back(ewmh.wmActionResize());
2712 
2713     if (hasWindowFunction(WindowFunctionMaximize)) {
2714       atoms.push_back(ewmh.wmActionMaximizeHorz());
2715       atoms.push_back(ewmh.wmActionMaximizeVert());
2716     }
2717 
2718     atoms.push_back(ewmh.wmActionFullscreen());
2719   }
2720 
2721   if (hasWindowFunction(WindowFunctionClose))
2722     atoms.push_back(ewmh.wmActionClose());
2723 
2724   if (atoms.empty())
2725     ewmh.removeProperty(client.window, ewmh.wmAllowedActions());
2726   else
2727     ewmh.setWMAllowedActions(client.window, atoms);
2728 }
2729 
2730 
redrawTitle(void) const2731 void BlackboxWindow::redrawTitle(void) const {
2732   const WindowStyle &style = _screen->resource().windowStyle();
2733   const bt::Rect u(0, 0, frame.rect.width(), style.title_height);
2734   bt::drawTexture(_screen->screenNumber(),
2735                   (client.state.focused
2736                    ? style.focus.title
2737                    : style.unfocus.title),
2738                   frame.title, u, u,
2739                   (client.state.focused
2740                    ? frame.ftitle
2741                    : frame.utitle));
2742 }
2743 
2744 
redrawLabel(void) const2745 void BlackboxWindow::redrawLabel(void) const {
2746   const WindowStyle &style = _screen->resource().windowStyle();
2747   bt::Rect u(0, 0, frame.label_w, style.label_height);
2748   Pixmap p = (client.state.focused ? frame.flabel : frame.ulabel);
2749   if (p == ParentRelative) {
2750     const bt::Texture &texture =
2751       (isFocused() ? style.focus.title : style.unfocus.title);
2752     int offset = texture.borderWidth();
2753     if (client.decorations & WindowDecorationIconify)
2754       offset += style.button_width + style.title_margin;
2755 
2756     const bt::Rect t(-(style.title_margin + offset),
2757                      -(style.title_margin + texture.borderWidth()),
2758                      frame.rect.width(), style.title_height);
2759     bt::drawTexture(_screen->screenNumber(), texture, frame.label, t, u,
2760                     (client.state.focused ? frame.ftitle : frame.utitle));
2761   } else {
2762     bt::drawTexture(_screen->screenNumber(),
2763                     (client.state.focused
2764                      ? style.focus.label
2765                      : style.unfocus.label),
2766                     frame.label, u, u, p);
2767   }
2768 
2769   const bt::Pen pen(_screen->screenNumber(),
2770                     ((client.state.focused)
2771                      ? style.focus.text
2772                      : style.unfocus.text));
2773   u.setCoords(u.left()  + style.label_margin,
2774               u.top() + style.label_margin,
2775               u.right() - style.label_margin,
2776               u.bottom() - style.label_margin);
2777   bt::drawText(style.font, pen, frame.label, u,
2778                style.alignment, client.visible_title);
2779 }
2780 
2781 
redrawAllButtons(void) const2782 void BlackboxWindow::redrawAllButtons(void) const {
2783   if (frame.iconify_button) redrawIconifyButton();
2784   if (frame.maximize_button) redrawMaximizeButton();
2785   if (frame.close_button) redrawCloseButton();
2786 }
2787 
2788 
redrawIconifyButton(bool pressed) const2789 void BlackboxWindow::redrawIconifyButton(bool pressed) const {
2790   const WindowStyle &style = _screen->resource().windowStyle();
2791   const bt::Rect u(0, 0, style.button_width, style.button_width);
2792   Pixmap p = (pressed ? frame.pbutton :
2793               (client.state.focused ? frame.fbutton : frame.ubutton));
2794   if (p == ParentRelative) {
2795     const bt::Texture &texture =
2796       (isFocused() ? style.focus.title : style.unfocus.title);
2797     const bt::Rect t(-(style.title_margin + texture.borderWidth()),
2798                      -(style.title_margin + texture.borderWidth()),
2799                      frame.rect.width(), style.title_height);
2800     bt::drawTexture(_screen->screenNumber(), texture, frame.iconify_button,
2801                     t, u, (client.state.focused
2802                            ? frame.ftitle
2803                            : frame.utitle));
2804   } else {
2805     bt::drawTexture(_screen->screenNumber(),
2806                     (pressed ? style.pressed :
2807                      (client.state.focused ? style.focus.button :
2808                       style.unfocus.button)),
2809                     frame.iconify_button, u, u, p);
2810   }
2811 
2812   const bt::Pen pen(_screen->screenNumber(),
2813                     (client.state.focused
2814                      ? style.focus.foreground
2815                      : style.unfocus.foreground));
2816   bt::drawBitmap(style.iconify, pen, frame.iconify_button, u);
2817 }
2818 
2819 
redrawMaximizeButton(bool pressed) const2820 void BlackboxWindow::redrawMaximizeButton(bool pressed) const {
2821   const WindowStyle &style = _screen->resource().windowStyle();
2822   const bt::Rect u(0, 0, style.button_width, style.button_width);
2823   Pixmap p = (pressed ? frame.pbutton :
2824               (client.state.focused ? frame.fbutton : frame.ubutton));
2825   if (p == ParentRelative) {
2826     const bt::Texture &texture =
2827       (isFocused() ? style.focus.title : style.unfocus.title);
2828     int button_w = style.button_width
2829                    + style.title_margin + texture.borderWidth();
2830     if (client.decorations & WindowDecorationClose)
2831       button_w *= 2;
2832     const bt::Rect t(-(frame.rect.width() - button_w),
2833                      -(style.title_margin + texture.borderWidth()),
2834                      frame.rect.width(), style.title_height);
2835     bt::drawTexture(_screen->screenNumber(), texture, frame.maximize_button,
2836                     t, u, (client.state.focused
2837                            ? frame.ftitle
2838                            : frame.utitle));
2839   } else {
2840     bt::drawTexture(_screen->screenNumber(),
2841                     (pressed ? style.pressed :
2842                      (client.state.focused ? style.focus.button :
2843                       style.unfocus.button)),
2844                     frame.maximize_button, u, u, p);
2845   }
2846 
2847   const bt::Pen pen(_screen->screenNumber(),
2848                     (client.state.focused
2849                      ? style.focus.foreground
2850                      : style.unfocus.foreground));
2851   bt::drawBitmap(isMaximized() ? style.restore : style.maximize,
2852                  pen, frame.maximize_button, u);
2853 }
2854 
2855 
redrawCloseButton(bool pressed) const2856 void BlackboxWindow::redrawCloseButton(bool pressed) const {
2857   const WindowStyle &style = _screen->resource().windowStyle();
2858   const bt::Rect u(0, 0, style.button_width, style.button_width);
2859   Pixmap p = (pressed ? frame.pbutton :
2860               (client.state.focused ? frame.fbutton : frame.ubutton));
2861   if (p == ParentRelative) {
2862     const bt::Texture &texture =
2863       (isFocused() ? style.focus.title : style.unfocus.title);
2864     const int button_w = style.button_width +
2865                          style.title_margin +
2866                          texture.borderWidth();
2867     const bt::Rect t(-(frame.rect.width() - button_w),
2868                      -(style.title_margin + texture.borderWidth()),
2869                      frame.rect.width(), style.title_height);
2870     bt::drawTexture(_screen->screenNumber(),texture, frame.close_button, t, u,
2871                     (client.state.focused ? frame.ftitle : frame.utitle));
2872   } else {
2873     bt::drawTexture(_screen->screenNumber(),
2874                     (pressed ? style.pressed :
2875                      (client.state.focused ? style.focus.button :
2876                       style.unfocus.button)),
2877                     frame.close_button, u, u, p);
2878   }
2879 
2880   const bt::Pen pen(_screen->screenNumber(),
2881                     (client.state.focused
2882                      ? style.focus.foreground
2883                      : style.unfocus.foreground));
2884   bt::drawBitmap(style.close, pen, frame.close_button, u);
2885 }
2886 
2887 
redrawHandle(void) const2888 void BlackboxWindow::redrawHandle(void) const {
2889   const WindowStyle &style = _screen->resource().windowStyle();
2890   const bt::Rect u(0, 0, frame.rect.width(), style.handle_height);
2891   bt::drawTexture(_screen->screenNumber(),
2892                   (client.state.focused ? style.focus.handle :
2893                                           style.unfocus.handle),
2894                   frame.handle, u, u,
2895                   (client.state.focused ? frame.fhandle : frame.uhandle));
2896 }
2897 
2898 
redrawGrips(void) const2899 void BlackboxWindow::redrawGrips(void) const {
2900   const WindowStyle &style = _screen->resource().windowStyle();
2901   const bt::Rect u(0, 0, style.grip_width, style.handle_height);
2902   Pixmap p = (client.state.focused ? frame.fgrip : frame.ugrip);
2903   if (p == ParentRelative) {
2904     bt::Rect t(0, 0, frame.rect.width(), style.handle_height);
2905     bt::drawTexture(_screen->screenNumber(),
2906                     (client.state.focused ? style.focus.handle :
2907                                             style.unfocus.handle),
2908                     frame.right_grip, t, u, p);
2909 
2910     t.setPos(-(frame.rect.width() - style.grip_width), 0);
2911     bt::drawTexture(_screen->screenNumber(),
2912                     (client.state.focused ? style.focus.handle :
2913                                             style.unfocus.handle),
2914                     frame.right_grip, t, u, p);
2915   } else {
2916     bt::drawTexture(_screen->screenNumber(),
2917                     (client.state.focused ? style.focus.grip :
2918                                             style.unfocus.grip),
2919                     frame.left_grip, u, u, p);
2920 
2921     bt::drawTexture(_screen->screenNumber(),
2922                     (client.state.focused ? style.focus.grip :
2923                                             style.unfocus.grip),
2924                     frame.right_grip, u, u, p);
2925   }
2926 }
2927 
2928 
2929 void
clientMessageEvent(const XClientMessageEvent * const event)2930 BlackboxWindow::clientMessageEvent(const XClientMessageEvent * const event) {
2931   if (event->format != 32)
2932     return;
2933 
2934   const bt::EWMH& ewmh = blackbox->ewmh();
2935 
2936   if (event->message_type == blackbox->wmChangeStateAtom()) {
2937     if (event->data.l[0] == IconicState) {
2938       if (hasWindowFunction(WindowFunctionIconify))
2939         iconify();
2940     } else if (event->data.l[0] == NormalState) {
2941       activate();
2942     }
2943   } else if (event->message_type == ewmh.activeWindow()) {
2944     activate();
2945   } else if (event->message_type == ewmh.closeWindow()) {
2946     if (hasWindowFunction(WindowFunctionClose))
2947       close();
2948   } else if (event->message_type == ewmh.moveresizeWindow()) {
2949     XConfigureRequestEvent request;
2950     request.window = event->window;
2951     request.value_mask =
2952       (event->data.l[0] >> 8) & (CWX | CWY | CWWidth | CWHeight);
2953     request.x = event->data.l[1];
2954     request.y = event->data.l[2];
2955     request.width = event->data.l[3];
2956     request.height = event->data.l[4];
2957 
2958     const int gravity = (event->data.l[0] & 0xff);
2959     const int old_gravity = client.wmnormal.win_gravity;
2960     if (event->data.l[0] != 0)
2961       client.wmnormal.win_gravity = gravity;
2962 
2963     configureRequestEvent(&request);
2964 
2965     client.wmnormal.win_gravity = old_gravity;
2966   } else if (event->message_type == ewmh.wmDesktop()) {
2967     if (hasWindowFunction(WindowFunctionChangeWorkspace)) {
2968       const unsigned int new_workspace = event->data.l[0];
2969       changeWorkspace(new_workspace);
2970     }
2971   } else if (event->message_type == ewmh.wmState()) {
2972     Atom action = event->data.l[0],
2973           first = event->data.l[1],
2974          second = event->data.l[2];
2975 
2976     if (first == ewmh.wmStateModal() || second == ewmh.wmStateModal()) {
2977       if ((action == ewmh.wmStateAdd() ||
2978            (action == ewmh.wmStateToggle() && ! client.ewmh.modal)) &&
2979           isTransient())
2980         client.ewmh.modal = true;
2981       else
2982         client.ewmh.modal = false;
2983     }
2984 
2985     if (hasWindowFunction(WindowFunctionMaximize)) {
2986       int max_horz = 0, max_vert = 0;
2987 
2988       if (first == ewmh.wmStateMaximizedHorz() ||
2989           second == ewmh.wmStateMaximizedHorz()) {
2990         max_horz = ((action == ewmh.wmStateAdd()
2991                      || (action == ewmh.wmStateToggle()
2992                          && !client.ewmh.maxh))
2993                     ? 1 : -1);
2994       }
2995 
2996       if (first == ewmh.wmStateMaximizedVert() ||
2997           second == ewmh.wmStateMaximizedVert()) {
2998         max_vert = ((action == ewmh.wmStateAdd()
2999                      || (action == ewmh.wmStateToggle()
3000                          && !client.ewmh.maxv))
3001                     ? 1 : -1);
3002       }
3003 
3004       if (max_horz != 0 || max_vert != 0) {
3005         if (isMaximized())
3006           maximize(0);
3007         unsigned int button = 0u;
3008         if (max_horz == 1 && max_vert != 1)
3009           button = 3u;
3010         else if (max_vert == 1 && max_horz != 1)
3011           button = 2u;
3012         else if (max_vert == 1 && max_horz == 1)
3013           button = 1u;
3014         if (button)
3015           maximize(button);
3016       }
3017     }
3018 
3019     if (hasWindowFunction(WindowFunctionShade)) {
3020       if (first == ewmh.wmStateShaded() ||
3021           second == ewmh.wmStateShaded()) {
3022         if (action == ewmh.wmStateRemove())
3023           setShaded(false);
3024         else if (action == ewmh.wmStateAdd())
3025           setShaded(true);
3026         else if (action == ewmh.wmStateToggle())
3027           setShaded(!isShaded());
3028       }
3029     }
3030 
3031     if (first == ewmh.wmStateSkipTaskbar()
3032         || second == ewmh.wmStateSkipTaskbar()
3033         || first == ewmh.wmStateSkipPager()
3034         || second == ewmh.wmStateSkipPager()) {
3035       if (first == ewmh.wmStateSkipTaskbar()
3036           || second == ewmh.wmStateSkipTaskbar()) {
3037         client.ewmh.skip_taskbar = (action == ewmh.wmStateAdd()
3038                                     || (action == ewmh.wmStateToggle()
3039                                         && !client.ewmh.skip_taskbar));
3040       }
3041       if (first == ewmh.wmStateSkipPager()
3042           || second == ewmh.wmStateSkipPager()) {
3043         client.ewmh.skip_pager = (action == ewmh.wmStateAdd()
3044                                   || (action == ewmh.wmStateToggle()
3045                                       && !client.ewmh.skip_pager));
3046       }
3047       // we do nothing with skip_*, but others might... we should at
3048       // least make sure these are present in _NET_WM_STATE
3049       updateEWMHState();
3050     }
3051 
3052     if (first == ewmh.wmStateHidden() ||
3053         second == ewmh.wmStateHidden()) {
3054       /*
3055         ignore _NET_WM_STATE_HIDDEN, the wm sets this state, not the
3056         application
3057       */
3058     }
3059 
3060     if (hasWindowFunction(WindowFunctionFullScreen)) {
3061       if (first == ewmh.wmStateFullscreen() ||
3062           second == ewmh.wmStateFullscreen()) {
3063         if (action == ewmh.wmStateAdd() ||
3064             (action == ewmh.wmStateToggle() &&
3065              ! client.ewmh.fullscreen)) {
3066           setFullScreen(true);
3067         } else if (action == ewmh.wmStateToggle() ||
3068                    action == ewmh.wmStateRemove()) {
3069           setFullScreen(false);
3070         }
3071       }
3072     }
3073 
3074     if (hasWindowFunction(WindowFunctionChangeLayer)) {
3075       if (first == ewmh.wmStateAbove() ||
3076           second == ewmh.wmStateAbove()) {
3077         if (action == ewmh.wmStateAdd() ||
3078             (action == ewmh.wmStateToggle() &&
3079              layer() != StackingList::LayerAbove)) {
3080           changeLayer(StackingList::LayerAbove);
3081         } else if (action == ewmh.wmStateToggle() ||
3082                    action == ewmh.wmStateRemove()) {
3083           changeLayer(StackingList::LayerNormal);
3084         }
3085       }
3086 
3087       if (first == ewmh.wmStateBelow() ||
3088           second == ewmh.wmStateBelow()) {
3089         if (action == ewmh.wmStateAdd() ||
3090             (action == ewmh.wmStateToggle() &&
3091              layer() != StackingList::LayerBelow)) {
3092           changeLayer(StackingList::LayerBelow);
3093         } else if (action == ewmh.wmStateToggle() ||
3094                    action == ewmh.wmStateRemove()) {
3095           changeLayer(StackingList::LayerNormal);
3096         }
3097       }
3098     }
3099   }
3100 }
3101 
3102 
unmapNotifyEvent(const XUnmapEvent * const event)3103 void BlackboxWindow::unmapNotifyEvent(const XUnmapEvent * const event) {
3104   if (event->window != client.window)
3105     return;
3106 
3107 #ifdef    DEBUG
3108   fprintf(stderr, "BlackboxWindow::unmapNotifyEvent() for 0x%lx\n",
3109           client.window);
3110 #endif // DEBUG
3111 
3112   _screen->releaseWindow(this);
3113 }
3114 
3115 
3116 void
destroyNotifyEvent(const XDestroyWindowEvent * const event)3117 BlackboxWindow::destroyNotifyEvent(const XDestroyWindowEvent * const event) {
3118   if (event->window != client.window)
3119     return;
3120 
3121 #ifdef    DEBUG
3122   fprintf(stderr, "BlackboxWindow::destroyNotifyEvent() for 0x%lx\n",
3123           client.window);
3124 #endif // DEBUG
3125 
3126   _screen->releaseWindow(this);
3127 }
3128 
3129 
reparentNotifyEvent(const XReparentEvent * const event)3130 void BlackboxWindow::reparentNotifyEvent(const XReparentEvent * const event) {
3131   if (event->window != client.window || event->parent == frame.plate)
3132     return;
3133 
3134 #ifdef    DEBUG
3135   fprintf(stderr, "BlackboxWindow::reparentNotifyEvent(): reparent 0x%lx to "
3136           "0x%lx.\n", client.window, event->parent);
3137 #endif // DEBUG
3138 
3139   /*
3140     put the ReparentNotify event back into the queue so that
3141     BlackboxWindow::restore(void) can do the right thing
3142   */
3143   XEvent replay;
3144   replay.xreparent = *event;
3145   XPutBackEvent(blackbox->XDisplay(), &replay);
3146 
3147   _screen->releaseWindow(this);
3148 }
3149 
3150 
propertyNotifyEvent(const XPropertyEvent * const event)3151 void BlackboxWindow::propertyNotifyEvent(const XPropertyEvent * const event) {
3152 #ifdef    DEBUG
3153   fprintf(stderr, "BlackboxWindow::propertyNotifyEvent(): for 0x%lx\n",
3154           client.window);
3155 #endif
3156 
3157   switch(event->atom) {
3158   case XA_WM_TRANSIENT_FOR: {
3159     if (isTransient()) {
3160       // remove ourselves from our transient_for
3161       BlackboxWindow *win = findTransientFor();
3162       if (win) {
3163         win->removeTransient(this);
3164       } else if (isGroupTransient()) {
3165         BWindowGroup *group = findWindowGroup();
3166         if (group)
3167           group->removeTransient(this);
3168       }
3169     }
3170 
3171     // determine if this is a transient window
3172     client.transient_for = ::readTransientInfo(blackbox,
3173                                                client.window,
3174                                                _screen->screenInfo(),
3175                                                client.wmhints);
3176 
3177     if (isTransient()) {
3178       BlackboxWindow *win = findTransientFor();
3179       if (win) {
3180         // add ourselves to our new transient_for
3181         win->addTransient(this);
3182         changeWorkspace(win->workspace());
3183         changeLayer(win->layer());
3184       } else if (isGroupTransient()) {
3185         BWindowGroup *group = findWindowGroup();
3186         if (group)
3187           group->addTransient(this);
3188       } else {
3189         // broken client
3190         client.transient_for = 0;
3191       }
3192     }
3193 
3194     ::update_decorations(client.decorations,
3195                          client.functions,
3196                          isTransient(),
3197                          client.ewmh,
3198                          client.motif,
3199                          client.wmnormal,
3200                          client.wmprotocols);
3201 
3202     reconfigure();
3203     break;
3204   }
3205 
3206   case XA_WM_HINTS: {
3207     // remove from current window group
3208     BWindowGroup *group = findWindowGroup();
3209     if (group) {
3210       if (isTransient() && !findTransientFor() && isGroupTransient())
3211         group->removeTransient(this);
3212       group->removeWindow(this);
3213       group = 0;
3214     }
3215 
3216     client.wmhints = ::readWMHints(blackbox, client.window);
3217 
3218     if (client.wmhints.window_group != None) {
3219       // add to new window group
3220       group = ::update_window_group(client.wmhints.window_group,
3221                                     blackbox,
3222                                     this);
3223       if (isTransient() && !findTransientFor() && isGroupTransient()) {
3224         if (group)
3225           group->addTransient(this);
3226       }
3227     }
3228     break;
3229   }
3230 
3231   case XA_WM_ICON_NAME: {
3232     client.icon_title = ::readWMIconName(blackbox, client.window);
3233     if (client.state.iconic)
3234       _screen->propagateWindowName(this);
3235     break;
3236   }
3237 
3238   case XA_WM_NAME: {
3239     client.title = ::readWMName(blackbox, client.window);
3240 
3241     client.visible_title =
3242       bt::ellideText(client.title, frame.label_w, bt::toUnicode("..."),
3243                      _screen->screenNumber(),
3244                      _screen->resource().windowStyle().font);
3245     blackbox->ewmh().setWMVisibleName(client.window, client.visible_title);
3246 
3247     if (client.decorations & WindowDecorationTitlebar)
3248       redrawLabel();
3249 
3250     _screen->propagateWindowName(this);
3251     break;
3252   }
3253 
3254   case XA_WM_NORMAL_HINTS: {
3255     WMNormalHints wmnormal = ::readWMNormalHints(blackbox, client.window,
3256                                                  _screen->screenInfo());
3257     if (wmnormal == client.wmnormal) {
3258       // apps like xv and GNU emacs seem to like to repeatedly set
3259       // this property over and over
3260       break;
3261     }
3262 
3263     client.wmnormal = wmnormal;
3264 
3265     ::update_decorations(client.decorations,
3266                          client.functions,
3267                          isTransient(),
3268                          client.ewmh,
3269                          client.motif,
3270                          client.wmnormal,
3271                          client.wmprotocols);
3272 
3273     reconfigure();
3274     break;
3275   }
3276 
3277   default: {
3278     if (event->atom == blackbox->wmProtocolsAtom()) {
3279       client.wmprotocols = ::readWMProtocols(blackbox, client.window);
3280 
3281       ::update_decorations(client.decorations,
3282                            client.functions,
3283                            isTransient(),
3284                            client.ewmh,
3285                            client.motif,
3286                            client.wmnormal,
3287                            client.wmprotocols);
3288 
3289       reconfigure();
3290     } else if (event->atom == blackbox->motifWmHintsAtom()) {
3291       client.motif = ::readMotifWMHints(blackbox, client.window);
3292 
3293       ::update_decorations(client.decorations,
3294                            client.functions,
3295                            isTransient(),
3296                            client.ewmh,
3297                            client.motif,
3298                            client.wmnormal,
3299                            client.wmprotocols);
3300 
3301       reconfigure();
3302     } else if (event->atom == blackbox->ewmh().wmStrut()) {
3303       if (! client.strut) {
3304         client.strut = new bt::EWMH::Strut;
3305         _screen->addStrut(client.strut);
3306       }
3307 
3308       blackbox->ewmh().readWMStrut(client.window, client.strut);
3309       if (client.strut->left || client.strut->right ||
3310           client.strut->top || client.strut->bottom) {
3311         _screen->updateStrut();
3312       } else {
3313         _screen->removeStrut(client.strut);
3314         delete client.strut;
3315       }
3316     }
3317 
3318     break;
3319   }
3320   } // switch
3321 }
3322 
3323 
exposeEvent(const XExposeEvent * const event)3324 void BlackboxWindow::exposeEvent(const XExposeEvent * const event) {
3325 #ifdef DEBUG
3326   fprintf(stderr, "BlackboxWindow::exposeEvent() for 0x%lx\n", client.window);
3327 #endif
3328 
3329   if (frame.title == event->window)
3330     redrawTitle();
3331   else if (frame.label == event->window)
3332     redrawLabel();
3333   else if (frame.close_button == event->window)
3334     redrawCloseButton();
3335   else if (frame.maximize_button == event->window)
3336     redrawMaximizeButton();
3337   else if (frame.iconify_button == event->window)
3338     redrawIconifyButton();
3339   else if (frame.handle == event->window)
3340     redrawHandle();
3341   else if (frame.left_grip == event->window ||
3342            frame.right_grip == event->window)
3343     redrawGrips();
3344 }
3345 
3346 
configureRequestEvent(const XConfigureRequestEvent * const event)3347 void BlackboxWindow::configureRequestEvent(const XConfigureRequestEvent *
3348                                            const event) {
3349   if (event->window != client.window || client.state.iconic)
3350     return;
3351 
3352   if (event->value_mask & CWBorderWidth)
3353     client.old_bw = event->border_width;
3354 
3355   if (event->value_mask & (CWX | CWY | CWWidth | CWHeight)) {
3356     bt::Rect req = frame.rect;
3357 
3358     if (event->value_mask & (CWX | CWY)) {
3359       req = ::restoreGravity(req, frame.margin, client.wmnormal.win_gravity);
3360 
3361       if (event->value_mask & CWX)
3362         req.setX(event->x);
3363       if (event->value_mask & CWY)
3364         req.setY(event->y);
3365 
3366       req = ::applyGravity(req, frame.margin, client.wmnormal.win_gravity);
3367     }
3368 
3369     if (event->value_mask & (CWWidth | CWHeight)) {
3370       if (event->value_mask & CWWidth)
3371         req.setWidth(event->width + frame.margin.left + frame.margin.right);
3372       if (event->value_mask & CWHeight)
3373         req.setHeight(event->height + frame.margin.top + frame.margin.bottom);
3374     }
3375 
3376     configure(req);
3377   }
3378 
3379   if (event->value_mask & CWStackMode) {
3380     switch (event->detail) {
3381     case Below:
3382     case BottomIf:
3383       _screen->lowerWindow(this);
3384       break;
3385 
3386     case Above:
3387     case TopIf:
3388     default:
3389       _screen->raiseWindow(this);
3390       break;
3391     }
3392   }
3393 }
3394 
3395 
buttonPressEvent(const XButtonEvent * const event)3396 void BlackboxWindow::buttonPressEvent(const XButtonEvent * const event) {
3397 #ifdef DEBUG
3398   fprintf(stderr, "BlackboxWindow::buttonPressEvent() for 0x%lx\n",
3399           client.window);
3400 #endif
3401 
3402   if (frame.maximize_button == event->window) {
3403     if (event->button < 4)
3404       redrawMaximizeButton(true);
3405   } else if (frame.iconify_button == event->window) {
3406     if (event->button == 1)
3407       redrawIconifyButton(true);
3408   } else if (frame.close_button == event->window) {
3409     if (event->button == 1)
3410       redrawCloseButton(true);
3411   } else {
3412     if (event->button == 1
3413         || (event->button == 3 && event->state == Mod1Mask)) {
3414       frame.grab_x = event->x_root - frame.rect.x();
3415       frame.grab_y = event->y_root - frame.rect.y();
3416 
3417       _screen->raiseWindow(this);
3418 
3419       if (! client.state.focused)
3420         (void) setInputFocus();
3421       else
3422         XInstallColormap(blackbox->XDisplay(), client.colormap);
3423 
3424       if (frame.plate == event->window) {
3425         XAllowEvents(blackbox->XDisplay(), ReplayPointer, event->time);
3426       } else if ((frame.title == event->window
3427                   || frame.label == event->window)
3428                  && hasWindowFunction(WindowFunctionShade)) {
3429         if ((event->time - lastButtonPressTime <=
3430              blackbox->resource().doubleClickInterval()) ||
3431             event->state == ControlMask) {
3432           lastButtonPressTime = 0;
3433           setShaded(!isShaded());
3434         } else {
3435           lastButtonPressTime = event->time;
3436         }
3437       }
3438     } else if (event->button == 2) {
3439       _screen->lowerWindow(this);
3440     } else if (event->button == 3
3441                || (event->button == 3 && event->state == Mod4Mask)) {
3442       const int extra = _screen->resource().windowStyle().frame_border_width;
3443       const bt::Rect rect(client.rect.x() - extra,
3444                           client.rect.y() - extra,
3445                           client.rect.width() + (extra * 2),
3446                           client.rect.height() + (extra * 2));
3447 
3448       Windowmenu *windowmenu = _screen->windowmenu(this);
3449       windowmenu->popup(event->x_root, event->y_root, rect);
3450     } else if (blackbox->resource().shadeWindowWithMouseWheel()) {
3451       if (event->button == 4
3452        && hasWindowFunction(WindowFunctionShade)
3453        && !isShaded()) {
3454         setShaded(true);
3455       } else if (event->button == 5
3456        && hasWindowFunction(WindowFunctionShade)
3457        && isShaded()) {
3458         setShaded(false);
3459       }
3460     }
3461   }
3462 }
3463 
3464 
buttonReleaseEvent(const XButtonEvent * const event)3465 void BlackboxWindow::buttonReleaseEvent(const XButtonEvent * const event) {
3466 #ifdef DEBUG
3467   fprintf(stderr, "BlackboxWindow::buttonReleaseEvent() for 0x%lx\n",
3468           client.window);
3469 #endif
3470 
3471   const WindowStyle &style = _screen->resource().windowStyle();
3472   if (event->window == frame.maximize_button) {
3473     if (event->button < 4) {
3474       if (bt::within(event->x, event->y,
3475                      style.button_width, style.button_width)) {
3476         maximize(event->button);
3477         _screen->raiseWindow(this);
3478       } else {
3479         redrawMaximizeButton();
3480       }
3481     }
3482   } else if (event->window == frame.iconify_button) {
3483     if (event->button == 1) {
3484       if (bt::within(event->x, event->y,
3485                      style.button_width, style.button_width))
3486         iconify();
3487       else
3488         redrawIconifyButton();
3489     }
3490   } else if (event->window == frame.close_button) {
3491     if (event->button == 1) {
3492       if (bt::within(event->x, event->y,
3493                      style.button_width, style.button_width))
3494         close();
3495       redrawCloseButton();
3496     }
3497   } else if (client.state.moving) {
3498     finishMove();
3499   } else if (client.state.resizing) {
3500     finishResize();
3501   } else if (event->window == frame.window) {
3502     if (event->button == 2 && event->state == Mod1Mask)
3503       XUngrabPointer(blackbox->XDisplay(), blackbox->XTime());
3504   }
3505 }
3506 
3507 
motionNotifyEvent(const XMotionEvent * const event)3508 void BlackboxWindow::motionNotifyEvent(const XMotionEvent * const event) {
3509 #ifdef DEBUG
3510   fprintf(stderr, "BlackboxWindow::motionNotifyEvent() for 0x%lx\n",
3511           client.window);
3512 #endif
3513 
3514   if (hasWindowFunction(WindowFunctionMove)
3515       && !client.state.resizing
3516       && event->state & Button1Mask
3517       && (frame.title == event->window || frame.label == event->window
3518           || frame.handle == event->window || frame.window == event->window)) {
3519     if (! client.state.moving)
3520       startMove();
3521     else
3522       continueMove(event->x_root, event->y_root);
3523   } else if (hasWindowFunction(WindowFunctionResize)
3524              && (event->state & Button1Mask
3525                  && (event->window == frame.right_grip
3526                      || event->window == frame.left_grip))
3527              || (event->state & Button3Mask
3528                  && event->state & Mod1Mask
3529                  && event->window == frame.window)) {
3530     if (!client.state.resizing)
3531       startResize(event->window);
3532     else
3533       continueResize(event->x_root, event->y_root);
3534   }
3535 }
3536 
3537 
enterNotifyEvent(const XCrossingEvent * const event)3538 void BlackboxWindow::enterNotifyEvent(const XCrossingEvent * const event) {
3539   if (event->window != frame.window || event->mode != NotifyNormal)
3540     return;
3541 
3542   if (blackbox->resource().focusModel() == ClickToFocusModel || !isVisible())
3543     return;
3544 
3545   switch (windowType()) {
3546   case WindowTypeDesktop:
3547   case WindowTypeDock:
3548     // these types cannot be focused w/ sloppy focus
3549     return;
3550 
3551   default:
3552     break;
3553   }
3554 
3555   XEvent next;
3556   bool leave = False, inferior = False;
3557 
3558   while (XCheckTypedWindowEvent(blackbox->XDisplay(), event->window,
3559                                 LeaveNotify, &next)) {
3560     if (next.type == LeaveNotify && next.xcrossing.mode == NotifyNormal) {
3561       leave = True;
3562       inferior = (next.xcrossing.detail == NotifyInferior);
3563     }
3564   }
3565 
3566   if ((! leave || inferior) && ! isFocused())
3567     (void) setInputFocus();
3568 
3569   if (blackbox->resource().autoRaise())
3570     timer->start();
3571 }
3572 
3573 
3574 void
leaveNotifyEvent(const XCrossingEvent * const)3575 BlackboxWindow::leaveNotifyEvent(const XCrossingEvent * const /*unused*/) {
3576   if (!(blackbox->resource().focusModel() == SloppyFocusModel
3577         && blackbox->resource().autoRaise()))
3578     return;
3579 
3580   if (timer->isTiming())
3581     timer->stop();
3582 }
3583 
3584 
3585 #ifdef    SHAPE
shapeEvent(const XEvent * const)3586 void BlackboxWindow::shapeEvent(const XEvent * const /*unused*/)
3587 { if (client.state.shaped) configureShape(); }
3588 #endif // SHAPE
3589 
3590 
3591 /*
3592  *
3593  */
restore(void)3594 void BlackboxWindow::restore(void) {
3595   XChangeSaveSet(blackbox->XDisplay(), client.window, SetModeDelete);
3596   XSelectInput(blackbox->XDisplay(), client.window, NoEventMask);
3597   XSelectInput(blackbox->XDisplay(), frame.plate, NoEventMask);
3598 
3599   client.state.visible = false;
3600 
3601   /*
3602     remove WM_STATE unless the we are shutting down (in which case we
3603     want to make sure we preserve the state across restarts).
3604   */
3605   if (!blackbox->shuttingDown()) {
3606     clearState(blackbox, client.window);
3607   } else if (isShaded() && !isIconic()) {
3608     // do not leave a shaded window as an icon unless it was an icon
3609     setState(NormalState);
3610   }
3611 
3612   client.rect = ::restoreGravity(frame.rect, frame.margin,
3613                                  client.wmnormal.win_gravity);
3614 
3615   blackbox->XGrabServer();
3616 
3617   XUnmapWindow(blackbox->XDisplay(), frame.window);
3618   XUnmapWindow(blackbox->XDisplay(), client.window);
3619 
3620   XSetWindowBorderWidth(blackbox->XDisplay(), client.window, client.old_bw);
3621   if (isMaximized()) {
3622     // preserve the original size
3623     client.rect = client.premax;
3624     XMoveResizeWindow(blackbox->XDisplay(), client.window,
3625                       client.premax.x(),
3626                       client.premax.y(),
3627                       client.premax.width(),
3628                       client.premax.height());
3629   } else {
3630     XMoveWindow(blackbox->XDisplay(), client.window,
3631                 client.rect.x() - frame.rect.x(),
3632                 client.rect.y() - frame.rect.y());
3633   }
3634 
3635   blackbox->XUngrabServer();
3636 
3637   XEvent unused;
3638   if (!XCheckTypedWindowEvent(blackbox->XDisplay(), client.window,
3639                               ReparentNotify, &unused)) {
3640     /*
3641       according to the ICCCM, the window manager is responsible for
3642       reparenting the window back to root... however, we don't want to
3643       do this if the window has been reparented by someone else
3644       (i.e. not us).
3645     */
3646     XReparentWindow(blackbox->XDisplay(), client.window,
3647                     _screen->screenInfo().rootWindow(),
3648                     client.rect.x(), client.rect.y());
3649   }
3650 
3651   if (blackbox->shuttingDown())
3652     XMapWindow(blackbox->XDisplay(), client.window);
3653 }
3654 
3655 
3656 // timer for autoraise
timeout(bt::Timer *)3657 void BlackboxWindow::timeout(bt::Timer *)
3658 { _screen->raiseWindow(this); }
3659 
3660 
startMove()3661 void BlackboxWindow::startMove() {
3662   // begin a move
3663   XGrabPointer(blackbox->XDisplay(), frame.window, false,
3664                Button1MotionMask | ButtonReleaseMask,
3665                GrabModeAsync, GrabModeAsync, None,
3666                blackbox->resource().cursors().move, blackbox->XTime());
3667 
3668   client.state.moving = true;
3669 
3670   if (! blackbox->resource().opaqueMove()) {
3671     blackbox->XGrabServer();
3672 
3673     frame.changing = frame.rect;
3674     _screen->showGeometry(BScreen::Position, frame.changing);
3675 
3676     bt::Pen pen(_screen->screenNumber(), bt::Color(0xff, 0xff, 0xff));
3677     const int bw = _screen->resource().windowStyle().frame_border_width,
3678               hw = bw / 2;
3679     pen.setGCFunction(GXxor);
3680     pen.setLineWidth(bw);
3681     pen.setSubWindowMode(IncludeInferiors);
3682     XDrawRectangle(blackbox->XDisplay(), _screen->screenInfo().rootWindow(),
3683                    pen.gc(),
3684                    frame.changing.x() + hw,
3685                    frame.changing.y() + hw,
3686                    frame.changing.width() - bw,
3687                    frame.changing.height() - bw);
3688   }
3689 }
3690 
3691 
3692 static
collisionAdjust(int * dx,int * dy,int x,int y,unsigned int width,unsigned int height,const bt::Rect & rect,int snap_distance,bool snapCenter=false)3693 void collisionAdjust(int *dx, int *dy, int x, int y,
3694                      unsigned int width, unsigned int height,
3695                      const bt::Rect& rect, int snap_distance,
3696                      bool snapCenter = false)
3697 {
3698   // window corners
3699   const int wleft = x,
3700            wright = x + width - 1,
3701              wtop = y,
3702           wbottom = y + height - 1,
3703   // left, right, top + bottom are for rect, douterleft = left border of rect
3704        dinnerleft = std::abs(wleft - rect.left()),
3705       dinnerright = std::abs(wright - rect.right()),
3706         dinnertop = std::abs(wtop - rect.top()),
3707      dinnerbottom = std::abs(wbottom - rect.bottom()),
3708        douterleft = std::abs(wright - rect.left()),
3709       douterright = std::abs(wleft - rect.right()),
3710         doutertop = std::abs(wbottom - rect.top()),
3711      douterbottom = std::abs(wtop - rect.bottom());
3712 
3713   if ((wtop <= rect.bottom() && wbottom >= rect.top())
3714       || doutertop <= snap_distance
3715       || douterbottom <= snap_distance) {
3716     // snap left or right
3717     if (douterleft <= dinnerleft && douterleft <= snap_distance)
3718       // snap outer left
3719       *dx = (x - (rect.left() - width));
3720     else if (douterright <= dinnerright && douterright <= snap_distance)
3721       // snap outer right
3722       *dx = (x - rect.right() - 1);
3723     else if (dinnerleft <= dinnerright && dinnerleft < snap_distance)
3724       // snap inner left
3725       *dx = (x - rect.left());
3726     else if (dinnerright < snap_distance)
3727       // snap inner right
3728       *dx = (x - (rect.right() - width + 1));
3729   }
3730 
3731   if ((wleft <= rect.right() && wright >= rect.left())
3732       || douterleft <= snap_distance
3733       || douterright <= snap_distance) {
3734     // snap top or bottom
3735     if (doutertop <= dinnertop && doutertop <= snap_distance)
3736       // snap outer top
3737       *dy = (y - (rect.top() - height));
3738     else if (douterbottom <= dinnerbottom && douterbottom <= snap_distance)
3739       // snap outer bottom
3740       *dy = (y - rect.bottom() - 1);
3741     else if (dinnertop <= dinnerbottom && dinnertop < snap_distance)
3742       // snap inner top
3743       *dy = (y - rect.top());
3744     else if (dinnerbottom < snap_distance)
3745       // snap inner bottom
3746       *dy = (y - (rect.bottom() - height + 1));
3747   }
3748 
3749   if (snapCenter) {
3750     const int cwx = x + width / 2;
3751     const int cwy = y + height / 2;
3752     const int crx = rect.x() + rect.width() / 2;
3753     const int cry = rect.y() + rect.height() / 2;
3754     const int cdx = std::abs(cwx - crx);
3755     const int cdy = std::abs(cwy - cry);
3756     if (cdx <= snap_distance)
3757       // snap to horizontal center
3758       *dx = x - (rect.x() + ((rect.width() - width) / 2));
3759     if (cdy <= snap_distance)
3760       // snap to vertical center
3761       *dy = y - (rect.y() + ((rect.height() - height) / 2));
3762   }
3763 }
3764 
3765 
snapAdjust(int * x,int * y)3766 void BlackboxWindow::snapAdjust(int *x, int *y) {
3767   int nx, ny, dx, dy, init_dx, init_dy;
3768   const int edge_distance = blackbox->resource().edgeSnapThreshold();
3769   const int win_distance  = blackbox->resource().windowSnapThreshold();
3770 
3771   nx = (win_distance > edge_distance) ? win_distance : edge_distance;
3772   ny = (win_distance > edge_distance) ? win_distance : edge_distance;
3773   dx = init_dx = ++nx; dy = init_dy = ++ny;
3774 
3775   if (edge_distance) {
3776     collisionAdjust(&dx, &dy, *x, *y, frame.rect.width(), frame.rect.height(),
3777                     _screen->availableArea(), edge_distance, true);
3778     nx = (dx != init_dx && std::abs(dx) < std::abs(nx)) ? dx : nx; dx = init_dx;
3779     ny = (dy != init_dy && std::abs(dy) < std::abs(ny)) ? dy : ny; dy = init_dy;
3780     if (!blackbox->resource().fullMaximization()) {
3781       collisionAdjust(&dx, &dy, *x, *y, frame.rect.width(), frame.rect.height(),
3782                       _screen->screenInfo().rect(), edge_distance);
3783       nx = (dx != init_dx && std::abs(dx) < std::abs(nx)) ? dx : nx; dx = init_dx;
3784       ny = (dy != init_dy && std::abs(dy) < std::abs(ny)) ? dy : ny; dy = init_dy;
3785     }
3786   }
3787   if (win_distance) {
3788     StackingList::const_iterator it = _screen->stackingList().begin(),
3789                                 end = _screen->stackingList().end();
3790     for (; it != end; ++it) {
3791       BlackboxWindow * const win = dynamic_cast<BlackboxWindow *>(*it);
3792       if (win && win != this &&
3793           win->workspace() == _screen->currentWorkspace()) {
3794         collisionAdjust(&dx, &dy, *x, *y, frame.rect.width(),
3795                         frame.rect.height(), win->frame.rect, win_distance);
3796         nx = (dx != init_dx && std::abs(dx) < std::abs(nx)) ? dx : nx; dx = init_dx;
3797         ny = (dy != init_dy && std::abs(dy) < std::abs(ny)) ? dy : ny; dy = init_dy;
3798       }
3799     }
3800   }
3801 
3802   *x = (nx != init_dx) ? (*x - nx) : *x;
3803   *y = (ny != init_dy) ? (*y - ny) : *y;
3804 }
3805 
3806 
continueMove(int x_root,int y_root)3807 void BlackboxWindow::continueMove(int x_root, int y_root) {
3808   int dx = x_root - frame.grab_x, dy = y_root - frame.grab_y;
3809 
3810   snapAdjust(&dx, &dy);
3811 
3812   if (blackbox->resource().opaqueMove()) {
3813     configure(dx, dy, frame.rect.width(), frame.rect.height());
3814   } else {
3815     bt::Pen pen(_screen->screenNumber(), bt::Color(0xff, 0xff, 0xff));
3816     const int bw = _screen->resource().windowStyle().frame_border_width,
3817               hw = bw / 2;
3818     pen.setGCFunction(GXxor);
3819     pen.setLineWidth(bw);
3820     pen.setSubWindowMode(IncludeInferiors);
3821     XDrawRectangle(blackbox->XDisplay(), _screen->screenInfo().rootWindow(),
3822                    pen.gc(),
3823                    frame.changing.x() + hw,
3824                    frame.changing.y() + hw,
3825                    frame.changing.width() - bw,
3826                    frame.changing.height() - bw);
3827 
3828     frame.changing.setPos(dx, dy);
3829 
3830     XDrawRectangle(blackbox->XDisplay(), _screen->screenInfo().rootWindow(),
3831                    pen.gc(),
3832                    frame.changing.x() + hw,
3833                    frame.changing.y() + hw,
3834                    frame.changing.width() - bw,
3835                    frame.changing.height() - bw);
3836   }
3837 
3838   _screen->showGeometry(BScreen::Position, bt::Rect(dx, dy, 0, 0));
3839 }
3840 
3841 
finishMove()3842 void BlackboxWindow::finishMove() {
3843   XUngrabPointer(blackbox->XDisplay(), blackbox->XTime());
3844 
3845   client.state.moving = false;
3846 
3847   if (!blackbox->resource().opaqueMove()) {
3848     bt::Pen pen(_screen->screenNumber(), bt::Color(0xff, 0xff, 0xff));
3849     const int bw = _screen->resource().windowStyle().frame_border_width,
3850               hw = bw / 2;
3851     pen.setGCFunction(GXxor);
3852     pen.setLineWidth(bw);
3853     pen.setSubWindowMode(IncludeInferiors);
3854     XDrawRectangle(blackbox->XDisplay(),
3855                    _screen->screenInfo().rootWindow(),
3856                    pen.gc(),
3857                    frame.changing.x() + hw,
3858                    frame.changing.y() + hw,
3859                    frame.changing.width() - bw,
3860                    frame.changing.height() - bw);
3861     blackbox->XUngrabServer();
3862 
3863     configure(frame.changing);
3864   } else {
3865     configure(frame.rect);
3866   }
3867 
3868   _screen->hideGeometry();
3869 }
3870 
3871 
startResize(Window window)3872 void BlackboxWindow::startResize(Window window) {
3873   if (frame.grab_x < (signed) frame.rect.width() / 2) {
3874     if (frame.grab_y < (signed) frame.rect.height() / 2)
3875       frame.corner = BottomRight;
3876     else
3877       frame.corner = TopRight;
3878   } else {
3879     if (frame.grab_y < (signed) frame.rect.height() / 2)
3880       frame.corner = BottomLeft;
3881     else
3882       frame.corner = TopLeft;
3883   }
3884 
3885   Cursor cursor = None;
3886   switch (frame.corner) {
3887   case TopLeft:
3888     cursor = blackbox->resource().cursors().resize_bottom_right;
3889     frame.grab_x = frame.rect.width() - frame.grab_x;
3890     frame.grab_y = frame.rect.height() - frame.grab_y;
3891     break;
3892   case BottomLeft:
3893     cursor = blackbox->resource().cursors().resize_top_right;
3894     frame.grab_x = frame.rect.width() - frame.grab_x;
3895     break;
3896   case TopRight:
3897     cursor = blackbox->resource().cursors().resize_bottom_left;
3898     frame.grab_y = frame.rect.height() - frame.grab_y;
3899     break;
3900   case BottomRight:
3901     cursor = blackbox->resource().cursors().resize_top_left;
3902     break;
3903   }
3904 
3905   // begin a resize
3906   XGrabPointer(blackbox->XDisplay(), window, False,
3907                ButtonMotionMask | ButtonReleaseMask,
3908                GrabModeAsync, GrabModeAsync, None, cursor, blackbox->XTime());
3909 
3910   client.state.resizing = true;
3911 
3912   frame.changing = constrain(frame.rect, frame.margin, client.wmnormal,
3913                              Corner(frame.corner));
3914 
3915   if (!blackbox->resource().opaqueResize()) {
3916     blackbox->XGrabServer();
3917 
3918     bt::Pen pen(_screen->screenNumber(), bt::Color(0xff, 0xff, 0xff));
3919     const int bw = _screen->resource().windowStyle().frame_border_width,
3920               hw = bw / 2;
3921     pen.setGCFunction(GXxor);
3922     pen.setLineWidth(bw);
3923     pen.setSubWindowMode(IncludeInferiors);
3924     XDrawRectangle(blackbox->XDisplay(),
3925                    _screen->screenInfo().rootWindow(),
3926                    pen.gc(),
3927                    frame.changing.x() + hw,
3928                    frame.changing.y() + hw,
3929                    frame.changing.width() - bw,
3930                    frame.changing.height() - bw);
3931   } else {
3932     // unset maximized state when resized
3933     if (isMaximized())
3934       maximize(0);
3935   }
3936 
3937   showGeometry(frame.changing);
3938 }
3939 
3940 
continueResize(int x_root,int y_root)3941 void BlackboxWindow::continueResize(int x_root, int y_root) {
3942   // continue a resize
3943   const bt::Rect curr = frame.changing;
3944 
3945   switch (frame.corner) {
3946   case TopLeft:
3947   case BottomLeft:
3948     frame.changing.setCoords(frame.changing.left(),
3949                              frame.changing.top(),
3950                              std::max<signed>(x_root + frame.grab_x,
3951                                               frame.changing.left()
3952                                               + (frame.margin.left
3953                                                  + frame.margin.right + 1)),
3954                              frame.changing.bottom());
3955     break;
3956   case TopRight:
3957   case BottomRight:
3958     frame.changing.setCoords(std::min<signed>(x_root - frame.grab_x,
3959                                               frame.changing.right()
3960                                               - (frame.margin.left
3961                                                  + frame.margin.right + 1)),
3962                              frame.changing.top(),
3963                              frame.changing.right(),
3964                              frame.changing.bottom());
3965     break;
3966   }
3967 
3968   switch (frame.corner) {
3969   case TopLeft:
3970   case TopRight:
3971     frame.changing.setCoords(frame.changing.left(),
3972                              frame.changing.top(),
3973                              frame.changing.right(),
3974                              std::max<signed>(y_root + frame.grab_y,
3975                                               frame.changing.top()
3976                                               + (frame.margin.top
3977                                                  + frame.margin.bottom + 1)));
3978     break;
3979   case BottomLeft:
3980   case BottomRight:
3981     frame.changing.setCoords(frame.changing.left(),
3982                              std::min<signed>(y_root - frame.grab_y,
3983                                               frame.rect.bottom()
3984                                               - (frame.margin.top
3985                                                  + frame.margin.bottom + 1)),
3986                              frame.changing.right(),
3987                              frame.changing.bottom());
3988     break;
3989   }
3990 
3991   frame.changing = constrain(frame.changing, frame.margin, client.wmnormal,
3992                              Corner(frame.corner));
3993 
3994   if (curr != frame.changing) {
3995     if (blackbox->resource().opaqueResize()) {
3996       configure(frame.changing);
3997     } else {
3998       bt::Pen pen(_screen->screenNumber(), bt::Color(0xff, 0xff, 0xff));
3999       const int bw = _screen->resource().windowStyle().frame_border_width,
4000                 hw = bw / 2;
4001       pen.setGCFunction(GXxor);
4002       pen.setLineWidth(bw);
4003       pen.setSubWindowMode(IncludeInferiors);
4004       XDrawRectangle(blackbox->XDisplay(),
4005                      _screen->screenInfo().rootWindow(),
4006                      pen.gc(),
4007                      curr.x() + hw,
4008                      curr.y() + hw,
4009                      curr.width() - bw,
4010                      curr.height() - bw);
4011 
4012       XDrawRectangle(blackbox->XDisplay(),
4013                      _screen->screenInfo().rootWindow(),
4014                      pen.gc(),
4015                      frame.changing.x() + hw,
4016                      frame.changing.y() + hw,
4017                      frame.changing.width() - bw,
4018                      frame.changing.height() - bw);
4019     }
4020 
4021     showGeometry(frame.changing);
4022   }
4023 }
4024 
4025 
finishResize()4026 void BlackboxWindow::finishResize() {
4027 
4028   if (!blackbox->resource().opaqueResize()) {
4029     bt::Pen pen(_screen->screenNumber(), bt::Color(0xff, 0xff, 0xff));
4030     const int bw = _screen->resource().windowStyle().frame_border_width,
4031               hw = bw / 2;
4032     pen.setGCFunction(GXxor);
4033     pen.setLineWidth(bw);
4034     pen.setSubWindowMode(IncludeInferiors);
4035     XDrawRectangle(blackbox->XDisplay(),
4036                    _screen->screenInfo().rootWindow(),
4037                    pen.gc(),
4038                    frame.changing.x() + hw,
4039                    frame.changing.y() + hw,
4040                    frame.changing.width() - bw,
4041                    frame.changing.height() - bw);
4042 
4043     blackbox->XUngrabServer();
4044 
4045     // unset maximized state when resized
4046     if (isMaximized())
4047       maximize(0);
4048   }
4049 
4050   client.state.resizing = false;
4051 
4052   XUngrabPointer(blackbox->XDisplay(), blackbox->XTime());
4053 
4054   _screen->hideGeometry();
4055 
4056   frame.changing = constrain(frame.changing, frame.margin, client.wmnormal,
4057                              Corner(frame.corner));
4058   configure(frame.changing);
4059 }
4060 
4061 
4062 /*
4063  * show the geometry of the window based on rectangle r.
4064  * The logical width and height are used here.  This refers to the user's
4065  * perception of the window size (for example an xterm resizes in cells,
4066  * not in pixels).  No extra work is needed if there is no difference between
4067  * the logical and actual dimensions.
4068  */
showGeometry(const bt::Rect & r) const4069 void BlackboxWindow::showGeometry(const bt::Rect &r) const {
4070   unsigned int w = r.width(), h = r.height();
4071 
4072   // remove the window frame
4073   w -= frame.margin.left + frame.margin.right;
4074   h -= frame.margin.top + frame.margin.bottom;
4075 
4076   if (client.wmnormal.flags & PResizeInc) {
4077     if (client.wmnormal.flags & (PMinSize|PBaseSize)) {
4078       w -= ((client.wmnormal.base_width)
4079             ? client.wmnormal.base_width
4080             : client.wmnormal.min_width);
4081       h -= ((client.wmnormal.base_height)
4082             ? client.wmnormal.base_height
4083             : client.wmnormal.min_height);
4084     }
4085 
4086     w /= client.wmnormal.width_inc;
4087     h /= client.wmnormal.height_inc;
4088   }
4089 
4090   _screen->showGeometry(BScreen::Size, bt::Rect(0, 0, w, h));
4091 }
4092 
4093 
4094 // see my rant above for an explanation of this operator
operator ==(const WMNormalHints & x,const WMNormalHints & y)4095 bool operator==(const WMNormalHints &x, const WMNormalHints &y) {
4096   return (x.flags == y.flags
4097           && x.min_width == y.min_width
4098           && x.min_height == y.min_height
4099           && x.max_width == y.max_width
4100           && x.max_height == y.max_height
4101           && x.width_inc == y.width_inc
4102           && x.height_inc == y.height_inc
4103           && x.min_aspect_x == y.min_aspect_x
4104           && x.min_aspect_y == y.min_aspect_y
4105           && x.max_aspect_x == y.max_aspect_x
4106           && x.max_aspect_y == y.max_aspect_y
4107           && x.base_width == y.base_width
4108           && x.base_height == y.base_height
4109           && x.win_gravity == y.win_gravity);
4110 }
4111