Skip to content
Merged
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
46 changes: 0 additions & 46 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,51 +1,5 @@
# G-Helper Linux — TODO

## High Priority

### Fix sysfs permissions for built-in kernel modules (systemd-tmpfiles)

**Problem:** Several sysfs nodes need 0666 permissions for non-root G-Helper, but the udev rules don't fire for built-in kernel modules (e.g. `intel_pstate` on kernel 6.17 is built-in, so `SUBSYSTEM=="module", KERNEL=="intel_pstate"` never matches). The install script's one-time `chmod` is lost on reboot since sysfs is tmpfs.

**Affected nodes:**
- `/sys/devices/system/cpu/intel_pstate/no_turbo` — CPU boost toggle (confirmed broken on G614JVR)
- `/sys/devices/system/cpu/cpufreq/boost` — AMD/generic CPU boost (likely same issue)
- `/sys/firmware/acpi/platform_profile` — performance profile switching
- `/sys/module/pcie_aspm/parameters/policy` — PCIe ASPM (already known read-only on some kernels)

**Fix:** Create `install/90-ghelper.conf` (systemd tmpfiles.d config):

```ini
# /etc/tmpfiles.d/90-ghelper.conf
# G-Helper Linux — sysfs permissions for built-in kernel modules
# These nodes can't be handled by udev rules when the driver is built-in.
# systemd-tmpfiles-setup.service runs this at every boot.

# CPU boost (Intel pstate)
z /sys/devices/system/cpu/intel_pstate/no_turbo 0666 - - -

# CPU boost (AMD / generic cpufreq)
z /sys/devices/system/cpu/cpufreq/boost 0666 - - -

# ACPI platform profile (balanced/performance/low-power)
z /sys/firmware/acpi/platform_profile 0666 - - -

# PCIe ASPM policy (may still be kernel-enforced read-only)
z /sys/module/pcie_aspm/parameters/policy 0666 - - -
```

**Deployment changes in `install/install.sh`:**
1. Download `90-ghelper.conf` alongside the udev rules
2. Install to `/etc/tmpfiles.d/90-ghelper.conf`
3. Run `systemd-tmpfiles --create /etc/tmpfiles.d/90-ghelper.conf` to apply immediately
4. Keep existing udev rules as-is (they work for modular drivers, fan curves, battery, etc.)

**Notes:**
- `z` directive sets permissions only if the path exists — safe for machines without intel_pstate
- `systemd-tmpfiles-setup.service` runs early in boot, before user login
- Belt-and-suspenders: udev handles what it can, tmpfiles catches the rest

---

## Medium Priority

### Auto screen refresh rate switching (AC/battery)
Expand Down
53 changes: 53 additions & 0 deletions install/90-ghelper.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# /etc/tmpfiles.d/90-ghelper.conf
# G-Helper Linux — sysfs permissions for built-in kernel modules
#
# Problem: udev rules can't fire for built-in kernel modules (they're loaded
# before udev starts). This tmpfiles config runs at every boot via
# systemd-tmpfiles-setup.service to set permissions on sysfs nodes
# that udev rules may miss.
#
# The 'z' directive sets permissions only if the path exists — safe for
# systems that don't have a particular hardware feature.
#
# This complements (not replaces) the udev rules in 90-ghelper.rules.
# Belt-and-suspenders: udev handles modular drivers, tmpfiles catches built-in.

# ── CPU boost ──
# Intel pstate (often built-in on modern kernels)
z /sys/devices/system/cpu/intel_pstate/no_turbo 0666 - - -

# AMD / generic cpufreq boost
z /sys/devices/system/cpu/cpufreq/boost 0666 - - -

# ── ACPI platform profile ──
# Controls balanced/performance/low-power mode switching
z /sys/firmware/acpi/platform_profile 0666 - - -

# ── PCIe ASPM policy ──
# May still be kernel-enforced read-only on some systems
z /sys/module/pcie_aspm/parameters/policy 0666 - - -

# ── Battery charge limit ──
# Needs to be writable before user login so G-Helper can re-apply saved limit
z /sys/class/power_supply/BAT0/charge_control_end_threshold 0666 - - -
z /sys/class/power_supply/BAT1/charge_control_end_threshold 0666 - - -
z /sys/class/power_supply/BATT/charge_control_end_threshold 0666 - - -

