commit
0736862a2c
4 changed files with 123 additions and 37 deletions
|
|
@ -12,6 +12,7 @@ ColumnLayout {
|
|||
id: root
|
||||
|
||||
property string label: ""
|
||||
property string tooltipText: ""
|
||||
property var model: {
|
||||
|
||||
}
|
||||
|
|
@ -29,12 +30,15 @@ ColumnLayout {
|
|||
}
|
||||
|
||||
Repeater {
|
||||
id: deviceList
|
||||
Layout.fillWidth: true
|
||||
model: root.model
|
||||
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
||||
|
||||
Rectangle {
|
||||
id: bluetoothDeviceRectangle
|
||||
property bool canConnect: BluetoothService.canConnect(modelData)
|
||||
property bool canDisconnect: BluetoothService.canDisconnect(modelData)
|
||||
property bool isBusy: BluetoothService.isDeviceBusy(modelData)
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
|
@ -42,13 +46,19 @@ ColumnLayout {
|
|||
radius: Style.radiusM * scaling
|
||||
|
||||
color: {
|
||||
if (availableDeviceArea.containsMouse && !isBusy)
|
||||
if (availableDeviceArea.containsMouse){
|
||||
if (canDisconnect && !isBusy)
|
||||
return Color.mError
|
||||
|
||||
if(!isBusy)
|
||||
return Color.mTertiary
|
||||
return Color.mPrimary
|
||||
}
|
||||
|
||||
if (modelData.pairing || modelData.state === BluetoothDeviceState.Connecting)
|
||||
return Color.mPrimary
|
||||
|
||||
if (modelData.blocked)
|
||||
if (modelData.blocked )
|
||||
return Color.mError
|
||||
|
||||
return Color.mSurfaceVariant
|
||||
|
|
@ -56,6 +66,13 @@ ColumnLayout {
|
|||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
|
||||
NTooltip {
|
||||
id: tooltip
|
||||
target: bluetoothDeviceRectangle
|
||||
positionAbove: Settings.data.bar.position === "bottom"
|
||||
text: root.tooltipText
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM * scaling
|
||||
|
|
@ -193,6 +210,7 @@ ColumnLayout {
|
|||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
|
||||
// Call to action
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 80 * scaling
|
||||
|
|
@ -204,12 +222,14 @@ ColumnLayout {
|
|||
border.color: {
|
||||
if (availableDeviceArea.containsMouse) {
|
||||
return Color.mOnTertiary
|
||||
} else {
|
||||
}
|
||||
if (bluetoothDeviceRectangle.canDisconnect && !isBusy) {
|
||||
return Color.mError
|
||||
}
|
||||
return Color.mPrimary
|
||||
}
|
||||
}
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
opacity: canConnect || isBusy ? 1 : 0.5
|
||||
opacity: canConnect || isBusy || canDisconnect ? 1 : 0.5
|
||||
|
||||
NText {
|
||||
anchors.centerIn: parent
|
||||
|
|
@ -220,7 +240,7 @@ ColumnLayout {
|
|||
if (modelData.blocked) {
|
||||
return "Blocked"
|
||||
}
|
||||
if (modelData.paired || modelData.trusted) {
|
||||
if(modelData.connected){
|
||||
return "Disconnect"
|
||||
}
|
||||
return "Connect"
|
||||
|
|
@ -228,8 +248,13 @@ ColumnLayout {
|
|||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
color: {
|
||||
|
||||
if (availableDeviceArea.containsMouse) {
|
||||
return Color.mOnTertiary
|
||||
}
|
||||
|
||||
if (bluetoothDeviceRectangle.canDisconnect && !isBusy) {
|
||||
return Color.mError
|
||||
} else {
|
||||
return Color.mPrimary
|
||||
}
|
||||
|
|
@ -239,22 +264,42 @@ ColumnLayout {
|
|||
}
|
||||
|
||||
MouseArea {
|
||||
id: availableDeviceArea
|
||||
|
||||
id: availableDeviceArea
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: canConnect && !isBusy ? Qt.PointingHandCursor : (isBusy ? Qt.BusyCursor : Qt.ArrowCursor)
|
||||
enabled: canConnect && !isBusy
|
||||
onClicked: {
|
||||
cursorShape: (canConnect || canDisconnect) && !isBusy ? Qt.PointingHandCursor : (isBusy ? Qt.BusyCursor : Qt.ArrowCursor)
|
||||
onEntered: {
|
||||
if (root.tooltipText && !isBusy) {
|
||||
tooltip.show()
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
if(root.tooltipText && !isBusy) {
|
||||
tooltip.hide()
|
||||
}
|
||||
}
|
||||
onClicked: function(mouse) {
|
||||
|
||||
if (!modelData || modelData.pairing) {
|
||||
return
|
||||
}
|
||||
|
||||
if (modelData.paired || modelData.trusted) {
|
||||
if (root.tooltipText && !isBusy) {
|
||||
tooltip.hide()
|
||||
}
|
||||
|
||||
|
||||
if (mouse.button === Qt.LeftButton){
|
||||
if (modelData.connected) {
|
||||
BluetoothService.disconnectDevice(modelData)
|
||||
} else {
|
||||
BluetoothService.connectDeviceWithTrust(modelData)
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
BluetoothService.forgetDevice(modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,30 +83,40 @@ NPanel {
|
|||
// Connected devices
|
||||
BluetoothDevicesList {
|
||||
label: "Connected devices"
|
||||
model: {
|
||||
if (!BluetoothService.adapter || !Bluetooth.devices)
|
||||
return []
|
||||
|
||||
var filtered = Bluetooth.devices.values.filter(dev => {
|
||||
return dev && !dev.blocked && (dev.paired || dev.trusted)
|
||||
})
|
||||
property var items: {
|
||||
if (!BluetoothService.adapter || !Bluetooth.devices) return []
|
||||
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && dev.connected)
|
||||
return BluetoothService.sortDevices(filtered)
|
||||
}
|
||||
model: items
|
||||
visible: items.length > 0
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Known devices
|
||||
BluetoothDevicesList {
|
||||
label: "Known devices"
|
||||
tooltipText: "Left click to connect, right click to forget"
|
||||
property var items: {
|
||||
if (!BluetoothService.adapter || !Bluetooth.devices) return []
|
||||
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.connected && (dev.paired || dev.trusted))
|
||||
return BluetoothService.sortDevices(filtered)
|
||||
}
|
||||
model: items
|
||||
visible: items.length > 0
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Available devices
|
||||
BluetoothDevicesList {
|
||||
label: "Available devices"
|
||||
model: {
|
||||
if (!BluetoothService.adapter || !Bluetooth.devices)
|
||||
return []
|
||||
|
||||
var filtered = Bluetooth.devices.values.filter(dev => {
|
||||
return dev && !dev.blocked && !dev.paired && !dev.trusted
|
||||
})
|
||||
property var items: {
|
||||
if (!BluetoothService.adapter || !Bluetooth.devices) return []
|
||||
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.paired && !dev.trusted)
|
||||
return BluetoothService.sortDevices(filtered)
|
||||
}
|
||||
model: items
|
||||
visible: items.length > 0
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ pragma Singleton
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
import qs.Commons
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
|
@ -85,8 +86,23 @@ Singleton {
|
|||
function canConnect(device) {
|
||||
if (!device)
|
||||
return false
|
||||
/*
|
||||
Paired
|
||||
|
||||
return !device.paired && !device.pairing && !device.blocked
|
||||
Means you’ve successfully exchanged keys with the device.
|
||||
|
||||
The devices remember each other and can authenticate without repeating the pairing process.
|
||||
|
||||
Example: once your headphones are paired, you don’t need to type a PIN every time.
|
||||
Hence, instead of !device.paired, should be device.connected
|
||||
*/
|
||||
return !device.connected && !device.pairing && !device.blocked
|
||||
}
|
||||
|
||||
function canDisconnect(device) {
|
||||
if (!device)
|
||||
return false
|
||||
return device.connected && !device.pairing && !device.blocked
|
||||
}
|
||||
|
||||
function getSignalStrength(device) {
|
||||
|
|
@ -162,7 +178,6 @@ Singleton {
|
|||
return
|
||||
}
|
||||
|
||||
device.trusted = false
|
||||
device.disconnect()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,19 +69,35 @@ Singleton {
|
|||
// Detect DDC monitors
|
||||
Process {
|
||||
id: ddcProc
|
||||
command: ["ddcutil", "detect", "--brief"]
|
||||
property list<var> ddcMonitors: []
|
||||
command: ["ddcutil", "detect", "--sleep-multiplier=0.5"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
// Do not filter out invalid displays. For some reason --brief returns some invalid which works fine
|
||||
var displays = text.trim().split("\n\n")
|
||||
root.ddcMonitors = displays.map(d => {
|
||||
var modelMatch = d.match(/Monitor:.*:(.*):.*/)
|
||||
|
||||
|
||||
ddcProc.ddcMonitors = displays.map(d => {
|
||||
|
||||
var ddcModelMatc = d.match(/This monitor does not support DDC\/CI/)
|
||||
var modelMatch = d.match(/Model:\s*(.*)/)
|
||||
var busMatch = d.match(/I2C bus:[ ]*\/dev\/i2c-([0-9]+)/)
|
||||
var ddcModel = ddcModelMatc ? ddcModelMatc.length > 0 : false
|
||||
var model = modelMatch ? modelMatch[1] : "Unknown"
|
||||
var bus = busMatch ? busMatch[1] : "Unknown"
|
||||
Logger.log(
|
||||
"Detected DDC Monitor:", model,
|
||||
"on bus", bus, "is DDC:", !ddcModel
|
||||
)
|
||||
return {
|
||||
"model": modelMatch ? modelMatch[1] : "",
|
||||
"busNum": busMatch ? busMatch[1] : ""
|
||||
"model": model,
|
||||
"busNum": bus,
|
||||
"isDdc": !ddcModel,
|
||||
}
|
||||
})
|
||||
root.ddcMonitors = ddcProc.ddcMonitors.filter(m => m.isDdc)
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue