Skip to content
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ dist
.vscode
notebooks
*.mo
build/
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ hhdctl = "hhd.http.ctl:main"
adjustor = "adjustor.hhd:autodetect"
legion_go = "hhd.device.legion_go:autodetect"
rog_ally = "hhd.device.rog_ally:autodetect"
rog_laptops = "hhd.device.rog_laptops:autodetect"
gpd_win = "hhd.device.gpd.win:autodetect"
msi_claw = "hhd.device.claw:autodetect"
onexplayer = "hhd.device.oxp:autodetect"
Expand Down
65 changes: 46 additions & 19 deletions src/hhd/device/aura/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from hhd.controller.base import RgbMode
from hhd.controller.lib.hid import Device as HIDDevice
from hhd.controller.lib.hid import enumerate_unique
from hhd.controller.lib.hid import enumerate
from hhd.i18n import _
from hhd.plugins import (
Config,
Expand Down Expand Up @@ -48,9 +48,11 @@

AURA_CONFIGS = {
# Z13 Lightbar
0x18C6: (_("Lightbar"), AURA_CONFIGS_LIGHTBAR),
0x18C6: (_("Lightbar"), AURA_CONFIGS_LIGHTBAR, "feature"),
# ROG Keyboards (Z13 incl.)
0x1A30: (_("Keyboard"), AURA_CONFIGS_USB),
0x1A30: (_("Keyboard"), AURA_CONFIGS_USB, "feature"),
# GA403 Keyboard (note: 193B is Slash/Anime, not keyboard Aura)
0x19B6: (_("Keyboard"), AURA_CONFIGS_USB, "output"),
}

AURA_CONFGIS_WMI = {
Expand All @@ -62,6 +64,7 @@

RGB_INPUT_ID = 0x5A
RGB_AURA_ID = 0x5D
# Note: 0x5E is for Slash/Anime, keyboard Aura uses 0x5D for all devices including 193B

RGB_HANDSHAKE = lambda key: bytes(
[
Expand Down Expand Up @@ -112,6 +115,7 @@ def init_rgb_dev(key: int, dev: HIDDevice):
class AuraDevice(TypedDict):
vid: int
pid: int
report_id: int
application: int
fn: str
name: str
Expand All @@ -121,6 +125,8 @@ class AuraDevice(TypedDict):
modes: dict[str, list[str]]
dev: HIDDevice
last_mode: RgbMode | None
method: Literal["feature", "output"]
last_cmd: bytes | None


def buf(x):
Expand Down Expand Up @@ -156,7 +162,7 @@ def get_aura_devices(
out = {}

found = set()
for d in enumerate_unique(vid=ASUS_VID):
for d in enumerate(vid=ASUS_VID):
application = d.get("usage_page", 0x0000) << 16 | d.get("usage", 0x0000)
if d["path"] in existing:
ref = existing[d["path"]]
Expand All @@ -175,7 +181,7 @@ def get_aura_devices(
if d["product_id"] not in AURA_CONFIGS:
continue

name, modes = AURA_CONFIGS[d["product_id"]]
name, modes, method = AURA_CONFIGS[d["product_id"]]
cfg_name = f"{d['vendor_id']:04x}_{d['product_id']:04x}"

try:
Expand All @@ -195,15 +201,18 @@ def get_aura_devices(
cfg_name=cfg_name,
vid=d["vendor_id"],
pid=d["product_id"],
report_id=RGB_AURA_ID, # 0x5D for all keyboard Aura devices
application=application,
fn=d["path"],
init=True,
disabled=False,
modes=modes,
dev=dev,
last_mode=None,
method=method,
last_cmd=None,
)
logger.info(
logger.debug(
"Found Aura device %s (%s, %04x:%04x) with modes:\n%s",
name,
d["path"].decode("utf-8"),
Expand All @@ -215,7 +224,7 @@ def get_aura_devices(
updated = False
for k in list(existing.keys()):
if k not in found:
logger.info(
logger.debug(
"Removing Aura device %s (%s, %04x:%04x) from known devices",
existing[k]["fn"],
existing[k]["name"],
Expand All @@ -242,6 +251,7 @@ def rgb_command(
o_red: int,
o_green: int,
o_blue: int,
report_id: int = RGB_AURA_ID,
):
c_direction = 0x00
set_speed = True
Expand Down Expand Up @@ -298,7 +308,7 @@ def rgb_command(
c_zone = 0x00
return buf(
[
RGB_AURA_ID,
report_id,
0xB3,
c_zone, # zone
c_mode, # mode
Expand Down Expand Up @@ -378,6 +388,7 @@ def get_aura_mode_cmd(cfg, dev: AuraDevice):
red2 if color2_set else 0,
green2 if color2_set else 0,
blue2 if color2_set else 0,
dev["report_id"],
),
mode,
always_init,
Expand All @@ -404,7 +415,7 @@ def set_aura_brightness(
try:
with open(WMI_LOCATION, "w") as f:
f.write(str(c))
logger.info("Set Aura brightness to %s", brightness)
logger.debug("Set Aura brightness to %s", brightness)
return False
except Exception as e:
logger.error("Failed to set WMI brightness: %s", e)
Expand Down Expand Up @@ -652,22 +663,37 @@ def update(self, conf: Config):

try:
if init:
init_rgb_dev(RGB_AURA_ID, d["dev"])
if d["method"] == "feature":
init_rgb_dev(d["report_id"], d["dev"])

if power_settings is not None:
# Set power settings on init
d["dev"].send_feature_report(
get_aura_power_cmd(power_settings),
)
cmd_power = get_aura_power_cmd(power_settings)
if d["method"] == "output":
d["dev"].write(cmd_power)
else:
d["dev"].send_feature_report(cmd_power)

# Needs time to initialize, get it next cycle
self.queue_apply[k] = curr_t + RGB_APPLY_DELAY
else:
d["dev"].send_feature_report(cmd)
changed_cmd = cmd != d["last_cmd"]
if changed_cmd or chanded_mode or always_init or init:
if d["method"] == "output":
d["dev"].write(cmd)
else:
d["dev"].send_feature_report(cmd)
d["last_cmd"] = cmd

d["last_mode"] = new_mode

if chanded_mode or queued or always_init or init:
d["dev"].send_feature_report(RGB_SET(RGB_AURA_ID))
d["dev"].send_feature_report(RGB_APPLY(RGB_AURA_ID))
if changed_cmd or chanded_mode or queued or always_init or init:
if d["method"] == "output":
d["dev"].write(RGB_SET(d["report_id"]))
d["dev"].write(RGB_APPLY(d["report_id"]))
else:
d["dev"].send_feature_report(RGB_SET(d["report_id"]))
d["dev"].send_feature_report(RGB_APPLY(d["report_id"]))
else:
self.queue_apply[k] = curr_t + RGB_APPLY_DELAY

Expand Down Expand Up @@ -701,7 +727,7 @@ def update(self, conf: Config):

self.devices, updated = get_aura_devices(self.devices)
if updated:
logger.info("Found %d Aura devices", len(self.devices))
logger.debug("Found %d Aura devices", len(self.devices))
self.emit({"type": "settings"})
if error:
self.emit({"type": "settings"})
Expand Down Expand Up @@ -784,9 +810,10 @@ def notify(self, events):
0, # o_red
0, # o_green
0, # o_blue
d["report_id"],
)
d["dev"].send_feature_report(cmd)
d["dev"].send_feature_report(RGB_SET(RGB_AURA_ID))
d["dev"].send_feature_report(RGB_SET(d["report_id"]))
self.queue_apply[d["cfg_name"]] = (
time.perf_counter() + RGB_TDP_DELAY
)
Expand Down
112 changes: 112 additions & 0 deletions src/hhd/device/rog_laptops/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
from threading import Event as TEvent, Thread
from typing import Sequence

from hhd.plugins import (
Config,
Context,
Emitter,
Event,
HHDPlugin,
load_relative_yaml,
get_outputs_config,
)
from hhd.plugins.settings import HHDSettings


class RogLaptopControllersPlugin(HHDPlugin):
name = "rog_laptop_controllers"
priority = 18
log = "rog_laptop"

def __init__(self, target_device: str, report_id: int) -> None:
self.t = None
self.should_exit = None
self.updated = TEvent()
self.started = False
self.t = None
self.target_device = target_device
self.report_id = report_id

def open(
self,
emit: Emitter,
context: Context,
):
self.emit = emit
self.context = context
self.prev = None

def settings(self) -> HHDSettings:
base = {"controllers": {"rog_laptops": load_relative_yaml("controllers.yml")}}
base["controllers"]["rog_laptops"]["children"]["controller_mode"].update(
get_outputs_config(
can_disable=True,
extra_buttons="none",
noob_default=True,
start_disabled=True,
)
)
return base

def update(self, conf: Config):
import logging
logger = logging.getLogger(__name__)

new_conf = conf["controllers.rog_laptops"]

if new_conf == self.prev:
return

if self.prev is None:
self.prev = new_conf
else:
self.prev.update(new_conf.conf)

self.updated.set()
self.start(self.prev)

def start(self, conf):
from .base import plugin_run

if self.started:
return
self.started = True

self.close()
self.should_exit = TEvent()
self.t = Thread(
target=plugin_run,
args=(
conf,
self.emit,
self.context,
self.should_exit,
self.updated,
self.target_device,
self.report_id,
),
)
self.t.start()

def close(self):
if not self.should_exit or not self.t:
return
self.should_exit.set()
self.t.join()
self.should_exit = None
self.t = None


def autodetect(existing: Sequence[HHDPlugin]) -> Sequence[HHDPlugin]:
if len(existing):
return existing

# Match product number
with open("/sys/devices/virtual/dmi/id/product_name") as f:
dmi = f.read().strip()

# GA403UI
if "GA403" in dmi:
return [RogLaptopControllersPlugin(target_device="GA403", report_id=0x5D)]

return []
Loading