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