some focus fixes. always set the new focus when we fallback or else weird states...
[dana/openbox.git] / openbox / dock.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    dock.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 "debug.h"
21 #include "dock.h"
22 #include "mainloop.h"
23 #include "screen.h"
24 #include "prop.h"
25 #include "config.h"
26 #include "grab.h"
27 #include "openbox.h"
28 #include "render/theme.h"
29
30 #define DOCK_EVENT_MASK (ButtonPressMask | ButtonReleaseMask | \
31                          EnterWindowMask | LeaveWindowMask | FocusChangeMask)
32 #define DOCKAPP_EVENT_MASK (StructureNotifyMask)
33
34 static ObDock *dock;
35
36 StrutPartial dock_strut;
37
38 static void dock_app_grab_button(ObDockApp *app, gboolean grab)
39 {
40     if (grab) {
41         grab_button_full(config_dock_app_move_button,
42                          config_dock_app_move_modifiers, app->icon_win,
43                          ButtonPressMask | ButtonReleaseMask |
44                          ButtonMotionMask,
45                          GrabModeAsync, OB_CURSOR_MOVE);
46     } else {
47         ungrab_button(config_dock_app_move_button,
48                       config_dock_app_move_modifiers, app->icon_win);
49     }
50 }
51
52 void dock_startup(gboolean reconfig)
53 {
54     XSetWindowAttributes attrib;
55
56     if (reconfig) {
57         GList *it;
58
59         XSetWindowBorder(ob_display, dock->frame,
60                          RrColorPixel(ob_rr_theme->
61                                       frame_focused_border_color));
62         XSetWindowBorderWidth(ob_display, dock->frame, ob_rr_theme->fbwidth);
63
64         RrAppearanceFree(dock->a_frame);
65         dock->a_frame = RrAppearanceCopy(ob_rr_theme->a_focused_title);
66
67         stacking_add(DOCK_AS_WINDOW(dock));
68
69         dock_configure();
70         dock_hide(TRUE);
71
72         for (it = dock->dock_apps; it; it = g_list_next(it))
73             dock_app_grab_button(it->data, TRUE);
74         return;
75     }
76
77     STRUT_PARTIAL_SET(dock_strut, 0, 0, 0, 0,
78                       0, 0, 0, 0, 0, 0, 0, 0);
79
80     dock = g_new0(ObDock, 1);
81     dock->obwin.type = Window_Dock;
82
83     dock->hidden = TRUE;
84
85     attrib.event_mask = DOCK_EVENT_MASK;
86     attrib.override_redirect = True;
87     dock->frame = XCreateWindow(ob_display, RootWindow(ob_display, ob_screen),
88                                 0, 0, 1, 1, 0,
89                                 RrDepth(ob_rr_inst), InputOutput,
90                                 RrVisual(ob_rr_inst),
91                                 CWOverrideRedirect | CWEventMask,
92                                 &attrib);
93     dock->a_frame = RrAppearanceCopy(ob_rr_theme->a_focused_title);
94     XSetWindowBorder(ob_display, dock->frame,
95                      RrColorPixel(ob_rr_theme->frame_focused_border_color));
96     XSetWindowBorderWidth(ob_display, dock->frame, ob_rr_theme->fbwidth);
97
98     g_hash_table_insert(window_map, &dock->frame, dock);
99     stacking_add(DOCK_AS_WINDOW(dock));
100 }
101
102 void dock_shutdown(gboolean reconfig)
103 {
104     if (reconfig) {
105         GList *it;
106
107         stacking_remove(DOCK_AS_WINDOW(dock));
108
109         for (it = dock->dock_apps; it; it = g_list_next(it))
110             dock_app_grab_button(it->data, FALSE);
111         return;
112     }
113
114     XDestroyWindow(ob_display, dock->frame);
115     RrAppearanceFree(dock->a_frame);
116     g_hash_table_remove(window_map, &dock->frame);
117     stacking_remove(dock);
118 }
119
120 void dock_add(Window win, XWMHints *wmhints)
121 {
122     ObDockApp *app;
123     XWindowAttributes attrib;
124     gchar **data;
125
126     app = g_new0(ObDockApp, 1);
127     app->obwin.type = Window_DockApp;
128     app->win = win;
129     app->icon_win = (wmhints->flags & IconWindowHint) ?
130         wmhints->icon_window : win;
131
132     if (PROP_GETSS(app->win, wm_class, locale, &data)) {
133         if (data[0]) {
134             app->name = g_strdup(data[0]);
135             if (data[1])
136                 app->class = g_strdup(data[1]);
137         }
138         g_strfreev(data);     
139     }
140
141     if (app->name == NULL) app->name = g_strdup("");
142     if (app->class == NULL) app->class = g_strdup("");
143     
144     if (XGetWindowAttributes(ob_display, app->icon_win, &attrib)) {
145         app->w = attrib.width;
146         app->h = attrib.height;
147     } else {
148         app->w = app->h = 64;
149     }
150
151     dock->dock_apps = g_list_append(dock->dock_apps, app);
152     dock_configure();
153
154     XReparentWindow(ob_display, app->icon_win, dock->frame, app->x, app->y);
155     /*
156       This is the same case as in frame.c for client windows. When Openbox is
157       starting, the window is already mapped so we see unmap events occur for
158       it. There are 2 unmap events generated that we see, one with the 'event'
159       member set the root window, and one set to the client, but both get
160       handled and need to be ignored.
161     */
162     if (ob_state() == OB_STATE_STARTING)
163         app->ignore_unmaps += 2;
164
165     if (app->win != app->icon_win) {
166         /* have to map it so that it can be re-managed on a restart */
167         XMoveWindow(ob_display, app->win, -1000, -1000);
168         XMapWindow(ob_display, app->win);
169     }
170     XMapWindow(ob_display, app->icon_win);
171     XSync(ob_display, False);
172
173     /* specify that if we exit, the window should not be destroyed and should
174        be reparented back to root automatically */
175     XChangeSaveSet(ob_display, app->icon_win, SetModeInsert);
176     XSelectInput(ob_display, app->icon_win, DOCKAPP_EVENT_MASK);
177
178     dock_app_grab_button(app, TRUE);
179
180     g_hash_table_insert(window_map, &app->icon_win, app);
181
182     ob_debug("Managed Dock App: 0x%lx (%s)\n", app->icon_win, app->class);
183 }
184
185 void dock_remove_all()
186 {
187     while (dock->dock_apps)
188         dock_remove(dock->dock_apps->data, TRUE);
189 }
190
191 void dock_remove(ObDockApp *app, gboolean reparent)
192 {
193     dock_app_grab_button(app, FALSE);
194     XSelectInput(ob_display, app->icon_win, NoEventMask);
195     /* remove the window from our save set */
196     XChangeSaveSet(ob_display, app->icon_win, SetModeDelete);
197     XSync(ob_display, False);
198
199     g_hash_table_remove(window_map, &app->icon_win);
200
201     if (reparent)
202         XReparentWindow(ob_display, app->icon_win,
203                         RootWindow(ob_display, ob_screen), app->x, app->y);
204
205     dock->dock_apps = g_list_remove(dock->dock_apps, app);
206     dock_configure();
207
208     ob_debug("Unmanaged Dock App: 0x%lx (%s)\n", app->icon_win, app->class);
209
210     g_free(app->name);
211     g_free(app->class);
212     g_free(app);
213 }
214
215 void dock_configure()
216 {
217     GList *it;
218     gint spot;
219     gint gravity;
220     gint minw, minh;
221     gint strw, strh;
222     Rect *a;
223
224     RrMinSize(dock->a_frame, &minw, &minh);
225
226     dock->w = dock->h = 0;
227
228     /* get the size */
229     for (it = dock->dock_apps; it; it = g_list_next(it)) {
230         ObDockApp *app = it->data;
231         switch (config_dock_orient) {
232         case OB_ORIENTATION_HORZ:
233             dock->w += app->w;
234             dock->h = MAX(dock->h, app->h);
235             break;
236         case OB_ORIENTATION_VERT:
237             dock->w = MAX(dock->w, app->w);
238             dock->h += app->h;
239             break;
240         }
241     }
242
243     spot = (config_dock_orient == OB_ORIENTATION_HORZ ? minw : minh) / 2;
244
245     /* position the apps */
246     for (it = dock->dock_apps; it; it = g_list_next(it)) {
247         ObDockApp *app = it->data;
248         switch (config_dock_orient) {
249         case OB_ORIENTATION_HORZ:
250             app->x = spot;
251             app->y = (dock->h - app->h) / 2;
252             spot += app->w;
253             break;
254         case OB_ORIENTATION_VERT:
255             app->x = (dock->w - app->w) / 2;
256             app->y = spot;
257             spot += app->h;
258             break;
259         }
260
261         XMoveWindow(ob_display, app->icon_win, app->x, app->y);
262     }
263
264     /* used for calculating offsets */
265     dock->w += ob_rr_theme->fbwidth * 2;
266     dock->h += ob_rr_theme->fbwidth * 2;
267
268     a = screen_physical_area();
269
270     /* calculate position */
271     if (config_dock_floating) {
272         dock->x = config_dock_x;
273         dock->y = config_dock_y;
274         gravity = NorthWestGravity;
275     } else {
276         switch (config_dock_pos) {
277         case OB_DIRECTION_NORTHWEST:
278             dock->x = 0;
279             dock->y = 0;
280             gravity = NorthWestGravity;
281             break;
282         case OB_DIRECTION_NORTH:
283             dock->x = a->width / 2;
284             dock->y = 0;
285             gravity = NorthGravity;
286             break;
287         case OB_DIRECTION_NORTHEAST:
288             dock->x = a->width;
289             dock->y = 0;
290             gravity = NorthEastGravity;
291             break;
292         case OB_DIRECTION_WEST:
293             dock->x = 0;
294             dock->y = a->height / 2;
295             gravity = WestGravity;
296             break;
297         case OB_DIRECTION_EAST:
298             dock->x = a->width;
299             dock->y = a->height / 2;
300             gravity = EastGravity;
301             break;
302         case OB_DIRECTION_SOUTHWEST:
303             dock->x = 0;
304             dock->y = a->height;
305             gravity = SouthWestGravity;
306             break;
307         case OB_DIRECTION_SOUTH:
308             dock->x = a->width / 2;
309             dock->y = a->height;
310             gravity = SouthGravity;
311             break;
312         case OB_DIRECTION_SOUTHEAST:
313             dock->x = a->width;
314             dock->y = a->height;
315             gravity = SouthEastGravity;
316             break;
317         default:
318             g_assert_not_reached();
319         }
320     }
321
322     switch(gravity) {
323     case NorthGravity:
324     case CenterGravity:
325     case SouthGravity:
326         dock->x -= dock->w / 2;
327         break;
328     case NorthEastGravity:
329     case EastGravity:
330     case SouthEastGravity:
331         dock->x -= dock->w;
332         break;
333     }
334     switch(gravity) {
335     case WestGravity:
336     case CenterGravity:
337     case EastGravity:
338         dock->y -= dock->h / 2;
339         break;
340     case SouthWestGravity:
341     case SouthGravity:
342     case SouthEastGravity:
343         dock->y -= dock->h;
344         break;
345     }
346
347     if (config_dock_hide && dock->hidden) {
348         if (!config_dock_floating) {
349             switch (config_dock_pos) {
350             case OB_DIRECTION_NORTHWEST:
351                 switch (config_dock_orient) {
352                 case OB_ORIENTATION_HORZ:
353                     dock->y -= dock->h - ob_rr_theme->fbwidth;
354                     break;
355                 case OB_ORIENTATION_VERT:
356                     dock->x -= dock->w - ob_rr_theme->fbwidth;
357                     break;
358                 }
359                 break;
360             case OB_DIRECTION_NORTH:
361                 dock->y -= dock->h - ob_rr_theme->fbwidth;
362                 break;
363             case OB_DIRECTION_NORTHEAST:
364                 switch (config_dock_orient) {
365                 case OB_ORIENTATION_HORZ:
366                     dock->y -= dock->h - ob_rr_theme->fbwidth;
367                     break;
368                 case OB_ORIENTATION_VERT:
369                     dock->x += dock->w - ob_rr_theme->fbwidth;
370                     break;
371                 }
372                 break;
373             case OB_DIRECTION_WEST:
374                 dock->x -= dock->w - ob_rr_theme->fbwidth;
375                 break;
376             case OB_DIRECTION_EAST:
377                 dock->x += dock->w - ob_rr_theme->fbwidth;
378                 break;
379             case OB_DIRECTION_SOUTHWEST:
380                 switch (config_dock_orient) {
381                 case OB_ORIENTATION_HORZ:
382                     dock->y += dock->h - ob_rr_theme->fbwidth;
383                     break;
384                 case OB_ORIENTATION_VERT:
385                     dock->x -= dock->w - ob_rr_theme->fbwidth;
386                     break;
387                 } break;
388             case OB_DIRECTION_SOUTH:
389                 dock->y += dock->h - ob_rr_theme->fbwidth;
390                 break;
391             case OB_DIRECTION_SOUTHEAST:
392                 switch (config_dock_orient) {
393                 case OB_ORIENTATION_HORZ:
394                     dock->y += dock->h - ob_rr_theme->fbwidth;
395                     break;
396                 case OB_ORIENTATION_VERT:
397                     dock->x += dock->w - ob_rr_theme->fbwidth;
398                     break;
399                 }
400                 break;
401             }    
402         }
403     }
404
405     if (!config_dock_floating && config_dock_hide) {
406         strw = ob_rr_theme->fbwidth;
407         strh = ob_rr_theme->fbwidth;
408     } else {
409         strw = dock->w;
410         strh = dock->h;
411     }
412
413     /* set the strut */
414     if (!dock->dock_apps) {
415         STRUT_PARTIAL_SET(dock_strut, 0, 0, 0, 0,
416                           0, 0, 0, 0, 0, 0, 0, 0);
417     } else if (config_dock_floating || config_dock_nostrut) {
418         STRUT_PARTIAL_SET(dock_strut, 0, 0, 0, 0,
419                           0, 0, 0, 0, 0, 0, 0, 0);
420     } else {
421         switch (config_dock_pos) {
422         case OB_DIRECTION_NORTHWEST:
423             switch (config_dock_orient) {
424             case OB_ORIENTATION_HORZ:
425                 STRUT_PARTIAL_SET(dock_strut, 0, strh, 0, 0,
426                                   0, 0, dock->x, dock->x + dock->w - 1,
427                                   0, 0, 0, 0);
428                 break;
429             case OB_ORIENTATION_VERT:
430                 STRUT_PARTIAL_SET(dock_strut, strw, 0, 0, 0,
431                                   dock->y, dock->y + dock->h - 1,
432                                   0, 0, 0, 0, 0, 0);
433                 break;
434             }
435             break;
436         case OB_DIRECTION_NORTH:
437             STRUT_PARTIAL_SET(dock_strut, 0, strh, 0, 0,
438                               dock->x, dock->x + dock->w - 1,
439                               0, 0, 0, 0, 0, 0);
440             break;
441         case OB_DIRECTION_NORTHEAST:
442             switch (config_dock_orient) {
443             case OB_ORIENTATION_HORZ:
444                 STRUT_PARTIAL_SET(dock_strut, 0, strh, 0, 0,
445                                   0, 0, dock->x, dock->x + dock->w -1,
446                                   0, 0, 0, 0);
447                 break;
448             case OB_ORIENTATION_VERT:
449                 STRUT_PARTIAL_SET(dock_strut, 0, 0, strw, 0,
450                                   0, 0, 0, 0,
451                                   dock->y, dock->y + dock->h - 1, 0, 0);
452                 break;
453             }
454             break;
455         case OB_DIRECTION_WEST:
456             STRUT_PARTIAL_SET(dock_strut, strw, 0, 0, 0,
457                               dock->y, dock->y + dock->h - 1,
458                               0, 0, 0, 0, 0, 0);
459             break;
460         case OB_DIRECTION_EAST:
461             STRUT_PARTIAL_SET(dock_strut, 0, 0, strw, 0,
462                               0, 0, 0, 0,
463                               dock->y, dock->y + dock->h - 1, 0, 0);
464             break;
465         case OB_DIRECTION_SOUTHWEST:
466             switch (config_dock_orient) {
467             case OB_ORIENTATION_HORZ:
468                 STRUT_PARTIAL_SET(dock_strut, 0, 0, 0, strh,
469                                   0, 0, 0, 0, 0, 0,
470                                   dock->x, dock->x + dock->w - 1);
471                 break;
472             case OB_ORIENTATION_VERT:
473                 STRUT_PARTIAL_SET(dock_strut, strw, 0, 0, 0,
474                                   dock->y, dock->y + dock->h - 1,
475                                   0, 0, 0, 0, 0, 0);
476                 break;
477             }
478             break;
479         case OB_DIRECTION_SOUTH:
480             STRUT_PARTIAL_SET(dock_strut, 0, 0, 0, strh,
481                               0, 0, 0, 0, 0, 0,
482                               dock->x, dock->x + dock->w - 1);
483             break;
484         case OB_DIRECTION_SOUTHEAST:
485             switch (config_dock_orient) {
486             case OB_ORIENTATION_HORZ:
487                 STRUT_PARTIAL_SET(dock_strut, 0, 0, 0, strh,
488                                   0, 0, 0, 0, 0, 0,
489                                   dock->x, dock->x + dock->w - 1);
490                 break;
491             case OB_ORIENTATION_VERT:
492                 STRUT_PARTIAL_SET(dock_strut, 0, 0, strw, 0,
493                                   0, 0, 0, 0,
494                                   dock->y, dock->y + dock->h - 1, 0, 0);
495                 break;
496             }
497             break;
498         }
499     }
500
501     dock->w += minw;
502     dock->h += minh;
503
504     /* not used for actually sizing shit */
505     dock->w -= ob_rr_theme->fbwidth * 2;
506     dock->h -= ob_rr_theme->fbwidth * 2;
507
508     if (dock->dock_apps) {
509         g_assert(dock->w > 0);
510         g_assert(dock->h > 0);
511
512         XMoveResizeWindow(ob_display, dock->frame,
513                           dock->x, dock->y, dock->w, dock->h);
514
515         RrPaint(dock->a_frame, dock->frame, dock->w, dock->h);
516         XMapWindow(ob_display, dock->frame);
517     } else
518         XUnmapWindow(ob_display, dock->frame);
519
520     /* but they are useful outside of this function! */
521     dock->w += ob_rr_theme->fbwidth * 2;
522     dock->h += ob_rr_theme->fbwidth * 2;
523
524     screen_update_areas();
525 }
526
527 void dock_app_configure(ObDockApp *app, gint w, gint h)
528 {
529     app->w = w;
530     app->h = h;
531     dock_configure();
532 }
533
534 void dock_app_drag(ObDockApp *app, XMotionEvent *e)
535 {
536     ObDockApp *over = NULL;
537     GList *it;
538     gint x, y;
539     gboolean after;
540     gboolean stop;
541
542     x = e->x_root;
543     y = e->y_root;
544
545     /* are we on top of the dock? */
546     if (!(x >= dock->x &&
547           y >= dock->y &&
548           x < dock->x + dock->w &&
549           y < dock->y + dock->h))
550         return;
551
552     x -= dock->x;
553     y -= dock->y;
554
555     /* which dock app are we on top of? */
556     stop = FALSE;
557     for (it = dock->dock_apps; it; it = g_list_next(it)) {
558         over = it->data;
559         switch (config_dock_orient) {
560         case OB_ORIENTATION_HORZ:
561             if (x >= over->x && x < over->x + over->w)
562                 stop = TRUE;
563             break;
564         case OB_ORIENTATION_VERT:
565             if (y >= over->y && y < over->y + over->h)
566                 stop = TRUE;
567             break;
568         }
569         /* dont go to it->next! */
570         if (stop) break;
571     }
572     if (!it || app == over) return;
573
574     x -= over->x;
575     y -= over->y;
576
577     switch (config_dock_orient) {
578     case OB_ORIENTATION_HORZ:
579         after = (x > over->w / 2);
580         break;
581     case OB_ORIENTATION_VERT:
582         after = (y > over->h / 2);
583         break;
584     default:
585         g_assert_not_reached();
586     }
587
588     /* remove before doing the it->next! */
589     dock->dock_apps = g_list_remove(dock->dock_apps, app);
590
591     if (after) it = it->next;
592
593     dock->dock_apps = g_list_insert_before(dock->dock_apps, it, app);
594     dock_configure();
595 }
596
597 static gboolean hide_timeout(gpointer data)
598 {
599     /* hide */
600     dock->hidden = TRUE;
601     dock_configure();
602
603     return FALSE; /* don't repeat */
604 }
605
606 static gboolean show_timeout(gpointer data)
607 {
608     /* hide */
609     dock->hidden = FALSE;
610     dock_configure();
611
612     return FALSE; /* don't repeat */
613 }
614
615 void dock_hide(gboolean hide)
616 {
617     if (!hide) {
618         if (dock->hidden && config_dock_hide) {
619             ob_main_loop_timeout_add(ob_main_loop, config_dock_show_delay,
620                                  show_timeout, NULL, g_direct_equal, NULL);
621         } else if (!dock->hidden && config_dock_hide) {
622             ob_main_loop_timeout_remove(ob_main_loop, hide_timeout);
623         }
624     } else {
625         if (!dock->hidden && config_dock_hide) {
626             ob_main_loop_timeout_add(ob_main_loop, config_dock_hide_delay,
627                                  hide_timeout, NULL, g_direct_equal, NULL);
628         } else if (dock->hidden && config_dock_hide) {
629             ob_main_loop_timeout_remove(ob_main_loop, show_timeout);
630         }
631     }
632 }