1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -05:00

osd/audio: bind audio change to pipewire, suppress OSDs on startup and

resume from suspend
This commit is contained in:
bbedward
2025-11-28 11:05:53 -05:00
parent 94a1aebe2b
commit dd409b4d1c
4 changed files with 418 additions and 396 deletions

View File

@@ -1,5 +1,4 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtCore
@@ -15,7 +14,6 @@ Singleton {
readonly property PwNode sink: Pipewire.defaultAudioSink
readonly property PwNode source: Pipewire.defaultAudioSource
property bool suppressOSD: true
property bool soundsAvailable: false
property bool gsettingsAvailable: false
property var availableSoundThemes: []
@@ -33,12 +31,14 @@ Singleton {
signal micMuteChanged
Timer {
id: startupTimer
interval: 500
repeat: false
running: true
onTriggered: root.suppressOSD = false
Connections {
target: root.sink?.audio ?? null
function onVolumeChanged() {
if (SessionData.suppressOSD)
return;
root.playVolumeChangeSoundIfEnabled();
}
}
function detectSoundsAvailability() {
@@ -47,35 +47,33 @@ Singleton {
import QtQuick
import QtMultimedia
Item {}
`, root, "AudioService.TestComponent")
`, root, "AudioService.TestComponent");
if (testObj) {
testObj.destroy()
testObj.destroy();
}
soundsAvailable = true
return true
soundsAvailable = true;
return true;
} catch (e) {
soundsAvailable = false
return false
soundsAvailable = false;
return false;
}
}
function checkGsettings() {
Proc.runCommand("checkGsettings", ["sh", "-c", "gsettings get org.gnome.desktop.sound theme-name 2>/dev/null"], (output, exitCode) => {
gsettingsAvailable = (exitCode === 0)
gsettingsAvailable = (exitCode === 0);
if (gsettingsAvailable) {
scanSoundThemes()
getCurrentSoundTheme()
scanSoundThemes();
getCurrentSoundTheme();
}
}, 0)
}, 0);
}
function scanSoundThemes() {
const xdgDataDirs = Quickshell.env("XDG_DATA_DIRS")
const searchPaths = xdgDataDirs && xdgDataDirs.trim() !== ""
? xdgDataDirs.split(":").concat(Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation)))
: ["/usr/share", "/usr/local/share", Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation))]
const xdgDataDirs = Quickshell.env("XDG_DATA_DIRS");
const searchPaths = xdgDataDirs && xdgDataDirs.trim() !== "" ? xdgDataDirs.split(":").concat(Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation))) : ["/usr/share", "/usr/local/share", Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation))];
const basePaths = searchPaths.map(p => p + "/sounds").join(" ")
const basePaths = searchPaths.map(p => p + "/sounds").join(" ");
const script = `
for base_dir in ${basePaths}; do
[ -d "$base_dir" ] || continue
@@ -84,65 +82,63 @@ Singleton {
basename "$theme_dir"
done
done | sort -u
`
`;
Proc.runCommand("scanSoundThemes", ["sh", "-c", script], (output, exitCode) => {
if (exitCode === 0 && output.trim()) {
const themes = output.trim().split('\n').filter(t => t && t.length > 0)
availableSoundThemes = themes
const themes = output.trim().split('\n').filter(t => t && t.length > 0);
availableSoundThemes = themes;
} else {
availableSoundThemes = []
availableSoundThemes = [];
}
}, 0)
}, 0);
}
function getCurrentSoundTheme() {
Proc.runCommand("getCurrentSoundTheme", ["sh", "-c", "gsettings get org.gnome.desktop.sound theme-name 2>/dev/null | sed \"s/'//g\""], (output, exitCode) => {
if (exitCode === 0 && output.trim()) {
currentSoundTheme = output.trim()
console.log("AudioService: Current system sound theme:", currentSoundTheme)
currentSoundTheme = output.trim();
console.log("AudioService: Current system sound theme:", currentSoundTheme);
if (SettingsData.useSystemSoundTheme) {
discoverSoundFiles(currentSoundTheme)
discoverSoundFiles(currentSoundTheme);
}
} else {
currentSoundTheme = ""
console.log("AudioService: No system sound theme found")
currentSoundTheme = "";
console.log("AudioService: No system sound theme found");
}
}, 0)
}, 0);
}
function setSoundTheme(themeName) {
if (!themeName || themeName === currentSoundTheme) {
return
return;
}
Proc.runCommand("setSoundTheme", ["sh", "-c", `gsettings set org.gnome.desktop.sound theme-name '${themeName}'`], (output, exitCode) => {
if (exitCode === 0) {
currentSoundTheme = themeName
currentSoundTheme = themeName;
if (SettingsData.useSystemSoundTheme) {
discoverSoundFiles(themeName)
discoverSoundFiles(themeName);
}
}
}, 0)
}, 0);
}
function discoverSoundFiles(themeName) {
if (!themeName) {
soundFilePaths = {}
soundFilePaths = {};
if (soundsAvailable) {
destroySoundPlayers()
createSoundPlayers()
destroySoundPlayers();
createSoundPlayers();
}
return
return;
}
const xdgDataDirs = Quickshell.env("XDG_DATA_DIRS")
const searchPaths = xdgDataDirs && xdgDataDirs.trim() !== ""
? xdgDataDirs.split(":").concat(Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation)))
: ["/usr/share", "/usr/local/share", Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation))]
const xdgDataDirs = Quickshell.env("XDG_DATA_DIRS");
const searchPaths = xdgDataDirs && xdgDataDirs.trim() !== "" ? xdgDataDirs.split(":").concat(Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation))) : ["/usr/share", "/usr/local/share", Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation))];
const extensions = ["oga", "ogg", "wav", "mp3", "flac"]
const themesToSearch = themeName !== "freedesktop" ? `${themeName} freedesktop` : themeName
const extensions = ["oga", "ogg", "wav", "mp3", "flac"];
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
@@ -179,26 +175,26 @@ Singleton {
[ $found -eq 1 ] && break
done
done
`
`;
Proc.runCommand("discoverSoundFiles", ["sh", "-c", script], (output, exitCode) => {
const paths = {}
const paths = {};
if (exitCode === 0 && output.trim()) {
const lines = output.trim().split('\n')
const lines = output.trim().split('\n');
for (let line of lines) {
const parts = line.split('=')
const parts = line.split('=');
if (parts.length === 2) {
paths[parts[0]] = "file://" + parts[1]
paths[parts[0]] = "file://" + parts[1];
}
}
}
soundFilePaths = paths
soundFilePaths = paths;
if (soundsAvailable) {
destroySoundPlayers()
createSoundPlayers()
destroySoundPlayers();
createSoundPlayers();
}
}, 0)
}, 0);
}
function getSoundPath(soundEvent) {
@@ -208,45 +204,45 @@ Singleton {
"power-unplug": "../assets/sounds/plasma/power-unplug.wav",
"message": "../assets/sounds/freedesktop/message.wav",
"message-new-instant": "../assets/sounds/freedesktop/message-new-instant.wav"
}
};
const specialConditions = {
"smooth": ["audio-volume-change"]
}
};
const themeLower = currentSoundTheme.toLowerCase()
const themeLower = currentSoundTheme.toLowerCase();
if (SettingsData.useSystemSoundTheme && specialConditions[themeLower]?.includes(soundEvent)) {
const bundledPath = Qt.resolvedUrl(soundMap[soundEvent] || "../assets/sounds/freedesktop/message.wav")
console.log("AudioService: Using bundled sound (special condition) for", soundEvent, ":", bundledPath)
return bundledPath
const bundledPath = Qt.resolvedUrl(soundMap[soundEvent] || "../assets/sounds/freedesktop/message.wav");
console.log("AudioService: Using bundled sound (special condition) for", soundEvent, ":", bundledPath);
return bundledPath;
}
if (SettingsData.useSystemSoundTheme && soundFilePaths[soundEvent]) {
console.log("AudioService: Using system sound for", soundEvent, ":", soundFilePaths[soundEvent])
return soundFilePaths[soundEvent]
console.log("AudioService: Using system sound for", soundEvent, ":", soundFilePaths[soundEvent]);
return soundFilePaths[soundEvent];
}
const bundledPath = Qt.resolvedUrl(soundMap[soundEvent] || "../assets/sounds/freedesktop/message.wav")
console.log("AudioService: Using bundled sound for", soundEvent, ":", bundledPath)
return bundledPath
const bundledPath = Qt.resolvedUrl(soundMap[soundEvent] || "../assets/sounds/freedesktop/message.wav");
console.log("AudioService: Using bundled sound for", soundEvent, ":", bundledPath);
return bundledPath;
}
function reloadSounds() {
console.log("AudioService: Reloading sounds, useSystemSoundTheme:", SettingsData.useSystemSoundTheme, "currentSoundTheme:", currentSoundTheme)
console.log("AudioService: Reloading sounds, useSystemSoundTheme:", SettingsData.useSystemSoundTheme, "currentSoundTheme:", currentSoundTheme);
if (SettingsData.useSystemSoundTheme && currentSoundTheme) {
discoverSoundFiles(currentSoundTheme)
discoverSoundFiles(currentSoundTheme);
} else {
soundFilePaths = {}
soundFilePaths = {};
if (soundsAvailable) {
destroySoundPlayers()
createSoundPlayers()
destroySoundPlayers();
createSoundPlayers();
}
}
}
function setupMediaDevices() {
if (!soundsAvailable || mediaDevices) {
return
return;
}
try {
@@ -259,7 +255,7 @@ Singleton {
console.log("AudioService: MediaDevices initialized, default output:", defaultAudioOutput?.description)
}
}
`, root, "AudioService.MediaDevices")
`, root, "AudioService.MediaDevices");
if (mediaDevices) {
mediaDevicesConnections = Qt.createQmlObject(`
@@ -272,48 +268,48 @@ Singleton {
root.createSoundPlayers()
}
}
`, root, "AudioService.MediaDevicesConnections")
`, root, "AudioService.MediaDevicesConnections");
}
} catch (e) {
console.log("AudioService: MediaDevices not available, using default audio output")
mediaDevices = null
console.log("AudioService: MediaDevices not available, using default audio output");
mediaDevices = null;
}
}
function destroySoundPlayers() {
if (volumeChangeSound) {
volumeChangeSound.destroy()
volumeChangeSound = null
volumeChangeSound.destroy();
volumeChangeSound = null;
}
if (powerPlugSound) {
powerPlugSound.destroy()
powerPlugSound = null
powerPlugSound.destroy();
powerPlugSound = null;
}
if (powerUnplugSound) {
powerUnplugSound.destroy()
powerUnplugSound = null
powerUnplugSound.destroy();
powerUnplugSound = null;
}
if (normalNotificationSound) {
normalNotificationSound.destroy()
normalNotificationSound = null
normalNotificationSound.destroy();
normalNotificationSound = null;
}
if (criticalNotificationSound) {
criticalNotificationSound.destroy()
criticalNotificationSound = null
criticalNotificationSound.destroy();
criticalNotificationSound = null;
}
}
function createSoundPlayers() {
if (!soundsAvailable) {
return
return;
}
setupMediaDevices()
setupMediaDevices();
try {
const deviceProperty = mediaDevices ? `device: root.mediaDevices.defaultAudioOutput\n ` : ""
const deviceProperty = mediaDevices ? `device: root.mediaDevices.defaultAudioOutput\n ` : "";
const volumeChangePath = getSoundPath("audio-volume-change")
const volumeChangePath = getSoundPath("audio-volume-change");
volumeChangeSound = Qt.createQmlObject(`
import QtQuick
import QtMultimedia
@@ -323,9 +319,9 @@ Singleton {
${deviceProperty}volume: 1.0
}
}
`, root, "AudioService.VolumeChangeSound")
`, root, "AudioService.VolumeChangeSound");
const powerPlugPath = getSoundPath("power-plug")
const powerPlugPath = getSoundPath("power-plug");
powerPlugSound = Qt.createQmlObject(`
import QtQuick
import QtMultimedia
@@ -335,9 +331,9 @@ Singleton {
${deviceProperty}volume: 1.0
}
}
`, root, "AudioService.PowerPlugSound")
`, root, "AudioService.PowerPlugSound");
const powerUnplugPath = getSoundPath("power-unplug")
const powerUnplugPath = getSoundPath("power-unplug");
powerUnplugSound = Qt.createQmlObject(`
import QtQuick
import QtMultimedia
@@ -347,9 +343,9 @@ Singleton {
${deviceProperty}volume: 1.0
}
}
`, root, "AudioService.PowerUnplugSound")
`, root, "AudioService.PowerUnplugSound");
const messagePath = getSoundPath("message")
const messagePath = getSoundPath("message");
normalNotificationSound = Qt.createQmlObject(`
import QtQuick
import QtMultimedia
@@ -359,9 +355,9 @@ Singleton {
${deviceProperty}volume: 1.0
}
}
`, root, "AudioService.NormalNotificationSound")
`, root, "AudioService.NormalNotificationSound");
const messageNewInstantPath = getSoundPath("message-new-instant")
const messageNewInstantPath = getSoundPath("message-new-instant");
criticalNotificationSound = Qt.createQmlObject(`
import QtQuick
import QtMultimedia
@@ -371,114 +367,114 @@ Singleton {
${deviceProperty}volume: 1.0
}
}
`, root, "AudioService.CriticalNotificationSound")
`, root, "AudioService.CriticalNotificationSound");
} catch (e) {
console.warn("AudioService: Error creating sound players:", e)
console.warn("AudioService: Error creating sound players:", e);
}
}
function playVolumeChangeSound() {
if (soundsAvailable && volumeChangeSound) {
volumeChangeSound.play()
volumeChangeSound.play();
}
}
function playPowerPlugSound() {
if (soundsAvailable && powerPlugSound) {
powerPlugSound.play()
powerPlugSound.play();
}
}
function playPowerUnplugSound() {
if (soundsAvailable && powerUnplugSound) {
powerUnplugSound.play()
powerUnplugSound.play();
}
}
function playNormalNotificationSound() {
if (soundsAvailable && normalNotificationSound && !SessionData.doNotDisturb) {
normalNotificationSound.play()
normalNotificationSound.play();
}
}
function playCriticalNotificationSound() {
if (soundsAvailable && criticalNotificationSound && !SessionData.doNotDisturb) {
criticalNotificationSound.play()
criticalNotificationSound.play();
}
}
function playVolumeChangeSoundIfEnabled() {
if (SettingsData.soundsEnabled && SettingsData.soundVolumeChanged) {
playVolumeChangeSound()
playVolumeChangeSound();
}
}
function displayName(node) {
if (!node) {
return ""
return "";
}
if (node.properties && node.properties["device.description"]) {
return node.properties["device.description"]
return node.properties["device.description"];
}
if (node.description && node.description !== node.name) {
return node.description
return node.description;
}
if (node.nickname && node.nickname !== node.name) {
return node.nickname
return node.nickname;
}
if (node.name.includes("analog-stereo")) {
return "Built-in Speakers"
return "Built-in Speakers";
}
if (node.name.includes("bluez")) {
return "Bluetooth Audio"
return "Bluetooth Audio";
}
if (node.name.includes("usb")) {
return "USB Audio"
return "USB Audio";
}
if (node.name.includes("hdmi")) {
return "HDMI Audio"
return "HDMI Audio";
}
return node.name
return node.name;
}
function subtitle(name) {
if (!name) {
return ""
return "";
}
if (name.includes('usb-')) {
if (name.includes('SteelSeries')) {
return "USB Gaming Headset"
return "USB Gaming Headset";
}
if (name.includes('Generic')) {
return "USB Audio Device"
return "USB Audio Device";
}
return "USB Audio"
return "USB Audio";
}
if (name.includes('pci-')) {
if (name.includes('01_00.1') || name.includes('01:00.1')) {
return "NVIDIA GPU Audio"
return "NVIDIA GPU Audio";
}
return "PCI Audio"
return "PCI Audio";
}
if (name.includes('bluez')) {
return "Bluetooth Audio"
return "Bluetooth Audio";
}
if (name.includes('analog')) {
return "Built-in Audio"
return "Built-in Audio";
}
if (name.includes('hdmi')) {
return "HDMI Audio"
return "HDMI Audio";
}
return ""
return "";
}
PwObjectTracker {
@@ -487,136 +483,134 @@ Singleton {
function setVolume(percentage) {
if (!root.sink?.audio) {
return "No audio sink available"
return "No audio sink available";
}
const clampedVolume = Math.max(0, Math.min(100, percentage))
root.sink.audio.volume = clampedVolume / 100
return `Volume set to ${clampedVolume}%`
const clampedVolume = Math.max(0, Math.min(100, percentage));
root.sink.audio.volume = clampedVolume / 100;
return `Volume set to ${clampedVolume}%`;
}
function toggleMute() {
if (!root.sink?.audio) {
return "No audio sink available"
return "No audio sink available";
}
root.sink.audio.muted = !root.sink.audio.muted
return root.sink.audio.muted ? "Audio muted" : "Audio unmuted"
root.sink.audio.muted = !root.sink.audio.muted;
return root.sink.audio.muted ? "Audio muted" : "Audio unmuted";
}
function setMicVolume(percentage) {
if (!root.source?.audio) {
return "No audio source available"
return "No audio source available";
}
const clampedVolume = Math.max(0, Math.min(100, percentage))
root.source.audio.volume = clampedVolume / 100
return `Microphone volume set to ${clampedVolume}%`
const clampedVolume = Math.max(0, Math.min(100, percentage));
root.source.audio.volume = clampedVolume / 100;
return `Microphone volume set to ${clampedVolume}%`;
}
function toggleMicMute() {
if (!root.source?.audio) {
return "No audio source available"
return "No audio source available";
}
root.source.audio.muted = !root.source.audio.muted
return root.source.audio.muted ? "Microphone muted" : "Microphone unmuted"
root.source.audio.muted = !root.source.audio.muted;
return root.source.audio.muted ? "Microphone muted" : "Microphone unmuted";
}
IpcHandler {
target: "audio"
function setvolume(percentage: string): string {
return root.setVolume(parseInt(percentage))
return root.setVolume(parseInt(percentage));
}
function increment(step: string): string {
if (!root.sink?.audio) {
return "No audio sink available"
return "No audio sink available";
}
if (root.sink.audio.muted) {
root.sink.audio.muted = false
root.sink.audio.muted = false;
}
const currentVolume = Math.round(root.sink.audio.volume * 100)
const stepValue = parseInt(step || "5")
const newVolume = Math.max(0, Math.min(100, currentVolume + stepValue))
const currentVolume = Math.round(root.sink.audio.volume * 100);
const stepValue = parseInt(step || "5");
const newVolume = Math.max(0, Math.min(100, currentVolume + stepValue));
root.sink.audio.volume = newVolume / 100
root.playVolumeChangeSoundIfEnabled()
return `Volume increased to ${newVolume}%`
root.sink.audio.volume = newVolume / 100;
return `Volume increased to ${newVolume}%`;
}
function decrement(step: string): string {
if (!root.sink?.audio) {
return "No audio sink available"
return "No audio sink available";
}
if (root.sink.audio.muted) {
root.sink.audio.muted = false
root.sink.audio.muted = false;
}
const currentVolume = Math.round(root.sink.audio.volume * 100)
const stepValue = parseInt(step || "5")
const newVolume = Math.max(0, Math.min(100, currentVolume - stepValue))
const currentVolume = Math.round(root.sink.audio.volume * 100);
const stepValue = parseInt(step || "5");
const newVolume = Math.max(0, Math.min(100, currentVolume - stepValue));
root.sink.audio.volume = newVolume / 100
root.playVolumeChangeSoundIfEnabled()
return `Volume decreased to ${newVolume}%`
root.sink.audio.volume = newVolume / 100;
return `Volume decreased to ${newVolume}%`;
}
function mute(): string {
return root.toggleMute()
return root.toggleMute();
}
function setmic(percentage: string): string {
return root.setMicVolume(parseInt(percentage))
return root.setMicVolume(parseInt(percentage));
}
function micmute(): string {
const result = root.toggleMicMute()
root.micMuteChanged()
return result
const result = root.toggleMicMute();
root.micMuteChanged();
return result;
}
function status(): string {
let result = "Audio Status:\n"
let result = "Audio Status:\n";
if (root.sink?.audio) {
const volume = Math.round(root.sink.audio.volume * 100)
const muteStatus = root.sink.audio.muted ? " (muted)" : ""
result += `Output: ${volume}%${muteStatus}\n`
const volume = Math.round(root.sink.audio.volume * 100);
const muteStatus = root.sink.audio.muted ? " (muted)" : "";
result += `Output: ${volume}%${muteStatus}\n`;
} else {
result += "Output: No sink available\n"
result += "Output: No sink available\n";
}
if (root.source?.audio) {
const micVolume = Math.round(root.source.audio.volume * 100)
const muteStatus = root.source.audio.muted ? " (muted)" : ""
result += `Input: ${micVolume}%${muteStatus}`
const micVolume = Math.round(root.source.audio.volume * 100);
const muteStatus = root.source.audio.muted ? " (muted)" : "";
result += `Input: ${micVolume}%${muteStatus}`;
} else {
result += "Input: No source available"
result += "Input: No source available";
}
return result
return result;
}
}
Connections {
target: SettingsData
function onUseSystemSoundThemeChanged() {
reloadSounds()
reloadSounds();
}
}
Component.onCompleted: {
if (!detectSoundsAvailability()) {
console.warn("AudioService: QtMultimedia not available - sound effects disabled")
console.warn("AudioService: QtMultimedia not available - sound effects disabled");
} else {
console.info("AudioService: Sound effects enabled")
checkGsettings()
Qt.callLater(createSoundPlayers)
console.info("AudioService: Sound effects enabled");
checkGsettings();
Qt.callLater(createSoundPlayers);
}
}
}

View File

@@ -1,5 +1,4 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
@@ -23,9 +22,9 @@ Singleton {
readonly property bool nativeInhibitorAvailable: {
try {
return typeof IdleInhibitor !== "undefined"
return typeof IdleInhibitor !== "undefined";
} catch (e) {
return false
return false;
}
}
@@ -42,10 +41,10 @@ Singleton {
property string seat: ""
property string display: ""
signal sessionLocked()
signal sessionUnlocked()
signal prepareForSleep()
signal loginctlStateChanged()
signal sessionLocked
signal sessionUnlocked
signal sessionResumed
signal loginctlStateChanged
property bool stateInitialized: false
@@ -57,30 +56,29 @@ Singleton {
running: true
repeat: false
onTriggered: {
detectElogindProcess.running = true
detectHibernateProcess.running = true
detectPrimeRunProcess.running = true
console.info("SessionService: Native inhibitor available:", nativeInhibitorAvailable)
detectElogindProcess.running = true;
detectHibernateProcess.running = true;
detectPrimeRunProcess.running = true;
console.info("SessionService: Native inhibitor available:", nativeInhibitorAvailable);
if (!SettingsData.loginctlLockIntegration) {
console.log("SessionService: loginctl lock integration disabled by user")
return
console.log("SessionService: loginctl lock integration disabled by user");
return;
}
if (socketPath && socketPath.length > 0) {
checkDMSCapabilities()
checkDMSCapabilities();
} else {
console.log("SessionService: DMS_SOCKET not set")
console.log("SessionService: DMS_SOCKET not set");
}
}
}
Process {
id: detectUwsmProcess
running: false
command: ["which", "uwsm"]
onExited: function (exitCode) {
hasUwsm = (exitCode === 0)
hasUwsm = (exitCode === 0);
}
}
@@ -90,8 +88,8 @@ Singleton {
command: ["sh", "-c", "ps -eo comm= | grep -E '^(elogind|elogind-daemon)$'"]
onExited: function (exitCode) {
console.log("SessionService: Elogind detection exited with code", exitCode)
isElogind = (exitCode === 0)
console.log("SessionService: Elogind detection exited with code", exitCode);
isElogind = (exitCode === 0);
}
}
@@ -101,7 +99,7 @@ Singleton {
command: ["grep", "-q", "disk", "/sys/power/state"]
onExited: function (exitCode) {
hibernateSupported = (exitCode === 0)
hibernateSupported = (exitCode === 0);
}
}
@@ -111,7 +109,7 @@ Singleton {
command: ["which", "prime-run"]
onExited: function (exitCode) {
hasPrimeRun = (exitCode === 0)
hasPrimeRun = (exitCode === 0);
}
}
@@ -124,164 +122,167 @@ Singleton {
splitMarker: "\n"
onRead: data => {
if (data.trim().toLowerCase().includes("not running")) {
_logout()
_logout();
}
}
}
onExited: function (exitCode) {
if (exitCode === 0) {
return
return;
}
_logout()
_logout();
}
}
function escapeShellArg(arg) {
return "'" + arg.replace(/'/g, "'\\''") + "'"
return "'" + arg.replace(/'/g, "'\\''") + "'";
}
function needsShellExecution(prefix) {
if (!prefix || prefix.length === 0) return false
return /[;&|<>()$`\\"']/.test(prefix)
if (!prefix || prefix.length === 0)
return false;
return /[;&|<>()$`\\"']/.test(prefix);
}
function launchDesktopEntry(desktopEntry, usePrimeRun) {
let cmd = desktopEntry.command
let cmd = desktopEntry.command;
if (usePrimeRun && hasPrimeRun) {
cmd = ["prime-run"].concat(cmd)
cmd = ["prime-run"].concat(cmd);
}
const userPrefix = SettingsData.launchPrefix?.trim() || ""
const defaultPrefix = Quickshell.env("DMS_DEFAULT_LAUNCH_PREFIX") || ""
const prefix = userPrefix.length > 0 ? userPrefix : defaultPrefix
const userPrefix = SettingsData.launchPrefix?.trim() || "";
const defaultPrefix = Quickshell.env("DMS_DEFAULT_LAUNCH_PREFIX") || "";
const prefix = userPrefix.length > 0 ? userPrefix : defaultPrefix;
if (prefix.length > 0 && needsShellExecution(prefix)) {
const escapedCmd = cmd.map(arg => escapeShellArg(arg)).join(" ")
const shellCmd = `${prefix} ${escapedCmd}`
const escapedCmd = cmd.map(arg => escapeShellArg(arg)).join(" ");
const shellCmd = `${prefix} ${escapedCmd}`;
Quickshell.execDetached({
command: ["sh", "-c", shellCmd],
workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME"),
})
workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME")
});
} else {
if (prefix.length > 0) {
const launchPrefix = prefix.split(" ")
cmd = launchPrefix.concat(cmd)
const launchPrefix = prefix.split(" ");
cmd = launchPrefix.concat(cmd);
}
Quickshell.execDetached({
command: cmd,
workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME"),
})
workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME")
});
}
}
function launchDesktopAction(desktopEntry, action, usePrimeRun) {
let cmd = action.command
let cmd = action.command;
if (usePrimeRun && hasPrimeRun) {
cmd = ["prime-run"].concat(cmd)
cmd = ["prime-run"].concat(cmd);
}
const userPrefix = SettingsData.launchPrefix?.trim() || ""
const defaultPrefix = Quickshell.env("DMS_DEFAULT_LAUNCH_PREFIX") || ""
const prefix = userPrefix.length > 0 ? userPrefix : defaultPrefix
const userPrefix = SettingsData.launchPrefix?.trim() || "";
const defaultPrefix = Quickshell.env("DMS_DEFAULT_LAUNCH_PREFIX") || "";
const prefix = userPrefix.length > 0 ? userPrefix : defaultPrefix;
if (prefix.length > 0 && needsShellExecution(prefix)) {
const escapedCmd = cmd.map(arg => escapeShellArg(arg)).join(" ")
const shellCmd = `${prefix} ${escapedCmd}`
const escapedCmd = cmd.map(arg => escapeShellArg(arg)).join(" ");
const shellCmd = `${prefix} ${escapedCmd}`;
Quickshell.execDetached({
command: ["sh", "-c", shellCmd],
workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME"),
})
workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME")
});
} else {
if (prefix.length > 0) {
const launchPrefix = prefix.split(" ")
cmd = launchPrefix.concat(cmd)
const launchPrefix = prefix.split(" ");
cmd = launchPrefix.concat(cmd);
}
Quickshell.execDetached({
command: cmd,
workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME"),
})
workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME")
});
}
}
// * Session management
function logout() {
if (hasUwsm) {
uwsmLogout.running = true
uwsmLogout.running = true;
}
_logout()
_logout();
}
function _logout() {
if (SettingsData.customPowerActionLogout.length === 0) {
if (CompositorService.isNiri) {
NiriService.quit()
return
NiriService.quit();
return;
}
if (CompositorService.isDwl) {
DwlService.quit()
return
DwlService.quit();
return;
}
if (CompositorService.isSway) {
try { I3.dispatch("exit") } catch(_){}
return
try {
I3.dispatch("exit");
} catch (_) {}
return;
}
Hyprland.dispatch("exit")
Hyprland.dispatch("exit");
} else {
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionLogout])
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionLogout]);
}
}
function suspend() {
if (SettingsData.customPowerActionSuspend.length === 0) {
Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "suspend"])
Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "suspend"]);
} else {
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionSuspend])
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionSuspend]);
}
}
function hibernate() {
if (SettingsData.customPowerActionHibernate.length === 0) {
Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "hibernate"])
Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "hibernate"]);
} else {
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionHibernate])
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionHibernate]);
}
}
function suspendThenHibernate() {
Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "suspend-then-hibernate"])
Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "suspend-then-hibernate"]);
}
function suspendWithBehavior(behavior) {
if (behavior === SettingsData.SuspendBehavior.Hibernate) {
hibernate()
hibernate();
} else if (behavior === SettingsData.SuspendBehavior.SuspendThenHibernate) {
suspendThenHibernate()
suspendThenHibernate();
} else {
suspend()
suspend();
}
}
function reboot() {
if (SettingsData.customPowerActionReboot.length === 0) {
Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "reboot"])
Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "reboot"]);
} else {
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionReboot])
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionReboot]);
}
}
function poweroff() {
if (SettingsData.customPowerActionPowerOff.length === 0) {
Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "poweroff"])
Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "poweroff"]);
} else {
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionPowerOff])
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionPowerOff]);
}
}
@@ -290,42 +291,42 @@ Singleton {
function enableIdleInhibit() {
if (idleInhibited) {
return
return;
}
console.log("SessionService: Enabling idle inhibit (native:", nativeInhibitorAvailable, ")")
idleInhibited = true
inhibitorChanged()
console.log("SessionService: Enabling idle inhibit (native:", nativeInhibitorAvailable, ")");
idleInhibited = true;
inhibitorChanged();
}
function disableIdleInhibit() {
if (!idleInhibited) {
return
return;
}
console.log("SessionService: Disabling idle inhibit (native:", nativeInhibitorAvailable, ")")
idleInhibited = false
inhibitorChanged()
console.log("SessionService: Disabling idle inhibit (native:", nativeInhibitorAvailable, ")");
idleInhibited = false;
inhibitorChanged();
}
function toggleIdleInhibit() {
if (idleInhibited) {
disableIdleInhibit()
disableIdleInhibit();
} else {
enableIdleInhibit()
enableIdleInhibit();
}
}
function setInhibitReason(reason) {
inhibitReason = reason
inhibitReason = reason;
if (idleInhibited && !nativeInhibitorAvailable) {
const wasActive = idleInhibited
idleInhibited = false
const wasActive = idleInhibited;
idleInhibited = false;
Qt.callLater(() => {
if (wasActive) {
idleInhibited = true
}
})
if (wasActive) {
idleInhibited = true;
}
});
}
}
@@ -334,24 +335,24 @@ Singleton {
command: {
if (!idleInhibited || nativeInhibitorAvailable) {
return ["true"]
return ["true"];
}
console.log("SessionService: Starting systemd/elogind inhibit process")
return [isElogind ? "elogind-inhibit" : "systemd-inhibit", "--what=idle", "--who=quickshell", `--why=${inhibitReason}`, "--mode=block", "sleep", "infinity"]
console.log("SessionService: Starting systemd/elogind inhibit process");
return [isElogind ? "elogind-inhibit" : "systemd-inhibit", "--what=idle", "--who=quickshell", `--why=${inhibitReason}`, "--mode=block", "sleep", "infinity"];
}
running: idleInhibited && !nativeInhibitorAvailable
onRunningChanged: {
console.log("SessionService: Inhibit process running:", running, "(native:", nativeInhibitorAvailable, ")")
console.log("SessionService: Inhibit process running:", running, "(native:", nativeInhibitorAvailable, ")");
}
onExited: function (exitCode) {
if (idleInhibited && exitCode !== 0 && !nativeInhibitorAvailable) {
console.warn("SessionService: Inhibitor process crashed with exit code:", exitCode)
idleInhibited = false
ToastService.showWarning("Idle inhibitor failed")
console.warn("SessionService: Inhibitor process crashed with exit code:", exitCode);
idleInhibited = false;
ToastService.showWarning("Idle inhibitor failed");
}
}
}
@@ -361,12 +362,12 @@ Singleton {
function onConnectionStateChanged() {
if (DMSService.isConnected) {
checkDMSCapabilities()
checkDMSCapabilities();
}
}
function onCapabilitiesReceived() {
syncSleepInhibitor()
syncSleepInhibitor();
}
}
@@ -375,7 +376,7 @@ Singleton {
enabled: DMSService.isConnected
function onCapabilitiesChanged() {
checkDMSCapabilities()
checkDMSCapabilities();
}
}
@@ -386,22 +387,22 @@ Singleton {
if (SettingsData.loginctlLockIntegration) {
if (socketPath && socketPath.length > 0 && loginctlAvailable) {
if (!stateInitialized) {
stateInitialized = true
getLoginctlState()
syncLockBeforeSuspend()
stateInitialized = true;
getLoginctlState();
syncLockBeforeSuspend();
}
}
} else {
stateInitialized = false
stateInitialized = false;
}
syncSleepInhibitor()
syncSleepInhibitor();
}
function onLockBeforeSuspendChanged() {
if (SettingsData.loginctlLockIntegration) {
syncLockBeforeSuspend()
syncLockBeforeSuspend();
}
syncSleepInhibitor()
syncSleepInhibitor();
}
}
@@ -410,109 +411,114 @@ Singleton {
enabled: SettingsData.loginctlLockIntegration
function onLoginctlStateUpdate(data) {
updateLoginctlState(data)
updateLoginctlState(data);
}
function onLoginctlEvent(event) {
handleLoginctlEvent(event)
handleLoginctlEvent(event);
}
}
function checkDMSCapabilities() {
if (!DMSService.isConnected) {
return
return;
}
if (DMSService.capabilities.length === 0) {
return
return;
}
if (DMSService.capabilities.includes("loginctl")) {
loginctlAvailable = true
loginctlAvailable = true;
if (SettingsData.loginctlLockIntegration && !stateInitialized) {
stateInitialized = true
getLoginctlState()
syncLockBeforeSuspend()
stateInitialized = true;
getLoginctlState();
syncLockBeforeSuspend();
}
} else {
loginctlAvailable = false
console.log("SessionService: loginctl capability not available in DMS")
loginctlAvailable = false;
console.log("SessionService: loginctl capability not available in DMS");
}
}
function getLoginctlState() {
if (!loginctlAvailable) return
if (!loginctlAvailable)
return;
DMSService.sendRequest("loginctl.getState", null, response => {
if (response.result) {
updateLoginctlState(response.result)
updateLoginctlState(response.result);
}
})
});
}
function syncLockBeforeSuspend() {
if (!loginctlAvailable) return
if (!loginctlAvailable)
return;
DMSService.sendRequest("loginctl.setLockBeforeSuspend", {
enabled: SettingsData.lockBeforeSuspend
}, response => {
if (response.error) {
console.warn("SessionService: Failed to sync lock before suspend:", response.error)
console.warn("SessionService: Failed to sync lock before suspend:", response.error);
} else {
console.log("SessionService: Synced lock before suspend:", SettingsData.lockBeforeSuspend)
console.log("SessionService: Synced lock before suspend:", SettingsData.lockBeforeSuspend);
}
})
});
}
function syncSleepInhibitor() {
if (!loginctlAvailable) return
if (!DMSService.apiVersion || DMSService.apiVersion < 4) return
if (!loginctlAvailable)
return;
if (!DMSService.apiVersion || DMSService.apiVersion < 4)
return;
DMSService.sendRequest("loginctl.setSleepInhibitorEnabled", {
enabled: SettingsData.loginctlLockIntegration && SettingsData.lockBeforeSuspend
}, response => {
if (response.error) {
console.warn("SessionService: Failed to sync sleep inhibitor:", response.error)
console.warn("SessionService: Failed to sync sleep inhibitor:", response.error);
} else {
console.log("SessionService: Synced sleep inhibitor:", SettingsData.loginctlLockIntegration)
console.log("SessionService: Synced sleep inhibitor:", SettingsData.loginctlLockIntegration);
}
})
});
}
function updateLoginctlState(state) {
const wasLocked = locked
const wasLocked = locked;
const wasSleeping = preparingForSleep;
sessionId = state.sessionId || ""
sessionPath = state.sessionPath || ""
locked = state.locked || false
active = state.active || false
idleHint = state.idleHint || false
lockedHint = state.lockedHint || false
sessionType = state.sessionType || ""
userName = state.userName || ""
seat = state.seat || ""
display = state.display || ""
sessionId = state.sessionId || "";
sessionPath = state.sessionPath || "";
locked = state.locked || false;
active = state.active || false;
idleHint = state.idleHint || false;
lockedHint = state.lockedHint || false;
preparingForSleep = state.preparingForSleep || false;
sessionType = state.sessionType || "";
userName = state.userName || "";
seat = state.seat || "";
display = state.display || "";
if (locked && !wasLocked) {
sessionLocked()
sessionLocked();
} else if (!locked && wasLocked) {
sessionUnlocked()
sessionUnlocked();
}
loginctlStateChanged()
if (wasSleeping && !preparingForSleep) {
sessionResumed();
}
loginctlStateChanged();
}
function handleLoginctlEvent(event) {
if (event.event === "Lock") {
locked = true
lockedHint = true
sessionLocked()
locked = true;
lockedHint = true;
sessionLocked();
} else if (event.event === "Unlock") {
locked = false
lockedHint = false
sessionUnlocked()
locked = false;
lockedHint = false;
sessionUnlocked();
}
}
}