Merge branch 'fixfocusbug'
[notion.git] / ioncore / focus.c
blob592de485a2f252440224d61c5d21e39e0b5f4df2
1 /*
2 * ion/ioncore/focus.c
4 * Copyright (c) Tuomo Valkonen 1999-2009.
6 * See the included file LICENSE for details.
7 */
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"
21 /*{{{ Hooks. */
24 WHook *region_do_warp_alt=NULL;
27 /*}}}*/
30 /*{{{ Focus list */
33 void region_focuslist_remove_with_mgrs(WRegion *reg)
35 WRegion *mgrp=region_manager_or_parent(reg);
37 UNLINK_ITEM(ioncore_g.focus_current, reg, active_next, active_prev);
39 if(mgrp!=NULL)
40 region_focuslist_remove_with_mgrs(mgrp);
44 void region_focuslist_push(WRegion *reg)
46 region_focuslist_remove_with_mgrs(reg);
47 LINK_ITEM_FIRST(ioncore_g.focus_current, reg, active_next, active_prev);
51 void region_focuslist_move_after(WRegion *reg, WRegion *after)
53 region_focuslist_remove_with_mgrs(reg);
54 LINK_ITEM_AFTER(ioncore_g.focus_current, after, reg,
55 active_next, active_prev);
59 void region_focuslist_deinit(WRegion *reg)
61 WRegion *replace=region_manager_or_parent(reg);
63 if(replace!=NULL)
64 region_focuslist_move_after(replace, reg);
66 UNLINK_ITEM(ioncore_g.focus_current, reg, active_next, active_prev);
70 /*EXTL_DOC
71 * Go to and return to a previously active region (if any).
73 * Note that this function is asynchronous; the region will not
74 * actually have received the focus when this function returns.
76 EXTL_EXPORT
77 WRegion *ioncore_goto_previous()
79 WRegion *next;
81 if(ioncore_g.focus_current==NULL)
82 return NULL;
84 /* Find the first region on focus history list that isn't currently
85 * active.
87 for(next=ioncore_g.focus_current->active_next;
88 next!=NULL;
89 next=next->active_next){
91 if(!REGION_IS_ACTIVE(next))
92 break;
95 if(next!=NULL)
96 region_goto(next);
98 return next;
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.
108 EXTL_EXPORT
109 bool ioncore_focushistory_i(ExtlFn iterfn)
111 WRegion *next;
113 if(ioncore_g.focus_current==NULL)
114 return FALSE;
116 /* Find the first region on focus history list that isn't currently
117 * active.
119 for(next=ioncore_g.focus_current->active_next;
120 next!=NULL;
121 next=next->active_next){
123 if(!extl_iter_obj(iterfn, (Obj*)next))
124 return FALSE;
127 return TRUE;
131 /*}}}*/
134 /*{{{ Await focus */
137 static Watch await_watch=WATCH_INIT;
140 static void await_watch_handler(Watch *watch, WRegion *prev)
142 WRegion *r;
143 while(1){
144 r=REGION_PARENT_REG(prev);
145 if(r==NULL)
146 break;
148 if(watch_setup(&await_watch, (Obj*)r,
149 (WatchHandler*)await_watch_handler))
150 break;
151 prev=r;
156 void region_set_await_focus(WRegion *reg)
158 if(reg==NULL){
159 watch_reset(&await_watch);
160 }else{
161 watch_setup(&await_watch, (Obj*)reg,
162 (WatchHandler*)await_watch_handler);
167 static bool region_is_parent(WRegion *reg, WRegion *aw)
169 while(aw!=NULL){
170 if(aw==reg)
171 return TRUE;
172 aw=REGION_PARENT_REG(aw);
175 return FALSE;
179 static bool region_is_await(WRegion *reg)
181 return region_is_parent(reg, (WRegion*)await_watch.obj);
185 static bool region_is_focusnext(WRegion *reg)
187 return region_is_parent(reg, ioncore_g.focus_next);
191 /* Only keep await status if focus event is to an ancestor of the await
192 * region.
194 static void check_clear_await(WRegion *reg)
196 if(region_is_await(reg) && reg!=(WRegion*)await_watch.obj)
197 return;
199 watch_reset(&await_watch);
203 WRegion *ioncore_await_focus()
205 return (WRegion*)(await_watch.obj);
209 /*}}}*/
212 /*{{{ Events */
215 void region_got_focus(WRegion *reg)
217 WRegion *par;
219 check_clear_await(reg);
221 region_set_activity(reg, SETPARAM_UNSET);
223 if(reg->active_sub==NULL){
224 region_focuslist_push(reg);
225 /*ioncore_g.focus_current=reg;*/
228 if(!REGION_IS_ACTIVE(reg)){
229 D(fprintf(stderr, "got focus (inact) %s [%p]\n", OBJ_TYPESTR(reg), reg);)
231 reg->flags|=REGION_ACTIVE;
232 region_set_manager_pseudoactivity(reg);
234 par=REGION_PARENT_REG(reg);
235 if(par!=NULL){
236 par->active_sub=reg;
237 region_update_owned_grabs(par);
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);)
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.
251 if(reg->active_sub==NULL && !OBJ_IS(reg, WClientWin))
252 rootwin_install_colormap(region_rootwin_of(reg), None);
256 void region_lost_focus(WRegion *reg)
258 WRegion *par;
260 if(!REGION_IS_ACTIVE(reg)){
261 D(fprintf(stderr, "lost focus (inact) %s [%p:]\n", OBJ_TYPESTR(reg), reg);)
262 return;
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);
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".
277 while(par!=NULL && !REGION_IS_ACTIVE(par) && !OBJ_IS(par, WScreen))
278 par=REGION_PARENT_REG(par);
279 ioncore_g.focus_current=par;
281 #endif
283 D(fprintf(stderr, "lost focus (act) %s [%p:]\n", OBJ_TYPESTR(reg), reg);)
285 reg->flags&=~REGION_ACTIVE;
286 region_unset_manager_pseudoactivity(reg);
288 region_inactivated(reg);
289 region_notify_change(reg, ioncore_g.notifies.inactivated);
293 /*}}}*/
296 /*{{{ Focus status requests */
299 /*EXTL_DOC
300 * Is \var{reg} active/does it or one of it's children of focus?
302 EXTL_SAFE
303 EXTL_EXPORT_MEMBER
304 bool region_is_active(WRegion *reg, bool pseudoact_ok)
306 return (REGION_IS_ACTIVE(reg) ||
307 (pseudoact_ok && REGION_IS_PSEUDOACTIVE(reg)));
311 bool region_manager_is_focusnext(WRegion *reg)
313 if(reg==NULL || ioncore_g.focus_next==NULL)
314 return FALSE;
316 if(reg==ioncore_g.focus_next)
317 return TRUE;
319 return region_manager_is_focusnext(REGION_MANAGER(reg));
323 bool region_may_control_focus(WRegion *reg)
325 if(OBJ_IS_BEING_DESTROYED(reg))
326 return FALSE;
328 if(REGION_IS_ACTIVE(reg) || REGION_IS_PSEUDOACTIVE(reg))
329 return TRUE;
331 if(region_is_await(reg) || region_is_focusnext(reg))
332 return TRUE;
334 if(region_manager_is_focusnext(reg))
335 return TRUE;
337 return FALSE;
341 /*}}}*/
344 /*{{{ set_focus, warp */
347 /*Time ioncore_focus_time=CurrentTime;*/
350 void region_finalise_focusing(WRegion* reg, Window win, bool warp, Time time, int set_input)
352 if(warp)
353 region_do_warp(reg);
355 if(REGION_IS_ACTIVE(reg) && ioncore_await_focus()==NULL)
356 return;
358 region_set_await_focus(reg);
359 if(set_input)
360 XSetInputFocus(ioncore_g.dpy, win, RevertToParent, time);
361 else if(reg->parent != NULL && reg->parent->win != NULL)
362 XSetInputFocus(ioncore_g.dpy, reg->parent->win, RevertToParent, time);
367 static WRegion *find_warp_to_reg(WRegion *reg)
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));
377 bool region_do_warp_default(WRegion *reg)
379 int x, y, w, h, px=0, py=0;
380 WRootWin *root;
382 reg=find_warp_to_reg(reg);
384 if(reg==NULL)
385 return FALSE;
387 D(fprintf(stderr, "region_do_warp %p %s\n", reg, OBJ_TYPESTR(reg)));
389 root=region_rootwin_of(reg);
391 region_rootpos(reg, &x, &y);
392 w=REGION_GEOM(reg).w;
393 h=REGION_GEOM(reg).h;
395 if(xwindow_pointer_pos(WROOTWIN_ROOT(root), &px, &py)){
396 if(px>=x && py>=y && px<x+w && py<y+h)
397 return TRUE;
400 rootwin_warp_pointer(root, x+5, y+5);
402 return TRUE;
406 void region_do_warp(WRegion *reg)
408 extl_protect(NULL);
409 hook_call_alt_o(region_do_warp_alt, (Obj*)reg);
410 extl_unprotect(NULL);
414 void region_maybewarp(WRegion *reg, bool warp)
416 ioncore_g.focus_next=reg;
417 ioncore_g.focus_next_source=IONCORE_FOCUSNEXT_OTHER;
418 ioncore_g.warp_next=(warp && ioncore_g.warp_enabled);
422 void region_maybewarp_now(WRegion *reg, bool warp)
424 ioncore_g.focus_next=NULL;
425 /* TODO: what if focus isn't set? Should focus_next be reset then? */
426 region_do_set_focus(reg, warp && ioncore_g.warp_enabled);
430 void region_set_focus(WRegion *reg)
432 region_maybewarp(reg, FALSE);
436 void region_warp(WRegion *reg)
438 region_maybewarp(reg, TRUE);
442 /*}}}*/
445 /*{{{ Misc. */
448 bool region_skip_focus(WRegion *reg)
450 while(reg!=NULL){
451 if(reg->flags&REGION_SKIP_FOCUS)
452 return TRUE;
453 reg=REGION_PARENT_REG(reg);
455 return FALSE;
458 /*EXTL_DOC
459 * Returns the currently focused region, if any.
461 EXTL_EXPORT
462 WRegion *ioncore_current()
464 return ioncore_g.focus_current;
468 /*}}}*/
471 /*{{{ Pointer focus hack */
474 /* This ugly hack tries to prevent focus change, when the pointer is
475 * in a window to be unmapped (or destroyed), and that does not have
476 * the focus, or should not soon have it.
478 void region_pointer_focus_hack(WRegion *reg)
480 WRegion *act;
482 if(ioncore_g.opmode!=IONCORE_OPMODE_NORMAL)
483 return;
485 if(ioncore_g.focus_next!=NULL &&
486 ioncore_g.focus_next_source<=IONCORE_FOCUSNEXT_POINTERHACK){
487 return;
490 act=ioncore_await_focus();
492 if((REGION_IS_ACTIVE(reg) && act==NULL) || !region_is_fully_mapped(reg))
493 return;
495 if(act==NULL)
496 act=ioncore_g.focus_current;
498 if(act==NULL ||
499 OBJ_IS_BEING_DESTROYED(act) ||
500 !region_is_fully_mapped(act) ||
501 region_skip_focus(act)){
502 return;
505 region_set_focus(act);
506 ioncore_g.focus_next_source=IONCORE_FOCUSNEXT_POINTERHACK;
510 /*}}}*/