# ── ASUS WMI platform attributes ──
# These may be built-in on some distributions
z /sys/devices/platform/asus-nb-wmi/throttle_thermal_policy 0666 - - -
z /sys/devices/platform/asus-nb-wmi/panel_od 0666 - - -
z /sys/devices/platform/asus-nb-wmi/ppt_pl1_spl 0666 - - -
z /sys/devices/platform/asus-nb-wmi/ppt_pl2_sppt 0666 - - -
z /sys/devices/platform/asus-nb-wmi/ppt_fppt 0666 - - -
z /sys/devices/platform/asus-nb-wmi/nv_dynamic_boost 0666 - - -
z /sys/devices/platform/asus-nb-wmi/nv_temp_target 0666 - - -
z /sys/bus/platform/devices/asus-nb-wmi/dgpu_disable 0666 - - -
z /sys/bus/platform/devices/asus-nb-wmi/gpu_mux_mode 0666 - - -
z /sys/bus/platform/devices/asus-nb-wmi/mini_led_mode 0666 - - -

# ── Keyboard backlight ──
z /sys/class/leds/asus::kbd_backlight/brightness 0666 - - -
z /sys/class/leds/asus::kbd_backlight/multi_intensity 0666 - - -
z /sys/class/leds/asus::kbd_backlight/kbd_rgb_mode 0666 - - -
z /sys/class/leds/asus::kbd_backlight/kbd_rgb_state 0666 - - -
8 changes: 8 additions & 0 deletions install/90-ghelper.rules
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ SUBSYSTEM=="leds", KERNEL=="asus::kbd_backlight", \
ATTR{multi_intensity}!="", \
RUN+="/bin/chmod 0666 /sys/class/leds/asus::kbd_backlight/multi_intensity"

# TUF Gaming keyboards: RGB mode control (mode + color + speed as byte array)
SUBSYSTEM=="leds", KERNEL=="asus::kbd_backlight", \
RUN+="/bin/sh -c '[ -f /sys/class/leds/asus::kbd_backlight/kbd_rgb_mode ] && chmod 0666 /sys/class/leds/asus::kbd_backlight/kbd_rgb_mode'"

# TUF Gaming keyboards: RGB power state control (boot/awake/sleep states)
SUBSYSTEM=="leds", KERNEL=="asus::kbd_backlight", \
RUN+="/bin/sh -c '[ -f /sys/class/leds/asus::kbd_backlight/kbd_rgb_state ] && chmod 0666 /sys/class/leds/asus::kbd_backlight/kbd_rgb_state'"

# ── Fan curves (hwmon) ──
# The asus_nb_wmi hwmon exposes pwm auto_point files for fan curve control.
# We make them writable for non-root users.
Expand Down
26 changes: 23 additions & 3 deletions install/install-local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,23 @@ _info "udev daemon reloaded"
udevadm trigger
_info "udev trigger fired — re-applying all RUN commands"

# ── systemd-tmpfiles (for built-in kernel modules that udev can't handle) ──
TMPFILES_SRC="$SCRIPT_DIR/90-ghelper.conf"
TMPFILES_DEST="/etc/tmpfiles.d/90-ghelper.conf"
if [[ -f "$TMPFILES_SRC" ]]; then
_install_file "$TMPFILES_SRC" "$TMPFILES_DEST" 644 "tmpfiles config" || true
# Apply immediately so permissions take effect without reboot
if command -v systemd-tmpfiles &>/dev/null; then
systemd-tmpfiles --create "$TMPFILES_DEST" 2>/dev/null && \
_info "systemd-tmpfiles applied — built-in module permissions set" || \
_warn "systemd-tmpfiles --create had warnings (some paths may not exist yet)"
else
_warn "systemd-tmpfiles not found — permissions for built-in modules will require manual setup"
fi
else
_warn "90-ghelper.conf not found in install/ — skipping tmpfiles deployment"
fi

# ══════════════════════════════════════════════════════════════════════════════
# [0x04] ESTABLISH SYSFS ACCESS LAYER
# ══════════════════════════════════════════════════════════════════════════════
Expand All @@ -225,7 +242,9 @@ for f in \
/sys/devices/system/cpu/intel_pstate/no_turbo \
/sys/devices/system/cpu/cpufreq/boost \
/sys/class/leds/asus::kbd_backlight/brightness \
/sys/class/leds/asus::kbd_backlight/multi_intensity; do
/sys/class/leds/asus::kbd_backlight/multi_intensity \
/sys/class/leds/asus::kbd_backlight/kbd_rgb_mode \
/sys/class/leds/asus::kbd_backlight/kbd_rgb_state; do
_ensure_chmod "$f"
done

