1
0
Fork 0
forked from wry/wry

portal: start portal automatically with compositor

This commit is contained in:
Julian Orth 2024-03-19 21:00:36 +01:00
parent 39d1e49672
commit 4d6f226254
14 changed files with 331 additions and 125 deletions

3
etc/jay-portals.conf Normal file
View file

@ -0,0 +1,3 @@
[preferred]
default=gtk
org.freedesktop.impl.portal.ScreenCast=jay

View file

@ -1,4 +1,3 @@
[portal]
DBusName=org.freedesktop.impl.portal.desktop.jay
Interfaces=org.freedesktop.impl.portal.ScreenCast;
UseIn=jay

View file

@ -1,4 +0,0 @@
[D-BUS Service]
Name=org.freedesktop.impl.portal.desktop.jay
Exec=/bin/false
SystemdService=xdg-desktop-portal-jay.service

View file

@ -1,7 +0,0 @@
[Unit]
Description=Jay Portal
[Service]
Type=dbus
BusName=org.freedesktop.impl.portal.desktop.jay
ExecStart=/home/julian/bin/jay portal

View file

@ -226,7 +226,7 @@ pub fn main() {
Cmd::Unlock => unlock::main(cli.global),
Cmd::RunPrivileged(a) => run_privileged::main(cli.global, a),
Cmd::SeatTest(a) => seat_test::main(cli.global, a),
Cmd::Portal => portal::run(cli.global),
Cmd::Portal => portal::run_freestanding(cli.global),
Cmd::Randr(a) => randr::main(cli.global, a),
Cmd::Input(a) => input::main(cli.global, a),
#[cfg(feature = "it")]

View file

@ -23,6 +23,7 @@ use {
io_uring::{IoUring, IoUringError},
leaks,
logger::Logger,
portal::{self, PortalStartup},
scale::Scale,
sighand::{self, SighandError},
state::{ConnectorData, IdleState, ScreenlockState, State, XWaylandState},
@ -52,8 +53,16 @@ pub const MAX_EXTENTS: i32 = (1 << 22) - 1;
pub fn start_compositor(global: GlobalArgs, args: RunArgs) {
let forker = create_forker();
let portal = portal::run_from_compositor(global.log_level.into());
let logger = Logger::install_compositor(global.log_level.into());
let res = start_compositor2(Some(forker), Some(logger.clone()), args, None);
let portal = match portal {
Ok(p) => Some(p),
Err(e) => {
log::error!("Could not spawn portal: {}", ErrorFmt(e));
None
}
};
let res = start_compositor2(Some(forker), portal, Some(logger.clone()), args, None);
leaks::log_leaked();
if let Err(e) = res {
let e = ErrorFmt(e);
@ -67,7 +76,7 @@ pub fn start_compositor(global: GlobalArgs, args: RunArgs) {
#[cfg(feature = "it")]
pub fn start_compositor_for_test(future: TestFuture) -> Result<(), CompositorError> {
let res = start_compositor2(None, None, RunArgs::default(), Some(future));
let res = start_compositor2(None, None, None, RunArgs::default(), Some(future));
leaks::log_leaked();
res
}
@ -106,6 +115,7 @@ pub type TestFuture = Box<dyn Fn(&Rc<State>) -> Box<dyn Future<Output = ()>>>;
fn start_compositor2(
forker: Option<Rc<ForkerProxy>>,
portal: Option<PortalStartup>,
logger: Option<Arc<Logger>>,
run_args: RunArgs,
test_future: Option<TestFuture>,
@ -161,7 +171,7 @@ fn start_compositor2(
pending_float_titles: Default::default(),
dbus: Dbus::new(&engine, &ring, &run_toplevel),
fdcloser: FdCloser::new(),
logger,
logger: logger.clone(),
connectors: Default::default(),
outputs: Default::default(),
drm_devs: Default::default(),
@ -225,6 +235,10 @@ fn start_compositor2(
forker.setenv(key.as_bytes(), val.as_bytes());
}
}
let mut _portal = None;
if let (Some(portal), Some(logger)) = (portal, &logger) {
_portal = Some(engine.spawn(portal.spawn(engine.clone(), ring.clone(), logger.clone())));
}
let _compositor = engine.spawn(start_compositor3(state.clone(), test_future));
ring.run()?;
state.clear();

View file

@ -1,18 +1,19 @@
mod clone3;
mod io;
use {
crate::{
async_engine::{AsyncEngine, SpawnedFuture},
compositor::{DISPLAY, WAYLAND_DISPLAY},
forker::{
clone3::{fork_with_pidfd, Forked},
io::{IoIn, IoOut},
},
forker::io::{IoIn, IoOut},
io_uring::IoUring,
state::State,
utils::{
buffd::BufFdError, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell,
buffd::BufFdError,
clone3::{fork_with_pidfd, Forked},
copyhashmap::CopyHashMap,
errorfmt::ErrorFmt,
numcell::NumCell,
process_name::set_process_name,
queue::AsyncQueue,
},
xwayland,
@ -323,7 +324,7 @@ impl Forker {
env::set_var("XDG_SESSION_TYPE", "wayland");
env::remove_var(DISPLAY);
env::remove_var(WAYLAND_DISPLAY);
setup_name("the ol' forker");
set_process_name("the ol' forker");
setup_deathsig(ppid);
reset_signals();
let socket = Rc::new(setup_fds(socket));
@ -568,13 +569,6 @@ fn setup_deathsig(ppid: c::pid_t) {
}
}
fn setup_name(name: &str) {
unsafe {
let name = name.into_ustr();
c::prctl(c::PR_SET_NAME, name.as_ptr());
}
}
fn map_fds(fds: Vec<(i32, OwnedFd)>) -> Result<Vec<OwnedFd>, SpawnError> {
let mut desired: Vec<_> = fds.iter().map(|v| v.0).collect();
desired.sort_by(|a, b| b.cmp(a));

View file

@ -88,11 +88,10 @@ impl JayCompositor {
let log_file = Rc::new(JayLogFile::new(req.id, &self.client));
track!(self.client, log_file);
self.client.add_client_obj(&log_file)?;
let path = match &self.client.state.logger {
Some(logger) => logger.path(),
_ => "".as_bytes().as_bstr(),
match &self.client.state.logger {
Some(logger) => log_file.send_path(logger.path().as_bstr()),
_ => log_file.send_path(b"".as_bstr()),
};
log_file.send_path(path);
Ok(())
}

View file

@ -1,8 +1,9 @@
use {
crate::utils::{errorfmt::ErrorFmt, oserror::OsError},
backtrace::Backtrace,
bstr::{BStr, BString, ByteSlice},
bstr::BString,
log::{Level, Log, Metadata, Record},
parking_lot::Mutex,
std::{
cell::Cell,
fs::DirBuilder,
@ -10,12 +11,12 @@ use {
os::unix::{ffi::OsStringExt, fs::DirBuilderExt},
ptr,
sync::{
atomic::{AtomicU32, Ordering::Relaxed},
atomic::{AtomicI32, AtomicU32, Ordering::Relaxed},
Arc,
},
time::SystemTime,
},
uapi::{c, format_ustr, Errno, Fd, OwnedFd},
uapi::{c, format_ustr, Errno, Fd, OwnedFd, Ustring},
};
thread_local! {
@ -24,8 +25,9 @@ thread_local! {
pub struct Logger {
level: AtomicU32,
path: BString,
file: OwnedFd,
path: Mutex<Arc<BString>>,
_file: Mutex<OwnedFd>,
file_fd: AtomicI32,
}
impl Logger {
@ -37,67 +39,31 @@ impl Logger {
fatal!("Error: Could not dup stderr: {}", ErrorFmt(e));
}
};
Self::install(level, b"", file)
Self::install(level, b"STDERR", file)
}
pub fn install_compositor(level: Level) -> Arc<Self> {
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();
fatal!("Error: Could not create log file: {}", ErrorFmt(e));
}
}
}
unreachable!();
};
std::panic::set_hook(Box::new(|p| {
if let Some(loc) = p.location() {
log::error!(
"Panic at {} line {} column {}",
loc.file(),
loc.line(),
loc.column()
);
} else {
log::error!("Panic at unknown location");
}
if let Some(msg) = p.payload().downcast_ref::<&str>() {
log::error!("Message: {}", msg);
}
if let Some(msg) = p.payload().downcast_ref::<String>() {
log::error!("Message: {}", msg);
}
log::error!("Backtrace:\n{:?}", Backtrace::new());
}));
let (path, file) = open_log_file("jay");
Self::install(level, path.as_bytes(), file)
}
pub fn install_pipe(file: OwnedFd, level: Level) -> Arc<Self> {
Self::install(level, b"PIPE", file)
}
fn install(level: Level, path: &[u8], file: OwnedFd) -> Arc<Self> {
let slf = Arc::new(Self {
level: AtomicU32::new(level as _),
path: path.to_vec().into(),
file,
path: Mutex::new(Arc::new(path.to_vec().into())),
file_fd: AtomicI32::new(file.raw()),
_file: Mutex::new(file),
});
log::set_boxed_logger(Box::new(LogWrapper {
logger: slf.clone(),
}))
.unwrap();
log::set_max_level(level.to_level_filter());
set_panic_hook();
slf
}
@ -106,17 +72,57 @@ impl Logger {
log::set_max_level(level.to_level_filter());
}
pub fn path(&self) -> &BStr {
self.path.as_bstr()
pub fn path(&self) -> Arc<BString> {
self.path.lock().clone()
}
pub fn redirect(&self, ty: &str) {
let (file, fd) = open_log_file(ty);
log::info!("Redirecting logs to {}", file.display());
*self.path.lock() = Arc::new(file.as_bytes().into());
self.file_fd.store(fd.raw(), Relaxed);
*self._file.lock() = fd;
}
pub fn write_raw(&self, buf: &[u8]) {
let mut fd = Fd::new(self.file_fd.load(Relaxed));
let _ = fd.write_all(buf);
}
}
fn create_log_dir() -> BString {
pub fn open_log_file(ty: &str) -> (Ustring, OwnedFd) {
let log_dir = create_log_dir(ty);
for i in 0.. {
let file_name = format_ustr!(
"{}/{ty}-{}-{}.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) => return (file_name, f),
Err(Errno(c::EEXIST)) => {}
Err(e) => {
let e: OsError = e.into();
fatal!("Error: Could not create log file: {}", ErrorFmt(e));
}
}
}
unreachable!()
}
fn create_log_dir(ty: &str) -> BString {
let mut log_dir = match dirs::data_local_dir() {
Some(d) => d,
None => fatal!("Error: $HOME is not set"),
};
log_dir.push("jay");
log_dir.push("logs");
log_dir.push(ty);
let res = DirBuilder::new()
.recursive(true)
.mode(0o755)
@ -131,6 +137,28 @@ fn create_log_dir() -> BString {
log_dir.into_os_string().into_vec().into()
}
fn set_panic_hook() {
std::panic::set_hook(Box::new(|p| {
if let Some(loc) = p.location() {
log::error!(
"Panic at {} line {} column {}",
loc.file(),
loc.line(),
loc.column()
);
} else {
log::error!("Panic at unknown location");
}
if let Some(msg) = p.payload().downcast_ref::<&str>() {
log::error!("Message: {}", msg);
}
if let Some(msg) = p.payload().downcast_ref::<String>() {
log::error!("Message: {}", msg);
}
log::error!("Backtrace:\n{:?}", Backtrace::new());
}));
}
struct LogWrapper {
logger: Arc<Logger>,
}
@ -170,7 +198,7 @@ impl Log for LogWrapper {
record.args(),
)
};
let mut fd = Fd::new(self.logger.file.raw());
let mut fd = Fd::new(self.logger.file_fd.load(Relaxed));
let _ = fd.write_all(buffer);
}

View file

@ -11,8 +11,9 @@ use {
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::Logger,
pipewire::pw_con::{PwCon, PwConHolder, PwConOwner},
portal::{
ptl_display::{watch_displays, PortalDisplay, PortalDisplayId},
@ -20,15 +21,29 @@ use {
ptl_screencast::{add_screencast_dbus_members, ScreencastSession},
},
utils::{
copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell,
run_toplevel::RunToplevel, xrd::xrd,
buf::Buf,
clone3::{fork_with_pidfd, Forked},
copyhashmap::CopyHashMap,
errorfmt::ErrorFmt,
numcell::NumCell,
oserror::OsError,
process_name::set_process_name,
run_toplevel::RunToplevel,
vecdeque_ext::VecDequeExt,
xrd::xrd,
},
video::dmabuf::DmaBufIds,
wheel::Wheel,
wire_dbus::org,
},
std::rc::{Rc, Weak},
uapi::c,
log::Level,
std::{
collections::VecDeque,
rc::{Rc, Weak},
sync::Arc,
},
thiserror::Error,
uapi::{c, OwnedFd},
};
const PORTAL_SUCCESS: u32 = 0;
@ -37,8 +52,110 @@ const PORTAL_CANCELLED: u32 = 1;
#[allow(dead_code)]
const PORTAL_ENDED: u32 = 2;
pub fn run(global: GlobalArgs) {
logger::Logger::install_stderr(global.log_level.into());
pub fn run_freestanding(global: GlobalArgs) {
let logger = Logger::install_stderr(global.log_level.into());
run(logger);
}
#[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({
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 mut buf = VecDeque::<u8>::new();
let mut buf2 = Buf::new(1024);
let mut done = false;
while !done {
match ring.read(&self.logs, buf2.clone()).await {
Ok(n) if n > 0 => buf.extend(&buf2[..n]),
Ok(_) => done = true,
Err(e) => {
log::error!("Could not read portal logs: {}", ErrorFmt(e));
return;
}
};
while let Some(pos) = buf.iter().position(|b| b == &b'\n') {
let (left, right) = buf.get_slices(..pos);
logger.write_raw(left);
logger.write_raw(right);
logger.write_raw(b" (portal)\n");
buf.drain(..=pos);
}
}
}
});
f1.await;
f2.await;
}
}
pub fn run_from_compositor(level: Level) -> Result<PortalStartup, PortalError> {
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);
std::process::exit(0);
}
}
}
fn run(logger: Arc<Logger>) {
let eng = AsyncEngine::new();
let ring = match IoUring::new(&eng, 32) {
Ok(r) => r,
@ -46,13 +163,16 @@ pub fn run(global: GlobalArgs) {
fatal!("Could not create an IO-uring: {}", ErrorFmt(e));
}
};
let _f = eng.spawn(run_async(eng.clone(), ring.clone()));
let _f = eng.spawn(run_async(eng.clone(), ring.clone(), logger));
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>) {
async fn run_async(eng: Rc<AsyncEngine>, ring: Rc<IoUring>, logger: Arc<Logger>) {
let (_rtl_future, rtl) = RunToplevel::install(&eng);
let dbus = Dbus::new(&eng, &ring, &rtl);
let dbus = init_dbus_session(&dbus, logger).await;
let xrd = match xrd() {
Some(xrd) => xrd,
_ => {
@ -71,9 +191,6 @@ async fn run_async(eng: Rc<AsyncEngine>, ring: Rc<IoUring>) {
fatal!("Could not connect to pipewire: {}", ErrorFmt(e));
}
};
let (_rtl_future, rtl) = RunToplevel::install(&eng);
let dbus = Dbus::new(&eng, &ring, &rtl);
let dbus = init_dbus_session(&dbus).await;
let state = Rc::new(PortalState {
xrd,
ring,
@ -101,37 +218,53 @@ async fn run_async(eng: Rc<AsyncEngine>, ring: Rc<IoUring>) {
const UNIQUE_NAME: &str = "org.freedesktop.impl.portal.desktop.jay";
async fn init_dbus_session(dbus: &Dbus) -> Rc<DbusSocket> {
async fn init_dbus_session(dbus: &Dbus, logger: Arc<Logger>) -> Rc<DbusSocket> {
let session = match dbus.session().await {
Ok(s) => s,
Err(e) => {
fatal!("Could not connect to dbus session daemon: {}", ErrorFmt(e));
}
};
session.call(
BUS_DEST,
BUS_PATH,
org::freedesktop::dbus::RequestName {
name: UNIQUE_NAME.into(),
flags: DBUS_NAME_FLAG_DO_NOT_QUEUE,
},
|rv| match rv {
Ok(r) if r.rv == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER => {
log::info!("Acquired unique name {}", UNIQUE_NAME);
return;
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);
logger.redirect("portal");
let fork = match fork_with_pidfd(false) {
Ok(f) => f,
Err(e) => fatal!("Could not fork: {}", ErrorFmt(e)),
};
match fork {
Forked::Parent { .. } => std::process::exit(0),
Forked::Child { .. } => {
if let Err(e) = uapi::setsid() {
log::error!("setsid failed: {}", ErrorFmt(OsError::from(e)));
}
}
}
Ok(r) => {
fatal!("Could not acquire unique name {}: {}", UNIQUE_NAME, r.rv);
}
Err(e) => {
fatal!(
"Could not communicate with the session bus: {}",
ErrorFmt(e)
);
}
},
);
session
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 {

View file

@ -7,6 +7,7 @@ pub mod buf;
pub mod buffd;
pub mod bufio;
pub mod cell_ext;
pub mod clone3;
pub mod clonecell;
pub mod copyhashmap;
pub mod debug_fn;
@ -27,6 +28,7 @@ pub mod opaque_cell;
pub mod option_ext;
pub mod oserror;
pub mod page_size;
pub mod process_name;
pub mod ptr_ext;
pub mod queue;
pub mod rc_eq;
@ -45,6 +47,7 @@ pub mod tri;
pub mod trim;
pub mod unlink_on_drop;
pub mod vec_ext;
pub mod vecdeque_ext;
pub mod vecstorage;
pub mod windows;
pub mod xrd;

View file

@ -30,6 +30,7 @@ pub fn fork_with_pidfd(pidfd_for_child: bool) -> Result<Forked, ForkerError> {
let mut args = clone_args {
flags: c::CLONE_PIDFD as u64,
pidfd: (&mut pidfd as *mut c::c_int) as _,
exit_signal: c::SIGCHLD as _,
..Default::default()
};
let mut child_pidfd = None;

View file

@ -0,0 +1,8 @@
use uapi::{c, IntoUstr};
pub fn set_process_name(name: &str) {
unsafe {
let name = name.into_ustr();
c::prctl(c::PR_SET_NAME, name.as_ptr());
}
}

35
src/utils/vecdeque_ext.rs Normal file
View file

@ -0,0 +1,35 @@
use std::{
collections::{Bound, VecDeque},
ops::RangeBounds,
};
pub trait VecDequeExt<T> {
fn get_slices(&self, range: impl RangeBounds<usize>) -> (&[T], &[T]);
}
impl<T> VecDequeExt<T> for VecDeque<T> {
fn get_slices(&self, range: impl RangeBounds<usize>) -> (&[T], &[T]) {
let (l, r) = self.as_slices();
let start = match range.start_bound().cloned() {
Bound::Included(n) => n,
Bound::Excluded(n) => n + 1,
Bound::Unbounded => 0,
};
let end = match range.end_bound().cloned() {
Bound::Included(n) => n + 1,
Bound::Excluded(n) => n,
Bound::Unbounded => self.len(),
};
let left = {
let lo = start.min(l.len());
let hi = end.min(l.len());
&l[lo..hi]
};
let right = {
let lo = start.saturating_sub(l.len());
let hi = end.saturating_sub(l.len());
&r[lo..hi]
};
(left, right)
}
}