]> icculus.org git repositories - dana/openbox.git/blob - openbox/slit.c
garb press events so they dont break bindings... X sucks
[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,
140                      ButtonPressMask | ButtonReleaseMask | ButtonMotionMask,
141                      GrabModeAsync, ob_cursors.move);
142
143     g_hash_table_insert(slit_app_map, &app->icon_win, app);
144
145     g_message("Managed Slit App: 0x%lx", app->icon_win);
146 }
147
148 void slit_remove_all()
149 {
150     int i;
151
152     for (i = 0; i < nslits; ++i)
153         while (slit[i].slit_apps)
154             slit_remove(slit[i].slit_apps->data, TRUE);
155 }
156
157 void slit_remove(SlitApp *app, gboolean reparent)
158 {
159     ungrab_button(2, 0, app->icon_win);
160     XSelectInput(ob_display, app->icon_win, NoEventMask);
161     /* remove the window from our save set */
162     XChangeSaveSet(ob_display, app->icon_win, SetModeDelete);
163     XSync(ob_display, False);
164
165     g_hash_table_remove(slit_app_map, &app->icon_win);
166
167     if (reparent)
168         XReparentWindow(ob_display, app->icon_win, ob_root, app->x, app->y);
169
170     app->slit->slit_apps = g_list_remove(app->slit->slit_apps, app);
171     slit_configure(app->slit);
172
173     g_message("Unmanaged Slit App: 0x%lx", app->icon_win);
174
175     g_free(app);
176 }
177
178 void slit_configure_all()
179 {
180     int i; for (i = 0; i < nslits; ++i) slit_configure(&slit[i]);
181 }
182
183 static void slit_configure(Slit *self)
184 {
185     GList *it;
186     int spot;
187
188     self->w = self->h = spot = 0;
189
190     for (it = self->slit_apps; it; it = it->next) {
191         struct SlitApp *app = it->data;
192         if (self->horz) {
193             app->x = spot;
194             app->y = 0;
195             self->w += app->w;
196             self->h = MAX(self->h, app->h);
197             spot += app->w;
198         } else {
199             app->x = 0;
200             app->y = spot;
201             self->w = MAX(self->w, app->w);
202             self->h += app->h;
203             spot += app->h;
204         }
205
206         XMoveWindow(ob_display, app->icon_win, app->x, app->y);
207     }
208
209     /* used for calculating offsets */
210     self->w += theme_bwidth * 2;
211     self->h += theme_bwidth * 2;
212
213     /* calculate position */
214     switch (self->pos) {
215     case SlitPos_Floating:
216         self->x = self->user_x;
217         self->y = self->user_y;
218         break;
219     case SlitPos_TopLeft:
220         self->x = 0;
221         self->y = 0;
222         self->gravity = NorthWestGravity;
223         break;
224     case SlitPos_Top:
225         self->x = screen_physical_size.width / 2;
226         self->y = 0;
227         self->gravity = NorthGravity;
228         break;
229     case SlitPos_TopRight:
230         self->x = screen_physical_size.width;
231         self->y = 0;
232         self->gravity = NorthEastGravity;
233         break;
234     case SlitPos_Left:
235         self->x = 0;
236         self->y = screen_physical_size.height / 2;
237         self->gravity = WestGravity;
238         break;
239     case SlitPos_Right:
240         self->x = screen_physical_size.width;
241         self->y = screen_physical_size.height / 2;
242         self->gravity = EastGravity;
243         break;
244     case SlitPos_BottomLeft:
245         self->x = 0;
246         self->y = screen_physical_size.height;
247         self->gravity = SouthWestGravity;
248         break;
249     case SlitPos_Bottom:
250         self->x = screen_physical_size.width / 2;
251         self->y = screen_physical_size.height;
252         self->gravity = SouthGravity;
253         break;
254     case SlitPos_BottomRight:
255         self->x = screen_physical_size.width;
256         self->y = screen_physical_size.height;
257         self->gravity = SouthEastGravity;
258         break;
259     }
260
261     switch(self->gravity) {
262     case NorthGravity:
263     case CenterGravity:
264     case SouthGravity:
265         self->x -= self->w / 2;
266         break;
267     case NorthEastGravity:
268     case EastGravity:
269     case SouthEastGravity:
270         self->x -= self->w;
271         break;
272     }
273     switch(self->gravity) {
274     case WestGravity:
275     case CenterGravity:
276     case EastGravity:
277         self->y -= self->h / 2;
278         break;
279     case SouthWestGravity:
280     case SouthGravity:
281     case SouthEastGravity:
282         self->y -= self->h;
283         break;
284     }
285
286     if (self->hide && self->hidden) {
287         g_message("hidden");
288         switch (self->pos) {
289         case SlitPos_Floating:
290             break;
291         case SlitPos_TopLeft:
292             if (self->horz)
293                 self->y -= self->h - theme_bwidth;
294             else
295                 self->x -= self->w - theme_bwidth;
296             break;
297         case SlitPos_Top:
298             self->y -= self->h - theme_bwidth;
299             break;
300         case SlitPos_TopRight:
301             if (self->horz)
302                 self->y -= self->h - theme_bwidth;
303             else
304                 self->x += self->w - theme_bwidth;
305             break;
306         case SlitPos_Left:
307             self->x -= self->w - theme_bwidth;
308             break;
309         case SlitPos_Right:
310             self->x += self->w - theme_bwidth;
311             break;
312         case SlitPos_BottomLeft:
313             if (self->horz)
314                 self->y += self->h - theme_bwidth;
315             else
316                 self->x -= self->w - theme_bwidth;
317             break;
318         case SlitPos_Bottom:
319             self->y += self->h - theme_bwidth;
320             break;
321         case SlitPos_BottomRight:
322             if (self->horz)
323                 self->y += self->h - theme_bwidth;
324             else
325                 self->x += self->w - theme_bwidth;
326             break;
327         }    
328     }
329
330     /* not used for actually sizing shit */
331     self->w -= theme_bwidth * 2;
332     self->h -= theme_bwidth * 2;
333
334     if (self->w > 0 && self->h > 0) {
335         RECT_SET(self->a_frame->area, 0, 0, self->w, self->h);
336         XMoveResizeWindow(ob_display, self->frame,
337                           self->x, self->y, self->w, self->h);
338
339         paint(self->frame, self->a_frame);
340         XMapWindow(ob_display, self->frame);
341     } else
342         XUnmapWindow(ob_display, self->frame);
343
344     /* but they are useful outside of this function! */
345     self->w += theme_bwidth * 2;
346     self->h += theme_bwidth * 2;
347 }
348
349 void slit_app_configure(SlitApp *app, int w, int h)
350 {
351     app->w = w;
352     app->h = h;
353     slit_configure(app->slit);
354 }
355
356 void slit_app_drag(SlitApp *app, XMotionEvent *e)
357 {
358     Slit *src, *dest = NULL;
359     SlitApp *over = NULL;
360     GList *it;
361     int i;
362     int x, y;
363     gboolean after;
364
365     src = app->slit;
366     x = e->x_root;
367     y = e->y_root;
368
369     /* which slit are we on top of? */
370     for (i = 0; i < nslits; ++i)
371         if (x >= slit[i].x &&
372             y >= slit[i].y &&
373             x < slit[i].x + slit[i].w &&
374             y < slit[i].y + slit[i].h) {
375             dest = &slit[i];
376             break;
377         }
378     if (!dest) return;
379
380     x -= dest->x;
381     y -= dest->y;
382
383     /* which slit app are we on top of? */
384     for (it = dest->slit_apps; it; it = it->next) {
385         over = it->data;
386         if (dest->horz) {
387             if (x >= over->x && x < over->x + over->w)
388                 break;
389         } else {
390             if (y >= over->y && y < over->y + over->h)
391                 break;
392         }
393     }
394     if (!it || app == over) return;
395
396     x -= over->x;
397     y -= over->y;
398
399     if (dest->horz)
400         after = (x > over->w / 2);
401     else
402         after = (y > over->h / 2);
403
404     /* remove before doing the it->next! */
405     src->slit_apps = g_list_remove(src->slit_apps, app);
406     if (src != dest) slit_configure(src);
407
408     if (after) it = it->next;
409
410     dest->slit_apps = g_list_insert_before(dest->slit_apps, it, app);
411     slit_configure(dest);
412 }
413
414 static void hide_timeout(Slit *self)
415 {
416     /* dont repeat */
417     timer_stop(self->hide_timer);
418     self->hide_timer = NULL;
419
420     /* hide */
421     self->hidden = TRUE;
422     slit_configure(self);
423 }
424
425 void slit_hide(Slit *self, gboolean hide)
426 {
427     if (self->hidden == hide || !self->hide)
428         return;
429     if (!hide) {
430         /* show */
431         self->hidden = FALSE;
432         slit_configure(self);
433
434         /* if was hiding, stop it */
435         if (self->hide_timer) {
436             timer_stop(self->hide_timer);
437             self->hide_timer = NULL;
438         }
439     } else {
440         g_assert(!self->hide_timer);
441         self->hide_timer = timer_start(slit_hide_timeout * 1000,
442                                        (TimeoutHandler)hide_timeout, self);
443     }
444 }