Merge pull request #179 from lesi-nedo/main

Fix to issue #165
This commit is contained in:
Lemmy 2025-08-31 15:19:38 -04:00 committed by GitHub
commit 0736862a2c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 123 additions and 37 deletions

View file

@ -12,6 +12,7 @@ ColumnLayout {
id: root id: root
property string label: "" property string label: ""
property string tooltipText: ""
property var model: { property var model: {
} }
@ -29,12 +30,15 @@ ColumnLayout {
} }
Repeater { Repeater {
id: deviceList
Layout.fillWidth: true Layout.fillWidth: true
model: root.model model: root.model
visible: BluetoothService.adapter && BluetoothService.adapter.enabled visible: BluetoothService.adapter && BluetoothService.adapter.enabled
Rectangle { Rectangle {
id: bluetoothDeviceRectangle
property bool canConnect: BluetoothService.canConnect(modelData) property bool canConnect: BluetoothService.canConnect(modelData)
property bool canDisconnect: BluetoothService.canDisconnect(modelData)
property bool isBusy: BluetoothService.isDeviceBusy(modelData) property bool isBusy: BluetoothService.isDeviceBusy(modelData)
Layout.fillWidth: true Layout.fillWidth: true
@ -42,13 +46,19 @@ ColumnLayout {
radius: Style.radiusM * scaling radius: Style.radiusM * scaling
color: { color: {
if (availableDeviceArea.containsMouse && !isBusy) if (availableDeviceArea.containsMouse){
return Color.mTertiary if (canDisconnect && !isBusy)
return Color.mError
if(!isBusy)
return Color.mTertiary
return Color.mPrimary
}
if (modelData.pairing || modelData.state === BluetoothDeviceState.Connecting) if (modelData.pairing || modelData.state === BluetoothDeviceState.Connecting)
return Color.mPrimary return Color.mPrimary
if (modelData.blocked) if (modelData.blocked )
return Color.mError return Color.mError
return Color.mSurfaceVariant return Color.mSurfaceVariant
@ -56,6 +66,13 @@ ColumnLayout {
border.color: Color.mOutline border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling) border.width: Math.max(1, Style.borderS * scaling)
NTooltip {
id: tooltip
target: bluetoothDeviceRectangle
positionAbove: Settings.data.bar.position === "bottom"
text: root.tooltipText
}
RowLayout { RowLayout {
anchors.fill: parent anchors.fill: parent
anchors.margins: Style.marginM * scaling anchors.margins: Style.marginM * scaling
@ -193,6 +210,7 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
} }
// Call to action // Call to action
Rectangle { Rectangle {
Layout.preferredWidth: 80 * scaling Layout.preferredWidth: 80 * scaling
@ -204,12 +222,14 @@ ColumnLayout {
border.color: { border.color: {
if (availableDeviceArea.containsMouse) { if (availableDeviceArea.containsMouse) {
return Color.mOnTertiary return Color.mOnTertiary
} else {
return Color.mPrimary
} }
if (bluetoothDeviceRectangle.canDisconnect && !isBusy) {
return Color.mError
}
return Color.mPrimary
} }
border.width: Math.max(1, Style.borderS * scaling) border.width: Math.max(1, Style.borderS * scaling)
opacity: canConnect || isBusy ? 1 : 0.5 opacity: canConnect || isBusy || canDisconnect ? 1 : 0.5
NText { NText {
anchors.centerIn: parent anchors.centerIn: parent
@ -220,7 +240,7 @@ ColumnLayout {
if (modelData.blocked) { if (modelData.blocked) {
return "Blocked" return "Blocked"
} }
if (modelData.paired || modelData.trusted) { if(modelData.connected){
return "Disconnect" return "Disconnect"
} }
return "Connect" return "Connect"
@ -228,8 +248,13 @@ ColumnLayout {
font.pointSize: Style.fontSizeXS * scaling font.pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightMedium font.weight: Style.fontWeightMedium
color: { color: {
if (availableDeviceArea.containsMouse) { if (availableDeviceArea.containsMouse) {
return Color.mOnTertiary return Color.mOnTertiary
}
if (bluetoothDeviceRectangle.canDisconnect && !isBusy) {
return Color.mError
} else { } else {
return Color.mPrimary return Color.mPrimary
} }
@ -239,21 +264,41 @@ ColumnLayout {
} }
MouseArea { MouseArea {
id: availableDeviceArea id: availableDeviceArea
acceptedButtons: Qt.LeftButton | Qt.RightButton
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: canConnect && !isBusy ? Qt.PointingHandCursor : (isBusy ? Qt.BusyCursor : Qt.ArrowCursor) cursorShape: (canConnect || canDisconnect) && !isBusy ? Qt.PointingHandCursor : (isBusy ? Qt.BusyCursor : Qt.ArrowCursor)
enabled: canConnect && !isBusy onEntered: {
onClicked: { if (root.tooltipText && !isBusy) {
tooltip.show()
}
}
onExited: {
if(root.tooltipText && !isBusy) {
tooltip.hide()
}
}
onClicked: function(mouse) {
if (!modelData || modelData.pairing) { if (!modelData || modelData.pairing) {
return return
} }
if (modelData.paired || modelData.trusted) { if (root.tooltipText && !isBusy) {
BluetoothService.disconnectDevice(modelData) tooltip.hide()
} else { }
BluetoothService.connectDeviceWithTrust(modelData)
if (mouse.button === Qt.LeftButton){
if (modelData.connected) {
BluetoothService.disconnectDevice(modelData)
} else {
BluetoothService.connectDeviceWithTrust(modelData)
}
} else if (mouse.button === Qt.RightButton) {
BluetoothService.forgetDevice(modelData)
} }
} }
} }

View file

@ -83,30 +83,40 @@ NPanel {
// Connected devices // Connected devices
BluetoothDevicesList { BluetoothDevicesList {
label: "Connected devices" label: "Connected devices"
model: { property var items: {
if (!BluetoothService.adapter || !Bluetooth.devices) if (!BluetoothService.adapter || !Bluetooth.devices) return []
return [] var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && dev.connected)
var filtered = Bluetooth.devices.values.filter(dev => {
return dev && !dev.blocked && (dev.paired || dev.trusted)
})
return BluetoothService.sortDevices(filtered) 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 Layout.fillWidth: true
} }
// Available devices // Available devices
BluetoothDevicesList { BluetoothDevicesList {
label: "Available devices" label: "Available devices"
model: { property var items: {
if (!BluetoothService.adapter || !Bluetooth.devices) if (!BluetoothService.adapter || !Bluetooth.devices) return []
return [] var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.paired && !dev.trusted)
var filtered = Bluetooth.devices.values.filter(dev => {
return dev && !dev.blocked && !dev.paired && !dev.trusted
})
return BluetoothService.sortDevices(filtered) return BluetoothService.sortDevices(filtered)
} }
model: items
visible: items.length > 0
Layout.fillWidth: true Layout.fillWidth: true
} }

View file

@ -3,6 +3,7 @@ pragma Singleton
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Bluetooth import Quickshell.Bluetooth
import qs.Commons
Singleton { Singleton {
id: root id: root
@ -85,8 +86,23 @@ Singleton {
function canConnect(device) { function canConnect(device) {
if (!device) if (!device)
return false return false
/*
Paired
return !device.paired && !device.pairing && !device.blocked Means youve 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 dont 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) { function getSignalStrength(device) {
@ -162,7 +178,6 @@ Singleton {
return return
} }
device.trusted = false
device.disconnect() device.disconnect()
} }

View file

@ -11,7 +11,7 @@ Singleton {
property list<var> ddcMonitors: [] property list<var> ddcMonitors: []
readonly property list<Monitor> monitors: variants.instances readonly property list<Monitor> monitors: variants.instances
property bool appleDisplayPresent: false property bool appleDisplayPresent: false
function getMonitorForScreen(screen: ShellScreen): var { function getMonitorForScreen(screen: ShellScreen): var {
return monitors.find(m => m.modelData === screen) return monitors.find(m => m.modelData === screen)
} }
@ -69,19 +69,35 @@ Singleton {
// Detect DDC monitors // Detect DDC monitors
Process { Process {
id: ddcProc id: ddcProc
command: ["ddcutil", "detect", "--brief"] property list<var> ddcMonitors: []
command: ["ddcutil", "detect", "--sleep-multiplier=0.5"]
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
// Do not filter out invalid displays. For some reason --brief returns some invalid which works fine // Do not filter out invalid displays. For some reason --brief returns some invalid which works fine
var displays = text.trim().split("\n\n") 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 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 { return {
"model": modelMatch ? modelMatch[1] : "", "model": model,
"busNum": busMatch ? busMatch[1] : "" "busNum": bus,
"isDdc": !ddcModel,
} }
}) })
root.ddcMonitors = ddcProc.ddcMonitors.filter(m => m.isDdc)
} }
} }
} }