├── mappings ├── empty ├── overwatch └── Desktop.txt ├── .gitignore ├── .vscode └── settings.json ├── src ├── backend │ ├── mod.rs │ ├── hidapi.rs │ └── sdl.rs ├── opts.rs ├── motion_stick.rs ├── mouse.rs ├── config │ ├── mod.rs │ ├── types.rs │ ├── all-settings-example │ ├── settings.rs │ └── parse.rs ├── calibration.rs ├── gyromouse.rs ├── main.rs ├── space_mapper.rs ├── engine.rs ├── joystick.rs └── mapping.rs ├── CHANGELOG.md ├── Cargo.toml ├── README.md ├── .github └── workflows │ └── rust.yml └── Cargo.lock /mappings/empty: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | mappings/scratchpad 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.allFeatures": false, 3 | "editor.formatOnSave": true 4 | } 5 | -------------------------------------------------------------------------------- /mappings/overwatch: -------------------------------------------------------------------------------- 1 | LLEFT = q 2 | LRIGHT = d 3 | LUP = z 4 | LDOWN = s 5 | RIGHT = h 6 | UP = SCROLLUP 7 | DOWN = c 8 | ZR = e 9 | ZL = SHIFT 10 | R = LMOUSE 11 | L = RMOUSE 12 | N = a 13 | W = r f 14 | E = none gyro_off 15 | S = SPACE 16 | 17 | #N = X_Y 18 | #S = X_A 19 | #W = X_X 20 | #E = X_B 21 | -------------------------------------------------------------------------------- /src/backend/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{config::settings::Settings, mapping::Buttons, opts::Run}; 2 | 3 | #[cfg(feature = "sdl2")] 4 | pub mod sdl; 5 | 6 | #[cfg(feature = "hidapi")] 7 | pub mod hidapi; 8 | 9 | pub trait Backend { 10 | fn list_devices(&mut self) -> anyhow::Result<()>; 11 | fn run(&mut self, opts: Run, settings: Settings, bindings: Buttons) -> anyhow::Result<()>; 12 | } 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | - Better background handling on Windows 10 | 11 | ## [0.1.0] - 2021-08-29 12 | 13 | First public version ! 14 | 15 | Most simple features from JoyShockMapper are supported. See 16 | `https://github.com/Yamakaky/gyromouse/blob/v0.1.0/src/config/all-settings-example` 17 | for a list of all supported settings. 18 | 19 | [0.1.0]: https://github.com/Yamakaky/gyromouse/releases/tag/v0.1.0 20 | -------------------------------------------------------------------------------- /mappings/Desktop.txt: -------------------------------------------------------------------------------- 1 | # Windows interaction using gyro 2 | # Clear previous settings 3 | RESET_MAPPINGS 4 | 5 | # Calibration 6 | REAL_WORLD_CALIBRATION = 5.3333 7 | IN_GAME_SENS = 1 8 | COUNTER_OS_MOUSE_SPEED 9 | 10 | # DPAD is arrows 11 | LEFT = LEFT 12 | RIGHT = RIGHT 13 | UP = UP 14 | DOWN = DOWN 15 | 16 | # Mouse Buttons and wheel 17 | R = FMOUSE 18 | L = BMOUSE 19 | ZR = RMOUSE RMOUSE 20 | ZL = LMOUSE LMOUSE 21 | 22 | L3 = MMOUSE 23 | LEFT_STICK_MODE = AIM 24 | LLEFT = SCROLLUP 25 | LRIGHT = SCROLLDOWN 26 | SCROLL_SENS = 60 27 | 28 | # Button pad is common buttons 29 | E = ENTER 30 | E,E = ESC 31 | W = !LMOUSE 32 | N = SPACE 33 | N,N = BACKSPACE 34 | S = GYRO_OFF 35 | 36 | + = LALT\ !TAB\ # Task view 37 | - = CONTROL\ LWINDOWS\ O\ # On Screen Keyboard 38 | HOME = LWINDOWS # Start Menu 39 | #HOME,HOME = LWINDOWS\ D\ # Minimize All 40 | 41 | # Right stick drives the cursor 42 | MIN_GYRO_SENS = 8 43 | MAX_GYRO_SENS = 16 44 | GYRO_CUTOFF_RECOVERY = 5 45 | MIN_GYRO_THRESHOLD = 5 46 | MAX_GYRO_THRESHOLD = 75 47 | GYRO_SMOOTH_THRESHOLD = 5 48 | 49 | # In case you want to use the right stick instead 50 | RIGHT_STICK_MODE = AIM 51 | STICK_SENS = 360 52 | STICK_POWER = 1 53 | STICK_ACCELERATION_RATE = 2 54 | STICK_ACCELERATION_CAP = 4 55 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gyromouse" 3 | version = "0.1.0" 4 | authors = ["Mikaël Fourrier "] 5 | edition = "2018" 6 | description = "Input mapper for gamepads in game and desktop" 7 | repository = "https://github.com/Yamakaky/gyromouse" 8 | resolver = "2" 9 | 10 | [features] 11 | default = ["sdl2", "vgamepad"] 12 | hidapi = ["hid-gamepad", "joycon"] 13 | sdl2-static = ["sdl2", "sdl2/static-link", "sdl2/use-vcpkg"] 14 | vgamepad = ["virtual-gamepad"] 15 | 16 | [profile.release] 17 | lto = true 18 | 19 | [dependencies] 20 | anyhow = { version = "1.0", features = ["backtrace"] } 21 | cgmath = "0.18" 22 | clap = { version = "4.0", features = ["cargo", "derive"] } 23 | enigo = { version = "0.2", features = ["wayland"] } 24 | enum-map = "2.0" 25 | hid-gamepad-types = { git = "https://github.com/Yamakaky/joy" } 26 | hid-gamepad = { git = "https://github.com/Yamakaky/joy", optional = true } 27 | joycon = { git = "https://github.com/Yamakaky/joy", optional = true } 28 | nom = "7.0.0" 29 | nom-supreme = "0.8" 30 | human-panic = "2.0" 31 | virtual-gamepad = { git = "https://github.com/Yamakaky/virtual-gamepad", optional = true } 32 | sdl2 = { version = "0.36", features = ["hidapi"], optional = true } 33 | env_logger = "0.11.3" 34 | 35 | [package.metadata.vcpkg] 36 | git = "https://github.com/microsoft/vcpkg" 37 | # Latest master 38 | rev = "cbf4a6641528cee6f172328984576f51698de726" 39 | dependencies = ["sdl2"] 40 | 41 | [package.metadata.vcpkg.target] 42 | x86_64-pc-windows-msvc = { triplet = "x64-windows-static-md" } 43 | -------------------------------------------------------------------------------- /src/opts.rs: -------------------------------------------------------------------------------- 1 | use std::{path::PathBuf, str::FromStr}; 2 | 3 | use clap::{Parser, ValueEnum}; 4 | 5 | /// Input mapper from gamepad keypress and movement to mouse and keyboard. 6 | /// 7 | /// See for more 8 | /// information about features and configuration file format. 9 | #[derive(Debug, Parser)] 10 | #[command(author, version, about)] 11 | pub struct Opts { 12 | /// Force the use of a specific backend for gamepad access. 13 | #[arg(short, long)] 14 | pub backend: Option, 15 | #[command(subcommand)] 16 | pub cmd: Option, 17 | } 18 | 19 | #[derive(Debug, Copy, Clone, ValueEnum)] 20 | pub enum Backend { 21 | #[cfg(feature = "sdl2")] 22 | Sdl, 23 | #[cfg(feature = "hidapi")] 24 | Hid, 25 | } 26 | 27 | #[derive(Debug, Parser)] 28 | pub enum Cmd { 29 | /// Validate the syntax of a configuration file. 30 | Validate(Run), 31 | /// Compute the value of REAL_WORLD_CALIBRATION. 32 | #[command(hide = true)] 33 | FlickCalibrate, 34 | /// Run the program using the specified configuration file. 35 | Run(Run), 36 | /// List connected gamepads. 37 | List, 38 | } 39 | 40 | #[derive(Debug, Parser)] 41 | pub struct Run { 42 | /// Configuration file to use. 43 | pub mapping_file: PathBuf, 44 | } 45 | 46 | impl FromStr for Backend { 47 | type Err = String; 48 | 49 | fn from_str(s: &str) -> Result { 50 | match s { 51 | #[cfg(feature = "sdl2")] 52 | "sdl" => Ok(Backend::Sdl), 53 | #[cfg(feature = "hidapi")] 54 | "hid" => Ok(Backend::Hid), 55 | _ => Err(format!("unknown backend: {}", s)), 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/motion_stick.rs: -------------------------------------------------------------------------------- 1 | use cgmath::{vec2, ElementWise, InnerSpace, Rad}; 2 | 3 | use crate::{ 4 | config::settings::Settings, 5 | joystick::{Stick, StickSide}, 6 | }; 7 | 8 | pub struct MotionStick { 9 | stick: Box, 10 | } 11 | 12 | impl MotionStick { 13 | pub fn new(settings: &Settings) -> Self { 14 | Self { 15 | stick: settings.new_motion_stick(), 16 | } 17 | } 18 | } 19 | 20 | impl MotionStick { 21 | pub fn handle( 22 | &mut self, 23 | up_vector: cgmath::Vector3, 24 | settings: &Settings, 25 | bindings: &mut crate::mapping::Buttons, 26 | mouse: &mut crate::mouse::Mouse, 27 | now: std::time::Instant, 28 | dt: std::time::Duration, 29 | ) { 30 | let up_vector = up_vector.normalize(); 31 | let mut stick = vec2(-up_vector.x.asin(), up_vector.z.asin()) 32 | .mul_element_wise(settings.stick.motion.axis.cast().expect("cannot fail")); 33 | 34 | //let deadzone = Rad::from(settings.stick.motion.deadzone).0; 35 | let deadzone = 0.; 36 | let fullzone = Rad::from(settings.stick.motion.fullzone).0; 37 | let amp = stick.magnitude(); 38 | let amp_zones = (amp - deadzone) / (fullzone - deadzone); 39 | let amp_clamped = amp_zones.max(0.).min(1.); 40 | if amp_clamped > 0. { 41 | stick = stick.normalize_to(amp_clamped); 42 | } 43 | 44 | // TODO: Fix motion stick deadzone usage 45 | // `stick.handle` will apply its own deadzone setting to our calibrated input, 46 | // thinking it's a raw value. 47 | self.stick 48 | .handle(stick, StickSide::Motion, settings, bindings, mouse, now, dt) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/mouse.rs: -------------------------------------------------------------------------------- 1 | use std::ops::AddAssign; 2 | 3 | use cgmath::{vec2, Deg, Vector2, Zero}; 4 | use enigo::{Coordinate, Enigo, Mouse as _}; 5 | 6 | use crate::config::settings::MouseSettings; 7 | 8 | #[derive(Debug, Clone, Copy)] 9 | pub struct MouseMovement { 10 | /// Horizontal axis, + to the right 11 | x: Deg, 12 | /// Vertical axis, + to the top 13 | y: Deg, 14 | } 15 | 16 | impl MouseMovement { 17 | pub fn new(x: Deg, y: Deg) -> Self { 18 | Self { x, y } 19 | } 20 | pub fn zero() -> Self { 21 | Self::new(Deg(0.), Deg(0.)) 22 | } 23 | /// Convert a Vector2 with degree movement values 24 | pub fn from_vec_deg(vec: Vector2) -> Self { 25 | Self { 26 | x: Deg(vec.x), 27 | y: Deg(vec.y), 28 | } 29 | } 30 | } 31 | 32 | impl AddAssign for MouseMovement { 33 | fn add_assign(&mut self, rhs: Self) { 34 | self.x += rhs.x; 35 | self.y += rhs.y; 36 | } 37 | } 38 | 39 | #[derive(Debug)] 40 | pub struct Mouse { 41 | enigo: Enigo, 42 | error_accumulator: Vector2, 43 | } 44 | 45 | impl Mouse { 46 | pub fn new() -> anyhow::Result { 47 | Ok(Mouse { 48 | enigo: Enigo::new(&enigo::Settings::default())?, 49 | error_accumulator: Vector2::zero(), 50 | }) 51 | } 52 | 53 | // mouse movement is pixel perfect, so we keep track of the error. 54 | pub fn mouse_move_relative(&mut self, settings: &MouseSettings, offset: MouseMovement) { 55 | let offset_pixel = 56 | vec2(offset.x.0, -offset.y.0) * settings.real_world_calibration * settings.in_game_sens; 57 | self.mouse_move_relative_pixel(offset_pixel); 58 | } 59 | 60 | pub fn mouse_move_relative_pixel(&mut self, offset: Vector2) { 61 | let sum = offset + self.error_accumulator; 62 | let rounded = vec2(sum.x.round(), sum.y.round()); 63 | self.error_accumulator = sum - rounded; 64 | if let Some(rounded) = rounded.cast::() { 65 | if rounded != Vector2::zero() { 66 | // In enigo, +y is toward the bottom 67 | self.enigo 68 | .move_mouse(rounded.x, rounded.y, Coordinate::Rel) 69 | .unwrap(); 70 | } 71 | } 72 | } 73 | 74 | pub fn mouse_move_absolute_pixel(&mut self, offset: Vector2) { 75 | self.enigo 76 | .move_mouse(offset.x, offset.y, Coordinate::Abs) 77 | .unwrap(); 78 | } 79 | 80 | pub fn enigo(&mut self) -> &mut Enigo { 81 | &mut self.enigo 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gyromouse 2 | 3 | A crossplatform mapper from gamepad inputs to keyboard and mouse actions, with special care for good gyro controls. Useful to play games and control a computer from the couch. 4 | 5 | ## Main features 6 | 7 | - [X] Translation of gamepad buttons, sticks, 3D movement and more into keyboard and mouse actions 8 | - [X] Control the mouse in 3D and 2D apps by physically moving the controller ([The gyro is a mouse](http://gyrowiki.jibbsmart.com/blog:the-gyro-revolution)). Only on compatible hardware including PS4/5, Switch and Steam controllers. 9 | - [X] Advanced input mapping including tap, hold, double click, layers and more 10 | - [X] Multiple stick modes including Flick Stick, mouse ring, scroll wheel and more 11 | - [X] Crossplatform support of Windows, Linux and macOS. 12 | - [ ] Planned: configuration in a GUI in addition of the text files 13 | - [ ] Planned: 3D render of the controller with its orientation in a window 14 | 15 | ## Quickstart 16 | 17 | 1. Download the latest release at https://github.com/Yamakaky/gyromouse/releases ; 18 | 2. Create a `default.txt` file in the same directory as `gyromouse`. Possible content is described below. You can start with one of the examples at https://github.com/Yamakaky/gyromouse/tree/master/mappings ; 19 | 3. Run `gyromouse`, either by double click (Windows) or in a terminal (Linux, macOS). This will run the input mapper using the configuration in `default.txt`. 20 | 21 | ### Windows 22 | 23 | No special setup are needed. When launching, you may need to [allow the app in Defender Smartscreen](https://www.addictivetips.com/windows-tips/whitelist-apps-in-the-smartscreen-on-windows-10) or in your antivirus. 24 | 25 | ### Linux 26 | 27 | `gyromouse` needs access rights to the controller device in `/dev` to access every features, the gyroscope in particular. If you don't see `Starting calibration, don't move the controller...` in the console when pluging in the controller, try doing one of the following: 28 | 29 | 1. Install udev rules from steam, for example `usr/lib/udev/rules.d/70-steam-input.rules` from the [Steam package in Archlinux](https://archlinux.org/packages/multilib/x86_64/steam/download), if a similar file is not installed by your distro; 30 | 2. Put your user in the `input` group and reboot (`usermod -a -G input `); 31 | 3. Give yourself access to the raw hid devices `chmod 666 /dev/hidraw*` (temporary, lasts until next reboot)); 32 | 4. Run `gyromouse` as root using `sudo` (last resort, not recommended). 33 | 34 | ### macOS 35 | 36 | TODO 37 | 38 | ## Configuration 39 | 40 | `gyromouse` uses the same configuration format as [JoyShockMapper](https://github.com/Electronicks/JoyShockMapper#commands). See [here](https://github.com/Yamakaky/gyromouse/blob/master/src/config/all-settings-example) for every command parsed by `gyromouse`, and for missing or partial features. Some of them are unimplemented and show a warning when used. Implemented features: 41 | - [X] Digital inputs 42 | - [X] Most controller inputs 43 | - [X] Most simple keys (letters, enter, space...) 44 | - [X] Tap, hold, simultaneous, double and chorded press 45 | - [X] Modifiers 46 | - [ ] Advanced triggers 47 | - [X] Sticks 48 | - [X] AIM, FLICK, FLICK_ONLY, ROTATE_ONLY, NO_MOUSE 49 | - [X] MOUSE_RING, MOUSE_AREA, SCROLL_WHEEL 50 | - [ ] Ring only for NO_MOUSE 51 | - [X] Gyro 52 | - [X] Most settings 53 | - [X] Local, world and player space 54 | - [ ] Basic sensor fusion only 55 | - [ ] Calibration on connection only 56 | -------------------------------------------------------------------------------- /src/config/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | mapping::{Action, Buttons, Layer}, 3 | ClickType, 4 | }; 5 | 6 | use self::{parse::Error, settings::Settings, types::*}; 7 | 8 | mod parse; 9 | pub mod settings; 10 | pub mod types; 11 | 12 | pub fn parse_file<'a>( 13 | source: &'a str, 14 | settings: &mut Settings, 15 | mapping: &mut Buttons, 16 | ) -> Vec>> { 17 | let (cmds, errors) = parse::jsm_parse(source); 18 | for cmd in cmds { 19 | match cmd { 20 | Cmd::Map(Key::Simple(key), ref actions) => map_key(mapping.get(key, 0), actions), 21 | // Double click 22 | Cmd::Map(Key::Chorded(k1, k2), ref actions) if k1 == k2 => { 23 | // TODO: Correctly handle modifiers for double click 24 | for action in actions { 25 | assert_eq!( 26 | action.event_mod, None, 27 | "event modificators not supported on double click" 28 | ); 29 | push( 30 | &mut mapping.get(k1, 0).on_double_click, 31 | action, 32 | ClickType::Click, 33 | ); 34 | } 35 | } 36 | Cmd::Map(Key::Chorded(k1, k2), ref actions) => { 37 | mapping 38 | .get(k1, 0) 39 | .on_down 40 | .push(Action::Layer(k1.to_layer(), true)); 41 | mapping 42 | .get(k1, 0) 43 | .on_up 44 | .push(Action::Layer(k1.to_layer(), false)); 45 | map_key(mapping.get(k2, k1.to_layer()), actions); 46 | } 47 | Cmd::Map(Key::Simul(_k1, _k2), ref _actions) => { 48 | // TODO: Support simultaneous key presses 49 | eprintln!("Warning: simultaneous keys are unsupported for now"); 50 | } 51 | Cmd::Setting(setting) => settings.apply(setting), 52 | Cmd::Reset => { 53 | settings.reset(); 54 | mapping.reset() 55 | } 56 | Cmd::Special(s) => { 57 | // TODO: Support special key presses 58 | eprintln!("Warning: special key {:?} is unsupported for now", s); 59 | } 60 | } 61 | } 62 | errors 63 | } 64 | 65 | fn convert_action_mod(action: &JSMAction, default: ClickType) -> Option { 66 | if let ActionType::Special(s) = action.action { 67 | if s == SpecialKey::None { 68 | return None; 69 | } 70 | } 71 | let action_type = match action.action_mod { 72 | None => default, 73 | Some(ActionModifier::Toggle) => ClickType::Toggle, 74 | Some(ActionModifier::Instant) => ClickType::Click, 75 | }; 76 | Some(Action::Ext((action.action, action_type).into())) 77 | } 78 | 79 | fn map_key(layer: &mut Layer, actions: &[JSMAction]) { 80 | use EventModifier::*; 81 | 82 | let mut first = true; 83 | for action in actions { 84 | match action.event_mod.unwrap_or_else(|| { 85 | if first { 86 | if actions.len() == 1 { 87 | Start 88 | } else { 89 | Tap 90 | } 91 | } else { 92 | Hold 93 | } 94 | }) { 95 | Tap => { 96 | push(&mut layer.on_click, action, ClickType::Click); 97 | } 98 | Hold => { 99 | push(&mut layer.on_hold_down, action, ClickType::Press); 100 | if action.action_mod.is_none() { 101 | push(&mut layer.on_hold_up, action, ClickType::Release); 102 | } 103 | } 104 | Start => { 105 | push(&mut layer.on_down, action, ClickType::Press); 106 | if action.action_mod.is_none() { 107 | push(&mut layer.on_up, action, ClickType::Release); 108 | } 109 | } 110 | Release => { 111 | if action.action_mod.is_none() { 112 | eprintln!("action modifier required on release event type"); 113 | } else { 114 | push(&mut layer.on_up, action, ClickType::Click); 115 | } 116 | } 117 | Turbo => { 118 | // TODO: Implement turbo keys 119 | eprintln!("Warning: Turbo event modifier is unsupported for now."); 120 | } 121 | } 122 | first = false; 123 | } 124 | } 125 | 126 | fn push(actions: &mut Vec, action: &JSMAction, default: ClickType) { 127 | if let Some(action) = convert_action_mod(action, default) { 128 | actions.push(action); 129 | } 130 | } 131 | 132 | #[cfg(test)] 133 | mod test { 134 | use crate::config::parse::jsm_parse; 135 | 136 | #[test] 137 | fn parse_all_settings() { 138 | let settings_str = include_str!("all-settings-example"); 139 | let (_, errors) = jsm_parse(settings_str); 140 | dbg!(&errors); 141 | assert!(errors.is_empty()); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@master 11 | 12 | - name: ⚙️ Create issues from TODO comments 13 | uses: ribtoks/tdg-github-action@master 14 | with: 15 | TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | REPO: ${{ github.repository }} 17 | SHA: ${{ github.sha }} 18 | REF: ${{ github.ref }} 19 | 20 | - name: ⚙️ Install Rust 21 | uses: actions-rs/toolchain@v1 22 | with: 23 | toolchain: stable 24 | override: true 25 | profile: minimal 26 | components: rustfmt 27 | 28 | - name: ⚙️ Check formatting 29 | run: cargo fmt --all -- --check 30 | 31 | build: 32 | name: Build for ${{ matrix.os }} 33 | runs-on: ${{ matrix.os }} 34 | strategy: 35 | fail-fast: false 36 | matrix: 37 | platform: [linux, windows, macos] 38 | include: 39 | - platform: linux 40 | os: ubuntu-latest 41 | artifact_name: gyromouse 42 | asset_name: gyromouse-linux 43 | release_name: gyromouse-linux.7z 44 | 45 | - platform: windows 46 | os: windows-latest 47 | artifact_name: gyromouse.exe 48 | asset_name: gyromouse-windows 49 | release_name: gyromouse-windows.7z 50 | 51 | - platform: macos 52 | os: macos-latest 53 | artifact_name: gyromouse 54 | asset_name: gyromouse-macos 55 | release_name: gyromouse-macos.7z 56 | 57 | steps: 58 | - uses: actions/checkout@v2 59 | with: 60 | fetch-depth: 0 61 | - uses: actions-rs/toolchain@v1 62 | with: 63 | toolchain: stable 64 | - name: ⚙️ Install clippy 65 | run: rustup component add clippy 66 | - uses: Swatinem/rust-cache@v1 67 | with: 68 | key: v5 69 | 70 | - name: ⚙️ Install cargo-vcpkg 71 | run: cargo vcpkg --version || cargo install cargo-vcpkg 72 | # Ubuntu build deps for SDL 73 | # https://hg.libsdl.org/SDL/file/default/docs/README-linux.md 74 | - name: ⚙️ Install OS dependencies 75 | if: matrix.platform == 'linux' 76 | run: | 77 | sudo apt-get update 78 | sudo apt-get install build-essential git make cmake autoconf automake \ 79 | libtool pkg-config libasound2-dev libpulse-dev libaudio-dev libjack-dev \ 80 | libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxi-dev \ 81 | libxinerama-dev libxxf86vm-dev libxss-dev libgl1-mesa-dev libdbus-1-dev \ 82 | libudev-dev libgles2-mesa-dev libegl1-mesa-dev libibus-1.0-dev \ 83 | fcitx-libs-dev libsamplerate0-dev libsndio-dev libwayland-dev \ 84 | libxkbcommon-dev libdrm-dev libgbm-dev \ 85 | \ 86 | libusb-1.0-0-dev libxdo-dev libbluetooth-dev libudev-dev libxtst-dev 87 | 88 | - name: 🔨 Build SDL 89 | uses: actions-rs/cargo@v1 90 | with: 91 | command: vcpkg 92 | args: --verbose build 93 | #- name: dbg sdl 94 | # if: matrix.platform == 'linux' 95 | # run: | 96 | # cp -f target/vcpkg/installed/x64-linux/*/lib/libSDL2d.a target/vcpkg/installed/x64-linux/lib/libSDL2.a 97 | # cp -f target/vcpkg/installed/x64-linux/*/lib/libSDL2maind.a target/vcpkg/installed/x64-linux/lib/libSDL2main.a 98 | # continue-on-error: true 99 | 100 | - name: 🔨 Build gyromouse 101 | uses: actions-rs/cargo@v1 102 | with: 103 | command: build 104 | args: --verbose --release --features sdl2-static 105 | 106 | - name: ⚙️ Run tests 107 | uses: actions-rs/cargo@v1 108 | with: 109 | command: test 110 | args: --verbose --release --features sdl2-static 111 | 112 | - name: ⚙️ Clippy 113 | uses: actions-rs/clippy-check@v1 114 | with: 115 | token: ${{ secrets.GITHUB_TOKEN }} 116 | 117 | - name: ☁️ Upload gyromouse 118 | uses: actions/upload-artifact@v2 119 | with: 120 | name: ${{ matrix.asset_name }} 121 | path: target/release/${{ matrix.artifact_name }} 122 | 123 | - name: ⚙️ Prepare Release archives 124 | shell: bash 125 | run: | 126 | mkdir gyromouse 127 | cp -r target/release/${{ matrix.artifact_name }} mappings README.md CHANGELOG.md gyromouse/ 128 | 7z a ${{ matrix.release_name }} gyromouse 129 | 130 | - name: ☁️ Create Release (tag) 131 | if: startsWith(github.ref, 'refs/tags/') 132 | uses: softprops/action-gh-release@v1 133 | with: 134 | draft: true 135 | discussion_category_name: Release 136 | files: ${{ matrix.release_name }} 137 | 138 | - name: ☁️ Create Release (nightly) 139 | if: github.ref == 'refs/heads/master' && matrix.platform == 'linux' 140 | uses: eine/tip@master 141 | with: 142 | tag: nightly 143 | token: ${{ secrets.GITHUB_TOKEN }} 144 | files: ${{ matrix.release_name }} 145 | -------------------------------------------------------------------------------- /src/backend/hidapi.rs: -------------------------------------------------------------------------------- 1 | use std::time::{Duration, Instant}; 2 | 3 | use crate::{ 4 | calibration::BetterCalibration, config::settings::Settings, engine::Engine, mapping::Buttons, 5 | mouse::Mouse, opts::Run, 6 | }; 7 | 8 | use anyhow::{bail, Result}; 9 | use enum_map::EnumMap; 10 | use hid_gamepad::sys::GamepadDevice; 11 | use hid_gamepad_types::{JoyKey, KeyStatus}; 12 | use joycon::{ 13 | hidapi::HidApi, 14 | joycon_sys::{ 15 | input::BatteryLevel, 16 | light::{self, PlayerLight}, 17 | }, 18 | JoyCon, 19 | }; 20 | 21 | use super::Backend; 22 | 23 | pub struct HidapiBackend { 24 | api: HidApi, 25 | } 26 | 27 | impl HidapiBackend { 28 | pub fn new() -> Result { 29 | Ok(Self { 30 | api: HidApi::new()?, 31 | }) 32 | } 33 | } 34 | 35 | impl Backend for HidapiBackend { 36 | fn list_devices(&mut self) -> Result<()> { 37 | println!("Listing gamepads:"); 38 | for device_info in self.api.device_list() { 39 | if hid_gamepad::open_gamepad(&self.api, device_info)?.is_some() { 40 | println!("Found one"); 41 | return Ok(()); 42 | } 43 | } 44 | bail!("No gamepad found"); 45 | } 46 | 47 | fn run(&mut self, _opts: Run, settings: Settings, bindings: Buttons) -> Result<()> { 48 | loop { 49 | for device_info in self.api.device_list() { 50 | if let Some(mut gamepad) = hid_gamepad::open_gamepad(&self.api, device_info)? { 51 | return hid_main(gamepad.as_mut(), settings, bindings); 52 | } 53 | } 54 | std::thread::sleep(std::time::Duration::from_secs(1)); 55 | self.api.refresh_devices()?; 56 | } 57 | } 58 | } 59 | 60 | fn hid_main(gamepad: &mut dyn GamepadDevice, settings: Settings, bindings: Buttons) -> Result<()> { 61 | if let Some(joycon) = gamepad.as_any().downcast_mut::() { 62 | dbg!(joycon.set_home_light(light::HomeLight::new( 63 | 0x8, 64 | 0x2, 65 | 0x0, 66 | &[(0xf, 0xf, 0), (0x2, 0xf, 0)], 67 | ))?); 68 | 69 | let battery_level = joycon.tick()?.info.battery_level(); 70 | 71 | joycon.set_player_light(light::PlayerLights::new( 72 | (battery_level >= BatteryLevel::Full).into(), 73 | (battery_level >= BatteryLevel::Medium).into(), 74 | (battery_level >= BatteryLevel::Low).into(), 75 | if battery_level >= BatteryLevel::Low { 76 | PlayerLight::On 77 | } else { 78 | PlayerLight::Blinking 79 | }, 80 | ))?; 81 | } 82 | 83 | let mut calibrator = BetterCalibration::default(); 84 | 85 | println!("calibrating"); 86 | loop { 87 | let report = gamepad.recv()?; 88 | if calibrator.push(report.motion[0], Instant::now(), Duration::from_secs(1)) { 89 | break; 90 | } 91 | } 92 | println!("calibrating done"); 93 | let mut engine = Engine::new(settings, bindings, calibrator.finish(), Mouse::new()); 94 | 95 | let mut last_keys = EnumMap::default(); 96 | loop { 97 | let report = gamepad.recv()?; 98 | let now = Instant::now(); 99 | 100 | diff(engine.buttons(), now, &last_keys, &report.keys); 101 | last_keys = report.keys; 102 | 103 | engine.handle_left_stick(report.left_joystick, now); 104 | engine.handle_right_stick(report.right_joystick, now); 105 | 106 | engine.apply_actions(now); 107 | 108 | let dt = Duration::from_secs_f64(1. / report.frequency as f64 * report.motion.len() as f64); 109 | engine.handle_motion_frame(&report.motion, dt); 110 | } 111 | } 112 | 113 | macro_rules! diff { 114 | ($mapping:ident, $now:ident, $old:expr, $new:expr, $key:ident) => { 115 | match ($old[$key], $new[$key]) { 116 | (KeyStatus::Released, KeyStatus::Pressed) => $mapping.key_down($key, $now), 117 | (KeyStatus::Pressed, KeyStatus::Released) => $mapping.key_up($key, $now), 118 | _ => (), 119 | } 120 | }; 121 | } 122 | 123 | fn diff( 124 | mapping: &mut Buttons, 125 | now: Instant, 126 | old: &EnumMap, 127 | new: &EnumMap, 128 | ) { 129 | use hid_gamepad_types::JoyKey::*; 130 | 131 | diff!(mapping, now, old, new, Up); 132 | diff!(mapping, now, old, new, Down); 133 | diff!(mapping, now, old, new, Left); 134 | diff!(mapping, now, old, new, Right); 135 | diff!(mapping, now, old, new, L); 136 | diff!(mapping, now, old, new, ZL); 137 | diff!(mapping, now, old, new, SL); 138 | diff!(mapping, now, old, new, SR); 139 | diff!(mapping, now, old, new, L3); 140 | diff!(mapping, now, old, new, R3); 141 | diff!(mapping, now, old, new, Minus); 142 | diff!(mapping, now, old, new, Plus); 143 | diff!(mapping, now, old, new, Capture); 144 | diff!(mapping, now, old, new, Home); 145 | diff!(mapping, now, old, new, W); 146 | diff!(mapping, now, old, new, N); 147 | diff!(mapping, now, old, new, S); 148 | diff!(mapping, now, old, new, E); 149 | diff!(mapping, now, old, new, R); 150 | diff!(mapping, now, old, new, ZR); 151 | diff!(mapping, now, old, new, SL); 152 | diff!(mapping, now, old, new, SR); 153 | } 154 | -------------------------------------------------------------------------------- /src/calibration.rs: -------------------------------------------------------------------------------- 1 | use cgmath::{num_traits::zero, MetricSpace, Vector3}; 2 | use hid_gamepad_types::{Acceleration, Motion, RotationSpeed}; 3 | use std::{ 4 | collections::VecDeque, 5 | time::{Duration, Instant}, 6 | }; 7 | 8 | #[derive(Debug, Clone, Copy)] 9 | pub struct Calibration { 10 | gyro: Vector3, 11 | } 12 | 13 | impl Calibration { 14 | pub fn empty() -> Self { 15 | Self { gyro: zero() } 16 | } 17 | 18 | pub fn calibrate(&self, mut motion: Motion) -> Motion { 19 | motion.rotation_speed = (motion.rotation_speed.as_vec() - self.gyro).into(); 20 | motion 21 | } 22 | } 23 | 24 | type Entry = Vector3; 25 | 26 | #[derive(Clone, Debug)] 27 | pub struct SimpleCalibration { 28 | history: VecDeque, 29 | capacity: usize, 30 | } 31 | 32 | impl SimpleCalibration { 33 | pub fn with_capacity(capacity: usize) -> SimpleCalibration { 34 | SimpleCalibration { 35 | history: VecDeque::with_capacity(capacity), 36 | capacity, 37 | } 38 | } 39 | 40 | #[allow(dead_code)] 41 | pub fn push(&mut self, entry: Entry) { 42 | if self.history.len() == self.capacity { 43 | self.history.pop_back(); 44 | } 45 | self.history.push_front(entry); 46 | } 47 | 48 | #[allow(dead_code)] 49 | pub fn reset(&mut self) { 50 | self.history.clear(); 51 | } 52 | 53 | #[allow(dead_code)] 54 | pub fn get_average(&mut self) -> Entry { 55 | let zero = Vector3::new(0., 0., 0.); 56 | let len = self.history.len() as f64; 57 | if len == 0. { 58 | return zero; 59 | } 60 | self.history 61 | .iter() 62 | .cloned() 63 | .fold(zero, |acc, val| acc + val) 64 | / len 65 | } 66 | } 67 | 68 | impl Default for SimpleCalibration { 69 | fn default() -> Self { 70 | SimpleCalibration::with_capacity(3 * 250_usize) 71 | } 72 | } 73 | 74 | #[derive(Debug, Clone, Copy)] 75 | pub struct BetterCalibration { 76 | last_sum: Vector3, 77 | last_count: u64, 78 | total_nb_samples: u64, 79 | last: Motion, 80 | state: BetterCalibrationState, 81 | } 82 | 83 | #[derive(Debug, Clone, Copy)] 84 | enum BetterCalibrationState { 85 | Moving, 86 | Static { 87 | sum: Vector3, 88 | count: u64, 89 | start: Instant, 90 | }, 91 | } 92 | 93 | impl BetterCalibration { 94 | pub fn push(&mut self, motion: Motion, now: Instant, limit: Duration) -> bool { 95 | let rot_dist = self 96 | .last 97 | .rotation_speed 98 | .as_vec() 99 | .distance(motion.rotation_speed.as_vec()); 100 | let acc_dist = self 101 | .last 102 | .acceleration 103 | .as_vec() 104 | .distance(motion.acceleration.as_vec()); 105 | let is_static = rot_dist < 1. && acc_dist < 0.01; 106 | let mut finished = false; 107 | if is_static { 108 | match self.state { 109 | BetterCalibrationState::Moving => { 110 | self.state = BetterCalibrationState::Static { 111 | sum: zero(), 112 | count: 0, 113 | start: now, 114 | } 115 | } 116 | BetterCalibrationState::Static { 117 | ref mut sum, 118 | ref mut count, 119 | start, 120 | } => { 121 | *sum += motion.rotation_speed.as_vec(); 122 | *count += 1; 123 | if now.duration_since(start) >= limit { 124 | finished = true; 125 | } 126 | } 127 | } 128 | } else if let BetterCalibrationState::Static { sum, count, .. } = self.state { 129 | if count > self.last_count { 130 | self.last_sum = sum; 131 | self.last_count = count; 132 | } 133 | self.state = BetterCalibrationState::Moving; 134 | } 135 | self.last = motion; 136 | self.total_nb_samples += 1; 137 | finished 138 | } 139 | 140 | pub fn finish(mut self) -> Calibration { 141 | if let BetterCalibrationState::Static { sum, count, .. } = self.state { 142 | if self.last_count < count { 143 | self.last_count = count; 144 | self.last_sum = sum; 145 | } 146 | } 147 | Calibration { 148 | gyro: self.last_sum / self.last_count as f64, 149 | } 150 | } 151 | } 152 | 153 | impl Default for BetterCalibration { 154 | fn default() -> Self { 155 | Self { 156 | last_sum: zero(), 157 | last_count: 0, 158 | last: Motion { 159 | rotation_speed: RotationSpeed { 160 | x: 0., 161 | y: 0., 162 | z: 0., 163 | }, 164 | acceleration: Acceleration { 165 | x: 0., 166 | y: 0., 167 | z: 0., 168 | }, 169 | }, 170 | total_nb_samples: 0, 171 | state: BetterCalibrationState::Moving, 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/config/types.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use cgmath::{ 4 | num_traits::{NumCast, ToPrimitive}, 5 | Deg, 6 | }; 7 | 8 | use crate::{ 9 | mapping::{ExtAction, MapKey}, 10 | ClickType, 11 | }; 12 | 13 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 14 | pub enum ActionModifier { 15 | Toggle, 16 | Instant, 17 | } 18 | 19 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 20 | pub enum EventModifier { 21 | Tap, 22 | Hold, 23 | Start, 24 | Release, 25 | Turbo, 26 | } 27 | 28 | #[derive(Debug, Copy, Clone)] 29 | pub struct JSMAction { 30 | pub action_mod: Option, 31 | pub event_mod: Option, 32 | pub action: ActionType, 33 | } 34 | 35 | #[derive(Debug, Copy, Clone)] 36 | pub enum ActionType { 37 | Key(enigo::Key), 38 | Mouse(enigo::Button), 39 | Special(SpecialKey), 40 | #[cfg(feature = "vgamepad")] 41 | Gamepad(virtual_gamepad::Key), 42 | } 43 | 44 | impl From<(ActionType, ClickType)> for ExtAction { 45 | fn from((a, b): (ActionType, ClickType)) -> Self { 46 | match a { 47 | ActionType::Key(k) => ExtAction::KeyPress(k, b), 48 | ActionType::Mouse(k) => ExtAction::MousePress(k, b), 49 | ActionType::Special(SpecialKey::GyroOn) => ExtAction::GyroOn(b), 50 | ActionType::Special(SpecialKey::GyroOff) => ExtAction::GyroOff(b), 51 | ActionType::Special(s) => { 52 | // TODO: Handle every special key. 53 | eprintln!("Warning: special key {:?} is unimplemented", s); 54 | ExtAction::None 55 | } 56 | #[cfg(feature = "vgamepad")] 57 | ActionType::Gamepad(k) => ExtAction::GamepadKeyPress(k, b), 58 | } 59 | } 60 | } 61 | 62 | #[derive(Debug, Clone)] 63 | pub enum Key { 64 | Simple(MapKey), 65 | Simul(MapKey, MapKey), 66 | Chorded(MapKey, MapKey), 67 | } 68 | 69 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 70 | pub enum SpecialKey { 71 | None, 72 | GyroOn, 73 | GyroOff, 74 | GyroInvertX(bool), 75 | GyroInvertY(bool), 76 | GyroTrackBall(bool), 77 | } 78 | 79 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 80 | pub enum TriggerMode { 81 | NoFull, 82 | NoSkip, 83 | NoSkipExclusive, 84 | MustSkip, 85 | MaySkip, 86 | MustSkipR, 87 | MaySkipR, 88 | } 89 | 90 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 91 | pub enum StickMode { 92 | Aim, 93 | Flick, 94 | FlickOnly, 95 | RotateOnly, 96 | MouseRing, 97 | MouseArea, 98 | NoMouse, 99 | ScrollWheel, 100 | } 101 | 102 | #[derive(Debug, Copy, Clone)] 103 | pub enum StickSetting { 104 | Deadzone(f64), 105 | FullZone(f64), 106 | Aim(AimStickSetting), 107 | Flick(FlickStickSetting), 108 | Scroll(ScrollStickSetting), 109 | Area(AreaStickSetting), 110 | Motion(MotionStickSetting), 111 | } 112 | 113 | #[derive(Debug, Copy, Clone)] 114 | pub enum AimStickSetting { 115 | Sens(f64), 116 | Power(f64), 117 | LeftAxis(InvertMode, Option), 118 | RightAxis(InvertMode, Option), 119 | AccelerationRate(f64), 120 | AccelerationCap(f64), 121 | } 122 | 123 | #[derive(Debug, Copy, Clone)] 124 | pub enum FlickStickSetting { 125 | FlickTime(Duration), 126 | Exponent(f64), 127 | ForwardDeadzoneArc(Deg), 128 | } 129 | 130 | #[derive(Debug, Copy, Clone)] 131 | pub enum ScrollStickSetting { 132 | Sens(Deg), 133 | } 134 | 135 | #[derive(Debug, Copy, Clone)] 136 | pub enum AreaStickSetting { 137 | ScreenResolutionX(u32), 138 | ScreenResolutionY(u32), 139 | Radius(u32), 140 | } 141 | 142 | #[derive(Debug, Copy, Clone)] 143 | pub enum MotionStickSetting { 144 | StickMode(StickMode), 145 | RingMode(RingMode), 146 | Deadzone(Deg), 147 | Fullzone(Deg), 148 | Axis(InvertMode, Option), 149 | } 150 | 151 | #[derive(Debug, Copy, Clone)] 152 | pub enum GyroSetting { 153 | Sensitivity(f64, Option), 154 | MinSens(f64, Option), 155 | MinThreshold(f64), 156 | MaxSens(f64, Option), 157 | MaxThreshold(f64), 158 | Space(GyroSpace), 159 | InvertX(InvertMode), 160 | InvertY(InvertMode), 161 | CutoffSpeed(f64), 162 | CutoffRecovery(f64), 163 | SmoothThreshold(f64), 164 | SmoothTime(Duration), 165 | } 166 | 167 | #[derive(Debug, Copy, Clone)] 168 | pub enum MouseSetting { 169 | CounterOSSpeed(bool), 170 | RealWorldCalibration(f64), 171 | InGameSens(f64), 172 | } 173 | 174 | #[derive(Debug, Copy, Clone)] 175 | pub enum GyroSpace { 176 | Local, 177 | WorldTurn, 178 | WorldLean, 179 | PlayerTurn, 180 | PlayerLean, 181 | } 182 | 183 | #[derive(Debug, Copy, Clone)] 184 | pub enum Setting { 185 | Gyro(GyroSetting), 186 | TriggerThreshold(f64), 187 | ZLMode(TriggerMode), 188 | ZRMode(TriggerMode), 189 | LeftStickMode(StickMode), 190 | RightStickMode(StickMode), 191 | LeftRingMode(RingMode), 192 | RightRingMode(RingMode), 193 | Stick(StickSetting), 194 | Mouse(MouseSetting), 195 | } 196 | 197 | #[derive(Debug, Clone)] 198 | pub enum Cmd { 199 | Map(Key, Vec), 200 | Special(SpecialKey), 201 | Setting(Setting), 202 | Reset, 203 | } 204 | 205 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 206 | pub enum RingMode { 207 | Inner, 208 | Outer, 209 | } 210 | 211 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 212 | pub enum InvertMode { 213 | Normal, 214 | Inverted, 215 | } 216 | 217 | impl ToPrimitive for InvertMode { 218 | fn to_i64(&self) -> std::option::Option { 219 | Some(match self { 220 | InvertMode::Normal => 1, 221 | InvertMode::Inverted => -1, 222 | }) 223 | } 224 | 225 | fn to_u64(&self) -> Option { 226 | None 227 | } 228 | } 229 | 230 | impl NumCast for InvertMode { 231 | fn from(_: T) -> Option { 232 | None 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/gyromouse.rs: -------------------------------------------------------------------------------- 1 | use cgmath::{vec2, ElementWise, InnerSpace, Vector2, Zero}; 2 | use std::{collections::VecDeque, time::Duration}; 3 | 4 | use crate::{config::settings::GyroSettings, mouse::MouseMovement}; 5 | 6 | #[derive(Debug, Default)] 7 | pub struct GyroMouse { 8 | smooth_buffer: VecDeque>, 9 | } 10 | 11 | impl GyroMouse { 12 | // #/// Nothing applied. 13 | // pub fn blank() -> GyroMouse { 14 | // GyroMouse { 15 | // apply_smoothing: false, 16 | // smooth_threshold: 5., 17 | // smooth_buffer: VecDeque::new(), 18 | // 19 | // apply_tightening: false, 20 | // tightening_threshold: 5., 21 | // 22 | // apply_acceleration: false, 23 | // acceleration_slow_sens: 8., 24 | // acceleration_slow_threshold: 5., 25 | // acceleration_fast_sens: 16., 26 | // acceleration_fast_threshold: 75., 27 | // 28 | // sensitivity: 1., 29 | // } 30 | // } 31 | // /// Good default values for a 2D mouse. 32 | // pub fn d2() -> GyroMouse { 33 | // GyroMouse { 34 | // apply_smoothing: true, 35 | // smooth_threshold: 5., 36 | // smooth_buffer: [Vector2::zero(); 25].iter().cloned().collect(), 37 | // 38 | // apply_tightening: true, 39 | // tightening_threshold: 5., 40 | // 41 | // apply_acceleration: true, 42 | // acceleration_slow_sens: 16., 43 | // acceleration_slow_threshold: 5., 44 | // acceleration_fast_sens: 32., 45 | // acceleration_fast_threshold: 75., 46 | // 47 | // sensitivity: 32., 48 | // } 49 | // } 50 | // 51 | // /// Good default values for a 3D mouse. 52 | // pub fn d3() -> GyroMouse { 53 | // GyroMouse { 54 | // apply_smoothing: false, 55 | // smooth_threshold: 0., 56 | // smooth_buffer: VecDeque::new(), 57 | // 58 | // apply_tightening: false, 59 | // tightening_threshold: 0., 60 | // 61 | // apply_acceleration: true, 62 | // acceleration_slow_sens: 1., 63 | // acceleration_slow_threshold: 0., 64 | // acceleration_fast_sens: 2., 65 | // acceleration_fast_threshold: 75., 66 | // 67 | // sensitivity: 1., 68 | // } 69 | // } 70 | // 71 | 72 | /// Process a new gyro sample. 73 | /// 74 | /// Parameter is pitch + yaw. 75 | /// 76 | /// Updates `self.orientation` and returns the applied change. 77 | /// 78 | /// `orientation` and return value have origin in bottom left. 79 | pub fn process( 80 | &mut self, 81 | settings: &GyroSettings, 82 | mut rot: Vector2, 83 | dt: Duration, 84 | ) -> MouseMovement { 85 | if settings.smooth_threshold > 0. { 86 | rot = self.tiered_smooth(settings, rot, dt); 87 | } 88 | if settings.cutoff_recovery > 0. { 89 | #[allow(clippy::float_cmp)] 90 | { 91 | assert_eq!(settings.cutoff_speed, 0.); 92 | } 93 | rot = self.tight(settings, rot); 94 | } 95 | let sens = self.get_sens(settings, rot); 96 | let sign = self.get_sign(settings); 97 | MouseMovement::from_vec_deg( 98 | rot.mul_element_wise(sens).mul_element_wise(sign) * dt.as_secs_f64(), 99 | ) 100 | } 101 | 102 | fn tiered_smooth( 103 | &mut self, 104 | settings: &GyroSettings, 105 | rot: Vector2, 106 | dt: Duration, 107 | ) -> Vector2 { 108 | let thresh_high = settings.smooth_threshold; 109 | let thresh_low = thresh_high / 2.; 110 | let magnitude = (rot.x.powf(2.) + rot.y.powf(2.)).sqrt(); 111 | let weight = ((magnitude - thresh_low) / (thresh_high - thresh_low)) 112 | .max(0.) 113 | .min(1.); 114 | let smoothed = self.smooth(settings, rot * (1. - weight), dt); 115 | rot * weight + smoothed 116 | } 117 | 118 | fn smooth(&mut self, settings: &GyroSettings, rot: Vector2, dt: Duration) -> Vector2 { 119 | self.smooth_buffer.push_front(rot); 120 | while dt * self.smooth_buffer.len() as u32 > settings.smooth_time { 121 | self.smooth_buffer.pop_back(); 122 | } 123 | let sum = self 124 | .smooth_buffer 125 | .iter() 126 | .fold(Vector2::zero(), |acc, x| acc + x); 127 | sum / self.smooth_buffer.len() as f64 128 | } 129 | 130 | fn tight(&mut self, settings: &GyroSettings, rot: Vector2) -> Vector2 { 131 | let magnitude = (rot.x.powf(2.) + rot.y.powf(2.)).sqrt(); 132 | if magnitude < settings.cutoff_recovery { 133 | let scale = magnitude / settings.cutoff_recovery; 134 | rot * scale 135 | } else { 136 | rot 137 | } 138 | } 139 | 140 | fn get_sens(&self, settings: &GyroSettings, rot: Vector2) -> Vector2 { 141 | if settings.slow_sens.magnitude2() > 0. && settings.slow_sens.magnitude2() > 0. { 142 | let magnitude = (rot.x.powf(2.) + rot.y.powf(2.)).sqrt(); 143 | let factor = ((magnitude - settings.slow_threshold) 144 | / (settings.fast_threshold - settings.slow_threshold)) 145 | .max(0.) 146 | .min(1.); 147 | settings.slow_sens * (1. - factor) + settings.fast_sens * factor 148 | } else { 149 | settings.sens 150 | } 151 | } 152 | 153 | fn get_sign(&self, settings: &GyroSettings) -> Vector2 { 154 | let x = if settings.invert.0 { -1. } else { 1. }; 155 | let y = if settings.invert.1 { -1. } else { 1. }; 156 | vec2(x, y) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(test, allow(dead_code, unreachable_code, unused_variables))] 2 | 3 | mod backend; 4 | mod calibration; 5 | mod config; 6 | mod engine; 7 | mod gyromouse; 8 | mod joystick; 9 | mod mapping; 10 | mod motion_stick; 11 | mod mouse; 12 | mod opts; 13 | mod space_mapper; 14 | 15 | use std::{fs::File, io::Read}; 16 | 17 | use anyhow::{bail, Context}; 18 | use backend::Backend; 19 | use clap::Parser as _; 20 | use nom_supreme::{ 21 | error::{BaseErrorKind, ErrorTree}, 22 | final_parser::{ExtractContext, Location}, 23 | }; 24 | use opts::Opts; 25 | 26 | use crate::{config::settings::Settings, mapping::Buttons, opts::Run}; 27 | 28 | #[derive(Debug, Copy, Clone)] 29 | pub enum ClickType { 30 | Press, 31 | Release, 32 | Click, 33 | Toggle, 34 | } 35 | 36 | fn main() { 37 | env_logger::init(); 38 | 39 | // https://github.com/rust-cli/human-panic/issues/77 40 | human_panic::setup_panic!(human_panic::Metadata::new( 41 | env!("CARGO_PKG_NAME"), 42 | env!("CARGO_PKG_VERSION") 43 | ) 44 | .authors(env!("CARGO_PKG_AUTHORS").replace(":", ", ")) 45 | .homepage(env!("CARGO_PKG_REPOSITORY"))); 46 | 47 | if let Err(e) = do_main() { 48 | eprintln!("Error: {:?}", e); 49 | } 50 | 51 | // Keep cmd.exe opened 52 | #[cfg(windows)] 53 | let _ = std::io::stdin() 54 | .read(&mut [0u8]) 55 | .expect("can't wait for end of program"); 56 | } 57 | 58 | fn do_main() -> anyhow::Result<()> { 59 | let opts = Opts::parse(); 60 | 61 | #[allow(unreachable_patterns)] 62 | let mut backend: Box = match opts.backend { 63 | #[cfg(feature = "sdl2")] 64 | Some(opts::Backend::Sdl) | None => Box::new(backend::sdl::SDLBackend::new()?), 65 | #[cfg(feature = "hidapi")] 66 | Some(opts::Backend::Hid) | None => Box::new(backend::hidapi::HidapiBackend::new()?), 67 | Some(_) | None => { 68 | bail!("A backend must be enabled"); 69 | } 70 | }; 71 | 72 | let mut settings = Settings::default(); 73 | let mut bindings = Buttons::new(); 74 | 75 | match opts.cmd { 76 | Some(opts::Cmd::Validate(v)) => { 77 | // TODO: factor this code with run 78 | let mut content_file = File::open(&v.mapping_file) 79 | .with_context(|| format!("opening config file {:?}", v.mapping_file))?; 80 | let content = { 81 | let mut buf = String::new(); 82 | content_file.read_to_string(&mut buf)?; 83 | buf 84 | }; 85 | let errors = config::parse_file(&content, &mut settings, &mut bindings); 86 | print_errors(errors, &content); 87 | Ok(()) 88 | } 89 | Some(opts::Cmd::FlickCalibrate) => todo!(), 90 | Some(opts::Cmd::Run(r)) => run(r, backend, settings, bindings), 91 | Some(opts::Cmd::List) => backend.list_devices(), 92 | None => { 93 | let default = { 94 | let mut path = std::env::current_exe()?; 95 | File::open(&path)?; 96 | path.pop(); 97 | path.push("default.txt"); 98 | path 99 | }; 100 | println!("Using default config file {:?}.", default); 101 | run( 102 | Run { 103 | mapping_file: default, 104 | }, 105 | backend, 106 | settings, 107 | bindings, 108 | ) 109 | } 110 | } 111 | } 112 | fn run( 113 | r: Run, 114 | mut backend: Box, 115 | mut settings: Settings, 116 | mut bindings: Buttons, 117 | ) -> anyhow::Result<()> { 118 | let mut content_file = File::open(&r.mapping_file) 119 | .with_context(|| format!("opening config file {:?}", r.mapping_file))?; 120 | let content = { 121 | let mut buf = String::new(); 122 | content_file.read_to_string(&mut buf)?; 123 | buf 124 | }; 125 | let errors = config::parse_file(&content, &mut settings, &mut bindings); 126 | print_errors(errors, &content); 127 | backend.run(r, settings, bindings) 128 | } 129 | 130 | fn print_errors(errors: Vec>>, content: &str) { 131 | for error in errors { 132 | match error { 133 | nom::Err::Incomplete(_) => todo!(), 134 | nom::Err::Error(_) => todo!(), 135 | nom::Err::Failure(e) => { 136 | let location: ErrorTree = e.extract_context(content); 137 | eprintln!("Parsing error:"); 138 | print_parse_error( 139 | content, 140 | &location.map_locations(|l| { 141 | let line = content.lines().nth(l.line - 1).expect("should not fail"); 142 | format!("line {} column {} (\"{}\")", l.line, l.column, line) 143 | }), 144 | ); 145 | } 146 | } 147 | } 148 | } 149 | 150 | fn print_parse_error(input: &str, e: &ErrorTree) { 151 | match e { 152 | ErrorTree::Base { location, kind } => { 153 | eprint!(" at {}: ", location); 154 | match kind { 155 | BaseErrorKind::Kind(nom::error::ErrorKind::Float) => println!("expected a number"), 156 | k => println!("{}", k), 157 | }; 158 | } 159 | ErrorTree::Stack { base, contexts: _ } => { 160 | //eprintln!("{:?}", contexts); 161 | print_parse_error(input, base); 162 | } 163 | ErrorTree::Alt(alts) => { 164 | let mut last_loc = None; 165 | for alt in alts { 166 | match alt { 167 | ErrorTree::Base { 168 | location, 169 | kind: BaseErrorKind::Expected(exp), 170 | } => { 171 | match last_loc.map(|l: &String| l == location) { 172 | None => eprint!(" at {}: expected {}", location, exp), 173 | Some(false) => eprint!("\n at {}: expected {}", location, exp), 174 | Some(true) => eprint!(" or {}", exp), 175 | } 176 | last_loc = Some(location); 177 | } 178 | _ => { 179 | println!(); 180 | print_parse_error(input, alt); 181 | last_loc = None; 182 | } 183 | } 184 | } 185 | println!(); 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/config/all-settings-example: -------------------------------------------------------------------------------- 1 | ### This file must parse, see testing in mod.rs 2 | ### Keep the random formating for spacE and case insensitive parsing 3 | 4 | # comment 5 | W = a # comment 6 | 7 | ## Digital inputs 8 | 9 | ### lhs 10 | 11 | UP = a 12 | DOWN = a 13 | LEFT = a 14 | RIGHT = a 15 | L = a 16 | ZL = a 17 | R = a 18 | ZR = a 19 | #ZRF = a 20 | #ZLF = a 21 | - = a 22 | + = a 23 | HOME = a 24 | CAPTURE = a 25 | #LSL = a 26 | #RSL = a 27 | #LSR = a 28 | #RSR = a 29 | L3 = a 30 | R3 = a 31 | N = a 32 | E = a 33 | S = a 34 | W = a 35 | LUP = a 36 | LDOWN = a 37 | LLEFT = a 38 | LRIGHT = a 39 | LRING = a 40 | RUP = a 41 | RDOWN = a 42 | RLEFT = a 43 | RRIGHT = a 44 | RRING = a 45 | MUP = a 46 | MDOWN = a 47 | MLEFT = a 48 | MRIGHT = a 49 | MRING = a 50 | #LEAN_LEFT = a 51 | #LEAN_RIGHT = a 52 | #TOUCH = a 53 | #MIC = a 54 | 55 | ### rhs 56 | 57 | W = 3 58 | #W = N7 59 | #W = ADD SUBTRACT DIVIDE MULTIPLY DECIMAL 60 | W = F7 61 | #W = F27 62 | W = I 63 | W = Z 64 | W = UP DOWN LEFT RIGHT 65 | #W = LSHIFT RSHIFT LALT RALT LCONTROL RCONTROL 66 | W = SHIFT ALT CONTROL 67 | W = LWINDOWS RWINDOWS 68 | #W = CONTEXT 69 | W = TAB ESC ENTER SPACE BACKSPACE 70 | #W =CAPS_LOCK SCROLL_LOCK NUM_LOCK 71 | #W = PAGEUP PAGEDOWN HOME END INSERT DELETE 72 | W = LMOUSE MMOUSE RMOUSE 73 | W = BMOUSE FMOUSE 74 | #W = SCROLLUP SCROLLDOWN 75 | #W = VOLUME_UP VOLUME_DOWN MUTE 76 | #W = NEXT_TRACK PREV_TRACK STOP_TRACK PLAY_PAUSE 77 | #W = SCREENSHOT 78 | W = NONE 79 | #W = DEFAULT 80 | #W = CALIBRATE 81 | W = GYRO_ON GYRO_OFF 82 | #W = GYRO_INVERT 83 | W = GYRO_INV_X 84 | W = GYRO_INV_Y 85 | W = GYRO_TRACKBALL 86 | #W = GYRO_TRACK_X 87 | #W = GYRO_TRACK_Y 88 | #W = ; ' , . / \ [ ] + - ` 89 | #W = "any console command" 90 | #W = SMALL_RUMBLE 91 | #W = BIG_RUMBLE 92 | #W = R123F 93 | 94 | ### Combination 95 | 96 | W,E = a 97 | W,W = a 98 | W+E = a 99 | 100 | ### Modifiers 101 | 102 | ZL = ^RMOUSE\ RMOUSE_ # ADS toggle on tap and release the toggle on hold 103 | E = !C\ !C/ # Convert in game toggle crouch to regular press 104 | UP = !1\ 1 # Convert Batarang throw double press to hold press 105 | W = R E\ # In Halo MCC, reload on tap but apply E right away to cover for in-game hold processing 106 | -,S = SPACE+ # Turbo press for button mash QTEs. No one likes to button mash :( 107 | R3 = !1\ LMOUSE+ !Q/ # Half life melee button 108 | UP,UP = !ENTER\ SHIFT\ !G\ !L\ !SPACE\ !H\ !F\ !ENTER/ # Pre recorded message 109 | UP,E = BACKSPACE+ 110 | 111 | ## Triggers 112 | 113 | TRIGGER_threSHOLD = 0.5 114 | #ADAPTIVE_triGGER = OFF 115 | #LEFT_TRIGGER_OFFSET = 20 116 | #LEFT_TRIGGER_RANGE = 167 117 | #RIGHT_TRIGGER_OFFSET = 31 118 | #RIGHT_TRIGGER_RANGE = 175 119 | 120 | 121 | Zr_MODE = NO_full 122 | Zr_MODE = NO_skip 123 | Zr_MODE = NO_skip_EXCLUSIVE 124 | Zr_MODE = MUst_skIP 125 | Zl_MODE = MAy_skiP 126 | Zl_MODE = MUst_skIP_R 127 | Zl_MODE = MAy_skiP_R 128 | ZL_MODE = NO_full 129 | 130 | ## Stick 131 | 132 | Left_Stick_MODE = aiM 133 | Left_Stick_MODE = flICK 134 | Left_Stick_MODE = flICK_ONLY 135 | Left_Stick_MODE = roTATE_ONLY 136 | rIGHT_Stick_MODE = moUSE_RING 137 | rIGHT_Stick_MODE = moUSE_AREA 138 | rIGHT_Stick_MODE = no_MOUSE 139 | rIGHT_Stick_MODE = scROLL_WHEEL 140 | 141 | LEFT_RING_MODE = OUTer 142 | right_RING_MODE = iNNER 143 | # compat in jsm, not needed 144 | #LEFT_RING_MODE = OUTER_ring 145 | #right_RING_MODE = inner_ring 146 | 147 | #CONTROLLER_ORIENTATION = FORWARD 148 | #CONTROLLER_ORIENTATION = LEFT 149 | #CONTROLLER_ORIENTATION = RIGHT 150 | #CONTROLLER_ORIENTATION = BACKWARD 151 | #CONTROLLER_ORIENTATION = JOYCON_SIDEWAYS 152 | 153 | STICK_SENS = 1. 154 | STICK_POWER = 2 155 | LEFT_STICK_AXIS = standard 156 | RIGHT_STICK_AXIS = INVERTed standard 157 | STICK_ACCELERATION_RATE =1.0 158 | STICK_ACCELERATION_CAP =10000. 159 | STICK_DEADZONE_INNER = 0.4 160 | STICK_DEADZONE_OUTER = 1. 161 | #LEFT_STICK_DEADZONE_INNER = 1. 162 | #LEFT_STICK_DEADZONE_OUTER = 1. 163 | #RIGHT_STICK_DEADZONE_INNER = 1. 164 | #RIGHT_STICK_DEADZONE_OUTER = 1. 165 | 166 | FLICK_TIME = 1. 167 | FLICK_TIME_EXPONENT = 1. 168 | #FLICK_SNAP_MODE = noNE 169 | #FLICK_SNAP_MODE = 4 170 | #FLICK_SNAP_MODE = 8 171 | #FLICK_SNAP_STRENGTH = 1. 172 | FLICK_DEADZONE_ANGLE = 01 173 | 174 | SCREEN_RESOLUTION_X = 1920 175 | SCREEN_RESOLUTION_Y = 1080 176 | MOUSE_RING_RADIUS = 400 177 | 178 | SCROLL_SENS = 10 179 | 180 | MOTION_STICK_MODE = aiM 181 | motion_stick_MODE = moUSE_AREA 182 | MOTION_RING_MODE = INner 183 | MOTION_ring_MODE = outeR 184 | MOTION_DEADZONE_INNER = 10 185 | MOTION_DEADZONE_OUTER = 5 186 | MOTION_STICK_AXIS=inverted 187 | MOTION_STICK_AXIS=standard 188 | #SET_MOTION_STICK_NEUTRAL 189 | #LEAN_THRESHOLD = 10 190 | 191 | #AUTO_CALIBRATE_GYRO = on 192 | #AUTO_CALIBRATE_GYRO = off 193 | #RESTART_gyro_Calibration 194 | #finish_gyro_Calibration 195 | 196 | ## Gyro 197 | 198 | GYRO_SENS = 1.1103 199 | GYRO_SENS = 1.1103 2. 200 | MIN_gyro_THRESHOLD = 2 201 | MAX_gyro_THRESHOLD = 1.10 202 | MIN_gyro_SENS = 1. 203 | MIN_gyro_SENS = 1. 1. 204 | MAX_gyro_SENS = 1.10 205 | MAX_gyro_SENS = 1.10 1. 206 | GYRO_SPACE = player_turn 207 | GYRO_SPACE = player_lean 208 | GYRO_SPACE = WORLD_turn 209 | GYRO_SPACE = WORLD_lean 210 | GYRO_SPACE = local 211 | GYRO_AXIS_X = STANDARD 212 | GYRO_AXIS_Y = inverted standard 213 | GYRO_CUTOFF_SPEED = 1. 214 | GYRO_CUTOFF_RECOVERY = 1. 215 | GYRO_SMOOTH_THRESHOLD = 0. 216 | GYRO_SMOOTH_TIME = 1. 217 | 218 | ## Calib 219 | 220 | REAL_WORLD_CALIBRATION = 1. 221 | IN_GAME_SENS = 1. 222 | COUNTER_OS_MOUSE_speed 223 | IGNORE_OS_MOUSE_SPEED 224 | #CALCULATE_REAL_WORLD_CALIBRATION 225 | #CALCULATE_REAL_WORLD_CALIBRATION 10 226 | 227 | ## Gamepad 228 | 229 | #VIRTUAL_CONTROLLER = XBOX 230 | #VIRTUAL_CONTROLLER = DS4 231 | #VIRTUAL_CONTROLLER =NONE 232 | 233 | #LEFT_STICK_MODE = LEFT_STICK 234 | #RIGHT_STICK_MODE = RIGHT_STICK 235 | #ZL_MODE = X_LT 236 | #ZR_MODE = X_RT 237 | #ZL_MODE = PS_L2 238 | #ZR_MODE = PS_R2 239 | 240 | ## Modeshift 241 | 242 | #R,GYRO_ON = NONE\ # Disable gyro when R is down 243 | #R,RIGHT_STICK_MODE = MOUSE_AREA # Select wheel item with stick 244 | #ZLF,GYRO_SENS = 0.5 0.4 # Half sensitivity on full pull 245 | 246 | ## Touchpad 247 | 248 | #TOUCHPAD_MODE = grip_and_STICK 249 | #TOUCHPAD_MODE = Mouse 250 | #touchpad_sens = 1. 251 | #grip_SIZE = 1 2 252 | 253 | #TOUCH_STICK_MODE = aim 254 | #TOUCH_DEADZONE_INNER = 1. 255 | #TOUCH_RING_MODE = inner 256 | #TOUCH_STICK_RADIUS = 1. 257 | #TOUCH_STICK_AXIS = invert 258 | 259 | ## Misc 260 | 261 | #RESET_MAPPINGS 262 | #RECONNECT_CONTROLLERS 263 | #JOYCON_GYRO_MASK = IGNORE_LEFT 264 | #JOYCON_GYRO_MASK = IGNORE_right 265 | #JOYCON_MOTION_MASK = ignore_both 266 | #JOYCON_MOTION_MASK = use_both 267 | #SLEEP 268 | #SLEEP 2 269 | #TICK_TIME = 3 270 | # todo: Add common color name for LIGHT_BAR 271 | # https://www.rapidtables.com/web/color/RGB_Color.html#color-table 272 | #LIGHT_BAR = x1234af 273 | #LIGHT_BAR = 128 2 183 274 | #HIDE_MINIMIZED 275 | #README 276 | #HELP 277 | #CLEAR 278 | -------------------------------------------------------------------------------- /src/space_mapper.rs: -------------------------------------------------------------------------------- 1 | use std::{f64::consts::PI, time::Duration}; 2 | 3 | use cgmath::{vec2, vec3, InnerSpace, Quaternion, Rotation, Vector2, Vector3, VectorSpace, Zero}; 4 | use hid_gamepad_types::{Motion, RotationSpeed}; 5 | 6 | pub fn map_input( 7 | motion: &Motion, 8 | dt: Duration, 9 | sensor_fusion: &mut dyn SensorFusion, 10 | mapper: &mut dyn SpaceMapper, 11 | ) -> Vector2 { 12 | let up_vector = sensor_fusion.compute_up_vector(motion, dt); 13 | mapper.map(motion.rotation_speed, up_vector) 14 | } 15 | pub trait SensorFusion { 16 | fn up_vector(&self) -> Vector3; 17 | fn compute_up_vector(&mut self, motion: &Motion, dt: Duration) -> Vector3; 18 | } 19 | 20 | /// Convert local space motion to 2D mouse-like motion. 21 | pub trait SpaceMapper { 22 | fn map(&self, rot_speed: RotationSpeed, grav: Vector3) -> Vector2; 23 | } 24 | 25 | #[derive(Debug, Copy, Clone)] 26 | pub struct SimpleFusion { 27 | up_vector: Vector3, 28 | correction_factor: f64, 29 | } 30 | 31 | impl SimpleFusion { 32 | pub fn new() -> Self { 33 | Self { 34 | up_vector: vec3(0., 1., 0.), 35 | correction_factor: 0.02, 36 | } 37 | } 38 | } 39 | 40 | impl SensorFusion for SimpleFusion { 41 | fn up_vector(&self) -> Vector3 { 42 | self.up_vector 43 | } 44 | fn compute_up_vector(&mut self, motion: &Motion, dt: Duration) -> Vector3 { 45 | let rotation = Quaternion::from(motion.rotation_speed * dt).invert(); 46 | self.up_vector = rotation.rotate_vector(self.up_vector); 47 | // TODO: Make the correction rate depend on dt instead of fixed per tick. 48 | self.up_vector += (motion.acceleration.as_vec() - self.up_vector) * self.correction_factor; 49 | self.up_vector 50 | } 51 | } 52 | 53 | #[derive(Debug, Copy, Clone)] 54 | pub struct AdaptativeFusion { 55 | shakiness: f64, 56 | smooth_accel: Vector3, 57 | up_vector: Vector3, 58 | } 59 | 60 | impl AdaptativeFusion { 61 | #[allow(dead_code)] 62 | pub fn new() -> Self { 63 | Self { 64 | shakiness: 0., 65 | // Way off when starting but should converge rapidly 66 | smooth_accel: Vector3::zero(), 67 | up_vector: Vector3::zero(), 68 | } 69 | } 70 | } 71 | 72 | impl SensorFusion for AdaptativeFusion { 73 | fn up_vector(&self) -> Vector3 { 74 | self.up_vector 75 | } 76 | 77 | // TODO: check http://gyrowiki.jibbsmart.com/blog:finding-gravity-with-sensor-fusion 78 | // TODO: check normalize() with magnitude 0. 79 | fn compute_up_vector(&mut self, motion: &Motion, dt: Duration) -> Vector3 { 80 | // settings 81 | let smoothing_half_time = 0.25; 82 | let shakiness_min_threshold = 0.4; 83 | let shakiness_max_threshold = 0.01; 84 | let still_rate = 1.; 85 | let shaky_rate = 0.1; 86 | let correction_gyro_factor = 0.1; 87 | let correction_gyro_min_threshold = 0.05; 88 | let correction_gyro_max_threshold = 0.25; 89 | let correction_min_speed = 0.01; 90 | 91 | let rot = motion.rotation_speed * dt; 92 | let rot_vec = vec3(rot.x.0, rot.y.0, rot.z.0); 93 | let acc = motion.acceleration; 94 | 95 | let invert_rotation = Quaternion::from(motion.rotation_speed * dt).invert(); 96 | 97 | self.up_vector = invert_rotation.rotate_vector(self.up_vector); 98 | self.smooth_accel = invert_rotation.rotate_vector(self.smooth_accel); 99 | let smooth_interpolator = if smoothing_half_time <= 0. { 100 | 0. 101 | } else { 102 | -dt.as_secs_f64() / smoothing_half_time 103 | }; 104 | self.shakiness = (self.shakiness * smooth_interpolator) 105 | .max((acc.as_vec() - self.smooth_accel).magnitude()); 106 | self.smooth_accel = acc.as_vec().lerp(self.smooth_accel, smooth_interpolator); 107 | 108 | let up_delta = acc.as_vec() - self.up_vector; 109 | let up_direction = up_delta.normalize(); 110 | let shake_factor = normalize( 111 | self.shakiness, 112 | shakiness_min_threshold, 113 | shakiness_max_threshold, 114 | ); 115 | let mut correction_rate = still_rate + (shaky_rate - still_rate) * shake_factor; 116 | 117 | let angle_rate = rot_vec.magnitude() * PI / 180.; 118 | let correction_limit = angle_rate * self.up_vector.magnitude() * correction_gyro_factor; 119 | if correction_rate > correction_limit { 120 | let close_enough_factor = normalize( 121 | up_delta.magnitude(), 122 | correction_gyro_min_threshold, 123 | correction_gyro_max_threshold, 124 | ); 125 | correction_rate += (correction_limit - correction_rate) * close_enough_factor; 126 | } 127 | 128 | correction_rate = correction_rate.max(correction_min_speed); 129 | 130 | let correction = up_direction.normalize_to(correction_rate * dt.as_secs_f64()); 131 | self.up_vector += if correction.magnitude2() < up_delta.magnitude2() { 132 | correction 133 | } else { 134 | up_delta 135 | }; 136 | self.up_vector 137 | } 138 | } 139 | 140 | // Normalize value as (0..1) between min and max. 141 | // Handles edge cases where min >= max 142 | fn normalize(val: f64, min: f64, max: f64) -> f64 { 143 | if min >= max { 144 | (val > max) as u8 as f64 145 | } else { 146 | (val - min) / (max - min) 147 | } 148 | .clamp(0., 1.) 149 | } 150 | 151 | #[derive(Default)] 152 | pub struct LocalSpace; 153 | 154 | impl SpaceMapper for LocalSpace { 155 | fn map(&self, rot_speed: RotationSpeed, _up_vector: Vector3) -> Vector2 { 156 | vec2(-rot_speed.y, rot_speed.x) 157 | } 158 | } 159 | 160 | #[derive(Default)] 161 | pub struct WorldSpace; 162 | 163 | impl SpaceMapper for WorldSpace { 164 | fn map(&self, rot_speed: RotationSpeed, up_vector: Vector3) -> Vector2 { 165 | let flatness = up_vector.y.abs(); 166 | let upness = up_vector.z.abs(); 167 | let side_reduction = (flatness.max(upness) - 0.125).clamp(0., 1.); 168 | 169 | let yaw_diff = -rot_speed.as_vec().dot(up_vector); 170 | 171 | let pitch = vec3(1., 0., 0.) - up_vector * up_vector.x; 172 | let pitch_diff = if pitch.magnitude2() > 0. { 173 | side_reduction * rot_speed.as_vec().dot(pitch.normalize()) 174 | } else { 175 | 0. 176 | }; 177 | vec2(yaw_diff, pitch_diff) 178 | } 179 | } 180 | 181 | pub struct PlayerSpace { 182 | yaw_relax_factor: f64, 183 | } 184 | 185 | impl Default for PlayerSpace { 186 | fn default() -> Self { 187 | Self { 188 | yaw_relax_factor: 1.41, 189 | } 190 | } 191 | } 192 | 193 | impl SpaceMapper for PlayerSpace { 194 | fn map(&self, rot_speed: RotationSpeed, up_vector: Vector3) -> Vector2 { 195 | let world_yaw = rot_speed.y * up_vector.y + rot_speed.z * up_vector.z; 196 | vec2( 197 | -world_yaw.signum() 198 | * (world_yaw.abs() * self.yaw_relax_factor) 199 | .min(vec2(rot_speed.y, rot_speed.z).magnitude()), 200 | rot_speed.x, 201 | ) 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/engine.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ops::DerefMut, 3 | time::{Duration, Instant}, 4 | }; 5 | 6 | use cgmath::Vector2; 7 | use enigo::{Direction, Keyboard as _, Mouse as _}; 8 | use hid_gamepad_types::{Acceleration, Motion, RotationSpeed}; 9 | 10 | use crate::{ 11 | calibration::Calibration, 12 | config::{settings::Settings, types::GyroSpace}, 13 | gyromouse::GyroMouse, 14 | joystick::{Stick, StickSide}, 15 | mapping::{Buttons, ExtAction}, 16 | motion_stick::MotionStick, 17 | mouse::{Mouse, MouseMovement}, 18 | space_mapper::{ 19 | self, LocalSpace, PlayerSpace, SensorFusion, SimpleFusion, SpaceMapper, WorldSpace, 20 | }, 21 | ClickType, 22 | }; 23 | 24 | pub struct Engine { 25 | settings: Settings, 26 | left_stick: Box, 27 | right_stick: Box, 28 | motion_stick: MotionStick, 29 | buttons: Buttons, 30 | mouse: Mouse, 31 | gyro: Gyro, 32 | #[cfg(feature = "vgamepad")] 33 | gamepad: Option>, 34 | } 35 | 36 | impl Engine { 37 | pub fn new( 38 | settings: Settings, 39 | buttons: Buttons, 40 | calibration: Calibration, 41 | mouse: Mouse, 42 | ) -> anyhow::Result { 43 | Ok(Engine { 44 | left_stick: settings.new_left_stick(), 45 | right_stick: settings.new_right_stick(), 46 | motion_stick: MotionStick::new(&settings), 47 | buttons, 48 | mouse, 49 | gyro: Gyro::new(&settings, calibration), 50 | settings, 51 | #[cfg(feature = "vgamepad")] 52 | // TODO: Conditional virtual gamepad creation 53 | // Only create if option is enabled 54 | //gamepad: virtual_gamepad::new(virtual_gamepad::GamepadType::DS4) 55 | // .map(|vg| -> Box { Box::new(vg) }) 56 | // .map_err(|e| { 57 | // eprintln!("Error initializing the virtual gamepad: {}", e); 58 | // e 59 | // }) 60 | // .ok(), 61 | gamepad: None, 62 | }) 63 | } 64 | 65 | pub fn buttons(&mut self) -> &mut Buttons { 66 | &mut self.buttons 67 | } 68 | 69 | pub fn handle_left_stick(&mut self, stick: Vector2, now: Instant, dt: Duration) { 70 | self.left_stick.handle( 71 | stick, 72 | StickSide::Left, 73 | &self.settings, 74 | &mut self.buttons, 75 | &mut self.mouse, 76 | now, 77 | dt, 78 | ); 79 | } 80 | 81 | pub fn handle_right_stick(&mut self, stick: Vector2, now: Instant, dt: Duration) { 82 | self.right_stick.handle( 83 | stick, 84 | StickSide::Right, 85 | &self.settings, 86 | &mut self.buttons, 87 | &mut self.mouse, 88 | now, 89 | dt, 90 | ); 91 | } 92 | 93 | pub fn apply_actions(&mut self, now: Instant) -> anyhow::Result<()> { 94 | #[cfg(feature = "vgamepad")] 95 | let mut gamepad_pressed = false; 96 | for action in self.buttons.tick(now) { 97 | let verbose = false; 98 | if verbose { 99 | println!("Action: {}", action); 100 | } 101 | match action { 102 | ExtAction::GyroOn(ClickType::Press) | ExtAction::GyroOff(ClickType::Release) => { 103 | self.gyro.enabled = true; 104 | } 105 | ExtAction::GyroOn(ClickType::Release) | ExtAction::GyroOff(ClickType::Press) => { 106 | self.gyro.enabled = false; 107 | } 108 | ExtAction::GyroOn(ClickType::Toggle) | ExtAction::GyroOff(ClickType::Toggle) => { 109 | self.gyro.enabled = !self.gyro.enabled; 110 | } 111 | ExtAction::GyroOn(ClickType::Click) | ExtAction::GyroOff(ClickType::Click) => { 112 | eprintln!("Warning: event type Click has no effect on gyro on/off"); 113 | } 114 | ExtAction::KeyPress(c, ClickType::Click) => { 115 | self.mouse.enigo().key(c, Direction::Click)? 116 | } 117 | ExtAction::KeyPress(c, ClickType::Press) => { 118 | self.mouse.enigo().key(c, Direction::Press)? 119 | } 120 | ExtAction::KeyPress(c, ClickType::Release) => { 121 | self.mouse.enigo().key(c, Direction::Release)? 122 | } 123 | ExtAction::KeyPress(_, ClickType::Toggle) => { 124 | // TODO: Implement key press toggle 125 | eprintln!("Warning: key press toggle is not implemented"); 126 | } 127 | ExtAction::MousePress(c, ClickType::Click) => { 128 | self.mouse.enigo().button(c, Direction::Click)? 129 | } 130 | ExtAction::MousePress(c, ClickType::Press) => { 131 | self.mouse.enigo().button(c, Direction::Press)? 132 | } 133 | ExtAction::MousePress(c, ClickType::Release) => { 134 | self.mouse.enigo().button(c, Direction::Release)? 135 | } 136 | ExtAction::MousePress(_, ClickType::Toggle) => { 137 | // TODO: Implement mouse click toggle 138 | eprintln!("Warning: mouse click toggle is not implemented"); 139 | } 140 | #[cfg(feature = "vgamepad")] 141 | ExtAction::GamepadKeyPress(key, ClickType::Press) => { 142 | if let Some(gamepad) = &mut self.gamepad { 143 | gamepad.key(key, true)?; 144 | gamepad_pressed = true; 145 | } 146 | } 147 | #[cfg(feature = "vgamepad")] 148 | ExtAction::GamepadKeyPress(key, ClickType::Release) => { 149 | if let Some(gamepad) = &mut self.gamepad { 150 | gamepad.key(key, false)?; 151 | gamepad_pressed = true; 152 | } 153 | } 154 | #[cfg(feature = "vgamepad")] 155 | ExtAction::GamepadKeyPress(_, _) => todo!(), 156 | ExtAction::None => {} 157 | } 158 | } 159 | #[cfg(feature = "vgamepad")] 160 | if let Some(gamepad) = &mut self.gamepad { 161 | if gamepad_pressed { 162 | gamepad.push()?; 163 | } 164 | } 165 | Ok(()) 166 | } 167 | 168 | pub fn apply_motion( 169 | &mut self, 170 | rotation_speed: RotationSpeed, 171 | acceleration: Acceleration, 172 | now: Instant, 173 | dt: Duration, 174 | ) { 175 | self.handle_motion_frame( 176 | &[Motion { 177 | rotation_speed, 178 | acceleration, 179 | }], 180 | now, 181 | dt, 182 | ) 183 | } 184 | 185 | pub fn handle_motion_frame(&mut self, motions: &[Motion], now: Instant, dt: Duration) { 186 | self.gyro 187 | .handle_frame(&self.settings, motions, &mut self.mouse, dt); 188 | self.handle_motion_stick(now, dt); 189 | } 190 | 191 | fn handle_motion_stick(&mut self, now: Instant, dt: Duration) { 192 | self.motion_stick.handle( 193 | self.gyro.sensor_fusion.up_vector(), 194 | &self.settings, 195 | &mut self.buttons, 196 | &mut self.mouse, 197 | now, 198 | dt, 199 | ) 200 | } 201 | 202 | pub fn set_calibration(&mut self, calibration: Calibration) { 203 | self.gyro.calibration = calibration; 204 | } 205 | } 206 | 207 | pub struct Gyro { 208 | enabled: bool, 209 | calibration: Calibration, 210 | sensor_fusion: Box, 211 | space_mapper: Box, 212 | gyromouse: GyroMouse, 213 | } 214 | 215 | impl Gyro { 216 | pub fn new(settings: &Settings, calibration: Calibration) -> Gyro { 217 | Gyro { 218 | enabled: true, 219 | calibration, 220 | sensor_fusion: Box::new(SimpleFusion::new()), 221 | space_mapper: match settings.gyro.space { 222 | GyroSpace::Local => Box::new(LocalSpace::default()), 223 | GyroSpace::WorldTurn => Box::new(WorldSpace::default()), 224 | GyroSpace::WorldLean => todo!("World Lean is unimplemented for now"), 225 | GyroSpace::PlayerTurn => Box::new(PlayerSpace::default()), 226 | GyroSpace::PlayerLean => todo!("Player Lean is unimplemented for now"), 227 | }, 228 | gyromouse: GyroMouse::default(), 229 | } 230 | } 231 | 232 | pub fn handle_frame( 233 | &mut self, 234 | settings: &Settings, 235 | motions: &[Motion], 236 | mouse: &mut Mouse, 237 | dt: Duration, 238 | ) { 239 | const SMOOTH_RATE: bool = true; 240 | let mut delta_position = MouseMovement::zero(); 241 | let dt = dt / motions.len() as u32; 242 | for (i, frame) in motions.iter().cloned().enumerate() { 243 | let frame = self.calibration.calibrate(frame); 244 | let delta = space_mapper::map_input( 245 | &frame, 246 | dt, 247 | self.sensor_fusion.deref_mut(), 248 | self.space_mapper.deref_mut(), 249 | ); 250 | let offset = self.gyromouse.process(&settings.gyro, delta, dt); 251 | delta_position += offset; 252 | if self.enabled && !SMOOTH_RATE { 253 | if i > 0 { 254 | std::thread::sleep(dt); 255 | } 256 | mouse.mouse_move_relative(&settings.mouse, offset); 257 | } 258 | } 259 | if self.enabled && SMOOTH_RATE { 260 | mouse.mouse_move_relative(&settings.mouse, delta_position); 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /src/backend/sdl.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | thread::sleep, 4 | time::{Duration, Instant}, 5 | }; 6 | 7 | use anyhow::{bail, Result}; 8 | use cgmath::{vec2, Vector3}; 9 | use hid_gamepad_types::{Acceleration, JoyKey, Motion, RotationSpeed}; 10 | use sdl2::{ 11 | self, 12 | controller::{Axis, Button, GameController}, 13 | event::Event, 14 | keyboard::Keycode, 15 | sensor::SensorType, 16 | GameControllerSubsystem, Sdl, 17 | }; 18 | 19 | use crate::{ 20 | calibration::{BetterCalibration, Calibration}, 21 | config::settings::Settings, 22 | engine::Engine, 23 | mapping::Buttons, 24 | mouse::Mouse, 25 | }; 26 | 27 | use super::Backend; 28 | 29 | pub struct SDLBackend { 30 | sdl: Sdl, 31 | game_controller_system: GameControllerSubsystem, 32 | } 33 | 34 | impl SDLBackend { 35 | pub fn new() -> Result { 36 | sdl2::hint::set("SDL_JOYSTICK_HIDAPI_PS4_RUMBLE", "1"); 37 | sdl2::hint::set("SDL_JOYSTICK_HIDAPI_PS5_RUMBLE", "1"); 38 | sdl2::hint::set("SDL_JOYSTICK_HIDAPI_JOY_CONS", "1"); 39 | sdl2::hint::set("SDL_JOYSTICK_HIDAPI_SWITCH_HOME_LED", "0"); 40 | sdl2::hint::set("SDL_GAMECONTROLLER_USE_BUTTON_LABELS", "0"); 41 | 42 | // Better Windows support 43 | sdl2::hint::set("SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS", "1"); 44 | sdl2::hint::set("SDL_HINT_JOYSTICK_THREAD", "1"); 45 | 46 | let sdl = sdl2::init().expect("can't initialize SDL"); 47 | let game_controller_system = sdl 48 | .game_controller() 49 | .expect("can't initialize SDL game controller subsystem"); 50 | Ok(Self { 51 | sdl, 52 | game_controller_system, 53 | }) 54 | } 55 | } 56 | 57 | impl Backend for SDLBackend { 58 | fn list_devices(&mut self) -> anyhow::Result<()> { 59 | let num_joysticks = match self.game_controller_system.num_joysticks() { 60 | Ok(x) => x, 61 | Err(e) => bail!("{}", e), 62 | }; 63 | if num_joysticks == 0 { 64 | println!("No controller detected"); 65 | } else { 66 | println!("Detected controllers:"); 67 | for i in 0..num_joysticks { 68 | let controller = self.game_controller_system.open(i)?; 69 | println!(" - {}", controller.name()); 70 | } 71 | } 72 | Ok(()) 73 | } 74 | 75 | fn run( 76 | &mut self, 77 | _opts: crate::opts::Run, 78 | settings: Settings, 79 | bindings: Buttons, 80 | ) -> anyhow::Result<()> { 81 | if self 82 | .game_controller_system 83 | .num_joysticks() 84 | .expect("can't enumerate the joysticks") 85 | == 0 86 | { 87 | println!("Waiting for a game controller to connect..."); 88 | } 89 | let mut event_pump = self 90 | .sdl 91 | .event_pump() 92 | .expect("can't create the SDL event pump"); 93 | 94 | let mut controllers: HashMap = HashMap::new(); 95 | 96 | let mut last_tick = Instant::now(); 97 | 98 | 'running: loop { 99 | let now = Instant::now(); 100 | let dt = now.duration_since(last_tick); 101 | 102 | for event in event_pump.poll_iter() { 103 | match event { 104 | Event::Quit { .. } 105 | | Event::KeyDown { 106 | keycode: Some(Keycode::Escape), 107 | .. 108 | } => break 'running, 109 | Event::ControllerDeviceAdded { which, .. } => { 110 | let mut controller = self.game_controller_system.open(which)?; 111 | 112 | if controllers 113 | .values() 114 | .any(|c| c.controller.name() == controller.name()) 115 | { 116 | continue; 117 | } 118 | 119 | if controller.name() == "Steam Virtual Gamepad" { 120 | continue; 121 | } 122 | 123 | println!("New controller: {}", controller.name()); 124 | 125 | // Ignore errors, handled later 126 | let calibrator = if controller 127 | .sensor_set_enabled(SensorType::Accelerometer, true) 128 | .and(controller.sensor_set_enabled(SensorType::Gyroscope, true)) 129 | .is_ok() 130 | { 131 | println!( 132 | "Starting calibration for {}, don't move the controller...", 133 | controller.name() 134 | ); 135 | Some(BetterCalibration::default()) 136 | } else { 137 | let _ = controller.set_rumble(220, 440, 100); 138 | None 139 | }; 140 | 141 | let engine = Engine::new( 142 | settings.clone(), 143 | bindings.clone(), 144 | Calibration::empty(), 145 | Mouse::new()?, 146 | )?; 147 | controllers.insert( 148 | controller.instance_id(), 149 | ControllerState { 150 | controller, 151 | engine, 152 | calibrator, 153 | }, 154 | ); 155 | } 156 | Event::ControllerDeviceRemoved { which, .. } => { 157 | if let Some(controller) = controllers.remove(&which) { 158 | println!("Controller disconnected: {}", controller.controller.name()); 159 | } 160 | } 161 | Event::ControllerButtonDown { 162 | timestamp: _, 163 | which, 164 | button, 165 | } => { 166 | if let Some(controller) = controllers.get_mut(&which) { 167 | controller 168 | .engine 169 | .buttons() 170 | .key_down(sdl_to_sys(button), now); 171 | } 172 | } 173 | Event::ControllerButtonUp { 174 | timestamp: _, 175 | which, 176 | button, 177 | } => { 178 | if let Some(controller) = controllers.get_mut(&which) { 179 | controller.engine.buttons().key_up(sdl_to_sys(button), now); 180 | } 181 | } 182 | _ => {} 183 | } 184 | } 185 | 186 | for controller in controllers.values_mut() { 187 | let c = &mut controller.controller; 188 | let engine = &mut controller.engine; 189 | let mut left = vec2(c.axis(Axis::LeftX), c.axis(Axis::LeftY)) 190 | .cast::() 191 | .expect("can't cast i16 to f64") 192 | / (i16::MAX as f64); 193 | let mut right = vec2(c.axis(Axis::RightX), c.axis(Axis::RightY)) 194 | .cast::() 195 | .expect("can't cast i16 to f64") 196 | / (i16::MAX as f64); 197 | 198 | // In SDL, -..+ y is top..bottom 199 | left.y = -left.y; 200 | right.y = -right.y; 201 | 202 | engine.handle_left_stick(left, now, dt); 203 | engine.handle_right_stick(right, now, dt); 204 | 205 | if c.sensor_enabled(SensorType::Accelerometer) 206 | && c.sensor_enabled(SensorType::Gyroscope) 207 | { 208 | let mut accel = [0.; 3]; 209 | c.sensor_get_data(SensorType::Accelerometer, &mut accel)?; 210 | let acceleration = Acceleration::from( 211 | Vector3::from(accel) 212 | .cast::() 213 | .expect("can't cast f32 to f64") 214 | / 9.82, 215 | ); 216 | let mut gyro = [0.; 3]; 217 | c.sensor_get_data(SensorType::Gyroscope, &mut gyro)?; 218 | let rotation_speed = RotationSpeed::from( 219 | Vector3::from(gyro) 220 | .cast::() 221 | .expect("can't cast f32 to f64") 222 | / std::f64::consts::PI 223 | * 180., 224 | ); 225 | 226 | if let Some(ref mut calibrator) = controller.calibrator { 227 | let finished = calibrator.push( 228 | Motion { 229 | rotation_speed, 230 | acceleration, 231 | }, 232 | now, 233 | Duration::from_secs(2), 234 | ); 235 | if finished { 236 | println!("Calibration finished for {}", c.name()); 237 | let _ = c.set_rumble(220, 440, 100); 238 | engine.set_calibration(calibrator.finish()); 239 | controller.calibrator = None; 240 | } 241 | } else { 242 | engine.apply_motion(rotation_speed, acceleration, now, dt); 243 | } 244 | } 245 | engine.apply_actions(now)?; 246 | } 247 | 248 | last_tick = now; 249 | sleep(Duration::from_millis(1)); 250 | } 251 | 252 | Ok(()) 253 | } 254 | } 255 | 256 | struct ControllerState { 257 | controller: GameController, 258 | engine: Engine, 259 | calibrator: Option, 260 | } 261 | 262 | fn sdl_to_sys(button: Button) -> JoyKey { 263 | match button { 264 | Button::A => JoyKey::S, 265 | Button::B => JoyKey::E, 266 | Button::X => JoyKey::W, 267 | Button::Y => JoyKey::N, 268 | Button::Back => JoyKey::Minus, 269 | Button::Guide => JoyKey::Home, 270 | Button::Start => JoyKey::Plus, 271 | Button::LeftStick => JoyKey::L3, 272 | Button::RightStick => JoyKey::R3, 273 | Button::LeftShoulder => JoyKey::L, 274 | Button::RightShoulder => JoyKey::R, 275 | Button::DPadUp => JoyKey::Up, 276 | Button::DPadDown => JoyKey::Down, 277 | Button::DPadLeft => JoyKey::Left, 278 | Button::DPadRight => JoyKey::Right, 279 | Button::Misc1 => todo!(), 280 | Button::Paddle1 => todo!(), 281 | Button::Paddle2 => todo!(), 282 | Button::Paddle3 => todo!(), 283 | Button::Paddle4 => todo!(), 284 | Button::Touchpad => todo!(), 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /src/config/settings.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use cgmath::{vec2, Deg, Vector2, Zero}; 4 | 5 | use crate::joystick::*; 6 | 7 | use super::types::*; 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct Settings { 11 | pub gyro: GyroSettings, 12 | pub stick: StickSettings, 13 | pub left_stick_mode: StickMode, 14 | pub right_stick_mode: StickMode, 15 | pub left_ring_mode: RingMode, 16 | pub right_ring_mode: RingMode, 17 | pub trigger_threshold: f64, 18 | // TODO: Support alternative trigger modes 19 | pub zl_mode: TriggerMode, 20 | pub zr_mode: TriggerMode, 21 | pub mouse: MouseSettings, 22 | } 23 | 24 | impl Default for Settings { 25 | fn default() -> Self { 26 | Self { 27 | gyro: GyroSettings::default(), 28 | stick: StickSettings::default(), 29 | left_stick_mode: StickMode::NoMouse, 30 | right_stick_mode: StickMode::Aim, 31 | left_ring_mode: RingMode::Outer, 32 | right_ring_mode: RingMode::Outer, 33 | trigger_threshold: 0.5, 34 | zl_mode: TriggerMode::NoFull, 35 | zr_mode: TriggerMode::NoFull, 36 | mouse: MouseSettings::default(), 37 | } 38 | } 39 | } 40 | 41 | impl Settings { 42 | pub fn apply(&mut self, setting: Setting) { 43 | match setting { 44 | Setting::Gyro(s) => self.gyro.apply(s), 45 | Setting::Stick(s) => self.stick.apply(s), 46 | Setting::LeftStickMode(m) => self.left_stick_mode = m, 47 | Setting::RightStickMode(m) => self.right_stick_mode = m, 48 | Setting::LeftRingMode(m) => self.left_ring_mode = m, 49 | Setting::RightRingMode(m) => self.right_ring_mode = m, 50 | Setting::TriggerThreshold(t) => self.trigger_threshold = t, 51 | Setting::ZLMode(m) => self.zl_mode = m, 52 | Setting::ZRMode(m) => self.zr_mode = m, 53 | Setting::Mouse(m) => self.mouse.apply(m), 54 | } 55 | } 56 | 57 | pub fn reset(&mut self) { 58 | *self = Self::default(); 59 | } 60 | 61 | pub fn new_left_stick(&self) -> Box { 62 | self.new_stick(self.left_stick_mode) 63 | } 64 | 65 | pub fn new_right_stick(&self) -> Box { 66 | self.new_stick(self.right_stick_mode) 67 | } 68 | 69 | pub fn new_motion_stick(&self) -> Box { 70 | self.new_stick(self.stick.motion.mode) 71 | } 72 | 73 | fn new_stick(&self, mode: StickMode) -> Box { 74 | match mode { 75 | StickMode::Aim => Box::new(CameraStick::new()), 76 | StickMode::Flick | StickMode::FlickOnly | StickMode::RotateOnly => { 77 | let flick = mode != StickMode::RotateOnly; 78 | let rotate = mode != StickMode::FlickOnly; 79 | Box::new(FlickStick::new(flick, rotate)) 80 | } 81 | StickMode::MouseRing => Box::new(AreaStick::ring()), 82 | StickMode::MouseArea => Box::new(AreaStick::area()), 83 | StickMode::NoMouse => Box::new(ButtonStick::new(self.left_ring_mode)), 84 | StickMode::ScrollWheel => Box::new(ScrollStick::new()), 85 | } 86 | } 87 | } 88 | 89 | #[derive(Debug, Clone)] 90 | pub struct StickSettings { 91 | pub deadzone: f64, 92 | pub fullzone: f64, 93 | pub aim: AimStickSettings, 94 | pub flick: FlickStickSettings, 95 | pub scroll: ScrollStickSettings, 96 | pub area: AreaStickSettings, 97 | pub motion: MotionStickSettings, 98 | } 99 | 100 | impl Default for StickSettings { 101 | fn default() -> Self { 102 | Self { 103 | deadzone: 0.15, 104 | fullzone: 0.9, 105 | aim: Default::default(), 106 | flick: Default::default(), 107 | scroll: Default::default(), 108 | area: Default::default(), 109 | motion: Default::default(), 110 | } 111 | } 112 | } 113 | 114 | impl StickSettings { 115 | fn apply(&mut self, setting: StickSetting) { 116 | match setting { 117 | StickSetting::Deadzone(d) => self.deadzone = d, 118 | StickSetting::FullZone(d) => self.fullzone = d, 119 | StickSetting::Aim(s) => self.aim.apply(s), 120 | StickSetting::Flick(s) => self.flick.apply(s), 121 | StickSetting::Scroll(s) => self.scroll.apply(s), 122 | StickSetting::Area(s) => self.area.apply(s), 123 | StickSetting::Motion(s) => self.motion.apply(s), 124 | } 125 | } 126 | } 127 | 128 | #[derive(Debug, Clone)] 129 | pub struct AimStickSettings { 130 | pub sens_dps: f64, 131 | pub power: f64, 132 | pub left_axis: Vector2, 133 | pub right_axis: Vector2, 134 | pub acceleration_rate: f64, 135 | pub acceleration_cap: f64, 136 | } 137 | 138 | impl Default for AimStickSettings { 139 | fn default() -> Self { 140 | Self { 141 | sens_dps: 360., 142 | power: 1., 143 | left_axis: vec2(InvertMode::Normal, InvertMode::Normal), 144 | right_axis: vec2(InvertMode::Normal, InvertMode::Normal), 145 | acceleration_rate: 0., 146 | acceleration_cap: 1000000., 147 | } 148 | } 149 | } 150 | 151 | impl AimStickSettings { 152 | fn apply(&mut self, setting: AimStickSetting) { 153 | match setting { 154 | AimStickSetting::Sens(s) => self.sens_dps = s, 155 | AimStickSetting::Power(s) => self.power = s, 156 | AimStickSetting::LeftAxis(v1, v2) => self.left_axis = vec2(v1, v2.unwrap_or(v1)), 157 | AimStickSetting::RightAxis(v1, v2) => self.right_axis = vec2(v1, v2.unwrap_or(v1)), 158 | AimStickSetting::AccelerationRate(s) => self.acceleration_rate = s, 159 | AimStickSetting::AccelerationCap(s) => self.acceleration_cap = s, 160 | } 161 | } 162 | } 163 | 164 | #[derive(Debug, Clone)] 165 | pub struct FlickStickSettings { 166 | pub flick_time: Duration, 167 | pub exponent: f64, 168 | pub forward_deadzone_arc: Deg, 169 | } 170 | 171 | impl Default for FlickStickSettings { 172 | fn default() -> Self { 173 | Self { 174 | flick_time: Duration::from_millis(100), 175 | exponent: 0., 176 | forward_deadzone_arc: Deg(0.), 177 | } 178 | } 179 | } 180 | 181 | impl FlickStickSettings { 182 | fn apply(&mut self, setting: FlickStickSetting) { 183 | match setting { 184 | FlickStickSetting::FlickTime(s) => self.flick_time = s, 185 | FlickStickSetting::Exponent(s) => self.exponent = s, 186 | FlickStickSetting::ForwardDeadzoneArc(s) => self.forward_deadzone_arc = s, 187 | } 188 | } 189 | } 190 | 191 | #[derive(Debug, Clone)] 192 | pub struct ScrollStickSettings { 193 | pub sens: Deg, 194 | } 195 | 196 | impl Default for ScrollStickSettings { 197 | fn default() -> Self { 198 | Self { sens: Deg(10.) } 199 | } 200 | } 201 | 202 | impl ScrollStickSettings { 203 | fn apply(&mut self, setting: ScrollStickSetting) { 204 | match setting { 205 | ScrollStickSetting::Sens(s) => self.sens = s, 206 | } 207 | } 208 | } 209 | 210 | #[derive(Debug, Clone)] 211 | pub struct AreaStickSettings { 212 | pub screen_resolution: Vector2, 213 | pub screen_radius: u32, 214 | } 215 | 216 | impl Default for AreaStickSettings { 217 | fn default() -> Self { 218 | Self { 219 | screen_resolution: vec2(1920, 1080), 220 | screen_radius: 50, 221 | } 222 | } 223 | } 224 | 225 | impl AreaStickSettings { 226 | fn apply(&mut self, setting: AreaStickSetting) { 227 | match setting { 228 | AreaStickSetting::ScreenResolutionX(r) => self.screen_resolution.x = r, 229 | AreaStickSetting::ScreenResolutionY(r) => self.screen_resolution.y = r, 230 | AreaStickSetting::Radius(r) => self.screen_radius = r, 231 | } 232 | } 233 | } 234 | 235 | #[derive(Debug, Clone)] 236 | pub struct MotionStickSettings { 237 | pub mode: StickMode, 238 | pub ring_mode: RingMode, 239 | pub deadzone: Deg, 240 | pub fullzone: Deg, 241 | pub axis: Vector2, 242 | } 243 | 244 | impl Default for MotionStickSettings { 245 | fn default() -> Self { 246 | Self { 247 | mode: StickMode::NoMouse, 248 | ring_mode: RingMode::Outer, 249 | deadzone: Deg(15.), 250 | fullzone: Deg(45.), 251 | axis: vec2(InvertMode::Normal, InvertMode::Normal), 252 | } 253 | } 254 | } 255 | 256 | #[derive(Debug, Clone, Copy)] 257 | pub struct GyroSettings { 258 | /// Sensitivity to use without acceleration. 259 | /// 260 | /// 261 | pub sens: Vector2, 262 | pub invert: (bool, bool), 263 | pub space: GyroSpace, 264 | /// Stabilize slow movements 265 | /// 266 | /// 267 | pub cutoff_speed: f64, 268 | pub cutoff_recovery: f64, 269 | /// Smoothing threshold. 270 | /// 271 | /// Rotations smaller than this will be smoothed over a small period of time. 272 | pub smooth_threshold: f64, 273 | pub smooth_time: Duration, 274 | /// Enables acceleration. 275 | /// 276 | /// 277 | pub slow_threshold: f64, 278 | pub slow_sens: Vector2, 279 | pub fast_threshold: f64, 280 | pub fast_sens: Vector2, 281 | } 282 | 283 | impl Default for GyroSettings { 284 | fn default() -> Self { 285 | Self { 286 | sens: vec2(1., 1.), 287 | invert: (false, false), 288 | space: GyroSpace::PlayerTurn, 289 | cutoff_speed: 0., 290 | cutoff_recovery: 0., 291 | smooth_threshold: 0., 292 | smooth_time: Duration::from_millis(125), 293 | slow_sens: Vector2::zero(), 294 | slow_threshold: 0., 295 | fast_sens: Vector2::zero(), 296 | fast_threshold: 0., 297 | } 298 | } 299 | } 300 | 301 | impl GyroSettings { 302 | fn apply(&mut self, setting: GyroSetting) { 303 | match setting { 304 | GyroSetting::Sensitivity(x, y) => { 305 | self.sens = vec2(x, y.unwrap_or(x)); 306 | } 307 | GyroSetting::MinSens(x, y) => { 308 | self.slow_sens = vec2(x, y.unwrap_or(x)); 309 | } 310 | GyroSetting::MinThreshold(s) => self.slow_threshold = s, 311 | GyroSetting::MaxSens(x, y) => { 312 | self.fast_sens = vec2(x, y.unwrap_or(x)); 313 | } 314 | GyroSetting::MaxThreshold(s) => self.fast_threshold = s, 315 | GyroSetting::Space(s) => self.space = s, 316 | GyroSetting::InvertX(b) => self.invert.0 = b == InvertMode::Inverted, 317 | GyroSetting::InvertY(b) => self.invert.1 = b == InvertMode::Inverted, 318 | GyroSetting::CutoffSpeed(s) => self.cutoff_speed = s, 319 | GyroSetting::CutoffRecovery(s) => self.cutoff_recovery = s, 320 | GyroSetting::SmoothThreshold(s) => self.smooth_threshold = s, 321 | GyroSetting::SmoothTime(s) => self.smooth_time = s, 322 | } 323 | } 324 | } 325 | 326 | #[derive(Debug, Clone, Copy)] 327 | pub struct MouseSettings { 328 | pub counter_os_speed: bool, 329 | pub real_world_calibration: f64, 330 | pub in_game_sens: f64, 331 | } 332 | 333 | impl Default for MouseSettings { 334 | fn default() -> Self { 335 | Self { 336 | counter_os_speed: false, 337 | real_world_calibration: 1., 338 | in_game_sens: 1., 339 | } 340 | } 341 | } 342 | 343 | impl MouseSettings { 344 | fn apply(&mut self, setting: MouseSetting) { 345 | match setting { 346 | MouseSetting::CounterOSSpeed(c) => { 347 | println!("Warning: counter os speed not supported"); 348 | self.counter_os_speed = c; 349 | } 350 | MouseSetting::RealWorldCalibration(c) => self.real_world_calibration = c, 351 | MouseSetting::InGameSens(s) => self.in_game_sens = s, 352 | } 353 | } 354 | } 355 | impl MotionStickSettings { 356 | fn apply(&mut self, setting: MotionStickSetting) { 357 | match setting { 358 | MotionStickSetting::StickMode(m) => self.mode = m, 359 | MotionStickSetting::RingMode(m) => self.ring_mode = m, 360 | MotionStickSetting::Deadzone(m) => self.deadzone = m, 361 | MotionStickSetting::Fullzone(m) => self.fullzone = m, 362 | MotionStickSetting::Axis(v1, v2) => self.axis = vec2(v1, v2.unwrap_or(v1)), 363 | } 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /src/joystick.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use std::time::{Duration, Instant}; 4 | 5 | use cgmath::{vec2, AbsDiffEq, Angle, Deg, ElementWise, InnerSpace, Rad, Vector2, Zero}; 6 | use enigo::{Axis, Mouse as _}; 7 | 8 | use crate::{ 9 | config::{settings::Settings, types::RingMode}, 10 | mapping::{Buttons, VirtualKey}, 11 | mouse::{Mouse, MouseMovement}, 12 | }; 13 | 14 | pub trait Stick { 15 | fn handle( 16 | &mut self, 17 | stick: Vector2, 18 | side: StickSide, 19 | settings: &Settings, 20 | bindings: &mut Buttons, 21 | mouse: &mut Mouse, 22 | now: Instant, 23 | dt: Duration, 24 | ); 25 | } 26 | 27 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 28 | pub enum StickSide { 29 | Left, 30 | Right, 31 | Motion, 32 | } 33 | 34 | impl StickSide { 35 | fn left(self) -> VirtualKey { 36 | match self { 37 | StickSide::Left => VirtualKey::LLeft, 38 | StickSide::Right => VirtualKey::RLeft, 39 | StickSide::Motion => VirtualKey::MLeft, 40 | } 41 | } 42 | 43 | fn right(self) -> VirtualKey { 44 | match self { 45 | StickSide::Left => VirtualKey::LRight, 46 | StickSide::Right => VirtualKey::RRight, 47 | StickSide::Motion => VirtualKey::MRight, 48 | } 49 | } 50 | 51 | fn up(self) -> VirtualKey { 52 | match self { 53 | StickSide::Left => VirtualKey::LUp, 54 | StickSide::Right => VirtualKey::RUp, 55 | StickSide::Motion => VirtualKey::MUp, 56 | } 57 | } 58 | 59 | fn down(self) -> VirtualKey { 60 | match self { 61 | StickSide::Left => VirtualKey::LDown, 62 | StickSide::Right => VirtualKey::RDown, 63 | StickSide::Motion => VirtualKey::MDown, 64 | } 65 | } 66 | 67 | fn ring(self) -> VirtualKey { 68 | match self { 69 | StickSide::Left => VirtualKey::LRing, 70 | StickSide::Right => VirtualKey::RRing, 71 | StickSide::Motion => VirtualKey::MRing, 72 | } 73 | } 74 | } 75 | 76 | pub struct CameraStick { 77 | current_speed: f64, 78 | } 79 | 80 | impl CameraStick { 81 | pub fn new() -> Self { 82 | CameraStick { current_speed: 0. } 83 | } 84 | } 85 | 86 | impl Stick for CameraStick { 87 | fn handle( 88 | &mut self, 89 | stick: Vector2, 90 | side: StickSide, 91 | settings: &Settings, 92 | _bindings: &mut Buttons, 93 | mouse: &mut Mouse, 94 | _now: Instant, 95 | dt: Duration, 96 | ) { 97 | // TODO: check settings semantic 98 | let s = &settings.stick; 99 | let amp = stick.magnitude(); 100 | let amp_zones = (amp - s.deadzone) / (s.fullzone - s.deadzone); 101 | if amp_zones >= 1. { 102 | self.current_speed = (self.current_speed + s.aim.acceleration_rate * dt.as_secs_f64()) 103 | .min(s.aim.acceleration_cap); 104 | } else { 105 | self.current_speed = 0.; 106 | } 107 | let amp_clamped = amp_zones.max(0.).min(1.); 108 | let amp_exp = amp_clamped.powf(s.aim.power); 109 | if stick.magnitude2() > 0. { 110 | let mut offset = stick.normalize_to(amp_exp) 111 | * s.aim.sens_dps 112 | * ((1. + self.current_speed) * dt.as_secs_f64()); 113 | offset.mul_assign_element_wise( 114 | match side { 115 | StickSide::Left => s.aim.left_axis, 116 | StickSide::Right => s.aim.right_axis, 117 | StickSide::Motion => s.motion.axis, 118 | } 119 | .cast::() 120 | .unwrap(), 121 | ); 122 | mouse.mouse_move_relative(&settings.mouse, MouseMovement::from_vec_deg(offset)); 123 | } 124 | } 125 | } 126 | 127 | #[derive(Debug)] 128 | enum FlickStickState { 129 | Center, 130 | Flicking { 131 | flick_start: Instant, 132 | last: Deg, 133 | target: Deg, 134 | }, 135 | Rotating { 136 | old_rotation: Deg, 137 | }, 138 | } 139 | 140 | #[derive(Debug)] 141 | pub struct FlickStick { 142 | state: FlickStickState, 143 | do_rotate: bool, 144 | do_flick: bool, 145 | } 146 | 147 | impl Default for FlickStick { 148 | fn default() -> Self { 149 | FlickStick { 150 | state: FlickStickState::Center, 151 | do_rotate: true, 152 | do_flick: true, 153 | } 154 | } 155 | } 156 | 157 | impl FlickStick { 158 | pub fn new(flick: bool, rotate: bool) -> Self { 159 | Self { 160 | state: FlickStickState::Center, 161 | do_rotate: rotate, 162 | do_flick: flick, 163 | } 164 | } 165 | } 166 | 167 | impl Stick for FlickStick { 168 | fn handle( 169 | &mut self, 170 | stick: Vector2, 171 | _side: StickSide, 172 | settings: &Settings, 173 | _bindings: &mut Buttons, 174 | mouse: &mut Mouse, 175 | now: Instant, 176 | _dt: Duration, 177 | ) { 178 | let s = &settings.stick; 179 | let offset = match self.state { 180 | FlickStickState::Center | FlickStickState::Rotating { .. } 181 | if stick.magnitude() < s.fullzone => 182 | { 183 | self.state = FlickStickState::Center; 184 | None 185 | } 186 | FlickStickState::Center => { 187 | let target = stick.angle(Vector2::unit_y()).into(); 188 | self.state = if self.do_flick { 189 | FlickStickState::Flicking { 190 | flick_start: now, 191 | last: Deg(0.), 192 | target, 193 | } 194 | } else { 195 | FlickStickState::Rotating { 196 | old_rotation: target, 197 | } 198 | }; 199 | None 200 | } 201 | FlickStickState::Flicking { 202 | flick_start, 203 | ref mut last, 204 | target, 205 | } => { 206 | let elapsed = now.duration_since(flick_start).as_secs_f64(); 207 | let max = s.flick.flick_time.as_secs_f64() * target.0.abs() / 180.; 208 | let dt_factor = elapsed / max; 209 | let current_angle = target * dt_factor.min(1.); 210 | let delta = current_angle - *last; 211 | if dt_factor > 1. { 212 | self.state = FlickStickState::Rotating { 213 | old_rotation: current_angle, 214 | }; 215 | } else { 216 | *last = current_angle; 217 | } 218 | Some(delta.normalize_signed()) 219 | } 220 | FlickStickState::Rotating { 221 | ref mut old_rotation, 222 | } => { 223 | if self.do_rotate { 224 | let angle = stick.angle(Vector2::unit_y()).into(); 225 | let delta = angle - *old_rotation; 226 | *old_rotation = angle; 227 | Some(delta.normalize_signed()) 228 | } else { 229 | None 230 | } 231 | } 232 | }; 233 | if let Some(offset) = offset { 234 | mouse.mouse_move_relative(&settings.mouse, MouseMovement::new(offset, Deg(0.))); 235 | } 236 | } 237 | } 238 | 239 | pub struct ButtonStick { 240 | angle: Deg, 241 | ring_mode: RingMode, 242 | } 243 | 244 | impl ButtonStick { 245 | pub fn new(ring_mode: RingMode) -> Self { 246 | Self { 247 | angle: Deg(30.), 248 | ring_mode, 249 | } 250 | } 251 | } 252 | 253 | impl Stick for ButtonStick { 254 | fn handle( 255 | &mut self, 256 | stick: Vector2, 257 | side: StickSide, 258 | settings: &Settings, 259 | bindings: &mut Buttons, 260 | _mouse: &mut Mouse, 261 | now: Instant, 262 | _dt: Duration, 263 | ) { 264 | let settings = &settings.stick; 265 | let amp = stick.magnitude(); 266 | let amp_zones = (amp - settings.deadzone) / (settings.fullzone - settings.deadzone); 267 | let amp_clamped = amp_zones.max(0.).min(1.); 268 | 269 | if amp_clamped > 0. { 270 | let stick = stick.normalize_to(amp_clamped); 271 | 272 | let epsilon = Rad::from(Deg(90.) - self.angle).0; 273 | 274 | let angle_r = stick.angle(Vector2::unit_x()); 275 | let angle_l = stick.angle(-Vector2::unit_x()); 276 | let angle_u = stick.angle(Vector2::unit_y()); 277 | let angle_d = stick.angle(-Vector2::unit_y()); 278 | 279 | bindings.key( 280 | side.ring(), 281 | match self.ring_mode { 282 | RingMode::Inner => amp_clamped < 1., 283 | RingMode::Outer => amp_clamped >= 1., 284 | }, 285 | now, 286 | ); 287 | bindings.key(side.right(), angle_r.abs_diff_eq(&Rad(0.), epsilon), now); 288 | bindings.key(side.left(), angle_l.abs_diff_eq(&Rad(0.), epsilon), now); 289 | bindings.key(side.up(), angle_u.abs_diff_eq(&Rad(0.), epsilon), now); 290 | bindings.key(side.down(), angle_d.abs_diff_eq(&Rad(0.), epsilon), now); 291 | } else { 292 | bindings.key_up(side.left(), now); 293 | bindings.key_up(side.right(), now); 294 | bindings.key_up(side.up(), now); 295 | bindings.key_up(side.down(), now); 296 | } 297 | } 298 | } 299 | 300 | pub struct AreaStick { 301 | snap: bool, 302 | last_location: Vector2, 303 | last_offset: Vector2, 304 | } 305 | 306 | impl AreaStick { 307 | pub fn area() -> Self { 308 | Self { 309 | snap: false, 310 | last_location: Vector2::zero(), 311 | last_offset: Vector2::zero(), 312 | } 313 | } 314 | 315 | pub fn ring() -> Self { 316 | Self { 317 | snap: true, 318 | last_location: Vector2::zero(), 319 | last_offset: Vector2::zero(), 320 | } 321 | } 322 | } 323 | 324 | impl Stick for AreaStick { 325 | fn handle( 326 | &mut self, 327 | stick: Vector2, 328 | _side: StickSide, 329 | settings: &Settings, 330 | _bindings: &mut Buttons, 331 | mouse: &mut Mouse, 332 | _now: Instant, 333 | _dt: Duration, 334 | ) { 335 | let radius = settings.stick.area.screen_radius as f64; 336 | let offset = if self.snap { 337 | if stick.magnitude() > settings.stick.deadzone { 338 | stick.normalize_to(radius) 339 | } else { 340 | Vector2::zero() 341 | } 342 | } else { 343 | stick * radius 344 | } 345 | .mul_element_wise(vec2(1., -1.)); 346 | let center = settings.stick.area.screen_resolution / 2; 347 | let location = center.cast::().unwrap() + offset.cast::().unwrap(); 348 | if self.snap { 349 | if location != self.last_location || location != Vector2::zero() { 350 | mouse.mouse_move_absolute_pixel(location); 351 | } 352 | } else { 353 | mouse.mouse_move_relative_pixel(offset.sub_element_wise(self.last_offset)); 354 | } 355 | self.last_location = location; 356 | self.last_offset = offset; 357 | } 358 | } 359 | 360 | pub enum ScrollStick { 361 | Center, 362 | Scrolling { last: Deg, acc: f64 }, 363 | } 364 | 365 | impl ScrollStick { 366 | pub fn new() -> Self { 367 | Self::Center 368 | } 369 | } 370 | 371 | impl Stick for ScrollStick { 372 | fn handle( 373 | &mut self, 374 | stick: Vector2, 375 | _side: StickSide, 376 | settings: &Settings, 377 | _bindings: &mut Buttons, 378 | mouse: &mut Mouse, 379 | _now: Instant, 380 | _dt: Duration, 381 | ) { 382 | let angle = vec2(0., 1.).angle(stick).into(); 383 | match self { 384 | _ if stick.magnitude() < settings.stick.deadzone => *self = Self::Center, 385 | ScrollStick::Center => { 386 | *self = ScrollStick::Scrolling { 387 | last: angle, 388 | acc: 0., 389 | } 390 | } 391 | ScrollStick::Scrolling { last, acc } => { 392 | let delta = (angle - *last).normalize_signed() / settings.stick.scroll.sens + *acc; 393 | let delta_rounded = delta.round(); 394 | *acc = delta - delta_rounded; 395 | mouse 396 | .enigo() 397 | .scroll(delta_rounded as i32, Axis::Vertical) 398 | .unwrap(); 399 | *last = angle; 400 | } 401 | } 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /src/mapping.rs: -------------------------------------------------------------------------------- 1 | use enigo::{Button, Key}; 2 | use enum_map::{Enum, EnumArray, EnumMap}; 3 | use hid_gamepad_types::JoyKey; 4 | use std::{collections::HashMap, fmt::Display, time::Duration}; 5 | use std::{convert::TryInto, time::Instant}; 6 | 7 | use crate::ClickType; 8 | 9 | #[derive(Debug, Copy, Clone)] 10 | pub enum Action { 11 | Layer(u8, bool), 12 | Ext(ExtAction), 13 | } 14 | 15 | #[derive(Debug, Copy, Clone)] 16 | pub enum ExtAction { 17 | None, 18 | KeyPress(Key, ClickType), 19 | MousePress(Button, ClickType), 20 | #[cfg(feature = "vgamepad")] 21 | GamepadKeyPress(virtual_gamepad::Key, ClickType), 22 | GyroOn(ClickType), 23 | GyroOff(ClickType), 24 | } 25 | 26 | impl Display for ExtAction { 27 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 28 | match self { 29 | ExtAction::None => f.write_str("none"), 30 | ExtAction::KeyPress(k, t) => write!(f, "{:?} {:?}", t, k), 31 | ExtAction::MousePress(m, t) => write!(f, "{:?} {:?}", t, m), 32 | ExtAction::GamepadKeyPress(k, t) => write!(f, "{:?} {:?}", t, k), 33 | ExtAction::GyroOn(t) => write!(f, "{:?} gyro on", t), 34 | ExtAction::GyroOff(t) => write!(f, "{:?} gyro off", t), 35 | } 36 | } 37 | } 38 | 39 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 40 | enum KeyStatus { 41 | Down, 42 | Up, 43 | Hold, 44 | DoubleUp, 45 | DoubleDown, 46 | } 47 | 48 | impl KeyStatus { 49 | pub fn is_down(self) -> bool { 50 | match self { 51 | KeyStatus::Down | KeyStatus::DoubleDown | KeyStatus::Hold => true, 52 | KeyStatus::Up | KeyStatus::DoubleUp => false, 53 | } 54 | } 55 | 56 | pub fn is_up(self) -> bool { 57 | !self.is_down() 58 | } 59 | } 60 | 61 | impl Default for KeyStatus { 62 | fn default() -> Self { 63 | KeyStatus::Up 64 | } 65 | } 66 | 67 | #[derive(Debug, Clone, Default)] 68 | pub struct Layer { 69 | pub on_down: Vec, 70 | pub on_up: Vec, 71 | 72 | pub on_click: Vec, 73 | pub on_double_click: Vec, 74 | pub on_hold_down: Vec, 75 | pub on_hold_up: Vec, 76 | } 77 | 78 | impl Layer { 79 | fn is_good(&self) -> bool { 80 | self.on_down.len() 81 | + self.on_up.len() 82 | + self.on_click.len() 83 | + self.on_hold_down.len() 84 | + self.on_hold_up.len() 85 | + self.on_double_click.len() 86 | > 0 87 | } 88 | 89 | fn is_simple_click(&self) -> bool { 90 | self.on_hold_down.is_empty() 91 | && self.on_hold_up.is_empty() 92 | && self.on_double_click.is_empty() 93 | } 94 | } 95 | 96 | #[derive(Debug, Clone)] 97 | struct KeyState { 98 | status: KeyStatus, 99 | last_update: Instant, 100 | } 101 | 102 | impl Default for KeyState { 103 | fn default() -> Self { 104 | KeyState { 105 | status: KeyStatus::Up, 106 | last_update: Instant::now(), 107 | } 108 | } 109 | } 110 | 111 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Enum)] 112 | pub enum VirtualKey { 113 | LUp, 114 | LDown, 115 | LLeft, 116 | LRight, 117 | LRing, 118 | RUp, 119 | RDown, 120 | RLeft, 121 | RRight, 122 | RRing, 123 | MUp, 124 | MDown, 125 | MLeft, 126 | MRight, 127 | MRing, 128 | } 129 | 130 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 131 | pub enum MapKey { 132 | Physical(JoyKey), 133 | Virtual(VirtualKey), 134 | } 135 | 136 | impl MapKey { 137 | pub fn to_layer(self) -> u8 { 138 | ::into_usize(self) 139 | .try_into() 140 | .expect("error converting MapKey to u8") 141 | } 142 | } 143 | 144 | const JOYKEY_SIZE: usize = ::LENGTH; 145 | const VIRTKEY_SIZE: usize = ::LENGTH; 146 | const MAP_KEY_SIZE: usize = JOYKEY_SIZE + VIRTKEY_SIZE; 147 | 148 | impl Enum for MapKey { 149 | const LENGTH: usize = MAP_KEY_SIZE; 150 | 151 | fn from_usize(value: usize) -> Self { 152 | if value < JOYKEY_SIZE { 153 | ::from_usize(value).into() 154 | } else if value < MAP_KEY_SIZE { 155 | ::from_usize(value - JOYKEY_SIZE).into() 156 | } else { 157 | unreachable!("MapKey value cannot be > MAP_KEY_SIZE"); 158 | } 159 | } 160 | 161 | fn into_usize(self) -> usize { 162 | match self { 163 | MapKey::Physical(p) => ::into_usize(p), 164 | MapKey::Virtual(v) => ::into_usize(v) + JOYKEY_SIZE, 165 | } 166 | } 167 | } 168 | 169 | impl EnumArray for MapKey { 170 | type Array = [T; MAP_KEY_SIZE]; 171 | } 172 | 173 | impl From for MapKey { 174 | fn from(k: JoyKey) -> Self { 175 | MapKey::Physical(k) 176 | } 177 | } 178 | 179 | impl From for MapKey { 180 | fn from(k: VirtualKey) -> Self { 181 | MapKey::Virtual(k) 182 | } 183 | } 184 | 185 | #[derive(Debug, Clone)] 186 | pub struct Buttons { 187 | bindings: EnumMap>, 188 | state: EnumMap, 189 | current_layers: Vec, 190 | 191 | ext_actions: Vec, 192 | 193 | pub hold_delay: Duration, 194 | pub double_click_interval: Duration, 195 | } 196 | 197 | impl Buttons { 198 | pub fn new() -> Self { 199 | Buttons { 200 | bindings: EnumMap::default(), 201 | state: EnumMap::default(), 202 | current_layers: vec![0], 203 | ext_actions: Vec::new(), 204 | hold_delay: Duration::from_millis(100), 205 | double_click_interval: Duration::from_millis(200), 206 | } 207 | } 208 | 209 | pub fn reset(&mut self) { 210 | *self = Self::new(); 211 | } 212 | 213 | pub fn get(&mut self, key: impl Into, layer: u8) -> &mut Layer { 214 | self.bindings[key.into()].entry(layer).or_default() 215 | } 216 | 217 | pub fn tick(&mut self, now: Instant) -> impl Iterator + '_ { 218 | for key in (0..::LENGTH).map(::from_usize) { 219 | let binding = self.find_binding(key); 220 | match self.state[key].status { 221 | KeyStatus::Down => { 222 | if !binding.on_hold_down.is_empty() 223 | && now.duration_since(self.state[key].last_update) >= self.hold_delay 224 | { 225 | Self::actions( 226 | &binding.on_hold_down, 227 | &mut self.current_layers, 228 | &mut self.ext_actions, 229 | ); 230 | self.state[key].status = KeyStatus::Hold; 231 | } 232 | } 233 | KeyStatus::DoubleUp => { 234 | if now.duration_since(self.state[key].last_update) >= self.double_click_interval 235 | { 236 | Self::maybe_clicks( 237 | &binding, 238 | &mut self.current_layers, 239 | &mut self.ext_actions, 240 | ); 241 | self.state[key].status = KeyStatus::Up; 242 | } 243 | } 244 | _ => (), 245 | } 246 | } 247 | self.ext_actions.drain(..) 248 | } 249 | 250 | pub fn key_down(&mut self, key: impl Into, now: Instant) { 251 | let key = key.into(); 252 | if self.state[key].status.is_down() { 253 | return; 254 | } 255 | let binding = self.find_binding(key); 256 | Self::actions( 257 | &binding.on_down, 258 | &mut self.current_layers, 259 | &mut self.ext_actions, 260 | ); 261 | if binding.is_simple_click() { 262 | Self::maybe_clicks(&binding, &mut self.current_layers, &mut self.ext_actions); 263 | } 264 | self.state[key].status = match self.state[key].status { 265 | KeyStatus::DoubleUp 266 | if now.duration_since(self.state[key].last_update) < self.double_click_interval => 267 | { 268 | KeyStatus::DoubleDown 269 | } 270 | KeyStatus::DoubleUp => { 271 | Self::maybe_clicks(&binding, &mut self.current_layers, &mut self.ext_actions); 272 | KeyStatus::Down 273 | } 274 | KeyStatus::Up => KeyStatus::Down, 275 | _ => unreachable!(), 276 | }; 277 | self.state[key].last_update = now; 278 | } 279 | 280 | pub fn key_up(&mut self, key: impl Into, now: Instant) { 281 | let key = key.into(); 282 | if self.state[key].status.is_up() { 283 | return; 284 | } 285 | let binding = self.find_binding(key); 286 | Self::actions( 287 | &binding.on_up, 288 | &mut self.current_layers, 289 | &mut self.ext_actions, 290 | ); 291 | let mut new_status = KeyStatus::Up; 292 | if !binding.is_simple_click() { 293 | if binding.on_hold_up.is_empty() 294 | || now.duration_since(self.state[key].last_update) < self.hold_delay 295 | { 296 | if !binding.on_double_click.is_empty() { 297 | match self.state[key].status { 298 | KeyStatus::DoubleDown => { 299 | Self::actions( 300 | &binding.on_double_click, 301 | &mut self.current_layers, 302 | &mut self.ext_actions, 303 | ); 304 | new_status = KeyStatus::Up; 305 | } 306 | KeyStatus::Down => { 307 | new_status = KeyStatus::DoubleUp; 308 | } 309 | _ => unreachable!(), 310 | } 311 | } else { 312 | Self::maybe_clicks(&binding, &mut self.current_layers, &mut self.ext_actions); 313 | } 314 | } else if !binding.on_hold_up.is_empty() { 315 | Self::actions( 316 | &binding.on_hold_up, 317 | &mut self.current_layers, 318 | &mut self.ext_actions, 319 | ); 320 | } 321 | } 322 | self.state[key].status = new_status; 323 | self.state[key].last_update = now; 324 | } 325 | 326 | pub fn key(&mut self, key: impl Into, pressed: bool, now: Instant) { 327 | let key = key.into(); 328 | if pressed { 329 | self.key_down(key, now); 330 | } else { 331 | self.key_up(key, now); 332 | } 333 | } 334 | 335 | fn maybe_clicks( 336 | binding: &Layer, 337 | current_layers: &mut Vec, 338 | ext_actions: &mut Vec, 339 | ) { 340 | Self::actions(&binding.on_click, current_layers, ext_actions); 341 | } 342 | 343 | fn find_binding(&self, key: MapKey) -> Layer { 344 | let layers = &self.bindings[key]; 345 | for i in self.current_layers.iter().rev() { 346 | if let Some(layer) = layers.get(i) { 347 | if layer.is_good() { 348 | // TODO: Fix ugly clone 349 | return layer.clone(); 350 | } 351 | } 352 | } 353 | Layer::default() 354 | } 355 | 356 | fn actions(actions: &[Action], current_layers: &mut Vec, ext_actions: &mut Vec) { 357 | for action in actions { 358 | match *action { 359 | Action::Layer(l, true) => { 360 | if current_layers.contains(&l) { 361 | current_layers.retain(|x| *x != l); 362 | } 363 | current_layers.push(l); 364 | } 365 | Action::Layer(l, false) => { 366 | current_layers.retain(|x| *x != l); 367 | } 368 | Action::Ext(action) => ext_actions.push(action), 369 | } 370 | } 371 | } 372 | } 373 | 374 | #[cfg(test)] 375 | mod test { 376 | use super::*; 377 | 378 | #[test] 379 | fn test_simple() { 380 | let mapping = { 381 | let mut mapping = Buttons::new(); 382 | mapping 383 | .get(JoyKey::S, 0) 384 | .on_click 385 | .push(Action::Ext(ExtAction::KeyPress(Key::Alt, ClickType::Press))); 386 | mapping 387 | .get(JoyKey::S, 0) 388 | .on_double_click 389 | .push(Action::Ext(ExtAction::KeyPress( 390 | Key::Space, 391 | ClickType::Press, 392 | ))); 393 | mapping 394 | }; 395 | 396 | let t0 = Instant::now(); 397 | let ms1 = Duration::from_millis(1); 398 | let taclick = t0 + ms1; 399 | let tbhold = t0 + mapping.hold_delay - ms1; 400 | let tahold = t0 + mapping.hold_delay + 2 * ms1; 401 | let tbdoub = t0 + mapping.double_click_interval - ms1; 402 | let tadoub = t0 + mapping.double_click_interval + 2 * ms1; 403 | 404 | { 405 | let mut mapping = mapping.clone(); 406 | mapping.key_down(JoyKey::S, t0); 407 | mapping.key_up(JoyKey::S, t0); 408 | assert!(mapping.tick(t0).next().is_none()); 409 | 410 | { 411 | let mut mapping = mapping.clone(); 412 | let mut a = mapping.tick(tadoub); 413 | assert!(matches!( 414 | dbg!(a.next()), 415 | Some(ExtAction::KeyPress(Key::Alt, ClickType::Press)) 416 | )); 417 | assert!(a.next().is_none()); 418 | } 419 | 420 | { 421 | let mut mapping = mapping.clone(); 422 | let t = tbdoub; 423 | mapping.key_down(JoyKey::S, t); 424 | mapping.key_up(JoyKey::S, t); 425 | let mut a = mapping.tick(t); 426 | assert!(matches!( 427 | dbg!(a.next()), 428 | Some(ExtAction::KeyPress(Key::Space, ClickType::Press)) 429 | )); 430 | assert!(a.next().is_none()); 431 | } 432 | 433 | { 434 | let mut mapping = mapping.clone(); 435 | let t = tadoub; 436 | mapping.key_down(JoyKey::S, t); 437 | mapping.key_up(JoyKey::S, t); 438 | let mut a = mapping.tick(t); 439 | assert!(matches!( 440 | dbg!(a.next()), 441 | Some(ExtAction::KeyPress(Key::Alt, ClickType::Press)) 442 | )); 443 | assert!(a.next().is_none()); 444 | } 445 | } 446 | } 447 | } 448 | -------------------------------------------------------------------------------- /src/config/parse.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use super::types::*; 4 | use cgmath::Deg; 5 | use hid_gamepad_types::JoyKey; 6 | use nom::{ 7 | branch::alt, 8 | character::{ 9 | complete::{line_ending, not_line_ending, satisfy, space0, space1}, 10 | is_alphanumeric, 11 | }, 12 | combinator::{eof, map, opt, peek, value}, 13 | multi::separated_list1, 14 | number::complete::double, 15 | IResult, Parser, 16 | }; 17 | use nom_supreme::{ 18 | error::ErrorTree, 19 | multi::collect_separated_terminated, 20 | parser_ext::ParserExt, 21 | tag::{ 22 | complete::{tag, tag_no_case}, 23 | TagError, 24 | }, 25 | }; 26 | 27 | use crate::mapping::{MapKey, VirtualKey}; 28 | 29 | pub type Input<'a> = &'a str; 30 | pub type Error<'a> = ErrorTree>; 31 | pub type IRes<'a, O> = IResult, O, Error<'a>>; 32 | 33 | pub fn jsm_parse(input: Input) -> (Vec, Vec>) { 34 | let mut errors = Vec::new(); 35 | 36 | let line = |input| -> IRes<'_, Option> { 37 | match alt((empty_line, line))(input) { 38 | Ok(ok) => Ok(ok), 39 | Err(e) => { 40 | errors.push(e); 41 | let res: IRes<'_, &str> = not_line_ending(input); 42 | let (rest, _) = res.expect("not_line ending cannot fail"); 43 | Ok((rest, None)) 44 | } 45 | } 46 | }; 47 | 48 | let cmds = collect_separated_terminated(line, line_ending, eof) 49 | .parse(input) 50 | .map(|(_, cmds): (_, Vec<_>)| cmds.into_iter().flatten().collect()) 51 | .expect("parser cannot fail"); 52 | (cmds, errors) 53 | } 54 | 55 | fn empty_line(input: Input) -> IRes<'_, Option> { 56 | let (input, _) = space0(input)?; 57 | let (input, _) = opt(comment)(input)?; 58 | peek(alt((line_ending, eof)))(input)?; 59 | Ok((input, None)) 60 | } 61 | 62 | fn line(input: Input) -> IRes<'_, Option> { 63 | let (input, _) = space0(input)?; 64 | let (input, cmd) = cmd.context("command").parse(input)?; 65 | let (input, _) = empty_line.cut().parse(input)?; 66 | Ok((input, Some(cmd))) 67 | } 68 | 69 | fn keys(input: Input) -> IRes<'_, Key> { 70 | fn simple(input: Input) -> IRes { 71 | mapkey(input).map(|(i, k)| (i, Key::Simple(k))) 72 | } 73 | fn simul(input: Input) -> IRes<'_, Key> { 74 | let (input, k1) = mapkey(input)?; 75 | let (input, _) = space0(input)?; 76 | let (input, _) = tag("+")(input)?; 77 | let (input, _) = space0(input)?; 78 | let (input, k2) = mapkey(input)?; 79 | Ok((input, Key::Simul(k1, k2))) 80 | } 81 | fn chorded(input: Input) -> IRes<'_, Key> { 82 | let (input, k1) = mapkey(input)?; 83 | let (input, _) = space0(input)?; 84 | let (input, _) = tag(",")(input)?; 85 | let (input, _) = space0(input)?; 86 | let (input, k2) = mapkey(input)?; 87 | Ok((input, Key::Chorded(k1, k2))) 88 | } 89 | alt((simul, chorded, simple))(input) 90 | } 91 | 92 | fn action(input: Input) -> IRes<'_, JSMAction> { 93 | let (input, action_mod) = opt(alt(( 94 | value(ActionModifier::Toggle, tag("^")), 95 | value(ActionModifier::Instant, tag("!")), 96 | ))) 97 | .context("modifier") 98 | .parse(input)?; 99 | let (input, action) = alt(( 100 | map(special, ActionType::Special), 101 | #[cfg(feature = "vgamepad")] 102 | map(gamepadkey, ActionType::Gamepad), 103 | map(mousekey, ActionType::Mouse), 104 | map(keyboardkey, ActionType::Key), 105 | )) 106 | .context("action") 107 | .parse(input)?; 108 | let (input, event_mod) = opt(alt(( 109 | value(EventModifier::Tap, tag("'")), 110 | value(EventModifier::Hold, tag("_")), 111 | value(EventModifier::Start, tag("\\")), 112 | value(EventModifier::Release, tag("/")), 113 | value(EventModifier::Turbo, tag("+")), 114 | )))(input)?; 115 | Ok(( 116 | input, 117 | JSMAction { 118 | action_mod, 119 | event_mod, 120 | action, 121 | }, 122 | )) 123 | } 124 | 125 | fn binding(input: Input) -> IRes<'_, Cmd> { 126 | let (input, key) = keys.context("parse keys").parse(input)?; 127 | let (input, actions) = equal_with_space 128 | .cut() 129 | .precedes(separated_list1(space1, action).context("parse actions")) 130 | .cut() 131 | .parse(input)?; 132 | Ok((input, Cmd::Map(key, actions))) 133 | } 134 | 135 | fn setting(input: Input) -> IRes<'_, Setting> { 136 | alt(( 137 | f64_setting("TRIGGER_THRESHOLD", Setting::TriggerThreshold), 138 | trigger_mode, 139 | gyro_setting, 140 | stick_mode_setting("LEFT_STICK_MODE", Setting::LeftStickMode), 141 | stick_mode_setting("RIGHT_STICK_MODE", Setting::RightStickMode), 142 | stick_mode_setting("MOTION_STICK_MODE", |v| { 143 | Setting::Stick(StickSetting::Motion(MotionStickSetting::StickMode(v))) 144 | }), 145 | ring_mode_setting("LEFT_RING_MODE", Setting::LeftRingMode), 146 | ring_mode_setting("RIGHT_RING_MODE", Setting::RightRingMode), 147 | ring_mode_setting("MOTION_RING_MODE", |v| { 148 | Setting::Stick(StickSetting::Motion(MotionStickSetting::RingMode(v))) 149 | }), 150 | map(stick_setting, Setting::Stick), 151 | map(mouse_setting, Setting::Mouse), 152 | ))(input) 153 | } 154 | 155 | fn u32_setting( 156 | tag: &'static str, 157 | value_map: impl Fn(u32) -> Output, 158 | ) -> impl FnMut(Input) -> IRes<'_, Output> { 159 | move |input| { 160 | let (input, _) = tag_no_case(tag)(input)?; 161 | let (input, val) = nom::character::complete::u32 162 | .preceded_by(equal_with_space) 163 | .cut() 164 | .parse(input)?; 165 | Ok((input, value_map(val))) 166 | } 167 | } 168 | 169 | fn f64_setting( 170 | tag: &'static str, 171 | value_map: impl Fn(f64) -> Output, 172 | ) -> impl FnMut(Input) -> IRes<'_, Output> { 173 | move |input| { 174 | let (input, _) = tag_no_case(tag)(input)?; 175 | let (input, val) = double.preceded_by(equal_with_space).cut().parse(input)?; 176 | Ok((input, value_map(val))) 177 | } 178 | } 179 | 180 | fn double_f64_setting( 181 | tag: &'static str, 182 | value_map: impl Fn(f64, Option) -> Output, 183 | ) -> impl FnMut(Input) -> IRes<'_, Output> { 184 | move |input| { 185 | let (input, _) = tag_no_case(tag)(input)?; 186 | let (input, v1) = equal_with_space.precedes(double).cut().parse(input)?; 187 | let (input, v2) = opt(space1.precedes(double)).cut().parse(input)?; 188 | Ok((input, value_map(v1, v2))) 189 | } 190 | } 191 | 192 | fn stick_setting(input: Input) -> IRes<'_, StickSetting> { 193 | alt(( 194 | f64_setting("STICK_DEADZONE_INNER", StickSetting::Deadzone), 195 | f64_setting("STICK_DEADZONE_OUTER", |v| StickSetting::FullZone(1. - v)), 196 | f64_setting("MOTION_DEADZONE_INNER", |v| { 197 | StickSetting::Motion(MotionStickSetting::Deadzone(Deg(v))) 198 | }), 199 | f64_setting("MOTION_DEADZONE_OUTER", |v| { 200 | StickSetting::Motion(MotionStickSetting::Fullzone(Deg(1. - v))) 201 | }), 202 | f64_setting("STICK_SENS", |v| { 203 | StickSetting::Aim(AimStickSetting::Sens(v)) 204 | }), 205 | f64_setting("STICK_POWER", |v| { 206 | StickSetting::Aim(AimStickSetting::Power(v)) 207 | }), 208 | setting_invert("LEFT_STICK_AXIS", |x, y| { 209 | StickSetting::Aim(AimStickSetting::LeftAxis(x, y)) 210 | }), 211 | setting_invert("RIGHT_STICK_AXIS", |x, y| { 212 | StickSetting::Aim(AimStickSetting::RightAxis(x, y)) 213 | }), 214 | setting_invert("MOTION_STICK_AXIS", |x, y| { 215 | StickSetting::Motion(MotionStickSetting::Axis(x, y)) 216 | }), 217 | f64_setting("STICK_ACCELERATION_RATE", |v| { 218 | StickSetting::Aim(AimStickSetting::AccelerationRate(v)) 219 | }), 220 | f64_setting("STICK_ACCELERATION_CAP", |v| { 221 | StickSetting::Aim(AimStickSetting::AccelerationCap(v)) 222 | }), 223 | f64_setting("FLICK_TIME_EXPONENT", |v| { 224 | StickSetting::Flick(FlickStickSetting::Exponent(v)) 225 | }), 226 | f64_setting("FLICK_TIME", |v| { 227 | StickSetting::Flick(FlickStickSetting::FlickTime(Duration::from_secs_f64(v))) 228 | }), 229 | f64_setting("FLICK_DEADZONE_ANGLE", |v| { 230 | StickSetting::Flick(FlickStickSetting::ForwardDeadzoneArc(Deg(v * 2.))) 231 | }), 232 | f64_setting("SCROLL_SENS", |v| { 233 | StickSetting::Scroll(ScrollStickSetting::Sens(Deg(v))) 234 | }), 235 | u32_setting("SCREEN_RESOLUTION_X", |v| { 236 | StickSetting::Area(AreaStickSetting::ScreenResolutionX(v)) 237 | }), 238 | u32_setting("SCREEN_RESOLUTION_Y", |v| { 239 | StickSetting::Area(AreaStickSetting::ScreenResolutionY(v)) 240 | }), 241 | u32_setting("MOUSE_RING_RADIUS", |v| { 242 | StickSetting::Area(AreaStickSetting::Radius(v)) 243 | }), 244 | ))(input) 245 | } 246 | 247 | fn setting_invert( 248 | tag: &'static str, 249 | value_map: impl Fn(InvertMode, Option) -> O, 250 | ) -> impl FnMut(Input) -> IRes<'_, O> { 251 | move |input| { 252 | let (input, _) = tag_no_case(tag)(input)?; 253 | let (input, _) = equal_with_space.cut().parse(input)?; 254 | let (input, v1) = alt(( 255 | value(InvertMode::Normal, tag_no_case("STANDARD")), 256 | value(InvertMode::Inverted, tag_no_case("INVERTED")), 257 | )) 258 | .cut() 259 | .parse(input)?; 260 | let (input, v2) = opt(space1.precedes(alt(( 261 | value(InvertMode::Normal, tag_no_case("STANDARD")), 262 | value(InvertMode::Inverted, tag_no_case("INVERTED")), 263 | )))) 264 | .cut() 265 | .parse(input)?; 266 | Ok((input, value_map(v1, v2))) 267 | } 268 | } 269 | 270 | fn ring_mode_setting( 271 | tag: &'static str, 272 | value_map: impl Fn(RingMode) -> O, 273 | ) -> impl FnMut(Input) -> IRes<'_, O> { 274 | move |input| { 275 | let (input, _) = tag_no_case(tag)(input)?; 276 | let (input, _) = equal_with_space.cut().parse(input)?; 277 | let (input, mode) = alt(( 278 | value(RingMode::Inner, tag_no_case("INNER")), 279 | value(RingMode::Outer, tag_no_case("OUTER")), 280 | )) 281 | .cut() 282 | .parse(input)?; 283 | Ok((input, value_map(mode))) 284 | } 285 | } 286 | 287 | fn gyro_setting(input: Input) -> IRes<'_, Setting> { 288 | map( 289 | alt(( 290 | double_f64_setting("GYRO_SENS", GyroSetting::Sensitivity), 291 | double_f64_setting("MIN_GYRO_SENS", GyroSetting::MinSens), 292 | f64_setting("MIN_GYRO_THRESHOLD", GyroSetting::MinThreshold), 293 | double_f64_setting("MAX_GYRO_SENS", GyroSetting::MaxSens), 294 | f64_setting("MAX_GYRO_THRESHOLD", GyroSetting::MaxThreshold), 295 | gyro_space, 296 | f64_setting("GYRO_CUTOFF_SPEED", GyroSetting::CutoffSpeed), 297 | f64_setting("GYRO_CUTOFF_RECOVERY", GyroSetting::CutoffRecovery), 298 | f64_setting("GYRO_SMOOTH_THRESHOLD", GyroSetting::SmoothThreshold), 299 | f64_setting("GYRO_SMOOTH_TIME", |secs| { 300 | GyroSetting::SmoothTime(Duration::from_secs_f64(secs)) 301 | }), 302 | setting_invert("GYRO_AXIS_X", |v1, _v2| GyroSetting::InvertX(v1)), 303 | setting_invert("GYRO_AXIS_Y", |v1, _v2| GyroSetting::InvertY(v1)), 304 | )), 305 | Setting::Gyro, 306 | )(input) 307 | } 308 | 309 | fn gyro_space(input: Input) -> IRes<'_, GyroSetting> { 310 | let (input, _) = tag_no_case("GYRO_SPACE")(input)?; 311 | let (input, space) = alt(( 312 | value(GyroSpace::Local, tag_no_case("LOCAL")), 313 | value(GyroSpace::WorldTurn, tag_no_case("WORLD_TURN")), 314 | value(GyroSpace::WorldLean, tag_no_case("WORLD_LEAN")), 315 | value(GyroSpace::PlayerTurn, tag_no_case("PLAYER_TURN")), 316 | value(GyroSpace::PlayerLean, tag_no_case("PLAYER_LEAN")), 317 | )) 318 | .preceded_by(equal_with_space) 319 | .cut() 320 | .parse(input)?; 321 | Ok((input, GyroSetting::Space(space))) 322 | } 323 | 324 | fn stick_mode_setting( 325 | tag: &'static str, 326 | value_map: impl Fn(StickMode) -> O, 327 | ) -> impl FnMut(Input) -> IRes<'_, O> { 328 | move |input| { 329 | let (input, _) = tag_no_case(tag)(input)?; 330 | let (input, _) = equal_with_space.cut().parse(input)?; 331 | let (input, mode) = alt(( 332 | value(StickMode::Aim, tag_no_case("AIM")), 333 | value(StickMode::FlickOnly, tag_no_case("FLICK_ONLY")), 334 | value(StickMode::Flick, tag_no_case("FLICK")), 335 | value(StickMode::MouseArea, tag_no_case("MOUSE_AREA")), 336 | value(StickMode::MouseRing, tag_no_case("MOUSE_RING")), 337 | value(StickMode::NoMouse, tag_no_case("NO_MOUSE")), 338 | value(StickMode::RotateOnly, tag_no_case("ROTATE_ONLY")), 339 | value(StickMode::ScrollWheel, tag_no_case("SCROLL_WHEEL")), 340 | )) 341 | .cut() 342 | .parse(input)?; 343 | Ok((input, value_map(mode))) 344 | } 345 | } 346 | 347 | fn trigger_mode(input: Input) -> IRes<'_, Setting> { 348 | let (input, key) = alt((tag_no_case("ZL_MODE"), tag_no_case("ZR_MODE")))(input)?; 349 | let (input, mode) = alt(( 350 | value(TriggerMode::MaySkipR, tag_no_case("MAY_SKIP_R")), 351 | value(TriggerMode::MaySkip, tag_no_case("MAY_SKIP")), 352 | value(TriggerMode::MustSkipR, tag_no_case("MUST_SKIP_R")), 353 | value(TriggerMode::MustSkip, tag_no_case("MUST_SKIP")), 354 | value(TriggerMode::NoFull, tag_no_case("NO_FULL")), 355 | value( 356 | TriggerMode::NoSkipExclusive, 357 | tag_no_case("NO_SKIP_EXCLUSIVE"), 358 | ), 359 | value(TriggerMode::NoSkip, tag_no_case("NO_SKIP")), 360 | )) 361 | .preceded_by(equal_with_space) 362 | .cut() 363 | .parse(input)?; 364 | if key == "ZR_MODE" { 365 | Ok((input, Setting::ZRMode(mode))) 366 | } else { 367 | Ok((input, Setting::ZLMode(mode))) 368 | } 369 | } 370 | fn mouse_setting(input: Input) -> IRes { 371 | alt(( 372 | f64_setting("REAL_WORLD_CALIBRATION", MouseSetting::RealWorldCalibration), 373 | f64_setting("IN_GAME_SENS", MouseSetting::InGameSens), 374 | value( 375 | MouseSetting::CounterOSSpeed(true), 376 | tag_no_case("COUNTER_OS_MOUSE_SPEED"), 377 | ), 378 | value( 379 | MouseSetting::CounterOSSpeed(false), 380 | tag_no_case("IGNORE_OS_MOUSE_SPEED"), 381 | ), 382 | ))(input) 383 | } 384 | 385 | fn equal_with_space(input: Input) -> IRes<'_, ()> { 386 | let (input, _) = space0(input)?; 387 | let (input, _) = tag("=").cut().parse(input)?; 388 | let (input, _) = space0(input)?; 389 | Ok((input, ())) 390 | } 391 | 392 | fn cmd(input: Input) -> IRes<'_, Cmd> { 393 | alt(( 394 | map(special, Cmd::Special), 395 | map(setting, Cmd::Setting), 396 | value(Cmd::Reset, tag_no_case("RESET_MAPPINGS")), 397 | binding.context("key binding"), 398 | )) 399 | .cut() 400 | .parse(input) 401 | } 402 | 403 | fn comment(input: Input) -> IRes<'_, ()> { 404 | let (input, _) = tag("#")(input)?; 405 | let (input, _) = not_line_ending(input)?; 406 | Ok((input, ())) 407 | } 408 | fn mapkey(input: Input) -> IRes<'_, MapKey> { 409 | alt((map(virtkey, MapKey::from), map(joykey, MapKey::from)))(input) 410 | } 411 | 412 | fn joykey(input: Input) -> IRes<'_, JoyKey> { 413 | alt(( 414 | alt(( 415 | value(JoyKey::Up, tag_no_case("Up")), 416 | value(JoyKey::Down, tag_no_case("Down")), 417 | value(JoyKey::Left, tag_no_case("Left")), 418 | value(JoyKey::Right, tag_no_case("Right")), 419 | value(JoyKey::ZL, tag_no_case("ZL")), 420 | value(JoyKey::ZR, tag_no_case("ZR")), 421 | value(JoyKey::SL, tag_no_case("SL")), 422 | value(JoyKey::SR, tag_no_case("SR")), 423 | value(JoyKey::L3, tag_no_case("L3")), 424 | value(JoyKey::R3, tag_no_case("R3")), 425 | value(JoyKey::N, tag_no_case("N")), 426 | value(JoyKey::S, tag_no_case("S")), 427 | value(JoyKey::E, tag_no_case("E")), 428 | )), 429 | alt(( 430 | value(JoyKey::W, tag_no_case("W")), 431 | value(JoyKey::L, tag_no_case("L")), 432 | value(JoyKey::R, tag_no_case("R")), 433 | value(JoyKey::Minus, tag_no_case("-")), 434 | value(JoyKey::Plus, tag_no_case("+")), 435 | value(JoyKey::Minus, tag_no_case("Minus")), 436 | value(JoyKey::Plus, tag_no_case("Plus")), 437 | value(JoyKey::Capture, tag_no_case("Capture")), 438 | value(JoyKey::Home, tag_no_case("Home")), 439 | )), 440 | ))(input) 441 | } 442 | 443 | fn virtkey(input: Input) -> IRes<'_, VirtualKey> { 444 | alt(( 445 | value(VirtualKey::LUp, tag_no_case("LUp")), 446 | value(VirtualKey::LDown, tag_no_case("LDown")), 447 | value(VirtualKey::LLeft, tag_no_case("LLeft")), 448 | value(VirtualKey::LRight, tag_no_case("LRight")), 449 | value(VirtualKey::LRing, tag_no_case("LRing")), 450 | value(VirtualKey::RUp, tag_no_case("RUp")), 451 | value(VirtualKey::RDown, tag_no_case("RDown")), 452 | value(VirtualKey::RLeft, tag_no_case("RLeft")), 453 | value(VirtualKey::RRight, tag_no_case("RRight")), 454 | value(VirtualKey::RRing, tag_no_case("RRing")), 455 | value(VirtualKey::MUp, tag_no_case("MUp")), 456 | value(VirtualKey::MDown, tag_no_case("MDown")), 457 | value(VirtualKey::MLeft, tag_no_case("MLeft")), 458 | value(VirtualKey::MRight, tag_no_case("MRight")), 459 | value(VirtualKey::MRing, tag_no_case("MRing")), 460 | ))(input) 461 | } 462 | 463 | fn keyboardkey(input: Input) -> IRes<'_, enigo::Key> { 464 | use enigo::Key::*; 465 | let char_parse = |input| { 466 | satisfy(|c| is_alphanumeric(c as u8))(input) 467 | .map(|(i, x)| (i, Unicode(x))) 468 | .map_err(|_: nom::Err>>| { 469 | nom::Err::Error(ErrorTree::from_tag(input, "a keyboard letter")) 470 | }) 471 | }; 472 | let key_parse = |key, tag| value(key, tag_no_case(tag)); 473 | alt(( 474 | alt(( 475 | key_parse(Alt, "alt"), 476 | //TODO: proper lalt and ralt 477 | key_parse(Alt, "lalt"), 478 | key_parse(Alt, "ralt"), 479 | key_parse(Backspace, "backspace"), 480 | key_parse(CapsLock, "capslock"), 481 | key_parse(Control, "Control"), 482 | key_parse(Delete, "Delete"), 483 | key_parse(DownArrow, "down"), 484 | key_parse(End, "End"), 485 | key_parse(Escape, "Esc"), 486 | key_parse(F1, "F1"), 487 | key_parse(F10, "F10"), 488 | key_parse(F11, "F11"), 489 | key_parse(F12, "F12"), 490 | key_parse(F2, "F2"), 491 | key_parse(F3, "F3"), 492 | key_parse(F4, "F4"), 493 | key_parse(F5, "F5"), 494 | )), 495 | alt(( 496 | key_parse(F6, "F6"), 497 | key_parse(F7, "F7"), 498 | key_parse(F8, "F8"), 499 | key_parse(F9, "F9"), 500 | key_parse(Home, "Home"), 501 | key_parse(LeftArrow, "left"), 502 | key_parse(Meta, "Meta"), 503 | key_parse(Meta, "Windows"), 504 | key_parse(Meta, "lWindows"), 505 | key_parse(Meta, "rWindows"), 506 | key_parse(Option, "Option"), 507 | key_parse(PageDown, "PageDown"), 508 | key_parse(PageUp, "PageUp"), 509 | key_parse(Return, "Enter"), 510 | key_parse(RightArrow, "right"), 511 | key_parse(Shift, "Shift"), 512 | key_parse(Space, "Space"), 513 | key_parse(Tab, "Tab"), 514 | key_parse(UpArrow, "up"), 515 | char_parse, 516 | )), 517 | ))(input) 518 | } 519 | 520 | fn mousekey(input: Input) -> IRes<'_, enigo::Button> { 521 | use enigo::Button::*; 522 | let key_parse = |key, tag| value(key, tag_no_case(tag)); 523 | alt(( 524 | key_parse(Left, "LMouse"), 525 | key_parse(Middle, "MMouse"), 526 | key_parse(Right, "RMouse"), 527 | // TODO: fix https://github.com/enigo-rs/enigo/issues/110 528 | key_parse(Left, "BMouse"), 529 | key_parse(Left, "FMouse"), 530 | key_parse(ScrollUp, "scrollup"), 531 | key_parse(ScrollDown, "scrolldown"), 532 | key_parse(ScrollLeft, "scrollleft"), 533 | key_parse(ScrollRight, "scrollright"), 534 | ))(input) 535 | } 536 | 537 | fn special(input: Input) -> IRes<'_, SpecialKey> { 538 | use SpecialKey::*; 539 | let parse = |key, tag| value(key, tag_no_case(tag)); 540 | alt(( 541 | parse(None, "none"), 542 | parse(GyroOn, "gyro_on"), 543 | parse(GyroOff, "gyro_off"), 544 | parse(GyroInvertX(true), "gyro_inv_x"), 545 | parse(GyroInvertY(true), "gyro_inv_y"), 546 | parse(GyroTrackBall(true), "gyro_trackball"), 547 | ))(input) 548 | } 549 | 550 | #[cfg(feature = "vgamepad")] 551 | fn gamepadkey(input: Input) -> IRes<'_, virtual_gamepad::Key> { 552 | use virtual_gamepad::Key::*; 553 | let parse = |key, tag| value(key, tag_no_case(tag)); 554 | alt(( 555 | parse(A, "X_A"), 556 | parse(B, "X_B"), 557 | parse(X, "X_X"), 558 | parse(Y, "X_Y"), 559 | ))(input) 560 | } 561 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "anstream" 31 | version = "0.6.14" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" 34 | dependencies = [ 35 | "anstyle", 36 | "anstyle-parse", 37 | "anstyle-query", 38 | "anstyle-wincon", 39 | "colorchoice", 40 | "is_terminal_polyfill", 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle" 46 | version = "1.0.7" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" 49 | 50 | [[package]] 51 | name = "anstyle-parse" 52 | version = "0.2.4" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" 55 | dependencies = [ 56 | "utf8parse", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-query" 61 | version = "1.0.3" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" 64 | dependencies = [ 65 | "windows-sys", 66 | ] 67 | 68 | [[package]] 69 | name = "anstyle-wincon" 70 | version = "3.0.3" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" 73 | dependencies = [ 74 | "anstyle", 75 | "windows-sys", 76 | ] 77 | 78 | [[package]] 79 | name = "anyhow" 80 | version = "1.0.83" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" 83 | dependencies = [ 84 | "backtrace", 85 | ] 86 | 87 | [[package]] 88 | name = "approx" 89 | version = "0.4.0" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278" 92 | dependencies = [ 93 | "num-traits", 94 | ] 95 | 96 | [[package]] 97 | name = "array-macro" 98 | version = "1.0.5" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "06e97b4e522f9e55523001238ac59d13a8603af57f69980de5d8de4bbbe8ada6" 101 | 102 | [[package]] 103 | name = "arrayvec" 104 | version = "0.7.4" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" 107 | 108 | [[package]] 109 | name = "autocfg" 110 | version = "1.3.0" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 113 | 114 | [[package]] 115 | name = "backtrace" 116 | version = "0.3.71" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" 119 | dependencies = [ 120 | "addr2line", 121 | "cc", 122 | "cfg-if", 123 | "libc", 124 | "miniz_oxide", 125 | "object", 126 | "rustc-demangle", 127 | ] 128 | 129 | [[package]] 130 | name = "bitfield" 131 | version = "0.13.2" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" 134 | 135 | [[package]] 136 | name = "bitflags" 137 | version = "1.3.2" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 140 | 141 | [[package]] 142 | name = "bitflags" 143 | version = "2.5.0" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 146 | 147 | [[package]] 148 | name = "bitvec" 149 | version = "1.0.1" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" 152 | dependencies = [ 153 | "funty", 154 | "radium", 155 | "tap", 156 | "wyz", 157 | ] 158 | 159 | [[package]] 160 | name = "block-sys" 161 | version = "0.2.1" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7" 164 | dependencies = [ 165 | "objc-sys", 166 | ] 167 | 168 | [[package]] 169 | name = "block2" 170 | version = "0.4.0" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "e58aa60e59d8dbfcc36138f5f18be5f24394d33b38b24f7fd0b1caa33095f22f" 173 | dependencies = [ 174 | "block-sys", 175 | "objc2", 176 | ] 177 | 178 | [[package]] 179 | name = "brownstone" 180 | version = "3.0.0" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "c5839ee4f953e811bfdcf223f509cb2c6a3e1447959b0bff459405575bc17f22" 183 | dependencies = [ 184 | "arrayvec", 185 | ] 186 | 187 | [[package]] 188 | name = "cc" 189 | version = "1.0.97" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" 192 | 193 | [[package]] 194 | name = "cfg-if" 195 | version = "1.0.0" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 198 | 199 | [[package]] 200 | name = "cgmath" 201 | version = "0.18.0" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "1a98d30140e3296250832bbaaff83b27dcd6fa3cc70fb6f1f3e5c9c0023b5317" 204 | dependencies = [ 205 | "approx", 206 | "num-traits", 207 | ] 208 | 209 | [[package]] 210 | name = "clap" 211 | version = "4.5.4" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" 214 | dependencies = [ 215 | "clap_builder", 216 | "clap_derive", 217 | ] 218 | 219 | [[package]] 220 | name = "clap_builder" 221 | version = "4.5.2" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" 224 | dependencies = [ 225 | "anstream", 226 | "anstyle", 227 | "clap_lex", 228 | "strsim", 229 | ] 230 | 231 | [[package]] 232 | name = "clap_derive" 233 | version = "4.5.4" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" 236 | dependencies = [ 237 | "heck", 238 | "proc-macro2", 239 | "quote", 240 | "syn 2.0.61", 241 | ] 242 | 243 | [[package]] 244 | name = "clap_lex" 245 | version = "0.7.0" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" 248 | 249 | [[package]] 250 | name = "colorchoice" 251 | version = "1.0.1" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" 254 | 255 | [[package]] 256 | name = "core-foundation" 257 | version = "0.9.4" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 260 | dependencies = [ 261 | "core-foundation-sys", 262 | "libc", 263 | ] 264 | 265 | [[package]] 266 | name = "core-foundation-sys" 267 | version = "0.8.6" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" 270 | 271 | [[package]] 272 | name = "core-graphics" 273 | version = "0.23.2" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" 276 | dependencies = [ 277 | "bitflags 1.3.2", 278 | "core-foundation", 279 | "core-graphics-types", 280 | "foreign-types", 281 | "libc", 282 | ] 283 | 284 | [[package]] 285 | name = "core-graphics-types" 286 | version = "0.1.3" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" 289 | dependencies = [ 290 | "bitflags 1.3.2", 291 | "core-foundation", 292 | "libc", 293 | ] 294 | 295 | [[package]] 296 | name = "dlib" 297 | version = "0.5.2" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" 300 | dependencies = [ 301 | "libloading", 302 | ] 303 | 304 | [[package]] 305 | name = "downcast-rs" 306 | version = "1.2.1" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" 309 | 310 | [[package]] 311 | name = "dualshock" 312 | version = "0.1.0" 313 | source = "git+https://github.com/Yamakaky/joy#a9900ccf9a77aa5ca6c8db3283bdc6e9cd2ee31e" 314 | dependencies = [ 315 | "anyhow", 316 | "cgmath", 317 | "dualshock-sys", 318 | "enum-map 2.7.3", 319 | "hid-gamepad-sys", 320 | "hidapi", 321 | ] 322 | 323 | [[package]] 324 | name = "dualshock-sys" 325 | version = "0.1.0" 326 | source = "git+https://github.com/Yamakaky/joy#a9900ccf9a77aa5ca6c8db3283bdc6e9cd2ee31e" 327 | dependencies = [ 328 | "bitfield", 329 | "cgmath", 330 | "num", 331 | "num-derive", 332 | "num-traits", 333 | ] 334 | 335 | [[package]] 336 | name = "enigo" 337 | version = "0.2.0" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "8a4655cdcad61e57cf28922aaa3221b06ce29e644422bba506851a03b8817468" 340 | dependencies = [ 341 | "core-graphics", 342 | "foreign-types-shared", 343 | "icrate", 344 | "libc", 345 | "log", 346 | "objc2", 347 | "tempfile", 348 | "wayland-client", 349 | "wayland-protocols-misc", 350 | "wayland-protocols-plasma", 351 | "wayland-protocols-wlr", 352 | "windows", 353 | "xkbcommon", 354 | "xkeysym", 355 | ] 356 | 357 | [[package]] 358 | name = "enum-map" 359 | version = "0.6.6" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "23595c55d463536d70a0cc71a521d4c1040a2e03816e455c38e8bb1f0981de98" 362 | dependencies = [ 363 | "array-macro", 364 | "enum-map-derive 0.4.6", 365 | ] 366 | 367 | [[package]] 368 | name = "enum-map" 369 | version = "2.7.3" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" 372 | dependencies = [ 373 | "enum-map-derive 0.17.0", 374 | ] 375 | 376 | [[package]] 377 | name = "enum-map-derive" 378 | version = "0.4.6" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "e5c450cf304c9e18d45db562025a14fb1ca0f5c769b6f609309f81d4c31de455" 381 | dependencies = [ 382 | "proc-macro2", 383 | "quote", 384 | "syn 1.0.109", 385 | ] 386 | 387 | [[package]] 388 | name = "enum-map-derive" 389 | version = "0.17.0" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" 392 | dependencies = [ 393 | "proc-macro2", 394 | "quote", 395 | "syn 2.0.61", 396 | ] 397 | 398 | [[package]] 399 | name = "env_filter" 400 | version = "0.1.0" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" 403 | dependencies = [ 404 | "log", 405 | "regex", 406 | ] 407 | 408 | [[package]] 409 | name = "env_logger" 410 | version = "0.11.3" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" 413 | dependencies = [ 414 | "anstream", 415 | "anstyle", 416 | "env_filter", 417 | "humantime", 418 | "log", 419 | ] 420 | 421 | [[package]] 422 | name = "equivalent" 423 | version = "1.0.1" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 426 | 427 | [[package]] 428 | name = "errno" 429 | version = "0.3.9" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 432 | dependencies = [ 433 | "libc", 434 | "windows-sys", 435 | ] 436 | 437 | [[package]] 438 | name = "evdev" 439 | version = "0.12.2" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "ab6055a93a963297befb0f4f6e18f314aec9767a4bbe88b151126df2433610a7" 442 | dependencies = [ 443 | "bitvec", 444 | "cfg-if", 445 | "libc", 446 | "nix", 447 | "thiserror", 448 | ] 449 | 450 | [[package]] 451 | name = "fastrand" 452 | version = "2.1.0" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" 455 | 456 | [[package]] 457 | name = "foreign-types" 458 | version = "0.5.0" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" 461 | dependencies = [ 462 | "foreign-types-macros", 463 | "foreign-types-shared", 464 | ] 465 | 466 | [[package]] 467 | name = "foreign-types-macros" 468 | version = "0.2.3" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" 471 | dependencies = [ 472 | "proc-macro2", 473 | "quote", 474 | "syn 2.0.61", 475 | ] 476 | 477 | [[package]] 478 | name = "foreign-types-shared" 479 | version = "0.3.1" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" 482 | 483 | [[package]] 484 | name = "funty" 485 | version = "2.0.0" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" 488 | 489 | [[package]] 490 | name = "getrandom" 491 | version = "0.2.15" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 494 | dependencies = [ 495 | "cfg-if", 496 | "libc", 497 | "wasi", 498 | ] 499 | 500 | [[package]] 501 | name = "gimli" 502 | version = "0.28.1" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 505 | 506 | [[package]] 507 | name = "gyromouse" 508 | version = "0.1.0" 509 | dependencies = [ 510 | "anyhow", 511 | "cgmath", 512 | "clap", 513 | "enigo", 514 | "enum-map 2.7.3", 515 | "env_logger", 516 | "hid-gamepad", 517 | "hid-gamepad-types", 518 | "human-panic", 519 | "joycon", 520 | "nom", 521 | "nom-supreme", 522 | "sdl2", 523 | "virtual-gamepad", 524 | ] 525 | 526 | [[package]] 527 | name = "hashbrown" 528 | version = "0.14.5" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 531 | 532 | [[package]] 533 | name = "heck" 534 | version = "0.5.0" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 537 | 538 | [[package]] 539 | name = "hex" 540 | version = "0.4.3" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 543 | 544 | [[package]] 545 | name = "hid-gamepad" 546 | version = "0.1.0" 547 | source = "git+https://github.com/Yamakaky/joy#a9900ccf9a77aa5ca6c8db3283bdc6e9cd2ee31e" 548 | dependencies = [ 549 | "anyhow", 550 | "cgmath", 551 | "dualshock", 552 | "enum-map 0.6.6", 553 | "hid-gamepad-sys", 554 | "hidapi", 555 | "joycon", 556 | "thiserror", 557 | ] 558 | 559 | [[package]] 560 | name = "hid-gamepad-sys" 561 | version = "0.1.0" 562 | source = "git+https://github.com/Yamakaky/joy#a9900ccf9a77aa5ca6c8db3283bdc6e9cd2ee31e" 563 | dependencies = [ 564 | "anyhow", 565 | "cgmath", 566 | "enum-map 2.7.3", 567 | "hid-gamepad-types", 568 | "hidapi", 569 | ] 570 | 571 | [[package]] 572 | name = "hid-gamepad-types" 573 | version = "0.1.0" 574 | source = "git+https://github.com/Yamakaky/joy#a9900ccf9a77aa5ca6c8db3283bdc6e9cd2ee31e" 575 | dependencies = [ 576 | "cgmath", 577 | "enum-map 2.7.3", 578 | ] 579 | 580 | [[package]] 581 | name = "hidapi" 582 | version = "1.5.0" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "798154e4b6570af74899d71155fb0072d5b17e6aa12f39c8ef22c60fb8ec99e7" 585 | dependencies = [ 586 | "cc", 587 | "libc", 588 | "pkg-config", 589 | "winapi", 590 | ] 591 | 592 | [[package]] 593 | name = "human-panic" 594 | version = "2.0.0" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "a4c5d0e9120f6bca6120d142c7ede1ba376dd6bf276d69dd3dbe6cbeb7824179" 597 | dependencies = [ 598 | "anstream", 599 | "anstyle", 600 | "backtrace", 601 | "os_info", 602 | "serde", 603 | "serde_derive", 604 | "toml", 605 | "uuid", 606 | ] 607 | 608 | [[package]] 609 | name = "humantime" 610 | version = "2.1.0" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 613 | 614 | [[package]] 615 | name = "icrate" 616 | version = "0.1.2" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "3fb69199826926eb864697bddd27f73d9fddcffc004f5733131e15b465e30642" 619 | dependencies = [ 620 | "block2", 621 | "objc2", 622 | ] 623 | 624 | [[package]] 625 | name = "indent_write" 626 | version = "2.2.0" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "0cfe9645a18782869361d9c8732246be7b410ad4e919d3609ebabdac00ba12c3" 629 | 630 | [[package]] 631 | name = "indexmap" 632 | version = "2.2.6" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" 635 | dependencies = [ 636 | "equivalent", 637 | "hashbrown", 638 | ] 639 | 640 | [[package]] 641 | name = "is_terminal_polyfill" 642 | version = "1.70.0" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" 645 | 646 | [[package]] 647 | name = "joinery" 648 | version = "2.1.0" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "72167d68f5fce3b8655487b8038691a3c9984ee769590f93f2a631f4ad64e4f5" 651 | 652 | [[package]] 653 | name = "joycon" 654 | version = "0.1.0" 655 | source = "git+https://github.com/Yamakaky/joy#a9900ccf9a77aa5ca6c8db3283bdc6e9cd2ee31e" 656 | dependencies = [ 657 | "anyhow", 658 | "cgmath", 659 | "enum-map 2.7.3", 660 | "hex", 661 | "hid-gamepad-sys", 662 | "hidapi", 663 | "joycon-sys", 664 | "tracing", 665 | ] 666 | 667 | [[package]] 668 | name = "joycon-sys" 669 | version = "0.1.0" 670 | source = "git+https://github.com/Yamakaky/joy#a9900ccf9a77aa5ca6c8db3283bdc6e9cd2ee31e" 671 | dependencies = [ 672 | "bitfield", 673 | "cgmath", 674 | "num", 675 | "num-derive", 676 | "num-traits", 677 | ] 678 | 679 | [[package]] 680 | name = "lazy_static" 681 | version = "1.4.0" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 684 | 685 | [[package]] 686 | name = "libc" 687 | version = "0.2.154" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" 690 | 691 | [[package]] 692 | name = "libloading" 693 | version = "0.8.3" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" 696 | dependencies = [ 697 | "cfg-if", 698 | "windows-targets", 699 | ] 700 | 701 | [[package]] 702 | name = "linux-raw-sys" 703 | version = "0.4.13" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" 706 | 707 | [[package]] 708 | name = "log" 709 | version = "0.4.21" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 712 | 713 | [[package]] 714 | name = "memchr" 715 | version = "2.7.2" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 718 | 719 | [[package]] 720 | name = "memmap2" 721 | version = "0.8.0" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed" 724 | dependencies = [ 725 | "libc", 726 | ] 727 | 728 | [[package]] 729 | name = "memoffset" 730 | version = "0.6.5" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 733 | dependencies = [ 734 | "autocfg", 735 | ] 736 | 737 | [[package]] 738 | name = "minimal-lexical" 739 | version = "0.2.1" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 742 | 743 | [[package]] 744 | name = "miniz_oxide" 745 | version = "0.7.2" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" 748 | dependencies = [ 749 | "adler", 750 | ] 751 | 752 | [[package]] 753 | name = "nix" 754 | version = "0.23.2" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" 757 | dependencies = [ 758 | "bitflags 1.3.2", 759 | "cc", 760 | "cfg-if", 761 | "libc", 762 | "memoffset", 763 | ] 764 | 765 | [[package]] 766 | name = "nom" 767 | version = "7.1.3" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 770 | dependencies = [ 771 | "memchr", 772 | "minimal-lexical", 773 | ] 774 | 775 | [[package]] 776 | name = "nom-supreme" 777 | version = "0.8.0" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "2bd3ae6c901f1959588759ff51c95d24b491ecb9ff91aa9c2ef4acc5b1dcab27" 780 | dependencies = [ 781 | "brownstone", 782 | "indent_write", 783 | "joinery", 784 | "memchr", 785 | "nom", 786 | ] 787 | 788 | [[package]] 789 | name = "num" 790 | version = "0.4.3" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" 793 | dependencies = [ 794 | "num-complex", 795 | "num-integer", 796 | "num-iter", 797 | "num-rational", 798 | "num-traits", 799 | ] 800 | 801 | [[package]] 802 | name = "num-complex" 803 | version = "0.4.6" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" 806 | dependencies = [ 807 | "num-traits", 808 | ] 809 | 810 | [[package]] 811 | name = "num-derive" 812 | version = "0.3.3" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" 815 | dependencies = [ 816 | "proc-macro2", 817 | "quote", 818 | "syn 1.0.109", 819 | ] 820 | 821 | [[package]] 822 | name = "num-integer" 823 | version = "0.1.46" 824 | source = "registry+https://github.com/rust-lang/crates.io-index" 825 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 826 | dependencies = [ 827 | "num-traits", 828 | ] 829 | 830 | [[package]] 831 | name = "num-iter" 832 | version = "0.1.45" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" 835 | dependencies = [ 836 | "autocfg", 837 | "num-integer", 838 | "num-traits", 839 | ] 840 | 841 | [[package]] 842 | name = "num-rational" 843 | version = "0.4.2" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" 846 | dependencies = [ 847 | "num-integer", 848 | "num-traits", 849 | ] 850 | 851 | [[package]] 852 | name = "num-traits" 853 | version = "0.2.19" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 856 | dependencies = [ 857 | "autocfg", 858 | ] 859 | 860 | [[package]] 861 | name = "objc-sys" 862 | version = "0.3.3" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "da284c198fb9b7b0603f8635185e85fbd5b64ee154b1ed406d489077de2d6d60" 865 | 866 | [[package]] 867 | name = "objc2" 868 | version = "0.5.1" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "b4b25e1034d0e636cd84707ccdaa9f81243d399196b8a773946dcffec0401659" 871 | dependencies = [ 872 | "objc-sys", 873 | "objc2-encode", 874 | ] 875 | 876 | [[package]] 877 | name = "objc2-encode" 878 | version = "4.0.1" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "88658da63e4cc2c8adb1262902cd6af51094df0488b760d6fd27194269c0950a" 881 | 882 | [[package]] 883 | name = "object" 884 | version = "0.32.2" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 887 | dependencies = [ 888 | "memchr", 889 | ] 890 | 891 | [[package]] 892 | name = "once_cell" 893 | version = "1.19.0" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 896 | 897 | [[package]] 898 | name = "os_info" 899 | version = "3.8.2" 900 | source = "registry+https://github.com/rust-lang/crates.io-index" 901 | checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" 902 | dependencies = [ 903 | "log", 904 | "serde", 905 | "windows-sys", 906 | ] 907 | 908 | [[package]] 909 | name = "pin-project-lite" 910 | version = "0.2.14" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 913 | 914 | [[package]] 915 | name = "pkg-config" 916 | version = "0.3.30" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" 919 | 920 | [[package]] 921 | name = "proc-macro2" 922 | version = "1.0.82" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" 925 | dependencies = [ 926 | "unicode-ident", 927 | ] 928 | 929 | [[package]] 930 | name = "quick-xml" 931 | version = "0.31.0" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" 934 | dependencies = [ 935 | "memchr", 936 | ] 937 | 938 | [[package]] 939 | name = "quote" 940 | version = "1.0.36" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 943 | dependencies = [ 944 | "proc-macro2", 945 | ] 946 | 947 | [[package]] 948 | name = "radium" 949 | version = "0.7.0" 950 | source = "registry+https://github.com/rust-lang/crates.io-index" 951 | checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" 952 | 953 | [[package]] 954 | name = "regex" 955 | version = "1.10.4" 956 | source = "registry+https://github.com/rust-lang/crates.io-index" 957 | checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" 958 | dependencies = [ 959 | "aho-corasick", 960 | "memchr", 961 | "regex-automata", 962 | "regex-syntax", 963 | ] 964 | 965 | [[package]] 966 | name = "regex-automata" 967 | version = "0.4.6" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" 970 | dependencies = [ 971 | "aho-corasick", 972 | "memchr", 973 | "regex-syntax", 974 | ] 975 | 976 | [[package]] 977 | name = "regex-syntax" 978 | version = "0.8.3" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" 981 | 982 | [[package]] 983 | name = "rustc-demangle" 984 | version = "0.1.24" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 987 | 988 | [[package]] 989 | name = "rustix" 990 | version = "0.38.34" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" 993 | dependencies = [ 994 | "bitflags 2.5.0", 995 | "errno", 996 | "libc", 997 | "linux-raw-sys", 998 | "windows-sys", 999 | ] 1000 | 1001 | [[package]] 1002 | name = "scoped-tls" 1003 | version = "1.0.1" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 1006 | 1007 | [[package]] 1008 | name = "sdl2" 1009 | version = "0.36.0" 1010 | source = "registry+https://github.com/rust-lang/crates.io-index" 1011 | checksum = "8356b2697d1ead5a34f40bcc3c5d3620205fe0c7be0a14656223bfeec0258891" 1012 | dependencies = [ 1013 | "bitflags 1.3.2", 1014 | "lazy_static", 1015 | "libc", 1016 | "sdl2-sys", 1017 | ] 1018 | 1019 | [[package]] 1020 | name = "sdl2-sys" 1021 | version = "0.36.0" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "26bcacfdd45d539fb5785049feb0038a63931aa896c7763a2a12e125ec58bd29" 1024 | dependencies = [ 1025 | "cfg-if", 1026 | "libc", 1027 | "vcpkg", 1028 | "version-compare", 1029 | ] 1030 | 1031 | [[package]] 1032 | name = "serde" 1033 | version = "1.0.201" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" 1036 | dependencies = [ 1037 | "serde_derive", 1038 | ] 1039 | 1040 | [[package]] 1041 | name = "serde_derive" 1042 | version = "1.0.201" 1043 | source = "registry+https://github.com/rust-lang/crates.io-index" 1044 | checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" 1045 | dependencies = [ 1046 | "proc-macro2", 1047 | "quote", 1048 | "syn 2.0.61", 1049 | ] 1050 | 1051 | [[package]] 1052 | name = "serde_spanned" 1053 | version = "0.6.5" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" 1056 | dependencies = [ 1057 | "serde", 1058 | ] 1059 | 1060 | [[package]] 1061 | name = "smallvec" 1062 | version = "1.13.2" 1063 | source = "registry+https://github.com/rust-lang/crates.io-index" 1064 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1065 | 1066 | [[package]] 1067 | name = "strsim" 1068 | version = "0.11.1" 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" 1070 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1071 | 1072 | [[package]] 1073 | name = "syn" 1074 | version = "1.0.109" 1075 | source = "registry+https://github.com/rust-lang/crates.io-index" 1076 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1077 | dependencies = [ 1078 | "proc-macro2", 1079 | "quote", 1080 | "unicode-ident", 1081 | ] 1082 | 1083 | [[package]] 1084 | name = "syn" 1085 | version = "2.0.61" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" 1088 | dependencies = [ 1089 | "proc-macro2", 1090 | "quote", 1091 | "unicode-ident", 1092 | ] 1093 | 1094 | [[package]] 1095 | name = "tap" 1096 | version = "1.0.1" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" 1099 | 1100 | [[package]] 1101 | name = "tempfile" 1102 | version = "3.10.1" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" 1105 | dependencies = [ 1106 | "cfg-if", 1107 | "fastrand", 1108 | "rustix", 1109 | "windows-sys", 1110 | ] 1111 | 1112 | [[package]] 1113 | name = "thiserror" 1114 | version = "1.0.60" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" 1117 | dependencies = [ 1118 | "thiserror-impl", 1119 | ] 1120 | 1121 | [[package]] 1122 | name = "thiserror-impl" 1123 | version = "1.0.60" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" 1126 | dependencies = [ 1127 | "proc-macro2", 1128 | "quote", 1129 | "syn 2.0.61", 1130 | ] 1131 | 1132 | [[package]] 1133 | name = "toml" 1134 | version = "0.8.12" 1135 | source = "registry+https://github.com/rust-lang/crates.io-index" 1136 | checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" 1137 | dependencies = [ 1138 | "serde", 1139 | "serde_spanned", 1140 | "toml_datetime", 1141 | "toml_edit", 1142 | ] 1143 | 1144 | [[package]] 1145 | name = "toml_datetime" 1146 | version = "0.6.5" 1147 | source = "registry+https://github.com/rust-lang/crates.io-index" 1148 | checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" 1149 | dependencies = [ 1150 | "serde", 1151 | ] 1152 | 1153 | [[package]] 1154 | name = "toml_edit" 1155 | version = "0.22.12" 1156 | source = "registry+https://github.com/rust-lang/crates.io-index" 1157 | checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" 1158 | dependencies = [ 1159 | "indexmap", 1160 | "serde", 1161 | "serde_spanned", 1162 | "toml_datetime", 1163 | ] 1164 | 1165 | [[package]] 1166 | name = "tracing" 1167 | version = "0.1.40" 1168 | source = "registry+https://github.com/rust-lang/crates.io-index" 1169 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1170 | dependencies = [ 1171 | "pin-project-lite", 1172 | "tracing-attributes", 1173 | "tracing-core", 1174 | ] 1175 | 1176 | [[package]] 1177 | name = "tracing-attributes" 1178 | version = "0.1.27" 1179 | source = "registry+https://github.com/rust-lang/crates.io-index" 1180 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 1181 | dependencies = [ 1182 | "proc-macro2", 1183 | "quote", 1184 | "syn 2.0.61", 1185 | ] 1186 | 1187 | [[package]] 1188 | name = "tracing-core" 1189 | version = "0.1.32" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1192 | dependencies = [ 1193 | "once_cell", 1194 | ] 1195 | 1196 | [[package]] 1197 | name = "unicode-ident" 1198 | version = "1.0.12" 1199 | source = "registry+https://github.com/rust-lang/crates.io-index" 1200 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1201 | 1202 | [[package]] 1203 | name = "utf8parse" 1204 | version = "0.2.1" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 1207 | 1208 | [[package]] 1209 | name = "uuid" 1210 | version = "1.8.0" 1211 | source = "registry+https://github.com/rust-lang/crates.io-index" 1212 | checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" 1213 | dependencies = [ 1214 | "getrandom", 1215 | ] 1216 | 1217 | [[package]] 1218 | name = "vcpkg" 1219 | version = "0.2.15" 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" 1221 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1222 | 1223 | [[package]] 1224 | name = "version-compare" 1225 | version = "0.1.1" 1226 | source = "registry+https://github.com/rust-lang/crates.io-index" 1227 | checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" 1228 | 1229 | [[package]] 1230 | name = "vigem-client" 1231 | version = "0.1.4" 1232 | source = "registry+https://github.com/rust-lang/crates.io-index" 1233 | checksum = "b857e6f99efe1e1eb1e4dfb035de8ae7ec8ec56bd1928edcbd7c6e4427634d52" 1234 | dependencies = [ 1235 | "winapi", 1236 | ] 1237 | 1238 | [[package]] 1239 | name = "virtual-gamepad" 1240 | version = "0.1.0" 1241 | source = "git+https://github.com/Yamakaky/virtual-gamepad#ddc6dae0d6f5c6074c7627f393b259c8606cbaaf" 1242 | dependencies = [ 1243 | "anyhow", 1244 | "evdev", 1245 | "vigem-client", 1246 | ] 1247 | 1248 | [[package]] 1249 | name = "wasi" 1250 | version = "0.11.0+wasi-snapshot-preview1" 1251 | source = "registry+https://github.com/rust-lang/crates.io-index" 1252 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1253 | 1254 | [[package]] 1255 | name = "wayland-backend" 1256 | version = "0.3.3" 1257 | source = "registry+https://github.com/rust-lang/crates.io-index" 1258 | checksum = "9d50fa61ce90d76474c87f5fc002828d81b32677340112b4ef08079a9d459a40" 1259 | dependencies = [ 1260 | "cc", 1261 | "downcast-rs", 1262 | "rustix", 1263 | "scoped-tls", 1264 | "smallvec", 1265 | "wayland-sys", 1266 | ] 1267 | 1268 | [[package]] 1269 | name = "wayland-client" 1270 | version = "0.31.2" 1271 | source = "registry+https://github.com/rust-lang/crates.io-index" 1272 | checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f" 1273 | dependencies = [ 1274 | "bitflags 2.5.0", 1275 | "rustix", 1276 | "wayland-backend", 1277 | "wayland-scanner", 1278 | ] 1279 | 1280 | [[package]] 1281 | name = "wayland-protocols" 1282 | version = "0.31.2" 1283 | source = "registry+https://github.com/rust-lang/crates.io-index" 1284 | checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" 1285 | dependencies = [ 1286 | "bitflags 2.5.0", 1287 | "wayland-backend", 1288 | "wayland-client", 1289 | "wayland-scanner", 1290 | ] 1291 | 1292 | [[package]] 1293 | name = "wayland-protocols-misc" 1294 | version = "0.2.0" 1295 | source = "registry+https://github.com/rust-lang/crates.io-index" 1296 | checksum = "bfa5933740b200188c9b4c38601b8212e8c154d7de0d2cb171944e137a77de1e" 1297 | dependencies = [ 1298 | "bitflags 2.5.0", 1299 | "wayland-backend", 1300 | "wayland-client", 1301 | "wayland-protocols", 1302 | "wayland-scanner", 1303 | ] 1304 | 1305 | [[package]] 1306 | name = "wayland-protocols-plasma" 1307 | version = "0.2.0" 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" 1309 | checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" 1310 | dependencies = [ 1311 | "bitflags 2.5.0", 1312 | "wayland-backend", 1313 | "wayland-client", 1314 | "wayland-protocols", 1315 | "wayland-scanner", 1316 | ] 1317 | 1318 | [[package]] 1319 | name = "wayland-protocols-wlr" 1320 | version = "0.2.0" 1321 | source = "registry+https://github.com/rust-lang/crates.io-index" 1322 | checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" 1323 | dependencies = [ 1324 | "bitflags 2.5.0", 1325 | "wayland-backend", 1326 | "wayland-client", 1327 | "wayland-protocols", 1328 | "wayland-scanner", 1329 | ] 1330 | 1331 | [[package]] 1332 | name = "wayland-scanner" 1333 | version = "0.31.1" 1334 | source = "registry+https://github.com/rust-lang/crates.io-index" 1335 | checksum = "63b3a62929287001986fb58c789dce9b67604a397c15c611ad9f747300b6c283" 1336 | dependencies = [ 1337 | "proc-macro2", 1338 | "quick-xml", 1339 | "quote", 1340 | ] 1341 | 1342 | [[package]] 1343 | name = "wayland-sys" 1344 | version = "0.31.1" 1345 | source = "registry+https://github.com/rust-lang/crates.io-index" 1346 | checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af" 1347 | dependencies = [ 1348 | "dlib", 1349 | "log", 1350 | "pkg-config", 1351 | ] 1352 | 1353 | [[package]] 1354 | name = "winapi" 1355 | version = "0.3.9" 1356 | source = "registry+https://github.com/rust-lang/crates.io-index" 1357 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1358 | dependencies = [ 1359 | "winapi-i686-pc-windows-gnu", 1360 | "winapi-x86_64-pc-windows-gnu", 1361 | ] 1362 | 1363 | [[package]] 1364 | name = "winapi-i686-pc-windows-gnu" 1365 | version = "0.4.0" 1366 | source = "registry+https://github.com/rust-lang/crates.io-index" 1367 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1368 | 1369 | [[package]] 1370 | name = "winapi-x86_64-pc-windows-gnu" 1371 | version = "0.4.0" 1372 | source = "registry+https://github.com/rust-lang/crates.io-index" 1373 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1374 | 1375 | [[package]] 1376 | name = "windows" 1377 | version = "0.56.0" 1378 | source = "registry+https://github.com/rust-lang/crates.io-index" 1379 | checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132" 1380 | dependencies = [ 1381 | "windows-core", 1382 | "windows-targets", 1383 | ] 1384 | 1385 | [[package]] 1386 | name = "windows-core" 1387 | version = "0.56.0" 1388 | source = "registry+https://github.com/rust-lang/crates.io-index" 1389 | checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6" 1390 | dependencies = [ 1391 | "windows-implement", 1392 | "windows-interface", 1393 | "windows-result", 1394 | "windows-targets", 1395 | ] 1396 | 1397 | [[package]] 1398 | name = "windows-implement" 1399 | version = "0.56.0" 1400 | source = "registry+https://github.com/rust-lang/crates.io-index" 1401 | checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" 1402 | dependencies = [ 1403 | "proc-macro2", 1404 | "quote", 1405 | "syn 2.0.61", 1406 | ] 1407 | 1408 | [[package]] 1409 | name = "windows-interface" 1410 | version = "0.56.0" 1411 | source = "registry+https://github.com/rust-lang/crates.io-index" 1412 | checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" 1413 | dependencies = [ 1414 | "proc-macro2", 1415 | "quote", 1416 | "syn 2.0.61", 1417 | ] 1418 | 1419 | [[package]] 1420 | name = "windows-result" 1421 | version = "0.1.1" 1422 | source = "registry+https://github.com/rust-lang/crates.io-index" 1423 | checksum = "749f0da9cc72d82e600d8d2e44cadd0b9eedb9038f71a1c58556ac1c5791813b" 1424 | dependencies = [ 1425 | "windows-targets", 1426 | ] 1427 | 1428 | [[package]] 1429 | name = "windows-sys" 1430 | version = "0.52.0" 1431 | source = "registry+https://github.com/rust-lang/crates.io-index" 1432 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1433 | dependencies = [ 1434 | "windows-targets", 1435 | ] 1436 | 1437 | [[package]] 1438 | name = "windows-targets" 1439 | version = "0.52.5" 1440 | source = "registry+https://github.com/rust-lang/crates.io-index" 1441 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 1442 | dependencies = [ 1443 | "windows_aarch64_gnullvm", 1444 | "windows_aarch64_msvc", 1445 | "windows_i686_gnu", 1446 | "windows_i686_gnullvm", 1447 | "windows_i686_msvc", 1448 | "windows_x86_64_gnu", 1449 | "windows_x86_64_gnullvm", 1450 | "windows_x86_64_msvc", 1451 | ] 1452 | 1453 | [[package]] 1454 | name = "windows_aarch64_gnullvm" 1455 | version = "0.52.5" 1456 | source = "registry+https://github.com/rust-lang/crates.io-index" 1457 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 1458 | 1459 | [[package]] 1460 | name = "windows_aarch64_msvc" 1461 | version = "0.52.5" 1462 | source = "registry+https://github.com/rust-lang/crates.io-index" 1463 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 1464 | 1465 | [[package]] 1466 | name = "windows_i686_gnu" 1467 | version = "0.52.5" 1468 | source = "registry+https://github.com/rust-lang/crates.io-index" 1469 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 1470 | 1471 | [[package]] 1472 | name = "windows_i686_gnullvm" 1473 | version = "0.52.5" 1474 | source = "registry+https://github.com/rust-lang/crates.io-index" 1475 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 1476 | 1477 | [[package]] 1478 | name = "windows_i686_msvc" 1479 | version = "0.52.5" 1480 | source = "registry+https://github.com/rust-lang/crates.io-index" 1481 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 1482 | 1483 | [[package]] 1484 | name = "windows_x86_64_gnu" 1485 | version = "0.52.5" 1486 | source = "registry+https://github.com/rust-lang/crates.io-index" 1487 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 1488 | 1489 | [[package]] 1490 | name = "windows_x86_64_gnullvm" 1491 | version = "0.52.5" 1492 | source = "registry+https://github.com/rust-lang/crates.io-index" 1493 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 1494 | 1495 | [[package]] 1496 | name = "windows_x86_64_msvc" 1497 | version = "0.52.5" 1498 | source = "registry+https://github.com/rust-lang/crates.io-index" 1499 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 1500 | 1501 | [[package]] 1502 | name = "wyz" 1503 | version = "0.5.1" 1504 | source = "registry+https://github.com/rust-lang/crates.io-index" 1505 | checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" 1506 | dependencies = [ 1507 | "tap", 1508 | ] 1509 | 1510 | [[package]] 1511 | name = "xkbcommon" 1512 | version = "0.7.0" 1513 | source = "registry+https://github.com/rust-lang/crates.io-index" 1514 | checksum = "13867d259930edc7091a6c41b4ce6eee464328c6ff9659b7e4c668ca20d4c91e" 1515 | dependencies = [ 1516 | "libc", 1517 | "memmap2", 1518 | "xkeysym", 1519 | ] 1520 | 1521 | [[package]] 1522 | name = "xkeysym" 1523 | version = "0.2.0" 1524 | source = "registry+https://github.com/rust-lang/crates.io-index" 1525 | checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621" 1526 | --------------------------------------------------------------------------------