]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/frame.c
a whole lot of changes to the moving/resizing code. it was broken for non-northwest...
[mikachu/openbox.git] / openbox / frame.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    frame.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "frame.h"
21 #include "client.h"
22 #include "openbox.h"
23 #include "extensions.h"
24 #include "prop.h"
25 #include "config.h"
26 #include "framerender.h"
27 #include "mainloop.h"
28 #include "focus.h"
29 #include "moveresize.h"
30 #include "render/theme.h"
31
32 #define PLATE_EVENTMASK (SubstructureRedirectMask | FocusChangeMask)
33 #define FRAME_EVENTMASK (EnterWindowMask | LeaveWindowMask | \
34                          ButtonPressMask | ButtonReleaseMask)
35 #define ELEMENT_EVENTMASK (ButtonPressMask | ButtonReleaseMask | \
36                            ButtonMotionMask | \
37                            EnterWindowMask | LeaveWindowMask)
38 /* The inner window does not need enter/leave events.
39    If it does get them, then it needs its own context for enter events
40    because sloppy focus will focus the window when you enter the inner window
41    from the frame. */
42 #define INNER_EVENTMASK (ButtonPressMask)
43
44 #define FRAME_HANDLE_Y(f) (f->innersize.top + f->client->area.height + \
45                            f->cbwidth_y)
46
47 static void layout_title(ObFrame *self);
48 static void flash_done(gpointer data);
49 static gboolean flash_timeout(gpointer data);
50
51 static void set_theme_statics(ObFrame *self);
52 static void free_theme_statics(ObFrame *self);
53
54 static Window createWindow(Window parent, Visual *visual,
55                            gulong mask, XSetWindowAttributes *attrib)
56 {
57     return XCreateWindow(ob_display, parent, 0, 0, 1, 1, 0,
58                          (visual ? 32 : RrDepth(ob_rr_inst)), InputOutput,
59                          (visual ? visual : RrVisual(ob_rr_inst)),
60                          mask, attrib);
61                        
62 }
63
64 static Visual *check_32bit_client(ObClient *c)
65 {
66     XWindowAttributes wattrib;
67     Status ret;
68
69     ret = XGetWindowAttributes(ob_display, c->window, &wattrib);
70     g_assert(ret != BadDrawable);
71     g_assert(ret != BadWindow);
72
73     if (wattrib.depth == 32)
74         return wattrib.visual;
75     return NULL;
76 }
77
78 ObFrame *frame_new(ObClient *client)
79 {
80     XSetWindowAttributes attrib;
81     gulong mask;
82     ObFrame *self;
83     Visual *visual;
84
85     self = g_new0(ObFrame, 1);
86
87     visual = check_32bit_client(client);
88
89     /* create the non-visible decor windows */
90
91     mask = CWEventMask;
92     if (visual) {
93         /* client has a 32-bit visual */
94         mask |= CWColormap | CWBackPixel | CWBorderPixel;
95         /* create a colormap with the visual */
96         self->colormap = attrib.colormap =
97             XCreateColormap(ob_display,
98                             RootWindow(ob_display, ob_screen),
99                             visual, AllocNone);
100         attrib.background_pixel = BlackPixel(ob_display, 0);
101         attrib.border_pixel = BlackPixel(ob_display, 0);
102     }
103     attrib.event_mask = FRAME_EVENTMASK;
104     self->window = createWindow(RootWindow(ob_display, ob_screen), visual,
105                                 mask, &attrib);
106
107     attrib.event_mask = INNER_EVENTMASK;
108     self->inner = createWindow(self->window, visual, mask, &attrib);
109
110     mask &= ~CWEventMask;
111     self->plate = createWindow(self->inner, visual, mask, &attrib);
112
113     /* create the visible decor windows */
114
115     mask = CWEventMask;
116     if (visual) {
117         /* client has a 32-bit visual */
118         mask |= CWColormap | CWBackPixel | CWBorderPixel;
119         attrib.colormap = RrColormap(ob_rr_inst);
120     }
121     attrib.event_mask = ELEMENT_EVENTMASK;
122     self->title = createWindow(self->window, NULL, mask, &attrib);
123
124     mask |= CWCursor;
125     attrib.cursor = ob_cursor(OB_CURSOR_NORTHWEST);
126     self->tltresize = createWindow(self->title, NULL, mask, &attrib);
127     self->tllresize = createWindow(self->title, NULL, mask, &attrib);
128     attrib.cursor = ob_cursor(OB_CURSOR_NORTHEAST);
129     self->trtresize = createWindow(self->title, NULL, mask, &attrib);
130     self->trrresize = createWindow(self->title, NULL, mask, &attrib);
131
132     mask &= ~CWCursor;
133     self->label = createWindow(self->title, NULL, mask, &attrib);
134     self->max = createWindow(self->title, NULL, mask, &attrib);
135     self->close = createWindow(self->title, NULL, mask, &attrib);
136     self->desk = createWindow(self->title, NULL, mask, &attrib);
137     self->shade = createWindow(self->title, NULL, mask, &attrib);
138     self->icon = createWindow(self->title, NULL, mask, &attrib);
139     self->iconify = createWindow(self->title, NULL, mask, &attrib);
140     self->handle = createWindow(self->window, NULL, mask, &attrib);
141
142     mask |= CWCursor;
143     attrib.cursor = ob_cursor(OB_CURSOR_SOUTHWEST);
144     self->lgrip = createWindow(self->handle, NULL, mask, &attrib);
145     attrib.cursor = ob_cursor(OB_CURSOR_SOUTHEAST);
146     self->rgrip = createWindow(self->handle, NULL, mask, &attrib); 
147
148     self->focused = FALSE;
149
150     /* the other stuff is shown based on decor settings */
151     XMapWindow(ob_display, self->plate);
152     XMapWindow(ob_display, self->inner);
153     XMapWindow(ob_display, self->lgrip);
154     XMapWindow(ob_display, self->rgrip);
155     XMapWindow(ob_display, self->label);
156
157     self->max_press = self->close_press = self->desk_press = 
158         self->iconify_press = self->shade_press = FALSE;
159     self->max_hover = self->close_hover = self->desk_hover = 
160         self->iconify_hover = self->shade_hover = FALSE;
161
162     set_theme_statics(self);
163
164     return (ObFrame*)self;
165 }
166
167 static void set_theme_statics(ObFrame *self)
168 {
169     /* set colors/appearance/sizes for stuff that doesn't change */
170     XSetWindowBorder(ob_display, self->window,
171                      RrColorPixel(ob_rr_theme->frame_b_color));
172     XSetWindowBorder(ob_display, self->inner,
173                      RrColorPixel(ob_rr_theme->frame_b_color));
174     XSetWindowBorder(ob_display, self->title,
175                      RrColorPixel(ob_rr_theme->frame_b_color));
176     XSetWindowBorder(ob_display, self->handle,
177                      RrColorPixel(ob_rr_theme->frame_b_color));
178     XSetWindowBorder(ob_display, self->rgrip,
179                      RrColorPixel(ob_rr_theme->frame_b_color));
180     XSetWindowBorder(ob_display, self->lgrip,
181                      RrColorPixel(ob_rr_theme->frame_b_color));
182
183     XResizeWindow(ob_display, self->max,
184                   ob_rr_theme->button_size, ob_rr_theme->button_size);
185     XResizeWindow(ob_display, self->iconify,
186                   ob_rr_theme->button_size, ob_rr_theme->button_size);
187     XResizeWindow(ob_display, self->icon,
188                   ob_rr_theme->button_size + 2, ob_rr_theme->button_size + 2);
189     XResizeWindow(ob_display, self->close,
190                   ob_rr_theme->button_size, ob_rr_theme->button_size);
191     XResizeWindow(ob_display, self->desk,
192                   ob_rr_theme->button_size, ob_rr_theme->button_size);
193     XResizeWindow(ob_display, self->shade,
194                   ob_rr_theme->button_size, ob_rr_theme->button_size);
195     if (ob_rr_theme->handle_height > 0) {
196         XResizeWindow(ob_display, self->lgrip,
197                       ob_rr_theme->grip_width, ob_rr_theme->handle_height);
198         XResizeWindow(ob_display, self->rgrip,
199                       ob_rr_theme->grip_width, ob_rr_theme->handle_height);
200     }
201     XResizeWindow(ob_display, self->tltresize,
202                   ob_rr_theme->grip_width, ob_rr_theme->paddingy + 1);
203     XResizeWindow(ob_display, self->trtresize,
204                   ob_rr_theme->grip_width, ob_rr_theme->paddingy + 1);
205     XResizeWindow(ob_display, self->tllresize,
206                   ob_rr_theme->paddingx + 1, ob_rr_theme->title_height);
207     XResizeWindow(ob_display, self->trrresize,
208                   ob_rr_theme->paddingx + 1, ob_rr_theme->title_height);
209
210     /* set up the dynamic appearances */
211     self->a_unfocused_title = RrAppearanceCopy(ob_rr_theme->a_unfocused_title);
212     self->a_focused_title = RrAppearanceCopy(ob_rr_theme->a_focused_title);
213     self->a_unfocused_label = RrAppearanceCopy(ob_rr_theme->a_unfocused_label);
214     self->a_focused_label = RrAppearanceCopy(ob_rr_theme->a_focused_label);
215     self->a_unfocused_handle =
216         RrAppearanceCopy(ob_rr_theme->a_unfocused_handle);
217     self->a_focused_handle = RrAppearanceCopy(ob_rr_theme->a_focused_handle);
218     self->a_icon = RrAppearanceCopy(ob_rr_theme->a_icon);
219 }
220
221 static void free_theme_statics(ObFrame *self)
222 {
223     RrAppearanceFree(self->a_unfocused_title); 
224     RrAppearanceFree(self->a_focused_title);
225     RrAppearanceFree(self->a_unfocused_label);
226     RrAppearanceFree(self->a_focused_label);
227     RrAppearanceFree(self->a_unfocused_handle);
228     RrAppearanceFree(self->a_focused_handle);
229     RrAppearanceFree(self->a_icon);
230 }
231
232 static void frame_free(ObFrame *self)
233 {
234     free_theme_statics(self);
235
236     XDestroyWindow(ob_display, self->window);
237     if (self->colormap)
238         XFreeColormap(ob_display, self->colormap);
239
240     g_free(self);
241 }
242
243 void frame_show(ObFrame *self)
244 {
245     if (!self->visible) {
246         self->visible = TRUE;
247         XMapWindow(ob_display, self->client->window);
248         XMapWindow(ob_display, self->window);
249     }
250 }
251
252 void frame_hide(ObFrame *self)
253 {
254     if (self->visible) {
255         self->visible = FALSE;
256         self->client->ignore_unmaps += 1;
257         /* we unmap the client itself so that we can get MapRequest
258            events, and because the ICCCM tells us to! */
259         XUnmapWindow(ob_display, self->window);
260         XUnmapWindow(ob_display, self->client->window);
261     }
262 }
263
264 void frame_adjust_theme(ObFrame *self)
265 {
266     free_theme_statics(self);
267     set_theme_statics(self);
268 }
269
270 void frame_adjust_shape(ObFrame *self)
271 {
272 #ifdef SHAPE
273     gint num;
274     XRectangle xrect[2];
275
276     if (!self->client->shaped) {
277         /* clear the shape on the frame window */
278         XShapeCombineMask(ob_display, self->window, ShapeBounding,
279                           self->innersize.left,
280                           self->innersize.top,
281                           None, ShapeSet);
282     } else {
283         /* make the frame's shape match the clients */
284         XShapeCombineShape(ob_display, self->window, ShapeBounding,
285                            self->innersize.left,
286                            self->innersize.top,
287                            self->client->window,
288                            ShapeBounding, ShapeSet);
289
290         num = 0;
291         if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
292             xrect[0].x = -ob_rr_theme->fbwidth;
293             xrect[0].y = -ob_rr_theme->fbwidth;
294             xrect[0].width = self->width + self->rbwidth * 2;
295             xrect[0].height = ob_rr_theme->title_height +
296                 self->bwidth * 2;
297             ++num;
298         }
299
300         if (self->decorations & OB_FRAME_DECOR_HANDLE) {
301             xrect[1].x = -ob_rr_theme->fbwidth;
302             xrect[1].y = FRAME_HANDLE_Y(self);
303             xrect[1].width = self->width + self->rbwidth * 2;
304             xrect[1].height = ob_rr_theme->handle_height +
305                 self->bwidth * 2;
306             ++num;
307         }
308
309         XShapeCombineRectangles(ob_display, self->window,
310                                 ShapeBounding, 0, 0, xrect, num,
311                                 ShapeUnion, Unsorted);
312     }
313 #endif
314 }
315
316 void frame_adjust_area(ObFrame *self, gboolean moved,
317                        gboolean resized, gboolean fake)
318 {
319     Strut oldsize;
320
321     oldsize = self->size;
322
323     if (resized) {
324         self->decorations = self->client->decorations;
325         self->max_horz = self->client->max_horz;
326
327         if (self->decorations & OB_FRAME_DECOR_BORDER) {
328             self->bwidth = ob_rr_theme->fbwidth;
329             self->cbwidth_x = ob_rr_theme->cbwidthx;
330             self->cbwidth_y = ob_rr_theme->cbwidthy;
331         } else {
332             self->bwidth = self->cbwidth_x = self->cbwidth_y = 0;
333         }
334         self->rbwidth = self->bwidth;
335
336         if (self->max_horz)
337             self->bwidth = self->cbwidth_x = 0;
338
339         STRUT_SET(self->innersize,
340                   self->cbwidth_x,
341                   self->cbwidth_y,
342                   self->cbwidth_x,
343                   self->cbwidth_y);
344         self->width = self->client->area.width + self->cbwidth_x * 2 -
345             (self->max_horz ? self->rbwidth * 2 : 0);
346         self->width = MAX(self->width, 1); /* no lower than 1 */
347
348         /* set border widths */
349         if (!fake) {
350             XSetWindowBorderWidth(ob_display, self->window, self->bwidth);
351             XSetWindowBorderWidth(ob_display, self->inner, self->bwidth);
352             XSetWindowBorderWidth(ob_display, self->title,  self->rbwidth);
353             XSetWindowBorderWidth(ob_display, self->handle, self->rbwidth);
354             XSetWindowBorderWidth(ob_display, self->lgrip,  self->rbwidth);
355             XSetWindowBorderWidth(ob_display, self->rgrip,  self->rbwidth);
356         }
357
358         if (self->decorations & OB_FRAME_DECOR_TITLEBAR)
359             self->innersize.top += ob_rr_theme->title_height + self->rbwidth +
360                 (self->rbwidth - self->bwidth);
361         if (self->decorations & OB_FRAME_DECOR_HANDLE &&
362             ob_rr_theme->handle_height > 0)
363             self->innersize.bottom += ob_rr_theme->handle_height +
364                 self->rbwidth + (self->rbwidth - self->bwidth);
365   
366         /* they all default off, they're turned on in layout_title */
367         self->icon_x = -1;
368         self->desk_x = -1;
369         self->shade_x = -1;
370         self->iconify_x = -1;
371         self->label_x = -1;
372         self->max_x = -1;
373         self->close_x = -1;
374
375         /* position/size and map/unmap all the windows */
376
377         if (!fake) {
378             if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
379                 XMoveResizeWindow(ob_display, self->title,
380                                   -self->bwidth, -self->bwidth,
381                                   self->width, ob_rr_theme->title_height);
382                 XMapWindow(ob_display, self->title);
383
384                 if (self->decorations & OB_FRAME_DECOR_GRIPS) {
385                     XMoveWindow(ob_display, self->tltresize, 0, 0);
386                     XMoveWindow(ob_display, self->tllresize, 0, 0);
387                     XMoveWindow(ob_display, self->trtresize,
388                                 self->width - ob_rr_theme->grip_width, 0);
389                     XMoveWindow(ob_display, self->trrresize,
390                                 self->width - ob_rr_theme->paddingx - 1, 0);
391                     XMapWindow(ob_display, self->tltresize);
392                     XMapWindow(ob_display, self->tllresize);
393                     XMapWindow(ob_display, self->trtresize);
394                     XMapWindow(ob_display, self->trrresize);
395                 } else {
396                     XUnmapWindow(ob_display, self->tltresize);
397                     XUnmapWindow(ob_display, self->tllresize);
398                     XUnmapWindow(ob_display, self->trtresize);
399                     XUnmapWindow(ob_display, self->trrresize);
400                 }
401             } else
402                 XUnmapWindow(ob_display, self->title);
403         }
404
405         if (self->decorations & OB_FRAME_DECOR_TITLEBAR)
406             /* layout the title bar elements */
407             layout_title(self);
408
409         if (!fake) {
410             if (self->decorations & OB_FRAME_DECOR_HANDLE &&
411                 ob_rr_theme->handle_height > 0)
412             {
413                 XMoveResizeWindow(ob_display, self->handle,
414                                   -self->bwidth, FRAME_HANDLE_Y(self),
415                                   self->width, ob_rr_theme->handle_height);
416                 XMapWindow(ob_display, self->handle);
417
418                 if (self->decorations & OB_FRAME_DECOR_GRIPS) {
419                     XMoveWindow(ob_display, self->lgrip,
420                                 -self->rbwidth, -self->rbwidth);
421                     XMoveWindow(ob_display, self->rgrip,
422                                 -self->rbwidth + self->width -
423                                 ob_rr_theme->grip_width, -self->rbwidth);
424                     XMapWindow(ob_display, self->lgrip);
425                     XMapWindow(ob_display, self->rgrip);
426                 } else {
427                     XUnmapWindow(ob_display, self->lgrip);
428                     XUnmapWindow(ob_display, self->rgrip);
429                 }
430             } else
431                 XUnmapWindow(ob_display, self->handle);
432
433             /* move and resize the inner border window which contains the plate
434              */
435             XMoveResizeWindow(ob_display, self->inner,
436                               self->innersize.left - self->cbwidth_x -
437                               self->bwidth,
438                               self->innersize.top - self->cbwidth_y -
439                               self->bwidth,
440                               self->client->area.width +
441                               self->cbwidth_x * 2,
442                               self->client->area.height +
443                               self->cbwidth_y * 2);
444
445             /* move and resize the plate */
446             XMoveResizeWindow(ob_display, self->plate,
447                               self->cbwidth_x, self->cbwidth_y,
448                               self->client->area.width,
449                               self->client->area.height);
450
451             /* when the client has StaticGravity, it likes to move around. */
452             XMoveWindow(ob_display, self->client->window, 0, 0);
453         }
454
455         STRUT_SET(self->size,
456                   self->innersize.left + self->bwidth,
457                   self->innersize.top + self->bwidth,
458                   self->innersize.right + self->bwidth,
459                   self->innersize.bottom + self->bwidth);
460     }
461
462     /* shading can change without being moved or resized */
463     RECT_SET_SIZE(self->area,
464                   self->client->area.width +
465                   self->size.left + self->size.right,
466                   (self->client->shaded ?
467                    ob_rr_theme->title_height + self->rbwidth * 2:
468                    self->client->area.height +
469                    self->size.top + self->size.bottom));
470
471     if (moved || resized) {
472         /* find the new coordinates, done after setting the frame.size, for
473            frame_client_gravity. */
474         self->area.x = self->client->area.x;
475         self->area.y = self->client->area.y;
476         frame_client_gravity(self, &self->area.x, &self->area.y,
477                              self->client->area.width,
478                              self->client->area.height);
479     }
480
481     if (!fake) {
482         /* move and resize the top level frame.
483            shading can change without being moved or resized */
484         XMoveResizeWindow(ob_display, self->window,
485                           self->area.x, self->area.y,
486                           self->area.width - self->bwidth * 2,
487                           self->area.height - self->bwidth * 2);
488
489         if (resized) {
490             framerender_frame(self);
491             frame_adjust_shape(self);
492         }
493
494         if (!STRUT_EQUAL(self->size, oldsize)) {
495             gulong vals[4];
496             vals[0] = self->size.left;
497             vals[1] = self->size.right;
498             vals[2] = self->size.top;
499             vals[3] = self->size.bottom;
500             PROP_SETA32(self->client->window, net_frame_extents,
501                         cardinal, vals, 4);
502         }
503
504         /* if this occurs while we are focus cycling, the indicator needs to
505            match the changes */
506         if (focus_cycle_target == self->client)
507             focus_cycle_draw_indicator();
508     }
509     if (resized && (self->decorations & OB_FRAME_DECOR_TITLEBAR))
510         XResizeWindow(ob_display, self->label, self->label_width,
511                       ob_rr_theme->label_height);
512 }
513
514 void frame_adjust_state(ObFrame *self)
515 {
516     framerender_frame(self);
517 }
518
519 void frame_adjust_focus(ObFrame *self, gboolean hilite)
520 {
521     self->focused = hilite;
522     framerender_frame(self);
523     XFlush(ob_display);
524 }
525
526 void frame_adjust_title(ObFrame *self)
527 {
528     framerender_frame(self);
529 }
530
531 void frame_adjust_icon(ObFrame *self)
532 {
533     framerender_frame(self);
534 }
535
536 void frame_grab_client(ObFrame *self, ObClient *client)
537 {
538     self->client = client;
539
540     /* reparent the client to the frame */
541     XReparentWindow(ob_display, client->window, self->plate, 0, 0);
542     /*
543       When reparenting the client window, it is usually not mapped yet, since
544       this occurs from a MapRequest. However, in the case where Openbox is
545       starting up, the window is already mapped, so we'll see unmap events for
546       it. There are 2 unmap events generated that we see, one with the 'event'
547       member set the root window, and one set to the client, but both get
548       handled and need to be ignored.
549     */
550     if (ob_state() == OB_STATE_STARTING)
551         client->ignore_unmaps += 2;
552
553     /* select the event mask on the client's parent (to receive config/map
554        req's) the ButtonPress is to catch clicks on the client border */
555     XSelectInput(ob_display, self->plate, PLATE_EVENTMASK);
556
557     frame_adjust_area(self, TRUE, TRUE, FALSE);
558
559     /* map the client so it maps when the frame does */
560     XMapWindow(ob_display, client->window);
561
562     /* set all the windows for the frame in the window_map */
563     g_hash_table_insert(window_map, &self->window, client);
564     g_hash_table_insert(window_map, &self->plate, client);
565     g_hash_table_insert(window_map, &self->inner, client);
566     g_hash_table_insert(window_map, &self->title, client);
567     g_hash_table_insert(window_map, &self->label, client);
568     g_hash_table_insert(window_map, &self->max, client);
569     g_hash_table_insert(window_map, &self->close, client);
570     g_hash_table_insert(window_map, &self->desk, client);
571     g_hash_table_insert(window_map, &self->shade, client);
572     g_hash_table_insert(window_map, &self->icon, client);
573     g_hash_table_insert(window_map, &self->iconify, client);
574     g_hash_table_insert(window_map, &self->handle, client);
575     g_hash_table_insert(window_map, &self->lgrip, client);
576     g_hash_table_insert(window_map, &self->rgrip, client);
577     g_hash_table_insert(window_map, &self->tltresize, client);
578     g_hash_table_insert(window_map, &self->tllresize, client);
579     g_hash_table_insert(window_map, &self->trtresize, client);
580     g_hash_table_insert(window_map, &self->trrresize, client);
581 }
582
583 void frame_release_client(ObFrame *self, ObClient *client)
584 {
585     XEvent ev;
586     gboolean reparent = TRUE;
587
588     g_assert(self->client == client);
589
590     /* check if the app has already reparented its window away */
591     while (XCheckTypedWindowEvent(ob_display, client->window,
592                                   ReparentNotify, &ev))
593     {
594         /* This check makes sure we don't catch our own reparent action to
595            our frame window. This doesn't count as the app reparenting itself
596            away of course.
597
598            Reparent events that are generated by us are just discarded here.
599            They are of no consequence to us anyhow.
600         */
601         if (ev.xreparent.parent != self->plate) {
602             reparent = FALSE;
603             XPutBackEvent(ob_display, &ev);
604             break;
605         }
606     }
607
608     if (reparent) {
609         /* according to the ICCCM - if the client doesn't reparent itself,
610            then we will reparent the window to root for them */
611         XReparentWindow(ob_display, client->window,
612                         RootWindow(ob_display, ob_screen),
613                         client->area.x,
614                         client->area.y);
615     }
616
617     /* remove all the windows for the frame from the window_map */
618     g_hash_table_remove(window_map, &self->window);
619     g_hash_table_remove(window_map, &self->plate);
620     g_hash_table_remove(window_map, &self->inner);
621     g_hash_table_remove(window_map, &self->title);
622     g_hash_table_remove(window_map, &self->label);
623     g_hash_table_remove(window_map, &self->max);
624     g_hash_table_remove(window_map, &self->close);
625     g_hash_table_remove(window_map, &self->desk);
626     g_hash_table_remove(window_map, &self->shade);
627     g_hash_table_remove(window_map, &self->icon);
628     g_hash_table_remove(window_map, &self->iconify);
629     g_hash_table_remove(window_map, &self->handle);
630     g_hash_table_remove(window_map, &self->lgrip);
631     g_hash_table_remove(window_map, &self->rgrip);
632     g_hash_table_remove(window_map, &self->tltresize);
633     g_hash_table_remove(window_map, &self->tllresize);
634     g_hash_table_remove(window_map, &self->trtresize);
635     g_hash_table_remove(window_map, &self->trrresize);
636
637     ob_main_loop_timeout_remove_data(ob_main_loop, flash_timeout, self, TRUE);
638
639     frame_free(self);
640 }
641
642 static void layout_title(ObFrame *self)
643 {
644     gchar *lc;
645     gint x;
646     gboolean n, d, i, l, m, c, s;
647
648     n = d = i = l = m = c = s = FALSE;
649
650     /* figure out whats being shown, and the width of the label */
651     self->label_width = self->width - (ob_rr_theme->paddingx + 1) * 2;
652     for (lc = config_title_layout; *lc != '\0'; ++lc) {
653         switch (*lc) {
654         case 'N':
655             if (n) { *lc = ' '; break; } /* rm duplicates */
656             n = TRUE;
657             self->label_width -= (ob_rr_theme->button_size + 2 +
658                                   ob_rr_theme->paddingx + 1);
659             break;
660         case 'D':
661             if (d) { *lc = ' '; break; }
662             if (!(self->decorations & OB_FRAME_DECOR_ALLDESKTOPS)
663                 && config_theme_hidedisabled)
664                 break;
665             d = TRUE;
666             self->label_width -= (ob_rr_theme->button_size +
667                                   ob_rr_theme->paddingx + 1);
668             break;
669         case 'S':
670             if (s) { *lc = ' '; break; }
671             if (!(self->decorations & OB_FRAME_DECOR_SHADE)
672                 && config_theme_hidedisabled)
673                 break;
674             s = TRUE;
675             self->label_width -= (ob_rr_theme->button_size +
676                                   ob_rr_theme->paddingx + 1);
677             break;
678         case 'I':
679             if (i) { *lc = ' '; break; }
680             if (!(self->decorations & OB_FRAME_DECOR_ICONIFY)
681                 && config_theme_hidedisabled)
682                 break;
683             i = TRUE;
684             self->label_width -= (ob_rr_theme->button_size +
685                                   ob_rr_theme->paddingx + 1);
686             break;
687         case 'L':
688             if (l) { *lc = ' '; break; }
689             l = TRUE;
690             break;
691         case 'M':
692             if (m) { *lc = ' '; break; }
693             if (!(self->decorations & OB_FRAME_DECOR_MAXIMIZE)
694                 && config_theme_hidedisabled)
695                 break;
696             m = TRUE;
697             self->label_width -= (ob_rr_theme->button_size +
698                                   ob_rr_theme->paddingx + 1);
699             break;
700         case 'C':
701             if (c) { *lc = ' '; break; }
702             if (!(self->decorations & OB_FRAME_DECOR_CLOSE)
703                 && config_theme_hidedisabled)
704                 break;
705             c = TRUE;
706             self->label_width -= (ob_rr_theme->button_size +
707                                   ob_rr_theme->paddingx + 1);
708             break;
709         }
710     }
711     if (self->label_width < 1) self->label_width = 1;
712
713     if (!n) XUnmapWindow(ob_display, self->icon);
714     if (!d) XUnmapWindow(ob_display, self->desk);
715     if (!s) XUnmapWindow(ob_display, self->shade);
716     if (!i) XUnmapWindow(ob_display, self->iconify);
717     if (!l) XUnmapWindow(ob_display, self->label);
718     if (!m) XUnmapWindow(ob_display, self->max);
719     if (!c) XUnmapWindow(ob_display, self->close);
720
721     x = ob_rr_theme->paddingx + 1;
722     for (lc = config_title_layout; *lc != '\0'; ++lc) {
723         switch (*lc) {
724         case 'N':
725             if (!n) break;
726             self->icon_x = x;
727             XMapWindow(ob_display, self->icon);
728             XMoveWindow(ob_display, self->icon, x, ob_rr_theme->paddingy);
729             x += ob_rr_theme->button_size + 2 + ob_rr_theme->paddingx + 1;
730             break;
731         case 'D':
732             if (!d) break;
733             self->desk_x = x;
734             XMapWindow(ob_display, self->desk);
735             XMoveWindow(ob_display, self->desk, x, ob_rr_theme->paddingy + 1);
736             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
737             break;
738         case 'S':
739             if (!s) break;
740             self->shade_x = x;
741             XMapWindow(ob_display, self->shade);
742             XMoveWindow(ob_display, self->shade, x, ob_rr_theme->paddingy + 1);
743             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
744             break;
745         case 'I':
746             if (!i) break;
747             self->iconify_x = x;
748             XMapWindow(ob_display, self->iconify);
749             XMoveWindow(ob_display,self->iconify, x, ob_rr_theme->paddingy + 1);
750             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
751             break;
752         case 'L':
753             if (!l) break;
754             self->label_x = x;
755             XMapWindow(ob_display, self->label);
756             XMoveWindow(ob_display, self->label, x, ob_rr_theme->paddingy);
757             x += self->label_width + ob_rr_theme->paddingx + 1;
758             break;
759         case 'M':
760             if (!m) break;
761             self->max_x = x;
762             XMapWindow(ob_display, self->max);
763             XMoveWindow(ob_display, self->max, x, ob_rr_theme->paddingy + 1);
764             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
765             break;
766         case 'C':
767             if (!c) break;
768             self->close_x = x;
769             XMapWindow(ob_display, self->close);
770             XMoveWindow(ob_display, self->close, x, ob_rr_theme->paddingy + 1);
771             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
772             break;
773         }
774     }
775 }
776
777 ObFrameContext frame_context_from_string(const gchar *name)
778 {
779     if (!g_ascii_strcasecmp("Desktop", name))
780         return OB_FRAME_CONTEXT_DESKTOP;
781     else if (!g_ascii_strcasecmp("Client", name))
782         return OB_FRAME_CONTEXT_CLIENT;
783     else if (!g_ascii_strcasecmp("Titlebar", name))
784         return OB_FRAME_CONTEXT_TITLEBAR;
785     else if (!g_ascii_strcasecmp("Handle", name))
786         return OB_FRAME_CONTEXT_HANDLE;
787     else if (!g_ascii_strcasecmp("Frame", name))
788         return OB_FRAME_CONTEXT_FRAME;
789     else if (!g_ascii_strcasecmp("TLCorner", name))
790         return OB_FRAME_CONTEXT_TLCORNER;
791     else if (!g_ascii_strcasecmp("TRCorner", name))
792         return OB_FRAME_CONTEXT_TRCORNER;
793     else if (!g_ascii_strcasecmp("BLCorner", name))
794         return OB_FRAME_CONTEXT_BLCORNER;
795     else if (!g_ascii_strcasecmp("BRCorner", name))
796         return OB_FRAME_CONTEXT_BRCORNER;
797     else if (!g_ascii_strcasecmp("Maximize", name))
798         return OB_FRAME_CONTEXT_MAXIMIZE;
799     else if (!g_ascii_strcasecmp("AllDesktops", name))
800         return OB_FRAME_CONTEXT_ALLDESKTOPS;
801     else if (!g_ascii_strcasecmp("Shade", name))
802         return OB_FRAME_CONTEXT_SHADE;
803     else if (!g_ascii_strcasecmp("Iconify", name))
804         return OB_FRAME_CONTEXT_ICONIFY;
805     else if (!g_ascii_strcasecmp("Icon", name))
806         return OB_FRAME_CONTEXT_ICON;
807     else if (!g_ascii_strcasecmp("Close", name))
808         return OB_FRAME_CONTEXT_CLOSE;
809     else if (!g_ascii_strcasecmp("MoveResize", name))
810         return OB_FRAME_CONTEXT_MOVE_RESIZE;
811     return OB_FRAME_CONTEXT_NONE;
812 }
813
814 ObFrameContext frame_context(ObClient *client, Window win)
815 {
816     ObFrame *self;
817
818     if (moveresize_in_progress)
819         return OB_FRAME_CONTEXT_MOVE_RESIZE;
820
821     if (win == RootWindow(ob_display, ob_screen))
822         return OB_FRAME_CONTEXT_DESKTOP;
823     if (client == NULL) return OB_FRAME_CONTEXT_NONE;
824     if (win == client->window) {
825         /* conceptually, this is the desktop, as far as users are
826            concerned */
827         if (client->type == OB_CLIENT_TYPE_DESKTOP)
828             return OB_FRAME_CONTEXT_DESKTOP;
829         return OB_FRAME_CONTEXT_CLIENT;
830     }
831
832     self = client->frame;
833     if (win == self->inner || win == self->plate) {
834         /* conceptually, this is the desktop, as far as users are
835            concerned */
836         if (client->type == OB_CLIENT_TYPE_DESKTOP)
837             return OB_FRAME_CONTEXT_DESKTOP;
838         return OB_FRAME_CONTEXT_CLIENT;
839     }
840
841     if (win == self->window)    return OB_FRAME_CONTEXT_FRAME;
842     if (win == self->title)     return OB_FRAME_CONTEXT_TITLEBAR;
843     if (win == self->label)     return OB_FRAME_CONTEXT_TITLEBAR;
844     if (win == self->handle)    return OB_FRAME_CONTEXT_HANDLE;
845     if (win == self->lgrip)     return OB_FRAME_CONTEXT_BLCORNER;
846     if (win == self->rgrip)     return OB_FRAME_CONTEXT_BRCORNER;
847     if (win == self->tltresize) return OB_FRAME_CONTEXT_TLCORNER;
848     if (win == self->tllresize) return OB_FRAME_CONTEXT_TLCORNER;
849     if (win == self->trtresize) return OB_FRAME_CONTEXT_TRCORNER;
850     if (win == self->trrresize) return OB_FRAME_CONTEXT_TRCORNER;
851     if (win == self->max)       return OB_FRAME_CONTEXT_MAXIMIZE;
852     if (win == self->iconify)   return OB_FRAME_CONTEXT_ICONIFY;
853     if (win == self->close)     return OB_FRAME_CONTEXT_CLOSE;
854     if (win == self->icon)      return OB_FRAME_CONTEXT_ICON;
855     if (win == self->desk)      return OB_FRAME_CONTEXT_ALLDESKTOPS;
856     if (win == self->shade)     return OB_FRAME_CONTEXT_SHADE;
857
858     return OB_FRAME_CONTEXT_NONE;
859 }
860
861 void frame_client_gravity(ObFrame *self, gint *x, gint *y, gint w, gint h)
862 {
863     /* horizontal */
864     switch (self->client->gravity) {
865     default:
866     case NorthWestGravity:
867     case SouthWestGravity:
868     case WestGravity:
869         break;
870
871     case NorthGravity:
872     case SouthGravity:
873     case CenterGravity:
874         *x -= (self->size.left + w) / 2;
875         break;
876
877     case NorthEastGravity:
878     case SouthEastGravity:
879     case EastGravity:
880         *x -= (self->size.left + self->size.right + w) - 1;
881         break;
882
883     case ForgetGravity:
884     case StaticGravity:
885         *x -= self->size.left;
886         break;
887     }
888
889     /* vertical */
890     switch (self->client->gravity) {
891     default:
892     case NorthWestGravity:
893     case NorthEastGravity:
894     case NorthGravity:
895         break;
896
897     case CenterGravity:
898     case EastGravity:
899     case WestGravity:
900         *y -= (self->size.top + h) / 2;
901         break;
902
903     case SouthWestGravity:
904     case SouthEastGravity:
905     case SouthGravity:
906         *y -= (self->size.top + self->size.bottom + h) - 1;
907         break;
908
909     case ForgetGravity:
910     case StaticGravity:
911         *y -= self->size.top;
912         break;
913     }
914 }
915
916 void frame_frame_gravity(ObFrame *self, gint *x, gint *y, gint w, gint h)
917 {
918     /* horizontal */
919     switch (self->client->gravity) {
920     default:
921     case NorthWestGravity:
922     case WestGravity:
923     case SouthWestGravity:
924         break;
925     case NorthGravity:
926     case CenterGravity:
927     case SouthGravity:
928         *x += (self->size.left + w) / 2;
929         break;
930     case NorthEastGravity:
931     case EastGravity:
932     case SouthEastGravity:
933         *x += (self->size.left + self->size.right + w) - 1;
934         break;
935     case StaticGravity:
936     case ForgetGravity:
937         *x += self->size.left;
938         break;
939     }
940
941     /* vertical */
942     switch (self->client->gravity) {
943     default:
944     case NorthWestGravity:
945     case NorthGravity:
946     case NorthEastGravity:
947         break;
948     case WestGravity:
949     case CenterGravity:
950     case EastGravity:
951         *y += (self->size.top + h) / 2;
952         break;
953     case SouthWestGravity:
954     case SouthGravity:
955     case SouthEastGravity:
956         *y += (self->size.top + self->size.bottom + h) - 1;
957         break;
958     case StaticGravity:
959     case ForgetGravity:
960         *y += self->size.top;
961         break;
962     }
963 }
964
965 static void flash_done(gpointer data)
966 {
967     ObFrame *self = data;
968
969     if (self->focused != self->flash_on)
970         frame_adjust_focus(self, self->focused);
971 }
972
973 static gboolean flash_timeout(gpointer data)
974 {
975     ObFrame *self = data;
976     GTimeVal now;
977
978     g_get_current_time(&now);
979     if (now.tv_sec > self->flash_end.tv_sec ||
980         (now.tv_sec == self->flash_end.tv_sec &&
981          now.tv_usec >= self->flash_end.tv_usec))
982         self->flashing = FALSE;
983
984     if (!self->flashing)
985         return FALSE; /* we are done */
986
987     self->flash_on = !self->flash_on;
988     if (!self->focused) {
989         frame_adjust_focus(self, self->flash_on);
990         self->focused = FALSE;
991     }
992
993     return TRUE; /* go again */
994 }
995
996 void frame_flash_start(ObFrame *self)
997 {
998     self->flash_on = self->focused;
999
1000     if (!self->flashing)
1001         ob_main_loop_timeout_add(ob_main_loop,
1002                                  G_USEC_PER_SEC * 0.6,
1003                                  flash_timeout,
1004                                  self,
1005                                  g_direct_equal,
1006                                  flash_done);
1007     g_get_current_time(&self->flash_end);
1008     g_time_val_add(&self->flash_end, G_USEC_PER_SEC * 5);
1009     
1010     self->flashing = TRUE;
1011 }
1012
1013 void frame_flash_stop(ObFrame *self)
1014 {
1015     self->flashing = FALSE;
1016 }