fullscreen flow: pill double-tap = real fullscreen (mode 0), dock+overview on overlay layer so edge swipes reach them over fullscreen apps
This commit is contained in:
parent
8915ac2a1a
commit
d38bae0cb6
4 changed files with 272 additions and 4 deletions
|
|
@ -33,6 +33,7 @@ modules/ii/dock/DockAppButton.qml ii/modules/ii/dock/DockAppButton.qml
|
|||
modules/ii/dock/DockButton.qml ii/modules/ii/dock/DockButton.qml
|
||||
modules/ii/dock/DockSeparator.qml ii/modules/ii/dock/DockSeparator.qml
|
||||
modules/ii/dock/DockStack.qml ii/modules/ii/dock/DockStack.qml
|
||||
modules/ii/overview/Overview.qml ii/modules/ii/overview/Overview.qml
|
||||
pill/shell.qml pill/shell.qml
|
||||
"
|
||||
|
||||
|
|
|
|||
|
|
@ -145,6 +145,10 @@ Scope { // Scope
|
|||
|
||||
implicitWidth: dockBackground.implicitWidth
|
||||
WlrLayershell.namespace: "quickshell:dock"
|
||||
// Overlay, not Top: fullscreen windows render above the Top
|
||||
// layer, and the whole point of the edge swipe is to reach the
|
||||
// dock from a fullscreen app.
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
color: "transparent"
|
||||
|
||||
implicitHeight: (Config.options?.dock.height ?? 70) + Appearance.sizes.elevationMargin + Appearance.sizes.hyprlandGapsOut
|
||||
|
|
|
|||
261
surfaces/quickshell/modules/ii/overview/Overview.qml
Normal file
261
surfaces/quickshell/modules/ii/overview/Overview.qml
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
// 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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -58,10 +58,12 @@ ShellRoot {
|
|||
}
|
||||
onDoubleClicked: {
|
||||
fired = true
|
||||
// Maximize/un-maximize the active window (Hyprland fullscreen
|
||||
// mode 1). No-op with nothing focused. Lua-fork dispatcher
|
||||
// syntax — classic "fullscreen 1" errors out on this build.
|
||||
Quickshell.execDetached(["hyprctl", "dispatch", "hl.dsp.window.fullscreen({ mode = 1 })"])
|
||||
// True fullscreen toggle (mode 0 = whole display, over bar
|
||||
// and gaps). Mode 1 (maximize) reads as a no-op on a phone
|
||||
// where the lone tiled window already fills the workspace.
|
||||
// No-op with nothing focused. Lua-fork dispatcher syntax —
|
||||
// classic "fullscreen 0" errors out on this build.
|
||||
Quickshell.execDetached(["hyprctl", "dispatch", "hl.dsp.window.fullscreen({ mode = 0 })"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue