Coherence: one component per idea, one spelling per word, one truth per claim
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
@@ -18,8 +18,10 @@
|
||||
// knobs come back up with it: they are about gestures, not about the touchpad.
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.config
|
||||
import qs.services
|
||||
import qs.modules.clipboard
|
||||
|
||||
SettingsPage {
|
||||
id: root
|
||||
@@ -99,7 +101,7 @@ SettingsPage {
|
||||
ToggleRow { setting: "leftHanded" }
|
||||
ToggleRow {
|
||||
setting: "middleClickPaste"
|
||||
detail: "Paste the primary selection in GTK and native Wayland applications; individual apps may choose not to support it"
|
||||
detail: "Paste the primary selection in GTK and native Wayland applications; individual applications may choose not to support it"
|
||||
}
|
||||
|
||||
OptionPickerRow {
|
||||
@@ -170,14 +172,19 @@ SettingsPage {
|
||||
Keybinds.applyReload();
|
||||
}
|
||||
|
||||
// Nothing, then the shell's own actions, then the compositor's window
|
||||
// verbs -- the same lists the shortcut editor offers, read from Keybinds so
|
||||
// this page never becomes a second opinion on what resolves.
|
||||
// Nothing, then an application, then the shell's own actions, then the
|
||||
// compositor's window verbs -- the same lists the shortcut editor offers,
|
||||
// read from Keybinds so this page never becomes a second opinion on what
|
||||
// resolves.
|
||||
//
|
||||
// Applications are deliberately absent: assigning one means searching
|
||||
// several hundred desktop entries, which a picker of this shape cannot do.
|
||||
// A gesture that already holds an application (written by the shortcut
|
||||
// vocabulary elsewhere) still shows, so it can be read and cleared.
|
||||
// Applications cannot be listed inline: a normal machine has several
|
||||
// hundred desktop entries and a picker of this shape would be a page nobody
|
||||
// can read. So the option is a door rather than a list -- picking it opens
|
||||
// the same search the shortcut editor uses, below the row. A gesture that
|
||||
// already holds an application shows it too, so it can be read and cleared
|
||||
// without going back through the search.
|
||||
readonly property string appPickerValue: "pick-application"
|
||||
|
||||
function gestureOptions(key: string): var {
|
||||
const out = [{ value: "", label: "Nothing", detail: "The swipe passes through to whatever is under it" }];
|
||||
const stored = DesktopPreferences.get(key);
|
||||
@@ -187,6 +194,11 @@ SettingsPage {
|
||||
label: String(stored.label),
|
||||
detail: "Application · launch-or-focus"
|
||||
});
|
||||
out.push({
|
||||
value: root.appPickerValue,
|
||||
label: "Launch an application…",
|
||||
detail: "Search everything installed, the way a custom shortcut does"
|
||||
});
|
||||
for (const action of Keybinds.shellActions)
|
||||
out.push({ value: "shell:" + action.target, label: action.label, detail: "Shell action" });
|
||||
for (const action of Keybinds.windowActions)
|
||||
@@ -194,6 +206,49 @@ SettingsPage {
|
||||
return out;
|
||||
}
|
||||
|
||||
// Which gesture is currently choosing an application, "" while none is.
|
||||
property string pickingAppFor: ""
|
||||
|
||||
// Capped at eight and only ever searched, never browsed -- the same rule
|
||||
// and the same reason as CustomShortcutEditor's list.
|
||||
readonly property var appMatches: {
|
||||
const needle = gestureAppSearch.text.trim().toLowerCase();
|
||||
if (needle === "")
|
||||
return [];
|
||||
const out = [];
|
||||
for (const entry of DesktopEntries.applications.values) {
|
||||
if (entry.noDisplay)
|
||||
continue;
|
||||
if (String(entry.name).toLowerCase().indexOf(needle) >= 0)
|
||||
out.push(entry);
|
||||
if (out.length >= 8)
|
||||
break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// The one path that writes an application onto a gesture. It builds the
|
||||
// same three-field record assignGesture() writes for the named actions --
|
||||
// an entry id is never a command, and hypr/actions.lua resolves it the same
|
||||
// way it resolves one written by a custom shortcut.
|
||||
function assignGestureApp(key: string, entry: var): void {
|
||||
const target = String(entry?.id ?? "");
|
||||
if (Keybinds.describeAction({ kind: "app", target: target }) === "")
|
||||
return;
|
||||
DesktopPreferences.set(key, {
|
||||
kind: "app",
|
||||
target: target,
|
||||
label: "Launch " + String(entry?.name ?? target)
|
||||
});
|
||||
root.closeAppPicker();
|
||||
Keybinds.applyReload();
|
||||
}
|
||||
|
||||
function closeAppPicker(): void {
|
||||
root.pickingAppFor = "";
|
||||
gestureAppSearch.text = "";
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
visible: InputDevices.hasTouchpad
|
||||
title: "Gestures"
|
||||
@@ -236,7 +291,67 @@ SettingsPage {
|
||||
current: root.gestureValue(String(gestureRow.modelData.key))
|
||||
enabled: !Keybinds.reloading
|
||||
divider: gestureRow.index < root.gestureKeys.length - 1
|
||||
onPicked: value => root.assignGesture(String(gestureRow.modelData.key), String(value))
|
||||
onPicked: value => {
|
||||
if (String(value) === root.appPickerValue) {
|
||||
root.pickingAppFor = String(gestureRow.modelData.key);
|
||||
gestureAppSearch.text = "";
|
||||
return;
|
||||
}
|
||||
root.closeAppPicker();
|
||||
root.assignGesture(String(gestureRow.modelData.key), String(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The search, unfolded under the four rows rather than inside one of
|
||||
// them: the picker has already collapsed by the time this appears, and
|
||||
// a list of eight results inside a collapsed row has nowhere to go.
|
||||
Column {
|
||||
width: parent.width
|
||||
visible: root.pickingAppFor !== ""
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 40
|
||||
|
||||
SearchField {
|
||||
id: gestureAppSearch
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
placeholder: "Search installed applications"
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.appMatches
|
||||
|
||||
SettingRow {
|
||||
id: candidate
|
||||
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: parent.width
|
||||
label: String(candidate.modelData.name ?? "")
|
||||
detail: String(candidate.modelData.id ?? "")
|
||||
controlWidth: 96
|
||||
divider: candidate.index < root.appMatches.length - 1
|
||||
activatable: true
|
||||
|
||||
onActivated: root.assignGestureApp(root.pickingAppFor, candidate.modelData)
|
||||
}
|
||||
}
|
||||
|
||||
TextRow {
|
||||
visible: root.appMatches.length === 0
|
||||
label: gestureAppSearch.text.trim() === ""
|
||||
? "Type to find an application"
|
||||
: "Nothing installed matches that"
|
||||
detail: "The gesture stores the application, never a command — the same three fields a custom shortcut stores"
|
||||
divider: false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user