import QtQuick import Quickshell import qs.Ui import qs.Commons BarWidget { id: root moduleName: "Media" readonly property var mediaService: bar && bar.shell ? bar.shell.firstPartyServiceFor("omarchy.media") : null readonly property var activePlayer: mediaService ? mediaService.activePlayer : null readonly property var sourcePlayers: mediaService ? mediaService.sourcePlayers : [] readonly property bool hasMedia: activePlayer !== null && (activePlayer.trackTitle || activePlayer.trackArtist) readonly property string playIcon: activePlayer && activePlayer.isPlaying ? "󰏤" : "󰐊" readonly property string title: activePlayer ? (activePlayer.trackTitle || "") : "" readonly property string artist: activePlayer ? (activePlayer.trackArtist || "") : "" property bool popupOpen: false function close() { popupOpen = false } property real maxLabelWidth: 180 visible: hasMedia implicitWidth: hasMedia ? row.implicitWidth + Style.space(14) : 0 implicitHeight: barSize Row { id: row anchors.centerIn: parent spacing: Style.space(6) Text { id: glyph anchors.verticalCenter: parent.verticalCenter text: root.playIcon color: activePlayer && activePlayer.isPlaying ? root.bar.foreground : Qt.darker(root.bar.foreground, 1.5) font.family: root.bar.fontFamily font.pixelSize: Style.font.body Behavior on color { ColorAnimation { duration: 160 } } } Item { id: scrollClip width: Math.min(root.maxLabelWidth, labelText.implicitWidth) height: glyph.height clip: true anchors.verticalCenter: parent.verticalCenter visible: !root.bar.vertical && root.title !== "" Text { id: labelText text: root.title + (root.artist ? " · " + root.artist : "") color: root.bar.foreground font.family: root.bar.fontFamily font.pixelSize: Style.font.body anchors.verticalCenter: parent.verticalCenter property bool needsScroll: implicitWidth > scrollClip.width NumberAnimation on x { id: scrollAnim running: labelText.needsScroll && !root.popupOpen && !root.bar.vertical loops: Animation.Infinite duration: Math.max(6000, labelText.implicitWidth * 25) from: scrollClip.width to: -labelText.implicitWidth easing.type: Easing.Linear } } } } MouseArea { anchors.fill: parent hoverEnabled: true cursorShape: root.activePlayer ? Qt.PointingHandCursor : Qt.ArrowCursor acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton onClicked: function(mouse) { if (!root.activePlayer) return if (mouse.button === Qt.MiddleButton) { if (root.mediaService) root.mediaService.runAction("next", false) } else if (mouse.button === Qt.RightButton) { root.popupOpen = !root.popupOpen } else { if (root.mediaService) root.mediaService.runAction("playPause", false) } } onWheel: function(wheel) { if (!root.activePlayer) return if (wheel.angleDelta.y > 0 && root.mediaService) root.mediaService.runAction("previous", false) else if (wheel.angleDelta.y < 0 && root.mediaService) root.mediaService.runAction("next", false) } onEntered: if (root.bar) root.bar.showTooltip(root, root.hasMedia ? (root.title + (root.artist ? " — " + root.artist : "")) : "") onExited: if (root.bar) root.bar.hideTooltip(root) } PopupCard { id: popup anchorItem: root bar: root.bar owner: root open: root.popupOpen contentWidth: popup.fittedContentWidth(Style.space(320)) contentHeight: popup.fittedContentHeight(column.implicitHeight) Column { id: column anchors.fill: parent spacing: Style.space(10) Row { spacing: Style.space(10) width: parent.width Rectangle { width: Style.space(64) height: Style.space(64) radius: Style.spacing.labelGap color: Style.normalFillFor(root.bar.foreground, Color.accent) border.color: Style.normalBorderFor(root.bar.foreground, Color.accent) border.width: Style.normalBorderWidth Image { anchors.fill: parent anchors.margins: Style.space(2) fillMode: Image.PreserveAspectCrop asynchronous: true source: root.activePlayer && root.activePlayer.trackArtUrl ? root.activePlayer.trackArtUrl : "" visible: source !== "" } Text { anchors.centerIn: parent visible: !root.activePlayer || !root.activePlayer.trackArtUrl text: "󰝚" color: root.bar.foreground font.family: root.bar.fontFamily font.pixelSize: Style.font.displayLarge } } Column { spacing: Style.space(4) width: parent.width - Style.space(74) Text { text: root.title || "Nothing playing" color: root.bar.foreground font.family: root.bar.fontFamily font.pixelSize: Style.font.subtitle font.bold: true elide: Text.ElideRight width: parent.width } Text { text: root.artist color: Qt.darker(root.bar.foreground, 1.3) font.family: root.bar.fontFamily font.pixelSize: Style.font.bodySmall elide: Text.ElideRight width: parent.width visible: text !== "" } Text { text: root.activePlayer && root.activePlayer.trackAlbum ? root.activePlayer.trackAlbum : "" color: Qt.darker(root.bar.foreground, 1.6) font.family: root.bar.fontFamily font.pixelSize: Style.font.caption elide: Text.ElideRight width: parent.width visible: text !== "" } } } Row { anchors.horizontalCenter: parent.horizontalCenter spacing: Style.space(6) Button { iconText: "󰒮" foreground: root.bar.foreground horizontalPadding: Style.spacing.controlPaddingX verticalPadding: Style.spacing.controlPaddingY enabled: root.activePlayer && root.activePlayer.canGoPrevious opacity: enabled ? 1.0 : 0.4 onClicked: if (root.mediaService) root.mediaService.runAction("previous", false, root.mediaService.playerKey(root.activePlayer)) } Button { iconText: root.activePlayer && root.activePlayer.isPlaying ? "󰏤" : "󰐊" foreground: root.bar.foreground horizontalPadding: Style.spacing.panelGap verticalPadding: Style.spacing.controlPaddingY iconSize: Style.font.iconLarge enabled: root.activePlayer && (root.activePlayer.canTogglePlaying || root.activePlayer.canPlay || root.activePlayer.canPause) opacity: enabled ? 1.0 : 0.4 onClicked: if (root.mediaService) root.mediaService.runAction("playPause", false, root.mediaService.playerKey(root.activePlayer)) } Button { iconText: "󰒭" foreground: root.bar.foreground horizontalPadding: Style.spacing.controlPaddingX verticalPadding: Style.spacing.controlPaddingY enabled: root.activePlayer && root.activePlayer.canGoNext opacity: enabled ? 1.0 : 0.4 onClicked: if (root.mediaService) root.mediaService.runAction("next", false, root.mediaService.playerKey(root.activePlayer)) } } PanelSeparator { visible: root.sourcePlayers.length > 1 foreground: root.bar.foreground } Column { id: sourceList visible: root.sourcePlayers.length > 1 width: parent.width spacing: Style.space(4) Repeater { model: root.sourcePlayers Rectangle { id: sourceRow required property var modelData readonly property var player: modelData readonly property bool selected: root.activePlayer && player && root.mediaService.playerKey(root.activePlayer) === root.mediaService.playerKey(player) readonly property string sourceTitle: player ? (player.trackTitle || player.identity || player.desktopEntry || "Media source") : "Media source" readonly property string sourceDetail: player && player.trackArtist ? player.trackArtist : (player && player.identity ? player.identity : "") width: sourceList.width height: sourceInner.implicitHeight + Style.space(10) radius: Style.spacing.labelGap color: selected ? Style.selectedFillFor(root.bar.foreground, Color.accent) : "transparent" border.color: selected ? Style.selectedBorderFor(root.bar.foreground, Color.accent) : "transparent" border.width: selected ? Style.normalBorderWidth : 0 Row { id: sourceInner anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter anchors.leftMargin: Style.space(8) anchors.rightMargin: Style.space(8) spacing: Style.space(8) Text { text: sourceRow.player && sourceRow.player.isPlaying ? "󰏤" : "󰐊" color: root.bar.foreground font.family: root.bar.fontFamily font.pixelSize: Style.font.body width: Style.space(18) horizontalAlignment: Text.AlignHCenter anchors.verticalCenter: parent.verticalCenter } Column { width: parent.width - Style.space(26) spacing: Style.space(1) anchors.verticalCenter: parent.verticalCenter Text { text: sourceRow.sourceTitle color: root.bar.foreground font.family: root.bar.fontFamily font.pixelSize: Style.font.bodySmall font.bold: sourceRow.selected elide: Text.ElideRight width: parent.width } Text { text: sourceRow.sourceDetail color: Qt.darker(root.bar.foreground, 1.5) font.family: root.bar.fontFamily font.pixelSize: Style.font.caption elide: Text.ElideRight width: parent.width visible: text !== "" } } } MouseArea { anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: if (root.mediaService) root.mediaService.selectPlayer(root.mediaService.playerKey(sourceRow.player)) } } } } } } }