Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
scripts/cce-app-menu (4K)
1 #!/usr/bin/env bash
2 # cce-app-menu — Context menu for CCE application windows
3 #
4 # Opened by the compositor (cursor.rs, the right-click border path): from a
5 # right-click on a window's handle disc in adjust mode, and from a right-click
6 # anywhere on a window in overview. It is a `cce-cloud --json` popup, the same
7 # mechanism as cce-desktop-menu and cce-grid's desktop-item menu, so it looks
8 # and dismisses like every other menu in the DE. The first page holds the
9 # window verbs; "Window Mode" is a second page (a button with `target_page`
10 # switches pages without closing the popup) listing the modes `ccectl
11 # set-mode` accepts, the current one marked.
12 #
13 # The mode is set with `set-mode <mode> <id>` — one window, by id — NOT
14 # `mode <mode> <app_id>`, which appends a persistent rule that also
15 # re-classes every later window of that app.
16
17 CCE_CTL="$HOME/.local/bin/ccectl"
18 CLEAR_CLOUD="$HOME/.local/bin/cce-cloud"
19
20 # Default to empty/none
21 x_arg=""
22 y_arg=""
23 window_index=""
24 app_id=""
25
26 while [[ $# -gt 0 ]]; do
27 case "$1" in
28 -x|--x-pos)
29 # A flag with no value must still consume itself: bare `shift 2`
30 # is a no-op when one arg remains, and the loop spins forever.
31 if [[ $# -ge 2 ]]; then x_arg="-x $2"; shift 2; else shift; fi
32 ;;
33 -y|--y-pos)
34 if [[ $# -ge 2 ]]; then y_arg="-y $2"; shift 2; else shift; fi
35 ;;
36 -i|--window-id)
37 if [[ $# -ge 2 ]]; then window_index="$2"; shift 2; else shift; fi
38 ;;
39 -a|--app-id)
40 if [[ $# -ge 2 ]]; then app_id="$2"; shift 2; else shift; fi
41 ;;
42 *)
43 shift
44 ;;
45 esac
46 done
47
48 if [[ -z "$window_index" || -z "$app_id" ]]; then
49 echo "Usage: cce-app-menu -i <window_index> -a <app_id> [-x <x>] [-y <y>]" >&2
50 exit 1
51 fi
52
53 # The window's current mode, so the submenu can mark it. `windows --json` is
54 # one object per line; a window that has gone since the click yields "".
55 current_mode=$("$CCE_CTL" windows --json 2>/dev/null | python3 -c '
56 import sys, json
57 want = sys.argv[1]
58 for line in sys.stdin:
59 line = line.strip()
60 if not line:
61 continue
62 try:
63 w = json.loads(line)
64 except ValueError:
65 continue
66 if str(w.get("id")) == want:
67 print(str(w.get("mode", "")).lower())
68 break
69 ' "$window_index")
70
71 # Build the layout in python so the app_id (client-chosen text) is escaped
72 # for JSON rather than spliced into it.
73 json_layout=$(python3 - "$app_id" "$current_mode" <<'PY'
74 import sys, json
75 app_id, current = sys.argv[1], sys.argv[2]
76 def mode(text, ident):
77 mark = "● " if ident == current else " "
78 return {"type": "button", "text": mark + text, "id": ident}
79 layout = {"pages": [
80 {"title": app_id, "justify": "left", "widgets": [
81 {"type": "button", "text": "Window Mode >", "id": "mode_page", "target_page": 1},
82 {"type": "button", "text": "Minimize", "id": "minimize"},
83 {"type": "button", "text": "Close Window", "id": "close"},
84 ]},
85 {"title": "Window Mode", "justify": "left", "widgets": [
86 mode("Floating", "floating"),
87 mode("Tiled", "tiled"),
88 mode("Fullscreen", "fullscreen"),
89 {"type": "button", "text": "< Back", "id": "back", "target_page": 0},
90 ]},
91 ]}
92 print(json.dumps(layout))
93 PY
94 )
95
96 # Spawn cce-cloud with the json layout and coordinates
97 selected=$(echo "$json_layout" | $CLEAR_CLOUD --json $x_arg $y_arg 2>/dev/null)
98
99 [[ -z "$selected" ]] && exit 0
100
101 # Parse button using python for reliability and robustness
102 btn=$(echo "$selected" | python3 -c "import sys, json; print(json.load(sys.stdin).get('button', ''))")
103
104 [[ -z "$btn" ]] && exit 0
105
106 case "$btn" in
107 "floating"|"tiled"|"fullscreen")
108 $CCE_CTL set-mode "$btn" "$window_index"
109 ;;
110 "minimize")
111 # minimize/close act on the focused window: focus this one first.
112 $CCE_CTL focus-window "$window_index"
113 $CCE_CTL minimize
114 ;;
115 "close")
116 $CCE_CTL focus-window "$window_index"
117 $CCE_CTL close
118 ;;
119 esac