- Dock drag-to-reorder for pinned apps (insertion gap, quick-slide vs dwell) - Fullscreen detection: scan all windows via HyprlandData.windowList - IdleCoordinator, GlobalStates, Session.qml updates - Deploy script, qmldir, settings, wallpaper, visualizer fixes - sessiond server, memory module updates Co-Authored-By: Claude <noreply@anthropic.com>
296 lines
11 KiB
QML
296 lines
11 KiB
QML
// Pixel3Arch patch to ii's stock Overview.qml (2026-07-07): opening the
|
|
// search/overview automatically brings up the on-screen keyboard
|
|
// (GlobalStates.oskOpen), and closing it closes the keyboard too.
|
|
//
|
|
// Also: while the OSK is open, this panel does NOT register with
|
|
// GlobalFocusGrab.addDismissable — see the onOskOpenChanged handler below.
|
|
// Reason: GlobalFocusGrab's HyprlandFocusGrab (hyprland_focus_grab_v1, a
|
|
// real Wayland protocol) clears whenever a tap lands outside its
|
|
// whitelisted surfaces, and wvkbd (the real OSK — see OnScreenKeyboard.qml)
|
|
// is a separate process that CANNOT be added to that whitelist — Quickshell
|
|
// only exposes whitelisting its own in-process windows, and there's no
|
|
// hyprctl or other lever either. Two attempts at a "shield window" to fake
|
|
// wvkbd's inclusion both failed on real device testing (see
|
|
// OnScreenKeyboard.qml's history for what was tried and why). So instead of
|
|
// fighting the grab, this panel just stops being dismissable-by-outside-tap
|
|
// while the keyboard is up — it still closes normally via Escape, the
|
|
// explicit toggle, or picking a search result. Everything else in this file
|
|
// is unchanged stock ii — diff against upstream before re-applying this
|
|
// patch if ii updates.
|
|
import qs
|
|
import qs.services
|
|
import qs.modules.common
|
|
import qs.modules.common.widgets
|
|
import Qt.labs.synchronizer
|
|
import QtQuick
|
|
import QtQuick.Controls
|
|
import QtQuick.Layouts
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import Quickshell.Wayland
|
|
import Quickshell.Hyprland
|
|
|
|
Scope {
|
|
id: overviewScope
|
|
property bool dontAutoCancelSearch: false
|
|
|
|
PanelWindow {
|
|
id: panelWindow
|
|
property string searchingText: ""
|
|
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen)
|
|
property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id)
|
|
visible: GlobalStates.overviewOpen
|
|
|
|
WlrLayershell.namespace: "quickshell:overview"
|
|
// Overlay, not Top: the second-stage edge swipe must bring the
|
|
// overview up over a fullscreen app (fullscreen renders above Top).
|
|
WlrLayershell.layer: WlrLayer.Overlay
|
|
WlrLayershell.keyboardFocus: GlobalStates.overviewOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
|
color: "transparent"
|
|
|
|
mask: Region {
|
|
item: GlobalStates.overviewOpen ? columnLayout : null
|
|
}
|
|
|
|
anchors {
|
|
top: true
|
|
bottom: true
|
|
left: true
|
|
right: true
|
|
}
|
|
|
|
Connections {
|
|
target: GlobalStates
|
|
function onOverviewOpenChanged() {
|
|
if (!GlobalStates.overviewOpen) {
|
|
searchWidget.disableExpandAnimation();
|
|
overviewScope.dontAutoCancelSearch = false;
|
|
GlobalFocusGrab.dismiss();
|
|
GlobalStates.oskOpen = false;
|
|
} else {
|
|
if (!overviewScope.dontAutoCancelSearch) {
|
|
searchWidget.cancelSearch();
|
|
}
|
|
// Search wants text input — bring up the on-screen
|
|
// keyboard automatically (Pixel3Arch, 2026-07-07). See
|
|
// overlays/quickshell-ii-patches/OnScreenKeyboard.qml.
|
|
// This also enters oskOpen=true, so the dismissable
|
|
// registration below (onOskOpenChanged) intentionally
|
|
// does NOT add panelWindow here — it's added only once
|
|
// oskOpen settles, to avoid a double-add race.
|
|
GlobalStates.oskOpen = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Registering as dismissable is gated on the OSK's state, not
|
|
// just overviewOpen — see the file-header comment for why. This
|
|
// fires both when overview opens (right after oskOpen flips true
|
|
// above) and whenever the OSK is toggled independently while
|
|
// overview stays open (e.g. closing the keyboard without closing
|
|
// search).
|
|
Connections {
|
|
target: GlobalStates
|
|
function onOskOpenChanged() {
|
|
if (!GlobalStates.overviewOpen) return;
|
|
if (GlobalStates.oskOpen) {
|
|
GlobalFocusGrab.removeDismissable(panelWindow);
|
|
} else {
|
|
GlobalFocusGrab.addDismissable(panelWindow);
|
|
}
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: GlobalFocusGrab
|
|
function onDismissed() {
|
|
GlobalStates.overviewOpen = false;
|
|
}
|
|
}
|
|
implicitWidth: columnLayout.implicitWidth
|
|
implicitHeight: columnLayout.implicitHeight
|
|
|
|
function setSearchingText(text) {
|
|
searchWidget.setSearchingText(text);
|
|
searchWidget.focusFirstItem();
|
|
}
|
|
|
|
// Tap-to-dismiss for the dead space INSIDE the overview page: the
|
|
// window's input mask only covers columnLayout, and the workspace
|
|
// grid's gaps (between workspaces, around the grid) consume nothing —
|
|
// taps there used to be silently ignored, which read as the page
|
|
// being stuck. Declared before (= stacked below) the column, sized to
|
|
// the overview grid only, so the search bar's padding stays inert and
|
|
// every real control (workspaces, windows, search) wins the tap.
|
|
// While the OSK is up this is the ONLY outside-tap dismiss — the
|
|
// focus-grab route is deliberately disabled then (see header).
|
|
MouseArea {
|
|
enabled: GlobalStates.overviewOpen && overviewLoader.active
|
|
x: columnLayout.x + overviewLoader.x
|
|
y: columnLayout.y + overviewLoader.y
|
|
width: overviewLoader.width
|
|
height: overviewLoader.height
|
|
onClicked: GlobalStates.overviewOpen = false
|
|
}
|
|
|
|
Column {
|
|
id: columnLayout
|
|
visible: GlobalStates.overviewOpen
|
|
anchors {
|
|
horizontalCenter: parent.horizontalCenter
|
|
top: parent.top
|
|
}
|
|
spacing: -8
|
|
|
|
Keys.onPressed: event => {
|
|
if (event.key === Qt.Key_Escape) {
|
|
GlobalStates.overviewOpen = false;
|
|
}
|
|
}
|
|
|
|
SearchWidget {
|
|
id: searchWidget
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
Synchronizer on searchingText {
|
|
property alias source: panelWindow.searchingText
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: overviewLoader
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
active: GlobalStates.overviewOpen && (Config?.options.overview.enable ?? true)
|
|
sourceComponent: OverviewWidget {
|
|
screen: panelWindow.screen
|
|
visible: (panelWindow.searchingText == "")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function toggleClipboard() {
|
|
if (GlobalStates.overviewOpen && overviewScope.dontAutoCancelSearch) {
|
|
GlobalStates.overviewOpen = false;
|
|
return;
|
|
}
|
|
overviewScope.dontAutoCancelSearch = true;
|
|
panelWindow.setSearchingText(Config.options.search.prefix.clipboard);
|
|
GlobalStates.overviewOpen = true;
|
|
}
|
|
|
|
function toggleEmojis() {
|
|
if (GlobalStates.overviewOpen && overviewScope.dontAutoCancelSearch) {
|
|
GlobalStates.overviewOpen = false;
|
|
return;
|
|
}
|
|
overviewScope.dontAutoCancelSearch = true;
|
|
panelWindow.setSearchingText(Config.options.search.prefix.emojis);
|
|
GlobalStates.overviewOpen = true;
|
|
}
|
|
|
|
IpcHandler {
|
|
target: "search"
|
|
|
|
function toggle() {
|
|
GlobalStates.overviewOpen = !GlobalStates.overviewOpen;
|
|
}
|
|
function workspacesToggle() {
|
|
GlobalStates.overviewOpen = !GlobalStates.overviewOpen;
|
|
}
|
|
function close() {
|
|
GlobalStates.overviewOpen = false;
|
|
}
|
|
function open() {
|
|
GlobalStates.overviewOpen = true;
|
|
}
|
|
// Device-side acceptance/debug surface: lets deploy checks exercise
|
|
// the exact query path the text field uses without synthesizing keys.
|
|
function setQuery(query: string): void {
|
|
overviewScope.dontAutoCancelSearch = true;
|
|
GlobalStates.overviewOpen = true;
|
|
panelWindow.setSearchingText(query);
|
|
}
|
|
function status(): string {
|
|
return JSON.stringify({
|
|
open: GlobalStates.overviewOpen,
|
|
query: LauncherSearch.query,
|
|
results: LauncherSearch.results.length,
|
|
desktopEntries: DesktopEntries.applications.values.length,
|
|
appSearchEntries: AppSearch.list.length,
|
|
widget: searchWidget.debugState()
|
|
});
|
|
}
|
|
function toggleReleaseInterrupt() {
|
|
GlobalStates.superReleaseMightTrigger = false;
|
|
}
|
|
function clipboardToggle() {
|
|
overviewScope.toggleClipboard();
|
|
}
|
|
}
|
|
|
|
GlobalShortcut {
|
|
name: "searchToggle"
|
|
description: "Toggles search on press"
|
|
|
|
onPressed: {
|
|
GlobalStates.overviewOpen = !GlobalStates.overviewOpen;
|
|
}
|
|
}
|
|
GlobalShortcut {
|
|
name: "overviewWorkspacesClose"
|
|
description: "Closes overview on press"
|
|
|
|
onPressed: {
|
|
GlobalStates.overviewOpen = false;
|
|
}
|
|
}
|
|
GlobalShortcut {
|
|
name: "overviewWorkspacesToggle"
|
|
description: "Toggles overview on press"
|
|
|
|
onPressed: {
|
|
GlobalStates.overviewOpen = !GlobalStates.overviewOpen;
|
|
}
|
|
}
|
|
GlobalShortcut {
|
|
name: "searchToggleRelease"
|
|
description: "Toggles search on release"
|
|
|
|
onPressed: {
|
|
GlobalStates.superReleaseMightTrigger = true;
|
|
}
|
|
|
|
onReleased: {
|
|
if (!GlobalStates.superReleaseMightTrigger) {
|
|
GlobalStates.superReleaseMightTrigger = true;
|
|
return;
|
|
}
|
|
GlobalStates.overviewOpen = !GlobalStates.overviewOpen;
|
|
}
|
|
}
|
|
GlobalShortcut {
|
|
name: "searchToggleReleaseInterrupt"
|
|
description: "Interrupts possibility of search being toggled on release. " + "This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key. " + "To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything."
|
|
|
|
onPressed: {
|
|
GlobalStates.superReleaseMightTrigger = false;
|
|
}
|
|
}
|
|
GlobalShortcut {
|
|
name: "overviewClipboardToggle"
|
|
description: "Toggle clipboard query on overview widget"
|
|
|
|
onPressed: {
|
|
overviewScope.toggleClipboard();
|
|
}
|
|
}
|
|
|
|
GlobalShortcut {
|
|
name: "overviewEmojiToggle"
|
|
description: "Toggle emoji query on overview widget"
|
|
|
|
onPressed: {
|
|
overviewScope.toggleEmojis();
|
|
}
|
|
}
|
|
}
|