Rebuild Displays around the canvas, and let the transaction keep color
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
+112
-18
@@ -13,8 +13,9 @@
|
||||
-- than an HDR desktop, the desktop runs SDR at 10-bit and HDR is handed to
|
||||
-- fullscreen games only, via render.cm_auto_hdr in looks.lua.
|
||||
--
|
||||
-- To try full-time HDR anyway, set cm = "hdr" below (or override it in
|
||||
-- overrides.lua) and read the notes in that file first.
|
||||
-- To try full-time HDR anyway, set shipped_cm = "hdr" below (or pick HDR for
|
||||
-- this display in Settings, which saves it as the display's colorProfile and
|
||||
-- wins over the shipped value) and read the notes in overrides.lua first.
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
local prefs = require("prefs")
|
||||
@@ -23,12 +24,21 @@ local prefs = require("prefs")
|
||||
-- { ["DP-2"] = {
|
||||
-- mode = "3840x2160@60", scale = 2, transform = 0,
|
||||
-- x = 0, y = 0, primary = true,
|
||||
-- vrrMode = -1, colorProfile = "auto", bitdepth = 10,
|
||||
-- sdrBrightness = 1, sdrSaturation = 1, mirrorOf = "",
|
||||
-- } }
|
||||
--
|
||||
-- Only mode, scale, and transform are read. Color management and bit depth
|
||||
-- stay here, because those are the settings with a documented reason attached
|
||||
-- (see the header) rather than preferences, and a settings page has no way to
|
||||
-- explain the screencopy tradeoff at the moment you would be changing it.
|
||||
-- The fields past `primary` are optional: records written before they existed
|
||||
-- carry none of them, and the shipped values below stand instead. That is the
|
||||
-- relationship in both directions -- what is written here is what a saved
|
||||
-- record inherits, and what Settings saves is what overrides it, so the two
|
||||
-- stop fighting over the same monitor rule.
|
||||
--
|
||||
-- Geometry and colour fail differently on purpose. A half-written position is
|
||||
-- refused outright (below), because guessing one can strand an output where
|
||||
-- nothing can reach it. An unreadable colour, VRR or mirror value is dropped
|
||||
-- on its own and the shipped default stands: the worst it costs is a wrong
|
||||
-- shade, and taking the whole record down with it would cost the arrangement.
|
||||
local displays = prefs.get("displays", {})
|
||||
if type(displays) ~= "table" then
|
||||
displays = {}
|
||||
@@ -125,19 +135,102 @@ local function display_position(entry, fallback)
|
||||
return fallback
|
||||
end
|
||||
|
||||
local color_profiles = { auto = true, srgb = true, wide = true, hdr = true }
|
||||
|
||||
local function color_profile(entry, fallback)
|
||||
local value = entry ~= nil and entry.colorProfile or nil
|
||||
if type(value) == "string" and color_profiles[value] then
|
||||
return value
|
||||
end
|
||||
return fallback
|
||||
end
|
||||
|
||||
local function bitdepth_value(entry, fallback)
|
||||
local value = entry ~= nil and entry.bitdepth or nil
|
||||
if value == 8 or value == 10 then
|
||||
return value
|
||||
end
|
||||
return fallback
|
||||
end
|
||||
|
||||
-- -1 means the display follows the global misc.vrr policy, which is said by
|
||||
-- leaving the key out. 3 is the global policy's own value and not a per-display
|
||||
-- choice, so it is not accepted here either.
|
||||
local function vrr_value(entry)
|
||||
local value = entry ~= nil and entry.vrrMode or nil
|
||||
if type(value) ~= "number" or value ~= math.floor(value)
|
||||
or value < 0 or value > 2 then
|
||||
return nil
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
-- Neutral is 1.0, and the neutral value is left out rather than written: a rule
|
||||
-- that names it pins the display to it, which is not the same as leaving the
|
||||
-- trim alone.
|
||||
local function sdr_value(value, minimum, maximum)
|
||||
if type(value) ~= "number" or value ~= value
|
||||
or value < minimum or value > maximum
|
||||
or math.abs(value - 1) < 0.001 then
|
||||
return nil
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
-- A mirror needs a target that is not itself and not another mirror -- Hyprland
|
||||
-- has no chain to follow -- and the primary may not mirror at all, since the
|
||||
-- arrangement is anchored on it.
|
||||
local function mirror_value(entry, output)
|
||||
local value = entry ~= nil and entry.mirrorOf or nil
|
||||
if type(value) ~= "string" or value == "" or value == output
|
||||
or value:match("^[%w_.-]+$") == nil
|
||||
or entry.primary == true then
|
||||
return nil
|
||||
end
|
||||
local target = displays[value]
|
||||
if type(target) == "table" and type(target.mirrorOf) == "string"
|
||||
and target.mirrorOf ~= "" then
|
||||
return nil
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
-- Colour, VRR and mirroring layered onto a rule that already carries
|
||||
-- mode/position/scale/transform. A monitor rule replaces the previous rule for
|
||||
-- that output whole, so the shipped defaults are passed in here rather than
|
||||
-- written in a rule of their own. `connector` is the output name the record was
|
||||
-- saved under, which is not always the rule's own output: the Kuycon rule
|
||||
-- matches by description.
|
||||
local function with_display_fields(rule, entry, connector, default_bitdepth, default_cm)
|
||||
rule.bitdepth = bitdepth_value(entry, default_bitdepth)
|
||||
rule.cm = color_profile(entry, default_cm)
|
||||
rule.vrr = vrr_value(entry)
|
||||
rule.sdrbrightness = entry ~= nil and sdr_value(entry.sdrBrightness, 0.8, 2.0) or nil
|
||||
rule.sdrsaturation = entry ~= nil and sdr_value(entry.sdrSaturation, 0.8, 1.2) or nil
|
||||
|
||||
-- A mirror shows its target's picture in its target's place, so the saved
|
||||
-- position is not the compositor's to honour or ours to ask for.
|
||||
local mirror = mirror_value(entry, connector)
|
||||
if mirror ~= nil then
|
||||
rule.mirror = mirror
|
||||
rule.position = "auto"
|
||||
end
|
||||
return rule
|
||||
end
|
||||
|
||||
-- Every connected output uses the same validated per-output store. Automatic
|
||||
-- placement and the compositor's normal color policy unless the entry says
|
||||
-- otherwise.
|
||||
for output, _ in pairs(displays) do
|
||||
local entry = display_entry(output)
|
||||
if entry ~= nil then
|
||||
hl.monitor({
|
||||
hl.monitor(with_display_fields({
|
||||
output = output,
|
||||
mode = entry.mode,
|
||||
position = display_position(entry, "auto"),
|
||||
scale = entry.scale,
|
||||
transform = entry.transform,
|
||||
})
|
||||
}, entry, output, nil, nil))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -150,22 +243,23 @@ end
|
||||
local shipped_mode = "4500x3000@60"
|
||||
local shipped_scale = 1.5
|
||||
local shipped_transform = 0
|
||||
|
||||
-- 10-bit output. 4500x3000@60 at 10bpc is ~24 Gbps, right at the edge of DP 1.4
|
||||
-- HBR3, so this relies on DSC. If the display fails to light up or falls back to
|
||||
-- a lower mode, drop this to 8 first.
|
||||
local shipped_bitdepth = 10
|
||||
|
||||
-- "auto" = sRGB at 8bpc, wide gamut at 10bpc. Not HDR; see header.
|
||||
local shipped_cm = "auto"
|
||||
|
||||
local kuycon = display_entry("DP-2")
|
||||
hl.monitor({
|
||||
hl.monitor(with_display_fields({
|
||||
output = "desc:GVT Kuycon P20",
|
||||
mode = kuycon and kuycon.mode or shipped_mode,
|
||||
position = display_position(kuycon, "0x0"),
|
||||
scale = kuycon and kuycon.scale or shipped_scale,
|
||||
transform = kuycon and kuycon.transform or shipped_transform,
|
||||
|
||||
-- 10-bit output. 4500x3000@60 at 10bpc is ~24 Gbps, right at the edge of
|
||||
-- DP 1.4 HBR3, so this relies on DSC. If the display fails to light up or
|
||||
-- falls back to a lower mode, drop this line first.
|
||||
bitdepth = 10,
|
||||
|
||||
-- "auto" = sRGB at 8bpc, wide gamut at 10bpc. Not HDR; see header.
|
||||
cm = "auto",
|
||||
})
|
||||
}, kuycon, "DP-2", shipped_bitdepth, shipped_cm))
|
||||
|
||||
-- Any monitor not named above: sane defaults rather than nothing.
|
||||
hl.monitor({
|
||||
|
||||
Reference in New Issue
Block a user