Play silent sound to avoid standby mode

Apple disabled options do avoid App Nap in recent OSX versions.
Playing an inaudible sound marks the application for preventing to get
set to standby mode as well as App Nap mode.
The alternative to that "hack" would be to add OSX native code, but
even then it is likely only possible to prevent App Nap but not
sleep mode.
If Bisq is in App Nap mode offers cannot be taken as well the user loses
network connection and offers.
See: https://github.com/bisq-network/bisq/issues/1701
This commit is contained in:
Manfred Karrer 2018-09-21 14:50:03 -05:00
parent 1504e4992a
commit 416d9ba8f3
No known key found for this signature in database
GPG key ID: 401250966A6B2C46
9 changed files with 157 additions and 4 deletions

View file

@ -1263,6 +1263,7 @@ message PreferencesPayload {
bool use_trade_notifications = 42;
bool use_market_notifications = 43;
bool use_price_notifications = 44;
bool use_standby_mode = 45;
}
///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -19,6 +19,7 @@ package bisq.core;
import bisq.core.alert.AlertModule;
import bisq.core.app.AppOptionKeys;
import bisq.core.app.AvoidStandbyMode;
import bisq.core.app.BisqEnvironment;
import bisq.core.app.BisqFacade;
import bisq.core.app.BisqSetup;
@ -93,6 +94,7 @@ public class CoreModule extends AppModule {
bind(Preferences.class).in(Singleton.class);
bind(BridgeAddressProvider.class).to(Preferences.class).in(Singleton.class);
bind(CorruptedDatabaseFilesHandler.class).in(Singleton.class);
bind(AvoidStandbyMode.class).in(Singleton.class);
bind(SeedNodeAddressLookup.class).in(Singleton.class);
bind(SeedNodeRepository.class).to(DefaultSeedNodeRepository.class).in(Singleton.class);

View file

@ -0,0 +1,118 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.app;
import bisq.core.user.Preferences;
import javax.inject.Inject;
import org.apache.commons.io.IOUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import lombok.extern.slf4j.Slf4j;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;
@Slf4j
public class AvoidStandbyMode {
private final Preferences preferences;
private volatile boolean isStopped;
@Inject
public AvoidStandbyMode(Preferences preferences) {
this.preferences = preferences;
preferences.getUseStandbyModeProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
isStopped = true;
log.info("AvoidStandbyMode stopped");
} else {
start();
}
});
}
public void init() {
isStopped = preferences.isUseStandbyMode();
if (!isStopped) {
start();
}
}
private void start() {
isStopped = false;
log.info("AvoidStandbyMode started");
Thread thread = new Thread(this::play);
thread.setName("AvoidStandbyMode-thread");
thread.start();
}
private void play() {
if (!isStopped) {
OutputStream outputStream = null;
InputStream inputStream = null;
try {
inputStream = getClass().getClassLoader().getResourceAsStream("silent.aiff");
File soundFile = File.createTempFile("Bisq-", "-PreventAppNap-soundFile");
soundFile.deleteOnExit();
outputStream = new FileOutputStream(soundFile);
IOUtils.copy(inputStream, outputStream);
outputStream.close();
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(soundFile);
byte tempBuffer[] = new byte[10000];
AudioFormat audioFormat = audioInputStream.getFormat();
DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, audioFormat);
SourceDataLine sourceDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo);
sourceDataLine.open(audioFormat);
sourceDataLine.start();
int cnt;
while ((cnt = audioInputStream.read(tempBuffer, 0, tempBuffer.length)) != -1 && !isStopped) {
if (cnt > 0) {
sourceDataLine.write(tempBuffer, 0, cnt);
}
}
sourceDataLine.drain();
sourceDataLine.close();
play();
} catch (Exception e) {
log.error(e.toString());
e.printStackTrace();
} finally {
try {
if (inputStream != null)
inputStream.close();
if (outputStream != null)
outputStream.close();
} catch (IOException ignore) {
}
}
}
}
}

View file

@ -142,6 +142,8 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
private final String btcNodesFromOptions;
private final String useTorFlagFromOptions;
private final String referralIdFromOptions;
@Getter
private final BooleanProperty useStandbyModeProperty = new SimpleBooleanProperty(prefPayload.isUseStandbyMode());
///////////////////////////////////////////////////////////////////////////////////////////
@ -168,6 +170,12 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
GlobalSettings.setUseAnimations(prefPayload.isUseAnimations());
persist();
});
useStandbyModeProperty.addListener((ov) -> {
prefPayload.setUseStandbyMode(useStandbyModeProperty.get());
persist();
});
fiatCurrenciesAsObservable.addListener((javafx.beans.Observable ov) -> {
prefPayload.getFiatCurrencies().clear();
prefPayload.getFiatCurrencies().addAll(fiatCurrenciesAsObservable);
@ -254,6 +262,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
// set all properties
useAnimationsProperty.set(prefPayload.isUseAnimations());
useStandbyModeProperty.set(prefPayload.isUseStandbyMode());
useCustomWithdrawalTxFeeProperty.set(prefPayload.isUseCustomWithdrawalTxFee());
withdrawalTxFeeInBytesProperty.set(prefPayload.getWithdrawalTxFeeInBytes());
@ -583,6 +592,9 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
persist();
}
public void setUseStandbyMode(boolean useStandbyMode) {
this.useStandbyModeProperty.set(useStandbyMode);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getter
@ -782,5 +794,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
List<String> getBridgeAddresses();
long getWithdrawalTxFeeInBytes();
void setUseStandbyMode(boolean useStandbyMode);
}
}

