]> git.decadent.org.uk Git - ion3.git/blob - ioncore/focus.c
b2341ab403b5cd99a65220b8c88e73a894473cf1
[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     if(REGION_IS_ACTIVE(reg) && ioncore_await_focus()==NULL)
332         return;
333     
334     region_set_await_focus(reg);
335     /*xwindow_do_set_focus(win);*/
336     XSetInputFocus(ioncore_g.dpy, win, RevertToParent, 
337                    CurrentTime/*ioncore_focus_time*/);
338     /*ioncore_focus_time=CurrentTime;*/
339 }
340
341
342
343 static WRegion *find_warp_to_reg(WRegion *reg)
344 {
345     if(reg==NULL)
346         return NULL;
347     if(reg->flags&REGION_PLEASE_WARP)
348         return reg;
349     return find_warp_to_reg(region_manager_or_parent(reg));
350 }
351
352
353 bool region_do_warp_default(WRegion *reg)
354 {
355     int x, y, w, h, px=0, py=0;
356     Window root;
357     
358     reg=find_warp_to_reg(reg);
359     
360     if(reg==NULL)
361         return FALSE;
362     
363     D(fprintf(stderr, "region_do_warp %p %s\n", reg, OBJ_TYPESTR(reg)));
364     
365     root=region_root_of(reg);
366     
367     region_rootpos(reg, &x, &y);
368     w=REGION_GEOM(reg).w;
369     h=REGION_GEOM(reg).h;
370
371     if(xwindow_pointer_pos(root, &px, &py)){
372         if(px>=x && py>=y && px<x+w && py<y+h)
373             return TRUE;
374     }
375     
376     XWarpPointer(ioncore_g.dpy, None, root, 0, 0, 0, 0,
377                  x+5, y+5);
378         
379     return TRUE;
380 }
381
382
383 void region_do_warp(WRegion *reg)
384 {
385     extl_protect(NULL);
386     hook_call_alt_o(region_do_warp_alt, (Obj*)reg);
387     extl_unprotect(NULL);
388 }
389
390
391 void region_maybewarp(WRegion *reg, bool warp)
392 {
393     ioncore_g.focus_next=reg;
394     ioncore_g.focus_next_source=IONCORE_FOCUSNEXT_OTHER;
395     ioncore_g.warp_next=(warp && ioncore_g.warp_enabled);
396 }
397
398
399 void region_maybewarp_now(WRegion *reg, bool warp)
400 {
401     ioncore_g.focus_next=NULL;
402     /* TODO: what if focus isn't set? Should focus_next be reset then? */
403     region_do_set_focus(reg, warp && ioncore_g.warp_enabled);
404 }
405
406
407 void region_set_focus(WRegion *reg)
408 {
409     region_maybewarp(reg, FALSE);
410 }
411
412
413 void region_warp(WRegion *reg)
414 {
415     region_maybewarp(reg, TRUE);
416 }
417
418
419 /*}}}*/
420
421
422 /*{{{ Misc. */
423
424
425 bool region_skip_focus(WRegion *reg)
426 {
427     while(reg!=NULL){
428         if(reg->flags&REGION_SKIP_FOCUS)
429             return TRUE;
430         reg=REGION_PARENT_REG(reg);
431     }
432     return FALSE;
433 }
434
435 /*EXTL_DOC
436  * Returns the currently focused region, if any.
437  */
438 EXTL_EXPORT
439 WRegion *ioncore_current()
440 {
441     return ioncore_g.focus_current;
442 }
443
444
445 /*}}}*/
446
447
448 /*{{{ Pointer focus hack */
449
450
451 /* This ugly hack tries to prevent focus change, when the pointer is
452  * in a window to be unmapped (or destroyed), and that does not have
453  * the focus, or should not soon have it.
454  */
455 void region_pointer_focus_hack(WRegion *reg)
456 {
457     WRegion *act;
458     
459     if(ioncore_g.opmode!=IONCORE_OPMODE_NORMAL)
460         return;
461         
462     if(ioncore_g.focus_next!=NULL &&
463        ioncore_g.focus_next_source<=IONCORE_FOCUSNEXT_POINTERHACK){
464         return;
465     }
466     
467     act=ioncore_await_focus();
468     
469     if((REGION_IS_ACTIVE(reg) && act==NULL) || !region_is_fully_mapped(reg))
470         return;
471     
472     if(act==NULL)
473         act=ioncore_g.focus_current;
474     
475     if(act==NULL || 
476        OBJ_IS_BEING_DESTROYED(act) || 
477        !region_is_fully_mapped(act) ||
478        region_skip_focus(act)){
479         return;
480     }
481
482     region_set_focus(act);
483     ioncore_g.focus_next_source=IONCORE_FOCUSNEXT_POINTERHACK;
484 }
485
486
487 /*}}}*/
488