Skip to content

Commit 8ac4efe

Browse files
authored
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
1 parent d4691ce commit 8ac4efe

4 files changed

Lines changed: 92 additions & 2 deletions

File tree

ArchiSteamFarm/ArchiSteamFarm.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
<PackageReference Include="SteamKit2" />
2525
<PackageReference Include="System.Composition" />
2626
<PackageReference Include="System.Security.Cryptography.ProtectedData" />
27+
<PackageReference Include="Tmds.DBus.Protocol" />
2728
</ItemGroup>
2829

2930
<ItemGroup>

ArchiSteamFarm/Core/OS.cs

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
using ArchiSteamFarm.Localization;
3838
using ArchiSteamFarm.Storage;
3939
using ArchiSteamFarm.Web;
40+
using Microsoft.Win32.SafeHandles;
41+
using Tmds.DBus.Protocol;
4042

4143
namespace ArchiSteamFarm.Core;
4244

@@ -69,13 +71,20 @@ internal static string Version {
6971
}
7072
}
7173

74+
private static SafeHandle? InhibitLock;
7275
private static Mutex? SingleInstance;
7376

74-
internal static void CoreInit(bool minimized, bool systemRequired) {
77+
internal static async Task CoreInit(bool minimized, bool systemRequired) {
7578
if (minimized) {
7679
MinimizeConsoleWindow();
7780
}
7881

82+
if (OperatingSystem.IsLinux()) {
83+
if (systemRequired) {
84+
await LinuxKeepSystemActive().ConfigureAwait(false);
85+
}
86+
}
87+
7988
if (OperatingSystem.IsWindows()) {
8089
if (systemRequired) {
8190
WindowsKeepSystemActive();
@@ -181,6 +190,12 @@ internal static void UnregisterProcess() {
181190
// Instead, we'll dispose the mutex which should automatically release it by the CLR
182191
SingleInstance.Dispose();
183192
SingleInstance = null;
193+
194+
// Release the inhibit lock as well, if needed
195+
if (InhibitLock != null) {
196+
InhibitLock.Dispose();
197+
InhibitLock = null;
198+
}
184199
}
185200

186201
internal static bool VerifyEnvironment() {
@@ -261,6 +276,79 @@ internal static void WindowsStopFlashingConsoleWindow() {
261276
NativeMethods.FlashWindowEx(ref flashInfo);
262277
}
263278

279+
[SupportedOSPlatform("Linux")]
280+
private static async Task LinuxKeepSystemActive() {
281+
if (!OperatingSystem.IsLinux()) {
282+
throw new PlatformNotSupportedException();
283+
}
284+
285+
// Docs: https://systemd.io/INHIBITOR_LOCKS
286+
string? systemAddress = Address.System;
287+
288+
if (string.IsNullOrEmpty(systemAddress)) {
289+
ASF.ArchiLogger.LogGenericError(Strings.FormatWarningFailedWithError(nameof(systemAddress)));
290+
291+
return;
292+
}
293+
294+
using Connection connection = new(systemAddress);
295+
296+
try {
297+
await connection.ConnectAsync().ConfigureAwait(false);
298+
} catch (ConnectException e) {
299+
// Possible if no DBus is available at all
300+
ASF.ArchiLogger.LogGenericDebuggingException(e);
301+
ASF.ArchiLogger.LogGenericError(Strings.FormatWarningFailedWithError(nameof(connection)));
302+
303+
return;
304+
}
305+
306+
MessageWriter writer = connection.GetMessageWriter();
307+
308+
writer.WriteMethodCallHeader(
309+
"org.freedesktop.login1",
310+
"/org/freedesktop/login1",
311+
"org.freedesktop.login1.Manager",
312+
"Inhibit",
313+
"ssss"
314+
);
315+
316+
// Colon-separated list of lock types
317+
writer.WriteString("idle");
318+
319+
// Human-readable, descriptive string of who is taking the lock
320+
writer.WriteString(SharedInfo.PublicIdentifier);
321+
322+
// Human-readable, descriptive string of why the lock is taken
323+
writer.WriteString("--system-required");
324+
325+
// Mode
326+
writer.WriteString("block");
327+
328+
MessageBuffer message = writer.CreateMessage();
329+
330+
try {
331+
// Inhibit() returns a single value, a file descriptor that encapsulates the lock
332+
InhibitLock = await connection.CallMethodAsync(
333+
message, static (response, _) => {
334+
Reader reader = response.GetBodyReader();
335+
336+
return reader.ReadHandle<SafeFileHandle>();
337+
}
338+
).ConfigureAwait(false);
339+
} catch (DBusException e) {
340+
// Possible if login manager does not support inhibit, although that should be super rare
341+
ASF.ArchiLogger.LogGenericDebuggingException(e);
342+
ASF.ArchiLogger.LogGenericError(Strings.FormatWarningFailedWithError(nameof(connection)));
343+
344+
return;
345+
}
346+
347+
if (InhibitLock == null) {
348+
ASF.ArchiLogger.LogGenericError(Strings.FormatWarningFailedWithError(nameof(InhibitLock)));
349+
}
350+
}
351+
264352
private static void MinimizeConsoleWindow() {
265353
(_, int top) = Console.GetCursorPosition();
266354

ArchiSteamFarm/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ private static async Task<bool> InitCore(IReadOnlyCollection<string>? args) {
278278
return false;
279279
}
280280

281-
OS.CoreInit(Minimized, SystemRequired);
281+
await OS.CoreInit(Minimized, SystemRequired).ConfigureAwait(false);
282282

283283
Console.Title = SharedInfo.ProgramIdentifier;
284284
ASF.ArchiLogger.LogGenericInfo(SharedInfo.ProgramIdentifier);

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,6 @@
2121
<PackageVersion Include="System.Composition" Version="10.0.1" />
2222
<PackageVersion Include="System.Composition.AttributedModel" Version="10.0.1" />
2323
<PackageVersion Include="System.Security.Cryptography.ProtectedData" Version="10.0.1" />
24+
<PackageVersion Include="Tmds.DBus.Protocol" Version="0.21.2" />
2425
</ItemGroup>
2526
</Project>

0 commit comments

Comments
 (0)