From 9c1a5e7ac8914ce005fc781db2d98ff7fa9e4202 Mon Sep 17 00:00:00 2001 From: Justin Weiss Date: Wed, 17 Jan 2024 22:24:14 -0800 Subject: [PATCH 1/2] Add IMU support for the GPD Win Mini This requires the BMI260 kernel module driver. --- pyproject.toml | 2 +- src/hhd/controller/physical/imu.py | 4 ++-- src/hhd/device/gpd/win/base.py | 23 +++++++++++++++++++++-- src/hhd/device/gpd/win/controllers.yml | 15 +++++++++++++++ 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index aaaf32e9..e9c9f3d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ hhd = "hhd.__main__:main" [project.entry-points."hhd.plugins"] legion_go = "hhd.device.legion_go:autodetect" rog_ally = "hhd.device.rog_ally:autodetect" -# gpd_win = "hhd.device.gpd.win:autodetect" +gpd_win = "hhd.device.gpd.win:autodetect" powerbuttond = "hhd.plugins.powerbutton:autodetect" # display = "hhd.plugins.display:autodetect" diff --git a/src/hhd/controller/physical/imu.py b/src/hhd/controller/physical/imu.py index 69f6071f..cbab2757 100644 --- a/src/hhd/controller/physical/imu.py +++ b/src/hhd/controller/physical/imu.py @@ -32,7 +32,7 @@ class DeviceInfo(NamedTuple): ACCEL_NAMES = ["accel_3d"] GYRO_NAMES = ["gyro_3d"] -IMU_NAMES = ["bmi323-imu"] +IMU_NAMES = ["bmi323-imu", "BMI0160"] ACCEL_MAPPINGS: dict[str, tuple[Axis, str | None, float, float | None]] = { "accel_x": ("accel_z", "accel", 1, 3), @@ -74,7 +74,7 @@ def find_sensor(sensors: Sequence[str]): with open(name_fn, "r") as f: name = f.read().strip() - if name in sensors: + if any(sensor in name for sensor in sensors): logger.info(f"Found device '{name}' at\n{sensor_dir}") return sensor_dir, name diff --git a/src/hhd/device/gpd/win/base.py b/src/hhd/device/gpd/win/base.py index 184bd34d..73df6ec5 100644 --- a/src/hhd/device/gpd/win/base.py +++ b/src/hhd/device/gpd/win/base.py @@ -4,11 +4,12 @@ from threading import Event as TEvent from typing import Sequence -from hhd.controller import Event, Multiplexer, can_read +from hhd.controller import Axis, Event, Multiplexer, can_read from hhd.controller.base import Event from hhd.controller.physical.evdev import B as EC from hhd.controller.physical.hidraw import GenericGamepadHidraw from hhd.controller.physical.evdev import GenericGamepadEvdev +from hhd.controller.physical.imu import CombinedImu from hhd.plugins import Config, Context, Emitter, get_outputs ERROR_DELAY = 1 @@ -21,6 +22,16 @@ GAMEPAD_VID = 0x045E GAMEPAD_PID = 0x028E +GPD_WIN_MAPPINGS: dict[str, tuple[Axis, str | None, float, float | None]] = { + "accel_x": ("accel_z", "accel", 1, 3), + "accel_y": ("accel_x", "accel", 1, 3), + "accel_z": ("accel_y", "accel", 1, 3), + "anglvel_x": ("gyro_x", "anglvel", 1, None), + "anglvel_y": ("gyro_z", "anglvel", -1, None), + "anglvel_z": ("gyro_y", "anglvel", -1, None), + "timestamp": ("gyro_ts", None, 1, None), +} + BACK_BUTTON_DELAY = 0.1 # /dev/input/event17 Microsoft X-Box 360 pad usb-0000:73:00.3-4.1/input0 @@ -152,7 +163,10 @@ def controller_loop(conf: Config, should_exit: TEvent, updated: TEvent): debug = conf.get("debug", False) # Output - d_producers, d_outs, d_params = get_outputs(conf["controller_mode"], None, False) + d_producers, d_outs, d_params = get_outputs(conf["controller_mode"], None, conf["imu"].to(bool)) + + # Imu + d_imu = CombinedImu(conf["imu_hz"].to(int), GPD_WIN_MAPPINGS, gyro_scale="0.000266") # Inputs d_xinput = GenericGamepadEvdev( @@ -190,6 +204,9 @@ def controller_loop(conf: Config, should_exit: TEvent, updated: TEvent): REPORT_FREQ_MIN = 25 REPORT_FREQ_MAX = 400 + if conf["imu"].to(bool): + REPORT_FREQ_MAX = max(REPORT_FREQ_MAX, conf["imu_hz"].to(float)) + REPORT_DELAY_MAX = 1 / REPORT_FREQ_MIN REPORT_DELAY_MIN = 1 / REPORT_FREQ_MAX @@ -207,6 +224,8 @@ def prepare(m): try: d_vend.open() prepare(d_xinput) + if conf.get("imu", False): + prepare(d_imu) prepare(d_kbd_1) for d in d_producers: prepare(d) diff --git a/src/hhd/device/gpd/win/controllers.yml b/src/hhd/device/gpd/win/controllers.yml index a952e56f..c544f454 100644 --- a/src/hhd/device/gpd/win/controllers.yml +++ b/src/hhd/device/gpd/win/controllers.yml @@ -15,6 +15,21 @@ children: # # Common settings # + imu: + type: bool + title: Motion Support + hint: >- + Enable gyroscope/accelerometer (IMU) support (.3% background CPU use) + default: True + + imu_hz: + type: discrete + title: Motion Hz + hint: >- + Sets the sampling frequency for the IMU. + Check `/sys/bus/iio/devices/iio:device0/in_anglvel_sampling_frequency_available`. + options: [50, 100, 200, 400, 800, 1600] + default: 400 debug: type: bool From d8aea4f393ee50aaa3c76e7346bec0e9b489d917 Mon Sep 17 00:00:00 2001 From: Justin Weiss Date: Thu, 18 Jan 2024 22:38:00 -0800 Subject: [PATCH 2/2] Add support for virtual / controller trackpad to Win Mini This enables tap-to-click when using the virtual trackpad. I haven't been able to get hold / long press actions to work. --- src/hhd/device/gpd/win/__init__.py | 9 ++++- src/hhd/device/gpd/win/base.py | 51 +++++++++++++++++++++++--- src/hhd/device/gpd/win/const.py | 16 ++++++++ src/hhd/device/gpd/win/controllers.yml | 2 + 4 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 src/hhd/device/gpd/win/const.py diff --git a/src/hhd/device/gpd/win/__init__.py b/src/hhd/device/gpd/win/__init__.py index 69f3ddea..877c97ee 100644 --- a/src/hhd/device/gpd/win/__init__.py +++ b/src/hhd/device/gpd/win/__init__.py @@ -8,6 +8,7 @@ HHDPlugin, load_relative_yaml, get_outputs_config, + get_touchpad_config, ) from hhd.plugins.settings import HHDSettings @@ -41,6 +42,12 @@ def settings(self) -> HHDSettings: base["controllers"]["gpd_win"]["children"]["controller_mode"].update( get_outputs_config(can_disable=False) ) + + if self.dmi == "G1617-01": + base["controllers"]["gpd_win"]["children"]["touchpad"] = get_touchpad_config() + else: + del base["controllers"]["gpd_win"]["children"]["touchpad"] + return base def update(self, conf: Config): @@ -93,4 +100,4 @@ def autodetect(existing: Sequence[HHDPlugin]) -> Sequence[HHDPlugin]: if not name: return [] - return [GpdWinControllersPlugin(name, dmi)] + return [GpdWinControllersPlugin(dmi, name)] diff --git a/src/hhd/device/gpd/win/base.py b/src/hhd/device/gpd/win/base.py index 73df6ec5..3303f38b 100644 --- a/src/hhd/device/gpd/win/base.py +++ b/src/hhd/device/gpd/win/base.py @@ -1,17 +1,23 @@ import logging +import re import select import time from threading import Event as TEvent from typing import Sequence from hhd.controller import Axis, Event, Multiplexer, can_read -from hhd.controller.base import Event +from hhd.controller.base import Event, TouchpadAction from hhd.controller.physical.evdev import B as EC from hhd.controller.physical.hidraw import GenericGamepadHidraw from hhd.controller.physical.evdev import GenericGamepadEvdev from hhd.controller.physical.imu import CombinedImu from hhd.plugins import Config, Context, Emitter, get_outputs +from .const import ( + GPD_TOUCHPAD_AXIS_MAP, + GPD_TOUCHPAD_BUTTON_MAP, +) + ERROR_DELAY = 1 SELECT_TIMEOUT = 1 @@ -21,6 +27,8 @@ GPD_WIN_4_PID = 0x0135 GAMEPAD_VID = 0x045E GAMEPAD_PID = 0x028E +TOUCHPAD_PID = 0x0255 +TOUCHPAD_VID = 0x093A GPD_WIN_MAPPINGS: dict[str, tuple[Axis, str | None, float, float | None]] = { "accel_x": ("accel_z", "accel", 1, 3), @@ -161,9 +169,12 @@ def plugin_run( def controller_loop(conf: Config, should_exit: TEvent, updated: TEvent): debug = conf.get("debug", False) + has_touchpad = "touchpad" in conf # Output - d_producers, d_outs, d_params = get_outputs(conf["controller_mode"], None, conf["imu"].to(bool)) + d_producers, d_outs, d_params = get_outputs( + conf["controller_mode"], conf["touchpad"] if has_touchpad else None, conf["imu"].to(bool) + ) # Imu d_imu = CombinedImu(conf["imu_hz"].to(int), GPD_WIN_MAPPINGS, gyro_scale="0.000266") @@ -178,6 +189,18 @@ def controller_loop(conf: Config, should_exit: TEvent, updated: TEvent): hide=True, ) + if has_touchpad: + d_touch = GenericGamepadEvdev( + vid=[TOUCHPAD_VID], + pid=[TOUCHPAD_PID], + name=[re.compile(".+Touchpad")], # "PNP0C50:00 093A:0255 Touchpad" + capabilities={EC("EV_KEY"): [EC("BTN_MOUSE")]}, + btn_map=GPD_TOUCHPAD_BUTTON_MAP, + axis_map=GPD_TOUCHPAD_AXIS_MAP, + aspect_ratio=1.333, + required=False, + ) + # Vendor d_vend = GpdWin4Hidraw( vid=[GPD_WIN_4_VID], @@ -196,10 +219,24 @@ def controller_loop(conf: Config, should_exit: TEvent, updated: TEvent): # btn_map={EC("KEY_SYSRQ"): "extra_l1", EC("KEY_PAUSE"): "extra_r1"}, ) - multiplexer = Multiplexer( - trigger="analog_to_discrete", - dpad="analog_to_discrete", - ) + if has_touchpad: + touch_actions = ( + conf["touchpad.controller"] + if conf["touchpad.mode"].to(TouchpadAction) == "controller" + else conf["touchpad.emulation"] + ) + + multiplexer = Multiplexer( + trigger="analog_to_discrete", + dpad="analog_to_discrete", + touchpad_short=touch_actions["short"].to(TouchpadAction), + touchpad_right=touch_actions["hold"].to(TouchpadAction), + ) + else: + multiplexer = Multiplexer( + trigger="analog_to_discrete", + dpad="analog_to_discrete", + ) REPORT_FREQ_MIN = 25 REPORT_FREQ_MAX = 400 @@ -226,6 +263,8 @@ def prepare(m): prepare(d_xinput) if conf.get("imu", False): prepare(d_imu) + if has_touchpad and d_params["uses_touch"]: + prepare(d_touch) prepare(d_kbd_1) for d in d_producers: prepare(d) diff --git a/src/hhd/device/gpd/win/const.py b/src/hhd/device/gpd/win/const.py new file mode 100644 index 00000000..5451c2b0 --- /dev/null +++ b/src/hhd/device/gpd/win/const.py @@ -0,0 +1,16 @@ +from hhd.controller import Axis, Button, Configuration +from hhd.controller.physical.evdev import B, to_map + +GPD_TOUCHPAD_BUTTON_MAP: dict[int, Button] = to_map( + { + "touchpad_touch": [B("BTN_TOOL_FINGER")], # also BTN_TOUCH + "touchpad_right": [B("BTN_TOOL_DOUBLETAP")], + } +) + +GPD_TOUCHPAD_AXIS_MAP: dict[int, Axis] = to_map( + { + "touchpad_x": [B("ABS_X")], # also ABS_MT_POSITION_X + "touchpad_y": [B("ABS_Y")], # also ABS_MT_POSITION_Y + } +) diff --git a/src/hhd/device/gpd/win/controllers.yml b/src/hhd/device/gpd/win/controllers.yml index c544f454..dedf4639 100644 --- a/src/hhd/device/gpd/win/controllers.yml +++ b/src/hhd/device/gpd/win/controllers.yml @@ -31,6 +31,8 @@ children: options: [50, 100, 200, 400, 800, 1600] default: 400 + touchpad: + debug: type: bool title: Debug