diff --git a/ArchiSteamFarm/ArchiSteamFarm.csproj b/ArchiSteamFarm/ArchiSteamFarm.csproj index cfc428939..e337a02db 100644 --- a/ArchiSteamFarm/ArchiSteamFarm.csproj +++ b/ArchiSteamFarm/ArchiSteamFarm.csproj @@ -24,6 +24,7 @@ + diff --git a/ArchiSteamFarm/Core/OS.cs b/ArchiSteamFarm/Core/OS.cs index 140fd200b..8cc3c4d46 100644 --- a/ArchiSteamFarm/Core/OS.cs +++ b/ArchiSteamFarm/Core/OS.cs @@ -37,6 +37,8 @@ using System.Threading.Tasks; using ArchiSteamFarm.Localization; using ArchiSteamFarm.Storage; using ArchiSteamFarm.Web; +using Microsoft.Win32.SafeHandles; +using Tmds.DBus.Protocol; namespace ArchiSteamFarm.Core; @@ -69,13 +71,20 @@ internal static class OS { } } + private static SafeHandle? InhibitLock; private static Mutex? SingleInstance; - internal static void CoreInit(bool minimized, bool systemRequired) { + internal static async Task CoreInit(bool minimized, bool systemRequired) { if (minimized) { MinimizeConsoleWindow(); } + if (OperatingSystem.IsLinux()) { + if (systemRequired) { + await LinuxKeepSystemActive().ConfigureAwait(false); + } + } + if (OperatingSystem.IsWindows()) { if (systemRequired) { WindowsKeepSystemActive(); @@ -181,6 +190,12 @@ internal static class OS { // Instead, we'll dispose the mutex which should automatically release it by the CLR SingleInstance.Dispose(); SingleInstance = null; + + // Release the inhibit lock as well, if needed + if (InhibitLock != null) { + InhibitLock.Dispose(); + InhibitLock = null; + } } internal static bool VerifyEnvironment() { @@ -261,6 +276,79 @@ internal static class OS { 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(); + } + ).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() { (_, int top) = Console.GetCursorPosition(); diff --git a/ArchiSteamFarm/Program.cs b/ArchiSteamFarm/Program.cs index fa4409a20..99b1fefa0 100644 --- a/ArchiSteamFarm/Program.cs +++ b/ArchiSteamFarm/Program.cs @@ -278,7 +278,7 @@ internal static class Program { return false; } - OS.CoreInit(Minimized, SystemRequired); + await OS.CoreInit(Minimized, SystemRequired).ConfigureAwait(false); Console.Title = SharedInfo.ProgramIdentifier; ASF.ArchiLogger.LogGenericInfo(SharedInfo.ProgramIdentifier); diff --git a/Directory.Packages.props b/Directory.Packages.props index 096b6d60d..5df6fac50 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -21,5 +21,6 @@ +