Skip to content

Commit 8238a21

Browse files
authored
Merge pull request #11 from utajum/fix/firmware-attrs-gpu-modes
Add asus-armoury support, GPU mode fixes, add uninstall and appimage options to install scripts, Fix blocking reboot
2 parents 7678779 + 1988673 commit 8238a21

File tree

13 files changed

+1138
-283
lines changed

13 files changed

+1138
-283
lines changed

install/90-ghelper.conf

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,22 @@ z /sys/bus/platform/devices/asus-nb-wmi/dgpu_disable 0666 - - -
4646
z /sys/bus/platform/devices/asus-nb-wmi/gpu_mux_mode 0666 - - -
4747
z /sys/bus/platform/devices/asus-nb-wmi/mini_led_mode 0666 - - -
4848

49+
# ── ASUS firmware-attributes (asus-armoury ──
50+
# On newer kernels, legacy asus-nb-wmi sysfs attributes are replaced by
51+
# firmware-attributes under /sys/class/firmware-attributes/asus-armoury/.
52+
# The 'z' directive is safe — it does nothing if the path doesn't exist.
53+
z /sys/class/firmware-attributes/asus-armoury/attributes/throttle_thermal_policy/current_value 0666 - - -
54+
z /sys/class/firmware-attributes/asus-armoury/attributes/dgpu_disable/current_value 0666 - - -
55+
z /sys/class/firmware-attributes/asus-armoury/attributes/gpu_mux_mode/current_value 0666 - - -
56+
z /sys/class/firmware-attributes/asus-armoury/attributes/panel_od/current_value 0666 - - -
57+
z /sys/class/firmware-attributes/asus-armoury/attributes/panel_overdrive/current_value 0666 - - -
58+
z /sys/class/firmware-attributes/asus-armoury/attributes/mini_led_mode/current_value 0666 - - -
59+
z /sys/class/firmware-attributes/asus-armoury/attributes/ppt_pl1_spl/current_value 0666 - - -
60+
z /sys/class/firmware-attributes/asus-armoury/attributes/ppt_pl2_sppt/current_value 0666 - - -
61+
z /sys/class/firmware-attributes/asus-armoury/attributes/ppt_fppt/current_value 0666 - - -
62+
z /sys/class/firmware-attributes/asus-armoury/attributes/nv_dynamic_boost/current_value 0666 - - -
63+
z /sys/class/firmware-attributes/asus-armoury/attributes/nv_temp_target/current_value 0666 - - -
64+
4965
# ── Keyboard backlight ──
5066
z /sys/class/leds/asus::kbd_backlight/brightness 0666 - - -
5167
z /sys/class/leds/asus::kbd_backlight/multi_intensity 0666 - - -

install/90-ghelper.rules

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ SUBSYSTEM=="backlight", ACTION=="add", \
117117
ACTION=="add|change", SUBSYSTEM=="module", KERNEL=="pcie_aspm", \
118118
RUN+="/bin/sh -c '[ -f /sys/module/pcie_aspm/parameters/policy ] && chmod 0666 /sys/module/pcie_aspm/parameters/policy'"
119119

120+
# ── ASUS firmware-attributes (asus-armoury, kernel 6.8+) ──
121+
# On newer kernels, ASUS WMI attributes move to firmware-attributes.
122+
# chmod all current_value files under the asus-armoury attributes dir.
123+
ACTION=="add|change", SUBSYSTEM=="firmware-attributes", \
124+
RUN+="/bin/sh -c 'for f in /sys/class/firmware-attributes/asus-armoury/attributes/*/current_value; do [ -f \"$f\" ] && chmod 0666 \"$f\"; done'"
125+
120126
# ── CPU online/offline (core toggling) ──
121127
SUBSYSTEM=="cpu", ACTION=="add", \
122128
RUN+="/bin/sh -c 'for f in /sys/devices/system/cpu/cpu*/online; do [ -f \"$f\" ] && chmod 0666 \"$f\"; done'"

install/install-local.sh

Lines changed: 269 additions & 103 deletions
Large diffs are not rendered by default.

install/install.sh

Lines changed: 253 additions & 74 deletions
Large diffs are not rendered by default.

src/App.axaml.cs

Lines changed: 187 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Runtime.InteropServices;
12
using Avalonia;
23
using Avalonia.Controls;
34
using Avalonia.Controls.ApplicationLifetimes;
@@ -26,7 +27,7 @@ public class App : Application
2627
// Business logic orchestrator
2728
public static ModeControl? Mode { get; private set; }
2829

29-
public static MainWindow? MainWindowInstance { get; private set; }
30+
public static MainWindow? MainWindowInstance { get; set; }
3031
public static TrayIcon? TrayIconInstance { get; set; }
3132

