Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
src/cce_ctl.rs (10.9K)
1 // cce-ctl — IPC client for cce
2
3 use std::env;
4 use std::io::{Read, Write};
5 use std::os::unix::net::UnixStream;
6 use std::process;
7
8 fn get_socket_path() -> String {
9 match env::var("WAYLAND_DISPLAY") {
10 Ok(display) => format!("/tmp/cce-{}.sock", display),
11 Err(_) => "/tmp/cce.sock".to_string(),
12 }
13 }
14
15 #[allow(dead_code)]
16 fn get_windows_path() -> String {
17 match env::var("WAYLAND_DISPLAY") {
18 Ok(display) => format!("/tmp/cce-windows-{}", display),
19 Err(_) => "/tmp/cce-windows".to_string(),
20 }
21 }
22
23 fn usage(name: &str, to_stderr: bool) {
24 let print = |s: &str| {
25 if to_stderr {
26 eprintln!("{}", s);
27 } else {
28 println!("{}", s);
29 }
30 };
31 print(&format!("usage: {} <command> [args...]", name));
32 print("");
33 print("commands:");
34 print(" layout <gap|gap_top|gap_left|gap_right|gap_bottom|offset|grid_gap|bar_height> <value>");
35 print(" close");
36 print(" minimize");
37 print(" focus-next");
38 print(" focus-prev");
39 print(" focus-up | focus-down | focus-left | focus-right");
40 print(" window-switcher # open the alt-tab window switcher (cce-cloud overlay)");
41 print(" fullscreen");
42 print(" mode-next");
43 print(" mode-next-shared");
44 print(" set-mode <floating|tiled|fullscreen> [app_id|id] # set a window's mode (focused if omitted)");
45 print(" zoom-in");
46 print(" zoom-out");
47 print(" zoom-reset");
48 print(" pan-left");
49 print(" pan-right");
50 print(" pan-up");
51 print(" pan-down");
52 print(" overlay-left");
53 print(" overlay-right");
54 print(" focus-window [--no-wait] <app_id|id> # focus and raise; replies once the window");
55 print(" # stops moving (a pan into view), 3s at most");
56 print(" close-window <app_id|id> [title-substring] # close a specific window");
57 print(" center-window [<app_id>] # pan focused/named window on-screen; replies x= y= w= h=");
58 print(" move-window <square> [<app_id>] # put focused/named window on a desktop square (e.g. C-9)");
59 print(" resize-window <w> <h> [<app_id|id>] # set focused/named window's size (logical px),");
60 print(" # top-left fixed, clamped to its min/max hints");
61 print(" min-size [list] # X11 minimum sizes learned from refusals (X11 px), numbered");
62 print(" min-size forget <app_id|id> # drop an open X11 window's learned minimum (relearned on refusal)");
63 print(" min-size forget-entry <n> # drop stored entry n from `min-size list`");
64 print(" move-window-left|-right|-up|-down # step the focused window one grid cell;");
65 print(" # tiled swaps with the tiled window already there,");
66 print(" # floating just moves");
67 print(" place-next <app_id> <x> <y> # one-shot: next map of app_id lands near this layout pos");
68 print(" place-next-cell <app_id> <x> <y> # one-shot: next map covers the grid square at this pos,");
69 print(" # keeping its size and growing away from its neighbours");
70 print(" overview # toggle; the exit lands on the focused window, not the pointer");
71 print(" windows [--json] # list windows; --json emits one JSON object per line");
72 print(" outputs [--json] # list outputs: mode, scale, logical size, physical mm, px/mm, and where the mm came from");
73 print(" idle [status] # idle timeouts in force, idle time, inhibited and by whom (app ids), displays_off/sleeping, and the power plan's override of each (plan_*)");
74 print(" idle wake|sleep|display on|display off # act now: wake darkened outputs, lock, then run the sleep command; darken/wake outputs");
75 print(" idle timeouts <display_off_s> <sleep_s> # set the config timeouts live (0 = off); config.kdl `idle { }` on reload; a Power-plan file under /run/cce overrides either while it exists");
76 print(" idle inhibit <token> <ttl_s> <who> # hold the idle timers off for a holder with no surface (cce-desktop-portal: portal Inhibit, org.freedesktop.ScreenSaver); lapses after ttl_s (1-600) unless renewed");
77 print(" idle uninhibit <token> | idle inhibit-clear # end one lease now / drop every lease");
78 print(" lock # lock the session now (cce-lock takes the prompt); replies `ok locked` once every output shows the locked scene");
79 print(" status-hide-mode [true|false]");
80 print(" adjust-position-mode [true|false|query]");
81 print(" exit [force] # log out; waits for windows to close, cancels if one stays (a save prompt); force skips the wait");
82 print(" restart");
83 print(" restart-compositor # exit cleanly; cce-display-manager relaunches the session");
84 print(" reload");
85 print(" repeat [<rate> <delay>] # key repeat for hardware keyboards: repeats/s (0 = off), ms before the first; until the next config load. No args prints it");
86 print(" input <device_name|*> scroll-factor <value>");
87 print(" config-done");
88 print(" spawn <command>");
89 print(" screenshot # capture the screen to ~/Pictures/screenshots");
90 print(" screenshot region <x> <y> <w> <h> # capture an on-screen region (logical px)");
91 print(" screenshot window [app_id|id] # capture a window (focused if omitted; works off-screen)");
92 print(" debug-buffers [app_id|id] # dump a window's scene buffers (pos/dest/natural/surface)");
93 print(" notify <title> [body]");
94 print(" bind <mods> <keysym> <action> [args...]");
95 print(" pbind <mods> <button> <action>");
96 print(" retile");
97 print(" mode <floating|tiled|fullscreen|popup|overlay> <app_id> [title]");
98 print(" pointer-location");
99 print(" camera (pan, zoom, and the pan target being eased to)");
100 print(" selection (overview drag-selection: window ids, image ids, band)");
101 print(" pointer-move-to <x> <y> (layout pixels)");
102 print(" pointer-move-by <dx> <dy>");
103 print(" pointer-scroll <dy> [dx] [finger] [natural]");
104 print(" (positive dy scrolls down; 15 = one notch; finger = a");
105 print(" two-finger swipe, ended by finger-stop; natural = from");
106 print(" a natural-scrolling touchpad, deltas already flipped)");
107 print(" pointer-scroll finger-stop");
108 print(" pointer-swipe <fingers> <dx> <dy> [steps] | begin <fingers> | update <dx> <dy> | end");
109 print(" (a 3/4-finger touchpad swipe; drives input.kdl gesture");
110 print(" chords such as focus_left \"swipe3_left\"; the staged");
111 print(" form holds a swipe short of its threshold)");
112 print(" pointer-pinch <scale> [rotation] [steps] | begin | update <scale> [rotation] | end");
113 print(" touchpad-view-regions <x11:ID|id|app_id> clear | <x,y,w,h> ...");
114 print(" (limit a touchpad_view_apps drag to these window-local");
115 print(" rects; a swipe elsewhere scrolls the app normally)");
116 print(" pointer-click [button] (left|right|middle|back|forward or evdev code)");
117 print(" pointer-press [button] (held until pointer-release — drives drags)");
118 print(" pointer-release [button]");
119 print(" touch down <id> <x> <y> | motion <id> <x> <y> | up <id> | cancel <id> | tap <x> <y>");
120 print(" (a touchscreen finger, layout pixels; a client that");
121 print(" bound wl_touch gets touch, anything else the pointer)");
122 print(" migrate-input (local: move config.kdl keybindings to input.kdl)");
123 print(" keypress <keycode> (evdev code; press+release to the focused client)");
124 print(" key-down <keycode> (modifier codes — ctrl 29/97, shift 42/54,");
125 print(" key-up <keycode> alt 56/100, super 125/126 — update client");
126 print(" xkb state, so e.g. 29+36 lands as ctrl+j)");
127 print(" shortcut bind <session> <id> <trigger> (portal GlobalShortcuts backend: CTRL+SHIFT+space)");
128 print(" shortcut unbind <session> [<id>] | clear | list");
129 }
130
131 /// The line sent for `args` (the command and its arguments). `focus-window`
132 /// waits for the window to settle unless told `--no-wait`: focusing can pan
133 /// the window into view, and a script's next pointer command would
134 /// otherwise land where the window was mid-flight. Apps reach the
135 /// compositor directly and keep the immediate reply.
136 fn command_line(args: &[String]) -> String {
137 let mut args: Vec<&str> = args.iter().map(String::as_str).collect();
138 if args.first() == Some(&"focus-window") {
139 match args.get(1) {
140 Some(&"--no-wait") => {
141 args.remove(1);
142 }
143 Some(&"--wait") => {}
144 _ => args.insert(1, "--wait"),
145 }
146 }
147 args.join(" ") + "\n"
148 }
149
150 pub fn run_cce_ctl(args: Vec<String>) {
151 if args.len() < 2 {
152 usage(&args[0], true);
153 process::exit(1);
154 }
155
156 if args[1] == "--help" || args[1] == "-h" || args[1] == "help" {
157 usage(&args[0], false);
158 return;
159 }
160
161 // Local file operation — no compositor needed.
162 if args[1] == "migrate-input" {
163 crate::migrate_input::run();
164 return;
165 }
166
167
168 // Connect to IPC socket
169 let stream = match UnixStream::connect(get_socket_path()) {
170 Ok(s) => s,
171 Err(e) => {
172 eprintln!("connect: {}", e);
173 process::exit(1);
174 }
175 };
176
177 let mut stream = stream;
178 // Build command string from args
179 let cmd = command_line(&args[1..]);
180 if let Err(e) = stream.write_all(cmd.as_bytes()) {
181 eprintln!("write: {}", e);
182 process::exit(1);
183 }
184
185 // Read response
186 let mut buf = [0u8; 4096];
187 loop {
188 match stream.read(&mut buf) {
189 Ok(0) => break,
190 Ok(n) => {
191 let s = String::from_utf8_lossy(&buf[..n]);
192 print!("{}", s);
193 }
194 Err(e) => {
195 eprintln!("read: {}", e);
196 break;
197 }
198 }
199 }
200 }
201
202 #[cfg(test)]
203 mod tests {
204 use super::command_line;
205
206 fn line(args: &[&str]) -> String {
207 command_line(&args.iter().map(|s| s.to_string()).collect::<Vec<_>>())
208 }
209
210 #[test]
211 fn focus_window_waits_unless_told_not_to() {
212 assert_eq!(line(&["focus-window", "cce-fonts"]), "focus-window --wait cce-fonts\n");
213 assert_eq!(line(&["focus-window", "--wait", "12"]), "focus-window --wait 12\n");
214 assert_eq!(line(&["focus-window", "--no-wait", "cce-fonts"]), "focus-window cce-fonts\n");
215 assert_eq!(line(&["windows", "--json"]), "windows --json\n");
216 }
217 }