Remove/mark some unused functions and parameters
[notion.git] / ioncore / focus.c
blob863ccaac3ea78aec3612d8ca515d1f01c9ae446c
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"
22 #include "screen-notify.h"
25 static void region_focuslist_awaiting_insertion_trigger(void);
27 /*{{{ Hooks. */
30 WHook *region_do_warp_alt=NULL;
33 /*}}}*/
36 /*{{{ Focus list */
39 void region_focuslist_remove_with_mgrs(WRegion *reg)
41 WRegion *mgrp=region_manager_or_parent(reg);
43 UNLINK_ITEM(ioncore_g.focuslist, reg, active_next, active_prev);
45 if(mgrp!=NULL)
46 region_focuslist_remove_with_mgrs(mgrp);
50 void region_focuslist_push(WRegion *reg)
52 region_focuslist_remove_with_mgrs(reg);
53 LINK_ITEM_FIRST(ioncore_g.focuslist, reg, active_next, active_prev);
57 void region_focuslist_move_after(WRegion *reg, WRegion *after)
59 region_focuslist_remove_with_mgrs(reg);
60 LINK_ITEM_AFTER(ioncore_g.focuslist, after, reg,
61 active_next, active_prev);
65 static void region_focuslist_deinit(WRegion *reg)
67 WRegion *replace=region_manager_or_parent(reg);
69 if(replace!=NULL)
70 region_focuslist_move_after(replace, reg);
72 UNLINK_ITEM(ioncore_g.focuslist, reg, active_next, active_prev);
76 /*EXTL_DOC
77 * Go to and return to a previously active region (if any).
79 * Note that this function is asynchronous; the region will not
80 * actually have received the focus when this function returns.
82 EXTL_EXPORT
83 WRegion *ioncore_goto_previous()
85 WRegion *next;
87 if(ioncore_g.focuslist==NULL)
88 return NULL;
90 /* We're trying to access the focus list from lua (likely from the UI). I
91 * thus force any pending focuslist updates to complete now */
92 region_focuslist_awaiting_insertion_trigger();
94 /* Find the first region on focus history list that isn't currently
95 * active.
97 for(next=ioncore_g.focuslist->active_next;
98 next!=NULL;
99 next=next->active_next){
101 if(!REGION_IS_ACTIVE(next))
102 break;
105 if(next!=NULL)
106 region_goto(next);
108 return next;
112 /*EXTL_DOC
113 * Iterate over focus history until \var{iterfn} returns \code{false}.
114 * The function is called in protected mode.
115 * This routine returns \code{true} if it reaches the end of list
116 * without this happening.
118 EXTL_EXPORT
119 bool ioncore_focushistory_i(ExtlFn iterfn)
121 WRegion *next;
123 if(ioncore_g.focuslist==NULL)
124 return FALSE;
126 /* We're trying to access the focus list from lua (likely from the UI). I
127 * thus force any pending focuslist updates to complete now */
128 region_focuslist_awaiting_insertion_trigger();
130 /* Find the first region on focus history list that isn't currently
131 * active.
133 for(next=ioncore_g.focuslist->active_next;
134 next!=NULL;
135 next=next->active_next){
137 if(!extl_iter_obj(iterfn, (Obj*)next))
138 return FALSE;
141 return TRUE;
145 /*}}}*/
148 /*{{{ Await focus */
151 static Watch await_watch=WATCH_INIT;
154 static void await_watch_handler(Watch *UNUSED(watch), WRegion *prev)
156 WRegion *r;
157 while(1){
158 r=REGION_PARENT_REG(prev);
159 if(r==NULL)
160 break;
162 if(watch_setup(&await_watch, (Obj*)r,
163 (WatchHandler*)await_watch_handler))
164 break;
165 prev=r;
170 void region_set_await_focus(WRegion *reg)
172 if(reg==NULL){
173 watch_reset(&await_watch);
174 }else{
175 watch_setup(&await_watch, (Obj*)reg,
176 (WatchHandler*)await_watch_handler);
181 static bool region_is_parent(WRegion *reg, WRegion *aw)
183 while(aw!=NULL){
184 if(aw==reg)
185 return TRUE;
186 aw=REGION_PARENT_REG(aw);
189 return FALSE;
193 static bool region_is_await(WRegion *reg)
195 return region_is_parent(reg, (WRegion*)await_watch.obj);
199 static bool region_is_focusnext(WRegion *reg)
201 return region_is_parent(reg, ioncore_g.focus_next);
205 /* Only keep await status if focus event is to an ancestor of the await
206 * region.
208 static void check_clear_await(WRegion *reg)
210 if(region_is_await(reg) && reg!=(WRegion*)await_watch.obj)
211 return;
213 watch_reset(&await_watch);
217 WRegion *ioncore_await_focus()
219 return (WRegion*)(await_watch.obj);
223 /*}}}*/
226 /*{{{ focuslist delayed insertion logic */
228 static WTimer* focuslist_insert_timer=NULL;
229 static WRegion* region_awaiting_insertion;
231 static void timer_expired__focuslist_insert(WTimer* UNUSED(dummy1), Obj* UNUSED(dummy2))
233 region_focuslist_push(region_awaiting_insertion);
234 region_awaiting_insertion = NULL;
237 static void schedule_focuslist_insert_timer(WRegion *reg)
239 /* if the delay is disabled, add to the list NOW */
240 if( ioncore_g.focuslist_insert_delay <= 0 )
242 region_focuslist_push(reg);
243 return;
246 if( focuslist_insert_timer == NULL)
248 focuslist_insert_timer=create_timer();
249 if( focuslist_insert_timer == NULL )
250 return;
253 region_awaiting_insertion = reg;
254 timer_set(focuslist_insert_timer, ioncore_g.focuslist_insert_delay,
255 (WTimerHandler*)timer_expired__focuslist_insert, NULL);
258 static void region_focuslist_awaiting_insertion_cancel_if_is( WRegion* reg )
260 if( region_awaiting_insertion == reg &&
261 focuslist_insert_timer != NULL )
263 timer_reset(focuslist_insert_timer);
267 static void region_focuslist_awaiting_insertion_trigger(void)
269 if( focuslist_insert_timer != NULL &&
270 region_awaiting_insertion != NULL )
272 timer_expired__focuslist_insert(NULL,NULL);
273 timer_reset(focuslist_insert_timer);
278 /*}}}*/
281 /*{{{ Events */
284 void region_got_focus(WRegion *reg)
286 WRegion *par;
288 check_clear_await(reg);
290 region_set_activity(reg, SETPARAM_UNSET);
292 if(reg->active_sub==NULL)
294 /* I update the current focus indicator right now. The focuslist is
295 * updated on a timer to keep the list ordered by importance (to keep
296 * windows that the user quickly cycles through at the bottom of the list) */
297 ioncore_g.focus_current = reg;
298 schedule_focuslist_insert_timer(reg);
301 if(!REGION_IS_ACTIVE(reg)){
302 D(fprintf(stderr, "got focus (inact) %s [%p]\n", OBJ_TYPESTR(reg), reg);)
304 reg->flags|=REGION_ACTIVE;
305 region_set_manager_pseudoactivity(reg);
307 par=REGION_PARENT_REG(reg);
308 if(par!=NULL){
309 par->active_sub=reg;
310 region_update_owned_grabs(par);
313 region_activated(reg);
314 region_notify_change(reg, ioncore_g.notifies.activated);
315 }else{
316 D(fprintf(stderr, "got focus (act) %s [%p]\n", OBJ_TYPESTR(reg), reg);)
319 /* Install default colour map only if there is no active subregion;
320 * their maps should come first. WClientWins will install their maps
321 * in region_activated. Other regions are supposed to use the same
322 * default map.
324 if(reg->active_sub==NULL && !OBJ_IS(reg, WClientWin))
325 rootwin_install_colormap(region_rootwin_of(reg), None);
327 screen_update_workspace_indicatorwin( reg );
331 void region_lost_focus(WRegion *reg)
333 WRegion *par;
335 if(!REGION_IS_ACTIVE(reg)){
336 D(fprintf(stderr, "lost focus (inact) %s [%p:]\n", OBJ_TYPESTR(reg), reg);)
337 return;
340 par=REGION_PARENT_REG(reg);
341 if(par!=NULL && par->active_sub==reg){
342 par->active_sub=NULL;
343 region_update_owned_grabs(par);
346 D(fprintf(stderr, "lost focus (act) %s [%p:]\n", OBJ_TYPESTR(reg), reg);)
348 reg->flags&=~REGION_ACTIVE;
349 region_unset_manager_pseudoactivity(reg);
351 region_inactivated(reg);
352 region_notify_change(reg, ioncore_g.notifies.inactivated);
356 void region_focus_deinit(WRegion *reg)
358 /* if the region that's waiting to be added to the focuslist is being
359 * deleted, cancel the insertion */
360 if(region_awaiting_insertion)
361 region_focuslist_awaiting_insertion_cancel_if_is(reg);
363 region_focuslist_deinit(reg);
365 if(ioncore_g.focus_current==reg)
366 ioncore_g.focus_current=ioncore_g.focuslist;
370 /*}}}*/
373 /*{{{ Focus status requests */
376 /*EXTL_DOC
377 * Is \var{reg} active/does it or one of it's children of focus?
379 EXTL_SAFE
380 EXTL_EXPORT_MEMBER
381 bool region_is_active(WRegion *reg, bool pseudoact_ok)
383 return (REGION_IS_ACTIVE(reg) ||
384 (pseudoact_ok && REGION_IS_PSEUDOACTIVE(reg)));
388 bool region_manager_is_focusnext(WRegion *reg)
390 if(reg==NULL || ioncore_g.focus_next==NULL)
391 return FALSE;
393 if(reg==ioncore_g.focus_next)
394 return TRUE;
396 return region_manager_is_focusnext(REGION_MANAGER(reg));
400 bool region_may_control_focus(WRegion *reg)
402 if(OBJ_IS_BEING_DESTROYED(reg))
403 return FALSE;
405 if(REGION_IS_ACTIVE(reg) || REGION_IS_PSEUDOACTIVE(reg))
406 return TRUE;
408 if(region_is_await(reg) || region_is_focusnext(reg))
409 return TRUE;
411 if(region_manager_is_focusnext(reg))
412 return TRUE;
414 return FALSE;
418 /*}}}*/
421 /*{{{ set_focus, warp */
424 /*Time ioncore_focus_time=CurrentTime;*/
426 bool ioncore_should_focus_parent_when_refusing_focus(WRegion* reg){
427 WWindow* parent = reg->parent;
429 LOG(DEBUG, FOCUS, "Region %s refusing focus", reg->ni.name);
431 if(parent==NULL)
432 return FALSE;
434 /**
435 * If the region is refusing focus, this might be because the mouse
436 * entered it but it doesn't accept any focus (like in the case of
437 * oclock). In that case we want to focus its parent WTiling, if any.
439 * However, when for example IntelliJ IDEA's main window is refusing
440 * the focus because some transient completion popup is open, we want
441 * to leave the focus alone.
443 LOG(DEBUG, FOCUS, "Parent is %s", parent->region.ni.name);
445 if (obj_is((Obj*)parent, &CLASSDESCR(WFrame))
446 && ((WFrame*)parent)->mode == FRAME_MODE_TILED) {
447 LOG(DEBUG, FOCUS, "Parent %s is a tiled WFrame", parent->region.ni.name);
448 return TRUE;
451 return FALSE;
454 void region_finalise_focusing(WRegion* reg, Window win, bool warp, Time time, int set_input) {
455 if(warp)
456 region_do_warp(reg);
458 if(REGION_IS_ACTIVE(reg) && ioncore_await_focus()==NULL)
459 return;
461 region_set_await_focus(reg);
463 if(set_input)
464 XSetInputFocus(ioncore_g.dpy, win, RevertToParent, time);
465 else if(ioncore_should_focus_parent_when_refusing_focus(reg)) {
466 XSetInputFocus(ioncore_g.dpy, reg->parent->win, RevertToParent, time);
472 static WRegion *find_warp_to_reg(WRegion *reg)
474 if(reg==NULL)
475 return NULL;
476 if(reg->flags&REGION_PLEASE_WARP)
477 return reg;
478 return find_warp_to_reg(region_manager_or_parent(reg));
482 bool region_do_warp_default(WRegion *reg)
484 int x, y, w, h, px=0, py=0;
485 WRootWin *root;
487 reg=find_warp_to_reg(reg);
489 if(reg==NULL)
490 return FALSE;
492 D(fprintf(stderr, "region_do_warp %p %s\n", reg, OBJ_TYPESTR(reg)));
494 root=region_rootwin_of(reg);
496 region_rootpos(reg, &x, &y);
497 w=REGION_GEOM(reg).w;
498 h=REGION_GEOM(reg).h;
500 if(xwindow_pointer_pos(WROOTWIN_ROOT(root), &px, &py)){
501 if(px>=x && py>=y && px<x+w && py<y+h)
502 return TRUE;
505 rootwin_warp_pointer(root, x+5, y+5);
507 return TRUE;
511 void region_do_warp(WRegion *reg)
513 extl_protect(NULL);
514 hook_call_alt_o(region_do_warp_alt, (Obj*)reg);
515 extl_unprotect(NULL);
519 void region_maybewarp(WRegion *reg, bool warp)
521 ioncore_g.focus_next=reg;
522 ioncore_g.focus_next_source=IONCORE_FOCUSNEXT_OTHER;
523 ioncore_g.warp_next=(warp && ioncore_g.warp_enabled);
527 void region_maybewarp_now(WRegion *reg, bool warp)
529 ioncore_g.focus_next=NULL;
530 /* TODO: what if focus isn't set? Should focus_next be reset then? */
531 region_do_set_focus(reg, warp && ioncore_g.warp_enabled);
535 void region_set_focus(WRegion *reg)
537 region_maybewarp(reg, FALSE);
541 void region_warp(WRegion *reg)
543 region_maybewarp(reg, TRUE);
547 /*}}}*/
550 /*{{{ Misc. */
553 bool region_skip_focus(WRegion *reg)
555 while(reg!=NULL){
556 if(reg->flags&REGION_SKIP_FOCUS)
557 return TRUE;
558 reg=REGION_PARENT_REG(reg);
560 return FALSE;
563 /*EXTL_DOC
564 * Returns the currently focused region, if any.
566 EXTL_EXPORT
567 WRegion *ioncore_current()
569 return ioncore_g.focus_current;
573 /*}}}*/
576 /*{{{ Pointer focus hack */
579 /* This ugly hack tries to prevent focus change, when the pointer is
580 * in a window to be unmapped (or destroyed), and that does not have
581 * the focus, or should not soon have it.
583 void region_pointer_focus_hack(WRegion *reg)
585 WRegion *act;
587 if(ioncore_g.opmode!=IONCORE_OPMODE_NORMAL)
588 return;
590 if(ioncore_g.focus_next!=NULL &&
591 ioncore_g.focus_next_source<=IONCORE_FOCUSNEXT_POINTERHACK){
592 return;
595 act=ioncore_await_focus();
597 if((REGION_IS_ACTIVE(reg) && act==NULL) || !region_is_fully_mapped(reg))
598 return;
600 if(act==NULL)
601 act=ioncore_g.focus_current;
603 if(act==NULL ||
604 OBJ_IS_BEING_DESTROYED(act) ||
605 !region_is_fully_mapped(act) ||
606 region_skip_focus(act)){
607 return;
610 region_set_focus(act);
611 ioncore_g.focus_next_source=IONCORE_FOCUSNEXT_POINTERHACK;
615 /*}}}*/
617 /*{{{ Debug printers */
619 void region_focuslist_debugprint(void)
621 WRegion* reg;
623 LOG(DEBUG, GENERAL, "focus list start =========================");
624 LOG(DEBUG, GENERAL, "currently-focused region ");
625 region_debugprint_parents( ioncore_g.focus_current );
626 LOG(DEBUG, GENERAL, "");
628 LOG(DEBUG, GENERAL, "top-of-focuslist ");
629 region_debugprint_parents( ioncore_g.focuslist );
630 LOG(DEBUG, GENERAL, "");
632 reg = ioncore_g.focuslist;
633 while(1)
635 LOG(DEBUG, GENERAL, "%p (%s)", (void*)reg, reg != NULL ? reg->ni.name : "NULL");
637 if( reg == NULL ||
638 reg->active_next == NULL ||
639 reg == reg->active_next )
641 break;
644 reg = reg->active_next;
647 LOG(DEBUG, GENERAL, "focus list end =========================");
650 /*}}}*/