Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add touchpad support to OrangePi NEO #106

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/hhd/controller/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@
"right_gyro_y",
"right_gyro_z",
"right_imu_ts",
"right_touchpad_x",
"right_touchpad_y",
]

RelAxis = Literal["mouse_x", "mouse_y", "mouse_wheel", "mouse_wheel_hires"]
Expand Down Expand Up @@ -96,6 +94,9 @@
"touchpad_touch",
"touchpad_left",
"touchpad_right",
"left_touchpad_touch",
"left_touchpad_left",
"left_touchpad_right",
]

MouseButton = Literal["btn_left", "btn_right", "btn_middle", "btn_side", "btn_extra"]
Expand Down
30 changes: 28 additions & 2 deletions src/hhd/controller/virtual/dualsense/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@
DS5_EDGE_MIN_TIMESTAMP_INTERVAL = 1500
MAX_IMU_SYNC_DELAY = 2

LEFT_TOUCH_CORRECTION = correct_touchpad(
DS5_EDGE_TOUCH_WIDTH, DS5_EDGE_TOUCH_HEIGHT, 1, "left"
)

logger = logging.getLogger(__name__)

_cache = ControllerCache()
Expand Down Expand Up @@ -297,7 +301,7 @@ def produce(self, fds: Sequence[int]) -> Sequence[Event]:
"red": red,
"blue": blue,
"green": green,
"red2": 0, # disable for OXP
"red2": 0, # disable for OXP
"blue2": 0,
"green2": 0,
"oxp": None,
Expand Down Expand Up @@ -433,6 +437,28 @@ def consume(self, events: Sequence[Event]):
(y & 0x0F) << 4
)
new_rep[self.ofs + 35] = y >> 4
case "left_touchpad_x":
tc = LEFT_TOUCH_CORRECTION
x = int(
min(max(ev["value"], tc.x_clamp[0]), tc.x_clamp[1])
* tc.x_mult
+ tc.x_ofs
)
new_rep[self.ofs + 37] = x & 0xFF
new_rep[self.ofs + 38] = (new_rep[self.ofs + 34] & 0xF0) | (
x >> 8
)
case "left_touchpad_y":
tc = LEFT_TOUCH_CORRECTION
y = int(
min(max(ev["value"], tc.y_clamp[0]), tc.y_clamp[1])
* tc.y_mult
+ tc.y_ofs
)
new_rep[self.ofs + 38] = (new_rep[self.ofs + 34] & 0x0F) | (
(y & 0x0F) << 4
)
new_rep[self.ofs + 39] = y >> 4
case "gyro_ts" | "accel_ts" | "imu_ts":
send = True
self.last_imu = time.perf_counter()
Expand Down Expand Up @@ -488,7 +514,7 @@ def consume(self, events: Sequence[Event]):
)
set_button(
new_rep,
self.btn_map["touchpad_touch2"],
self.btn_map["left_touchpad_touch"],
ev["value"],
)

Expand Down
3 changes: 2 additions & 1 deletion src/hhd/controller/virtual/dualsense/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@ def prefill_ds5_report(bluetooth: bool):
"extra_l3": BM(((ofs + 9) << 3) + 4),
"share": BM(((ofs + 9) << 3) + 5),
"touchpad_touch": BM(((ofs + 32) << 3), flipped=True),
"touchpad_touch2": BM(((ofs + 36) << 3), flipped=True),
# "touchpad_touch2": BM(((ofs + 36) << 3), flipped=True),
"left_touchpad_touch": BM(((ofs + 36) << 3), flipped=True),
"touchpad_left": BM(((ofs + 9) << 3) + 6),
"mode": BM(((ofs + 9) << 3) + 7),
}
Expand Down
11 changes: 11 additions & 0 deletions src/hhd/controller/virtual/uinput/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,3 +528,14 @@ class AX(NamedTuple):
"touchpad_right": B("BTN_RIGHT"),
"touchpad_left": B("BTN_LEFT"),
}

LEFT_TOUCHPAD_AXIS_MAP: dict[Axis, AX] = {
"left_touchpad_x": AX(B("ABS_X"), 1023, bounds=(0, 2048)),
"left_touchpad_y": AX(B("ABS_Y"), 1023, bounds=(0, 2048)),
}

LEFT_TOUCHPAD_BUTTON_MAP: dict[Button, int] = {
"left_touchpad_touch": B("BTN_TOUCH"),
"left_touchpad_right": B("BTN_RIGHT"),
"left_touchpad_left": B("BTN_LEFT"),
}
8 changes: 8 additions & 0 deletions src/hhd/device/orange_pi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
get_outputs_config,
load_relative_yaml,
)
from hhd.plugins.inputs import get_touchpad_config
from hhd.plugins.settings import HHDSettings

from .const import CONFS, DEFAULT_MAPPINGS, get_default_config
Expand Down Expand Up @@ -52,6 +53,13 @@ def settings(self) -> HHDSettings:
)
)

