From 8dfbb0dea7b1acea2041adbdc1fce174c4cd26b3 Mon Sep 17 00:00:00 2001 From: ruinivist Date: Sun, 8 Feb 2026 13:26:24 +0000 Subject: [PATCH] init --- LICENSE | 121 ++++++++++++++++ README.md | 12 ++ plasmoid/contents/config/config.qml | 10 ++ plasmoid/contents/config/main.xml | 19 +++ plasmoid/contents/ui/GeneralConfig.qml | 46 ++++++ plasmoid/contents/ui/Player.qml | 107 ++++++++++++++ plasmoid/contents/ui/Representation.qml | 179 ++++++++++++++++++++++++ plasmoid/contents/ui/main.qml | 28 ++++ plasmoid/metadata.json | 21 +++ run | 3 + 10 files changed, 546 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 plasmoid/contents/config/config.qml create mode 100644 plasmoid/contents/config/main.xml create mode 100644 plasmoid/contents/ui/GeneralConfig.qml create mode 100644 plasmoid/contents/ui/Player.qml create mode 100644 plasmoid/contents/ui/Representation.qml create mode 100644 plasmoid/contents/ui/main.qml create mode 100644 plasmoid/metadata.json create mode 100755 run diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/README.md b/README.md new file mode 100644 index 0000000..00b78f3 --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# NowPlaying + +## now on kde 6: https://www.pling.com/p/2195583/ + +A KDE Plasma widget inspired from the Cleartext rainmeter widget. Still in beta. + +Hello,\ +Full credit to the guy windows guy for the idea, I just made a clone.\ +Now of course, it is not as feature fledged (read as no features) as that one but I aim to add most, if not all of it, if time permits.\ +Feel free to open any issues, requests, suggestions etc. + +PS: Don't forget to star the project. I care about fake internet points. diff --git a/plasmoid/contents/config/config.qml b/plasmoid/contents/config/config.qml new file mode 100644 index 0000000..7253f1c --- /dev/null +++ b/plasmoid/contents/config/config.qml @@ -0,0 +1,10 @@ +import QtQuick 2.12 +import org.kde.plasma.configuration 2.0 + +ConfigModel { + ConfigCategory { + name: i18n("General") + icon: "preferences-desktop" + source: "GeneralConfig.qml" + } +} \ No newline at end of file diff --git a/plasmoid/contents/config/main.xml b/plasmoid/contents/config/main.xml new file mode 100644 index 0000000..7feb4f2 --- /dev/null +++ b/plasmoid/contents/config/main.xml @@ -0,0 +1,19 @@ + + + + + + + 100 + + + + "Noto Sans" + + + + + diff --git a/plasmoid/contents/ui/GeneralConfig.qml b/plasmoid/contents/ui/GeneralConfig.qml new file mode 100644 index 0000000..8ded248 --- /dev/null +++ b/plasmoid/contents/ui/GeneralConfig.qml @@ -0,0 +1,46 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami as Kirigami + +Item { + id: configRoot + + signal configurationChanged + + property alias cfg_opacity: opacitySpinBox.value + + property string cfg_fontFamily + + ColumnLayout { + spacing: Kirigami.Units.mediumSpacing + + RowLayout{ + Label { + text: i18n("Opacity percent") + } + SpinBox { + id: opacitySpinBox + from: 0 + to: 100 + } + } + + RowLayout{ + Label { + text: i18n("Font") + } + ComboBox{ + id: fontFamilyComboBox + model: Qt.fontFamilies() + onCurrentIndexChanged: { + var current = Qt.fontFamilies()[currentIndex] + cfg_fontFamily=current + configRoot.configurationChanged() + } + } + } + + } +} diff --git a/plasmoid/contents/ui/Player.qml b/plasmoid/contents/ui/Player.qml new file mode 100644 index 0000000..752e87a --- /dev/null +++ b/plasmoid/contents/ui/Player.qml @@ -0,0 +1,107 @@ +/** +Credits: https://github.com/ccatterina/plasmusic-toolbar + +looked handy, ty +*/ + + +import QtQuick 2.15 +import QtQml.Models 2.3 +import org.kde.plasma.private.mpris as Mpris + +QtObject { + id: root + + property var 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; + } + } + } + + property string sourceName: "any" + + 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 hasn't a specific propety 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() { + mpris2Model.currentPlayer?.PlayPause(); + } + + function setPosition(position) { + mpris2Model.currentPlayer.position = position; + } + + function next() { + mpris2Model.currentPlayer?.Next(); + } + + function previous() { + mpris2Model.currentPlayer?.Previous(); + } + + function updatePosition() { + 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) { + var hours = Math.floor(s / 3600) + var minutes = Math.floor((s - (hours * 3600)) / 60) + var seconds = Math.ceil(s - (hours * 3600) - (minutes * 60)) + minutes = (minutes < 10) ? "0" + minutes : minutes + seconds = (seconds < 10) ? "0" + seconds : seconds + var time = minutes + ":" + seconds + return time + } +} diff --git a/plasmoid/contents/ui/Representation.qml b/plasmoid/contents/ui/Representation.qml new file mode 100644 index 0000000..e7a7990 --- /dev/null +++ b/plasmoid/contents/ui/Representation.qml @@ -0,0 +1,179 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 +import org.kde.plasma.components as PlasmaComponents +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami as Kirigami + +MouseArea { + id: mediaControlsMouseArea + focus: true + Keys.onPressed: { + 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 + } + } + } + hoverEnabled: true + + readonly property double buttonSize: 16 + + RowLayout { + ColumnLayout { + Layout.fillHeight: true + id: leftColumn + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + } + + ColumnLayout { + id: nowPlayingLabels + Layout.alignment: Qt.AlignRight + spacing: 0 + + Label { + Layout.alignment: Qt.AlignRight + text: "NOW" + lineHeight: 0.8 + font.pixelSize: 16 + font.bold: true + font.family: plasmoid.configuration.fontFamily + } + Label { + id: nowPlayingLabel2 + Layout.alignment: Qt.AlignRight + text: "PLAYING" + lineHeight: 0.8 + font.bold: true + font.pixelSize: 16 + font.family: plasmoid.configuration.fontFamily + } + } + + RowLayout { + id: mediaControls + Layout.alignment: Qt.AlignHCenter + opacity: mediaControlsMouseArea.containsMouse ? 1 : 0 + visible: opacity > 0 + + Behavior on opacity { + PropertyAnimation { + easing.type: Easing.InOutQuad + duration: 250 + } + } + + Button { + Layout.preferredWidth: buttonSize + Layout.preferredHeight: buttonSize + contentItem: Kirigami.Icon { + source: "media-skip-backward" + } + padding: 0 + background: null + onClicked: { + player.previous() + console.log("prev clicked") + } + } + Button { + Layout.preferredWidth: buttonSize + Layout.preferredHeight: buttonSize + id: playButton + contentItem: Kirigami.Icon { + source: player.playbackStatus === 2 ? "media-playback-pause" : "media-playback-start" + } + padding: 0 + background: null + onClicked: { + player.togglePlayPause() + console.log("pause clicked") + } + } + Button { + Layout.preferredWidth: buttonSize + Layout.preferredHeight: buttonSize + contentItem: Kirigami.Icon { + source: "media-skip-forward" + } + onClicked: { + player.next() + } + padding: 0 + background: null + } + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: 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 + } + } + ] + + transitions: Transition { + NumberAnimation { + properties: "Layout.bottomMargin" + duration: 250 + easing.type: Easing.InOutQuad + } + } + } + + Rectangle { + id: separator + width: 1 + Layout.fillHeight: true + } + + ColumnLayout { + Layout.fillWidth: true + id: infoColumn + PlasmaComponents.Label { + font.family: plasmoid.configuration.fontFamily + text: player.title + Layout.fillWidth: true + font.pixelSize: 28 + lineHeight: 0.8 + font.bold: true + elide: Text.ElideRight + } + PlasmaComponents.Label { + font.family: plasmoid.configuration.fontFamily + elide: Text.ElideRight + Layout.maximumWidth: 300 + Layout.fillWidth: true + text: player.artists + font.pixelSize: 26 + lineHeight: 0.8 + } + } + } +} diff --git a/plasmoid/contents/ui/main.qml b/plasmoid/contents/ui/main.qml new file mode 100644 index 0000000..e27e3a1 --- /dev/null +++ b/plasmoid/contents/ui/main.qml @@ -0,0 +1,28 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 +import org.kde.plasma.plasmoid +import org.kde.plasma.core as PlasmaCore +import org.kde.plasma.plasma5support as Plasma5Support +import org.kde.plasma.private.mpris as Mpris +import org.kde.kirigami as Kirigami + +PlasmoidItem { + id: root + + Layout.minimumWidth: Kirigami.Units.gridUnit*25 + Layout.minimumHeight: Kirigami.Units.gridUnit*5 + + Plasmoid.backgroundHints: "NoBackground" + + opacity: plasmoid.configuration.opacity/100 + + Representation{ + anchors.fill: parent + } + + + Player{ + id: player + } +} diff --git a/plasmoid/metadata.json b/plasmoid/metadata.json new file mode 100644 index 0000000..d3fceaa --- /dev/null +++ b/plasmoid/metadata.json @@ -0,0 +1,21 @@ +{ + "KPlugin": { + "Authors": [ + { + "Name": "ruinivist" + } + ], + "Category": "Multimedia", + "Description": "An aesthetic song name widget with media controls. Meant to be placed on Desktop not in panel.", + "Icon": "applications-multimedia", + "Id": "org.ruiny.NowPlaying", + "License": "GPLv3", + "Name": "NowPlaying", + "Version": "1.0", + "Website": "https://github.com/ruinivist/NowPlaying" + }, + "KPackageStructure": "Plasma/Applet", + "X-Plasma-API-Minimum-Version": "6.0", + "X-Plasma-Provides": [ "org.kde.plasma.multimediacontrols" ], + "X-Plasma-DBusActivationService": "org.mpris.MediaPlayer2.*" +} diff --git a/run b/run new file mode 100755 index 0000000..b4550cf --- /dev/null +++ b/run @@ -0,0 +1,3 @@ +kpackagetool6 -t Plasma/Applet --install plasmoid/ +kpackagetool6 -t Plasma/Applet --upgrade plasmoid/ +plasmoidviewer -a ./plasmoid \ No newline at end of file