forked from Shinonome/dots-hyprland
bluetooth menu
This commit is contained in:
@@ -0,0 +1,23 @@
|
|||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
// From https://github.com/caelestia-dots/shell (GPLv3)
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
function getBluetoothDeviceMaterialSymbol(systemIconName: string): string {
|
||||||
|
if (systemIconName.includes("headset") || systemIconName.includes("headphones"))
|
||||||
|
return "headphones";
|
||||||
|
if (systemIconName.includes("audio"))
|
||||||
|
return "speaker";
|
||||||
|
if (systemIconName.includes("phone"))
|
||||||
|
return "smartphone";
|
||||||
|
if (systemIconName.includes("mouse"))
|
||||||
|
return "mouse";
|
||||||
|
if (systemIconName.includes("keyboard"))
|
||||||
|
return "keyboard";
|
||||||
|
return "bluetooth";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ RippleButton {
|
|||||||
colBackground: ColorUtils.transparentize(Appearance.colors.colLayer3)
|
colBackground: ColorUtils.transparentize(Appearance.colors.colLayer3)
|
||||||
colBackgroundHover: Appearance.colors.colLayer3Hover
|
colBackgroundHover: Appearance.colors.colLayer3Hover
|
||||||
colRipple: Appearance.colors.colLayer3Active
|
colRipple: Appearance.colors.colLayer3Active
|
||||||
|
property alias colText: buttonTextWidget.color
|
||||||
|
|
||||||
contentItem: StyledText {
|
contentItem: StyledText {
|
||||||
id: buttonTextWidget
|
id: buttonTextWidget
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.functions
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
RippleButton {
|
||||||
|
id: root
|
||||||
|
property bool active: false
|
||||||
|
|
||||||
|
horizontalPadding: Appearance.rounding.large
|
||||||
|
verticalPadding: 12
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
pointingHandCursor: !active
|
||||||
|
implicitWidth: contentItem.implicitWidth + horizontalPadding * 2
|
||||||
|
implicitHeight: contentItem.implicitHeight + verticalPadding * 2
|
||||||
|
Behavior on implicitHeight {
|
||||||
|
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
colBackground: ColorUtils.transparentize(Appearance.colors.colLayer3)
|
||||||
|
colBackgroundHover: active ? colBackground : Appearance.colors.colLayer3Hover
|
||||||
|
colRipple: Appearance.colors.colLayer3Active
|
||||||
|
buttonRadius: 0
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ Button {
|
|||||||
id: root
|
id: root
|
||||||
property bool toggled
|
property bool toggled
|
||||||
property string buttonText
|
property string buttonText
|
||||||
|
property bool pointingHandCursor: true
|
||||||
property real buttonRadius: Appearance?.rounding?.small ?? 4
|
property real buttonRadius: Appearance?.rounding?.small ?? 4
|
||||||
property real buttonRadiusPressed: buttonRadius
|
property real buttonRadiusPressed: buttonRadius
|
||||||
property real buttonEffectiveRadius: root.down ? root.buttonRadiusPressed : root.buttonRadius
|
property real buttonEffectiveRadius: root.down ? root.buttonRadiusPressed : root.buttonRadius
|
||||||
@@ -58,7 +59,7 @@ Button {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: root.pointingHandCursor ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||||
onPressed: (event) => {
|
onPressed: (event) => {
|
||||||
if(event.button === Qt.RightButton) {
|
if(event.button === Qt.RightButton) {
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
import qs
|
import qs
|
||||||
import qs.services
|
import qs.services
|
||||||
import qs.services.network
|
|
||||||
import qs.modules.common
|
import qs.modules.common
|
||||||
import qs.modules.common.widgets
|
import qs.modules.common.widgets
|
||||||
import qs.modules.common.functions
|
|
||||||
import "./quickToggles/"
|
import "./quickToggles/"
|
||||||
import "./wifiNetworks/"
|
import "./wifiNetworks/"
|
||||||
|
import "./bluetoothDevices/"
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Qt5Compat.GraphicalEffects
|
|
||||||
import Quickshell.Io
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Wayland
|
import Quickshell.Bluetooth
|
||||||
import Quickshell.Hyprland
|
import Quickshell.Hyprland
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -21,12 +18,14 @@ Item {
|
|||||||
property int sidebarPadding: 12
|
property int sidebarPadding: 12
|
||||||
property string settingsQmlPath: Quickshell.shellPath("settings.qml")
|
property string settingsQmlPath: Quickshell.shellPath("settings.qml")
|
||||||
property bool showWifiDialog: false
|
property bool showWifiDialog: false
|
||||||
|
property bool showBluetoothDialog: false
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: GlobalStates
|
target: GlobalStates
|
||||||
function onSidebarRightOpenChanged() {
|
function onSidebarRightOpenChanged() {
|
||||||
if (!GlobalStates.sidebarRightOpen) {
|
if (!GlobalStates.sidebarRightOpen) {
|
||||||
root.showWifiDialog = false;
|
root.showWifiDialog = false;
|
||||||
|
root.showBluetoothDialog = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,7 +128,13 @@ Item {
|
|||||||
root.showWifiDialog = true;
|
root.showWifiDialog = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BluetoothToggle {}
|
BluetoothToggle {
|
||||||
|
altAction: () => {
|
||||||
|
Bluetooth.defaultAdapter.enabled = true;
|
||||||
|
Bluetooth.defaultAdapter.discovering = true;
|
||||||
|
root.showBluetoothDialog = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
NightLight {}
|
NightLight {}
|
||||||
GameMode {}
|
GameMode {}
|
||||||
IdleInhibitor {}
|
IdleInhibitor {}
|
||||||
@@ -138,7 +143,6 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CenterWidgetGroup {
|
CenterWidgetGroup {
|
||||||
focus: sidebarRoot.visible
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -176,4 +180,28 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onShowBluetoothDialogChanged: if (showBluetoothDialog) bluetoothDialogLoader.active = true;
|
||||||
|
Loader {
|
||||||
|
id: bluetoothDialogLoader
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
active: root.showBluetoothDialog || item.visible
|
||||||
|
onActiveChanged: {
|
||||||
|
if (active) {
|
||||||
|
item.show = true;
|
||||||
|
item.forceActiveFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceComponent: BluetoothDialog {
|
||||||
|
onDismiss: {
|
||||||
|
show = false
|
||||||
|
root.showBluetoothDialog = false
|
||||||
|
}
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (!visible && !root.showBluetoothDialog) bluetoothDialogLoader.active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import qs
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import qs.services
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
DialogListItem {
|
||||||
|
id: root
|
||||||
|
required property var device
|
||||||
|
property bool expanded: false
|
||||||
|
pointingHandCursor: !expanded
|
||||||
|
|
||||||
|
onClicked: expanded = !expanded
|
||||||
|
|
||||||
|
component ActionButton: DialogButton {
|
||||||
|
colBackground: Appearance.colors.colPrimary
|
||||||
|
colBackgroundHover: Appearance.colors.colPrimaryHover
|
||||||
|
colRipple: Appearance.colors.colPrimaryActive
|
||||||
|
colText: Appearance.colors.colOnPrimary
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
anchors {
|
||||||
|
fill: parent
|
||||||
|
topMargin: root.verticalPadding
|
||||||
|
leftMargin: root.horizontalPadding
|
||||||
|
rightMargin: root.horizontalPadding
|
||||||
|
}
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
// Name
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
MaterialSymbol {
|
||||||
|
iconSize: Appearance.font.pixelSize.larger
|
||||||
|
text: Icons.getBluetoothDeviceMaterialSymbol(root.device?.icon || "")
|
||||||
|
color: Appearance.colors.colOnSurfaceVariant
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 2
|
||||||
|
Layout.fillWidth: true
|
||||||
|
StyledText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
color: Appearance.colors.colOnSurfaceVariant
|
||||||
|
elide: Text.ElideRight
|
||||||
|
text: root.device?.name
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
visible: root.device?.connected || root.device?.paired
|
||||||
|
Layout.fillWidth: true
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.smaller
|
||||||
|
color: Appearance.colors.colSubtext
|
||||||
|
elide: Text.ElideRight
|
||||||
|
text: {
|
||||||
|
if (!root.device?.paired) return "";
|
||||||
|
let statusText = root.device?.connected ? Translation.tr("Connected") : Translation.tr("Paired");
|
||||||
|
if (!root.device?.batteryAvailable) return statusText;
|
||||||
|
statusText += ` • ${root.device?.battery * 100}%`;
|
||||||
|
return statusText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialSymbol {
|
||||||
|
text: "keyboard_arrow_down"
|
||||||
|
iconSize: Appearance.font.pixelSize.larger
|
||||||
|
color: Appearance.colors.colOnLayer3
|
||||||
|
rotation: root.expanded ? 180 : 0
|
||||||
|
Behavior on rotation {
|
||||||
|
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
visible: root.expanded
|
||||||
|
Layout.topMargin: 8
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
ActionButton {
|
||||||
|
buttonText: root.device?.connected ? Translation.tr("Disconnect") : Translation.tr("Connect")
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (root.device?.connected) {
|
||||||
|
root.device.disconnect();
|
||||||
|
} else {
|
||||||
|
root.device.connect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ActionButton {
|
||||||
|
visible: root.device?.paired
|
||||||
|
colBackground: Appearance.colors.colError
|
||||||
|
colBackgroundHover: Appearance.colors.colErrorHover
|
||||||
|
colRipple: Appearance.colors.colErrorActive
|
||||||
|
colText: Appearance.colors.colOnError
|
||||||
|
|
||||||
|
buttonText: Translation.tr("Forget")
|
||||||
|
onClicked: {
|
||||||
|
root.device?.forget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import qs
|
||||||
|
import qs.services
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import qs.modules.common.functions
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Qt5Compat.GraphicalEffects
|
||||||
|
import Quickshell.Io
|
||||||
|
import Quickshell.Bluetooth
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import Quickshell.Hyprland
|
||||||
|
|
||||||
|
WindowDialog {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
WindowDialogTitle {
|
||||||
|
text: Translation.tr("Bluetooth devices")
|
||||||
|
}
|
||||||
|
// TODO: add indeterminate progress bar when scanning
|
||||||
|
WindowDialogSeparator {}
|
||||||
|
StyledListView {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: -15
|
||||||
|
Layout.bottomMargin: -16
|
||||||
|
Layout.leftMargin: -Appearance.rounding.large
|
||||||
|
Layout.rightMargin: -Appearance.rounding.large
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
spacing: 0
|
||||||
|
animateAppearance: false
|
||||||
|
|
||||||
|
model: ScriptModel {
|
||||||
|
values: [...Bluetooth.devices.values].sort((a, b) => (b.connected - a.connected) || (b.paired - a.paired))
|
||||||
|
}
|
||||||
|
delegate: BluetoothDeviceItem {
|
||||||
|
required property BluetoothDevice modelData
|
||||||
|
device: modelData
|
||||||
|
anchors {
|
||||||
|
left: parent?.left
|
||||||
|
right: parent?.right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WindowDialogSeparator {}
|
||||||
|
WindowDialogButtonRow {
|
||||||
|
DialogButton {
|
||||||
|
buttonText: Translation.tr("Details")
|
||||||
|
onClicked: {
|
||||||
|
Quickshell.execDetached(["bash", "-c", `${Config.options.apps.bluetooth}`]);
|
||||||
|
GlobalStates.sidebarRightOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogButton {
|
||||||
|
buttonText: Translation.tr("Done")
|
||||||
|
onClicked: root.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,17 +3,9 @@ import qs.services
|
|||||||
import qs.services.network
|
import qs.services.network
|
||||||
import qs.modules.common
|
import qs.modules.common
|
||||||
import qs.modules.common.widgets
|
import qs.modules.common.widgets
|
||||||
import qs.modules.common.functions
|
|
||||||
import "./quickToggles/"
|
|
||||||
import "./wifiNetworks/"
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Qt5Compat.GraphicalEffects
|
|
||||||
import Quickshell.Io
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Wayland
|
|
||||||
import Quickshell.Hyprland
|
|
||||||
|
|
||||||
WindowDialog {
|
WindowDialog {
|
||||||
id: root
|
id: root
|
||||||
@@ -44,7 +36,6 @@ WindowDialog {
|
|||||||
return b.strength - a.strength;
|
return b.strength - a.strength;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// model: Network.wifiNetworks
|
|
||||||
delegate: WifiNetworkItem {
|
delegate: WifiNetworkItem {
|
||||||
required property WifiAccessPoint modelData
|
required property WifiAccessPoint modelData
|
||||||
wifiNetwork: modelData
|
wifiNetwork: modelData
|
||||||
|
|||||||
@@ -1,37 +1,21 @@
|
|||||||
import qs
|
import qs
|
||||||
import qs.modules.common
|
import qs.modules.common
|
||||||
import qs.modules.common.functions
|
|
||||||
import qs.modules.common.widgets
|
import qs.modules.common.widgets
|
||||||
import qs.services
|
import qs.services
|
||||||
import qs.services.network
|
import qs.services.network
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
|
||||||
|
|
||||||
RippleButton {
|
DialogListItem {
|
||||||
id: root
|
id: root
|
||||||
required property WifiAccessPoint wifiNetwork
|
required property WifiAccessPoint wifiNetwork
|
||||||
|
|
||||||
horizontalPadding: Appearance.rounding.large
|
active: (wifiNetwork?.askingPassword || wifiNetwork?.active)
|
||||||
verticalPadding: 12
|
|
||||||
implicitWidth: mainLayout.implicitWidth + horizontalPadding * 2
|
|
||||||
implicitHeight: mainLayout.implicitHeight + verticalPadding * 2
|
|
||||||
Behavior on implicitHeight {
|
|
||||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
|
||||||
}
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
buttonRadius: 0
|
|
||||||
colBackground: ColorUtils.transparentize(Appearance.colors.colLayer3)
|
|
||||||
colBackgroundHover: (wifiNetwork?.askingPassword || wifiNetwork?.active) ? colBackground : Appearance.colors.colLayer3Hover
|
|
||||||
colRipple: Appearance.colors.colLayer3Active
|
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
Network.connectToWifiNetwork(wifiNetwork);
|
Network.connectToWifiNetwork(wifiNetwork);
|
||||||
}
|
}
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
contentItem: ColumnLayout {
|
||||||
id: mainLayout
|
|
||||||
anchors {
|
anchors {
|
||||||
fill: parent
|
fill: parent
|
||||||
topMargin: root.verticalPadding
|
topMargin: root.verticalPadding
|
||||||
@@ -52,8 +36,9 @@ RippleButton {
|
|||||||
}
|
}
|
||||||
StyledText {
|
StyledText {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: root.wifiNetwork?.ssid ?? Translation.tr("Unknown")
|
|
||||||
color: Appearance.colors.colOnSurfaceVariant
|
color: Appearance.colors.colOnSurfaceVariant
|
||||||
|
elide: Text.ElideRight
|
||||||
|
text: root.wifiNetwork?.ssid ?? Translation.tr("Unknown")
|
||||||
}
|
}
|
||||||
MaterialSymbol {
|
MaterialSymbol {
|
||||||
visible: (root.wifiNetwork?.isSecure || root.wifiNetwork?.active) ?? false
|
visible: (root.wifiNetwork?.isSecure || root.wifiNetwork?.active) ?? false
|
||||||
@@ -65,8 +50,8 @@ RippleButton {
|
|||||||
|
|
||||||
ColumnLayout { // Password
|
ColumnLayout { // Password
|
||||||
id: passwordPrompt
|
id: passwordPrompt
|
||||||
visible: root.wifiNetwork?.askingPassword ?? false
|
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
|
visible: root.wifiNetwork?.askingPassword ?? false
|
||||||
|
|
||||||
MaterialTextField {
|
MaterialTextField {
|
||||||
id: passwordField
|
id: passwordField
|
||||||
@@ -107,8 +92,8 @@ RippleButton {
|
|||||||
|
|
||||||
ColumnLayout { // Public wifi login page
|
ColumnLayout { // Public wifi login page
|
||||||
id: publicWifiPortal
|
id: publicWifiPortal
|
||||||
visible: root.wifiNetwork?.active && (root.wifiNetwork?.security ?? "").trim().length === 0
|
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
|
visible: root.wifiNetwork?.active && (root.wifiNetwork?.security ?? "").trim().length === 0
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
DialogButton {
|
DialogButton {
|
||||||
@@ -124,5 +109,9 @@ RippleButton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import QtQuick
|
|||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property bool enabled: Bluetooth.defaultAdapter?.enabled
|
readonly property bool enabled: Bluetooth.defaultAdapter?.enabled ?? false
|
||||||
readonly property BluetoothDevice firstActiveDevice: Bluetooth.defaultAdapter?.devices.values.find(device => device.connected) ?? null
|
readonly property BluetoothDevice firstActiveDevice: Bluetooth.defaultAdapter?.devices.values.find(device => device.connected) ?? null
|
||||||
readonly property int activeDeviceCount: Bluetooth.defaultAdapter?.devices.values.filter(device => device.connected).length ?? 0
|
readonly property int activeDeviceCount: Bluetooth.defaultAdapter?.devices.values.filter(device => device.connected).length ?? 0
|
||||||
readonly property bool connected: Bluetooth.devices.values.some(d => d.connected)
|
readonly property bool connected: Bluetooth.devices.values.some(d => d.connected)
|
||||||
|
|||||||
Reference in New Issue
Block a user