Skip to content

Input focus

Sergii edited this page Dec 11, 2018 · 26 revisions

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

Points of interest

There are several user actions which triggers focus switch:

  1. Window open (including application's start) or close (including application's quit).

    [actions.c: wSetFocusTo()]

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

  2. Click on window titlebar.

    [actions.c: wSetFocusTo()]

  3. Click inside window.

    [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()]

Focus Handling Concepts

Here I describe desired behavior of focus management part of Workspace Window Manager (WM).

  1. If there's no application to focus - Workspace should grab focus - main menu should be visible.

Anatomy of X11 application from WM's POV

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, set with XWMHints.window_group 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 usefull 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

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. And 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 selction, WM needs some window to switch focus to. For GNUstep applications it's main menu window.

AutoLaunch

Code

GNUstep's part

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.

Process receiving of TakeFocus atom from Window Manager

- (NSEvent *)_handleTakeFocusAtom:(XEvent)xEvent forContext:(NSGraphicsContext *)gcontext

Processing of ButtonPress event

Original version of GNUstep GUI backend sends only first click on appicon to WM. Double-click operations should be handled by WM too. I made modification to original GNUstep code 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;
        }
    }

Window Manager's part

WApplication (application.h)

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.

Description of some field inside struct WApplication {}

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 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). Set in wSetFocusTo().

New 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 (normally 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