Make --system-required work on Linux (systemd) (#3526)

* Initial POC

* Extract the parts that interest us from auto-generated file

* Add support for no dbus being available

* Add final bits
This commit is contained in:
Łukasz Domeradzki
2025-12-14 01:46:22 +01:00
committed by GitHub
parent d4691ced51
commit 8ac4efe392
4 changed files with 92 additions and 2 deletions

View File

@@ -24,6 +24,7 @@
<PackageReference Include="SteamKit2" /> <PackageReference Include="SteamKit2" />
<PackageReference Include="System.Composition" /> <PackageReference Include="System.Composition" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" /> <PackageReference Include="System.Security.Cryptography.ProtectedData" />
<PackageReference Include="Tmds.DBus.Protocol" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -37,6 +37,8 @@ using System.Threading.Tasks;
using ArchiSteamFarm.Localization; using ArchiSteamFarm.Localization;
using ArchiSteamFarm.Storage; using ArchiSteamFarm.Storage;
using ArchiSteamFarm.Web; using ArchiSteamFarm.Web;
using Microsoft.Win32.SafeHandles;
using Tmds.DBus.Protocol;
namespace ArchiSteamFarm.Core; namespace ArchiSteamFarm.Core;
@@ -69,13 +71,20 @@ internal static class OS {
} }
} }
private static SafeHandle? InhibitLock;
private static Mutex? SingleInstance; private static Mutex? SingleInstance;
internal static void CoreInit(bool minimized, bool systemRequired) { internal static async Task CoreInit(bool minimized, bool systemRequired) {
if (minimized) { if (minimized) {
MinimizeConsoleWindow(); MinimizeConsoleWindow();
} }
if (OperatingSystem.IsLinux()) {
if (systemRequired) {
await LinuxKeepSystemActive().ConfigureAwait(false);
}
}
if (OperatingSystem.IsWindows()) { if (OperatingSystem.IsWindows()) {
if (systemRequired) { if (systemRequired) {
WindowsKeepSystemActive(); WindowsKeepSystemActive();
@@ -181,6 +190,12 @@ internal static class OS {
// Instead, we'll dispose the mutex which should automatically release it by the CLR // Instead, we'll dispose the mutex which should automatically release it by the CLR
SingleInstance.Dispose(); SingleInstance.Dispose();
SingleInstance = null; SingleInstance = null;
// Release the inhibit lock as well, if needed
if (InhibitLock != null) {
InhibitLock.Dispose();
InhibitLock = null;
}
} }
internal static bool VerifyEnvironment() { internal static bool VerifyEnvironment() {
@@ -261,6 +276,79 @@ internal static class OS {
NativeMethods.FlashWindowEx(ref flashInfo); NativeMethods.FlashWindowEx(ref flashInfo);
} }
[SupportedOSPlatform("Linux")]
private static async Task LinuxKeepSystemActive() {
if (!OperatingSystem.IsLinux()) {
throw new PlatformNotSupportedException();
}
// Docs: https://systemd.io/INHIBITOR_LOCKS
string? systemAddress = Address.System;
if (string.IsNullOrEmpty(systemAddress)) {
ASF.ArchiLogger.LogGenericError(Strings.FormatWarningFailedWithError(nameof(systemAddress)));
return;
}
using Connection connection = new(systemAddress);
try {
await connection.ConnectAsync().ConfigureAwait(false);
} catch (ConnectException e) {
// Possible if no DBus is available at all
ASF.ArchiLogger.LogGenericDebuggingException(e);
ASF.ArchiLogger.LogGenericError(Strings.FormatWarningFailedWithError(nameof(connection)));
return;
}
MessageWriter writer = connection.GetMessageWriter();
writer.WriteMethodCallHeader(
"org.freedesktop.login1",
"/org/freedesktop/login1",
"org.freedesktop.login1.Manager",
"Inhibit",
"ssss"
);
// Colon-separated list of lock types
writer.WriteString("idle");
// Human-readable, descriptive string of who is taking the lock
writer.WriteString(SharedInfo.PublicIdentifier);
// Human-readable, descriptive string of why the lock is taken
writer.WriteString("--system-required");
// Mode
writer.WriteString("block");
MessageBuffer message = writer.CreateMessage();
try {
// Inhibit() returns a single value, a file descriptor that encapsulates the lock
InhibitLock = await connection.CallMethodAsync(
message, static (response, _) => {
Reader reader = response.GetBodyReader();
return reader.ReadHandle<SafeFileHandle>();
}
).ConfigureAwait(false);
} catch (DBusException e) {
// Possible if login manager does not support inhibit, although that should be super rare
ASF.ArchiLogger.LogGenericDebuggingException(e);
ASF.ArchiLogger.LogGenericError(Strings.FormatWarningFailedWithError(nameof(connection)));
return;
}
if (InhibitLock == null) {
ASF.ArchiLogger.LogGenericError(Strings.FormatWarningFailedWithError(nameof(InhibitLock)));
}
}
private static void MinimizeConsoleWindow() { private static void MinimizeConsoleWindow() {
(_, int top) = Console.GetCursorPosition(); (_, int top) = Console.GetCursorPosition();

View File

@@ -278,7 +278,7 @@ internal static class Program {
return false; return false;
} }
OS.CoreInit(Minimized, SystemRequired); await OS.CoreInit(Minimized, SystemRequired).ConfigureAwait(false);
Console.Title = SharedInfo.ProgramIdentifier; Console.Title = SharedInfo.ProgramIdentifier;
ASF.ArchiLogger.LogGenericInfo(SharedInfo.ProgramIdentifier); ASF.ArchiLogger.LogGenericInfo(SharedInfo.ProgramIdentifier);

View File

@@ -21,5 +21,6 @@
<PackageVersion Include="System.Composition" Version="10.0.1" /> <PackageVersion Include="System.Composition" Version="10.0.1" />
<PackageVersion Include="System.Composition.AttributedModel" Version="10.0.1" /> <PackageVersion Include="System.Composition.AttributedModel" Version="10.0.1" />
<PackageVersion Include="System.Security.Cryptography.ProtectedData" Version="10.0.1" /> <PackageVersion Include="System.Security.Cryptography.ProtectedData" Version="10.0.1" />
<PackageVersion Include="Tmds.DBus.Protocol" Version="0.21.2" />
</ItemGroup> </ItemGroup>
</Project> </Project>