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