diff --git a/Cargo.lock b/Cargo.lock index 79c97c36..bd6e0988 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,15 +28,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "aho-corasick" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" -dependencies = [ - "memchr", -] - [[package]] name = "anyhow" version = "1.0.56" @@ -132,19 +123,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chrono" -version = "0.4.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" -dependencies = [ - "libc", - "num-integer", - "num-traits", - "time", - "winapi", -] - [[package]] name = "clap" version = "3.1.6" @@ -163,6 +141,15 @@ dependencies = [ "textwrap", ] +[[package]] +name = "clap_complete" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df6f3613c0a3cddfd78b41b10203eb322cb29b600cbdf808a7d3db95691b8e25" +dependencies = [ + "clap", +] + [[package]] name = "clap_derive" version = "3.1.4" @@ -186,16 +173,23 @@ dependencies = [ ] [[package]] -name = "env_logger" -version = "0.9.0" +name = "dirs" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", ] [[package]] @@ -306,11 +300,12 @@ dependencies = [ "bitflags", "bstr", "byteorder", - "chrono", "clap", + "clap_complete", "default-config", - "env_logger", + "dirs", "futures-util", + "humantime", "isnt", "jay-config", "libloading", @@ -392,16 +387,6 @@ dependencies = [ "syn", ] -[[package]] -name = "num-integer" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" -dependencies = [ - "autocfg", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.14" @@ -545,14 +530,32 @@ dependencies = [ "getrandom", ] +[[package]] +name = "redox_syscall" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae183fc1b06c149f0c1793e1eb447c8b04bfe46d48e9e48bfb8d2d7ed64ecf0" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7776223e2696f1aa4c6b0170e83212f47296a00424305117d013dfe86fb0fe55" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + [[package]] name = "regex" version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" dependencies = [ - "aho-corasick", - "memchr", "regex-syntax", ] @@ -660,17 +663,6 @@ dependencies = [ "syn", ] -[[package]] -name = "time" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" -dependencies = [ - "libc", - "wasi", - "winapi", -] - [[package]] name = "uapi" version = "0.2.7" diff --git a/Cargo.toml b/Cargo.toml index deae7ba4..28edbcf5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,8 +17,7 @@ panic = "abort" uapi = "0.2.7" thiserror = "1.0.30" ahash = "0.7.6" -log = "0.4.14" -env_logger = "0.9.0" +log = { version = "0.4.16", features = ["std"] } futures-util = "0.3.19" num-traits = "0.2.14" num-derive = "0.3.3" @@ -35,9 +34,11 @@ jay-config = { path = "jay-config" } default-config = { path = "default-config" } pin-project = "1.0.10" clap = { version = "3.1.6", features = ["derive", "wrap_help"] } +clap_complete = "3.1.1" +humantime = "2.1.0" +dirs = "4.0.0" backtrace = { version = "0.3.64", optional = true } -chrono = { version = "0.4.19", optional = true } [build-dependencies] repc = "0.1.1" @@ -48,4 +49,4 @@ bstr = { version = "0.2.17", default-features = false, features = ["std"] } #opt-level = 3 [features] -rc_tracking = ["backtrace", "chrono"] +rc_tracking = ["backtrace"] diff --git a/build/build.rs b/build/build.rs index 3e432be7..360f5165 100644 --- a/build/build.rs +++ b/build/build.rs @@ -8,7 +8,7 @@ clippy::unnecessary_to_owned, clippy::match_like_matches_macro, clippy::too_many_arguments, - clippy::iter_skip_next, + clippy::iter_skip_next )] extern crate core; diff --git a/build/wire.rs b/build/wire.rs index 96b9ca8a..ae345cff 100644 --- a/build/wire.rs +++ b/build/wire.rs @@ -590,6 +590,7 @@ fn write_message(f: &mut W, obj: &BStr, message: &Message) -> Result<( writeln!(f, " pub const {}: u32 = {};", uppercase, message.id.val)?; write_message_type(f, obj, message, has_reference_type)?; let lifetime = if has_reference_type { "<'a>" } else { "" }; + let lifetime_b = if has_reference_type { "<'b>" } else { "" }; let parser = if message.fields.len() > 0 { "parser" } else { @@ -600,6 +601,12 @@ fn write_message(f: &mut W, obj: &BStr, message: &Message) -> Result<( " impl<'a> RequestParser<'a> for {}{} {{", message.camel_name, lifetime )?; + writeln!( + f, + " type Generic<'b> = {}{};", + message.camel_name, lifetime_b, + )?; + writeln!(f, " const ID: u32 = {};", message.id.val,)?; writeln!( f, " fn parse({}: &mut MsgParser<'_, 'a>) -> Result {{", diff --git a/src/acceptor.rs b/src/acceptor.rs index 8386320f..8b562d90 100644 --- a/src/acceptor.rs +++ b/src/acceptor.rs @@ -37,7 +37,7 @@ pub enum AcceptorError { } pub struct Acceptor { - id: EventLoopId, + ids: [EventLoopId; 2], socket: AllocatedSocket, global: Rc, } @@ -47,10 +47,12 @@ struct AllocatedSocket { name: Ustring, // /run/user/1000/wayland-x path: Ustring, - fd: Rc, + insecure: Rc, // /run/user/1000/wayland-x.lock lock_path: Ustring, _lock_fd: OwnedFd, + // /run/user/1000/wayland-x.jay + secure: Rc, } impl Drop for AllocatedSocket { @@ -60,13 +62,19 @@ impl Drop for AllocatedSocket { } } -fn bind_socket(fd: &Rc, xrd: &str, id: u32) -> Result { +fn bind_socket( + insecure: &Rc, + secure: &Rc, + xrd: &str, + id: u32, +) -> Result { let mut addr: c::sockaddr_un = uapi::pod_zeroed(); addr.sun_family = c::AF_UNIX as _; let name = format_ustr!("wayland-{}", id); let path = format_ustr!("{}/{}", xrd, name.display()); + let jay_path = format_ustr!("{}.jay", path.display()); let lock_path = format_ustr!("{}.lock", path.display()); - if path.len() + 1 > addr.sun_path.len() { + if jay_path.len() + 1 > addr.sun_path.len() { return Err(AcceptorError::XrdTooLong(xrd.to_string())); } let lock_fd = match uapi::open(&*lock_path, c::O_CREAT | c::O_CLOEXEC | c::O_RDWR, 0o644) { @@ -76,26 +84,29 @@ fn bind_socket(fd: &Rc, xrd: &str, id: u32) -> Result { - log::info!("Unlinking {}", path.display()); - let _ = uapi::unlink(&*path); + for (name, fd) in [(&path, insecure), (&jay_path, secure)] { + match uapi::lstat(name) { + Ok(_) => { + log::info!("Unlinking {}", name.display()); + let _ = uapi::unlink(name); + } + Err(Errno(c::ENOENT)) => {} + Err(e) => return Err(AcceptorError::SocketStat(e.into())), + } + let sun_path = uapi::as_bytes_mut(&mut addr.sun_path[..]); + sun_path[..name.len()].copy_from_slice(name.as_bytes()); + sun_path[name.len()] = 0; + if let Err(e) = uapi::bind(fd.raw(), &addr) { + return Err(AcceptorError::BindFailed(e.into())); } - Err(Errno(c::ENOENT)) => {} - Err(e) => return Err(AcceptorError::SocketStat(e.into())), - } - let sun_path = uapi::as_bytes_mut(&mut addr.sun_path[..]); - sun_path[..path.len()].copy_from_slice(path.as_bytes()); - sun_path[path.len()] = 0; - if let Err(e) = uapi::bind(fd.raw(), &addr) { - return Err(AcceptorError::BindFailed(e.into())); } Ok(AllocatedSocket { name, path, - fd: fd.clone(), + insecure: insecure.clone(), lock_path, _lock_fd: lock_fd, + secure: secure.clone(), }) } @@ -104,16 +115,22 @@ fn allocate_socket() -> Result { Ok(d) => d, Err(_) => return Err(AcceptorError::XrdNotSet), }; - let fd = match uapi::socket( - c::AF_UNIX, - c::SOCK_STREAM | c::SOCK_NONBLOCK | c::SOCK_CLOEXEC, - 0, - ) { - Ok(f) => Rc::new(f), - Err(e) => return Err(AcceptorError::SocketFailed(e.into())), - }; + let mut fds = [None, None]; + for fd in &mut fds { + let socket = match uapi::socket( + c::AF_UNIX, + c::SOCK_STREAM | c::SOCK_NONBLOCK | c::SOCK_CLOEXEC, + 0, + ) { + Ok(f) => Rc::new(f), + Err(e) => return Err(AcceptorError::SocketFailed(e.into())), + }; + *fd = Some(socket); + } + let unsecure = fds[0].take().unwrap(); + let secure = fds[1].take().unwrap(); for i in 1..1000 { - match bind_socket(&fd, &xrd, i) { + match bind_socket(&unsecure, &secure, &xrd, i) { Ok(s) => return Ok(s), Err(e) => { log::warn!("Cannot use the wayland-{} socket: {}", i, ErrorFmt(e)); @@ -124,34 +141,49 @@ fn allocate_socket() -> Result { } impl Acceptor { - pub fn install(global: &Rc) -> Result { + pub fn install(state: &Rc) -> Result { let socket = allocate_socket()?; log::info!("bound to socket {}", socket.path.display()); - if let Err(e) = uapi::listen(socket.fd.raw(), 4096) { - return Err(AcceptorError::ListenFailed(e.into())); + for fd in [&socket.secure, &socket.insecure] { + if let Err(e) = uapi::listen(fd.raw(), 4096) { + return Err(AcceptorError::ListenFailed(e.into())); + } } - let id = global.el.id(); + let id1 = state.el.id(); + let id2 = state.el.id(); let name = socket.name.to_owned(); let acc = Rc::new(Acceptor { - id, + ids: [id1, id2], socket, - global: global.clone(), + global: state.clone(), }); - global + state.el.insert( + id1, + Some(acc.socket.insecure.raw()), + c::EPOLLIN, + acc.clone(), + )?; + state .el - .insert(id, Some(acc.socket.fd.raw()), c::EPOLLIN, acc)?; + .insert(id2, Some(acc.socket.secure.raw()), c::EPOLLIN, acc)?; Ok(name) } } impl EventLoopDispatcher for Acceptor { - fn dispatch(self: Rc, events: i32) -> Result<(), Box> { + fn dispatch( + self: Rc, + fd: Option, + events: i32, + ) -> Result<(), Box> { if events & (c::EPOLLERR | c::EPOLLHUP) != 0 { return Err(Box::new(AcceptorError::ErrorEvent)); } + let fd = fd.unwrap(); + let secure = fd == self.socket.secure.raw(); loop { let fd = match uapi::accept4( - self.socket.fd.raw(), + fd, uapi::sockaddr_none_mut(), c::SOCK_NONBLOCK | c::SOCK_CLOEXEC, ) { @@ -160,7 +192,7 @@ impl EventLoopDispatcher for Acceptor { Err(e) => return Err(Box::new(AcceptorError::AcceptFailed(e.into()))), }; let id = self.global.clients.id(); - if let Err(e) = self.global.clients.spawn(id, &self.global, fd) { + if let Err(e) = self.global.clients.spawn(id, &self.global, fd, secure) { return Err(Box::new(AcceptorError::SpawnFailed(e))); } } @@ -170,6 +202,7 @@ impl EventLoopDispatcher for Acceptor { impl Drop for Acceptor { fn drop(&mut self) { - let _ = self.global.el.remove(self.id); + let _ = self.global.el.remove(self.ids[0]); + let _ = self.global.el.remove(self.ids[1]); } } diff --git a/src/async_engine.rs b/src/async_engine.rs index 46e416f3..42b3f05d 100644 --- a/src/async_engine.rs +++ b/src/async_engine.rs @@ -471,7 +471,7 @@ mod queue { } impl EventLoopDispatcher for Dispatcher { - fn dispatch(self: Rc, _events: i32) -> Result<(), Box> { + fn dispatch(self: Rc, _fd: Option, _events: i32) -> Result<(), Box> { let mut stash = self.stash.borrow_mut(); let mut yield_stash = self.yield_stash.borrow_mut(); while self.queue.num_queued.get() > 0 { @@ -609,7 +609,7 @@ mod fd { } impl EventLoopDispatcher for AsyncFdData { - fn dispatch(self: Rc, events: i32) -> Result<(), Box> { + fn dispatch(self: Rc, _fd: Option, events: i32) -> Result<(), Box> { let mut status = FdStatus::Ok; if events & (c::EPOLLERR | c::EPOLLHUP) != 0 { status = FdStatus::Err; diff --git a/src/backends/x.rs b/src/backends/x.rs index abb743ae..416da12c 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -607,14 +607,19 @@ impl XBackendData { Some(o) => o, _ => return Ok(()), }; + let mut matched_any = false; for image in &output.images { if image.last_serial.get() == event.serial { + matched_any = true; image.idle.set(true); if image.render_on_idle.replace(false) { self.schedule_present(&output); } } } + if !matched_any { + fatal!("idle event did not match any images {:#?}", event); + } Ok(()) } diff --git a/src/cli.rs b/src/cli.rs index 359fafe7..5f43c231 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,40 +1,108 @@ -use clap::{ArgEnum, Args, Parser, Subcommand}; +mod generate; +mod log; +use crate::compositor::start_compositor; +use ::log::Level; +use clap::{ArgEnum, Args, Parser, Subcommand}; +use clap_complete::Shell; + +/// A wayland compositor. #[derive(Parser, Debug)] -pub struct Cli { +struct Jay { #[clap(flatten)] - pub global: GlobalArgs, + global: GlobalArgs, #[clap(subcommand)] - pub command: Cmd, + command: Cmd, } #[derive(Args, Debug)] pub struct GlobalArgs { - #[clap(long)] - hurr: String, + /// The log level. + #[clap(arg_enum, long, default_value_t)] + pub log_level: CliLogLevel, } #[derive(Subcommand, Debug)] pub enum Cmd { - /// Run the compositor - Run, - Test(Test), + /// Run the compositor. + Run(RunArgs), + /// Generate shell completion scripts for jay. + GenerateCompletion(GenerateArgs), + /// Open the log file. + Log(LogArgs), } #[derive(Args, Debug)] -pub struct Test { - /// a +pub struct RunArgs { + /// The backends to try. /// - /// b + /// By default, jay will try to start the available backends in this order: x11,metal. + /// The first backend that can be started will be used. /// - /// c - #[clap(long, use_value_delimiter = true, arg_enum)] - shell: Vec, + /// Using this option, you can change which backends will be tried and change the order in + /// which they will be tried. Multiple backends can be supplied as a comma-separated list. + #[clap(arg_enum, use_value_delimiter = true, long)] + pub backends: Vec, } -#[derive(ArgEnum, Debug, Copy, Clone)] -pub enum Hurr { - Bash, - Fish, - Zsh, +#[derive(Args, Debug)] +pub struct LogArgs { + /// Print the path of the log file. + #[clap(long)] + path: bool, + /// Follow the log. + #[clap(long, short)] + follow: bool, + /// Immediately jump to the end in the pager. + #[clap(long, short = 'e')] + pager_end: bool, +} + +#[derive(ArgEnum, Debug, Copy, Clone, Hash)] +pub enum CliBackend { + X11, + Metal, +} + +#[derive(ArgEnum, Debug, Copy, Clone, Hash)] +pub enum CliLogLevel { + Trace, + Debug, + Info, + Warn, + Error, +} + +impl Into for CliLogLevel { + fn into(self) -> Level { + match self { + CliLogLevel::Trace => Level::Trace, + CliLogLevel::Debug => Level::Debug, + CliLogLevel::Info => Level::Info, + CliLogLevel::Warn => Level::Warn, + CliLogLevel::Error => Level::Error, + } + } +} + +impl Default for CliLogLevel { + fn default() -> Self { + Self::Info + } +} + +#[derive(Args, Debug)] +pub struct GenerateArgs { + /// The shell to generate completions for + #[clap(arg_enum)] + shell: Shell, +} + +pub fn main() { + let cli = Jay::parse(); + match cli.command { + Cmd::Run(a) => start_compositor(cli.global, a), + Cmd::GenerateCompletion(g) => generate::main(g), + Cmd::Log(a) => log::main(cli.global, a), + } } diff --git a/src/cli/generate.rs b/src/cli/generate.rs new file mode 100644 index 00000000..fef27f20 --- /dev/null +++ b/src/cli/generate.rs @@ -0,0 +1,9 @@ +use crate::cli::{GenerateArgs, Jay}; +use clap::CommandFactory; +use std::io::stdout; + +pub fn main(args: GenerateArgs) { + let stdout = stdout(); + let mut stdout = stdout.lock(); + clap_complete::generate(args.shell, &mut Jay::command(), "jay", &mut stdout); +} diff --git a/src/cli/log.rs b/src/cli/log.rs new file mode 100644 index 00000000..277e5158 --- /dev/null +++ b/src/cli/log.rs @@ -0,0 +1,99 @@ +use crate::cli::{GlobalArgs, LogArgs}; +use crate::object::WL_DISPLAY_ID; +use crate::tools::tool_client::{Handle, ToolClient}; +use crate::utils::errorfmt::ErrorFmt; +use crate::wire::wl_display::GetRegistry; +use crate::wire::{ + jay_compositor, jay_log_file, wl_registry, JayCompositor, JayCompositorId, WlRegistryId, +}; +use bstr::{BString, ByteSlice}; +use jay_compositor::GetLogFile; +use jay_log_file::Path; +use std::cell::{Cell, RefCell}; +use std::ops::Deref; +use std::os::unix::process::CommandExt; +use std::process; +use std::process::Command; +use std::rc::Rc; +use wl_registry::{Bind, Global}; + +pub fn main(global: GlobalArgs, args: LogArgs) { + let tc = ToolClient::new(global.log_level.into()); + let logger = Rc::new(Log { + tc: tc.clone(), + registry: Cell::new(WlRegistryId::NONE), + comp: Cell::new(JayCompositorId::NONE), + path: RefCell::new(None), + args, + }); + tc.run(run(logger)); +} + +struct Log { + tc: Rc, + registry: Cell, + comp: Cell, + path: RefCell>, + args: LogArgs, +} + +async fn run(log: Rc) { + let tc = &log.tc; + let registry = tc.id(); + tc.send(GetRegistry { + self_id: WL_DISPLAY_ID, + registry, + }); + log.registry.set(registry); + Global::handle(tc, registry, log.clone(), |log, g| { + if g.interface == JayCompositor.name() { + let id: JayCompositorId = log.tc.id(); + log.tc.send(Bind { + self_id: log.registry.get(), + name: g.name, + interface: g.interface, + version: 1, + id: id.into(), + }); + log.comp.set(id); + } + }); + tc.round_trip().await; + let comp = log.comp.get(); + if comp.is_none() { + fatal!( + "Server does not provide the {} interface", + JayCompositor.name() + ); + } + let log_file = tc.id(); + tc.send(GetLogFile { + self_id: comp, + id: log_file, + }); + Path::handle(tc, log_file, log.clone(), |log, path| { + *log.path.borrow_mut() = Some(path.path.to_vec().into()); + }); + tc.round_trip().await; + let path = log.path.borrow_mut(); + let path = match path.deref() { + Some(p) => p, + _ => fatal!("Server did not send the path of the log file"), + }; + if log.args.path { + println!("{}", path); + process::exit(0); + } + let mut command = Command::new("less"); + if log.args.pager_end { + command.arg("+G"); + } + if log.args.follow { + command.arg("+F"); + } else { + command.arg("-S"); + } + command.arg(path.as_bytes().to_os_str().unwrap()); + let err = command.exec(); + fatal!("Could not spawn `less`: {}", ErrorFmt(err)); +} diff --git a/src/client.rs b/src/client.rs index f49ba466..975d1737 100644 --- a/src/client.rs +++ b/src/client.rs @@ -75,6 +75,7 @@ impl Clients { id: ClientId, global: &Rc, socket: OwnedFd, + secure: bool, ) -> Result<(), ClientError> { let (uid, pid) = { let mut cred = c::ucred { @@ -93,7 +94,7 @@ impl Clients { } } }; - self.spawn2(id, global, socket, uid, pid, None)?; + self.spawn2(id, global, socket, uid, pid, secure, None)?; Ok(()) } @@ -104,6 +105,7 @@ impl Clients { socket: OwnedFd, uid: c::uid_t, pid: c::pid_t, + secure: bool, xwayland_queue: Option>>, ) -> Result, ClientError> { let data = Rc::new(Client { @@ -118,6 +120,7 @@ impl Clients { dispatch_frame_requests: AsyncQueue::new(), tracker: Default::default(), xwayland_queue, + secure, }); track!(data, data); let display = Rc::new(WlDisplay::new(&data)); @@ -129,11 +132,12 @@ impl Clients { data: data.clone(), }; log::info!( - "Client {} connected, pid: {}, uid: {}, fd: {}", + "Client {} connected, pid: {}, uid: {}, fd: {}, secure: {}", id, pid, uid, - client.data.socket.raw() + client.data.socket.raw(), + secure, ); self.clients.borrow_mut().insert(client.data.id, client); Ok(data) @@ -155,13 +159,15 @@ impl Clients { } } - pub fn broadcast(&self, mut f: B) + pub fn broadcast(&self, secure: bool, mut f: B) where B: FnMut(&Rc), { let clients = self.clients.borrow(); for client in clients.values() { - f(&client.data); + if !secure || client.data.secure { + f(&client.data); + } } } } @@ -194,6 +200,8 @@ pub trait EventFormatter: Debug { } pub trait RequestParser<'a>: Debug + Sized { + type Generic<'b>: RequestParser<'b>; + const ID: u32; fn parse(parser: &mut MsgParser<'_, 'a>) -> Result; } @@ -209,6 +217,7 @@ pub struct Client { pub dispatch_frame_requests: AsyncQueue>, pub tracker: Tracker, pub xwayland_queue: Option>>, + pub secure: bool, } impl Client { diff --git a/src/compositor.rs b/src/compositor.rs index 1b2970b3..4f5e9ff6 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -1,5 +1,6 @@ use crate::acceptor::{Acceptor, AcceptorError}; use crate::async_engine::{AsyncEngine, AsyncError, Phase}; +use crate::cli::{GlobalArgs, RunArgs}; use crate::client::Clients; use crate::clientmem::ClientMemError; use crate::config::ConfigProxy; @@ -8,6 +9,7 @@ use crate::event_loop::{EventLoop, EventLoopError}; use crate::forker::ForkerError; use crate::globals::Globals; use crate::ifs::wl_surface::NoneSurfaceExt; +use crate::logger::Logger; use crate::render::RenderError; use crate::sighand::SighandError; use crate::state::State; @@ -22,21 +24,20 @@ use crate::utils::run_toplevel::RunToplevel; use crate::wheel::{Wheel, WheelError}; use crate::xkbcommon::XkbContext; use crate::{clientmem, forker, leaks, render, sighand, tasks, xwayland}; -use log::LevelFilter; +use forker::ForkerProxy; use std::cell::Cell; use std::ops::Deref; use std::rc::Rc; +use std::sync::Arc; use thiserror::Error; -pub fn start_compositor() { - env_logger::builder() - .format_timestamp_millis() - .filter_level(LevelFilter::Info) - .filter_level(LevelFilter::Debug) - // .filter_level(LevelFilter::Trace) - .init(); - if let Err(e) = main_() { - log::error!("A fatal error occurred: {}", ErrorFmt(e)); +pub fn start_compositor(global: GlobalArgs, args: RunArgs) { + let logger = Logger::install_compositor(global.log_level.into()); + if let Err(e) = main_(logger.clone(), &args) { + let e = ErrorFmt(e); + log::error!("A fatal error occurred: {}", e); + eprintln!("A fatal error occurred: {}", e); + eprintln!("See {} for more details.", logger.path()); std::process::exit(1); } } @@ -61,8 +62,8 @@ enum MainError { ForkerError(#[from] ForkerError), } -fn main_() -> Result<(), MainError> { - let forker = Rc::new(forker::ForkerProxy::create()?); +fn main_(logger: Arc, _args: &RunArgs) -> Result<(), MainError> { + let forker = Rc::new(ForkerProxy::create()?); leaks::init(); render::init()?; clientmem::init()?; @@ -108,6 +109,7 @@ fn main_() -> Result<(), MainError> { pending_float_titles: Default::default(), dbus: Dbus::new(&engine, &run_toplevel), fdcloser: FdCloser::new(), + logger, }); forker.install(&state); let config = ConfigProxy::default(&state); diff --git a/src/event_loop.rs b/src/event_loop.rs index 52fe2277..78777032 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -30,7 +30,11 @@ pub enum EventLoopError { pub struct EventLoopId(u64); pub trait EventLoopDispatcher { - fn dispatch(self: Rc, events: i32) -> Result<(), Box>; + fn dispatch( + self: Rc, + fd: Option, + events: i32, + ) -> Result<(), Box>; } #[derive(Clone)] @@ -157,7 +161,7 @@ impl EventLoop { break 'outer; } if let Some(entry) = self.entries.get(&id) { - if let Err(e) = entry.dispatcher.clone().dispatch(0) { + if let Err(e) = entry.dispatcher.clone().dispatch(entry.fd, 0) { return Err(EventLoopError::DispatcherError(e)); } } @@ -185,7 +189,11 @@ impl EventLoop { continue; } }; - if let Err(e) = entry.dispatcher.clone().dispatch(event.events as i32) { + if let Err(e) = entry + .dispatcher + .clone() + .dispatch(entry.fd, event.events as i32) + { return Err(EventLoopError::DispatcherError(e)); } } diff --git a/src/globals.rs b/src/globals.rs index 635b850a..27d99b02 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -1,6 +1,7 @@ use crate::client::Client; use crate::ifs::ipc::wl_data_device_manager::WlDataDeviceManagerGlobal; use crate::ifs::ipc::zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1Global; +use crate::ifs::jay_compositor::JayCompositorGlobal; use crate::ifs::org_kde_kwin_server_decoration_manager::OrgKdeKwinServerDecorationManagerGlobal; use crate::ifs::wl_compositor::WlCompositorGlobal; use crate::ifs::wl_drm::WlDrmGlobal; @@ -77,6 +78,9 @@ pub trait Global: GlobalBase { fn singleton(&self) -> bool; fn version(&self) -> u32; fn break_loops(&self) {} + fn secure(&self) -> bool { + false + } } pub struct Globals { @@ -111,6 +115,7 @@ impl Globals { add_singleton!(ZwpPrimarySelectionDeviceManagerV1Global); add_singleton!(ZwlrLayerShellV1Global); add_singleton!(ZxdgOutputManagerV1Global); + add_singleton!(JayCompositorGlobal); slf } @@ -132,7 +137,7 @@ impl Globals { fn insert(&self, state: &State, global: Rc) { self.insert_no_broadcast_(&global); - self.broadcast(state, |r| r.send_global(&global)); + self.broadcast(state, global.secure(), |r| r.send_global(&global)); } pub fn get(&self, name: GlobalName) -> Result, GlobalsError> { @@ -142,7 +147,9 @@ impl Globals { pub fn remove(&self, state: &State, global: &T) -> Result<(), GlobalsError> { let _global = self.take(global.name(), true)?; global.remove(self); - self.broadcast(state, |r| r.send_global_remove(global.name())); + self.broadcast(state, global.secure(), |r| { + r.send_global_remove(global.name()) + }); Ok(()) } @@ -151,12 +158,15 @@ impl Globals { } pub fn notify_all(&self, registry: &Rc) { + let secure = registry.client.secure; let globals = self.registry.lock(); macro_rules! emit { ($singleton:expr) => { for global in globals.values() { - if global.singleton() == $singleton { - registry.send_global(global); + if secure || !global.secure() { + if global.singleton() == $singleton { + registry.send_global(global); + } } } }; @@ -165,8 +175,8 @@ impl Globals { emit!(false); } - fn broadcast)>(&self, state: &State, f: F) { - state.clients.broadcast(|c| { + fn broadcast)>(&self, state: &State, secure: bool, f: F) { + state.clients.broadcast(secure, |c| { let registries = c.lock_registries(); for registry in registries.values() { f(registry); diff --git a/src/ifs.rs b/src/ifs.rs index 0d61c7f3..751b47b1 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -1,4 +1,6 @@ pub mod ipc; +pub mod jay_compositor; +pub mod jay_log_file; pub mod org_kde_kwin_server_decoration; pub mod org_kde_kwin_server_decoration_manager; pub mod wl_buffer; diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs new file mode 100644 index 00000000..bdb6117a --- /dev/null +++ b/src/ifs/jay_compositor.rs @@ -0,0 +1,123 @@ +use crate::client::{Client, ClientError}; +use crate::globals::{Global, GlobalName}; +use crate::ifs::jay_log_file::JayLogFile; +use crate::leaks::Tracker; +use crate::object::Object; +use crate::utils::buffd::{MsgParser, MsgParserError}; +use crate::wire::jay_compositor::*; +use crate::wire::JayCompositorId; +use std::rc::Rc; +use thiserror::Error; + +pub struct JayCompositorGlobal { + name: GlobalName, +} + +impl JayCompositorGlobal { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: JayCompositorId, + client: &Rc, + _version: u32, + ) -> Result<(), JayCompositorError> { + let obj = Rc::new(JayCompositor { + id, + client: client.clone(), + tracker: Default::default(), + }); + track!(client, obj); + client.add_client_obj(&obj)?; + Ok(()) + } +} + +global_base!(JayCompositorGlobal, JayCompositor, JayCompositorError); + +impl Global for JayCompositorGlobal { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 1 + } + + fn secure(&self) -> bool { + true + } +} + +simple_add_global!(JayCompositorGlobal); + +pub struct JayCompositor { + id: JayCompositorId, + client: Rc, + tracker: Tracker, +} + +impl JayCompositor { + fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), DestroyError> { + let _req: Destroy = self.client.parse(self, parser)?; + self.client.remove_obj(self)?; + Ok(()) + } + + fn get_log_file(&self, parser: MsgParser<'_, '_>) -> Result<(), GetLogFileError> { + let req: GetLogFile = self.client.parse(self, parser)?; + let log_file = Rc::new(JayLogFile::new(req.id, &self.client)); + track!(self.client, log_file); + self.client.add_client_obj(&log_file)?; + log_file.send_path(self.client.state.logger.path()); + Ok(()) + } +} + +object_base! { + JayCompositor, JayCompositorError; + + DESTROY => destroy, + GET_LOG_FILE => get_log_file, +} + +impl Object for JayCompositor { + fn num_requests(&self) -> u32 { + GET_LOG_FILE + 1 + } +} + +simple_add_obj!(JayCompositor); + +#[derive(Debug, Error)] +pub enum JayCompositorError { + #[error("Could not process a `destroy` request")] + DestroyError(#[from] DestroyError), + #[error("Could not process a `get_log_file` request")] + GetLogFileError(#[from] GetLogFileError), + #[error(transparent)] + ClientError(Box), +} +efrom!(JayCompositorError, ClientError); + +#[derive(Debug, Error)] +pub enum DestroyError { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), +} +efrom!(DestroyError, ClientError); +efrom!(DestroyError, MsgParserError); + +#[derive(Debug, Error)] +pub enum GetLogFileError { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), +} +efrom!(GetLogFileError, ClientError); +efrom!(GetLogFileError, MsgParserError); diff --git a/src/ifs/jay_log_file.rs b/src/ifs/jay_log_file.rs new file mode 100644 index 00000000..3bc9f2cd --- /dev/null +++ b/src/ifs/jay_log_file.rs @@ -0,0 +1,68 @@ +use crate::client::{Client, ClientError}; +use crate::leaks::Tracker; +use crate::object::Object; +use crate::utils::buffd::{MsgParser, MsgParserError}; +use crate::wire::jay_log_file::*; +use crate::wire::JayLogFileId; +use bstr::BStr; +use std::rc::Rc; +use thiserror::Error; + +pub struct JayLogFile { + pub id: JayLogFileId, + pub client: Rc, + pub tracker: Tracker, +} + +impl JayLogFile { + pub fn new(id: JayLogFileId, client: &Rc) -> Self { + Self { + id, + client: client.clone(), + tracker: Default::default(), + } + } + + fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), DestroyError> { + let _req: Destroy = self.client.parse(self, parser)?; + self.client.remove_obj(self)?; + Ok(()) + } + + pub fn send_path(&self, path: &BStr) { + self.client.event(Path { + self_id: self.id, + path, + }); + } +} + +object_base! { + JayLogFile, JayLogFileError; + + DESTROY => destroy, +} + +impl Object for JayLogFile { + fn num_requests(&self) -> u32 { + DESTROY + 1 + } +} + +simple_add_obj!(JayLogFile); + +#[derive(Debug, Error)] +pub enum JayLogFileError { + #[error("Could not process a `destroy` request")] + DestroyError(#[from] DestroyError), +} + +#[derive(Debug, Error)] +pub enum DestroyError { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), +} +efrom!(DestroyError, ClientError); +efrom!(DestroyError, MsgParserError); diff --git a/src/ifs/wl_registry.rs b/src/ifs/wl_registry.rs index 551b270f..495d7e87 100644 --- a/src/ifs/wl_registry.rs +++ b/src/ifs/wl_registry.rs @@ -11,7 +11,7 @@ use thiserror::Error; pub struct WlRegistry { id: WlRegistryId, - client: Rc, + pub client: Rc, pub tracker: Tracker, } diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 7d51cfb7..30fde601 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -36,6 +36,7 @@ use crate::utils::copyhashmap::CopyHashMap; use crate::utils::errorfmt::ErrorFmt; use crate::utils::linkedlist::{LinkedList, LinkedNode}; use crate::utils::numcell::NumCell; +use crate::utils::rc_eq::rc_eq; use crate::wire::wl_seat::*; use crate::wire::{ WlDataDeviceId, WlKeyboardId, WlPointerId, WlSeatId, ZwpPrimarySelectionDeviceV1Id, @@ -52,7 +53,6 @@ use std::ops::{Deref, DerefMut}; use std::rc::Rc; use thiserror::Error; use uapi::{c, Errno, OwnedFd}; -use crate::utils::rc_eq::rc_eq; const POINTER: u32 = 1; const KEYBOARD: u32 = 2; diff --git a/src/ifs/zxdg_decoration_manager_v1.rs b/src/ifs/zxdg_decoration_manager_v1.rs index e874c759..dd0ec01a 100644 --- a/src/ifs/zxdg_decoration_manager_v1.rs +++ b/src/ifs/zxdg_decoration_manager_v1.rs @@ -12,6 +12,7 @@ use thiserror::Error; pub struct ZxdgDecorationManagerV1Global { name: GlobalName, } + impl ZxdgDecorationManagerV1Global { pub fn new(name: GlobalName) -> Self { Self { name } diff --git a/src/logger.rs b/src/logger.rs new file mode 100644 index 00000000..beb3c5ae --- /dev/null +++ b/src/logger.rs @@ -0,0 +1,157 @@ +use crate::utils::errorfmt::ErrorFmt; +use crate::utils::oserror::OsError; +use crate::utils::ptr_ext::MutPtrExt; +use bstr::{BStr, BString, ByteSlice}; +use log::{Level, Log, Metadata, Record}; +use std::cell::UnsafeCell; +use std::fs::DirBuilder; +use std::io::Write; +use std::os::unix::ffi::OsStringExt; +use std::os::unix::fs::DirBuilderExt; +use std::sync::atomic::AtomicU32; +use std::sync::atomic::Ordering::Relaxed; +use std::sync::Arc; +use std::time::SystemTime; +use uapi::{c, format_ustr, Errno, Fd, OwnedFd}; + +#[thread_local] +static BUFFER: UnsafeCell> = UnsafeCell::new(Vec::new()); + +pub struct Logger { + level: AtomicU32, + path: BString, + file: OwnedFd, +} + +impl Logger { + pub fn install_stderr(level: Level) -> Arc { + let file = match uapi::fcntl_dupfd_cloexec(2, 0) { + Ok(fd) => fd, + Err(e) => { + let e = OsError::from(e); + eprintln!("Error: Could not dup stderr: {}", ErrorFmt(e)); + std::process::exit(1); + } + }; + Self::install(level, b"", file) + } + + pub fn install_compositor(level: Level) -> Arc { + let log_dir = create_log_dir(); + let (path, file) = 'file: { + for i in 0.. { + let file_name = format_ustr!( + "{}/jay-{}-{}.txt", + log_dir, + humantime::format_rfc3339_millis(SystemTime::now()), + i, + ); + match uapi::open( + &file_name, + c::O_CREAT | c::O_EXCL | c::O_CLOEXEC | c::O_WRONLY, + 0o644, + ) { + Ok(f) => break 'file (file_name, f), + Err(Errno(c::EEXIST)) => {} + Err(e) => { + let e: OsError = e.into(); + eprintln!("Error: Could not create log file: {}", ErrorFmt(e)); + std::process::exit(1); + } + } + } + unreachable!(); + }; + Self::install(level, path.as_bytes(), file) + } + + fn install(level: Level, path: &[u8], file: OwnedFd) -> Arc { + let slf = Arc::new(Self { + level: AtomicU32::new(level as _), + path: path.to_vec().into(), + file, + }); + log::set_boxed_logger(Box::new(LogWrapper { + logger: slf.clone(), + })) + .unwrap(); + log::set_max_level(level.to_level_filter()); + slf + } + + pub fn set_level(&self, level: Level) { + self.level.store(level as _, Relaxed); + log::set_max_level(level.to_level_filter()); + } + + pub fn path(&self) -> &BStr { + self.path.as_bstr() + } +} + +fn create_log_dir() -> BString { + let mut log_dir = match dirs::data_local_dir() { + Some(d) => d, + None => { + eprintln!("Error: $HOME is not set"); + std::process::exit(1); + } + }; + log_dir.push("jay"); + let res = DirBuilder::new() + .recursive(true) + .mode(0o755) + .create(&log_dir); + if let Err(e) = res { + eprintln!( + "Error: Could not create log directory {}: {}", + log_dir.display(), + ErrorFmt(e) + ); + std::process::exit(1); + } + log_dir.into_os_string().into_vec().into() +} + +struct LogWrapper { + logger: Arc, +} + +impl Log for LogWrapper { + fn enabled(&self, metadata: &Metadata) -> bool { + metadata.level() as u32 <= self.logger.level.load(Relaxed) + } + + fn log(&self, record: &Record) { + if record.level() as u32 > self.logger.level.load(Relaxed) { + return; + } + let buffer = unsafe { BUFFER.get().deref_mut() }; + buffer.clear(); + let now = SystemTime::now(); + let _ = if let Some(mp) = record.module_path() { + writeln!( + buffer, + "[{} {:5} {}] {}", + humantime::format_rfc3339_millis(now), + record.level(), + mp, + record.args(), + ) + } else { + writeln!( + buffer, + "[{} {:5}] {}", + humantime::format_rfc3339_millis(now), + record.level(), + record.args(), + ) + }; + let mut fd = Fd::new(self.logger.file.raw()); + let _ = fd.write_all(buffer); + } + + fn flush(&self) { + // nothing + } +} diff --git a/src/macros.rs b/src/macros.rs index e0e947b2..9b881d3f 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -397,3 +397,10 @@ macro_rules! atoms { } } } + +macro_rules! fatal { + ($($arg:tt)+) => {{ + log::error!($($arg)+); + std::process::exit(1); + }} +} diff --git a/src/main.rs b/src/main.rs index 78436544..712cd369 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,13 +27,9 @@ clippy::option_map_unit_fn, clippy::wrong_self_convention, clippy::single_char_add_str, - clippy::ptr_eq, + clippy::ptr_eq )] -use crate::cli::{Cli, Cmd}; -use crate::compositor::start_compositor; -use clap::Parser; - #[macro_use] mod macros; #[macro_use] @@ -58,6 +54,7 @@ mod format; mod globals; mod ifs; mod libinput; +mod logger; mod logind; mod object; mod pango; @@ -70,6 +67,7 @@ mod tasks; mod text; mod theme; mod time; +mod tools; mod tree; mod udev; mod utils; @@ -82,10 +80,5 @@ mod xkbcommon; mod xwayland; fn main() { - let cli: Cli = Cli::parse(); - println!("{:?}", cli); - match cli.command { - Cmd::Run => start_compositor(), - _ => {} - } + cli::main(); } diff --git a/src/pango.rs b/src/pango.rs index 0e0523bf..b4053375 100644 --- a/src/pango.rs +++ b/src/pango.rs @@ -212,10 +212,7 @@ impl PangoCairoContext { if l.is_null() { return Err(PangoError::CreateLayout); } - Ok(PangoLayout { - c: self.clone(), - l, - }) + Ok(PangoLayout { c: self.clone(), l }) } } } diff --git a/src/render/renderer/renderer.rs b/src/render/renderer/renderer.rs index 9affabad..106e2374 100644 --- a/src/render/renderer/renderer.rs +++ b/src/render/renderer/renderer.rs @@ -17,9 +17,9 @@ use crate::render::Texture; use crate::state::State; use crate::theme::Color; use crate::tree::{ContainerNode, FloatNode, Node, OutputNode, WorkspaceNode}; +use crate::utils::rc_eq::rc_eq; use std::ops::Deref; use std::rc::Rc; -use crate::utils::rc_eq::rc_eq; pub struct Renderer<'a> { pub(super) ctx: &'a Rc, diff --git a/src/sighand.rs b/src/sighand.rs index 6bca2657..be37e1a7 100644 --- a/src/sighand.rs +++ b/src/sighand.rs @@ -46,7 +46,7 @@ struct Sighand { } impl EventLoopDispatcher for Sighand { - fn dispatch(self: Rc, events: i32) -> Result<(), Box> { + fn dispatch(self: Rc, _fd: Option, events: i32) -> Result<(), Box> { if events & (c::EPOLLERR | c::EPOLLHUP) != 0 { return Err(Box::new(SighandError::ErrorEvent)); } diff --git a/src/state.rs b/src/state.rs index 013eb6bc..86e464d6 100644 --- a/src/state.rs +++ b/src/state.rs @@ -12,6 +12,7 @@ use crate::globals::{Globals, GlobalsError, WaylandGlobal}; use crate::ifs::wl_output::WlOutputGlobal; use crate::ifs::wl_seat::{SeatIds, WlSeatGlobal}; use crate::ifs::wl_surface::NoneSurfaceExt; +use crate::logger::Logger; use crate::rect::Rect; use crate::render::RenderContext; use crate::theme::Theme; @@ -66,6 +67,7 @@ pub struct State { pub pending_float_titles: AsyncQueue>, pub dbus: Dbus, pub fdcloser: Arc, + pub logger: Arc, } pub struct InputDeviceData { diff --git a/src/tools.rs b/src/tools.rs new file mode 100644 index 00000000..df75aeeb --- /dev/null +++ b/src/tools.rs @@ -0,0 +1 @@ +pub mod tool_client; diff --git a/src/tools/tool_client.rs b/src/tools/tool_client.rs new file mode 100644 index 00000000..d5f547bf --- /dev/null +++ b/src/tools/tool_client.rs @@ -0,0 +1,386 @@ +use crate::async_engine::{AsyncEngine, AsyncError, SpawnedFuture}; +use crate::client::{EventFormatter, RequestParser}; +use crate::event_loop::{EventLoop, EventLoopError}; +use crate::logger::Logger; +use crate::object::{ObjectId, WL_DISPLAY_ID}; +use crate::utils::asyncevent::AsyncEvent; +use crate::utils::bitfield::Bitfield; +use crate::utils::buffd::{ + BufFdError, BufFdIn, BufFdOut, MsgFormatter, MsgParser, MsgParserError, OutBuffer, + OutBufferSwapchain, +}; +use crate::utils::errorfmt::ErrorFmt; +use crate::utils::numcell::NumCell; +use crate::utils::oserror::OsError; +use crate::utils::stack::Stack; +use crate::utils::vec_ext::VecExt; +use crate::wheel::{Wheel, WheelError}; +use crate::wire::{wl_callback, wl_display, WlCallbackId}; +use ahash::AHashMap; +use log::Level; +use std::cell::{Cell, RefCell}; +use std::collections::VecDeque; +use std::future::{Future, Pending}; +use std::mem; +use std::rc::Rc; +use std::sync::Arc; +use thiserror::Error; +use uapi::{c, format_ustr}; + +#[derive(Debug, Error)] +pub enum ToolClientError { + #[error("Could not create an event loop")] + CreateEventLoop(#[source] EventLoopError), + #[error("Could not create a timer wheel")] + CreateWheel(#[source] WheelError), + #[error("Could not create an async engine")] + CreateEngine(#[source] AsyncError), + #[error("XDG_RUNTIME_DIR is not set")] + XrdNotSet, + #[error("WAYLAND_DISPLAY is not set")] + WaylandDisplayNotSet, + #[error("Could not create a socket")] + CreateSocket(#[source] OsError), + #[error("The socket path is too long")] + SocketPathTooLong, + #[error("Could not connect to the compositor")] + Connect(#[source] OsError), + #[error("Could not create an async fd")] + AsyncFd(#[source] AsyncError), + #[error("The message length is smaller than 8 bytes")] + MsgLenTooSmall, + #[error("The size of the message is not a multiple of 4")] + UnalignedMessage, + #[error(transparent)] + BufFdError(#[from] BufFdError), + #[error("The size of the message is not a multiple of 4")] + Parsing(&'static str, MsgParserError), + #[error("Could not read from the compositor")] + Read(#[source] BufFdError), + #[error("Could not write to the compositor")] + Write(#[source] BufFdError), +} + +pub struct ToolClient { + pub logger: Arc, + pub el: Rc, + pub wheel: Rc, + pub eng: Rc, + obj_ids: RefCell, + handlers: RefCell< + AHashMap< + ObjectId, + AHashMap Result<(), ToolClientError>>>, + >, + >, + bufs: Stack>, + swapchain: Rc>, + flush_request: AsyncEvent, + pending_futures: RefCell>>, + next_id: NumCell, + incoming: Cell>>, + outgoing: Cell>>, +} + +impl ToolClient { + pub fn new(level: Level) -> Rc { + match Self::try_new(level) { + Ok(s) => s, + Err(e) => { + fatal!("Could not create a tool client: {}", ErrorFmt(e)); + } + } + } + + pub fn run(&self, f: F) + where + F: Future + 'static, + { + let _future = self.eng.spawn(async move { + f.await; + std::process::exit(0); + }); + if let Err(e) = self.el.run() { + fatal!("A fatal error occurred: {}", ErrorFmt(e)); + } + } + + pub fn try_new(level: Level) -> Result, ToolClientError> { + let logger = Logger::install_stderr(level); + let el = match EventLoop::new() { + Ok(e) => e, + Err(e) => return Err(ToolClientError::CreateEventLoop(e)), + }; + let wheel = match Wheel::install(&el) { + Ok(w) => w, + Err(e) => return Err(ToolClientError::CreateWheel(e)), + }; + let eng = match AsyncEngine::install(&el, &wheel) { + Ok(e) => e, + Err(e) => return Err(ToolClientError::CreateEngine(e)), + }; + let xrd = match std::env::var("XDG_RUNTIME_DIR") { + Ok(d) => d, + Err(_) => return Err(ToolClientError::XrdNotSet), + }; + let wd = match std::env::var("WAYLAND_DISPLAY") { + Ok(d) => d, + Err(_) => return Err(ToolClientError::WaylandDisplayNotSet), + }; + let path = format_ustr!("{}/{}.jay", xrd, wd); + let socket = match uapi::socket( + c::AF_UNIX, + c::SOCK_STREAM | c::SOCK_CLOEXEC | c::SOCK_NONBLOCK, + 0, + ) { + Ok(s) => Rc::new(s), + Err(e) => return Err(ToolClientError::CreateSocket(e.into())), + }; + let mut addr: c::sockaddr_un = uapi::pod_zeroed(); + addr.sun_family = c::AF_UNIX as _; + if path.len() >= addr.sun_path.len() { + return Err(ToolClientError::SocketPathTooLong); + } + let sun_path = uapi::as_bytes_mut(&mut addr.sun_path[..]); + sun_path[..path.len()].copy_from_slice(path.as_bytes()); + sun_path[path.len()] = 0; + if let Err(e) = uapi::connect(socket.raw(), &addr) { + return Err(ToolClientError::Connect(e.into())); + } + let fd = match eng.fd(&socket) { + Ok(fd) => fd, + Err(e) => return Err(ToolClientError::AsyncFd(e)), + }; + let mut obj_ids = Bitfield::default(); + obj_ids.take(0); + obj_ids.take(1); + let slf = Rc::new(Self { + logger, + el, + wheel, + eng, + obj_ids: RefCell::new(obj_ids), + handlers: Default::default(), + bufs: Default::default(), + swapchain: Default::default(), + flush_request: Default::default(), + pending_futures: Default::default(), + next_id: Default::default(), + incoming: Default::default(), + outgoing: Default::default(), + }); + wl_display::Error::handle(&slf, WL_DISPLAY_ID, (), |_, val| { + fatal!("The compositor returned a fatal error: {}", val.message); + }); + wl_display::DeleteId::handle(&slf, WL_DISPLAY_ID, slf.clone(), |tc, val| { + tc.obj_ids.borrow_mut().release(val.id); + }); + slf.incoming.set(Some( + slf.eng.spawn( + Incoming { + tc: slf.clone(), + buf: BufFdIn::new(fd.clone()), + } + .run(), + ), + )); + slf.outgoing.set(Some( + slf.eng.spawn( + Outgoing { + tc: slf.clone(), + buf: BufFdOut::new(fd.clone()), + buffers: Default::default(), + } + .run(), + ), + )); + Ok(slf) + } + + fn handle(self: &Rc, id: ObjectId, recv: R, h: H) + where + T: RequestParser<'static>, + F: Future + 'static, + R: 'static, + H: for<'a> Fn(&R, T::Generic<'a>) -> Option + 'static, + { + let slf = self.clone(); + let mut handlers = self.handlers.borrow_mut(); + handlers.entry(id.into()).or_default().insert( + T::ID, + Rc::new(move |parser| { + let val = match as RequestParser<'_>>::parse(parser) { + Ok(val) => val, + Err(e) => return Err(ToolClientError::Parsing(std::any::type_name::(), e)), + }; + let res = h(&recv, val); + if let Some(res) = res { + let id = slf.next_id.fetch_add(1); + let slf2 = slf.clone(); + let future = slf.eng.spawn(async move { + res.await; + slf2.pending_futures.borrow_mut().remove(&id); + }); + slf.pending_futures.borrow_mut().insert(id, future); + } + Ok(()) + }), + ); + } + + pub fn send(&self, msg: M) { + let mut fds = vec![]; + let mut swapchain = self.swapchain.borrow_mut(); + let mut fmt = MsgFormatter::new(&mut swapchain.cur, &mut fds); + msg.format(&mut fmt); + fmt.write_len(); + if swapchain.cur.is_full() { + swapchain.commit(); + } + self.flush_request.trigger(); + } + + pub fn id>(&self) -> T { + let id = self.obj_ids.borrow_mut().acquire(); + ObjectId::from_raw(id).into() + } + + pub async fn round_trip(self: &Rc) { + let callback: WlCallbackId = self.id(); + self.send(wl_display::Sync { + self_id: WL_DISPLAY_ID, + callback, + }); + let ah = Rc::new(AsyncEvent::default()); + wl_callback::Done::handle(self, callback, ah.clone(), |ah, _| { + ah.trigger(); + }); + ah.triggered().await; + } +} + +pub const NONE_FUTURE: Option> = None; + +pub trait Handle: RequestParser<'static> { + fn handle(tl: &Rc, id: impl Into, r: R, h: H) + where + R: 'static, + H: for<'a> Fn(&R, Self::Generic<'a>) + 'static; + + fn handle2(tl: &Rc, id: impl Into, r: R, h: H) + where + R: 'static, + F: Future + 'static, + H: for<'a> Fn(&R, Self::Generic<'a>) -> F + 'static; +} + +impl> Handle for T { + fn handle(tl: &Rc, id: impl Into, r: R, h: H) + where + R: 'static, + H: for<'a> Fn(&R, T::Generic<'a>) + 'static, + { + tl.handle::(id.into(), r, move |a, b| { + h(a, b); + NONE_FUTURE + }); + } + + fn handle2(tl: &Rc, id: impl Into, r: R, h: H) + where + R: 'static, + F: Future + 'static, + H: for<'a> Fn(&R, T::Generic<'a>) -> F + 'static, + { + tl.handle::(id.into(), r, move |a, b| Some(h(a, b))); + } +} + +struct Outgoing { + tc: Rc, + buf: BufFdOut, + buffers: VecDeque, +} + +impl Outgoing { + async fn run(mut self: Self) { + loop { + self.tc.flush_request.triggered().await; + if let Err(e) = self.flush().await { + fatal!("Could not process an outgoing message: {}", ErrorFmt(e)); + } + } + } + + async fn flush(&mut self) -> Result<(), ToolClientError> { + { + let mut swapchain = self.tc.swapchain.borrow_mut(); + swapchain.commit(); + mem::swap(&mut swapchain.pending, &mut self.buffers); + } + while let Some(mut cur) = self.buffers.pop_front() { + if let Err(e) = self.buf.flush_no_timeout(&mut cur).await { + return Err(ToolClientError::Write(e)); + } + self.tc.swapchain.borrow_mut().free.push(cur); + } + Ok(()) + } +} + +struct Incoming { + tc: Rc, + buf: BufFdIn, +} + +impl Incoming { + async fn run(mut self: Self) { + loop { + if let Err(e) = self.handle_msg().await { + fatal!("Could not process an incoming message: {}", ErrorFmt(e)); + } + } + } + + async fn handle_msg(&mut self) -> Result<(), ToolClientError> { + let mut hdr = [0u32, 0]; + if let Err(e) = self.buf.read_full(&mut hdr[..]).await { + return Err(ToolClientError::Read(e)); + } + let obj_id = ObjectId::from_raw(hdr[0]); + let len = (hdr[1] >> 16) as usize; + let request = hdr[1] & 0xffff; + if len < 8 { + return Err(ToolClientError::MsgLenTooSmall); + } + if len % 4 != 0 { + return Err(ToolClientError::UnalignedMessage); + } + let len = len / 4 - 2; + let mut data_buf = self.tc.bufs.pop().unwrap_or_default(); + data_buf.clear(); + data_buf.reserve(len); + let unused = data_buf.split_at_spare_mut_ext().1; + if let Err(e) = self.buf.read_full(&mut unused[..len]).await { + return Err(ToolClientError::Read(e)); + } + unsafe { + data_buf.set_len(len); + } + let mut handler = None; + { + let handlers = self.tc.handlers.borrow_mut(); + if let Some(handlers) = handlers.get(&obj_id) { + handler = handlers.get(&request).cloned(); + } + } + if let Some(handler) = handler { + let mut parser = MsgParser::new(&mut self.buf, &data_buf); + handler(&mut parser)?; + } + if data_buf.capacity() > 0 { + self.tc.bufs.push(data_buf); + } + Ok(()) + } +} diff --git a/src/tree/container.rs b/src/tree/container.rs index 1751f439..9f461ee4 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -17,11 +17,11 @@ use crate::state::State; use crate::text; use crate::utils::errorfmt::ErrorFmt; use crate::utils::numcell::NumCell; +use crate::utils::rc_eq::rc_eq; use std::fmt::{Debug, Formatter}; use std::mem; use std::ops::{Deref, DerefMut, Sub}; use std::rc::Rc; -use crate::utils::rc_eq::rc_eq; #[allow(dead_code)] #[derive(Copy, Clone, Debug, Eq, PartialEq)] diff --git a/src/utils.rs b/src/utils.rs index e5859ddc..5c3e6be2 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -16,6 +16,7 @@ pub mod numcell; pub mod oserror; pub mod ptr_ext; pub mod queue; +pub mod rc_eq; pub mod run_toplevel; pub mod smallmap; pub mod stack; @@ -25,4 +26,3 @@ pub mod trim; pub mod vasprintf; pub mod vec_ext; pub mod vecstorage; -pub mod rc_eq; diff --git a/src/utils/bitfield.rs b/src/utils/bitfield.rs index 8e136115..930c38f0 100644 --- a/src/utils/bitfield.rs +++ b/src/utils/bitfield.rs @@ -10,6 +10,15 @@ pub struct Bitfield { } impl Bitfield { + pub fn take(&mut self, val: u32) { + let idx = val as usize / SEG_SIZE; + let pos = val as usize % SEG_SIZE; + while self.vals.len() <= idx { + self.vals.push(0); + } + self.vals[idx] &= !(1 << pos); + } + pub fn acquire(&mut self) -> u32 { for (idx, n) in self.vals.iter_mut().enumerate() { if *n != 0 { diff --git a/src/utils/buffd.rs b/src/utils/buffd.rs index ad796d9b..91f087fd 100644 --- a/src/utils/buffd.rs +++ b/src/utils/buffd.rs @@ -1,6 +1,6 @@ use crate::async_engine::AsyncError; pub use buf_in::BufFdIn; -pub use buf_out::{BufFdOut, OutBufferSwapchain}; +pub use buf_out::{BufFdOut, OutBuffer, OutBufferSwapchain}; pub use formatter::MsgFormatter; pub use parser::{MsgParser, MsgParserError}; use thiserror::Error; diff --git a/src/utils/buffd/buf_out.rs b/src/utils/buffd/buf_out.rs index 1c0b17ec..ebda6ced 100644 --- a/src/utils/buffd/buf_out.rs +++ b/src/utils/buffd/buf_out.rs @@ -112,6 +112,17 @@ impl BufFdOut { Ok(()) } + pub async fn flush_no_timeout(&mut self, buf: &mut OutBuffer) -> Result<(), BufFdError> { + while buf.read_pos < buf.write_pos { + if self.flush_sync(buf)? { + self.fd.writable().await?; + } + } + buf.read_pos = 0; + buf.write_pos = 0; + Ok(()) + } + fn flush_sync(&mut self, buffer: &mut OutBuffer) -> Result { while buffer.read_pos < buffer.write_pos { let mut buf = unsafe { &(*buffer.buf)[buffer.read_pos..buffer.write_pos] }; diff --git a/src/utils/buffd/parser.rs b/src/utils/buffd/parser.rs index 34927e5a..ed544567 100644 --- a/src/utils/buffd/parser.rs +++ b/src/utils/buffd/parser.rs @@ -37,7 +37,7 @@ impl<'a, 'b> MsgParser<'a, 'b> { Self { buf, pos: 0, - data: unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) }, + data: uapi::as_bytes(data), } } diff --git a/src/wheel.rs b/src/wheel.rs index 86056bac..9c9aed97 100644 --- a/src/wheel.rs +++ b/src/wheel.rs @@ -172,7 +172,11 @@ struct WheelWrapper { } impl EventLoopDispatcher for WheelWrapper { - fn dispatch(self: Rc, events: i32) -> Result<(), Box> { + fn dispatch( + self: Rc, + _fd: Option, + events: i32, + ) -> Result<(), Box> { if events & (c::EPOLLERR | c::EPOLLHUP) != 0 { return Err(Box::new(WheelError::ErrorEvent)); } @@ -238,7 +242,7 @@ struct PeriodicDispatcher { } impl EventLoopDispatcher for PeriodicDispatcher { - fn dispatch(self: Rc, events: i32) -> Result<(), Box> { + fn dispatch(self: Rc, _fd: Option, events: i32) -> Result<(), Box> { if events & (c::EPOLLERR | c::EPOLLHUP) != 0 { return Err(Box::new(WheelError::ErrorEvent)); } diff --git a/src/wire_xcon.rs b/src/wire_xcon.rs index fa185cee..bacc90ec 100644 --- a/src/wire_xcon.rs +++ b/src/wire_xcon.rs @@ -1,4 +1,12 @@ -#![allow(unused_imports, unused_variables, dead_code, unused_assignments, clippy::eval_order_dependence, clippy::double_parens, clippy::unnecessary_cast)] +#![allow( + unused_imports, + unused_variables, + dead_code, + unused_assignments, + clippy::eval_order_dependence, + clippy::double_parens, + clippy::unnecessary_cast +)] use crate::xcon::{Formatter, Message, Parser, Request, XEvent, XconError}; use bstr::BStr; diff --git a/src/xwayland.rs b/src/xwayland.rs index 9b5703fb..a69616da 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -161,9 +161,15 @@ async fn run( }; let client_id = state.clients.id(); let queue = Rc::new(AsyncQueue::new()); - let client = state - .clients - .spawn2(client_id, state, client1, 9999, 9999, Some(queue.clone())); + let client = state.clients.spawn2( + client_id, + state, + client1, + 9999, + 9999, + true, + Some(queue.clone()), + ); let client = match client { Ok(c) => c, Err(e) => return Err(XWaylandError::SpawnClient(e)), diff --git a/wire/jay_compositor.txt b/wire/jay_compositor.txt new file mode 100644 index 00000000..f539733e --- /dev/null +++ b/wire/jay_compositor.txt @@ -0,0 +1,9 @@ +# requests + +msg destroy = 0 { + +} + +msg get_log_file = 1 { + id: id(jay_log_file), +} diff --git a/wire/jay_log_file.txt b/wire/jay_log_file.txt new file mode 100644 index 00000000..87ffde49 --- /dev/null +++ b/wire/jay_log_file.txt @@ -0,0 +1,10 @@ +# requests + +msg destroy = 0 { +} + +# events + +msg path = 0 { + path: bstr, +}