diff options
Diffstat (limited to 'backends')
| -rw-r--r-- | backends/wininput.c | 148 | ||||
| -rw-r--r-- | backends/wininput.h | 10 | ||||
| -rw-r--r-- | backends/wininput.md | 114 | 
3 files changed, 239 insertions, 33 deletions
diff --git a/backends/wininput.c b/backends/wininput.c index 352be66..46151d4 100644 --- a/backends/wininput.c +++ b/backends/wininput.c @@ -4,6 +4,55 @@  #include <string.h>  #include "wininput.h" +static key_info keys[] = { +	{VK_LBUTTON, "lmb", button}, {VK_RBUTTON, "rmb", button}, {VK_MBUTTON, "mmb", button}, +	{VK_XBUTTON1, "xmb1", button}, {VK_XBUTTON2, "xmb2", button}, +	{VK_BACK, "backspace"}, +	{VK_TAB, "tab"}, +	{VK_CLEAR, "clear"}, +	{VK_RETURN, "enter"}, +	{VK_SHIFT, "shift"}, +	{VK_CONTROL, "control"}, {VK_MENU, "alt"}, +	{VK_CAPITAL, "capslock"}, +	{VK_ESCAPE, "escape"}, +	{VK_SPACE, "space"}, +	{VK_PRIOR, "pageup"}, {VK_NEXT, "pagedown"}, +	{VK_END, "end"}, {VK_HOME, "home"}, +	{VK_PAUSE, "pause"}, {VK_NUMLOCK, "numlock"}, {VK_SCROLL, "scrolllock"}, +	{VK_INSERT, "insert"}, {VK_DELETE, "delete"}, {VK_SNAPSHOT, "printscreen"}, +	{VK_LEFT, "left"}, {VK_UP, "up"}, {VK_RIGHT, "right"}, {VK_DOWN, "down"}, +	{VK_SELECT, "select"}, +	{VK_PRINT, "print"}, +	{VK_EXECUTE, "execute"}, +	{VK_HELP, "help"}, +	{VK_APPS, "apps"}, +	{VK_SLEEP, "sleep"}, +	{VK_NUMPAD0, "num0"}, {VK_NUMPAD1, "num1"}, {VK_NUMPAD2, "num2"}, {VK_NUMPAD3, "num3"}, +	{VK_NUMPAD4, "num4"}, {VK_NUMPAD5, "num5"}, {VK_NUMPAD6, "num6"}, {VK_NUMPAD7, "num7"}, +	{VK_NUMPAD8, "num8"}, {VK_NUMPAD9, "num9"}, {VK_MULTIPLY, "multiply"}, {VK_ADD, "plus"}, +	{VK_SEPARATOR, "comma"}, {VK_SUBTRACT, "minus"}, {VK_DECIMAL, "dot"}, {VK_DIVIDE, "divide"}, +	{VK_F1, "f1"}, {VK_F2, "f2"}, {VK_F3, "f3"}, {VK_F4, "f4"}, {VK_F5, "f5"}, +	{VK_F6, "f6"}, {VK_F7, "f7"}, {VK_F8, "f8"}, {VK_F9, "f9"}, {VK_F10, "f10"}, +	{VK_F11, "f11"}, {VK_F12, "f12"}, {VK_F13, "f13"}, {VK_F14, "f14"}, {VK_F15, "f15"}, +	{VK_F16, "f16"}, {VK_F17, "f17"}, {VK_F18, "f18"}, {VK_F19, "f19"}, {VK_F20, "f20"}, +	{VK_F21, "f21"}, {VK_F22, "f22"}, {VK_F23, "f23"}, {VK_F24, "f24"}, +	{VK_LWIN, "lwin"}, {VK_RWIN, "rwin"}, +	{VK_LSHIFT, "lshift"}, {VK_RSHIFT, "rshift"}, +	{VK_LCONTROL, "lctrl"}, {VK_RCONTROL, "rctrl"}, +	{VK_LMENU, "lmenu"}, {VK_RMENU, "rmenu"}, +	{VK_BROWSER_BACK, "previous"}, {VK_BROWSER_FORWARD, "next"}, {VK_BROWSER_REFRESH, "refresh"}, +	{VK_BROWSER_STOP, "stop"}, {VK_BROWSER_SEARCH, "search"}, {VK_BROWSER_FAVORITES, "favorites"}, +	{VK_BROWSER_HOME, "homepage"}, +	{VK_VOLUME_MUTE, "mute"}, {VK_VOLUME_DOWN, "voldown"}, {VK_VOLUME_UP, "volup"}, +	{VK_MEDIA_NEXT_TRACK, "nexttrack"}, {VK_MEDIA_PREV_TRACK, "prevtrack"}, +	{VK_MEDIA_STOP, "stopmedia"}, {VK_MEDIA_PLAY_PAUSE, "togglemedia"}, +	{VK_LAUNCH_MEDIA_SELECT, "mediaselect"}, +	{VK_LAUNCH_MAIL, "mail"}, {VK_LAUNCH_APP1, "app1"}, {VK_LAUNCH_APP2, "app2"}, +	{VK_OEM_PLUS, "plus"}, {VK_OEM_COMMA, "comma"}, +	{VK_OEM_MINUS, "minus"}, {VK_OEM_PERIOD, "period"}, +	{VK_ZOOM, "zoom"} +}; +  MM_PLUGIN_API int init(){  	backend wininput = {  		.name = BACKEND_NAME, @@ -52,6 +101,8 @@ static int wininput_instance(instance* inst){  }  static channel* wininput_channel(instance* inst, char* spec, uint8_t flags){ +	size_t u; +	uint16_t scancode = 0;  	char* token = spec;  	wininput_channel_ident ident = {  		.label = 0 @@ -68,33 +119,62 @@ static channel* wininput_channel(instance* inst, char* spec, uint8_t flags){  			ident.fields.channel = position;  			ident.fields.control = 1;  		} -		else if(!strcmp(token, "lmb")){ -			ident.fields.channel = button; -		} -		else if(!strcmp(token, "rmb")){ -			ident.fields.channel = button; -			ident.fields.control = 1; -		} -		else if(!strcmp(token, "mmb")){ -			ident.fields.channel = button; -			ident.fields.control = 2; -		} -		else if(!strcmp(token, "xmb1")){ -			ident.fields.channel = button; -			ident.fields.control = 3; -		} -		else if(!strcmp(token, "xmb2")){ -			ident.fields.channel = button; -			ident.fields.control = 4; -		}  		else{ -			LOGPF("Unknown control %s", token); -			return NULL; +			//check the buttons +			for(u = 0; u < sizeof(keys) / sizeof(keys[0]); u++){ +				if(keys[u].channel == button && !strcmp(keys[u].name, token)){ +					DBGPF("Using keymap %" PRIsize_t " (%d) for spec %s", u, keys[u].keycode, token); +					ident.fields.channel = button; +					ident.fields.control = keys[u].keycode; +					break; +				} +			}  		}  	} -	else if(!strncmp(spec, "keyboard.", 9)){ -		token += 9; -		//TODO +	else if(!strncmp(spec, "key.", 4)){ +		token += 4; +		ident.fields.type = keyboard; +		ident.fields.channel = keypress; +	 +		for(u = 0; u < sizeof(keys) / sizeof(keys[0]); u++){ +			if(keys[u].channel == keypress && !strcmp(keys[u].name, token)){ +				DBGPF("Using keymap %" PRIsize_t " (%d) for spec %s", u, keys[u].keycode, token); +				ident.fields.control = keys[u].keycode; +				break; +			} +		} + +		//no entry in translation table +		if(u == sizeof(keys) / sizeof(keys[0])){ +			if(strlen(token) == 1){ +				//try to translate +				scancode = VkKeyScan(token[0]); +				if(scancode != 0x7f7f){ +					DBGPF("Using keyscan result %02X (via %04X) for spec %s", scancode & 0xFF, scancode, token); +					ident.fields.type = keyboard; +					ident.fields.channel = keypress; +					ident.fields.control = scancode & 0xFF; +				} +				else{ +					LOGPF("Invalid channel specification %s", token); +					return NULL; +				} +			} +			else if(strlen(token) > 1){ +				//try to use as literal +				scancode = strtoul(token, NULL, 0); +				if(!scancode){ +					LOGPF("Invalid channel specification %s", token); +					return NULL; +				} +				DBGPF("Using direct conversion %d for spec %s", scancode & 0xFF, token); +				ident.fields.control = scancode & 0xFF; +			} +			else{ +				LOGPF("Invalid channel specification %s", spec); +				return NULL; +			} +		}  	}  	else{  		LOGPF("Unknown channel spec %s", spec); @@ -126,21 +206,21 @@ static INPUT wininput_event_mouse(wininput_instance_data* data, uint8_t channel,  	}  	if(channel == button){  		switch(control){ -			case 0: +			case VK_LBUTTON:  				flags_up |= MOUSEEVENTF_LEFTUP;  				flags_down |= MOUSEEVENTF_LEFTDOWN;  				break; -			case 1: +			case VK_RBUTTON:  				flags_up |= MOUSEEVENTF_RIGHTUP;  				flags_down |= MOUSEEVENTF_RIGHTDOWN;  				break; -			case 2: +			case VK_MBUTTON:  				flags_up |= MOUSEEVENTF_MIDDLEUP;  				flags_down |= MOUSEEVENTF_MIDDLEDOWN;  				break; -			case 3: -			case 4: -				ev.mi.mouseData = (control == 3) ? XBUTTON1 : XBUTTON2; +			case VK_XBUTTON1: +			case VK_XBUTTON2: +				ev.mi.mouseData = (control == VK_XBUTTON1) ? XBUTTON1 : XBUTTON2;  				flags_up |= MOUSEEVENTF_XUP;  				flags_down |= MOUSEEVENTF_XDOWN;  				break; @@ -162,6 +242,13 @@ static INPUT wininput_event_keyboard(wininput_instance_data* data, uint8_t chann  		.type = INPUT_KEYBOARD  	}; +	if(channel == keypress){ +		ev.ki.wVk = control; +		if(value < 0.9){ +			ev.ki.dwFlags |= KEYEVENTF_KEYUP; +		} +	} +  	return ev;  } @@ -173,7 +260,6 @@ static int wininput_set(instance* inst, size_t num, channel** c, channel_value*  	size_t n = 0, offset = 0;  	INPUT events[500]; -	//FIXME might want to coalesce mouse events  	if(num > sizeof(events) / sizeof(events[0])){  		LOGPF("Truncating output on %s to the last %" PRIsize_t " events, please notify the developers", inst->name, sizeof(events) / sizeof(events[0]));  		offset = num - sizeof(events) / sizeof(events[0]); diff --git a/backends/wininput.h b/backends/wininput.h index 240f1a6..ef817e8 100644 --- a/backends/wininput.h +++ b/backends/wininput.h @@ -18,14 +18,20 @@ enum /*wininput_channel_type*/ {  };  enum /*wininput_control_channel*/ { +	keypress = 0, +	button,  	position,  	//wheel, /*relative*/ -	button, -	keypress,  	key_unicode  }; +typedef struct /*_wininput_key_info*/ { +	uint8_t keycode; +	char* name; +	uint8_t channel; +} key_info; +  typedef union {  	struct {  		uint8_t pad[4]; diff --git a/backends/wininput.md b/backends/wininput.md new file mode 100644 index 0000000..672a24e --- /dev/null +++ b/backends/wininput.md @@ -0,0 +1,114 @@ +### The `wininput` backend + +This backend allows using the mouse and keyboard as input and output channels on a Windows system. +For example, it can be used to create hotkey-like behaviour (by reading keyboard input) or to control +a computer remotely. + +As Windows merges all keyboard and mouse input into a single data stream, no fine-grained per-device +access (as is available under Linux) is possible. + +#### Global configuration + +This backend does not take any global configuration. + +#### Instance configuration + +This backend does not take any instance-specific configuration. + +#### Channel specification + +The mouse is exposed as two channels for the position (with the origin being the upper-left corner of the desktop) + +* `mouse.x` +* `mouse.y` + +as well as one channel per mouse button + +* `mouse.lmb` (Left mouse button) +* `mouse.rmb` (Right mouse button) +* `mouse.mmb` (Middle mouse button) +* `mouse.xmb1` (Extra mouse button 1) +* `mouse.xmb2` (Extra mouse button 2) + +All keys that have an [assigned virtual keycode](https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes) +are mappable as MIDIMonster channels using the syntax `key.<keyname>`, with *keyname* being one of the following specifiers: + +* One of the keynames listed below (e.g., `key.enter`) +* For "simple" keys (A-z, 0-9, etc), simply the key glyph (e.g. `key.a`) +* A hexadecimal number specifying the virtual keycode + +Keys are pressed once the normalized event value is greater than `0.9`, and released if under that. + +The following keynames are defined in an internal mapping table: + +| Key name			| Description		| +|-------------------------------|-----------------------| +| `backspace`			|			| +| `tab`				|			| +| `clear`			|			| +| `enter`			|			| +| `shift`			|			| +| `control`			|			| +| `alt`				|			| +| `capslock`			|			| +| `escape`			|			| +| `space`,			|			| +| `pageup`, `pagedown`		|			| +| `end`				|			| +| `home`			|			| +| `pause`			|			| +| `numlock` 			|			| +| `scrolllock`			|			| +| `insert`			|			| +| `delete`			|			| +| `printscreen`			|			| +| `up`, `down`, `left`, `right`	|			| +| `select`			|			| +| `print`			|			| +| `execute`			|			| +| `help`			|			| +| `apps`			|			| +| `sleep`			|			| +| `num0` - `num9`		|			| +| `multiply`			|			| +| `plus`			|			| +| `comma`			|			| +| `minus`			|			| +| `dot`				|			| +| `divide`			|			| +| `f1` - `f24`			|			| +| `lwin`, `rwin`		|			| +| `lshift`, `rshift`		|			| +| `lctrl, `rctrl`		|			| +| `lmenu`, `rmenu`		|			| +| `previous`, `next`		| Browser controls	| +| `refresh`			| Browser controls	| +| `stop`			| Browser controls	| +| `search`			| Browser controls	| +| `favorites`			| Browser controls	| +| `homepage`			| Browser controls	| +| `mute`			|			| +| `voldown`, `volup`		|			| +| `nexttrack`, `prevtrack`	|			| +| `stopmedia`, `togglemedia`	|			| +| `mediaselect`			|			| +| `mail`			|			| +| `app1`, `app2`		|			| +| `zoom`			|			| + +Example mappings: +``` +generator.x > wi1.mouse.x +input.a > wi1.key.a +input.X > wi1.key.escape +``` + +#### Known bugs / problems + +Keyboard and mouse input is subject to UIPI. You can not send input to applications that run at a higher +privilege level than the MIDIMonster. + +Some antivirus applications may detect this backend as problematic file, because it uses the same system +interfaces to read keyboard and mouse input as any malicious application would. While it is definitely +possible to configure the MIDIMonster to do malicious things, the code itself does not log anything. +You can verify this by reading the backend code yourself.  | 