Expand Down Expand Up @@ -360,8 +379,9 @@ echo "${GREEN}${BOLD} ╠══════════════════
echo "${GREEN}${BOLD} ║ ║${RESET}"
echo "${GREEN}${BOLD} ║${RESET} ${CYAN}0xF0${RESET} Binary → $BINARY_DEST"
echo "${GREEN}${BOLD} ║${RESET} ${CYAN}0xF1${RESET} udev → $UDEV_DEST"
echo "${GREEN}${BOLD} ║${RESET} ${CYAN}0xF2${RESET} Desktop → $DESKTOP_DEST"
echo "${GREEN}${BOLD} ║${RESET} ${CYAN}0xF3${RESET} Autostart → ~/.config/autostart/ghelper.desktop"
echo "${GREEN}${BOLD} ║${RESET} ${CYAN}0xF2${RESET} tmpfiles → /etc/tmpfiles.d/90-ghelper.conf"
echo "${GREEN}${BOLD} ║${RESET} ${CYAN}0xF3${RESET} Desktop → $DESKTOP_DEST"
echo "${GREEN}${BOLD} ║${RESET} ${CYAN}0xF4${RESET} Autostart → ~/.config/autostart/ghelper.desktop"
echo "${GREEN}${BOLD} ║ ║${RESET}"
echo "${GREEN}${BOLD} ╠════════════════════════════════════════════════════════════════╣${RESET}"
echo "${GREEN}${BOLD} ║ ║${RESET}"
Expand Down
27 changes: 23 additions & 4 deletions install/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ trap 'rm -rf "$WORK_DIR"' EXIT
_step 1 "DOWNLOADING PAYLOADS FROM REMOTE"

BINARIES=(ghelper libSkiaSharp.so libHarfBuzzSharp.so)
ASSETS=(90-ghelper.rules ghelper.desktop)
ASSETS=(90-ghelper.rules 90-ghelper.conf ghelper.desktop)

