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