diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index d1c48860..9470bdca 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -434,6 +434,7 @@ Singleton { property bool soundNewNotification: true property bool soundVolumeChanged: true property bool soundPluggedIn: true + property bool soundLogin: false property int acMonitorTimeout: 0 property int acLockTimeout: 0 diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index 803124c5..c0dcda7d 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -242,6 +242,7 @@ var SPEC = { soundsEnabled: { def: true }, useSystemSoundTheme: { def: false }, + soundLogin: { def: false }, soundNewNotification: { def: true }, soundVolumeChanged: { def: true }, soundPluggedIn: { def: true }, diff --git a/quickshell/DMSShell.qml b/quickshell/DMSShell.qml index bf436f85..df3cbb82 100644 --- a/quickshell/DMSShell.qml +++ b/quickshell/DMSShell.qml @@ -221,10 +221,22 @@ Item { } } + Timer { + id: loginSoundTimer + // Half a second delay before playing login sound, otherwise the sound may be cut off + // 50 is the minimum that seems to work, but 500 is safer + interval: 500 + repeat: false + onTriggered: { + AudioService.playLoginSoundIfApplicable(); + } + } + Component.onCompleted: { dockRecreateDebounce.start(); // Force PolkitService singleton to initialize PolkitService.polkitAvailable; + loginSoundTimer.start(); } Loader { diff --git a/quickshell/Modules/Settings/SoundsTab.qml b/quickshell/Modules/Settings/SoundsTab.qml index 7201f89f..a8e9958f 100644 --- a/quickshell/Modules/Settings/SoundsTab.qml +++ b/quickshell/Modules/Settings/SoundsTab.qml @@ -91,6 +91,16 @@ Item { visible: AudioService.gsettingsAvailable } + SettingsToggleRow { + tab: "sounds" + tags: ["sound", "login", "startup", "boot"] + settingKey: "soundLogin" + text: I18n.tr("Login") + description: I18n.tr("Play sound after logging in") + checked: SettingsData.soundLogin + onToggled: checked => SettingsData.set("soundLogin", checked) + } + SettingsToggleRow { tab: "sounds" tags: ["sound", "notification", "new"] diff --git a/quickshell/Services/AudioService.qml b/quickshell/Services/AudioService.qml index a1eabcc5..015fedc3 100644 --- a/quickshell/Services/AudioService.qml +++ b/quickshell/Services/AudioService.qml @@ -26,6 +26,7 @@ Singleton { property var powerUnplugSound: null property var normalNotificationSound: null property var criticalNotificationSound: null + property var loginSound: null property real notificationsVolume: 1.0 property bool notificationsAudioMuted: false @@ -67,6 +68,16 @@ Singleton { } } + // Used in playLoginSoundIfApplicable() + Process { + id: loginSoundChecker + onExited: (exitCode) => { + if (exitCode === 0) { + playLoginSound(); + } + } +} + function getAvailableSinks() { const hidden = SessionData.hiddenOutputDeviceNames ?? []; return Pipewire.nodes.values.filter(node => node.audio && node.isSink && !node.isStream && !hidden.includes(node.name)); @@ -395,7 +406,7 @@ EOFCONFIG const themesToSearch = themeName !== "freedesktop" ? `${themeName} freedesktop` : themeName; const script = ` - for event_key in audio-volume-change power-plug power-unplug message message-new-instant; do + for event_key in audio-volume-change power-plug power-unplug message message-new-instant desktop-login; do found=0 case "$event_key" in @@ -457,7 +468,8 @@ EOFCONFIG "power-plug": "../assets/sounds/plasma/power-plug.wav", "power-unplug": "../assets/sounds/plasma/power-unplug.wav", "message": "../assets/sounds/freedesktop/message.wav", - "message-new-instant": "../assets/sounds/freedesktop/message-new-instant.wav" + "message-new-instant": "../assets/sounds/freedesktop/message-new-instant.wav", + "desktop-login": "../assets/sounds/freedesktop/desktop-login.wav" }; const specialConditions = { @@ -551,6 +563,10 @@ EOFCONFIG criticalNotificationSound.destroy(); criticalNotificationSound = null; } + if (loginSound) { + loginSound.destroy(); + loginSound = null; + } } function createSoundPlayers() { @@ -622,6 +638,19 @@ EOFCONFIG } } `, root, "AudioService.CriticalNotificationSound"); + + const loginPath = getSoundPath("desktop-login"); + loginSound = Qt.createQmlObject(` + import QtQuick + import QtMultimedia + MediaPlayer { + source: "${loginPath}" + audioOutput: AudioOutput { + ${deviceProperty}volume: notificationsVolume + } + } + `, root, "AudioService.LoginSound"); + } catch (e) { console.warn("AudioService: Error creating sound players:", e); } @@ -661,6 +690,31 @@ EOFCONFIG criticalNotificationSound.play(); } + function playLoginSound() { + if (!soundsAvailable || !loginSound || notificationsAudioMuted || isMediaPlaying()) { + return; + } + loginSound.play(); + } + + function playLoginSoundIfApplicable() { + if (SettingsData.soundsEnabled && SettingsData.soundLogin && !notificationsAudioMuted) { + // plays login sound on session start, but only if a specific file doesn't exist, + // to prevent it from playing on every DMS restart during the session + const runtimeDir = Quickshell.env("XDG_RUNTIME_DIR"); + const sessionId = Quickshell.env("XDG_SESSION_ID") || "0"; + + if (!runtimeDir) return; + + const loginFile = `${runtimeDir}/danklinux.login-${sessionId}`; + + // if file doesn't exist, touch it (0) + // If it exists, do nothing (1) + loginSoundChecker.command = ["sh", "-c", `[ ! -f ${loginFile} ] && touch ${loginFile}`]; + loginSoundChecker.running = true; + } + } + function playVolumeChangeSoundIfEnabled() { if (SettingsData.soundsEnabled && SettingsData.soundVolumeChanged && !notificationsAudioMuted) { playVolumeChangeSound(); diff --git a/quickshell/assets/sounds/freedesktop/desktop-login.oga b/quickshell/assets/sounds/freedesktop/desktop-login.oga new file mode 100644 index 00000000..b7da8e96 Binary files /dev/null and b/quickshell/assets/sounds/freedesktop/desktop-login.oga differ diff --git a/quickshell/assets/sounds/freedesktop/desktop-login.wav b/quickshell/assets/sounds/freedesktop/desktop-login.wav new file mode 100644 index 00000000..d5112489 Binary files /dev/null and b/quickshell/assets/sounds/freedesktop/desktop-login.wav differ diff --git a/quickshell/translations/settings_search_index.json b/quickshell/translations/settings_search_index.json index 37f56a67..749fc4ae 100644 --- a/quickshell/translations/settings_search_index.json +++ b/quickshell/translations/settings_search_index.json @@ -5101,6 +5101,26 @@ ], "description": "Play sounds for system events" }, + { + "section": "soundLogin", + "label": "Login", + "tabIndex": 15, + "category": "Sounds", + "keywords": [ + "after", + "audio", + "boot", + "effects", + "logging", + "login", + "play", + "sfx", + "sound", + "sounds", + "startup" + ], + "description": "Play sound after logging in" + }, { "section": "soundNewNotification", "label": "New Notification",