blob: 19a47c84077de1819f8479a5c6820e185f690f44 [file] [log] [blame] [edit]
// 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(())
}