Skip to content

Input focus

Sergii Stoian edited this page Sep 9, 2020 · 26 revisions

This page is dedicated to description of input focus management problems, findings and my vision of solutions, notes.

Focus Handling Concepts

Definitions

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 - separate window that represents vertically aligned menu items with titlebar and application name on it. Only GNUstep applications have main menu. Other application toolkits use concept of in-window menu (menu bar).

Active application - it's an application that has visible focused window. For GNUstep - application that has main menu visible on screen.

WM - window manager, part of Workspace application that responsible for various window management tasks (draws titlebar and resizebar, application icons, dock, workspaces etc).

Here I describe desired behavior of focus management part of WM.

  1. If there's no application to focus - Workspace should grab focus - Workspace's main menu should become visible.
  2. If last application window was closed:
    • GNUstep: main menu stays on screen, application is active no matter what application was focused before;
    • Xlib: focus switches to previously active application.
  3. If application opens window or attention panel - focus switches to that window/panel.

Actions

There are several actions which either directly or indirectly trigger focus switch:

  1. Window/panel opening (application start, windows creation, Open/Save dialogs, alert panels).
  2. 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.
  3. Click on window decorations (title and resize bars).
  4. Click inside window (contents managed by the application).
  5. Double-click on application icon (in Dock or Icon Yard).
  6. Switch between applications with 'Cmd-Tab' key equivalent.
  7. Miniaturize/deminiaturize window (with mouse or keyboard).
  8. Hide/unhide application (with mouse or keyboard).
  9. Shade/unshade window (with mouse or keyboard).
  10. Workspace switch.

Anatomy of X11 application

In X11 world it's usual to pass focus handling to window manager. X11 applications are responsible for window creating/destroying and providing hints to window manager if particular window can receive input focus. When window appears on screen (MapRequest or MapNotify events) window manager starts managing window to perform its duties. When window disappears from screen (UnmapNotify event), window manager stops management of this window (drops information about window meta data - WWindow) with wUnmanageWindow() function call.

WM takes into account several Xlib windows:

  • group leader - special window that is not visible 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 X11 application starts, WM registers windows with wManageWindow() (window.c) function call and creates WWindow structure. During this process WM saves X11's ID of the window in client_win field 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 for searching and identifying windows which belongs to the 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 traits

Main application menu

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.

Custom focus handling

Unlike other applications every GNUstep application has its own focus management engine. Roughly it works like this:

  • if window loses focus (FocusOut event):
    • if focused window that belongs to this app - transfer focus to it;
    • 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 events when GNUstep application change focused window.

Application to WM communication

In X11 paradigm 2 objects exists: X server and X client. All user applications are X clients. There is special kind of client - window manager - application that helps managing application windows (place on screen, iconify) and handle events reported by X server.

Code

To make things work right, focus management should be intact at both sides: WM and GNUstep application (gnustep-back).

GNUstep

Essential part of communicating with window manager resides in Sources/x11/XGServerEvent.m file of GUI backend (gnustep-back). GNUstep application receives TakeFocus atom from WM and handles it in -_handleTakeFocusAtom:forContext: method (resides in Sources/x11/XGServerEvent.m).

WM

Here I want to descibe essential parts of WM which are involved focus management (either initiate focus change or make changes to focus related data structure inside WM and changing input focus of windows).

WApplication (application.h)

Every running application has WApplication instance. Application exists only if at least one window was mapped. GNUstep application exists until main application menu exists. If window was unmapped from screen WM unmanages this window (remove WWindow structure), window will be managed again upon receiving MapNotify event (WM/src/event.c, handleMapNotify()).

WApplication structure also holds GNUstep application main menu window. Thus WApplication instance must contain:

  • WAppIcon *app_icon - appplication icon
  • WArray *windows - list of windows (at least one) which belongs to application
  • WWindow *menu_win - main application menu window for GNUstep application

It means:

  • X11 application must contain at least app_icon and one element in windows array.
  • GNUstep application must contain at least app_icon and menu_win. For application without windows windows array must contain only one element - menu_win.

To service this purpose Workspace WM - unlike WindowMaker - doesn't unmanage menu_win when application deactivates - it's treated specially:

  • On application start: menu window sends MapNotify, WM starts managing it.
  • On application deactivation (focus switch to other app, application hiding): menu window disappears from screen - UnmapNotify event occurs - but WM doesn't unmanage it.
  • On application activation: GNUstep maps main menu window, MapRequest event is generated (instead of MapNotify). WM recognizes it as main menu window and calls wWindowMap() to proceed with menu window appearing on screen.

Other fields of WApplication

  • main_window - this is the invisible window that identifies application as a group of windows (it's a group leader). Every WWindow and WAppIcon contains field Window main_window. That's how application icon, menu and windows/panels can be connected to present 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 events. 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). This field is set in wSetFocusTo().

GNUstep application handling by WM

On application start wApplicationCreate() is called:

  • creates wapp->windows array
  • adds wwin to this array
  • saved wwin to wapp->menu_win if it's main menu (usually it is)

When new window opens (MapRequest/MapNotify, event.c) wApplicationAdd() is called:

  • adds wwin into wapp->windows array
  • wapp->refcount++

When window is closed (UnmapNotify, event.c) and window is not main menu wApplicationRemoveWindow() is called:

  • removes wwin from wapp->windows array
  • wapp->refcount--

When application quits (DestroyNotify, event.c):

  • several calls to wApplicationRemoveWindow() is performed
  • wApplicationDestroy() is called:
    • wUnmanageWindow() for wapp->menu_win is called
    • wapp->windows array destroyed
    • wapp->refcount--
    • wapp is freed

Points of interest inside WM (Applications/Workspace/WM/src)

There are several user actions which triggers focus switch:

  1. Window opening (including application's start) or closing (including application).

    actions.c: wSetFocusTo()

    event.c: handleMapRequest(), handleMapNotify(), handleUnmapNotify(), handleDestroyNotify()

  2. Click on window decorations (title and resize bars).

    actions.c: wSetFocusTo()

  3. Click inside window (contents managed by the application).

    actions.c: wSetFocusTo()

  4. Double-click on application icon (in Dock or Icon Yard).

    dock.c: iconDblClick(); appicon.c: iconDblClick()

  5. Switch between applications with 'Cmd-Tab' key equivalent.

    switchpanel.c: makeWindowListArray() cycling.c: StartWindozeCycle()

  6. Miniaturize/deminiaturize window (with mouse or keyboard).

  7. Hide/unhide application (with mouse or keyboard).

  8. Workspace switch.

    workspace.c: wWorkspaceForceChange()