Fix bug when the focused window deinitialized
[notion.git] / ioncore / focus.c
blob7a517ada8bcf524b47318a685fe3c18d01572421
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 <libmainloop/signal.h>
11 #include "common.h"
12 #include "focus.h"
13 #include "global.h"
14 #include "window.h"
15 #include "region.h"
16 #include "frame.h"
17 #include "colormap.h"
18 #include "activity.h"
19 #include "xwindow.h"
20 #include "regbind.h"
21 #include "log.h"
24 /*{{{ Hooks. */
27 WHook *region_do_warp_alt=NULL;
30 /*}}}*/
33 /*{{{ Focus list */
36 void region_focuslist_remove_with_mgrs(WRegion *reg)
38 WRegion *mgrp=region_manager_or_parent(reg);
40 UNLINK_ITEM(ioncore_g.focuslist, reg, active_next, active_prev);
42 if(mgrp!=NULL)
43 region_focuslist_remove_with_mgrs(mgrp);
47 void region_focuslist_push(WRegion *reg)
49 region_focuslist_remove_with_mgrs(reg);
50 LINK_ITEM_FIRST(ioncore_g.focuslist, reg, active_next, active_prev);
54 void region_focuslist_move_after(WRegion *reg, WRegion *after)
56 region_focuslist_remove_with_mgrs(reg);
57 LINK_ITEM_AFTER(ioncore_g.focuslist, after, reg,
58 active_next, active_prev);
62 static void region_focuslist_deinit(WRegion *reg)
64 WRegion *replace=region_manager_or_parent(reg);
66 if(replace!=NULL)
67 region_focuslist_move_after(replace, reg);
69 UNLINK_ITEM(ioncore_g.focuslist, reg, active_next, active_prev);
72 void region_focus_deinit(WRegion *reg)
74 region_focuslist_deinit(reg);
76 if(ioncore_g.focus_current==reg)
77 ioncore_g.focus_current=ioncore_g.focuslist;
81 /*EXTL_DOC
82 * Go to and return to a previously active region (if any).
84 * Note that this function is asynchronous; the region will not
85 * actually have received the focus when this function returns.
87 EXTL_EXPORT
88 WRegion *ioncore_goto_previous()
90 WRegion *next;
92 if(ioncore_g.focuslist==NULL)
93 return NULL;
95 // We're trying to access the focus list from lua (likely from the UI). I
96 // thus force any pending focuslist updates to complete now
97 region_focuslist_awaiting_insertion_trigger();
99 /* Find the first region on focus history list that isn't currently
100 * active.
102 for(next=ioncore_g.focuslist->active_next;
103 next!=NULL;
104 next=next->active_next){
106 if(!REGION_IS_ACTIVE(next))
107 break;
110 if(next!=NULL)
111 region_goto(next);
113 return next;
117 /*EXTL_DOC
118 * Iterate over focus history until \var{iterfn} returns \code{false}.
119 * The function is called in protected mode.
120 * This routine returns \code{true} if it reaches the end of list
121 * without this happening.
123 EXTL_EXPORT
124 bool ioncore_focushistory_i(ExtlFn iterfn)
126 WRegion *next;
128 if(ioncore_g.focuslist==NULL)
129 return FALSE;
131 // We're trying to access the focus list from lua (likely from the UI). I
132 // thus force any pending focuslist updates to complete now
133 region_focuslist_awaiting_insertion_trigger();
135 /* Find the first region on focus history list that isn't currently
136 * active.
138 for(next=ioncore_g.focuslist->active_next;
139 next!=NULL;
140 next=next->active_next){
142 if(!extl_iter_obj(iterfn, (Obj*)next))
143 return FALSE;
146 return TRUE;
150 /*}}}*/
153 /*{{{ Await focus */
156 static Watch await_watch=WATCH_INIT;
159 static void await_watch_handler(Watch *watch, WRegion *prev)
161 WRegion *r;
162 while(1){
163 r=REGION_PARENT_REG(prev);
164 if(r==NULL)
165 break;
167 if(watch_setup(&await_watch, (Obj*)r,
168 (WatchHandler*)await_watch_handler))
169 break;
170 prev=r;
175 void region_set_await_focus(WRegion *reg)
177 if(reg==NULL){
178 watch_reset(&await_watch);
179 }else{
180 watch_setup(&await_watch, (Obj*)reg,
181 (WatchHandler*)await_watch_handler);
186 static bool region_is_parent(WRegion *reg, WRegion *aw)
188 while(aw!=NULL){
189 if(aw==reg)
190 return TRUE;
191 aw=REGION_PARENT_REG(aw);
194 return FALSE;
198 static bool region_is_await(WRegion *reg)
200 return region_is_parent(reg, (WRegion*)await_watch.obj);
204 static bool region_is_focusnext(WRegion *reg)
206 return region_is_parent(reg, ioncore_g.focus_next);
210 /* Only keep await status if focus event is to an ancestor of the await
211 * region.
213 static void check_clear_await(WRegion *reg)
215 if(region_is_await(reg) && reg!=(WRegion*)await_watch.obj)
216 return;
218 watch_reset(&await_watch);
222 WRegion *ioncore_await_focus()
224 return (WRegion*)(await_watch.obj);
228 /*}}}*/
231 /*{{{ focuslist delayed insertion logic */
233 static WTimer* focuslist_insert_timer=NULL;
234 static WRegion* region_awaiting_insertion;
236 static void timer_expired__focuslist_insert(WTimer* dummy1, Obj* dummy2)
238 region_focuslist_push(region_awaiting_insertion);
239 region_awaiting_insertion = NULL;
242 static void schedule_focuslist_insert_timer(WRegion *reg)
244 // if the delay is disabled, add to the list NOW
245 if( ioncore_g.focuslist_insert_delay <= 0 )
247 region_focuslist_push(reg);
248 return;
251 if( focuslist_insert_timer == NULL)
253 focuslist_insert_timer=create_timer();
254 if( focuslist_insert_timer == NULL )
255 return;
258 region_awaiting_insertion = reg;
259 timer_set(focuslist_insert_timer, ioncore_g.focuslist_insert_delay,
260 (WTimerHandler*)timer_expired__focuslist_insert, NULL);
264 void region_focuslist_awaiting_insertion_cancel(void)
266 if( focuslist_insert_timer == NULL )
267 return;
269 timer_reset(focuslist_insert_timer);
272 void region_focuslist_awaiting_insertion_trigger(void)
274 if( focuslist_insert_timer != NULL &&
275 region_awaiting_insertion != NULL )
277 timer_expired__focuslist_insert(NULL,NULL);
278 timer_reset(focuslist_insert_timer);
282 const WRegion* region_focuslist_region_awaiting_insertion(void)
284 return region_awaiting_insertion;
288 /*}}}*/
291 /*{{{ Events */
294 void region_got_focus(WRegion *reg)
296 WRegion *par;
298 check_clear_await(reg);
300 region_set_activity(reg, SETPARAM_UNSET);
302 if(reg->active_sub==NULL)
304 // I update the current focus indicator right now. The focuslist is
305 // updated on a timer to keep the list ordered by importance (to keep
306 // windows that the user quickly cycles through at the bottom of the list)
307 ioncore_g.focus_current = reg;
308 schedule_focuslist_insert_timer(reg);
311 if(!REGION_IS_ACTIVE(reg)){
312 D(fprintf(stderr, "got focus (inact) %s [%p]\n", OBJ_TYPESTR(reg), reg);)
314 reg->flags|=REGION_ACTIVE;
315 region_set_manager_pseudoactivity(reg);
317 par=REGION_PARENT_REG(reg);
318 if(par!=NULL){
319 par->active_sub=reg;
320 region_update_owned_grabs(par);
323 region_activated(reg);
324 region_notify_change(reg, ioncore_g.notifies.activated);
325 }else{
326 D(fprintf(stderr, "got focus (act) %s [%p]\n", OBJ_TYPESTR(reg), reg);)
329 /* Install default colour map only if there is no active subregion;
330 * their maps should come first. WClientWins will install their maps
331 * in region_activated. Other regions are supposed to use the same
332 * default map.
334 if(reg->active_sub==NULL && !OBJ_IS(reg, WClientWin))
335 rootwin_install_colormap(region_rootwin_of(reg), None);
339 void region_lost_focus(WRegion *reg)
341 WRegion *par;
343 if(!REGION_IS_ACTIVE(reg)){
344 D(fprintf(stderr, "lost focus (inact) %s [%p:]\n", OBJ_TYPESTR(reg), reg);)
345 return;
348 par=REGION_PARENT_REG(reg);
349 if(par!=NULL && par->active_sub==reg){
350 par->active_sub=NULL;
351 region_update_owned_grabs(par);
354 D(fprintf(stderr, "lost focus (act) %s [%p:]\n", OBJ_TYPESTR(reg), reg);)
356 reg->flags&=~REGION_ACTIVE;
357 region_unset_manager_pseudoactivity(reg);
359 region_inactivated(reg);
360 region_notify_change(reg, ioncore_g.notifies.inactivated);
364 /*}}}*/
367 /*{{{ Focus status requests */
370 /*EXTL_DOC
371 * Is \var{reg} active/does it or one of it's children of focus?
373 EXTL_SAFE
374 EXTL_EXPORT_MEMBER
375 bool region_is_active(WRegion *reg, bool pseudoact_ok)
377 return (REGION_IS_ACTIVE(reg) ||
378 (pseudoact_ok && REGION_IS_PSEUDOACTIVE(reg)));
382 bool region_manager_is_focusnext(WRegion *reg)
384 if(reg==NULL || ioncore_g.focus_next==NULL)
385 return FALSE;
387 if(reg==ioncore_g.focus_next)
388 return TRUE;
390 return region_manager_is_focusnext(REGION_MANAGER(reg));
394 bool region_may_control_focus(WRegion *reg)
396 if(OBJ_IS_BEING_DESTROYED(reg))
397 return FALSE;
399 if(REGION_IS_ACTIVE(reg) || REGION_IS_PSEUDOACTIVE(reg))
400 return TRUE;
402 if(region_is_await(reg) || region_is_focusnext(reg))
403 return TRUE;
405 if(region_manager_is_focusnext(reg))
406 return TRUE;
408 return FALSE;
412 /*}}}*/
415 /*{{{ set_focus, warp */
418 /*Time ioncore_focus_time=CurrentTime;*/
420 bool ioncore_should_focus_parent_when_refusing_focus(WRegion* reg){
421 WWindow* parent = reg->parent;
423 LOG(FOCUS, DEBUG, "Region %s refusing focus", reg->ni.name);
425 if(parent==NULL)
426 return FALSE;
428 /**
429 * If the region is refusing focus, this might be because the mouse
430 * entered it but it doesn't accept any focus (like in the case of
431 * oclock). In that case we want to focus its parent WTiling, if any.
433 * However, when for example IntelliJ IDEA's main window is refusing
434 * the focus because some transient completion popup is open, we want
435 * to leave the focus alone.
437 LOG(FOCUS, DEBUG, "Parent is %s", parent->region.ni.name);
439 if (obj_is((Obj*)parent, &CLASSDESCR(WFrame))
440 && ((WFrame*)parent)->mode == FRAME_MODE_TILED) {
441 LOG(FOCUS, DEBUG, "Parent %s is a tiled WFrame", parent->region.ni.name);
442 return TRUE;
445 return FALSE;
448 void region_finalise_focusing(WRegion* reg, Window win, bool warp, Time time, int set_input) {
449 if(warp)
450 region_do_warp(reg);
452 if(REGION_IS_ACTIVE(reg) && ioncore_await_focus()==NULL)
453 return;
455 region_set_await_focus(reg);
457 if(set_input)
458 XSetInputFocus(ioncore_g.dpy, win, RevertToParent, time);
459 else if(ioncore_should_focus_parent_when_refusing_focus(reg)) {
460 XSetInputFocus(ioncore_g.dpy, reg->parent->win, RevertToParent, time);
466 static WRegion *find_warp_to_reg(WRegion *reg)
468 if(reg==NULL)
469 return NULL;
470 if(reg->flags&REGION_PLEASE_WARP)
471 return reg;
472 return find_warp_to_reg(region_manager_or_parent(reg));
476 bool region_do_warp_default(WRegion *reg)
478 int x, y, w, h, px=0, py=0;
479 WRootWin *root;
481 reg=find_warp_to_reg(reg);
483 if(reg==NULL)
484 return FALSE;
486 D(fprintf(stderr, "region_do_warp %p %s\n", reg, OBJ_TYPESTR(reg)));
488 root=region_rootwin_of(reg);
490 region_rootpos(reg, &x, &y);
491 w=REGION_GEOM(reg).w;
492 h=REGION_GEOM(reg).h;
494 if(xwindow_pointer_pos(WROOTWIN_ROOT(root), &px, &py)){
495 if(px>=x && py>=y && px<x+w && py<y+h)
496 return TRUE;
499 rootwin_warp_pointer(root, x+5, y+5);
501 return TRUE;
505 void region_do_warp(WRegion *reg)
507 extl_protect(NULL);
508 hook_call_alt_o(region_do_warp_alt, (Obj*)reg);
509 extl_unprotect(NULL);
513 void region_maybewarp(WRegion *reg, bool warp)
515 ioncore_g.focus_next=reg;
516 ioncore_g.focus_next_source=IONCORE_FOCUSNEXT_OTHER;
517 ioncore_g.warp_next=(warp && ioncore_g.warp_enabled);
521 void region_maybewarp_now(WRegion *reg, bool warp)
523 ioncore_g.focus_next=NULL;
524 /* TODO: what if focus isn't set? Should focus_next be reset then? */
525 region_do_set_focus(reg, warp && ioncore_g.warp_enabled);
529 void region_set_focus(WRegion *reg)
531 region_maybewarp(reg, FALSE);
535 void region_warp(WRegion *reg)
537 region_maybewarp(reg, TRUE);
541 /*}}}*/
544 /*{{{ Misc. */
547 bool region_skip_focus(WRegion *reg)
549 while(reg!=NULL){
550 if(reg->flags&REGION_SKIP_FOCUS)
551 return TRUE;
552 reg=REGION_PARENT_REG(reg);
554 return FALSE;
557 /*EXTL_DOC
558 * Returns the currently focused region, if any.
560 EXTL_EXPORT
561 WRegion *ioncore_current()
563 return ioncore_g.focus_current;
567 /*}}}*/
570 /*{{{ Pointer focus hack */
573 /* This ugly hack tries to prevent focus change, when the pointer is
574 * in a window to be unmapped (or destroyed), and that does not have
575 * the focus, or should not soon have it.
577 void region_pointer_focus_hack(WRegion *reg)
579 WRegion *act;
581 if(ioncore_g.opmode!=IONCORE_OPMODE_NORMAL)
582 return;
584 if(ioncore_g.focus_next!=NULL &&
585 ioncore_g.focus_next_source<=IONCORE_FOCUSNEXT_POINTERHACK){
586 return;
589 act=ioncore_await_focus();
591 if((REGION_IS_ACTIVE(reg) && act==NULL) || !region_is_fully_mapped(reg))
592 return;
594 if(act==NULL)
595 act=ioncore_g.focus_current;
597 if(act==NULL ||
598 OBJ_IS_BEING_DESTROYED(act) ||
599 !region_is_fully_mapped(act) ||
600 region_skip_focus(act)){
601 return;
604 region_set_focus(act);
605 ioncore_g.focus_next_source=IONCORE_FOCUSNEXT_POINTERHACK;
609 /*}}}*/