3233
// Legacy event codes for non-configurable keys
@@ -112,9 +113,23 @@ public override void OnFrameworkInitializationCompleted()
112113
// Update tray icon to match current mode
113114
UpdateTrayIcon();
114115

116+
// Start power state monitoring for auto GPU mode and auto performance
117+
Power?.StartPowerMonitoring();
118+
if (Power != null)
119+
{
120+
Power.PowerStateChanged += OnPowerStateChanged;
121+
}
122+
123+
// Apply auto GPU mode on startup if enabled
124+
AutoGpuMode();
125+
115126
// Restore clamshell mode if it was enabled
116127
if (AppConfig.Is("toggle_clamshell_mode"))
117128
UI.Views.ExtraWindow.StartClamshellInhibit();
129+
130+
// Register Unix signal handlers for clean shutdown on SIGTERM/SIGINT
131+
// This prevents KDE/GNOME from hanging on logout/reboot
132+
RegisterSignalHandlers(desktop);
118133
}
119134

120135
base.OnFrameworkInitializationCompleted();
@@ -139,6 +154,9 @@ private void InitializePlatform()
139154
Logger.WriteLine($"Model: {System.GetModelName()}");
140155
Logger.WriteLine($"BIOS: {System.GetBiosVersion()}");
141156

157+
// Log which sysfs backend each attribute resolved to (legacy vs firmware-attributes)
158+
SysfsHelper.LogResolvedAttributes();
159+
142160
// Log detected features
143161
LogFeatureDetection();
144162
}
@@ -511,14 +529,35 @@ private NativeMenu CreateTrayMenu(IClassicDesktopStyleApplicationLifetime deskto
511529
menu.Add(new NativeMenuItemSeparator());
512530

513531
// GPU modes
514-
var eco = new NativeMenuItem("Eco (iGPU only)");
515-
eco.Click += (_, _) => SetGpuMode(ecoEnabled: true);
532+
var eco = new NativeMenuItem("GPU: Eco (iGPU only)");
533+
eco.Click += (_, _) =>
534+
{
535+
AppConfig.Set("gpu_auto", 0);
536+
SetGpuMode(ecoEnabled: true);
537+
MainWindowInstance?.RefreshGpuModePublic();
538+
};
516539
menu.Add(eco);
517540

518-
var standard = new NativeMenuItem("Standard (dGPU)");
519-
standard.Click += (_, _) => SetGpuMode(ecoEnabled: false);
541+
var standard = new NativeMenuItem("GPU: Standard (dGPU)");
542+
standard.Click += (_, _) =>
543+
{
544+
AppConfig.Set("gpu_auto", 0);
545+
SetGpuMode(ecoEnabled: false);
546+
MainWindowInstance?.RefreshGpuModePublic();
547+
};
520548
menu.Add(standard);
521549

550+
var optimized = new NativeMenuItem("GPU: Optimized (auto)");
551+
optimized.Click += (_, _) =>
552+
{
553+
AppConfig.Set("gpu_auto", 1);
554+
AutoGpuMode();
555+
MainWindowInstance?.RefreshGpuModePublic();
556+
System?.ShowNotification("GPU Mode",
557+
"Optimized — auto Eco/Standard based on power", "video-display");
558+
};
559+
menu.Add(optimized);
560+
522561
menu.Add(new NativeMenuItemSeparator());
523562

524563
// Settings
@@ -541,7 +580,16 @@ private NativeMenu CreateTrayMenu(IClassicDesktopStyleApplicationLifetime deskto
541580

542581
private void ToggleMainWindow()
543582
{
544-
if (MainWindowInstance == null) return;
583+
// Window may have been disposed by closing (KDE logout, user clicking X).
584+
// Recreate it if needed — app stays alive via ShutdownMode.OnExplicitShutdown.
585+
if (MainWindowInstance == null || MainWindowInstance.PlatformImpl == null)
586+
{
587+
MainWindowInstance = new MainWindow();
588+
if (AppConfig.Is("topmost")) MainWindowInstance.Topmost = true;
589+
MainWindowInstance.Show();
590+
MainWindowInstance.Activate();
591+
return;
592+
}
545593

546594
if (MainWindowInstance.IsVisible)
547595
{
@@ -562,11 +610,144 @@ private void SetGpuMode(bool ecoEnabled)
562610
System?.ShowNotification("GPU Mode", status, "video-display");
563611
}
564612

613+
/// <summary>
614+
/// Handle power state change (AC plugged/unplugged).
615+
/// Triggers auto GPU mode switch and auto performance mode.
616+
/// </summary>
617+
private void OnPowerStateChanged(bool onAc)
618+
{
619+
Logger.WriteLine($"Power state changed: AC={onAc}");
620+
621+
// Auto GPU mode (Optimized = auto Eco/Standard based on AC power)
622+
AutoGpuMode();
623+
624+
// Auto performance mode (if configured)
625+
Mode?.AutoPerformance(powerChanged: true);
626+
627+
// Refresh UI
628+
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
629+
{
630+
MainWindowInstance?.RefreshGpuModePublic();
631+
});
632+
}
633+
634+
/// <summary>
635+
/// Auto-switch GPU between Eco and Standard based on AC power state.
636+
/// This implements Windows G-Helper's "Optimized" GPU mode (gpu_auto flag).
637+
/// Called on startup and on every power state change.
638+
/// </summary>
639+
public static void AutoGpuMode()
640+
{
641+
if (!AppConfig.Is("gpu_auto")) return;
642+
643+
var wmi = Wmi;
644+
var power = Power;
645+
if (wmi == null || power == null) return;
646+
647+
// Don't auto-switch if in Ultimate (MUX=0) — user must manually switch out
648+
int mux = wmi.GetGpuMuxMode();
649+
if (mux == 0)
650+
{
651+
Logger.WriteLine("AutoGpuMode: MUX=0 (Ultimate), skipping auto-switch");
652+
return;
653+
}
654+
655+
bool onAc = power.IsOnAcPower();
656+
bool ecoEnabled = wmi.GetGpuEco();
657+
658+
if (onAc && ecoEnabled)
659+
{
660+
// Plugged in → switch to Standard (enable dGPU)
661+
Logger.WriteLine("AutoGpuMode: AC power detected, switching Eco → Standard");
662+
Task.Run(() =>
663+
{
664+
try
665+
{
666+
wmi.SetGpuEco(false);
667+
System?.ShowNotification("GPU Mode",
668+
"Optimized: AC power — dGPU enabled", "video-display");
669+
}
670+
catch (Exception ex)
671+
{
672+
Logger.WriteLine($"AutoGpuMode Eco→Standard failed: {ex.Message}");
673+
}
674+
});
675+
}
676+
else if (!onAc && !ecoEnabled)
677+
{
678+
// On battery → switch to Eco (disable dGPU for battery life)
679+
Logger.WriteLine("AutoGpuMode: Battery detected, switching Standard → Eco");
680+
Task.Run(() =>
681+
{
682+
try
683+
{
684+
wmi.SetGpuEco(true);
685+
System?.ShowNotification("GPU Mode",
686+
"Optimized: Battery — dGPU disabled", "video-display");
687+
}
688+
catch (Exception ex)
689+
{
690+
Logger.WriteLine($"AutoGpuMode Standard→Eco failed: {ex.Message}");
691+
}
692+
});
693+
}
694+
}
695+
696+
// Unix signal handlers for clean shutdown on SIGTERM/SIGINT (logout/reboot)
697+
private static List<PosixSignalRegistration>? _signalRegistrations;
698+
699+
private void RegisterSignalHandlers(IClassicDesktopStyleApplicationLifetime desktop)
700+
{
701+
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
702+
return;
703+
704+
try
705+
{
706+
_signalRegistrations = new();
707+
708+
// SIGTERM: sent by KDE/GNOME during logout/reboot
709+
_signalRegistrations.Add(PosixSignalRegistration.Create(PosixSignal.SIGTERM, _ =>
710+
{
711+
Logger.WriteLine("Received SIGTERM - initiating shutdown");
712+
ShutdownFromSignal(desktop);
713+
}));
714+
715+
// SIGINT: Ctrl+C in terminal
716+
_signalRegistrations.Add(PosixSignalRegistration.Create(PosixSignal.SIGINT, _ =>
717+
{
718+
Logger.WriteLine("Received SIGINT - initiating shutdown");
719+
ShutdownFromSignal(desktop);
720+
}));
721+
722+
Logger.WriteLine("Unix signal handlers registered (SIGTERM, SIGINT)");
723+
}
724+
catch (Exception ex)
725+
{
726+
Logger.WriteLine($"Failed to register signal handlers: {ex.Message}");
727+
}
728+
}
729+
730+
private void ShutdownFromSignal(IClassicDesktopStyleApplicationLifetime desktop)
731+
{
732+
// Signal handler runs on a threadpool thread.
733+
// Don't rely on UI thread — it may already be blocked during session shutdown.
734+
Logger.WriteLine("Signal shutdown: cleaning up...");
735+
736+
try { Power?.StopPowerMonitoring(); } catch { }
737+
try { UI.Views.ExtraWindow.StopClamshellInhibit(); } catch { }
738+
try { Input?.Dispose(); } catch { }
739+
try { Wmi?.Dispose(); } catch { }
740+
741+
Logger.WriteLine("Signal shutdown: exiting process");
742+
Environment.Exit(0);
743+
}
744+
565745
private void Shutdown(IClassicDesktopStyleApplicationLifetime desktop)
566746
{
567747
Logger.WriteLine("Shutting down...");
568748

569749
// Cleanup
750+
Power?.StopPowerMonitoring();
570751
UI.Views.ExtraWindow.StopClamshellInhibit();
571752
Input?.Dispose();
572753
Wmi?.Dispose();

src/Helpers/Diagnostics.cs

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,8 @@ private static void AppendSysfsState(StringBuilder sb)
145145
{
146146
sb.AppendLine("--- Sysfs State ---");
147147

148-
// Fixed paths we use
149-
var paths = new[]
148+
// Fixed paths (non-WMI attributes — always at known locations)
149+
var fixedPaths = new[]
150150
{
151151
// Battery
152152
"/sys/class/power_supply/BAT0/charge_control_end_threshold",
@@ -156,40 +156,25 @@ private static void AppendSysfsState(StringBuilder sb)
156156
// Keyboard
157157
"/sys/class/leds/asus::kbd_backlight/brightness",
158158
"/sys/class/leds/asus::kbd_backlight/multi_intensity",
159-
// Performance
160-
"/sys/devices/platform/asus-nb-wmi/throttle_thermal_policy",
161-
"/sys/devices/platform/asus-nb-wmi/ppt_pl1_spl",
162-
"/sys/devices/platform/asus-nb-wmi/ppt_pl2_sppt",
163-
"/sys/devices/platform/asus-nb-wmi/ppt_fppt",
164-
"/sys/devices/platform/asus-nb-wmi/nv_dynamic_boost",
165-
"/sys/devices/platform/asus-nb-wmi/nv_temp_target",
166-
"/sys/devices/platform/asus-nb-wmi/panel_od",
167159
// Platform profile
168160
"/sys/firmware/acpi/platform_profile",
169161
"/sys/firmware/acpi/platform_profile_choices",
170-
// GPU
171-
"/sys/bus/platform/devices/asus-nb-wmi/dgpu_disable",
172-
"/sys/bus/platform/devices/asus-nb-wmi/gpu_mux_mode",
173-
"/sys/bus/platform/devices/asus-nb-wmi/mini_led_mode",
174162
// CPU boost
175163
"/sys/devices/system/cpu/intel_pstate/no_turbo",
176164
"/sys/devices/system/cpu/cpufreq/boost",
177165
// ASPM
178166
"/sys/module/pcie_aspm/parameters/policy",
179167
};
180168

181-
foreach (var path in paths)
169+
foreach (var path in fixedPaths)
182170
{
183171
if (!File.Exists(path))
184-
continue; // skip missing — don't clutter output
172+
continue;
185173

186174
var perms = GetFilePermissions(path);
187175
var value = Platform.Linux.SysfsHelper.ReadAttribute(path) ?? "(read failed)";
188176

189-
// Shorten path for readability
190177
var shortPath = path
191-
.Replace("/sys/devices/platform/asus-nb-wmi/", "asus-nb-wmi/")
192-
.Replace("/sys/bus/platform/devices/asus-nb-wmi/", "asus-nb-wmi/")
193178
.Replace("/sys/class/power_supply/", "power_supply/")
194179
.Replace("/sys/class/leds/", "leds/")
195180
.Replace("/sys/devices/system/cpu/", "cpu/")
@@ -199,6 +184,34 @@ private static void AppendSysfsState(StringBuilder sb)
199184
sb.AppendLine($" {shortPath}: {perms} = {value}");
200185
}
201186

187+
// Resolved WMI attributes (may be legacy sysfs or firmware-attributes)
188+
sb.AppendLine();
189+
sb.AppendLine(" WMI attributes (resolved via ResolveAttrPath):");
190+
191+
var wmiAttrs = new[]
192+
{
193+
"throttle_thermal_policy", "ppt_pl1_spl", "ppt_pl2_sppt", "ppt_fppt",
194+
"nv_dynamic_boost", "nv_temp_target", "panel_od",
195+
"dgpu_disable", "gpu_mux_mode", "mini_led_mode"
196+
};
197+
198+
foreach (var attr in wmiAttrs)
199+
{
200+
var resolved = Platform.Linux.SysfsHelper.ResolveAttrPath(attr,
201+
Platform.Linux.SysfsHelper.AsusWmiPlatform,
202+
Platform.Linux.SysfsHelper.AsusBusPlatform);
203+
204+
if (resolved == null)
205+
continue;
206+
207+
var perms = GetFilePermissions(resolved);
208+
var value = Platform.Linux.SysfsHelper.ReadAttribute(resolved) ?? "(read failed)";
209+
string backend = Platform.Linux.SysfsHelper.IsFirmwareAttributesPath(resolved)
210+
? "fw-attr" : "legacy";
211+
212+
sb.AppendLine($" {attr} [{backend}]: {perms} = {value}");
213+
}
214+
202215
sb.AppendLine();
203216
}
204217

0 commit comments

Comments
 (0)