Subject: PATCH: FakeKeypress

From: Scott Smedley <[email-masked]>
Date: Sat, 6 Mar 2004 13:44:39 +1100

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-masked]
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

---

From: Dominik Vogt <[email-masked]>
Date: Mon, 15 Mar 2004 15:15:53 +0100

> FvwmCommand 'WindowId 0x3800002 FakeKeypress press A'

I have applied the patch with some minor reformatting and
renaming - nothing serious.  Works like a charm, at least for some
applications (namely, FvwmConsole ignores it).

Ciao

Dominik ^_^  ^_^
--
Visit the official FVWM web page at <URL:http://www.fvwm.org/>.
To unsubscribe from the list, send "unsubscribe fvwm-workers" in the
body of a message to [email-masked].
To report problems, send mail to [email-masked]

---

From: Scott Smedley <[email-masked]>
Date: Tue, 16 Mar 2004 10:45:40 +1100

Hi Dominik,

: I have applied the patch with some minor reformatting and
: renaming - nothing serious.  Works like a charm, at least for some
: applications (namely, FvwmConsole ignores it).

Hmmmm, it doesn't appear to work with xterms ...

Module FvwmConsole -terminal rxvt

will make FakeKeypress work with FvwmConsole.

SCoTT.

---

From: Dominik Vogt <[email-masked]>
Date: Tue, 16 Mar 2004 09:23:43 +0100

> will make FakeKeypress work with FvwmConsole.

Well, we can do little about it.  If xterm doesn't want synthetic
KeyPress events we can not force it to.

Ciao

Dominik ^_^  ^_^