fix(capture): restore key overlay updates
This commit is contained in:
+29
-12
@@ -29,6 +29,11 @@ Item {
|
|||||||
// it. The history tick prunes entries whose linger window passed. With
|
// it. The history tick prunes entries whose linger window passed. With
|
||||||
// historyCount 1 this is exactly "the current combo, lingering".
|
// historyCount 1 this is exactly "the current combo, lingering".
|
||||||
property var entries: []
|
property var entries: []
|
||||||
|
// Epoch (seconds) of the last state payload we successfully parsed. Used
|
||||||
|
// to detect a dead capture: if no new payload arrives within maxStateAgeMs
|
||||||
|
// and the top combo was never released, we treat it as released so the
|
||||||
|
// panel self-heals instead of freezing on a stale combo forever.
|
||||||
|
property real lastStateT: 0
|
||||||
// How many combos stay on screen (1..5, default 1). Older entries fade
|
// How many combos stay on screen (1..5, default 1). Older entries fade
|
||||||
// out via the entryOpacity() gradient; a count of 1 is the classic
|
// out via the entryOpacity() gradient; a count of 1 is the classic
|
||||||
// current-combo-only display.
|
// current-combo-only display.
|
||||||
@@ -467,9 +472,10 @@ Item {
|
|||||||
if (!root.paused) {
|
if (!root.paused) {
|
||||||
try {
|
try {
|
||||||
var parsed = JSON.parse(stateFile.text())
|
var parsed = JSON.parse(stateFile.text())
|
||||||
if (parsed && Array.isArray(parsed.keys)) {
|
if parsed && Array.isArray(parsed.keys) {
|
||||||
var age = Math.floor(Date.now() / 1000) - (parsed.t || 0)
|
var age = Math.floor(Date.now() / 1000) - (parsed.t || 0)
|
||||||
if (age <= Math.ceil(root.maxStateAgeMs / 1000)) next = parsed.keys
|
if (age <= Math.ceil(root.maxStateAgeMs / 1000)) next = parsed.keys
|
||||||
|
if ((parsed.t || 0) > 0) root.lastStateT = parsed.t
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
} if (next.length > 0 && root.mode === "bindings") {
|
} if (next.length > 0 && root.mode === "bindings") {
|
||||||
@@ -529,6 +535,16 @@ Item {
|
|||||||
repeat: true
|
repeat: true
|
||||||
running: root.entries.length > 0
|
running: root.entries.length > 0
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
|
// If the capture stopped updating (crashed, plugin unloaded, or a
|
||||||
|
// config reload disabled it), the top combo may be stuck with
|
||||||
|
// releasedAt === 0 forever because no empty "all keys up" payload
|
||||||
|
// ever arrives. Treat a stale state file as a release so the combo
|
||||||
|
// lingers normally and then clears, instead of freezing on screen.
|
||||||
|
if (root.entries.length > 0 && root.entries[0].releasedAt === 0 &&
|
||||||
|
root.lastStateT > 0 &&
|
||||||
|
Math.floor(Date.now() / 1000) - root.lastStateT > Math.ceil(root.maxStateAgeMs / 1000)) {
|
||||||
|
root.entries[0] = { keys: root.entries[0].keys, releasedAt: Date.now() }
|
||||||
|
}
|
||||||
var now = Date.now()
|
var now = Date.now()
|
||||||
var kept = []
|
var kept = []
|
||||||
for (var i = 0; i < root.entries.length; i++) {
|
for (var i = 0; i < root.entries.length; i++) {
|
||||||
@@ -995,6 +1011,17 @@ Item {
|
|||||||
// Debug overlay: live readout of the card's position/dimensions and the
|
// Debug overlay: live readout of the card's position/dimensions and the
|
||||||
// movement state, shown next to the card while moving and after release.
|
// movement state, shown next to the card while moving and after release.
|
||||||
// Toggle with: omarchy-shell key-visualizer debug
|
// Toggle with: omarchy-shell key-visualizer debug
|
||||||
|
readonly property var debugFont: Qt.font({
|
||||||
|
family: Style.font.family,
|
||||||
|
pixelSize: Style.font.bodySmall,
|
||||||
|
bold: false
|
||||||
|
})
|
||||||
|
|
||||||
|
FontMetrics {
|
||||||
|
id: debugFontMetrics
|
||||||
|
font: debugFont
|
||||||
|
}
|
||||||
|
|
||||||
BorderSurface {
|
BorderSurface {
|
||||||
id: debugOverlay
|
id: debugOverlay
|
||||||
visible: root.debugOverlay
|
visible: root.debugOverlay
|
||||||
@@ -1012,7 +1039,7 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: root.cardPad
|
anchors.margins: root.cardPad
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
font: debugFont
|
font: root.debugFont
|
||||||
color: Color.popups.text
|
color: Color.popups.text
|
||||||
text: {
|
text: {
|
||||||
var lb = "\n"
|
var lb = "\n"
|
||||||
@@ -1026,16 +1053,6 @@ Item {
|
|||||||
}
|
}
|
||||||
readonly property bool debugDragging: dragArea.dragging
|
readonly property bool debugDragging: dragArea.dragging
|
||||||
|
|
||||||
FontMetrics {
|
|
||||||
id: debugFontMetrics
|
|
||||||
font: debugFont
|
|
||||||
}
|
|
||||||
readonly property var debugFont: Qt.font({
|
|
||||||
family: Style.font.family,
|
|
||||||
pixelSize: Style.font.bodySmall,
|
|
||||||
bold: false
|
|
||||||
})
|
|
||||||
|
|
||||||
// Combo mode banner — a separate visual stacked against the history
|
// Combo mode banner — a separate visual stacked against the history
|
||||||
// card (below it for bottom positions, above it for top positions).
|
// card (below it for bottom positions, above it for top positions).
|
||||||
// Shows the combo counter, multiplier and running score; hue and
|
// Shows the combo counter, multiplier and running score; hue and
|
||||||
|
|||||||
@@ -225,7 +225,10 @@ Panel {
|
|||||||
owner: root
|
owner: root
|
||||||
bar: root.bar
|
bar: root.bar
|
||||||
open: root.opened
|
open: root.opened
|
||||||
contentWidth: Style.space(250)
|
contentWidth: panel.fittedContentWidth(
|
||||||
|
Math.max(Style.space(250),
|
||||||
|
filterLabel.implicitWidth + Style.spacing.xl + modeButtons.implicitWidth
|
||||||
|
+ panel.padding * 2 + Border.left(panel.borderSpec) + Border.right(panel.borderSpec)))
|
||||||
contentHeight: menuColumn.implicitHeight + panel.padding * 2
|
contentHeight: menuColumn.implicitHeight + panel.padding * 2
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
@@ -295,6 +298,7 @@ Panel {
|
|||||||
height: modeButtons.height
|
height: modeButtons.height
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
|
id: filterLabel
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: "Filter"
|
text: "Filter"
|
||||||
|
|||||||
+5
-9
@@ -32,9 +32,8 @@ local function is_runtime_secure(r)
|
|||||||
if not r or r == "" then return false end
|
if not r or r == "" then return false end
|
||||||
if r == "/tmp" then return false end
|
if r == "/tmp" then return false end
|
||||||
if r:sub(1, 1) ~= "/" then return false end
|
if r:sub(1, 1) ~= "/" then return false end
|
||||||
-- Shell ownership probes fail from Hyprland's embedded Lua environment.
|
|
||||||
-- /run/user is root-controlled, so accepting only this process's systemd
|
-- /run/user is root-controlled, so accepting only this process's systemd
|
||||||
-- runtime path still excludes shared or caller-supplied directories.
|
-- runtime path avoids shell probes that fail in Hyprland's Lua runtime.
|
||||||
local uid = effective_uid()
|
local uid = effective_uid()
|
||||||
return uid ~= nil and r == "/run/user/" .. uid
|
return uid ~= nil and r == "/run/user/" .. uid
|
||||||
end
|
end
|
||||||
@@ -53,10 +52,8 @@ local SUPER_FLAG = runtime and (runtime .. "/omarchy-key-visualizer-super") or n
|
|||||||
|
|
||||||
local function secure_write(path, content)
|
local function secure_write(path, content)
|
||||||
if not runtime or (path ~= STATE_FILE and path ~= SUPER_FLAG) then return false end
|
if not runtime or (path ~= STATE_FILE and path ~= SUPER_FLAG) then return false end
|
||||||
-- FileView watches the existing inode, so replacing the path on every key
|
-- FileView watches the existing inode. These are fixed paths inside the
|
||||||
-- leaves Quickshell attached to an unlinked file. Updating in place keeps
|
-- current user's private runtime directory, so update them in place.
|
||||||
-- the watcher live. The containing runtime directory is private to the
|
|
||||||
-- effective user and the two accepted paths are fixed above.
|
|
||||||
local f = io.open(path, "w")
|
local f = io.open(path, "w")
|
||||||
if not f then return false end
|
if not f then return false end
|
||||||
f:write(content)
|
f:write(content)
|
||||||
@@ -206,9 +203,8 @@ local function emit_super()
|
|||||||
secure_write(SUPER_FLAG, down and "1" or "0")
|
secure_write(SUPER_FLAG, down and "1" or "0")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Create both files when the hook loads. This clears stale state after a
|
-- Create stable files before Quickshell starts watching them and clear stale
|
||||||
-- compositor restart and gives FileView stable paths to watch before the
|
-- state left by a compositor restart.
|
||||||
-- first keyboard event arrives.
|
|
||||||
emit()
|
emit()
|
||||||
emit_super()
|
emit_super()
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"schemaVersion": 1,
|
"schemaVersion": 1,
|
||||||
"id": "felixzsh.key-visualizer",
|
"id": "felixzsh.key-visualizer",
|
||||||
"name": "Key Visualizer",
|
"name": "Key Visualizer",
|
||||||
"version": "1.8.1",
|
"version": "1.8.3",
|
||||||
"author": "felixzsh",
|
"author": "felixzsh",
|
||||||
"description": "Shows the keys you press on screen. Great for keybinding tutorials, demos, and screencasts.",
|
"description": "Shows the keys you press on screen. Great for keybinding tutorials, demos, and screencasts.",
|
||||||
"kinds": [
|
"kinds": [
|
||||||
|
|||||||
Reference in New Issue
Block a user