Hi all,

Find attached a patch to allow sending of arbitrary key events to
windows. It introduces a new command to FVWM: FakeKeypress

Examples of use:

# save all GVim sessions: Esc:w\n
FvwmCommand 'All (gvim) FakeKeypress press Escape press colon press w press 
Return'
# save & exit all GVim sessions: Esc:wq\n
FvwmCommand 'All (gvim) FakeKeypress press Escape press colon press w press q 
press Return'

# 1984: Do we live under a totalitarian regime?
FvwmCommand 'All (gcalc) FakeKeypress press 2 press plus press 2 press equal'

# Send A to a specific window.
FvwmCommand 'WindowId 0x3800002 FakeKeypress press A'

SCoTT. :)
-- 
[EMAIL PROTECTED]
Index: fvwm/builtins.c
===================================================================
RCS file: /home/cvs/fvwm/fvwm/fvwm/builtins.c,v
retrieving revision 1.405
diff -u -r1.405 builtins.c
--- fvwm/builtins.c     19 Feb 2004 09:36:01 -0000      1.405
+++ fvwm/builtins.c     6 Mar 2004 02:43:25 -0000
@@ -81,7 +81,7 @@
 /* ---------------------------- included code files ------------------------ */
 
 /* ---------------------------- local types -------------------------------- */
-
+typedef enum {FakeMouseEvent, FakeKeyEvent} FakeEventType;
 /* ---------------------------- forward declarations ----------------------- */
 
 /* ---------------------------- local variables ---------------------------- */
@@ -3718,7 +3718,39 @@
        return;
 }
 
