-
Notifications
You must be signed in to change notification settings - Fork 71
Input focus
This page is dedicated to description of input focus management problems, findings and my vision of solutions.
Focused window - window with titlebar of black color and white title text. Focused window receives keyboard input.
In OpenStep focused window often referred as key window
.
Main menu - only for GNUstep applications - separate window that represents vertically aligned menu items with titlebar and application name on it.
Active application - it's an application that has visible focused window. For GNUstep - application that has main menu visible on screen.
Here I describe desired behavior of focus management part of Workspace Window Manager (WM).
- If there's no application to focus - Workspace should grab focus - Workspace's main menu should become visible.
- If last application window was closed:
- GNUstep: main menu stays on screen, application is active;
- Xlib: focus switches to previously active application.
- If application opens window or attention panel - focus switches to currently focused window (main menu appears for GNUstep applications).
There are several actions which either directly or indirectly trigger focus switch.
- Window/panel opening:
- during start of application;
- new document creation;
- Open/Save dialogs;
- alert panels.
- Window closing:
- initiated by application (user selects menu item, use keyboard shortcuts);
- initiated by WM (titlebbar "Close"/"Kill" button click);
- GNUstep application deactivation: main menu, some panels.
- Click on window decorations (title and resize bars).
- Click inside window (contents managed by the application).
- Double-click on application icon (in Dock or Icon Yard).
- Switch between applications with 'Cmd-Tab' key equivalent.
- Miniaturize/deminiaturize window (with mouse or keyboard).
- Hide/unhide application (with mouse or keyboard).
- Shade/unshade window (with mouse or keyboard).
- Workspace switch.
In X11 world it's normal to pass focus management functions to window manager. X11 applications are responsible for window creation/destroying and providing hints to window manager (as described in previous section) if particular window can receive input focus. When window appears on screen window manager starts managing window to perform its duties. When window is withdrawn from screen (UnmapNotify event), window manager stops management of this window (drops information about window meta data) with wUnmanageWindow() function call.
WM takes into account several Xlib windows:
-
group leader - special window: not visible (withdrawn) on screen, with
XWMHints.window_group
set to window created with XCreateWindow(); - one or more windows with
XWMHints.window_group
set to group leader window.
When Xlib application starts, WM register windows with wManageWindow()
(window.c) function call. This function
returns structure WWindow
. During registering WM, among other, saves Xlib's ID of the window Window client_win
and "group leader" into Window main_window
. After WWindow
structure was filled, WM can identify window's
application by reading it's main_window
field. That's simple. Set of WWindow is a linked list (every WWindow has a
link next and previous WWindow).
In other words - application can exist if at least 2 windows were created: "group leader" and window with
XWMHints.window_group
set to "group leader" window ID. So "group leader" is a key search and identify windows which
belongs to application.
Another meta information is created on application windows appearing: WApplication. This structure contains some useful information about application - main window ("group leader" again), application icon, last focused window, array of windows, last workspace application was active on, etc. Set of WApplication is also a linked list.
When one application's window closes (UnmapNotify), WWindow structure is removed from WApplicatiton and deleted. When application is destroyed (DestroyNotify) WApplication is deleted.
GNUstep applications are special because they have special window: main menu. When GNUstep application deactivates it hides main menu (and vice versa: main menu appears on screen when application becomes active). From window manager perspective main menu window becomes withdrawn from screen (unmapped). So window manager stops managing such windows. It can be a problem because minimal GNUstep application contains only main menu and application becomes invisible for WM on main menu withdrawal from screen.
That's why I added menu_win
field to WApplication structure and prevent deletion of main menu window's WWindow
.
This is important part of focus switch to GNUstep application. For example, Cmd-Tab switch panel basically uses
WApplication information. When user makes selection, WM needs some application's window to switch focus to.
For GNUstep applications it's main menu window at least.
Unlike other applications every GNUstep application has its own focus management engine. Roughly it works like this:
- if window loses focus (
FocusOut
event):- there is other window that belongs to this app - transfer focus to it;
- otherwise focus goes to main menu;
- if focused window doesn't belong to this app - app deactivates
- if window receives focus (
FocusIn
event,WM_TAKE_FOCUS
protocol message):- if app is active - set focus to window;
- if app is inactive - activates it (main menu becomes visible) and set focus to window.
Custom focus handling of GNUstep application should be treated specially by WM. For example, application activation generates extra events (MapNotify, FocusIn/FocusOut). WM has its own window focus stack and should correctly respond to focus changes when GNUstep application change focused window.
/*Let me illustrate the diffrences between WM and GNUstep worlds. Example 1: you have Firefox(X11) window focused and manually switch to Emacs(X11) clicking inside Emacs window. WM:
- Firefox window marked as "unfocused" - titlebar becomes light gray.
- Input focus is set to Emacs window (
XSetInputFocus
in wSetFocusTo). - Firefox window placed next to Emacs window in focus list order (wSetFocusTo).
- Emacs window becomes "focused" and titlebar becomes black (wWindowFocus). Example 2: you have Firefox(X11) window focused and manually switch to TextEdit(GNUstep) clicking inside document window "UNTITLED.rtf". WM:
- Firefox window marked as "unfocused" - titlebar becomes light gray (wSetFocusTo).
- Send
WM_TAKE_FOCUS
in wSetFocusTo. - Firefox window placed next to TextEdit window in focus list order (wSetFocusTo). TextEdit:
- TextEdit shows main menu (MapNotify, FocusIn events).
- TextEdit set input focus to document window, it becomes "focused" and titlebar becomes black (event.c, handleFocusIn()->wSetFocusTo()).*/
Essential part of communicating with window manager resides in Sources/x11/XGServerEvent.m file of GUI backend (gnustep-back). To make things work right, focus management should be intact at both sides: window manager and GNUstep application GUI backend.
- (NSEvent *)_handleTakeFocusAtom:(XEvent)xEvent forContext:(NSGraphicsContext *)gcontext
Original version of GNUstep GUI backend sends only first click on appicon to WM. But double-click actions should be handled by WM too. I made modification to original GNUstep code to make things work.
if (generic.flags.useWindowMakerIcons == 1)
{
/*
* We must hand over control of our icon/miniwindow
* to Window Maker.
*/
if ((cWin->win_attrs.window_style
& (NSMiniWindowMask | NSIconWindowMask)) != 0
&& eventType == NSLeftMouseDown /*&& clickCount == 1*/) <<< commented out
{
if (cWin->parent == None)
break;
xEvent.xbutton.window = cWin->parent;
XUngrabPointer(dpy, CurrentTime);
XSendEvent(dpy, cWin->parent, True, ButtonPressMask, &xEvent);
XFlush(dpy);
if (clickCount != 2) <<< added
break;
}
}
Every running application has WApplication instance. Normal X11 application exists only if at least one window was mapped.
WApplication instance for registered application should contain defined:
- app_icon - appplication icon
- windows - list of windows (at least one) which belongs to application
- menu_win for GNUstep application
GNUstep application minimal appearance is appicon and menu. For focus handling/switching tasks menu_win
must be set to
make correct focus switching.
main_window
- this is the invisible window that identifies application as a group of windows. Also it's called as
"group leader". Every WWindow and WAppIcon contains field Window main_window
. That's how application icon, menu and
windows/panels can be identified as single application.
main_window_desc
- generated WWindow structure for main_window
. So main_window
can be used as managed WWindow.
last_focused
- should be set to last window of application that has focus before FocusOut, hide, workspace switch e
vents. Set in wSetFocusTo()
.
last_workspace
- contains workspace number of last_focused window workspace. If, for exmaple, application has 2
windows on different workspaces, double-click on appicon should: switch to workspace where last_focused window resides,
set focus to that window (activate application). Set in wSetFocusTo()
.
On application start wApplicationCreate() is called:
- creates wapp->windows array
- adds
wwin
to this array - saved
wwin
towapp->menu_win
if it's main menu (normally it is)
When new window opens (MapRequest/MapNotify, event.c) wApplicationAdd() is called:
- adds
wwin
intowapp->windows
array wapp->refcount++
When window is closed (UnmapNotify, event.c) and window is not main menu wApplicationRemoveWindow()
is called:
- removes
wwin
fromwapp->windows
array wapp->refcount--
When application quits (DestroyNotify, event.c):
- several calls to
wApplicationRemoveWindow()
is performed -
wApplicationDestroy()
is called:-
wUnmanageWindow()
forwapp->menu_win
is called -
wapp->windows
array destroyed wapp->refcount--
-
wapp
is freed
-
There are several user actions which triggers focus switch:
-
Window opening (including application's start) or closing (including application).
actions.c:
wSetFocusTo()
event.c:
handleMapRequest()
,handleMapNotify()
,handleUnmapNotify()
,handleDestroyNotify()
-
Click on window decorations (title and resize bars).
actions.c:
wSetFocusTo()
-
Click inside window (contents managed by the application).
actions.c:
wSetFocusTo()
-
Double-click on application icon (in Dock or Icon Yard).
dock.c:
iconDblClick()
; appicon.c:iconDblClick()
-
Switch between applications with 'Cmd-Tab' key equivalent.
switchpanel.c:
makeWindowListArray()
cycling.c:StartWindozeCycle()
-
Miniaturize/deminiaturize window (with mouse or keyboard).
-
Hide/unhide application (with mouse or keyboard).
-
Workspace switch.
workspace.c:
wWorkspaceForceChange()
Copyright (c) Sergii Stoian