import "root:/modules/common" import "root:/modules/common/widgets" import "root:/services" import "root:/modules/common/functions/string_utils.js" as StringUtils import "root:/modules/common/functions/file_utils.js" as FileUtils import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Layouts import QtQuick.Controls import Quickshell import Quickshell.Io import Quickshell.Services.Mpris import Quickshell.Widgets import Quickshell.Wayland import Quickshell.Hyprland Scope { id: root property bool visible: false readonly property MprisPlayer activePlayer: MprisController.activePlayer readonly property var realPlayers: Mpris.players.values.filter(player => isRealPlayer(player)) readonly property var meaningfulPlayers: filterDuplicatePlayers(realPlayers) readonly property real osdWidth: Appearance.sizes.osdWidth readonly property real widgetWidth: Appearance.sizes.mediaControlsWidth readonly property real widgetHeight: Appearance.sizes.mediaControlsHeight property real contentPadding: 13 property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1 property real artRounding: Appearance.rounding.verysmall property list visualizerPoints: [] property bool hasPlasmaIntegration: false function isRealPlayer(player) { // return true return ( // Remove unecessary native buses from browsers if there's plasma integration !(hasPlasmaIntegration && player.dbusName.startsWith('org.mpris.MediaPlayer2.firefox')) && !(hasPlasmaIntegration && player.dbusName.startsWith('org.mpris.MediaPlayer2.chromium')) && // playerctld just copies other buses and we don't need duplicates !player.dbusName?.startsWith('org.mpris.MediaPlayer2.playerctld') && // Non-instance mpd bus !(player.dbusName?.endsWith('.mpd') && !player.dbusName.endsWith('MediaPlayer2.mpd')) ); } function filterDuplicatePlayers(players) { let filtered = []; let used = new Set(); for (let i = 0; i < players.length; ++i) { if (used.has(i)) continue; let p1 = players[i]; let group = [i]; // Find duplicates by trackTitle prefix for (let j = i + 1; j < players.length; ++j) { let p2 = players[j]; if (p1.trackTitle && p2.trackTitle && (p1.trackTitle.includes(p2.trackTitle) || p2.trackTitle.includes(p1.trackTitle)) || (p1.position - p2.position <= 2 && p1.length - p2.length <= 2)) { group.push(j); } } // Pick the one with non-empty trackArtUrl, or fallback to the first let chosenIdx = group.find(idx => players[idx].trackArtUrl && players[idx].trackArtUrl.length > 0); if (chosenIdx === undefined) chosenIdx = group[0]; filtered.push(players[chosenIdx]); group.forEach(idx => used.add(idx)); } return filtered; } Process { id: cavaProc running: mediaControlsLoader.active onRunningChanged: { if (!cavaProc.running) { root.visualizerPoints = []; } } command: ["cava", "-p", `${FileUtils.trimFileProtocol(Directories.config)}/quickshell/scripts/cava/raw_output_config.txt`] stdout: SplitParser { onRead: data => { // Parse `;`-separated values into the visualizerPoints array let points = data.split(";").map(p => parseFloat(p.trim())).filter(p => !isNaN(p)); root.visualizerPoints = points; } } } Loader { id: mediaControlsLoader active: false sourceComponent: PanelWindow { id: mediaControlsRoot visible: true exclusiveZone: 0 implicitWidth: ( (mediaControlsRoot.screen.width / 2) // Middle of screen - (osdWidth / 2) // Dodge OSD - (widgetWidth / 2) // Account for widget width ) * 2 implicitHeight: playerColumnLayout.implicitHeight color: "transparent" WlrLayershell.namespace: "quickshell:mediaControls" anchors { top: !Config.options.bar.bottom bottom: Config.options.bar.bottom left: true } mask: Region { item: playerColumnLayout } ColumnLayout { id: playerColumnLayout anchors.top: parent.top anchors.bottom: parent.bottom x: (mediaControlsRoot.screen.width / 2) // Middle of screen - (osdWidth / 2) // Dodge OSD - (widgetWidth) // Account for widget width + (Appearance.sizes.elevationMargin) // It's fine for shadows to overlap spacing: -Appearance.sizes.elevationMargin // Shadow overlap okay Repeater { model: ScriptModel { values: root.meaningfulPlayers } delegate: PlayerControl { required property MprisPlayer modelData player: modelData visualizerPoints: root.visualizerPoints } } } } } IpcHandler { target: "mediaControls" function toggle(): void { mediaControlsLoader.active = !mediaControlsLoader.active; if(mediaControlsLoader.active) Notifications.timeoutAll(); } function close(): void { mediaControlsLoader.active = false; } function open(): void { mediaControlsLoader.active = true; Notifications.timeoutAll(); } } GlobalShortcut { name: "mediaControlsToggle" description: qsTr("Toggles media controls on press") onPressed: { if (!mediaControlsLoader.active && Mpris.players.values.filter(player => isRealPlayer(player)).length === 0) { return; } mediaControlsLoader.active = !mediaControlsLoader.active; if(mediaControlsLoader.active) Notifications.timeoutAll(); } } GlobalShortcut { name: "mediaControlsOpen" description: qsTr("Opens media controls on press") onPressed: { mediaControlsLoader.active = true; Notifications.timeoutAll(); } } GlobalShortcut { name: "mediaControlsClose" description: qsTr("Closes media controls on press") onPressed: { mediaControlsLoader.active = false; } } }