mod ptl_display;
mod ptl_gui;
mod ptl_remote_desktop;
mod ptl_render_ctx;
mod ptl_screencast;
mod ptl_session;
mod ptl_text;
use crate::async_engine::AsyncEngine;
use crate::cli::GlobalArgs;
use crate::cmm::cmm_manager::ColorManager;
use crate::compositor::LogLevel;
use crate::dbus::BUS_DEST;
use crate::dbus::BUS_PATH;
use crate::dbus::DBUS_NAME_FLAG_DO_NOT_QUEUE;
use crate::dbus::DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER;
use crate::dbus::Dbus;
use crate::dbus::DbusSocket;
use crate::env::XDG_RUNTIME_DIR;
use crate::env::initial_log_level;
use crate::eventfd_cache::EventfdCache;
use crate::forker::ForkerError;
use crate::io_uring::IoUring;
use crate::logger::Logger;
use crate::pipewire::pw_con::PwCon;
use crate::pipewire::pw_con::PwConHolder;
use crate::pipewire::pw_con::PwConOwner;
use crate::portal::ptl_display::PortalDisplay;
use crate::portal::ptl_display::PortalDisplayId;
use crate::portal::ptl_display::watch_displays;
use crate::portal::ptl_remote_desktop::add_remote_desktop_dbus_members;
use crate::portal::ptl_render_ctx::PortalRenderCtx;
use crate::portal::ptl_screencast::add_screencast_dbus_members;
use crate::portal::ptl_session::PortalSession;
use crate::utils::clone3::Forked;
use crate::utils::clone3::fork_with_pidfd;
use crate::utils::copyhashmap::CopyHashMap;
use crate::utils::errorfmt::ErrorFmt;
use crate::utils::line_logger::log_lines;
use crate::utils::numcell::NumCell;
use crate::utils::pipe::Pipe;
use crate::utils::pipe::pipe;
use crate::utils::process_name::set_process_name;
use crate::utils::run_toplevel::RunToplevel;
use crate::version::VERSION;
use crate::video::dmabuf::DmaBufIds;
use crate::wheel::Wheel;
use crate::wire_dbus::org;
use jay_algorithms::oserror::OsError;
use jay_algorithms::oserror::OsErrorExt;
use std::ffi::OsStr;
use std::io::BufReader;
use std::io::BufWriter;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::process::CommandExt;
use std::process::Command;
use std::process::exit;
use std::rc::Rc;
use std::rc::Weak;
use std::sync::Arc;
use thiserror::Error;
use uapi::OwnedFd;
use uapi::WEXITSTATUS;
use uapi::c;
use uapi::getpid;
const PORTAL_SUCCESS: u32 = 0;
#[expect(unused)]
const PORTAL_CANCELLED: u32 = 1;
#[expect(unused)]
const PORTAL_ENDED: u32 = 2;
pub fn run_freestanding(_global: GlobalArgs) {
let logger = Logger::install_stderr(initial_log_level());
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<OwnedFd>,
pid: c::pid_t,
pidfd: Rc<OwnedFd>,
}
impl PortalStartup {
pub async fn spawn(self, eng: Rc<AsyncEngine>, ring: Rc<IoUring>, logger: Arc<Logger>) {
let f1 = eng.spawn("check portal exit code", {
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).to_os_error() {
Ok(r) => r,
Err(e) => {
log::error!(
"Could not retrieve exit status of portal ({}): {}",
self.pid,
ErrorFmt(e),
);
return;
}
};
let status = WEXITSTATUS(status);
if status != 0 {
log::error!("Portal exited with non-0 exit code: {status}");
}
}
});
let f2 = eng.spawn("portal logger", {
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: LogLevel) -> Result<PortalStartup, PortalError> {
let Pipe { read, write } = match pipe() {
Ok(p) => p,
Err(e) => return Err(PortalError::CreatePipe(e)),
};
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);
}
}
}
fn run(logger: Arc<Logger>, freestanding: bool) -> ! {
let Pipe { read, write } = match pipe() {
Ok(p) => p,
Err(e) => {
fatal!("Could not create a pipe: {}", ErrorFmt(e));
}
};
let fork = match fork_with_pidfd(false) {
Ok(f) => f,
Err(e) => {
fatal!("Could not fork: {}", ErrorFmt(e));
}
};
let Forked::Parent { pid, .. } = fork else {
drop(read);
run2(logger, write);
exit(0);
};
drop(write);
let read = BufReader::new(read);
let Ok(log_file) = bincode::deserialize_from::<_, Vec<u8>>(read) else {
let (_, status) = match uapi::waitpid(pid, 0).to_os_error() {
Ok(r) => r,
Err(e) => {
fatal!(
"Could not retrieve exit status of portal ({pid}): {}",
ErrorFmt(e),
);
}
};
exit(WEXITSTATUS(status));
};
if freestanding {
let e = Command::new("tail")
.arg("-f")
.arg("-n")
.arg("+1")
.arg(OsStr::from_bytes(&log_file))
.exec();
fatal!("Could not exec `tail`: {}", ErrorFmt(e));
}
exit(0);
}
fn run2(logger: Arc<Logger>, path_sink: OwnedFd) {
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(
"portal",
run_async(eng.clone(), ring.clone(), logger, path_sink),
);
if let Err(e) = ring.run() {
fatal!("The IO-uring returned an error: {}", ErrorFmt(e));
}
}
async fn run_async(
eng: Rc<AsyncEngine>,
ring: Rc<IoUring>,
logger: Arc<Logger>,
path_sink: OwnedFd,
) {
let (_rtl_future, rtl) = RunToplevel::install(&eng);
let dbus = Dbus::new(&eng, &ring, &rtl);
let dbus = init_dbus_session(&dbus, logger, path_sink).await;
let Some(xrd) = *XDG_RUNTIME_DIR else {
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 eventfd_cache = EventfdCache::new(&ring, &eng);
let state = Rc::new(PortalState {
xrd,
ring,
eventfd_cache,
eng,
wheel,
displays: Default::default(),
dbus,
sessions: Default::default(),
next_id: NumCell::new(1),
render_ctxs: Default::default(),
dma_buf_ids: Default::default(),
pw_con: pw_con.as_ref().map(|c| c.con.clone()),
color_manager: ColorManager::new(),
});
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<Logger>, path_sink: OwnedFd) -> Rc<DbusSocket> {
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 sink = BufWriter::new(path_sink);
if let Err(e) = bincode::serialize_into(sink, log_file.as_bytes()) {
log::error!("Could not send log file to parent: {}", ErrorFmt(e));
}
if let Err(e) = uapi::setsid().to_os_error() {
log::error!("setsid failed: {}", ErrorFmt(e));
}
log::info!("pid = {}", getpid());
set_process_name("jay portal");
session
}
Ok(_) => {
log::info!("Portal is already running");
exit(0);
}
Err(e) => {
fatal!(
"Could not communicate with the session bus: {}",
ErrorFmt(e)
);
}
}
}
struct PortalState {
xrd: &'static str,
ring: Rc<IoUring>,
eventfd_cache: Rc<EventfdCache>,
eng: Rc<AsyncEngine>,
wheel: Rc<Wheel>,
displays: CopyHashMap<PortalDisplayId, Rc<PortalDisplay>>,
dbus: Rc<DbusSocket>,
sessions: CopyHashMap<String, Rc<PortalSession>>,
next_id: NumCell<u32>,
render_ctxs: CopyHashMap<c::dev_t, Weak<PortalRenderCtx>>,
dma_buf_ids: Rc<DmaBufIds>,
pw_con: Option<Rc<PwCon>>,
color_manager: Rc<ColorManager>,
}
impl PortalState {
fn id<T: From<u32>>(&self) -> T {
T::from(self.next_id.fetch_add(1))
}
}
impl PwConOwner for PortalState {
fn killed(&self) {
fatal!("The pipewire connection has been closed");
}
}