commit 5464944a562de737345fa32a6fc7c5f3af3f7f8f Author: Semih Kaiser Date: Tue Sep 29 01:05:45 2026 +0200 Init diff --git a/Controller.qml b/Controller.qml new file mode 100644 index 0000000..33a6132 --- /dev/null +++ b/Controller.qml @@ -0,0 +1,7 @@ +import QtQuick + +QtObject { + property bool connected: false + property string percentage: "-1" + property string chargingState: "unknown" +} \ No newline at end of file diff --git a/DualsenseWidget.qml b/DualsenseWidget.qml new file mode 100644 index 0000000..17c0f7d --- /dev/null +++ b/DualsenseWidget.qml @@ -0,0 +1,237 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import Quickshell +import Quickshell.Bluetooth +import Quickshell.Io + +PanelWindow { + id: root + + visible: controller !== null && controller.connected + aboveWindows: false + + anchors { + right: true + top: true + } + + margins { + top: 16 + right: 8 + } + + implicitWidth: expanded ? 300 : 22 + implicitHeight: 120 + color: "transparent" + + property bool expanded: false + + Controller { + id: controller + connected: false + } + + Process { + id: connectionProcess + + command: ["sh", "-c", "dualsensectl monitor add 'echo connected' remove 'echo disconnected'"] + running: true + + stdout: SplitParser { + onRead: line => { + if (line === "connected") { + controller.connected = true + } else { + controller.connected = false + } + } + } + } + + Timer { + interval: 30000 + running: true + repeat: true + onTriggered: batteryProcess.running = true + } + + Timer { + interval: 1000 + running: true + onTriggered: batteryProcess.running = true + } + + Process { + id: batteryProcess + + command: ["sh", "-c", "dualsensectl battery"] + running: true + stdout: StdioCollector { + onStreamFinished: () => { + const s = text.split(" "); + controller.percentage = s[0]; + controller.chargingState = s[1]; + if (s[1].indexOf("charging") === 0) { + controller.chargingState = "Charging" + } + if (s[1].indexOf("discharging") === 0) { + controller.chargingState = "Discharging" + } + if (s[1].indexOf("full") === 0) { + controller.chargingState = "Full" + } + } + } + } + + Rectangle { + id: surface + + anchors { + right: parent.right + top: parent.top + bottom: parent.bottom + } + + width: root.expanded ? parent.width : 22 + radius: 10 + color: '#1e1e2e' + border.color: "#555c637a" + border.width: 1 + clip: true + + MouseArea { + anchors.fill: parent + hoverEnabled: true + + onClicked: (evt) => { + root.expanded = !root.expanded + } + } + + HoverHandler { + id: hoverHandler + + onHoveredChanged: { + if (!hovered) + root.expanded = false + } + } + + Rectangle { + id: edgeHandle + + width: 5 + height: 70 + radius: 3 + + anchors { + right: parent.right + verticalCenter: parent.verticalCenter + rightMargin: 8 + } + + color: '#828282' + + Rectangle { + id: powerLevel + + anchors { + bottom: parent.bottom + } + + width: parent.width + height: controller.percentage * 0.01 * parent.height + radius: 3 + + color: controller.chargingState === "Charging" + ? "#f5c542" + : controller.chargingState === "Full" ? '#68b864' : "#8ab4f8" + } + } + + RowLayout { + + visible: root.expanded + + anchors { + left: parent.left + right: edgeHandle.left + verticalCenter: parent.verticalCenter + leftMargin: 16 + rightMargin: 8 + } + + spacing: 10 + + Image { + id: image + source: "icons/dualsense.png" + Layout.preferredWidth: 100 + Layout.preferredHeight: parent.height + fillMode: Image.PreserveAspectFit + } + + ColumnLayout { + Label { + Layout.fillWidth: true + + text: "Dualsense" + + color: "white" + font.bold: true + font.pixelSize: 16 + elide: Text.ElideRight + } + + Label { + Layout.fillWidth: true + + text: `Battery: ${Math.round(controller.percentage)} %` + + color: "#d8dee9" + font.pixelSize: 14 + } + + Label { + Layout.fillWidth: true + + text: `Status: ${controller.chargingState}` + + color: root.chargingState === "charging" + ? "#f5c542" + : "#d8dee9" + + font.pixelSize: 14 + } + } + + Button { + contentItem: Image { + source: "icons/power.svg" + height: 48 + fillMode: Image.PreserveAspectFit + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + background: Rectangle { + color: 'transparent' + height: 48 + border.width: 0 + } + + onClicked: { + disconnectProcess.running = true + } + } + + Process { + id: disconnectProcess + command: ["sh", "-c", "dualsensectl power-off"] + } + } + } +} \ No newline at end of file diff --git a/icons/dualsense.png b/icons/dualsense.png new file mode 100644 index 0000000..b142cf3 Binary files /dev/null and b/icons/dualsense.png differ diff --git a/icons/power.svg b/icons/power.svg new file mode 100644 index 0000000..b568aad --- /dev/null +++ b/icons/power.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/shell.qml b/shell.qml new file mode 100644 index 0000000..17084d0 --- /dev/null +++ b/shell.qml @@ -0,0 +1,3 @@ +import Quickshell + +DualsenseWidget {} \ No newline at end of file