mpris: add ipc handler for multimedia keys

This commit is contained in:
kossLAN 2025-06-13 13:59:15 -04:00
parent 15ffd6d5b0
commit 233e12e094
Signed by: kossLAN
SSH key fingerprint: SHA256:bdV0x+wdQHGJ6LgmstH3KV8OpWY+OOFmJcPcB0wQPV8
8 changed files with 43 additions and 456 deletions

View file

@ -1,177 +0,0 @@
import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import "../.."
import "../../widgets" as Widgets
Rectangle {
required property var player
radius: 5
color: "transparent"
implicitHeight: 220
RowLayout {
id: cardLayout
spacing: 15
anchors {
fill: parent
leftMargin: 10
rightMargin: 10
topMargin: 10 // Added top margin for better spacing
bottomMargin: 10 // Added bottom margin for better spacing
}
Rectangle {
id: mprisImage
color: "transparent"
radius: 10
width: 200
height: 200
Layout.alignment: Qt.AlignVCenter
visible: true
Image {
anchors.fill: parent
source: player.trackArtUrl
sourceSize.width: 1024
sourceSize.height: 1024
fillMode: Image.PreserveAspectFit
layer.enabled: true
layer.effect: OpacityMask {
source: Rectangle {
width: mprisImage.width
height: mprisImage.height
radius: 10
color: "white"
}
maskSource: Rectangle {
width: mprisImage.width
height: mprisImage.height
radius: 10
color: "black"
}
layer.enabled: true
layer.effect: DropShadow {
transparentBorder: true
spread: 0.02
samples: 25
color: "#80000000"
}
}
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 5
Text {
text: player.trackArtist
color: "white"
font.pointSize: 13
font.bold: true
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
elide: Text.ElideRight
}
Text {
text: player.trackTitle
color: "white"
font.pointSize: 13
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
elide: Text.ElideRight
}
RowLayout {
spacing: 6
ColorQuantizer {
id: colorQuantizer
source: Qt.resolvedUrl(Media.trackedPlayer?.trackArtUrl ?? "")
depth: 0
rescaleSize: 64
}
Text {
text: timeStr(player.position)
color: "white"
font {
pointSize: 9
bold: true
}
}
Widgets.RoundSlider {
from: 0
to: 1
accentColor: colorQuantizer.colors[0]
//value: root.node.audio.volume
//onValueChanged: node.audio.volume = value
Layout.fillWidth: true
Layout.preferredHeight: 16
}
Text {
text: timeStr(player.length)
color: "white"
font {
pointSize: 9
bold: true
}
}
}
// Music Controls
RowLayout {
spacing: 2
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Widgets.IconButton {
implicitSize: 36
activeRectangle: false
padding: 4
source: "root:resources/mpris/previous.svg"
onClicked: player.previous()
}
Widgets.IconButton {
implicitSize: 36
activeRectangle: false
padding: 4
source: player?.isPlaying ? "root:resources/mpris/pause.svg" : "root:resources/mpris/play.svg"
onClicked: {
if (!player.canPlay)
return;
player.isPlaying ? player.pause() : player.play();
}
}
Widgets.IconButton {
implicitSize: 36
activeRectangle: false
padding: 4
source: "root:resources/mpris/next.svg"
onClicked: player.next()
}
}
}
}
function timeStr(time: int): string {
const seconds = time % 60;
const minutes = Math.floor(time / 60);
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
}
}

View file

@ -1,151 +0,0 @@
import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Services.Mpris
import "../.."
PopupWindow {
id: root
width: mediaPlayerContainer.width + 10
height: mediaPlayerContainer.height + 10
color: "transparent"
visible: mediaPlayerContainer.opacity > 0
anchor.rect.x: parentWindow.width / 2 - width / 2
anchor.rect.y: parentWindow.height
function show() {
mediaPlayerContainer.opacity = 1;
}
function hide() {
mediaPlayerContainer.opacity = 0;
}
HoverHandler {
id: hoverHandler
enabled: true
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
onHoveredChanged: {
if (hovered == false) {
hide();
}
}
}
Rectangle {
id: mediaPlayerContainer
width: 500
height: mediaPlayerColumn.height + 20
color: ShellGlobals.colors.background
radius: 5
opacity: 0
anchors.centerIn: parent
layer.enabled: true
layer.effect: OpacityMask {
source: Rectangle {
width: mediaPlayerContainer.width
height: mediaPlayerContainer.height
radius: mediaPlayerContainer.radius
color: "white"
}
maskSource: Rectangle {
width: mediaPlayerContainer.width
height: mediaPlayerContainer.height
radius: mediaPlayerContainer.radius
color: "black"
}
layer.enabled: true
layer.effect: DropShadow {
transparentBorder: true
spread: 0.02
samples: 25
color: "#80000000"
}
}
Behavior on opacity {
NumberAnimation {
duration: 300
easing.type: Easing.OutCubic
}
}
ColorQuantizer {
id: colorQuantizer
source: Qt.resolvedUrl(Media.trackedPlayer?.trackArtUrl ?? "")
depth: 2
rescaleSize: 64
onColorsChanged: {
Media.colors = colors;
}
}
ShaderEffect {
property color topLeftColor: colorQuantizer?.colors[0]?.lighter(1.2) ?? "white"
property color topRightColor: colorQuantizer?.colors[1]?.lighter(1.2) ?? "black"
property color bottomLeftColor: colorQuantizer?.colors[2]?.lighter(1.2) ?? "white"
property color bottomRightColor: colorQuantizer?.colors[3]?.lighter(1.2) ?? "black"
anchors.fill: parent
fragmentShader: "root:/shaders/vertexgradient.frag.qsb"
vertexShader: "root:/shaders/vertexgradient.vert.qsb"
Behavior on topLeftColor {
ColorAnimation {
duration: 500
easing.type: Easing.InOutQuad
}
}
Behavior on topRightColor {
ColorAnimation {
duration: 500
easing.type: Easing.InOutQuad
}
}
Behavior on bottomLeftColor {
ColorAnimation {
duration: 500
easing.type: Easing.InOutQuad
}
}
Behavior on bottomRightColor {
ColorAnimation {
duration: 500
easing.type: Easing.InOutQuad
}
}
}
ColumnLayout {
id: mediaPlayerColumn
spacing: 10
Layout.fillWidth: true
Layout.preferredWidth: parent.width
Layout.margins: 10
implicitHeight: childrenRect.height
anchors {
top: parent.top
left: parent.left
right: parent.right
margins: 10
}
// Media Cards
Repeater {
model: Mpris.players
Card {
required property var modelData
player: modelData
Layout.fillWidth: true
}
}
}
}
}

View file

@ -1,86 +0,0 @@
import QtQuick
import Quickshell.Widgets
import Quickshell.Services.Mpris
import "../.."
Item {
id: root
required property var bar
width: statusInfo.width + 125
height: parent.height
visible: Mpris.players.values.length != 0
Player {
id: mediaPlayer
anchor.window: bar
anchor.rect.x: parentWindow.width / 2 - width / 2
anchor.rect.y: parentWindow.height
}
MouseArea {
id: playButton
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
if (mediaPlayer.visible) {
mediaPlayer.hide();
} else {
mediaPlayer.show();
}
} else {
if (!Media.trackedPlayer.canPlay || Media.trackedPlayer == null)
return;
if (Media.trackedPlayer.isPlaying)
Media.trackedPlayer.pause();
else
Media.trackedPlayer.play();
}
}
anchors.fill: parent
}
Item {
id: statusInfo
width: statusIcon.width + statusIcon.anchors.rightMargin + nowPlayingText.width
height: parent.height
visible: Media.trackedPlayer != null
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
top: parent.top
bottom: parent.botton
margins: 3.5
}
IconImage {
id: statusIcon
implicitSize: 13
source: Media.trackedPlayer?.isPlaying ? "root:resources/mpris/pause.svg" : "root:resources/mpris/play.svg"
anchors {
verticalCenter: parent.verticalCenter
right: nowPlayingText.left
rightMargin: 10
}
}
Text {
id: nowPlayingText
color: ShellGlobals.colors.text
text: `${Media.trackedPlayer?.trackArtist} - ${Media.trackedPlayer?.trackTitle}`
font.pointSize: 11
width: Math.min(implicitWidth, 250)
elide: Text.ElideRight
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
}
}
}
}

