From 0f4243c7f2225cbb13caf2c147fd0f1ca0858dff Mon Sep 17 00:00:00 2001 From: 0x1f6 <178943044+0x1f6@users.noreply.github.com> Date: Thu, 1 Oct 2026 00:10:09 +0200 Subject: [PATCH] Plan a layout when the rows setting exceeds what the screens fill ft-layout crashed - and so did arranging the screens - whenever the row setting in the layout preset is above what the screens fill. plan() builds a grid with cols = ceil(count / rows) columns; when that leaves fewer full rows than the setting asked for, the extra rows are empty, and the per-row height max() over an empty row raised ValueError: max() arg is an empty sequence at plan, line 300 (flat) and 312 (curved) The smallest real case is 4 screens with 3 rows: cols = ceil(4/3) = 2, the first two rows hold all 4 screens, the third row is empty. Both the flat wall and the curved preset crashed, so apply, plan and the auto-arrange at desktop start all failed until the row setting was lowered. The Rows spinner in Frametop Display Settings (main.qml) allows any row count up to the screen count, and clamping to the screen count does not prevent this - 3 rows for 4 screens is within that range and never fits a full grid - so the crash was reachable from the UI as shipped. Reproduced by calling plan() directly with 4 screens and 3 rows: both kinds raised. Also verified the whole placement matrix (counts 1-10, rows 1-4) places every screen after the fix. The fix trims rows to the number of rows the screens actually fill, rows = ceil(count / cols), after cols is computed. The row count is used again for stacking (gap times rows - 1, the sum of row heights), so the stacking matches the trimmed grid: no empty row is ever built, and a rows setting that can't be honoured degrades to the tightest fit instead of failing. (cherry picked from commit f2bdbd97a0561a180d91aba034f7848b5a1be24f) Co-Authored-By: Claude Opus 5.5 --- layout/ft_layout.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/layout/ft_layout.py b/layout/ft_layout.py index a7fdd36..21b9900 100755 --- a/layout/ft_layout.py +++ b/layout/ft_layout.py @@ -318,6 +318,10 @@ def plan(layout, count, panel_size=None): p = layout["preset"] rows = max(1, min(int(p.get("rows", 1)), count)) cols = math.ceil(count / rows) + # A row setting above what the screens fill leaves empty grid rows (4 screens, + # 3 rows -> cols 2 -> a third row with nothing in it), and max() over an empty + # row crashes plan(). Trim rows to what the screens actually fill. + rows = max(1, math.ceil(count / cols)) d = max(0.3, float(p.get("distance", 2.0))) gap = max(0.0, float(p.get("gap", 0.05))) height = float(p.get("height", 0.0))