Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
src/server/run_server.rs (9.5K)
1 // SPDX-FileCopyrightText: © 2020 The River Developers
2 // SPDX-License-Identifier: GPL-3.0-only
3
4 use clap::Parser;
5 use std::ffi::{CStr, CString};
6 use std::os::raw::c_char;
7
8 use crate::ffi;
9 use crate::server;
10 use crate::process;
11
12 const USAGE: &str = "\
13 usage: river [options]
14
15 -h, --help Print this help message and exit.
16 --version Print the version number and exit.
17 -c <command> Run `sh -c <command>` on startup instead of the default init executable.
18 --log-level <level> Set the log level to error, warning, info, or debug.
19 --no-xwayland Disable xwayland even if built with support.
20 ";
21
22 #[derive(Parser, Debug)]
23 #[command(disable_help_flag = true)]
24 struct Args {
25 #[arg(short = 'h', long = "help")]
26 help: bool,
27
28 #[arg(long = "version")]
29 version: bool,
30
31 #[arg(short = 'c')]
32 command: Option<String>,
33
34 #[arg(long = "log-level", default_value = "info")]
35 log_level: String,
36
37 #[arg(long = "no-xwayland")]
38 no_xwayland: bool,
39 }
40
41 #[no_mangle]
42 pub unsafe extern "C" fn river_wlroots_log_callback(
43 importance: ffi::wlr_log_importance,
44 ptr: *const c_char,
45 len: usize,
46 ) {
47 let message_slice = std::slice::from_raw_parts(ptr as *const u8, len);
48 let message = String::from_utf8_lossy(message_slice);
49 let message_trimmed = message.trim();
50
51 match importance {
52 ffi::wlr_log_importance_WLR_ERROR => log::error!(target: "wlroots", "{}", message_trimmed),
53 ffi::wlr_log_importance_WLR_INFO => log::info!(target: "wlroots", "{}", message_trimmed),
54 ffi::wlr_log_importance_WLR_DEBUG => log::debug!(target: "wlroots", "{}", message_trimmed),
55 _ => {}
56 }
57 }
58
59 fn default_init_path() -> Option<String> {
60 let path = if let Ok(xdg_config_home) = std::env::var("XDG_CONFIG_HOME") {
61 format!("{}/cce/init", xdg_config_home)
62 } else if let Ok(home) = std::env::var("HOME") {
63 format!("{}/.config/cce/init", home)
64 } else {
65 return None;
66 };
67
68 if std::path::Path::new(&path).exists() {
69 Some(path)
70 } else {
71 None
72 }
73 }
74
75 fn detect_classic(path: &str) {
76 if let Ok(content) = std::fs::read_to_string(path) {
77 if content.contains("riverctl") {
78 log::error!(
79 "The init file {} contains the string \"riverctl\".\n\
80 This version of river does not support riverctl.\n\
81 See https://isaacfreund.com/software/river for more information.",
82 path
83 );
84 std::process::exit(1);
85 }
86 }
87 }
88
89 pub fn run_server() {
90 let args = match Args::try_parse() {
91 Ok(a) => a,
92 Err(_) => {
93 eprintln!("{}", USAGE);
94 std::process::exit(1);
95 }
96 };
97
98 if args.help {
99 println!("{}", USAGE);
100 std::process::exit(0);
101 }
102
103 if args.version {
104 println!("0.5.0-dev -xwayland (Rust rewrite starter)");
105 std::process::exit(0);
106 }
107
108 let log_level = match args.log_level.as_str() {
109 "error" => log::LevelFilter::Error,
110 "warning" => log::LevelFilter::Warn,
111 "info" => log::LevelFilter::Info,
112 "debug" => log::LevelFilter::Debug,
113 _ => log::LevelFilter::Info,
114 };
115
116 env_logger::Builder::new()
117 .filter(None, log_level)
118 .init();
119
120 log::info!("initializing river (Rust rewrite)");
121
122 let importance = match log_level {
123 log::LevelFilter::Debug => ffi::wlr_log_importance_WLR_DEBUG,
124 log::LevelFilter::Info => ffi::wlr_log_importance_WLR_INFO,
125 _ => ffi::wlr_log_importance_WLR_ERROR,
126 };
127
128 unsafe {
129 ffi::river_init_wlroots_log(importance);
130 }
131
132 let startup_command = if let Some(cmd) = args.command {
133 Some(cmd)
134 } else {
135 default_init_path()
136 };
137
138 if let Some(ref cmd) = startup_command {
139 detect_classic(cmd);
140 }
141
142 let mut server = Box::new(server::Server::default());
143 if let Err(e) = server.init(!args.no_xwayland) {
144 log::error!("failed to initialize server: {}", e);
145 std::process::exit(1);
146 }
147
148 if let Some(path) = crate::config::default_config_path() {
149 log::info!("loading config from {}", path);
150 if let Err(e) = crate::config::parse_config(&path, &mut server.wm) {
151 log::error!("failed to parse config at {}: {}", path, e);
152 }
153 } else {
154 log::warn!("no config file found, using defaults");
155 }
156
157 if let Some(state_path) = crate::config::default_state_path() {
158 unsafe {
159 server.wm.load_state(&state_path);
160 }
161 }
162
163 process::setup();
164
165 let socket_ptr = unsafe {
166 ffi::wl_display_add_socket_auto(server.wl_server)
167 };
168 if socket_ptr.is_null() {
169 log::error!("failed to add wayland socket");
170 server.deinit();
171 std::process::exit(1);
172 }
173 let socket_str = unsafe { CStr::from_ptr(socket_ptr).to_string_lossy().into_owned() };
174 log::info!("running server on display socket: {}", socket_str);
175
176 std::env::set_var("WAYLAND_DISPLAY", &socket_str);
177
178 unsafe { server.wm.start_ipc(Some(socket_str.clone())) };
179
180 let status_sender = crate::status_server::spawn_status_server(Some(socket_str.clone()));
181 server.wm.status_sender = Some(status_sender);
182
183 let stream_hub = crate::stream_server::spawn_stream_server(Some(socket_str.clone()));
184 unsafe { server.wm.start_stream(stream_hub) };
185
186
187 let started = unsafe { ffi::wlr_backend_start(server.backend) };
188 if !started {
189 log::error!("failed to start wlr_backend");
190 server.deinit();
191 std::process::exit(1);
192 }
193
194 // Suppress the "requested activation" notification burst that session
195 // restore is about to trigger: every respawned window issues an
196 // xdg-activation request as it maps. The grace window covers the whole
197 // startup sequence (startup programs + restored windows).
198 server::begin_startup_activation_grace();
199
200 // Spawn configured startup programs
201 let current_startup = server.wm.startup.clone();
202 for prog in current_startup {
203 unsafe {
204 server.wm.spawn_startup_program(prog);
205 }
206 }
207
208 unsafe {
209 server.wm.spawn_restored_windows();
210 }
211
212 struct ServerGuard {
213 init_pid: Option<nix::unistd::Pid>,
214 wm: *mut crate::window_manager::WindowManager,
215 }
216 impl Drop for ServerGuard {
217 fn drop(&mut self) {
218 let _ = std::fs::remove_file("/tmp/cce-status-interface-adjust-mode");
219 if let Some(pid) = self.init_pid {
220 log::info!("sending SIGTERM to child process group {}", pid);
221 let _ = nix::sys::signal::kill(
222 nix::unistd::Pid::from_raw(-pid.as_raw()),
223 nix::sys::signal::Signal::SIGTERM,
224 );
225 }
226 unsafe {
227 if !self.wm.is_null() {
228 for (_, pid) in &(*self.wm).startup_pids {
229 log::info!("sending SIGTERM to startup program pid {}", pid);
230 let _ = nix::sys::signal::kill(
231 *pid,
232 nix::sys::signal::Signal::SIGTERM,
233 );
234 }
235 }
236 }
237 }
238 }
239
240 let child_pgid = if let Some(ref cmd) = startup_command {
241 log::info!("running init executable '{}'", cmd);
242 unsafe {
243 match nix::unistd::fork() {
244 Ok(nix::unistd::ForkResult::Child) => {
245 process::cleanup_child();
246 std::env::set_var("WAYLAND_DISPLAY", &socket_str);
247
248 if !args.no_xwayland && !server.xwayland.is_null() {
249 let xwayland_cast = server.xwayland as *mut server::WlrXwayland;
250 if !(*xwayland_cast).display_name.is_null() {
251 let display_name = CStr::from_ptr((*xwayland_cast).display_name)
252 .to_string_lossy()
253 .into_owned();
254 std::env::set_var("DISPLAY", display_name);
255 }
256 }
257
258 let cmd_c = CString::new(cmd.clone()).unwrap();
259 let sh_c = CString::new("/bin/sh").unwrap();
260 let c_c = CString::new("-c").unwrap();
261 let args = [sh_c.as_c_str(), c_c.as_c_str(), cmd_c.as_c_str()];
262
263 let env: Vec<CString> = std::env::vars()
264 .map(|(k, v)| CString::new(format!("{}={}", k, v)).unwrap())
265 .collect();
266 let env_ptrs: Vec<&CStr> = env.iter().map(|s| s.as_c_str()).collect();
267
268 eprintln!("[execve] target cmd: {}, env WAYLAND_DISPLAY: {:?}", cmd, std::env::var("WAYLAND_DISPLAY"));
269 let _ = nix::unistd::execve(&sh_c, &args, &env_ptrs);
270 std::process::exit(1);
271 }
272 Ok(nix::unistd::ForkResult::Parent { child }) => Some(child),
273 Err(_) => {
274 log::error!("failed to fork child process");
275 None
276 }
277 }
278 }
279 } else {
280 None
281 };
282
283 let _guard = ServerGuard {
284 init_pid: child_pgid,
285 wm: &mut server.wm as *mut crate::window_manager::WindowManager,
286 };
287
288 log::info!("running server");
289 unsafe {
290 ffi::wl_display_run(server.wl_server);
291 }
292
293 log::info!("shutting down server");
294 unsafe {
295 server.wm.save_state();
296 }
297 server.wm.shutting_down = true;
298 std::mem::drop(_guard);
299 server.deinit();
300 }