]> icculus.org git repositories - mikachu/openbox.git/blob - src/Window.cc
fixes to make sticky windows work better. they appear in all workspace lists, they...
[mikachu/openbox.git] / src / Window.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2 // Window.cc for Blackbox - an X11 Window manager
3 // Copyright (c) 2001 - 2002 Sean 'Shaleh' Perry <shaleh at debian.org>
4 // Copyright (c) 1997 - 2000, 2002 Brad Hughes <bhughes at trolltech.com>
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining a
7 // copy of this software and associated documentation files (the "Software"),
8 // to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 // and/or sell copies of the Software, and to permit persons to whom the
11 // Software is furnished to do so, subject to the following conditions:
12 //
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 // DEALINGS IN THE SOFTWARE.
23
24 #ifdef    HAVE_CONFIG_H
25 #  include "../config.h"
26 #endif // HAVE_CONFIG_H
27
28 extern "C" {
29 #include <X11/Xatom.h>
30 #include <X11/keysym.h>
31
32 #ifdef HAVE_STRING_H
33 #  include <string.h>
34 #endif // HAVE_STRING_H
35
36 #ifdef    DEBUG
37 #  ifdef    HAVE_STDIO_H
38 #    include <stdio.h>
39 #  endif // HAVE_STDIO_H
40 #endif // DEBUG
41
42 #ifdef HAVE_STDLIB_H
43    #include <stdlib.h>
44 #endif // HAVE_STDLIB_H
45 }
46
47 #include "i18n.hh"
48 #include "blackbox.hh"
49 #include "Clientmenu.hh"
50 #include "Font.hh"
51 #include "GCCache.hh"
52 #include "Iconmenu.hh"
53 #include "Image.hh"
54 #include "Screen.hh"
55 #include "Toolbar.hh"
56 #include "Util.hh"
57 #include "Window.hh"
58 #include "Windowmenu.hh"
59 #include "Workspace.hh"
60
61 using std::string;
62 using std::abs;
63
64 // change this to change what modifier keys openbox uses for mouse bindings
65 // for example: Mod1Mask | ControlMask
66 //          or: ControlMask| ShiftMask
67 const unsigned int ModMask = Mod1Mask;
68
69 /*
70  * Initializes the class with default values/the window's set initial values.
71  */
72 BlackboxWindow::BlackboxWindow(Blackbox *b, Window w, BScreen *s) {
73   // fprintf(stderr, "BlackboxWindow size: %d bytes\n",
74   // sizeof(BlackboxWindow));
75
76 #ifdef    DEBUG
77   fprintf(stderr, "BlackboxWindow::BlackboxWindow(): creating 0x%lx\n", w);
78 #endif // DEBUG
79
80   /*
81     set timer to zero... it is initialized properly later, so we check
82     if timer is zero in the destructor, and assume that the window is not
83     fully constructed if timer is zero...
84   */
85   timer = 0;
86   blackbox = b;
87   client.window = w;
88   screen = s;
89   xatom = blackbox->getXAtom();
90
91   if (! validateClient()) {
92     delete this;
93     return;
94   }
95
96   // fetch client size and placement
97   XWindowAttributes wattrib;
98   if (! XGetWindowAttributes(blackbox->getXDisplay(),
99                              client.window, &wattrib) ||
100       ! wattrib.screen || wattrib.override_redirect) {
101 #ifdef    DEBUG
102     fprintf(stderr,
103             "BlackboxWindow::BlackboxWindow(): XGetWindowAttributes failed\n");
104 #endif // DEBUG
105
106     delete this;
107     return;
108   }
109
110 #ifdef DEBUG
111   fprintf(stderr, "0x%lx: initial (%d, %d) w: %d, h: %d\n", client.window,
112           wattrib.x, wattrib.y, wattrib.width, wattrib.height);
113 #endif // DEBUG
114
115   // set the eventmask early in the game so that we make sure we get
116   // all the events we are interested in
117   XSetWindowAttributes attrib_set;
118   attrib_set.event_mask = PropertyChangeMask | FocusChangeMask |
119                           StructureNotifyMask;
120   attrib_set.do_not_propagate_mask = ButtonPressMask | ButtonReleaseMask |
121                                      ButtonMotionMask;
122   XChangeWindowAttributes(blackbox->getXDisplay(), client.window,
123                           CWEventMask|CWDontPropagate, &attrib_set);
124
125   flags.moving = flags.resizing = flags.shaded = flags.visible =
126     flags.iconic = flags.focused = flags.stuck = flags.modal =
127     flags.send_focus_message = flags.shaped = flags.skip_taskbar =
128     flags.skip_pager = flags.fullscreen = False;
129   flags.maximized = 0;
130
131   blackbox_attrib.workspace = window_number = BSENTINEL;
132
133   blackbox_attrib.flags = blackbox_attrib.attrib = blackbox_attrib.stack
134     = blackbox_attrib.decoration = 0l;
135   blackbox_attrib.premax_x = blackbox_attrib.premax_y = 0;
136   blackbox_attrib.premax_w = blackbox_attrib.premax_h = 0;
137
138   frame.border_w = 1;
139   frame.window = frame.plate = frame.title = frame.handle = None;
140   frame.close_button = frame.iconify_button = frame.maximize_button = None;
141   frame.right_grip = frame.left_grip = None;
142
143   frame.ulabel_pixel = frame.flabel_pixel = frame.utitle_pixel =
144   frame.ftitle_pixel = frame.uhandle_pixel = frame.fhandle_pixel =
145     frame.ubutton_pixel = frame.fbutton_pixel = frame.pbutton_pixel =
146     frame.uborder_pixel = frame.fborder_pixel = frame.ugrip_pixel =
147     frame.fgrip_pixel = 0;
148   frame.utitle = frame.ftitle = frame.uhandle = frame.fhandle = None;
149   frame.ulabel = frame.flabel = frame.ubutton = frame.fbutton = None;
150   frame.pbutton = frame.ugrip = frame.fgrip = None;
151
152   decorations = Decor_Titlebar | Decor_Border | Decor_Handle |
153                 Decor_Iconify | Decor_Maximize;
154   functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize;
155
156   client.normal_hint_flags = 0;
157   client.window_group = None;
158   client.transient_for = 0;
159
160   current_state = NormalState;
161
162   /*
163     get the initial size and location of client window (relative to the
164     _root window_). This position is the reference point used with the
165     window's gravity to find the window's initial position.
166   */
167   client.rect.setRect(wattrib.x, wattrib.y, wattrib.width, wattrib.height);
168   client.old_bw = wattrib.border_width;
169
170   windowmenu = 0;
171   lastButtonPressTime = 0;
172
173   timer = new BTimer(blackbox, this);
174   timer->setTimeout(blackbox->getAutoRaiseDelay());
175
176   if (! getBlackboxHints()) {
177     getMWMHints();
178     getNetWMHints();
179   }
180
181   // get size, aspect, minimum/maximum size and other hints set by the
182   // client
183   getWMProtocols();
184   getWMHints();
185   getWMNormalHints();
186
187 #ifdef DEBUG
188   fprintf(stderr, "0x%lx: after hints (%d, %d) w: %d, h: %d\n", client.window,
189           client.rect.x(), client.rect.y(),
190           client.rect.width(), client.rect.height());
191 #endif // DEBUG
192
193   frame.window = createToplevelWindow();
194   frame.plate = createChildWindow(frame.window);
195   associateClientWindow();
196
197   blackbox->saveWindowSearch(frame.window, this);
198   blackbox->saveWindowSearch(frame.plate, this);
199   blackbox->saveWindowSearch(client.window, this);
200
201   // determine if this is a transient window
202   getTransientInfo();
203
204   // determine the window's type, so we can decide its decorations and
205   // functionality, or if we should not manage it at all
206   getWindowType();
207
208   // adjust the window decorations/behavior based on the window type
209   switch (window_type) {
210   case Type_Desktop:
211   case Type_Dock:
212   case Type_Menu:
213   case Type_Toolbar:
214   case Type_Utility:
215   case Type_Splash:
216     // none of these windows are decorated or manipulated by the window manager
217     decorations = 0;
218     functions = 0;
219     blackbox_attrib.workspace = 0;  // we do need to belong to a workspace
220     flags.stuck = True;             // we show up on all workspaces
221     break;
222
223   case Type_Dialog:
224     // dialogs cannot be maximized, and don't display a handle
225     decorations &= ~(Decor_Maximize | Decor_Handle);
226     functions &= ~Func_Maximize;
227     break;
228
229   case Type_Normal:
230     // normal windows retain all of the possible decorations and functionality
231     break;
232   }
233
234   // further adjeust the window's decorations/behavior based on window sizes
235   if ((client.normal_hint_flags & PMinSize) &&
236       (client.normal_hint_flags & PMaxSize) &&
237       client.max_width <= client.min_width &&
238       client.max_height <= client.min_height) {
239     decorations &= ~(Decor_Maximize | Decor_Handle);
240     functions &= ~(Func_Resize | Func_Maximize);
241   }
242   upsize();
243
244 #ifdef DFEBUG
245   fprintf(stderr, "0x%lx: sizes reflect the frame from now on\n",
246           client.window);
247   fprintf(stderr, "0x%lx: after upsize (%d, %d) w: %d, h: %d\n", client.window,
248           frame.rect.x(), frame.rect.y(),
249           frame.rect.width(), frame.rect.height());
250 #endif // DEBUG
251
252   setAllowedActions();
253
254   bool place_window = True;
255   if (blackbox->isStartup() || isTransient() ||
256       client.normal_hint_flags & (PPosition|USPosition)) {
257     applyGravity(frame.rect);
258
259     if (blackbox->isStartup() || client.rect.intersects(screen->getRect()))
260       place_window = False;
261   }
262
263 #ifdef DEBUG
264   fprintf(stderr, "0x%lx: after gravity (%d, %d) w: %d, h: %d\n",
265           client.window,
266           frame.rect.x(), frame.rect.y(),
267           frame.rect.width(), frame.rect.height());
268 #endif // DEBUG
269
270   // add the window's strut. note this is done *after* placing the window.
271   screen->addStrut(&client.strut);
272   updateStrut();
273   
274   if (decorations & Decor_Titlebar)
275     createTitlebar();
276
277   if (decorations & Decor_Handle)
278     createHandle();
279
280 #ifdef    SHAPE
281   if (blackbox->hasShapeExtensions() && flags.shaped) {
282     configureShape();
283   }
284 #endif // SHAPE
285
286   windowmenu = new Windowmenu(this);
287
288   if (blackbox_attrib.workspace >= screen->getWorkspaceCount())
289     screen->getCurrentWorkspace()->addWindow(this, place_window);
290   else
291     screen->getWorkspace(blackbox_attrib.workspace)->
292       addWindow(this, place_window);
293
294   if (! place_window) {
295     // don't need to call configure if we are letting the workspace
296     // place the window
297     configure(frame.rect.x(), frame.rect.y(),
298               frame.rect.width(), frame.rect.height());
299
300 #ifdef DEBUG
301     fprintf(stderr, "0x%lx: after configure (%d, %d) w: %d, h: %d\n",
302             client.window,
303             frame.rect.x(), frame.rect.y(),
304             frame.rect.width(), frame.rect.height());
305 #endif // DEBUG
306   }
307
308   // preserve the window's initial state on first map, and its current state
309   // across a restart
310   unsigned long initial_state = current_state;
311   if (! getState())
312     current_state = initial_state;
313
314   // get sticky state from our parent window if we've got one
315   if (isTransient() && client.transient_for != (BlackboxWindow *) ~0ul &&
316       client.transient_for->isStuck() != flags.stuck)
317     stick();
318
319   if (flags.shaded) {
320     flags.shaded = False;
321     initial_state = current_state;
322     shade();
323
324     /*
325       At this point in the life of a window, current_state should only be set
326       to IconicState if the window was an *icon*, not if it was shaded.
327     */
328     if (initial_state != IconicState)
329       current_state = NormalState;
330   }
331
332   if (flags.stuck) {
333     flags.stuck = False;
334     stick();
335   }
336
337   if (flags.maximized && (functions & Func_Maximize))
338     remaximize();
339
340   /*
341     When the window is mapped (and also when its attributes are restored), the
342     current_state that was set here will be used.
343     It is set to Normal if the window is to be mapped or it is set to Iconic
344     if the window is to be iconified.
345     *Note* that for sticky windows, the same rules apply here, they are in
346     fact never set to Iconic since there is no way for us to tell if a sticky
347     window was iconified previously.
348   */
349  
350   positionWindows();
351   decorate();
352   grabButtons();
353
354   XMapSubwindows(blackbox->getXDisplay(), frame.window);
355
356   redrawWindowFrame();
357
358 #ifdef DEBUG
359   fprintf(stderr, "0x%lx: end of constructor (%d, %d) w: %d, h: %d\n",
360           client.window,
361           frame.rect.x(), frame.rect.y(),
362           frame.rect.width(), frame.rect.height());
363 #endif // DEBUG
364 }
365
366
367 BlackboxWindow::~BlackboxWindow(void) {
368 #ifdef    DEBUG
369   fprintf(stderr, "BlackboxWindow::~BlackboxWindow: destroying 0x%lx\n",
370           client.window);
371 #endif // DEBUG
372
373   if (! timer) // window not managed...
374     return;
375
376   screen->removeStrut(&client.strut);
377   screen->updateAvailableArea();
378
379   // We don't need to worry about resizing because resizing always grabs the X
380   // server. This should only ever happen if using opaque moving.
381   if (flags.moving)
382     endMove();
383
384   delete timer;
385
386   delete windowmenu;
387
388   if (client.window_group) {
389     BWindowGroup *group = blackbox->searchGroup(client.window_group);
390     if (group) group->removeWindow(this);
391   }
392
393   // remove ourselves from our transient_for
394   if (isTransient()) {
395     if (client.transient_for != (BlackboxWindow *) ~0ul) {
396       client.transient_for->client.transientList.remove(this);
397     }
398     client.transient_for = (BlackboxWindow*) 0;
399   }
400
401   if (client.transientList.size() > 0) {
402     // reset transient_for for all transients
403     BlackboxWindowList::iterator it, end = client.transientList.end();
404     for (it = client.transientList.begin(); it != end; ++it) {
405       (*it)->client.transient_for = (BlackboxWindow*) 0;
406     }
407   }
408
409   if (frame.title)
410     destroyTitlebar();
411
412   if (frame.handle)
413     destroyHandle();
414
415   if (frame.plate) {
416     blackbox->removeWindowSearch(frame.plate);
417     XDestroyWindow(blackbox->getXDisplay(), frame.plate);
418   }
419
420   if (frame.window) {
421     blackbox->removeWindowSearch(frame.window);
422     XDestroyWindow(blackbox->getXDisplay(), frame.window);
423   }
424
425   blackbox->removeWindowSearch(client.window);
426 }
427
428
429 /*
430  * Creates a new top level window, with a given location, size, and border
431  * width.
432  * Returns: the newly created window
433  */
434 Window BlackboxWindow::createToplevelWindow(void) {
435   XSetWindowAttributes attrib_create;
436   unsigned long create_mask = CWBackPixmap | CWBorderPixel | CWColormap |
437                               CWOverrideRedirect | CWEventMask;
438
439   attrib_create.background_pixmap = None;
440   attrib_create.colormap = screen->getColormap();
441   attrib_create.override_redirect = True;
442   attrib_create.event_mask = ButtonPressMask | ButtonReleaseMask |
443                              ButtonMotionMask | EnterWindowMask;
444
445   return XCreateWindow(blackbox->getXDisplay(), screen->getRootWindow(),
446                        0, 0, 1, 1, frame.border_w, screen->getDepth(),
447                        InputOutput, screen->getVisual(), create_mask,
448                        &attrib_create);
449 }
450
451
452 /*
453  * Creates a child window, and optionally associates a given cursor with
454  * the new window.
455  */
456 Window BlackboxWindow::createChildWindow(Window parent, Cursor cursor) {
457   XSetWindowAttributes attrib_create;
458   unsigned long create_mask = CWBackPixmap | CWBorderPixel |
459                               CWEventMask;
460
461   attrib_create.background_pixmap = None;
462   attrib_create.event_mask = ButtonPressMask | ButtonReleaseMask |
463                              ButtonMotionMask | ExposureMask;
464
465   if (cursor) {
466     create_mask |= CWCursor;
467     attrib_create.cursor = cursor;
468   }
469
470   return XCreateWindow(blackbox->getXDisplay(), parent, 0, 0, 1, 1, 0,
471                        screen->getDepth(), InputOutput, screen->getVisual(),
472                        create_mask, &attrib_create);
473 }
474
475
476 void BlackboxWindow::associateClientWindow(void) {
477   XSetWindowBorderWidth(blackbox->getXDisplay(), client.window, 0);
478   getWMName();
479   getWMIconName();
480
481   XChangeSaveSet(blackbox->getXDisplay(), client.window, SetModeInsert);
482
483   XSelectInput(blackbox->getXDisplay(), frame.plate, SubstructureRedirectMask);
484
485   XGrabServer(blackbox->getXDisplay());
486
487   unsigned long event_mask = PropertyChangeMask | FocusChangeMask |
488                              StructureNotifyMask;
489   XSelectInput(blackbox->getXDisplay(), client.window,
490                event_mask & ~StructureNotifyMask);
491   XReparentWindow(blackbox->getXDisplay(), client.window, frame.plate, 0, 0);
492   XSelectInput(blackbox->getXDisplay(), client.window, event_mask);
493
494   XUngrabServer(blackbox->getXDisplay());
495
496   XRaiseWindow(blackbox->getXDisplay(), frame.plate);
497   XMapSubwindows(blackbox->getXDisplay(), frame.plate);
498
499
500 #ifdef    SHAPE
501   if (blackbox->hasShapeExtensions()) {
502     XShapeSelectInput(blackbox->getXDisplay(), client.window,
503                       ShapeNotifyMask);
504
505     Bool shaped = False;
506     int foo;
507     unsigned int ufoo;
508
509     XShapeQueryExtents(blackbox->getXDisplay(), client.window, &shaped,
510                        &foo, &foo, &ufoo, &ufoo, &foo, &foo, &foo,
511                        &ufoo, &ufoo);
512     flags.shaped = shaped;
513   }
514 #endif // SHAPE
515 }
516
517
518 void BlackboxWindow::decorate(void) {
519   BTexture* texture;
520
521   texture = &(screen->getWindowStyle()->b_focus);
522   frame.fbutton = texture->render(frame.button_w, frame.button_w,
523                                   frame.fbutton);
524   if (! frame.fbutton)
525     frame.fbutton_pixel = texture->color().pixel();
526
527   texture = &(screen->getWindowStyle()->b_unfocus);
528   frame.ubutton = texture->render(frame.button_w, frame.button_w,
529                                   frame.ubutton);
530   if (! frame.ubutton)
531     frame.ubutton_pixel = texture->color().pixel();
532
533   texture = &(screen->getWindowStyle()->b_pressed);
534   frame.pbutton = texture->render(frame.button_w, frame.button_w,
535                                   frame.pbutton);
536   if (! frame.pbutton)
537     frame.pbutton_pixel = texture->color().pixel();
538
539   if (decorations & Decor_Titlebar) {
540     texture = &(screen->getWindowStyle()->t_focus);
541     frame.ftitle = texture->render(frame.inside_w, frame.title_h,
542                                    frame.ftitle);
543     if (! frame.ftitle)
544       frame.ftitle_pixel = texture->color().pixel();
545
546     texture = &(screen->getWindowStyle()->t_unfocus);
547     frame.utitle = texture->render(frame.inside_w, frame.title_h,
548                                    frame.utitle);
549     if (! frame.utitle)
550       frame.utitle_pixel = texture->color().pixel();
551
552     XSetWindowBorder(blackbox->getXDisplay(), frame.title,
553                      screen->getBorderColor()->pixel());
554
555     decorateLabel();
556   }
557
558   if (decorations & Decor_Border) {
559     frame.fborder_pixel = screen->getWindowStyle()->f_focus.pixel();
560     frame.uborder_pixel = screen->getWindowStyle()->f_unfocus.pixel();
561     blackbox_attrib.flags |= AttribDecoration;
562     blackbox_attrib.decoration = DecorNormal;
563   } else {
564     blackbox_attrib.flags |= AttribDecoration;
565     blackbox_attrib.decoration = DecorNone;
566   }
567
568   if (decorations & Decor_Handle) {
569     texture = &(screen->getWindowStyle()->h_focus);
570     frame.fhandle = texture->render(frame.inside_w, frame.handle_h,
571                                     frame.fhandle);
572     if (! frame.fhandle)
573       frame.fhandle_pixel = texture->color().pixel();
574
575     texture = &(screen->getWindowStyle()->h_unfocus);
576     frame.uhandle = texture->render(frame.inside_w, frame.handle_h,
577                                     frame.uhandle);
578     if (! frame.uhandle)
579       frame.uhandle_pixel = texture->color().pixel();
580
581     texture = &(screen->getWindowStyle()->g_focus);
582     frame.fgrip = texture->render(frame.grip_w, frame.handle_h, frame.fgrip);
583     if (! frame.fgrip)
584       frame.fgrip_pixel = texture->color().pixel();
585
586     texture = &(screen->getWindowStyle()->g_unfocus);
587     frame.ugrip = texture->render(frame.grip_w, frame.handle_h, frame.ugrip);
588     if (! frame.ugrip)
589       frame.ugrip_pixel = texture->color().pixel();
590
591     XSetWindowBorder(blackbox->getXDisplay(), frame.handle,
592                      screen->getBorderColor()->pixel());
593     XSetWindowBorder(blackbox->getXDisplay(), frame.left_grip,
594                      screen->getBorderColor()->pixel());
595     XSetWindowBorder(blackbox->getXDisplay(), frame.right_grip,
596                      screen->getBorderColor()->pixel());
597   }
598
599   XSetWindowBorder(blackbox->getXDisplay(), frame.window,
600                    screen->getBorderColor()->pixel());
601 }
602
603
604 void BlackboxWindow::decorateLabel(void) {
605   BTexture *texture;
606
607   texture = &(screen->getWindowStyle()->l_focus);
608   frame.flabel = texture->render(frame.label_w, frame.label_h, frame.flabel);
609   if (! frame.flabel)
610     frame.flabel_pixel = texture->color().pixel();
611
612   texture = &(screen->getWindowStyle()->l_unfocus);
613   frame.ulabel = texture->render(frame.label_w, frame.label_h, frame.ulabel);
614   if (! frame.ulabel)
615     frame.ulabel_pixel = texture->color().pixel();
616 }
617
618
619 void BlackboxWindow::createHandle(void) {
620   frame.handle = createChildWindow(frame.window);
621   blackbox->saveWindowSearch(frame.handle, this);
622
623   frame.left_grip =
624     createChildWindow(frame.handle, blackbox->getLowerLeftAngleCursor());
625   blackbox->saveWindowSearch(frame.left_grip, this);
626
627   frame.right_grip =
628     createChildWindow(frame.handle, blackbox->getLowerRightAngleCursor());
629   blackbox->saveWindowSearch(frame.right_grip, this);
630 }
631
632
633 void BlackboxWindow::destroyHandle(void) {
634   if (frame.fhandle)
635     screen->getImageControl()->removeImage(frame.fhandle);
636
637   if (frame.uhandle)
638     screen->getImageControl()->removeImage(frame.uhandle);
639
640   if (frame.fgrip)
641     screen->getImageControl()->removeImage(frame.fgrip);
642
643   if (frame.ugrip)
644     screen->getImageControl()->removeImage(frame.ugrip);
645
646   blackbox->removeWindowSearch(frame.left_grip);
647   blackbox->removeWindowSearch(frame.right_grip);
648
649   XDestroyWindow(blackbox->getXDisplay(), frame.left_grip);
650   XDestroyWindow(blackbox->getXDisplay(), frame.right_grip);
651   frame.left_grip = frame.right_grip = None;
652
653   blackbox->removeWindowSearch(frame.handle);
654   XDestroyWindow(blackbox->getXDisplay(), frame.handle);
655   frame.handle = None;
656 }
657
658
659 void BlackboxWindow::createTitlebar(void) {
660   frame.title = createChildWindow(frame.window);
661   frame.label = createChildWindow(frame.title);
662   blackbox->saveWindowSearch(frame.title, this);
663   blackbox->saveWindowSearch(frame.label, this);
664
665   if (decorations & Decor_Iconify) createIconifyButton();
666   if (decorations & Decor_Maximize) createMaximizeButton();
667   if (decorations & Decor_Close) createCloseButton();
668 }
669
670
671 void BlackboxWindow::destroyTitlebar(void) {
672   if (frame.close_button)
673     destroyCloseButton();
674
675   if (frame.iconify_button)
676     destroyIconifyButton();
677
678   if (frame.maximize_button)
679     destroyMaximizeButton();
680
681   if (frame.ftitle)
682     screen->getImageControl()->removeImage(frame.ftitle);
683
684   if (frame.utitle)
685     screen->getImageControl()->removeImage(frame.utitle);
686
687   if (frame.flabel)
688     screen->getImageControl()->removeImage(frame.flabel);
689
690   if( frame.ulabel)
691     screen->getImageControl()->removeImage(frame.ulabel);
692
693   if (frame.fbutton)
694     screen->getImageControl()->removeImage(frame.fbutton);
695
696   if (frame.ubutton)
697     screen->getImageControl()->removeImage(frame.ubutton);
698
699   if (frame.pbutton)
700     screen->getImageControl()->removeImage(frame.pbutton);
701
702   blackbox->removeWindowSearch(frame.title);
703   blackbox->removeWindowSearch(frame.label);
704
705   XDestroyWindow(blackbox->getXDisplay(), frame.label);
706   XDestroyWindow(blackbox->getXDisplay(), frame.title);
707   frame.title = frame.label = None;
708 }
709
710
711 void BlackboxWindow::createCloseButton(void) {
712   if (frame.title != None) {
713     frame.close_button = createChildWindow(frame.title);
714     blackbox->saveWindowSearch(frame.close_button, this);
715   }
716 }
717
718
719 void BlackboxWindow::destroyCloseButton(void) {
720   blackbox->removeWindowSearch(frame.close_button);
721   XDestroyWindow(blackbox->getXDisplay(), frame.close_button);
722   frame.close_button = None;
723 }
724
725
726 void BlackboxWindow::createIconifyButton(void) {
727   if (frame.title != None) {
728     frame.iconify_button = createChildWindow(frame.title);
729     blackbox->saveWindowSearch(frame.iconify_button, this);
730   }
731 }
732
733
734 void BlackboxWindow::destroyIconifyButton(void) {
735   blackbox->removeWindowSearch(frame.iconify_button);
736   XDestroyWindow(blackbox->getXDisplay(), frame.iconify_button);
737   frame.iconify_button = None;
738 }
739
740
741 void BlackboxWindow::createMaximizeButton(void) {
742   if (frame.title != None) {
743     frame.maximize_button = createChildWindow(frame.title);
744     blackbox->saveWindowSearch(frame.maximize_button, this);
745   }
746 }
747
748
749 void BlackboxWindow::destroyMaximizeButton(void) {
750   blackbox->removeWindowSearch(frame.maximize_button);
751   XDestroyWindow(blackbox->getXDisplay(), frame.maximize_button);
752   frame.maximize_button = None;
753 }
754
755
756 void BlackboxWindow::positionButtons(bool redecorate_label) {
757   string layout = blackbox->getTitlebarLayout();
758   string parsed;
759
760   bool hasclose, hasiconify, hasmaximize, haslabel;
761   hasclose = hasiconify = hasmaximize = haslabel = false;
762
763   string::const_iterator it, end;
764   for (it = layout.begin(), end = layout.end(); it != end; ++it) {
765     switch(*it) {
766     case 'C':
767       if (! hasclose && (decorations & Decor_Close)) {
768         hasclose = true;
769         parsed += *it;
770       }
771       break;
772     case 'I':
773       if (! hasiconify && (decorations & Decor_Iconify)) {
774         hasiconify = true;
775         parsed += *it;
776       }
777       break;
778     case 'M':
779       if (! hasmaximize && (decorations & Decor_Maximize)) {
780         hasmaximize = true;
781         parsed += *it;
782       }
783       break;
784     case 'L':
785       if (! haslabel) {
786         haslabel = true;
787         parsed += *it;
788       }
789     }
790   }
791   if (! hasclose && frame.close_button)
792     destroyCloseButton();
793   if (! hasiconify && frame.iconify_button)
794     destroyIconifyButton();
795   if (! hasmaximize && frame.maximize_button)
796     destroyMaximizeButton();
797   if (! haslabel)
798     parsed += 'L';      // require that the label be in the layout
799
800   const unsigned int bsep = frame.bevel_w + 1;  // separation between elements
801   const unsigned int by = frame.bevel_w + 1;
802   const unsigned int ty = frame.bevel_w;
803
804   frame.label_w = frame.inside_w - bsep * 2 -
805     (frame.button_w + bsep) * (parsed.size() - 1);
806
807   unsigned int x = bsep;
808   for (it = parsed.begin(), end = parsed.end(); it != end; ++it) {
809     switch(*it) {
810     case 'C':
811       if (! frame.close_button) createCloseButton();
812       XMoveResizeWindow(blackbox->getXDisplay(), frame.close_button, x, by,
813                         frame.button_w, frame.button_w);
814       x += frame.button_w + bsep;
815       break;
816     case 'I':
817       if (! frame.iconify_button) createIconifyButton();
818       XMoveResizeWindow(blackbox->getXDisplay(), frame.iconify_button, x, by,
819                         frame.button_w, frame.button_w);
820       x += frame.button_w + bsep;
821       break;
822     case 'M':
823       if (! frame.maximize_button) createMaximizeButton();
824       XMoveResizeWindow(blackbox->getXDisplay(), frame.maximize_button, x, by,
825                         frame.button_w, frame.button_w);
826       x += frame.button_w + bsep;
827       break;
828     case 'L':
829       XMoveResizeWindow(blackbox->getXDisplay(), frame.label, x, ty,
830                         frame.label_w, frame.label_h);
831       x += frame.label_w + bsep;
832       break;
833     }
834   }
835
836   if (redecorate_label) decorateLabel();
837   redrawLabel();
838   redrawAllButtons();
839 }
840
841
842 void BlackboxWindow::reconfigure(void) {
843   restoreGravity(client.rect);
844   upsize();
845   applyGravity(frame.rect);
846   positionWindows();
847   decorate();
848   redrawWindowFrame();
849
850   ungrabButtons();
851   grabButtons();
852
853   if (windowmenu) {
854     windowmenu->move(windowmenu->getX(), frame.rect.y() + frame.title_h);
855     windowmenu->reconfigure();
856   }
857 }
858
859
860 void BlackboxWindow::grabButtons(void) {
861   if (! screen->isSloppyFocus() || screen->doClickRaise())
862     // grab button 1 for changing focus/raising
863     blackbox->grabButton(Button1, 0, frame.plate, True, ButtonPressMask,
864                          GrabModeSync, GrabModeSync, frame.plate, None,
865                          screen->allowScrollLock());
866   
867   if (functions & Func_Move)
868     blackbox->grabButton(Button1, ModMask, frame.window, True,
869                          ButtonReleaseMask | ButtonMotionMask, GrabModeAsync,
870                          GrabModeAsync, frame.window, None,
871                          screen->allowScrollLock());
872   if (functions & Func_Resize)
873     blackbox->grabButton(Button3, ModMask, frame.window, True,
874                          ButtonReleaseMask | ButtonMotionMask, GrabModeAsync,
875                          GrabModeAsync, frame.window, None,
876                          screen->allowScrollLock());
877   // alt+middle lowers the window
878   blackbox->grabButton(Button2, ModMask, frame.window, True,
879                        ButtonReleaseMask, GrabModeAsync, GrabModeAsync,
880                        frame.window, None,
881                        screen->allowScrollLock());
882 }
883
884
885 void BlackboxWindow::ungrabButtons(void) {
886   if (! screen->isSloppyFocus() || screen->doClickRaise())
887     blackbox->ungrabButton(Button1, 0, frame.plate);
888
889   blackbox->ungrabButton(Button1, ModMask, frame.window);
890   blackbox->ungrabButton(Button2, ModMask, frame.window);
891   blackbox->ungrabButton(Button3, ModMask, frame.window);
892 }
893
894
895 void BlackboxWindow::positionWindows(void) {
896   XMoveResizeWindow(blackbox->getXDisplay(), frame.window,
897                     frame.rect.x(), frame.rect.y(), frame.inside_w,
898                     (flags.shaded) ? frame.title_h : frame.inside_h);
899   XSetWindowBorderWidth(blackbox->getXDisplay(), frame.window,
900                         frame.border_w);
901   XSetWindowBorderWidth(blackbox->getXDisplay(), frame.plate,
902                         frame.mwm_border_w);
903   XMoveResizeWindow(blackbox->getXDisplay(), frame.plate,
904                     frame.margin.left - frame.mwm_border_w - frame.border_w,
905                     frame.margin.top - frame.mwm_border_w - frame.border_w,
906                     client.rect.width(), client.rect.height());
907   XMoveResizeWindow(blackbox->getXDisplay(), client.window,
908                     0, 0, client.rect.width(), client.rect.height());
909   // ensure client.rect contains the real location
910   client.rect.setPos(frame.rect.left() + frame.margin.left,
911                      frame.rect.top() + frame.margin.top);
912
913   if (decorations & Decor_Titlebar) {
914     if (frame.title == None) createTitlebar();
915
916     XSetWindowBorderWidth(blackbox->getXDisplay(), frame.title,
917                           frame.border_w);
918     XMoveResizeWindow(blackbox->getXDisplay(), frame.title, -frame.border_w,
919                       -frame.border_w, frame.inside_w, frame.title_h);
920
921     positionButtons();
922     XMapSubwindows(blackbox->getXDisplay(), frame.title);
923     XMapWindow(blackbox->getXDisplay(), frame.title);
924   } else if (frame.title) {
925     destroyTitlebar();
926   }
927   if (decorations & Decor_Handle) {
928     if (frame.handle == None) createHandle();
929     XSetWindowBorderWidth(blackbox->getXDisplay(), frame.handle,
930                           frame.border_w);
931     XSetWindowBorderWidth(blackbox->getXDisplay(), frame.left_grip,
932                           frame.border_w);
933     XSetWindowBorderWidth(blackbox->getXDisplay(), frame.right_grip,
934                           frame.border_w);
935
936     // use client.rect here so the value is correct even if shaded
937     XMoveResizeWindow(blackbox->getXDisplay(), frame.handle,
938                       -frame.border_w,
939                       client.rect.height() + frame.margin.top +
940                       frame.mwm_border_w - frame.border_w,
941                       frame.inside_w, frame.handle_h);
942     XMoveResizeWindow(blackbox->getXDisplay(), frame.left_grip,
943                       -frame.border_w, -frame.border_w,
944                       frame.grip_w, frame.handle_h);
945     XMoveResizeWindow(blackbox->getXDisplay(), frame.right_grip,
946                       frame.inside_w - frame.grip_w - frame.border_w,
947                       -frame.border_w, frame.grip_w, frame.handle_h);
948
949     XMapSubwindows(blackbox->getXDisplay(), frame.handle);
950     XMapWindow(blackbox->getXDisplay(), frame.handle);
951   } else if (frame.handle) {
952     destroyHandle();
953   }
954   XSync(blackbox->getXDisplay(), False);
955 }
956
957
958 void BlackboxWindow::updateStrut(void) {
959   unsigned long num = 4;
960   unsigned long *data;
961   if (! xatom->getValue(client.window, XAtom::net_wm_strut, XAtom::cardinal,
962                         num, &data))
963     return;
964  
965   if (num == 4) {
966     client.strut.left = data[0];
967     client.strut.right = data[1];
968     client.strut.top = data[2];
969     client.strut.bottom = data[3];
970
971     screen->updateAvailableArea();
972   }
973
974   delete [] data;
975 }
976
977
978 void BlackboxWindow::getWindowType(void) {
979   unsigned long val;
980   if (xatom->getValue(client.window, XAtom::net_wm_window_type, XAtom::atom,
981                       val)) {
982     if (val == xatom->getAtom(XAtom::net_wm_window_type_desktop))
983       window_type = Type_Desktop;
984     else if (val == xatom->getAtom(XAtom::net_wm_window_type_dock))
985       window_type = Type_Dock;
986     else if (val == xatom->getAtom(XAtom::net_wm_window_type_toolbar))
987       window_type = Type_Toolbar;
988     else if (val == xatom->getAtom(XAtom::net_wm_window_type_menu))
989       window_type = Type_Menu;
990     else if (val == xatom->getAtom(XAtom::net_wm_window_type_utility))
991       window_type = Type_Utility;
992     else if (val == xatom->getAtom(XAtom::net_wm_window_type_splash))
993       window_type = Type_Splash;
994     else if (val == xatom->getAtom(XAtom::net_wm_window_type_dialog))
995       window_type = Type_Dialog;
996     else //if (val[0] == xatom->getAtom(XAtom::net_wm_window_type_normal))
997       window_type = Type_Normal;
998     return;
999   }
1000
1001   /*
1002    * the window type hint was not set, which means we either classify ourself
1003    * as a normal window or a dialog, depending on if we are a transient.
1004    */
1005   if (isTransient())
1006     window_type = Type_Dialog;
1007
1008   window_type = Type_Normal;
1009 }
1010
1011
1012 void BlackboxWindow::getWMName(void) {
1013   if (xatom->getValue(client.window, XAtom::net_wm_name,
1014                       XAtom::utf8, client.title) &&
1015       !client.title.empty()) {
1016     xatom->eraseValue(client.window, XAtom::net_wm_visible_name);
1017     return;
1018   }
1019   //fall through to using WM_NAME
1020   if (xatom->getValue(client.window, XAtom::wm_name, XAtom::ansi, client.title)
1021       && !client.title.empty()) {
1022     xatom->eraseValue(client.window, XAtom::net_wm_visible_name);
1023     return;
1024   }
1025   // fall back to an internal default
1026   client.title = i18n(WindowSet, WindowUnnamed, "Unnamed");
1027   xatom->setValue(client.window, XAtom::net_wm_visible_name, XAtom::utf8,
1028                   client.title);
1029 }
1030
1031
1032 void BlackboxWindow::getWMIconName(void) {
1033   if (xatom->getValue(client.window, XAtom::net_wm_icon_name,
1034                       XAtom::utf8, client.icon_title) && 
1035       !client.icon_title.empty()) {
1036     xatom->eraseValue(client.window, XAtom::net_wm_visible_icon_name);
1037     return;
1038   }
1039   //fall through to using WM_ICON_NAME
1040   if (xatom->getValue(client.window, XAtom::wm_icon_name, XAtom::ansi,
1041                       client.icon_title) && 
1042       !client.icon_title.empty()) {
1043     xatom->eraseValue(client.window, XAtom::net_wm_visible_icon_name);
1044     return;
1045   }
1046   // fall back to using the main name
1047   client.icon_title = client.title;
1048   xatom->setValue(client.window, XAtom::net_wm_visible_icon_name, XAtom::utf8,
1049                   client.icon_title);
1050 }
1051
1052
1053 /*
1054  * Retrieve which WM Protocols are supported by the client window.
1055  * If the WM_DELETE_WINDOW protocol is supported, add the close button to the
1056  * window's decorations and allow the close behavior.
1057  * If the WM_TAKE_FOCUS protocol is supported, save a value that indicates
1058  * this.
1059  */
1060 void BlackboxWindow::getWMProtocols(void) {
1061   Atom *proto;
1062   int num_return = 0;
1063
1064   if (XGetWMProtocols(blackbox->getXDisplay(), client.window,
1065                       &proto, &num_return)) {
1066     for (int i = 0; i < num_return; ++i) {
1067       if (proto[i] == xatom->getAtom(XAtom::wm_delete_window)) {
1068         decorations |= Decor_Close;
1069         functions |= Func_Close;
1070       } else if (proto[i] == xatom->getAtom(XAtom::wm_take_focus))
1071         flags.send_focus_message = True;
1072       else if (proto[i] == xatom->getAtom(XAtom::blackbox_structure_messages))
1073         screen->addNetizen(new Netizen(screen, client.window));
1074     }
1075
1076     XFree(proto);
1077   }
1078 }
1079
1080
1081 /*
1082  * Gets the value of the WM_HINTS property.
1083  * If the property is not set, then use a set of default values.
1084  */
1085 void BlackboxWindow::getWMHints(void) {
1086   focus_mode = F_Passive;
1087
1088   // remove from current window group
1089   if (client.window_group) {
1090     BWindowGroup *group = blackbox->searchGroup(client.window_group);
1091     if (group) group->removeWindow(this);
1092   }
1093   client.window_group = None;
1094
1095   XWMHints *wmhint = XGetWMHints(blackbox->getXDisplay(), client.window);
1096   if (! wmhint) {
1097     return;
1098   }
1099
1100   if (wmhint->flags & InputHint) {
1101     if (wmhint->input == True) {
1102       if (flags.send_focus_message)
1103         focus_mode = F_LocallyActive;
1104     } else {
1105       if (flags.send_focus_message)
1106         focus_mode = F_GloballyActive;
1107       else
1108         focus_mode = F_NoInput;
1109     }
1110   }
1111
1112   if (wmhint->flags & StateHint)
1113     current_state = wmhint->initial_state;
1114
1115   if (wmhint->flags & WindowGroupHint) {
1116     client.window_group = wmhint->window_group;
1117
1118     // add window to the appropriate group
1119     BWindowGroup *group = blackbox->searchGroup(client.window_group);
1120     if (! group) { // no group found, create it!
1121       new BWindowGroup(blackbox, client.window_group);
1122       group = blackbox->searchGroup(client.window_group);
1123     }
1124     if (group)
1125       group->addWindow(this);
1126   }
1127
1128   XFree(wmhint);
1129 }
1130
1131
1132 /*
1133  * Gets the value of the WM_NORMAL_HINTS property.
1134  * If the property is not set, then use a set of default values.
1135  */
1136 void BlackboxWindow::getWMNormalHints(void) {
1137   long icccm_mask;
1138   XSizeHints sizehint;
1139
1140   client.min_width = client.min_height =
1141     client.width_inc = client.height_inc = 1;
1142   client.base_width = client.base_height = 0;
1143   client.win_gravity = NorthWestGravity;
1144 #if 0
1145   client.min_aspect_x = client.min_aspect_y =
1146     client.max_aspect_x = client.max_aspect_y = 1;
1147 #endif
1148
1149   /*
1150     use the full screen, not the strut modified size. otherwise when the
1151     availableArea changes max_width/height will be incorrect and lead to odd
1152     rendering bugs.
1153   */
1154   const Rect& screen_area = screen->getRect();
1155   client.max_width = screen_area.width();
1156   client.max_height = screen_area.height();
1157
1158   if (! XGetWMNormalHints(blackbox->getXDisplay(), client.window,
1159                           &sizehint, &icccm_mask))
1160     return;
1161
1162   client.normal_hint_flags = sizehint.flags;
1163
1164   if (sizehint.flags & PMinSize) {
1165     if (sizehint.min_width >= 0)
1166       client.min_width = sizehint.min_width;
1167     if (sizehint.min_height >= 0)
1168       client.min_height = sizehint.min_height;
1169   }
1170
1171   if (sizehint.flags & PMaxSize) {
1172     if (sizehint.max_width > static_cast<signed>(client.min_width))
1173       client.max_width = sizehint.max_width;
1174     else
1175       client.max_width = client.min_width;
1176
1177     if (sizehint.max_height > static_cast<signed>(client.min_height))
1178       client.max_height = sizehint.max_height;
1179     else
1180       client.max_height = client.min_height;
1181   }
1182
1183   if (sizehint.flags & PResizeInc) {
1184     client.width_inc = sizehint.width_inc;
1185     client.height_inc = sizehint.height_inc;
1186   }
1187
1188 #if 0 // we do not support this at the moment
1189   if (sizehint.flags & PAspect) {
1190     client.min_aspect_x = sizehint.min_aspect.x;
1191     client.min_aspect_y = sizehint.min_aspect.y;
1192     client.max_aspect_x = sizehint.max_aspect.x;
1193     client.max_aspect_y = sizehint.max_aspect.y;
1194   }
1195 #endif
1196
1197   if (sizehint.flags & PBaseSize) {
1198     client.base_width = sizehint.base_width;
1199     client.base_height = sizehint.base_height;
1200   }
1201
1202   if (sizehint.flags & PWinGravity)
1203     client.win_gravity = sizehint.win_gravity;
1204 }
1205
1206
1207 /*
1208  * Gets the NETWM hints for the class' contained window.
1209  */
1210 void BlackboxWindow::getNetWMHints(void) {
1211   unsigned long workspace;
1212
1213   if (xatom->getValue(client.window, XAtom::net_wm_desktop, XAtom::cardinal,
1214                       workspace)) {
1215     if (workspace == 0xffffffff)
1216       flags.stuck = True;
1217     else
1218       blackbox_attrib.workspace = workspace;
1219   }
1220
1221   unsigned long *state;
1222   unsigned long num = (unsigned) -1;
1223   if (xatom->getValue(client.window, XAtom::net_wm_state, XAtom::atom,
1224                       num, &state)) {
1225     bool vert = False,
1226          horz = False;
1227     for (unsigned long i = 0; i < num; ++i) {
1228       if (state[i] == xatom->getAtom(XAtom::net_wm_state_modal))
1229         flags.modal = True;
1230       else if (state[i] == xatom->getAtom(XAtom::net_wm_state_shaded))
1231         flags.shaded = True;
1232       else if (state[i] == xatom->getAtom(XAtom::net_wm_state_skip_taskbar))
1233         flags.skip_taskbar = True;
1234       else if (state[i] == xatom->getAtom(XAtom::net_wm_state_skip_pager))
1235         flags.skip_pager = True;
1236       else if (state[i] == xatom->getAtom(XAtom::net_wm_state_fullscreen))
1237         flags.fullscreen = True;
1238       else if (state[i] == xatom->getAtom(XAtom::net_wm_state_hidden))
1239         setState(IconicState);
1240       else if (state[i] == xatom->getAtom(XAtom::net_wm_state_maximized_vert))
1241         vert = True;
1242       else if (state[i] == xatom->getAtom(XAtom::net_wm_state_maximized_horz))
1243         horz = True;
1244     }
1245     if (vert && horz)
1246       flags.maximized = 1;
1247     else if (vert)
1248       flags.maximized = 2;
1249     else if (horz)
1250       flags.maximized = 3;
1251
1252     delete [] state;
1253   }
1254 }
1255
1256
1257 /*
1258  * Gets the MWM hints for the class' contained window.
1259  * This is used while initializing the window to its first state, and not
1260  * thereafter.
1261  * Returns: true if the MWM hints are successfully retreived and applied;
1262  * false if they are not.
1263  */
1264 void BlackboxWindow::getMWMHints(void) {
1265   unsigned long num;
1266   MwmHints *mwm_hint;
1267
1268   num = PropMwmHintsElements;
1269   if (! xatom->getValue(client.window, XAtom::motif_wm_hints,
1270                         XAtom::motif_wm_hints, num,
1271                         (unsigned long **)&mwm_hint))
1272     return;
1273   if (num < PropMwmHintsElements) {
1274     delete [] mwm_hint;
1275     return;
1276   }
1277
1278   if (mwm_hint->flags & MwmHintsDecorations) {
1279     if (mwm_hint->decorations & MwmDecorAll) {
1280       decorations = Decor_Titlebar | Decor_Handle | Decor_Border |
1281                     Decor_Iconify | Decor_Maximize | Decor_Close;
1282     } else {
1283       decorations = 0;
1284
1285       if (mwm_hint->decorations & MwmDecorBorder)
1286         decorations |= Decor_Border;
1287       if (mwm_hint->decorations & MwmDecorHandle)
1288         decorations |= Decor_Handle;
1289       if (mwm_hint->decorations & MwmDecorTitle)
1290         decorations |= Decor_Titlebar;
1291       if (mwm_hint->decorations & MwmDecorIconify)
1292         decorations |= Decor_Iconify;
1293       if (mwm_hint->decorations & MwmDecorMaximize)
1294         decorations |= Decor_Maximize;
1295     }
1296   }
1297
1298   if (mwm_hint->flags & MwmHintsFunctions) {
1299     if (mwm_hint->functions & MwmFuncAll) {
1300       functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize |
1301                   Func_Close;
1302     } else {
1303       functions = 0;
1304
1305       if (mwm_hint->functions & MwmFuncResize)
1306         functions |= Func_Resize;
1307       if (mwm_hint->functions & MwmFuncMove)
1308         functions |= Func_Move;
1309       if (mwm_hint->functions & MwmFuncIconify)
1310         functions |= Func_Iconify;
1311       if (mwm_hint->functions & MwmFuncMaximize)
1312         functions |= Func_Maximize;
1313       if (mwm_hint->functions & MwmFuncClose)
1314         functions |= Func_Close;
1315     }
1316   }
1317   delete [] mwm_hint;
1318 }
1319
1320
1321 /*
1322  * Gets the blackbox hints from the class' contained window.
1323  * This is used while initializing the window to its first state, and not
1324  * thereafter.
1325  * Returns: true if the hints are successfully retreived and applied; false if
1326  * they are not.
1327  */
1328 bool BlackboxWindow::getBlackboxHints(void) {
1329   unsigned long num;
1330   BlackboxHints *blackbox_hint;
1331
1332   num = PropBlackboxHintsElements;
1333   if (! xatom->getValue(client.window, XAtom::blackbox_hints,
1334                         XAtom::blackbox_hints, num,
1335                         (unsigned long **)&blackbox_hint))
1336     return False;
1337   if (num < PropBlackboxHintsElements) {
1338     delete [] blackbox_hint;
1339     return False;
1340   }
1341
1342   if (blackbox_hint->flags & AttribShaded)
1343     flags.shaded = (blackbox_hint->attrib & AttribShaded);
1344
1345   if ((blackbox_hint->flags & AttribMaxHoriz) &&
1346       (blackbox_hint->flags & AttribMaxVert))
1347     flags.maximized = (blackbox_hint->attrib &
1348                        (AttribMaxHoriz | AttribMaxVert)) ? 1 : 0;
1349   else if (blackbox_hint->flags & AttribMaxVert)
1350     flags.maximized = (blackbox_hint->attrib & AttribMaxVert) ? 2 : 0;
1351   else if (blackbox_hint->flags & AttribMaxHoriz)
1352     flags.maximized = (blackbox_hint->attrib & AttribMaxHoriz) ? 3 : 0;
1353
1354   if (blackbox_hint->flags & AttribOmnipresent)
1355     flags.stuck = (blackbox_hint->attrib & AttribOmnipresent);
1356
1357   if (blackbox_hint->flags & AttribWorkspace)
1358     blackbox_attrib.workspace = blackbox_hint->workspace;
1359
1360   // if (blackbox_hint->flags & AttribStack)
1361   //   don't yet have always on top/bottom for blackbox yet... working
1362   //   on that
1363
1364   if (blackbox_hint->flags & AttribDecoration) {
1365     switch (blackbox_hint->decoration) {
1366     case DecorNone:
1367       decorations = 0;
1368       break;
1369
1370     case DecorTiny:
1371       decorations |= Decor_Titlebar | Decor_Iconify;
1372       decorations &= ~(Decor_Border | Decor_Handle | Decor_Maximize);
1373       functions &= ~(Func_Resize | Func_Maximize);
1374
1375       break;
1376
1377     case DecorTool:
1378       decorations |= Decor_Titlebar;
1379       decorations &= ~(Decor_Iconify | Decor_Border | Decor_Handle);
1380       functions &= ~(Func_Resize | Func_Maximize | Func_Iconify);
1381
1382       break;
1383
1384     case DecorNormal:
1385     default:
1386       decorations |= Decor_Titlebar | Decor_Border | Decor_Handle |
1387                      Decor_Iconify | Decor_Maximize;
1388       break;
1389     }
1390
1391     reconfigure();
1392   }
1393   
1394   delete [] blackbox_hint;
1395
1396   return True;
1397 }
1398
1399
1400 void BlackboxWindow::getTransientInfo(void) {
1401   if (client.transient_for &&
1402       client.transient_for != (BlackboxWindow *) ~0ul) {
1403     // reset transient_for in preparation of looking for a new owner
1404     client.transient_for->client.transientList.remove(this);
1405   }
1406
1407   // we have no transient_for until we find a new one
1408   client.transient_for = 0;
1409
1410   Window trans_for;
1411   if (! XGetTransientForHint(blackbox->getXDisplay(), client.window,
1412                              &trans_for)) {
1413     // transient_for hint not set
1414     return;
1415   }
1416
1417   if (trans_for == client.window) {
1418     // wierd client... treat this window as a normal window
1419     return;
1420   }
1421
1422   if (trans_for == None || trans_for == screen->getRootWindow()) {
1423     // this is an undocumented interpretation of the ICCCM. a transient
1424     // associated with None/Root/itself is assumed to be a modal root
1425     // transient.  we don't support the concept of a global transient,
1426     // so we just associate this transient with nothing, and perhaps
1427     // we will add support later for global modality.
1428     client.transient_for = (BlackboxWindow *) ~0ul;
1429     flags.modal = True;
1430     return;
1431   }
1432
1433   client.transient_for = blackbox->searchWindow(trans_for);
1434   if (! client.transient_for &&
1435       client.window_group && trans_for == client.window_group) {
1436     // no direct transient_for, perhaps this is a group transient?
1437     BWindowGroup *group = blackbox->searchGroup(client.window_group);
1438     if (group) client.transient_for = group->find(screen);
1439   }
1440
1441   if (! client.transient_for || client.transient_for == this) {
1442     // no transient_for found, or we have a wierd client that wants to be
1443     // a transient for itself, so we treat this window as a normal window
1444     client.transient_for = (BlackboxWindow*) 0;
1445     return;
1446   }
1447
1448   // register ourselves with our new transient_for
1449   client.transient_for->client.transientList.push_back(this);
1450   flags.stuck = client.transient_for->flags.stuck;
1451 }
1452
1453
1454 BlackboxWindow *BlackboxWindow::getTransientFor(void) const {
1455   if (client.transient_for &&
1456       client.transient_for != (BlackboxWindow*) ~0ul)
1457     return client.transient_for;
1458   return 0;
1459 }
1460
1461
1462 /*
1463  * This function is responsible for updating both the client and the frame
1464  * rectangles.
1465  * According to the ICCCM a client message is not sent for a resize, only a
1466  * move.
1467  */
1468 void BlackboxWindow::configure(int dx, int dy,
1469                                unsigned int dw, unsigned int dh) {
1470   bool send_event = ((frame.rect.x() != dx || frame.rect.y() != dy) &&
1471                      ! flags.moving);
1472
1473   if (dw != frame.rect.width() || dh != frame.rect.height()) {
1474     frame.rect.setRect(dx, dy, dw, dh);
1475     frame.inside_w = frame.rect.width() - (frame.border_w * 2);
1476     frame.inside_h = frame.rect.height() - (frame.border_w * 2);
1477
1478     if (frame.rect.right() <= 0 || frame.rect.bottom() <= 0)
1479       frame.rect.setPos(0, 0);
1480
1481     client.rect.setCoords(frame.rect.left() + frame.margin.left,
1482                           frame.rect.top() + frame.margin.top,
1483                           frame.rect.right() - frame.margin.right,
1484                           frame.rect.bottom() - frame.margin.bottom);
1485
1486 #ifdef    SHAPE
1487     if (blackbox->hasShapeExtensions() && flags.shaped) {
1488       configureShape();
1489     }
1490 #endif // SHAPE
1491
1492     positionWindows();
1493     decorate();
1494     redrawWindowFrame();
1495   } else {
1496     frame.rect.setPos(dx, dy);
1497
1498     XMoveWindow(blackbox->getXDisplay(), frame.window,
1499                 frame.rect.x(), frame.rect.y());
1500     /*
1501       we may have been called just after an opaque window move, so even though
1502       the old coords match the new ones no ConfigureNotify has been sent yet.
1503       There are likely other times when this will be relevant as well.
1504     */
1505     if (! flags.moving) send_event = True;
1506   }
1507
1508   if (send_event) {
1509     // if moving, the update and event will occur when the move finishes
1510     client.rect.setPos(frame.rect.left() + frame.margin.left,
1511                        frame.rect.top() + frame.margin.top);
1512
1513     XEvent event;
1514     event.type = ConfigureNotify;
1515
1516     event.xconfigure.display = blackbox->getXDisplay();
1517     event.xconfigure.event = client.window;
1518     event.xconfigure.window = client.window;
1519     event.xconfigure.x = client.rect.x();
1520     event.xconfigure.y = client.rect.y();
1521     event.xconfigure.width = client.rect.width();
1522     event.xconfigure.height = client.rect.height();
1523     event.xconfigure.border_width = client.old_bw;
1524     event.xconfigure.above = frame.window;
1525     event.xconfigure.override_redirect = False;
1526
1527     XSendEvent(blackbox->getXDisplay(), client.window, False,
1528                StructureNotifyMask, &event);
1529     screen->updateNetizenConfigNotify(&event);
1530     XFlush(blackbox->getXDisplay());
1531   }
1532 }
1533
1534
1535 #ifdef SHAPE
1536 void BlackboxWindow::configureShape(void) {
1537   XShapeCombineShape(blackbox->getXDisplay(), frame.window, ShapeBounding,
1538                      frame.margin.left - frame.border_w,
1539                      frame.margin.top - frame.border_w,
1540                      client.window, ShapeBounding, ShapeSet);
1541
1542   int num = 0;
1543   XRectangle xrect[2];
1544
1545   if (decorations & Decor_Titlebar) {
1546     xrect[0].x = xrect[0].y = -frame.border_w;
1547     xrect[0].width = frame.rect.width();
1548     xrect[0].height = frame.title_h + (frame.border_w * 2);
1549     ++num;
1550   }
1551
1552   if (decorations & Decor_Handle) {
1553     xrect[1].x = -frame.border_w;
1554     xrect[1].y = frame.rect.height() - frame.margin.bottom +
1555                  frame.mwm_border_w - frame.border_w;
1556     xrect[1].width = frame.rect.width();
1557     xrect[1].height = frame.handle_h + (frame.border_w * 2);
1558     ++num;
1559   }
1560
1561   XShapeCombineRectangles(blackbox->getXDisplay(), frame.window,
1562                           ShapeBounding, 0, 0, xrect, num,
1563                           ShapeUnion, Unsorted);
1564 }
1565 #endif // SHAPE
1566
1567
1568 bool BlackboxWindow::setInputFocus(void) {
1569   if (flags.focused) return True;
1570
1571   assert(! flags.iconic &&
1572          (flags.stuck ||  // window must be on the current workspace or sticky
1573           blackbox_attrib.workspace == screen->getCurrentWorkspaceID()));
1574
1575   /*
1576      We only do this check for normal windows and dialogs because other windows
1577      do this on purpose, such as kde's kicker, and we don't want to go moving
1578      it.
1579   */
1580   if (window_type == Type_Normal || window_type == Type_Dialog)
1581     if (! frame.rect.intersects(screen->getRect())) {
1582       // client is outside the screen, move it to the center
1583       configure((screen->getWidth() - frame.rect.width()) / 2,
1584                 (screen->getHeight() - frame.rect.height()) / 2,
1585                 frame.rect.width(), frame.rect.height());
1586     }
1587
1588   if (client.transientList.size() > 0) {
1589     // transfer focus to any modal transients
1590     BlackboxWindowList::iterator it, end = client.transientList.end();
1591     for (it = client.transientList.begin(); it != end; ++it) {
1592       if ((*it)->flags.modal) return (*it)->setInputFocus();
1593     }
1594   }
1595
1596   bool ret = True;
1597   if (focus_mode == F_LocallyActive || focus_mode == F_Passive) {
1598     XSetInputFocus(blackbox->getXDisplay(), client.window,
1599                    RevertToPointerRoot, CurrentTime);
1600   } else {
1601     /* we could set the focus to none, since the window doesn't accept focus,
1602      * but we shouldn't set focus to nothing since this would surely make
1603      * someone angry
1604      */
1605     ret = False;
1606   }
1607
1608   if (flags.send_focus_message) {
1609     XEvent ce;
1610     ce.xclient.type = ClientMessage;
1611     ce.xclient.message_type = xatom->getAtom(XAtom::wm_protocols);
1612     ce.xclient.display = blackbox->getXDisplay();
1613     ce.xclient.window = client.window;
1614     ce.xclient.format = 32;
1615     ce.xclient.data.l[0] = xatom->getAtom(XAtom::wm_take_focus);
1616     ce.xclient.data.l[1] = blackbox->getLastTime();
1617     ce.xclient.data.l[2] = 0l;
1618     ce.xclient.data.l[3] = 0l;
1619     ce.xclient.data.l[4] = 0l;
1620     XSendEvent(blackbox->getXDisplay(), client.window, False,
1621                NoEventMask, &ce);
1622     XFlush(blackbox->getXDisplay());
1623   }
1624
1625   return ret;
1626 }
1627
1628
1629 void BlackboxWindow::iconify(void) {
1630   if (flags.iconic) return;
1631
1632   // We don't need to worry about resizing because resizing always grabs the X
1633   // server. This should only ever happen if using opaque moving.
1634   if (flags.moving)
1635     endMove();
1636     
1637   if (windowmenu) windowmenu->hide();
1638
1639   /*
1640    * we don't want this XUnmapWindow call to generate an UnmapNotify event, so
1641    * we need to clear the event mask on client.window for a split second.
1642    * HOWEVER, since X11 is asynchronous, the window could be destroyed in that
1643    * split second, leaving us with a ghost window... so, we need to do this
1644    * while the X server is grabbed
1645    */
1646   unsigned long event_mask = PropertyChangeMask | FocusChangeMask |
1647                              StructureNotifyMask;
1648   XGrabServer(blackbox->getXDisplay());
1649   XSelectInput(blackbox->getXDisplay(), client.window,
1650                event_mask & ~StructureNotifyMask);
1651   XUnmapWindow(blackbox->getXDisplay(), client.window);
1652   XSelectInput(blackbox->getXDisplay(), client.window, event_mask);
1653   XUngrabServer(blackbox->getXDisplay());
1654
1655   XUnmapWindow(blackbox->getXDisplay(), frame.window);
1656   flags.visible = False;
1657   flags.iconic = True;
1658
1659   setState(IconicState);
1660
1661   screen->getWorkspace(blackbox_attrib.workspace)->removeWindow(this);
1662
1663   if (isTransient()) {
1664     if (client.transient_for != (BlackboxWindow *) ~0ul &&
1665         ! client.transient_for->flags.iconic) {
1666       // iconify our transient_for
1667       client.transient_for->iconify();
1668     }
1669   }
1670
1671   screen->addIcon(this);
1672
1673   if (client.transientList.size() > 0) {
1674     // iconify all transients
1675     BlackboxWindowList::iterator it, end = client.transientList.end();
1676     for (it = client.transientList.begin(); it != end; ++it) {
1677       if (! (*it)->flags.iconic) (*it)->iconify();
1678     }
1679   }
1680   screen->updateStackingList();
1681 }
1682
1683
1684 void BlackboxWindow::show(void) {
1685   flags.visible = True;
1686   flags.iconic = False;
1687
1688   current_state = (flags.shaded) ? IconicState : NormalState;
1689   setState(current_state);
1690
1691   XMapWindow(blackbox->getXDisplay(), client.window);
1692   XMapSubwindows(blackbox->getXDisplay(), frame.window);
1693   XMapWindow(blackbox->getXDisplay(), frame.window);
1694
1695 #if 0
1696   int real_x, real_y;
1697   Window child;
1698   XTranslateCoordinates(blackbox->getXDisplay(), client.window,
1699                         screen->getRootWindow(),
1700                         0, 0, &real_x, &real_y, &child);
1701   fprintf(stderr, "%s -- assumed: (%d, %d), real: (%d, %d)\n", getTitle(),
1702           client.rect.left(), client.rect.top(), real_x, real_y);
1703   assert(client.rect.left() == real_x && client.rect.top() == real_y);
1704 #endif
1705 }
1706
1707
1708 void BlackboxWindow::deiconify(bool reassoc, bool raise) {
1709   if (flags.iconic || reassoc)
1710     screen->reassociateWindow(this, BSENTINEL, False);
1711   else if (blackbox_attrib.workspace != screen->getCurrentWorkspaceID())
1712     return;
1713
1714   show();
1715
1716   // reassociate and deiconify all transients
1717   if (reassoc && client.transientList.size() > 0) {
1718     BlackboxWindowList::iterator it, end = client.transientList.end();
1719     for (it = client.transientList.begin(); it != end; ++it) {
1720       (*it)->deiconify(True, False);
1721     }
1722   }
1723
1724   if (raise)
1725     screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
1726 }
1727
1728
1729 void BlackboxWindow::close(void) {
1730   XEvent ce;
1731   ce.xclient.type = ClientMessage;
1732   ce.xclient.message_type =  xatom->getAtom(XAtom::wm_protocols);
1733   ce.xclient.display = blackbox->getXDisplay();
1734   ce.xclient.window = client.window;
1735   ce.xclient.format = 32;
1736   ce.xclient.data.l[0] = xatom->getAtom(XAtom::wm_delete_window);
1737   ce.xclient.data.l[1] = CurrentTime;
1738   ce.xclient.data.l[2] = 0l;
1739   ce.xclient.data.l[3] = 0l;
1740   ce.xclient.data.l[4] = 0l;
1741   XSendEvent(blackbox->getXDisplay(), client.window, False, NoEventMask, &ce);
1742   XFlush(blackbox->getXDisplay());
1743 }
1744
1745
1746 void BlackboxWindow::withdraw(void) {
1747   // We don't need to worry about resizing because resizing always grabs the X
1748   // server. This should only ever happen if using opaque moving.
1749   if (flags.moving)
1750     endMove();
1751     
1752   flags.visible = False;
1753   flags.iconic = False;
1754
1755   setState(current_state);
1756
1757   XUnmapWindow(blackbox->getXDisplay(), frame.window);
1758
1759   XGrabServer(blackbox->getXDisplay());
1760
1761   unsigned long event_mask = PropertyChangeMask | FocusChangeMask |
1762                              StructureNotifyMask;
1763   XSelectInput(blackbox->getXDisplay(), client.window,
1764                event_mask & ~StructureNotifyMask);
1765   XUnmapWindow(blackbox->getXDisplay(), client.window);
1766   XSelectInput(blackbox->getXDisplay(), client.window, event_mask);
1767
1768   XUngrabServer(blackbox->getXDisplay());
1769
1770   if (windowmenu) windowmenu->hide();
1771 }
1772
1773
1774 void BlackboxWindow::maximize(unsigned int button) {
1775   // We don't need to worry about resizing because resizing always grabs the X
1776   // server. This should only ever happen if using opaque moving.
1777   if (flags.moving)
1778     endMove();
1779
1780   // handle case where menu is open then the max button is used instead
1781   if (windowmenu && windowmenu->isVisible()) windowmenu->hide();
1782
1783   if (flags.maximized) {
1784     flags.maximized = 0;
1785
1786     blackbox_attrib.flags &= ! (AttribMaxHoriz | AttribMaxVert);
1787     blackbox_attrib.attrib &= ! (AttribMaxHoriz | AttribMaxVert);
1788
1789     /*
1790       when a resize finishes, maximize(0) is called to clear any maximization
1791       flags currently set.  Otherwise it still thinks it is maximized.
1792       so we do not need to call configure() because resizing will handle it
1793     */
1794     if (! flags.resizing)
1795       configure(blackbox_attrib.premax_x, blackbox_attrib.premax_y,
1796                 blackbox_attrib.premax_w, blackbox_attrib.premax_h);
1797
1798     blackbox_attrib.premax_x = blackbox_attrib.premax_y = 0;
1799     blackbox_attrib.premax_w = blackbox_attrib.premax_h = 0;
1800
1801     redrawAllButtons(); // in case it is not called in configure()
1802     setState(current_state);
1803     return;
1804   }
1805
1806   blackbox_attrib.premax_x = frame.rect.x();
1807   blackbox_attrib.premax_y = frame.rect.y();
1808   blackbox_attrib.premax_w = frame.rect.width();
1809   // use client.rect so that clients can be restored even if shaded
1810   blackbox_attrib.premax_h =
1811     client.rect.height() + frame.margin.top + frame.margin.bottom;
1812
1813 #ifdef    XINERAMA
1814   if (screen->isXineramaActive() && blackbox->doXineramaMaximizing()) {
1815     // find the area to use
1816     RectList availableAreas = screen->allAvailableAreas();
1817     RectList::iterator it, end = availableAreas.end();
1818
1819     for (it = availableAreas.begin(); it != end; ++it)
1820       if (it->intersects(frame.rect)) break;
1821     if (it == end) // the window isn't inside an area
1822       it = availableAreas.begin(); // so just default to the first one
1823
1824     frame.changing = *it;
1825   } else
1826 #endif // XINERAMA
1827   frame.changing = screen->availableArea();
1828
1829   switch(button) {
1830   case 1:
1831     blackbox_attrib.flags |= AttribMaxHoriz | AttribMaxVert;
1832     blackbox_attrib.attrib |= AttribMaxHoriz | AttribMaxVert;
1833     break;
1834
1835   case 2:
1836     blackbox_attrib.flags |= AttribMaxVert;
1837     blackbox_attrib.attrib |= AttribMaxVert;
1838
1839     frame.changing.setX(frame.rect.x());
1840     frame.changing.setWidth(frame.rect.width());
1841     break;
1842
1843   case 3:
1844     blackbox_attrib.flags |= AttribMaxHoriz;
1845     blackbox_attrib.attrib |= AttribMaxHoriz;
1846
1847     frame.changing.setY(frame.rect.y());
1848     frame.changing.setHeight(frame.rect.height());
1849     break;
1850   }
1851
1852   constrain(TopLeft);
1853
1854   if (flags.shaded) {
1855     blackbox_attrib.flags ^= AttribShaded;
1856     blackbox_attrib.attrib ^= AttribShaded;
1857     flags.shaded = False;
1858   }
1859
1860   flags.maximized = button;
1861
1862   configure(frame.changing.x(), frame.changing.y(),
1863             frame.changing.width(), frame.changing.height());
1864   if (flags.focused)
1865     screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
1866   redrawAllButtons(); // in case it is not called in configure()
1867   setState(current_state);
1868 }
1869
1870
1871 // re-maximizes the window to take into account availableArea changes
1872 void BlackboxWindow::remaximize(void) {
1873   if (flags.shaded) {
1874     // we only update the window's attributes otherwise we lose the shade bit
1875     switch(flags.maximized) {
1876     case 1:
1877       blackbox_attrib.flags |= AttribMaxHoriz | AttribMaxVert;
1878       blackbox_attrib.attrib |= AttribMaxHoriz | AttribMaxVert;
1879       break;
1880
1881     case 2:
1882       blackbox_attrib.flags |= AttribMaxVert;
1883       blackbox_attrib.attrib |= AttribMaxVert;
1884       break;
1885
1886     case 3:
1887       blackbox_attrib.flags |= AttribMaxHoriz;
1888       blackbox_attrib.attrib |= AttribMaxHoriz;
1889       break;
1890     }
1891     return;
1892   }
1893
1894   // save the original dimensions because maximize will wipe them out
1895   int premax_x = blackbox_attrib.premax_x,
1896     premax_y = blackbox_attrib.premax_y,
1897     premax_w = blackbox_attrib.premax_w,
1898     premax_h = blackbox_attrib.premax_h;
1899
1900   unsigned int button = flags.maximized;
1901   flags.maximized = 0; // trick maximize() into working
1902   maximize(button);
1903
1904   // restore saved values
1905   blackbox_attrib.premax_x = premax_x;
1906   blackbox_attrib.premax_y = premax_y;
1907   blackbox_attrib.premax_w = premax_w;
1908   blackbox_attrib.premax_h = premax_h;
1909 }
1910
1911
1912 void BlackboxWindow::setWorkspace(unsigned int n) {
1913   blackbox_attrib.flags |= AttribWorkspace;
1914   blackbox_attrib.workspace = n;
1915   if (n == BSENTINEL) { // iconified window
1916     /*
1917        we set the workspace to 'all workspaces' so that taskbars will show the
1918        window. otherwise, it made uniconifying a window imposible without the
1919        blackbox workspace menu
1920     */
1921     n = 0xffffffff;
1922   }
1923   xatom->setValue(client.window, XAtom::net_wm_desktop, XAtom::cardinal, n);
1924 }
1925
1926
1927 void BlackboxWindow::shade(void) {
1928   if (flags.shaded) {
1929     XResizeWindow(blackbox->getXDisplay(), frame.window,
1930                   frame.inside_w, frame.inside_h);
1931     flags.shaded = False;
1932     blackbox_attrib.flags ^= AttribShaded;
1933     blackbox_attrib.attrib ^= AttribShaded;
1934
1935     setState(NormalState);
1936
1937     // set the frame rect to the normal size
1938     frame.rect.setHeight(client.rect.height() + frame.margin.top +
1939                          frame.margin.bottom);
1940   } else {
1941     if (! (decorations & Decor_Titlebar))
1942       return; // can't shade it without a titlebar!
1943
1944     XResizeWindow(blackbox->getXDisplay(), frame.window,
1945                   frame.inside_w, frame.title_h);
1946     flags.shaded = True;
1947     blackbox_attrib.flags |= AttribShaded;
1948     blackbox_attrib.attrib |= AttribShaded;
1949
1950     setState(IconicState);
1951
1952     // set the frame rect to the shaded size
1953     frame.rect.setHeight(frame.title_h + (frame.border_w * 2));
1954   }
1955 }
1956
1957
1958 /*
1959  * (Un)Sticks a window and its relatives.
1960  */
1961 void BlackboxWindow::stick(void) {
1962   if (flags.stuck) {
1963     blackbox_attrib.flags ^= AttribOmnipresent;
1964     blackbox_attrib.attrib ^= AttribOmnipresent;
1965
1966     for (unsigned int i = 0; i < screen->getNumberOfWorkspaces(); ++i)
1967       if (i != blackbox_attrib.workspace)
1968         screen->getWorkspace(i)->removeWindow(this, True);
1969
1970     flags.stuck = False;
1971
1972     if (! flags.iconic)
1973       screen->reassociateWindow(this, BSENTINEL, True);
1974     // temporary fix since sticky windows suck. set the hint to what we
1975     // actually hold in our data.
1976     xatom->setValue(client.window, XAtom::net_wm_desktop, XAtom::cardinal,
1977                     blackbox_attrib.workspace);
1978
1979     setState(current_state);
1980   } else {
1981     flags.stuck = True;
1982
1983     blackbox_attrib.flags |= AttribOmnipresent;
1984     blackbox_attrib.attrib |= AttribOmnipresent;
1985
1986     // temporary fix since sticky windows suck. set the hint to a different
1987     // value than that contained in the class' data.
1988     xatom->setValue(client.window, XAtom::net_wm_desktop, XAtom::cardinal,
1989                     0xffffffff);
1990     
1991     for (unsigned int i = 0; i < screen->getNumberOfWorkspaces(); ++i)
1992       if (i != blackbox_attrib.workspace)
1993         screen->getWorkspace(i)->addWindow(this, False, True);
1994
1995     setState(current_state);
1996   }
1997   // go up the chain
1998   if (isTransient() && client.transient_for != (BlackboxWindow *) ~0ul &&
1999       client.transient_for->isStuck() != flags.stuck)
2000     client.transient_for->stick();
2001   // go down the chain
2002   BlackboxWindowList::iterator it;
2003   const BlackboxWindowList::iterator end = client.transientList.end();
2004   for (it = client.transientList.begin(); it != end; ++it)
2005     if ((*it)->isStuck() != flags.stuck)
2006       (*it)->stick();
2007 }
2008
2009
2010 void BlackboxWindow::redrawWindowFrame(void) const {
2011   if (decorations & Decor_Titlebar) {
2012     if (flags.focused) {
2013       if (frame.ftitle)
2014         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2015                                    frame.title, frame.ftitle);
2016       else
2017         XSetWindowBackground(blackbox->getXDisplay(),
2018                              frame.title, frame.ftitle_pixel);
2019     } else {
2020       if (frame.utitle)
2021         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2022                                    frame.title, frame.utitle);
2023       else
2024         XSetWindowBackground(blackbox->getXDisplay(),
2025                              frame.title, frame.utitle_pixel);
2026     }
2027     XClearWindow(blackbox->getXDisplay(), frame.title);
2028
2029     redrawLabel();
2030     redrawAllButtons();
2031   }
2032
2033   if (decorations & Decor_Handle) {
2034     if (flags.focused) {
2035       if (frame.fhandle)
2036         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2037                                    frame.handle, frame.fhandle);
2038       else
2039         XSetWindowBackground(blackbox->getXDisplay(),
2040                              frame.handle, frame.fhandle_pixel);
2041
2042       if (frame.fgrip) {
2043         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2044                                    frame.left_grip, frame.fgrip);
2045         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2046                                    frame.right_grip, frame.fgrip);
2047       } else {
2048         XSetWindowBackground(blackbox->getXDisplay(),
2049                              frame.left_grip, frame.fgrip_pixel);
2050         XSetWindowBackground(blackbox->getXDisplay(),
2051                              frame.right_grip, frame.fgrip_pixel);
2052       }
2053     } else {
2054       if (frame.uhandle)
2055         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2056                                    frame.handle, frame.uhandle);
2057       else
2058         XSetWindowBackground(blackbox->getXDisplay(),
2059                              frame.handle, frame.uhandle_pixel);
2060
2061       if (frame.ugrip) {
2062         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2063                                    frame.left_grip, frame.ugrip);
2064         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2065                                    frame.right_grip, frame.ugrip);
2066       } else {
2067         XSetWindowBackground(blackbox->getXDisplay(),
2068                              frame.left_grip, frame.ugrip_pixel);
2069         XSetWindowBackground(blackbox->getXDisplay(),
2070                              frame.right_grip, frame.ugrip_pixel);
2071       }
2072     }
2073     XClearWindow(blackbox->getXDisplay(), frame.handle);
2074     XClearWindow(blackbox->getXDisplay(), frame.left_grip);
2075     XClearWindow(blackbox->getXDisplay(), frame.right_grip);
2076   }
2077
2078   if (decorations & Decor_Border) {
2079     if (flags.focused)
2080       XSetWindowBorder(blackbox->getXDisplay(),
2081                        frame.plate, frame.fborder_pixel);
2082     else
2083       XSetWindowBorder(blackbox->getXDisplay(),
2084                        frame.plate, frame.uborder_pixel);
2085   }
2086 }
2087
2088
2089 void BlackboxWindow::setFocusFlag(bool focus) {
2090   // only focus a window if it is visible
2091   if (focus && !flags.visible)
2092     return;
2093
2094   flags.focused = focus;
2095
2096   redrawWindowFrame();
2097
2098   if (screen->isSloppyFocus() && screen->doAutoRaise()) {
2099     if (isFocused()) timer->start();
2100     else timer->stop();
2101   }
2102
2103   if (flags.focused)
2104     blackbox->setFocusedWindow(this);
2105  
2106   if (! flags.iconic) {
2107     // iconic windows arent in a workspace menu!
2108     if (flags.stuck)
2109       screen->getCurrentWorkspace()->setFocused(this, isFocused());
2110     else
2111       screen->getWorkspace(blackbox_attrib.workspace)->
2112         setFocused(this, flags.focused);
2113   }
2114 }
2115
2116
2117 void BlackboxWindow::installColormap(bool install) {
2118   int i = 0, ncmap = 0;
2119   Colormap *cmaps = XListInstalledColormaps(blackbox->getXDisplay(),
2120                                             client.window, &ncmap);
2121   if (cmaps) {
2122     XWindowAttributes wattrib;
2123     if (XGetWindowAttributes(blackbox->getXDisplay(),
2124                              client.window, &wattrib)) {
2125       if (install) {
2126         // install the window's colormap
2127         for (i = 0; i < ncmap; i++) {
2128           if (*(cmaps + i) == wattrib.colormap)
2129             // this window is using an installed color map... do not install
2130             install = False;
2131         }
2132         // otherwise, install the window's colormap
2133         if (install)
2134           XInstallColormap(blackbox->getXDisplay(), wattrib.colormap);
2135       } else {
2136         // uninstall the window's colormap
2137         for (i = 0; i < ncmap; i++) {
2138           if (*(cmaps + i) == wattrib.colormap)
2139             // we found the colormap to uninstall
2140             XUninstallColormap(blackbox->getXDisplay(), wattrib.colormap);
2141         }
2142       }
2143     }
2144
2145     XFree(cmaps);
2146   }
2147 }
2148
2149
2150 void BlackboxWindow::setAllowedActions(void) {
2151   Atom actions[7];
2152   int num = 0;
2153   
2154   actions[num++] = xatom->getAtom(XAtom::net_wm_action_shade);
2155   actions[num++] = xatom->getAtom(XAtom::net_wm_action_change_desktop);
2156   actions[num++] = xatom->getAtom(XAtom::net_wm_action_close);
2157
2158   if (functions & Func_Move)
2159     actions[num++] = xatom->getAtom(XAtom::net_wm_action_move);
2160   if (functions & Func_Resize)
2161     actions[num++] = xatom->getAtom(XAtom::net_wm_action_resize);
2162   if (functions & Func_Maximize) {
2163     actions[num++] = xatom->getAtom(XAtom::net_wm_action_maximize_horz);
2164     actions[num++] = xatom->getAtom(XAtom::net_wm_action_maximize_vert);
2165   }
2166
2167   xatom->setValue(client.window, XAtom::net_wm_allowed_actions, XAtom::atom,
2168                   actions, num);
2169 }
2170
2171
2172 void BlackboxWindow::setState(unsigned long new_state) {
2173   current_state = new_state;
2174
2175   unsigned long state[2];
2176   state[0] = current_state;
2177   state[1] = None;
2178   xatom->setValue(client.window, XAtom::wm_state, XAtom::wm_state, state, 2);
2179  
2180   xatom->setValue(client.window, XAtom::blackbox_attributes,
2181                   XAtom::blackbox_attributes, (unsigned long *)&blackbox_attrib,
2182                   PropBlackboxAttributesElements);
2183
2184   Atom netstate[8];
2185   int num = 0;
2186   if (flags.modal)
2187     netstate[num++] = xatom->getAtom(XAtom::net_wm_state_modal);
2188   if (flags.shaded)
2189     netstate[num++] = xatom->getAtom(XAtom::net_wm_state_shaded);
2190   if (flags.iconic)
2191     netstate[num++] = xatom->getAtom(XAtom::net_wm_state_hidden);
2192   if (flags.skip_taskbar)
2193     netstate[num++] = xatom->getAtom(XAtom::net_wm_state_skip_taskbar);
2194   if (flags.skip_pager)
2195     netstate[num++] = xatom->getAtom(XAtom::net_wm_state_skip_pager);
2196   if (flags.fullscreen)
2197     netstate[num++] = xatom->getAtom(XAtom::net_wm_state_fullscreen);
2198   if (flags.maximized == 1 || flags.maximized == 2)
2199     netstate[num++] = xatom->getAtom(XAtom::net_wm_state_maximized_vert);
2200   if (flags.maximized == 1 || flags.maximized == 3)
2201     netstate[num++] = xatom->getAtom(XAtom::net_wm_state_maximized_horz);
2202   xatom->setValue(client.window, XAtom::net_wm_state, XAtom::atom,
2203                   netstate, num);
2204 }
2205
2206
2207 bool BlackboxWindow::getState(void) {
2208   bool ret = xatom->getValue(client.window, XAtom::wm_state, XAtom::wm_state,
2209                              current_state);
2210   if (! ret) current_state = 0;
2211   return ret;
2212 }
2213
2214
2215 void BlackboxWindow::restoreAttributes(void) {
2216   unsigned long num = PropBlackboxAttributesElements;
2217   BlackboxAttributes *net;
2218   if (! xatom->getValue(client.window, XAtom::blackbox_attributes,
2219                         XAtom::blackbox_attributes, num,
2220                         (unsigned long **)&net))
2221     return;
2222   if (num < PropBlackboxAttributesElements) {
2223     delete [] net;
2224     return;
2225   }
2226
2227   if (net->flags & AttribShaded && net->attrib & AttribShaded) {
2228     flags.shaded = False;
2229     unsigned long orig_state = current_state;
2230     shade();
2231
2232     /*
2233       At this point in the life of a window, current_state should only be set
2234       to IconicState if the window was an *icon*, not if it was shaded.
2235     */
2236     if (orig_state != IconicState)
2237       current_state = WithdrawnState;
2238  }
2239
2240   if (net->workspace != screen->getCurrentWorkspaceID() &&
2241       net->workspace < screen->getWorkspaceCount())
2242     screen->reassociateWindow(this, net->workspace, True);
2243
2244   if ((blackbox_attrib.workspace != screen->getCurrentWorkspaceID()) &&
2245       (blackbox_attrib.workspace < screen->getWorkspaceCount())) {
2246     // set to WithdrawnState so it will be mapped on the new workspace
2247     if (current_state == NormalState) current_state = WithdrawnState;
2248   } else if (current_state == WithdrawnState) {
2249     // the window is on this workspace and is Withdrawn, so it is waiting to
2250     // be mapped
2251     current_state = NormalState;
2252   }
2253
2254   if (net->flags & AttribOmnipresent && net->attrib & AttribOmnipresent) {
2255     flags.stuck = False;
2256     stick();
2257
2258     // if the window was on another workspace, it was going to be hidden. this
2259     // specifies that the window should be mapped since it is sticky.
2260     if (current_state == WithdrawnState) current_state = NormalState;
2261   }
2262
2263   if (net->flags & AttribMaxHoriz || net->flags & AttribMaxVert) {
2264     int x = net->premax_x, y = net->premax_y;
2265     unsigned int w = net->premax_w, h = net->premax_h;
2266     flags.maximized = 0;
2267
2268     unsigned int m = 0;
2269     if ((net->flags & AttribMaxHoriz) &&
2270         (net->flags & AttribMaxVert))
2271       m = (net->attrib & (AttribMaxHoriz | AttribMaxVert)) ? 1 : 0;
2272     else if (net->flags & AttribMaxVert)
2273       m = (net->attrib & AttribMaxVert) ? 2 : 0;
2274     else if (net->flags & AttribMaxHoriz)
2275       m = (net->attrib & AttribMaxHoriz) ? 3 : 0;
2276
2277     if (m) maximize(m);
2278
2279     blackbox_attrib.premax_x = x;
2280     blackbox_attrib.premax_y = y;
2281     blackbox_attrib.premax_w = w;
2282     blackbox_attrib.premax_h = h;
2283   }
2284
2285   if (net->flags & AttribDecoration) {
2286     switch (net->decoration) {
2287     case DecorNone:
2288       decorations = 0;
2289
2290       break;
2291
2292     default:
2293     case DecorNormal:
2294       decorations |= Decor_Titlebar | Decor_Handle | Decor_Border |
2295         Decor_Iconify | Decor_Maximize;
2296
2297       break;
2298
2299     case DecorTiny:
2300       decorations |= Decor_Titlebar | Decor_Iconify;
2301       decorations &= ~(Decor_Border | Decor_Handle | Decor_Maximize);
2302
2303       break;
2304
2305     case DecorTool:
2306       decorations |= Decor_Titlebar;
2307       decorations &= ~(Decor_Iconify | Decor_Border | Decor_Handle);
2308
2309       break;
2310     }
2311
2312     // sanity check the new decor
2313     if (! (functions & Func_Resize) || isTransient())
2314       decorations &= ~(Decor_Maximize | Decor_Handle);
2315     if (! (functions & Func_Maximize))
2316       decorations &= ~Decor_Maximize;
2317
2318     if (decorations & Decor_Titlebar) {
2319       if (functions & Func_Close)   // close button is controlled by function
2320         decorations |= Decor_Close; // not decor type
2321     } else { 
2322       if (flags.shaded) // we can not be shaded if we lack a titlebar
2323         shade();
2324     }
2325
2326     if (flags.visible && frame.window) {
2327       XMapSubwindows(blackbox->getXDisplay(), frame.window);
2328       XMapWindow(blackbox->getXDisplay(), frame.window);
2329     }
2330
2331     reconfigure();
2332     setState(current_state);
2333   }
2334
2335   // with the state set it will then be the map event's job to read the
2336   // window's state and behave accordingly
2337
2338   delete [] net;
2339 }
2340
2341
2342 /*
2343  * Positions the Rect r according the the client window position and
2344  * window gravity.
2345  */
2346 void BlackboxWindow::applyGravity(Rect &r) {
2347   // apply horizontal window gravity
2348   switch (client.win_gravity) {
2349   default:
2350   case NorthWestGravity:
2351   case SouthWestGravity:
2352   case WestGravity:
2353     r.setX(client.rect.x());
2354     break;
2355
2356   case NorthGravity:
2357   case SouthGravity:
2358   case CenterGravity:
2359     r.setX(client.rect.x() - (frame.margin.left + frame.margin.right) / 2);
2360     break;
2361
2362   case NorthEastGravity:
2363   case SouthEastGravity:
2364   case EastGravity:
2365     r.setX(client.rect.x() - frame.margin.left - frame.margin.right + 2);
2366     break;
2367
2368   case ForgetGravity:
2369   case StaticGravity:
2370     r.setX(client.rect.x() - frame.margin.left);
2371     break;
2372   }
2373
2374   // apply vertical window gravity
2375   switch (client.win_gravity) {
2376   default:
2377   case NorthWestGravity:
2378   case NorthEastGravity:
2379   case NorthGravity:
2380     r.setY(client.rect.y());
2381     break;
2382
2383   case CenterGravity:
2384   case EastGravity:
2385   case WestGravity:
2386     r.setY(client.rect.y() - (frame.margin.top + frame.margin.bottom) / 2);
2387     break;
2388
2389   case SouthWestGravity:
2390   case SouthEastGravity:
2391   case SouthGravity:
2392     r.setY(client.rect.y() - frame.margin.top - frame.margin.bottom + 2);
2393     break;
2394
2395   case ForgetGravity:
2396   case StaticGravity:
2397     r.setY(client.rect.y() - frame.margin.top);
2398     break;
2399   }
2400 }
2401
2402
2403 /*
2404  * The reverse of the applyGravity function.
2405  *
2406  * Positions the Rect r according to the frame window position and
2407  * window gravity.
2408  */
2409 void BlackboxWindow::restoreGravity(Rect &r) {
2410   // restore horizontal window gravity
2411   switch (client.win_gravity) {
2412   default:
2413   case NorthWestGravity:
2414   case SouthWestGravity:
2415   case WestGravity:
2416     r.setX(frame.rect.x());
2417     break;
2418
2419   case NorthGravity:
2420   case SouthGravity:
2421   case CenterGravity:
2422     r.setX(frame.rect.x() + (frame.margin.left + frame.margin.right) / 2);
2423     break;
2424
2425   case NorthEastGravity:
2426   case SouthEastGravity:
2427   case EastGravity:
2428     r.setX(frame.rect.x() + frame.margin.left + frame.margin.right - 2);
2429     break;
2430
2431   case ForgetGravity:
2432   case StaticGravity:
2433     r.setX(frame.rect.x() + frame.margin.left);
2434     break;
2435   }
2436
2437   // restore vertical window gravity
2438   switch (client.win_gravity) {
2439   default:
2440   case NorthWestGravity:
2441   case NorthEastGravity:
2442   case NorthGravity:
2443     r.setY(frame.rect.y());
2444     break;
2445
2446   case CenterGravity:
2447   case EastGravity:
2448   case WestGravity:
2449     r.setY(frame.rect.y() + (frame.margin.top + frame.margin.bottom) / 2);
2450     break;
2451
2452   case SouthWestGravity:
2453   case SouthEastGravity:
2454   case SouthGravity:
2455     r.setY(frame.rect.y() + frame.margin.top + frame.margin.bottom - 2);
2456     break;
2457
2458   case ForgetGravity:
2459   case StaticGravity:
2460     r.setY(frame.rect.y() + frame.margin.top);
2461     break;
2462   }
2463 }
2464
2465
2466 void BlackboxWindow::redrawLabel(void) const {
2467   if (flags.focused) {
2468     if (frame.flabel)
2469       XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2470                                  frame.label, frame.flabel);
2471     else
2472       XSetWindowBackground(blackbox->getXDisplay(),
2473                            frame.label, frame.flabel_pixel);
2474   } else {
2475     if (frame.ulabel)
2476       XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2477                                  frame.label, frame.ulabel);
2478     else
2479       XSetWindowBackground(blackbox->getXDisplay(),
2480                            frame.label, frame.ulabel_pixel);
2481   }
2482   XClearWindow(blackbox->getXDisplay(), frame.label);
2483
2484   WindowStyle *style = screen->getWindowStyle();
2485
2486   int pos = frame.bevel_w * 2;
2487   style->doJustify(client.title.c_str(), pos, frame.label_w, frame.bevel_w * 4);
2488   style->font->drawString(frame.label, pos, 1,
2489                           (flags.focused ? style->l_text_focus :
2490                            style->l_text_unfocus),
2491                           client.title);
2492 }
2493
2494
2495 void BlackboxWindow::redrawAllButtons(void) const {
2496   if (frame.iconify_button) redrawIconifyButton(False);
2497   if (frame.maximize_button) redrawMaximizeButton(flags.maximized);
2498   if (frame.close_button) redrawCloseButton(False);
2499 }
2500
2501
2502 void BlackboxWindow::redrawIconifyButton(bool pressed) const {
2503   if (! pressed) {
2504     if (flags.focused) {
2505       if (frame.fbutton)
2506         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2507                                    frame.iconify_button, frame.fbutton);
2508       else
2509         XSetWindowBackground(blackbox->getXDisplay(),
2510                              frame.iconify_button, frame.fbutton_pixel);
2511     } else {
2512       if (frame.ubutton)
2513         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2514                                    frame.iconify_button, frame.ubutton);
2515       else
2516         XSetWindowBackground(blackbox->getXDisplay(), frame.iconify_button,
2517                              frame.ubutton_pixel);
2518     }
2519   } else {
2520     if (frame.pbutton)
2521       XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2522                                  frame.iconify_button, frame.pbutton);
2523     else
2524       XSetWindowBackground(blackbox->getXDisplay(),
2525                            frame.iconify_button, frame.pbutton_pixel);
2526   }
2527   XClearWindow(blackbox->getXDisplay(), frame.iconify_button);
2528
2529   BPen pen((flags.focused) ? screen->getWindowStyle()->b_pic_focus :
2530            screen->getWindowStyle()->b_pic_unfocus);
2531   XDrawRectangle(blackbox->getXDisplay(), frame.iconify_button, pen.gc(),
2532                  2, (frame.button_w - 5), (frame.button_w - 5), 2);
2533 }
2534
2535
2536 void BlackboxWindow::redrawMaximizeButton(bool pressed) const {
2537   if (! pressed) {
2538     if (flags.focused) {
2539       if (frame.fbutton)
2540         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2541                                    frame.maximize_button, frame.fbutton);
2542       else
2543         XSetWindowBackground(blackbox->getXDisplay(), frame.maximize_button,
2544                              frame.fbutton_pixel);
2545     } else {
2546       if (frame.ubutton)
2547         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2548                                    frame.maximize_button, frame.ubutton);
2549       else
2550         XSetWindowBackground(blackbox->getXDisplay(), frame.maximize_button,
2551                              frame.ubutton_pixel);
2552     }
2553   } else {
2554     if (frame.pbutton)
2555       XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2556                                  frame.maximize_button, frame.pbutton);
2557     else
2558       XSetWindowBackground(blackbox->getXDisplay(), frame.maximize_button,
2559                            frame.pbutton_pixel);
2560   }
2561   XClearWindow(blackbox->getXDisplay(), frame.maximize_button);
2562
2563   BPen pen((flags.focused) ? screen->getWindowStyle()->b_pic_focus :
2564            screen->getWindowStyle()->b_pic_unfocus);
2565   XDrawRectangle(blackbox->getXDisplay(), frame.maximize_button, pen.gc(),
2566                  2, 2, (frame.button_w - 5), (frame.button_w - 5));
2567   XDrawLine(blackbox->getXDisplay(), frame.maximize_button, pen.gc(),
2568             2, 3, (frame.button_w - 3), 3);
2569 }
2570
2571
2572 void BlackboxWindow::redrawCloseButton(bool pressed) const {
2573   if (! pressed) {
2574     if (flags.focused) {
2575       if (frame.fbutton)
2576         XSetWindowBackgroundPixmap(blackbox->getXDisplay(), frame.close_button,
2577                                    frame.fbutton);
2578       else
2579         XSetWindowBackground(blackbox->getXDisplay(), frame.close_button,
2580                              frame.fbutton_pixel);
2581     } else {
2582       if (frame.ubutton)
2583         XSetWindowBackgroundPixmap(blackbox->getXDisplay(), frame.close_button,
2584                                    frame.ubutton);
2585       else
2586         XSetWindowBackground(blackbox->getXDisplay(), frame.close_button,
2587                              frame.ubutton_pixel);
2588     }
2589   } else {
2590     if (frame.pbutton)
2591       XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2592                                  frame.close_button, frame.pbutton);
2593     else
2594       XSetWindowBackground(blackbox->getXDisplay(),
2595                            frame.close_button, frame.pbutton_pixel);
2596   }
2597   XClearWindow(blackbox->getXDisplay(), frame.close_button);
2598
2599   BPen pen((flags.focused) ? screen->getWindowStyle()->b_pic_focus :
2600            screen->getWindowStyle()->b_pic_unfocus);
2601   XDrawLine(blackbox->getXDisplay(), frame.close_button, pen.gc(),
2602             2, 2, (frame.button_w - 3), (frame.button_w - 3));
2603   XDrawLine(blackbox->getXDisplay(), frame.close_button, pen.gc(),
2604             2, (frame.button_w - 3), (frame.button_w - 3), 2);
2605 }
2606
2607
2608 void BlackboxWindow::mapRequestEvent(const XMapRequestEvent *re) {
2609   if (re->window != client.window)
2610     return;
2611
2612 #ifdef    DEBUG
2613   fprintf(stderr, "BlackboxWindow::mapRequestEvent() for 0x%lx\n",
2614           client.window);
2615 #endif // DEBUG
2616
2617   switch (current_state) {
2618   case IconicState:
2619     iconify();
2620     break;
2621
2622   case WithdrawnState:
2623     withdraw();
2624     break;
2625
2626   case NormalState:
2627   case InactiveState:
2628   case ZoomState:
2629   default:
2630 #ifdef DEBUG
2631     fprintf(stderr, "0x%lx: just before show (%d, %d) w: %d, h: %d\n",
2632             client.window,
2633             frame.rect.x(), frame.rect.y(),
2634             frame.rect.width(), frame.rect.height());
2635 #endif // DEBUG
2636     show();
2637     screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
2638     if (isNormal()) {
2639       if (! blackbox->isStartup()) {
2640         XSync(blackbox->getXDisplay(), False); // make sure the frame is mapped
2641         if (screen->doFocusNew()|| (isTransient() && getTransientFor() &&
2642                                     getTransientFor()->isFocused())) {
2643           setInputFocus();
2644         }
2645         if (screen->getPlacementPolicy() == BScreen::ClickMousePlacement) {
2646           int x, y, rx, ry;
2647           Window c, r;
2648           unsigned int m;
2649           XQueryPointer(blackbox->getXDisplay(), screen->getRootWindow(),
2650                         &r, &c, &rx, &ry, &x, &y, &m);
2651           beginMove(rx, ry);
2652         }
2653       }
2654     }
2655     break;
2656   }
2657 }
2658
2659
2660 void BlackboxWindow::unmapNotifyEvent(const XUnmapEvent *ue) {
2661   if (ue->window != client.window)
2662     return;
2663
2664 #ifdef    DEBUG
2665   fprintf(stderr, "BlackboxWindow::unmapNotifyEvent() for 0x%lx\n",
2666           client.window);
2667 #endif // DEBUG
2668
2669   screen->unmanageWindow(this, False);
2670 }
2671
2672
2673 void BlackboxWindow::destroyNotifyEvent(const XDestroyWindowEvent *de) {
2674   if (de->window != client.window)
2675     return;
2676
2677 #ifdef    DEBUG
2678   fprintf(stderr, "BlackboxWindow::destroyNotifyEvent() for 0x%lx\n",
2679           client.window);
2680 #endif // DEBUG
2681
2682   screen->unmanageWindow(this, False);
2683 }
2684
2685
2686 void BlackboxWindow::reparentNotifyEvent(const XReparentEvent *re) {
2687   if (re->window != client.window || re->parent == frame.plate)
2688     return;
2689
2690 #ifdef    DEBUG
2691   fprintf(stderr, "BlackboxWindow::reparentNotifyEvent(): reparent 0x%lx to "
2692           "0x%lx.\n", client.window, re->parent);
2693 #endif // DEBUG
2694
2695   XEvent ev;
2696   ev.xreparent = *re;
2697   XPutBackEvent(blackbox->getXDisplay(), &ev);
2698   screen->unmanageWindow(this, True);
2699 }
2700
2701
2702 void BlackboxWindow::propertyNotifyEvent(const XPropertyEvent *pe) {
2703   if (pe->state == PropertyDelete)
2704     return;
2705
2706 #ifdef    DEBUG
2707   fprintf(stderr, "BlackboxWindow::propertyNotifyEvent(): for 0x%lx\n",
2708           client.window);
2709 #endif
2710
2711   switch(pe->atom) {
2712   case XA_WM_CLASS:
2713   case XA_WM_CLIENT_MACHINE:
2714   case XA_WM_COMMAND:
2715     break;
2716
2717   case XA_WM_TRANSIENT_FOR: {
2718     // determine if this is a transient window
2719     getTransientInfo();
2720
2721     // adjust the window decorations based on transience
2722     if (isTransient()) {
2723       decorations &= ~(Decor_Maximize | Decor_Handle);
2724       functions &= ~Func_Maximize;
2725       setAllowedActions();
2726     }
2727
2728     reconfigure();
2729 #ifdef DEBUG
2730     fprintf(stderr, "0x%lx: transient hint (%d, %d) w: %d, h: %d\n",
2731             client.window,
2732             frame.rect.x(), frame.rect.y(),
2733             frame.rect.width(), frame.rect.height());
2734 #endif
2735   }
2736     break;
2737
2738   case XA_WM_HINTS:
2739     getWMHints();
2740     break;
2741
2742   case XA_WM_ICON_NAME:
2743     getWMIconName();
2744     if (flags.iconic) screen->propagateWindowName(this);
2745     break;
2746
2747   case XAtom::net_wm_name:
2748   case XA_WM_NAME:
2749     getWMName();
2750
2751     if (decorations & Decor_Titlebar)
2752       redrawLabel();
2753
2754     screen->propagateWindowName(this);
2755     break;
2756
2757   case XA_WM_NORMAL_HINTS: {
2758     getWMNormalHints();
2759
2760     if ((client.normal_hint_flags & PMinSize) &&
2761         (client.normal_hint_flags & PMaxSize)) {
2762       // the window now can/can't resize itself, so the buttons need to be
2763       // regrabbed.
2764       ungrabButtons();
2765       if (client.max_width <= client.min_width &&
2766           client.max_height <= client.min_height) {
2767         decorations &= ~(Decor_Maximize | Decor_Handle);
2768         functions &= ~(Func_Resize | Func_Maximize);
2769       } else {
2770         if (! isTransient()) {
2771           decorations |= Decor_Maximize | Decor_Handle;
2772           functions |= Func_Maximize;
2773         }
2774         functions |= Func_Resize;
2775       }
2776       grabButtons();
2777       setAllowedActions();
2778     }
2779
2780     Rect old_rect = frame.rect;
2781
2782     upsize();
2783
2784     if (old_rect != frame.rect)
2785       reconfigure();
2786
2787 #ifdef DEBUG
2788     fprintf(stderr, "0x%lx: normal hint (%d, %d) w: %d, h: %d\n",
2789             client.window,
2790             frame.rect.x(), frame.rect.y(),
2791             frame.rect.width(), frame.rect.height());
2792 #endif // DEBUG
2793     break;
2794   }
2795
2796   default:
2797     if (pe->atom == xatom->getAtom(XAtom::wm_protocols)) {
2798       getWMProtocols();
2799
2800       if ((decorations & Decor_Close) && (! frame.close_button)) {
2801         createCloseButton();
2802         if (decorations & Decor_Titlebar) {
2803           positionButtons(True);
2804           XMapSubwindows(blackbox->getXDisplay(), frame.title);
2805         }
2806         if (windowmenu) windowmenu->reconfigure();
2807       }
2808     } else if (pe->atom == xatom->getAtom(XAtom::net_wm_strut)) {
2809       updateStrut();
2810     }
2811
2812     break;
2813   }
2814 }
2815
2816
2817 void BlackboxWindow::exposeEvent(const XExposeEvent *ee) {
2818 #ifdef DEBUG
2819   fprintf(stderr, "BlackboxWindow::exposeEvent() for 0x%lx\n", client.window);
2820 #endif
2821
2822   if (frame.label == ee->window && (decorations & Decor_Titlebar))
2823     redrawLabel();
2824   else if (frame.close_button == ee->window)
2825     redrawCloseButton(False);
2826   else if (frame.maximize_button == ee->window)
2827     redrawMaximizeButton(flags.maximized);
2828   else if (frame.iconify_button == ee->window)
2829     redrawIconifyButton(False);
2830 }
2831
2832
2833 void BlackboxWindow::configureRequestEvent(const XConfigureRequestEvent *cr) {
2834   if (cr->window != client.window || flags.iconic)
2835     return;
2836
2837   if (cr->value_mask & CWBorderWidth)
2838     client.old_bw = cr->border_width;
2839
2840   if (cr->value_mask & (CWX | CWY | CWWidth | CWHeight)) {
2841     Rect req = frame.rect;
2842
2843     if (cr->value_mask & (CWX | CWY)) {
2844       if (cr->value_mask & CWX)
2845         client.rect.setX(cr->x);
2846       if (cr->value_mask & CWY)
2847         client.rect.setY(cr->y);
2848
2849       applyGravity(req);
2850     }
2851
2852     if (cr->value_mask & CWWidth) {
2853       req.setWidth(cr->width + frame.margin.left + frame.margin.right);
2854 #ifdef DEBUG
2855       fprintf(stderr, "0x%lx: new width - %d\n", client.window, cr->width);
2856 #endif // DEBUG
2857     }
2858
2859     if (cr->value_mask & CWHeight) {
2860       req.setHeight(cr->height + frame.margin.top + frame.margin.bottom);
2861 #ifdef DEBUG
2862       fprintf(stderr, "0x%lx: new height - %d\n", client.window, cr->height);
2863 #endif // DEBUG
2864     }
2865
2866     configure(req.x(), req.y(), req.width(), req.height());
2867   }
2868
2869   if (cr->value_mask & CWStackMode && !isDesktop()) {
2870     switch (cr->detail) {
2871     case Below:
2872     case BottomIf:
2873       screen->getWorkspace(blackbox_attrib.workspace)->lowerWindow(this);
2874       break;
2875
2876     case Above:
2877     case TopIf:
2878     default:
2879       screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
2880       break;
2881     }
2882   }
2883
2884 #ifdef DEBUG
2885   fprintf(stderr, "0x%lx: change request (%d, %d) w: %d, h: %d\n",
2886           client.window,
2887           frame.rect.x(), frame.rect.y(),
2888           frame.rect.width(), frame.rect.height());
2889 #endif // DEBUG
2890 }
2891
2892
2893 void BlackboxWindow::buttonPressEvent(const XButtonEvent *be) {
2894 #ifdef DEBUG
2895   fprintf(stderr, "BlackboxWindow::buttonPressEvent() for 0x%lx\n",
2896           client.window);
2897 #endif
2898
2899   if (frame.maximize_button == be->window && be->button <= 3) {
2900     redrawMaximizeButton(True);
2901   } else if (be->button == 1 || (be->button == 3 && be->state == ModMask)) {
2902     if (! flags.focused)
2903       setInputFocus();
2904
2905     if (frame.iconify_button == be->window) {
2906       redrawIconifyButton(True);
2907     } else if (frame.close_button == be->window) {
2908       redrawCloseButton(True);
2909     } else if (frame.plate == be->window) {
2910       if (windowmenu && windowmenu->isVisible()) windowmenu->hide();
2911
2912       screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
2913
2914       XAllowEvents(blackbox->getXDisplay(), ReplayPointer, be->time);
2915     } else {
2916       if (frame.title == be->window || frame.label == be->window) {
2917         if (((be->time - lastButtonPressTime) <=
2918              blackbox->getDoubleClickInterval()) ||
2919             (be->state == ControlMask)) {
2920           lastButtonPressTime = 0;
2921           shade();
2922         } else {
2923           lastButtonPressTime = be->time;
2924         }
2925       }
2926
2927       if (windowmenu && windowmenu->isVisible()) windowmenu->hide();
2928
2929       screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
2930     }
2931   } else if (be->button == 2 && (be->window != frame.iconify_button) &&
2932              (be->window != frame.close_button)) {
2933     screen->getWorkspace(blackbox_attrib.workspace)->lowerWindow(this);
2934   } else if (windowmenu && be->button == 3 &&
2935              (frame.title == be->window || frame.label == be->window ||
2936               frame.handle == be->window || frame.window == be->window)) {
2937     if (windowmenu->isVisible()) {
2938       windowmenu->hide();
2939     } else {
2940       int mx = be->x_root - windowmenu->getWidth() / 2,
2941           my = be->y_root - windowmenu->getHeight() / 2;
2942
2943       // snap the window menu into a corner/side if necessary
2944       int left_edge, right_edge, top_edge, bottom_edge;
2945
2946       /*
2947          the " + (frame.border_w * 2) - 1" bits are to get the proper width
2948          and height of the menu, as the sizes returned by it do not include
2949          the borders.
2950        */
2951       left_edge = frame.rect.x();
2952       right_edge = frame.rect.right() -
2953         (windowmenu->getWidth() + (frame.border_w * 2) - 1);
2954       top_edge = client.rect.top() - (frame.border_w + frame.mwm_border_w);
2955       bottom_edge = client.rect.bottom() -
2956         (windowmenu->getHeight() + (frame.border_w * 2) - 1) +
2957         (frame.border_w + frame.mwm_border_w);
2958
2959       if (mx < left_edge)
2960         mx = left_edge;
2961       if (mx > right_edge)
2962         mx = right_edge;
2963       if (my < top_edge)
2964         my = top_edge;
2965       if (my > bottom_edge)
2966         my = bottom_edge;
2967
2968       windowmenu->move(mx, my);
2969       windowmenu->show();
2970       XRaiseWindow(blackbox->getXDisplay(), windowmenu->getWindowID());
2971       XRaiseWindow(blackbox->getXDisplay(),
2972                    windowmenu->getSendToMenu()->getWindowID());
2973     }
2974   // mouse wheel up
2975   } else if (be->button == 4) {
2976     if ((be->window == frame.label ||
2977          be->window == frame.title ||
2978          be->window == frame.maximize_button ||
2979          be->window == frame.iconify_button ||
2980          be->window == frame.close_button) &&
2981         ! flags.shaded)
2982       shade();
2983   // mouse wheel down
2984   } else if (be->button == 5) {
2985     if ((be->window == frame.label ||
2986          be->window == frame.title ||
2987          be->window == frame.maximize_button ||
2988          be->window == frame.iconify_button ||
2989          be->window == frame.close_button) &&
2990         flags.shaded)
2991       shade();
2992   }
2993 }
2994
2995
2996 void BlackboxWindow::buttonReleaseEvent(const XButtonEvent *re) {
2997 #ifdef DEBUG
2998   fprintf(stderr, "BlackboxWindow::buttonReleaseEvent() for 0x%lx\n",
2999           client.window);
3000 #endif
3001
3002   if (re->window == frame.maximize_button &&
3003       re->button >= 1 && re->button <= 3) {
3004     if ((re->x >= 0 && re->x <= static_cast<signed>(frame.button_w)) &&
3005         (re->y >= 0 && re->y <= static_cast<signed>(frame.button_w))) {
3006       maximize(re->button);
3007     } else {
3008       redrawMaximizeButton(flags.maximized);
3009     }
3010   } else if (re->window == frame.iconify_button && re->button == 1) {
3011     if ((re->x >= 0 && re->x <= static_cast<signed>(frame.button_w)) &&
3012         (re->y >= 0 && re->y <= static_cast<signed>(frame.button_w))) {
3013       iconify();
3014     } else {
3015       redrawIconifyButton(False);
3016     }
3017   } else if (re->window == frame.close_button & re->button == 1) {
3018     if ((re->x >= 0 && re->x <= static_cast<signed>(frame.button_w)) &&
3019         (re->y >= 0 && re->y <= static_cast<signed>(frame.button_w)))
3020       close();
3021     redrawCloseButton(False);
3022   } else if (flags.moving) {
3023     endMove();
3024   } else if (flags.resizing) {
3025     endResize();
3026   } else if (re->window == frame.window) {
3027     if (re->button == 2 && re->state == ModMask)
3028       XUngrabPointer(blackbox->getXDisplay(), CurrentTime);
3029   }
3030 }
3031
3032
3033
3034 void BlackboxWindow::beginMove(int x_root, int y_root) {
3035   assert(! (flags.resizing || flags.moving));
3036
3037   /*
3038     Only one window can be moved/resized at a time. If another window is already
3039     being moved or resized, then stop it before whating to work with this one.
3040   */
3041   BlackboxWindow *changing = blackbox->getChangingWindow();
3042   if (changing && changing != this) {
3043     if (changing->flags.moving)
3044       changing->endMove();
3045     else // if (changing->flags.resizing)
3046       changing->endResize();
3047   }
3048   
3049   XGrabPointer(blackbox->getXDisplay(), frame.window, False,
3050                PointerMotionMask | ButtonReleaseMask,
3051                GrabModeAsync, GrabModeAsync,
3052                None, blackbox->getMoveCursor(), CurrentTime);
3053
3054   if (windowmenu && windowmenu->isVisible())
3055     windowmenu->hide();
3056
3057   flags.moving = True;
3058   blackbox->setChangingWindow(this);
3059
3060   if (! screen->doOpaqueMove()) {
3061     XGrabServer(blackbox->getXDisplay());
3062
3063     frame.changing = frame.rect;
3064     screen->showPosition(frame.changing.x(), frame.changing.y());
3065
3066     XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
3067                    screen->getOpGC(),
3068                    frame.changing.x(),
3069                    frame.changing.y(),
3070                    frame.changing.width() - 1,
3071                    frame.changing.height() - 1);
3072   }
3073
3074   frame.grab_x = x_root - frame.rect.x() - frame.border_w;
3075   frame.grab_y = y_root - frame.rect.y() - frame.border_w;
3076 }
3077
3078
3079 void BlackboxWindow::doMove(int x_root, int y_root) {
3080   assert(flags.moving);
3081   assert(blackbox->getChangingWindow() == this);
3082
3083   int dx = x_root - frame.grab_x, dy = y_root - frame.grab_y;
3084   dx -= frame.border_w;
3085   dy -= frame.border_w;
3086
3087   if (screen->doWorkspaceWarping()) {
3088     // workspace warping
3089     bool warp = False;
3090     unsigned int dest = screen->getCurrentWorkspaceID();
3091     if (x_root <= 0) {
3092       warp = True;
3093
3094       if (dest > 0) dest--;
3095       else dest = screen->getNumberOfWorkspaces() - 1;
3096
3097     } else if (x_root >= screen->getRect().right()) {
3098       warp = True;
3099
3100       if (dest < screen->getNumberOfWorkspaces() - 1) dest++;
3101       else dest = 0;
3102     }
3103     if (warp) {
3104       endMove();
3105       bool focus = flags.focused; // had focus while moving?
3106       if (! flags.stuck)
3107         screen->reassociateWindow(this, dest, False);
3108       screen->changeWorkspaceID(dest);
3109       if (focus)
3110         setInputFocus();
3111
3112       /*
3113          If the XWarpPointer is done after the configure, we can end up
3114          grabbing another window, so made sure you do it first.
3115       */
3116       int dest_x;
3117       if (x_root <= 0) {
3118         dest_x = screen->getRect().right() - 1;
3119         XWarpPointer(blackbox->getXDisplay(), None, 
3120                      screen->getRootWindow(), 0, 0, 0, 0,
3121                      dest_x, y_root);
3122
3123         configure(dx + (screen->getRect().width() - 1), dy,
3124                   frame.rect.width(), frame.rect.height());
3125       } else {
3126         dest_x = 0;
3127         XWarpPointer(blackbox->getXDisplay(), None, 
3128                      screen->getRootWindow(), 0, 0, 0, 0,
3129                      dest_x, y_root);
3130
3131         configure(dx - (screen->getRect().width() - 1), dy,
3132                   frame.rect.width(), frame.rect.height());
3133       }
3134
3135       beginMove(dest_x, y_root);
3136       return;
3137     }
3138   }
3139
3140   const int snap_distance = screen->getEdgeSnapThreshold();
3141
3142   if (snap_distance) {
3143     // window corners
3144     const int wleft = dx,
3145               wright = dx + frame.rect.width() - 1,
3146               wtop = dy,
3147               wbottom = dy + frame.rect.height() - 1;
3148
3149     if (screen->getWindowToWindowSnap()) {
3150       Workspace *w = screen->getWorkspace(getWorkspaceNumber());
3151       assert(w);
3152
3153       // try snap to another window
3154       for (unsigned int i = 0, c = w->getCount(); i < c; ++i) {
3155         BlackboxWindow *snapwin = w->getWindow(i);
3156         if (snapwin == this)
3157           continue;   // don't snap to self
3158
3159         bool snapped = False;
3160         
3161         const Rect &winrect = snapwin->frameRect();
3162         int dleft = abs(wright - winrect.left()),
3163            dright = abs(wleft - winrect.right()),
3164              dtop = abs(wbottom - winrect.top()),
3165           dbottom = abs(wtop - winrect.bottom());
3166
3167         if (wtop >= (signed)(winrect.y() - frame.rect.height() + 1) &&
3168             wtop < (signed)(winrect.y() + winrect.height() - 1)) {
3169
3170           // snap left of other window?
3171           if (dleft < snap_distance && dleft <= dright) {
3172             dx = winrect.left() - frame.rect.width();
3173             snapped = True;
3174           }
3175           // snap right of other window?
3176           else if (dright < snap_distance) {
3177             dx = winrect.right() + 1;
3178             snapped = True;
3179           }
3180
3181           if (snapped) {
3182             if (screen->getWindowCornerSnap()) {
3183               // try corner-snap to its other sides
3184               dtop = abs(wtop - winrect.top());
3185               dbottom = abs(wbottom - winrect.bottom());
3186               if (dtop < snap_distance && dtop <= dbottom)
3187                 dy = winrect.top();
3188               else if (dbottom < snap_distance)
3189                 dy = winrect.bottom() - frame.rect.height() + 1;
3190             }
3191
3192             continue;
3193           }
3194         }
3195
3196         if (wleft >= (signed)(winrect.x() - frame.rect.width() + 1) &&
3197             wleft < (signed)(winrect.x() + winrect.width() - 1)) {
3198
3199           // snap top of other window?
3200           if (dtop < snap_distance && dtop <= dbottom) {
3201             dy = winrect.top() - frame.rect.height();
3202             snapped = True;
3203           }
3204           // snap bottom of other window?
3205           else if (dbottom < snap_distance) {
3206             dy = winrect.bottom() + 1;
3207             snapped = True;
3208           }
3209
3210           if (snapped) {
3211             if (screen->getWindowCornerSnap()) {
3212               // try corner-snap to its other sides
3213               dleft = abs(wleft - winrect.left());
3214               dright = abs(wright - winrect.right());
3215               if (dleft < snap_distance && dleft <= dright)
3216                 dx = winrect.left();
3217               else if (dright < snap_distance)
3218                 dx = winrect.right() - frame.rect.width() + 1;
3219             }
3220
3221             continue;
3222           }
3223         }
3224       }
3225     }
3226
3227     RectList snaplist; // the list of rects we will try to snap to
3228
3229     // snap to the strut (and screen boundaries for xinerama)
3230 #ifdef    XINERAMA
3231     if (screen->isXineramaActive() && blackbox->doXineramaSnapping()) {
3232       if (! screen->doFullMax())
3233         snaplist.insert(snaplist.begin(),
3234                         screen->allAvailableAreas().begin(),
3235                         screen->allAvailableAreas().end());
3236
3237       // always snap to the screen edges
3238       snaplist.insert(snaplist.begin(),
3239                       screen->getXineramaAreas().begin(),
3240                       screen->getXineramaAreas().end());
3241     } else
3242 #endif // XINERAMA
3243     {
3244       if (! screen->doFullMax())
3245         snaplist.push_back(screen->availableArea());
3246
3247       // always snap to the screen edges
3248       snaplist.push_back(screen->getRect());
3249     }
3250
3251     RectList::const_iterator it, end = snaplist.end();
3252     for (it = snaplist.begin(); it != end; ++it) {
3253       const Rect &srect = *it;
3254
3255       // if we're not in the rectangle then don't snap to it.
3256       if (! srect.intersects(Rect(wleft, wtop, frame.rect.width(),
3257                                   frame.rect.height())))
3258         continue;
3259
3260       int dleft = abs(wleft - srect.left()),
3261          dright = abs(wright - srect.right()),
3262            dtop = abs(wtop - srect.top()),
3263         dbottom = abs(wbottom - srect.bottom());
3264
3265         // snap left?
3266         if (dleft < snap_distance && dleft <= dright)
3267           dx = srect.left();
3268         // snap right?
3269         else if (dright < snap_distance)
3270           dx = srect.right() - frame.rect.width() + 1;
3271
3272         // snap top?
3273         if (dtop < snap_distance && dtop <= dbottom)
3274           dy = srect.top();
3275         // snap bottom?
3276         else if (dbottom < snap_distance)
3277           dy = srect.bottom() - frame.rect.height() + 1;
3278     }
3279   }
3280
3281   if (screen->doOpaqueMove()) {
3282     configure(dx, dy, frame.rect.width(), frame.rect.height());
3283   } else {
3284     XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
3285                    screen->getOpGC(),
3286                    frame.changing.x(),
3287                    frame.changing.y(),
3288                    frame.changing.width() - 1,
3289                    frame.changing.height() - 1);
3290
3291     frame.changing.setPos(dx, dy);
3292
3293     XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
3294                    screen->getOpGC(),
3295                    frame.changing.x(),
3296                    frame.changing.y(),
3297                    frame.changing.width() - 1,
3298                    frame.changing.height() - 1);
3299   }
3300
3301   screen->showPosition(dx, dy);
3302 }
3303
3304
3305 void BlackboxWindow::endMove(void) {
3306   assert(flags.moving);
3307   assert(blackbox->getChangingWindow() == this);
3308
3309   flags.moving = False;
3310   blackbox->setChangingWindow(0);
3311
3312   if (! screen->doOpaqueMove()) {
3313     /* when drawing the rubber band, we need to make sure we only draw inside
3314      * the frame... frame.changing_* contain the new coords for the window,
3315      * so we need to subtract 1 from changing_w/changing_h every where we
3316      * draw the rubber band (for both moving and resizing)
3317      */
3318     XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
3319                    screen->getOpGC(), frame.changing.x(), frame.changing.y(),
3320                    frame.changing.width() - 1, frame.changing.height() - 1);
3321       XUngrabServer(blackbox->getXDisplay());
3322   
3323       configure(frame.changing.x(), frame.changing.y(),
3324                 frame.changing.width(), frame.changing.height());
3325   } else {
3326     configure(frame.rect.x(), frame.rect.y(),
3327               frame.rect.width(), frame.rect.height());
3328   }
3329   screen->hideGeometry();
3330
3331   XUngrabPointer(blackbox->getXDisplay(), CurrentTime);
3332
3333   // if there are any left over motions from the move, drop them now
3334   XSync(blackbox->getXDisplay(), false); // make sure we don't miss any
3335   XEvent e;
3336   while (XCheckTypedWindowEvent(blackbox->getXDisplay(), frame.window,
3337                                 MotionNotify, &e));
3338 }
3339
3340
3341 void BlackboxWindow::beginResize(int x_root, int y_root, Corner dir) {
3342   assert(! (flags.resizing || flags.moving));
3343
3344   /*
3345     Only one window can be moved/resized at a time. If another window is already
3346     being moved or resized, then stop it before whating to work with this one.
3347   */
3348   BlackboxWindow *changing = blackbox->getChangingWindow();
3349   if (changing && changing != this) {
3350     if (changing->flags.moving)
3351       changing->endMove();
3352     else // if (changing->flags.resizing)
3353       changing->endResize();
3354   }
3355
3356   resize_dir = dir;
3357
3358   Cursor cursor;
3359   Corner anchor;
3360   
3361   switch (resize_dir) {
3362   case BottomLeft:
3363     anchor = TopRight;
3364     cursor = blackbox->getLowerLeftAngleCursor();
3365     break;
3366
3367   case BottomRight:
3368     anchor = TopLeft;
3369     cursor = blackbox->getLowerRightAngleCursor();
3370     break;
3371
3372   case TopLeft:
3373     anchor = BottomRight;
3374     cursor = blackbox->getUpperLeftAngleCursor();
3375     break;
3376
3377   case TopRight:
3378     anchor = BottomLeft;
3379     cursor = blackbox->getUpperRightAngleCursor();
3380     break;
3381
3382   default:
3383     assert(false); // unhandled Corner
3384     return;        // unreachable, for the compiler
3385   }
3386   
3387   XGrabServer(blackbox->getXDisplay());
3388   XGrabPointer(blackbox->getXDisplay(), frame.window, False,
3389                PointerMotionMask | ButtonReleaseMask,
3390                GrabModeAsync, GrabModeAsync, None, cursor, CurrentTime);
3391
3392   flags.resizing = True;
3393   blackbox->setChangingWindow(this);
3394
3395   int gw, gh;
3396   frame.changing = frame.rect;
3397
3398   constrain(anchor,  &gw, &gh);
3399
3400   XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
3401                  screen->getOpGC(), frame.changing.x(), frame.changing.y(),
3402                  frame.changing.width() - 1, frame.changing.height() - 1);
3403
3404   screen->showGeometry(gw, gh);
3405   
3406   frame.grab_x = x_root;
3407   frame.grab_y = y_root;
3408 }
3409
3410
3411 void BlackboxWindow::doResize(int x_root, int y_root) {
3412   assert(flags.resizing);
3413   assert(blackbox->getChangingWindow() == this);
3414
3415   XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
3416                  screen->getOpGC(), frame.changing.x(), frame.changing.y(),
3417                  frame.changing.width() - 1, frame.changing.height() - 1);
3418
3419   int gw, gh;
3420   Corner anchor;
3421
3422   switch (resize_dir) {
3423   case BottomLeft:
3424     anchor = TopRight;
3425     frame.changing.setSize(frame.rect.width() - (x_root - frame.grab_x),
3426                            frame.rect.height() + (y_root - frame.grab_y));
3427     break;
3428   case BottomRight:
3429     anchor = TopLeft;
3430     frame.changing.setSize(frame.rect.width() + (x_root - frame.grab_x),
3431                            frame.rect.height() + (y_root - frame.grab_y));
3432     break;
3433   case TopLeft:
3434     anchor = BottomRight;
3435     frame.changing.setSize(frame.rect.width() - (x_root - frame.grab_x),
3436                            frame.rect.height() - (y_root - frame.grab_y));
3437     break;
3438   case TopRight:
3439     anchor = BottomLeft;
3440     frame.changing.setSize(frame.rect.width() + (x_root - frame.grab_x),
3441                            frame.rect.height() - (y_root - frame.grab_y));
3442     break;
3443
3444   default:
3445     assert(false); // unhandled Corner
3446     return;        // unreachable, for the compiler
3447   }
3448   
3449   constrain(anchor, &gw, &gh);
3450
3451   XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
3452                  screen->getOpGC(), frame.changing.x(), frame.changing.y(),
3453                  frame.changing.width() - 1, frame.changing.height() - 1);
3454
3455   screen->showGeometry(gw, gh);
3456 }
3457
3458
3459 void BlackboxWindow::endResize(void) {
3460   assert(flags.resizing);
3461   assert(blackbox->getChangingWindow() == this);
3462
3463   XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
3464                  screen->getOpGC(), frame.changing.x(), frame.changing.y(),
3465                  frame.changing.width() - 1, frame.changing.height() - 1);
3466   XUngrabServer(blackbox->getXDisplay());
3467
3468   // unset maximized state after resized when fully maximized
3469   if (flags.maximized == 1)
3470     maximize(0);
3471   
3472   flags.resizing = False;
3473   blackbox->setChangingWindow(0);
3474
3475   configure(frame.changing.x(), frame.changing.y(),
3476             frame.changing.width(), frame.changing.height());
3477   screen->hideGeometry();
3478
3479   XUngrabPointer(blackbox->getXDisplay(), CurrentTime);
3480   
3481   // if there are any left over motions from the resize, drop them now
3482   XSync(blackbox->getXDisplay(), false); // make sure we don't miss any
3483   XEvent e;
3484   while (XCheckTypedWindowEvent(blackbox->getXDisplay(), frame.window,
3485                                 MotionNotify, &e));
3486 }
3487
3488
3489 void BlackboxWindow::motionNotifyEvent(const XMotionEvent *me) {
3490 #ifdef DEBUG
3491   fprintf(stderr, "BlackboxWindow::motionNotifyEvent() for 0x%lx\n",
3492           client.window);
3493 #endif
3494
3495   if (flags.moving) {
3496     doMove(me->x_root, me->y_root);
3497   } else if (flags.resizing) {
3498     doResize(me->x_root, me->y_root);
3499   } else {
3500     if (!flags.resizing && me->state & Button1Mask && (functions & Func_Move) &&
3501         (frame.title == me->window || frame.label == me->window ||
3502          frame.handle == me->window || frame.window == me->window)) {
3503       beginMove(me->x_root, me->y_root);
3504     } else if ((functions & Func_Resize) &&
3505                (me->state & Button1Mask && (me->window == frame.right_grip ||
3506                                             me->window == frame.left_grip)) ||
3507                (me->state & Button3Mask && me->state & ModMask &&
3508                 me->window == frame.window)) {
3509       unsigned int zones = screen->getResizeZones();
3510       Corner corner;
3511       
3512       if (me->window == frame.left_grip) {
3513         corner = BottomLeft;
3514       } else if (me->window == frame.right_grip || zones == 1) {
3515         corner = BottomRight;
3516       } else {
3517         bool top;
3518         bool left = (me->x_root - frame.rect.x() <=
3519                      static_cast<signed>(frame.rect.width() / 2));
3520         if (zones == 2)
3521           top = False;
3522         else // (zones == 4)
3523           top = (me->y_root - frame.rect.y() <=
3524                  static_cast<signed>(frame.rect.height() / 2));
3525         corner = (top ? (left ? TopLeft : TopRight) :
3526                         (left ? BottomLeft : BottomRight));
3527       }
3528
3529       beginResize(me->x_root, me->y_root, corner);
3530     }
3531   }
3532 }
3533
3534
3535 #ifdef    SHAPE
3536 void BlackboxWindow::shapeEvent(XShapeEvent *) {
3537   if (blackbox->hasShapeExtensions() && flags.shaped) {
3538     configureShape();
3539   }
3540 }
3541 #endif // SHAPE
3542
3543
3544 bool BlackboxWindow::validateClient(void) const {
3545   XSync(blackbox->getXDisplay(), False);
3546
3547   XEvent e;
3548   if (XCheckTypedWindowEvent(blackbox->getXDisplay(), client.window,
3549                              DestroyNotify, &e) ||
3550       XCheckTypedWindowEvent(blackbox->getXDisplay(), client.window,
3551                              UnmapNotify, &e)) {
3552     XPutBackEvent(blackbox->getXDisplay(), &e);
3553
3554     return False;
3555   }
3556
3557   return True;
3558 }
3559
3560
3561 void BlackboxWindow::restore(bool remap) {
3562   XChangeSaveSet(blackbox->getXDisplay(), client.window, SetModeDelete);
3563   XSelectInput(blackbox->getXDisplay(), client.window, NoEventMask);
3564   XSelectInput(blackbox->getXDisplay(), frame.plate, NoEventMask);
3565
3566   // do not leave a shaded window as an icon unless it was an icon
3567   if (flags.shaded && ! flags.iconic) setState(NormalState);
3568
3569   restoreGravity(client.rect);
3570
3571   XUnmapWindow(blackbox->getXDisplay(), frame.window);
3572   XUnmapWindow(blackbox->getXDisplay(), client.window);
3573
3574   XSetWindowBorderWidth(blackbox->getXDisplay(), client.window, client.old_bw);
3575
3576   XEvent ev;
3577   if (XCheckTypedWindowEvent(blackbox->getXDisplay(), client.window,
3578                              ReparentNotify, &ev)) {
3579     remap = True;
3580   } else {
3581     // according to the ICCCM - if the client doesn't reparent to
3582     // root, then we have to do it for them
3583     XReparentWindow(blackbox->getXDisplay(), client.window,
3584                     screen->getRootWindow(),
3585                     client.rect.x(), client.rect.y());
3586   }
3587
3588   if (remap) XMapWindow(blackbox->getXDisplay(), client.window);
3589 }
3590
3591
3592 // timer for autoraise
3593 void BlackboxWindow::timeout(void) {
3594   screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
3595 }
3596
3597
3598 void BlackboxWindow::changeBlackboxHints(const BlackboxHints *net) {
3599   if ((net->flags & AttribShaded) &&
3600       ((blackbox_attrib.attrib & AttribShaded) !=
3601        (net->attrib & AttribShaded)))
3602     shade();
3603
3604   if (flags.visible && // watch out for requests when we can not be seen
3605       (net->flags & (AttribMaxVert | AttribMaxHoriz)) &&
3606       ((blackbox_attrib.attrib & (AttribMaxVert | AttribMaxHoriz)) !=
3607        (net->attrib & (AttribMaxVert | AttribMaxHoriz)))) {
3608     if (flags.maximized) {
3609       maximize(0);
3610     } else {
3611       int button = 0;
3612
3613       if ((net->flags & AttribMaxHoriz) && (net->flags & AttribMaxVert))
3614         button = ((net->attrib & (AttribMaxHoriz | AttribMaxVert)) ?  1 : 0);
3615       else if (net->flags & AttribMaxVert)
3616         button = ((net->attrib & AttribMaxVert) ? 2 : 0);
3617       else if (net->flags & AttribMaxHoriz)
3618         button = ((net->attrib & AttribMaxHoriz) ? 3 : 0);
3619
3620       maximize(button);
3621     }
3622   }
3623
3624   if ((net->flags & AttribOmnipresent) &&
3625       ((blackbox_attrib.attrib & AttribOmnipresent) !=
3626        (net->attrib & AttribOmnipresent)))
3627     stick();
3628
3629   if ((net->flags & AttribWorkspace) &&
3630       (blackbox_attrib.workspace != net->workspace)) {
3631     screen->reassociateWindow(this, net->workspace, True);
3632
3633     if (screen->getCurrentWorkspaceID() != net->workspace) {
3634       withdraw();
3635     } else {
3636       show();
3637       screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
3638     }
3639   }
3640
3641   if (net->flags & AttribDecoration) {
3642     switch (net->decoration) {
3643     case DecorNone:
3644       decorations = 0;
3645
3646       break;
3647
3648     default:
3649     case DecorNormal:
3650       decorations |= Decor_Titlebar | Decor_Border | Decor_Iconify;
3651   
3652       decorations = ((functions & Func_Resize) && !isTransient() ?
3653                      decorations | Decor_Handle :
3654                      decorations &= ~Decor_Handle);
3655       decorations = (functions & Func_Maximize ?
3656                      decorations | Decor_Maximize :
3657                      decorations &= ~Decor_Maximize);
3658
3659       break;
3660
3661     case DecorTiny:
3662       decorations |= Decor_Titlebar | Decor_Iconify;
3663       decorations &= ~(Decor_Border | Decor_Handle);
3664       
3665       decorations = (functions & Func_Maximize ?
3666                      decorations | Decor_Maximize :
3667                      decorations &= ~Decor_Maximize);
3668
3669       break;
3670
3671     case DecorTool:
3672       decorations |= Decor_Titlebar;
3673       decorations &= ~(Decor_Iconify | Decor_Border);
3674
3675       decorations = ((functions & Func_Resize) && !isTransient() ?
3676                      decorations | Decor_Handle :
3677                      decorations &= ~Decor_Handle);
3678       decorations = (functions & Func_Maximize ?
3679                      decorations | Decor_Maximize :
3680                      decorations &= ~Decor_Maximize);
3681
3682       break;
3683     }
3684
3685     // we can not be shaded if we lack a titlebar
3686     if (flags.shaded && ! (decorations & Decor_Titlebar))
3687       shade();
3688
3689     if (flags.visible && frame.window) {
3690       XMapSubwindows(blackbox->getXDisplay(), frame.window);
3691       XMapWindow(blackbox->getXDisplay(), frame.window);
3692     }
3693
3694     reconfigure();
3695     setState(current_state);
3696   }
3697 }
3698
3699
3700 /*
3701  * Set the sizes of all components of the window frame
3702  * (the window decorations).
3703  * These values are based upon the current style settings and the client
3704  * window's dimensions.
3705  */
3706 void BlackboxWindow::upsize(void) {
3707   frame.bevel_w = screen->getBevelWidth();
3708
3709   if (decorations & Decor_Border) {
3710     frame.border_w = screen->getBorderWidth();
3711     if (! isTransient())
3712       frame.mwm_border_w = screen->getFrameWidth();
3713     else
3714       frame.mwm_border_w = 0;
3715   } else {
3716     frame.mwm_border_w = frame.border_w = 0;
3717   }
3718
3719   if (decorations & Decor_Titlebar) {
3720     // the height of the titlebar is based upon the height of the font being
3721     // used to display the window's title
3722     WindowStyle *style = screen->getWindowStyle();
3723     frame.title_h = style->font->height() + (frame.bevel_w * 2) + 2;
3724
3725     frame.label_h = frame.title_h - (frame.bevel_w * 2);
3726     frame.button_w = (frame.label_h - 2);
3727
3728     // set the top frame margin
3729     frame.margin.top = frame.border_w + frame.title_h +
3730                        frame.border_w + frame.mwm_border_w;
3731   } else {
3732     frame.title_h = 0;
3733     frame.label_h = 0;
3734     frame.button_w = 0;
3735
3736     // set the top frame margin
3737     frame.margin.top = frame.border_w + frame.mwm_border_w;
3738   }
3739
3740   // set the left/right frame margin
3741   frame.margin.left = frame.margin.right = frame.border_w + frame.mwm_border_w;
3742
3743   if (decorations & Decor_Handle) {
3744     frame.grip_w = frame.button_w * 2;
3745     frame.handle_h = screen->getHandleWidth();
3746
3747     // set the bottom frame margin
3748     frame.margin.bottom = frame.border_w + frame.handle_h +
3749                           frame.border_w + frame.mwm_border_w;
3750   } else {
3751     frame.handle_h = 0;
3752     frame.grip_w = 0;
3753
3754     // set the bottom frame margin
3755     frame.margin.bottom = frame.border_w + frame.mwm_border_w;
3756   }
3757
3758   /*
3759     We first get the normal dimensions and use this to define the inside_w/h
3760     then we modify the height if shading is in effect.
3761     If the shade state is not considered then frame.rect gets reset to the
3762     normal window size on a reconfigure() call resulting in improper
3763     dimensions appearing in move/resize and other events.
3764   */
3765   unsigned int
3766     height = client.rect.height() + frame.margin.top + frame.margin.bottom,
3767     width = client.rect.width() + frame.margin.left + frame.margin.right;
3768
3769   frame.inside_w = width - (frame.border_w * 2);
3770   frame.inside_h = height - (frame.border_w * 2);
3771
3772   if (flags.shaded)
3773     height = frame.title_h + (frame.border_w * 2);
3774   frame.rect.setSize(width, height);
3775 }
3776
3777
3778 /*
3779  * Calculate the size of the client window and constrain it to the
3780  * size specified by the size hints of the client window.
3781  *
3782  * The logical width and height are placed into pw and ph, if they
3783  * are non-zero.  Logical size refers to the users perception of
3784  * the window size (for example an xterm resizes in cells, not in pixels).
3785  *
3786  * The physical geometry is placed into frame.changing_{x,y,width,height}.
3787  * Physical geometry refers to the geometry of the window in pixels.
3788  */
3789 void BlackboxWindow::constrain(Corner anchor, int *pw, int *ph) {
3790   // frame.changing represents the requested frame size, we need to
3791   // strip the frame margin off and constrain the client size
3792   frame.changing.setCoords(frame.changing.left() + frame.margin.left,
3793                            frame.changing.top() + frame.margin.top,
3794                            frame.changing.right() - frame.margin.right,
3795                            frame.changing.bottom() - frame.margin.bottom);
3796
3797   int dw = frame.changing.width(), dh = frame.changing.height(),
3798     base_width = (client.base_width) ? client.base_width : client.min_width,
3799     base_height = (client.base_height) ? client.base_height :
3800                                          client.min_height;
3801
3802   // constrain
3803   if (dw < static_cast<signed>(client.min_width)) dw = client.min_width;
3804   if (dh < static_cast<signed>(client.min_height)) dh = client.min_height;
3805   if (dw > static_cast<signed>(client.max_width)) dw = client.max_width;
3806   if (dh > static_cast<signed>(client.max_height)) dh = client.max_height;
3807
3808   dw -= base_width;
3809   dw /= client.width_inc;
3810   dh -= base_height;
3811   dh /= client.height_inc;
3812
3813   if (pw) {
3814     if (client.width_inc == 1)
3815       *pw = dw + base_width;
3816     else
3817       *pw = dw;
3818   }
3819   if (ph) {
3820     if (client.height_inc == 1)
3821       *ph = dh + base_height;
3822     else
3823       *ph = dh;
3824   }
3825
3826   dw *= client.width_inc;
3827   dw += base_width;
3828   dh *= client.height_inc;
3829   dh += base_height;
3830
3831   frame.changing.setSize(dw, dh);
3832
3833   // add the frame margin back onto frame.changing
3834   frame.changing.setCoords(frame.changing.left() - frame.margin.left,
3835                            frame.changing.top() - frame.margin.top,
3836                            frame.changing.right() + frame.margin.right,
3837                            frame.changing.bottom() + frame.margin.bottom);
3838
3839   // move frame.changing to the specified anchor
3840   int dx = 0,
3841       dy = 0;
3842   switch (anchor) {
3843   case TopLeft:
3844     break;
3845
3846   case TopRight:
3847     dx = frame.rect.right() - frame.changing.right();
3848     break;
3849
3850   case BottomLeft:
3851     dy = frame.rect.bottom() - frame.changing.bottom();
3852     break;
3853
3854   case BottomRight:
3855     dx = frame.rect.right() - frame.changing.right();
3856     dy = frame.rect.bottom() - frame.changing.bottom();
3857     break;
3858
3859   default:
3860     assert(false);  // unhandled corner
3861   }
3862   frame.changing.setPos(frame.changing.x() + dx, frame.changing.y() + dy);
3863 }
3864
3865
3866 void WindowStyle::doJustify(const std::string &text, int &start_pos,
3867                             unsigned int max_length,
3868                             unsigned int modifier) const {
3869   size_t text_len = text.size();
3870   unsigned int length;
3871
3872   do {
3873     length = font->measureString(string(text, 0, text_len)) + modifier;
3874   } while (length > max_length && text_len-- > 0);
3875
3876   switch (justify) {
3877   case RightJustify:
3878     start_pos += max_length - length;
3879     break;
3880
3881   case CenterJustify:
3882     start_pos += (max_length - length) / 2;
3883     break;
3884
3885   case LeftJustify:
3886   default:
3887     break;
3888   }
3889 }
3890
3891
3892 BWindowGroup::BWindowGroup(Blackbox *b, Window _group)
3893   : blackbox(b), group(_group) {
3894   XWindowAttributes wattrib;
3895   if (! XGetWindowAttributes(blackbox->getXDisplay(), group, &wattrib)) {
3896     // group window doesn't seem to exist anymore
3897     delete this;
3898     return;
3899   }
3900
3901   XSelectInput(blackbox->getXDisplay(), group,
3902                PropertyChangeMask | FocusChangeMask | StructureNotifyMask);
3903
3904   blackbox->saveGroupSearch(group, this);
3905 }
3906
3907
3908 BWindowGroup::~BWindowGroup(void) {
3909   blackbox->removeGroupSearch(group);
3910 }
3911
3912
3913 BlackboxWindow *
3914 BWindowGroup::find(BScreen *screen, bool allow_transients) const {
3915   BlackboxWindow *ret = blackbox->getFocusedWindow();
3916
3917   // does the focus window match (or any transient_fors)?
3918   while (ret) {
3919     if (ret->getScreen() == screen && ret->getGroupWindow() == group) {
3920       if (ret->isTransient() && allow_transients) break;
3921       else if (! ret->isTransient()) break;
3922     }
3923
3924     ret = ret->getTransientFor();
3925   }
3926
3927   if (ret) return ret;
3928
3929   // the focus window didn't match, look in the group's window list
3930   BlackboxWindowList::const_iterator it, end = windowList.end();
3931   for (it = windowList.begin(); it != end; ++it) {
3932     ret = *it;
3933     if (ret->getScreen() == screen && ret->getGroupWindow() == group) {
3934       if (ret->isTransient() && allow_transients) break;
3935       else if (! ret->isTransient()) break;
3936     }
3937   }
3938
3939   return ret;
3940 }