]> git.decadent.org.uk Git - ion3.git/blob - ioncore/focus.c
Merge commit '20070203' into HEAD
[ion3.git] / ioncore / focus.c
1 /*
2  * ion/ioncore/focus.c
3  *
4  * Copyright (c) Tuomo Valkonen 1999-2007. 
5  *
6  * Ion is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  */
11
12 #include <libmainloop/hooks.h>
13 #include "common.h"
14 #include "focus.h"
15 #include "global.h"
16 #include "window.h"
17 #include "region.h"
18 #include "colormap.h"
19 #include "activity.h"
20 #include "xwindow.h"
21 #include "regbind.h"
22
23
24 /*{{{ Hooks. */
25
26
27 WHook *region_do_warp_alt=NULL;
28
29
30 /*}}}*/
31
32
33 /*{{{ Focus list */
34
35
36 void region_focuslist_remove_with_mgrs(WRegion *reg)
37 {
38     WRegion *mgrp=region_manager_or_parent(reg);
39     
40     UNLINK_ITEM(ioncore_g.focus_current, reg, active_next, active_prev);
41     
42     if(mgrp!=NULL)
43         region_focuslist_remove_with_mgrs(mgrp);
44 }
45
46
47 void region_focuslist_push(WRegion *reg)
48 {
49     region_focuslist_remove_with_mgrs(reg);
50     LINK_ITEM_FIRST(ioncore_g.focus_current, reg, active_next, active_prev);
51 }
52
53
54 void region_focuslist_move_after(WRegion *reg, WRegion *after)
55 {
56     region_focuslist_remove_with_mgrs(reg);
57     LINK_ITEM_AFTER(ioncore_g.focus_current, after, reg, 
58                     active_next, active_prev);
59 }
60
61
62 void region_focuslist_deinit(WRegion *reg)
63 {
64     WRegion *replace=region_manager_or_parent(reg);
65     
66     if(replace!=NULL)
67         region_focuslist_move_after(replace, reg);
68         
69     UNLINK_ITEM(ioncore_g.focus_current, reg, active_next, active_prev);
70 }
71
72
73 /*EXTL_DOC
74  * Go to and return to a previously active region (if any).
75  * 
76  * Note that this function is asynchronous; the region will not
77  * actually have received the focus when this function returns.
78  */
79 EXTL_EXPORT
80 WRegion *ioncore_goto_previous()
81 {
82     WRegion *next;
83     
84     if(ioncore_g.focus_current==NULL)
85         return NULL;
86     
87     /* Find the first region on focus history list that isn't currently
88      * active.
89      */
90     for(next=ioncore_g.focus_current->active_next;
91         next!=NULL; 
92         next=next->active_next){
93         
94         if(!REGION_IS_ACTIVE(next))
95             break;
96     }
97     
98     if(next!=NULL)
99         region_goto(next);
100     
101     return next;
102 }
103
104
105 /*EXTL_DOC
106  * Iterate over focus history until \var{iterfn} returns \code{false}.
107  * The function itself returns \code{true} if it reaches the end of list
108  * without this happening.
109  */
110 EXTL_EXPORT
111 bool ioncore_focushistory_i(ExtlFn iterfn)
112 {
113     WRegion *next;
114     
115     if(ioncore_g.focus_current==NULL)
116         return FALSE;
117     
118     /* Find the first region on focus history list that isn't currently
119      * active.
120      */
121     for(next=ioncore_g.focus_current->active_next;
122         next!=NULL; 
123         next=next->active_next){
124         
125         if(!extl_iter_obj(iterfn, (Obj*)next))
126             return FALSE;
127     }
128     
129     return TRUE;
130 }
131
132
133 /*}}}*/
134
135
136 /*{{{ Await focus */
137
138
139 static Watch await_watch=WATCH_INIT;
140
141
142 static void await_watch_handler(Watch *watch, WRegion *prev)
143 {
144     WRegion *r;
145     while(1){
146         r=REGION_PARENT_REG(prev);
147         if(r==NULL)
148             break;
149         
150         if(watch_setup(&await_watch, (Obj*)r, 
151                        (WatchHandler*)await_watch_handler))
152             break;
153         prev=r;
154     }
155 }
156
157
158 void region_set_await_focus(WRegion *reg)
159 {
160     if(reg==NULL){
161         watch_reset(&await_watch);
162     }else{
163         watch_setup(&await_watch, (Obj*)reg,
164                     (WatchHandler*)await_watch_handler);
165     }
166 }
167
168
169 static bool region_is_await(WRegion *reg)
170 {
171     WRegion *aw=(WRegion*)await_watch.obj;
172     
173     while(aw!=NULL){
174         if(aw==reg)
175             return TRUE;
176         aw=REGION_PARENT_REG(aw);
177     }
178     
179     return FALSE;
180 }
181
182
183 /* Only keep await status if focus event is to an ancestor of the await 
184  * region.
185  */
186 static void check_clear_await(WRegion *reg)
187 {
188     if(region_is_await(reg) && reg!=(WRegion*)await_watch.obj)
189         return;
190     
191     watch_reset(&await_watch);
192 }
193
194
195 WRegion *ioncore_await_focus()
196 {
197     return (WRegion*)(await_watch.obj);
198 }
199
200
201 /*}}}*/
202
203
204 /*{{{ Events */
205
206
207 void region_got_focus(WRegion *reg)
208 {
209     WRegion *par;
210     
211     check_clear_await(reg);
212     
213     region_set_activity(reg, SETPARAM_UNSET);
214
215     if(reg->active_sub==NULL){
216         region_focuslist_push(reg);
217         /*ioncore_g.focus_current=reg;*/
218     }
219     
220     if(!REGION_IS_ACTIVE(reg)){
221         D(fprintf(stderr, "got focus (inact) %s [%p]\n", OBJ_TYPESTR(reg), reg);)
222         
223         reg->flags|=REGION_ACTIVE;
224         region_set_manager_pseudoactivity(reg);
225         
226         par=REGION_PARENT_REG(reg);
227         if(par!=NULL){
228             par->active_sub=reg;
229             region_update_owned_grabs(par);
230         }
231         
232         region_activated(reg);
233         region_notify_change(reg, ioncore_g.notifies.activated);
234     }else{
235         D(fprintf(stderr, "got focus (act) %s [%p]\n", OBJ_TYPESTR(reg), reg);)
236     }
237
238     /* Install default colour map only if there is no active subregion;
239      * their maps should come first. WClientWins will install their maps
240      * in region_activated. Other regions are supposed to use the same
241      * default map.
242      */
243     if(reg->active_sub==NULL && !OBJ_IS(reg, WClientWin))
244         rootwin_install_colormap(region_rootwin_of(reg), None); 
245 }
246
247
248 void region_lost_focus(WRegion *reg)
249 {
250     WRegion *par;
251     
252     if(!REGION_IS_ACTIVE(reg)){
253         D(fprintf(stderr, "lost focus (inact) %s [%p:]\n", OBJ_TYPESTR(reg), reg);)
254         return;
255     }
256     
257     par=REGION_PARENT_REG(reg);
258     if(par!=NULL && par->active_sub==reg){
259         par->active_sub=NULL;
260         region_update_owned_grabs(par);
261     }
262
263
264 #if 0    
265     if(ioncore_g.focus_current==reg){
266         /* Find the closest active parent, or if none is found, stop at the
267          * screen and mark it "currently focused".
268          */
269         while(par!=NULL && !REGION_IS_ACTIVE(par) && !OBJ_IS(par, WScreen))
270             par=REGION_PARENT_REG(par);
271         ioncore_g.focus_current=par;
272     }
273 #endif
274
275     D(fprintf(stderr, "lost focus (act) %s [%p:]\n", OBJ_TYPESTR(reg), reg);)
276     
277     reg->flags&=~REGION_ACTIVE;
278     region_unset_manager_pseudoactivity(reg);
279     
280     region_inactivated(reg);
281     region_notify_change(reg, ioncore_g.notifies.inactivated);
282 }
283
284
285 /*}}}*/
286
287
288 /*{{{ Focus status requests */
289
290
291 /*EXTL_DOC
292  * Is \var{reg} active/does it or one of it's children of focus?
293  */
294 EXTL_SAFE
295 EXTL_EXPORT_MEMBER
296 bool region_is_active(WRegion *reg)
297 {
298     return REGION_IS_ACTIVE(reg);
299 }
300
301
302 bool region_may_control_focus(WRegion *reg)
303 {
304     if(OBJ_IS_BEING_DESTROYED(reg))
305         return FALSE;
306
307     if(REGION_IS_ACTIVE(reg) || REGION_IS_PSEUDOACTIVE(reg))
308         return TRUE;
309     
310     if(region_is_await(reg))
311         return TRUE;
312
313     return FALSE;
314 }
315
316
317 /*}}}*/
318
319
320 /*{{{ set_focus, warp */
321
322
323 /*Time ioncore_focus_time=CurrentTime;*/
324
325
326 void region_finalise_focusing(WRegion* reg, Window win, bool warp)
327 {
328     if(warp)
329         region_do_warp(reg);
330     
331     region_set_await_focus(reg);
332     /*xwindow_do_set_focus(win);*/
333     XSetInputFocus(ioncore_g.dpy, win, RevertToParent, 
334                    CurrentTime/*ioncore_focus_time*/);
335     /*ioncore_focus_time=CurrentTime;*/
336 }
337
338
339
340 static WRegion *find_warp_to_reg(WRegion *reg)
341 {
342     if(reg==NULL)
343         return NULL;
344     if(reg->flags&REGION_PLEASE_WARP)
345         return reg;
346     return find_warp_to_reg(region_manager_or_parent(reg));
347 }
348
349
350 bool region_do_warp_default(WRegion *reg)
351 {
352     int x, y, w, h, px=0, py=0;
353     Window root;
354     
355     reg=find_warp_to_reg(reg);
356     
357     if(reg==NULL)
358         return FALSE;
359     
360     D(fprintf(stderr, "region_do_warp %p %s\n", reg, OBJ_TYPESTR(reg)));
361     
362     root=region_root_of(reg);
363     
364     region_rootpos(reg, &x, &y);
365     w=REGION_GEOM(reg).w;
366     h=REGION_GEOM(reg).h;
367
368     if(xwindow_pointer_pos(root, &px, &py)){
369         if(px>=x && py>=y && px<x+w && py<y+h)
370             return TRUE;
371     }
372     
373     XWarpPointer(ioncore_g.dpy, None, root, 0, 0, 0, 0,
374                  x+5, y+5);
375         
376     return TRUE;
377 }
378
379
380 void region_do_warp(WRegion *reg)
381 {
382     extl_protect(NULL);
383     hook_call_alt_o(region_do_warp_alt, (Obj*)reg);
384     extl_unprotect(NULL);
385 }
386
387
388 void region_maybewarp(WRegion *reg, bool warp)
389 {
390     ioncore_g.focus_next=reg;
391     ioncore_g.warp_next=(warp && ioncore_g.warp_enabled);
392 }
393
394
395 void region_maybewarp_now(WRegion *reg, bool warp)
396 {
397     ioncore_g.focus_next=NULL;
398     /* TODO: what if focus isn't set? Should focus_next be reset then? */
399     region_do_set_focus(reg, warp);
400 }
401
402
403 void region_set_focus(WRegion *reg)
404 {
405     region_maybewarp(reg, FALSE);
406 }
407
408
409 void region_warp(WRegion *reg)
410 {
411     region_maybewarp(reg, TRUE);
412 }
413
414
415 /*}}}*/
416
417
418 /*{{{ Misc. */
419
420
421 bool region_skip_focus(WRegion *reg)
422 {
423     while(reg!=NULL){
424         if(reg->flags&REGION_SKIP_FOCUS)
425             return TRUE;
426         reg=REGION_PARENT_REG(reg);
427     }
428     return FALSE;
429 }
430
431 /*EXTL_DOC
432  * Returns the currently focused region, if any.
433  */
434 EXTL_EXPORT
435 WRegion *ioncore_current()
436 {
437     return ioncore_g.focus_current;
438 }
439
440
441 /*}}}*/
442
443
444 /*{{{ Pointer focus hack */
445
446
447 /* This ugly hack tries to prevent focus change, when the pointer is
448  * in a window to be unmapped (or destroyed), and that does not have
449  * the focus, or should not soon have it.
450  */
451 void region_pointer_focus_hack(WRegion *reg)
452 {
453     WRegion *act=ioncore_await_focus();
454     const WRectangle *g=&REGION_GEOM(reg);
455     int x, y;
456     
457     if(ioncore_g.opmode!=IONCORE_OPMODE_NORMAL)
458         return;
459     
460     if(!REGION_IS_ACTIVE(reg) && act==NULL)
461         act=ioncore_g.focus_current;
462         
463     if(act==NULL || OBJ_IS_BEING_DESTROYED(act))
464         return;
465         
466     /* Ok, anything under us should not get focus as we're unmapped:
467      * Either we don't have the focus, or focus change somewhere else
468      * is pending.
469      *
470      * It might be possible to do the pointer check more efficiently
471      * by trying to maintain our internal pointer containment state
472      * by tracking Enter/Leave events...
473      */
474     
475     xwindow_pointer_pos(region_xwindow(reg), &x, &y);
476     
477     if(x>=0 && y>=0 && x<g->w && y<g->h){
478         D(fprintf(stderr, "Pointer here and shouldn't alter focus!\n"));
479         region_set_focus(act);
480     }
481 }
482
483
484 /*}}}*/
485