use { crate::{ gfx_api::{cross_intersect_formats, GfxFormat}, gfx_apis::create_gfx_context, ifs::wl_seat::POINTER, object::Version, portal::{ ptl_render_ctx::{PortalRenderCtx, PortalServerRenderCtx}, ptl_session::PortalSession, ptr_gui::WindowData, PortalState, }, utils::{ bitflags::BitflagsExt, clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, hash_map_ext::HashMapExt, opaque::{opaque, Opaque}, oserror::OsError, }, video::drm::Drm, wire::{ wl_pointer, JayCompositor, WlCompositor, WlOutput, WlSeat, WlSurfaceId, WpFractionalScaleManagerV1, WpViewporter, ZwlrLayerShellV1, ZwpLinuxDmabufV1, }, wl_usr::{ usr_ifs::{ usr_jay_compositor::UsrJayCompositor, usr_jay_output::{UsrJayOutput, UsrJayOutputOwner}, usr_jay_pointer::UsrJayPointer, usr_jay_render_ctx::UsrJayRenderCtxOwner, usr_jay_workspace::{UsrJayWorkspace, UsrJayWorkspaceOwner}, usr_jay_workspace_watcher::{UsrJayWorkspaceWatcher, UsrJayWorkspaceWatcherOwner}, usr_linux_dmabuf::UsrLinuxDmabuf, usr_wl_compositor::UsrWlCompositor, usr_wl_output::{UsrWlOutput, UsrWlOutputOwner}, usr_wl_pointer::{UsrWlPointer, UsrWlPointerOwner}, usr_wl_registry::{UsrWlRegistry, UsrWlRegistryOwner}, usr_wl_seat::{UsrWlSeat, UsrWlSeatOwner}, usr_wlr_layer_shell::UsrWlrLayerShell, usr_wp_fractional_scale_manager::UsrWpFractionalScaleManager, usr_wp_viewporter::UsrWpViewporter, }, UsrCon, UsrConOwner, }, }, ahash::AHashMap, jay_config::video::GfxApi, std::{ cell::{Cell, RefCell}, ops::Deref, os::unix::ffi::OsStrExt, rc::Rc, str::FromStr, }, uapi::{c, AsUstr, OwnedFd}, }; struct PortalDisplayPrelude { con: Rc, state: Rc, registry: Rc, globals: RefCell>>, } shared_ids!(PortalDisplayId); pub struct PortalDisplay { pub id: PortalDisplayId, pub unique_id: Opaque, pub con: Rc, pub(super) state: Rc, registry: Rc, _workspace_watcher: Rc, pub dmabuf: CloneCell>>, pub jc: Rc, pub ls: Rc, pub comp: Rc, pub fsm: Rc, pub vp: Rc, pub render_ctx: CloneCell>>, pub outputs: CopyHashMap>, pub seats: CopyHashMap>, pub workspaces: CopyHashMap>, pub windows: CopyHashMap>, pub sessions: CopyHashMap>, } pub struct PortalOutput { pub global_id: u32, pub dpy: Rc, pub wl: Rc, pub jay: Rc, } pub struct PortalSeat { pub global_id: u32, pub dpy: Rc, pub wl: Rc, pub jay_pointer: Rc, pub pointer: CloneCell>>, pub name: RefCell, pub capabilities: Cell, pub pointer_focus: CloneCell>>, } impl UsrWlSeatOwner for PortalSeat { fn name(&self, name: &str) { *self.name.borrow_mut() = name.to_string(); } fn capabilities(self: Rc, value: u32) { let old = self.capabilities.replace(value); if old.contains(POINTER) != value.contains(POINTER) { if old.contains(POINTER) { if let Some(pointer) = self.pointer.take() { pointer.con.remove_obj(pointer.deref()); } } else { let pointer = self.wl.get_pointer(); pointer.owner.set(Some(self.clone())); self.pointer.set(Some(pointer)); } } } } impl UsrWlPointerOwner for PortalSeat { fn enter(&self, ev: &wl_pointer::Enter) { if let Some(window) = self.dpy.windows.get(&ev.surface) { self.pointer_focus.set(Some(window.clone())); window.motion(self, ev.surface_x, ev.surface_y, true); } } fn leave(&self, _ev: &wl_pointer::Leave) { self.pointer_focus.take(); } fn motion(&self, ev: &wl_pointer::Motion) { if let Some(window) = self.pointer_focus.get() { window.motion(self, ev.surface_x, ev.surface_y, false); } } fn button(&self, ev: &wl_pointer::Button) { if let Some(window) = self.pointer_focus.get() { window.button(self, ev.button, ev.state); } } } impl UsrWlRegistryOwner for PortalDisplayPrelude { fn global(self: Rc, name: u32, interface: &str, version: u32) { self.globals .borrow_mut() .entry(interface.to_string()) .or_default() .push((name, version)); } } impl UsrJayRenderCtxOwner for PortalDisplay { fn no_device(&self) { self.render_ctx.take(); } fn device(&self, fd: Rc, server_formats: Option>) { self.render_ctx.take(); let drm = match Drm::open_existing(fd) { Ok(d) => d, Err(e) => { log::error!("Could not open the drm device: {}", ErrorFmt(e)); return; } }; let dev_id = drm.dev(); let mut render_ctx = None; if let Some(ctx) = self.state.render_ctxs.get(&dev_id) { if let Some(ctx) = ctx.upgrade() { render_ctx = Some(ctx); } } if render_ctx.is_none() { let ctx = match create_gfx_context(&self.state.eng, &self.state.ring, &drm, GfxApi::OpenGl) { Ok(c) => c, Err(e) => { log::error!( "Could not create render context from drm device: {}", ErrorFmt(e) ); return; } }; let ctx = Rc::new(PortalRenderCtx { _dev_id: dev_id, ctx, }); self.state.render_ctxs.set(dev_id, Rc::downgrade(&ctx)); render_ctx = Some(ctx); } if let Some(ctx) = render_ctx { let client_formats = ctx.ctx.formats(); let usable_formats = match &server_formats { None => client_formats, Some(server_formats) => { Rc::new(cross_intersect_formats(&client_formats, server_formats)) } }; self.render_ctx.set(Some(Rc::new(PortalServerRenderCtx { ctx, usable_formats, server_formats, }))); } } } impl UsrConOwner for PortalDisplay { fn killed(&self) { log::info!("Removing display {}", self.id); for sc in self.sessions.lock().drain_values() { sc.kill(); } self.windows.clear(); self.state.displays.remove(&self.id); } } impl UsrWlRegistryOwner for PortalDisplay { fn global(self: Rc, name: u32, interface: &str, version: u32) { if interface == WlOutput.name() { add_output(&self, name, version); } else if interface == WlSeat.name() { add_seat(&self, name, version); } else if interface == ZwpLinuxDmabufV1.name() { let ls = Rc::new(UsrLinuxDmabuf { id: self.con.id(), con: self.con.clone(), owner: Default::default(), version: Version(version.min(5)), }); self.con.add_object(ls.clone()); self.registry.request_bind(name, ls.version.0, ls.deref()); self.dmabuf.set(Some(ls)); } } } impl UsrJayWorkspaceWatcherOwner for PortalDisplay { fn new(self: Rc, ev: Rc, linear_id: u32) { ev.owner.set(Some(self.clone())); self.workspaces.set(linear_id, ev); } } impl UsrJayWorkspaceOwner for PortalDisplay { fn destroyed(&self, ws: &UsrJayWorkspace) { self.workspaces.remove(&ws.linear_id.get()); self.con.remove_obj(ws); } } impl UsrJayOutputOwner for PortalOutput { fn destroyed(&self) { log::info!( "Display {}: Output {} removed", self.dpy.con.server_id, self.global_id, ); self.dpy.outputs.remove(&self.global_id); self.dpy.con.remove_obj(self.wl.deref()); self.dpy.con.remove_obj(self.jay.deref()); } } impl UsrWlOutputOwner for PortalOutput {} async fn maybe_add_display(state: &Rc, name: &str) { let tail = match name.strip_prefix("wayland-") { Some(t) => t, _ => return, }; let head = match tail.strip_suffix(".jay") { Some(h) => h, _ => return, }; let num = match u32::from_str(head) { Ok(n) => n, _ => return, }; let path = format!("{}/{}", state.xrd, name); let con = match UsrCon::new( &state.ring, &state.wheel, &state.eng, &state.dma_buf_ids, &path, num, ) .await { Ok(c) => c, Err(e) => { log::error!( "Could not connect to wayland display {}: {}", name, ErrorFmt(e) ); return; } }; let registry = con.get_registry(); let dpy = Rc::new(PortalDisplayPrelude { con: con.clone(), state: state.clone(), registry, globals: Default::default(), }); dpy.registry.owner.set(Some(dpy.clone())); con.sync(move || { finish_display_connect(dpy); }); log::info!("Connected to wayland display {num}: {name}"); } fn finish_display_connect(dpy: Rc) { let mut jc_opt = None; let mut ls_opt = None; let mut fsm_opt = None; let mut comp_opt = None; let mut vp_opt = None; let mut dmabuf_opt = None; let mut outputs = vec![]; let mut seats = vec![]; for (interface, instances) in dpy.globals.borrow_mut().deref() { for &(name, version) in instances { if interface == JayCompositor.name() { let jc = Rc::new(UsrJayCompositor { id: dpy.con.id(), con: dpy.con.clone(), owner: Default::default(), caps: Default::default(), version: Version(version.min(12)), }); dpy.con.add_object(jc.clone()); dpy.registry.request_bind(name, jc.version.0, jc.deref()); jc_opt = Some(jc); } else if interface == WpFractionalScaleManagerV1.name() { let ls = Rc::new(UsrWpFractionalScaleManager { id: dpy.con.id(), con: dpy.con.clone(), version: Version(version.min(1)), }); dpy.con.add_object(ls.clone()); dpy.registry.request_bind(name, ls.version.0, ls.deref()); fsm_opt = Some(ls); } else if interface == ZwlrLayerShellV1.name() { let ls = Rc::new(UsrWlrLayerShell { id: dpy.con.id(), con: dpy.con.clone(), version: Version(version.min(5)), }); dpy.con.add_object(ls.clone()); dpy.registry.request_bind(name, ls.version.0, ls.deref()); ls_opt = Some(ls); } else if interface == WpViewporter.name() { let ls = Rc::new(UsrWpViewporter { id: dpy.con.id(), con: dpy.con.clone(), version: Version(version.min(1)), }); dpy.con.add_object(ls.clone()); dpy.registry.request_bind(name, ls.version.0, ls.deref()); vp_opt = Some(ls); } else if interface == WlCompositor.name() { let ls = Rc::new(UsrWlCompositor { id: dpy.con.id(), con: dpy.con.clone(), version: Version(version.min(6)), }); dpy.con.add_object(ls.clone()); dpy.registry.request_bind(name, ls.version.0, ls.deref()); comp_opt = Some(ls); } else if interface == ZwpLinuxDmabufV1.name() { let ls = Rc::new(UsrLinuxDmabuf { id: dpy.con.id(), con: dpy.con.clone(), owner: Default::default(), version: Version(version.min(5)), }); dpy.con.add_object(ls.clone()); dpy.registry.request_bind(name, ls.version.0, ls.deref()); dmabuf_opt = Some(ls); } else if interface == WlOutput.name() { outputs.push((name, version)); } else if interface == WlSeat.name() { seats.push((name, version)); } } } macro_rules! get { ($opt:expr, $ty:expr) => { match $opt { Some(c) => c, _ => { log::error!("Compositor did not advertise a {}", $ty.name()); dpy.con.kill(); return; } } }; } let jc = get!(jc_opt, JayCompositor); let ls = get!(ls_opt, ZwlrLayerShellV1); let comp = get!(comp_opt, WlCompositor); let fsm = get!(fsm_opt, WpFractionalScaleManagerV1); let vp = get!(vp_opt, WpViewporter); let ww = jc.watch_workspaces(); let dpy = Rc::new(PortalDisplay { id: dpy.state.id(), unique_id: opaque(), con: dpy.con.clone(), state: dpy.state.clone(), registry: dpy.registry.clone(), _workspace_watcher: ww.clone(), dmabuf: CloneCell::new(dmabuf_opt), jc, outputs: Default::default(), render_ctx: Default::default(), seats: Default::default(), ls, comp, fsm, vp, windows: Default::default(), sessions: Default::default(), workspaces: Default::default(), }); dpy.state.displays.set(dpy.id, dpy.clone()); dpy.con.owner.set(Some(dpy.clone())); dpy.registry.owner.set(Some(dpy.clone())); ww.owner.set(Some(dpy.clone())); let jrc = dpy.jc.get_render_context(); jrc.owner.set(Some(dpy.clone())); for (name, version) in outputs { add_output(&dpy, name, version); } for (name, version) in seats { add_seat(&dpy, name, version); } log::info!("Display {} initialized", dpy.id); } fn add_seat(dpy: &Rc, name: u32, version: u32) { let wl = Rc::new(UsrWlSeat { id: dpy.con.id(), con: dpy.con.clone(), owner: Default::default(), version: Version(version.min(9)), }); dpy.con.add_object(wl.clone()); dpy.registry.request_bind(name, wl.version.0, wl.deref()); let jay_pointer = dpy.jc.get_pointer(&wl); let js = Rc::new(PortalSeat { global_id: name, dpy: dpy.clone(), wl, jay_pointer, pointer: Default::default(), name: RefCell::new("".to_string()), capabilities: Cell::new(0), pointer_focus: Default::default(), }); js.wl.owner.set(Some(js.clone())); dpy.seats.set(name, js); } fn add_output(dpy: &Rc, name: u32, version: u32) { let wl = Rc::new(UsrWlOutput { id: dpy.con.id(), con: dpy.con.clone(), owner: Default::default(), version: Version(version.min(4)), name: Default::default(), }); dpy.con.add_object(wl.clone()); dpy.registry.request_bind(name, wl.version.0, wl.deref()); let jo = dpy.jc.get_output(&wl); let po = Rc::new(PortalOutput { global_id: name, dpy: dpy.clone(), wl: wl.clone(), jay: jo.clone(), }); po.wl.owner.set(Some(po.clone())); po.jay.owner.set(Some(po.clone())); dpy.outputs.set(name, po); } pub(super) async fn watch_displays(state: Rc) { let inotify = Rc::new(uapi::inotify_init1(c::IN_CLOEXEC).unwrap()); if let Err(e) = uapi::inotify_add_watch(inotify.raw(), state.xrd.as_str(), c::IN_CREATE) { log::error!( "Cannot watch directory `{}`: {}", state.xrd, ErrorFmt(OsError::from(e)) ); return; } let rd = match std::fs::read_dir(&state.xrd) { Ok(rd) => rd, Err(e) => { log::error!("Cannot enumerate `{}`: {}", state.xrd, ErrorFmt(e)); return; } }; for entry in rd { let entry = match entry { Ok(e) => e, Err(e) => { log::error!("Cannot enumerate `{}`: {}", state.xrd, ErrorFmt(e)); return; } }; if let Ok(s) = std::str::from_utf8(entry.file_name().as_bytes()) { maybe_add_display(&state, s).await; } } let mut buf = vec![0u8; 4096]; loop { if let Err(e) = state.ring.readable(&inotify).await { log::error!("Cannot wait for `{}` to change: {}", state.xrd, ErrorFmt(e)); } let events = match uapi::inotify_read(inotify.raw(), &mut buf[..]) { Ok(s) => s, Err(e) => { log::error!("Could not read from inotify fd: {}", ErrorFmt(e)); return; } }; for event in events { if event.mask.contains(c::IN_CREATE) { if let Ok(s) = std::str::from_utf8(event.name().as_ustr().as_bytes()) { maybe_add_display(&state, s).await; } } } } }