View file

@ -109,6 +109,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
boolean useTradeNotifications = true;
boolean useMarketNotifications = true;
boolean usePriceNotifications = true;
boolean useStandbyMode = false;
///////////////////////////////////////////////////////////////////////////////////////////
@ -162,7 +163,8 @@ public final class PreferencesPayload implements PersistableEnvelope {
.setUseSoundForMobileNotifications(useSoundForMobileNotifications)
.setUseTradeNotifications(useTradeNotifications)
.setUseMarketNotifications(useMarketNotifications)
.setUsePriceNotifications(usePriceNotifications);
.setUsePriceNotifications(usePriceNotifications)
.setUseStandbyMode(useStandbyMode);
Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory);
Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((PB.TradeCurrency) e.toProtoMessage()));
Optional.ofNullable(offerBookChartScreenCurrencyCode).ifPresent(builder::setOfferBookChartScreenCurrencyCode);
@ -235,6 +237,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
proto.getUseSoundForMobileNotifications(),
proto.getUseTradeNotifications(),
proto.getUseMarketNotifications(),
proto.getUsePriceNotifications());
proto.getUsePriceNotifications(),
proto.getUseStandbyMode());
}
}

View file

@ -825,6 +825,7 @@ setting.preferences.general=General preferences
setting.preferences.explorer=Bitcoin block explorer:
setting.preferences.deviation=Max. deviation from market price:
setting.preferences.autoSelectArbitrators=Auto select arbitrators:
setting.preferences.avoidStandbyMode=Avoid standby mode:
setting.preferences.deviationToLarge=Values higher than {0}% are not allowed.
setting.preferences.txFee=Withdrawal transaction fee (satoshis/byte):
setting.preferences.useCustomValue=Use custom value

Binary file not shown.

View file

@ -34,6 +34,7 @@ import bisq.desktop.util.ImageUtil;
import bisq.core.alert.AlertManager;
import bisq.core.app.AppOptionKeys;
import bisq.core.app.AvoidStandbyMode;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.WalletsManager;
import bisq.core.filter.FilterManager;
@ -122,6 +123,8 @@ public class BisqApp extends Application implements UncaughtExceptionHandler {
scene = createAndConfigScene(mainView, injector);
setupStage(scene);
injector.getInstance(AvoidStandbyMode.class).init();
UserThread.runPeriodically(() -> Profiler.printSystemLoad(log), LOG_MEMORY_PERIOD_MIN, TimeUnit.MINUTES);
} catch (Throwable throwable) {
log.error("Error during app init", throwable);

View file

@ -94,7 +94,8 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
private ComboBox<TradeCurrency> preferredTradeCurrencyComboBox;
// private ComboBox<BaseCurrencyNetwork> selectBaseCurrencyNetworkComboBox;
private CheckBox useAnimationsCheckBox, autoSelectArbitratorsCheckBox, showOwnOffersInOfferBook, sortMarketCurrenciesNumericallyCheckBox, useCustomFeeCheckbox;
private CheckBox useAnimationsCheckBox, autoSelectArbitratorsCheckBox, avoidStandbyModeCheckBox,
showOwnOffersInOfferBook, sortMarketCurrenciesNumericallyCheckBox, useCustomFeeCheckbox;
private int gridRow = 0;
private InputTextField transactionFeeInputTextField, ignoreTradersListInputTextField, referralIdInputTextField;
private ChangeListener<Boolean> transactionFeeFocusedListener;
@ -177,7 +178,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
///////////////////////////////////////////////////////////////////////////////////////////
private void initializeGeneralOptions() {
TitledGroupBg titledGroupBg = addTitledGroupBg(root, gridRow, 8, Res.get("setting.preferences.general"));
TitledGroupBg titledGroupBg = addTitledGroupBg(root, gridRow, 9, Res.get("setting.preferences.general"));
GridPane.setColumnSpan(titledGroupBg, 4);
// selectBaseCurrencyNetwork
@ -296,6 +297,10 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
if (!newValue.equals(oldValue))
referralIdService.setReferralId(newValue);
};
// AvoidStandbyMode
avoidStandbyModeCheckBox = addLabelCheckBox(root, ++gridRow,
Res.get("setting.preferences.avoidStandbyMode"), "").second;
}
private void initializeDisplayCurrencies() {
@ -646,6 +651,11 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
autoSelectArbitratorsCheckBox.setSelected(preferences.isAutoSelectArbitrators());
autoSelectArbitratorsCheckBox.setOnAction(e -> preferences.setAutoSelectArbitrators(autoSelectArbitratorsCheckBox.isSelected()));
// We use opposite property (useStandbyMode) in preferences to have the default value (false) set as we want it,
// so users who update gets set avoidStandbyMode=true (useStandbyMode=false)
avoidStandbyModeCheckBox.setSelected(!preferences.isUseStandbyMode());
avoidStandbyModeCheckBox.setOnAction(e -> preferences.setUseStandbyMode(!avoidStandbyModeCheckBox.isSelected()));
}
/* private void onSelectNetwork() {
@ -695,5 +705,6 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
showOwnOffersInOfferBook.setOnAction(null);
autoSelectArbitratorsCheckBox.setOnAction(null);
resetDontShowAgainButton.setOnAction(null);
avoidStandbyModeCheckBox.setOnAction(null);
}
}