Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
scripts/cce-desktop-menu (6.5K)
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. jq, not python, here and below: the popup waits on everything
59 # before it, and each python start costs 100-200 ms (more under load) where
60 # jq costs a few.
61 current_mode=""
62 if [[ -n "$window_index" ]]; then
63 current_mode=$("$CCE_CTL" windows --json 2>/dev/null | jq -Rrn --arg want "$window_index" '
64 first(inputs | fromjson? | select((.id | tostring) == $want)
65 | .mode // "" | tostring | ascii_downcase)' 2>/dev/null)
66 fi
67
68 # Build the layout in jq so the app_id (client-chosen text) is escaped
69 # for JSON rather than spliced into it. The "Window Mode" entry sits with
70 # Overview, the other window-manager verb; a `target_page` button switches
71 # pages without closing the popup.
72 json_layout=$(jq -nc --arg app_id "$app_id" --arg current "$current_mode" '
73 def btn($text; $id): {type: "button", text: $text, id: $id};
74 # The mode page is a radio group: cce-cloud draws the toolkit leading
75 # marks ("● " on, "○ " off) as the circle glyphs, and a `target_page` button
76 # as a page row (chevron-right) or, turning back to an earlier page, a back
77 # row (chevron-left) -- so no label spells a mark or an arrow of its own.
78 def mode($text; $id): btn((if $id == $current then "● " else "○ " end) + $text; $id);
79 [
80 btn("Terminal"; "terminal"),
81 btn("Files"; "files"),
82 btn("Data Editor"; "data_editor"),
83 btn("Applications"; "apps"),
84 btn("System Settings"; "settings"),
85 btn("Overview"; "overview")
86 ]
87 # The focused-window verbs, together, the same three cce-app-menu has.
88 + (if $current != "" then [
89 btn("Window Mode"; "mode_page") + {target_page: 1},
90 btn("Minimize"; "minimize"),
91 btn("Close Window"; "close")
92 ] else [] end)
93 + [btn("Reload Config"; "reload"), btn("Logout"; "exit")]
94 | {pages: (
95 [{title: "Desktop Context Menu", justify: "left", widgets: .}]
96 + (if $current != "" then [{title: $app_id, justify: "left", widgets: [
97 mode("Floating"; "floating"),
98 mode("Tiled"; "tiled"),
99 mode("Fullscreen"; "fullscreen"),
100 btn("Back"; "back") + {target_page: 0}
101 ]}] else [] end)
102 )}')
103
104 # Spawn cce-cloud with the json layout and coordinates
105 selected=$(echo "$json_layout" | $CLEAR_CLOUD --json $x_arg $y_arg 2>/dev/null)
106
107 [[ -z "$selected" ]] && exit 0
108
109 btn=$(jq -r '.button // ""' <<<"$selected" 2>/dev/null)
110
111 case "$btn" in
112 "terminal")
113 # The DE's default terminal, resolved like the launcher: $TERMINAL
114 # (session env, exported by startcce) → config.kdl `default_terminal`
115 # (the settings app's Default Apps pick) → foot.
116 term="$TERMINAL"
117 if [[ -z "$term" ]] || ! command -v "$term" >/dev/null 2>&1; then
118 term=$(sed -n 's/^[[:space:]]*default_terminal[[:space:]]*"\([^"]*\)".*/\1/p' "$HOME/.config/cce/config.kdl" 2>/dev/null | head -n 1)
119 fi
120 if [[ -z "$term" ]] || ! command -v "$term" >/dev/null 2>&1; then
121 term=foot
122 fi
123 place_at "$term"
124 "$term" &
125 ;;
126 "files")
127 # Run files
128 place_at cce-files
129 "$HOME"/.local/bin/cce-files &
130 ;;
131 "data_editor")
132 # Run data editor
133 place_at cce-data-editor
134 "$HOME"/.local/bin/cce-data-editor &
135 ;;
136 "apps")
137 # Run applications search/list, AT the click: the launcher passes the
138 # position on to whatever it launches, so picking an app from here
139 # opens it on this square too.
140 $CLEAR_CLOUD --mode apps $x_arg $y_arg &
141 ;;
142 "settings")
143 # Run settings interface
144 place_at cce-system-interface
145 "$HOME"/.local/bin/cce-system-interface &
146 ;;
147 "overview")
148 $CCE_CTL overview
149 ;;
150 "floating"|"tiled"|"fullscreen")
151 # The background right-click dropped focus for the popup's sake;
152 # the window just acted on is the one to hand it back to.
153 if [[ -n "$window_index" ]]; then
154 $CCE_CTL set-mode "$btn" "$window_index"
155 $CCE_CTL focus-window "$window_index"
156 fi
157 ;;
158 "minimize"|"close")
159 # Both act on the focused window, and focus was dropped: refocus
160 # the named window first.
161 if [[ -n "$window_index" ]]; then
162 $CCE_CTL focus-window "$window_index"
163 $CCE_CTL "$btn"
164 fi
165 ;;
166 "reload")
167 $CCE_CTL reload
168 ;;
169 "exit")
170 $CCE_CTL exit
171 ;;
172 esac