Watch
1
0
Fork
You've already forked souveraine
0

shell: quick toggle types come from the chooser, not a second list

The hand-written allowlist in AndroidQuickPanel had drifted from it, so the
phone rendered 12 of its 17 configured toggles and said nothing about the rest.
Adds the missing fullScreenshot and waydroidTerminate, and a deploy guard so
the phone chooser can't fall behind the base one again.
This commit is contained in:
Fimeg 2026-07-31 12:51:10 -04:00
commit e6e3dd3b2a
8 changed files with 248 additions and 1 deletions

View file

@ -433,6 +433,30 @@ if (( qmldir_broken )); then
echo "manifest above, or remove its qmldir line." >&2
exit 1
fi
# The phone's quick-toggle chooser is a WHOLE-FILE override of the base one, so
# a choice added to the base never reaches the phone unless it is added there
# too. That drift is silent by construction: the panel derives what it can build
# from the chooser, so a missing choice is not an error, it is a toggle that
# quietly stops existing on one device. Base must be a subset of phone.
#
# This is the same class as the qmldir check above and it has already cost a
# session: five configured toggles rendered nothing for a day because a second
# enumeration had drifted from this one.
chooser_rel="modules/ii/sidebarRight/quickToggles/androidStyle/AndroidToggleDelegateChooser.qml"
base_chooser="$SRC/ii-base/$chooser_rel"
phone_chooser="$SRC/ii-phone/$chooser_rel"
if [[ -f "$base_chooser" && -f "$phone_chooser" ]]; then
role_values() { grep -o 'roleValue: "[A-Za-z]*"' "$1" | sed 's/.*"\(.*\)"/\1/' | sort -u; }
missing_on_phone="$(comm -23 <(role_values "$base_chooser") <(role_values "$phone_chooser") | tr '\n' ' ')"
if [[ -n "${missing_on_phone// /}" ]]; then
echo "BROKEN toggle chooser overlay: ii-phone is missing base choices: $missing_on_phone" >&2
echo "The phone chooser overrides the base file wholesale — add the same" >&2
echo "DelegateChoice blocks to ii-phone/$chooser_rel or those toggles will" >&2
echo "silently not exist on the phone." >&2
exit 1
fi
fi
if grep -q 'qsConfig", "ii"' "$HOME/.config/hypr/hyprland/variables.lua" 2>/dev/null; then
echo "NOTE: hyprland qsConfig is still 'ii' — flip variables.lua to 'souveraine' to switch."
fi

View file

@ -0,0 +1,49 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs
import qs.services
import qs.modules.common
/**
* Whole-screen capture, as opposed to ScreenSnipToggle's region select.
*
* `fullScreenshot` had been listed in the phone's config for long enough that
* everyone assumed a component existed; none ever did, in any tree or any
* commit. It rendered nothing because there was nothing to render.
*/
QuickToggleModel {
id: root
name: Translation.tr("Screenshot")
hasStatusText: false
toggled: false
icon: "screenshot_monitor"
// Close the panel first, or the screenshot is a picture of the panel.
// Same 300 ms the region snip uses the close animation has to finish.
mainAction: () => {
GlobalStates.sidebarRightOpen = false;
captureDelay.start();
}
Timer {
id: captureDelay
interval: 300
repeat: false
onTriggered: captureProc.running = true
}
// To a file AND the clipboard: on the phone there is rarely anywhere to
// paste it right away, and on the laptop the file is the annoying half.
Process {
id: captureProc
command: ["bash", "-c",
"mkdir -p \"$HOME/Pictures\"; "
+ "f=\"$HOME/Pictures/screenshot-$(date +%Y%m%d-%H%M%S).png\"; "
+ "grim \"$f\" || { notify-send Screenshot 'Capture failed.' -a Shell; exit 1; }; "
+ "wl-copy -t image/png < \"$f\"; "
+ "notify-send Screenshot \"${f##*/} — saved and copied\" -a Shell"]
}
tooltipText: Translation.tr("Whole screen to ~/Pictures and the clipboard")
}

View file

