Merge pull request #9140 from ogarza/center-qconsole
Center the Quake console in a 2:1 panel instead of spanning the screen.
This commit is contained in:
3 files changed
+336
-37
No files matched your search
+130
-22
@@ -5,6 +5,14 @@
|
||||
-- How much of the usable screen the console covers, measured from the top.
|
||||
local share = 0.5
|
||||
|
||||
-- A console holding a single window is boxed into a centered panel this many
|
||||
-- times wider than it is tall, rather than stretched the width of the screen.
|
||||
-- A second app on the scratchpad gets the full width back: two windows splitting
|
||||
-- a half-width column is worse than the band this replaced.
|
||||
local box = 2
|
||||
|
||||
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
|
||||
-- the workspace itself: Hyprland only tags a spawn with the workspace it came
|
||||
@@ -23,22 +31,25 @@ hl.config({
|
||||
},
|
||||
})
|
||||
|
||||
-- The panel is always flush with the top and always centered, so two numbers
|
||||
-- describe it: the gap down each side and the gap underneath.
|
||||
--
|
||||
-- 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 beside, below = nil, nil
|
||||
|
||||
local function cover(bottom)
|
||||
if covering == bottom then
|
||||
return
|
||||
local function cover(side, bottom)
|
||||
if beside == side and below == bottom then
|
||||
return false
|
||||
end
|
||||
covering = bottom
|
||||
beside, below = side, bottom
|
||||
|
||||
hl.workspace_rule({
|
||||
workspace = "special:scratchpad",
|
||||
workspace = SCRATCHPAD,
|
||||
gaps_in = 0,
|
||||
gaps_out = { top = 0, right = 0, bottom = bottom, left = 0 },
|
||||
gaps_out = { top = 0, right = side, bottom = bottom, left = side },
|
||||
|
||||
-- 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 +58,136 @@ local function cover(bottom)
|
||||
|
||||
on_created_empty = seed,
|
||||
})
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- One window reads as a console and gets the panel. A second app has turned the
|
||||
-- scratchpad into a workspace, and a workspace wants the whole width. Only tiled
|
||||
-- windows count: the gaps are what size the panel, and a floating window on top
|
||||
-- of the console is not laid out by them.
|
||||
local function alone()
|
||||
local tiled = 0
|
||||
for _, window in ipairs(hl.get_workspace_windows(SCRATCHPAD)) do
|
||||
if not window.floating then
|
||||
tiled = tiled + 1
|
||||
end
|
||||
end
|
||||
return tiled <= 1
|
||||
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 area left around it, and that area is
|
||||
-- recomputed whenever the monitor it opens on changes.
|
||||
local function fit(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.
|
||||
-- width, height and reserved below.
|
||||
if not monitor or not monitor.scale or monitor.scale <= 0 then
|
||||
return
|
||||
return false
|
||||
end
|
||||
|
||||
-- Width and height are the panel's own pixels, so a monitor turned on its
|
||||
-- side still reports them the way the panel is built. The odd transforms are
|
||||
-- the quarter turns, and those are the ones that swap the work area.
|
||||
local width, height = monitor.width, monitor.height
|
||||
if monitor.transform % 2 == 1 then
|
||||
width, height = height, width
|
||||
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
|
||||
height = height / monitor.scale - reserved.top - reserved.bottom
|
||||
width = width / monitor.scale - reserved.left - reserved.right
|
||||
|
||||
cover(math.max(0, math.floor(usable * (1 - share))))
|
||||
local tall = math.floor(height * share)
|
||||
local wide = width
|
||||
if alone() then
|
||||
wide = math.min(width, tall * box)
|
||||
end
|
||||
|
||||
return cover(math.floor((width - wide) / 2), math.floor(height - tall))
|
||||
end
|
||||
|
||||
-- The console keeps the geometry of the output it is open on: a follow_mouse hop
|
||||
-- onto another screen must not resize a console that is already showing. While
|
||||
-- it is hidden there is nothing to size but the output that will show it next.
|
||||
local function console_monitor()
|
||||
local ws = hl.get_workspace(SCRATCHPAD)
|
||||
local mon = ws and ws.visible and ws.monitor
|
||||
|
||||
if mon and mon.scale and mon.scale > 0 then
|
||||
return mon
|
||||
end
|
||||
|
||||
return hl.get_active_monitor()
|
||||
end
|
||||
|
||||
local function refit(monitor)
|
||||
if fit(monitor or console_monitor()) then
|
||||
-- Land the new gaps in this pass rather than a frame later, so the console
|
||||
-- does not visibly resize itself once it has already dropped down.
|
||||
hl.exec_scheduled_prop_refresh_immediately()
|
||||
end
|
||||
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)
|
||||
fit()
|
||||
-- the console unruled, so it is never seeded without its placement. A reload
|
||||
-- runs this again with the console already on screen, so it starts from the
|
||||
-- output the console is on rather than whichever one the pointer is over.
|
||||
cover(0, 0)
|
||||
fit(console_monitor())
|
||||
|
||||
hl.on("monitor.layout_changed", fit)
|
||||
hl.on("monitor.focused", fit)
|
||||
hl.on("monitor.layout_changed", function()
|
||||
refit()
|
||||
end)
|
||||
|
||||
hl.on("monitor.focused", function()
|
||||
refit()
|
||||
end)
|
||||
|
||||
-- Special workspaces open on the monitor they are toggled on, not on whichever
|
||||
-- output last happened to be focused when the rule was written, so these two
|
||||
-- take the monitor they are handed rather than looking one up.
|
||||
hl.on("workspace.special_active", function(ws, mon)
|
||||
if ws and ws.name == SCRATCHPAD then
|
||||
refit(mon)
|
||||
end
|
||||
end)
|
||||
|
||||
hl.on("workspace.move_to_monitor", function(ws, mon)
|
||||
if ws and ws.name == SCRATCHPAD then
|
||||
refit(mon)
|
||||
end
|
||||
end)
|
||||
|
||||
-- The panel is only centered while the console holds one tiled window, so the
|
||||
-- count has to be rechecked as apps come and go: opened and closed, moved on or
|
||||
-- off (Super+Alt+S, Super+Shift+1), and floated or tiled (Super+T, Super+O).
|
||||
-- window.close is left out, since it still counts the window on its way out and
|
||||
-- window.destroy follows it anyway. Measured on Hyprland 0.56.2, a move is
|
||||
-- trailed by several window.update_rules, as is a float toggle, and the last of
|
||||
-- those always reads the settled count; the earlier ones refit to what the rule
|
||||
-- already is, which cover() skips.
|
||||
--
|
||||
-- Only while it is on screen, though. A hidden console is refitted on its way in
|
||||
-- by workspace.special_active, and every window opened anywhere on the desktop
|
||||
-- would otherwise rewrite the rule.
|
||||
local function recount()
|
||||
local ws = hl.get_workspace(SCRATCHPAD)
|
||||
if ws and ws.visible then
|
||||
refit()
|
||||
end
|
||||
end
|
||||
|
||||
hl.on("window.open", recount)
|
||||
hl.on("window.destroy", recount)
|
||||
hl.on("window.move_to_workspace", recount)
|
||||
hl.on("window.update_rules", recount)
|
||||
|
||||
-- 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"
|
||||
|
||||
Reference in new issue
Block a user