Split display layout into its own widget and fix installer early exit
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
.pragma library
|
||||
|
||||
// hyprctl reports logical size as the raw mode divided by scale, so a 1920x1200
|
||||
// panel at 1.5 occupies 1280x800 of layout space. Positions are in that same
|
||||
// logical space, which is what the canvas has to draw.
|
||||
function logicalSize(monitor) {
|
||||
var scale = Number(monitor.scale) || 1
|
||||
var width = Number(monitor.width) || 0
|
||||
var height = Number(monitor.height) || 0
|
||||
if (isRotated(monitor)) {
|
||||
var swap = width
|
||||
width = height
|
||||
height = swap
|
||||
}
|
||||
return {
|
||||
width: Math.round(width / scale),
|
||||
height: Math.round(height / scale)
|
||||
}
|
||||
}
|
||||
|
||||
function isRotated(monitor) {
|
||||
var transform = Number(monitor.transform) || 0
|
||||
return transform === 1 || transform === 3 || transform === 5 || transform === 7
|
||||
}
|
||||
|
||||
function parseMonitors(raw) {
|
||||
var parsed
|
||||
try {
|
||||
parsed = JSON.parse(String(raw || "[]"))
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
if (!Array.isArray(parsed)) return []
|
||||
|
||||
var monitors = []
|
||||
for (var i = 0; i < parsed.length; i++) {
|
||||
var m = parsed[i] || {}
|
||||
var size = logicalSize(m)
|
||||
monitors.push({
|
||||
name: String(m.name || ""),
|
||||
description: String(m.description || ""),
|
||||
mode: modeStringOf(m),
|
||||
modes: Array.isArray(m.availableModes) ? m.availableModes : [],
|
||||
x: Number(m.x) || 0,
|
||||
y: Number(m.y) || 0,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
scale: Number(m.scale) || 1,
|
||||
transform: Number(m.transform) || 0,
|
||||
disabled: m.disabled === true,
|
||||
focused: m.focused === true,
|
||||
mirrorOf: String(m.mirrorOf || "none")
|
||||
})
|
||||
}
|
||||
monitors.sort(function(a, b) { return a.x - b.x })
|
||||
return monitors
|
||||
}
|
||||
|
||||
function modeStringOf(monitor) {
|
||||
var width = Number(monitor.width) || 0
|
||||
var height = Number(monitor.height) || 0
|
||||
var rate = Number(monitor.refreshRate) || 0
|
||||
if (!width || !height) return "preferred"
|
||||
return width + "x" + height + "@" + rate.toFixed(2)
|
||||
}
|
||||
|
||||
// The canvas is a scaled-down picture of the desktop. Monitors can sit at
|
||||
// negative coordinates, so the bounding box is translated to the origin before
|
||||
// a single scale factor is chosen for both axes.
|
||||
function layoutBounds(monitors) {
|
||||
if (!monitors.length) return { x: 0, y: 0, width: 1, height: 1 }
|
||||
var minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity
|
||||
for (var i = 0; i < monitors.length; i++) {
|
||||
var m = monitors[i]
|
||||
if (m.disabled) continue
|
||||
minX = Math.min(minX, m.x)
|
||||
minY = Math.min(minY, m.y)
|
||||
maxX = Math.max(maxX, m.x + m.width)
|
||||
maxY = Math.max(maxY, m.y + m.height)
|
||||
}
|
||||
if (minX === Infinity) return { x: 0, y: 0, width: 1, height: 1 }
|
||||
return { x: minX, y: minY, width: Math.max(1, maxX - minX), height: Math.max(1, maxY - minY) }
|
||||
}
|
||||
|
||||
function canvasScale(bounds, availableWidth, availableHeight) {
|
||||
return Math.min(availableWidth / bounds.width, availableHeight / bounds.height)
|
||||
}
|
||||
|
||||
// Snap a dragged edge to a neighbour's edge when it lands within the threshold,
|
||||
// so monitors end up touching exactly rather than a few pixels apart.
|
||||
function snapPosition(monitors, name, x, y, threshold) {
|
||||
var snappedX = x
|
||||
var snappedY = y
|
||||
var self = null
|
||||
for (var i = 0; i < monitors.length; i++) {
|
||||
if (monitors[i].name === name) { self = monitors[i]; break }
|
||||
}
|
||||
if (!self) return { x: Math.round(x), y: Math.round(y) }
|
||||
|
||||
for (var j = 0; j < monitors.length; j++) {
|
||||
var other = monitors[j]
|
||||
if (other.name === name || other.disabled) continue
|
||||
|
||||
var candidatesX = [
|
||||
other.x + other.width,
|
||||
other.x - self.width,
|
||||
other.x
|
||||
]
|
||||
for (var cx = 0; cx < candidatesX.length; cx++) {
|
||||
if (Math.abs(snappedX - candidatesX[cx]) <= threshold) {
|
||||
snappedX = candidatesX[cx]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var candidatesY = [
|
||||
other.y + other.height,
|
||||
other.y - self.height,
|
||||
other.y
|
||||
]
|
||||
for (var cy = 0; cy < candidatesY.length; cy++) {
|
||||
if (Math.abs(snappedY - candidatesY[cy]) <= threshold) {
|
||||
snappedY = candidatesY[cy]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return { x: Math.round(snappedX), y: Math.round(snappedY) }
|
||||
}
|
||||
|
||||
// Hyprland's keyword form: monitor = name,mode,position,scale[,transform,N]
|
||||
function monitorKeyword(monitor) {
|
||||
if (monitor.disabled) return monitor.name + ",disable"
|
||||
var parts = [
|
||||
monitor.name,
|
||||
monitor.mode,
|
||||
monitor.x + "x" + monitor.y,
|
||||
String(monitor.scale)
|
||||
]
|
||||
var keyword = parts.join(",")
|
||||
if (monitor.transform && monitor.transform !== 0)
|
||||
keyword += ",transform," + monitor.transform
|
||||
if (monitor.mirrorOf && monitor.mirrorOf !== "none")
|
||||
keyword += ",mirror," + monitor.mirrorOf
|
||||
return keyword
|
||||
}
|
||||
|
||||
function keywordsFor(monitors) {
|
||||
var out = []
|
||||
for (var i = 0; i < monitors.length; i++) out.push(monitorKeyword(monitors[i]))
|
||||
return out
|
||||
}
|
||||
|
||||
// Hyprland rejects a fractional scale that does not land on a whole number of
|
||||
// physical pixels. It steps in 1/120, so a scale is valid when both axes come
|
||||
// out integral at that granularity.
|
||||
function scaleIsValid(monitor, scale) {
|
||||
if (!scale || scale <= 0) return false
|
||||
var stepped = Math.round(scale * 120) / 120
|
||||
var width = Number(monitor.width) * Number(monitor.scale)
|
||||
var height = Number(monitor.height) * Number(monitor.scale)
|
||||
var logicalWidth = width / stepped
|
||||
var logicalHeight = height / stepped
|
||||
return Math.abs(logicalWidth - Math.round(logicalWidth)) < 0.001
|
||||
&& Math.abs(logicalHeight - Math.round(logicalHeight)) < 0.001
|
||||
}
|
||||
|
||||
function nearbyScales(monitor) {
|
||||
var options = []
|
||||
for (var step = 60; step <= 300; step += 6) {
|
||||
var candidate = step / 120
|
||||
if (scaleIsValid(monitor, candidate)) options.push(candidate)
|
||||
}
|
||||
if (!options.length) options.push(1)
|
||||
return options
|
||||
}
|
||||
Reference in New Issue
Block a user