#!/usr/bin/env bash # SPDX-FileCopyrightText: 2026 Marco Allegretti # SPDX-License-Identifier: GPL-2.0-or-later set -euo pipefail repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" effect_qml="$repo_root/kwin/effects/shift-tile-preview/contents/ui/main.qml" effect_metadata="$repo_root/kwin/effects/shift-tile-preview/metadata.json" animation_effect_js="$repo_root/kwin/effects/shift-tile-animations/contents/code/main.js" animation_effect_metadata="$repo_root/kwin/effects/shift-tile-animations/metadata.json" effects_cmake="$repo_root/kwin/effects/CMakeLists.txt" tiling_script="$repo_root/kwin/scripts/shift-tiling/contents/ui/main.qml" decoration_qml="$repo_root/kwin/decorations/org.shift.decoration/contents/ui/main.qml" running_apps_panel="$repo_root/containments/homescreens/folio/qml/RunningAppsPanel.qml" folio_main="$repo_root/containments/homescreens/folio/qml/main.qml" env_config="$repo_root/envmanager/config.h" require_line() { local file="$1" local needle="$2" if ! grep -Fq -- "$needle" "$file"; then echo "Missing dynamic tile motion invariant in ${file#$repo_root/}: $needle" >&2 exit 1 fi } reject_line() { local file="$1" local needle="$2" if grep -Fq -- "$needle" "$file"; then echo "Rejected dynamic tile motion pattern in ${file#$repo_root/}: $needle" >&2 exit 1 fi } require_line "$effects_cmake" "add_kwin_effect(shift-tile-preview shift-tile-preview)" require_line "$effects_cmake" "add_kwin_effect(shift-tile-animations shift-tile-animations)" require_line "$effect_metadata" '"Id": "shift-tile-preview"' require_line "$effect_metadata" '"X-Plasma-API": "declarativescript"' require_line "$animation_effect_metadata" '"Id": "shift-tile-animations"' require_line "$animation_effect_metadata" '"X-Plasma-API": "javascript"' require_line "$env_config" '{"shift-tilingEnabled", convergenceModeEnabled}' require_line "$env_config" '{"shift-tile-previewEnabled", convergenceModeEnabled}' require_line "$env_config" '{"shift-tile-animationsEnabled", convergenceModeEnabled}' require_line "$env_config" '"shift-tile-preview"' require_line "$env_config" '"shift-tile-animations"' require_line "$env_config" '"shift-tiling"' require_line "$effect_qml" "KWinComponents.SceneEffect" require_line "$effect_qml" "visible: false" require_line "$effect_qml" "ShellSettings.Settings.dynamicTilingEnabled" require_line "$effect_qml" "interactiveMoveResizeStarted.connect" require_line "$effect_qml" "interactiveMoveResizeStepped.connect" require_line "$effect_qml" "interactiveMoveResizeFinished.connect" require_line "$effect_qml" "showPreview(\"swap\"" require_line "$effect_qml" "showPreview(\"float\"" require_line "$effect_qml" "showPreview(\"restore\"" require_line "$effect_qml" "KWinComponents.Workspace.showOutline(previewGeometry)" require_line "$effect_qml" "KWinComponents.Workspace.hideOutline()" reject_line "$effect_qml" "MobileShell.MotionNumberAnimation" reject_line "$effect_qml" "MobileShell.MotionColorAnimation" reject_line "$effect_qml" "disableEffectTimer" require_line "$animation_effect_js" "class ShiftTileAnimationsEffect" require_line "$animation_effect_js" "window.windowFrameGeometryChanged.connect" require_line "$animation_effect_js" "window.windowStartUserMovedResized.connect" require_line "$animation_effect_js" "window.windowFinishUserMovedResized.connect" require_line "$animation_effect_js" "animationTime(" require_line "$animation_effect_js" "type: Effect.Size" require_line "$animation_effect_js" "type: Effect.Translation" require_line "$animation_effect_js" "curve: QEasingCurve.OutCubic" require_line "$animation_effect_js" "window.shiftTileUserMoveResize" require_line "$tiling_script" "readonly property int maxWindowsPerPage: 4" require_line "$tiling_script" "readonly property real stablePrimaryRatio: 0.58" require_line "$tiling_script" "readonly property var layoutModes: [\"master\", \"columns\", \"rows\"]" require_line "$tiling_script" "property int lastWindowRequestSerial: -1" require_line "$tiling_script" "property int lastLayoutRequestSerial: -1" require_line "$tiling_script" "lastWindowRequestSerial = ShellSettings.Settings.dynamicTilingWindowRequestSerial" require_line "$tiling_script" "lastLayoutRequestSerial = ShellSettings.Settings.dynamicTilingLayoutRequestSerial" require_line "$tiling_script" "function desktopKey(desktop)" require_line "$tiling_script" "function desktopForWindow(win)" require_line "$tiling_script" "function normalizeWindowDesktopScope(win)" require_line "$tiling_script" "function layoutKeyFor(outputName, desktop)" require_line "$tiling_script" "function desktopForLayoutKey(outputName)" require_line "$tiling_script" "function layoutKeyForWindow(win)" require_line "$tiling_script" "function assignWindowToDesktop(win, desktop)" require_line "$tiling_script" "function targetLayoutForNewWindow(win)" require_line "$tiling_script" "function leafIndexForWindow(outputName, win)" require_line "$tiling_script" "function leafForSlot(outputName, preferredIndex)" require_line "$tiling_script" "property var lastFocusedWindowKeys: ({})" require_line "$tiling_script" "function rememberFocusedWindow(win)" require_line "$tiling_script" "function replacementLeafForLayout(outputName, fallbackIndex)" require_line "$tiling_script" "function swapWindowBetweenLayouts(win, sourceName, targetName)" require_line "$tiling_script" "windowCountForLayout(currentKey) < maxWindowsPerPage" require_line "$tiling_script" "windowCountForLayout(targetName) >= maxWindowsPerPage && swapWindowBetweenLayouts(win, currentName, targetName)" require_line "$tiling_script" "KWinComponents.Workspace.currentDesktop = target.desktop" require_line "$tiling_script" "assignWindowToDesktop(displacedWindow, sourceDesktop)" require_line "$tiling_script" "const targetLeaf = replacementLeafForLayout(targetName, sourceIndex)" require_line "$tiling_script" "function onActiveWindowChanged()" require_line "$tiling_script" "root.rememberFocusedWindow(KWinComponents.Workspace.activeWindow)" require_line "$tiling_script" "win.desktops = [desktop]" require_line "$tiling_script" "win.desktopsChanged.connect" require_line "$tiling_script" "function dynamicTilingOwnsWindow(win)" require_line "$tiling_script" "function normalizeNativeWindowOperationState(win)" require_line "$tiling_script" "function handleNativeWindowOperationStateChange(win)" require_line "$tiling_script" "function adoptWindow(win)" require_line "$tiling_script" "win.maximizeModeChanged.connect" require_line "$tiling_script" "win.fullScreenChanged.connect" require_line "$tiling_script" "win.noBorderChanged.connect" require_line "$tiling_script" "win.keepAboveChanged.connect" require_line "$tiling_script" "win.keepBelowChanged.connect" require_line "$tiling_script" "property bool dragNativeResize: false" require_line "$tiling_script" "if (dragNativeResize && dragSourceScreen && dragSourceIndex >= 0)" require_line "$tiling_script" "function orderedWindowsForScreen(outputName)" require_line "$tiling_script" "function buildStableStack(windows, startIndex)" require_line "$tiling_script" "function buildLinearLayout(windows, startIndex, orientation)" require_line "$tiling_script" "function layoutModeForScreen(outputName)" require_line "$tiling_script" "function buildStableLayout(windows, mode)" require_line "$tiling_script" "function setStableLayout(outputName, windows)" require_line "$tiling_script" "function cycleLayoutMode(outputName)" require_line "$tiling_script" "function swapDraggedWindow(target)" require_line "$tiling_script" "showDragOutline(\"swap\", target.rect)" require_line "$tiling_script" "swapDraggedWindow(dragSwapTarget)" require_line "$tiling_script" "setStableLayout(outputName, windows)" require_line "$tiling_script" "setStableLayout(outputName, remaining)" require_line "$tiling_script" "retileScreen(name)" require_line "$tiling_script" "markLayoutChanged(transaction, targetName)" require_line "$tiling_script" "function promoteWindow(win)" require_line "$tiling_script" "setStableLayout(outputName, reordered)" require_line "$tiling_script" "name: \"SHIFT Tiling Promote Primary\"" require_line "$tiling_script" "sequence: \"Meta+Shift+Return\"" require_line "$tiling_script" "name: \"SHIFT Tiling Cycle Layout\"" require_line "$tiling_script" "sequence: \"Meta+Shift+T\"" require_line "$decoration_qml" "readonly property bool windowMenuAllowed: !ShellSettings.Settings.convergenceModeEnabled" require_line "$decoration_qml" "return root.windowMenuAllowed;" require_line "$decoration_qml" "enabled: !root.windowMenuAllowed" require_line "$decoration_qml" "acceptedButtons: Qt.RightButton" require_line "$decoration_qml" "readonly property int frameThickness: decoration.client.maximized ? 0 : normalCornerRadius" require_line "$decoration_qml" "borders.left = normalCornerRadius;" require_line "$decoration_qml" "borders.right = normalCornerRadius;" require_line "$decoration_qml" "borders.bottom = normalCornerRadius;" require_line "$decoration_qml" "PathArc { x: root.width - root.cornerRadius; y: root.height; radiusX: root.cornerRadius; radiusY: root.cornerRadius }" require_line "$decoration_qml" "PathArc { x: 0; y: root.height - root.cornerRadius; radiusX: root.cornerRadius; radiusY: root.cornerRadius }" require_line "$folio_main" "readonly property int layoutMenuWindowCount: Math.max(0, ShellSettings.Settings.dynamicTilingLayoutWindowCount)" require_line "$folio_main" "&& layoutMenuWindowCount >= 2" require_line "$folio_main" "if (layoutMenuEnabled) {" require_line "$folio_main" "regions.push(rightEdgeRegion)" require_line "$folio_main" "if (layoutMenuEnabled && layoutMenuOpen)" require_line "$folio_main" "rightEdgeHovered = false" require_line "$folio_main" "layoutMenuHovered = false" require_line "$folio_main" "id: rightEdgeHoverArea" require_line "$folio_main" "convergenceChrome.rightEdgeHovered = containsMouse" require_line "$folio_main" "width: convergenceChrome.layoutMenuEnabled ? convergenceChrome.rightEdgeHotzoneWidth : 0" running_panel_group_disabled_count="$(grep -F "groupMode: TaskManager.TasksModel.GroupDisabled" "$running_apps_panel" | wc -l)" if [[ "$running_panel_group_disabled_count" -ne 2 ]]; then echo "Expected the Folio Running panel to disable grouping for both task models; found $running_panel_group_disabled_count" >&2 exit 1 fi reject_line "$running_apps_panel" "groupMode: TaskManager.TasksModel.GroupApplications" if awk ' /ShellSettings\.Settings\.requestDynamicTilingWindowAction\(taskCard\.windowId, action\)/ { seen = 1; next } seen && /return/ { exit 0 } seen && /tasksModel\.requestVirtualDesktops\(taskCard\.modelIndex, \[desktopId\]\)/ { exit 1 } END { exit 1 } ' "$running_apps_panel"; then echo "Expected the Folio Running panel desktop drop to fall through to TaskManager after notifying dynamic tiling" >&2 exit 1 fi reject_line "$tiling_script" "targetKey = lastLeafKey(rootNode)" reject_line "$tiling_script" "function lastLeafKey(node)" reject_line "$tiling_script" "win.output.name !== outputName" reject_line "$tiling_script" "const targetName = output.name" reject_line "$tiling_script" "const desktop = win.desktops[0]" reject_line "$tiling_script" "setScreenLayout(outputName, splitLeaf(rootNode, targetKey" reject_line "$tiling_script" "function moveWindowToSplit(target)" reject_line "$tiling_script" "function splitLeaf(node, targetKey" reject_line "$tiling_script" "function tileInsertDirection(cursor, rect)" reject_line "$tiling_script" "showDragOutline(\"insert\"" reject_line "$tiling_script" "KWinComponents.Workspace.showOutline(dragOutlineRect)" reject_line "$tiling_script" "KWinComponents.Workspace.hideOutline()" reject_line "$tiling_script" "property int lastWindowRequestSerial: ShellSettings.Settings.dynamicTilingWindowRequestSerial" reject_line "$tiling_script" "property int lastLayoutRequestSerial: ShellSettings.Settings.dynamicTilingLayoutRequestSerial" reject_line "$effect_qml" "effect.visible = true" printf '%s\n' 'dynamic-tiles-motion-ok'