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

scripts/cce-desktop-menu (6.7K)

  1 #!/usr/bin/env bash
  2 # cce-desktop-menu — Context menu for CCE desktop background
  3 #
  4 # A `cce-cloud --json` popup, like cce-app-menu and cce-grid's item menu. The
  5 # compositor (cursor.rs, the background right-click) passes the FOCUSED
  6 # window as `-i <id> -a <app_id>` when there is one whose mode can be set:
  7 # it drops focus right after the spawn so the popup can take the keyboard,
  8 # so the script cannot ask for it. With a window named, the menu gains the
  9 # window verbs cce-app-menu has: a "Window Mode" page whose picks go through
 10 # `ccectl set-mode <mode> <id>`, plus Minimize and Close Window.
 11 
 12 CCE_CTL="$HOME/.local/bin/ccectl"
 13 CLEAR_CLOUD="$HOME/.local/bin/cce-cloud"
 14 
 15 # Default to empty coordinates if not provided
 16 x_arg=""
 17 y_arg=""
 18 x_val=""
 19 y_val=""
 20 window_index=""
 21 app_id=""
 22 
 23 # Open the app WHERE THE MENU WAS OPENED: the next window matching $1 covers
 24 # the grid square under the click, keeping the size it had last time and
 25 # growing away from whatever already occupies the neighbouring squares. You
 26 # asked for it here, so it opens here — rather than wherever it happened to be
 27 # when you last closed it. The key is the command name; the compositor matches
 28 # it loosely against the app_id the client picks.
 29 place_at() {
 30     [[ -n "$x_val" && -n "$y_val" ]] || return 0
 31     "$CCE_CTL" place-next-cell "$1" "$x_val" "$y_val" >/dev/null 2>&1 || true
 32 }
 33 
 34 while [[ $# -gt 0 ]]; do
 35     case "$1" in
 36         -x|--x-pos)
 37             # A flag with no value must still consume itself: bare `shift 2`
 38             # is a no-op when one arg remains, and the loop spins forever.
 39             if [[ $# -ge 2 ]]; then x_arg="-x $2"; x_val="$2"; shift 2; else shift; fi
 40             ;;
 41         -y|--y-pos)
 42             if [[ $# -ge 2 ]]; then y_arg="-y $2"; y_val="$2"; shift 2; else shift; fi
 43             ;;
 44         -i|--window-id)
 45             if [[ $# -ge 2 ]]; then window_index="$2"; shift 2; else shift; fi
 46             ;;
 47         -a|--app-id)
 48             if [[ $# -ge 2 ]]; then app_id="$2"; shift 2; else shift; fi
 49             ;;
 50         *)
 51             shift
 52             ;;
 53     esac
 54 done
 55 
 56 # The named window's current mode, so the submenu can mark it. One JSON
 57 # object per line; a window gone since the click yields "" and the page is
 58 # left out.
 59 current_mode=""
 60 if [[ -n "$window_index" ]]; then
 61     current_mode=$("$CCE_CTL" windows --json 2>/dev/null | python3 -c '
 62 import sys, json
 63 want = sys.argv[1]
 64 for line in sys.stdin:
 65     line = line.strip()
 66     if not line:
 67         continue
 68     try:
 69         w = json.loads(line)
 70     except ValueError:
 71         continue
 72     if str(w.get("id")) == want:
 73         print(str(w.get("mode", "")).lower())
 74         break
 75 ' "$window_index")
 76 fi
 77 
 78 # Build the layout in python so the app_id (client-chosen text) is escaped
 79 # for JSON rather than spliced into it. The "Window Mode" entry sits with
 80 # Overview, the other window-manager verb; a `target_page` button switches
 81 # pages without closing the popup.
 82 json_layout=$(python3 - "$app_id" "$current_mode" <<'PY'
 83 import sys, json
 84 app_id, current = sys.argv[1], sys.argv[2]
 85 def mode(text, ident):
 86     mark = "● " if ident == current else "   "
 87     return {"type": "button", "text": mark + text, "id": ident}
 88 main = [
 89     {"type": "button", "text": "Terminal", "id": "terminal"},
 90     {"type": "button", "text": "Files", "id": "files"},
 91     {"type": "button", "text": "Data Editor", "id": "data_editor"},
 92     {"type": "button", "text": "Applications", "id": "apps"},
 93     {"type": "button", "text": "System Settings", "id": "settings"},
 94     {"type": "button", "text": "Overview", "id": "overview"},
 95 ]
 96 pages = [{"title": "Desktop Context Menu", "justify": "left", "widgets": main}]
 97 if current:
 98     # The focused-window verbs, together, the same three cce-app-menu has.
 99     main.append({"type": "button", "text": "Window Mode >", "id": "mode_page", "target_page": 1})
100     main.append({"type": "button", "text": "Minimize", "id": "minimize"})
101     main.append({"type": "button", "text": "Close Window", "id": "close"})
102     pages.append({"title": app_id, "justify": "left", "widgets": [
103         mode("Floating", "floating"),
104         mode("Tiled", "tiled"),
105         mode("Fullscreen", "fullscreen"),
106         {"type": "button", "text": "< Back", "id": "back", "target_page": 0},
107     ]})
108 main.append({"type": "button", "text": "Reload Config", "id": "reload"})
109 main.append({"type": "button", "text": "Logout", "id": "exit"})
110 print(json.dumps({"pages": pages}))
111 PY
112 )
113 
114 # Spawn cce-cloud with the json layout and coordinates
115 selected=$(echo "$json_layout" | $CLEAR_CLOUD --json $x_arg $y_arg 2>/dev/null)
116 
117 [[ -z "$selected" ]] && exit 0
118 
119 # Parse button using python for reliability and robustness
120 btn=$(echo "$selected" | python3 -c "import sys, json; print(json.load(sys.stdin).get('button', ''))")
121 
122 case "$btn" in
123     "terminal")
124         # The DE's default terminal, resolved like the launcher: $TERMINAL
125         # (session env, exported by startcce) → config.kdl `default_terminal`
126         # (the settings app's Default Apps pick) → foot.
127         term="$TERMINAL"
128         if [[ -z "$term" ]] || ! command -v "$term" >/dev/null 2>&1; then
129             term=$(sed -n 's/^[[:space:]]*default_terminal[[:space:]]*"\([^"]*\)".*/\1/p' "$HOME/.config/cce/config.kdl" 2>/dev/null | head -n 1)
130         fi
131         if [[ -z "$term" ]] || ! command -v "$term" >/dev/null 2>&1; then
132             term=foot
133         fi
134         place_at "$term"
135         "$term" &
136         ;;
137     "files")
138         # Run files
139         place_at cce-files
140         "$HOME"/.local/bin/cce-files &
141         ;;
142     "data_editor")
143         # Run data editor
144         place_at cce-data-editor
145         "$HOME"/.local/bin/cce-data-editor &
146         ;;
147     "apps")
148         # Run applications search/list, AT the click: the launcher passes the
149         # position on to whatever it launches, so picking an app from here
150         # opens it on this square too.
151         $CLEAR_CLOUD --mode apps $x_arg $y_arg &
152         ;;
153     "settings")
154         # Run settings interface
155         place_at cce-system-interface
156         "$HOME"/.local/bin/cce-system-interface &
157         ;;
158     "overview")
159         $CCE_CTL overview
160         ;;
161     "floating"|"tiled"|"fullscreen")
162         # The background right-click dropped focus for the popup's sake;
163         # the window just acted on is the one to hand it back to.
164         if [[ -n "$window_index" ]]; then
165             $CCE_CTL set-mode "$btn" "$window_index"
166             $CCE_CTL focus-window "$window_index"
167         fi
168         ;;
169     "minimize"|"close")
170         # Both act on the focused window, and focus was dropped: refocus
171         # the named window first.
172         if [[ -n "$window_index" ]]; then
173             $CCE_CTL focus-window "$window_index"
174             $CCE_CTL "$btn"
175         fi
176         ;;
177     "reload")
178         $CCE_CTL reload
179         ;;
180     "exit")
181         $CCE_CTL exit
182         ;;
183 esac