if self.dconf.get("touchpad", False):
base["controllers"]["handheld"]["children"][
"touchpad"
] = get_touchpad_config(dual_touchpad=True)
else:
del base["controllers"]["handheld"]["children"]["touchpad"]

base["controllers"]["handheld"]["children"]["imu_axis"] = get_gyro_config(
self.dconf.get("mapping", DEFAULT_MAPPINGS)
)
Expand Down
79 changes: 70 additions & 9 deletions src/hhd/device/orange_pi/base.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
import logging
import os
import re
import select
import time
from threading import Event as TEvent

import evdev

from hhd.controller import Multiplexer, DEBUG_MODE
from hhd.controller.base import TouchpadAction
from hhd.controller.lib.hide import unhide_all
from hhd.controller.physical.evdev import B as EC
from hhd.controller.physical.evdev import GenericGamepadEvdev
from hhd.controller.physical.imu import CombinedImu, HrtimerTrigger
from hhd.controller.physical.rgb import LedDevice
from hhd.plugins import Config, Context, Emitter, get_gyro_state, get_outputs

from .const import AT_BTN_MAPPINGS, GAMEPAD_BTN_MAPPINGS, DEFAULT_MAPPINGS
from .const import (
AT_BTN_MAPPINGS,
GAMEPAD_BTN_MAPPINGS,
DEFAULT_MAPPINGS,
OPI_TOUCHPAD_AXIS_MAP,
OPI_TOUCHPAD_BUTTON_MAP,
LEFT_TOUCHPAD_AXIS_MAP,
LEFT_TOUCHPAD_BUTTON_MAP,
)

ERROR_DELAY = 1
SELECT_TIMEOUT = 1
Expand All @@ -27,6 +37,9 @@
KBD_VID = 0x0001
KBD_PID = 0x0001

TOUCHPAD_VID = 0x0911
TOUCHPAD_PID = 0x5288

BACK_BUTTON_DELAY = 0.1


Expand Down Expand Up @@ -90,13 +103,15 @@ def controller_loop(
conf: Config, should_exit: TEvent, updated: TEvent, dconf: dict, emit: Emitter
):
debug = DEBUG_MODE
has_touchpad = dconf.get("touchpad", False)

# Output
d_producers, d_outs, d_params = get_outputs(
conf["controller_mode"],
None,
conf["touchpad"] if has_touchpad else None,
conf["imu"].to(bool),
emit=emit,
dual_touchpad=True
)
motion = d_params.get("uses_motion", True) and conf.get("imu", True)

Expand Down Expand Up @@ -136,13 +151,26 @@ def controller_loop(
btn_map=dconf.get("gamepad_mapping", GAMEPAD_BTN_MAPPINGS),
)

multiplexer = Multiplexer(
trigger="analog_to_discrete",
dpad="analog_to_discrete",
share_to_qam=True,
nintendo_mode=conf["nintendo_mode"].to(bool),
emit=emit,
params=d_params,
d_touch = GenericGamepadEvdev(
vid=[TOUCHPAD_VID],
pid=[TOUCHPAD_PID],
name=[re.compile("OPI0002.+Touchpad")],
capabilities={EC("EV_KEY"): [EC("BTN_MOUSE")]},
btn_map=OPI_TOUCHPAD_BUTTON_MAP,
axis_map=OPI_TOUCHPAD_AXIS_MAP,
aspect_ratio=1,
required=True,
)

d_touch_left = GenericGamepadEvdev(
vid=[TOUCHPAD_VID],
pid=[TOUCHPAD_PID],
name=[re.compile("OPI0001.+Touchpad")],
capabilities={EC("EV_KEY"): [EC("BTN_MOUSE")]},
btn_map=LEFT_TOUCHPAD_BUTTON_MAP,
axis_map=LEFT_TOUCHPAD_AXIS_MAP,
aspect_ratio=1,
required=True,
)

# d_volume_btn = UInputDevice(
Expand All @@ -162,6 +190,36 @@ def controller_loop(
if d_rgb.supported:
logger.info(f"RGB Support activated through kernel driver.")


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",
share_to_qam=True,
touchpad_short=touch_actions["short"].to(TouchpadAction),
touchpad_hold=touch_actions["hold"].to(TouchpadAction),
nintendo_mode=conf["nintendo_mode"].to(bool),
emit=emit,
params=d_params,
qam_multi_tap=False,
)
else:
multiplexer = Multiplexer(
trigger="analog_to_discrete",
dpad="analog_to_discrete",
share_to_qam=True,
nintendo_mode=conf["nintendo_mode"].to(bool),
emit=emit,
params=d_params,
qam_multi_tap=False,
)

REPORT_FREQ_MIN = 25
REPORT_FREQ_MAX = 400

Expand Down Expand Up @@ -191,6 +249,9 @@ def prepare(m):
start_imu = d_timer.open()
if start_imu:
prepare(d_imu)
if has_touchpad and d_params["uses_touch"]:
prepare(d_touch)
prepare(d_touch_left)
prepare(d_kbd_1)
prepare(d_kbd_2)
for d in d_producers:
Expand Down
32 changes: 31 additions & 1 deletion src/hhd/device/orange_pi/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,36 @@
from hhd.controller.physical.evdev import B, to_map
from hhd.plugins import gen_gyro_state

OPI_TOUCHPAD_BUTTON_MAP: dict[int, Button] = to_map(
{
"touchpad_touch": [B("BTN_TOOL_FINGER")], # also BTN_TOUCH
"touchpad_right": [B("BTN_TOOL_DOUBLETAP"), B("BTN_RIGHT")],
"touchpad_left": [B("BTN_MOUSE")],
}
)

OPI_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
}
)

