diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..404cc01 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: CI + +on: + push: + pull_request: + +jobs: + lint-qml: + name: QML lint & type check + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install Qt 6 and xvfb + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + qt6-declarative-dev-tools \ + qml-qt6 \ + qml6-module-qtquick \ + qml6-module-qtquick-controls \ + qml6-module-qtquick-layouts \ + qml6-module-qtquick-window \ + qml6-module-qtquick-templates \ + xvfb + + - name: Run QML checks + run: ./scripts/lint-qml.sh diff --git a/assets/background.jpg b/assets/background.jpg new file mode 100644 index 0000000..b3c0fa0 Binary files /dev/null and b/assets/background.jpg differ diff --git a/components/LoginForm.qml b/components/LoginForm.qml index 8af93e5..5483df8 100644 --- a/components/LoginForm.qml +++ b/components/LoginForm.qml @@ -47,7 +47,7 @@ Item { color: root.textColor font.pointSize: root.fontSize + 8 font.weight: Font.Light - font.family: root.fontFamily || undefined + font.family: root.fontFamily renderType: Text.NativeRendering } @@ -59,7 +59,7 @@ Item { placeholderText: "Username" text: root.defaultUsername font.pointSize: root.fontSize - font.family: root.fontFamily || undefined + font.family: root.fontFamily color: root.textColor placeholderTextColor: Qt.rgba(1, 1, 1, 0.5) @@ -86,7 +86,7 @@ Item { placeholderText: "Password" echoMode: TextInput.Password font.pointSize: root.fontSize - font.family: root.fontFamily || undefined + font.family: root.fontFamily color: root.textColor placeholderTextColor: Qt.rgba(1, 1, 1, 0.5) @@ -122,7 +122,7 @@ Item { height: 44 text: "Log In" font.pointSize: root.fontSize - font.family: root.fontFamily || undefined + font.family: root.fontFamily contentItem: Text { text: loginButton.text diff --git a/components/SessionSelector.qml b/components/SessionSelector.qml index 9296122..f91f5aa 100644 --- a/components/SessionSelector.qml +++ b/components/SessionSelector.qml @@ -28,7 +28,7 @@ Item { textRole: "name" font.pointSize: root.fontSize - font.family: root.fontFamily || undefined + font.family: root.fontFamily contentItem: Text { leftPadding: 10 diff --git a/preview/Preview.qml b/preview/Preview.qml index 0e01776..7286446 100644 --- a/preview/Preview.qml +++ b/preview/Preview.qml @@ -32,6 +32,8 @@ Window { QtObject { id: mockConfig + // Set to "" to use the solid fallback color, or provide a path + // to an image (e.g. drop your own into assets/background.jpg). property string background: Qt.resolvedUrl("../assets/background.jpg") property string type: "image" property string color: "#1a1a2e" @@ -121,9 +123,14 @@ Window { Image { id: backgroundImage anchors.fill: parent - source: mockConfig.background + source: mockConfig.background || "" fillMode: Image.PreserveAspectCrop asynchronous: true + onStatusChanged: { + if (status === Image.Error && source != "") + console.log("Background image not found — using fallback color. " + + "Drop an image into assets/background.jpg or update theme.conf.") + } } Rectangle { diff --git a/scripts/lint-qml.sh b/scripts/lint-qml.sh new file mode 100755 index 0000000..b969643 --- /dev/null +++ b/scripts/lint-qml.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +# +# lint-qml.sh - Static analysis and runtime type-error check for QML files. +# +# Runs qmllint (static) on all QML source files, then launches the preview +# under xvfb for a few seconds to catch runtime type-assignment warnings. +# +# Usage: ./scripts/lint-qml.sh +# +# Exit codes: +# 0 All checks passed +# 1 qmllint found issues or runtime errors detected + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +cd "$PROJECT_DIR" + +# ── Locate Qt 6 tools ──────────────────────────────────────────── +# Binary names and paths vary across distros: +# Arch: qmllint, qml6 (on PATH) +# Ubuntu: qmllint, qml (in /usr/lib/qt6/bin) +for candidate in /usr/lib/qt6/bin /usr/lib64/qt6/bin; do + [[ -d "$candidate" ]] && export PATH="$candidate:$PATH" +done + +find_tool() { + for name in "$@"; do + if command -v "$name" &>/dev/null; then + echo "$name" + return + fi + done + echo "" +} + +QMLLINT=$(find_tool qmllint qmllint6) +QML_RUNTIME=$(find_tool qml6 qml) + +FAILED=0 + +# ── Static analysis with qmllint ────────────────────────────────── +echo "=== qmllint: static analysis ===" + +if [[ -z "$QMLLINT" ]]; then + echo "SKIP: qmllint not found." +else + QML_FILES=$(find . -name '*.qml' -not -path './.git/*') + echo "Checking: $QML_FILES" + echo "" + + # qmllint emits warnings for SDDM context properties (sddm, config, + # userModel, etc.) that only exist at runtime. These are expected and + # unavoidable. We capture the output and only fail on actual errors + # (syntax errors, unknown components from our own code), not warnings + # about unqualified/unresolved access to SDDM injected globals. + LINT_OUTPUT=$($QMLLINT $QML_FILES 2>&1) || true + if echo "$LINT_OUTPUT" | grep -qiE '^Error:'; then + echo "$LINT_OUTPUT" + echo "FAIL: qmllint found errors." + FAILED=1 + else + echo "PASS: qmllint clean (warnings only)." + fi +fi +echo "" + +# ── Runtime type-error check ────────────────────────────────────── +echo "=== Runtime: checking for type errors ===" + +if [[ -z "$QML_RUNTIME" ]]; then + echo "SKIP: qml runtime not found (tried qml6, qml)." + echo "" + exit $FAILED +fi + +# Use the Basic style to avoid Breeze/Plasma-specific errors that only +# occur outside a full Plasma session (e.g. T.Overlay in ComboBox). +export QT_QUICK_CONTROLS_STYLE=Basic + +STDERR_LOG=$(mktemp) +trap 'rm -f "$STDERR_LOG"' EXIT + +# Run preview for 3 seconds under a virtual framebuffer, capture stderr. +if command -v xvfb-run &>/dev/null; then + timeout 3 xvfb-run -a "$QML_RUNTIME" preview/Preview.qml 2>"$STDERR_LOG" || true +elif [ -n "${DISPLAY:-}" ] || [ -n "${WAYLAND_DISPLAY:-}" ]; then + timeout 3 "$QML_RUNTIME" preview/Preview.qml 2>"$STDERR_LOG" || true +else + echo "SKIP: no display and xvfb-run not available." + echo "" + exit $FAILED +fi + +# Filter for errors we care about (type assignment, ReferenceError, TypeError). +if grep -E 'Unable to assign|ReferenceError|TypeError' "$STDERR_LOG" \ + | grep -v 'Cannot open:' \ + | grep -q .; then + echo "FAIL: runtime type errors detected:" + grep -E 'Unable to assign|ReferenceError|TypeError' "$STDERR_LOG" \ + | grep -v 'Cannot open:' + FAILED=1 +else + echo "PASS: no runtime type errors." +fi +echo "" + +exit $FAILED diff --git a/scripts/preview.sh b/scripts/preview.sh index 69f0108..d9c95e8 100755 --- a/scripts/preview.sh +++ b/scripts/preview.sh @@ -13,6 +13,22 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +ENTR_PID="" + +cleanup() { + trap - SIGINT SIGTERM # prevent re-entry + [[ -n "$ENTR_PID" ]] && kill "$ENTR_PID" 2>/dev/null && wait "$ENTR_PID" 2>/dev/null + echo "" + echo "Preview stopped." + exit 0 +} +trap cleanup SIGINT SIGTERM + +# Force the Basic Qt Quick Controls style so the standalone preview doesn't +# load Breeze, which depends on Plasma-specific overlay types that are +# unavailable outside a full Plasma session. +export QT_QUICK_CONTROLS_STYLE=Basic + echo "=== KDE Lockscreen Builder — Live Preview ===" echo "Project: $PROJECT_DIR" echo "Press Ctrl+C to stop" @@ -22,5 +38,8 @@ cd "$PROJECT_DIR" while true; do find . \( -name '*.qml' -o -name '*.conf' -o -name '*.jpg' -o -name '*.png' -o -name '*.svg' \) \ - | entr -d -r qml6 preview/Preview.qml + | entr -d -r qml6 preview/Preview.qml & + ENTR_PID=$! + wait "$ENTR_PID" || true + ENTR_PID="" done