From e9dbfa5ea1ce339b2182c9baf734a91d459077f8 Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Sun, 19 Apr 2026 11:06:49 +0200 Subject: [PATCH] Enable keyboard control in convergence dock Make the convergence dock fully usable from the keyboard. Tab now reaches Home, favorites, running tasks, and Overview. Left and right move across section boundaries, and Enter/Space triggers the same actions as mouse clicks. Also add accessible role/name/press actions for these controls so screen readers expose meaningful button semantics. --- .../homescreens/folio/qml/FavouritesBar.qml | 121 +++++++++++++++--- 1 file changed, 102 insertions(+), 19 deletions(-) diff --git a/containments/homescreens/folio/qml/FavouritesBar.qml b/containments/homescreens/folio/qml/FavouritesBar.qml index 20f10dd6..3e338c08 100644 --- a/containments/homescreens/folio/qml/FavouritesBar.qml +++ b/containments/homescreens/folio/qml/FavouritesBar.qml @@ -125,6 +125,7 @@ MouseArea { Rectangle { id: homeButton visible: root.convergenceMode + activeFocusOnTab: root.convergenceMode anchors.left: parent.left anchors.top: parent.top anchors.bottom: parent.bottom @@ -134,6 +135,26 @@ MouseArea { : (homeMouseArea.containsMouse ? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.1) : "transparent") radius: Kirigami.Units.cornerRadius + Accessible.role: Accessible.Button + Accessible.name: i18n("Home") + Accessible.onPressAction: MobileShellState.ShellDBusClient.openHomeScreen() + + Keys.onReturnPressed: MobileShellState.ShellDBusClient.openHomeScreen() + Keys.onEnterPressed: MobileShellState.ShellDBusClient.openHomeScreen() + Keys.onSpacePressed: MobileShellState.ShellDBusClient.openHomeScreen() + Keys.onRightPressed: { + let first = repeater.itemAt(0) + if (first) { first.keyboardFocus(); return } + let firstTask = taskRepeater.itemAt(0) + if (firstTask) { firstTask.forceActiveFocus(); return } + overviewButton.forceActiveFocus() + } + + KeyboardHighlight { + anchors.fill: parent + visible: homeButton.activeFocus + } + Kirigami.Icon { anchors.centerIn: parent width: Math.min(parent.width, parent.height) * 0.75 @@ -155,6 +176,7 @@ MouseArea { Rectangle { id: overviewButton visible: root.convergenceMode + activeFocusOnTab: root.convergenceMode anchors.right: parent.right anchors.top: parent.top anchors.bottom: parent.bottom @@ -164,6 +186,26 @@ MouseArea { : (overviewMouseArea.containsMouse ? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.1) : "transparent") radius: Kirigami.Units.cornerRadius + Accessible.role: Accessible.Button + Accessible.name: i18n("Overview") + Accessible.onPressAction: root.folio.triggerOverview() + + Keys.onReturnPressed: root.folio.triggerOverview() + Keys.onEnterPressed: root.folio.triggerOverview() + Keys.onSpacePressed: root.folio.triggerOverview() + Keys.onLeftPressed: { + let lastTask = taskRepeater.itemAt(taskRepeater.count - 1) + if (lastTask) { lastTask.forceActiveFocus(); return } + let lastFav = repeater.itemAt(repeater.count - 1) + if (lastFav) { lastFav.keyboardFocus(); return } + homeButton.forceActiveFocus() + } + + KeyboardHighlight { + anchors.fill: parent + visible: overviewButton.activeFocus + } + Kirigami.Icon { anchors.centerIn: parent width: Math.min(parent.width, parent.height) * 0.75 @@ -320,9 +362,12 @@ MouseArea { break; case Qt.Key_Left: if (isLocationBottom) { - let nextDelegate = repeater.itemAt(delegate.index - 1); - if (nextDelegate) { - nextDelegate.keyboardFocus(); + let prevDelegate = repeater.itemAt(delegate.index - 1); + if (prevDelegate) { + prevDelegate.keyboardFocus(); + event.accepted = true; + } else if (root.convergenceMode) { + homeButton.forceActiveFocus(); event.accepted = true; } } @@ -333,6 +378,14 @@ MouseArea { if (nextDelegate) { nextDelegate.keyboardFocus(); event.accepted = true; + } else if (root.convergenceMode) { + let firstTask = taskRepeater.itemAt(0); + if (firstTask) { + firstTask.forceActiveFocus(); + } else { + overviewButton.forceActiveFocus(); + } + event.accepted = true; } } break; @@ -852,9 +905,49 @@ MouseArea { required property int index required property var model + activeFocusOnTab: root.convergenceMode + readonly property bool isLocationBottom: folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Bottom readonly property string taskStorageId: root.runningTaskStorageId(taskDelegate.model) + Accessible.role: Accessible.Button + Accessible.name: taskDelegate.model.display || "" + Accessible.onPressAction: taskDelegate.activateTask() + + function activateTask() { + var winIds = taskDelegate.model.WinIdList + if (winIds && winIds.length > 1) { + if (thumbnailPopup.opened && thumbnailPopup.taskIndex === taskDelegate.index) { + thumbnailPopup.close() + } else { + thumbnailPopup.targetDelegate = taskDelegate + thumbnailPopup.taskIndex = taskDelegate.index + thumbnailPopup.windowIds = winIds + thumbnailPopup.isGroup = taskDelegate.model.IsGroupParent === true + thumbnailPopup.open() + } + } else { + thumbnailPopup.close() + tasksModel.requestActivate(tasksModel.makeModelIndex(taskDelegate.index)) + } + } + + Keys.onReturnPressed: taskDelegate.activateTask() + Keys.onEnterPressed: taskDelegate.activateTask() + Keys.onSpacePressed: taskDelegate.activateTask() + Keys.onLeftPressed: { + let prev = taskRepeater.itemAt(taskDelegate.index - 1) + if (prev) { prev.forceActiveFocus(); return } + let lastFav = repeater.itemAt(repeater.count - 1) + if (lastFav) { lastFav.keyboardFocus(); return } + homeButton.forceActiveFocus() + } + Keys.onRightPressed: { + let next = taskRepeater.itemAt(taskDelegate.index + 1) + if (next) { next.forceActiveFocus(); return } + overviewButton.forceActiveFocus() + } + // Position after all favourites property double fromCenterValue: (repeater.count + taskDelegate.index) - (root.totalItemCount / 2) Behavior on fromCenterValue { @@ -881,6 +974,11 @@ MouseArea { : (taskMouseArea.containsMouse ? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.1) : "transparent") } + KeyboardHighlight { + anchors.fill: parent + visible: taskDelegate.activeFocus + } + // Task icon Kirigami.Icon { anchors.centerIn: parent @@ -970,22 +1068,7 @@ MouseArea { thumbnailShowTimer.stop() taskContextMenu.open(); } else { - var winIds = taskDelegate.model.WinIdList - if (winIds && winIds.length > 1) { - // Multiple windows: toggle thumbnail popup - if (thumbnailPopup.opened && thumbnailPopup.taskIndex === taskDelegate.index) { - thumbnailPopup.close() - } else { - thumbnailPopup.targetDelegate = taskDelegate - thumbnailPopup.taskIndex = taskDelegate.index - thumbnailPopup.windowIds = winIds - thumbnailPopup.isGroup = taskDelegate.model.IsGroupParent === true - thumbnailPopup.open() - } - } else { - thumbnailPopup.close() - tasksModel.requestActivate(tasksModel.makeModelIndex(taskDelegate.index)); - } + taskDelegate.activateTask() } } onContainsMouseChanged: {