index

How to make Sawfish distribute the focus reliably (in a deterministic way)

(this text is from 2004? current work is done on sawfish-mmc and this text is obsolete/unmaintained now.)

Summary:

In Sawfish WM there have been problems with focus being mishandled at the moment, when certain types of applications closed their windows.

That is, the user (you) closed the window(s) the right way --- instructing the application to do so---- and sawfish suddenly didn't focus any other window.

I have looked at the problem closely 2 times, and this time i think i got it right.

Contents: For first reading skip the first 2 sections:

the story of the focus problem:

The first time i claimed:


I tried to trace down, what happened and this is the conclusion:

This way unmap-notify-hook is not run (because at the moment, when unmap notify event arrives, the window is already gone).

How to solve this problem: probably the best would be to really run the hooks. I've not tried to implement it.

What i did instead is: change the function commit_queued_focus_change (void), and introduce a new hook:

(require 'sawfish.wm.util.window-order)
(add-hook 'lost-focus-hook
	  (lambda ()
	    ;;(message "lost-focus-hook")
	    (unless (input-focus)
	      (window-order-focus-most-recent))))

Other people at that time chose another solution, entirely implemented in lisp:
(add-hook 'destroy-notify-hook window-order-focus-most-recent)


introduction

(skip to What's wrong with remove_window, this is just how i got to this problem the 2nd time)

I have all the sawfish C sources full of tracing/debugging code (printf) to see the flow. I can conditionally (de-)activate selected parts (handling focus, key-events ...).

In April 2004, i was able to re-read the Xlib manual more rigorously, and returned to my idea of:

freezing keyboard while we wait for the right window to focus.

(What does that mean:)

imagine you have a window, let's say it's an Xterm w/ plain shell inside. Suddenly you want to switch to that window, and issue a command, say ls.

In sawfish, i can switch to a window by using wid.jl, which once invoked, asks for a key (event) and using that key as a bookmark finds the right window, and transfers the input focus in it, and also changes the vieport, workspace appropriately. (let's say the desired xterm is bookmarked as "s").

So, to issue "ls {RET}" in that xterm, i could type: "H-a s ls {RET}". But there's a problem. When Sawfish reads "H-a s", it tells the X server, that it has stopped needing the keyboard (that means that the focus returns to the window focused before), and then it makes all the tricks w/ other windows to present the virtual Workspace/viewport, and only then it requests to focus the desired Xterm.

But the X server has it's own policy on the focus, and when the original window is unmapped (by sawfish, b/c the window is not on the destination workspace), X hands the focus over to another window ---- one currently under the pointer. The result is that the keys "ls" might end in another window, not the xterm i wanted.

So, i started to get under my control the keyboard grab (XAllowKeys, XUngrabKeyboard). Nowadays I have all this working.

But working on this, i noticed, that my hack on lost-focus-hook causes some useless focus changes. So i needed to avoid that first. ... That's why i solved the focus problem & wrote this page.

What's wrong with remove_window

.... it is the root of the problem (unmap-notify-hook being skipped). See why:

When a window is closed, this is the sequence of events which arrive from X server to Sawfish:

How does Sawfish react on this ?

on the first property_notify SF tries to request the new property of the window, but (unless the closing application is really very slow) the window is already gone for X server, so we are 'given' an error, therefore run the error_handler, and since the type of the error is "invalid window" we invoke remove_window.

Now, i claim that remove_window is overcomplicated function. It is called from 4 very different places:

And currently it does too much, when called from error_handler. Besides other, it marks the window in order NOT to invoke any other X requests (that's ok for me) on it and also that it can be destroyed, i.e. removed from our (sawfish's) data structures (wrong for me).

Now, before processing any other event, the events-processing-loop handle_input_mask runs emit_pending_destroys, and the window is in fact removed from our datas, and destroy-notify-hook is run. Note, that destroy-notify-hook is not run for the destroyNotify event!

X server (probably. i should find a proof!) can recycle window IDs (WID), so Sawfish is afraid of getting confused, and tries to remove anything connected w/ the WID, as soon as it is told that the old window is gone, and new one (completely unrelated) could appear.

[24 dic 2005] Xlib does not recycle resource IDs! I wasted a lot of time.

But we still have on the queue all the events related to the old window !!!

Without the window being registered in the data, we cannot invoke the unmap-notify-hook (destroy-notify-hook has already been run).

Constraints on the solution

(maintining existing API, and adding the 'determinism')

To illustrate the importance:

quoting from /usr/share/sawfish/1.2/lisp/sawfish/wm/state/transient.jl: If a transient window gets unmapped that currently has the input focus, pass it (the focus) to its parent.

if i have a (2 level) chain of such windows, A (transient for ) B (transient to) C, and closing A implies closing B, C should be focused by this rule. But now Sawfish can fail to do it.

solution proposal:

Which only marks the window as: don't call any X request on it anymore. That's when WINDOW_IS_GONE_P should return True.

   if (w->id == queued_focus_id)
      {
         Fcall_window_hook (Qlost_focus_hook, rep_VAL(w), Qnil, Qnil);
      }