get the size for all the apps before positioning them
[mikachu/openbox.git] / openbox / dock.c
1 #include "dock.h"
2 #include "screen.h"
3 #include "prop.h"
4 #include "config.h"
5 #include "grab.h"
6 #include "openbox.h"
7 #include "render/theme.h"
8
9 #define DOCK_EVENT_MASK (ButtonPressMask | ButtonReleaseMask | \
10                          EnterWindowMask | LeaveWindowMask)
11 #define DOCKAPP_EVENT_MASK (StructureNotifyMask)
12
13 static Dock *dock;
14
15 Strut dock_strut;
16
17 void dock_startup()
18 {
19     XSetWindowAttributes attrib;
20
21     STRUT_SET(dock_strut, 0, 0, 0, 0);
22
23     dock = g_new0(struct Dock, 1);
24     dock->obwin.type = Window_Dock;
25
26     dock->hidden = TRUE;
27
28     attrib.event_mask = DOCK_EVENT_MASK;
29     attrib.override_redirect = True;
30     dock->frame = XCreateWindow(ob_display, ob_root, 0, 0, 1, 1, 0,
31                                 render_depth, InputOutput, render_visual,
32                                 CWOverrideRedirect | CWEventMask,
33                                 &attrib);
34     dock->a_frame = appearance_copy(theme_a_unfocused_title);
35     XSetWindowBorder(ob_display, dock->frame, theme_b_color->pixel);
36     XSetWindowBorderWidth(ob_display, dock->frame, theme_bwidth);
37
38     g_hash_table_insert(window_map, &dock->frame, dock);
39     stacking_add(DOCK_AS_WINDOW(dock));
40     stacking_raise(DOCK_AS_WINDOW(dock));
41 }
42
43 void dock_shutdown()
44 {
45     XDestroyWindow(ob_display, dock->frame);
46     appearance_free(dock->a_frame);
47     g_hash_table_remove(window_map, &dock->frame);
48     stacking_remove(dock);
49 }
50
51 void dock_add(Window win, XWMHints *wmhints)
52 {
53     DockApp *app;
54     XWindowAttributes attrib;
55     char **data;
56
57     app = g_new0(DockApp, 1);
58     app->obwin.type = Window_DockApp;
59     app->win = win;
60     app->icon_win = (wmhints->flags & IconWindowHint) ?
61         wmhints->icon_window : win;
62
63     if (PROP_GETSS(app->win, wm_class, locale, &data)) {
64         if (data[0]) {
65             app->name = g_strdup(data[0]);
66             if (data[1])
67                 app->class = g_strdup(data[1]);
68         }
69         g_strfreev(data);     
70     }
71
72     if (app->name == NULL) app->name = g_strdup("");
73     if (app->class == NULL) app->class = g_strdup("");
74     
75     if (XGetWindowAttributes(ob_display, app->icon_win, &attrib)) {
76         app->w = attrib.width;
77         app->h = attrib.height;
78     } else {
79         app->w = app->h = 64;
80     }
81
82     dock->dock_apps = g_list_append(dock->dock_apps, app);
83     dock_configure();
84
85     XReparentWindow(ob_display, app->icon_win, dock->frame, app->x, app->y);
86     /*
87       This is the same case as in frame.c for client windows. When Openbox is
88       starting, the window is already mapped so we see unmap events occur for
89       it. There are 2 unmap events generated that we see, one with the 'event'
90       member set the root window, and one set to the client, but both get
91       handled and need to be ignored.
92     */
93     if (ob_state == State_Starting)
94         app->ignore_unmaps += 2;
95
96     if (app->win != app->icon_win) {
97         /* have to map it so that it can be re-managed on a restart */
98         XMoveWindow(ob_display, app->win, -1000, -1000);
99         XMapWindow(ob_display, app->win);
100     }
101     XMapWindow(ob_display, app->icon_win);
102     XSync(ob_display, False);
103
104     /* specify that if we exit, the window should not be destroyed and should
105        be reparented back to root automatically */
106     XChangeSaveSet(ob_display, app->icon_win, SetModeInsert);
107     XSelectInput(ob_display, app->icon_win, DOCKAPP_EVENT_MASK);
108
109     grab_button_full(2, 0, app->icon_win,
110                      ButtonPressMask | ButtonReleaseMask | ButtonMotionMask,
111                      GrabModeAsync, ob_cursors.move);
112
113     g_hash_table_insert(window_map, &app->icon_win, app);
114
115     g_message("Managed Dock App: 0x%lx (%s)", app->icon_win, app->class);
116 }
117
118 void dock_remove_all()
119 {
120     while (dock->dock_apps)
121         dock_remove(dock->dock_apps->data, TRUE);
122 }
123
124 void dock_remove(DockApp *app, gboolean reparent)
125 {
126     ungrab_button(2, 0, app->icon_win);
127     XSelectInput(ob_display, app->icon_win, NoEventMask);
128     /* remove the window from our save set */
129     XChangeSaveSet(ob_display, app->icon_win, SetModeDelete);
130     XSync(ob_display, False);
131
132     g_hash_table_remove(window_map, &app->icon_win);
133
134     if (reparent)
135         XReparentWindow(ob_display, app->icon_win, ob_root, app->x, app->y);
136
137     dock->dock_apps = g_list_remove(dock->dock_apps, app);
138     dock_configure();
139
140     g_message("Unmanaged Dock App: 0x%lx (%s)", app->icon_win, app->class);
141
142     g_free(app->name);
143     g_free(app->class);
144     g_free(app);
145 }
146
147 void dock_configure()
148 {
149     GList *it;
150     int spot;
151     int gravity;
152
153     dock->w = dock->h = spot = 0;
154
155     /* get the size */
156     for (it = dock->dock_apps; it; it = it->next) {
157         struct DockApp *app = it->data;
158         if (config_dock_horz) {
159             dock->w += app->w;
160             dock->h = MAX(dock->h, app->h);
161         } else {
162             dock->w = MAX(dock->w, app->w);
163             dock->h += app->h;
164         }
165     }
166
167     /* position the apps */
168     for (it = dock->dock_apps; it; it = it->next) {
169         struct DockApp *app = it->data;
170         if (config_dock_horz) {
171             app->x = spot;
172             app->y = (dock->h - app->h) / 2;
173             spot += app->w;
174         } else {
175             app->x = (dock->w - app->w) / 2;
176             app->y = spot;
177             spot += app->h;
178         }
179
180         XMoveWindow(ob_display, app->icon_win, app->x, app->y);
181     }
182
183     /* used for calculating offsets */
184     dock->w += theme_bwidth * 2;
185     dock->h += theme_bwidth * 2;
186
187     /* calculate position */
188     switch (config_dock_pos) {
189     case DockPos_Floating:
190         dock->x = config_dock_x;
191         dock->y = config_dock_y;
192         gravity = NorthWestGravity;
193         break;
194     case DockPos_TopLeft:
195         dock->x = 0;
196         dock->y = 0;
197         gravity = NorthWestGravity;
198         break;
199     case DockPos_Top:
200         dock->x = screen_physical_size.width / 2;
201         dock->y = 0;
202         gravity = NorthGravity;
203         break;
204     case DockPos_TopRight:
205         dock->x = screen_physical_size.width;
206         dock->y = 0;
207         gravity = NorthEastGravity;
208         break;
209     case DockPos_Left:
210         dock->x = 0;
211         dock->y = screen_physical_size.height / 2;
212         gravity = WestGravity;
213         break;
214     case DockPos_Right:
215         dock->x = screen_physical_size.width;
216         dock->y = screen_physical_size.height / 2;
217         gravity = EastGravity;
218         break;
219     case DockPos_BottomLeft:
220         dock->x = 0;
221         dock->y = screen_physical_size.height;
222         gravity = SouthWestGravity;
223         break;
224     case DockPos_Bottom:
225         dock->x = screen_physical_size.width / 2;
226         dock->y = screen_physical_size.height;
227         gravity = SouthGravity;
228         break;
229     case DockPos_BottomRight:
230         dock->x = screen_physical_size.width;
231         dock->y = screen_physical_size.height;
232         gravity = SouthEastGravity;
233         break;
234     }
235
236     switch(gravity) {
237     case NorthGravity:
238     case CenterGravity:
239     case SouthGravity:
240         dock->x -= dock->w / 2;
241         break;
242     case NorthEastGravity:
243     case EastGravity:
244     case SouthEastGravity:
245         dock->x -= dock->w;
246         break;
247     }
248     switch(gravity) {
249     case WestGravity:
250     case CenterGravity:
251     case EastGravity:
252         dock->y -= dock->h / 2;
253         break;
254     case SouthWestGravity:
255     case SouthGravity:
256     case SouthEastGravity:
257         dock->y -= dock->h;
258         break;
259     }
260
261     if (config_dock_hide && dock->hidden) {
262         switch (config_dock_pos) {
263         case DockPos_Floating:
264             break;
265         case DockPos_TopLeft:
266             if (config_dock_horz)
267                 dock->y -= dock->h - theme_bwidth;
268             else
269                 dock->x -= dock->w - theme_bwidth;
270             break;
271         case DockPos_Top:
272             dock->y -= dock->h - theme_bwidth;
273             break;
274         case DockPos_TopRight:
275             if (config_dock_horz)
276                 dock->y -= dock->h - theme_bwidth;
277             else
278                 dock->x += dock->w - theme_bwidth;
279             break;
280         case DockPos_Left:
281             dock->x -= dock->w - theme_bwidth;
282             break;
283         case DockPos_Right:
284             dock->x += dock->w - theme_bwidth;
285             break;
286         case DockPos_BottomLeft:
287             if (config_dock_horz)
288                 dock->y += dock->h - theme_bwidth;
289             else
290                 dock->x -= dock->w - theme_bwidth;
291             break;
292         case DockPos_Bottom:
293             dock->y += dock->h - theme_bwidth;
294             break;
295         case DockPos_BottomRight:
296             if (config_dock_horz)
297                 dock->y += dock->h - theme_bwidth;
298             else
299                 dock->x += dock->w - theme_bwidth;
300             break;
301         }    
302     }
303
304     /* set the strut */
305     switch (config_dock_pos) {
306     case DockPos_Floating:
307         STRUT_SET(dock_strut, 0, 0, 0, 0);
308         break;
309     case DockPos_TopLeft:
310         if (config_dock_horz)
311             STRUT_SET(dock_strut, 0, dock->h, 0, 0);
312         else
313             STRUT_SET(dock_strut, dock->w, 0, 0, 0);
314         break;
315     case DockPos_Top:
316         STRUT_SET(dock_strut, 0, dock->h, 0, 0);
317         break;
318     case DockPos_TopRight:
319         if (config_dock_horz)
320             STRUT_SET(dock_strut, 0, dock->h, 0, 0);
321         else
322             STRUT_SET(dock_strut, 0, 0, dock->w, 0);
323         break;
324     case DockPos_Left:
325         STRUT_SET(dock_strut, dock->w, 0, 0, 0);
326         break;
327     case DockPos_Right:
328         STRUT_SET(dock_strut, 0, 0, dock->w, 0);
329         break;
330     case DockPos_BottomLeft:
331         if (config_dock_horz)
332             STRUT_SET(dock_strut, 0, 0, 0, dock->h);
333         else
334             STRUT_SET(dock_strut, dock->w, 0, 0, 0);
335         break;
336     case DockPos_Bottom:
337         STRUT_SET(dock_strut, 0, 0, 0, dock->h);
338         break;
339     case DockPos_BottomRight:
340         if (config_dock_horz)
341             STRUT_SET(dock_strut, 0, 0, 0, dock->h);
342         else
343             STRUT_SET(dock_strut, 0, 0, dock->w, 0);
344         break;
345     }
346
347     /* not used for actually sizing shit */
348     dock->w -= theme_bwidth * 2;
349     dock->h -= theme_bwidth * 2;
350
351     if (dock->w > 0 && dock->h > 0) {
352         RECT_SET(dock->a_frame->area, 0, 0, dock->w, dock->h);
353         XMoveResizeWindow(ob_display, dock->frame,
354                           dock->x, dock->y, dock->w, dock->h);
355
356         paint(dock->frame, dock->a_frame);
357         XMapWindow(ob_display, dock->frame);
358     } else
359         XUnmapWindow(ob_display, dock->frame);
360
361     /* but they are useful outside of this function! */
362     dock->w += theme_bwidth * 2;
363     dock->h += theme_bwidth * 2;
364
365     screen_update_struts();
366 }
367
368 void dock_app_configure(DockApp *app, int w, int h)
369 {
370     app->w = w;
371     app->h = h;
372     dock_configure();
373 }
374
375 void dock_app_drag(DockApp *app, XMotionEvent *e)
376 {
377     DockApp *over = NULL;
378     GList *it;
379     int x, y;
380     gboolean after;
381
382     x = e->x_root;
383     y = e->y_root;
384
385     /* are we on top of the dock? */
386     if (!(x >= dock->x &&
387           y >= dock->y &&
388           x < dock->x + dock->w &&
389           y < dock->y + dock->h))
390         return;
391
392     x -= dock->x;
393     y -= dock->y;
394
395     /* which dock app are we on top of? */
396     for (it = dock->dock_apps; it; it = it->next) {
397         over = it->data;
398         if (config_dock_horz) {
399             if (x >= over->x && x < over->x + over->w)
400                 break;
401         } else {
402             if (y >= over->y && y < over->y + over->h)
403                 break;
404         }
405     }
406     if (!it || app == over) return;
407
408     x -= over->x;
409     y -= over->y;
410
411     if (config_dock_horz)
412         after = (x > over->w / 2);
413     else
414         after = (y > over->h / 2);
415
416     /* remove before doing the it->next! */
417     dock->dock_apps = g_list_remove(dock->dock_apps, app);
418
419     if (after) it = it->next;
420
421     dock->dock_apps = g_list_insert_before(dock->dock_apps, it, app);
422     dock_configure();
423 }
424
425 static void hide_timeout(void *n)
426 {
427     /* dont repeat */
428     timer_stop(dock->hide_timer);
429     dock->hide_timer = NULL;
430
431     /* hide */
432     dock->hidden = TRUE;
433     dock_configure();
434 }
435
436 void dock_hide(gboolean hide)
437 {
438     if (dock->hidden == hide || !config_dock_hide)
439         return;
440     if (!hide) {
441         /* show */
442         dock->hidden = FALSE;
443         dock_configure();
444
445         /* if was hiding, stop it */
446         if (dock->hide_timer) {
447             timer_stop(dock->hide_timer);
448             dock->hide_timer = NULL;
449         }
450     } else {
451         g_assert(!dock->hide_timer);
452         dock->hide_timer = timer_start(config_dock_hide_timeout * 1000,
453                                        (TimeoutHandler)hide_timeout,
454                                        NULL);
455     }
456 }