From 571eec24d37e8730ed8fcd2bbdad6d66876758bb Mon Sep 17 00:00:00 2001 From: 4yn <4yn@users.noreply.github.com> Date: Sun, 24 Jul 2022 15:55:57 +0800 Subject: [PATCH] use statically linked interception library for directinput emulation --- src-interception/.gitignore | 1 + src-interception/Cargo.lock | 140 ++++++++ src-interception/Cargo.toml | 13 + src-interception/README.md | 173 ++++++++++ src-interception/build.rs | 11 + src-interception/include/interception.h | 194 +++++++++++ src-interception/src/bindings.rs | 315 ++++++++++++++++++ src-interception/src/interception.c | 328 +++++++++++++++++++ src-interception/src/lib.rs | 373 ++++++++++++++++++++++ src-interception/src/scancode.rs | 147 +++++++++ src-slider_io/Cargo.lock | 46 +++ src-slider_io/Cargo.toml | 1 + src-slider_io/src/bin/test_directinput.rs | 42 +++ src-slider_io/src/config.rs | 1 + src-slider_io/src/output/config.rs | 10 + src-slider_io/src/output/keyboard.rs | 137 ++++++-- src-slider_io/src/output/output.rs | 3 +- src-tauri/Cargo.lock | 15 + src-tauri/src/main.rs | 4 + src/App.svelte | 25 ++ 20 files changed, 1948 insertions(+), 31 deletions(-) create mode 100644 src-interception/.gitignore create mode 100644 src-interception/Cargo.lock create mode 100644 src-interception/Cargo.toml create mode 100644 src-interception/README.md create mode 100644 src-interception/build.rs create mode 100644 src-interception/include/interception.h create mode 100644 src-interception/src/bindings.rs create mode 100644 src-interception/src/interception.c create mode 100644 src-interception/src/lib.rs create mode 100644 src-interception/src/scancode.rs create mode 100644 src-slider_io/src/bin/test_directinput.rs diff --git a/src-interception/.gitignore b/src-interception/.gitignore new file mode 100644 index 0000000..9f97022 --- /dev/null +++ b/src-interception/.gitignore @@ -0,0 +1 @@ +target/ \ No newline at end of file diff --git a/src-interception/Cargo.lock b/src-interception/Cargo.lock new file mode 100644 index 0000000..1e19495 --- /dev/null +++ b/src-interception/Cargo.lock @@ -0,0 +1,140 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "interception" +version = "0.1.0" +dependencies = [ + "bitflags", + "cc", + "num_enum", + "serde", +] + +[[package]] +name = "num_enum" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "unicode-ident" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" diff --git a/src-interception/Cargo.toml b/src-interception/Cargo.toml new file mode 100644 index 0000000..2e7ba1e --- /dev/null +++ b/src-interception/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "interception" +version = "0.1.0" +edition = "2018" +publish = false + +[build-dependencies] +cc = "1.0" + +[dependencies] +bitflags = "1.2.1" +num_enum = "0.5.0" +serde = { version = "1.0.114", features = ["derive"] } \ No newline at end of file diff --git a/src-interception/README.md b/src-interception/README.md new file mode 100644 index 0000000..72241fe --- /dev/null +++ b/src-interception/README.md @@ -0,0 +1,173 @@ +Derived from https://github.com/bozbez/interception-sys and https://github.com/bozbez/interception-rs + +Repackaged to link statically with https://github.com/oblitum/Interception 1.0.1 instead of using dynamic library. + +Interception is licensed under LGPL 3.0 for non-commercial use. Interception-sys and interception-rs according to its Cargo.toml are licensed under LGPL 3.0. + +``` + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. +``` diff --git a/src-interception/build.rs b/src-interception/build.rs new file mode 100644 index 0000000..9d9558c --- /dev/null +++ b/src-interception/build.rs @@ -0,0 +1,11 @@ +fn main() { + println!("cargo:rerun-if-changed=include/interception.h"); + println!("cargo:rerun-if-changed=src/interception.c"); + cc::Build::new() + .define("INTERCEPTION_STATIC", None) + .file("src/interception.c") + .include("include/") + .compile("interception"); + + println!("cargo:rustc-link-lib=static=interception"); +} diff --git a/src-interception/include/interception.h b/src-interception/include/interception.h new file mode 100644 index 0000000..8331c6d --- /dev/null +++ b/src-interception/include/interception.h @@ -0,0 +1,194 @@ +#ifndef _INTERCEPTION_H_ +#define _INTERCEPTION_H_ + +#ifdef INTERCEPTION_STATIC + #define INTERCEPTION_API +#else + #if defined _WIN32 || defined __CYGWIN__ + #ifdef INTERCEPTION_EXPORT + #ifdef __GNUC__ + #define INTERCEPTION_API __attribute__((dllexport)) + #else + #define INTERCEPTION_API __declspec(dllexport) + #endif + #else + #ifdef __GNUC__ + #define INTERCEPTION_API __attribute__((dllimport)) + #else + #define INTERCEPTION_API __declspec(dllimport) + #endif + #endif + #else + #if __GNUC__ >= 4 + #define INTERCEPTION_API __attribute__ ((visibility("default"))) + #else + #define INTERCEPTION_API + #endif + #endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define INTERCEPTION_MAX_KEYBOARD 10 + +#define INTERCEPTION_MAX_MOUSE 10 + +#define INTERCEPTION_MAX_DEVICE ((INTERCEPTION_MAX_KEYBOARD) + (INTERCEPTION_MAX_MOUSE)) + +#define INTERCEPTION_KEYBOARD(index) ((index) + 1) + +#define INTERCEPTION_MOUSE(index) ((INTERCEPTION_MAX_KEYBOARD) + (index) + 1) + +typedef void *InterceptionContext; + +typedef int InterceptionDevice; + +typedef int InterceptionPrecedence; + +typedef unsigned short InterceptionFilter; + +typedef int (*InterceptionPredicate)(InterceptionDevice device); + +enum InterceptionKeyState +{ + INTERCEPTION_KEY_DOWN = 0x00, + INTERCEPTION_KEY_UP = 0x01, + INTERCEPTION_KEY_E0 = 0x02, + INTERCEPTION_KEY_E1 = 0x04, + INTERCEPTION_KEY_TERMSRV_SET_LED = 0x08, + INTERCEPTION_KEY_TERMSRV_SHADOW = 0x10, + INTERCEPTION_KEY_TERMSRV_VKPACKET = 0x20 +}; + +enum InterceptionFilterKeyState +{ + INTERCEPTION_FILTER_KEY_NONE = 0x0000, + INTERCEPTION_FILTER_KEY_ALL = 0xFFFF, + INTERCEPTION_FILTER_KEY_DOWN = INTERCEPTION_KEY_UP, + INTERCEPTION_FILTER_KEY_UP = INTERCEPTION_KEY_UP << 1, + INTERCEPTION_FILTER_KEY_E0 = INTERCEPTION_KEY_E0 << 1, + INTERCEPTION_FILTER_KEY_E1 = INTERCEPTION_KEY_E1 << 1, + INTERCEPTION_FILTER_KEY_TERMSRV_SET_LED = INTERCEPTION_KEY_TERMSRV_SET_LED << 1, + INTERCEPTION_FILTER_KEY_TERMSRV_SHADOW = INTERCEPTION_KEY_TERMSRV_SHADOW << 1, + INTERCEPTION_FILTER_KEY_TERMSRV_VKPACKET = INTERCEPTION_KEY_TERMSRV_VKPACKET << 1 +}; + +enum InterceptionMouseState +{ + INTERCEPTION_MOUSE_LEFT_BUTTON_DOWN = 0x001, + INTERCEPTION_MOUSE_LEFT_BUTTON_UP = 0x002, + INTERCEPTION_MOUSE_RIGHT_BUTTON_DOWN = 0x004, + INTERCEPTION_MOUSE_RIGHT_BUTTON_UP = 0x008, + INTERCEPTION_MOUSE_MIDDLE_BUTTON_DOWN = 0x010, + INTERCEPTION_MOUSE_MIDDLE_BUTTON_UP = 0x020, + + INTERCEPTION_MOUSE_BUTTON_1_DOWN = INTERCEPTION_MOUSE_LEFT_BUTTON_DOWN, + INTERCEPTION_MOUSE_BUTTON_1_UP = INTERCEPTION_MOUSE_LEFT_BUTTON_UP, + INTERCEPTION_MOUSE_BUTTON_2_DOWN = INTERCEPTION_MOUSE_RIGHT_BUTTON_DOWN, + INTERCEPTION_MOUSE_BUTTON_2_UP = INTERCEPTION_MOUSE_RIGHT_BUTTON_UP, + INTERCEPTION_MOUSE_BUTTON_3_DOWN = INTERCEPTION_MOUSE_MIDDLE_BUTTON_DOWN, + INTERCEPTION_MOUSE_BUTTON_3_UP = INTERCEPTION_MOUSE_MIDDLE_BUTTON_UP, + + INTERCEPTION_MOUSE_BUTTON_4_DOWN = 0x040, + INTERCEPTION_MOUSE_BUTTON_4_UP = 0x080, + INTERCEPTION_MOUSE_BUTTON_5_DOWN = 0x100, + INTERCEPTION_MOUSE_BUTTON_5_UP = 0x200, + + INTERCEPTION_MOUSE_WHEEL = 0x400, + INTERCEPTION_MOUSE_HWHEEL = 0x800 +}; + +enum InterceptionFilterMouseState +{ + INTERCEPTION_FILTER_MOUSE_NONE = 0x0000, + INTERCEPTION_FILTER_MOUSE_ALL = 0xFFFF, + + INTERCEPTION_FILTER_MOUSE_LEFT_BUTTON_DOWN = INTERCEPTION_MOUSE_LEFT_BUTTON_DOWN, + INTERCEPTION_FILTER_MOUSE_LEFT_BUTTON_UP = INTERCEPTION_MOUSE_LEFT_BUTTON_UP, + INTERCEPTION_FILTER_MOUSE_RIGHT_BUTTON_DOWN = INTERCEPTION_MOUSE_RIGHT_BUTTON_DOWN, + INTERCEPTION_FILTER_MOUSE_RIGHT_BUTTON_UP = INTERCEPTION_MOUSE_RIGHT_BUTTON_UP, + INTERCEPTION_FILTER_MOUSE_MIDDLE_BUTTON_DOWN = INTERCEPTION_MOUSE_MIDDLE_BUTTON_DOWN, + INTERCEPTION_FILTER_MOUSE_MIDDLE_BUTTON_UP = INTERCEPTION_MOUSE_MIDDLE_BUTTON_UP, + + INTERCEPTION_FILTER_MOUSE_BUTTON_1_DOWN = INTERCEPTION_MOUSE_BUTTON_1_DOWN, + INTERCEPTION_FILTER_MOUSE_BUTTON_1_UP = INTERCEPTION_MOUSE_BUTTON_1_UP, + INTERCEPTION_FILTER_MOUSE_BUTTON_2_DOWN = INTERCEPTION_MOUSE_BUTTON_2_DOWN, + INTERCEPTION_FILTER_MOUSE_BUTTON_2_UP = INTERCEPTION_MOUSE_BUTTON_2_UP, + INTERCEPTION_FILTER_MOUSE_BUTTON_3_DOWN = INTERCEPTION_MOUSE_BUTTON_3_DOWN, + INTERCEPTION_FILTER_MOUSE_BUTTON_3_UP = INTERCEPTION_MOUSE_BUTTON_3_UP, + + INTERCEPTION_FILTER_MOUSE_BUTTON_4_DOWN = INTERCEPTION_MOUSE_BUTTON_4_DOWN, + INTERCEPTION_FILTER_MOUSE_BUTTON_4_UP = INTERCEPTION_MOUSE_BUTTON_4_UP, + INTERCEPTION_FILTER_MOUSE_BUTTON_5_DOWN = INTERCEPTION_MOUSE_BUTTON_5_DOWN, + INTERCEPTION_FILTER_MOUSE_BUTTON_5_UP = INTERCEPTION_MOUSE_BUTTON_5_UP, + + INTERCEPTION_FILTER_MOUSE_WHEEL = INTERCEPTION_MOUSE_WHEEL, + INTERCEPTION_FILTER_MOUSE_HWHEEL = INTERCEPTION_MOUSE_HWHEEL, + + INTERCEPTION_FILTER_MOUSE_MOVE = 0x1000 +}; + +enum InterceptionMouseFlag +{ + INTERCEPTION_MOUSE_MOVE_RELATIVE = 0x000, + INTERCEPTION_MOUSE_MOVE_ABSOLUTE = 0x001, + INTERCEPTION_MOUSE_VIRTUAL_DESKTOP = 0x002, + INTERCEPTION_MOUSE_ATTRIBUTES_CHANGED = 0x004, + INTERCEPTION_MOUSE_MOVE_NOCOALESCE = 0x008, + INTERCEPTION_MOUSE_TERMSRV_SRC_SHADOW = 0x100 +}; + +typedef struct +{ + unsigned short state; + unsigned short flags; + short rolling; + int x; + int y; + unsigned int information; +} InterceptionMouseStroke; + +typedef struct +{ + unsigned short code; + unsigned short state; + unsigned int information; +} InterceptionKeyStroke; + +typedef char InterceptionStroke[sizeof(InterceptionMouseStroke)]; + +InterceptionContext INTERCEPTION_API interception_create_context(void); + +void INTERCEPTION_API interception_destroy_context(InterceptionContext context); + +InterceptionPrecedence INTERCEPTION_API interception_get_precedence(InterceptionContext context, InterceptionDevice device); + +void INTERCEPTION_API interception_set_precedence(InterceptionContext context, InterceptionDevice device, InterceptionPrecedence precedence); + +InterceptionFilter INTERCEPTION_API interception_get_filter(InterceptionContext context, InterceptionDevice device); + +void INTERCEPTION_API interception_set_filter(InterceptionContext context, InterceptionPredicate predicate, InterceptionFilter filter); + +InterceptionDevice INTERCEPTION_API interception_wait(InterceptionContext context); + +InterceptionDevice INTERCEPTION_API interception_wait_with_timeout(InterceptionContext context, unsigned long milliseconds); + +int INTERCEPTION_API interception_send(InterceptionContext context, InterceptionDevice device, const InterceptionStroke *stroke, unsigned int nstroke); + +int INTERCEPTION_API interception_receive(InterceptionContext context, InterceptionDevice device, InterceptionStroke *stroke, unsigned int nstroke); + +unsigned int INTERCEPTION_API interception_get_hardware_id(InterceptionContext context, InterceptionDevice device, void *hardware_id_buffer, unsigned int buffer_size); + +int INTERCEPTION_API interception_is_invalid(InterceptionDevice device); + +int INTERCEPTION_API interception_is_keyboard(InterceptionDevice device); + +int INTERCEPTION_API interception_is_mouse(InterceptionDevice device); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src-interception/src/bindings.rs b/src-interception/src/bindings.rs new file mode 100644 index 0000000..9bcc038 --- /dev/null +++ b/src-interception/src/bindings.rs @@ -0,0 +1,315 @@ +/* automatically generated by rust-bindgen 0.54.1 */ + +pub const INTERCEPTION_MAX_KEYBOARD: u32 = 10; +pub const INTERCEPTION_MAX_MOUSE: u32 = 10; +pub const INTERCEPTION_MAX_DEVICE: u32 = 20; +pub type InterceptionContext = *mut ::std::os::raw::c_void; +pub type InterceptionDevice = ::std::os::raw::c_int; +pub type InterceptionPrecedence = ::std::os::raw::c_int; +pub type InterceptionFilter = ::std::os::raw::c_ushort; +pub type InterceptionPredicate = ::std::option::Option< + unsafe extern "C" fn(device: InterceptionDevice) -> ::std::os::raw::c_int, +>; +pub const InterceptionKeyState_INTERCEPTION_KEY_DOWN: InterceptionKeyState = 0; +pub const InterceptionKeyState_INTERCEPTION_KEY_UP: InterceptionKeyState = 1; +pub const InterceptionKeyState_INTERCEPTION_KEY_E0: InterceptionKeyState = 2; +pub const InterceptionKeyState_INTERCEPTION_KEY_E1: InterceptionKeyState = 4; +pub const InterceptionKeyState_INTERCEPTION_KEY_TERMSRV_SET_LED: InterceptionKeyState = 8; +pub const InterceptionKeyState_INTERCEPTION_KEY_TERMSRV_SHADOW: InterceptionKeyState = 16; +pub const InterceptionKeyState_INTERCEPTION_KEY_TERMSRV_VKPACKET: InterceptionKeyState = 32; +pub type InterceptionKeyState = u32; +pub const InterceptionFilterKeyState_INTERCEPTION_FILTER_KEY_NONE: InterceptionFilterKeyState = 0; +pub const InterceptionFilterKeyState_INTERCEPTION_FILTER_KEY_ALL: InterceptionFilterKeyState = + 65535; +pub const InterceptionFilterKeyState_INTERCEPTION_FILTER_KEY_DOWN: InterceptionFilterKeyState = 1; +pub const InterceptionFilterKeyState_INTERCEPTION_FILTER_KEY_UP: InterceptionFilterKeyState = 2; +pub const InterceptionFilterKeyState_INTERCEPTION_FILTER_KEY_E0: InterceptionFilterKeyState = 4; +pub const InterceptionFilterKeyState_INTERCEPTION_FILTER_KEY_E1: InterceptionFilterKeyState = 8; +pub const InterceptionFilterKeyState_INTERCEPTION_FILTER_KEY_TERMSRV_SET_LED: + InterceptionFilterKeyState = 16; +pub const InterceptionFilterKeyState_INTERCEPTION_FILTER_KEY_TERMSRV_SHADOW: + InterceptionFilterKeyState = 32; +pub const InterceptionFilterKeyState_INTERCEPTION_FILTER_KEY_TERMSRV_VKPACKET: + InterceptionFilterKeyState = 64; +pub type InterceptionFilterKeyState = u32; +pub const InterceptionMouseState_INTERCEPTION_MOUSE_LEFT_BUTTON_DOWN: InterceptionMouseState = 1; +pub const InterceptionMouseState_INTERCEPTION_MOUSE_LEFT_BUTTON_UP: InterceptionMouseState = 2; +pub const InterceptionMouseState_INTERCEPTION_MOUSE_RIGHT_BUTTON_DOWN: InterceptionMouseState = 4; +pub const InterceptionMouseState_INTERCEPTION_MOUSE_RIGHT_BUTTON_UP: InterceptionMouseState = 8; +pub const InterceptionMouseState_INTERCEPTION_MOUSE_MIDDLE_BUTTON_DOWN: InterceptionMouseState = 16; +pub const InterceptionMouseState_INTERCEPTION_MOUSE_MIDDLE_BUTTON_UP: InterceptionMouseState = 32; +pub const InterceptionMouseState_INTERCEPTION_MOUSE_BUTTON_1_DOWN: InterceptionMouseState = 1; +pub const InterceptionMouseState_INTERCEPTION_MOUSE_BUTTON_1_UP: InterceptionMouseState = 2; +pub const InterceptionMouseState_INTERCEPTION_MOUSE_BUTTON_2_DOWN: InterceptionMouseState = 4; +pub const InterceptionMouseState_INTERCEPTION_MOUSE_BUTTON_2_UP: InterceptionMouseState = 8; +pub const InterceptionMouseState_INTERCEPTION_MOUSE_BUTTON_3_DOWN: InterceptionMouseState = 16; +pub const InterceptionMouseState_INTERCEPTION_MOUSE_BUTTON_3_UP: InterceptionMouseState = 32; +pub const InterceptionMouseState_INTERCEPTION_MOUSE_BUTTON_4_DOWN: InterceptionMouseState = 64; +pub const InterceptionMouseState_INTERCEPTION_MOUSE_BUTTON_4_UP: InterceptionMouseState = 128; +pub const InterceptionMouseState_INTERCEPTION_MOUSE_BUTTON_5_DOWN: InterceptionMouseState = 256; +pub const InterceptionMouseState_INTERCEPTION_MOUSE_BUTTON_5_UP: InterceptionMouseState = 512; +pub const InterceptionMouseState_INTERCEPTION_MOUSE_WHEEL: InterceptionMouseState = 1024; +pub const InterceptionMouseState_INTERCEPTION_MOUSE_HWHEEL: InterceptionMouseState = 2048; +pub type InterceptionMouseState = u32; +pub const InterceptionFilterMouseState_INTERCEPTION_FILTER_MOUSE_NONE: + InterceptionFilterMouseState = 0; +pub const InterceptionFilterMouseState_INTERCEPTION_FILTER_MOUSE_ALL: InterceptionFilterMouseState = + 65535; +pub const InterceptionFilterMouseState_INTERCEPTION_FILTER_MOUSE_LEFT_BUTTON_DOWN: + InterceptionFilterMouseState = 1; +pub const InterceptionFilterMouseState_INTERCEPTION_FILTER_MOUSE_LEFT_BUTTON_UP: + InterceptionFilterMouseState = 2; +pub const InterceptionFilterMouseState_INTERCEPTION_FILTER_MOUSE_RIGHT_BUTTON_DOWN: + InterceptionFilterMouseState = 4; +pub const InterceptionFilterMouseState_INTERCEPTION_FILTER_MOUSE_RIGHT_BUTTON_UP: + InterceptionFilterMouseState = 8; +pub const InterceptionFilterMouseState_INTERCEPTION_FILTER_MOUSE_MIDDLE_BUTTON_DOWN: + InterceptionFilterMouseState = 16; +pub const InterceptionFilterMouseState_INTERCEPTION_FILTER_MOUSE_MIDDLE_BUTTON_UP: + InterceptionFilterMouseState = 32; +pub const InterceptionFilterMouseState_INTERCEPTION_FILTER_MOUSE_BUTTON_1_DOWN: + InterceptionFilterMouseState = 1; +pub const InterceptionFilterMouseState_INTERCEPTION_FILTER_MOUSE_BUTTON_1_UP: + InterceptionFilterMouseState = 2; +pub const InterceptionFilterMouseState_INTERCEPTION_FILTER_MOUSE_BUTTON_2_DOWN: + InterceptionFilterMouseState = 4; +pub const InterceptionFilterMouseState_INTERCEPTION_FILTER_MOUSE_BUTTON_2_UP: + InterceptionFilterMouseState = 8; +pub const InterceptionFilterMouseState_INTERCEPTION_FILTER_MOUSE_BUTTON_3_DOWN: + InterceptionFilterMouseState = 16; +pub const InterceptionFilterMouseState_INTERCEPTION_FILTER_MOUSE_BUTTON_3_UP: + InterceptionFilterMouseState = 32; +pub const InterceptionFilterMouseState_INTERCEPTION_FILTER_MOUSE_BUTTON_4_DOWN: + InterceptionFilterMouseState = 64; +pub const InterceptionFilterMouseState_INTERCEPTION_FILTER_MOUSE_BUTTON_4_UP: + InterceptionFilterMouseState = 128; +pub const InterceptionFilterMouseState_INTERCEPTION_FILTER_MOUSE_BUTTON_5_DOWN: + InterceptionFilterMouseState = 256; +pub const InterceptionFilterMouseState_INTERCEPTION_FILTER_MOUSE_BUTTON_5_UP: + InterceptionFilterMouseState = 512; +pub const InterceptionFilterMouseState_INTERCEPTION_FILTER_MOUSE_WHEEL: + InterceptionFilterMouseState = 1024; +pub const InterceptionFilterMouseState_INTERCEPTION_FILTER_MOUSE_HWHEEL: + InterceptionFilterMouseState = 2048; +pub const InterceptionFilterMouseState_INTERCEPTION_FILTER_MOUSE_MOVE: + InterceptionFilterMouseState = 4096; +pub type InterceptionFilterMouseState = u32; +pub const InterceptionMouseFlag_INTERCEPTION_MOUSE_MOVE_RELATIVE: InterceptionMouseFlag = 0; +pub const InterceptionMouseFlag_INTERCEPTION_MOUSE_MOVE_ABSOLUTE: InterceptionMouseFlag = 1; +pub const InterceptionMouseFlag_INTERCEPTION_MOUSE_VIRTUAL_DESKTOP: InterceptionMouseFlag = 2; +pub const InterceptionMouseFlag_INTERCEPTION_MOUSE_ATTRIBUTES_CHANGED: InterceptionMouseFlag = 4; +pub const InterceptionMouseFlag_INTERCEPTION_MOUSE_MOVE_NOCOALESCE: InterceptionMouseFlag = 8; +pub const InterceptionMouseFlag_INTERCEPTION_MOUSE_TERMSRV_SRC_SHADOW: InterceptionMouseFlag = 256; +pub type InterceptionMouseFlag = u32; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct InterceptionMouseStroke { + pub state: ::std::os::raw::c_ushort, + pub flags: ::std::os::raw::c_ushort, + pub rolling: ::std::os::raw::c_short, + pub x: ::std::os::raw::c_int, + pub y: ::std::os::raw::c_int, + pub information: ::std::os::raw::c_uint, +} +#[test] +fn bindgen_test_layout_InterceptionMouseStroke() { + assert_eq!( + ::std::mem::size_of::(), + 20usize, + concat!("Size of: ", stringify!(InterceptionMouseStroke)) + ); + assert_eq!( + ::std::mem::align_of::(), + 4usize, + concat!("Alignment of ", stringify!(InterceptionMouseStroke)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).state as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(InterceptionMouseStroke), + "::", + stringify!(state) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).flags as *const _ as usize }, + 2usize, + concat!( + "Offset of field: ", + stringify!(InterceptionMouseStroke), + "::", + stringify!(flags) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).rolling as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(InterceptionMouseStroke), + "::", + stringify!(rolling) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).x as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(InterceptionMouseStroke), + "::", + stringify!(x) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).y as *const _ as usize }, + 12usize, + concat!( + "Offset of field: ", + stringify!(InterceptionMouseStroke), + "::", + stringify!(y) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).information as *const _ as usize + }, + 16usize, + concat!( + "Offset of field: ", + stringify!(InterceptionMouseStroke), + "::", + stringify!(information) + ) + ); +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct InterceptionKeyStroke { + pub code: ::std::os::raw::c_ushort, + pub state: ::std::os::raw::c_ushort, + pub information: ::std::os::raw::c_uint, +} +#[test] +fn bindgen_test_layout_InterceptionKeyStroke() { + assert_eq!( + ::std::mem::size_of::(), + 8usize, + concat!("Size of: ", stringify!(InterceptionKeyStroke)) + ); + assert_eq!( + ::std::mem::align_of::(), + 4usize, + concat!("Alignment of ", stringify!(InterceptionKeyStroke)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).code as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(InterceptionKeyStroke), + "::", + stringify!(code) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).state as *const _ as usize }, + 2usize, + concat!( + "Offset of field: ", + stringify!(InterceptionKeyStroke), + "::", + stringify!(state) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).information as *const _ as usize + }, + 4usize, + concat!( + "Offset of field: ", + stringify!(InterceptionKeyStroke), + "::", + stringify!(information) + ) + ); +} +pub type InterceptionStroke = [::std::os::raw::c_char; 20usize]; +extern "C" { + pub fn interception_create_context() -> InterceptionContext; +} +extern "C" { + pub fn interception_destroy_context(context: InterceptionContext); +} +extern "C" { + pub fn interception_get_precedence( + context: InterceptionContext, + device: InterceptionDevice, + ) -> InterceptionPrecedence; +} +extern "C" { + pub fn interception_set_precedence( + context: InterceptionContext, + device: InterceptionDevice, + precedence: InterceptionPrecedence, + ); +} +extern "C" { + pub fn interception_get_filter( + context: InterceptionContext, + device: InterceptionDevice, + ) -> InterceptionFilter; +} +extern "C" { + pub fn interception_set_filter( + context: InterceptionContext, + predicate: InterceptionPredicate, + filter: InterceptionFilter, + ); +} +extern "C" { + pub fn interception_wait(context: InterceptionContext) -> InterceptionDevice; +} +extern "C" { + pub fn interception_wait_with_timeout( + context: InterceptionContext, + milliseconds: ::std::os::raw::c_ulong, + ) -> InterceptionDevice; +} +extern "C" { + pub fn interception_send( + context: InterceptionContext, + device: InterceptionDevice, + stroke: *const InterceptionStroke, + nstroke: ::std::os::raw::c_uint, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn interception_receive( + context: InterceptionContext, + device: InterceptionDevice, + stroke: *mut InterceptionStroke, + nstroke: ::std::os::raw::c_uint, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn interception_get_hardware_id( + context: InterceptionContext, + device: InterceptionDevice, + hardware_id_buffer: *mut ::std::os::raw::c_void, + buffer_size: ::std::os::raw::c_uint, + ) -> ::std::os::raw::c_uint; +} +extern "C" { + pub fn interception_is_invalid(device: InterceptionDevice) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn interception_is_keyboard(device: InterceptionDevice) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn interception_is_mouse(device: InterceptionDevice) -> ::std::os::raw::c_int; +} diff --git a/src-interception/src/interception.c b/src-interception/src/interception.c new file mode 100644 index 0000000..2e4c180 --- /dev/null +++ b/src-interception/src/interception.c @@ -0,0 +1,328 @@ +#include +#include +#include + +#include "interception.h" + +#define IOCTL_SET_PRECEDENCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_GET_PRECEDENCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_SET_FILTER CTL_CODE(FILE_DEVICE_UNKNOWN, 0x804, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_GET_FILTER CTL_CODE(FILE_DEVICE_UNKNOWN, 0x808, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_SET_EVENT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x810, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_WRITE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x820, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_READ CTL_CODE(FILE_DEVICE_UNKNOWN, 0x840, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_GET_HARDWARE_ID CTL_CODE(FILE_DEVICE_UNKNOWN, 0x880, METHOD_BUFFERED, FILE_ANY_ACCESS) + +typedef struct _KEYBOARD_INPUT_DATA +{ + USHORT UnitId; + USHORT MakeCode; + USHORT Flags; + USHORT Reserved; + ULONG ExtraInformation; +} KEYBOARD_INPUT_DATA, *PKEYBOARD_INPUT_DATA; + +typedef struct _MOUSE_INPUT_DATA +{ + USHORT UnitId; + USHORT Flags; + USHORT ButtonFlags; + USHORT ButtonData; + ULONG RawButtons; + LONG LastX; + LONG LastY; + ULONG ExtraInformation; +} MOUSE_INPUT_DATA, *PMOUSE_INPUT_DATA; + +typedef struct +{ + void *handle; + void *unempty; +} *InterceptionDeviceArray; + +InterceptionContext interception_create_context(void) +{ + InterceptionDeviceArray device_array = 0; + char device_name[] = "\\\\.\\interception00"; + DWORD bytes_returned; + InterceptionDevice i; + + device_array = (InterceptionDeviceArray)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, INTERCEPTION_MAX_DEVICE * sizeof(*((InterceptionDeviceArray) 0))); + if(!device_array) return 0; + + for(i = 0; i < INTERCEPTION_MAX_DEVICE; ++i) + { + HANDLE zero_padded_handle[2] = {0}; + + sprintf(&device_name[sizeof(device_name) - 3], "%02d", i); + + device_array[i].handle = CreateFileA(device_name, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); + + if (device_array[i].handle == INVALID_HANDLE_VALUE) { + interception_destroy_context(device_array); + return 0; + } + + device_array[i].unempty = CreateEventA(NULL, TRUE, FALSE, NULL); + + if(device_array[i].unempty == NULL) + { + interception_destroy_context(device_array); + return 0; + } + + zero_padded_handle[0] = device_array[i].unempty; + + if(!DeviceIoControl(device_array[i].handle, IOCTL_SET_EVENT, zero_padded_handle, sizeof(zero_padded_handle), NULL, 0, &bytes_returned, NULL)) + { + interception_destroy_context(device_array); + return 0; + } + } + + return device_array; +} + +void interception_destroy_context(InterceptionContext context) +{ + InterceptionDeviceArray device_array = (InterceptionDeviceArray)context; + unsigned int i; + + if(!context) return; + + for(i = 0; i < INTERCEPTION_MAX_DEVICE; ++i) + { + if(device_array[i].handle != INVALID_HANDLE_VALUE) + CloseHandle(device_array[i].handle); + + if(device_array[i].unempty != NULL) + CloseHandle(device_array[i].unempty); + } + + HeapFree(GetProcessHeap(), 0, context); +} + +InterceptionPrecedence interception_get_precedence(InterceptionContext context, InterceptionDevice device) +{ + InterceptionDeviceArray device_array = (InterceptionDeviceArray)context; + InterceptionPrecedence precedence = 0; + DWORD bytes_returned; + + if(context && device_array[device - 1].handle) + DeviceIoControl(device_array[device - 1].handle, IOCTL_GET_PRECEDENCE, NULL, 0, (LPVOID)&precedence, sizeof(InterceptionPrecedence), &bytes_returned, NULL); + + return precedence; +} + +void interception_set_precedence(InterceptionContext context, InterceptionDevice device, InterceptionPrecedence precedence) +{ + InterceptionDeviceArray device_array = (InterceptionDeviceArray)context; + DWORD bytes_returned; + + if(context && device_array[device - 1].handle) + DeviceIoControl(device_array[device - 1].handle, IOCTL_SET_PRECEDENCE, (LPVOID)&precedence, sizeof(InterceptionPrecedence), NULL, 0, &bytes_returned, NULL); +} + +InterceptionFilter interception_get_filter(InterceptionContext context, InterceptionDevice device) +{ + InterceptionDeviceArray device_array = (InterceptionDeviceArray)context; + InterceptionFilter filter = 0; + DWORD bytes_returned; + + if(context && device_array[device - 1].handle) + DeviceIoControl(device_array[device - 1].handle, IOCTL_GET_FILTER, NULL, 0, (LPVOID)&filter, sizeof(InterceptionFilter), &bytes_returned, NULL); + + return filter; +} + +void interception_set_filter(InterceptionContext context, InterceptionPredicate interception_predicate, InterceptionFilter filter) +{ + InterceptionDeviceArray device_array = (InterceptionDeviceArray)context; + InterceptionDevice i; + DWORD bytes_returned; + + if(context) + for(i = 0; i < INTERCEPTION_MAX_DEVICE; ++i) + if(device_array[i].handle && interception_predicate(i + 1)) + DeviceIoControl(device_array[i].handle, IOCTL_SET_FILTER, (LPVOID)&filter, sizeof(InterceptionFilter), NULL, 0, &bytes_returned, NULL); +} + +InterceptionDevice interception_wait(InterceptionContext context) +{ + return interception_wait_with_timeout(context, INFINITE); +} + +InterceptionDevice interception_wait_with_timeout(InterceptionContext context, unsigned long milliseconds) +{ + InterceptionDeviceArray device_array = (InterceptionDeviceArray)context; + HANDLE wait_handles[INTERCEPTION_MAX_DEVICE]; + DWORD i, j, k; + + if(!context) return 0; + + for(i = 0, j = 0; i < INTERCEPTION_MAX_DEVICE; ++i) + { + if (device_array[i].unempty) + wait_handles[j++] = device_array[i].unempty; + } + + k = WaitForMultipleObjects(j, wait_handles, FALSE, milliseconds); + + if(k == WAIT_FAILED || k == WAIT_TIMEOUT) return 0; + + for(i = 0, j = 0; i < INTERCEPTION_MAX_DEVICE; ++i) + { + if (device_array[i].unempty) + if (k == j++) + break; + } + + return i + 1; +} + +int interception_send(InterceptionContext context, InterceptionDevice device, const InterceptionStroke *stroke, unsigned int nstroke) +{ + InterceptionDeviceArray device_array = (InterceptionDeviceArray)context; + DWORD strokeswritten = 0; + + if(context == 0 || nstroke == 0 || interception_is_invalid(device) || !device_array[device - 1].handle) return 0; + + if(interception_is_keyboard(device)) + { + PKEYBOARD_INPUT_DATA rawstrokes = (PKEYBOARD_INPUT_DATA)HeapAlloc(GetProcessHeap(), 0, nstroke * sizeof(KEYBOARD_INPUT_DATA)); + unsigned int i; + + if(!rawstrokes) return 0; + + for(i = 0; i < nstroke; ++i) + { + InterceptionKeyStroke *key_stroke = (InterceptionKeyStroke *) stroke; + + rawstrokes[i].UnitId = 0; + rawstrokes[i].MakeCode = key_stroke[i].code; + rawstrokes[i].Flags = key_stroke[i].state; + rawstrokes[i].Reserved = 0; + rawstrokes[i].ExtraInformation = key_stroke[i].information; + } + + DeviceIoControl(device_array[device - 1].handle, IOCTL_WRITE, rawstrokes,(DWORD)nstroke * sizeof(KEYBOARD_INPUT_DATA), NULL, 0, &strokeswritten, NULL); + + HeapFree(GetProcessHeap(), 0, rawstrokes); + + strokeswritten /= sizeof(KEYBOARD_INPUT_DATA); + } + else + { + PMOUSE_INPUT_DATA rawstrokes = (PMOUSE_INPUT_DATA)HeapAlloc(GetProcessHeap(), 0, nstroke * sizeof(MOUSE_INPUT_DATA)); + unsigned int i; + + if(!rawstrokes) return 0; + + for(i = 0; i < nstroke; ++i) + { + InterceptionMouseStroke *mouse_stroke = (InterceptionMouseStroke *) stroke; + + rawstrokes[i].UnitId = 0; + rawstrokes[i].Flags = mouse_stroke[i].flags; + rawstrokes[i].ButtonFlags = mouse_stroke[i].state; + rawstrokes[i].ButtonData = mouse_stroke[i].rolling; + rawstrokes[i].RawButtons = 0; + rawstrokes[i].LastX = mouse_stroke[i].x; + rawstrokes[i].LastY = mouse_stroke[i].y; + rawstrokes[i].ExtraInformation = mouse_stroke[i].information; + } + + DeviceIoControl(device_array[device - 1].handle, IOCTL_WRITE, rawstrokes, (DWORD)nstroke * sizeof(MOUSE_INPUT_DATA), NULL, 0, &strokeswritten, NULL); + + HeapFree(GetProcessHeap(), 0, rawstrokes); + + strokeswritten /= sizeof(MOUSE_INPUT_DATA); + } + + return strokeswritten; +} + +int interception_receive(InterceptionContext context, InterceptionDevice device, InterceptionStroke *stroke, unsigned int nstroke) +{ + InterceptionDeviceArray device_array = (InterceptionDeviceArray)context; + DWORD strokesread = 0; + + if(context == 0 || nstroke == 0 || interception_is_invalid(device) || !device_array[device - 1].handle) return 0; + + if(interception_is_keyboard(device)) + { + PKEYBOARD_INPUT_DATA rawstrokes = (PKEYBOARD_INPUT_DATA)HeapAlloc(GetProcessHeap(), 0, nstroke * sizeof(KEYBOARD_INPUT_DATA)); + unsigned int i; + + if(!rawstrokes) return 0; + + DeviceIoControl(device_array[device - 1].handle, IOCTL_READ, NULL, 0, rawstrokes, (DWORD)nstroke * sizeof(KEYBOARD_INPUT_DATA), &strokesread, NULL); + + strokesread /= sizeof(KEYBOARD_INPUT_DATA); + + for(i = 0; i < (unsigned int)strokesread; ++i) + { + InterceptionKeyStroke *key_stroke = (InterceptionKeyStroke *) stroke; + + key_stroke[i].code = rawstrokes[i].MakeCode; + key_stroke[i].state = rawstrokes[i].Flags; + key_stroke[i].information = rawstrokes[i].ExtraInformation; + } + + HeapFree(GetProcessHeap(), 0, rawstrokes); + } + else + { + PMOUSE_INPUT_DATA rawstrokes = (PMOUSE_INPUT_DATA)HeapAlloc(GetProcessHeap(), 0, nstroke * sizeof(MOUSE_INPUT_DATA)); + unsigned int i; + + if(!rawstrokes) return 0; + + DeviceIoControl(device_array[device - 1].handle, IOCTL_READ, NULL, 0, rawstrokes, (DWORD)nstroke * sizeof(MOUSE_INPUT_DATA), &strokesread, NULL); + + strokesread /= sizeof(MOUSE_INPUT_DATA); + + for(i = 0; i < (unsigned int)strokesread; ++i) + { + InterceptionMouseStroke *mouse_stroke = (InterceptionMouseStroke *) stroke; + + mouse_stroke[i].flags = rawstrokes[i].Flags; + mouse_stroke[i].state = rawstrokes[i].ButtonFlags; + mouse_stroke[i].rolling = rawstrokes[i].ButtonData; + mouse_stroke[i].x = rawstrokes[i].LastX; + mouse_stroke[i].y = rawstrokes[i].LastY; + mouse_stroke[i].information = rawstrokes[i].ExtraInformation; + } + + HeapFree(GetProcessHeap(), 0, rawstrokes); + } + + return strokesread; +} + +unsigned int interception_get_hardware_id(InterceptionContext context, InterceptionDevice device, void *hardware_id_buffer, unsigned int buffer_size) +{ + InterceptionDeviceArray device_array = (InterceptionDeviceArray)context; + DWORD output_size = 0; + + if(context == 0 || interception_is_invalid(device) || !device_array[device - 1].handle) return 0; + + DeviceIoControl(device_array[device - 1].handle, IOCTL_GET_HARDWARE_ID, NULL, 0, hardware_id_buffer, buffer_size, &output_size, NULL); + + return output_size; +} + +int interception_is_invalid(InterceptionDevice device) +{ + return !interception_is_keyboard(device) && !interception_is_mouse(device); +} + +int interception_is_keyboard(InterceptionDevice device) +{ + return device >= INTERCEPTION_KEYBOARD(0) && device <= INTERCEPTION_KEYBOARD(INTERCEPTION_MAX_KEYBOARD - 1); +} + +int interception_is_mouse(InterceptionDevice device) +{ + return device >= INTERCEPTION_MOUSE(0) && device <= INTERCEPTION_MOUSE(INTERCEPTION_MAX_MOUSE - 1); +} \ No newline at end of file diff --git a/src-interception/src/lib.rs b/src-interception/src/lib.rs new file mode 100644 index 0000000..62f2e81 --- /dev/null +++ b/src-interception/src/lib.rs @@ -0,0 +1,373 @@ +#[macro_use] +extern crate bitflags; + +mod bindings; +use bindings as raw; +pub mod scancode; + +pub use scancode::ScanCode; + +use std::convert::{TryFrom, TryInto}; +use std::default::Default; +use std::time::Duration; +use std::vec::Vec; + +pub type Device = i32; +pub type Precedence = i32; + +pub enum Filter { + MouseFilter(MouseFilter), + KeyFilter(KeyFilter), +} + +pub type Predicate = extern "C" fn(device: Device) -> bool; + +bitflags! { + pub struct MouseState: u16 { + const LEFT_BUTTON_DOWN = 1; + const LEFT_BUTTON_UP = 2; + + const RIGHT_BUTTON_DOWN = 4; + const RIGHT_BUTTON_UP = 8; + + const MIDDLE_BUTTON_DOWN = 16; + const MIDDLE_BUTTON_UP = 32; + + const BUTTON_4_DOWN = 64; + const BUTTON_4_UP = 128; + + const BUTTON_5_DOWN = 256; + const BUTTON_5_UP = 512; + + const WHEEL = 1024; + const HWHEEL = 2048; + + // MouseFilter only + const MOVE = 4096; + } +} + +pub type MouseFilter = MouseState; + +bitflags! { + pub struct MouseFlags: u16 { + const MOVE_RELATIVE = 0; + const MOVE_ABSOLUTE = 1; + + const VIRTUAL_DESKTOP = 2; + const ATTRIBUTES_CHANGED = 4; + + const MOVE_NO_COALESCE = 8; + + const TERMSRV_SRC_SHADOW = 256; + } +} + +bitflags! { + pub struct KeyState: u16 { + const DOWN = 0; + const UP = 1; + + const E0 = 2; + const E1 = 3; + + const TERMSRV_SET_LED = 8; + const TERMSRV_SHADOW = 16; + const TERMSRV_VKPACKET = 32; + } +} + +bitflags! { + pub struct KeyFilter: u16 { + const DOWN = 1; + const UP = 2; + + const E0 = 4; + const E1 = 8; + + const TERMSRV_SET_LED = 16; + const TERMSRV_SHADOW = 32; + const TERMSRV_VKPACKET = 64; + } +} + +#[derive(Debug, Copy, Clone)] +pub enum Stroke { + Mouse { + state: MouseState, + flags: MouseFlags, + rolling: i16, + x: i32, + y: i32, + information: u32, + }, + + Keyboard { + code: ScanCode, + state: KeyState, + information: u32, + }, +} + +impl TryFrom for Stroke { + type Error = &'static str; + + fn try_from(raw_stroke: raw::InterceptionMouseStroke) -> Result { + let state = match MouseState::from_bits(raw_stroke.state) { + Some(state) => state, + None => return Err("Extra bits in raw mouse state"), + }; + + let flags = match MouseFlags::from_bits(raw_stroke.flags) { + Some(flags) => flags, + None => return Err("Extra bits in raw mouse flags"), + }; + + Ok(Stroke::Mouse { + state: state, + flags: flags, + rolling: raw_stroke.rolling, + x: raw_stroke.x, + y: raw_stroke.y, + information: raw_stroke.information, + }) + } +} + +impl TryFrom for Stroke { + type Error = &'static str; + + fn try_from(raw_stroke: raw::InterceptionKeyStroke) -> Result { + let state = match KeyState::from_bits(raw_stroke.state) { + Some(state) => state, + None => return Err("Extra bits in raw keyboard state"), + }; + + let code = match ScanCode::try_from(raw_stroke.code) { + Ok(code) => code, + Err(_) => ScanCode::Esc, + }; + + Ok(Stroke::Keyboard { + code: code, + state: state, + information: raw_stroke.information, + }) + } +} + +impl TryFrom for raw::InterceptionMouseStroke { + type Error = &'static str; + + fn try_from(stroke: Stroke) -> Result { + if let Stroke::Mouse { + state, + flags, + rolling, + x, + y, + information, + } = stroke + { + Ok(raw::InterceptionMouseStroke { + state: state.bits(), + flags: flags.bits(), + rolling: rolling, + x: x, + y: y, + information: information, + }) + } else { + Err("Stroke must be a mouse stroke") + } + } +} + +impl TryFrom for raw::InterceptionKeyStroke { + type Error = &'static str; + + fn try_from(stroke: Stroke) -> Result { + if let Stroke::Keyboard { + code, + state, + information, + } = stroke + { + Ok(raw::InterceptionKeyStroke { + code: code as u16, + state: state.bits(), + information: information, + }) + } else { + Err("Stroke must be a keyboard stroke") + } + } +} + +pub struct Interception { + ctx: raw::InterceptionContext, +} + +impl Interception { + pub fn new() -> Option { + let ctx = unsafe { raw::interception_create_context() }; + + if ctx == std::ptr::null_mut() { + return None; + } + + Some(Interception { ctx: ctx }) + } + + pub fn get_precedence(&self, device: Device) -> Precedence { + unsafe { raw::interception_get_precedence(self.ctx, device) } + } + + pub fn set_precedence(&self, device: Device, precedence: Precedence) { + unsafe { raw::interception_set_precedence(self.ctx, device, precedence) } + } + + pub fn get_filter(&self, device: Device) -> Filter { + if is_invalid(device) { + return Filter::KeyFilter(KeyFilter::empty()); + } + + let raw_filter = unsafe { raw::interception_get_filter(self.ctx, device) }; + if is_mouse(device) { + let filter = match MouseFilter::from_bits(raw_filter) { + Some(filter) => filter, + None => MouseFilter::empty(), + }; + + Filter::MouseFilter(filter) + } else { + let filter = match KeyFilter::from_bits(raw_filter) { + Some(filter) => filter, + None => KeyFilter::empty(), + }; + + Filter::KeyFilter(filter) + } + } + + pub fn set_filter(&self, predicate: Predicate, filter: Filter) { + let filter = match filter { + Filter::MouseFilter(filter) => filter.bits(), + Filter::KeyFilter(filter) => filter.bits(), + }; + + unsafe { + let predicate = std::mem::transmute(Some(predicate)); + raw::interception_set_filter(self.ctx, predicate, filter) + } + } + + pub fn wait(&self) -> Device { + unsafe { raw::interception_wait(self.ctx) } + } + + pub fn wait_with_timeout(&self, duration: Duration) -> Device { + let millis = match u32::try_from(duration.as_millis()) { + Ok(m) => m, + Err(_) => u32::MAX, + }; + + unsafe { raw::interception_wait_with_timeout(self.ctx, millis) } + } + + pub fn send(&self, device: Device, strokes: &[Stroke]) -> i32 { + if is_mouse(device) { + self.send_internal::(device, strokes) + } else if is_keyboard(device) { + self.send_internal::(device, strokes) + } else { + 0 + } + } + + fn send_internal>(&self, device: Device, strokes: &[Stroke]) -> i32 { + let mut raw_strokes = Vec::new(); + + for stroke in strokes { + if let Ok(raw_stroke) = T::try_from(*stroke) { + raw_strokes.push(raw_stroke) + } + } + + let ptr = raw_strokes.as_ptr(); + let len = match u32::try_from(raw_strokes.len()) { + Ok(l) => l, + Err(_) => u32::MAX, + }; + + unsafe { raw::interception_send(self.ctx, device, std::mem::transmute(ptr), len) } + } + + pub fn receive(&self, device: Device, strokes: &mut [Stroke]) -> i32 { + if is_mouse(device) { + self.receive_internal::(device, strokes) + } else if is_keyboard(device) { + self.receive_internal::(device, strokes) + } else { + 0 + } + } + + fn receive_internal + Default + Copy>( + &self, + device: Device, + strokes: &mut [Stroke], + ) -> i32 { + let mut raw_strokes: Vec = Vec::with_capacity(strokes.len()); + raw_strokes.resize_with(strokes.len(), Default::default); + + let ptr = raw_strokes.as_ptr(); + let len = match u32::try_from(raw_strokes.len()) { + Ok(l) => l, + Err(_) => u32::MAX, + }; + + let num_read = + unsafe { raw::interception_receive(self.ctx, device, std::mem::transmute(ptr), len) }; + + let mut num_valid: i32 = 0; + for i in 0..num_read { + if let Ok(stroke) = raw_strokes[i as usize].try_into() { + strokes[num_valid as usize] = stroke; + num_valid += 1; + } + } + + num_valid + } + + pub fn get_hardware_id(&self, device: Device, buffer: &mut [u8]) -> u32 { + let ptr = buffer.as_mut_ptr(); + let len = match u32::try_from(buffer.len()) { + Ok(l) => l, + Err(_) => u32::MAX, + }; + + unsafe { + raw::interception_get_hardware_id(self.ctx, device, std::mem::transmute(ptr), len) + } + } +} + +impl Drop for Interception { + fn drop(&mut self) { + unsafe { raw::interception_destroy_context(self.ctx) } + } +} + +pub extern "C" fn is_invalid(device: Device) -> bool { + unsafe { raw::interception_is_invalid(device) != 0 } +} + +pub extern "C" fn is_keyboard(device: Device) -> bool { + unsafe { raw::interception_is_keyboard(device) != 0 } +} + +pub extern "C" fn is_mouse(device: Device) -> bool { + unsafe { raw::interception_is_mouse(device) != 0 } +} diff --git a/src-interception/src/scancode.rs b/src-interception/src/scancode.rs new file mode 100644 index 0000000..bba61e3 --- /dev/null +++ b/src-interception/src/scancode.rs @@ -0,0 +1,147 @@ +use num_enum::TryFromPrimitive; +use serde::{Deserialize, Serialize}; + +// ref: https://handmade.network/wiki/2823-keyboard_inputs_-_scancodes,_raw_input,_text_input,_key_names +#[derive(Serialize, Deserialize, Hash, Debug, Eq, PartialEq, Copy, Clone, TryFromPrimitive)] +#[repr(u16)] +pub enum ScanCode { + Esc = 0x01, + + Num1 = 0x02, + Num2 = 0x03, + Num3 = 0x04, + Num4 = 0x05, + Num5 = 0x06, + Num6 = 0x07, + Num7 = 0x08, + Num8 = 0x09, + Num9 = 0x0A, + Num0 = 0x0B, + + Minus = 0x0C, + Equals = 0x0D, + Backspace = 0x0E, + + Tab = 0x0F, + + Q = 0x10, + W = 0x11, + E = 0x12, + R = 0x13, + T = 0x14, + Y = 0x15, + U = 0x16, + I = 0x17, + O = 0x18, + P = 0x19, + + LeftBracket = 0x1A, + RightBracket = 0x1B, + Enter = 0x1C, + + LeftControl = 0x1D, + + A = 0x1E, + S = 0x1F, + D = 0x20, + F = 0x21, + G = 0x22, + H = 0x23, + J = 0x24, + K = 0x25, + L = 0x26, + + SemiColon = 0x27, + Apostrophe = 0x28, + Grave = 0x29, + LeftShift = 0x2A, + BackSlash = 0x2B, + + Z = 0x2C, + X = 0x2D, + C = 0x2E, + V = 0x2F, + B = 0x30, + N = 0x31, + M = 0x32, + + Comma = 0x33, + Period = 0x34, + Slash = 0x35, + RightShift = 0x36, + NumpadMultiply = 0x37, + LeftAlt = 0x38, + Space = 0x39, + CapsLock = 0x3A, + + F1 = 0x3B, + F2 = 0x3C, + F3 = 0x3D, + F4 = 0x3E, + F5 = 0x3F, + F6 = 0x40, + F7 = 0x41, + F8 = 0x42, + F9 = 0x43, + F10 = 0x44, + + NumLock = 0x45, + ScrollLock = 0x46, + + Numpad7 = 0x47, + Numpad8 = 0x48, + Numpad9 = 0x49, + + NumpadMinus = 0x4A, + + Numpad4 = 0x4B, + Numpad5 = 0x4C, + Numpad6 = 0x4D, + + NumpadPlus = 0x4E, + + Numpad1 = 0x4F, + Numpad2 = 0x50, + Numpad3 = 0x51, + Numpad0 = 0x52, + + NumpadPeriod = 0x53, + AltPrintScreen = 0x54, /* Alt + print screen. */ + Int1 = 0x56, /* Key between the left shift and Z. */ + + F11 = 0x57, + F12 = 0x58, + + Oem1 = 0x5A, /* VK_OEM_WSCTRL */ + Oem2 = 0x5B, /* VK_OEM_FINISH */ + Oem3 = 0x5C, /* VK_OEM_JUMP */ + + EraseEOF = 0x5D, + + Oem4 = 0x5E, /* VK_OEM_BACKTAB */ + Oem5 = 0x5F, /* VK_OEM_AUTO */ + + Zoom = 0x62, + Help = 0x63, + + F13 = 0x64, + F14 = 0x65, + F15 = 0x66, + F16 = 0x67, + F17 = 0x68, + F18 = 0x69, + F19 = 0x6A, + F20 = 0x6B, + F21 = 0x6C, + F22 = 0x6D, + F23 = 0x6E, + + Oem6 = 0x6F, /* VK_OEM_PA3 */ + Katakana = 0x70, + Oem7 = 0x71, /* VK_OEM_RESET */ + F24 = 0x76, + + SBCSChar = 0x77, + Convert = 0x79, + NonConvert = 0x7B, /* VK_OEM_PA1 */ +} diff --git a/src-slider_io/Cargo.lock b/src-slider_io/Cargo.lock index 1b6b9a3..c9f6f08 100644 --- a/src-slider_io/Cargo.lock +++ b/src-slider_io/Cargo.lock @@ -613,6 +613,20 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "interception" +version = "0.1.2" +dependencies = [ + "bitflags", + "interception-sys", + "num_enum", + "serde", +] + +[[package]] +name = "interception-sys" +version = "0.1.3" + [[package]] name = "ipconfig" version = "0.3.0" @@ -864,6 +878,27 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "once_cell" version = "1.9.0" @@ -1053,6 +1088,16 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml", +] + [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -1308,6 +1353,7 @@ dependencies = [ "futures-util", "hyper", "image", + "interception", "ipconfig", "log", "palette", diff --git a/src-slider_io/Cargo.toml b/src-slider_io/Cargo.toml index af5047d..3693fc2 100644 --- a/src-slider_io/Cargo.toml +++ b/src-slider_io/Cargo.toml @@ -31,6 +31,7 @@ serialport = "4.0.1" wwserial = {path = "../src-wwserial" } vigem-client = { version = "0.1.2", features = ["unstable"] } winapi = "0.3.9" +interception = {path = "../src-interception-rs" } ipconfig = "0.3.0" # webserver diff --git a/src-slider_io/src/bin/test_directinput.rs b/src-slider_io/src/bin/test_directinput.rs new file mode 100644 index 0000000..9f19c49 --- /dev/null +++ b/src-slider_io/src/bin/test_directinput.rs @@ -0,0 +1,42 @@ +extern crate slider_io; + +use std::io; + +use slider_io::{config::Config, context::Context}; + +#[tokio::main] +async fn main() { + env_logger::Builder::new() + .filter_level(log::LevelFilter::Debug) + .init(); + + let config = Config::from_str( + r##"{ + "deviceMode": "brokenithm", + "outputMode": "kb-32-tasoller", + "ledMode": "none", + "disableAirStrings": false, + "divaSerialPort": "COM1", + "divaBrightness": 63, + "brokenithmPort": 1606, + "keyboardSensitivity": 20, + "keyboardDirectInput": true, + "outputPolling": "100", + "outputWebsocketUrl": "localhost:3000", + "ledFaster": false, + "ledColorActive": "#ff00ff", + "ledColorInactive": "#ffff00", + "ledSensitivity": 20, + "ledWebsocketUrl": "localhost:3001", + "ledSerialPort": "COM5" + }"##, + ) + .unwrap(); + println!("{:?}", config); + + let ctx = Context::new(config); + + println!("Press enter to quit"); + let mut input = String::new(); + io::stdin().read_line(&mut input).unwrap(); +} diff --git a/src-slider_io/src/config.rs b/src-slider_io/src/config.rs index 8982f7d..cf9a5cc 100644 --- a/src-slider_io/src/config.rs +++ b/src-slider_io/src/config.rs @@ -37,6 +37,7 @@ impl Config { "divaBrightness": 63, "brokenithmPort": 1606, "keyboardSensitivity": 20, + "keyboardDirectInput": false, "outputPolling": "100", "outputWebsocketUrl": "localhost:3000", "ledFaster": false, diff --git a/src-slider_io/src/output/config.rs b/src-slider_io/src/output/config.rs index 81f330b..613ae92 100644 --- a/src-slider_io/src/output/config.rs +++ b/src-slider_io/src/output/config.rs @@ -41,6 +41,7 @@ pub enum OutputMode { layout: KeyboardLayout, polling: PollingRate, sensitivity: u8, + direct_input: bool, }, Gamepad { layout: GamepadLayout, @@ -89,46 +90,55 @@ impl OutputMode { layout: KeyboardLayout::Tasoller, polling: PollingRate::from_str(v["outputPolling"].as_str()?)?, sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, + direct_input: v["keyboardDirectInput"].as_bool()?, }, "kb-32-yuancon" => OutputMode::Keyboard { layout: KeyboardLayout::Yuancon, polling: PollingRate::from_str(v["outputPolling"].as_str()?)?, sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, + direct_input: v["keyboardDirectInput"].as_bool()?, }, "kb-32-umiguri" => OutputMode::Keyboard { layout: KeyboardLayout::Umiguri, polling: PollingRate::from_str(v["outputPolling"].as_str()?)?, sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, + direct_input: v["keyboardDirectInput"].as_bool()?, }, "kb-16" => OutputMode::Keyboard { layout: KeyboardLayout::TasollerHalf, polling: PollingRate::from_str(v["outputPolling"].as_str()?)?, sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, + direct_input: v["keyboardDirectInput"].as_bool()?, }, "kb-8" => OutputMode::Keyboard { layout: KeyboardLayout::EightK, polling: PollingRate::from_str(v["outputPolling"].as_str()?)?, sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, + direct_input: v["keyboardDirectInput"].as_bool()?, }, "kb-6" => OutputMode::Keyboard { layout: KeyboardLayout::SixK, polling: PollingRate::from_str(v["outputPolling"].as_str()?)?, sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, + direct_input: v["keyboardDirectInput"].as_bool()?, }, "kb-4" => OutputMode::Keyboard { layout: KeyboardLayout::FourK, polling: PollingRate::from_str(v["outputPolling"].as_str()?)?, sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, + direct_input: v["keyboardDirectInput"].as_bool()?, }, "kb-voltex" => OutputMode::Keyboard { layout: KeyboardLayout::Voltex, polling: PollingRate::from_str(v["outputPolling"].as_str()?)?, sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, + direct_input: v["keyboardDirectInput"].as_bool()?, }, "kb-neardayo" => OutputMode::Keyboard { layout: KeyboardLayout::Neardayo, polling: PollingRate::from_str(v["outputPolling"].as_str()?)?, sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, + direct_input: v["keyboardDirectInput"].as_bool()?, }, "gamepad-voltex" => OutputMode::Gamepad { layout: GamepadLayout::Voltex, diff --git a/src-slider_io/src/output/keyboard.rs b/src-slider_io/src/output/keyboard.rs index dd2afee..9f0e69c 100644 --- a/src-slider_io/src/output/keyboard.rs +++ b/src-slider_io/src/output/keyboard.rs @@ -1,13 +1,14 @@ +use interception::{Interception, KeyState, ScanCode, Stroke}; +use log::{error, info}; use std::mem; use winapi::{ ctypes::c_int, - um::winuser::{SendInput, INPUT, INPUT_KEYBOARD, KEYBDINPUT, KEYEVENTF_KEYUP}, + um::winuser::{ + MapVirtualKeyA, SendInput, INPUT, INPUT_KEYBOARD, KEYBDINPUT, KEYEVENTF_KEYUP, MAPVK_VK_TO_VSC, + }, }; -use super::{ - config::KeyboardLayout, - output::OutputHandler -}; +use super::{config::KeyboardLayout, output::OutputHandler}; #[rustfmt::skip] const TASOLLER_KB_MAP: [usize; 41] = [ @@ -126,18 +127,25 @@ const VOLTEX_KB_MAP_NEARDAYO: [usize; 41] = [ ]; pub struct KeyboardOutput { - ground_to_idx: [usize; 41], - idx_to_keycode: [u16; 41], - // keycode_to_idx: [usize; 256], + input_to_idx: [usize; 41], + key_idx_to_keycode: [u16; 41], + key_idx_to_scancode: [Option; 41], next_keys: [bool; 41], last_keys: [bool; 41], + direct_input: bool, + interception_handle: Option, + kb_buf: [INPUT; 41], + kb_direct_buf: [Stroke; 41], n_kb_buf: u32, } +// interception isn't send, but lazy to wrap +unsafe impl Send for KeyboardOutput {} + impl KeyboardOutput { - pub fn new(layout: KeyboardLayout) -> Self { + pub fn new(layout: KeyboardLayout, direct_input: bool) -> Self { let kb_map = match layout { KeyboardLayout::Tasoller => &TASOLLER_KB_MAP, KeyboardLayout::Yuancon => &YUANCON_KB_MAP, @@ -150,20 +158,43 @@ impl KeyboardOutput { KeyboardLayout::Neardayo => &VOLTEX_KB_MAP_NEARDAYO, }; - let mut ground_to_idx = [0 as usize; 41]; - let mut idx_to_keycode = [0 as u16; 41]; + let mut input_to_key_idx = [0 as usize; 41]; + let mut key_idx_to_keycode = [0 as u16; 41]; + let mut key_idx_to_scancode = [None as Option; 41]; let mut keycode_to_idx = [0xffff as usize; 256]; let mut keycode_count: usize = 0; for (ground, keycode) in kb_map.iter().enumerate() { if keycode_to_idx[*keycode] == 0xffff { keycode_to_idx[*keycode] = keycode_count; - idx_to_keycode[keycode_count] = *keycode as u16; + key_idx_to_keycode[keycode_count] = *keycode as u16; + key_idx_to_scancode[keycode_count] = + ScanCode::try_from(unsafe { MapVirtualKeyA((*keycode) as u32, MAPVK_VK_TO_VSC) as u16 }) + .ok(); + // info!( + // "mapped {:?} to {:?}", + // key_idx_to_keycode[keycode_count], key_idx_to_scancode[keycode_count] + // ); keycode_count += 1; } - ground_to_idx[ground] = keycode_to_idx[*keycode] + input_to_key_idx[ground] = keycode_to_idx[*keycode] } + let interception_handle = match direct_input { + true => { + let inner_handle = Interception::new(); + + if inner_handle.is_some() { + info!("Keyboard emulation with interception loaded"); + } else { + error!("Keyboard emulation cannot load interception, falling back to SendKeys()"); + } + inner_handle + } + false => None, + }; + let direct_input = interception_handle.is_some(); + let mut kb_buf = [INPUT { type_: INPUT_KEYBOARD, u: unsafe { mem::zeroed() }, @@ -178,13 +209,24 @@ impl KeyboardOutput { inner.dwExtraInfo = 0; } + let kb_direct_buf = [Stroke::Keyboard { + code: ScanCode::Esc, + state: KeyState::UP, + information: 0, + }; 41]; + Self { - ground_to_idx, - idx_to_keycode, - // keycode_to_idx, + input_to_idx: input_to_key_idx, + key_idx_to_keycode, + key_idx_to_scancode, next_keys: [false; 41], last_keys: [false; 41], + + direct_input, + interception_handle, + kb_buf, + kb_direct_buf, n_kb_buf: 0, } } @@ -198,24 +240,52 @@ impl KeyboardOutput { .zip(self.last_keys.iter_mut()) .enumerate() { - let keycode = self.idx_to_keycode[i]; - if keycode == 0 { + let keycode = self.key_idx_to_keycode[i]; + let scancode = self.key_idx_to_scancode[i]; + + if (!self.direct_input && keycode == 0) || (self.direct_input && scancode.is_none()) { continue; } - match (*n, *l) { - (true, false) => { + match (self.direct_input, *n, *l) { + (false, true, false) => { let inner: &mut KEYBDINPUT = unsafe { self.kb_buf[self.n_kb_buf as usize].u.ki_mut() }; inner.wVk = keycode; inner.dwFlags = 0; self.n_kb_buf += 1; - // println!("{} down", keycode); } - (false, true) => { + (false, false, true) => { let inner: &mut KEYBDINPUT = unsafe { self.kb_buf[self.n_kb_buf as usize].u.ki_mut() }; inner.wVk = keycode; inner.dwFlags = KEYEVENTF_KEYUP; self.n_kb_buf += 1; - // println!("{} up", keycode); + } + (true, true, false) => { + // info!("keydown {:?}", scancode); + let inner: &mut Stroke = &mut self.kb_direct_buf[self.n_kb_buf as usize]; + if let Stroke::Keyboard { + code, + state, + information: _, + } = inner + { + *code = scancode.unwrap(); + *state = KeyState::DOWN; + self.n_kb_buf += 1; + } + } + (true, false, true) => { + // info!("keyup {:?}", scancode); + let inner: &mut Stroke = &mut self.kb_direct_buf[self.n_kb_buf as usize]; + if let Stroke::Keyboard { + code, + state, + information: _, + } = inner + { + *code = scancode.unwrap(); + *state = KeyState::UP; + self.n_kb_buf += 1; + } } _ => {} } @@ -223,12 +293,19 @@ impl KeyboardOutput { } if self.n_kb_buf != 0 { - unsafe { - SendInput( - self.n_kb_buf, - self.kb_buf.as_mut_ptr(), - mem::size_of::() as c_int, - ); + match self.direct_input { + false => unsafe { + SendInput( + self.n_kb_buf, + self.kb_buf.as_mut_ptr(), + mem::size_of::() as c_int, + ); + }, + true => { + if let Some(handle) = self.interception_handle.as_mut() { + handle.send(1, &self.kb_direct_buf[0..self.n_kb_buf as usize]); + } + } } } } @@ -239,7 +316,7 @@ impl OutputHandler for KeyboardOutput { self.next_keys.fill(false); for (idx, x) in flat_input.iter().enumerate() { if *x { - self.next_keys[self.ground_to_idx[idx]] = true; + self.next_keys[self.input_to_idx[idx]] = true; } } self.send(); diff --git a/src-slider_io/src/output/output.rs b/src-slider_io/src/output/output.rs index f057738..cd9a957 100644 --- a/src-slider_io/src/output/output.rs +++ b/src-slider_io/src/output/output.rs @@ -42,9 +42,10 @@ impl AsyncJob for OutputJob { layout, polling, sensitivity, + direct_input, } => { self.sensitivity = sensitivity; - self.handler = Some(Box::new(KeyboardOutput::new(layout.clone()))); + self.handler = Some(Box::new(KeyboardOutput::new(layout.clone(), direct_input))); self.timer = interval(Duration::from_micros(polling.to_t_u64())); true diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 3ca17e8..e0bfa93 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1497,6 +1497,20 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "interception" +version = "0.1.2" +dependencies = [ + "bitflags", + "interception-sys", + "num_enum", + "serde", +] + +[[package]] +name = "interception-sys" +version = "0.1.3" + [[package]] name = "ipconfig" version = "0.3.0" @@ -2954,6 +2968,7 @@ dependencies = [ "futures-util", "hyper", "image 0.23.14", + "interception", "ipconfig", "log", "palette", diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index e7a2c1a..12c11b1 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -38,6 +38,7 @@ fn main() { env_logger::Builder::new() .filter_level(log::LevelFilter::Debug) .init(); + info!("Starting slidershim"); #[cfg(not(debug_assertions))] { @@ -45,7 +46,9 @@ fn main() { simple_logging::log_to_file(log_file_path.as_path(), log::LevelFilter::Debug).unwrap(); } + info!("Loading config"); let config = Arc::new(Mutex::new(Some(slider_io::Config::load()))); + info!("Loading manager"); let manager = Arc::new(Mutex::new(slider_io::Manager::new())); { let config_handle = config.lock(); @@ -55,6 +58,7 @@ fn main() { manager_handle.update_config(config_handle_ref.clone()); } + info!("Running tauri"); tauri::Builder::default() .system_tray( // System tray content diff --git a/src/App.svelte b/src/App.svelte index 577ddf7..036f84d 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -15,6 +15,7 @@ let divaBrightness = 63; let brokenithmPort = 1606; let keyboardSensitivity = 20; + let keyboardDirectInput = false; let outputPolling = "100"; let outputWebsocketUrl = "http://localhost:3000"; let ledFaster = false; @@ -69,6 +70,7 @@ divaBrightness = payload.divaBrightness || 63; brokenithmPort = payload.brokenithmPort || 1606; keyboardSensitivity = payload.keyboardSensitivity || 20; + keyboardDirectInput = payload.keyboardDirectInput || false; outputPolling = payload.outputPolling || "100"; outputWebsocketUrl = payload.outputWebsocketUrl || "http://localhost:3000/"; @@ -125,6 +127,7 @@ divaBrightness, brokenithmPort, keyboardSensitivity, + keyboardDirectInput, outputPolling, outputWebsocketUrl, ledFaster, @@ -386,6 +389,28 @@ {/if} + {#if outputMode.slice(0, 2) === "kb"} + + + Use DirectInput emulation + + + + + + + + + DirectInput emulation requires Interception to be installed + + + {/if} {#if outputMode === "websocket"} Output URL