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 @@
+