This commit is contained in:
2026-04-12 12:05:16 +00:00
parent 8dfbb0dea7
commit 60711adff0
19 changed files with 613 additions and 230 deletions
+11
View File
@@ -0,0 +1,11 @@
import QtQuick
import org.kde.plasma.configuration
ConfigModel {
ConfigCategory {
name: i18n("General")
icon: "preferences-desktop"
source: "configGeneral.qml"
}
}
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd">
<kcfgfile name=""/>
<group name="General">
<entry name="opacity" type="Int">
<label>Opacity</label>
<default>100</default>
</entry>
<entry name="fontFamily" type="String">
<label>Font family</label>
<default>Noto Sans</default>
</entry>
</group>
</kcfg>
+111
View File
@@ -0,0 +1,111 @@
/**
Credits: https://github.com/ccatterina/plasmusic-toolbar
looked handy, ty
*/
import QtQml.Models
import QtQuick
import org.kde.plasma.private.mpris as Mpris
QtObject {
id: root
property string sourceName: "any"
property var mpris2Model
readonly property bool ready: {
if (!mpris2Model.currentPlayer)
return false;
return mpris2Model.currentPlayer.desktopEntry === sourceName || sourceName === "any";
}
readonly property string artists: ready ? mpris2Model.currentPlayer.artist : ""
readonly property string title: ready ? mpris2Model.currentPlayer.track : ""
readonly property int playbackStatus: ready ? mpris2Model.currentPlayer.playbackStatus : Mpris.PlaybackStatus.Unknown
readonly property int shuffle: ready ? mpris2Model.currentPlayer.shuffle : Mpris.ShuffleStatus.Unknown
readonly property string artUrl: ready ? mpris2Model.currentPlayer.artUrl : ""
readonly property int loopStatus: ready ? mpris2Model.currentPlayer.loopStatus : Mpris.LoopStatus.Unknown
readonly property double songPosition: ready ? mpris2Model.currentPlayer.position : 0
readonly property double songLength: ready ? mpris2Model.currentPlayer.length : 0
readonly property real volume: ready ? mpris2Model.currentPlayer.volume : 0
readonly property string identity: ready ? mpris2Model.currentPlayer.identity : ""
readonly property bool canGoNext: ready ? mpris2Model.currentPlayer.canGoNext : false
readonly property bool canGoPrevious: ready ? mpris2Model.currentPlayer.canGoPrevious : false
readonly property bool canPlay: ready ? mpris2Model.currentPlayer.canPlay : false
readonly property bool canPause: ready ? mpris2Model.currentPlayer.canPause : false
readonly property bool canSeek: ready ? mpris2Model.currentPlayer.canSeek : false
readonly property bool canRaise: ready ? mpris2Model.currentPlayer.canRaise : false
// To know whether Shuffle and Loop can be changed we have to check if the property is defined,
// unlike the other commands, LoopStatus and Shuffle do not have specific properties such as
// CanPause, CanSeek, etc.
readonly property bool canChangeShuffle: ready ? mpris2Model.currentPlayer.shuffle !== undefined : false
readonly property bool canChangeLoopStatus: ready ? mpris2Model.currentPlayer.loopStatus !== undefined : false
function togglePlayPause() {
if (mpris2Model.currentPlayer)
mpris2Model.currentPlayer.PlayPause();
}
function setPosition(position) {
mpris2Model.currentPlayer.position = position;
}
function next() {
if (mpris2Model.currentPlayer)
mpris2Model.currentPlayer.Next();
}
function previous() {
if (mpris2Model.currentPlayer)
mpris2Model.currentPlayer.Previous();
}
function updatePosition() {
if (mpris2Model.currentPlayer)
mpris2Model.currentPlayer.updatePosition();
}
function setVolume(volume) {
mpris2Model.currentPlayer.volume = volume;
}
function changeVolume(delta, showOSD) {
mpris2Model.currentPlayer.changeVolume(delta, showOSD);
}
function setShuffle(shuffle) {
mpris2Model.currentPlayer.shuffle = shuffle;
}
function setLoopStatus(loopStatus) {
mpris2Model.currentPlayer.loopStatus = loopStatus;
}
function raise() {
mpris2Model.currentPlayer.Raise();
}
function formatTrackTime(s) {
const hours = Math.floor(s / 3600);
let minutes = Math.floor((s - hours * 3600) / 60);
let seconds = Math.ceil(s - hours * 3600 - minutes * 60);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
return minutes + ":" + seconds;
}
mpris2Model: Mpris.Mpris2Model {
onRowsInserted: (_, rowIndex) => {
const CONTAINER_ROLE = Qt.UserRole + 1;
const player = this.data(this.index(rowIndex, 0), CONTAINER_ROLE);
if (player.desktopEntry === root.sourceName)
this.currentIndex = rowIndex;
}
}
}
+211
View File
@@ -0,0 +1,211 @@
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.plasma.components as PlasmaComponents
MouseArea {
id: mediaControlsMouseArea
readonly property double buttonSize: 16
focus: true
hoverEnabled: true
Keys.onPressed: (event) => {
if (!event.modifiers) {
event.accepted = true;
if (event.key === Qt.Key_Space || event.key === Qt.Key_K)
player.togglePlayPause();
else if (event.key === Qt.Key_P)
player.previous();
else if (event.key === Qt.Key_N)
player.next();
else
event.accepted = false;
}
}
RowLayout {
anchors.fill: parent
ColumnLayout {
id: leftColumn
Layout.fillHeight: true
states: [
State {
name: "buttonsVisible"
when: mediaControlsMouseArea.containsMouse
PropertyChanges {
target: nowPlayingLabels
Layout.bottomMargin: 10
}
},
State {
name: "buttonsHidden"
when: !mediaControlsMouseArea.containsMouse
PropertyChanges {
target: nowPlayingLabels
Layout.bottomMargin: 0
}
}
]
Item {
Layout.fillHeight: true
Layout.fillWidth: true
}
ColumnLayout {
id: nowPlayingLabels
Layout.alignment: Qt.AlignRight
spacing: 0
PlasmaComponents.Label {
Layout.alignment: Qt.AlignRight
text: "NOW"
lineHeight: 0.8
font.pixelSize: 16
font.bold: true
// qmllint disable missing-property
font.family: plasmoid.configuration.fontFamily
}
PlasmaComponents.Label {
id: nowPlayingLabel2
Layout.alignment: Qt.AlignRight
text: "PLAYING"
lineHeight: 0.8
font.bold: true
font.pixelSize: 16
// qmllint disable missing-property
font.family: plasmoid.configuration.fontFamily
}
}
RowLayout {
id: mediaControls
Layout.alignment: Qt.AlignHCenter
opacity: mediaControlsMouseArea.containsMouse ? 1 : 0
visible: opacity > 0
QQC2.Button {
Layout.preferredWidth: buttonSize
Layout.preferredHeight: buttonSize
padding: 0
background: null
onClicked: {
player.previous();
console.log("prev clicked");
}
contentItem: Kirigami.Icon {
source: "media-skip-backward"
}
}
QQC2.Button {
id: playButton
Layout.preferredWidth: buttonSize
Layout.preferredHeight: buttonSize
padding: 0
background: null
onClicked: {
player.togglePlayPause();
console.log("pause clicked");
}
contentItem: Kirigami.Icon {
source: player.playbackStatus === 2 ? "media-playback-pause" : "media-playback-start"
}
}
QQC2.Button {
Layout.preferredWidth: buttonSize
Layout.preferredHeight: buttonSize
padding: 0
background: null
onClicked: player.next()
contentItem: Kirigami.Icon {
source: "media-skip-forward"
}
}
Behavior on opacity {
PropertyAnimation {
duration: 250
easing.type: Easing.InOutQuad
}
}
}
Item {
Layout.fillHeight: true
Layout.fillWidth: true
}
transitions: Transition {
NumberAnimation {
duration: 250
easing.type: Easing.InOutQuad
properties: "Layout.bottomMargin"
}
}
}
Rectangle {
id: separator
Layout.fillHeight: true
width: 1
}
ColumnLayout {
id: infoColumn
Layout.fillWidth: true
PlasmaComponents.Label {
Layout.fillWidth: true
text: player.title
font.pixelSize: 28
font.bold: true
// qmllint disable missing-property
font.family: plasmoid.configuration.fontFamily
lineHeight: 0.8
elide: Text.ElideRight
}
PlasmaComponents.Label {
Layout.maximumWidth: 300
Layout.fillWidth: true
text: player.artists
font.pixelSize: 26
// qmllint disable missing-property
font.family: plasmoid.configuration.fontFamily
lineHeight: 0.8
elide: Text.ElideRight
}
}
}
}
+40
View File
@@ -0,0 +1,40 @@
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kcmutils as KCM
import org.kde.kirigami as Kirigami
KCM.SimpleKCM {
id: configRoot
property alias cfg_opacity: opacitySpinBox.value
property string cfg_fontFamily
readonly property var availableFonts: Qt.fontFamilies()
onCfg_fontFamilyChanged: {
const index = availableFonts.indexOf(cfg_fontFamily);
if (index >= 0 && fontFamilyComboBox.currentIndex !== index)
fontFamilyComboBox.currentIndex = index;
}
Kirigami.FormLayout {
QQC2.SpinBox {
id: opacitySpinBox
Kirigami.FormData.label: i18n("Opacity percent:")
from: 0
to: 100
}
QQC2.ComboBox {
id: fontFamilyComboBox
Kirigami.FormData.label: i18n("Font:")
model: configRoot.availableFonts
onActivated: configRoot.cfg_fontFamily = currentText
}
}
}
+37
View File
@@ -0,0 +1,37 @@
import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.plasma.plasmoid
PlasmoidItem {
id: root
// Properties accessed from configuration.
// "qmllint" sees `plasmoid` as QObject here, but Plasma injects `configuration` at runtime.
// qmllint disable missing-property
property int configuredOpacity: plasmoid.configuration.opacity
width: Kirigami.Units.gridUnit * 25
height: Kirigami.Units.gridUnit * 5
Layout.minimumWidth: Kirigami.Units.gridUnit * 25
Layout.minimumHeight: Kirigami.Units.gridUnit * 5
Plasmoid.backgroundHints: "NoBackground"
opacity: configuredOpacity / 100
Player {
id: player
}
fullRepresentation: Representation {
Layout.minimumWidth: Kirigami.Units.gridUnit * 25
Layout.minimumHeight: Kirigami.Units.gridUnit * 5
Layout.preferredWidth: root.width
Layout.preferredHeight: root.height
}
compactRepresentation: Representation {
Layout.minimumWidth: Kirigami.Units.gridUnit * 25
Layout.minimumHeight: Kirigami.Units.gridUnit * 5
}
}