1 /* focus.c - records a history of focusses for reverting focus, also does focus cycling
2    Copyright (C) 1996-2017 Paul Sheer
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2 of the License, or
7    (at your option) any later version.
8 
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software
16    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
17    02111-1307, USA.
18  */
19 
20 #include <config.h>
21 #include <stdio.h>
22 #include <my_string.h>
23 #include <stdlib.h>
24 #include <stdarg.h>
25 
26 #include <X11/Intrinsic.h>
27 
28 #include "stringtools.h"
29 #include "app_glob.c"
30 
31 #include "coolwidget.h"
32 #include "coollocal.h"
33 
34 #include "mad.h"
35 
36 /* Focus stack: This stack remembers the focus history
37 so that when widgets are destroyed, the focus returns to
38 the most recent widget in the history. This is equivalent
39 to the revert_to in the XSetInputFocus() function,
40 however XSetInputFocus() has an effective history of
41 only one.
42 
43 The commands to change focus are CFocus(CWidget *w)
44 */
45 
46 extern struct look *look;
47 
48 #define FOCUS_STACK_SIZE 128
49 static Window focus_stack[FOCUS_STACK_SIZE];
50 static int focus_sp = 0;
51 static Window current_focus = -1;
52 static Window current_ic_focus = -1;
53 static Window dnd_focus = -1;
54 
save_current_focus_for_dnd(Window new_focus)55 void save_current_focus_for_dnd (Window new_focus)
56 {
57     dnd_focus = current_focus;
58     current_focus = new_focus;
59 }
60 
restore_current_focus_for_dnd(void)61 void restore_current_focus_for_dnd (void)
62 {
63     current_focus = dnd_focus;
64 }
65 
add_to_focus_stack(Window w)66 void add_to_focus_stack (Window w)
67 {
68     int i;
69     i = focus_sp;
70     while (i--)
71 	if (focus_stack[i] == w) {
72 	    focus_sp = i + 1;
73 	    return;
74 	}
75     if (focus_sp >= FOCUS_STACK_SIZE) {
76 #ifdef FOCUS_DEBUG
77 /* NLS ? */
78 	printf ("add_to_focus_stack(): focus_sp overflow\n");
79 #endif
80 	return;
81     }
82     focus_stack[focus_sp++] = w;
83 #ifdef FOCUS_DEBUG
84 /* NLS ? */
85     printf ("add_to_focus_stack(%x): focus_sp = %d\n", (unsigned int) w, focus_sp);
86 #endif
87 }
88 
focus_stack_remove_window(Window w)89 void focus_stack_remove_window (Window w)
90 {
91     int i;
92     i = focus_sp;
93     while (i--)
94 	if (focus_stack[i] == w) {
95 	    focus_stack[i] = 0;
96 	    while (focus_sp && !focus_stack[focus_sp - 1])
97 		focus_sp--;
98 #ifdef FOCUS_DEBUG
99 /* NLS ? */
100     printf ("focus_stack_remove_window(): focus_sp = %d\n", focus_sp);
101 #endif
102 	    return;
103 	}
104 }
105 
CGetFocus(void)106 Window CGetFocus(void)
107 {
108     return current_focus;
109 }
110 
CGetICFocus(void)111 Window CGetICFocus(void)
112 {
113     return current_ic_focus;
114 }
115 
CFocusLast(void)116 void CFocusLast (void)
117 {
118     Window w;
119     if (!focus_sp)
120 	return;
121     w = focus_stack[focus_sp - 1];
122     if (w == current_focus)
123 	return;
124     if (w)
125 	focus_window (w);
126 }
127 
128 /*
129    Each main window must record its last focussed window. This is so
130    that when the window manager sets the focus to a main window. We
131    can look up what widget within that window last had the focus, and
132    move focus to that widget.
133  */
get_last_focussed_in_main(Window main)134 static Window *get_last_focussed_in_main (Window main)
135 {
136     static Window dummy;
137     CWidget *w;
138     w = CWidgetOfWindow (main);
139     if (w)
140 	return &(w->last_child_focussed);
141     dummy = 0;
142     return &dummy;		/* instead of 'return 0;' to prevent segfaults */
143 }
144 
145 /* {{{ Focus border creation and rendering */
146 
147 /* this is four windows that surround the focussed window */
148 struct focus_win focus_border =
149 {
150     0, 0, 0, 0, 0, 0, 0
151 };
152 
get_focus_border_widget(void)153 Window get_focus_border_widget (void)
154 {
155     return focus_border.current;
156 }
157 
158 /* draw a focus window around wodget w */
create_focus_border(CWidget * w,int border)159 void create_focus_border (CWidget * w, int border)
160 {
161     int x, y;
162     XSetWindowAttributes xswa;
163     xswa.colormap = CColormap;
164     xswa.bit_gravity = NorthWestGravity;
165     if (border > 2)
166 	xswa.background_pixel = color_palette(17);	/* for drag and drop highlight borders */
167     else
168 	xswa.background_pixel = COLOR_FLAT;
169     if (w->parentid == CRoot) {
170 	xswa.override_redirect = 1;
171     } else {
172 	xswa.override_redirect = 0;
173     }
174 
175     focus_border.border = border;
176     x = w->x;
177     y = w->y;
178 
179     if (w->parentid == CRoot) {
180 	Window child;
181 	XTranslateCoordinates(CDisplay, w->winid, CRoot, 0, 0, &x, &y, &child);
182     }
183 
184     focus_border.top =
185 	XCreateWindow (CDisplay, w->parentid,
186 		      x - WIDGET_FOCUS_RING, y - WIDGET_FOCUS_RING,
187 		     w->width + WIDGET_FOCUS_RING * 2, WIDGET_FOCUS_RING,
188 		 0, CDepth, InputOutput, CVisual,
189 	    CWOverrideRedirect | CWColormap | CWBackPixel | CWBitGravity,
190 			    &xswa);
191     focus_border.bottom =
192 	XCreateWindow (CDisplay, w->parentid,
193 			     x - WIDGET_FOCUS_RING, y + w->height,
194 		     w->width + WIDGET_FOCUS_RING * 2, WIDGET_FOCUS_RING,
195 		 0, CDepth, InputOutput, CVisual,
196 	    CWOverrideRedirect | CWColormap | CWBackPixel | CWBitGravity,
197 			    &xswa);
198     focus_border.left =
199 	XCreateWindow (CDisplay, w->parentid,
200 			     x - WIDGET_FOCUS_RING, y,
201 			     WIDGET_FOCUS_RING, w->height,
202 		 0, CDepth, InputOutput, CVisual,
203 	    CWOverrideRedirect | CWColormap | CWBackPixel | CWBitGravity,
204 			    &xswa);
205     focus_border.right =
206 	XCreateWindow (CDisplay, w->parentid,
207 			     x + w->width, y,
208 			     WIDGET_FOCUS_RING, w->height,
209 		 0, CDepth, InputOutput, CVisual,
210 	    CWOverrideRedirect | CWColormap | CWBackPixel | CWBitGravity,
211 			    &xswa);
212     focus_border.current = w->winid;
213     focus_border.width = w->width;
214     focus_border.height = w->height;
215     XSelectInput (CDisplay, focus_border.top, ExposureMask);
216     XSelectInput (CDisplay, focus_border.bottom, ExposureMask);
217     XSelectInput (CDisplay, focus_border.left, ExposureMask);
218     XSelectInput (CDisplay, focus_border.right, ExposureMask);
219     XMapWindow (CDisplay, focus_border.top);
220     XMapWindow (CDisplay, focus_border.bottom);
221     XMapWindow (CDisplay, focus_border.left);
222     XMapWindow (CDisplay, focus_border.right);
223 }
224 
destroy_focus_border(void)225 void destroy_focus_border (void)
226 {
227     if (!focus_border.top)
228 	return;
229     XDestroyWindow (CDisplay, focus_border.top);
230     XDestroyWindow (CDisplay, focus_border.bottom);
231     XDestroyWindow (CDisplay, focus_border.left);
232     XDestroyWindow (CDisplay, focus_border.right);
233     memset (&focus_border, 0, sizeof (focus_border));
234 }
235 
window_of_focus_border(Window win)236 int window_of_focus_border (Window win)
237 {
238     if (!focus_border.top)
239 	return 0;
240     if (win == focus_border.top)
241 	return 1;
242     if (win == focus_border.bottom)
243 	return 1;
244     if (win == focus_border.left)
245 	return 1;
246     if (win == focus_border.right)
247 	return 1;
248     return 0;
249 }
250 
render_focus_border(Window win)251 void render_focus_border (Window win)
252 {
253     (*look->render_focus_border) (win);
254 }
255 
set_ic_focus(CWidget * w)256 static void set_ic_focus (CWidget * w)
257 {
258 #ifdef USE_XIM
259     if (w->mainid) {
260 	CIC = (CWidgetOfWindow (w->mainid))->input_context;
261 	current_ic_focus = w->mainid;
262     } else {
263 	CIC = w->input_context;
264 	current_ic_focus = w->mainid;
265     }
266     if (CIC)
267 	XSetICFocus (CIC);
268 #endif
269 }
270 
271 /* }}} Focus border creation and rendering */
272 
273 /* This is called when the app (and not the WM) wants to change focus. */
focus_widget(CWidget * w)274 static void focus_widget (CWidget * w)
275 {
276     CWidget *old;
277 
278     if (current_focus == w->winid) {
279 #ifdef FOCUS_DEBUG
280 	printf ("current_focus == w->winid, returning\n");
281 #endif
282 	return;
283     }
284 
285     destroy_focus_border ();
286     if ((w->options & WIDGET_TAKES_FOCUS_RING))
287 	create_focus_border (w, 1);
288 
289     old = CWidgetOfWindow (current_focus);
290 
291     current_focus = w->winid;	/* notify library of new focus */
292 
293     CSendMessage (old, FocusOut);	/* unfocus the current focus */
294 
295 /* focus on the widgets main window if it has changed */
296     if (old) {
297 	if (old->mainid != w->mainid)
298 	    goto set_main;
299     } else {
300       set_main:
301 /* set that main windows 'last_child_focussed' */
302 	XSetInputFocus (CDisplay, w->mainid, RevertToNone, CurrentTime);
303 	set_ic_focus (w);
304     }
305     *(get_last_focussed_in_main (w->mainid)) = w->winid;
306 #ifdef FOCUS_DEBUG
307     printf ("setting last_focus of %s = %s\n", (char *) CWidgetOfWindow (w->mainid), w->ident);
308 #endif
309 
310     add_to_focus_stack (w->winid);	/* record focus history */
311 
312 /* focus the new widget */
313     CSendMessage (w, FocusIn);
314 }
315 
focus_main(Window main,int type)316 static void focus_main (Window main, int type)
317 {
318     CWidget *w;
319 
320     if (type == FocusOut) {
321 	w = CWidgetOfWindow (current_focus);
322 #ifdef FOCUS_DEBUG
323 	printf ("focus_main(Out): %s\n", w->ident);
324 #endif
325 	current_focus = -1;	/* inform the library that nothing is focussed */
326 	CSendMessage (w, FocusOut);
327 	destroy_focus_border ();
328     } else {			/* type == FocusIn */
329 /* find the widget within this main that last had the focus: */
330 	current_focus = *(get_last_focussed_in_main (main));
331 
332 	w = CWidgetOfWindow (current_focus);
333 #ifdef FOCUS_DEBUG
334 	printf ("focus_main(In): %s\n", w->ident);
335 #endif
336 	if (w) {
337 	    add_to_focus_stack (w->winid);	/* record focus history */
338 	    CSendMessage (w, FocusIn);	/* focus the new widget */
339 	    if ((w->options & WIDGET_TAKES_FOCUS_RING))
340 		create_focus_border (w, 2);
341 	    set_ic_focus (w);
342 	}
343     }
344 }
345 
346 /*
347    This is called from CNextEvent when the WM sends
348    focus to a new different main window.
349  */
process_external_focus(Window win,int type)350 void process_external_focus (Window win, int type)
351 {
352     CWidget *w;
353 #ifdef FOCUS_DEBUG
354     printf ("Entering process_external_focus()\n");
355 #endif
356     w = CWidgetOfWindow (win);
357     if (!w) {
358 #ifdef FOCUS_DEBUG
359 	printf ("Leaving process_external_focus() - not our window\n");
360 #endif
361 	return;
362     }
363 #ifdef FOCUS_DEBUG
364     printf ("widget = %s\n", w->ident);
365 #endif
366     if (w->parentid == CRoot) {	/* should always be true since only
367 				   main windows have FocusChangeMask */
368 	focus_main (w->winid, type);
369 #ifdef FOCUS_DEBUG
370 	printf ("Leaving process_external_focus() - main window\n");
371 #endif
372 	return;
373     } else {
374 #ifdef FOCUS_DEBUG
375 	printf ("Leaving process_external_focus() - not main window\n");
376 #endif
377     }
378 }
379 
focus_window(Window win)380 void focus_window (Window win)
381 {
382     CWidget *w;
383     w = CWidgetOfWindow (win);
384     if (w)
385 	CFocusNormal (w);
386 }
387 
388 /* CFocus() is a macro for */
CFocusNormal(CWidget * w)389 void CFocusNormal (CWidget * w)
390 {
391     if (!w)
392 	return;
393     if (!w->takes_focus)
394 	return;
395     if (w->mapped & WINDOW_MAPPED) {
396 	focus_widget (w);
397     } else {
398 	w->mapped |= WINDOW_FOCUS_WHEN_MAPPED;
399     }
400 }
401 
402 extern int option_never_raise_wm_windows;
403 
CTryFocus(CWidget * w,int raise_wm_window)404 int CTryFocus (CWidget * w, int raise_wm_window)
405 {
406     if (!option_never_raise_wm_windows) {
407 	CFocus (w);
408 	if (raise_wm_window)
409 	    CRaiseWMWindow (w->ident);
410 	return 1;
411     } else {
412 /* focus only if it does not involve switching focus of a WM window: */
413 	CWidget *v;
414 	Window *win;
415 	v = CWidgetOfWindow (CGetFocus ());
416 	if (v)
417 	    if (v->mainid == w->mainid) {
418 		CFocus (w);
419 		return 1;
420 	    }
421 #ifdef FOCUS_DEBUG
422 	printf ("setting last_focussed to %s\n", w->ident);
423 #endif
424 	win = get_last_focussed_in_main (w->mainid);
425 	if (*win)
426 	    add_to_focus_stack (*win);
427 	add_to_focus_stack ((*win = w->winid));
428     }
429     return 0;
430 }
431 
CFocusDebug(CWidget * w,int line,char * file)432 void CFocusDebug (CWidget *w, int line, char *file)
433 {
434 /* NLS ? */
435     printf ("CFocus(%x): %s:%d (ident = %s)\n", (unsigned int) w->winid, file, line, w->ident);
436     CFocusNormal (w);
437 }
438 
439 
440 /* get next sibling of w that has takes_focus set (i.e. that takes user input of any sort) */
CNextFocus(CWidget * w)441 CWidget *CNextFocus (CWidget * w)
442 {
443     int i, j;
444     i = j = find_next_child_of (w->parentid, w->winid);
445     for (;;) {
446 	if (!i) {
447 	    i = find_first_child_of (w->parentid);
448 	    if (!i)
449 		return 0;
450 	}
451 	if (CIndex (i)->takes_focus && !CIndex(i)->disabled)
452 	    return CIndex (i);
453 	w = CIndex (i);
454 	i = find_next_child_of (w->parentid, w->winid);
455 	if (i == j) /* done a round trip */
456 	    return 0;
457     }
458 }
459 
460 /* previous sibling of same */
CPreviousFocus(CWidget * w)461 CWidget *CPreviousFocus (CWidget * w)
462 {
463     int i, j;
464     i = j = find_previous_child_of (w->parentid, w->winid);
465     for (;;) {
466 	if (!i) {
467 	    i = find_last_child_of (w->parentid);
468 	    if (!i)
469 		return 0;
470 	}
471 	if (CIndex (i)->takes_focus && !CIndex(i)->disabled)
472 	    return CIndex (i);
473 	w = CIndex (i);
474 	i = find_previous_child_of (w->parentid, w->winid);
475 	if (i == j) /* done a round trip */
476 	    return 0;
477     }
478 }
479 
480 /* first child of widget that takes focus (eg w is a window and
481     a button in the window is returned) */
CChildFocus(CWidget * w)482 CWidget *CChildFocus (CWidget * w)
483 {
484     int j, i = find_first_child_of (w->winid);
485     if(!i)
486 	return 0;
487     w = CIndex (i);
488     if(w->takes_focus)
489 	return w;
490     j = i = find_next_child_of (w->parentid, w->winid);
491     for (;;) {
492 	if (!i) {
493 	    i = find_first_child_of (w->parentid);
494 	    if (!i)
495 		return 0;
496 	}
497 	if (CIndex (i)->takes_focus)
498 	    return CIndex (i);
499 	w = CIndex (i);
500 	i = find_next_child_of (w->parentid, w->winid);
501 	if (i == j) /* done a round trip */
502 	    return 0;
503     }
504 }
505 
506 /* search for two generations down for the first descendent that is a widget.
507    If it does not take focus, then its first child is focussed.
508    If it has no children, the next descendent is searched for. */
CFindFirstDescendent(Window win)509 CWidget *CFindFirstDescendent (Window win)
510 {
511     int i, j;
512 
513     i = find_first_child_of (win);
514     if (i) {			/* is it a child ? */
515 	if (CIndex (i)->takes_focus && !CIndex (i)->disabled) {
516 	    return (CIndex (i));
517 	} else {
518 	    CWidget *w;
519 	    w = CChildFocus (CIndex (i));
520 	    if (w)
521 		return w;
522 	}
523     } else {			/* not a child */
524 	Window root, parent, *children = 0;
525 	unsigned int nchildren = 0;
526 	if (!win)
527 	    return 0;
528 	XQueryTree (CDisplay, win, &root, &parent, &children, &nchildren);
529 	if (!nchildren) {
530 	    if (children)
531 		XFree (children);
532 	    return 0;
533 	}
534 	for (j = 0; j < nchildren; j++)
535 	    if ((i = find_first_child_of (children[j]))) {	/* is it a grandchild ? */
536 		if (CIndex (i)->takes_focus && !CIndex (i)->disabled) {
537 		    XFree (children);
538 		    return (CIndex (i));
539 		} else {
540 		    CWidget *w;
541 		    w = CChildFocus (CIndex (i));
542 		    if (w) {
543 			XFree (children);
544 			return w;
545 		    }
546 		}
547 	    }
548 	XFree (children);
549     }
550     return 0;			/* not a grandchild */
551 }
552 
553