mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-06 05:25:41 -05:00
bluetooth: integrate with DMS API v9 - Supports proper pairing with an agent & pin, passcode, etc.
This commit is contained in:
@@ -1050,6 +1050,7 @@ Singleton {
|
|||||||
function setCornerRadius(radius) {
|
function setCornerRadius(radius) {
|
||||||
cornerRadius = radius
|
cornerRadius = radius
|
||||||
saveSettings()
|
saveSettings()
|
||||||
|
NiriService.generateNiriLayoutConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
function setClockFormat(use24Hour) {
|
function setClockFormat(use24Hour) {
|
||||||
|
|||||||
360
Modals/BluetoothPairingModal.qml
Normal file
360
Modals/BluetoothPairingModal.qml
Normal file
@@ -0,0 +1,360 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Modals.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
DankModal {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string deviceName: ""
|
||||||
|
property string deviceAddress: ""
|
||||||
|
property string requestType: ""
|
||||||
|
property string token: ""
|
||||||
|
property int passkey: 0
|
||||||
|
property string pinInput: ""
|
||||||
|
property string passkeyInput: ""
|
||||||
|
|
||||||
|
function show(pairingData) {
|
||||||
|
token = pairingData.token || ""
|
||||||
|
deviceName = pairingData.deviceName || ""
|
||||||
|
deviceAddress = pairingData.deviceAddr || ""
|
||||||
|
requestType = pairingData.requestType || ""
|
||||||
|
passkey = pairingData.passkey || 0
|
||||||
|
pinInput = ""
|
||||||
|
passkeyInput = ""
|
||||||
|
|
||||||
|
open()
|
||||||
|
Qt.callLater(() => {
|
||||||
|
if (contentLoader.item) {
|
||||||
|
if (requestType === "pin" && contentLoader.item.pinInputField) {
|
||||||
|
contentLoader.item.pinInputField.forceActiveFocus()
|
||||||
|
} else if (requestType === "passkey" && contentLoader.item.passkeyInputField) {
|
||||||
|
contentLoader.item.passkeyInputField.forceActiveFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldBeVisible: false
|
||||||
|
width: 420
|
||||||
|
height: {
|
||||||
|
if (requestType === "confirm" || requestType === "authorize" || requestType.startsWith("authorize-service"))
|
||||||
|
return 200
|
||||||
|
return 230
|
||||||
|
}
|
||||||
|
|
||||||
|
onShouldBeVisibleChanged: () => {
|
||||||
|
if (!shouldBeVisible) {
|
||||||
|
pinInput = ""
|
||||||
|
passkeyInput = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpened: {
|
||||||
|
Qt.callLater(() => {
|
||||||
|
if (contentLoader.item) {
|
||||||
|
if (requestType === "pin" && contentLoader.item.pinInputField) {
|
||||||
|
contentLoader.item.pinInputField.forceActiveFocus()
|
||||||
|
} else if (requestType === "passkey" && contentLoader.item.passkeyInputField) {
|
||||||
|
contentLoader.item.passkeyInputField.forceActiveFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onBackgroundClicked: () => {
|
||||||
|
DMSService.bluetoothCancelPairing(token)
|
||||||
|
close()
|
||||||
|
pinInput = ""
|
||||||
|
passkeyInput = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
content: Component {
|
||||||
|
FocusScope {
|
||||||
|
id: pairingContent
|
||||||
|
|
||||||
|
property alias pinInputField: pinInputField
|
||||||
|
property alias passkeyInputField: passkeyInputField
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
focus: true
|
||||||
|
|
||||||
|
Keys.onEscapePressed: event => {
|
||||||
|
DMSService.bluetoothCancelPairing(token)
|
||||||
|
close()
|
||||||
|
pinInput = ""
|
||||||
|
passkeyInput = ""
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - 40
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Pair Bluetooth Device")
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: {
|
||||||
|
if (requestType === "confirm")
|
||||||
|
return I18n.tr("Confirm passkey for ") + deviceName
|
||||||
|
if (requestType === "authorize")
|
||||||
|
return I18n.tr("Authorize pairing with ") + deviceName
|
||||||
|
if (requestType.startsWith("authorize-service"))
|
||||||
|
return I18n.tr("Authorize service for ") + deviceName
|
||||||
|
if (requestType === "pin")
|
||||||
|
return I18n.tr("Enter PIN for ") + deviceName
|
||||||
|
if (requestType === "passkey")
|
||||||
|
return I18n.tr("Enter passkey for ") + deviceName
|
||||||
|
return deviceName
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceTextMedium
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
iconName: "close"
|
||||||
|
iconSize: Theme.iconSize - 4
|
||||||
|
iconColor: Theme.surfaceText
|
||||||
|
onClicked: () => {
|
||||||
|
DMSService.bluetoothCancelPairing(token)
|
||||||
|
close()
|
||||||
|
pinInput = ""
|
||||||
|
passkeyInput = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 50
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceHover
|
||||||
|
border.color: pinInputField.activeFocus ? Theme.primary : Theme.outlineStrong
|
||||||
|
border.width: pinInputField.activeFocus ? 2 : 1
|
||||||
|
visible: requestType === "pin"
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: () => {
|
||||||
|
pinInputField.forceActiveFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: pinInputField
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
textColor: Theme.surfaceText
|
||||||
|
text: pinInput
|
||||||
|
placeholderText: I18n.tr("Enter PIN")
|
||||||
|
backgroundColor: "transparent"
|
||||||
|
enabled: root.shouldBeVisible
|
||||||
|
onTextEdited: () => {
|
||||||
|
pinInput = text
|
||||||
|
}
|
||||||
|
onAccepted: () => {
|
||||||
|
submitPairing()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 50
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceHover
|
||||||
|
border.color: passkeyInputField.activeFocus ? Theme.primary : Theme.outlineStrong
|
||||||
|
border.width: passkeyInputField.activeFocus ? 2 : 1
|
||||||
|
visible: requestType === "passkey"
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: () => {
|
||||||
|
passkeyInputField.forceActiveFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: passkeyInputField
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
textColor: Theme.surfaceText
|
||||||
|
text: passkeyInput
|
||||||
|
placeholderText: I18n.tr("Enter 6-digit passkey")
|
||||||
|
backgroundColor: "transparent"
|
||||||
|
enabled: root.shouldBeVisible
|
||||||
|
onTextEdited: () => {
|
||||||
|
passkeyInput = text
|
||||||
|
}
|
||||||
|
onAccepted: () => {
|
||||||
|
submitPairing()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 60
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceContainerHighest
|
||||||
|
visible: requestType === "confirm"
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Passkey:")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceTextMedium
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: String(passkey).padStart(6, "0")
|
||||||
|
font.pixelSize: Theme.fontSizeXLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Bold
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: 40
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: Math.max(70, cancelText.contentWidth + Theme.spacingM * 2)
|
||||||
|
height: 36
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: cancelArea.containsMouse ? Theme.surfaceTextHover : "transparent"
|
||||||
|
border.color: Theme.surfaceVariantAlpha
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: cancelText
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: I18n.tr("Cancel")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: cancelArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: () => {
|
||||||
|
DMSService.bluetoothCancelPairing(token)
|
||||||
|
close()
|
||||||
|
pinInput = ""
|
||||||
|
passkeyInput = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: Math.max(80, pairText.contentWidth + Theme.spacingM * 2)
|
||||||
|
height: 36
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: pairArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
|
||||||
|
enabled: {
|
||||||
|
if (requestType === "pin")
|
||||||
|
return pinInput.length > 0
|
||||||
|
if (requestType === "passkey")
|
||||||
|
return passkeyInput.length === 6
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
opacity: enabled ? 1 : 0.5
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: pairText
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: {
|
||||||
|
if (requestType === "confirm")
|
||||||
|
return I18n.tr("Confirm")
|
||||||
|
if (requestType === "authorize" || requestType.startsWith("authorize-service"))
|
||||||
|
return I18n.tr("Authorize")
|
||||||
|
return I18n.tr("Pair")
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.background
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: pairArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
enabled: parent.enabled
|
||||||
|
onClicked: () => {
|
||||||
|
submitPairing()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitPairing() {
|
||||||
|
const secrets = {}
|
||||||
|
|
||||||
|
if (requestType === "pin") {
|
||||||
|
secrets["pin"] = pinInput
|
||||||
|
} else if (requestType === "passkey") {
|
||||||
|
secrets["passkey"] = passkeyInput
|
||||||
|
} else if (requestType === "confirm" || requestType === "authorize" || requestType.startsWith("authorize-service")) {
|
||||||
|
secrets["decision"] = "yes"
|
||||||
|
}
|
||||||
|
|
||||||
|
DMSService.bluetoothSubmitPairing(token, secrets, true, response => {
|
||||||
|
if (response.error) {
|
||||||
|
ToastService.showError(I18n.tr("Pairing failed"), response.error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
close()
|
||||||
|
pinInput = ""
|
||||||
|
passkeyInput = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import Quickshell.Bluetooth
|
|||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
import qs.Modals
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
implicitHeight: BluetoothService.adapter && BluetoothService.adapter.enabled ? headerRow.height + bluetoothContent.height + Theme.spacingM : headerRow.height
|
implicitHeight: BluetoothService.adapter && BluetoothService.adapter.enabled ? headerRow.height + bluetoothContent.height + Theme.spacingM : headerRow.height
|
||||||
@@ -14,9 +15,14 @@ Rectangle {
|
|||||||
border.width: 0
|
border.width: 0
|
||||||
|
|
||||||
property var bluetoothCodecModalRef: null
|
property var bluetoothCodecModalRef: null
|
||||||
|
property var devicesBeingPaired: new Set()
|
||||||
|
|
||||||
signal showCodecSelector(var device)
|
signal showCodecSelector(var device)
|
||||||
|
|
||||||
|
function isDeviceBeingPaired(deviceAddress) {
|
||||||
|
return devicesBeingPaired.has(deviceAddress)
|
||||||
|
}
|
||||||
|
|
||||||
function updateDeviceCodecDisplay(deviceAddress, codecName) {
|
function updateDeviceCodecDisplay(deviceAddress, codecName) {
|
||||||
for (let i = 0; i < pairedRepeater.count; i++) {
|
for (let i = 0; i < pairedRepeater.count; i++) {
|
||||||
let item = pairedRepeater.itemAt(i)
|
let item = pairedRepeater.itemAt(i)
|
||||||
@@ -327,7 +333,7 @@ Rectangle {
|
|||||||
required property int index
|
required property int index
|
||||||
|
|
||||||
property bool canConnect: BluetoothService.canConnect(modelData)
|
property bool canConnect: BluetoothService.canConnect(modelData)
|
||||||
property bool isBusy: BluetoothService.isDeviceBusy(modelData)
|
property bool isBusy: BluetoothService.isDeviceBusy(modelData) || isDeviceBeingPaired(modelData.address)
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 50
|
height: 50
|
||||||
@@ -335,7 +341,7 @@ Rectangle {
|
|||||||
color: availableMouseArea.containsMouse && !isBusy ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest
|
color: availableMouseArea.containsMouse && !isBusy ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||||
border.width: 0
|
border.width: 0
|
||||||
opacity: canConnect ? 1 : 0.6
|
opacity: (canConnect && !isBusy) ? 1 : 0.6
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
@@ -367,7 +373,7 @@ Rectangle {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: {
|
text: {
|
||||||
if (modelData.pairing) return "Pairing..."
|
if (modelData.pairing || isBusy) return "Pairing..."
|
||||||
if (modelData.blocked) return "Blocked"
|
if (modelData.blocked) return "Blocked"
|
||||||
return BluetoothService.getSignalStrength(modelData)
|
return BluetoothService.getSignalStrength(modelData)
|
||||||
}
|
}
|
||||||
@@ -390,12 +396,12 @@ Rectangle {
|
|||||||
anchors.rightMargin: Theme.spacingM
|
anchors.rightMargin: Theme.spacingM
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: {
|
text: {
|
||||||
if (modelData.pairing) return "Pairing..."
|
if (isBusy) return "Pairing..."
|
||||||
if (!canConnect) return "Cannot pair"
|
if (!canConnect) return "Cannot pair"
|
||||||
return "Pair"
|
return "Pair"
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: canConnect ? Theme.primary : Theme.surfaceVariantText
|
color: (canConnect && !isBusy) ? Theme.primary : Theme.surfaceVariantText
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,7 +413,20 @@ Rectangle {
|
|||||||
enabled: canConnect && !isBusy
|
enabled: canConnect && !isBusy
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (modelData) {
|
if (modelData) {
|
||||||
BluetoothService.connectDeviceWithTrust(modelData)
|
const deviceAddr = modelData.address
|
||||||
|
devicesBeingPaired.add(deviceAddr)
|
||||||
|
devicesBeingPairedChanged()
|
||||||
|
|
||||||
|
BluetoothService.pairDevice(modelData, response => {
|
||||||
|
devicesBeingPaired.delete(deviceAddr)
|
||||||
|
devicesBeingPairedChanged()
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
ToastService.showError(I18n.tr("Pairing failed"), response.error)
|
||||||
|
} else if (!BluetoothService.enhancedPairingAvailable) {
|
||||||
|
ToastService.showSuccess(I18n.tr("Device paired"))
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -522,4 +541,15 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BluetoothPairingModal {
|
||||||
|
id: bluetoothPairingModal
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: DMSService
|
||||||
|
|
||||||
|
function onBluetoothPairingRequest(data) {
|
||||||
|
bluetoothPairingModal.show(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import QtQuick
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Bluetooth
|
import Quickshell.Bluetooth
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
@@ -15,6 +16,7 @@ Singleton {
|
|||||||
readonly property bool enabled: (adapter && adapter.enabled) ?? false
|
readonly property bool enabled: (adapter && adapter.enabled) ?? false
|
||||||
readonly property bool discovering: (adapter && adapter.discovering) ?? false
|
readonly property bool discovering: (adapter && adapter.discovering) ?? false
|
||||||
readonly property var devices: adapter ? adapter.devices : null
|
readonly property var devices: adapter ? adapter.devices : null
|
||||||
|
readonly property bool enhancedPairingAvailable: DMSService.dmsAvailable && DMSService.apiVersion >= 9 && DMSService.capabilities.includes("bluetooth")
|
||||||
readonly property bool connected: {
|
readonly property bool connected: {
|
||||||
if (!adapter || !adapter.devices) {
|
if (!adapter || !adapter.devices) {
|
||||||
return false
|
return false
|
||||||
@@ -173,6 +175,25 @@ Singleton {
|
|||||||
device.connect()
|
device.connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pairDevice(device, callback) {
|
||||||
|
if (!device) {
|
||||||
|
if (callback) callback({error: "Invalid device"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The DMS backend actually implements a bluez agent, so we can pair anything
|
||||||
|
if (enhancedPairingAvailable) {
|
||||||
|
const devicePath = getDevicePath(device)
|
||||||
|
DMSService.bluetoothPair(devicePath, callback)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quickshell does not implement a bluez agent, so we can try to pair but only with devices that don't require a passcode
|
||||||
|
device.trusted = true
|
||||||
|
device.connect()
|
||||||
|
if (callback) callback({success: true})
|
||||||
|
}
|
||||||
|
|
||||||
function getCardName(device) {
|
function getCardName(device) {
|
||||||
if (!device) {
|
if (!device) {
|
||||||
return ""
|
return ""
|
||||||
@@ -180,6 +201,14 @@ Singleton {
|
|||||||
return `bluez_card.${device.address.replace(/:/g, "_")}`
|
return `bluez_card.${device.address.replace(/:/g, "_")}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDevicePath(device) {
|
||||||
|
if (!device || !device.address) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
const adapterPath = adapter ? "/org/bluez/hci0" : "/org/bluez/hci0"
|
||||||
|
return `${adapterPath}/dev_${device.address.replace(/:/g, "_")}`
|
||||||
|
}
|
||||||
|
|
||||||
function isAudioDevice(device) {
|
function isAudioDevice(device) {
|
||||||
if (!device) {
|
if (!device) {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ Singleton {
|
|||||||
signal loginctlEvent(var event)
|
signal loginctlEvent(var event)
|
||||||
signal capabilitiesReceived()
|
signal capabilitiesReceived()
|
||||||
signal credentialsRequest(var data)
|
signal credentialsRequest(var data)
|
||||||
|
signal bluetoothPairingRequest(var data)
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (socketPath && socketPath.length > 0) {
|
if (socketPath && socketPath.length > 0) {
|
||||||
@@ -217,7 +218,10 @@ Singleton {
|
|||||||
|
|
||||||
function sendSubscribeRequest() {
|
function sendSubscribeRequest() {
|
||||||
const request = {
|
const request = {
|
||||||
"method": "subscribe"
|
"method": "subscribe",
|
||||||
|
"params": {
|
||||||
|
"services": ["bluetooth", "bluetooth.pairing"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (verboseLogs) {
|
if (verboseLogs) {
|
||||||
@@ -270,6 +274,8 @@ Singleton {
|
|||||||
} else {
|
} else {
|
||||||
loginctlStateUpdate(data)
|
loginctlStateUpdate(data)
|
||||||
}
|
}
|
||||||
|
} else if (service === "bluetooth.pairing") {
|
||||||
|
bluetoothPairingRequest(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,4 +414,48 @@ Singleton {
|
|||||||
function unlockSession(callback) {
|
function unlockSession(callback) {
|
||||||
sendRequest("loginctl.unlock", null, callback)
|
sendRequest("loginctl.unlock", null, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function bluetoothPair(devicePath, callback) {
|
||||||
|
sendRequest("bluetooth.pair", {
|
||||||
|
"device": devicePath
|
||||||
|
}, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
function bluetoothConnect(devicePath, callback) {
|
||||||
|
sendRequest("bluetooth.connect", {
|
||||||
|
"device": devicePath
|
||||||
|
}, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
function bluetoothDisconnect(devicePath, callback) {
|
||||||
|
sendRequest("bluetooth.disconnect", {
|
||||||
|
"device": devicePath
|
||||||
|
}, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
function bluetoothRemove(devicePath, callback) {
|
||||||
|
sendRequest("bluetooth.remove", {
|
||||||
|
"device": devicePath
|
||||||
|
}, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
function bluetoothTrust(devicePath, callback) {
|
||||||
|
sendRequest("bluetooth.trust", {
|
||||||
|
"device": devicePath
|
||||||
|
}, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
function bluetoothSubmitPairing(token, secrets, accept, callback) {
|
||||||
|
sendRequest("bluetooth.pairing.submit", {
|
||||||
|
"token": token,
|
||||||
|
"secrets": secrets,
|
||||||
|
"accept": accept
|
||||||
|
}, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
function bluetoothCancelPairing(token, callback) {
|
||||||
|
sendRequest("bluetooth.pairing.cancel", {
|
||||||
|
"token": token
|
||||||
|
}, callback)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
|
|
||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtCore
|
import QtCore
|
||||||
import QtQuick
|
import QtQuick
|
||||||
@@ -640,7 +640,7 @@ Singleton {
|
|||||||
const enrichedToplevel = {
|
const enrichedToplevel = {
|
||||||
"appId": bestMatch.appId,
|
"appId": bestMatch.appId,
|
||||||
"title": bestMatch.title,
|
"title": bestMatch.title,
|
||||||
"activated": bestMatch.activated,
|
"activated": niriWindow.is_focused ?? false,
|
||||||
"niriWindowId": niriWindow.id,
|
"niriWindowId": niriWindow.id,
|
||||||
"niriWorkspaceId": niriWindow.workspace_id,
|
"niriWorkspaceId": niriWindow.workspace_id,
|
||||||
"activate": function () {
|
"activate": function () {
|
||||||
@@ -726,7 +726,7 @@ Singleton {
|
|||||||
const enrichedToplevel = {
|
const enrichedToplevel = {
|
||||||
"appId": bestMatch.appId,
|
"appId": bestMatch.appId,
|
||||||
"title": bestMatch.title,
|
"title": bestMatch.title,
|
||||||
"activated": bestMatch.activated,
|
"activated": niriWindow.is_focused ?? false,
|
||||||
"niriWindowId": niriWindow.id,
|
"niriWindowId": niriWindow.id,
|
||||||
"niriWorkspaceId": niriWindow.workspace_id,
|
"niriWorkspaceId": niriWindow.workspace_id,
|
||||||
"activate": function () {
|
"activate": function () {
|
||||||
@@ -753,12 +753,10 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function generateNiriLayoutConfig() {
|
function generateNiriLayoutConfig() {
|
||||||
const niriSocket = Quickshell.env("NIRI_SOCKET")
|
if (!CompositorService.isNiri || configGenerationPending)
|
||||||
if (!niriSocket || niriSocket.length === 0)
|
|
||||||
return
|
|
||||||
if (configGenerationPending)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
suppressNextToast()
|
||||||
configGenerationPending = true
|
configGenerationPending = true
|
||||||
configGenerationDebounce.restart()
|
configGenerationDebounce.restart()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user