Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Linux OS suspend inhibitors to avoid standby #4049

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 102 additions & 3 deletions core/src/main/java/bisq/core/app/AvoidStandbyModeService.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,23 @@
import bisq.common.config.Config;
import bisq.common.storage.FileUtil;
import bisq.common.storage.ResourceNotFoundException;
import bisq.common.util.Utilities;

import javax.inject.Inject;
import javax.inject.Singleton;

import java.nio.file.Paths;

import java.io.File;
import java.io.IOException;

import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import lombok.extern.slf4j.Slf4j;


Expand All @@ -46,14 +56,16 @@ public class AvoidStandbyModeService {

private final Preferences preferences;
private final Config config;

private final Optional<String> inhibitorPathSpec;
private CountDownLatch cancelInhibitSuspendLatch;
private volatile boolean isStopped;


@Inject
public AvoidStandbyModeService(Preferences preferences, Config config) {
this.preferences = preferences;
this.config = config;

this.inhibitorPathSpec = inhibitorPath();
preferences.getUseStandbyModeProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
isStopped = true;
Expand All @@ -74,9 +86,55 @@ public void init() {
private void start() {
isStopped = false;
log.info("AvoidStandbyModeService started");
new Thread(this::play, "AvoidStandbyModeService-thread").start();
if (Utilities.isLinux()) {
startInhibitor();
new Thread(this::stopInhibitor, "StopAvoidStandbyModeService-thread").start();
} else {
new Thread(this::play, "AvoidStandbyModeService-thread").start();
}
}

private void startInhibitor() {
try {
inhibitCommand().ifPresent(cmd -> {
try {
final Process process = new ProcessBuilder(cmd).start();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
if (process.isAlive()) {
process.destroy();
log.info("AvoidStandbyModeService shutdown");
}
}, "AvoidStandbyModeService.ShutDownHook"));
cancelInhibitSuspendLatch = new CountDownLatch(1);
log.info("disabled power management via {}", String.join(" ", cmd));
} catch (Exception e) {
e.printStackTrace();
}
});
} catch (Exception e) {
log.error("could not use inhibitor to avoid standby mode", e);
}
}

private void stopInhibitor() {
try {
if (!isStopped) {
Objects.requireNonNull(cancelInhibitSuspendLatch).await();
}
inhibitorPathSpec.ifPresent(cmd -> {
Optional<ProcessHandle> javaProcess = ProcessHandle.of(ProcessHandle.current().pid());
javaProcess.ifPresent(process -> process.children().forEach(childProcess -> childProcess.info().command().ifPresent(command -> {
if (command.equals(cmd) && childProcess.isAlive()) {
childProcess.destroy();
log.info("AvoidStandbyModeService stopped");
}
})));
});
Objects.requireNonNull(cancelInhibitSuspendLatch).countDown();
} catch (Exception e) {
log.error("stop inhibitor thread interrupted", e);
}
}

private void play() {
try {
Expand Down Expand Up @@ -113,4 +171,45 @@ private SourceDataLine getSourceDataLine(AudioFormat audioFormat) throws LineUna
DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, audioFormat);
return (SourceDataLine) AudioSystem.getLine(dataLineInfo);
}

private Optional<String> inhibitorPath() {
Optional<String> gnomeSessionInhibitorPathSpec = gnomeSessionInhibitPathSpec.get();
return gnomeSessionInhibitorPathSpec.isPresent() ? gnomeSessionInhibitorPathSpec : systemdInhibitPathSpec.get();
}

private Optional<String[]> inhibitCommand() {
if (inhibitorPathSpec.isPresent()) {
String cmd = inhibitorPathSpec.get();
final String[] params;
if (cmd.contains("gnome-session-inhibit")) {
params = new String[]{cmd, "--app-id", "Bisq", "--inhibit", "suspend", "--reason", "Avoid Standby", "--inhibit-only"};
} else {
// systemd-inhibit arguments: must run a command; there is no --inhibit-only option
params = new String[]{cmd, "--who", "Bisq", "--what", "sleep", "--why", "Avoid Standby", "--mode", "block", "tail", "-f", "/dev/null"};
}
return Optional.of(params);
} else {
return Optional.empty();
}
}

private final Predicate<String> isCommandInstalled = (p) -> {
File executable = Paths.get(p).toFile();
return executable.exists() && executable.canExecute();
};

private final Function<String[], Optional<String>> commandPath = (possiblePaths) -> {
for (String path : possiblePaths) {
if (isCommandInstalled.test(path)) {
return Optional.of(path);
}
}
return Optional.empty();
};

private final Supplier<Optional<String>> gnomeSessionInhibitPathSpec = () ->
commandPath.apply(new String[]{"/usr/bin/gnome-session-inhibit", "/bin/gnome-session-inhibit"});

private final Supplier<Optional<String>> systemdInhibitPathSpec = () ->
commandPath.apply(new String[]{"/usr/bin/systemd-inhibit", "/bin/systemd-inhibit"});
}