diff --git a/core/src/main/java/bisq/core/app/AvoidStandbyModeService.java b/core/src/main/java/bisq/core/app/AvoidStandbyModeService.java index 365e0f9a70c..470f7b3c71a 100644 --- a/core/src/main/java/bisq/core/app/AvoidStandbyModeService.java +++ b/core/src/main/java/bisq/core/app/AvoidStandbyModeService.java @@ -22,13 +22,25 @@ 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.ArrayList; +import java.util.List; +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; @@ -46,6 +58,8 @@ public class AvoidStandbyModeService { private final Preferences preferences; private final Config config; + private final Optional inhibitorPathSpec; + private CountDownLatch stopLinuxInhibitorCountdownLatch; private volatile boolean isStopped; @@ -53,11 +67,12 @@ public class AvoidStandbyModeService { 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; - log.info("AvoidStandbyModeService stopped"); + if (Utilities.isLinux() && runningInhibitorProcess().isPresent()) { + Objects.requireNonNull(stopLinuxInhibitorCountdownLatch).countDown(); + } } else { start(); } @@ -73,13 +88,62 @@ public void init() { private void start() { isStopped = false; - log.info("AvoidStandbyModeService started"); - new Thread(this::play, "AvoidStandbyModeService-thread").start(); + if (Utilities.isLinux() || Utilities.isOSX()) { + startInhibitor(); + } else { + new Thread(this::playSilentAudioFile, "AvoidStandbyModeService-thread").start(); + } } + public void shutDown() { + isStopped = true; + stopInhibitor(); + } + + private void startInhibitor() { + try { + if (runningInhibitorProcess().isPresent()) { + log.info("Inhibitor already started"); + return; + } + inhibitCommand().ifPresent(cmd -> { + try { + new ProcessBuilder(cmd).start(); + log.info("Started -- disabled power management via {}", String.join(" ", cmd)); + if (Utilities.isLinux()) { + stopLinuxInhibitorCountdownLatch = new CountDownLatch(1); + new Thread(this::stopInhibitor, "StopAvoidStandbyModeService-thread").start(); + } + } catch (Exception e) { + e.printStackTrace(); + } + }); + } catch (Exception e) { + log.error("Cannot avoid standby mode", e); + } + } + + private void stopInhibitor() { + try { + // Cannot toggle off osx caffeinate, but it will shutdown with bisq. + if (Utilities.isLinux()) { + if (!isStopped) { + Objects.requireNonNull(stopLinuxInhibitorCountdownLatch).await(); + } + Optional runningInhibitor = runningInhibitorProcess(); + runningInhibitor.ifPresent(processHandle -> { + processHandle.destroy(); + log.info("Stopped"); + }); + } + } catch (Exception e) { + log.error("Stop inhibitor thread interrupted", e); + } + } - private void play() { + private void playSilentAudioFile() { try { + log.info("Started"); while (!isStopped) { try (AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(getSoundFile()); SourceDataLine sourceDataLine = getSourceDataLine(audioInputStream.getFormat())) { @@ -113,4 +177,73 @@ private SourceDataLine getSourceDataLine(AudioFormat audioFormat) throws LineUna DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, audioFormat); return (SourceDataLine) AudioSystem.getLine(dataLineInfo); } + + private Optional inhibitorPath() { + for (Optional installedInhibitor : installedInhibitors.get()) { + if (installedInhibitor.isPresent()) { + return installedInhibitor; + } + } + return Optional.empty(); // falling back to silent audio file player + } + + private Optional inhibitCommand() { + final String[] params; + if (inhibitorPathSpec.isPresent()) { + String cmd = inhibitorPathSpec.get(); + if (Utilities.isLinux()) { + params = cmd.contains("gnome-session-inhibit") + ? new String[]{cmd, "--app-id", "Bisq", "--inhibit", "suspend", "--reason", "Avoid Standby", "--inhibit-only"} + : new String[]{cmd, "--who", "Bisq", "--what", "sleep", "--why", "Avoid Standby", "--mode", "block", "tail", "-f", "/dev/null"}; + } else { + params = Utilities.isOSX() ? new String[]{cmd, "-w", "" + ProcessHandle.current().pid()} : null; + } + } else { + params = null; // fall back to silent audio file player + } + return params == null ? Optional.empty() : Optional.of(params); + } + + private Optional runningInhibitorProcess() { + final ProcessHandle[] inhibitorProc = new ProcessHandle[1]; + inhibitorPathSpec.ifPresent(cmd -> { + Optional jvmProc = ProcessHandle.of(ProcessHandle.current().pid()); + jvmProc.ifPresent(proc -> proc.children().forEach(childProc -> childProc.info().command().ifPresent(command -> { + if (command.equals(cmd) && childProc.isAlive()) { + inhibitorProc[0] = childProc; + } + }))); + }); + return inhibitorProc[0] == null ? Optional.empty() : Optional.of(inhibitorProc[0]); + } + + private final Predicate isCmdInstalled = (p) -> { + File executable = Paths.get(p).toFile(); + return executable.exists() && executable.canExecute(); + }; + + private final Function> cmdPath = (possiblePaths) -> { + for (String path : possiblePaths) { + if (isCmdInstalled.test(path)) { + return Optional.of(path); + } + } + return Optional.empty(); + }; + + private final Supplier>> installedInhibitors = () -> + new ArrayList<>() {{ + add(gnomeSessionInhibitPathSpec.get()); // On linux, preferred inhibitor is gnome-session-inhibit, + add(systemdInhibitPathSpec.get()); // then fall back to systemd-inhibit if it is installed. + add(caffeinatePathSec.get()); // On OSX, caffeinate should be installed. + }}; + + private final Supplier> gnomeSessionInhibitPathSpec = () -> + cmdPath.apply(new String[]{"/usr/bin/gnome-session-inhibit", "/bin/gnome-session-inhibit"}); + + private final Supplier> systemdInhibitPathSpec = () -> + cmdPath.apply(new String[]{"/usr/bin/systemd-inhibit", "/bin/systemd-inhibit"}); + + private final Supplier> caffeinatePathSec = () -> + cmdPath.apply(new String[]{"/usr/bin/caffeinate"}); } diff --git a/core/src/main/java/bisq/core/app/BisqExecutable.java b/core/src/main/java/bisq/core/app/BisqExecutable.java index 078691cd4b9..73f85dacc4f 100644 --- a/core/src/main/java/bisq/core/app/BisqExecutable.java +++ b/core/src/main/java/bisq/core/app/BisqExecutable.java @@ -229,6 +229,7 @@ public void gracefulShutDown(ResultHandler resultHandler) { injector.getInstance(BsqWalletService.class).shutDown(); }); }); + injector.getInstance(AvoidStandbyModeService.class).shutDown(); // we wait max 20 sec. UserThread.runAfter(() -> { log.warn("Timeout triggered resultHandler"); diff --git a/core/src/main/java/bisq/core/app/OSXStandbyModeDisabler.java b/core/src/main/java/bisq/core/app/OSXStandbyModeDisabler.java deleted file mode 100644 index cf5832abc8e..00000000000 --- a/core/src/main/java/bisq/core/app/OSXStandbyModeDisabler.java +++ /dev/null @@ -1,27 +0,0 @@ -package bisq.core.app; - -import bisq.common.util.Utilities; - -import java.io.IOException; - -import lombok.extern.slf4j.Slf4j; - -@Slf4j -public class OSXStandbyModeDisabler { - public void doIt() { - if (!Utilities.isOSX()) { - return; - } - long pid = ProcessHandle.current().pid(); - try { - String[] params = {"/usr/bin/caffeinate", "-w", "" + pid}; - - // we only start the process. caffeinate blocks until we exit. - new ProcessBuilder(params).start(); - log.info("disabled power management via " + String.join(" ", params)); - } catch (IOException e) { - log.error("could not disable standby mode on osx", e); - } - - } -} diff --git a/desktop/src/main/java/bisq/desktop/app/BisqApp.java b/desktop/src/main/java/bisq/desktop/app/BisqApp.java index aade8378880..f8d35763edc 100644 --- a/desktop/src/main/java/bisq/desktop/app/BisqApp.java +++ b/desktop/src/main/java/bisq/desktop/app/BisqApp.java @@ -33,7 +33,6 @@ import bisq.desktop.util.ImageUtil; import bisq.core.app.AvoidStandbyModeService; -import bisq.core.app.OSXStandbyModeDisabler; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.WalletsManager; import bisq.core.dao.governance.voteresult.MissingDataRequestService; @@ -133,7 +132,6 @@ public void startApplication(Runnable onUiReadyHandler) { scene = createAndConfigScene(mainView, injector); setupStage(scene); - injector.getInstance(OSXStandbyModeDisabler.class).doIt(); injector.getInstance(AvoidStandbyModeService.class).init(); UserThread.runPeriodically(() -> Profiler.printSystemLoad(log), LOG_MEMORY_PERIOD_MIN, TimeUnit.MINUTES); diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index 976b440e638..6fe9ff8fa12 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -173,7 +173,7 @@ public PreferencesView(PreferencesViewModel model, !rpcUser.isEmpty() && !rpcPassword.isEmpty() && rpcBlockNotificationPort != Config.UNSPECIFIED_PORT; - this.displayStandbyModeFeature = Utilities.isOSX() || Utilities.isWindows(); + this.displayStandbyModeFeature = Utilities.isLinux() || Utilities.isOSX() || Utilities.isWindows(); } @Override