From 130e613eb5ecebf0952afd552f74a8c595b33d05 Mon Sep 17 00:00:00 2001 From: zach Date: Thu, 11 Jun 2026 14:35:42 +0200 Subject: [PATCH] clipboard history using cliphist --- Components/ButtonBase.qml | 82 ++++++++++ Components/IconButton.qml | 75 +++------ Drawers/Interactions.qml | 7 +- Drawers/Panels.qml | 11 ++ Drawers/Windows.qml | 17 +- Helpers/ClipHistory.qml | 150 ++++++++++++++++++ Modules/ClipWrapper.qml | 6 - Modules/Clipboard/Content.qml | 148 +++++++++++++++++ Modules/Clipboard/Wrapper.qml | 42 +++++ .../Sidebar/Utils/Cards/Record.qml | 6 +- .../Sidebar/Utils/Cards/Toggles.qml | 4 +- .../Settings/Controls/SettingBarEntryList.qml | 4 +- Modules/Shortcuts.qml | 9 ++ Modules/Wrapper.qml | 1 - 14 files changed, 492 insertions(+), 70 deletions(-) create mode 100644 Components/ButtonBase.qml create mode 100644 Helpers/ClipHistory.qml create mode 100644 Modules/Clipboard/Content.qml create mode 100644 Modules/Clipboard/Wrapper.qml diff --git a/Components/ButtonBase.qml b/Components/ButtonBase.qml new file mode 100644 index 0000000..3fdd628 --- /dev/null +++ b/Components/ButtonBase.qml @@ -0,0 +1,82 @@ +import QtQuick +import qs.Config + +CustomRect { + id: root + + enum ButtonType { + Filled, + Tonal, + Text + } + + property color activeColor + property color activeOnColor + property bool checked + property real checkedRadius: Appearance.rounding.medium + property real defaultRadius: Appearance.rounding.large + property color disabledColor: Qt.alpha(DynamicColors.palette.m3onSurface, 0.1) + property color disabledOnColor: Qt.alpha(DynamicColors.palette.m3onSurface, 0.38) + property bool fillWidth + property real horizontalPadding: padding + readonly property alias hovered: stateLayer.containsMouse + required implicitHeight + required implicitWidth + property color inactiveColor + property color inactiveOnColor + property bool internalChecked + property bool isRound + property bool isToggle + readonly property color onColor: !enabled ? disabledOnColor : internalChecked ? activeOnColor : inactiveOnColor + property real padding + readonly property alias pressed: stateLayer.pressed + property real pressedRadius: Appearance.rounding.small + readonly property alias radiusAnim: radiusAnim + property bool radiusMorph: true + property alias shapeMorph: stateLayer.shapeMorph + property real shapeMorphExpansion: shapeMorph && pressed ? 24 : 0 + readonly property alias stateLayer: stateLayer + property int type: ButtonBase.Filled + property real verticalPadding: padding + + signal clicked + + color: type === ButtonBase.Text ? "transparent" : !enabled ? disabledColor : internalChecked ? activeColor : inactiveColor + radius: { + if (radiusMorph && pressed) + return pressedRadius; + if (internalChecked) + return checkedRadius; + if (isRound) + return (height || implicitHeight) / 2 * Math.min(1, Appearance.rounding.scale); + return defaultRadius; + } + + Behavior on radius { + Anim { + id: radiusAnim + + type: Anim.DefaultEffects + } + } + Behavior on shapeMorphExpansion { + Anim { + type: Anim.FastSpatial + } + } + + onCheckedChanged: internalChecked = checked + + StateLayer { + id: stateLayer + + color: root.internalChecked ? root.activeOnColor : root.inactiveOnColor + enabled: enabled + + onClicked: { + if (root.isToggle) + root.internalChecked = !root.internalChecked; + root.clicked(); + } + } +} diff --git a/Components/IconButton.qml b/Components/IconButton.qml index fe99571..2727b8f 100644 --- a/Components/IconButton.qml +++ b/Components/IconButton.qml @@ -1,76 +1,45 @@ -import qs.Config import QtQuick +import qs.Config -CustomRect { +ButtonBase { id: root - enum Type { - Filled, - Tonal, - Text - } - - property color activeColour: type === IconButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondary - property color activeOnColour: type === IconButton.Filled ? DynamicColors.palette.m3onPrimary : type === IconButton.Tonal ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3primary - property bool checked - property color disabledColour: Qt.alpha(DynamicColors.palette.m3onSurface, 0.1) - property color disabledOnColour: Qt.alpha(DynamicColors.palette.m3onSurface, 0.38) property alias font: label.font property alias icon: label.text - property color inactiveColour: { - if (!toggle && type === IconButton.Filled) + readonly property alias label: label + + activeColor: type === IconButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondary + activeOnColor: type === IconButton.Filled ? DynamicColors.palette.m3onPrimary : type === IconButton.Tonal ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3primary + implicitHeight: { + const h = label.implicitHeight + padding * 2; + if (h % 2 !== 0) + return h + 1; + return h; + } + implicitWidth: implicitHeight + inactiveColor: { + if (!isToggle && type === IconButton.Filled) return DynamicColors.palette.m3primary; return type === IconButton.Filled ? DynamicColors.tPalette.m3surfaceContainer : DynamicColors.palette.m3secondaryContainer; } - property color inactiveOnColour: { - if (!toggle && type === IconButton.Filled) + inactiveOnColor: { + if (!isToggle && type === IconButton.Filled) return DynamicColors.palette.m3onPrimary; return type === IconButton.Tonal ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurfaceVariant; } - property bool internalChecked - property alias label: label - property real padding: type === IconButton.Text ? 10 / 2 : 7 - property alias radiusAnim: radiusAnim - property alias stateLayer: stateLayer - property bool toggle - property int type: IconButton.Filled - - signal clicked - - color: type === IconButton.Text ? "transparent" : !enabled ? disabledColour : internalChecked ? activeColour : inactiveColour - implicitHeight: label.implicitHeight + padding * 2 - implicitWidth: implicitHeight - radius: internalChecked ? 6 : (implicitHeight / 2 * Math.min(1, 1)) * Appearance.rounding.scale - - Behavior on radius { - Anim { - id: radiusAnim - } - } - - onCheckedChanged: internalChecked = checked - - StateLayer { - id: stateLayer - - color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour - - onClicked: { - if (root.toggle) - root.internalChecked = !root.internalChecked; - root.clicked(); - } - } + padding: type === IconButton.Text ? Appearance.padding.extraSmall / 2 : Appearance.padding.small MaterialIcon { id: label anchors.centerIn: parent - color: !root.enabled ? root.disabledOnColour : root.internalChecked ? root.activeOnColour : root.inactiveOnColour - fill: !root.toggle || root.internalChecked ? 1 : 0 + anchors.verticalCenterOffset: 1 + color: root.onColor + fill: !root.isToggle || root.internalChecked ? 1 : 0 Behavior on fill { Anim { + type: Anim.DefaultEffects } } } diff --git a/Drawers/Interactions.qml b/Drawers/Interactions.qml index 56b1cb2..0e9eb4d 100644 --- a/Drawers/Interactions.qml +++ b/Drawers/Interactions.qml @@ -74,7 +74,7 @@ Item { const dragX = x - centroid.pressPosition.x; const dragY = y - centroid.pressPosition.y; - if (centroid.pressPosition.y >= root.screen.height - Config.barConfig.border && dragY < -200) + if (centroid.pressPosition.y >= root.screen.height - Config.barConfig.border && centroid.pressPosition.x > root.screen.width / 5 && dragY < -200) root.visibilities.launcher = true; if (root.singleGestureTriggered) @@ -90,7 +90,10 @@ Item { } } - if (!Config.dock.hoverToReveal && centroid.pressPosition.y > root.screen.height - root.bar.implicitHeight) + if (centroid.pressPosition.y > root.screen.height - Config.barConfig.border && centroid.pressPosition.x < root.screen.width / 5 && dragY < -50) + root.visibilities.clipboard = true; + + if (!Config.dock.hoverToReveal && centroid.pressPosition.y > root.screen.height - root.bar.implicitHeight && centroid.pressPosition.x > root.screen.width / 5) if (dragY < -10) { root.visibilities.dock = true; root.singleGestureTriggered = true; diff --git a/Drawers/Panels.qml b/Drawers/Panels.qml index 5c84747..f693b0f 100644 --- a/Drawers/Panels.qml +++ b/Drawers/Panels.qml @@ -13,12 +13,14 @@ import qs.Modules.Resources as Resources import qs.Modules.Settings as Settings import qs.Modules.Drawing as Drawing import qs.Modules.Dock as Dock +import qs.Modules.Clipboard as Clipboard import qs.Config Item { id: root required property Item bar + readonly property alias clipboard: clipboard readonly property alias dashboard: dashboard readonly property alias dashboardWrapper: dashboardWrapper readonly property alias dock: dock @@ -207,4 +209,13 @@ Item { screen: root.screen visibilities: root.visibilities } + + Clipboard.Wrapper { + id: clipboard + + anchors.bottom: parent.bottom + anchors.left: parent.left + screen: root.screen + visibilities: root.visibilities + } } diff --git a/Drawers/Windows.qml b/Drawers/Windows.qml index 5d5b71f..c08ff63 100644 --- a/Drawers/Windows.qml +++ b/Drawers/Windows.qml @@ -40,6 +40,7 @@ CustomWindow { visibilities.settings = false; visibilities.resources = false; visibilities.dock = false; + visibilities.clipboard = false; panels.popouts.hasCurrent = false; } onHasFullscreenChanged: { @@ -49,6 +50,7 @@ CustomWindow { visibilities.osd = false; visibilities.settings = false; visibilities.resources = false; + visibilities.clipboard = false; visibilities.dock = false; panels.popouts.hasCurrent = false; } @@ -96,7 +98,7 @@ CustomWindow { HyprlandFocusGrab { id: focusGrab - active: visibilities.dock || visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu")) + active: visibilities.dock || visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.clipboard || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu")) windows: [root] onCleared: { @@ -106,6 +108,7 @@ CustomWindow { visibilities.osd = false; visibilities.settings = false; visibilities.resources = false; + visibilities.clipboard = false; visibilities.dock = false; panels.popouts.hasCurrent = false; } @@ -115,6 +118,7 @@ CustomWindow { id: visibilities property bool bar + property bool clipboard property bool dashboard property bool dock property bool isDrawing @@ -303,6 +307,14 @@ CustomWindow { panel: panels.drawing radius: Appearance.rounding.normal } + + PanelBg { + id: clipboardBg + + deformAmount: 0.03 + panel: panels.clipboard + radius: 29 + } } Loader { @@ -355,6 +367,9 @@ CustomWindow { screen: root.screen visibilities: visibilities + clipboard.transform: Matrix4x4 { + matrix: clipboardBg.deformMatrix + } dashboard.transform: Matrix4x4 { matrix: dashBg.deformMatrix } diff --git a/Helpers/ClipHistory.qml b/Helpers/ClipHistory.qml new file mode 100644 index 0000000..a847f9e --- /dev/null +++ b/Helpers/ClipHistory.qml @@ -0,0 +1,150 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Config +import "../scripts/fuzzysort.js" as Fuzzy + +Singleton { + id: root + + property string cliphistBinary: "cliphist" + property list entries: [] + property real pasteDelay: 0.05 + readonly property var preparedEntries: entries.map(a => ({ + name: Fuzzy.prepare(`${a.replace(/^\s*\S+\s+/, "")}`), + entry: a + })) + property string pressPasteCommand: "ydotool key -d 1 29:1 47:1 47:0 29:0" + property real scoreThreshold: 0.2 + + function copy(entry) { + if (root.cliphistBinary.includes("cliphist")) + Quickshell.execDetached(["bash", "-c", `printf '${shellSingleQuoteEscape(entry)}' | ${root.cliphistBinary} decode | wl-copy`]); + else { + const entryNumber = entry.split("\t")[0]; + Quickshell.execDetached(["bash", "-c", `${root.cliphistBinary} decode ${entryNumber} | wl-copy`]); + } + } + + function deleteEntry(entry) { + deleteProc.deleteEntry(entry); + } + + function entryIsImage(entry) { + return !!(/^\d+\t\[\[.*binary data.*\d+x\d+.*\]\]$/.test(entry)); + } + + function fuzzyQuery(search: string): var { + if (search.trim() === "") { + return entries; + } + return Fuzzy.go(search, preparedEntries, { + all: true, + key: "name" + }).map(r => { + return r.obj.entry; + }); + } + + function paste(entry) { + if (root.cliphistBinary.includes("cliphist")) + Quickshell.execDetached(["bash", "-c", `printf '${shellSingleQuoteEscape(entry)}' | ${root.cliphistBinary} decode | wl-copy && wl-paste`]); + else { + const entryNumber = entry.split("\t")[0]; + Quickshell.execDetached(["bash", "-c", `${root.cliphistBinary} decode ${entryNumber} | wl-copy; ${root.pressPasteCommand}`]); + } + } + + function refresh() { + readProc.buffer = []; + readProc.running = true; + } + + function shellSingleQuoteEscape(str) { + return String(str).replace(/'/g, "'\\''"); + } + + function wipe() { + wipeProc.running = true; + } + + Process { + id: deleteProc + + property string entry: "" + + function deleteEntry(entry) { + deleteProc.entry = entry; + deleteProc.running = true; + deleteProc.entry = ""; + } + + command: ["bash", "-c", `echo '${root.shellSingleQuoteEscape(deleteProc.entry)}' | ${root.cliphistBinary} delete`] + + onExited: (exitCode, exitStatus) => { + root.refresh(); + } + } + + Process { + id: wipeProc + + command: [root.cliphistBinary, "wipe"] + + onExited: (exitCode, exitStatus) => { + root.refresh(); + } + } + + Connections { + function onClipboardTextChanged() { + delayedUpdateTimer.restart(); + } + + target: Quickshell + } + + Timer { + id: delayedUpdateTimer + + interval: 50 + repeat: false + + onTriggered: { + root.refresh(); + } + } + + Process { + id: readProc + + property list buffer: [] + + command: [root.cliphistBinary, "list"] + + stdout: SplitParser { + onRead: line => { + readProc.buffer.push(line); + } + } + + onExited: (exitCode, exitStatus) => { + if (exitCode === 0) { + root.entries = readProc.buffer; + } else { + console.error("[Cliphist] Failed to refresh with code", exitCode, "and status", exitStatus); + } + } + } + + IpcHandler { + function update(): void { + root.refresh(); + } + + target: "cliphistService" + } +} diff --git a/Modules/ClipWrapper.qml b/Modules/ClipWrapper.qml index 6b735f5..904f27a 100644 --- a/Modules/ClipWrapper.qml +++ b/Modules/ClipWrapper.qml @@ -17,21 +17,15 @@ Item { implicitWidth: content.implicitWidth visible: width > 0 && height > 0 x: { - if (content.isDetached) - return (parent.width - content.nonAnimWidth) / 2; - const off = content.currentCenter - Config.barConfig.border - content.nonAnimWidth / 2; const diff = parent.width - Math.floor(off + content.nonAnimWidth); if (diff < 0) return off + diff; return Math.floor(Math.max(off, 0)); } - y: content.isDetached ? (parent.height - content.nonAnimHeight) / 2 : 0 Behavior on offsetScale { Anim { - duration: Appearance.anim.durations.expressiveDefaultSpatial - easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial } } Behavior on x { diff --git a/Modules/Clipboard/Content.qml b/Modules/Clipboard/Content.qml new file mode 100644 index 0000000..f4685eb --- /dev/null +++ b/Modules/Clipboard/Content.qml @@ -0,0 +1,148 @@ +pragma ComponentBehavior: Bound + +import Quickshell +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Helpers +import qs.Config + +Item { + id: root + + required property ShellScreen screen + required property PersistentProperties visibilities + + implicitHeight: screen.height / 2 + implicitWidth: 500 + + Component.onCompleted: { + if (!ClipHistory.entries.length > 0) + ClipHistory.refresh(); + searchField.forceActiveFocus(); + } + + CustomClippingRect { + id: search + + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + color: DynamicColors.tPalette.m3surfaceContainer + implicitHeight: 50 + radius: Appearance.rounding.full + + MaterialIcon { + id: searchIcon + + anchors.left: parent.left + anchors.margins: Appearance.padding.large + anchors.verticalCenter: parent.verticalCenter + text: "search" + } + + CustomTextField { + id: searchField + + anchors.bottom: parent.bottom + anchors.left: searchIcon.right + anchors.leftMargin: Appearance.spacing.small + anchors.right: parent.right + anchors.top: parent.top + color: DynamicColors.palette.m3onSurface + placeholderText: "Search clipboard history..." + } + } + + CustomClippingRect { + id: entries + + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.top: search.bottom + anchors.topMargin: Appearance.spacing.normal + radius: Appearance.rounding.normal + + ListView { + id: view + + anchors.fill: parent + spacing: Appearance.spacing.normal + + delegate: RowLayout { + id: clipItem + + required property string modelData + + height: 50 + width: view.width + + CustomClippingRect { + id: textRect + + Layout.fillHeight: true + Layout.fillWidth: true + // Layout.preferredWidth: implicitWidth + (textLayer.pressed ? 18 : 0) + // implicitWidth: 250 + radius: textLayer.pressed ? (Appearance.rounding.small / 2) : Appearance.rounding.small + + Behavior on Layout.preferredWidth { + Anim { + type: Anim.FastEffects + } + } + Behavior on radius { + Anim { + type: Anim.FastEffects + } + } + + CustomText { + id: text + + anchors.left: parent.left + anchors.margins: Appearance.padding.normal + anchors.verticalCenter: parent.verticalCenter + elide: Text.ElideRight + text: clipItem.modelData + } + + StateLayer { + id: textLayer + + onClicked: ClipHistory.copy(clipItem.modelData) + } + } + + IconButton { + Layout.fillHeight: true + Layout.margins: Appearance.padding.smallest + Layout.preferredWidth: height + icon: "content_copy" + isToggle: false + // implicitWidth: 30 + } + + IconButton { + Layout.fillHeight: true + Layout.margins: Appearance.padding.smallest + Layout.preferredWidth: height + icon: "delete" + inactiveColor: Qt.alpha(DynamicColors.palette.m3error, 0.8) + inactiveOnColor: DynamicColors.palette.m3onError + isToggle: false + } + } + model: ScriptModel { + values: { + const entries = ClipHistory.entries; + const search = searchField.text; + var regex = new RegExp(search, "i"); + + return entries.filter(n => regex.test(n)); + } + } + } + } +} diff --git a/Modules/Clipboard/Wrapper.qml b/Modules/Clipboard/Wrapper.qml new file mode 100644 index 0000000..6bac907 --- /dev/null +++ b/Modules/Clipboard/Wrapper.qml @@ -0,0 +1,42 @@ +pragma ComponentBehavior: Bound + +import Quickshell +import QtQuick +import qs.Components +import qs.Config + +Item { + id: root + + property int contentHeight + property real offsetScale: shouldBeActive ? 0 : 1 + required property ShellScreen screen + readonly property bool shouldBeActive: visibilities.clipboard + required property PersistentProperties visibilities + + anchors.bottomMargin: (-implicitHeight - 5) * offsetScale + implicitHeight: content.implicitHeight + Appearance.padding.normal * 2 + implicitWidth: content.implicitWidth + Appearance.padding.normal * 2 || 400 + opacity: 1 - offsetScale + visible: offsetScale < 1 + + Behavior on offsetScale { + Anim { + duration: Appearance.anim.durations.expressiveDefaultSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial + } + } + + Loader { + id: content + + active: root.shouldBeActive || root.visible + anchors.centerIn: parent + asynchronous: true + + sourceComponent: Content { + screen: root.screen + visibilities: root.visibilities + } + } +} diff --git a/Modules/Notifications/Sidebar/Utils/Cards/Record.qml b/Modules/Notifications/Sidebar/Utils/Cards/Record.qml index 1961e1f..16d1f3c 100644 --- a/Modules/Notifications/Sidebar/Utils/Cards/Record.qml +++ b/Modules/Notifications/Sidebar/Utils/Cards/Record.qml @@ -267,8 +267,8 @@ CustomRect { checked: Recorder.paused font.pointSize: Appearance.font.size.large icon: Recorder.paused ? "play_arrow" : "pause" + isToggle: true label.animate: true - toggle: true type: IconButton.Tonal onClicked: { @@ -280,8 +280,8 @@ CustomRect { IconButton { font.pointSize: Appearance.font.size.large icon: "stop" - inactiveColour: DynamicColors.palette.m3error - inactiveOnColour: DynamicColors.palette.m3onError + inactiveColor: DynamicColors.palette.m3error + inactiveOnColor: DynamicColors.palette.m3onError onClicked: Recorder.stop() } diff --git a/Modules/Notifications/Sidebar/Utils/Cards/Toggles.qml b/Modules/Notifications/Sidebar/Utils/Cards/Toggles.qml index 13c3fb5..93ad96c 100644 --- a/Modules/Notifications/Sidebar/Utils/Cards/Toggles.qml +++ b/Modules/Notifications/Sidebar/Utils/Cards/Toggles.qml @@ -101,11 +101,11 @@ CustomRect { component Toggle: IconButton { Layout.fillWidth: true Layout.preferredWidth: implicitWidth + (stateLayer.pressed ? 18 : internalChecked ? 7 : 0) - inactiveColour: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2) + inactiveColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2) + isToggle: true radius: stateLayer.pressed ? 6 / 2 : internalChecked ? 6 : 8 radiusAnim.duration: MaterialEasing.expressiveEffectsTime radiusAnim.easing.bezierCurve: MaterialEasing.expressiveEffects - toggle: true Behavior on Layout.preferredWidth { Anim { diff --git a/Modules/Settings/Controls/SettingBarEntryList.qml b/Modules/Settings/Controls/SettingBarEntryList.qml index 06c6338..6f29eea 100644 --- a/Modules/Settings/Controls/SettingBarEntryList.qml +++ b/Modules/Settings/Controls/SettingBarEntryList.qml @@ -221,11 +221,11 @@ Item { font: Appearance.font.family.sans // icon: root.iconForId(modelData.entry.id) icon: root.labelForId(modelData.entry.id) - inactiveColour: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2) + inactiveColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2) + isToggle: true radius: stateLayer.pressed ? 6 / 2 : internalChecked ? 6 : 8 radiusAnim.duration: MaterialEasing.expressiveEffectsTime radiusAnim.easing.bezierCurve: MaterialEasing.expressiveEffects - toggle: true visible: !["spacer", "upower", "dash", "audio"].some(prefix => modelData.entry.id.startsWith(prefix)) Behavior on Layout.preferredWidth { diff --git a/Modules/Shortcuts.qml b/Modules/Shortcuts.qml index 0a57d75..68c6662 100644 --- a/Modules/Shortcuts.qml +++ b/Modules/Shortcuts.qml @@ -59,4 +59,13 @@ Scope { visibilities.settings = !visibilities.settings; } } + + CustomShortcut { + name: "toggle-clipboard" + + onPressed: { + const visibilities = Visibilities.getForActive(); + visibilities.clipboard = !visibilities.clipboard; + } + } } diff --git a/Modules/Wrapper.qml b/Modules/Wrapper.qml index b45c609..776585e 100644 --- a/Modules/Wrapper.qml +++ b/Modules/Wrapper.qml @@ -27,7 +27,6 @@ Item { detachedMode = ""; } - focus: hasCurrent implicitHeight: nonAnimHeight implicitWidth: nonAnimWidth