Prevent shortcut reset collisions
This commit is contained in:
@@ -13,9 +13,16 @@ ShellRoot {
|
|||||||
return Keybinds.rebind(current, next);
|
return Keybinds.rebind(current, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetBind(current: string): void { Keybinds.resetBind(current); }
|
function resetBind(current: string): bool { return Keybinds.resetBind(current); }
|
||||||
function resetAll(): void { Keybinds.resetAll(); }
|
function resetAll(): void { Keybinds.resetAll(); }
|
||||||
|
|
||||||
|
function seedResetCollision(shipped: string, current: string, occupantShipped: string): void {
|
||||||
|
const overrides = {};
|
||||||
|
overrides[shipped] = current;
|
||||||
|
overrides[occupantShipped] = shipped;
|
||||||
|
DesktopPreferences.set("keybindOverrides", overrides);
|
||||||
|
}
|
||||||
|
|
||||||
function chordFor(description: string): string {
|
function chordFor(description: string): string {
|
||||||
const found = Keybinds.binds.find(bind => bind.description === description);
|
const found = Keybinds.binds.find(bind => bind.description === description);
|
||||||
return found ? found.luaChord : "";
|
return found ? found.luaChord : "";
|
||||||
@@ -24,7 +31,8 @@ ShellRoot {
|
|||||||
function overrideState(): string {
|
function overrideState(): string {
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
overrides: Keybinds.overrides,
|
overrides: Keybinds.overrides,
|
||||||
count: Object.keys(Keybinds.overrides).length
|
count: Object.keys(Keybinds.overrides).length,
|
||||||
|
lastError: Keybinds.lastError
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -108,6 +108,17 @@ Singleton {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A moved shortcut vacates its shipped chord, so another override may use
|
||||||
|
// it legitimately. Refuse to reset the first shortcut until that occupant
|
||||||
|
// moves away; otherwise Hyprland would receive two binds on one chord.
|
||||||
|
function overrideOccupantFor(chord: string, exceptShipped: string): string {
|
||||||
|
for (const shipped in root.overrides) {
|
||||||
|
if (shipped !== exceptShipped && root.overrides[shipped] === chord)
|
||||||
|
return shipped;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
function rebind(currentChord: string, newChord: string): bool {
|
function rebind(currentChord: string, newChord: string): bool {
|
||||||
if (newChord === "" || newChord === currentChord)
|
if (newChord === "" || newChord === currentChord)
|
||||||
return false;
|
return false;
|
||||||
@@ -133,14 +144,25 @@ Singleton {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetBind(currentChord: string): void {
|
function resetBind(currentChord: string): bool {
|
||||||
const shipped = root.shippedChordFor(currentChord);
|
const shipped = root.shippedChordFor(currentChord);
|
||||||
if (shipped === currentChord)
|
if (shipped === currentChord)
|
||||||
return;
|
return true;
|
||||||
|
|
||||||
|
const occupant = root.overrideOccupantFor(shipped, shipped);
|
||||||
|
if (occupant !== "") {
|
||||||
|
root.lastError = `${shipped} is used by another rebound shortcut. Reset that shortcut first.`;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const next = Object.assign({}, root.overrides);
|
const next = Object.assign({}, root.overrides);
|
||||||
delete next[shipped];
|
delete next[shipped];
|
||||||
DesktopPreferences.set("keybindOverrides", next);
|
if (!DesktopPreferences.set("keybindOverrides", next)) {
|
||||||
|
root.lastError = "That shortcut could not be reset.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
root.applyReload();
|
root.applyReload();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetAll(): void {
|
function resetAll(): void {
|
||||||
|
|||||||
@@ -17,13 +17,25 @@ set -euo pipefail
|
|||||||
|
|
||||||
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
harness="$repo_dir/config/dot/quickshell/keybinds-harness.qml"
|
harness="$repo_dir/config/dot/quickshell/keybinds-harness.qml"
|
||||||
config_home="$(mktemp -d /tmp/panama-rebind-config.XXXXXX)"
|
service="$repo_dir/config/dot/quickshell/services/Keybinds.qml"
|
||||||
|
|
||||||
fail() {
|
fail() {
|
||||||
printf 'keybind rebind contract: %s\n' "$1" >&2
|
printf 'keybind rebind contract: %s\n' "$1" >&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rg -Fq 'function overrideOccupantFor(chord: string, exceptShipped: string): string' "$service" \
|
||||||
|
|| fail 'resetBind has no override collision guard'
|
||||||
|
rg -Fq 'root.overrideOccupantFor(shipped, shipped)' "$service" \
|
||||||
|
|| fail 'resetBind does not check whether another override occupies its shipped chord'
|
||||||
|
|
||||||
|
if [[ "${PANAMA_KEYBINDS_STATIC_ONLY:-0}" == "1" ]]; then
|
||||||
|
printf 'keybind rebind contract: PASS (static)\n'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
config_home="$(mktemp -d /tmp/panama-rebind-config.XXXXXX)"
|
||||||
|
|
||||||
# The compositor is the live one -- that is the point -- but preferences are
|
# The compositor is the live one -- that is the point -- but preferences are
|
||||||
# isolated so this cannot leave an override in the user's real settings.
|
# isolated so this cannot leave an override in the user's real settings.
|
||||||
# hyprctl reload re-reads the real settings file, so the compositor is only
|
# hyprctl reload re-reads the real settings file, so the compositor is only
|
||||||
@@ -87,6 +99,19 @@ sleep 0.5
|
|||||||
[[ "$(qs_for_harness ipc call keybinds-test overrideState | jq -r .count)" == "0" ]] \
|
[[ "$(qs_for_harness ipc call keybinds-test overrideState | jq -r .count)" == "0" ]] \
|
||||||
|| fail 'resetAll left overrides behind'
|
|| fail 'resetAll left overrides behind'
|
||||||
|
|
||||||
|
# Terminal moved away from its shipped chord, then Files moved into it.
|
||||||
|
# Resetting Terminal must refuse instead of producing two binds on one chord.
|
||||||
|
qs_for_harness ipc call keybinds-test seedResetCollision \
|
||||||
|
"$terminal" "SUPER + SHIFT + F9" "$files" >/dev/null
|
||||||
|
sleep 0.2
|
||||||
|
[[ "$(qs_for_harness ipc call keybinds-test resetBind "SUPER + SHIFT + F9")" == "false" ]] \
|
||||||
|
|| fail 'resetBind reclaimed a shipped chord occupied by another override'
|
||||||
|
collision_state="$(qs_for_harness ipc call keybinds-test overrideState)"
|
||||||
|
jq -e --arg terminal "$terminal" --arg files "$files" \
|
||||||
|
'.count == 2 and .overrides[$terminal] == "SUPER + SHIFT + F9" and .overrides[$files] == $terminal and (.lastError | length > 0)' \
|
||||||
|
<<<"$collision_state" >/dev/null \
|
||||||
|
|| fail "a refused reset changed overrides or gave no explanation: $collision_state"
|
||||||
|
|
||||||
trap - EXIT
|
trap - EXIT
|
||||||
cleanup
|
cleanup
|
||||||
printf 'keybind rebind contract: PASS\n'
|
printf 'keybind rebind contract: PASS\n'
|
||||||
|
|||||||
Reference in New Issue
Block a user