aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorcbdev <cb@cbcdn.com>2020-05-04 20:25:00 +0200
committercbdev <cb@cbcdn.com>2020-05-04 20:25:00 +0200
commiteabdc18aa2209f3526d1068510991ce7f17a4fe6 (patch)
tree812fa84dd06ec88536100ed9fed18dabdcc8fe6f
parentc2248f6bee6b3eef770b557d9be7659dae586222 (diff)
downloadmidimonster-eabdc18aa2209f3526d1068510991ce7f17a4fe6.tar.gz
midimonster-eabdc18aa2209f3526d1068510991ce7f17a4fe6.tar.bz2
midimonster-eabdc18aa2209f3526d1068510991ce7f17a4fe6.zip
Implement wininput input direction
-rw-r--r--backends/wininput.c223
-rw-r--r--backends/wininput.h13
-rw-r--r--backends/wininput.md6
3 files changed, 215 insertions, 27 deletions
diff --git a/backends/wininput.c b/backends/wininput.c
index 46151d4..0c7e490 100644
--- a/backends/wininput.c
+++ b/backends/wininput.c
@@ -4,6 +4,9 @@
#include <string.h>
#include "wininput.h"
+//TODO check whether feedback elimination is required
+//TODO refactor & simplify
+
static key_info keys[] = {
{VK_LBUTTON, "lmb", button}, {VK_RBUTTON, "rmb", button}, {VK_MBUTTON, "mmb", button},
{VK_XBUTTON1, "xmb1", button}, {VK_XBUTTON2, "xmb2", button},
@@ -53,11 +56,23 @@ static key_info keys[] = {
{VK_ZOOM, "zoom"}
};
+static struct {
+ int virtual_x, virtual_y, virtual_width, virtual_height;
+ uint16_t mouse_x, mouse_y;
+ size_t requests;
+ wininput_request* request;
+ uint32_t interval;
+} cfg = {
+ .requests = 0,
+ .interval = 50
+};
+
MM_PLUGIN_API int init(){
backend wininput = {
.name = BACKEND_NAME,
.conf = wininput_configure,
.create = wininput_instance,
+ .interval = wininput_interval,
.conf_instance = wininput_configure_instance,
.channel = wininput_channel,
.handle = wininput_set,
@@ -79,8 +94,17 @@ MM_PLUGIN_API int init(){
return 0;
}
+static uint32_t wininput_interval(){
+ return cfg.interval;
+}
+
static int wininput_configure(char* option, char* value){
- LOG("The backend does not take any global configuration");
+ if(!strcmp(option, "interval")){
+ cfg.interval = strtoul(value, NULL, 0);
+ return 0;
+ }
+
+ LOGPF("Unknown backend configuration option %s", option);
return 1;
}
@@ -90,18 +114,57 @@ static int wininput_configure_instance(instance* inst, char* option, char* value
}
static int wininput_instance(instance* inst){
- wininput_instance_data* data = calloc(1, sizeof(wininput_instance_data));
- if(!data){
+ return 0;
+}
+
+static int wininput_subscribe(wininput_channel_ident ident, channel* chan){
+ size_t u, n;
+
+ //find an existing request
+ for(u = 0; u < cfg.requests; u++){
+ if(cfg.request[u].ident.label == ident.label){
+ break;
+ }
+ }
+
+ if(u == cfg.requests){
+ //create a new request
+ cfg.request = realloc(cfg.request, (cfg.requests + 1) * sizeof(wininput_request));
+ if(!cfg.request){
+ cfg.requests = 0;
+ LOG("Failed to allocate memory");
+ return 1;
+ }
+
+ cfg.request[u].ident.label = ident.label;
+ cfg.request[u].channels = 0;
+ cfg.request[u].channel = NULL;
+ cfg.request[u].state = 0;
+ cfg.requests++;
+ }
+
+ //check if already in subscriber list
+ for(n = 0; n < cfg.request[u].channels; n++){
+ if(cfg.request[u].channel[n] == chan){
+ return 0;
+ }
+ }
+
+ //add to subscriber list
+ cfg.request[u].channel = realloc(cfg.request[u].channel, (cfg.request[u].channels + 1) * sizeof(channel*));
+ if(!cfg.request[u].channel){
+ cfg.request[u].channels = 0;
LOG("Failed to allocate memory");
return 1;
}
-
- inst->impl = data;
+ cfg.request[u].channel[n] = chan;
+ cfg.request[u].channels++;
return 0;
}
static channel* wininput_channel(instance* inst, char* spec, uint8_t flags){
size_t u;
+ channel* chan = NULL;
uint16_t scancode = 0;
char* token = spec;
wininput_channel_ident ident = {
@@ -129,6 +192,11 @@ static channel* wininput_channel(instance* inst, char* spec, uint8_t flags){
break;
}
}
+
+ if(u == sizeof(keys) / sizeof(keys[0])){
+ LOGPF("Unknown mouse control %s", token);
+ return NULL;
+ }
}
}
else if(!strncmp(spec, "key.", 4)){
@@ -181,12 +249,26 @@ static channel* wininput_channel(instance* inst, char* spec, uint8_t flags){
}
if(ident.label){
- return mm_channel(inst, ident.label, 1);
+ chan = mm_channel(inst, ident.label, 1);
+ if(chan && (flags & mmchannel_input) && wininput_subscribe(ident, chan)){
+ return NULL;
+ }
+ return chan;
}
return NULL;
}
-static INPUT wininput_event_mouse(wininput_instance_data* data, uint8_t channel, uint8_t control, double value){
+//for some reason, sendinput only takes "normalized absolute coordinates", which are never again used in the API
+static void wininput_mouse_normalize(long* x, long* y){
+ //TODO this needs to take a possible origin offset into account
+ long normalized_x = (double) (*x) * (65535.0f / (double) cfg.virtual_width);
+ long normalized_y = (double) (*y) * (65535.0f / (double) cfg.virtual_height);
+
+ *x = normalized_x;
+ *y = normalized_y;
+}
+
+static INPUT wininput_event_mouse(uint8_t channel, uint8_t control, double value){
DWORD flags_down = 0, flags_up = 0;
INPUT ev = {
.type = INPUT_MOUSE
@@ -194,15 +276,15 @@ static INPUT wininput_event_mouse(wininput_instance_data* data, uint8_t channel,
if(channel == position){
if(control){
- data->mouse.y = value * 0xFFFF;
+ cfg.mouse_y = value * 0xFFFF;
}
else{
- data->mouse.x = value * 0xFFFF;
+ cfg.mouse_x = value * 0xFFFF;
}
- ev.mi.dwFlags |= MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE;
- ev.mi.dx = data->mouse.x;
- ev.mi.dy = data->mouse.y;
+ ev.mi.dwFlags |= MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE | MOUSEEVENTF_VIRTUALDESK;
+ ev.mi.dx = cfg.mouse_x;
+ ev.mi.dy = cfg.mouse_y;
}
if(channel == button){
switch(control){
@@ -237,7 +319,7 @@ static INPUT wininput_event_mouse(wininput_instance_data* data, uint8_t channel,
return ev;
}
-static INPUT wininput_event_keyboard(wininput_instance_data* data, uint8_t channel, uint8_t control, double value){
+static INPUT wininput_event_keyboard(uint8_t channel, uint8_t control, double value){
INPUT ev = {
.type = INPUT_KEYBOARD
};
@@ -256,7 +338,6 @@ static int wininput_set(instance* inst, size_t num, channel** c, channel_value*
wininput_channel_ident ident = {
.label = 0
};
- wininput_instance_data* data = (wininput_instance_data*) inst->impl;
size_t n = 0, offset = 0;
INPUT events[500];
@@ -268,10 +349,10 @@ static int wininput_set(instance* inst, size_t num, channel** c, channel_value*
for(n = 0; n + offset < num; n++){
ident.label = c[n + offset]->ident;
if(ident.fields.type == mouse){
- events[n] = wininput_event_mouse(data, ident.fields.channel, ident.fields.control, v[n + offset].normalised);
+ events[n] = wininput_event_mouse(ident.fields.channel, ident.fields.control, v[n + offset].normalised);
}
else if(ident.fields.type == keyboard){
- events[n] = wininput_event_keyboard(data, ident.fields.channel, ident.fields.control, v[n + offset].normalised);
+ events[n] = wininput_event_keyboard(ident.fields.channel, ident.fields.control, v[n + offset].normalised);
}
else{
n--;
@@ -289,21 +370,123 @@ static int wininput_set(instance* inst, size_t num, channel** c, channel_value*
}
static int wininput_handle(size_t num, managed_fd* fds){
- //TODO
+ channel_value val = {
+ .normalised = 0
+ };
+ uint8_t mouse_updated = 0, synthesize_off = 0, push_event = 0;
+ uint16_t key_state = 0;
+ POINT cursor_position;
+ size_t u = 0, n;
+
+ for(u = 0; u < cfg.requests; u++){
+ synthesize_off = 0;
+ push_event = 0;
+ val.normalised = 0;
+
+ if(cfg.request[u].ident.fields.type == mouse
+ && cfg.request[u].ident.fields.channel == position){
+ if(!mouse_updated){
+ //update mouse coordinates
+ if(!GetCursorPos(&cursor_position)){
+ LOG("Failed to update mouse position");
+ continue;
+ }
+ wininput_mouse_normalize(&cursor_position.x, &cursor_position.y);
+ mouse_updated = 1;
+ if(cfg.mouse_x != cursor_position.x
+ || cfg.mouse_y != cursor_position.y){
+ cfg.mouse_x = cursor_position.x;
+ cfg.mouse_y = cursor_position.y;
+ mouse_updated = 2;
+ }
+ }
+
+ val.normalised = (double) cfg.mouse_x / (double) 0xFFFF;
+ if(cfg.request[u].ident.fields.control){
+ val.normalised = (double) cfg.mouse_y / (double) 0xFFFF;
+ }
+
+ if(mouse_updated == 2){
+ push_event = 1;
+ }
+ }
+ else{
+ //check key state
+ key_state = GetAsyncKeyState(cfg.request[u].ident.fields.control);
+ if(key_state == 1){
+ //pressed and released?
+ synthesize_off = 1;
+ }
+ if((key_state & ~1) != cfg.request[u].state){
+ //key state changed
+ if(key_state){
+ val.normalised = 1.0;
+ }
+ cfg.request[u].state = key_state & ~1;
+ push_event = 1;
+ }
+ }
+
+ if(push_event){
+ //push current value to all channels
+ DBGPF("Pushing event %f on request %" PRIsize_t, val.normalised, u);
+ for(n = 0; n < cfg.request[u].channels; n++){
+ mm_channel_event(cfg.request[u].channel[n], val);
+ }
+
+ if(synthesize_off){
+ val.normalised = 0;
+ //push synthesized value to all channels
+ DBGPF("Synthesizing event %f on request %" PRIsize_t, val.normalised, u);
+ for(n = 0; n < cfg.request[u].channels; n++){
+ mm_channel_event(cfg.request[u].channel[n], val);
+ }
+ }
+ }
+ }
return 0;
}
static int wininput_start(size_t n, instance** inst){
- //TODO
+ POINT cursor_position;
+
+ //if no input requested, don't request polling
+ if(!cfg.requests){
+ cfg.interval = 0;
+ }
+
+ //read virtual desktop extents for later normalization
+ cfg.virtual_width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
+ cfg.virtual_height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
+ cfg.virtual_x = GetSystemMetrics(SM_XVIRTUALSCREEN);
+ cfg.virtual_y = GetSystemMetrics(SM_YVIRTUALSCREEN);
+ DBGPF("Virtual screen is %dx%d with offset %dx%d", cfg.virtual_width, cfg.virtual_height, cfg.virtual_x, cfg.virtual_y);
+
+ //initialize mouse position
+ if(!GetCursorPos(&cursor_position)){
+ LOG("Failed to read initial mouse position");
+ return 1;
+ }
+
+ DBGPF("Current mouse coordinates: %dx%d (%04Xx%04X)", cursor_position.x, cursor_position.y, cursor_position.x, cursor_position.y);
+ wininput_mouse_normalize(&cursor_position.x, &cursor_position.y);
+ DBGPF("Current normalized mouse position: %04Xx%04X", cursor_position.x, cursor_position.y);
+ cfg.mouse_x = cursor_position.x;
+ cfg.mouse_y = cursor_position.y;
+
+ DBGPF("Tracking %" PRIsize_t " input requests", cfg.requests);
return 0;
}
static int wininput_shutdown(size_t n, instance** inst){
size_t u;
- for(u = 0; u < n; u++){
- free(inst[u]->impl);
+ for(u = 0; u < cfg.requests; u++){
+ free(cfg.request[u].channel);
}
+ free(cfg.request);
+ cfg.request = NULL;
+ cfg.requests = 0;
LOG("Backend shut down");
return 0;
diff --git a/backends/wininput.h b/backends/wininput.h
index ef817e8..0318724 100644
--- a/backends/wininput.h
+++ b/backends/wininput.h
@@ -5,6 +5,7 @@ static int wininput_configure(char* option, char* value);
static int wininput_configure_instance(instance* inst, char* option, char* value);
static int wininput_instance(instance* inst);
static channel* wininput_channel(instance* inst, char* spec, uint8_t flags);
+static uint32_t wininput_interval();
static int wininput_set(instance* inst, size_t num, channel** c, channel_value* v);
static int wininput_handle(size_t num, managed_fd* fds);
static int wininput_start(size_t n, instance** inst);
@@ -42,9 +43,9 @@ typedef union {
uint64_t label;
} wininput_channel_ident;
-typedef struct {
- struct {
- uint16_t x;
- uint16_t y;
- } mouse;
-} wininput_instance_data;
+typedef struct /*_input_request*/ {
+ wininput_channel_ident ident;
+ size_t channels;
+ channel** channel;
+ uint16_t state;
+} wininput_request;
diff --git a/backends/wininput.md b/backends/wininput.md
index 6455dd1..6ead158 100644
--- a/backends/wininput.md
+++ b/backends/wininput.md
@@ -61,7 +61,7 @@ The following keynames are defined in an internal mapping table:
| `minus` | | `dot` | |
| `divide` | | `f1` - `f24` | |
| `lwin`, `rwin` | | `lshift`, `rshift` | |
-| `lctrl, `rctrl` | | `lmenu`, `rmenu` | |
+| `lctrl`, `rctrl` | | `lmenu`, `rmenu` | |
| `previous`, `next` | Browser controls | `refresh` | Browser controls |
| `stop` | Browser controls | `search` | Browser controls |
| `favorites` | Browser controls | `homepage` | Browser controls |
@@ -82,6 +82,10 @@ input.X > wi1.key.escape
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.
+Due to inconsistencies in the Windows API, mouse position input and output may differ for the same cursor location.
+This may be correlated with the use and arrangement of multi-monitor desktops. If you encounter problems with either
+receiving or sending mouse positions, please include a description of your monitor alignment in the issue.
+
Some antivirus applications may detect this backend as problematic 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.