-- ───────────────────────────────────────────────────────────────────────────── -- Monitors -- -- Kuycon P20 on DP-2: 4500x3000 @ 60Hz, 1.5x fractional scale. -- 4500/1.5 = 3000 and 3000/1.5 = 2000, both integers, so this is a "clean" -- fractional scale and Hyprland will not complain. -- -- On HDR -------------------------------------------------------------------- -- GNOME runs this panel in bt2100/HDR. Hyprland CAN do that (cm = "hdr"), but -- on 0.56.x it currently breaks screencopy: grim screenshots come back empty, -- and OBS/Sunshine capture and hyprlock's blurred background go with them. -- Root cause is still open upstream. Since screen capture matters more here -- 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. -- ───────────────────────────────────────────────────────────────────────────── local prefs = require("prefs") -- Per-output overrides written by Panama Settings, keyed by output name: -- { ["DP-2"] = { -- mode = "3840x2160@60", scale = 2, transform = 0, -- x = 0, y = 0, primary = true, -- } } -- -- Only mode, scale, and transform are read. Colour 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. local displays = prefs.get("displays", {}) if type(displays) ~= "table" then displays = {} end local function mode_dimensions(mode) if type(mode) ~= "string" then return nil, nil end local width, height, refresh = mode:match("^(%d+)x(%d+)@(%d+%.%d+)$") if width == nil then width, height, refresh = mode:match("^(%d+)x(%d+)@(%d+)$") end width, height, refresh = tonumber(width), tonumber(height), tonumber(refresh) if width == nil or height == nil or refresh == nil or width <= 0 or height <= 0 or refresh <= 0 then return nil, nil end return width, height end local function valid_mode(mode) local width = mode_dimensions(mode) return width ~= nil end local function valid_scale(mode, scale) local width, height = mode_dimensions(mode) if width == nil or type(scale) ~= "number" or scale ~= scale or scale <= 0 or scale > 4 then return false end local logical_width = width / scale local logical_height = height / scale return math.abs(logical_width - math.floor(logical_width + 0.5)) < 0.0001 and math.abs(logical_height - math.floor(logical_height + 0.5)) < 0.0001 end local function valid_transform(transform) return type(transform) == "number" and transform == math.floor(transform) and transform >= 0 and transform <= 3 end local function valid_coordinate(value) return type(value) == "number" and value == value and value == math.floor(value) and value >= -100000 and value <= 100000 end local function valid_position(entry) return valid_coordinate(entry.x) and valid_coordinate(entry.y) end local function valid_primary(entry) return type(entry.primary) == "boolean" end local function has_layout_fields(entry) return entry.x ~= nil or entry.y ~= nil or entry.primary ~= nil end local function display_entry(output) if type(output) ~= "string" or output == "" or output:match("^[%w_.-]+$") == nil then return nil end local entry = displays[output] if type(entry) ~= "table" then return nil end if not valid_mode(entry.mode) or not valid_scale(entry.mode, entry.scale) or not valid_transform(entry.transform) then return nil end -- Legacy records have none of the layout fields and keep automatic -- placement. A partially written extended record is unsafe: accepting its -- mode but guessing its position could overlap or strand another output. if has_layout_fields(entry) and (not valid_position(entry) or not valid_primary(entry)) then return nil end return entry end local function display_position(entry, fallback) if entry ~= nil and has_layout_fields(entry) then return string.format("%dx%d", entry.x, entry.y) end return fallback end local shipped_mode = "4500x3000@60" local shipped_scale = 1.5 local shipped_transform = 0 local dp2 = display_entry("DP-2") hl.monitor({ output = "DP-2", mode = dp2 and dp2.mode or shipped_mode, position = display_position(dp2, "0x0"), scale = dp2 and dp2.scale or shipped_scale, transform = dp2 and dp2.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", }) -- Other connected outputs use the same validated per-output store. They keep -- automatic placement and the compositor's normal colour policy; DP-2 alone -- carries the panel-specific 10-bit policy documented above. for output, _ in pairs(displays) do if output ~= "DP-2" then local entry = display_entry(output) if entry ~= nil then hl.monitor({ output = output, mode = entry.mode, position = display_position(entry, "auto"), scale = entry.scale, transform = entry.transform, }) end end end -- Any monitor not named above: sane defaults rather than nothing. hl.monitor({ output = "", mode = "preferred", position = "auto", scale = "auto", }) return true