@ -29,7 +29,52 @@ AbstractQuickPanel {
readonly property real baseCellHeight: 56
// Toggles
readonly property list<string> availableToggleTypes: ["network", "bluetooth", "idleInhibitor", "easyEffects", "nightLight", "darkMode", "cloudflareWarp", "gameMode", "screenSnip", "colorPicker", "onScreenKeyboard", "mic", "audio", "notifications", "powerProfile","musicRecognition", "antiFlashbang"]
//
// The one enumeration of what a toggle can be is the chooser's own list of
// choices. This used to be a second, hand-written array here, and it drifted
// as a duplicated list always eventually does. Five configured toggles
// (vpn, autoRotate, wallpaperShuffle, fullScreenshot, waydroidTerminate)
// were silently dropped by validToggles below as "ghosts" while their
// models and their android delegates sat complete in the tree: the phone
// rendered 12 of the 17 in its config, with no error in the journal and
// none in quickshell's own log, because a filtered entry is silent by
// design. Asking the chooser is the only arrangement in which the two
// cannot disagree and it gets the phone's extra choices right for free,
// since ii-phone/ overrides the chooser and this file is shared.
//
// Read once at completion rather than bound: DelegateChooser.choices is a
// CONSTANT property with no change signal, so a binding that happened to
// evaluate before the probe's choices were parented would never re-run.
property list<string> availableToggleTypes: []
AndroidToggleDelegateChooser {
id: toggleTypeProbe
// Never renders anything it exists to be asked what it can build.
// DelegateChoice holds a Component; nothing is instantiated until a
// view actually selects it.
baseCellWidth: 0
baseCellHeight: 0
spacing: 0
startingIndex: 0
}
Component.onCompleted: {
const types = [];
for (let i = 0; i < toggleTypeProbe.choices.length; i++) {
const value = toggleTypeProbe.choices[i].roleValue;
if (value !== undefined && value !== null)
types.push(String(value));
}
if (types.length === 0) {
// Never silently. An empty list here filters away every configured
// toggle and leaves a blank panel, which is a worse failure than
// the one this replaced.
console.log("[quickToggles] chooser reported NO types — the panel will be empty");
} else {
console.log("[quickToggles] " + types.length + " types from chooser: " + types.join(", "));
}
root.availableToggleTypes = types;
}
readonly property int columns: Config.options.sidebar.quickToggles.android.columns
readonly property list<var> toggles: Config.ready ? Config.options.sidebar.quickToggles.android.toggles : []
// Filter out ghost items (config entries with types that have no matching delegate).
@ -43,6 +88,13 @@ AbstractQuickPanel {
if (t && availableToggleTypes.includes(t.type) && !seen.has(t.type)) {
result.push({ type: t.type, size: t.size, _configIndex: i })
seen.add(t.type)
} else if (t && availableToggleTypes.length > 0 && !seen.has(t.type)) {
// Say it out loud. A dropped entry used to vanish without a
// trace that silence is what made vpn/autoRotate/
// wallpaperShuffle look like a rendering bug for a whole
// session, when the panel was simply refusing to build them.
console.log("[quickToggles] config lists '" + t.type
+ "' but the chooser cannot build it — skipping")
}
}
return result

View file

@ -0,0 +1,10 @@
import qs.modules.common
import qs.modules.common.models.quickToggles
import qs.modules.common.widgets
import qs.services
import QtQuick
import Quickshell
AndroidQuickToggleButton {
toggleModel: FullScreenshotToggle {}
}

View file

@ -292,4 +292,18 @@ DelegateChooser {
cellSpacing: root.spacing
cellSize: modelData.size
} }
DelegateChoice { roleValue: "fullScreenshot"; AndroidFullScreenshotToggle {
required property int index
required property var modelData
buttonIndex: root.startingIndex + index
buttonData: modelData
editMode: root.editMode
dragIndex: root.dragIndex
expandedSize: modelData.size > 1
baseCellWidth: root.baseCellWidth
baseCellHeight: root.baseCellHeight
cellSpacing: root.spacing
cellSize: modelData.size
} }
}

View file

@ -0,0 +1,59 @@
// Pixel3Arch addition: stop the Waydroid session. Phone-only waydroid is
// installed on the phone and not on the laptop, so this lives in the overlay.
// Deploy to modules/common/models/quickToggles/.
import QtQuick
import Quickshell
import Quickshell.Io
import qs.services
import qs.modules.common
/**
* Terminate the Android container's session.
*
* `waydroidTerminate` sat in the phone's config with no component behind it in
* any tree or commit a configured verb that did nothing, silently.
*/
QuickToggleModel {
id: root
/// True while a stop is in flight, so a double tap cannot race itself.
property bool busy: false
name: Translation.tr("Stop Android")
hasStatusText: false
toggled: false
icon: "android"
// Deliberate, and always pressable. There is deliberately NO status poll:
// `waydroid status` talks to the container and can block for seconds, and
// a button that greys itself out on a stale read is a verb the owner
// cannot reach. Press it; it reports what actually happened.
mainAction: () => {
if (root.busy)
return;
root.busy = true;
stopProc.running = true;
}
Process {
id: stopProc
command: ["bash", "-c",
"waydroid session stop >/dev/null 2>&1; "
+ "waydroid status 2>/dev/null | grep -q 'Session:.*RUNNING' && echo running || echo stopped"]
stdout: StdioCollector {
id: stopResult
onStreamFinished: {
root.busy = false;
const stillRunning = stopResult.text.trim() === "running";
Quickshell.execDetached(["notify-send", Translation.tr("Android"),
stillRunning
? Translation.tr("Session did not stop.")
: Translation.tr("Session stopped."),
"-a", "Shell"]);
}
}
onExited: root.busy = false
}
tooltipText: Translation.tr("Stop the Waydroid session")
}

View file

@ -322,4 +322,32 @@ DelegateChooser {
cellSpacing: root.spacing
cellSize: modelData.size
} }
DelegateChoice { roleValue: "fullScreenshot"; AndroidFullScreenshotToggle {
required property int index
required property var modelData
buttonIndex: root.startingIndex + index
buttonData: modelData
editMode: root.editMode
dragIndex: root.dragIndex
expandedSize: modelData.size > 1
baseCellWidth: root.baseCellWidth
baseCellHeight: root.baseCellHeight
cellSpacing: root.spacing
cellSize: modelData.size
} }
DelegateChoice { roleValue: "waydroidTerminate"; AndroidWaydroidTerminateToggle {
required property int index
required property var modelData
buttonIndex: root.startingIndex + index
buttonData: modelData
editMode: root.editMode
dragIndex: root.dragIndex
expandedSize: modelData.size > 1
baseCellWidth: root.baseCellWidth
baseCellHeight: root.baseCellHeight
cellSpacing: root.spacing
cellSize: modelData.size
} }
}

View file

@ -0,0 +1,11 @@
// Pixel3Arch addition: phone-only, waydroid is not installed on the laptop.
import qs.modules.common
import qs.modules.common.models.quickToggles
import qs.modules.common.widgets
import qs.services
import QtQuick
import Quickshell
AndroidQuickToggleButton {
toggleModel: WaydroidTerminateToggle {}
}