displays: ceil logical output sizes on niri so neighbours never overlap

fixes #3875
port 2.0
This commit is contained in:
bbedward
2026-10-23 00:00:13 -04:00
parent d8734bbe1a
commit acc2c947e5
2 changed files with 26 additions and 18 deletions
+16 -15
View File
@@ -833,6 +833,16 @@ function physicalSize(output) {
};
}
function roundLogical(value, compositor) {
switch (compositor) {
case "niri":
// niri rounds the placed output, smithay's Space ceils mapped ones, only ceil never overlaps in either order (#3875)
return Math.ceil(value);
default:
return Math.round(value);
}
}
function logicalSize(output, compositor) {
if (!output)
return {
@@ -841,19 +851,10 @@ function logicalSize(output, compositor) {
};
const phys = physicalSize(output);
const scale = output.logical?.scale || 1.0;
switch (compositor) {
case "niri":
// niri floors logical sizes, rounding up by one leaves a dead seam at snapped edges (#2526)
return {
"w": Math.floor(phys.w / scale),
"h": Math.floor(phys.h / scale)
};
default:
return {
"w": Math.round(phys.w / scale),
"h": Math.round(phys.h / scale)
};
}
return {
"w": roundLogical(phys.w / scale, compositor),
"h": roundLogical(phys.h / scale, compositor)
};
}
function defaultBounds() {
@@ -991,8 +992,8 @@ function recalculateAdjacentPositions(outputs, pendingChanges, changedOutput, ne
if (!output?.logical)
return [];
const oldPhys = physicalSize(output);
const oldLogicalW = Math.round(oldPhys.w / (output.logical.scale || 1.0));
const newLogicalW = Math.round(oldPhys.w / newScale);
const oldLogicalW = roundLogical(oldPhys.w / (output.logical.scale || 1.0), compositor);
const newLogicalW = roundLogical(oldPhys.w / newScale, compositor);
const changedX = pendingChanges[changedOutput]?.position?.x ?? output.logical.x;
const changedY = pendingChanges[changedOutput]?.position?.y ?? output.logical.y;
const moves = [];
+10 -3
View File
@@ -201,15 +201,16 @@ test("aqueous heads: fingerprint, head list and comparison tolerate scale noise
assert.equal(model.outputHeadsMatch(customMode, wlr.outputs, wlr.outputs), true);
});
test("sizes: rotation swaps, niri floors and hyprland rounds logical sizes, bounds default to 1920x1080", () => {
test("sizes: rotation swaps, niri ceils and hyprland rounds logical sizes, bounds default to 1920x1080", () => {
assert.deepEqual(model.physicalSize(live["DP-1"]), { w: 3840, h: 2160 });
assert.deepEqual(model.physicalSize(live["eDP-1"]), { w: 1200, h: 1920 });
assert.deepEqual(model.physicalSize(null), { w: 1920, h: 1080 });
assert.deepEqual(model.logicalSize(live["DP-1"], "niri"), { w: 2560, h: 1440 });
assert.deepEqual(model.logicalSize(live["eDP-1"], "niri"), { w: 923, h: 1476 });
assert.deepEqual(model.logicalSize(live["eDP-1"], "niri"), { w: 924, h: 1477 });
assert.deepEqual(model.logicalSize({ modes: [{ width: 2560, height: 1600 }], current_mode: 0, logical: { scale: 1.75 } }, "niri"), { w: 1463, h: 915 });
assert.deepEqual(model.logicalSize(live["eDP-1"], "hyprland"), { w: 923, h: 1477 });
assert.deepEqual(model.logicalSize({ logical: { width: 2560, height: 1440, scale: 2, transform: "90" } }, "niri"), { w: 1440, h: 2560 });
assert.deepEqual(model.outputBounds(live, "niri"), { minX: 0, minY: 0, maxX: 3483, maxY: 1476, width: 3483, height: 1476 });
assert.deepEqual(model.outputBounds(live, "niri"), { minX: 0, minY: 0, maxX: 3484, maxY: 1477, width: 3484, height: 1477 });
assert.deepEqual(model.outputBounds(live, "hyprland").maxY, 1477);
assert.deepEqual(model.outputBounds({}, "niri"), { minX: 0, minY: 0, maxX: 1920, maxY: 1080, width: 1920, height: 1080 });
});
@@ -222,6 +223,12 @@ test("canvas: overlap ignores disabled outputs, snapping to neighbour edges, adj
assert.equal(model.checkOverlap(layout, "DP-1", 0, 0, 10, 10), false);
assert.equal(model.checkOverlap({ outputs: live, disabled: {}, compositor: "hyprland" }, "DP-1", 0, 0, 10, 10), true);
assert.deepEqual([[2570, 30], [3000, 3000], [2400, 100], [1000, 1500], [-900, 1300]].map(([x, y]) => model.snapToEdges(layout, "eDP-1", x, y, 923, 1476)), [{ x: 2560, y: 0 }, { x: 3000, y: 3000 }, { x: 2560, y: 0 }, { x: 1000, y: 1440 }, { x: -923, y: 1440 }]);
const issue3875 = { compositor: "niri", disabled: {}, outputs: {
"DP-1": { modes: [{ width: 3440, height: 1440 }], current_mode: 0, logical: { x: 0, y: 0, scale: 1 } },
"eDP-1": { modes: [{ width: 2560, height: 1600 }], current_mode: 0, logical: { x: 3440, y: 526, scale: 1.75 } },
"DP-2": { modes: [{ width: 5120, height: 1440 }], current_mode: 0, logical: { x: 10022, y: 0, scale: 1 } }
} };
assert.deepEqual(model.snapToEdges(issue3875, "DP-2", 4890, -5, 5120, 1440), { x: 4903, y: 0 });
assert.deepEqual(model.recalculateAdjacentPositions(live, {}, "DP-1", 2, "niri"), [{ name: "eDP-1", x: 1920, y: 0 }]);
assert.deepEqual(model.recalculateAdjacentPositions(live, { "eDP-1": { position: { x: 2563, y: 5 } } }, "eDP-1", 1, "niri"), [{ name: "eDP-1", x: 2560, y: 5 }]);
assert.deepEqual(model.recalculateAdjacentPositions(live, {}, "HDMI-A-1", 1, "niri"), []);