diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/DesktopModule.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/DesktopModule.kt index 738ffc8041d3c38cc2b321743271f0fb95a726c8..2fe9d0556de97a3695869ad8be04d53f3a12ac79 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/DesktopModule.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/DesktopModule.kt @@ -52,8 +52,9 @@ import org.briarproject.bramble.util.OsUtils.isMac import org.briarproject.briar.attachment.AttachmentModule import org.briarproject.briar.desktop.attachment.media.ImageCompressor import org.briarproject.briar.desktop.attachment.media.ImageCompressorImpl -import org.briarproject.briar.desktop.notification.NotificationProvider +import org.briarproject.briar.desktop.notification.SoundNotificationProvider import org.briarproject.briar.desktop.notification.StubNotificationProvider +import org.briarproject.briar.desktop.notification.VisualNotificationProvider import org.briarproject.briar.desktop.notification.linux.LibnotifyNotificationProvider import org.briarproject.briar.desktop.settings.Configuration import org.briarproject.briar.desktop.settings.ConfigurationImpl @@ -205,9 +206,13 @@ internal class DesktopModule( @Provides @Singleton - internal fun provideNotificationProvider(): NotificationProvider = + internal fun provideVisualNotificationProvider(): VisualNotificationProvider = if (isLinux()) LibnotifyNotificationProvider else StubNotificationProvider + @Provides + @Singleton + internal fun provideSoundNotificationProvider() = SoundNotificationProvider + @Provides @Singleton internal fun provideMessageCounter(messageCounter: MessageCounterImpl): MessageCounter = messageCounter diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/notification/NotificationProvider.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/notification/NotificationProvider.kt index 4d4a3e78468dda4e1d8870f6ef8a1b080adeaf7f..60449a37d827f73b03ab5837fc9d73ecad11aab3 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/notification/NotificationProvider.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/notification/NotificationProvider.kt @@ -38,12 +38,14 @@ interface NotificationProvider { fun notifyPrivateMessages(num: Int, contacts: Int) } -object StubNotificationProvider : NotificationProvider { +interface VisualNotificationProvider : NotificationProvider + +object StubNotificationProvider : VisualNotificationProvider { override val available: Boolean get() = false override val errorMessage: String - get() = i18n("settings.notifications.error.unsupported") + get() = i18n("settings.notifications.visual.error.unsupported") override fun init() {} override fun uninit() {} diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/notification/SoundNotificationProvider.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/notification/SoundNotificationProvider.kt new file mode 100644 index 0000000000000000000000000000000000000000..8611f17a789d3aa1ce8b0c17566253e06aaed7a0 --- /dev/null +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/notification/SoundNotificationProvider.kt @@ -0,0 +1,57 @@ +/* + * Briar Desktop + * Copyright (C) 2021-2022 The Briar Project + * + * This program 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. + * + * This program 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 this program. If not, see <https://www.gnu.org/licenses/>. + */ +package org.briarproject.briar.desktop.notification + +import mu.KotlinLogging +import org.briarproject.briar.desktop.utils.AudioUtils.loadAudioFromResource +import org.briarproject.briar.desktop.utils.AudioUtils.play +import org.briarproject.briar.desktop.utils.KLoggerUtils.e +import javax.sound.sampled.Clip + +object SoundNotificationProvider : NotificationProvider { + + private val LOG = KotlinLogging.logger {} + + private lateinit var sound: Clip + + override var available: Boolean = false + private set + + override val errorMessage: String + get() = "" + + override fun init() { + try { + sound = loadAudioFromResource("/audio/notification.wav") ?: throw Exception() // NON-NLS + available = true + } catch (ex: Exception) { + LOG.e(ex) { "Error while loading notification sound" } + } + } + + override fun uninit() { + if (available) { + sound.close() + available = false + } + } + + override fun notifyPrivateMessages(num: Int, contacts: Int) { + if (available) sound.play() + } +} diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/notification/linux/LibnotifyNotificationProvider.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/notification/linux/LibnotifyNotificationProvider.kt index 06728605d735f2aaa7b896fde2e2b92bb71d1677..3a0c44f4e05e15d639ebceec29faac1bc2845fc0 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/notification/linux/LibnotifyNotificationProvider.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/notification/linux/LibnotifyNotificationProvider.kt @@ -21,29 +21,21 @@ import com.sun.jna.Library import com.sun.jna.Native import com.sun.jna.Pointer import mu.KotlinLogging -import org.briarproject.briar.desktop.notification.NotificationProvider -import org.briarproject.briar.desktop.utils.AudioUtils.loadAudioFromResource -import org.briarproject.briar.desktop.utils.AudioUtils.play +import org.briarproject.briar.desktop.notification.VisualNotificationProvider import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18nF import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18nP import org.briarproject.briar.desktop.utils.KLoggerUtils.e import org.briarproject.briar.desktop.utils.KLoggerUtils.i -import javax.sound.sampled.Clip -object LibnotifyNotificationProvider : NotificationProvider { +object LibnotifyNotificationProvider : VisualNotificationProvider { private val LOG = KotlinLogging.logger {} - private var libNotifyAvailable: Boolean = false - private var soundAvailable: Boolean = false - private lateinit var libNotify: LibNotify - private lateinit var sound: Clip - - override val available: Boolean - get() = libNotifyAvailable + override var available: Boolean = false + private set private enum class Error { NONE, LOAD, INIT } @@ -51,19 +43,12 @@ object LibnotifyNotificationProvider : NotificationProvider { override val errorMessage: String get() = when (error) { - Error.LOAD -> i18n("settings.notifications.error.libnotify.load") - Error.INIT -> i18n("settings.notifications.error.libnotify.init") + Error.LOAD -> i18n("settings.notifications.visual.error.libnotify.load") + Error.INIT -> i18n("settings.notifications.visual.error.libnotify.init") else -> "" } override fun init() { - try { - sound = loadAudioFromResource("/audio/notification.wav") ?: throw Exception() // NON-NLS - soundAvailable = true - } catch (ex: Exception) { - LOG.e(ex) { "Error while loading notification sound" } - } - try { libNotify = Native.load("libnotify.so.4", LibNotify::class.java) // NON-NLS } catch (err: UnsatisfiedLinkError) { @@ -72,8 +57,8 @@ object LibnotifyNotificationProvider : NotificationProvider { return } - libNotifyAvailable = libNotify.notify_init(i18n("main.title")) - if (!libNotifyAvailable) { + available = libNotify.notify_init(i18n("main.title")) + if (!available) { error = Error.INIT LOG.e { "unable to initialize libnotify" } return @@ -90,22 +75,14 @@ object LibnotifyNotificationProvider : NotificationProvider { } override fun uninit() { - if (libNotifyAvailable) { + if (available) { libNotify.notify_uninit() - libNotifyAvailable = false - } - if (soundAvailable) { - sound.close() - soundAvailable = false + available = false } } override fun notifyPrivateMessages(num: Int, contacts: Int) { - if (!libNotifyAvailable) { - // play sound even if libnotify unavailable - if (soundAvailable) sound.play() - return - } + if (!available) return /** * summary @@ -153,8 +130,6 @@ object LibnotifyNotificationProvider : NotificationProvider { // todo: error handling LOG.e { "error while sending notification via libnotify" } } - - if (soundAvailable) sound.play() } /** diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/settings/EncryptedSettings.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/settings/EncryptedSettings.kt index 441e1afd87937e3238e665dd122d9f96074b678e..23e6206b118aa643f1c7f4bcbce29ee302526e85 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/settings/EncryptedSettings.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/settings/EncryptedSettings.kt @@ -19,9 +19,11 @@ package org.briarproject.briar.desktop.settings interface EncryptedSettingsReadOnly { - val showNotifications: Boolean + val visualNotifications: Boolean + val soundNotifications: Boolean } interface EncryptedSettings : EncryptedSettingsReadOnly { - override var showNotifications: Boolean + override var visualNotifications: Boolean + override var soundNotifications: Boolean } diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/settings/EncryptedSettingsImpl.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/settings/EncryptedSettingsImpl.kt index 2645061fa19f01c0c16df44fe30f85213d162332..ad7eab753c447506d618787a90a642591d6eaf67 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/settings/EncryptedSettingsImpl.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/settings/EncryptedSettingsImpl.kt @@ -26,7 +26,8 @@ import javax.inject.Inject const val SETTINGS_NAMESPACE = "desktop-ui" // NON-NLS -const val PREF_NOTIFY = "notify" // NON-NLS +const val PREF_NOTIFY_VISUAL = "notify_visual" // NON-NLS +const val PREF_NOTIFY_SOUND = "notify_sound" // NON-NLS class EncryptedSettingsImpl @Inject internal constructor( @@ -42,11 +43,19 @@ class EncryptedSettingsImpl private lateinit var settings: Settings - override var showNotifications: Boolean - get() = settings.getBoolean(PREF_NOTIFY, true) + override var visualNotifications: Boolean + get() = settings.getBoolean(PREF_NOTIFY_VISUAL, true) @IoExecutor set(value) { - settings.putBoolean(PREF_NOTIFY, value) + settings.putBoolean(PREF_NOTIFY_VISUAL, value) + settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE) + } + + override var soundNotifications: Boolean + get() = settings.getBoolean(PREF_NOTIFY_SOUND, true) + @IoExecutor + set(value) { + settings.putBoolean(PREF_NOTIFY_SOUND, value) settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE) } } diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/settings/SettingDetails.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/settings/SettingDetails.kt index 8147dead82f986a08ff37157ee2674af41df8901..f1f48635f8f1b6d450e918276d98e0796611086b 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/settings/SettingDetails.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/settings/SettingDetails.kt @@ -108,24 +108,24 @@ fun SettingDetails(viewModel: SettingsViewModel) { } } - val notificationError = viewModel.notificationProviderState is NotificationProviderState.ERROR - val checked = !notificationError && viewModel.showNotifications.value + val notificationError = viewModel.visualNotificationProviderState is NotificationProviderState.ERROR + val visualNotificationsChecked = !notificationError && viewModel.visualNotifications.value DetailItem( - label = i18n("settings.notifications.title"), + label = i18n("settings.notifications.visual.title"), description = ( - if (checked) i18n("access.settings.currently_enabled") + if (visualNotificationsChecked) i18n("access.settings.currently_enabled") else i18n("access.settings.currently_disabled") ) + ". " + i18n("access.settings.click_to_toggle_notifications") ) { Switch( - checked = checked, - onCheckedChange = { viewModel.toggleShowNotifications() }, + checked = visualNotificationsChecked, + onCheckedChange = { viewModel.toggleVisualNotifications() }, enabled = !notificationError ) } - if (viewModel.notificationProviderState is NotificationProviderState.ERROR) { + if (viewModel.visualNotificationProviderState is NotificationProviderState.ERROR) { Row( Modifier.fillMaxWidth() .padding(horizontal = 16.dp) @@ -141,13 +141,28 @@ fun SettingDetails(viewModel: SettingsViewModel) { MaterialTheme.colors.warningForeground ) Text( - text = viewModel.notificationProviderState.message, + text = viewModel.visualNotificationProviderState.message, color = MaterialTheme.colors.warningForeground, style = MaterialTheme.typography.body2, modifier = Modifier.fillMaxWidth() ) } } + + val soundNotificationsChecked = viewModel.soundNotifications.value + + DetailItem( + label = i18n("settings.notifications.sound.title"), + description = ( + if (soundNotificationsChecked) i18n("access.settings.currently_enabled") + else i18n("access.settings.currently_disabled") + ) + ". " + i18n("access.settings.click_to_toggle_notifications") + ) { + Switch( + checked = soundNotificationsChecked, + onCheckedChange = { viewModel.toggleSoundNotifications() }, + ) + } } } diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/settings/SettingsViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/settings/SettingsViewModel.kt index aa52fab973117f75f66ae8bf21d97fac0407024b..382fa37eca4e1d4100ce64206561c4d8ce62c404 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/settings/SettingsViewModel.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/settings/SettingsViewModel.kt @@ -23,7 +23,7 @@ import org.briarproject.bramble.api.account.AccountManager import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator import org.briarproject.bramble.api.db.TransactionManager import org.briarproject.bramble.api.lifecycle.LifecycleManager -import org.briarproject.briar.desktop.notification.NotificationProvider +import org.briarproject.briar.desktop.notification.VisualNotificationProvider import org.briarproject.briar.desktop.settings.SettingsViewModel.NotificationProviderState.ERROR import org.briarproject.briar.desktop.settings.SettingsViewModel.NotificationProviderState.READY import org.briarproject.briar.desktop.threading.BriarExecutors @@ -50,7 +50,7 @@ constructor( private val encryptedSettings: EncryptedSettings, private val accountManager: AccountManager, private val passwordStrengthEstimator: PasswordStrengthEstimator, - private val notificationProvider: NotificationProvider, + private val visualNotificationProvider: VisualNotificationProvider, ) : DbViewModel(briarExecutors, lifecycleManager, db) { private val _selectedSetting = mutableStateOf(SettingCategory.DISPLAY) val selectedSetting = _selectedSetting.asState() @@ -69,16 +69,19 @@ constructor( val changePasswordSubViewModel = ChangePasswordSubViewModel(accountManager, passwordStrengthEstimator) - private val _showNotifications = mutableStateOf(encryptedSettings.showNotifications) - val showNotifications = _showNotifications.asState() + private val _visualNotifications = mutableStateOf(encryptedSettings.visualNotifications) + val visualNotifications = _visualNotifications.asState() + + private val _soundNotifications = mutableStateOf(encryptedSettings.soundNotifications) + val soundNotifications = _soundNotifications.asState() sealed class NotificationProviderState { object READY : NotificationProviderState() class ERROR(val message: String) : NotificationProviderState() } - val notificationProviderState = - if (notificationProvider.available) READY else ERROR(notificationProvider.errorMessage) + val visualNotificationProviderState = + if (visualNotificationProvider.available) READY else ERROR(visualNotificationProvider.errorMessage) fun selectSetting(selectedOption: SettingCategory) { _selectedSetting.value = selectedOption @@ -102,9 +105,15 @@ constructor( _changePasswordDialogVisible.value = false } - fun toggleShowNotifications() { - val newValue = !_showNotifications.value - _showNotifications.value = newValue - runOnDbThread { encryptedSettings.showNotifications = newValue } + fun toggleVisualNotifications() { + val newValue = !_visualNotifications.value + _visualNotifications.value = newValue + runOnDbThread { encryptedSettings.visualNotifications = newValue } + } + + fun toggleSoundNotifications() { + val newValue = !_soundNotifications.value + _soundNotifications.value = newValue + runOnDbThread { encryptedSettings.soundNotifications = newValue } } } diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt index b7acda7f56929cd9c3fcbe71dcdc799ffbb51183..44dc403110fff81ff35e95df42266ab08fc81037 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt @@ -48,7 +48,8 @@ import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent import org.briarproject.briar.desktop.expiration.ExpirationBanner import org.briarproject.briar.desktop.login.ErrorScreen import org.briarproject.briar.desktop.login.StartupScreen -import org.briarproject.briar.desktop.notification.NotificationProvider +import org.briarproject.briar.desktop.notification.SoundNotificationProvider +import org.briarproject.briar.desktop.notification.VisualNotificationProvider import org.briarproject.briar.desktop.settings.Configuration import org.briarproject.briar.desktop.settings.UnencryptedSettings.Theme.AUTO import org.briarproject.briar.desktop.settings.UnencryptedSettings.Theme.DARK @@ -92,7 +93,8 @@ constructor( private val eventBus: EventBus, private val viewModelProvider: ViewModelProvider, private val configuration: Configuration, - private val notificationProvider: NotificationProvider, + private val visualNotificationProvider: VisualNotificationProvider, + private val soundNotificationProvider: SoundNotificationProvider, private val messageCounter: MessageCounterImpl, ) : BriarUi { @@ -153,13 +155,15 @@ constructor( val messageCounterListener: MessageCounterListener = { (total, contacts) -> if (total > 0 && !focusState.focused) { window.iconImage = iconBadge - if (configuration.showNotifications) { - notificationProvider.notifyPrivateMessages(total, contacts) - } + if (configuration.visualNotifications) + visualNotificationProvider.notifyPrivateMessages(total, contacts) + if (configuration.soundNotifications) + soundNotificationProvider.notifyPrivateMessages(total, contacts) } } - notificationProvider.init() + visualNotificationProvider.init() + soundNotificationProvider.init() eventBus.addListener(eventListener) window.addWindowFocusListener(focusListener) messageCounter.addListener(messageCounterListener) @@ -168,7 +172,8 @@ constructor( messageCounter.removeListener(messageCounterListener) eventBus.removeListener(eventListener) window.removeWindowFocusListener(focusListener) - notificationProvider.uninit() + visualNotificationProvider.uninit() + soundNotificationProvider.uninit() } } diff --git a/briar-desktop/src/main/resources/strings/BriarDesktop.properties b/briar-desktop/src/main/resources/strings/BriarDesktop.properties index b7f24e82a2be50673a6c6e04bd51dec265811f36..5314cd961e49ab6160094bfb55a22a24345ce08c 100644 --- a/briar-desktop/src/main/resources/strings/BriarDesktop.properties +++ b/briar-desktop/src/main/resources/strings/BriarDesktop.properties @@ -286,9 +286,11 @@ settings.security.password.changed=Password has been changed. # Settings Notifications settings.notifications.title=Notifications -settings.notifications.error.unsupported=Notifications are currently not supported on your system. -settings.notifications.error.libnotify.load=Notifications can only be shown visually if libnotify is available. Please consider installing it following the usual installation procedure for your system. -settings.notifications.error.libnotify.init=Briar Desktop could not connect to any notification server. Please make sure to have a freedesktop.org-compliant notification server installed and configured properly. +settings.notifications.visual.title=Visual notifications +settings.notifications.visual.error.unsupported=Visual notifications are currently not supported on your system. You can still enable sound notifications. +settings.notifications.visual.error.libnotify.load=Notifications can only be shown visually if libnotify is available. Please consider installing it following the usual installation procedure for your system. +settings.notifications.visual.error.libnotify.init=Briar Desktop could not connect to any notification server. Please make sure to have a freedesktop.org-compliant notification server installed and configured properly. +settings.notifications.sound.title=Sound notifications # Settings Actions settings.actions.title=Actions diff --git a/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/DesktopTestModule.kt b/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/DesktopTestModule.kt index 7ac394f9433338a27bc40418a40b9b0edfb4ecdc..b37bfe2ef1bb30a7c3e10dc2b09a0246ae4e68f7 100644 --- a/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/DesktopTestModule.kt +++ b/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/DesktopTestModule.kt @@ -53,8 +53,9 @@ import org.briarproject.briar.api.test.TestAvatarCreator import org.briarproject.briar.attachment.AttachmentModule import org.briarproject.briar.desktop.attachment.media.ImageCompressor import org.briarproject.briar.desktop.attachment.media.ImageCompressorImpl -import org.briarproject.briar.desktop.notification.NotificationProvider +import org.briarproject.briar.desktop.notification.SoundNotificationProvider import org.briarproject.briar.desktop.notification.StubNotificationProvider +import org.briarproject.briar.desktop.notification.VisualNotificationProvider import org.briarproject.briar.desktop.notification.linux.LibnotifyNotificationProvider import org.briarproject.briar.desktop.settings.Configuration import org.briarproject.briar.desktop.settings.ConfigurationImpl @@ -211,9 +212,13 @@ internal class DesktopTestModule( @Provides @Singleton - internal fun provideNotificationProvider(): NotificationProvider = + internal fun provideVisualNotificationProvider(): VisualNotificationProvider = if (isLinux()) LibnotifyNotificationProvider else StubNotificationProvider + @Provides + @Singleton + internal fun provideSoundNotificationProvider() = SoundNotificationProvider + @Provides @Singleton internal fun provideMessageCounter(messageCounter: MessageCounterImpl): MessageCounter = messageCounter