aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-11-07 12:00:58 +0100
committerDanilo M. <danix@danix.xyz>2026-11-07 12:00:58 +0100
commitd9a76b01d5a50af44e576c62efc1dbbf76db6203 (patch)
tree921d6dbbf17f87a64aa8379807fdceab69fa02fb
parent0811ff78e1716a7984aaa315bac0b2b4a8de248d (diff)
downloadquickshell-d9a76b01d5a50af44e576c62efc1dbbf76db6203.tar.gz
quickshell-d9a76b01d5a50af44e576c62efc1dbbf76db6203.zip
sonix: drawer tile and the Device tab
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
-rw-r--r--desktop/modules/sonix/SonixDevice.qml108
-rw-r--r--desktop/modules/sonix/SonixModule.qml40
-rw-r--r--desktop/modules/sonix/SonixPage.qml56
-rw-r--r--desktop/modules/sonix/SonixRow.qml66
-rw-r--r--desktop/modules/sonix/SonixTile.qml36
-rw-r--r--desktop/shell.qml2
6 files changed, 308 insertions, 0 deletions
diff --git a/desktop/modules/sonix/SonixDevice.qml b/desktop/modules/sonix/SonixDevice.qml
new file mode 100644
index 0000000..c52b2f4
--- /dev/null
+++ b/desktop/modules/sonix/SonixDevice.qml
@@ -0,0 +1,108 @@
+// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+import QtQuick
+import "../.."
+
+Column {
+ id: dev
+ signal go(string name)
+ readonly property var p: Sonix.player
+ spacing: 10
+
+ component Line: Row {
+ id: line
+ property string k: ""
+ property string v: ""
+ spacing: 10
+ Text {
+ width: 110
+ text: line.k
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 }
+ color: Theme.subtext
+ }
+ Text {
+ text: line.v
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 }
+ color: Theme.text
+ }
+ }
+
+ Line { k: "Name"; v: Sonix.name }
+ Line { k: "Address"; v: `${Sonix.host}:${Sonix.port}` }
+ Line {
+ k: "Battery"
+ v: dev.p.battery === undefined ? "\u2026" : dev.p.battery + "%" + (dev.p.charging ? ", charging" : "")
+ }
+ Row {
+ spacing: 10
+ Text {
+ width: 110
+ text: "Accent"
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 }
+ color: Theme.subtext
+ }
+ Rectangle {
+ width: 18; height: 18; radius: 4
+ color: dev.p.accent || "transparent"
+ border.width: 1
+ border.color: Qt.alpha(Theme.text, 0.2)
+ }
+ Text {
+ text: dev.p.accent || ""
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 }
+ color: Theme.text
+ }
+ }
+ Line {
+ k: "Library"
+ v: dev.p.scanning ? `scanning\u2026 ${dev.p.scan_count} found`
+ : dev.p.tracks === undefined ? "\u2026" : dev.p.tracks + " tracks"
+ }
+ Line {
+ k: "Now"
+ v: (["stopped", "playing", "paused"][dev.p.state || 0]) + (dev.p.title ? " \u00b7 " + dev.p.title : "")
+ }
+
+ Row {
+ spacing: 8
+ Button {
+ text: "Rescan"
+ enabled: !dev.p.scanning
+ onClicked: Sonix.command({ do: "scan" }, null, (ok, d, err) => msg.text = ok ? "" : err)
+ }
+ Button {
+ text: "Open Wi-Fi transfer"
+ onClicked: Qt.openUrlExternally(`http://${Sonix.host}/`)
+ }
+ Button {
+ text: "Boot logo\u2026"
+ onClicked: dev.go("bootlogo")
+ }
+ }
+
+ Text {
+ width: dev.width
+ wrapMode: Text.Wrap
+ text: "Wi-Fi transfer answers only while its page is open on the player."
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 5 }
+ color: Theme.subtext
+ }
+
+ Text {
+ id: msg
+ visible: text !== ""
+ width: dev.width
+ wrapMode: Text.Wrap
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
+ color: Theme.red
+ }
+}
diff --git a/desktop/modules/sonix/SonixModule.qml b/desktop/modules/sonix/SonixModule.qml
new file mode 100644
index 0000000..5146bf8
--- /dev/null
+++ b/desktop/modules/sonix/SonixModule.qml
@@ -0,0 +1,40 @@
+// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+import QtQuick
+import "../.."
+
+// The Sonix player in the drawer: a tile while it is on the network, and a
+// page with its device state, playlists and folders. See README.md.
+Module {
+ id: sonixMod
+
+ name: "sonix"
+ label: Sonix.name || "Sonix"
+ icon: String.fromCodePoint(0xF075A) // md-music
+ tiled: Sonix.present
+ active: Sonix.present && Sonix.player.state === 1
+
+ // Names typed under Playlists > New: SonixLink makes a playlist only by
+ // adding a track to it, so a new one lives here until its first track.
+ property var draftPlaylists: []
+
+ tileContent: Component { SonixTile {} }
+
+ page: Component {
+ Page {
+ id: pg
+ title: Sonix.name || "Sonix"
+ // Not `mod: mod`: inside SonixPage that names its own property.
+ SonixPage { width: parent.width; mod: sonixMod; onGo: name => pg.go(name) }
+ }
+ }
+}
diff --git a/desktop/modules/sonix/SonixPage.qml b/desktop/modules/sonix/SonixPage.qml
new file mode 100644
index 0000000..1ee89a6
--- /dev/null
+++ b/desktop/modules/sonix/SonixPage.qml
@@ -0,0 +1,56 @@
+// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+import QtQuick
+import "../.."
+
+Column {
+ id: page
+ required property var mod
+ signal go(string name)
+ property string tab: "device"
+ spacing: 14
+
+ Component.onCompleted: Sonix.hold()
+ Component.onDestruction: Sonix.release()
+
+ Text {
+ visible: !Sonix.present
+ text: "The player is not on the network."
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 }
+ color: Theme.subtext
+ }
+
+ Row {
+ visible: Sonix.present
+ spacing: 6
+ Repeater {
+ model: [["device", "Device"], ["playlists", "Playlists"], ["folders", "Folders"]]
+ delegate: Button {
+ required property var modelData
+ text: modelData[1]
+ border.color: page.tab === modelData[0] ? Theme.accent : Qt.alpha(Theme.text, 0.2)
+ onClicked: page.tab = modelData[0]
+ }
+ }
+ }
+
+ Loader {
+ width: page.width
+ active: Sonix.present
+ sourceComponent: page.tab === "device" ? deviceTab
+ : page.tab === "playlists" ? playlistsTab : foldersTab
+ }
+
+ Component { id: deviceTab; SonixDevice { onGo: name => page.go(name) } }
+ Component { id: playlistsTab; Item {} }
+ Component { id: foldersTab; Item {} }
+}
diff --git a/desktop/modules/sonix/SonixRow.qml b/desktop/modules/sonix/SonixRow.qml
new file mode 100644
index 0000000..518aa95
--- /dev/null
+++ b/desktop/modules/sonix/SonixRow.qml
@@ -0,0 +1,66 @@
+// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+import QtQuick
+import "../.."
+
+// A list row: a label, an optional second line, trailing buttons (children),
+// and a click on the text.
+Item {
+ id: row
+ property string text: ""
+ property string sub: ""
+ default property alias buttons: trail.data
+ signal clicked()
+
+ width: parent ? parent.width : implicitWidth
+ implicitHeight: Math.max(texts.implicitHeight, trail.implicitHeight) + 10
+
+ Rectangle {
+ anchors.fill: parent
+ radius: 6
+ color: hit.containsMouse ? Qt.alpha(Theme.accent, 0.12) : "transparent"
+ }
+
+ Column {
+ id: texts
+ anchors { left: parent.left; leftMargin: 8; right: trail.left; rightMargin: 8; verticalCenter: parent.verticalCenter }
+ Text {
+ width: parent.width
+ elide: Text.ElideRight
+ text: row.text
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 }
+ color: Theme.text
+ }
+ Text {
+ visible: row.sub !== ""
+ width: parent.width
+ elide: Text.ElideRight
+ text: row.sub
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 5 }
+ color: Theme.subtext
+ }
+ }
+
+ MouseArea {
+ id: hit
+ anchors { left: parent.left; right: trail.left; top: parent.top; bottom: parent.bottom }
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: row.clicked()
+ }
+
+ Row {
+ id: trail
+ anchors { right: parent.right; rightMargin: 4; verticalCenter: parent.verticalCenter }
+ spacing: 2
+ }
+}
diff --git a/desktop/modules/sonix/SonixTile.qml b/desktop/modules/sonix/SonixTile.qml
new file mode 100644
index 0000000..4d99d28
--- /dev/null
+++ b/desktop/modules/sonix/SonixTile.qml
@@ -0,0 +1,36 @@
+// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+import QtQuick
+import "../.."
+
+// "95% · playing". Holds the service while the drawer shows the tile.
+Item {
+ width: parent ? parent.width : implicitWidth
+ implicitHeight: line.implicitHeight
+
+ Component.onCompleted: Sonix.hold()
+ Component.onDestruction: Sonix.release()
+
+ Text {
+ id: line
+ width: parent.width
+ elide: Text.ElideRight
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
+ color: Theme.text
+ text: {
+ const p = Sonix.player;
+ if (p.battery === undefined) return "\u2026";
+ return `${p.battery}%${p.charging ? " charging" : ""} \u00b7 `
+ + (p.state === 1 ? "playing" : p.state === 2 ? "paused" : "idle");
+ }
+ }
+}
diff --git a/desktop/shell.qml b/desktop/shell.qml
index d9e5b6c..7fc293b 100644
--- a/desktop/shell.qml
+++ b/desktop/shell.qml
@@ -38,6 +38,7 @@ import "modules/notifications"
import "modules/obs"
import "modules/perf"
import "modules/sound"
+import "modules/sonix"
import "modules/status"
import "modules/updates"
import "modules/user"
@@ -80,6 +81,7 @@ ShellRoot {
NetworkModule {},
BluetoothModule {},
KdeConnectModule {},
+ SonixModule {},
MailModule {},
StatusModule {},
PerfModule {},