git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commit4ad129c301c155154b666894939b07379d5737fe
parent2629d604a0
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-05 19:33
cce-desktop-menu: build the menu with jq, not python

The popup waits on everything the script does before it spawns cce-cloud,
and that was up to three python starts: the focused window's mode, the
layout, and (after) the picked button. Each costs 100-200 ms idle and more
under load; measured on battery with a busy build, the pre-popup part took
300-450 ms and now takes 60-110. Same JSON, compared key-for-key in both
layouts, including an app_id full of quotes and backslashes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 scripts/cce-desktop-menu | 83 ++++++++++++++++++++----------------------------
 1 file changed, 34 insertions(+), 49 deletions(-)

diff --git a/scripts/cce-desktop-menu b/scripts/cce-desktop-menu
index f850bf94..473d2094 100755
--- a/scripts/cce-desktop-menu
+++ b/scripts/cce-desktop-menu
@@ -55,69 +55,54 @@ done
 
 # The named window's current mode, so the submenu can mark it. One JSON
 # object per line; a window gone since the click yields "" and the page is
-# left out.
+# left out. jq, not python, here and below: the popup waits on everything
+# before it, and each python start costs 100-200 ms (more under load) where
+# jq costs a few.
 current_mode=""
 if [[ -n "$window_index" ]]; then
-    current_mode=$("$CCE_CTL" windows --json 2>/dev/null | python3 -c '
-import sys, json
-want = sys.argv[1]
-for line in sys.stdin:
-    line = line.strip()
-    if not line:
-        continue
-    try:
-        w = json.loads(line)
-    except ValueError:
-        continue
-    if str(w.get("id")) == want:
-        print(str(w.get("mode", "")).lower())
-        break
-' "$window_index")
+    current_mode=$("$CCE_CTL" windows --json 2>/dev/null | jq -Rrn --arg want "$window_index" '
+        first(inputs | fromjson? | select((.id | tostring) == $want)
+              | .mode // "" | tostring | ascii_downcase)' 2>/dev/null)
 fi
 
-# Build the layout in python so the app_id (client-chosen text) is escaped
+# Build the layout in jq so the app_id (client-chosen text) is escaped
 # for JSON rather than spliced into it. The "Window Mode" entry sits with
 # Overview, the other window-manager verb; a `target_page` button switches
 # pages without closing the popup.
-json_layout=$(python3 - "$app_id" "$current_mode" <<'PY'
-import sys, json
-app_id, current = sys.argv[1], sys.argv[2]
-def mode(text, ident):
-    mark = "● " if ident == current else "   "
-    return {"type": "button", "text": mark + text, "id": ident}
-main = [
-    {"type": "button", "text": "Terminal", "id": "terminal"},
-    {"type": "button", "text": "Files", "id": "files"},
-    {"type": "button", "text": "Data Editor", "id": "data_editor"},
-    {"type": "button", "text": "Applications", "id": "apps"},
-    {"type": "button", "text": "System Settings", "id": "settings"},
-    {"type": "button", "text": "Overview", "id": "overview"},
+json_layout=$(jq -nc --arg app_id "$app_id" --arg current "$current_mode" '
+def btn($text; $id): {type: "button", text: $text, id: $id};
+def mode($text; $id): btn((if $id == $current then "● " else "   " end) + $text; $id);
+[
+    btn("Terminal"; "terminal"),
+    btn("Files"; "files"),
+    btn("Data Editor"; "data_editor"),
+    btn("Applications"; "apps"),
+    btn("System Settings"; "settings"),
+    btn("Overview"; "overview")
 ]
-pages = [{"title": "Desktop Context Menu", "justify": "left", "widgets": main}]
-if current:
-    # The focused-window verbs, together, the same three cce-app-menu has.
-    main.append({"type": "button", "text": "Window Mode >", "id": "mode_page", "target_page": 1})
-    main.append({"type": "button", "text": "Minimize", "id": "minimize"})
-    main.append({"type": "button", "text": "Close Window", "id": "close"})
-    pages.append({"title": app_id, "justify": "left", "widgets": [
-        mode("Floating", "floating"),
-        mode("Tiled", "tiled"),
-        mode("Fullscreen", "fullscreen"),
-        {"type": "button", "text": "< Back", "id": "back", "target_page": 0},
-    ]})
-main.append({"type": "button", "text": "Reload Config", "id": "reload"})
-main.append({"type": "button", "text": "Logout", "id": "exit"})
-print(json.dumps({"pages": pages}))
-PY
-)
+# The focused-window verbs, together, the same three cce-app-menu has.
++ (if $current != "" then [
+    btn("Window Mode >"; "mode_page") + {target_page: 1},
+    btn("Minimize"; "minimize"),
+    btn("Close Window"; "close")
+] else [] end)
++ [btn("Reload Config"; "reload"), btn("Logout"; "exit")]
+| {pages: (
+    [{title: "Desktop Context Menu", justify: "left", widgets: .}]
+    + (if $current != "" then [{title: $app_id, justify: "left", widgets: [
+        mode("Floating"; "floating"),
+        mode("Tiled"; "tiled"),
+        mode("Fullscreen"; "fullscreen"),
+        btn("< Back"; "back") + {target_page: 0}
+    ]}] else [] end)
+)}')
 
 # Spawn cce-cloud with the json layout and coordinates
 selected=$(echo "$json_layout" | $CLEAR_CLOUD --json $x_arg $y_arg 2>/dev/null)
 
 [[ -z "$selected" ]] && exit 0
 
-# Parse button using python for reliability and robustness
-btn=$(echo "$selected" | python3 -c "import sys, json; print(json.load(sys.stdin).get('button', ''))")
+btn=$(jq -r '.button // ""' <<<"$selected" 2>/dev/null)
 
 case "$btn" in
     "terminal")