View file

@ -1,10 +0,0 @@
# Lockscreen
This is a simple but functional lockscreen that follows the system color scheme.
The only authentication method it supports is a password.
You can run the lockscreen with `quickshell -p shell.qml`.
You can run the lockscreen in test mode (as a window) with `quickshell -p test.qml`.
![](./image.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View file

@ -1,25 +0,0 @@
import QtQuick
import Quickshell
ShellRoot {
LockContext {
id: lockContext
onUnlocked: Qt.quit();
}
FloatingWindow {
LockSurface {
anchors.fill: parent
context: lockContext
}
}
// exit the example if the window closes
Connections {
target: Quickshell
function onLastWindowClosed() {
Qt.quit();
}
}
}

View file

@ -1,13 +1,42 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io
import Quickshell.Services.Mpris import Quickshell.Services.Mpris
Singleton { Singleton {
id: root id: root
property MprisPlayer trackedPlayer property MprisPlayer trackedPlayer
property var colors: ["white"]
IpcHandler {
target: "mpris"
function next(): void {
root.trackedPlayer.next();
}
function prev(): void {
root.trackedPlayer.previous();
}
function play(): void {
root.trackedPlayer.play();
}
function pause(): void {
root.trackedPlayer.pause();
}
function play_pause(): void {
if (root.trackedPlayer.isPlaying) {
root.trackedPlayer.pause();
} else {
root.trackedPlayer.play();
}
}
}
Instantiator { Instantiator {
model: Mpris.players model: Mpris.players
@ -31,8 +60,8 @@ Singleton {
} }
} }
if (trackedPlayer == null && Mpris.players.values.length != 0) { if (root.trackedPlayer == null && Mpris.players.values.length != 0) {
trackedPlayer = Mpris.players.values[0]; root.trackedPlayer = Mpris.players.values[0];
} }
} }
} }
@ -43,4 +72,6 @@ Singleton {
} }
} }
} }
function init() {}
} }

