nova-shell/modules/Bluetooth.qml

113 lines
3.7 KiB
QML

import QtQuick
import Quickshell
import Quickshell.Io
import "." as M
M.BarSection {
id: root
spacing: M.Theme.moduleSpacing
opacity: M.Modules.bluetooth.enable && root.state !== "unavailable" ? 1 : 0
visible: opacity > 0
tooltip: {
if (root.state === "off")
return "Bluetooth: off";
if (root.state === "connected")
return "Bluetooth: " + root.device + (root.batteryPct >= 0 ? "\nBattery: " + root.batteryPct + "%" : "");
return "Bluetooth: on";
}
property string state: "unavailable"
property string device: ""
property int batteryPct: -1
function _parse(text) {
const lines = text.trim().split("\n");
const t = lines[0] || "";
const sep = t.indexOf(":");
root.state = sep === -1 ? t : t.slice(0, sep);
root.device = sep === -1 ? "" : t.slice(sep + 1);
root.batteryPct = -1;
for (let i = 1; i < lines.length; i++) {
if (lines[i].startsWith("bat:"))
root.batteryPct = parseInt(lines[i].slice(4)) || -1;
}
}
Process {
id: proc
running: true
command: ["sh", "-c", "s=$(bluetoothctl show 2>/dev/null); " + "[ -z \"$s\" ] && echo unavailable && exit; " + "echo \"$s\" | grep -q 'Powered: yes' || { echo off:; exit; }; " + "info=$(bluetoothctl info 2>/dev/null); " + "d=$(echo \"$info\" | awk -F': ' '/\\tName:/{n=$2}/Connected: yes/{c=1}END{if(c)print n}'); " + "[ -n \"$d\" ] && echo \"connected:$d\" || { echo on:; exit; }; " + "bat=$(echo \"$info\" | awk -F': ' '/Battery Percentage.*\\(/{gsub(/[^0-9]/,\"\",$2);print $2}'); " + "[ -n \"$bat\" ] && echo \"bat:$bat\""]
stdout: StdioCollector {
onStreamFinished: root._parse(text)
}
}
// Event-driven: watch BlueZ DBus property changes
Process {
id: btMonitor
running: true
command: ["sh", "-c", "dbus-monitor --system \"interface='org.freedesktop.DBus.Properties',member='PropertiesChanged',path_namespace='/org/bluez'\" 2>/dev/null"]
stdout: SplitParser {
splitMarker: "\n"
onRead: _debounce.restart()
}
}
Timer {
id: _debounce
interval: 500
onTriggered: proc.running = true
}
Timer {
interval: 60000
running: true
repeat: true
onTriggered: proc.running = true
}
Process {
id: toggle
property string cmd: ""
command: ["bluetoothctl", "power", cmd]
onRunningChanged: if (!running && cmd !== "")
proc.running = true
}
M.BarIcon {
icon: "\uF294"
color: root.state === "off" ? M.Theme.base04 : root.accentColor
anchors.verticalCenter: parent.verticalCenter
}
M.BarLabel {
visible: root.state === "connected"
label: root.device + (root.batteryPct >= 0 ? " " + root.batteryPct + "%" : "")
anchors.verticalCenter: parent.verticalCenter
}
required property var bar
TapHandler {
acceptedButtons: Qt.LeftButton
cursorShape: Qt.PointingHandCursor
onTapped: {
menuLoader.active = !menuLoader.active;
M.FlyoutState.visible = false;
}
}
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: {
toggle.cmd = root.state === "off" ? "on" : "off";
toggle.running = true;
}
}
Loader {
id: menuLoader
active: false
sourceComponent: M.BluetoothMenu {
screen: root.bar.screen
anchorX: root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0)
onDismissed: menuLoader.active = false
}
}
}