feat(bluetooth): rebuild settings UI and hide unavailable adapters

main
reionwong 2 months ago
parent e6717f0ac9
commit 4c0642e4e2

2
debian/control vendored

@ -31,8 +31,6 @@ Depends: qml6-module-qtquick-controls,
qml6-module-qtquick-dialogs,
qml6-module-qtquick-particles,
qml6-module-qt5compat-graphicaleffects,
qml6-module-org-kde-bluezqt,
bluedevil,
network-manager,
fishui,
libcutefish,

@ -17,10 +17,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.4
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
import Cutefish.Settings 1.0
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import FishUI 1.0 as FishUI
import Cutefish.Bluetooth 1.0 as Bluetooth
import "../"
@ -28,51 +27,43 @@ import "../"
ItemPage {
id: control
headerTitle: qsTr("Bluetooth")
readonly property bool bluetoothEnabled: Bluetooth.Manager.enabled
property bool bluetoothDisConnected: Bluetooth.Manager.bluetoothBlocked
onBluetoothDisConnectedChanged: {
bluetoothSwitch.checked = !bluetoothDisConnected
}
function setBluetoothEnabled(enabled) {
Bluetooth.Manager.bluetoothBlocked = !enabled
for (var i = 0; i < Bluetooth.Manager.adapters.length; ++i) {
var adapter = Bluetooth.Manager.adapters[i]
adapter.powered = enabled
}
function updateActivity() {
Bluetooth.Manager.agentEnabled = visible
Bluetooth.Manager.discoveryRequested = visible && Bluetooth.Manager.enabled
}
Bluetooth.DevicesProxyModel {
id: devicesProxyModel
sourceModel: devicesModel
onVisibleChanged: updateActivity()
onBluetoothEnabledChanged: {
Bluetooth.Manager.discoveryRequested = visible && bluetoothEnabled
}
Bluetooth.DevicesModel {
id: devicesModel
Component.onCompleted: updateActivity()
Component.onDestruction: {
Bluetooth.Manager.discoveryRequested = false
Bluetooth.Manager.agentEnabled = false
}
Bluetooth.BluetoothManager {
id: bluetoothMgr
onShowPairDialog: {
_pairDialog.title = name
_pairDialog.pin = pin
_pairDialog.visible = true
Bluetooth.DevicesModel { id: devicesModel }
PairDialog { }
Dialog {
id: forgetDialog
property var device: null
title: qsTr("Forget this device?")
anchors.centerIn: parent
modal: true
standardButtons: Dialog.Cancel | Dialog.Ok
Label {
text: forgetDialog.device ? forgetDialog.device.name : ""
textFormat: Text.PlainText
}
onPairFailed: {
rootWindow.showPassiveNotification(qsTr("Pairing unsuccessful"), 3000)
}
onConnectFailed: {
rootWindow.showPassiveNotification(qsTr("Connecting Unsuccessful"), 3000)
onAccepted: {
if (device)
device.forget()
device = null
}
}
PairDialog {
id: _pairDialog
onRejected: device = null
}
Scrollable {
@ -81,188 +72,200 @@ ItemPage {
ColumnLayout {
id: layout
anchors.fill: parent
anchors.bottomMargin: FishUI.Units.largeSpacing
width: parent.contentWidth
spacing: FishUI.Units.largeSpacing
RoundedItem {
id: mainItem
spacing: FishUI.Units.largeSpacing
RowLayout {
Label {
text: qsTr("Bluetooth")
color: FishUI.Theme.disabledTextColor
}
Item {
Layout.fillWidth: true
}
Switch {
id: bluetoothSwitch
Layout.fillHeight: true
rightPadding: 0
checked: !Bluetooth.Manager.bluetoothBlocked
onCheckedChanged: setBluetoothEnabled(checked)
checked: Bluetooth.Manager.enabled
enabled: Bluetooth.Manager.operational && Bluetooth.Manager.adapters.length > 0
onClicked: Bluetooth.Manager.enabled = checked
}
}
ListView {
id: _listView
visible: count > 0
interactive: false
spacing: 0
Label {
Layout.fillWidth: true
Layout.preferredHeight: {
var totalHeight = 0
for (var i = 0; i < _listView.visibleChildren.length; ++i) {
totalHeight += _listView.visibleChildren[i].height
wrapMode: Text.Wrap
visible: !Bluetooth.Manager.operational || !Bluetooth.Manager.adapters.length
|| Bluetooth.Manager.hardwareBlocked || !Bluetooth.Manager.enabled
text: !Bluetooth.Manager.operational ? qsTr("Bluetooth service is unavailable")
: Bluetooth.Manager.hardwareBlocked ? qsTr("Bluetooth is blocked by a hardware switch")
: !Bluetooth.Manager.adapters.length ? qsTr("No Bluetooth adapters found")
: qsTr("Bluetooth is disabled")
}
RowLayout {
visible: Bluetooth.Manager.enabled
Label {
Layout.fillWidth: true
text: Bluetooth.Manager.scanning ? qsTr("Searching for devices…") : qsTr("Devices")
}
BusyIndicator {
running: Bluetooth.Manager.scanning
implicitWidth: 28
implicitHeight: 28
}
Button {
text: Bluetooth.Manager.discoveryRequested ? qsTr("Stop searching") : qsTr("Search")
onClicked: {
Bluetooth.Manager.clearError()
Bluetooth.Manager.discoveryRequested = !Bluetooth.Manager.discoveryRequested
}
return totalHeight
}
}
}
model: Bluetooth.Manager.bluetoothOperational ? devicesProxyModel : []
RoundedItem {
visible: Bluetooth.Manager.errorText.length > 0
Label {
Layout.fillWidth: true
wrapMode: Text.Wrap
textFormat: Text.PlainText
text: Bluetooth.Manager.errorText
}
Button {
text: qsTr("Dismiss")
onClicked: Bluetooth.Manager.clearError()
}
}
section.property: "Section"
section.criteria: ViewSection.FullString
section.delegate: Label {
color: FishUI.Theme.disabledTextColor
topPadding: FishUI.Units.largeSpacing
bottomPadding: FishUI.Units.largeSpacing
text: section == "My devices" ? qsTr("My devices")
: qsTr("Other devices")
Repeater {
model: Bluetooth.Manager.adapters
delegate: RoundedItem {
required property var modelData
Label {
Layout.fillWidth: true
text: modelData.name + " · " + modelData.address
textFormat: Text.PlainText
elide: Text.ElideRight
}
RowLayout {
Layout.fillWidth: true
TextField {
id: adapterName
Layout.fillWidth: true
placeholderText: modelData.name
maximumLength: 248
}
Button {
text: qsTr("Rename")
enabled: adapterName.text.length > 0
onClicked: {
modelData.name = adapterName.text
adapterName.clear()
}
}
}
RowLayout {
Switch {
text: qsTr("Powered")
checked: modelData.powered
onClicked: modelData.powered = checked
}
Switch {
text: qsTr("Visible to other devices")
enabled: modelData.powered
checked: modelData.discoverable
onClicked: modelData.discoverable = checked
}
}
Switch {
text: qsTr("Allow incoming pairing")
enabled: modelData.powered
checked: modelData.pairable
onClicked: modelData.pairable = checked
}
}
}
delegate: Item {
width: ListView.view.width
height: _itemLayout.implicitHeight + FishUI.Units.largeSpacing
property bool paired: model.Connected && model.Paired
Label {
visible: Bluetooth.Manager.enabled && devicesModel.count === 0
text: qsTr("No devices found. Make your device discoverable and search again.")
wrapMode: Text.Wrap
Layout.fillWidth: true
}
Repeater {
model: devicesModel
delegate: RoundedItem {
required property var device
visible: Bluetooth.Manager.enabled
RowLayout {
ColumnLayout {
id: _itemLayout
anchors.fill: parent
anchors.leftMargin: 0
anchors.rightMargin: 0
anchors.topMargin: FishUI.Units.smallSpacing
anchors.bottomMargin: FishUI.Units.smallSpacing
spacing: 0
Item {
Layout.fillWidth: true
Label {
Layout.fillWidth: true
height: _contentLayout.implicitHeight + FishUI.Units.largeSpacing
Rectangle {
anchors.fill: parent
radius: FishUI.Theme.smallRadius
color: FishUI.Theme.textColor
opacity: mouseArea.pressed ? 0.15 : mouseArea.containsMouse ? 0.1 : 0.0
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton
onClicked: {
if (model.Connected || model.Paired){
additionalSettings.toggle()
return
}
if (model.Paired) {
bluetoothMgr.connectToDevice(model.Address)
} else {
bluetoothMgr.requestParingConnection(model.Address)
}
}
}
RowLayout {
id: _contentLayout
anchors.fill: parent
anchors.rightMargin: FishUI.Units.smallSpacing
Image {
width: 16
height: 16
sourceSize: Qt.size(16, 16)
source: FishUI.Theme.darkMode ? "qrc:/images/sidebar/dark/bluetooth.svg"
: "qrc:/images/sidebar/light/bluetooth.svg"
Layout.alignment: Qt.AlignVCenter
}
Label {
text: model.DeviceFullName
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
}
Label {
visible: model.Paired
text: model.Connected ? qsTr("Connected") : qsTr("Not Connected")
}
}
text: device.name
textFormat: Text.PlainText
elide: Text.ElideRight
}
Hideable {
id: additionalSettings
spacing: 0
ColumnLayout {
Item {
height: FishUI.Units.largeSpacing
}
RowLayout {
spacing: FishUI.Units.largeSpacing
Layout.leftMargin: FishUI.Units.smallSpacing
Button {
text: qsTr("Connect")
visible: !model.Connected
onClicked: {
if (model.Paired) {
bluetoothMgr.connectToDevice(model.Address)
} else {
bluetoothMgr.requestParingConnection(model.Address)
}
}
}
Button {
text: qsTr("Disconnect")
visible: model.Connected
onClicked: {
bluetoothMgr.deviceDisconnect(model.Address)
additionalSettings.hide()
}
}
Button {
text: qsTr("Forget This Device")
flat: true
onClicked: {
bluetoothMgr.deviceRemoved(model.Address)
additionalSettings.hide()
}
}
}
}
HorizontalDivider {}
Label {
text: device.address + " · " + device.adapterPath.split("/").pop()
color: FishUI.Theme.disabledTextColor
}
Label {
text: device.connected ? qsTr("Connected")
: device.paired ? qsTr("Paired") : qsTr("Not paired")
}
}
Label {
visible: device.batteryPercentage >= 0
text: qsTr("%1% battery").arg(device.batteryPercentage)
}
BusyIndicator {
running: device.busy
visible: running
implicitWidth: 28
implicitHeight: 28
}
}
RowLayout {
Button {
text: device.connected ? qsTr("Disconnect")
: device.paired ? qsTr("Connect") : qsTr("Pair")
enabled: !device.busy && (device.paired || Bluetooth.Manager.agentReady)
onClicked: {
if (device.connected)
device.disconnectFromDevice()
else if (device.paired)
device.connectToDevice()
else
device.pair()
}
}
Button {
visible: device.busy && !device.paired
text: qsTr("Cancel pairing")
onClicked: device.cancelPairing()
}
Button {
visible: device.paired
enabled: !device.busy
text: qsTr("Forget")
onClicked: {
forgetDialog.device = device
forgetDialog.open()
}
}
CheckBox {
visible: device.paired
text: qsTr("Trusted")
checked: device.trusted
enabled: !device.busy
onClicked: device.trusted = checked
}
CheckBox {
text: qsTr("Blocked")
checked: device.blocked
enabled: !device.busy
onClicked: device.blocked = checked
}
}
}
}
Item {
height: FishUI.Units.largeSpacing * 2
}
}
}
}

@ -1,103 +1,93 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
import Cutefish.Settings 1.0
import FishUI 1.0 as FishUI
import "../"
FishUI.Window {
id: control
windowRadius: 16
windowButtonsTopMargin: 8
windowButtonsRightMargin: 8
minimizeButtonVisible: false
header.height: 44
contentTopMargin: 0
width: contentWidth
height: contentHeight
property int contentWidth: 360
property int contentHeight: mainLayout.implicitHeight + 32
minimumWidth: contentWidth
minimumHeight: contentHeight
maximumWidth: contentWidth
maximumHeight: contentHeight
modality: Qt.WindowModal
flags: Qt.Dialog | Qt.FramelessWindowHint
visible: false
title: " "
property var pin: ""
background.color: FishUI.Theme.secondBackgroundColor
DragHandler {
target: null
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
grabPermissions: PointerHandler.CanTakeOverFromItems | PointerHandler.CanTakeOverFromHandlersOfDifferentType | PointerHandler.ApprovesTakeOverByAnything
onActiveChanged: if (active) { control.helper.startSystemMove(control) }
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Cutefish.Bluetooth 1.0 as Bluetooth
Dialog {
id: dialog
anchors.centerIn: parent
width: Math.min(parent.width - 32, 420)
modal: true
closePolicy: Popup.NoAutoClose
title: qsTr("Bluetooth pairing")
readonly property string kind: Bluetooth.Manager.pairingKind
readonly property bool inputRequired: kind === "RequestPinCode" || kind === "RequestPasskey"
readonly property bool displayOnly: kind === "DisplayPinCode" || kind === "DisplayPasskey"
Connections {
target: Bluetooth.Manager
function onPairingChanged() {
if (Bluetooth.Manager.pairingKind.length) {
if (!dialog.opened) {
codeInput.clear()
dialog.open()
if (dialog.inputRequired)
codeInput.forceActiveFocus()
}
} else {
dialog.close()
}
}
}
ColumnLayout {
id: mainLayout
anchors.fill: parent
anchors.margins: 16
contentItem: ColumnLayout {
spacing: 16
Label {
Layout.fillWidth: true
Layout.rightMargin: 32
text: "Bluetooth Pairing Request"
text: Bluetooth.Manager.pairingDevice
textFormat: Text.PlainText
font.pixelSize: 14
font.weight: Font.DemiBold
color: FishUI.Theme.textColor
wrapMode: Text.Wrap
}
Label {
Layout.fillWidth: true
text: control.title.trim()
textFormat: Text.PlainText
font.pixelSize: 14
color: FishUI.Theme.disabledTextColor
text: dialog.kind === "RequestConfirmation" ? qsTr("Does this code match the code on your device?")
: dialog.kind === "RequestPinCode" ? qsTr("Enter the PIN for your device.")
: dialog.kind === "RequestPasskey" ? qsTr("Enter the passkey shown on your device.")
: dialog.displayOnly ? qsTr("Enter this code on your device, then press Enter.")
: dialog.kind === "AuthorizeService" ? qsTr("Allow this device to use the following Bluetooth service?")
: qsTr("Allow this device to pair?")
wrapMode: Text.Wrap
visible: text.length > 0
}
Label {
text: control.pin
Layout.fillWidth: true
visible: Bluetooth.Manager.pairingCode.length > 0
text: Bluetooth.Manager.pairingCode
textFormat: Text.PlainText
visible: control.pin !== ""
font.pixelSize: 24
font.weight: Font.DemiBold
Layout.alignment: Qt.AlignHCenter
wrapMode: Text.WrapAnywhere
font.pixelSize: dialog.kind === "AuthorizeService" ? 14 : 28
horizontalAlignment: Text.AlignHCenter
}
Label {
visible: dialog.kind === "DisplayPasskey"
text: qsTr("%1 digits entered").arg(Bluetooth.Manager.pairingEntered)
}
TextField {
id: codeInput
Layout.fillWidth: true
visible: dialog.inputRequired
maximumLength: dialog.kind === "RequestPasskey" ? 6 : 16
inputMethodHints: dialog.kind === "RequestPasskey" ? Qt.ImhDigitsOnly : Qt.ImhNone
onAccepted: Bluetooth.Manager.respondPairing(true, text)
}
Label {
Layout.fillWidth: true
visible: Bluetooth.Manager.errorText.length > 0
text: Bluetooth.Manager.errorText
textFormat: Text.PlainText
wrapMode: Text.Wrap
}
RowLayout {
spacing: 8
Layout.alignment: Qt.AlignRight
FishUI.ConfirmDialogButton {
Button {
text: qsTr("Cancel")
onClicked: {
control.visible = false
bluetoothMgr.confirmMatchButton(false)
}
onClicked: Bluetooth.Manager.cancelPairing()
}
FishUI.ConfirmDialogButton {
text: qsTr("OK")
primary: true
onClicked: {
control.visible = false
bluetoothMgr.confirmMatchButton(true)
}
Button {
visible: !dialog.displayOnly
text: dialog.kind === "RequestConfirmation" ? qsTr("Confirm") : qsTr("Accept")
enabled: !dialog.inputRequired || codeInput.text.length > 0
onClicked: Bluetooth.Manager.respondPairing(true, codeInput.text)
}
}
}

@ -17,84 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.4
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
import Cutefish.Settings 1.0
import FishUI 1.0 as FishUI
import org.kde.bluezqt 1.0 as BluezQt
import org.kde.plasma.private.bluetooth 1.0
import QtQuick
import "Bluetooth" as BluetoothPage
ItemPage {
headerTitle: qsTr("Bluetooth")
function setBluetoothEnabled(enabled) {
BluezQt.Manager.bluetoothBlocked = !enabled
for (var i = 0; i < BluezQt.Manager.adapters.length; ++i) {
var adapter = BluezQt.Manager.adapters[i];
adapter.powered = enabled;
}
}
// Label {
// id: noBluetoothMessage
// anchors.centerIn: parent
// visible: BluezQt.Manager.rfkill.state === BluezQt.Rfkill.Unknown
// text: qsTr("No Bluetooth adapters found")
// }
Label {
anchors.centerIn: parent
text: qsTr("Bluetooth is disabled")
visible: BluezQt.Manager.operational && !BluezQt.Manager.bluetoothOperational // && !noBluetoothMessage.visible
}
Scrollable {
anchors.fill: parent
contentHeight: layout.implicitHeight
ColumnLayout {
id: layout
anchors.fill: parent
anchors.bottomMargin: FishUI.Units.largeSpacing
Switch {
id: bluetoothSwitch
height: 50
onCheckedChanged: setBluetoothEnabled(checked)
Component.onCompleted: bluetoothSwitch.checked = BluezQt.Manager.operational
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
DevicesProxyModel {
id: devicesModel
sourceModel: BluezQt.DevicesModel { }
}
model: BluezQt.Manager.bluetoothOperational ? devicesModel : []
delegate: Item {
width: ListView.view.width
height: 50
Rectangle {
anchors.fill: parent
color: "red"
}
ColumnLayout {
anchors.fill: parent
Label {
text: model.name
}
}
}
}
}
}
}
BluetoothPage.Main { }

@ -23,8 +23,10 @@ import QtQuick.Layouts 1.3
import FishUI 1.0 as FishUI
import Cutefish.Settings 1.0
import Cutefish.Network 1.0 as NM
import Cutefish.Bluetooth 1.0 as Bluetooth
Item {
id: sideBar
implicitWidth: 230
property int itemRadiusV: 8
@ -32,6 +34,67 @@ Item {
property alias view: listView
property alias model: listModel
property alias currentIndex: listView.currentIndex
property bool updatingModel: false
signal currentPageRemoved()
signal bluetoothItemAdded()
Component.onCompleted: syncBluetoothItem()
Connections {
target: Bluetooth.Manager
function onAvailableChanged() {
Qt.callLater(sideBar.syncBluetoothItem)
}
}
function syncBluetoothItem() {
var bluetoothIndex = -1
for (var i = 0; i < listModel.count; ++i) {
if (listModel.get(i).name === "bluetooth") {
bluetoothIndex = i
break
}
}
if (Bluetooth.Manager.available === (bluetoothIndex >= 0))
return
var selectedPage = listView.currentIndex >= 0
? listModel.get(listView.currentIndex).page : ""
var selectedRemoved = bluetoothIndex >= 0 && listView.currentIndex === bluetoothIndex
updatingModel = true
if (Bluetooth.Manager.available) {
var insertIndex = 0
while (insertIndex < listModel.count
&& (listModel.get(insertIndex).name === "wlan"
|| listModel.get(insertIndex).name === "ethernet"))
++insertIndex
listModel.insert(insertIndex, {
title: qsTr("Bluetooth"),
name: "bluetooth",
page: "qrc:/qml/Bluetooth/Main.qml",
iconSource: "bluetooth.svg",
iconColor: "#0067FF",
category: qsTr("Network and connection")
})
} else {
listModel.remove(bluetoothIndex)
}
if (selectedRemoved) {
listView.currentIndex = 0
} else {
for (var j = 0; j < listModel.count; ++j) {
if (listModel.get(j).page === selectedPage) {
listView.currentIndex = j
break
}
}
}
updatingModel = false
if (selectedRemoved)
currentPageRemoved()
else if (Bluetooth.Manager.available)
bluetoothItemAdded()
}
Rectangle {
anchors.fill: parent
@ -77,15 +140,6 @@ Item {
// category: qsTr("Network and connection")
// }
ListElement {
title: qsTr("Bluetooth")
name: "bluetooth"
page: "qrc:/qml/Bluetooth/Main.qml"
iconSource: "bluetooth.svg"
iconColor: "#0067FF"
category: qsTr("Network and connection")
}
// ListElement {
// title: qsTr("VPN")
// name: "vpn"

@ -34,6 +34,7 @@ FishUI.Window {
minimumHeight: 600
property alias stackView: _stackView
property string pendingPageName: ""
background.color: FishUI.Theme.backgroundColor
background.opacity: blurEnabled && blurAvailable ? (FishUI.Theme.darkMode ? 0.7 : 0.5) : 1
@ -58,7 +59,13 @@ FishUI.Window {
: Math.round(implicitWidth * pixelRatio) / pixelRatio
onCurrentIndexChanged: {
switchPageFromIndex(currentIndex)
if (!sideBar.updatingModel)
switchPageFromIndex(currentIndex)
}
onCurrentPageRemoved: switchPageFromIndex(currentIndex)
onBluetoothItemAdded: {
if (rootWindow.pendingPageName === "bluetooth")
rootWindow.switchPageFromName("bluetooth")
}
}
@ -77,11 +84,15 @@ FishUI.Window {
}
function switchPageFromIndex(index) {
if (index < 0 || index >= sideBar.model.count)
return
pendingPageName = ""
_stackView.pop()
_stackView.push(Qt.resolvedUrl(sideBar.model.get(index).page))
}
function switchPageFromName(pageName) {
pendingPageName = pageName === "bluetooth" ? pageName : ""
for (var i = 0; i < sideBar.model.count; ++i) {
if (pageName === sideBar.model.get(i).name) {
switchPageFromIndex(i)

Loading…
Cancel
Save