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