Center the Quake console in a 2:1 panel instead of spanning the screen.
Size it from leftover workspace gaps so it refits per monitor, and let omarchy_qconsole_ratio change the width.
This commit is contained in:
1 parent
002c70a544
commit
0bec60f6e4
4 files changed
+232
-24
No files matched your search
@@ -10,6 +10,11 @@ dofile((os.getenv("OMARCHY_PATH") or "/usr/share/omarchy") .. "/default/hypr/boo
|
||||
-- keeping core window-manager bindings:
|
||||
-- omarchy_preinstalled_bindings = false
|
||||
|
||||
-- Center the quake console in a tiling box. The number is how many times
|
||||
-- wider the box is than its height. 1 is a square. Comment this out for a
|
||||
-- full-width drop-down.
|
||||
omarchy_qconsole_ratio = 2
|
||||
|
||||
-- Load Omarchy defaults.
|
||||
require("default.hypr.omarchy")
|
||||
|
||||
|
||||
+143
-18
@@ -4,6 +4,9 @@
|
||||
|
||||
-- How much of the usable screen the console covers, measured from the top.
|
||||
local share = 0.5
|
||||
local min_size = 64
|
||||
|
||||
local SCRATCHPAD = "special:scratchpad"
|
||||
|
||||
-- Seed the console with the default agent the first time it opens, rather than
|
||||
-- at boot, so nothing is running until it is wanted. The exec rule has to pin
|
||||
@@ -25,20 +28,33 @@ hl.config({
|
||||
|
||||
-- Refitting replaces the rule in place rather than stacking a new one, but it
|
||||
-- still schedules a monitor and window state refresh, and monitor.focused fires
|
||||
-- on every hop between screens. Most of those hops do not change the number, so
|
||||
-- on every hop between screens. Most of those hops do not change the gaps, so
|
||||
-- only write the rule when it actually moves.
|
||||
local covering = nil
|
||||
|
||||
local function cover(bottom)
|
||||
if covering == bottom then
|
||||
return
|
||||
local function same_gaps(a, b)
|
||||
return a
|
||||
and b
|
||||
and a.top == b.top
|
||||
and a.right == b.right
|
||||
and a.bottom == b.bottom
|
||||
and a.left == b.left
|
||||
end
|
||||
|
||||
local function cover(gaps_out)
|
||||
if type(gaps_out) == "number" then
|
||||
gaps_out = { top = 0, right = 0, bottom = gaps_out, left = 0 }
|
||||
end
|
||||
covering = bottom
|
||||
|
||||
if same_gaps(covering, gaps_out) then
|
||||
return false
|
||||
end
|
||||
covering = gaps_out
|
||||
|
||||
hl.workspace_rule({
|
||||
workspace = "special:scratchpad",
|
||||
workspace = SCRATCHPAD,
|
||||
gaps_in = 0,
|
||||
gaps_out = { top = 0, right = 0, bottom = bottom, left = 0 },
|
||||
gaps_out = gaps_out,
|
||||
|
||||
-- Nothing to highlight in a console that is only ever focused when it is
|
||||
-- open, and the active border reads as a stray frame around a panel that
|
||||
@@ -47,39 +63,148 @@ local function cover(bottom)
|
||||
|
||||
on_created_empty = seed,
|
||||
})
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function reserved_edges(monitor)
|
||||
local reserved = monitor.reserved
|
||||
if type(reserved) ~= "table" then
|
||||
return { top = 0, right = 0, bottom = 0, left = 0 }
|
||||
end
|
||||
|
||||
return {
|
||||
top = reserved.top or 0,
|
||||
right = reserved.right or 0,
|
||||
bottom = reserved.bottom or 0,
|
||||
left = reserved.left or 0,
|
||||
}
|
||||
end
|
||||
|
||||
-- A positive omarchy_qconsole_ratio (set in hyprland.lua before defaults load)
|
||||
-- centers the console in a tiling box that many times wider than it is tall.
|
||||
-- Values below 1 clamp to a square. Unset keeps the full-width drop-down.
|
||||
local function box_ratio()
|
||||
local ratio = _G.omarchy_qconsole_ratio
|
||||
if type(ratio) == "number" and ratio > 0 then
|
||||
return math.max(1, ratio)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function is_scratchpad(ws)
|
||||
return ws and (ws.name == SCRATCHPAD or ws.name == "scratchpad")
|
||||
end
|
||||
|
||||
-- Sizing the console with a window rule would freeze it at whatever the screen
|
||||
-- measured when it first opened, because Hyprland resolves those expressions
|
||||
-- once, as the window maps. Rescaling the monitor afterwards would leave a
|
||||
-- console that is no longer half of anything. Gaps are re-applied by the layout
|
||||
-- instead, so the console is sized by the gap left underneath it and that gap
|
||||
-- is recomputed whenever the monitor layout changes.
|
||||
local function fit()
|
||||
local monitor = hl.get_active_monitor()
|
||||
-- instead, so the console is sized by the leftover area and that area is
|
||||
-- recomputed whenever the monitor it is opening on changes.
|
||||
local function fit(monitor)
|
||||
monitor = monitor or hl.get_active_monitor()
|
||||
|
||||
-- A monitor handle whose output has gone away answers nil to every field, and
|
||||
-- layout changes are exactly when that happens, so this also covers reading
|
||||
-- height and reserved below.
|
||||
if not monitor or not monitor.scale or monitor.scale <= 0 then
|
||||
return
|
||||
return false
|
||||
end
|
||||
|
||||
-- Monitor dimensions are in physical pixels; gaps are logical, so the scale
|
||||
-- has to come out before the reserved area (already logical) comes off.
|
||||
local reserved = monitor.reserved
|
||||
local usable = monitor.height / monitor.scale - reserved.top - reserved.bottom
|
||||
local reserved = reserved_edges(monitor)
|
||||
local usable_h = monitor.height / monitor.scale - reserved.top - reserved.bottom
|
||||
local usable_w = nil
|
||||
if monitor.width then
|
||||
usable_w = monitor.width / monitor.scale - reserved.left - reserved.right
|
||||
end
|
||||
|
||||
cover(math.max(0, math.floor(usable * (1 - share))))
|
||||
local ratio = box_ratio()
|
||||
local h = math.max(min_size, math.floor(usable_h * share))
|
||||
local side = 0
|
||||
local bottom = math.max(0, math.floor(usable_h - h))
|
||||
|
||||
if ratio and usable_w then
|
||||
local w = math.floor(h * ratio)
|
||||
if w > usable_w then
|
||||
w = math.floor(usable_w)
|
||||
end
|
||||
side = math.max(0, math.floor((usable_w - w) / 2))
|
||||
if usable_w - (side * 2) < min_size then
|
||||
side = math.max(0, math.floor((usable_w - min_size) / 2))
|
||||
end
|
||||
end
|
||||
|
||||
if usable_h - bottom < min_size then
|
||||
bottom = math.max(0, math.floor(usable_h - min_size))
|
||||
end
|
||||
|
||||
return cover({ top = 0, right = side, bottom = bottom, left = side })
|
||||
end
|
||||
|
||||
local function apply_now()
|
||||
if hl.exec_scheduled_prop_refresh_immediately then
|
||||
hl.exec_scheduled_prop_refresh_immediately()
|
||||
end
|
||||
end
|
||||
|
||||
local function scratchpad_on_other_monitor(mon)
|
||||
if not hl.get_workspace or not mon then
|
||||
return false
|
||||
end
|
||||
|
||||
local ws = hl.get_workspace(SCRATCHPAD)
|
||||
if not is_scratchpad(ws) or not ws.visible or not ws.monitor or not ws.monitor.name or not mon.name then
|
||||
return false
|
||||
end
|
||||
|
||||
return ws.monitor.name ~= mon.name
|
||||
end
|
||||
|
||||
-- Until a monitor can be read, cover the whole work area rather than leaving
|
||||
-- the console unruled, so it is never seeded without its placement.
|
||||
cover(0)
|
||||
cover({ top = 0, right = 0, bottom = 0, left = 0 })
|
||||
fit()
|
||||
|
||||
hl.on("monitor.layout_changed", fit)
|
||||
hl.on("monitor.focused", fit)
|
||||
hl.on("monitor.layout_changed", function()
|
||||
local ws = hl.get_workspace and hl.get_workspace(SCRATCHPAD)
|
||||
if is_scratchpad(ws) and ws.visible and ws.monitor then
|
||||
fit(ws.monitor)
|
||||
else
|
||||
fit()
|
||||
end
|
||||
end)
|
||||
|
||||
-- follow_mouse hops fire this; do not rewrite an open console to a different
|
||||
-- output's gaps (that is what zeroed the window on the 1080p screen).
|
||||
hl.on("monitor.focused", function(mon)
|
||||
if scratchpad_on_other_monitor(mon) then
|
||||
return
|
||||
end
|
||||
fit(mon)
|
||||
end)
|
||||
|
||||
-- Special workspaces toggle on the monitor they open on, not whichever output
|
||||
-- last happened to be focused when the rule was written.
|
||||
hl.on("workspace.special_active", function(ws, mon)
|
||||
if not is_scratchpad(ws) then
|
||||
return
|
||||
end
|
||||
if fit(mon) then
|
||||
apply_now()
|
||||
end
|
||||
end)
|
||||
|
||||
hl.on("workspace.move_to_monitor", function(ws, mon)
|
||||
if not is_scratchpad(ws) then
|
||||
return
|
||||
end
|
||||
if fit(mon) then
|
||||
apply_now()
|
||||
end
|
||||
end)
|
||||
|
||||
-- The direction names the edge the offset is measured from, not where the
|
||||
-- workspace goes: "slide top" drops it down into view, and "slide bottom"
|
||||
|
||||
@@ -66,6 +66,8 @@ Finally, there's a special scratchpad workspace that drops down over whatever wo
|
||||
|
||||
It works especially well for a terminal running an agent, or for controls you want to interact with quickly without leaving the current workspace. To move a window off the scratchpad, send it directly to another workspace with something like `Super + Shift + 1`.
|
||||
|
||||
By default the scratchpad drops down as a centered panel. Open `~/.config/hypr/hyprland.lua` if you want to change how wide that panel is (`omarchy_qconsole_ratio` is near the top). `1` makes a square; `2` makes a panel twice as wide as it is tall; `3` or `4` is wider still. Anything smaller than `1` still gives you a square. Comment the line out if you'd rather have it span the full width of the screen.
|
||||
|
||||
### It takes some getting used to!
|
||||
|
||||
It takes a little while to get used to navigating your desktop like this, but once you do, it'll be hard to go back to a traditional mouse-driven desktop experience!
|
||||
@@ -10,9 +10,10 @@ require_command lua
|
||||
# scaled display, so it is worth pinning down.
|
||||
# base-test.sh does not set -e, so the assertions have to fail the file
|
||||
# themselves rather than leaving the pass below to run regardless.
|
||||
OMARCHY_PATH="$ROOT" lua - <<'LUA' || fail "the console covers half the work area at any monitor scale"
|
||||
OMARCHY_PATH="$ROOT" lua - <<'LUA' || fail "the console is a half-height 2:1 panel by default"
|
||||
local rules, handlers = {}, {}
|
||||
local monitor = nil
|
||||
local workspace = nil
|
||||
|
||||
hl = {
|
||||
config = function() end,
|
||||
@@ -20,8 +21,14 @@ hl = {
|
||||
workspace_rule = function(rule) table.insert(rules, rule) end,
|
||||
on = function(event, callback) handlers[event] = callback end,
|
||||
get_active_monitor = function() return monitor end,
|
||||
get_workspace = function() return workspace end,
|
||||
exec_scheduled_prop_refresh_immediately = function() end,
|
||||
}
|
||||
|
||||
-- Same default as config/hypr/hyprland.lua: a centered panel twice as wide as
|
||||
-- it is tall. Commenting that assignment out is what restores full width.
|
||||
omarchy_qconsole_ratio = 2
|
||||
|
||||
dofile(os.getenv("OMARCHY_PATH") .. "/default/hypr/bootstrap.lua")
|
||||
require("default.hypr.qconsole")
|
||||
|
||||
@@ -29,6 +36,11 @@ local function current()
|
||||
return rules[#rules]
|
||||
end
|
||||
|
||||
local function gaps()
|
||||
local g = current().gaps_out
|
||||
return g.top, g.right, g.bottom, g.left
|
||||
end
|
||||
|
||||
-- Config loads before the outputs are up, so the first pass has no monitor to
|
||||
-- read. It still has to leave a rule behind, or the console would open unseeded.
|
||||
assert(#rules > 0, "console is ruled even before a monitor can be read")
|
||||
@@ -44,6 +56,7 @@ local function rescale(height, scale, bar)
|
||||
end
|
||||
|
||||
-- Same panel, same logical size, different scale: the console must not care.
|
||||
-- These fixtures omit width, so the ratio cannot inset the sides yet.
|
||||
assert(rescale(1080, 1, 40) == 520, "half of a 1080p work area, unscaled")
|
||||
assert(rescale(2160, 2, 40) == 520, "the same half once the monitor is scaled 2x")
|
||||
assert(rescale(2160, 1.5, 40) == 700, "and at a fractional scale")
|
||||
@@ -52,11 +65,10 @@ assert(rescale(2160, 1.5, 40) == 700, "and at a fractional scale")
|
||||
-- console short.
|
||||
assert(rescale(1440, 1, 0) == 720, "a monitor with nothing reserved")
|
||||
|
||||
-- The console stays flush with the top and the sides, the way a Quake console
|
||||
-- drops in, and keeps its seed across every refit.
|
||||
local final = current()
|
||||
assert(final.gaps_out.top == 0 and final.gaps_out.left == 0 and final.gaps_out.right == 0,
|
||||
"the console is flush to the top and sides")
|
||||
assert(final.gaps_out.top == 0, "the console stays flush with the top")
|
||||
assert(final.gaps_out.left == 0 and final.gaps_out.right == 0,
|
||||
"without a width the sides stay flush")
|
||||
assert(final.on_created_empty:find("omarchy%-agent"), "refitting keeps the console seeded")
|
||||
assert(final.no_border == true, "the console drops the active window border")
|
||||
|
||||
@@ -86,5 +98,69 @@ monitor.scale = 2
|
||||
handlers["monitor.layout_changed"]()
|
||||
assert(#rules == written + 1, "a real change still rewrites it")
|
||||
assert(current().gaps_out.bottom == 360, "and lands on half the rescaled screen")
|
||||
|
||||
-- Default ratio: 16:9 is a centered 2:1 panel, not a full-width drop-down.
|
||||
monitor = { width = 1920, height = 1080, scale = 1, reserved = { top = 0, bottom = 0, left = 0, right = 0 } }
|
||||
handlers["monitor.layout_changed"]()
|
||||
local top, right, bottom, left = gaps()
|
||||
assert(top == 0 and left == 420 and right == 420 and bottom == 540, "16:9 default is a 1080x540 panel")
|
||||
|
||||
local dell = { name = "DP-1", width = 6144, height = 2560, scale = 1, reserved = { top = 30, bottom = 0, left = 0, right = 0 } }
|
||||
monitor = dell
|
||||
handlers["monitor.layout_changed"]()
|
||||
top, right, bottom, left = gaps()
|
||||
assert(left == 1807 and right == 1807 and bottom == 1265, "the same 2:1 panel on 6K")
|
||||
|
||||
-- Same logical box at scale 2x (physical 12288x5120).
|
||||
-- It checks that scale does not change the panel's logical size.
|
||||
monitor = { name = "DP-1", width = 12288, height = 5120, scale = 2, reserved = { top = 30, bottom = 0, left = 0, right = 0 } }
|
||||
handlers["monitor.layout_changed"]()
|
||||
top, right, bottom, left = gaps()
|
||||
assert(left == 1807 and right == 1807 and bottom == 1265, "the 6K box is in logical pixels")
|
||||
|
||||
-- Acer 1920x1080, bar 30, same ratio. Opening here after a 6K fit must rewrite;
|
||||
-- leaving 6K side gaps would make leftover width negative.
|
||||
local acer = { name = "HDMI-A-1", width = 1920, height = 1080, scale = 1, reserved = { top = 30, bottom = 0, left = 0, right = 0 } }
|
||||
monitor = dell
|
||||
handlers["monitor.layout_changed"]()
|
||||
handlers["workspace.special_active"]({ name = "special:scratchpad" }, acer)
|
||||
top, right, bottom, left = gaps()
|
||||
assert(left == 435 and right == 435 and bottom == 525, "opening on 1080p after a 6K fit resizes the 2:1 box")
|
||||
assert(1920 - left - right > 0 and 1080 - 30 - bottom > 0, "1080p leftover is never negative")
|
||||
|
||||
-- follow_mouse onto the 6K while the console is already showing on 1080p must
|
||||
-- not steal the global rule (that is what oversized the Dell after a hop).
|
||||
workspace = { name = "special:scratchpad", visible = true, monitor = acer }
|
||||
written = #rules
|
||||
handlers["monitor.focused"](dell)
|
||||
assert(#rules == written, "focus on another output does not rewrite an open console")
|
||||
workspace = nil
|
||||
|
||||
-- Cache: same 1440p height, 16:9 vs 21:9. Sides change even when bottom does not.
|
||||
monitor = { width = 2560, height = 1440, scale = 1, reserved = { top = 0, bottom = 0, left = 0, right = 0 } }
|
||||
handlers["monitor.layout_changed"]()
|
||||
written = #rules
|
||||
monitor = { width = 3440, height = 1440, scale = 1, reserved = { top = 0, bottom = 0, left = 0, right = 0 } }
|
||||
handlers["monitor.layout_changed"]()
|
||||
assert(#rules == written + 1, "a same-height ultrawide hop still rewrites the sides")
|
||||
top, right, bottom, left = gaps()
|
||||
assert(left == 1000 and right == 1000 and bottom == 720, "3440x1440 at ratio 2 is a 2:1 box")
|
||||
|
||||
omarchy_qconsole_ratio = 1
|
||||
monitor = { width = 1920, height = 1080, scale = 1, reserved = { top = 0, bottom = 0, left = 0, right = 0 } }
|
||||
handlers["monitor.layout_changed"]()
|
||||
top, right, bottom, left = gaps()
|
||||
assert(left == 690 and right == 690 and bottom == 540, "ratio 1 is a square")
|
||||
|
||||
omarchy_qconsole_ratio = 0.5
|
||||
handlers["monitor.layout_changed"]()
|
||||
top, right, bottom, left = gaps()
|
||||
assert(left == 690 and right == 690 and bottom == 540, "a ratio below 1 clamps to a square")
|
||||
|
||||
omarchy_qconsole_ratio = nil
|
||||
monitor = dell
|
||||
handlers["monitor.layout_changed"]()
|
||||
top, right, bottom, left = gaps()
|
||||
assert(left == 0 and right == 0 and bottom == 1265, "clearing the ratio restores full width")
|
||||
LUA
|
||||
pass "the console covers half the work area at any monitor scale"
|
||||
pass "the console is a half-height 2:1 panel by default"
|
||||
Reference in new issue
Block a user