-void CMD_FakeClick(F_CMD_ARGS)
+/* Determine which modifiers are required with a keycode to make <keysym>. */
+static Bool FKeysymToKeycode (Display *dpy, KeySym keysym,
+       unsigned int *keycode, unsigned int *modifiers)
+{
+       int m;
+
+       *keycode = XKeysymToKeycode(dpy, keysym);
+       *modifiers = 0;
+
+       for (m = 0; m <= 8; ++m)
+       {
+               KeySym ks = XKeycodeToKeysym(dpy, *keycode, m);
+               if (ks == keysym)
+               {
+                       switch (m)
+                       {
+                               case 0: /* No modifiers */
+                                       break;
+                               case 1: /* Shift modifier */
+                                       *modifiers |= ShiftMask;
+                                       break;
+                               default:
+                                       fvwm_msg(ERR, "FKeysymToKeycode",
+                                               "Unhandled modifier %d", m);
+                                       break;
+                       }
+                       return True;
+               }
+       }
+       return False;
+}
+
+static void fakeEvent(F_CMD_ARGS, FakeEventType type)
 {
        char *token;
        char *optlist[] = {
@@ -3732,19 +3764,19 @@
        unsigned int mask = 0;
        Window root = Scr.Root;
        int maxdepth = 0;
+       static char args[128];
+       strncpy(args, action, sizeof(args) - 1);
+
+       /* get the mask of pressed/released buttons/keys */
+       FQueryPointer(
+               dpy, Scr.Root, &root, &JunkRoot, &JunkX, &JunkY, &JunkX,
+               &JunkY, &mask);
 
-       /* get the mask of pressed/released buttons */
-       if (FQueryPointer(
-                   dpy, Scr.Root, &root, &JunkRoot, &JunkX, &JunkY, &JunkX,
-                   &JunkY, &mask) == False)
-       {
-               /* pointer is on a different screen - that's okay here */
-       }
        token = PeekToken(action, &action);
        while (token && action)
        {
                int index = GetTokenIndex(token, optlist, 0, NULL);
-               int val;
+               int val, depth;
                XEvent e;
                Window w;
                Window child_w;
@@ -3754,13 +3786,9 @@
                int ry = 0;
                Bool do_unset;
                long add_mask = 0;
+               KeySym keysym = NoSymbol;
 
                XFlush(dpy);
-               if (GetIntegerArguments(action, &action, &val, 1) != 1)
-               {
-                       /* error */
-                       return;
-               }
                do_unset = True;
                switch (index)
                {
@@ -3770,38 +3798,61 @@
                        /* fall through */
                case 2:
                case 3:
-                       /* button press or release */
-                       if (val >= 1 && val <= NUMBER_OF_MOUSE_BUTTONS)
+                       /* key/button press or release */
+                       if (type == FakeMouseEvent)
                        {
-                               int depth = 1;
-
-                               w = None;
-                               child_w = root;
-                               for (depth = 1; depth != maxdepth &&
-                                            w != child_w && child_w != None;
-                                    depth++)
-                               {
-                                       w = child_w;
-                                       if (FQueryPointer(
-                                                   dpy, w, &root, &child_w,
-                                                   &rx, &ry, &x, &y,
-                                                   &JunkMask) == False)
-                                       {
-                                               /* pointer is on a different
-                                                * screen - that's okay here */
-                                       }
+                               if ((GetIntegerArguments(action, &action, &val, 
1) != 1) ||
+                                       val < 1 || val > 
NUMBER_OF_MOUSE_BUTTONS)
+                               {
+                                       fvwm_msg(ERR, "fakeEvent",
+                                               "Invalid button specifier in 
\"%s\" for FakeClick.",
+                                               args);
+                                       return; /* error */
                                }
-                               if (do_unset)
+                       }
+                       else /* type == FakeKeyEvent */
+                       {
+                               char *key = PeekToken(action, &action);
+                               if (key == NULL)
                                {
-                                       e.type = ButtonRelease;
-                                       add_mask = ButtonPressMask;
+                                       fvwm_msg(ERR, "fakeEvent",
+                                               "No keysym specifier in \"%s\" 
for FakeKeypress.",
+                                               args);
+                                       return;
                                }
-                               else
+
+                               // Do *NOT* use FvwmStringToKeysym() as it is
+                               // case insensitive.
+                               keysym = XStringToKeysym(key);
+                               if (keysym == NoSymbol)
                                {
-                                       e.type = ButtonPress;
-                                       add_mask = ButtonPressMask |
-                                               ButtonReleaseMask;
+                                       fvwm_msg(ERR, "fakeEvent",
+                                               "Invalid keysym specifier (%s) 
in \"%s\" for "
+                                               "FakeKeypress.", key, args);
+                                       return;
                                }
+                       }
+
+                       w = None;
+                       child_w = root;
+                       for (depth = 1;
+                                depth != maxdepth && w != child_w && child_w 
!= None;
+                                depth++)
+                       {
+                               w = child_w;
+                               if (FQueryPointer(
+                                               dpy, w, &root, &child_w,
+                                               &rx, &ry, &x, &y,
+                                               &JunkMask) == False)
+                               {
+                                       /* pointer is on a different
+                                        * screen - that's okay here */
+                               }
+                       }
+
+                       if (type == FakeMouseEvent)
+                       {
+                               e.type = (do_unset ? ButtonRelease : 
ButtonPress);
                                e.xbutton.display = dpy;
                                e.xbutton.window = w;
                                e.xbutton.subwindow = None;
@@ -3814,10 +3865,11 @@
                                e.xbutton.button = val;
                                e.xbutton.state = mask;
                                e.xbutton.same_screen = (Scr.Root == root);
-                               FSendEvent(
-                                       dpy, PointerWindow, True,
-                                       SubstructureNotifyMask | add_mask, &e);
-                               XFlush(dpy);
+                               /* SS: I think this mask handling code is buggy.
+                                * The value of <mask> is overridden during a
+                                * "wait" operation. Also why are we only using
+                                * Button1Mask? What if the user has requested
+                                * a FakeClick using some other button? */
                                if (do_unset)
                                {
                                        mask &= ~(Button1Mask << (val - 1));
@@ -3826,37 +3878,76 @@
                                {
                                        mask |= (Button1Mask << (val - 1));
                                }
+                               add_mask = (do_unset ? ButtonPressMask : 
ButtonReleaseMask);
                        }
-                       else
+                       else /* type == FakeKeyEvent */
                        {
-                               /* error */
-                               return;
+                               e.type = (do_unset ? KeyRelease : KeyPress);
+                               e.xkey.display = dpy;
+                               e.xkey.subwindow = None;
+                               e.xkey.root = root;
+                               e.xkey.time = fev_get_evtime();
+                               e.xkey.x = x;
+                               e.xkey.y = y;
+                               e.xkey.x_root = rx;
+                               e.xkey.y_root = ry;
+                               e.xkey.same_screen = (Scr.Root == root);
+
+                               w = e.xkey.window = exc->w.w;
+
+                               if (FKeysymToKeycode(dpy, keysym,
+                                       &(e.xkey.keycode), &(e.xkey.state)) != 
True)
+                               {
+                                       fvwm_msg(DBG, "fakeEvent",
+                                               "FKeysymToKeycode failed");
+                                       return;
+                               }
+                               e.xkey.state |= mask;
+                               add_mask = (do_unset ? KeyReleaseMask : 
KeyPressMask);
+#if 0
+                               fvwm_msg(DBG, "fakeEvent",
+                                       "state=%d keysym=%ld keycode=%d",
+                                       e.xkey.state, keysym, e.xkey.keycode);
+
+                               fvwm_msg(DBG, "fakeEvent",
+                                       "str=%s", XKeysymToString(keysym));
+#endif
                        }
+
+                       FSendEvent(dpy, w, True,
+                               SubstructureNotifyMask | add_mask, &e);
+                       XFlush(dpy);
                        break;
                case 4:
                case 5:
                        /* wait */
-                       if (val > 0 && val <= 1000000)
+                       if ((GetIntegerArguments(action, &action, &val, 1) != 
1) ||
+                               val <= 0 || val > 1000000)
                        {
-                               usleep(1000 * val);
-                               if (FQueryPointer(
-                                           dpy, Scr.Root, &root, &JunkRoot,
-                                           &JunkX, &JunkY, &JunkX, &JunkY,
-                                           &mask) == False)
-                               {
-                                       /* pointer is on a different screen -
-                                        * that's okay here */
-                               }
+                               fvwm_msg(ERR, "fakeEvent",
+                                       "Invalid wait value in \"%s\"", args);
+                               return;
                        }
-                       else
+
+                       usleep(1000 * val);
+                       if (FQueryPointer(
+                                       dpy, Scr.Root, &root, &JunkRoot,
+                                       &JunkX, &JunkY, &JunkX, &JunkY,
+                                       &mask) == False)
                        {
-                               /* error */
-                               return;
+                               /* pointer is on a different screen -
+                                * that's okay here */
                        }
                        break;
                case 6:
                case 7:
                        /* set modifier */
+                       if (GetIntegerArguments(action, &action, &val, 1) != 1)
+                       {
+                               fvwm_msg(ERR, "fakeEvent",
+                                       "Invalid modifier value in \"%s\"", 
args);
+                               return;
+                       }
                        do_unset = False;
                        if (val < 0)
                        {
@@ -3884,6 +3975,8 @@
                                /* error */
                                return;
                        }
+                       /* SS: Could be buggy if a "modifier" operation
+                        * preceeds a "wait" operation. */
                        if (do_unset)
                        {
                                mask &= ~val;
@@ -3896,10 +3989,17 @@
                case 8:
                case 9:
                        /* new max depth */
+                       if (GetIntegerArguments(action, &action, &val, 1) != 1)
+                       {
+                               fvwm_msg(ERR, "fakeEvent",
+                                       "Invalid depth value in \"%s\"", args);
+                               return;
+                       }
                        maxdepth = val;
                        break;
                default:
-                       /* error */
+                       fvwm_msg(ERR, "fakeEvent",
+                               "Invalid command (%s) in \"%s\"", token, args);
                        return;
                }
                if (action)
@@ -3909,6 +4009,16 @@
        }
 
        return;
+}
+
+void CMD_FakeClick(F_CMD_ARGS)
+{
+       fakeEvent(F_PASS_ARGS, FakeMouseEvent);
+}
+
+void CMD_FakeKeypress(F_CMD_ARGS)
+{
+       fakeEvent(F_PASS_ARGS, FakeKeyEvent);
 }
 
 /* A function to handle stroke (olicha Nov 11, 1999) */
Index: fvwm/commands.h
===================================================================
RCS file: /home/cvs/fvwm/fvwm/fvwm/commands.h,v
retrieving revision 1.41
diff -u -r1.41 commands.h
--- fvwm/commands.h     16 Jul 2003 09:10:40 -0000      1.41
+++ fvwm/commands.h     6 Mar 2004 02:43:25 -0000
@@ -62,6 +62,7 @@
        F_EXEC,
        F_EXEC_SETUP,
        F_FAKE_CLICK,
+       F_FAKE_KEYPRESS,
        F_FOCUSSTYLE,
        F_FUNCTION,
        F_GLOBAL_OPTS,
@@ -268,6 +269,7 @@
 P(Exec);
 P(ExecUseShell);
 P(FakeClick);
+P(FakeKeypress);
 P(FlipFocus);
 P(Focus);
 P(FocusStyle);
Index: fvwm/functable.c
===================================================================
RCS file: /home/cvs/fvwm/fvwm/fvwm/functable.c,v
retrieving revision 1.29
diff -u -r1.29 functable.c
--- fvwm/functable.c    16 Jul 2003 09:10:40 -0000      1.29
+++ fvwm/functable.c    6 Mar 2004 02:43:28 -0000
@@ -259,6 +259,10 @@
        CMD_ENT("fakeclick", CMD_FakeClick, F_FAKE_CLICK, 0, 0),
        /* - Generate a mouse click */
 
+       CMD_ENT("fakekeypress", CMD_FakeKeypress, F_FAKE_KEYPRESS,
+               0, 0),
+       /* - Send a keyboard event to a window */
+
        CMD_ENT("flipfocus", CMD_FlipFocus, F_FLIP_FOCUS,
                FUNC_NEEDS_WINDOW, CRS_SELECT),
        /* - Focus a window without rotating windowlist order */
Index: fvwm/fvwm.1.in
===================================================================
RCS file: /home/cvs/fvwm/fvwm/fvwm/fvwm.1.in,v
retrieving revision 1.123
diff -u -r1.123 fvwm.1.in
--- fvwm/fvwm.1.in      19 Feb 2004 09:36:01 -0000      1.123
+++ fvwm/fvwm.1.in      6 Mar 2004 02:44:04 -0000
@@ -4025,6 +4025,51 @@
 2) with a delay of 250 milliseconds between the press and the
 release.
 
+
+.TP
+.BI "FakeKeypress [" "command value" "] ..."
+This command is mainly intended for debugging fvwm and no
+guarantees are made that it works for you.
+.B FakeKeypress
+can simulate key press and release events and pass them
+to fvwm or applications.  The parameters are a list of
+commands which consist of pairs of command tokens and values.
+The
+.IR press " and " release
+commands are followed by the name of a keysym. Standard KeySym
+names are obtained from <X11/keysymdef.h> by removing the XK_
+prefix from each name.
+The
+.I wait
+and
+.I modifiers
+commands are the same as those used by FakeClick.
+
+Save all GVim sessions with: "Esc:w\\n"
+.EX
+All (gvim) FakeKeypress press Escape \\
+                        press colon \\
+                        press w \\
+                        press Return
+.EE
+
+
+Save & exit all GVim sessions with: "Esc:wq\\n"
+.EX
+All (gvim) FakeKeypress press Escape \\
+                        press colon \\
+                        press w \\
+                        press q \\
+                        press Return
+.EE
+
+
+Send A to a specific window:
+.EX
+WindowId 0x3800002 FakeKeypress press A
+.EE
+
+
 .TP
 .BI "GlobalOpts [" options "]"
 As announced in the past, this command has been removed.  Please

Reply via email to