diff options
| author | AlexanderCurl <alexc@alexc.hu> | 2026-04-18 17:46:06 +0100 |
|---|---|---|
| committer | AlexanderCurl <alexc@alexc.hu> | 2026-04-18 17:46:06 +0100 |
| commit | 70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 (patch) | |
| tree | ab1123d4067c1b086dd6faa7ee4ea643236b565a /raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/AudioService.qml | |
| parent | 5d94c0a7d44a2255b81815a52a7056a94a39842d (diff) | |
| download | RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.tar.gz RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.zip | |
Replaced file structures for themes in the correct format
Diffstat (limited to 'raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/AudioService.qml')
| -rw-r--r-- | raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/AudioService.qml | 1075 |
1 files changed, 0 insertions, 1075 deletions
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/AudioService.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/AudioService.qml deleted file mode 100644 index 015fedc..0000000 --- a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/AudioService.qml +++ /dev/null @@ -1,1075 +0,0 @@ -pragma Singleton -pragma ComponentBehavior: Bound - -import QtCore -import QtQuick -import Quickshell -import Quickshell.Io -import Quickshell.Services.Pipewire -import qs.Common -import qs.Services - -Singleton { - id: root - - readonly property PwNode sink: Pipewire.defaultAudioSink - readonly property PwNode source: Pipewire.defaultAudioSource - - readonly property bool soundsAvailable: MultimediaService.available - property bool gsettingsAvailable: false - property var availableSoundThemes: [] - property string currentSoundTheme: "" - property var soundFilePaths: ({}) - - property var volumeChangeSound: null - property var powerPlugSound: null - 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 - - property var mediaDevices: null - property var mediaDevicesConnections: null - - property var deviceAliases: ({}) - property string wireplumberConfigPath: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)) + "/wireplumber/wireplumber.conf.d/51-dms-audio-aliases.conf" - property bool wireplumberReloading: false - - readonly property int sinkMaxVolume: { - const name = sink?.name ?? ""; - if (!name) - return 100; - return SessionData.deviceMaxVolumes[name] ?? 100; - } - - signal micMuteChanged - signal audioOutputCycled(string deviceName, string deviceIcon) - signal deviceAliasChanged(string nodeName, string newAlias) - signal wireplumberReloadStarted - signal wireplumberReloadCompleted(bool success) - - function getMaxVolumePercent(node) { - if (!node?.name) - return 100; - return SessionData.deviceMaxVolumes[node.name] ?? 100; - } - - Connections { - target: SessionData - function onDeviceMaxVolumesChanged() { - if (!root.sink?.audio) - return; - const maxVol = root.sinkMaxVolume; - const currentPercent = Math.round(root.sink.audio.volume * 100); - if (currentPercent > maxVol) - root.sink.audio.volume = maxVol / 100; - } - } - - // 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)); - } - - function cycleAudioOutput() { - const sinks = getAvailableSinks(); - if (sinks.length < 2) - return null; - - const currentName = root.sink?.name ?? ""; - const currentIndex = sinks.findIndex(s => s.name === currentName); - const nextIndex = (currentIndex + 1) % sinks.length; - const nextSink = sinks[nextIndex]; - Pipewire.preferredDefaultAudioSink = nextSink; - const name = displayName(nextSink); - audioOutputCycled(name, sinkIcon(nextSink)); - return name; - } - - function getDeviceAlias(nodeName) { - if (!nodeName) - return null; - return deviceAliases[nodeName] || null; - } - - function hasDeviceAlias(nodeName) { - if (!nodeName) - return false; - return deviceAliases.hasOwnProperty(nodeName) && deviceAliases[nodeName] !== null && deviceAliases[nodeName] !== ""; - } - - function setDeviceAlias(nodeName, customAlias) { - if (!nodeName) { - console.error("AudioService: Cannot set alias - nodeName is empty"); - return false; - } - - if (!customAlias || customAlias.trim() === "") { - return removeDeviceAlias(nodeName); - } - - const trimmedAlias = customAlias.trim(); - - const updated = Object.assign({}, deviceAliases); - updated[nodeName] = trimmedAlias; - deviceAliases = updated; - - writeWireplumberConfig(); - deviceAliasChanged(nodeName, trimmedAlias); - return true; - } - - function removeDeviceAlias(nodeName) { - if (!nodeName) - return false; - - if (!hasDeviceAlias(nodeName)) - return false; - - const updated = Object.assign({}, deviceAliases); - delete updated[nodeName]; - deviceAliases = updated; - - writeWireplumberConfig(); - deviceAliasChanged(nodeName, ""); - return true; - } - - function writeWireplumberConfig() { - const configDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)) + "/wireplumber/wireplumber.conf.d"; - const configContent = generateWireplumberConfig(); - - const shellCmd = `mkdir -p "${configDir}" && cat > "${wireplumberConfigPath}" << 'EOFCONFIG' -${configContent} -EOFCONFIG -`; - - Proc.runCommand("writeWireplumberConfig", ["sh", "-c", shellCmd], (output, exitCode) => { - if (exitCode !== 0) { - console.error("AudioService: Failed to write WirePlumber config. Exit code:", exitCode); - console.error("AudioService: Error output:", output); - ToastService.showError(I18n.tr("Failed to save audio config"), output || ""); - return; - } - - reloadWireplumberConfig(); - }, 0); - } - - function generateWireplumberConfig() { - let config = "# Generated by DankMaterialShell - Audio Device Aliases\n"; - config += "# Do not edit manually - changes will be overwritten\n"; - config += "# Last updated: " + new Date().toISOString() + "\n\n"; - - const aliasKeys = Object.keys(deviceAliases); - if (aliasKeys.length === 0) { - config += "# No device aliases configured\n"; - return config; - } - - const alsaAliases = []; - const bluezAliases = []; - const otherAliases = []; - - for (const nodeName of aliasKeys) { - const alias = deviceAliases[nodeName]; - if (!alias) - continue; - - const rule = { - nodeName: nodeName, - alias: alias - }; - - if (nodeName.includes("alsa")) { - alsaAliases.push(rule); - } else if (nodeName.includes("bluez")) { - bluezAliases.push(rule); - } else { - otherAliases.push(rule); - } - } - - if (alsaAliases.length > 0) { - config += "monitor.alsa.rules = [\n"; - for (let i = 0; i < alsaAliases.length; i++) { - const rule = alsaAliases[i]; - config += " {\n"; - config += ` matches = [ { "node.name" = "${rule.nodeName}" } ]\n`; - config += ` actions = { update-props = { "node.description" = "${rule.alias}" } }\n`; - config += " }"; - if (i < alsaAliases.length - 1) - config += ","; - config += "\n"; - } - config += "]\n\n"; - } - - if (bluezAliases.length > 0) { - config += "monitor.bluez.rules = [\n"; - for (let i = 0; i < bluezAliases.length; i++) { - const rule = bluezAliases[i]; - config += " {\n"; - config += ` matches = [ { "node.name" = "${rule.nodeName}" } ]\n`; - config += ` actions = { update-props = { "node.description" = "${rule.alias}" } }\n`; - config += " }"; - if (i < bluezAliases.length - 1) - config += ","; - config += "\n"; - } - config += "]\n\n"; - } - - if (otherAliases.length > 0) { - config += "# Other device aliases (RAOP, USB, and other devices)\n"; - config += "wireplumber.rules = [\n"; - for (let i = 0; i < otherAliases.length; i++) { - const rule = otherAliases[i]; - config += " {\n"; - config += ` matches = [\n`; - config += ` { "node.name" = "${rule.nodeName}" }\n`; - config += ` ]\n`; - config += ` actions = {\n`; - config += ` update-props = {\n`; - config += ` "node.description" = "${rule.alias}"\n`; - config += ` "node.nick" = "${rule.alias}"\n`; - config += ` "device.description" = "${rule.alias}"\n`; - config += ` }\n`; - config += ` }\n`; - config += " }"; - if (i < otherAliases.length - 1) - config += ","; - config += "\n"; - } - config += "]\n"; - } - - return config; - } - - function reloadWireplumberConfig() { - if (wireplumberReloading) { - return; - } - - wireplumberReloading = true; - wireplumberReloadStarted(); - - Proc.runCommand("restartWireplumber", ["systemctl", "--user", "restart", "wireplumber"], (output, exitCode) => { - wireplumberReloading = false; - - if (exitCode === 0) { - ToastService.showInfo(I18n.tr("Audio system restarted"), I18n.tr("Device names updated")); - wireplumberReloadCompleted(true); - } else { - console.error("AudioService: Failed to restart WirePlumber:", output); - ToastService.showError(I18n.tr("Failed to restart audio system"), output); - wireplumberReloadCompleted(false); - } - }, 5000); - } - - function loadDeviceAliases() { - const configPath = wireplumberConfigPath; - - Proc.runCommand("readWireplumberConfig", ["cat", configPath], (output, exitCode) => { - if (exitCode !== 0) { - console.log("AudioService: No existing WirePlumber config found"); - return; - } - - const aliases = {}; - const lines = output.split('\n'); - let currentNodeName = null; - - for (const line of lines) { - const nodeNameMatch = line.match(/"node\.name"\s*=\s*"([^"]+)"/); - if (nodeNameMatch) { - currentNodeName = nodeNameMatch[1]; - } - - const descriptionMatch = line.match(/"node\.description"\s*=\s*"([^"]+)"/); - if (descriptionMatch && currentNodeName) { - aliases[currentNodeName] = descriptionMatch[1]; - currentNodeName = null; - } - } - - if (Object.keys(aliases).length > 0) { - deviceAliases = aliases; - console.log("AudioService: Loaded", Object.keys(aliases).length, "device aliases"); - } - }, 0); - } - - Connections { - target: root.sink?.audio ?? null - - function onVolumeChanged() { - if (SessionData.suppressOSD) - return; - root.playVolumeChangeSoundIfEnabled(); - } - } - - function checkGsettings() { - Proc.runCommand("checkGsettings", ["sh", "-c", "gsettings get org.gnome.desktop.sound theme-name 2>/dev/null"], (output, exitCode) => { - gsettingsAvailable = (exitCode === 0); - if (gsettingsAvailable) { - scanSoundThemes(); - getCurrentSoundTheme(); - } - }, 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 basePaths = searchPaths.map(p => p + "/sounds").join(" "); - const script = ` - for base_dir in ${basePaths}; do - [ -d "$base_dir" ] || continue - for theme_dir in "$base_dir"/*; do - [ -d "$theme_dir/stereo" ] || continue - 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; - } else { - availableSoundThemes = []; - } - }, 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); - if (SettingsData.useSystemSoundTheme) { - discoverSoundFiles(currentSoundTheme); - } - } else { - currentSoundTheme = ""; - console.log("AudioService: No system sound theme found"); - } - }, 0); - } - - function setSoundTheme(themeName) { - if (!themeName || themeName === currentSoundTheme) { - return; - } - - Proc.runCommand("setSoundTheme", ["sh", "-c", `gsettings set org.gnome.desktop.sound theme-name '${themeName}'`], (output, exitCode) => { - if (exitCode === 0) { - currentSoundTheme = themeName; - if (SettingsData.useSystemSoundTheme) { - discoverSoundFiles(themeName); - } - } - }, 0); - } - - function discoverSoundFiles(themeName) { - if (!themeName) { - soundFilePaths = {}; - if (soundsAvailable) { - destroySoundPlayers(); - createSoundPlayers(); - } - 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 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 desktop-login; do - found=0 - - case "$event_key" in - message) - names="dialog-information message message-lowpriority bell" - ;; - message-new-instant) - names="dialog-warning message-new-instant message-highlight" - ;; - *) - names="$event_key" - ;; - esac - - for theme in ${themesToSearch}; do - for event_name in $names; do - for base_path in ${searchPaths.join(" ")}; do - sounds_path="$base_path/sounds" - for ext in ${extensions.join(" ")}; do - file_path="$sounds_path/$theme/stereo/$event_name.$ext" - if [ -f "$file_path" ]; then - echo "$event_key=$file_path" - found=1 - break - fi - done - [ $found -eq 1 ] && break - done - [ $found -eq 1 ] && break - done - [ $found -eq 1 ] && break - done - done - `; - - Proc.runCommand("discoverSoundFiles", ["sh", "-c", script], (output, exitCode) => { - const paths = {}; - if (exitCode === 0 && output.trim()) { - const lines = output.trim().split('\n'); - for (let line of lines) { - const parts = line.split('='); - if (parts.length === 2) { - paths[parts[0]] = "file://" + parts[1]; - } - } - } - soundFilePaths = paths; - - if (soundsAvailable) { - destroySoundPlayers(); - createSoundPlayers(); - } - }, 0); - } - - function getSoundPath(soundEvent) { - const soundMap = { - "audio-volume-change": "../assets/sounds/freedesktop/audio-volume-change.wav", - "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", - "desktop-login": "../assets/sounds/freedesktop/desktop-login.wav" - }; - - const specialConditions = { - "smooth": ["audio-volume-change"] - }; - - 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; - } - - if (SettingsData.useSystemSoundTheme && 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; - } - - function reloadSounds() { - console.log("AudioService: Reloading sounds, useSystemSoundTheme:", SettingsData.useSystemSoundTheme, "currentSoundTheme:", currentSoundTheme); - if (SettingsData.useSystemSoundTheme && currentSoundTheme) { - discoverSoundFiles(currentSoundTheme); - } else { - soundFilePaths = {}; - if (soundsAvailable) { - destroySoundPlayers(); - createSoundPlayers(); - } - } - } - - function setupMediaDevices() { - if (!soundsAvailable || mediaDevices) { - return; - } - - try { - mediaDevices = Qt.createQmlObject(` - import QtQuick - import QtMultimedia - MediaDevices { - id: devices - Component.onCompleted: { - console.log("AudioService: MediaDevices initialized, default output:", defaultAudioOutput?.description) - } - } - `, root, "AudioService.MediaDevices"); - - if (mediaDevices) { - mediaDevicesConnections = Qt.createQmlObject(` - import QtQuick - Connections { - target: root.mediaDevices - function onDefaultAudioOutputChanged() { - console.log("AudioService: Default audio output changed, recreating sound players") - root.destroySoundPlayers() - root.createSoundPlayers() - } - } - `, root, "AudioService.MediaDevicesConnections"); - } - } catch (e) { - console.log("AudioService: MediaDevices not available, using default audio output"); - mediaDevices = null; - } - } - - function destroySoundPlayers() { - if (volumeChangeSound) { - volumeChangeSound.destroy(); - volumeChangeSound = null; - } - if (powerPlugSound) { - powerPlugSound.destroy(); - powerPlugSound = null; - } - if (powerUnplugSound) { - powerUnplugSound.destroy(); - powerUnplugSound = null; - } - if (normalNotificationSound) { - normalNotificationSound.destroy(); - normalNotificationSound = null; - } - if (criticalNotificationSound) { - criticalNotificationSound.destroy(); - criticalNotificationSound = null; - } - if (loginSound) { - loginSound.destroy(); - loginSound = null; - } - } - - function createSoundPlayers() { - if (!soundsAvailable) { - return; - } - - setupMediaDevices(); - - try { - const deviceProperty = mediaDevices ? `device: root.mediaDevices.defaultAudioOutput\n ` : ""; - - const volumeChangePath = getSoundPath("audio-volume-change"); - volumeChangeSound = Qt.createQmlObject(` - import QtQuick - import QtMultimedia - MediaPlayer { - source: "${volumeChangePath}" - audioOutput: AudioOutput { - ${deviceProperty}volume: notificationsVolume - } - } - `, root, "AudioService.VolumeChangeSound"); - - const powerPlugPath = getSoundPath("power-plug"); - powerPlugSound = Qt.createQmlObject(` - import QtQuick - import QtMultimedia - MediaPlayer { - source: "${powerPlugPath}" - audioOutput: AudioOutput { - ${deviceProperty}volume: notificationsVolume - } - } - `, root, "AudioService.PowerPlugSound"); - - const powerUnplugPath = getSoundPath("power-unplug"); - powerUnplugSound = Qt.createQmlObject(` - import QtQuick - import QtMultimedia - MediaPlayer { - source: "${powerUnplugPath}" - audioOutput: AudioOutput { - ${deviceProperty}volume: notificationsVolume - } - } - `, root, "AudioService.PowerUnplugSound"); - - const messagePath = getSoundPath("message"); - normalNotificationSound = Qt.createQmlObject(` - import QtQuick - import QtMultimedia - MediaPlayer { - source: "${messagePath}" - audioOutput: AudioOutput { - ${deviceProperty}volume: notificationsVolume - } - } - `, root, "AudioService.NormalNotificationSound"); - - const messageNewInstantPath = getSoundPath("message-new-instant"); - criticalNotificationSound = Qt.createQmlObject(` - import QtQuick - import QtMultimedia - MediaPlayer { - source: "${messageNewInstantPath}" - audioOutput: AudioOutput { - ${deviceProperty}volume: notificationsVolume - } - } - `, 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); - } - } - - function isMediaPlaying() { - return MprisController.activePlayer?.isPlaying ?? false; - } - - function playVolumeChangeSound() { - if (!soundsAvailable || !volumeChangeSound || notificationsAudioMuted || isMediaPlaying()) - return; - volumeChangeSound.play(); - } - - function playPowerPlugSound() { - if (!soundsAvailable || !powerPlugSound || notificationsAudioMuted || isMediaPlaying()) - return; - powerPlugSound.play(); - } - - function playPowerUnplugSound() { - if (!soundsAvailable || !powerUnplugSound || notificationsAudioMuted || isMediaPlaying()) - return; - powerUnplugSound.play(); - } - - function playNormalNotificationSound() { - if (!soundsAvailable || !normalNotificationSound || SessionData.doNotDisturb || notificationsAudioMuted || isMediaPlaying()) - return; - normalNotificationSound.play(); - } - - function playCriticalNotificationSound() { - if (!soundsAvailable || !criticalNotificationSound || SessionData.doNotDisturb || notificationsAudioMuted || isMediaPlaying()) - return; - 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(); - } - } - - function sinkIcon(node) { - if (!node) - return "speaker"; - - const props = node.properties || {}; - const formFactor = (props["device.form-factor"] || "").toLowerCase(); - - switch (formFactor) { - case "headphone": - case "headset": - case "hands-free": - case "handset": - return "headset"; - case "tv": - case "monitor": - return "tv"; - case "speaker": - case "computer": - case "hifi": - case "portable": - case "car": - return "speaker"; - } - - const bus = (props["device.bus"] || "").toLowerCase(); - if (bus === "bluetooth") - return "headset"; - - const name = (node.name || "").toLowerCase(); - if (name.includes("hdmi")) - return "tv"; - if (name.includes("iec958") || name.includes("spdif")) - return "speaker"; - - if (bus === "usb") - return "headset"; - - return "speaker"; - } - - function displayName(node) { - if (!node) { - return ""; - } - - // FIRST: Check if we have a custom alias in our deviceAliases map - // This ensures we always show the user's custom name, regardless of - // whether WirePlumber has applied it to the node properties yet - if (node.name && deviceAliases[node.name]) { - return deviceAliases[node.name]; - } - - // Check node.properties["node.description"] for WirePlumber-applied aliases - // This is the live property updated by WirePlumber rules - if (node.properties && node.properties["node.description"]) { - const desc = node.properties["node.description"]; - if (desc !== node.name) { - return desc; - } - } - - // Check cached description as fallback - if (node.description && node.description !== node.name) { - return node.description; - } - - // Fallback to device description property - if (node.properties && node.properties["device.description"]) { - return node.properties["device.description"]; - } - - // Fallback to nickname - if (node.nickname && node.nickname !== node.name) { - return node.nickname; - } - - // Fallback to friendly names based on node name patterns - if (node.name.includes("analog-stereo")) { - return "Built-in Audio Analog Stereo"; - } - if (node.name.includes("bluez")) { - return "Bluetooth Audio"; - } - if (node.name.includes("usb")) { - return "USB Audio"; - } - if (node.name.includes("hdmi")) { - return "HDMI Audio"; - } - - return node.name; - } - - function originalName(node) { - if (!node) { - return ""; - } - - // Get the original name without checking for custom aliases - // Check pattern-based friendly names FIRST (before device.description) - // This ensures we show user-friendly names like "Built-in Audio Analog Stereo" - // instead of hardware chip names like "ALC274 Analog" - if (node.name.includes("analog-stereo")) { - return "Built-in Audio Analog Stereo"; - } - if (node.name.includes("bluez")) { - return "Bluetooth Audio"; - } - if (node.name.includes("usb")) { - return "USB Audio"; - } - if (node.name.includes("hdmi")) { - return "HDMI Audio"; - } - if (node.name.includes("raop_sink")) { - // Extract friendly name from RAOP node name - const match = node.name.match(/raop_sink\.([^.]+)/); - if (match) { - return match[1].replace(/-/g, " "); - } - } - - // Fallback to device.description property - if (node.properties && node.properties["device.description"]) { - return node.properties["device.description"]; - } - - // Fallback to nickname - if (node.nickname && node.nickname !== node.name) { - return node.nickname; - } - - return node.name; - } - - function subtitle(name) { - if (!name) { - return ""; - } - - if (name.includes('usb-')) { - if (name.includes('SteelSeries')) { - return "USB Gaming Headset"; - } - if (name.includes('Generic')) { - return "USB Audio Device"; - } - return "USB Audio"; - } - - if (name.includes('pci-')) { - if (name.includes('01_00.1') || name.includes('01:00.1')) { - return "NVIDIA GPU Audio"; - } - return "PCI Audio"; - } - - if (name.includes('bluez')) { - return "Bluetooth Audio"; - } - if (name.includes('analog')) { - return "Built-in Audio"; - } - if (name.includes('hdmi')) { - return "HDMI Audio"; - } - - return ""; - } - - PwObjectTracker { - objects: Pipewire.nodes.values.filter(node => node.audio && !node.isStream) - } - - Connections { - target: Pipewire - function onDefaultAudioSinkChanged() { - if (soundsAvailable) { - Qt.callLater(root.destroySoundPlayers); - Qt.callLater(root.createSoundPlayers); - } - } - } - - function setVolume(percentage) { - if (!root.sink?.audio) - return "No audio sink available"; - - const maxVol = root.sinkMaxVolume; - const clampedVolume = Math.max(0, Math.min(maxVol, percentage)); - root.sink.audio.volume = clampedVolume / 100; - return `Volume set to ${clampedVolume}%`; - } - - function toggleMute() { - if (!root.sink?.audio) { - return "No audio sink available"; - } - - 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"; - } - - 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"; - } - - 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)); - } - - function increment(step: string): string { - if (!root.sink?.audio) - return "No audio sink available"; - - if (root.sink.audio.muted) - root.sink.audio.muted = false; - - const maxVol = root.sinkMaxVolume; - const currentVolume = Math.round(root.sink.audio.volume * 100); - const stepValue = parseInt(step || "5"); - const newVolume = Math.max(0, Math.min(maxVol, currentVolume + stepValue)); - - 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"; - - if (root.sink.audio.muted) - root.sink.audio.muted = false; - - const maxVol = root.sinkMaxVolume; - const currentVolume = Math.round(root.sink.audio.volume * 100); - const stepValue = parseInt(step || "5"); - const newVolume = Math.max(0, Math.min(maxVol, currentVolume - stepValue)); - - root.sink.audio.volume = newVolume / 100; - return `Volume decreased to ${newVolume}%`; - } - - function mute(): string { - return root.toggleMute(); - } - - function setmic(percentage: string): string { - return root.setMicVolume(parseInt(percentage)); - } - - function micmute(): string { - const result = root.toggleMicMute(); - root.micMuteChanged(); - return result; - } - - function status(): string { - 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)" : ""; - const maxVol = root.sinkMaxVolume; - result += `Output: ${volume}%${muteStatus} (max: ${maxVol}%)\n`; - } else { - 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}`; - } else { - result += "Input: No source available"; - } - - return result; - } - - function getmaxvolume(): string { - return `${root.sinkMaxVolume}`; - } - - function setmaxvolume(percent: string): string { - if (!root.sink?.name) - return "No audio sink available"; - const val = parseInt(percent); - if (isNaN(val)) - return "Invalid percentage"; - SessionData.setDeviceMaxVolume(root.sink.name, val); - return `Max volume set to ${SessionData.getDeviceMaxVolume(root.sink.name)}%`; - } - - function getmaxvolumefor(nodeName: string): string { - if (!nodeName) - return "No node name specified"; - return `${SessionData.getDeviceMaxVolume(nodeName)}`; - } - - function setmaxvolumefor(nodeName: string, percent: string): string { - if (!nodeName) - return "No node name specified"; - const val = parseInt(percent); - if (isNaN(val)) - return "Invalid percentage"; - SessionData.setDeviceMaxVolume(nodeName, val); - return `Max volume for ${nodeName} set to ${SessionData.getDeviceMaxVolume(nodeName)}%`; - } - - function cycleoutput(): string { - const result = root.cycleAudioOutput(); - if (!result) - return "Only one audio output available"; - return `Switched to: ${result}`; - } - } - - Connections { - target: SettingsData - function onUseSystemSoundThemeChanged() { - reloadSounds(); - } - } - - Component.onCompleted: { - if (soundsAvailable) { - checkGsettings(); - Qt.callLater(createSoundPlayers); - } - - loadDeviceAliases(); - } -} |