1 /*
2  * Copyright (C) 2000-2007 Carsten Haitzler, Geoff Harrison and various contributors
3  * Copyright (C) 2004-2021 Kim Woelders
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a copy
6  * of this software and associated documentation files (the "Software"), to
7  * deal in the Software without restriction, including without limitation the
8  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9  * sell copies of the Software, and to permit persons to whom the Software is
10  * furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies of the Software, its documentation and marketing & publicity
14  * materials, and acknowledgment shall be given in the documentation, materials
15  * and software packages that this Software was used.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20  * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21  * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23  */
24 #include "config.h"
25 
26 #include <X11/Xlib.h>
27 
28 #include "E.h"
29 #include "cursors.h"
30 #include "desktops.h"		/* FIXME - Should not be here */
31 #include "dialog.h"
32 #include "emodule.h"
33 #include "ewins.h"
34 #include "focus.h"
35 #include "grabs.h"
36 #include "hints.h"
37 #include "icons.h"
38 #include "settings.h"
39 #include "timers.h"
40 #include "xwin.h"
41 
42 #define EwinListFocusRaise(ewin) EobjListFocusRaise(EoObj(ewin))
43 
44 static const char  *const focus_why[] = {
45    "NOP", "INIT", "SET", "NONE", "ENTER", "LEAVE", "EWIN_NEW", "EWIN_UNMAP",
46    "DESK_ENTER", "DESK_LEAVE", "NEXT", "PREV", "CLICK",
47 };
48 
49 static char         focus_inhibit = 1;
50 static char         focus_is_set = 0;
51 static char         click_pending_update_grabs = 0;
52 static int          focus_pending_why = 0;
53 static EWin        *focus_pending_ewin = NULL;
54 static EWin        *focus_pending_new = NULL;
55 static EWin        *focus_pending_raise = NULL;
56 static Timer       *focus_timer_autoraise = NULL;
57 static int          focus_request = 0;
58 
59 void
FocusEnable(int on)60 FocusEnable(int on)
61 {
62    if (on)
63      {
64 	if (focus_inhibit > 0)
65 	   focus_inhibit--;
66      }
67    else
68      {
69 	focus_inhibit++;
70      }
71 
72    if (EDebug(EDBUG_TYPE_FOCUS))
73       Eprintf("%s: inhibit=%d\n", __func__, focus_inhibit);
74 }
75 
76 /*
77  * Return !0 if it is OK to focus ewin.
78  */
79 static int
FocusEwinValid(EWin * ewin,int want_on_screen,int click,int want_visible)80 FocusEwinValid(EWin * ewin, int want_on_screen, int click, int want_visible)
81 {
82    int                 ok;
83 
84    if (!ewin)
85       return 0;
86 
87    if (ewin->state.inhibit_focus)
88       ok = 0;
89 
90    else if (!EoIsMapped(ewin) || !EoIsShown(ewin) ||
91 	    ewin->state.state != EWIN_STATE_MAPPED)
92       ok = 0;
93 
94    else if (ewin->state.sliding)
95       ok = 0;
96 
97    else if (ewin->props.focusclick && !click)
98       ok = 0;
99 
100    else if (want_visible && ewin->state.visibility == VisibilityFullyObscured)
101       ok = 0;
102 
103    else if (want_on_screen && !EwinIsOnScreen(ewin))
104       ok = 0;
105 
106    else
107       ok = 1;
108 
109 #if 0
110    Eprintf
111       ("%s: %#x %s: st=%d sh=%d inh=%d/%d ons=%d(%d) vis=%d(%d) cl=%d(%d): Ok=%d\n",
112        __func__, EwinGetClientXwin(ewin), EwinGetTitle(ewin),
113        ewin->state.state, EoIsShown(ewin),
114        ewin->state.inhibit_focus, ewin->state.sliding,
115        EwinIsOnScreen(ewin), want_on_screen,
116        ewin->state.visibility, want_visible, ok, click, ewin->props.focusclick);
117 #endif
118    return ok;
119 }
120 
121 /*
122  * Return !0 if new ewin should be focused.
123  */
124 static int
FocusEwinValidNew(EWin * ewin)125 FocusEwinValidNew(EWin * ewin)
126 {
127    int                 ok = 0;
128 
129    if (Conf.focus.all_new_windows_get_focus)
130       ok = 1;
131 
132    else if (Conf.focus.new_windows_get_focus_if_group_focused &&
133 	    Mode.focuswin &&
134 	    EwinGetWindowGroup(ewin) == EwinGetWindowGroup(Mode.focuswin))
135       ok = 1;
136 
137    else if (EwinIsTransient(ewin))
138      {
139 	if (Conf.focus.new_transients_get_focus)
140 	  {
141 	     ok = 1;
142 	  }
143 	else if (Conf.focus.new_transients_get_focus_if_group_focused &&
144 		 Mode.focuswin)
145 	  {
146 	     if ((EwinGetTransientFor(ewin) ==
147 		  EwinGetClientXwin(Mode.focuswin)) ||
148 		 (EwinGetWindowGroup(ewin) ==
149 		  EwinGetWindowGroup(Mode.focuswin)))
150 		ok = 1;
151 	  }
152      }
153 
154 #if 0
155    Eprintf("%s: %#x %s: Ok=%d\n", __func__,
156 	   EwinGetClientXwin(ewin), EwinGetTitle(ewin), ok);
157 #endif
158 
159    return ok;
160 }
161 
162 /*
163  * Return the ewin to focus after entering area or losing focused window.
164  */
165 static EWin        *
FocusEwinSelect(void)166 FocusEwinSelect(void)
167 {
168    EWin               *const *lst, *ewin;
169    int                 num, i;
170 
171    switch (Conf.focus.mode)
172      {
173      default:
174      case MODE_FOCUS_POINTER:
175 	ewin = GetEwinPointerInClient();
176 	if (ewin && !FocusEwinValid(ewin, 1, 0, 0))
177 	   ewin = NULL;
178 	break;
179 
180      case MODE_FOCUS_SLOPPY:
181 	ewin = GetEwinPointerInClient();
182 	if (ewin && FocusEwinValid(ewin, 1, 0, 0))
183 	   break;
184 	goto do_select;
185 
186      case MODE_FOCUS_CLICK:
187 	goto do_select;
188 
189       do_select:
190 	ewin = NULL;
191 	lst = EwinListFocusGet(&num);
192 	for (i = 0; i < num; i++)
193 	  {
194 	     if (!FocusEwinValid(lst[i], 1, 0, 0) ||
195 		 lst[i]->props.skip_focuslist)
196 		continue;
197 	     ewin = lst[i];
198 	     break;
199 	  }
200 	break;
201      }
202 
203    return ewin;
204 }
205 
206 static int
AutoraiseTimeout(void * data)207 AutoraiseTimeout(void *data)
208 {
209    EWin               *ewin = (EWin *) data;
210 
211    if (Conf.focus.mode == MODE_FOCUS_CLICK)
212       goto done;
213 
214    if (EwinFindByPtr(ewin))	/* May be gone */
215       EwinRaise(ewin);
216 
217  done:
218    focus_timer_autoraise = NULL;
219    return 0;
220 }
221 
222 static void
FocusRaisePending(void)223 FocusRaisePending(void)
224 {
225    EWin               *ewin = focus_pending_raise;
226    unsigned int        mask;
227 
228    /* The focusing cycle ends when no more modifiers are depressed */
229    mask = 0;
230    EQueryPointer(NULL, NULL, NULL, NULL, &mask);
231    if ((mask & Mode.masks.mod_key_mask) != 0)
232       return;
233 
234    if (EwinFindByPtr(ewin))	/* May be gone */
235       EwinListFocusRaise(ewin);
236 
237    GrabKeyboardRelease();
238 
239    focus_pending_raise = NULL;
240 }
241 
242 /*
243  * dir > 0: Focus previously focused window
244  * else   : Focus least recently focused window
245  */
246 static void
FocusCycleEwin(int dir)247 FocusCycleEwin(int dir)
248 {
249    EWin               *const *lst;
250    EWin               *ewin;
251    int                 i, j, num;
252 
253    lst = EwinListFocusGet(&num);
254    if (num <= 1)
255       return;
256 
257    dir = (dir > 0) ? 1 : -1;
258 
259    for (j = 0; j < num; j++)
260      {
261 	if (lst[j] == Mode.focuswin)
262 	   break;
263      }
264    for (i = 1; i < num; i++)
265      {
266 	ewin = lst[(j + i * dir + num) % num];
267 	if (!FocusEwinValid(ewin, 1, 0, 0) || ewin->props.skip_focuslist)
268 	   continue;
269 	FocusToEWin(ewin, FOCUS_PREV);
270 	break;
271      }
272 }
273 
274 static void
ClickGrabsSet(EWin * ewin)275 ClickGrabsSet(EWin * ewin)
276 {
277    int                 set = 0;
278    unsigned int        raise_button = AnyButton;
279 
280    if (Conf.focus.clickraises == 2)
281       raise_button = Button1;
282 
283    if ((Conf.focus.clickraises && !EwinListStackIsRaised(ewin)) ||
284        (!ewin->state.active && !ewin->state.inhibit_focus))
285       set = 1;
286 
287    if (set)
288      {
289 	if (ewin->state.click_grab_isset &&
290 	    ewin->state.click_grab_button != raise_button)
291 	  {
292 	     GrabButtonRelease(ewin->state.click_grab_button, AnyModifier,
293 			       EwinGetClientConWin(ewin));
294 	     ewin->state.click_grab_isset = 0;
295 	  }
296 	if (!ewin->state.click_grab_isset)
297 	  {
298 	     GrabButtonSet(raise_button, AnyModifier, EwinGetClientConWin(ewin),
299 			   ButtonPressMask, ECSR_PGRAB, 1);
300 	     if (EDebug(EDBUG_TYPE_GRABS))
301 		Eprintf("%s: %#x set %s\n", __func__,
302 			EwinGetClientXwin(ewin), EwinGetTitle(ewin));
303 	     ewin->state.click_grab_isset = 1;
304 	     ewin->state.click_grab_button = raise_button;
305 	  }
306      }
307    else
308      {
309 	if (ewin->state.click_grab_isset)
310 	  {
311 	     GrabButtonRelease(ewin->state.click_grab_button, AnyModifier,
312 			       EwinGetClientConWin(ewin));
313 	     if (EDebug(EDBUG_TYPE_GRABS))
314 		Eprintf("%s: %#x unset %s\n", __func__,
315 			EwinGetClientXwin(ewin), EwinGetTitle(ewin));
316 	     ewin->state.click_grab_isset = 0;
317 	  }
318      }
319 }
320 
321 static void
FocusEwinSetActive(EWin * ewin,int active)322 FocusEwinSetActive(EWin * ewin, int active)
323 {
324    if (ewin->state.active == (unsigned)active)
325       return;
326 
327    ewin->state.active = active;
328    EwinBorderUpdateState(ewin);
329    EwinUpdateOpacity(ewin);
330 
331    if (active && ewin->state.attention)
332       ewin->state.attention = 0;
333 
334    HintsSetWindowState(ewin);
335 }
336 
337 static void
doClickGrabsUpdate(void)338 doClickGrabsUpdate(void)
339 {
340    EWin               *const *lst, *ewin;
341    int                 i, num;
342 
343    lst = EwinListGetAll(&num);
344    for (i = 0; i < num; i++)
345      {
346 	ewin = lst[i];
347 	ClickGrabsSet(ewin);
348      }
349    click_pending_update_grabs = 0;
350 }
351 
352 void
ClickGrabsUpdate(void)353 ClickGrabsUpdate(void)
354 {
355    click_pending_update_grabs = 1;
356 }
357 
358 static void
doFocusToEwin(EWin * ewin,int why)359 doFocusToEwin(EWin * ewin, int why)
360 {
361    int                 do_raise = 0, do_warp = 0;
362 
363    if (focus_inhibit)
364       return;
365 
366    if (EDebug(EDBUG_TYPE_FOCUS))
367       Eprintf("%s: %#x %s why=%s\n", __func__,
368 	      (ewin) ? EwinGetClientXwin(ewin) : 0,
369 	      (ewin) ? EwinGetTitle(ewin) : "None", focus_why[why]);
370 
371    switch (why)
372      {
373      case FOCUS_NEXT:
374      case FOCUS_PREV:
375 	if (Conf.focus.raise_on_next)
376 	   do_raise = 1;
377 	if (Conf.focus.warp_on_next)
378 	   do_warp = 1;
379 	/* Fall thru */
380      default:
381      case FOCUS_SET:
382      case FOCUS_ENTER:
383      case FOCUS_LEAVE:		/* Unused */
384      case FOCUS_CLICK:
385 	if (ewin && ewin == Mode.focuswin)
386 	   return;
387 	if (!ewin)		/* Unfocus */
388 	   break;
389 	if (!FocusEwinValid(ewin, 1, why == FOCUS_CLICK, 0))
390 	   return;
391 	break;
392 
393      case FOCUS_INIT:
394      case FOCUS_DESK_ENTER:
395 	ewin = FocusEwinSelect();
396 	break;
397 
398      case FOCUS_DESK_LEAVE:
399 	focus_is_set = 0;
400 	/* FALLTHROUGH */
401      case FOCUS_NONE:
402 	ewin = NULL;
403 	if (ewin == Mode.focuswin)
404 	   return;
405 	break;
406 
407      case FOCUS_EWIN_UNMAP:
408 	if (Mode.focuswin)
409 	   return;
410 	ewin = FocusEwinSelect();
411 	if (ewin == Mode.focuswin)
412 	   ewin = NULL;
413 	break;
414 
415      case FOCUS_EWIN_NEW:
416 	if (!ewin)
417 	   return;
418 
419 	if (Mode.place.doing_manual)
420 	   goto check_focus_new;
421 
422 	if (ewin->props.focus_when_mapped)
423 	   goto check_focus_new;
424 
425 	if (FocusEwinValidNew(ewin))
426 	  {
427 	     if (EwinIsTransient(ewin))
428 		DeskGotoByEwin(ewin, 0);
429 	     goto check_focus_new;
430 	  }
431 
432 	if (Conf.focus.mode != MODE_FOCUS_CLICK && ewin == Mode.mouse_over_ewin)
433 	   goto check_focus_new;
434 
435 	return;
436 
437       check_focus_new:
438 	if (!FocusEwinValid(ewin, 1, 0, 0))
439 	   return;
440 	break;
441      }
442 
443    if (ewin == Mode.focuswin && focus_is_set)
444       return;
445 
446    /* Check if ewin is a valid focus window target */
447 
448    if (!ewin)
449       goto done;
450 
451    /* NB! ewin != NULL */
452 
453    if (why != FOCUS_CLICK && ewin->props.focusclick)
454       return;
455 
456    if (Conf.autoraise.enable)
457      {
458 	TIMER_DEL(focus_timer_autoraise);
459 
460 	if (Conf.focus.mode != MODE_FOCUS_CLICK)
461 	   TIMER_ADD(focus_timer_autoraise, Conf.autoraise.delay,
462 		     AutoraiseTimeout, ewin);
463      }
464 
465    if (do_raise)
466       EwinRaise(ewin);
467 
468    if (Conf.focus.warp_always)
469       do_warp = 1;
470    if (do_warp)
471       EwinWarpTo(ewin, 0);
472 
473    switch (why)
474      {
475      case FOCUS_PREV:
476      case FOCUS_NEXT:
477 	GrabKeyboardSet(VROOT);	/* Causes idler to be called on KeyRelease */
478 	focus_pending_raise = ewin;
479 	break;
480      case FOCUS_DESK_ENTER:
481 	if (Conf.focus.mode == MODE_FOCUS_CLICK)
482 	   break;
483 	/* FALLTHROUGH */
484      default:
485      case FOCUS_INIT:
486 	EwinListFocusRaise(ewin);
487 	break;
488      }
489 
490    SoundPlay(SOUND_FOCUS_SET);
491  done:
492 
493    ClickGrabsUpdate();
494 
495    /* Unset old focus window (if any) highlighting */
496    if (Mode.focuswin)
497       FocusEwinSetActive(Mode.focuswin, 0);
498    ICCCM_Cmap(ewin);
499 
500    /* Quit if pointer is not on our screen */
501 
502    if (!Mode.events.on_screen)
503      {
504 	Mode.focuswin = NULL;
505 	return;
506      }
507 
508    /* Set new focus window (if any) highlighting */
509    Mode.focuswin = ewin;
510    if (Mode.focuswin)
511       FocusEwinSetActive(Mode.focuswin, 1);
512 
513    if (why == FOCUS_DESK_LEAVE)
514       return;
515 
516    ICCCM_Focus(ewin);
517    focus_is_set = 1;
518 }
519 
520 void
FocusToEWin(EWin * ewin,int why)521 FocusToEWin(EWin * ewin, int why)
522 {
523    if (EDebug(EDBUG_TYPE_FOCUS))
524       Eprintf("%s(%d) %#x %s why=%s\n", __func__, focus_inhibit,
525 	      (ewin) ? EwinGetClientXwin(ewin) : 0,
526 	      (ewin) ? EwinGetTitle(ewin) : "None", focus_why[why]);
527 
528    switch (why)
529      {
530      case FOCUS_EWIN_NEW:
531 	if (!FocusEwinValidNew(ewin))
532 	   break;
533 	if (!FocusEwinValid(ewin, 0, 0, 0))
534 	   break;
535 	focus_pending_new = ewin;
536 	focus_pending_why = why;
537 	focus_pending_ewin = ewin;
538 	focus_request = (int)NextRequest(disp) - 1;
539 	break;
540 
541      default:
542 	if (ewin && !FocusEwinValid(ewin, 0, why == FOCUS_CLICK, 0))
543 	   break;
544 	focus_pending_why = why;
545 	focus_pending_ewin = ewin;
546 	focus_request = (int)NextRequest(disp) - 1;
547 	break;
548 
549      case FOCUS_EWIN_UNMAP:
550 	focus_pending_why = why;
551 	focus_pending_ewin = NULL;
552 	if (ewin == Mode.focuswin)
553 	  {
554 	     Mode.focuswin = NULL;
555 	     focus_is_set = 0;
556 	     if (!EoIsGone(ewin))
557 		FocusEwinSetActive(ewin, 0);
558 	  }
559 	if (ewin == focus_pending_new)
560 	   focus_pending_new = NULL;
561 	break;
562      }
563 }
564 
565 static void
FocusSet(void)566 FocusSet(void)
567 {
568    if (focus_pending_new && Conf.focus.all_new_windows_get_focus)
569       doFocusToEwin(focus_pending_new, FOCUS_EWIN_NEW);
570    else
571       doFocusToEwin(focus_pending_ewin, focus_pending_why);
572    focus_pending_why = 0;
573    focus_pending_ewin = focus_pending_new = NULL;
574 }
575 
576 void
FocusNewDeskBegin(void)577 FocusNewDeskBegin(void)
578 {
579    /* Freeze keyboard */
580    GrabKeyboardFreeze(VROOT);
581 
582    focus_pending_new = NULL;
583    doFocusToEwin(NULL, FOCUS_DESK_LEAVE);
584    FocusEnable(0);
585 }
586 
587 void
FocusNewDesk(void)588 FocusNewDesk(void)
589 {
590    FocusEnable(1);
591    FocusToEWin(NULL, FOCUS_DESK_ENTER);
592 
593    /* Unfreeze keyboard */
594    GrabKeyboardRelease();
595 }
596 
597 static void
_FocusScreenSendEvent(EX_Window xwin,int x,int y,EX_Time t,int enter)598 _FocusScreenSendEvent(EX_Window xwin, int x, int y, EX_Time t, int enter)
599 {
600    XEvent              xe;
601 
602    xe.type = (enter) ? EnterNotify : LeaveNotify;
603    xe.xcrossing.window = xwin;
604    xe.xcrossing.root = xwin;
605    xe.xcrossing.subwindow = 0;
606    xe.xcrossing.time = t;
607    xe.xcrossing.x = xe.xcrossing.x_root = x;
608    xe.xcrossing.y = xe.xcrossing.y_root = y;
609    xe.xcrossing.mode = NotifyNormal;
610    xe.xcrossing.detail = NotifyNonlinear;
611    xe.xcrossing.same_screen = (enter) ? True : False;
612    xe.xcrossing.focus = (enter) ? False : True;
613    xe.xcrossing.state = 0;
614 
615    XSendEvent(disp, xwin, False, EnterWindowMask | LeaveWindowMask, &xe);
616 }
617 
618 void
FocusScreen(int scr)619 FocusScreen(int scr)
620 {
621    EX_Window           xwin;
622    EX_Time             t;
623    int                 x, y;
624 
625    if (scr < 0 || scr >= ScreenCount(disp))
626       return;
627 
628    /* IIRC warping to a different screen once did cause
629     * LeaveNotify's on the current root window. This does not
630     * happen in xorg 1.5.3 (and probably other versions).
631     * So, send LeaveNotify to current root and EnterNotify
632     * to new root. */
633 
634    t = EGetTimestamp();
635 
636    /* Report LeaveNotify on current root window */
637    xwin = WinGetXwin(VROOT);
638    EXQueryPointer(xwin, &x, &y, NULL, NULL);
639    _FocusScreenSendEvent(xwin, x, y, t, 0);
640 
641    /* Do warp and report EnterNotify on new root window */
642    xwin = RootWindow(disp, scr);
643    x = DisplayWidth(disp, scr) / 2;
644    y = DisplayHeight(disp, scr) / 2;
645    EXWarpPointer(xwin, x, y);
646    _FocusScreenSendEvent(xwin, x, y, t, 1);
647 }
648 
649 static void
FocusInit(void)650 FocusInit(void)
651 {
652    /* Start focusing windows */
653    FocusEnable(1);
654 
655    FocusToEWin(NULL, FOCUS_INIT);
656    FocusSet();
657 
658    /* Enable window placement features */
659    Mode.place.enable_features++;
660 }
661 
662 static void
FocusExit(void)663 FocusExit(void)
664 {
665 }
666 
667 /*
668  * Focus event handlers
669  */
670 
671 void
FocusHandleEnter(EWin * ewin,XEvent * ev)672 FocusHandleEnter(EWin * ewin, XEvent * ev)
673 {
674    Mode.mouse_over_ewin = ewin;
675 
676 #if 0				/* FIXME - Remove? */
677    if (ev->xcrossing.mode == NotifyUngrab &&
678        ev->xcrossing.detail == NotifyNonlinearVirtual)
679      {
680 	if (EDebug(1))
681 	   Eprintf("%s: Previously ignored: focused: %s, enter: %s\n", __func__,
682 		   EoGetNameSafe(Mode.focuswin), EoGetNameSafe(ewin));
683      }
684 #endif
685 
686    if ((int)ev->xcrossing.serial - focus_request < 0)
687      {
688 	/* This event was caused by a request older than the latest
689 	 * focus assignment request - ignore */
690 	if (EDebug(EDBUG_TYPE_FOCUS))
691 	   Eprintf("%s: Ignore serial < %#x\n", __func__, focus_request);
692 	return;
693      }
694 
695    if (!ewin)
696      {
697 	/* Entering root may mean entering this screen */
698 	FocusToEWin(NULL, FOCUS_DESK_ENTER);
699 	return;
700      }
701 
702    switch (Conf.focus.mode)
703      {
704      default:
705      case MODE_FOCUS_CLICK:
706 	break;
707      case MODE_FOCUS_SLOPPY:
708 	if (FocusEwinValid(ewin, 1, 0, 0))
709 	   FocusToEWin(ewin, FOCUS_ENTER);
710 	break;
711      case MODE_FOCUS_POINTER:
712 	if (FocusEwinValid(ewin, 1, 0, 0))
713 	   FocusToEWin(ewin, FOCUS_ENTER);
714 	else
715 	   FocusToEWin(NULL, FOCUS_NONE);
716 	break;
717      }
718 }
719 
720 void
FocusHandleLeave(EWin * ewin,XEvent * ev)721 FocusHandleLeave(EWin * ewin, XEvent * ev)
722 {
723    if ((int)ev->xcrossing.serial - focus_request < 0)
724      {
725 	/* This event was caused by a request older than the latest
726 	 * focus assignment request - ignore */
727 	if (EDebug(EDBUG_TYPE_FOCUS))
728 	   Eprintf("%s: Ignore serial < %#x\n", __func__, focus_request);
729 	return;
730      }
731 
732    /* Leaving root may mean entering other screen */
733    if (!ewin)
734      {
735 	if (ev->xcrossing.mode == NotifyNormal &&
736 	    ev->xcrossing.detail != NotifyInferior)
737 	   FocusToEWin(NULL, FOCUS_DESK_LEAVE);
738      }
739 }
740 
741 void
FocusHandleChange(EWin * ewin __UNUSED__,XEvent * ev __UNUSED__)742 FocusHandleChange(EWin * ewin __UNUSED__, XEvent * ev __UNUSED__)
743 {
744 #if 0				/* Debug */
745    if (ewin == Mode.focuswin && ev->type == FocusOut)
746       Eprintf("%s: ??? Lost focus: %s\n", __func__, EwinGetTitle(ewin));
747 #endif
748 }
749 
750 void
FocusHandleClick(EWin * ewin,Win win)751 FocusHandleClick(EWin * ewin, Win win)
752 {
753    if (Conf.focus.clickraises)
754       EwinRaise(ewin);
755 
756    FocusToEWin(ewin, FOCUS_CLICK);
757 
758    /* Allow click to pass thorugh */
759    if (EDebug(EDBUG_TYPE_GRABS))
760       Eprintf("%s: %#x %#x\n", __func__, WinGetXwin(win),
761 	      EwinGetContainerXwin(ewin));
762    if (win == EwinGetClientConWin(ewin))
763      {
764 	ESync(ESYNC_FOCUS);
765 	GrabPointerThaw();
766 	ESync(ESYNC_FOCUS);
767      }
768 }
769 
770 #if ENABLE_DIALOGS
771 /*
772  * Configuration dialog
773  */
774 
775 typedef struct {
776    struct {
777       int                 mode;
778       int                 clickalways;
779       char                new_focus;
780       char                new_focus_if_group;
781       char                popup_focus;
782       char                popup_focus_if_group;
783       char                raise_focus;
784       char                warp_focus;
785       char                warp_always;
786    } focus;
787 
788    struct {
789       char                enable;
790       int                 time;
791    } autoraise;
792 
793    struct {
794       char                enable;
795       char                warp_after_focus;
796       char                raise_after_focus;
797       char                showsticky;
798       char                showshaded;
799       char                showiconified;
800       char                showalldesks;
801       char                warpfocused;
802       char                show_shape;
803       int                 icon_mode;
804    } focuslist;
805 } FocusDlgData;
806 
807 static void
_DlgApplyFocus(Dialog * d,int val __UNUSED__,void * data __UNUSED__)808 _DlgApplyFocus(Dialog * d, int val __UNUSED__, void *data __UNUSED__)
809 {
810    FocusDlgData       *dd = DLG_DATA_GET(d, FocusDlgData);
811 
812    Conf.focus.mode = dd->focus.mode;
813    Conf.focus.clickraises = dd->focus.clickalways;
814    Conf.focus.all_new_windows_get_focus = dd->focus.new_focus;
815    Conf.focus.new_windows_get_focus_if_group_focused =
816       dd->focus.new_focus_if_group;
817    Conf.focus.new_transients_get_focus = dd->focus.popup_focus;
818    Conf.focus.new_transients_get_focus_if_group_focused =
819       dd->focus.popup_focus_if_group;
820    Conf.focus.raise_on_next = dd->focus.raise_focus;
821    Conf.focus.warp_on_next = dd->focus.warp_focus;
822    Conf.focus.warp_always = dd->focus.warp_always;
823 
824    Conf.autoraise.enable = dd->autoraise.enable;
825    Conf.autoraise.delay = 10 * dd->autoraise.time;
826 
827    Conf.warplist.enable = dd->focuslist.enable;
828    Conf.warplist.warp_on_select = dd->focuslist.warp_after_focus;
829    Conf.warplist.raise_on_select = dd->focuslist.raise_after_focus;
830    Conf.warplist.showsticky = dd->focuslist.showsticky;
831    Conf.warplist.showshaded = dd->focuslist.showshaded;
832    Conf.warplist.showiconified = dd->focuslist.showiconified;
833    Conf.warplist.showalldesks = dd->focuslist.showalldesks;
834    Conf.warplist.warpfocused = dd->focuslist.warpfocused;
835    Conf.warplist.show_shape = dd->focuslist.show_shape;
836    Conf.warplist.icon_mode = dd->focuslist.icon_mode;
837 
838    ClickGrabsUpdate();
839 
840    autosave();
841 }
842 
843 static void
_DlgFillFocus(Dialog * d,DItem * table,void * data __UNUSED__)844 _DlgFillFocus(Dialog * d, DItem * table, void *data __UNUSED__)
845 {
846    FocusDlgData       *dd = DLG_DATA_GET(d, FocusDlgData);
847    DItem              *di, *radio;
848 
849    dd->focus.mode = Conf.focus.mode;
850    dd->focus.clickalways = Conf.focus.clickraises;
851    dd->focus.new_focus = Conf.focus.all_new_windows_get_focus;
852    dd->focus.new_focus_if_group =
853       Conf.focus.new_windows_get_focus_if_group_focused;
854    dd->focus.popup_focus = Conf.focus.new_transients_get_focus;
855    dd->focus.popup_focus_if_group =
856       Conf.focus.new_transients_get_focus_if_group_focused;
857    dd->focus.raise_focus = Conf.focus.raise_on_next;
858    dd->focus.warp_focus = Conf.focus.warp_on_next;
859    dd->focus.warp_always = Conf.focus.warp_always;
860 
861    dd->autoraise.enable = Conf.autoraise.enable;
862    dd->autoraise.time = Conf.autoraise.delay / 10;
863 
864    dd->focuslist.enable = Conf.warplist.enable;
865    dd->focuslist.raise_after_focus = Conf.warplist.raise_on_select;
866    dd->focuslist.warp_after_focus = Conf.warplist.warp_on_select;
867    dd->focuslist.showsticky = Conf.warplist.showsticky;
868    dd->focuslist.showshaded = Conf.warplist.showshaded;
869    dd->focuslist.showiconified = Conf.warplist.showiconified;
870    dd->focuslist.showalldesks = Conf.warplist.showalldesks;
871    dd->focuslist.warpfocused = Conf.warplist.warpfocused;
872    dd->focuslist.show_shape = Conf.warplist.show_shape;
873    dd->focuslist.icon_mode = Conf.warplist.icon_mode;
874 
875    DialogItemTableSetOptions(table, 2, 0, 0, 0);
876 
877    radio = di = DialogAddItem(table, DITEM_RADIOBUTTON);
878    DialogItemSetColSpan(di, 2);
879    DialogItemSetText(di, _("Focus follows pointer"));
880    DialogItemRadioButtonSetFirst(di, radio);
881    DialogItemRadioButtonGroupSetVal(di, 0);
882 
883    di = DialogAddItem(table, DITEM_RADIOBUTTON);
884    DialogItemSetColSpan(di, 2);
885    DialogItemSetText(di, _("Focus follows pointer sloppily"));
886    DialogItemRadioButtonSetFirst(di, radio);
887    DialogItemRadioButtonGroupSetVal(di, 1);
888 
889    di = DialogAddItem(table, DITEM_RADIOBUTTON);
890    DialogItemSetColSpan(di, 2);
891    DialogItemSetText(di, _("Focus follows mouse clicks"));
892    DialogItemRadioButtonSetFirst(di, radio);
893    DialogItemRadioButtonGroupSetVal(di, 2);
894    DialogItemRadioButtonGroupSetValPtr(radio, &dd->focus.mode);
895 
896    di = DialogAddItem(table, DITEM_SEPARATOR);
897    DialogItemSetColSpan(di, 2);
898 
899    radio = di = DialogAddItem(table, DITEM_RADIOBUTTON);
900    DialogItemSetColSpan(di, 2);
901    DialogItemSetText(di, _("Clicking in a window does not raise it"));
902    DialogItemRadioButtonSetFirst(di, radio);
903    DialogItemRadioButtonGroupSetVal(di, 0);
904 
905    di = DialogAddItem(table, DITEM_RADIOBUTTON);
906    DialogItemSetColSpan(di, 2);
907    DialogItemSetText(di, _("Clicking in a window always raises it"));
908    DialogItemRadioButtonSetFirst(di, radio);
909    DialogItemRadioButtonGroupSetVal(di, 1);
910 
911    di = DialogAddItem(table, DITEM_RADIOBUTTON);
912    DialogItemSetColSpan(di, 2);
913    DialogItemSetText(di, _("Only primary mouse button can raise window"));
914    DialogItemRadioButtonSetFirst(di, radio);
915    DialogItemRadioButtonGroupSetVal(di, 2);
916    DialogItemRadioButtonGroupSetValPtr(radio, &dd->focus.clickalways);
917 
918    di = DialogAddItem(table, DITEM_SEPARATOR);
919    DialogItemSetColSpan(di, 2);
920 
921    di = DialogAddItem(table, DITEM_CHECKBUTTON);
922    DialogItemSetColSpan(di, 2);
923    DialogItemSetText(di, _("All new windows first get the focus"));
924    DialogItemCheckButtonSetPtr(di, &dd->focus.new_focus);
925 
926    di = DialogAddItem(table, DITEM_CHECKBUTTON);
927    DialogItemSetColSpan(di, 2);
928    DialogItemSetText(di,
929 		     _
930 		     ("New windows get the focus if their window group is focused"));
931    DialogItemCheckButtonSetPtr(di, &dd->focus.new_focus_if_group);
932 
933    di = DialogAddItem(table, DITEM_CHECKBUTTON);
934    DialogItemSetColSpan(di, 2);
935    DialogItemSetText(di, _("Only new dialog windows get the focus"));
936    DialogItemCheckButtonSetPtr(di, &dd->focus.popup_focus);
937 
938    di = DialogAddItem(table, DITEM_CHECKBUTTON);
939    DialogItemSetColSpan(di, 2);
940    DialogItemSetText(di,
941 		     _
942 		     ("Only new dialogs whose owner is focused get the focus"));
943    DialogItemCheckButtonSetPtr(di, &dd->focus.popup_focus_if_group);
944 
945    di = DialogAddItem(table, DITEM_CHECKBUTTON);
946    DialogItemSetColSpan(di, 2);
947    DialogItemSetText(di, _("Raise windows while switching focus"));
948    DialogItemCheckButtonSetPtr(di, &dd->focus.raise_focus);
949 
950    di = DialogAddItem(table, DITEM_CHECKBUTTON);
951    DialogItemSetColSpan(di, 2);
952    DialogItemSetText(di,
953 		     _("Send mouse pointer to window while switching focus"));
954    DialogItemCheckButtonSetPtr(di, &dd->focus.warp_focus);
955 
956    di = DialogAddItem(table, DITEM_CHECKBUTTON);
957    DialogItemSetColSpan(di, 2);
958    DialogItemSetText(di,
959 		     _("Always send mouse pointer to window on focus switch"));
960    DialogItemCheckButtonSetPtr(di, &dd->focus.warp_always);
961 
962    di = DialogAddItem(table, DITEM_SEPARATOR);
963    DialogItemSetColSpan(di, 2);
964 
965    di = DialogAddItem(table, DITEM_CHECKBUTTON);
966    DialogItemSetColSpan(di, 2);
967    DialogItemSetText(di, _("Raise windows automatically"));
968    DialogItemCheckButtonSetPtr(di, &dd->autoraise.enable);
969 
970    di = DialogAddItem(table, DITEM_TEXT);
971    DialogItemSetFill(di, 0, 0);
972    DialogItemSetAlign(di, 0, 512);
973    DialogItemSetText(di, _("Autoraise delay:"));
974 
975    di = DialogAddItem(table, DITEM_SLIDER);
976    DialogItemSliderSetBounds(di, 0, 300);
977    DialogItemSliderSetUnits(di, 10);
978    DialogItemSliderSetJump(di, 25);
979    DialogItemSliderSetValPtr(di, &dd->autoraise.time);
980 
981    di = DialogAddItem(table, DITEM_SEPARATOR);
982    DialogItemSetColSpan(di, 2);
983 
984    di = DialogAddItem(table, DITEM_CHECKBUTTON);
985    DialogItemSetColSpan(di, 2);
986    DialogItemSetText(di, _("Display and use focus list"));
987    DialogItemCheckButtonSetPtr(di, &dd->focuslist.enable);
988 
989    di = DialogAddItem(table, DITEM_CHECKBUTTON);
990    DialogItemSetColSpan(di, 2);
991    DialogItemSetText(di, _("Include sticky windows in focus list"));
992    DialogItemCheckButtonSetPtr(di, &dd->focuslist.showsticky);
993 
994    di = DialogAddItem(table, DITEM_CHECKBUTTON);
995    DialogItemSetColSpan(di, 2);
996    DialogItemSetText(di, _("Include shaded windows in focus list"));
997    DialogItemCheckButtonSetPtr(di, &dd->focuslist.showshaded);
998 
999    di = DialogAddItem(table, DITEM_CHECKBUTTON);
1000    DialogItemSetColSpan(di, 2);
1001    DialogItemSetText(di, _("Include iconified windows in focus list"));
1002    DialogItemCheckButtonSetPtr(di, &dd->focuslist.showiconified);
1003 
1004    di = DialogAddItem(table, DITEM_CHECKBUTTON);
1005    DialogItemSetColSpan(di, 2);
1006    DialogItemSetText(di, _("Include windows on other desks in focus list"));
1007    DialogItemCheckButtonSetPtr(di, &dd->focuslist.showalldesks);
1008 
1009    di = DialogAddItem(table, DITEM_CHECKBUTTON);
1010    DialogItemSetColSpan(di, 2);
1011    DialogItemSetText(di, _("Focus windows while switching"));
1012    DialogItemCheckButtonSetPtr(di, &dd->focuslist.warpfocused);
1013 
1014    di = DialogAddItem(table, DITEM_CHECKBUTTON);
1015    DialogItemSetColSpan(di, 2);
1016    DialogItemSetText(di, _("Outline windows while switching"));
1017    DialogItemCheckButtonSetPtr(di, &dd->focuslist.show_shape);
1018 
1019    di = DialogAddItem(table, DITEM_CHECKBUTTON);
1020    DialogItemSetColSpan(di, 2);
1021    DialogItemSetText(di, _("Raise windows after focus switch"));
1022    DialogItemCheckButtonSetPtr(di, &dd->focuslist.raise_after_focus);
1023 
1024    di = DialogAddItem(table, DITEM_CHECKBUTTON);
1025    DialogItemSetColSpan(di, 2);
1026    DialogItemSetText(di, _("Send mouse pointer to window after focus switch"));
1027    DialogItemCheckButtonSetPtr(di, &dd->focuslist.warp_after_focus);
1028 
1029    di = DialogAddItem(table, DITEM_SEPARATOR);
1030    DialogItemSetColSpan(di, 2);
1031 
1032    di = DialogAddItem(table, DITEM_TEXT);
1033    DialogItemSetColSpan(di, 2);
1034    DialogItemSetText(di,
1035 		     _
1036 		     ("Focuslist image display policy (if one operation fails, try the next):"));
1037 
1038    radio = di = DialogAddItem(table, DITEM_RADIOBUTTON);
1039    DialogItemSetColSpan(di, 2);
1040    DialogItemSetText(di, _("First E Icon, then App Icon"));
1041    DialogItemRadioButtonSetFirst(di, radio);
1042    DialogItemRadioButtonGroupSetVal(di, EWIN_ICON_MODE_IMG_APP);
1043 
1044    di = DialogAddItem(table, DITEM_RADIOBUTTON);
1045    DialogItemSetColSpan(di, 2);
1046    DialogItemSetText(di, _("First App Icon, then E Icon"));
1047    DialogItemRadioButtonSetFirst(di, radio);
1048    DialogItemRadioButtonGroupSetVal(di, EWIN_ICON_MODE_APP_IMG);
1049 
1050    di = DialogAddItem(table, DITEM_RADIOBUTTON);
1051    DialogItemSetColSpan(di, 2);
1052    DialogItemSetText(di, _("None"));
1053    DialogItemRadioButtonSetFirst(di, radio);
1054    DialogItemRadioButtonGroupSetVal(di, EWIN_ICON_MODE_NONE);
1055    DialogItemRadioButtonGroupSetValPtr(radio, &dd->focuslist.icon_mode);
1056 }
1057 
1058 const DialogDef     DlgFocus = {
1059    "CONFIGURE_FOCUS",
1060    N_("Focus"), N_("Focus Settings"),
1061    sizeof(FocusDlgData),
1062    SOUND_SETTINGS_FOCUS,
1063    "pix/focus.png",
1064    N_("Enlightenment Focus\n" "Settings Dialog"),
1065    _DlgFillFocus,
1066    DLG_OAC, _DlgApplyFocus, NULL
1067 };
1068 #endif /* ENABLE_DIALOGS */
1069 
1070 /*
1071  * Focus Module
1072  */
1073 
1074 static int
FocusInitTimeout(void * data __UNUSED__)1075 FocusInitTimeout(void *data __UNUSED__)
1076 {
1077    FocusInit();
1078    return 0;
1079 }
1080 
1081 static void
_FocusIdler(void * data __UNUSED__)1082 _FocusIdler(void *data __UNUSED__)
1083 {
1084    if (focus_inhibit)
1085       return;
1086    if (focus_pending_why)
1087       FocusSet();
1088    if (click_pending_update_grabs)
1089       doClickGrabsUpdate();
1090    if (focus_pending_raise)
1091       FocusRaisePending();
1092 }
1093 
1094 static void
FocusSighan(int sig,void * prm __UNUSED__)1095 FocusSighan(int sig, void *prm __UNUSED__)
1096 {
1097    switch (sig)
1098      {
1099      case ESIGNAL_START:
1100 	/* Delay focusing a bit to allow things to settle down */
1101 	IdlerAdd(_FocusIdler, NULL);
1102 	TIMER_ADD_NP(500, FocusInitTimeout, NULL);
1103 	break;
1104 
1105      case ESIGNAL_EXIT:
1106 	FocusExit();
1107 	break;
1108      }
1109 }
1110 
1111 static void
FocusIpc(const char * params)1112 FocusIpc(const char *params)
1113 {
1114    const char         *p;
1115    char                cmd[128], prm[4096];
1116    int                 len;
1117 
1118    cmd[0] = prm[0] = '\0';
1119    p = params;
1120    if (p)
1121      {
1122 	len = 0;
1123 	sscanf(p, "%100s %4000s %n", cmd, prm, &len);
1124 	p += len;
1125      }
1126 
1127    if (!p || cmd[0] == '?')
1128      {
1129 	EWin               *ewin;
1130 
1131 	ewin = GetFocusEwin();
1132 	if (ewin)
1133 	   IpcPrintf("Focused: %#x\n", EwinGetClientXwin(ewin));
1134 	else
1135 	   IpcPrintf("Focused: none\n");
1136      }
1137    else if (!strncmp(cmd, "mode", 2))
1138      {
1139 	int                 mode = Conf.focus.mode;
1140 
1141 	if (!strcmp(prm, "click"))
1142 	  {
1143 	     mode = MODE_FOCUS_CLICK;
1144 	     Mode.grabs.pointer_grab_active = 1;
1145 	  }
1146 	else if (!strcmp(prm, "clicknograb"))
1147 	  {
1148 	     mode = MODE_FOCUS_CLICK;
1149 	     Mode.grabs.pointer_grab_active = 0;
1150 	  }
1151 	else if (!strcmp(prm, "pointer"))
1152 	  {
1153 	     mode = MODE_FOCUS_POINTER;
1154 	  }
1155 	else if (!strcmp(prm, "sloppy"))
1156 	  {
1157 	     mode = MODE_FOCUS_SLOPPY;
1158 	  }
1159 	else if (!strcmp(prm, "?"))
1160 	  {
1161 	     if (Conf.focus.mode == MODE_FOCUS_CLICK)
1162 	       {
1163 		  if (Mode.grabs.pointer_grab_active)
1164 		     p = "click";
1165 		  else
1166 		     p = "clicknograb";
1167 	       }
1168 	     else if (Conf.focus.mode == MODE_FOCUS_SLOPPY)
1169 		p = "sloppy";
1170 	     else if (Conf.focus.mode == MODE_FOCUS_POINTER)
1171 		p = "pointer";
1172 	     else
1173 		p = "unknown";
1174 	     IpcPrintf("Focus Mode: %s\n", p);
1175 	  }
1176 	else
1177 	  {
1178 	     IpcPrintf("Error: unknown focus type\n");
1179 	  }
1180 	if (Conf.focus.mode != mode)
1181 	  {
1182 	     Conf.focus.mode = mode;
1183 	     ClickGrabsUpdate();
1184 	     autosave();
1185 	  }
1186      }
1187    else if (!strncmp(cmd, "next", 2))
1188      {
1189 	/* Focus previously focused window */
1190 	if (Conf.warplist.enable)
1191 	   WarpFocus(1);
1192 	else
1193 	   FocusCycleEwin(1);
1194      }
1195    else if (!strncmp(cmd, "prev", 2))
1196      {
1197 	/* Focus least recently focused window */
1198 	if (Conf.warplist.enable)
1199 	   WarpFocus(-1);
1200 	else
1201 	   FocusCycleEwin(-1);
1202      }
1203 }
1204 
1205 static const IpcItem FocusIpcArray[] = {
1206    {
1207     FocusIpc,
1208     "focus", "sf",
1209     "Focus functions",
1210     "  focus ?                     Show focus info\n"
1211     "  focus mode                  Set focus mode. Modes:\n"
1212     "    click:       The traditional click-to-focus mode.\n"
1213     "    clicknograb: A similar focus mode, but without the grabbing of the click\n"
1214     "      (you cannot click anywhere in a window to focus it)\n"
1215     "    pointer:     The focus will follow the mouse pointer\n"
1216     "    sloppy:      The focus follows the mouse, but when over the desktop background\n"
1217     "                 the last window does not lose the focus\n"}
1218    ,
1219 };
1220 
1221 static const CfgItem FocusCfgItems[] = {
1222    CFG_ITEM_INT(Conf.focus, mode, MODE_FOCUS_SLOPPY),
1223    CFG_ITEM_INT(Conf.focus, clickraises, 1),
1224    CFG_ITEM_BOOL(Conf.focus, transientsfollowleader, 1),
1225    CFG_ITEM_BOOL(Conf.focus, switchfortransientmap, 1),
1226    CFG_ITEM_BOOL(Conf.focus, all_new_windows_get_focus, 0),
1227    CFG_ITEM_BOOL(Conf.focus, new_windows_get_focus_if_group_focused, 1),
1228    CFG_ITEM_BOOL(Conf.focus, new_transients_get_focus, 0),
1229    CFG_ITEM_BOOL(Conf.focus, new_transients_get_focus_if_group_focused, 1),
1230    CFG_ITEM_BOOL(Conf.focus, raise_on_next, 1),
1231    CFG_ITEM_BOOL(Conf.focus, warp_on_next, 0),
1232    CFG_ITEM_BOOL(Conf.focus, warp_always, 0),
1233 
1234    CFG_ITEM_BOOL(Conf, autoraise.enable, 0),
1235    CFG_ITEM_INT(Conf, autoraise.delay, 500),
1236 };
1237 
1238 /*
1239  * Module descriptor
1240  */
1241 extern const EModule ModFocus;
1242 
1243 const EModule       ModFocus = {
1244    "focus", NULL,
1245    FocusSighan,
1246    MOD_ITEMS(FocusIpcArray),
1247    MOD_ITEMS(FocusCfgItems)
1248 };
1249