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