| // Copyright 2021 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #[macro_use] |
| extern crate bitflags; |
| |
| use log::{debug, info, warn}; |
| use structopt::StructOpt; |
| |
| use nix::sys::stat::{umask, Mode}; |
| use nix::sys::wait::{waitpid, WaitPidFlag}; |
| |
| use std::cell::RefCell; |
| use std::env; |
| use std::io::ErrorKind; |
| use std::os::unix::io::{IntoRawFd, RawFd}; |
| use std::rc::Rc; |
| |
| use tokio::net::{UnixListener, UnixStream}; |
| use tokio::signal::unix::{signal, SignalKind}; |
| |
| #[macro_use] |
| mod ffi; |
| mod aura_shell; |
| mod compositor; |
| mod display; |
| mod exo; |
| mod input; |
| mod move_resize; |
| mod protocol; |
| mod remote_shell; |
| mod rotate; |
| mod screenshooter; |
| mod server; |
| mod shell; |
| mod splash; |
| mod switcher; |
| mod terminal; |
| mod xdg_shell; |
| |
| use compositor::Compositor; |
| use server::Server; |
| use shell::Shell; |
| |
| pub type Position = ffi::weston_position; |
| pub type Size = ffi::weston_size; |
| pub type Geometry = ffi::weston_geometry; |
| |
| struct Umask { |
| old_mode: Mode, |
| } |
| |
| impl Umask { |
| fn with_mode(mode: Mode) -> Umask { |
| Umask { old_mode: umask(mode) } |
| } |
| } |
| |
| impl Drop for Umask { |
| fn drop(&mut self) { |
| umask(self.old_mode); |
| } |
| } |
| |
| fn bind_socket(name: &str) -> UnixListener { |
| let path = env::var_os("XDG_RUNTIME_DIR") |
| .expect("environment variable XDG_RUNTIME_DIR is not set.") |
| .into_string() |
| .expect("invalid value"); |
| |
| let _guard = Umask::with_mode(Mode::empty()); |
| |
| let socket_path = format!("{}/{}", path, name); |
| if let Ok(sock) = UnixListener::bind(&socket_path) { |
| return sock; |
| } |
| |
| match std::os::unix::net::UnixStream::connect(&socket_path) { |
| Ok(_) => panic!("croscomp already running"), |
| Err(error) if error.kind() == ErrorKind::ConnectionRefused => { |
| let _ = std::fs::remove_file(&socket_path); |
| } |
| _ => (), |
| } |
| |
| match UnixListener::bind(&socket_path) { |
| Ok(sock) => sock, |
| Err(error) => { |
| if error.kind() == ErrorKind::AddrInUse { |
| panic!("address in use") |
| } else { |
| panic!("other error") |
| } |
| } |
| } |
| } |
| |
| #[no_mangle] |
| extern "C" fn flush_client(client: *mut ffi::wl_client) { |
| tokio::task::spawn_local(async move { |
| unsafe { |
| ffi::wl_client_flush(client); |
| while ffi::wl_client_needs_flush(client) { |
| let fd = ffi::wl_client_get_fd(client); |
| let fd = tokio::io::unix::AsyncFd::new(fd).unwrap(); |
| let mut guard = fd.writable().await.unwrap(); |
| ffi::wl_client_flush(client); |
| guard.clear_ready(); |
| } |
| } |
| }); |
| } |
| |
| fn serve_client(display: *mut ffi::wl_display, stream: UnixStream) { |
| let fd = stream.into_std().unwrap().into_raw_fd(); |
| serve_client_fd(display, fd); |
| } |
| |
| fn serve_client_fd(display: *mut ffi::wl_display, fd: RawFd) { |
| let client = unsafe { ffi::wl_client_create(display, fd) }; |
| |
| tokio::task::spawn_local(async move { |
| loop { |
| let fd = tokio::io::unix::AsyncFd::new(fd); |
| match fd { |
| Ok(fd) => { |
| let mut guard = fd.readable().await.unwrap(); |
| |
| unsafe { |
| ffi::wl_client_read(client); |
| } |
| |
| guard.clear_ready(); |
| } |
| // probably should kill the client if not already disconected. |
| Err(_would_block) => return, |
| } |
| } |
| }); |
| } |
| |
| #[cfg(feature = "drm-backend")] |
| extern "C" fn drm_head_notify(_listener: *mut ffi::wl_listener, data: *mut ::std::os::raw::c_void) { |
| use protocol::wl_output::Transform; |
| |
| const SCALE: i32 = 2; |
| |
| let native = unsafe { &mut *(data as *mut ffi::weston_compositor) }; |
| let compositor = unsafe { &mut *container_of_mut!(native as *mut _, Compositor, native) }; |
| |
| for head in compositor |
| .head_iter_mut() |
| .filter(|h| h.connected && !h.is_enabled()) |
| { |
| unsafe { |
| let api = ffi::weston_plugin_api_get( |
| native, |
| ffi::WESTON_DRM_OUTPUT_API_NAME.as_ptr() as *const _, |
| std::mem::size_of::<ffi::weston_drm_output_api>() as ffi::size_t, |
| ) as *mut ffi::weston_drm_output_api; |
| |
| let output = native.create_output_with_head(head); |
| |
| use ffi::weston_drm_backend_output_mode::*; |
| let set_mode = (*api).set_mode.unwrap(); |
| set_mode( |
| output, |
| WESTON_DRM_BACKEND_OUTPUT_PREFERRED, |
| std::ptr::null(), |
| ); |
| output.set_scale(SCALE); |
| |
| output.set_transform(Transform::Normal as u32); |
| let set_gbm_format = (*api).set_gbm_format.unwrap(); |
| set_gbm_format(output, std::ptr::null()); |
| |
| let set_seat = (*api).set_seat.unwrap(); |
| set_seat(output, "\0".as_ptr() as *const _); |
| |
| if ffi::weston_output_enable(output) != 0 { |
| warn!("weston_output_enable return non-0"); |
| } |
| } |
| } |
| } |
| |
| #[cfg(feature = "wayland-backend")] |
| extern "C" fn wayland_head_notify( |
| _listener: *mut ffi::wl_listener, |
| data: *mut ::std::os::raw::c_void, |
| ) { |
| use protocol::wl_output::Transform; |
| |
| let native = unsafe { &mut *(data as *mut ffi::weston_compositor) }; |
| let compositor = unsafe { &mut *container_of_mut!(native as *mut _, Compositor, native) }; |
| |
| for head in compositor |
| .head_iter_mut() |
| .filter(|h| h.connected && !h.is_enabled()) |
| { |
| unsafe { |
| let api = ffi::weston_plugin_api_get( |
| native, |
| ffi::WESTON_WINDOWED_OUTPUT_API_NAME.as_ptr() as *const _, |
| std::mem::size_of::<ffi::weston_windowed_output_api>() as ffi::size_t, |
| ) as *mut ffi::weston_windowed_output_api; |
| |
| let output = native.create_output_with_head(head); |
| |
| output.set_scale(1); |
| |
| output.set_transform(Transform::Normal as u32); |
| |
| let set_size = (*api).output_set_size.unwrap(); |
| set_size(output, 1024, 600); |
| |
| if ffi::weston_output_enable(output) != 0 { |
| warn!("weston_output_enable return non-0"); |
| } |
| } |
| } |
| } |
| |
| extern "C" fn weston_log_bridge(msg: *const ::std::os::raw::c_char) { |
| unsafe { |
| info!( |
| "{}", |
| std::ffi::CStr::from_ptr(msg) |
| .to_str() |
| .expect("invalid utf-8") |
| ) |
| } |
| } |
| |
| #[cfg(feature = "drm-backend")] |
| fn load_drm_backend(compositor: Rc<RefCell<Compositor>>) -> Result<ffi::wl_listener, ()> { |
| let mut config = ffi::weston_drm_backend_config { |
| base: ffi::weston_backend_config { |
| struct_version: ffi::WESTON_DRM_BACKEND_CONFIG_VERSION, |
| struct_size: std::mem::size_of::<ffi::weston_drm_backend_config>() as ffi::size_t, |
| }, |
| tty: -1, |
| use_pixman: false, |
| seat_id: std::ptr::null_mut(), |
| gbm_format: std::ptr::null_mut(), |
| configure_device: None, |
| pageflip_timeout: 0, |
| specific_device: std::ptr::null_mut(), |
| use_pixman_shadow: false, |
| continue_without_input: true, |
| }; |
| |
| let _ret = unsafe { |
| ffi::weston_backend_drm_init(&mut compositor.borrow_mut().native, &mut config.base) |
| }; |
| |
| Ok(ffi::wl_listener::new(drm_head_notify)) |
| } |
| |
| #[cfg(not(feature = "drm-backend"))] |
| fn load_drm_backend(_compositor: Rc<RefCell<Compositor>>) -> Result<ffi::wl_listener, ()> { |
| Err(()) |
| } |
| |
| #[cfg(feature = "wayland-backend")] |
| fn load_wayland_backend(compositor: Rc<RefCell<Compositor>>) -> Result<ffi::wl_listener, ()> { |
| let mut config = ffi::weston_wayland_backend_config { |
| base: ffi::weston_backend_config { |
| struct_version: ffi::WESTON_WAYLAND_BACKEND_CONFIG_VERSION, |
| struct_size: std::mem::size_of::<ffi::weston_wayland_backend_config>() as ffi::size_t, |
| }, |
| use_pixman: true, |
| sprawl: false, |
| display_name: 0 as *mut _, |
| fullscreen: false, |
| cursor_theme: 0 as *mut _, |
| cursor_size: 32, |
| }; |
| |
| let native = &mut compositor.borrow_mut().native; |
| |
| unsafe { |
| let res = ffi::weston_backend_wayland_init(native, &mut config.base); |
| if res == -1 { |
| return Err(()); |
| }; |
| |
| let api = ffi::weston_plugin_api_get( |
| native, |
| ffi::WESTON_WINDOWED_OUTPUT_API_NAME.as_ptr() as *const _, |
| std::mem::size_of::<ffi::weston_windowed_output_api>() as ffi::size_t, |
| ) as *mut ffi::weston_windowed_output_api; |
| |
| let create_head = (*api).create_head.unwrap(); |
| create_head(native, "head\0".as_ptr() as *mut _); |
| } |
| |
| Ok(ffi::wl_listener::new(wayland_head_notify)) |
| } |
| |
| #[cfg(not(feature = "wayland-backend"))] |
| fn load_wayland_backend(_compositor: Rc<RefCell<Compositor>>) -> Result<ffi::wl_listener, ()> { |
| Err(()) |
| } |
| |
| #[derive(StructOpt, Debug)] |
| #[structopt(name = "croscomp", about = "ChromeOS compositor")] |
| struct Opt { |
| /// Use wayland backend |
| #[structopt(long)] |
| use_wayland: bool, |
| |
| /// Don't run splash screen |
| #[structopt(long)] |
| no_splash: bool, |
| |
| /// Name of wayland socket. Relative to XDG_RUNTIME_DIR. |
| #[structopt(long)] |
| socket: Option<String>, |
| } |
| |
| #[tokio::main(flavor = "current_thread")] |
| async fn main() -> std::io::Result<()> { |
| let opt = Opt::from_args(); |
| |
| unsafe { ffi::weston_set_log_handler(Some(weston_log_bridge)) }; |
| env_logger::init(); |
| let ctx = unsafe { ffi::weston_log_ctx_create() }; |
| |
| let compositor = Compositor::new(ctx); |
| |
| let mut rules = ffi::xkb_rule_names { |
| rules: std::ptr::null(), |
| model: std::ptr::null(), |
| layout: std::ptr::null(), |
| variant: std::ptr::null(), |
| options: std::ptr::null(), |
| }; |
| |
| compositor.borrow_mut().set_xkb_rule_names(&mut rules); |
| |
| let mut listener = if opt.use_wayland { |
| load_wayland_backend(compositor.clone()).unwrap() |
| } else { |
| load_drm_backend(compositor.clone()).unwrap() |
| }; |
| |
| compositor |
| .borrow_mut() |
| .add_heads_changed_listener(&mut listener); |
| |
| compositor.borrow_mut().flush_heads_changed(); |
| |
| let local = tokio::task::LocalSet::new(); |
| |
| local.spawn_local(async move { |
| let mut stream = signal(SignalKind::child()).unwrap(); |
| loop { |
| stream.recv().await; |
| waitpid(None, Some(WaitPidFlag::WNOHANG)).unwrap(); |
| } |
| }); |
| |
| let socket_name = match (&opt.socket, opt.use_wayland) { |
| (None, true) => "wayland-10", |
| (None, false) => "wayland-0", |
| (Some(socket), _) => socket, |
| }; |
| let sock = bind_socket(socket_name); |
| |
| local.spawn_local({ |
| let display = compositor.borrow_mut().server.native; |
| |
| async move { |
| loop { |
| match sock.accept().await { |
| Ok((stream, _addr)) => serve_client(display, stream), |
| Err(_) => { |
| warn!("accept failed"); |
| break; |
| } |
| } |
| } |
| } |
| }); |
| |
| let shell = Shell::new(&compositor); |
| |
| display::init(&shell); |
| |
| terminal::init(&shell); |
| |
| splash::init(&shell); |
| |
| switcher::init(&shell); |
| |
| rotate::init(&shell); |
| |
| aura_shell::init(&shell); |
| |
| screenshooter::init(&shell); |
| |
| xdg_shell::init(&shell); |
| |
| if !opt.no_splash { |
| // TODO: not croscomp's job |
| local.spawn_local({ |
| let compositor = Rc::clone(&compositor); |
| let display = compositor.borrow_mut().server.native; |
| let path = "/usr/bin/weston-simple-splash"; |
| let stream = compositor.borrow_mut().create_client_stream(path); |
| async move { |
| serve_client_fd(display, stream.unwrap().into_raw_fd()); |
| } |
| }); |
| } else { |
| let shell = Rc::clone(&shell); |
| Shell::finish_init(&shell); |
| } |
| |
| compositor.borrow_mut().wake(); |
| |
| local |
| .run_until({ |
| let compositor = Rc::clone(&compositor); |
| let event_loop = compositor.borrow_mut().native.event_loop; |
| |
| async move { |
| while !compositor.borrow().terminate { |
| unsafe { |
| let fd: RawFd = ffi::wl_event_loop_get_fd(event_loop); |
| let fd = tokio::io::unix::AsyncFd::new(fd).unwrap(); |
| |
| let mut guard = fd.readable().await.unwrap(); |
| |
| ffi::wl_event_loop_dispatch(event_loop, 0); |
| |
| guard.clear_ready(); |
| } |
| } |
| } |
| }) |
| .await; |
| |
| debug!( |
| "# exit from main: shell strong count {}", |
| Rc::strong_count(&shell) |
| ); |
| |
| Ok(()) |
| } |