Compare commits

...

3 commits

6 changed files with 164 additions and 4 deletions

View file

@ -103,7 +103,7 @@ PanelWindow {
M.BarGroup { M.BarGroup {
borderColor: M.Theme.base0D borderColor: M.Theme.base0D
M.Network { bar: bar; visible: M.Modules.network.enable } M.Network { bar: bar; visible: M.Modules.network.enable }
M.Bluetooth {} M.Bluetooth { bar: bar }
} }
// Controls // Controls

View file

@ -1,4 +1,5 @@
import QtQuick import QtQuick
import Quickshell
import Quickshell.Io import Quickshell.Io
import "." as M import "." as M
@ -66,12 +67,29 @@ M.BarSection {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
required property var bar
TapHandler { TapHandler {
acceptedButtons: Qt.LeftButton
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onTapped: menuLoader.active = !menuLoader.active
}
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: { onTapped: {
toggle.cmd = root.state === "off" ? "on" : "off"; toggle.cmd = root.state === "off" ? "on" : "off";
toggle.running = true; 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
}
}
} }

139
modules/BluetoothMenu.qml Normal file
View file

@ -0,0 +1,139 @@
import QtQuick
import Quickshell
import Quickshell.Io
import "." as M
M.PopupPanel {
id: menuWindow
panelWidth: 250
property var _devices: []
property Process _scanner: Process {
id: scanner
running: true
command: ["sh", "-c",
"bluetoothctl devices Paired 2>/dev/null | while read -r _ mac name; do " +
"info=$(bluetoothctl info \"$mac\" 2>/dev/null); " +
"conn=$(echo \"$info\" | grep -c 'Connected: yes'); " +
"bat=$(echo \"$info\" | awk -F'[(): ]' '/Battery Percentage/{for(i=1;i<=NF;i++) if($i+0==$i && $i!=\"\") print $i}'); " +
"echo \"$mac:$conn:${bat:-}:$name\"; " +
"done"
]
stdout: StdioCollector {
onStreamFinished: {
const devs = [];
for (const line of text.trim().split("\n")) {
if (!line) continue;
const i1 = line.indexOf(":");
const i2 = line.indexOf(":", i1 + 1);
const i3 = line.indexOf(":", i2 + 1);
if (i3 < 0) continue;
devs.push({
mac: line.slice(0, i1),
connected: line.slice(i1 + 1, i2) === "1",
battery: parseInt(line.slice(i2 + 1, i3)) || -1,
name: line.slice(i3 + 1)
});
}
devs.sort((a, b) => {
if (a.connected !== b.connected) return a.connected ? -1 : 1;
return a.name.localeCompare(b.name);
});
menuWindow._devices = devs;
}
}
}
property Process _toggleProc: Process {
id: toggleProc
property string action: ""
property string mac: ""
command: ["bluetoothctl", action, mac]
onRunningChanged: if (!running) scanner.running = true
}
Repeater {
model: menuWindow._devices
delegate: Item {
id: entry
required property var modelData
required property int index
width: menuWindow.panelWidth
height: 32
Rectangle {
anchors.fill: parent
anchors.leftMargin: 4
anchors.rightMargin: 4
color: entryArea.containsMouse ? M.Theme.base02 : "transparent"
radius: M.Theme.radius
}
Text {
id: btIcon
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: "\uF294"
color: entry.modelData.connected ? M.Theme.base0D : M.Theme.base04
font.pixelSize: M.Theme.fontSize + 1
font.family: M.Theme.iconFontFamily
}
Text {
anchors.left: btIcon.right
anchors.leftMargin: 8
anchors.right: batLabel.left
anchors.rightMargin: 4
anchors.verticalCenter: parent.verticalCenter
text: entry.modelData.name
color: entry.modelData.connected ? M.Theme.base0D : M.Theme.base05
font.pixelSize: M.Theme.fontSize
font.family: M.Theme.fontFamily
font.bold: entry.modelData.connected
elide: Text.ElideRight
}
Text {
id: batLabel
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: entry.modelData.battery >= 0 ? entry.modelData.battery + "%" : ""
color: M.Theme.base04
font.pixelSize: M.Theme.fontSize - 1
font.family: M.Theme.fontFamily
width: entry.modelData.battery >= 0 ? implicitWidth : 0
}
MouseArea {
id: entryArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
toggleProc.action = entry.modelData.connected ? "disconnect" : "connect";
toggleProc.mac = entry.modelData.mac;
toggleProc.running = true;
menuWindow.dismiss();
}
}
}
}
Text {
visible: menuWindow._devices.length === 0
width: menuWindow.panelWidth
height: 32
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: "No paired devices"
color: M.Theme.base04
font.pixelSize: M.Theme.fontSize
font.family: M.Theme.fontFamily
}
}

View file

@ -31,6 +31,8 @@ M.PopupPanel {
source: menuWindow.player?.trackArtUrl ?? "" source: menuWindow.player?.trackArtUrl ?? ""
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
visible: status === Image.Ready visible: status === Image.Ready
asynchronous: true
cache: false
} }
Rectangle { Rectangle {

View file

@ -10,7 +10,7 @@ M.PopupPanel {
property var _networks: [] property var _networks: []
Process { property Process _scanner: Process {
id: scanner id: scanner
running: true running: true
command: ["sh", "-c", command: ["sh", "-c",
@ -68,14 +68,14 @@ M.PopupPanel {
} }
} }
Process { property Process _connectProc: Process {
id: connectProc id: connectProc
property string uuid: "" property string uuid: ""
command: ["nmcli", "connection", "up", uuid] command: ["nmcli", "connection", "up", uuid]
onRunningChanged: if (!running) scanner.running = true onRunningChanged: if (!running) scanner.running = true
} }
Process { property Process _disconnectProc: Process {
id: disconnectProc id: disconnectProc
property string uuid: "" property string uuid: ""
command: ["nmcli", "connection", "down", uuid] command: ["nmcli", "connection", "down", uuid]

View file

@ -24,6 +24,7 @@ MprisMenu 1.0 MprisMenu.qml
Network 1.0 Network.qml Network 1.0 Network.qml
NetworkMenu 1.0 NetworkMenu.qml NetworkMenu 1.0 NetworkMenu.qml
Bluetooth 1.0 Bluetooth.qml Bluetooth 1.0 Bluetooth.qml
BluetoothMenu 1.0 BluetoothMenu.qml
Backlight 1.0 Backlight.qml Backlight 1.0 Backlight.qml
Cpu 1.0 Cpu.qml Cpu 1.0 Cpu.qml
Memory 1.0 Memory.qml Memory 1.0 Memory.qml