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