LEFT_TOUCHPAD_BUTTON_MAP: dict[int, Button] = to_map(
{
"left_touchpad_touch": [B("BTN_TOOL_FINGER")], # also BTN_TOUCH
"left_touchpad_right": [B("BTN_TOOL_DOUBLETAP"), B("BTN_RIGHT")],
"left_touchpad_left": [B("BTN_MOUSE")],
}
)

LEFT_TOUCHPAD_AXIS_MAP: dict[int, Axis] = to_map(
{
"left_touchpad_x": [B("ABS_X")], # also ABS_MT_POSITION_X
"left_touchpad_y": [B("ABS_Y")], # also ABS_MT_POSITION_Y
}
)

DEFAULT_MAPPINGS: dict[str, tuple[Axis, str | None, float, float | None]] = {
"accel_x": ("accel_x", "accel", 1, None),
"accel_y": ("accel_z", "accel", 1, None),
Expand Down Expand Up @@ -31,7 +61,7 @@
CONFS = {
# New hardware new firmware, the unit below was dissassembled
# "G1621-02": {"name": "OrangePi G1621-02/G1621-02", "hrtimer": True},
"NEO-01": {"name": "OrangePi NEO-01/NEO-01", "hrtimer": True},
"NEO-01": {"name": "OrangePi NEO-01/NEO-01", "hrtimer": True, "touchpad": True},
}


Expand Down
2 changes: 2 additions & 0 deletions src/hhd/device/orange_pi/controllers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ children:

imu_axis:

touchpad:

nintendo_mode:
type: bool
title: Nintendo Mode (A-B Swap)
Expand Down
8 changes: 5 additions & 3 deletions src/hhd/plugins/inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ def get_vendor():
return "Uknown"


def get_touchpad_config():
return load_relative_yaml("touchpad.yml")

def get_touchpad_config(dual_touchpad: bool = False):
conf = load_relative_yaml("touchpad.yml")
if dual_touchpad:
del conf["modes"]['controller']["children"]["correction"]
return conf

def get_gyro_config(
mapping: dict[str, tuple[Axis, str | None, float, float | None]] | None
Expand Down
21 changes: 20 additions & 1 deletion src/hhd/plugins/outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
TOUCHPAD_BUTTON_MAP,
TOUCHPAD_CAPABILITIES,
XBOX_ELITE_BUTTON_MAP,
LEFT_TOUCHPAD_BUTTON_MAP,
LEFT_TOUCHPAD_AXIS_MAP,
UInputDevice,
)
from .plugin import is_steam_gamepad_running, open_steam_kbd
Expand All @@ -38,6 +40,7 @@ def get_outputs(
rgb_modes: Mapping[RgbMode, Sequence[RgbSettings]] | None = None,
rgb_zones: RgbZones = "mono",
controller_disabled: bool = False,
dual_touchpad: bool = False,
) -> tuple[Sequence[Producer], Sequence[Consumer], Mapping[str, Any]]:
producers = []
consumers = []
Expand All @@ -47,7 +50,10 @@ def get_outputs(
desktop_disable = False
if touch_conf is not None:
touchpad = touch_conf["mode"].to(str)
correction = touch_conf["controller.correction"].to(TouchpadCorrectionType)
if dual_touchpad:
correction = "right"
else:
correction = touch_conf["controller.correction"].to(TouchpadCorrectionType)
if touchpad in ("emulation", "controller"):
desktop_disable = touch_conf[touchpad]["desktop_disable"].to(bool)
else:
Expand Down Expand Up @@ -220,6 +226,19 @@ def get_outputs(
)
producers.append(d)
consumers.append(d)
if dual_touchpad:
d = UInputDevice(
name="Handheld Daemon Left Touchpad",
phys="phys-hhd-left",
capabilities=TOUCHPAD_CAPABILITIES,
pid=HHD_PID_TOUCHPAD,
btn_map=LEFT_TOUCHPAD_BUTTON_MAP,
axis_map=LEFT_TOUCHPAD_AXIS_MAP,
output_timestamps=True,
ignore_cmds=True,
)
producers.append(d)
consumers.append(d)
uses_touch = True

return (
Expand Down