mod ptl_display; mod ptl_remote_desktop; mod ptl_render_ctx; mod ptl_screencast; mod ptr_gui; use { crate::{ async_engine::AsyncEngine, cli::GlobalArgs, dbus::{ Dbus, DbusSocket, BUS_DEST, BUS_PATH, DBUS_NAME_FLAG_DO_NOT_QUEUE, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER, }, forker::ForkerError, io_uring::IoUring, logger::Logger, pipewire::pw_con::{PwConHolder, PwConOwner}, portal::{ ptl_display::{watch_displays, PortalDisplay, PortalDisplayId}, ptl_remote_desktop::{add_remote_desktop_dbus_members, RemoteDesktopSession}, ptl_render_ctx::PortalRenderCtx, ptl_screencast::{add_screencast_dbus_members, ScreencastSession}, }, utils::{ clone3::{fork_with_pidfd, Forked}, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, line_logger::log_lines, numcell::NumCell, oserror::OsError, process_name::set_process_name, run_toplevel::RunToplevel, xrd::xrd, }, version::VERSION, video::dmabuf::DmaBufIds, wheel::Wheel, wire_dbus::org, }, log::Level, std::{ os::unix::process::CommandExt, process::Command, rc::{Rc, Weak}, sync::Arc, }, thiserror::Error, uapi::{c, getpid, OwnedFd}, }; const PORTAL_SUCCESS: u32 = 0; #[allow(dead_code)] const PORTAL_CANCELLED: u32 = 1; #[allow(dead_code)] const PORTAL_ENDED: u32 = 2; pub fn run_freestanding(global: GlobalArgs) { let logger = Logger::install_stderr(global.log_level.into()); run(logger, true); } #[derive(Debug, Error)] pub enum PortalError { #[error("Could not create pipe")] CreatePipe(#[source] OsError), #[error("Could not fork")] Fork(#[source] ForkerError), } pub struct PortalStartup { logs: Rc, pid: c::pid_t, pidfd: Rc, } impl PortalStartup { pub async fn spawn(self, eng: Rc, ring: Rc, logger: Arc) { let f1 = eng.spawn({ let ring = ring.clone(); async move { if let Err(e) = ring.readable(&self.pidfd).await { log::error!( "Could not wait for portal pidfd to become readable: {}", ErrorFmt(e) ); return; } let (_, status) = match uapi::waitpid(self.pid, 0) { Ok(r) => r, Err(e) => { log::error!( "Could not retrieve exit status of portal ({}): {}", self.pid, ErrorFmt(OsError::from(e)) ); return; } }; let status = uapi::WEXITSTATUS(status); if status != 0 { log::error!("Portal exited with non-0 exit code: {status}"); } } }); let f2 = eng.spawn({ let ring = ring.clone(); let logger = logger.clone(); async move { let res = log_lines(&ring, &self.logs, |left, right| { logger.write_raw(left); logger.write_raw(right); logger.write_raw(b" (portal)\n"); }) .await; if let Err(e) = res { log::error!("Could not read portal logs: {}", ErrorFmt(e)); } } }); f1.await; f2.await; } } pub fn run_from_compositor(level: Level) -> Result { let (read, write) = match uapi::pipe2(c::O_CLOEXEC) { Ok(p) => p, Err(e) => return Err(PortalError::CreatePipe(e.into())), }; let fork = match fork_with_pidfd(false) { Ok(f) => f, Err(e) => return Err(PortalError::Fork(e)), }; match fork { Forked::Parent { pidfd, pid } => Ok(PortalStartup { logs: Rc::new(read), pid, pidfd: Rc::new(pidfd), }), Forked::Child { .. } => { drop(read); let logger = Logger::install_pipe(write, level); run(logger, false); std::process::exit(0); } } } fn run(logger: Arc, freestanding: bool) { let eng = AsyncEngine::new(); let ring = match IoUring::new(&eng, 32) { Ok(r) => r, Err(e) => { fatal!("Could not create an IO-uring: {}", ErrorFmt(e)); } }; let _f = eng.spawn(run_async(eng.clone(), ring.clone(), logger, freestanding)); if let Err(e) = ring.run() { fatal!("The IO-uring returned an error: {}", ErrorFmt(e)); } } async fn run_async( eng: Rc, ring: Rc, logger: Arc, freestanding: bool, ) { let (_rtl_future, rtl) = RunToplevel::install(&eng); let dbus = Dbus::new(&eng, &ring, &rtl); let dbus = init_dbus_session(&dbus, logger, freestanding).await; let xrd = match xrd() { Some(xrd) => xrd, _ => { fatal!("XDG_RUNTIME_DIR is not set"); } }; let wheel = match Wheel::new(&eng, &ring) { Ok(w) => w, Err(e) => { fatal!("Could not create a timer wheel: {}", ErrorFmt(e)); } }; let pw_con = match PwConHolder::new(&eng, &ring).await { Ok(p) => Some(p), Err(e) => { log::error!("Could not connect to pipewire: {}", ErrorFmt(e)); None } }; let state = Rc::new(PortalState { xrd, ring, eng, wheel, displays: Default::default(), dbus, screencasts: Default::default(), remote_desktop_sessions: Default::default(), next_id: NumCell::new(1), render_ctxs: Default::default(), dma_buf_ids: Default::default(), }); if let Some(pw_con) = &pw_con { pw_con.con.owner.set(Some(state.clone())); } let _root = { let obj = state .dbus .add_object("/org/freedesktop/portal/desktop") .unwrap(); if let Some(pw_con) = &pw_con { add_screencast_dbus_members(&state, &pw_con.con, &obj); } add_remote_desktop_dbus_members(&state, &obj); obj }; watch_displays(state.clone()).await; } const UNIQUE_NAME: &str = "org.freedesktop.impl.portal.desktop.jay"; async fn init_dbus_session(dbus: &Dbus, logger: Arc, freestanding: bool) -> Rc { let session = match dbus.session().await { Ok(s) => s, Err(e) => { fatal!("Could not connect to dbus session daemon: {}", ErrorFmt(e)); } }; let rv = session .call_async( BUS_DEST, BUS_PATH, org::freedesktop::dbus::RequestName { name: UNIQUE_NAME.into(), flags: DBUS_NAME_FLAG_DO_NOT_QUEUE, }, ) .await; match rv { Ok(r) if r.get().rv == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER => { log::info!("Acquired unique name {}", UNIQUE_NAME); let log_file = logger.redirect("portal"); log::info!("version = {VERSION}"); let fork = match fork_with_pidfd(false) { Ok(f) => f, Err(e) => fatal!("Could not fork: {}", ErrorFmt(e)), }; match fork { Forked::Parent { .. } => { if freestanding { let e = Command::new("tail") .arg("-f") .arg("-n") .arg("+1") .arg(&log_file) .exec(); eprintln!("Could not exec `tail`: {}", ErrorFmt(e)); std::process::exit(1); } std::process::exit(0) } Forked::Child { .. } => { if let Err(e) = uapi::setsid() { log::error!("setsid failed: {}", ErrorFmt(OsError::from(e))); } log::info!("pid = {}", getpid()); } } set_process_name("jay portal"); session } Ok(_) => { log::info!("Portal is already running"); std::process::exit(0); } Err(e) => { fatal!( "Could not communicate with the session bus: {}", ErrorFmt(e) ); } } } struct PortalState { xrd: String, ring: Rc, eng: Rc, wheel: Rc, displays: CopyHashMap>, dbus: Rc, screencasts: CopyHashMap>, remote_desktop_sessions: CopyHashMap>, next_id: NumCell, render_ctxs: CopyHashMap>, dma_buf_ids: Rc, } impl PortalState { pub fn id>(&self) -> T { T::from(self.next_id.fetch_add(1)) } } impl PwConOwner for PortalState { fn killed(&self) { fatal!("The pipewire connection has been closed"); } }