View file

@ -5,6 +5,7 @@ import Quickshell
import QtQuick import QtQuick
import "bar" as Bar import "bar" as Bar
import "notifications" as Notifications import "notifications" as Notifications
import "mpris" as Mpris
import "volume-osd" as VolumeOSD import "volume-osd" as VolumeOSD
import "settings" as Settings import "settings" as Settings
import "launcher" as Launcher import "launcher" as Launcher
@ -13,13 +14,16 @@ import "wallpaper" as Wallpaper
import "screencapture" as ScreenCapture import "screencapture" as ScreenCapture
ShellRoot { ShellRoot {
// Singleton's that need to be loaded in some way
Component.onCompleted: { Component.onCompleted: {
Launcher.Controller.init(); Launcher.Controller.init();
Settings.Controller.init(); Settings.Controller.init();
Notifications.NotificationCenter.init();
ScreenCapture.Controller.init(); ScreenCapture.Controller.init();
Mpris.Controller.init();
Notifications.NotificationCenter.init();
} }
// Elements that need context from all screens
Variants { Variants {
model: Quickshell.screens model: Quickshell.screens
@ -31,13 +35,14 @@ ShellRoot {
screen: scope.modelData screen: scope.modelData
} }
LockScreen.Controller { LockScreen.Controller {}
}
} }
} }
// On activation components
Notifications.Controller {} Notifications.Controller {}
VolumeOSD.Controller {} VolumeOSD.Controller {}
// this is an exception...
Wallpaper.Controller {} Wallpaper.Controller {}
} }