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