feat: drag and drop keyvisualizer
This commit is contained in:
+152
-2
@@ -96,6 +96,22 @@ Item {
|
|||||||
return (runtime && runtime.length > 0 ? runtime : "/tmp") + "/omarchy-key-visualizer.json"
|
return (runtime && runtime.length > 0 ? runtime : "/tmp") + "/omarchy-key-visualizer.json"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Super-held flag written by the Lua capture hook. While Super is down the
|
||||||
|
// overlay's input mask covers the card, so a SUPER+drag moves the visualizer
|
||||||
|
// itself (the Lua has unbound the compositor's SUPER+mouse while it is mapped).
|
||||||
|
property bool superHeld: false
|
||||||
|
readonly property string superPath: {
|
||||||
|
var runtime = Quickshell.env("XDG_RUNTIME_DIR")
|
||||||
|
return (runtime && runtime.length > 0 ? runtime : "/tmp") + "/omarchy-key-visualizer-super"
|
||||||
|
}
|
||||||
|
// True while the cursor hovers the card with Super held: the moment when the
|
||||||
|
// compositor's SUPER+mouse move/resize binds are temporarily unbound so the
|
||||||
|
// drag captures the visualizer instead of a window underneath.
|
||||||
|
property bool overCard: false
|
||||||
|
// Whether the SUPER+mouse binds are currently unbound by us (avoids redundant
|
||||||
|
// hyprctl eval calls on every hover transition).
|
||||||
|
property bool dragArmed: false
|
||||||
|
|
||||||
// Pause flag shared with the bar widget: while the file holds "1" the
|
// Pause flag shared with the bar widget: while the file holds "1" the
|
||||||
// display is frozen (keys are ignored). The bar button writes/removes the
|
// display is frozen (keys are ignored). The bar button writes/removes the
|
||||||
// file; both sides watch it, so a click on any monitor updates all of them.
|
// file; both sides watch it, so a click on any monitor updates all of them.
|
||||||
@@ -548,6 +564,15 @@ Item {
|
|||||||
onFileChanged: reload()
|
onFileChanged: reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: superFile
|
||||||
|
path: root.superPath
|
||||||
|
watchChanges: true
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: root.superHeld = (text() === "1")
|
||||||
|
onFileChanged: reload()
|
||||||
|
}
|
||||||
|
|
||||||
onPausedChanged: if (root.paused) {
|
onPausedChanged: if (root.paused) {
|
||||||
root.entries = []
|
root.entries = []
|
||||||
root.opened = false
|
root.opened = false
|
||||||
@@ -589,6 +614,68 @@ Item {
|
|||||||
if (isFinite(cfg.offsetY)) root.offsetY = Math.round(root.clamp(cfg.offsetY, -2000, 2000))
|
if (isFinite(cfg.offsetY)) root.offsetY = Math.round(root.clamp(cfg.offsetY, -2000, 2000))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Round-trips the current options to the shared config. Used by the SUPER+drag
|
||||||
|
// to persist the offset on release (it updates offsetX/offsetY live while
|
||||||
|
// dragging, then commits once). Mirrors the panel's writeConfig.
|
||||||
|
function persistConfig() {
|
||||||
|
var cfg = {
|
||||||
|
mode: root.mode,
|
||||||
|
position: root.position,
|
||||||
|
margin: root.margin,
|
||||||
|
lingerMs: root.lingerMs,
|
||||||
|
historyCount: root.historyCount,
|
||||||
|
comboMode: root.comboMode,
|
||||||
|
offsetX: root.offsetX,
|
||||||
|
offsetY: root.offsetY
|
||||||
|
}
|
||||||
|
persistProc.command = ["sh", "-c",
|
||||||
|
"printf '%s\\n' '" + JSON.stringify(cfg) + "' > " + Util.shellQuote(root.configPath)]
|
||||||
|
persistProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: persistProc
|
||||||
|
}
|
||||||
|
|
||||||
|
// While the cursor hovers the card with Super held, temporarily unbind the
|
||||||
|
// compositor's SUPER+mouse move/resize so the drag reaches the visualizer
|
||||||
|
// instead of a window underneath. Restored the moment the cursor leaves or
|
||||||
|
// Super is released, so normal window dragging keeps working elsewhere.
|
||||||
|
// Done via `hyprctl eval` so the shell can toggle the Lua-defined binds live.
|
||||||
|
function armSuperDrag() {
|
||||||
|
dragBindProc.command = ["sh", "-c",
|
||||||
|
"hyprctl eval \"hl.unbind('SUPER + mouse:272'); hl.unbind('SUPER + mouse:273')\""]
|
||||||
|
dragBindProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function disarmSuperDrag() {
|
||||||
|
dragBindProc.command = ["sh", "-c",
|
||||||
|
"hyprctl eval \"hl.unbind('SUPER + mouse:272'); hl.unbind('SUPER + mouse:273'); hl.bind('SUPER + mouse:272', hl.dsp.window.drag(), {mouse=true}); hl.bind('SUPER + mouse:273', hl.dsp.window.resize(), {mouse=true})\""]
|
||||||
|
dragBindProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: dragBindProc
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSuperDrag() {
|
||||||
|
if (dragArea.dragging) return
|
||||||
|
var shouldArm = root.superHeld && root.overCard && root.opened
|
||||||
|
if (shouldArm && !root.dragArmed) {
|
||||||
|
root.dragArmed = true
|
||||||
|
root.armSuperDrag()
|
||||||
|
} else if (!shouldArm && root.dragArmed) {
|
||||||
|
root.dragArmed = false
|
||||||
|
root.disarmSuperDrag()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSuperHeldChanged: {
|
||||||
|
if (!root.superHeld) root.overCard = false
|
||||||
|
root.updateSuperDrag()
|
||||||
|
}
|
||||||
|
onOverCardChanged: root.updateSuperDrag()
|
||||||
|
|
||||||
function migrateConfig() {
|
function migrateConfig() {
|
||||||
// First load with the new location: carry over values from the old
|
// First load with the new location: carry over values from the old
|
||||||
// plugin-dir config (if any) and remove it, or seed the defaults.
|
// plugin-dir config (if any) and remove it, or seed the defaults.
|
||||||
@@ -710,8 +797,28 @@ Item {
|
|||||||
WlrLayershell.layer: WlrLayer.Overlay
|
WlrLayershell.layer: WlrLayer.Overlay
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||||
exclusionMode: ExclusionMode.Ignore
|
exclusionMode: ExclusionMode.Ignore
|
||||||
// Visual-only surface: never block clicks to the desktop below.
|
// Click-through normally; while Super is held the card's area captures the
|
||||||
mask: Region {}
|
// pointer so a SUPER+drag moves the visualizer (see dragArea). While
|
||||||
|
// dragging, capture the whole surface so the drag does not cut off when
|
||||||
|
// there is no window underneath.
|
||||||
|
mask: root.superHeld ? (dragArea.dragging ? fullMask : cardMask) : emptyMask
|
||||||
|
|
||||||
|
// Two pre-declared regions so the mask can switch between "click-through"
|
||||||
|
// (empty) and "capture the card" without rebuilding a region per frame.
|
||||||
|
Region {
|
||||||
|
id: emptyMask
|
||||||
|
}
|
||||||
|
Region {
|
||||||
|
id: cardMask
|
||||||
|
item: card
|
||||||
|
}
|
||||||
|
Region {
|
||||||
|
id: fullMask
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
width: panel.width
|
||||||
|
height: panel.height
|
||||||
|
}
|
||||||
|
|
||||||
BorderSurface {
|
BorderSurface {
|
||||||
id: card
|
id: card
|
||||||
@@ -787,6 +894,49 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SUPER+drag: when Super is held the card's mask captures the pointer, so
|
||||||
|
// pressing and dragging on the card moves the visualizer live. While the
|
||||||
|
// cursor hovers the card with Super held, the compositor's SUPER+mouse
|
||||||
|
// move/resize binds are temporarily unbound so the drag reaches us instead
|
||||||
|
// of a window underneath; they are restored when the cursor leaves (or
|
||||||
|
// Super is released), so normal window dragging keeps working elsewhere.
|
||||||
|
MouseArea {
|
||||||
|
id: dragArea
|
||||||
|
x: card.x
|
||||||
|
y: card.y
|
||||||
|
width: card.width
|
||||||
|
height: card.height
|
||||||
|
enabled: root.superHeld && root.opened
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: dragging ? Qt.ClosedHandCursor : Qt.SizeAllCursor
|
||||||
|
|
||||||
|
property bool dragging: false
|
||||||
|
property real grabX: 0
|
||||||
|
property real grabY: 0
|
||||||
|
property int grabOffsetX: 0
|
||||||
|
property int grabOffsetY: 0
|
||||||
|
|
||||||
|
onEntered: root.overCard = true
|
||||||
|
onExited: root.overCard = false
|
||||||
|
onPressed: {
|
||||||
|
var p = dragArea.mapToItem(null, mouse.x, mouse.y)
|
||||||
|
grabX = p.x
|
||||||
|
grabY = p.y
|
||||||
|
grabOffsetX = root.offsetX
|
||||||
|
grabOffsetY = root.offsetY
|
||||||
|
dragging = true
|
||||||
|
}
|
||||||
|
onPositionChanged: {
|
||||||
|
if (!(mouse.buttons & Qt.LeftButton)) return
|
||||||
|
var p = dragArea.mapToItem(null, mouse.x, mouse.y)
|
||||||
|
root.offsetX = Math.round(root.clamp(grabOffsetX + (p.x - grabX), -2000, 2000))
|
||||||
|
root.offsetY = Math.round(root.clamp(grabOffsetY + (p.y - grabY), -2000, 2000))
|
||||||
|
}
|
||||||
|
onReleased: { dragging = false; root.persistConfig(); root.updateSuperDrag() }
|
||||||
|
onCanceled: { dragging = false; root.persistConfig(); root.updateSuperDrag() }
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
|||||||
@@ -145,6 +145,25 @@ local function emit()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Super-held flag: the panel/display watches this to know when to capture the
|
||||||
|
-- SUPER+drag on the overlay (instead of a window underneath). Written only on
|
||||||
|
-- transitions so it does not spam the filesystem on every key.
|
||||||
|
local SUPER_FLAG = runtime .. "/omarchy-key-visualizer-super"
|
||||||
|
local last_super = nil
|
||||||
|
local function super_down()
|
||||||
|
return pressed[133] or pressed[134]
|
||||||
|
end
|
||||||
|
local function emit_super()
|
||||||
|
local down = super_down()
|
||||||
|
if down == last_super then return end
|
||||||
|
last_super = down
|
||||||
|
local f = io.open(SUPER_FLAG, "w")
|
||||||
|
if f then
|
||||||
|
f:write(down and "1" or "0")
|
||||||
|
f:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Combos: a combination of keys is treated as a unit. The display only
|
-- Combos: a combination of keys is treated as a unit. The display only
|
||||||
-- updates on key-down (the combo grows as you press) and when the last key
|
-- updates on key-down (the combo grows as you press) and when the last key
|
||||||
-- is released (the empty payload starts the panel's linger with the last
|
-- is released (the empty payload starts the panel's linger with the last
|
||||||
@@ -165,9 +184,11 @@ hl.on("input.keyboard.key", function(keycode, timeMs, state)
|
|||||||
end
|
end
|
||||||
if not found then combo[#combo + 1] = keycode end
|
if not found then combo[#combo + 1] = keycode end
|
||||||
end
|
end
|
||||||
|
emit_super()
|
||||||
emit()
|
emit()
|
||||||
else
|
else
|
||||||
pressed[keycode] = false
|
pressed[keycode] = false
|
||||||
|
emit_super()
|
||||||
if not MODS[keycode] then
|
if not MODS[keycode] then
|
||||||
for i, kc in ipairs(combo) do
|
for i, kc in ipairs(combo) do
|
||||||
if kc == keycode then
|
if kc == keycode then
|
||||||
|
|||||||
Reference in New Issue
Block a user