[comp.windows.x] Keyboard focus management and the "Cycle" key

josh@mit-vax.LCS.MIT.EDU (Joshua Marantz) (10/11/88)

The "Cycle" key is an excellent feature of DEC's UIS window manager
that allows you to manage windows without taking your hands off the
keyboard.  It does this by assigning a specific function key on the
LK-201 keyboard (F5) to pop the Least Recently Used window and set the
keyboard focus to it.  I have found that this is better for me than
taking my hands off the keyboard, pointing the mouse at the window I
want, and returning my hands to the keyboard.  Of course, most of the
applications that I use are keyboard driven.

I have implemented this feature for "twm" using XGrabKey.  The diffs
are at the end of this posting.  My intention is to hype this feature
for all window managers.  I hope that whoever maintains them would
consider putting in this feature.  Does anyone know whether the
DECwindows and Open Look specifications provide for a "Cycle" key?
I'd hope that at least DECwindows does, since they were successful
with it in UIS.

My real hope was that I could leave my cycle-key processor hanging
around, independent of the window manager.  That way I could set up
the cycle-key-daemon when I logged in, without having to modify the
window manager.  I was hoping that twm would intercept my XRaiseWindow
calls and do the right thing with its own environment.  Maybe when the
ICCCM settles down, this sort of thing will be possible.

My twm implementation is quick and dirty -- it has hardwired
Sun-specific keycodes for L9 and L10.  For twm, it would be best to
provide keyboard bindings in .twmrc (using XGrabKey, which seems to do
the right thing), and have a new function f.cycle that does both an
f.raise and an f.focus.

-Joshua Marantz
Viewlogic Systems, Inc.

"diff -c" for events.c as of "twm Version 3.0 6/3/88" follows
----------------------------------------------------------------
*** original-events.c	Tue Sep 20 17:36:02 1988
--- events.c	Mon Oct 10 16:45:53 1988
***************
*** 74,79 ****
--- 74,86 ----
  int DragHeight;
  static int enter_flag;
  
+ #define CYCLE 1
+ #if CYCLE
+ extern void HandleKeyPress ();
+ extern void HandleKeyRelease ();
+ static void cycle_init ();
+ #endif    
+ 
  /***********************************************************************
   *
   *  Procedure:
***************
*** 82,88 ****
   ***********************************************************************
   */
  
- void
  InitEvents()
  {
      int i;
--- 89,94 ----
***************
*** 107,112 ****
--- 113,123 ----
      EventHandler[ConfigureNotify] = HandleConfigureNotify;
      EventHandler[ClientMessage] = HandleClientMessage;
      EventHandler[PropertyNotify] = HandlePropertyNotify;
+ #if CYCLE
+     EventHandler[KeyPress] = HandleKeyPress;
+     EventHandler[KeyRelease] = HandleKeyRelease;
+     cycle_init ();
+ #endif
  }
  
  /***********************************************************************
***************
*** 1176,1178 ****
--- 1187,1288 ----
      fprintf(stderr, "type = %d\n", event.type);
  #endif
  }
+ 
+ 
+ #if CYCLE
+ /* Begin cycle-key code */
+ 
+ #define CYCLE_KEY 102           /* L9 */
+ #define UNFOCUS_KEY 104         /* L10 */
+ 
+ static void cycle_init() {
+     XGrabKey (dpy,
+               CYCLE_KEY,
+               AnyModifier,      /* modifiers */
+               DefaultRootWindow (dpy),
+               True,             /* Owner events */
+               GrabModeAsync,    /* pointer mode */
+               GrabModeAsync);   /* keybard mode */
+     XGrabKey (dpy,
+               UNFOCUS_KEY,
+               AnyModifier,      /* modifiers */
+               DefaultRootWindow (dpy),
+               True,             /* Owner events */
+               GrabModeAsync,    /* pointer mode */
+               GrabModeAsync);   /* keybard mode */
+ }
+ 
+ static void unfocus() {
+     ExecuteFunction (F_UNFOCUS, NULL, None, NULL, event, C_TITLE, FALSE);
+ }
+ 
+ static int get_twm_frame(window, t)
+     Window window;
+     TwmWindow **t;
+ {
+     if (XFindContext(dpy, window, TwmContext, t) == XCNOENT)
+         return (FALSE);
+     else
+         return (TRUE);
+ }
+ 
+ static void cycle_windows() {
+     Window root_return, parent_return, *children;
+     int status, nchildren, i;
+     TwmWindow *t;
+ 
+     status = XQueryTree (dpy,
+                          DefaultRootWindow (dpy),
+                          &root_return,
+                          &parent_return,
+                          &children,
+                          &nchildren);
+ 
+     if (status == 1) {
+ 
+         /*
+           Find an acceptable window to pop.  Note that XQueryTree
+           returns windows in the array "children" ordered according to
+           the visual stack, from bottom to top.
+         */
+         for (i = 0; i < nchildren; i++) {
+ 
+             /* If twm knows about it and it is not an icon, pop it */
+             if (get_twm_frame (children[i], &t) &&
+                 (children[i] != t -> icon_w))
+             {
+                 /* Always raise the window */
+                 ExecuteFunction (F_RAISE, NULL, children[i],
+                                  t, event, C_TITLE, FALSE);
+ 
+                 /* Only set its focus if it accepts input */
+                 if ((t -> wmhints != NULL) &&
+                     (t -> wmhints -> input == True))   /* Want input */
+                     ExecuteFunction (F_FOCUS, NULL, children[i],
+                                      t, event, C_TITLE, FALSE);
+                 else
+                     unfocus ();
+ 
+                 break;
+             }
+         }
+         XFree (children);
+     }
+ }
+ 
+ void HandleKeyPress() {
+     XKeyEvent *kep;
+ 
+     if (event.type == KeyPress) {
+         kep = (XKeyEvent *) &event;
+ 
+         if (kep -> keycode == CYCLE_KEY)
+             cycle_windows ();
+         else if (kep -> keycode == UNFOCUS_KEY)
+             unfocus ();
+     }
+ }
+ 
+ void HandleKeyRelease() {
+ }
+ #endif

josh@mit-vax.LCS.MIT.EDU (Joshua Marantz) (10/11/88)

After having used my twm cycle key for a while, I realize it has a
couple of problems.  When running in a focussed environment, it is
helpful if the window manager sets the focus to new windows as they
are created, and resets the focus to the most recently used window
when the window with the focus is destroyed.

Another difference between UIS and the modified twm is that if you use
the mouse to pop a UIS window, the window that previously held the
focus will be first on the cycle-stack.  To do this with twm, twm's
internal list of windows would have to be used rather than XQueryTree.
This requires more extensive changes to twm.

Emacs 18.52 has a problem in that is blackens its text cursor whenever
the mouse cursor enters its window, regardless of the focus.  This is
very misleading.  Another problem is that xclock seems to have set the
"input" field of the window manager hints to True, causing focus to be
(uselessly) set to it as I cycle around.  The way I coded it, the
focus should be set to the root when popping an output-only window,
though I'm not even sure I'd like that.  As it stands, I cannot use
the input field to differentiate xclock from other applications.  Are
there any other ways to decide an application does not want the kbd?

-Joshua Marantz
Viewlogic Systems, Inc.

swick@ATHENA.MIT.EDU (Ralph R. Swick) (10/11/88)

>  Another problem is that xclock seems to have set the
> "input" field of the window manager hints to True

This is a bug and has been fixed for R3.