aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorcbdev <cb@cbcdn.com>2020-05-02 16:55:13 +0200
committercbdev <cb@cbcdn.com>2020-05-02 16:55:13 +0200
commit56507d7ac27d0562689ea7c505fa026ecc38494f (patch)
treef77767d1a30e66fc2ab448950b72b99b817d4b6a
parent4f467a30f88a628e0e49858986d9278e12a92ce5 (diff)
downloadmidimonster-56507d7ac27d0562689ea7c505fa026ecc38494f.tar.gz
midimonster-56507d7ac27d0562689ea7c505fa026ecc38494f.tar.bz2
midimonster-56507d7ac27d0562689ea7c505fa026ecc38494f.zip
Implement keyboard output for wininput
-rw-r--r--backends/wininput.c148
-rw-r--r--backends/wininput.h10
-rw-r--r--backends/wininput.md114
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.