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