Skip to content

Commit 7d1c029

Browse files
authored
Merge pull request #8 from utajum/appimage-update
Fix AppImage self-update 'Text file busy' error, add system diagnostics
2 parents 605321c + ec81f70 commit 7d1c029

File tree

3 files changed

+422
-6
lines changed

3 files changed

+422
-6
lines changed

src/Helpers/Diagnostics.cs

Lines changed: 374 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,374 @@
1+
using System.Text;
2+
3+
namespace GHelper.Linux.Helpers;
4+
5+
/// <summary>
6+
/// Generates a comprehensive system diagnostics report for troubleshooting.
7+
/// Collects sysfs state, permissions, kernel modules, model detection flags,
8+
/// and hardware info into a formatted text block for GitHub issue reports.
9+
/// </summary>
10+
public static class Diagnostics
11+
{
12+
public static string GenerateReport()
13+
{
14+
var sb = new StringBuilder();
15+
16+
sb.AppendLine("=== G-Helper Linux Diagnostics ===");
17+
sb.AppendLine();
18+
19+
// ── System Identity ──
20+
AppendSystemInfo(sb);
21+
22+
// ── Model Detection Flags ──
23+
AppendModelFlags(sb);
24+
25+
// ── Kernel Modules ──
26+
AppendKernelModules(sb);
27+
28+
// ── Sysfs Permissions & Values ──
29+
AppendSysfsState(sb);
30+
31+
// ── hwmon Devices ──
32+
AppendHwmon(sb);
33+
34+
// ── USB HID (ASUS) ──
35+
AppendUsbDevices(sb);
36+
37+
// ── firmware-attributes (asus_armoury) ──
38+
AppendFirmwareAttributes(sb);
39+
40+
// ── Input Devices ──
41+
AppendInputDevices(sb);
42+
43+
// ── udev / tmpfiles ──
44+
AppendInstallState(sb);
45+
46+
return sb.ToString();
47+
}
48+
49+
private static void AppendSystemInfo(StringBuilder sb)
50+
{
51+
sb.AppendLine("--- System ---");
52+
53+
sb.AppendLine($"G-Helper: v{AppConfig.AppVersion}");
54+
55+
var appImage = Environment.GetEnvironmentVariable("APPIMAGE");
56+
sb.AppendLine($"Mode: {(string.IsNullOrEmpty(appImage) ? "binary" : $"AppImage ({appImage})")}");
57+
58+
var model = Platform.Linux.SysfsHelper.ReadAttribute(
59+
Path.Combine(Platform.Linux.SysfsHelper.DmiId, "product_name")) ?? "?";
60+
sb.AppendLine($"Product: {model}");
61+
62+
var bios = Platform.Linux.SysfsHelper.ReadAttribute(
63+
Path.Combine(Platform.Linux.SysfsHelper.DmiId, "bios_version")) ?? "?";
64+
sb.AppendLine($"BIOS: {bios}");
65+
66+
var kernel = Platform.Linux.SysfsHelper.RunCommand("uname", "-r") ?? "?";
67+
sb.AppendLine($"Kernel: {kernel}");
68+
69+
// OS release
70+
try
71+
{
72+
if (File.Exists("/etc/os-release"))
73+
{
74+
var lines = File.ReadAllLines("/etc/os-release");
75+
foreach (var line in lines)
76+
{
77+
if (line.StartsWith("PRETTY_NAME="))
78+
{
79+
sb.AppendLine($"OS: {line[12..].Trim('"')}");
80+
break;
81+
}
82+
}
83+
}
84+
}
85+
catch { }
86+
87+
// Desktop environment
88+
var desktop = Environment.GetEnvironmentVariable("XDG_CURRENT_DESKTOP") ?? "?";
89+
var session = Environment.GetEnvironmentVariable("XDG_SESSION_TYPE") ?? "?";
90+
sb.AppendLine($"Desktop: {desktop} ({session})");
91+
92+
sb.AppendLine();
93+
}
94+
95+
private static void AppendModelFlags(StringBuilder sb)
96+
{
97+
sb.AppendLine("--- Model Detection ---");
98+
99+
var flags = new (string Name, bool Value)[]
100+
{
101+
("IsTUF", AppConfig.IsTUF()),
102+
("IsROG", AppConfig.IsROG()),
103+
("IsStrix", AppConfig.IsStrix()),
104+
("IsVivoZenbook", AppConfig.IsVivoZenbook()),
105+
("IsProArt", AppConfig.IsProArt()),
106+
("IsAlly", AppConfig.IsAlly()),
107+
("NoGpu", AppConfig.NoGpu()),
108+
("IsChargeLimit6080", AppConfig.IsChargeLimit6080()),
109+
("IsSingleColor", AppConfig.IsSingleColor()),
110+
("NoAura", AppConfig.NoAura()),
111+
("IsAdvancedRGB", AppConfig.IsAdvancedRGB()),
112+
("Is4ZoneRGB", AppConfig.Is4ZoneRGB()),
113+
("IsDynamicLighting", AppConfig.IsDynamicLighting()),
114+
("IsIntelHX", AppConfig.IsIntelHX()),
115+
("IsAMDLight", AppConfig.IsAMDLight()),
116+
("IsResetRequired", AppConfig.IsResetRequired()),
117+
("IsFanRequired", AppConfig.IsFanRequired()),
118+
("IsSleepBacklight", AppConfig.IsSleepBacklight()),
119+
("IsSlash", AppConfig.IsSlash()),
120+
};
121+
122+
foreach (var (name, value) in flags)
123+
{
124+
if (value) sb.AppendLine($" {name}: true");
125+
}
126+
127+
// Show any that are true; if none are true, say so
128+
if (!flags.Any(f => f.Value))
129+
sb.AppendLine(" (no model flags matched)");
130+
131+
sb.AppendLine();
132+
}
133+
134+
private static void AppendKernelModules(StringBuilder sb)
135+
{
136+
sb.AppendLine("--- Kernel Modules ---");
137+
138+
var lsmod = Platform.Linux.SysfsHelper.RunCommand("bash",
139+
"-c \"lsmod 2>/dev/null | grep -iE 'asus|hid_asus' || echo '(none found)'\"");
140+
sb.AppendLine(lsmod ?? "(lsmod failed)");
141+
sb.AppendLine();
142+
}
143+
144+
private static void AppendSysfsState(StringBuilder sb)
145+
{
146+
sb.AppendLine("--- Sysfs State ---");
147+
148+
// Fixed paths we use
149+
var paths = new[]
150+
{
151+
// Battery
152+
"/sys/class/power_supply/BAT0/charge_control_end_threshold",
153+
"/sys/class/power_supply/BAT1/charge_control_end_threshold",
154+
"/sys/class/power_supply/BATC/charge_control_end_threshold",
155+
"/sys/class/power_supply/BATT/charge_control_end_threshold",
156+
// Keyboard
157+
"/sys/class/leds/asus::kbd_backlight/brightness",
158+
"/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",
167+
// Platform profile
168+
"/sys/firmware/acpi/platform_profile",
169+
"/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",
174+
// CPU boost
175+
"/sys/devices/system/cpu/intel_pstate/no_turbo",
176+
"/sys/devices/system/cpu/cpufreq/boost",
177+
// ASPM
178+
"/sys/module/pcie_aspm/parameters/policy",
179+
};
180+
181+
foreach (var path in paths)
182+
{
183+
if (!File.Exists(path))
184+
continue; // skip missing — don't clutter output
185+
186+
var perms = GetFilePermissions(path);
187+
var value = Platform.Linux.SysfsHelper.ReadAttribute(path) ?? "(read failed)";
188+
189+
// Shorten path for readability
190+
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/")
193+
.Replace("/sys/class/power_supply/", "power_supply/")
194+
.Replace("/sys/class/leds/", "leds/")
195+
.Replace("/sys/devices/system/cpu/", "cpu/")
196+
.Replace("/sys/firmware/acpi/", "acpi/")
197+
.Replace("/sys/module/pcie_aspm/parameters/", "pcie_aspm/");
198+
199+
sb.AppendLine($" {shortPath}: {perms} = {value}");
200+
}
201+
202+
sb.AppendLine();
203+
}
204+
205+
private static void AppendHwmon(StringBuilder sb)
206+
{
207+
sb.AppendLine("--- hwmon ---");
208+
209+
try
210+
{
211+
if (Directory.Exists(Platform.Linux.SysfsHelper.Hwmon))
212+
{
213+
foreach (var hwmonDir in Directory.GetDirectories(Platform.Linux.SysfsHelper.Hwmon))
214+
{
215+
var name = Platform.Linux.SysfsHelper.ReadAttribute(
216+
Path.Combine(hwmonDir, "name")) ?? "(no name)";
217+
var dirName = Path.GetFileName(hwmonDir);
218+
219+
// For asus-related hwmon, list key files
220+
if (name.Contains("asus", StringComparison.OrdinalIgnoreCase) ||
221+
name.Contains("coretemp", StringComparison.OrdinalIgnoreCase) ||
222+
name.Contains("k10temp", StringComparison.OrdinalIgnoreCase) ||
223+
name.Contains("amdgpu", StringComparison.OrdinalIgnoreCase) ||
224+
name.Contains("nvidia", StringComparison.OrdinalIgnoreCase))
225+
{
226+
var extras = new List<string>();
227+
if (File.Exists(Path.Combine(hwmonDir, "fan1_input"))) extras.Add("fan1");
228+
if (File.Exists(Path.Combine(hwmonDir, "fan2_input"))) extras.Add("fan2");
229+
if (File.Exists(Path.Combine(hwmonDir, "fan3_input"))) extras.Add("fan3");
230+
if (File.Exists(Path.Combine(hwmonDir, "pwm1_enable"))) extras.Add("pwm1");
231+
if (File.Exists(Path.Combine(hwmonDir, "pwm2_enable"))) extras.Add("pwm2");
232+
if (File.Exists(Path.Combine(hwmonDir, "temp1_input"))) extras.Add("temp1");
233+
234+
var extraStr = extras.Count > 0 ? $" [{string.Join(", ", extras)}]" : "";
235+
sb.AppendLine($" {dirName} = {name}{extraStr}");
236+
}
237+
else
238+
{
239+
sb.AppendLine($" {dirName} = {name}");
240+
}
241+
}
242+
}
243+
else
244+
{
245+
sb.AppendLine(" /sys/class/hwmon not found");
246+
}
247+
}
248+
catch (Exception ex)
249+
{
250+
sb.AppendLine($" (error: {ex.Message})");
251+
}
252+
253+
sb.AppendLine();
254+
}
255+
256+
private static void AppendUsbDevices(StringBuilder sb)
257+
{
258+
sb.AppendLine("--- USB HID (ASUS 0x0b05) ---");
259+
260+
var lsusb = Platform.Linux.SysfsHelper.RunCommand("bash",
261+
"-c \"lsusb 2>/dev/null | grep -i '0b05' || echo '(none found)'\"");
262+
sb.AppendLine(lsusb ?? "(lsusb failed)");
263+
sb.AppendLine();
264+
}
265+
266+
private static void AppendFirmwareAttributes(StringBuilder sb)
267+
{
268+
sb.AppendLine("--- firmware-attributes ---");
269+
270+
const string fwAttrBase = "/sys/class/firmware-attributes";
271+
272+
if (!Directory.Exists(fwAttrBase))
273+
{
274+
sb.AppendLine(" /sys/class/firmware-attributes: not present");
275+
sb.AppendLine();
276+
return;
277+
}
278+
279+
try
280+
{
281+
foreach (var deviceDir in Directory.GetDirectories(fwAttrBase))
282+
{
283+
var deviceName = Path.GetFileName(deviceDir);
284+
sb.AppendLine($" {deviceName}:");
285+
286+
var attrsDir = Path.Combine(deviceDir, "attributes");
287+
if (!Directory.Exists(attrsDir)) continue;
288+
289+
foreach (var attrDir in Directory.GetDirectories(attrsDir))
290+
{
291+
var attrName = Path.GetFileName(attrDir);
292+
var currentValue = Platform.Linux.SysfsHelper.ReadAttribute(
293+
Path.Combine(attrDir, "current_value"));
294+
295+
if (currentValue != null)
296+
sb.AppendLine($" {attrName} = {currentValue}");
297+
else
298+
sb.AppendLine($" {attrName} (no current_value)");
299+
}
300+
}
301+
}
302+
catch (Exception ex)
303+
{
304+
sb.AppendLine($" (error: {ex.Message})");
305+
}
306+
307+
sb.AppendLine();
308+
}
309+
310+
private static void AppendInputDevices(StringBuilder sb)
311+
{
312+
sb.AppendLine("--- Input Devices (ASUS) ---");
313+
314+
try
315+
{
316+
if (File.Exists("/proc/bus/input/devices"))
317+
{
318+
var content = File.ReadAllText("/proc/bus/input/devices");
319+
var devices = content.Split("\n\n", StringSplitOptions.RemoveEmptyEntries);
320+
321+
foreach (var device in devices)
322+
{
323+
if (device.Contains("asus", StringComparison.OrdinalIgnoreCase) ||
324+
device.Contains("0b05", StringComparison.OrdinalIgnoreCase))
325+
{
326+
// Extract just the Name and Handlers lines
327+
foreach (var line in device.Split('\n'))
328+
{
329+
var trimmed = line.Trim();
330+
if (trimmed.StartsWith("N:") || trimmed.StartsWith("H:"))
331+
sb.AppendLine($" {trimmed}");
332+
}
333+
sb.AppendLine();
334+
}
335+
}
336+
}
337+
}
338+
catch { }
339+
340+
sb.AppendLine();
341+
}
342+
343+
private static void AppendInstallState(StringBuilder sb)
344+
{
345+
sb.AppendLine("--- Install State ---");
346+
347+
var udevExists = File.Exists("/etc/udev/rules.d/90-ghelper.rules");
348+
sb.AppendLine($" udev rules: {(udevExists ? "installed" : "NOT FOUND")}");
349+
350+
var tmpfilesExists = File.Exists("/etc/tmpfiles.d/90-ghelper.conf");
351+
sb.AppendLine($" tmpfiles.d: {(tmpfilesExists ? "installed" : "NOT FOUND")}");
352+
353+
var symlinkTarget = Platform.Linux.SysfsHelper.RunCommand("readlink", "-f /usr/local/bin/ghelper");
354+
sb.AppendLine($" /usr/local/bin/ghelper: {symlinkTarget ?? "NOT FOUND"}");
355+
356+
var optExists = File.Exists("/opt/ghelper/ghelper");
357+
sb.AppendLine($" /opt/ghelper/ghelper: {(optExists ? "installed" : "NOT FOUND")}");
358+
359+
sb.AppendLine();
360+
}
361+
362+
private static string GetFilePermissions(string path)
363+
{
364+
try
365+
{
366+
var stat = Platform.Linux.SysfsHelper.RunCommand("stat", $"-c %a {path}");
367+
return stat ?? "???";
368+
}
369+
catch
370+
{
371+
return "???";
372+
}
373+
}
374+
}

src/UI/Views/UpdatesWindow.axaml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<StackPanel Margin="12,8,12,8" Spacing="8">
1818

1919
<!-- Header -->
20-
<Grid ColumnDefinitions="*,Auto,Auto">
20+
<Grid ColumnDefinitions="*,Auto,Auto,Auto">
2121
<TextBlock x:Name="labelTitle" Grid.Column="0"
2222
Text="BIOS &amp; Driver Updates"
2323
Foreground="#F0F0F0" FontSize="16" FontWeight="Bold"
@@ -26,7 +26,10 @@
2626
Text="Checking..."
2727
Foreground="#06B48A" FontSize="13"
2828
VerticalAlignment="Center" Margin="0,0,12,0" />
29-
<Button x:Name="buttonRefresh" Grid.Column="2"
29+
<Button x:Name="buttonDiagnostics" Grid.Column="2"
30+
Content="Copy Diagnostics" Click="ButtonDiagnostics_Click"
31+
MinWidth="120" Margin="0,0,6,0" />
32+
<Button x:Name="buttonRefresh" Grid.Column="3"
3033
Content="Refresh" Click="ButtonRefresh_Click"
3134
MinWidth="80" />
3235
</Grid>

0 commit comments

Comments
 (0)