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