git.lucas.co / cce-compositor
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