dl_count=0
dl_total=$(( ${#BINARIES[@]} + ${#ASSETS[@]} ))
Expand Down Expand Up @@ -253,6 +253,22 @@ _info "udev daemon reloaded"
udevadm trigger
_info "udev trigger fired — re-applying all RUN commands"

# ── systemd-tmpfiles (for built-in kernel modules that udev can't handle) ──
TMPFILES_DEST="/etc/tmpfiles.d/90-ghelper.conf"
if [[ -f "$WORK_DIR/90-ghelper.conf" ]]; then
_install_file "$WORK_DIR/90-ghelper.conf" "$TMPFILES_DEST" 644 "tmpfiles config" || true
# Apply immediately so permissions take effect without reboot
if command -v systemd-tmpfiles &>/dev/null; then
systemd-tmpfiles --create "$TMPFILES_DEST" 2>/dev/null && \
_info "systemd-tmpfiles applied — built-in module permissions set" || \
_warn "systemd-tmpfiles --create had warnings (some paths may not exist yet)"
else
_warn "systemd-tmpfiles not found — permissions for built-in modules will require manual setup"
fi
else
_warn "90-ghelper.conf not found in download — skipping tmpfiles deployment"
fi

# ══════════════════════════════════════════════════════════════════════════════
# [0x04] ESTABLISH SYSFS ACCESS LAYER
# ══════════════════════════════════════════════════════════════════════════════
Expand All @@ -278,7 +294,9 @@ for f in \
/sys/devices/system/cpu/intel_pstate/no_turbo \
/sys/devices/system/cpu/cpufreq/boost \
/sys/class/leds/asus::kbd_backlight/brightness \
/sys/class/leds/asus::kbd_backlight/multi_intensity; do
/sys/class/leds/asus::kbd_backlight/multi_intensity \
/sys/class/leds/asus::kbd_backlight/kbd_rgb_mode \
/sys/class/leds/asus::kbd_backlight/kbd_rgb_state; do
_ensure_chmod "$f"
done

Expand Down Expand Up @@ -370,8 +388,9 @@ echo "${GREEN}${BOLD} ║
echo "${GREEN}${BOLD} ║${RESET} ${CYAN}0xF0${RESET} Binary → $INSTALL_DIR/ghelper"
echo "${GREEN}${BOLD} ║${RESET} ${CYAN}0xF1${RESET} Symlink → /usr/local/bin/ghelper"
echo "${GREEN}${BOLD} ║${RESET} ${CYAN}0xF2${RESET} udev → $UDEV_DEST"
echo "${GREEN}${BOLD} ║${RESET} ${CYAN}0xF3${RESET} Desktop → $DESKTOP_DEST"
echo "${GREEN}${BOLD} ║${RESET} ${CYAN}0xF4${RESET} Autostart → ~/.config/autostart/ghelper.desktop"
echo "${GREEN}${BOLD} ║${RESET} ${CYAN}0xF3${RESET} tmpfiles → /etc/tmpfiles.d/90-ghelper.conf"
echo "${GREEN}${BOLD} ║${RESET} ${CYAN}0xF4${RESET} Desktop → $DESKTOP_DEST"
echo "${GREEN}${BOLD} ║${RESET} ${CYAN}0xF5${RESET} Autostart → ~/.config/autostart/ghelper.desktop"
echo "${GREEN}${BOLD} ║ ║${RESET}"
echo "${GREEN}${BOLD} ╠════════════════════════════════════════════════════════════════╣${RESET}"
echo "${GREEN}${BOLD} ║ ║${RESET}"
Expand Down
19 changes: 18 additions & 1 deletion src/Mode/ModeControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,24 @@ public void SetPerformanceMode(int mode = -1, bool notify = false)
};
App.Power?.SetPlatformProfile(profile);

// 3. Apply fan curves and power limits
// 3. Verify: on some kernels, throttle_thermal_policy and platform_profile
// are coupled — writing platform_profile may reset throttle_thermal_policy.
// Read back and re-apply if needed.
int verifyPolicy = App.Wmi?.GetThrottleThermalPolicy() ?? -1;
string verifyProfile = App.Power?.GetPlatformProfile() ?? "unknown";
Helpers.Logger.WriteLine($"SetPerformanceMode verify: thermal_policy={verifyPolicy} (expected {baseMode}), platform_profile={verifyProfile} (expected {profile})");

if (verifyPolicy >= 0 && verifyPolicy != baseMode)
{
Helpers.Logger.WriteLine($"WARNING: throttle_thermal_policy was overridden ({verifyPolicy} != {baseMode}), re-applying");
App.Wmi?.SetThrottleThermalPolicy(baseMode);
// Brief delay then verify again
Thread.Sleep(100);
int recheck = App.Wmi?.GetThrottleThermalPolicy() ?? -1;
Helpers.Logger.WriteLine($"SetPerformanceMode re-verify: thermal_policy={recheck}");
}

// 4. Apply fan curves and power limits
Task.Run(async () =>
{
// If reset was needed, wait for firmware to process the bounce
Expand Down
47 changes: 47 additions & 0 deletions src/Platform/Linux/LinuxAsusWmi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,53 @@ public void SetKeyboardRgb(byte r, byte g, byte b)
SysfsHelper.WriteAttribute(intensityPath, $"{r} {g} {b}");
}

/// <summary>
/// Set TUF keyboard RGB mode via sysfs kbd_rgb_mode attribute.
/// This is the primary RGB control for TUF Gaming keyboards.
/// Format: space-separated byte array "cmd mode R G B speed"
/// Learned from asusctl: rog-platform/src/keyboard_led.rs + asusd/src/aura_laptop/mod.rs
/// </summary>
public void SetKeyboardRgbMode(int mode, byte r, byte g, byte b, int speed)
{
var modePath = Path.Combine(SysfsHelper.Leds, "asus::kbd_backlight", "kbd_rgb_mode");
if (!SysfsHelper.Exists(modePath))
{
Helpers.Logger.WriteLine($"kbd_rgb_mode not available at {modePath}");
return;
}
// Protocol: [1, mode, R, G, B, speed] — matches asusctl's TUF write
string value = $"1 {mode} {r} {g} {b} {speed}";
SysfsHelper.WriteAttribute(modePath, value);
}

/// <summary>
/// Set TUF keyboard RGB power state via sysfs kbd_rgb_state attribute.
/// Controls which lighting states are active (boot/awake/sleep).
/// Format: space-separated byte array "cmd boot awake sleep keyboard"
/// Learned from asusctl: rog-aura/src/keyboard/power.rs TUF format
/// </summary>
public void SetKeyboardRgbState(bool boot, bool awake, bool sleep)
{
var statePath = Path.Combine(SysfsHelper.Leds, "asus::kbd_backlight", "kbd_rgb_state");
if (!SysfsHelper.Exists(statePath))
{
Helpers.Logger.WriteLine($"kbd_rgb_state not available at {statePath}");
return;
}
// Protocol: [1, boot, awake, sleep, 1] — matches asusctl's TUF power state
string value = $"1 {(boot ? 1 : 0)} {(awake ? 1 : 0)} {(sleep ? 1 : 0)} 1";
SysfsHelper.WriteAttribute(statePath, value);
}

/// <summary>
/// Check if TUF-specific kbd_rgb_mode sysfs attribute is available.
/// </summary>
public bool HasKeyboardRgbMode()
{
return SysfsHelper.Exists(
Path.Combine(SysfsHelper.Leds, "asus::kbd_backlight", "kbd_rgb_mode"));
}

// ── Temperature ──

private int GetCpuTemp()
Expand Down
55 changes: 52 additions & 3 deletions src/USB/Aura.cs
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,18 @@ public static void ApplyPower()
ShutdownRear = AppConfig.IsNotFalse("keyboard_shutdown_lid"),
};

// TUF: use sysfs kbd_rgb_state instead of HID power message
if (_isACPI)
{
var wmi = App.Wmi as GHelper.Linux.Platform.Linux.LinuxAsusWmi;
if (wmi != null && wmi.HasKeyboardRgbMode())
{
wmi.SetKeyboardRgbState(flags.BootKeyb, flags.AwakeKeyb, flags.SleepKeyb);
Logger.WriteLine($"TUF kbd_rgb_state: boot={flags.BootKeyb} awake={flags.AwakeKeyb} sleep={flags.SleepKeyb}");
return;
}
}

AsusHid.Write(AuraPowerMessage(flags));
}

Expand Down Expand Up @@ -461,10 +473,38 @@ public static void ApplyAura()

AsusHid.Write(new List<byte[]> { msg, MESSAGE_SET, MESSAGE_APPLY });

// TUF uses ACPI path (handled via sysfs multi_intensity on Linux)
// TUF/VivoZenPro: use sysfs kbd_rgb_mode (primary) + multi_intensity (fallback)
if (_isACPI)
{
App.Wmi?.SetKeyboardRgb(ColorR, ColorG, ColorB);
var wmi = App.Wmi as GHelper.Linux.Platform.Linux.LinuxAsusWmi;
if (wmi != null && wmi.HasKeyboardRgbMode())
{
// Map AuraMode to TUF kbd_rgb_mode byte value
int tufMode = Mode switch
{
AuraMode.AuraStatic => 0,
AuraMode.AuraBreathe => 1,
AuraMode.AuraColorCycle => 2,
AuraMode.AuraRainbow => 3,
AuraMode.AuraStrobe => 10,
_ => 0 // Default to static for unsupported modes
};
// Map speed enum to TUF speed byte (0=slow, 1=normal, 2=fast)
int tufSpeed = Speed switch
{
AuraSpeed.Slow => 0,
AuraSpeed.Normal => 1,
AuraSpeed.Fast => 2,
_ => 1
};
wmi.SetKeyboardRgbMode(tufMode, ColorR, ColorG, ColorB, tufSpeed);
Logger.WriteLine($"TUF kbd_rgb_mode: mode={tufMode} color=#{ColorR:X2}{ColorG:X2}{ColorB:X2} speed={tufSpeed}");
}
else
{
// Fallback to multi_intensity (older kernels or non-TUF ACPI models)
App.Wmi?.SetKeyboardRgb(ColorR, ColorG, ColorB);
}
}
}

Expand All @@ -478,7 +518,16 @@ public static void ApplyDirect(byte r, byte g, byte b, bool init = false)

if (_isACPI)
{
App.Wmi?.SetKeyboardRgb(r, g, b);
var wmi = App.Wmi as GHelper.Linux.Platform.Linux.LinuxAsusWmi;
if (wmi != null && wmi.HasKeyboardRgbMode())
{
// Use kbd_rgb_mode with Static mode (0) for direct color
wmi.SetKeyboardRgbMode(0, r, g, b, 0);
}
else
{
App.Wmi?.SetKeyboardRgb(r, g, b);
}
return;
}

Expand Down