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