diff options
Diffstat (limited to 'modules/bar/popouts')
| -rw-r--r-- | modules/bar/popouts/Battery.qml | 1 | ||||
| -rw-r--r-- | modules/bar/popouts/Bluetooth.qml | 1 | ||||
| -rw-r--r-- | modules/bar/popouts/Content.qml | 7 | ||||
| -rw-r--r-- | modules/bar/popouts/KbLayout.qml | 28 | ||||
| -rw-r--r-- | modules/bar/popouts/Network.qml | 20 | ||||
| -rw-r--r-- | modules/bar/popouts/TrayMenu.qml | 4 | ||||
| -rw-r--r-- | modules/bar/popouts/Wrapper.qml | 6 | ||||
| -rw-r--r-- | modules/bar/popouts/kblayout/KbLayout.qml | 158 | ||||
| -rw-r--r-- | modules/bar/popouts/kblayout/KbLayoutModel.qml | 200 |
9 files changed, 373 insertions, 52 deletions
diff --git a/modules/bar/popouts/Battery.qml b/modules/bar/popouts/Battery.qml index 35d32c5..ac975e1 100644 --- a/modules/bar/popouts/Battery.qml +++ b/modules/bar/popouts/Battery.qml @@ -40,7 +40,6 @@ Column { anchors.horizontalCenter: parent.horizontalCenter active: PowerProfiles.degradationReason !== PerformanceDegradationReason.None - asynchronous: true height: active ? (item?.implicitHeight ?? 0) : 0 diff --git a/modules/bar/popouts/Bluetooth.qml b/modules/bar/popouts/Bluetooth.qml index 91ac560..676da82 100644 --- a/modules/bar/popouts/Bluetooth.qml +++ b/modules/bar/popouts/Bluetooth.qml @@ -142,7 +142,6 @@ ColumnLayout { } Loader { - asynchronous: true active: device.modelData.bonded sourceComponent: Item { implicitWidth: connectBtn.implicitWidth diff --git a/modules/bar/popouts/Content.qml b/modules/bar/popouts/Content.qml index da993fa..c9a7c5d 100644 --- a/modules/bar/popouts/Content.qml +++ b/modules/bar/popouts/Content.qml @@ -6,6 +6,8 @@ import Quickshell import Quickshell.Services.SystemTray import QtQuick +import "./kblayout" + Item { id: root @@ -114,9 +116,12 @@ Item { Popout { name: "kblayout" - sourceComponent: KbLayout {} + sourceComponent: KbLayout { + wrapper: root.wrapper + } } + Popout { name: "lockstatus" sourceComponent: LockStatus {} diff --git a/modules/bar/popouts/KbLayout.qml b/modules/bar/popouts/KbLayout.qml deleted file mode 100644 index ace5af2..0000000 --- a/modules/bar/popouts/KbLayout.qml +++ /dev/null @@ -1,28 +0,0 @@ -import qs.components -import qs.components.controls -import qs.services -import qs.config -import Quickshell -import QtQuick.Layouts - -ColumnLayout { - id: root - - spacing: Appearance.spacing.normal - - StyledText { - Layout.topMargin: Appearance.padding.normal - Layout.rightMargin: Appearance.padding.normal - text: qsTr("Keyboard layout: %1").arg(Hypr.kbLayoutFull) - font.weight: 500 - } - - TextButton { - Layout.bottomMargin: Appearance.padding.normal - Layout.rightMargin: Appearance.padding.normal - Layout.fillWidth: true - - text: qsTr("Switch layout") - onClicked: Hypr.extras.message("switchxkblayout all next") - } -} diff --git a/modules/bar/popouts/Network.qml b/modules/bar/popouts/Network.qml index 0e99613..5b32e4a 100644 --- a/modules/bar/popouts/Network.qml +++ b/modules/bar/popouts/Network.qml @@ -131,17 +131,13 @@ ColumnLayout { Nmcli.disconnectFromNetwork(); } else { root.connectingToSsid = networkItem.modelData.ssid; - NetworkConnection.handleConnect( - networkItem.modelData, - null, - (network) => { - // Password is required - show password dialog - root.passwordNetwork = network; - root.showPasswordDialog = true; - root.wrapper.currentName = "wirelesspassword"; - } - ); - + NetworkConnection.handleConnect(networkItem.modelData, null, network => { + // Password is required - show password dialog + root.passwordNetwork = network; + root.showPasswordDialog = true; + root.wrapper.currentName = "wirelesspassword"; + }); + // Clear connecting state if connection succeeds immediately (saved profile) // This is handled by the onActiveChanged connection below } @@ -216,7 +212,7 @@ ColumnLayout { anchors.centerIn: parent strokeWidth: Appearance.padding.small / 2 bgColour: "transparent" - implicitHeight: parent.implicitHeight - Appearance.padding.smaller * 2 + implicitSize: parent.implicitHeight - Appearance.padding.smaller * 2 running: Nmcli.scanning } } diff --git a/modules/bar/popouts/TrayMenu.qml b/modules/bar/popouts/TrayMenu.qml index c87c0e1..9b743db 100644 --- a/modules/bar/popouts/TrayMenu.qml +++ b/modules/bar/popouts/TrayMenu.qml @@ -85,7 +85,6 @@ StackView { anchors.right: parent.right active: !item.modelData.isSeparator - asynchronous: true sourceComponent: Item { implicitHeight: label.implicitHeight @@ -118,7 +117,6 @@ StackView { anchors.left: parent.left active: item.modelData.icon !== "" - asynchronous: true sourceComponent: IconImage { implicitSize: label.implicitHeight @@ -155,7 +153,6 @@ StackView { anchors.right: parent.right active: item.modelData.hasChildren - asynchronous: true sourceComponent: MaterialIcon { text: "chevron_right" @@ -169,7 +166,6 @@ StackView { Loader { active: menu.isSubMenu - asynchronous: true sourceComponent: Item { implicitWidth: back.implicitWidth diff --git a/modules/bar/popouts/Wrapper.qml b/modules/bar/popouts/Wrapper.qml index 5ef4f9d..fc74222 100644 --- a/modules/bar/popouts/Wrapper.qml +++ b/modules/bar/popouts/Wrapper.qml @@ -35,8 +35,8 @@ Item { if (mode === "winfo") { detachedMode = mode; } else { - detachedMode = "any"; queuedMode = mode; + detachedMode = "any"; } focus = true; } @@ -101,7 +101,6 @@ Item { id: content shouldBeActive: root.hasCurrent && !root.detachedMode - asynchronous: true anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter @@ -112,7 +111,6 @@ Item { Comp { shouldBeActive: root.detachedMode === "winfo" - asynchronous: true anchors.centerIn: parent sourceComponent: WindowInfo { @@ -123,7 +121,6 @@ Item { Comp { shouldBeActive: root.detachedMode === "any" - asynchronous: true anchors.centerIn: parent sourceComponent: ControlCenter { @@ -173,7 +170,6 @@ Item { property bool shouldBeActive - asynchronous: true active: false opacity: 0 diff --git a/modules/bar/popouts/kblayout/KbLayout.qml b/modules/bar/popouts/kblayout/KbLayout.qml new file mode 100644 index 0000000..f612f58 --- /dev/null +++ b/modules/bar/popouts/kblayout/KbLayout.qml @@ -0,0 +1,158 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.components +import qs.components.controls +import qs.services +import qs.config +import qs.utils + +import "." + +ColumnLayout { + id: root + + required property Item wrapper + + spacing: Appearance.spacing.small + width: Config.bar.sizes.kbLayoutWidth + + KbLayoutModel { id: kb } + + function refresh() { kb.refresh() } + Component.onCompleted: kb.start() + + StyledText { + Layout.topMargin: Appearance.padding.normal + Layout.rightMargin: Appearance.padding.small + text: qsTr("Keyboard Layouts") + font.weight: 500 + } + + ListView { + id: list + model: kb.visibleModel + + Layout.fillWidth: true + Layout.rightMargin: Appearance.padding.small + Layout.topMargin: Appearance.spacing.small + + clip: true + interactive: true + implicitHeight: Math.min(contentHeight, 320) + visible: kb.visibleModel.count > 0 + spacing: Appearance.spacing.small + + add: Transition { + NumberAnimation { properties: "opacity"; from: 0; to: 1; duration: 140 } + NumberAnimation { properties: "y"; duration: 180; easing.type: Easing.OutCubic } + } + remove: Transition { NumberAnimation { properties: "opacity"; to: 0; duration: 100 } } + move: Transition { NumberAnimation { properties: "y"; duration: 180; easing.type: Easing.OutCubic } } + displaced: Transition { NumberAnimation { properties: "y"; duration: 180; easing.type: Easing.OutCubic } } + + delegate: Item { + required property int layoutIndex + required property string label + + width: list.width + height: Math.max(36, rowText.implicitHeight + Appearance.padding.small * 2) + + readonly property bool isDisabled: layoutIndex > 3 + + StateLayer { + id: layer + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + implicitHeight: parent.height - 4 + + radius: Appearance.rounding.full + enabled: !isDisabled + + function onClicked(): void { + if (!isDisabled) + kb.switchTo(layoutIndex); + } + } + + StyledText { + id: rowText + anchors.verticalCenter: layer.verticalCenter + anchors.left: layer.left + anchors.right: layer.right + anchors.leftMargin: Appearance.padding.small + anchors.rightMargin: Appearance.padding.small + text: label + elide: Text.ElideRight + opacity: isDisabled ? 0.4 : 1.0 + } + + ToolTip.visible: isDisabled && layer.containsMouse + ToolTip.text: "XKB limitation: maximum 4 layouts allowed" + } + } + + Rectangle { + visible: kb.activeLabel.length > 0 + Layout.fillWidth: true + Layout.rightMargin: Appearance.padding.small + Layout.topMargin: Appearance.spacing.small + + height: 1 + color: Colours.palette.m3onSurfaceVariant + opacity: 0.35 + } + + RowLayout { + id: activeRow + + visible: kb.activeLabel.length > 0 + Layout.fillWidth: true + Layout.rightMargin: Appearance.padding.small + Layout.topMargin: Appearance.spacing.small + spacing: Appearance.spacing.small + + opacity: 1 + scale: 1 + + MaterialIcon { + text: "keyboard" + color: Colours.palette.m3primary + } + + StyledText { + Layout.fillWidth: true + text: kb.activeLabel + elide: Text.ElideRight + font.weight: 500 + color: Colours.palette.m3primary + } + + Connections { + target: kb + function onActiveLabelChanged() { + if (!activeRow.visible) + return; + popIn.restart(); + } + } + + SequentialAnimation { + id: popIn + running: false + + ParallelAnimation { + NumberAnimation { target: activeRow; property: "opacity"; to: 0.0; duration: 70 } + NumberAnimation { target: activeRow; property: "scale"; to: 0.92; duration: 70 } + } + + ParallelAnimation { + NumberAnimation { target: activeRow; property: "opacity"; to: 1.0; duration: 160; easing.type: Easing.OutCubic } + NumberAnimation { target: activeRow; property: "scale"; to: 1.0; duration: 220; easing.type: Easing.OutBack } + } + } + } +} diff --git a/modules/bar/popouts/kblayout/KbLayoutModel.qml b/modules/bar/popouts/kblayout/KbLayoutModel.qml new file mode 100644 index 0000000..41e45b3 --- /dev/null +++ b/modules/bar/popouts/kblayout/KbLayoutModel.qml @@ -0,0 +1,200 @@ +pragma ComponentBehavior: Bound + +import QtQuick + +import Quickshell +import Quickshell.Io + +import qs.config +import Caelestia + +Item { + id: model + visible: false + + ListModel { id: _visibleModel } + property alias visibleModel: _visibleModel + + property string activeLabel: "" + property int activeIndex: -1 + + function start() { + _xkbXmlBase.running = true; + _getKbLayoutOpt.running = true; + } + + function refresh() { + _notifiedLimit = false; + _getKbLayoutOpt.running = true; + } + + function switchTo(idx) { + _switchProc.command = ["hyprctl", "switchxkblayout", "all", String(idx)]; + _switchProc.running = true; + } + + ListModel { id: _layoutsModel } + + property var _xkbMap: ({}) + property bool _notifiedLimit: false + + Process { + id: _xkbXmlBase + command: ["xmllint", "--xpath", "//layout/configItem[name and description]", "/usr/share/X11/xkb/rules/base.xml"] + stdout: StdioCollector { onStreamFinished: _buildXmlMap(text) } + onRunningChanged: if (!running && (typeof exitCode !== "undefined") && exitCode !== 0) _xkbXmlEvdev.running = true + } + + Process { + id: _xkbXmlEvdev + command: ["xmllint", "--xpath", "//layout/configItem[name and description]", "/usr/share/X11/xkb/rules/evdev.xml"] + stdout: StdioCollector { onStreamFinished: _buildXmlMap(text) } + } + + function _buildXmlMap(xml) { + const map = {}; + + const re = /<name>\s*([^<]+?)\s*<\/name>[\s\S]*?<description>\s*([^<]+?)\s*<\/description>/g; + + let m; + while ((m = re.exec(xml)) !== null) { + const code = (m[1] || "").trim(); + const desc = (m[2] || "").trim(); + if (!code || !desc) continue; + map[code] = _short(desc); + } + + if (Object.keys(map).length === 0) + return; + + _xkbMap = map; + + if (_layoutsModel.count > 0) { + const tmp = []; + for (let i = 0; i < _layoutsModel.count; i++) { + const it = _layoutsModel.get(i); + tmp.push({ layoutIndex: it.layoutIndex, token: it.token, label: _pretty(it.token) }); + } + _layoutsModel.clear(); + tmp.forEach(t => _layoutsModel.append(t)); + _fetchActiveLayouts.running = true; + } + } + + function _short(desc) { + const m = desc.match(/^(.*)\((.*)\)$/); + if (!m) return desc; + const lang = m[1].trim(); + const region = m[2].trim(); + const code = (region.split(/[,\s-]/)[0] || region).slice(0, 2).toUpperCase(); + return `${lang} (${code})`; + } + + Process { + id: _getKbLayoutOpt + command: ["hyprctl", "-j", "getoption", "input:kb_layout"] + stdout: StdioCollector { + onStreamFinished: { + try { + const j = JSON.parse(text); + const raw = (j?.str || j?.value || "").toString().trim(); + if (raw.length) { + _setLayouts(raw); + _fetchActiveLayouts.running = true; + return; + } + } catch (e) {} + _fetchLayoutsFromDevices.running = true; + } + } + } + + Process { + id: _fetchLayoutsFromDevices + command: ["hyprctl", "-j", "devices"] + stdout: StdioCollector { + onStreamFinished: { + try { + const dev = JSON.parse(text); + const kb = dev?.keyboards?.find(k => k.main) || dev?.keyboards?.[0]; + const raw = (kb?.layout || "").trim(); + if (raw.length) _setLayouts(raw); + } catch (e) {} + _fetchActiveLayouts.running = true; + } + } + } + + Process { + id: _fetchActiveLayouts + command: ["hyprctl", "-j", "devices"] + stdout: StdioCollector { + onStreamFinished: { + try { + const dev = JSON.parse(text); + const kb = dev?.keyboards?.find(k => k.main) || dev?.keyboards?.[0]; + const idx = kb?.active_layout_index ?? -1; + + activeIndex = idx >= 0 ? idx : -1; + activeLabel = + (idx >= 0 && idx < _layoutsModel.count) + ? _layoutsModel.get(idx).label + : ""; + } catch (e) { + activeIndex = -1; + activeLabel = ""; + } + + _rebuildVisible(); + } + } + } + + Process { + id: _switchProc + onRunningChanged: if (!running) _fetchActiveLayouts.running = true + } + + function _setLayouts(raw) { + const parts = raw.split(",").map(s => s.trim()).filter(Boolean); + _layoutsModel.clear(); + + const seen = new Set(); + let idx = 0; + + for (const p of parts) { + if (seen.has(p)) continue; + seen.add(p); + _layoutsModel.append({ layoutIndex: idx, token: p, label: _pretty(p) }); + idx++; + } + } + + function _rebuildVisible() { + _visibleModel.clear(); + + let arr = []; + for (let i = 0; i < _layoutsModel.count; i++) + arr.push(_layoutsModel.get(i)); + + arr = arr.filter(i => i.layoutIndex !== activeIndex); + arr.forEach(i => _visibleModel.append(i)); + + if (!Config.utilities.toasts.kbLimit) + return; + + if (_layoutsModel.count > 4) { + Toaster.toast( + qsTr("Keyboard layout limit"), + qsTr("XKB supports only 4 layouts at a time"), + "warning" + ); + } + } + + function _pretty(token) { + const code = token.replace(/\(.*\)$/, "").trim(); + if (_xkbMap[code]) return code.toUpperCase() + " - " + _xkbMap[code]; + return code.toUpperCase() + " - " + code; + } +} |