diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..6dcfb24221390b1080fcfb4d4c1c412d59efac12 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright 2018 Collabora Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/examples/inputsynth-xi2.c b/examples/inputsynth-xi2.c index fff569d807be679c10786f8b7ad24463c63b1942..e12ba31c1d9e0b93b83eb8c691176c34d85692a5 100644 --- a/examples/inputsynth-xi2.c +++ b/examples/inputsynth-xi2.c @@ -22,6 +22,34 @@ int main () usleep (5000); } + int char_x = 500; + int char_y = 500; + + input_synth_move_cursor (input, char_x, char_y); + + input_synth_click (input, char_x, char_y, 3, TRUE); + usleep (5000); + input_synth_click (input, char_x, char_y, 3, FALSE); + + usleep (50000); + + input_synth_click (input, char_x, char_y, 3, TRUE); + usleep (5000); + input_synth_click (input, char_x, char_y, 3, FALSE); + + usleep (50000); + + input_synth_char (input, 'h'); + usleep (50000); + input_synth_char (input, 'e'); + usleep (50000); + input_synth_char (input, 'l'); + usleep (50000); + input_synth_char (input, 'l'); + usleep (50000); + input_synth_char (input, 'o'); + usleep (50000); + g_object_unref (input); return 0; diff --git a/src/inputsynth-xi2.c b/src/inputsynth-xi2.c index 4eae95ee90d15416b41456238364e27d32a104c0..309a37b0fb702dfcfec51165092577e73fe55d22 100644 --- a/src/inputsynth-xi2.c +++ b/src/inputsynth-xi2.c @@ -1,3 +1,10 @@ +/* + * LibInputSynth + * Copyright 2018 Collabora Ltd. + * Author: Christoph Haag <christoph.haag@collabora.com> + * SPDX-License-Identifier: MIT + */ + #include "inputsynth-xi2.h" #include <stdbool.h> @@ -6,8 +13,24 @@ G_DEFINE_TYPE (InputSynthXi2, input_synth_xi2, INPUT_TYPE_SYNTH) +typedef struct KeySymTable { + KeySym *table; + int min_keycode; + //int max_keycode; + int keysyms_per_keycode; + int num_elements; + + XModifierKeymap *modmap; +} KeySymTable; + +typedef struct ModifierKeyCodes { + // there are only up to 8 modifier keys + KeyCode key_codes[8]; + int length; +} ModifierKeyCodes; + static int -get_master_pointer_dev (Display *dpy, char *name) +get_master_dev (Display *dpy, char *name, int use) { int num_devices; XIDeviceInfo *info, *dev; @@ -15,7 +38,7 @@ get_master_pointer_dev (Display *dpy, char *name) for (int i = 0; i < num_devices; i++) { dev = &info[i]; - if (dev->use == XIMasterPointer) + if (dev->use == use) if (strcmp (dev->name, name) == 0) return dev->deviceid; } @@ -23,7 +46,7 @@ get_master_pointer_dev (Display *dpy, char *name) } static int -get_slave_pointer_dev (Display *dpy, char *name) +get_slave_dev (Display *dpy, char *name, int use) { int num_devices; XIDeviceInfo *info, *dev; @@ -31,7 +54,7 @@ get_slave_pointer_dev (Display *dpy, char *name) for (int i = 0; i < num_devices; i++) { dev = &info[i]; - if (dev->use == XISlavePointer) + if (dev->use == use) if (strcmp (dev->name, name) == 0) return dev->deviceid; } @@ -49,7 +72,7 @@ add_xi_master (Display *dpy, char *create_name, char *name) }; int ret = XIChangeHierarchy (dpy, (XIAnyHierarchyChangeInfo *)&c, 1); - int master_id = get_master_pointer_dev (dpy, name); + int master_id = get_master_dev (dpy, name, XIMasterPointer); if (ret != 0) g_printerr ("Error while craeting Master pointer: %d\n", ret); @@ -60,38 +83,25 @@ static int delete_xi_masters (Display *dpy, char *name) { XIRemoveMasterInfo r = { - .type = XIRemoveMaster + .type = XIRemoveMaster, + .deviceid = get_master_dev (dpy, name, XIMasterPointer), + .return_mode = XIFloating }; - int device_id = get_master_pointer_dev (dpy, name); - while (device_id != -1) - { - r.deviceid = device_id; - // do not attach vr pointers to master - r.return_mode = XIFloating; - int ret = XIChangeHierarchy (dpy, (XIAnyHierarchyChangeInfo *)&r, 1); - if (ret == 0) - g_print ("Deleted master %s: %d\n", name, device_id); - else - g_printerr ("Error while deleting master pointer %s, %d\n", - name, device_id); - // returns -1 when there are no more - device_id = get_master_pointer_dev (dpy, name); - } - XSync(dpy, false); + + int ret = XIChangeHierarchy (dpy, (XIAnyHierarchyChangeInfo *)&r, 1); + if (ret == 0) + g_print ("Deleted master %s: %d\n", name, r.deviceid); + else + g_printerr ("Error while deleting master pointer %s, %d\n", + name, r.deviceid); return 0; } static void -input_synth_xi2_init (InputSynthXi2 *self) +_init_input_synth_devs (InputSynthXi2 *self) { - GdkDisplay *gdk_dpy = gdk_display_get_default (); - if (gdk_dpy) - self->dpy = gdk_x11_display_get_xdisplay (gdk_dpy); - else - self->dpy = XOpenDisplay (NULL); - int vrpointer_master_id = - get_master_pointer_dev (self->dpy, INPUT_SYNTH_MASTER_NAME); + get_master_dev (self->dpy, INPUT_SYNTH_MASTER_NAME, XIMasterPointer); if (vrpointer_master_id == -1) { vrpointer_master_id = add_xi_master ( @@ -105,30 +115,58 @@ input_synth_xi2_init (InputSynthXi2 *self) } int vrpointer_slave_id = - get_slave_pointer_dev (self->dpy, INPUT_SYNTH_SLAVE_NAME); + get_slave_dev (self->dpy, INPUT_SYNTH_SLAVE_NAME, XISlavePointer); if (vrpointer_slave_id != -1) - g_print ("Using InputSynth Pointer slave %d!\n", vrpointer_slave_id); + g_print ("Using existing VR Pointer slave %d!\n", vrpointer_slave_id); else g_printerr ("Error: slave pointer %s not found!\n", INPUT_SYNTH_SLAVE_NAME); - self->xdev.device_id = vrpointer_slave_id; + self->pointer_slave_dev.device_id = vrpointer_slave_id; + + int vrkeyboard_slave_id = + get_slave_dev (self->dpy, INPUT_SYNTH_KEYBOARD_SLAVE_NAME, XISlaveKeyboard); + if (vrkeyboard_slave_id == -1) + { + g_print ("No xtest vr keyboard slave. This should not happen!\n"); + } + self->keyboard_slave_dev.device_id = vrkeyboard_slave_id; +} + +KeySymTable *_init_keysym_table (Display *dpy); + +static void +input_synth_xi2_init (InputSynthXi2 *self) +{ + GdkDisplay *gdk_dpy = gdk_display_get_default (); + if (gdk_dpy) + self->dpy = gdk_x11_display_get_xdisplay (gdk_dpy); + else + self->dpy = XOpenDisplay (NULL); + + g_print ("VR pointer mode: #%d Pointer\n", self->single_cursor ? 1 : 2); + + self->keysym_table = _init_keysym_table (self->dpy); + + self->single_cursor = FALSE; // TODO: config + + if (!self->single_cursor) + _init_input_synth_devs (self); } void input_synth_xi2_move_cursor (InputSynth *self_parent, float x, float y) { InputSynthXi2 *self = INPUT_SYNTH_XI2 (self_parent); - /* - g_print("Move %f %f meter / %d %d pixel\n", - x_worldscale, y_worldscale, x, y); - */ - // TODO: use this as fallback when xinput2 second cursor creation fails - // XTestFakeMotionEvent(self->dpy, 0, x, y, CurrentTime); - int axes[2] = {x, y}; - XTestFakeDeviceMotionEvent (self->dpy, &self->xdev, False, 0, axes, 2, - CurrentTime); + if (self->single_cursor) + XTestFakeMotionEvent(self->dpy, 0, x, y, CurrentTime); + else + { + int axes[2] = {x, y}; + XTestFakeDeviceMotionEvent (self->dpy, &self->pointer_slave_dev, False, 0, + axes, 2, CurrentTime); + } XSync (self->dpy, false); } @@ -137,15 +175,15 @@ input_synth_xi2_click (InputSynth *self_parent, float x, float y, int button, gboolean press) { InputSynthXi2 *self = INPUT_SYNTH_XI2 (self_parent); - // g_print ("press: %d %f %f / %d %d \n", - // button, x_worldscale, y_worldscale, x, y); - // XTestFakeButtonEvent (self->dpy, 1, true, CurrentTime); - - // why does the device variant have axes but not the generic variant? - int axes[2] = {x, y}; - XTestFakeDeviceButtonEvent (self->dpy, &self->xdev, button, press, - axes, 2, CurrentTime); + if (self->single_cursor) + XTestFakeButtonEvent (self->dpy, button, press, CurrentTime); + else + { + int axes[2] = {x, y}; + XTestFakeDeviceButtonEvent (self->dpy, &self->pointer_slave_dev, button, + press, axes, 2, CurrentTime); + } XSync (self->dpy, false); } @@ -160,11 +198,217 @@ static void input_synth_xi2_finalize (GObject *gobject) { InputSynthXi2 *self = INPUT_SYNTH_XI2 (gobject); - if (self->dpy) + if (self->single_cursor) + return; + + /* send a dummy event to the root window (not sure if this is useful) */ + Drawable rootW = XRootWindow (self->dpy, 0); + XClientMessageEvent dummyEvent; + memset(&dummyEvent, 0, sizeof(XClientMessageEvent)); + dummyEvent.type = ClientMessage; + dummyEvent.window = rootW; + dummyEvent.format = 32; + XSendEvent(self->dpy, rootW, 0, 0, (XEvent*)&dummyEvent); + + /* XSync with discarding currently queued events */ + XSync(self->dpy, 1); + + /* we still have to wait in case clutter is currently processing an event + * if it takes longer than 1 second, something is seriously broken anyway */ + usleep (100000); + + g_print ("Removing X11 VR pointer \"%s\"\n", INPUT_SYNTH_MASTER_NAME); + delete_xi_masters (self->dpy, INPUT_SYNTH_MASTER_NAME); + XSync (self->dpy, true); +} + +KeySymTable * +_init_keysym_table (Display *dpy) +{ + int min_keycode; + int max_keycode; + int keysyms_per_keycode; + XDisplayKeycodes (dpy, &min_keycode, &max_keycode); + + + int keycode_count = max_keycode - min_keycode + 1; + KeySym *keysyms = XGetKeyboardMapping (dpy, min_keycode, keycode_count, + &keysyms_per_keycode); + + int num_elements = keycode_count * keysyms_per_keycode; + + XModifierKeymap *modmap = XGetModifierMapping(dpy); + + KeySymTable *keysym_table = malloc (sizeof (KeySymTable)); + keysym_table->table = keysyms; + keysym_table->min_keycode = min_keycode; + //keysym_table->max_keycode = max_keycode; + keysym_table->keysyms_per_keycode = keysyms_per_keycode; + keysym_table->num_elements = num_elements; + keysym_table->modmap = modmap; + + return keysym_table; +} + +int +_find_modifier_num (KeySymTable *keysym_table, KeySym sym) +{ + int min_keycode = keysym_table->min_keycode; + int num_elements = keysym_table->num_elements; + int keysyms_per_keycode = keysym_table->keysyms_per_keycode; + KeySym *keysyms = keysym_table->table; + for (int keycode = min_keycode; keycode < num_elements / keysyms_per_keycode; + keycode++) + { + for (int keysym_num = 0; keysym_num < keysyms_per_keycode; keysym_num++) + { + + // TODO: 2 (ctr+alt) or 3 (ctrl+alt+shift) are weird + // XK_apostrophe (') on the german keyboard is Shift+#, but also the + // weird Ctrl+Alt+ä combinatoin, which comes first, but only works in + // some (?) apps, so skip the weird ones. + if (keysym_num == 2 || keysym_num == 3) continue; + + int index = (keycode - min_keycode) * keysyms_per_keycode + + keysym_num; + if (keysyms[index] == sym) + { + int modifier_num = keysym_num; + return modifier_num; + } + } + } + return -1; +} + +ModifierKeyCodes +_modifier_keycode_for (InputSynthXi2 *self, + KeySym sym) +{ + /* each row of the key sym table is one key code + * in each row there are up to "keysyms_per_keycode" key syms that are located + * on the same physical key in the current layout + * + * for example in the german layout the row for the q key looks like this + * key code 24; key syms: q, Q, q, Q, at, Greek_OMEGA, at + * (note that XKeysymToString() omits the XK_ prefix that can be found in the + * #define in keysymdef.h, e.g. the "at" key sym is defined as XK_at) + * + * _find_modifier_num() finds the index of the first applicable sym: + * XK_q => 0 + * XK_Q => 1 + * XK_at => 4 + */ + int modifier_num = _find_modifier_num (self->keysym_table, sym); + + if (modifier_num == -1) { + g_print ("ERROR: Did not find key sym!\n"); + return (ModifierKeyCodes) { .length = -1 }; + } else { + //g_print ("Found key sym with mod number %d!\n", modifier_num); + } + + /* There are up to 8 modifiers that can be enumerated (shift, ctrl, etc.) + * + * each of these 8 keys has up to "max_keypermod" "equivalent" modifiers + * (e.g. left shift, right shift, etc.) + * + * TODO: + * from XK_at appearing in column 4 of the table above we know that XK_at + * can be achieved by q + modifier "4" and + * I know from my keyboard that modifier 4 is the alt gr key (which has keysym + * XK_ISO_Level3_Shift) but I do not know how to programatically find wich + * entry of the XModifierKeymap entries below corresponds to e.g. column 4 + * + * If modmap->modifiermap[modmap_index(4)] was XK_ISO_Level3_Shift we would + * have solved the problem + + XModifierKeymap *modmap = keysym_table->modmap; + // + 0: There are modmap->max_keypermod "equivalent" modifier keys. + int modmap_index = modifier_num * modmap->max_keypermod + 0; + KeyCode key_code = modmap->modifiermap[modmap_index]; + return key_code; + */ + + // for now hardcode the modifier key + switch (modifier_num) { + case 0: + return (ModifierKeyCodes) { + .key_codes[0] = XKeysymToKeycode (self->dpy, NoSymbol), + .length = 1 + }; + case 1: + return (ModifierKeyCodes) { + .key_codes[0] = XKeysymToKeycode (self->dpy, XK_Shift_L), + .length = 1 + }; + /* TODO: 2 and 3 are super weird, we don't use them */ + case 4: + return (ModifierKeyCodes) { + .key_codes[0] = XKeysymToKeycode (self->dpy, XK_ISO_Level3_Shift), + .length = 1 + }; + case 5: + return (ModifierKeyCodes) { + .key_codes[0] = XKeysymToKeycode (self->dpy, XK_Shift_L), + .key_codes[1] = XKeysymToKeycode (self->dpy, XK_ISO_Level3_Shift), + .length = 2 + }; + default: + return (ModifierKeyCodes) { .length = 0 }; + } +} + +void +input_synth_char_xi2 (InputSynth *self_parent, char c) +{ + InputSynthXi2 *self = INPUT_SYNTH_XI2 (self_parent); + + /* many openvr key codes match Latin 1 keysyms from X11/keysymdef.h + * lucky coincidence or subject to change? + */ + int key_sym; + /* OpenVR Backspace = 8 and XK_Backspace = 0xff08 etc. */ + if (c >= 8 && c <= 17) + key_sym = 0xff00 + c; + else + key_sym = c; + + /* character 10 on the open vr keyboard (Line Feed) + * should be return for us. There is no return on the openvr keyboard?! + */ + if (c == 10) + key_sym = XK_Return; + + ModifierKeyCodes mod_key_codes = + _modifier_keycode_for (self, key_sym); + + if (mod_key_codes.length == -1) + { + g_print ("There was an error, not synthing %c keysym %d %s\n", + c, key_sym, XKeysymToString (key_sym)); + return; + } + + for (int i = 0; i < mod_key_codes.length; i++) { - g_print ("Removing X11 VR pointer \"%s\"\n", INPUT_SYNTH_MASTER_NAME); - delete_xi_masters (self->dpy, INPUT_SYNTH_MASTER_NAME); + KeySym key_code = mod_key_codes.key_codes[i]; + //g_print ("%c: modkey %lu\n", c, key_code); + XTestFakeKeyEvent (self->dpy, key_code, true, 0); } + + unsigned int key_code = XKeysymToKeycode (self->dpy, key_sym); + XTestFakeKeyEvent (self->dpy, key_code, true, 0); + XTestFakeKeyEvent (self->dpy, key_code, false, 0); + + for (int i = 0; i < mod_key_codes.length; i++) + { + KeySym key_code = mod_key_codes.key_codes[i]; + //g_print ("%c: modkey %lu\n", c, key_code); + XTestFakeKeyEvent (self->dpy, key_code, false, 0); + } + + XSync (self->dpy, false); } static void @@ -177,4 +421,5 @@ input_synth_xi2_class_init (InputSynthXi2Class *klass) InputSynthClass *input_synth_class = INPUT_SYNTH_CLASS (klass); input_synth_class->input_synth_click = input_synth_xi2_click; input_synth_class->input_synth_move_cursor = input_synth_xi2_move_cursor; + input_synth_class->input_synth_char = input_synth_char_xi2; } diff --git a/src/inputsynth-xi2.h b/src/inputsynth-xi2.h index d178e6952a095bbb5bc8729c1ebf5f8ca1063e5a..0a4ad595c455a728f7ffd04d3c9c92a50f3c41b7 100644 --- a/src/inputsynth-xi2.h +++ b/src/inputsynth-xi2.h @@ -1,3 +1,11 @@ +/* + * LibInputSynth + * Copyright 2018 Collabora Ltd. + * Author: Christoph Haag <christoph.haag@collabora.com> + * SPDX-License-Identifier: MIT + */ + + #ifndef __INPUT_SYNTH_XI2_H__ #define __INPUT_SYNTH_XI2_H__ @@ -15,6 +23,8 @@ // xinput 2 will also append the suffix " pointer" to the slave pointer #define INPUT_SYNTH_SLAVE_NAME "InputSynthPointer XTEST pointer" +#define INPUT_SYNTH_KEYBOARD_SLAVE_NAME "InputSynthPointer XTEST keyboard" + // Xtest for sending fake events to the X server #include <X11/Xlib.h> #include <X11/extensions/XTest.h> @@ -35,7 +45,10 @@ struct _InputSynthXi2 { InputSynth parent; Display *dpy; - XDevice xdev; + XDevice pointer_slave_dev; + XDevice keyboard_slave_dev; + struct KeySymTable *keysym_table; + gboolean single_cursor; }; InputSynthXi2 *input_synth_xi2_new (void); diff --git a/src/inputsynth.c b/src/inputsynth.c index e12e16b36c62b42ca59e90c81129690b7dea7029..6ff88cf28afeea0d25bceb35216d731ab61cd737 100644 --- a/src/inputsynth.c +++ b/src/inputsynth.c @@ -1,5 +1,5 @@ /* - * Shell GLib + * LibInputSynth * Copyright 2018 Collabora Ltd. * Author: Christoph Haag <christoph.haag@collabora.com> * SPDX-License-Identifier: MIT @@ -49,3 +49,10 @@ void input_synth_click (InputSynth *self, float x, float y, g_return_if_fail (klass->input_synth_click != NULL); klass->input_synth_click (self, x, y, button, press); } + +void input_synth_char (InputSynth *self, char c) +{ + InputSynthClass *klass = INPUT_SYNTH_GET_CLASS (self); + g_return_if_fail (klass->input_synth_click != NULL); + klass->input_synth_char (self, c); +} diff --git a/src/inputsynth.h b/src/inputsynth.h index e6119b65b5c71a4fb0334ff53407389919ab4929..1b7412c68e9c2ae54204cf87d07dcf411c0c7da3 100644 --- a/src/inputsynth.h +++ b/src/inputsynth.h @@ -1,5 +1,5 @@ /* - * Shell GLib + * LibInputSynth * Copyright 2018 Collabora Ltd. * Author: Christoph Haag <christoph.haag@collabora.com> * SPDX-License-Identifier: MIT @@ -21,6 +21,7 @@ struct _InputSynthClass void (*input_synth_move_cursor) (InputSynth *self, float x, float y); void (*input_synth_click) (InputSynth *self, float x, float y, int button, gboolean press); + void (*input_synth_char) (InputSynth *self, char c); }; InputSynth *input_synth_new (void); @@ -28,6 +29,7 @@ InputSynth *input_synth_new (void); void input_synth_move_cursor (InputSynth *self, float x, float y); void input_synth_click (InputSynth *self, float x, float y, int button, gboolean press); +void input_synth_char (InputSynth *self, char c); G_END_DECLS