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