From 7f540a58e586c0a84a10eaacdbf62695793f0dc6 Mon Sep 17 00:00:00 2001 From: Gabriel Brown Date: Mon, 17 Aug 2026 19:42:15 -0400 Subject: [PATCH] Avoid visual Phone harness teardown --- .../modules/quicksettings/PhoneActions.qml | 24 +++++++++++++++++++ .../modules/quicksettings/PhoneControls.qml | 19 +++++---------- .../quickshell/modules/quicksettings/qmldir | 1 + .../dot/quickshell/phone-controls-harness.qml | 9 ++++--- tests/quickshell/control-center-contract.sh | 9 ++++--- tests/quickshell/phone-messages-contract.sh | 21 +++++++++++++--- 6 files changed, 59 insertions(+), 24 deletions(-) create mode 100644 config/dot/quickshell/modules/quicksettings/PhoneActions.qml diff --git a/config/dot/quickshell/modules/quicksettings/PhoneActions.qml b/config/dot/quickshell/modules/quicksettings/PhoneActions.qml new file mode 100644 index 0000000..963d2a8 --- /dev/null +++ b/config/dot/quickshell/modules/quicksettings/PhoneActions.qml @@ -0,0 +1,24 @@ +import QtQuick +import qs.services + +// Nonvisual owner of Phone Controls' stable action model and enablement rules. +// Keeping this logic free of delegates and icons makes it safe to exercise in +// disposable diagnostic engines. +QtObject { + id: root + + readonly property var actionModels: [ + { id: "share", glyph: "\u{F0142}", label: "Send file" }, + { id: "clipboard", glyph: "\u{F014C}", label: "Clipboard" }, + { id: "ring", glyph: "\u{F009A}", label: "Ring" }, + { id: "messages", glyph: "\u{F0365}", label: "Messages" } + ] + + function actionEnabled(action: string): bool { + if (action === "messages") + return SystemSettings.bluebubblesAvailable; + return KdeConnect.phoneReachable + && !KdeConnect.transferActive + && KdeConnect.supports(action); + } +} diff --git a/config/dot/quickshell/modules/quicksettings/PhoneControls.qml b/config/dot/quickshell/modules/quicksettings/PhoneControls.qml index 12e0eaf..bde114f 100644 --- a/config/dot/quickshell/modules/quicksettings/PhoneControls.qml +++ b/config/dot/quickshell/modules/quicksettings/PhoneControls.qml @@ -10,14 +10,11 @@ Item { signal toggleExpanded readonly property var phone: KdeConnect.preferredPhone - // Keep the four slots stable. The KDE actions can become unavailable when - // the phone disconnects, but the layout should not shift beneath Messages. - readonly property var actionModels: [ - { id: "share", glyph: "\u{F0142}", label: "Send file" }, - { id: "clipboard", glyph: "\u{F014C}", label: "Clipboard" }, - { id: "ring", glyph: "\u{F009A}", label: "Ring" }, - { id: "messages", glyph: "\u{F0365}", label: "Messages" } - ] + readonly property var actionModels: phoneActions.actionModels + + PhoneActions { + id: phoneActions + } implicitHeight: content.implicitHeight @@ -288,11 +285,7 @@ Item { } function actionEnabled(action: string): bool { - if (action === "messages") - return SystemSettings.bluebubblesAvailable; - return KdeConnect.phoneReachable - && !KdeConnect.transferActive - && KdeConnect.supports(action); + return phoneActions.actionEnabled(action); } function actionAccessibleDescription(action: string): string { diff --git a/config/dot/quickshell/modules/quicksettings/qmldir b/config/dot/quickshell/modules/quicksettings/qmldir index 67885c2..e1d356e 100644 --- a/config/dot/quickshell/modules/quicksettings/qmldir +++ b/config/dot/quickshell/modules/quicksettings/qmldir @@ -8,6 +8,7 @@ HomeBrightnessSlider 1.0 HomeBrightnessSlider.qml HomeControls 1.0 HomeControls.qml HomeTile 1.0 HomeTile.qml IconButton 1.0 IconButton.qml +PhoneActions 1.0 PhoneActions.qml PhoneControls 1.0 PhoneControls.qml QuickSettings 1.0 QuickSettings.qml QuickSettingsPanel 1.0 QuickSettingsPanel.qml diff --git a/config/dot/quickshell/phone-controls-harness.qml b/config/dot/quickshell/phone-controls-harness.qml index caf9ae4..17670a3 100644 --- a/config/dot/quickshell/phone-controls-harness.qml +++ b/config/dot/quickshell/phone-controls-harness.qml @@ -8,9 +8,8 @@ import qs.services // Component-only diagnostic surface. The contract replaces the two services in // its copied configuration with fixture singletons before this file is run. ShellRoot { - PhoneControls { - id: phoneControls - width: 408 + PhoneActions { + id: phoneActions } IpcHandler { @@ -46,9 +45,9 @@ ShellRoot { function status(): string { return JSON.stringify({ - actions: phoneControls.actionModels.map(action => ({ + actions: phoneActions.actionModels.map(action => ({ id: action.id, - enabled: phoneControls.actionEnabled(action.id) + enabled: phoneActions.actionEnabled(action.id) })), bluebubblesAvailable: SystemSettings.bluebubblesAvailable, appLaunches: SystemSettings.launchCount, diff --git a/tests/quickshell/control-center-contract.sh b/tests/quickshell/control-center-contract.sh index 7432660..b1ae077 100755 --- a/tests/quickshell/control-center-contract.sh +++ b/tests/quickshell/control-center-contract.sh @@ -85,16 +85,16 @@ rg -Fq 'visible: KdeConnect.phoneReachable' \ "$source_config_path/modules/bar/StatusCluster.qml" \ || fail 'reachable phone state is absent from the bar' -for component in ControlSectionHeader HomeBrightnessSlider HomeControls HomeTile PhoneControls RecentExchange; do +for component in ControlSectionHeader HomeBrightnessSlider HomeControls HomeTile PhoneActions PhoneControls RecentExchange; do [[ -f "$quicksettings_path/$component.qml" ]] \ || fail "$component is missing" done -[[ "$(rg -c 'id: "(share|clipboard|ring|messages)"' "$quicksettings_path/PhoneControls.qml")" -eq 4 ]] \ +[[ "$(rg -c 'id: "(share|clipboard|ring|messages)"' "$quicksettings_path/PhoneActions.qml")" -eq 4 ]] \ || fail 'Phone controls do not expose four stable action models' rg -Fq 'columns: 4' "$quicksettings_path/PhoneControls.qml" \ || fail 'Phone actions are not arranged in four equal columns' -if rg -Fq '.filter(' "$quicksettings_path/PhoneControls.qml"; then +if rg -Fq '.filter(' "$quicksettings_path/PhoneActions.qml"; then fail 'Phone action columns change when a KDE capability is unavailable' fi rg -Fq 'SystemSettings.bluebubblesAvailable' "$quicksettings_path/PhoneControls.qml" \ @@ -107,6 +107,9 @@ rg -Fq 'KdeConnect.phoneReachable' "$quicksettings_path/PhoneControls.qml" \ rg -Fq 'HomeBrightnessSlider 1.0 HomeBrightnessSlider.qml' \ "$quicksettings_path/qmldir" \ || fail 'brightness slider is not registered in the quick-settings module' +rg -Fq 'PhoneActions 1.0 PhoneActions.qml' \ + "$quicksettings_path/qmldir" \ + || fail 'Phone actions model is not registered in the quick-settings module' [[ "$(rg -c '^[[:space:]]*columns: 2$' "$quicksettings_path/HomeControls.qml")" -ge 2 ]] \ || fail 'resting and expanded Home shelves are not both two-column grids' diff --git a/tests/quickshell/phone-messages-contract.sh b/tests/quickshell/phone-messages-contract.sh index b755493..75e1e7f 100755 --- a/tests/quickshell/phone-messages-contract.sh +++ b/tests/quickshell/phone-messages-contract.sh @@ -4,6 +4,8 @@ set -euo pipefail project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" phone_controls="$project_root/config/dot/quickshell/modules/quicksettings/PhoneControls.qml" +phone_actions="$project_root/config/dot/quickshell/modules/quicksettings/PhoneActions.qml" +quicksettings_qmldir="$project_root/config/dot/quickshell/modules/quicksettings/qmldir" system_settings="$project_root/config/dot/quickshell/services/SystemSettings.qml" harness="$project_root/config/dot/quickshell/phone-controls-harness.qml" kde_fixture="$project_root/tests/quickshell/fixtures/PhoneControlsKdeConnect.qml" @@ -17,11 +19,24 @@ fail() { } [[ -f "$phone_controls" ]] || fail 'Phone controls component is missing' +[[ -f "$phone_actions" ]] || fail 'Nonvisual Phone actions component is missing' [[ -f "$system_settings" ]] || fail 'System settings service is missing' [[ -f "$harness" ]] || fail 'Phone controls runtime harness is missing' [[ -f "$kde_fixture" ]] || fail 'Phone controls KDE fixture is missing' [[ -f "$settings_fixture" ]] || fail 'Phone controls System Settings fixture is missing' +rg -Fq 'PhoneActions 1.0 PhoneActions.qml' "$quicksettings_qmldir" \ + || fail 'Phone actions component is not registered' +rg -Fq 'PhoneActions {' "$phone_controls" \ + || fail 'Phone controls do not consume the shared action model' +rg -Fq 'readonly property var actionModels: phoneActions.actionModels' "$phone_controls" \ + || fail 'Phone controls do not render the shared action models' +rg -Fq 'PhoneActions {' "$harness" \ + || fail 'Runtime harness does not use the nonvisual action model' +if rg -Fq 'PhoneControls {' "$harness"; then + fail 'Runtime harness still instantiates the visual Phone controls tree' +fi + cleanup() { XDG_STATE_HOME="$state_home" QS_DISABLE_CRASH_HANDLER=1 \ qs -p "$config_path/phone-controls-harness.qml" kill >/dev/null 2>&1 || true @@ -76,11 +91,11 @@ rg -Fq 'command: ["flatpak", "info", "app.bluebubbles.BlueBubbles"]' "$system_se || fail 'BlueBubbles installed-state probe is missing' for action in share clipboard ring messages; do - rg -Fq "id: \"$action\"" "$phone_controls" \ + rg -Fq "id: \"$action\"" "$phone_actions" \ || fail "Phone actions do not include $action" done -if rg -Fq '.filter(' "$phone_controls"; then +if rg -Fq '.filter(' "$phone_actions"; then fail 'Phone actions are filtered instead of keeping four stable columns' fi rg -Fq 'columns: 4' "$phone_controls" \ @@ -93,7 +108,7 @@ rg -Fq 'KdeConnect.phoneReachable' "$phone_controls" \ || fail 'KDE action reachability semantics are missing' rg -Fq 'function actionEnabled(action: string): bool' "$phone_controls" \ || fail 'Phone controls do not expose action enablement' -rg -Fq 'KdeConnect.supports(action)' "$phone_controls" \ +rg -Fq 'KdeConnect.supports(action)' "$phone_actions" \ || fail 'KDE action capability semantics are missing' rg -Fq 'else if (action === "messages")' "$phone_controls" \ || fail 'Messages has no independent invocation branch'