1
0
Fork 0
forked from wry/wry

portal: add a desktop portal

This commit is contained in:
Julian Orth 2022-07-30 19:21:30 +02:00
parent 323a6ed953
commit a162055f1d
38 changed files with 2389 additions and 27 deletions

4
etc/jay.portal Normal file
View file

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

View file

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

View file

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

View file

@ -9,7 +9,7 @@ mod set_log_level;
mod unlock;
use {
crate::compositor::start_compositor,
crate::{compositor::start_compositor, portal},
::log::Level,
clap::{ArgEnum, Args, Parser, Subcommand},
clap_complete::Shell,
@ -53,6 +53,8 @@ pub enum Cmd {
RunPrivileged(RunPrivilegedArgs),
/// Tests the events produced by a seat.
SeatTest(SeatTestArgs),
/// Run the desktop portal.
Portal,
#[cfg(feature = "it")]
RunTests,
}
@ -214,6 +216,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),
#[cfg(feature = "it")]
Cmd::RunTests => crate::it::run_tests(),
}

View file

@ -276,10 +276,8 @@ const ALLOW_INTERACTIVE_AUTHORIZATION: u8 = 0x4;
pub const DBUS_NAME_FLAG_ALLOW_REPLACEMENT: u32 = 0x1;
#[allow(dead_code)]
pub const DBUS_NAME_FLAG_REPLACE_EXISTING: u32 = 0x2;
#[allow(dead_code)]
pub const DBUS_NAME_FLAG_DO_NOT_QUEUE: u32 = 0x4;
#[allow(dead_code)]
pub const DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER: u32 = 1;
#[allow(dead_code)]
pub const DBUS_REQUEST_NAME_REPLY_IN_QUEUE: u32 = 2;
@ -604,7 +602,6 @@ impl Drop for DbusObject {
}
impl DbusObject {
#[allow(dead_code)]
pub fn add_method<T, F>(&self, handler: F)
where
T: MethodCall<'static>,
@ -623,7 +620,6 @@ impl DbusObject {
self.data.methods.set(key, rhd);
}
#[allow(dead_code)]
pub fn set_property<T>(&self, value: Variant<'static>)
where
T: Property + 'static,
@ -649,12 +645,10 @@ impl DbusObject {
self.data.properties.set(key, phd);
}
#[allow(dead_code)]
pub fn emit_signal<'a, T: Signal<'a>>(&self, signal: &T) {
self.socket.emit_signal(&self.data.path, signal);
}
#[allow(dead_code)]
pub fn path(&self) -> &str {
&self.data.path
}

View file

@ -43,7 +43,6 @@ impl DbusSocket {
}
}
#[allow(dead_code)]
pub fn call_noreply<'a, T: MethodCall<'a>>(&self, destination: &str, path: &str, msg: T) {
if !self.dead.get() {
self.send_call(path, destination, NO_REPLY_EXPECTED, &msg);
@ -135,7 +134,6 @@ impl DbusSocket {
}
}
#[allow(dead_code)]
pub fn add_object(
self: &Rc<Self>,
object: impl Into<Cow<'static, str>>,
@ -158,7 +156,6 @@ impl DbusSocket {
}
}
#[allow(dead_code)]
pub fn handle_signal<T, F>(
self: &Rc<Self>,
sender: Option<&str>,
@ -271,7 +268,6 @@ impl DbusSocket {
);
}
#[allow(dead_code)]
pub fn emit_signal<'a, T: Signal<'a>>(&self, path: &str, msg: &T) -> u32 {
let (msg, serial) = self.format_signal(path, msg);
self.bufio.send(msg);

View file

@ -76,7 +76,7 @@ use {
uapi::{c, Errno, OwnedFd},
};
const POINTER: u32 = 1;
pub const POINTER: u32 = 1;
const KEYBOARD: u32 = 2;
#[allow(dead_code)]
const TOUCH: u32 = 4;

View file

@ -17,7 +17,7 @@ use {
const ROLE: u32 = 0;
pub(super) const RELEASED: u32 = 0;
pub(super) const PRESSED: u32 = 1;
pub const PRESSED: u32 = 1;
pub const VERTICAL_SCROLL: u32 = 0;
pub const HORIZONTAL_SCROLL: u32 = 1;

View file

@ -168,7 +168,7 @@ impl TestBackend {
return Err(TestBackendError::NoDrmNode);
};
let file = match uapi::open(node.as_path(), c::O_RDWR | c::O_CLOEXEC, 0) {
Ok(f) => f,
Ok(f) => Rc::new(f),
Err(e) => {
return Err(TestBackendError::OpenDrmNode(
node.as_os_str().as_bytes().as_bstr().to_string(),

View file

@ -174,6 +174,40 @@ macro_rules! id {
};
}
macro_rules! shared_ids {
($id:ident) => {
shared_ids!($id, u32);
};
($id:ident, $ty:ty) => {
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct $id($ty);
impl $id {
#[allow(dead_code)]
pub fn raw(&self) -> $ty {
self.0
}
#[allow(dead_code)]
pub fn from_raw(id: $ty) -> Self {
Self(id)
}
}
impl From<$ty> for $id {
fn from(id: $ty) -> Self {
Self(id)
}
}
impl std::fmt::Display for $id {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
}
}
};
}
macro_rules! linear_ids {
($ids:ident, $id:ident) => {
linear_ids!($ids, $id, u32);

View file

@ -60,6 +60,7 @@ mod logind;
mod object;
mod pango;
mod pipewire;
mod portal;
mod rect;
mod render;
mod screenshoter;

View file

@ -1,5 +1,3 @@
#![allow(dead_code)]
pub mod pw_con;
pub mod pw_formatter;
pub mod pw_ifs;

View file

@ -41,6 +41,7 @@ pub struct PwMemTyped<T> {
_phantom: PhantomData<T>,
}
#[allow(dead_code)]
pub struct PwMemSlice {
mem: Rc<PwMemMap>,
range: Range<usize>,
@ -134,6 +135,7 @@ impl PwMemMap {
}
impl<T: Pod> PwMemTyped<T> {
#[allow(dead_code)]
pub unsafe fn read(&self) -> &T {
(self.mem.map.ptr.cast::<u8>().add(self.offset) as *const T).deref()
}

161
src/portal.rs Normal file
View file

@ -0,0 +1,161 @@
mod ptl_display;
mod ptl_render_ctx;
mod ptl_screencast;
mod ptr_gui;
use {
crate::{
async_engine::{AsyncEngine, SpawnedFuture},
cli::GlobalArgs,
dbus::{
Dbus, DbusSocket, BUS_DEST, BUS_PATH, DBUS_NAME_FLAG_DO_NOT_QUEUE,
DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER,
},
io_uring::IoUring,
logger,
pipewire::pw_con::{PwCon, PwConHolder, PwConOwner},
portal::{
ptl_display::{watch_displays, PortalDisplay, PortalDisplayId},
ptl_render_ctx::PortalRenderCtx,
ptl_screencast::{add_screencast_dbus_members, ScreencastSession},
},
utils::{
copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell,
run_toplevel::RunToplevel, xrd::xrd,
},
wheel::Wheel,
wire_dbus::org,
},
std::{
cell::Cell,
rc::{Rc, Weak},
},
uapi::c,
};
const PORTAL_SUCCESS: u32 = 0;
#[allow(dead_code)]
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());
let xrd = match xrd() {
Some(xrd) => xrd,
_ => {
fatal!("XDG_RUNTIME_DIR is not set");
}
};
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 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) {
Ok(p) => p,
Err(e) => {
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);
let state = Rc::new(PortalState {
xrd,
ring,
eng,
wheel,
pw_con: pw_con.con.clone(),
displays: Default::default(),
watch_displays: Cell::new(None),
dbus,
screencasts: Default::default(),
next_id: NumCell::new(1),
render_ctxs: Default::default(),
});
let _root = {
let obj = state
.dbus
.add_object("/org/freedesktop/portal/desktop")
.unwrap();
add_screencast_dbus_members(&state, &obj);
obj
};
state
.watch_displays
.set(Some(state.eng.spawn(watch_displays(state.clone()))));
state.pw_con.owner.set(Some(state.clone()));
if let Err(e) = state.ring.run() {
fatal!("The IO-uring returned an error: {}", ErrorFmt(e));
}
}
const UNIQUE_NAME: &str = "org.freedesktop.impl.portal.desktop.jay";
fn init_dbus_session(dbus: &Dbus) -> Rc<DbusSocket> {
let session = match dbus.session() {
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;
}
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
}
struct PortalState {
xrd: String,
ring: Rc<IoUring>,
eng: Rc<AsyncEngine>,
wheel: Rc<Wheel>,
pw_con: Rc<PwCon>,
displays: CopyHashMap<PortalDisplayId, Rc<PortalDisplay>>,
watch_displays: Cell<Option<SpawnedFuture<()>>>,
dbus: Rc<DbusSocket>,
screencasts: CopyHashMap<String, Rc<ScreencastSession>>,
next_id: NumCell<u32>,
render_ctxs: CopyHashMap<c::dev_t, Weak<PortalRenderCtx>>,
}
impl PortalState {
pub 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");
}
}

486
src/portal/ptl_display.rs Normal file
View file

@ -0,0 +1,486 @@
use {
crate::{
ifs::wl_seat::POINTER,
portal::{ptl_render_ctx::PortalRenderCtx, ptr_gui::WindowData, PortalState},
render::RenderContext,
utils::{
bitflags::BitflagsExt, clonecell::CloneCell, copyhashmap::CopyHashMap,
errorfmt::ErrorFmt, 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_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,
std::{
cell::{Cell, RefCell},
ops::Deref,
os::unix::ffi::OsStrExt,
rc::Rc,
str::FromStr,
},
uapi::{c, AsUstr, OwnedFd},
};
use crate::portal::ptl_screencast::ScreencastSession;
struct PortalDisplayPrelude {
con: Rc<UsrCon>,
state: Rc<PortalState>,
registry: Rc<UsrWlRegistry>,
globals: RefCell<AHashMap<String, Vec<(u32, u32)>>>,
}
shared_ids!(PortalDisplayId);
pub struct PortalDisplay {
pub id: PortalDisplayId,
pub con: Rc<UsrCon>,
pub(super) state: Rc<PortalState>,
registry: Rc<UsrWlRegistry>,
pub dmabuf: CloneCell<Option<Rc<UsrLinuxDmabuf>>>,
pub jc: Rc<UsrJayCompositor>,
pub ls: Rc<UsrWlrLayerShell>,
pub comp: Rc<UsrWlCompositor>,
pub fsm: Rc<UsrWpFractionalScaleManager>,
pub vp: Rc<UsrWpViewporter>,
pub render_ctx: CloneCell<Option<Rc<PortalRenderCtx>>>,
pub outputs: CopyHashMap<u32, Rc<PortalOutput>>,
pub seats: CopyHashMap<u32, Rc<PortalSeat>>,
pub windows: CopyHashMap<WlSurfaceId, Rc<WindowData>>,
pub screencasts: CopyHashMap<String, Rc<ScreencastSession>>,
}
pub struct PortalOutput {
pub global_id: u32,
pub dpy: Rc<PortalDisplay>,
pub wl: Rc<UsrWlOutput>,
pub jay: Rc<UsrJayOutput>,
}
pub struct PortalSeat {
pub global_id: u32,
pub dpy: Rc<PortalDisplay>,
pub wl: Rc<UsrWlSeat>,
pub jay_pointer: Rc<UsrJayPointer>,
pub pointer: CloneCell<Option<Rc<UsrWlPointer>>>,
pub name: RefCell<String>,
pub capabilities: Cell<u32>,
pub pointer_focus: CloneCell<Option<Rc<WindowData>>>,
}
impl UsrWlSeatOwner for PortalSeat {
fn name(&self, name: &str) {
*self.name.borrow_mut() = name.to_string();
}
fn capabilities(self: Rc<Self>, 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<Self>, 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<OwnedFd>) {
self.render_ctx.take();
let dev_id = match uapi::fstat(fd.raw()) {
Ok(s) => s.st_dev,
Err(e) => {
log::error!("Could not fstat display device: {}", ErrorFmt(e));
return;
}
};
if let Some(ctx) = self.state.render_ctxs.get(&dev_id) {
if let Some(ctx) = ctx.upgrade() {
self.render_ctx.set(Some(ctx));
}
}
if self.render_ctx.get().is_none() {
let drm = Drm::open_existing(fd);
let ctx = match RenderContext::from_drm_device(&drm) {
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,
ctx: Rc::new(ctx),
});
self.render_ctx.set(Some(ctx.clone()));
self.state.render_ctxs.set(dev_id, Rc::downgrade(&ctx));
}
}
}
impl UsrConOwner for PortalDisplay {
fn killed(&self) {
log::info!("Removing display {}", self.id);
for (_, sc) in self.screencasts.lock().drain() {
sc.kill();
}
self.windows.clear();
self.state.displays.remove(&self.id);
}
}
impl UsrWlRegistryOwner for PortalDisplay {
fn global(self: Rc<Self>, 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(),
});
self.con.add_object(ls.clone());
self.registry.request_bind(name, version, ls.deref());
self.dmabuf.set(Some(ls));
}
}
}
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 {}
fn maybe_add_display(state: &Rc<PortalState>, 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, &path, num) {
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<PortalDisplayPrelude>) {
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(),
});
dpy.con.add_object(jc.clone());
dpy.registry.request_bind(name, version, jc.deref());
jc_opt = Some(jc);
} else if interface == WpFractionalScaleManagerV1.name() {
let ls = Rc::new(UsrWpFractionalScaleManager {
id: dpy.con.id(),
con: dpy.con.clone(),
});
dpy.con.add_object(ls.clone());
dpy.registry.request_bind(name, version, ls.deref());
fsm_opt = Some(ls);
} else if interface == ZwlrLayerShellV1.name() {
let ls = Rc::new(UsrWlrLayerShell {
id: dpy.con.id(),
con: dpy.con.clone(),
});
dpy.con.add_object(ls.clone());
dpy.registry.request_bind(name, version, ls.deref());
ls_opt = Some(ls);
} else if interface == WpViewporter.name() {
let ls = Rc::new(UsrWpViewporter {
id: dpy.con.id(),
con: dpy.con.clone(),
});
dpy.con.add_object(ls.clone());
dpy.registry.request_bind(name, version, ls.deref());
vp_opt = Some(ls);
} else if interface == WlCompositor.name() {
let ls = Rc::new(UsrWlCompositor {
id: dpy.con.id(),
con: dpy.con.clone(),
});
dpy.con.add_object(ls.clone());
dpy.registry.request_bind(name, version, 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(),
});
dpy.con.add_object(ls.clone());
dpy.registry.request_bind(name, version, 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 dpy = Rc::new(PortalDisplay {
id: dpy.state.id(),
con: dpy.con.clone(),
state: dpy.state.clone(),
registry: dpy.registry.clone(),
dmabuf: CloneCell::new(dmabuf_opt),
jc,
outputs: Default::default(),
render_ctx: Default::default(),
seats: Default::default(),
ls,
comp,
fsm,
vp,
windows: Default::default(),
screencasts: Default::default(),
});
dpy.state.displays.set(dpy.id, dpy.clone());
dpy.con.owner.set(Some(dpy.clone()));
dpy.registry.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<PortalDisplay>, name: u32, version: u32) {
let wl = Rc::new(UsrWlSeat {
id: dpy.con.id(),
con: dpy.con.clone(),
owner: Default::default(),
});
dpy.con.add_object(wl.clone());
dpy.registry.request_bind(name, version, 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<PortalDisplay>, name: u32, version: u32) {
let wl = Rc::new(UsrWlOutput {
id: dpy.con.id(),
con: dpy.con.clone(),
owner: Default::default(),
});
dpy.con.add_object(wl.clone());
dpy.registry.request_bind(name, version, 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<PortalState>) {
let inotify = Rc::new(uapi::inotify_init1(c::IN_CLOEXEC | c::IN_NONBLOCK).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);
}
}
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);
}
}
}
}
}

View file

@ -0,0 +1,6 @@
use {crate::render::RenderContext, std::rc::Rc, uapi::c};
pub struct PortalRenderCtx {
pub dev_id: c::dev_t,
pub ctx: Rc<RenderContext>,
}

View file

@ -0,0 +1,450 @@
mod screencast_gui;
use {
crate::{
dbus::{prelude::Variant, DbusObject, DictEntry, DynamicType, PendingReply},
pipewire::{
pw_ifs::pw_client_node::{
PwClientNode, PwClientNodeBufferConfig, PwClientNodeOwner, PwClientNodePort,
PwClientNodePortSupportedFormats, SUPPORTED_META_VIDEO_CROP,
},
pw_pod::{
spa_point, spa_rectangle, spa_region, PwPodRectangle, SPA_DATA_DmaBuf,
SPA_MEDIA_SUBTYPE_raw, SPA_MEDIA_TYPE_video, SpaChunkFlags, SPA_STATUS_HAVE_DATA,
},
},
portal::{
ptl_display::{PortalDisplay, PortalDisplayId, PortalOutput},
ptl_screencast::screencast_gui::SelectionGui,
PortalState, PORTAL_SUCCESS,
},
utils::{
clonecell::{CloneCell, UnsafeCellCloneSafe},
copyhashmap::CopyHashMap,
},
video::dmabuf::DmaBuf,
wire::jay_screencast::Ready,
wire_dbus::{
org,
org::freedesktop::impl_::portal::{
screen_cast::{
CreateSession, CreateSessionReply, SelectSources, SelectSourcesReply, Start,
StartReply,
},
session::{CloseReply as SessionCloseReply, Closed},
},
},
wl_usr::usr_ifs::usr_jay_screencast::{UsrJayScreencast, UsrJayScreencastOwner},
},
std::{
borrow::Cow,
cell::{Cell, RefCell},
ops::Deref,
rc::Rc,
},
};
shared_ids!(ScreencastSessionId);
pub struct ScreencastSession {
_id: ScreencastSessionId,
state: Rc<PortalState>,
pub app: String,
session_obj: DbusObject,
pub phase: CloneCell<ScreencastPhase>,
}
#[derive(Clone)]
pub enum ScreencastPhase {
Init,
SourcesSelected,
Selecting(Rc<SelectingScreencast>),
Starting(Rc<StartingScreencast>),
Started(Rc<StartedScreencast>),
Terminated,
}
unsafe impl UnsafeCellCloneSafe for ScreencastPhase {}
pub struct SelectingScreencast {
pub session: Rc<ScreencastSession>,
pub request_obj: Rc<DbusObject>,
pub reply: Rc<PendingReply<StartReply<'static>>>,
pub guis: CopyHashMap<PortalDisplayId, Rc<SelectionGui>>,
pub output_selected: Cell<bool>,
}
pub struct StartingScreencast {
pub session: Rc<ScreencastSession>,
pub request_obj: Rc<DbusObject>,
pub reply: Rc<PendingReply<StartReply<'static>>>,
pub node: Rc<PwClientNode>,
pub dpy: Rc<PortalDisplay>,
pub output: Rc<PortalOutput>,
}
pub struct StartedScreencast {
session: Rc<ScreencastSession>,
node: Rc<PwClientNode>,
port: Rc<PwClientNodePort>,
buffers: RefCell<Vec<DmaBuf>>,
dpy: Rc<PortalDisplay>,
jay_screencast: Rc<UsrJayScreencast>,
}
bitflags! {
CursorModes: u32;
HIDDEN = 1,
EMBEDDED = 2,
METADATA = 4,
}
bitflags! {
SourceTypes: u32;
MONITOR = 1,
WINDOW = 2,
}
impl PwClientNodeOwner for StartingScreencast {
fn bound_id(&self, node_id: u32) {
{
let inner_type = DynamicType::DictEntry(
Box::new(DynamicType::String),
Box::new(DynamicType::Variant),
);
let kt = DynamicType::Struct(vec![
DynamicType::U32,
DynamicType::Array(Box::new(inner_type.clone())),
]);
let variants = &[DictEntry {
key: "streams".into(),
value: Variant::Array(
kt,
vec![Variant::U32(node_id), Variant::Array(inner_type, vec![])],
),
}];
self.reply.ok(&StartReply {
response: PORTAL_SUCCESS,
results: Cow::Borrowed(variants),
});
}
let port = self.node.create_port(true);
port.can_alloc_buffers.set(true);
port.supported_metas.set(SUPPORTED_META_VIDEO_CROP);
let jsc = self.dpy.jc.create_screencast();
jsc.set_output(&self.output.jay);
jsc.set_use_linear_buffers(true);
jsc.set_allow_all_workspaces(true);
jsc.configure();
let started = Rc::new(StartedScreencast {
session: self.session.clone(),
node: self.node.clone(),
port,
buffers: Default::default(),
dpy: self.dpy.clone(),
jay_screencast: jsc,
});
self.session
.phase
.set(ScreencastPhase::Started(started.clone()));
started.jay_screencast.owner.set(Some(started.clone()));
self.node.owner.set(Some(started.clone()));
}
}
impl PwClientNodeOwner for StartedScreencast {
fn port_format_changed(&self, port: &Rc<PwClientNodePort>) {
self.node.send_port_update(port);
}
fn use_buffers(&self, port: &Rc<PwClientNodePort>) {
self.node
.send_port_output_buffers(port, &self.buffers.borrow_mut());
}
fn start(self: Rc<Self>) {
self.jay_screencast.set_running(true);
self.jay_screencast.configure();
}
fn pause(self: Rc<Self>) {
self.jay_screencast.set_running(false);
self.jay_screencast.configure();
}
fn suspend(self: Rc<Self>) {
self.jay_screencast.set_running(false);
self.jay_screencast.configure();
}
}
impl ScreencastSession {
pub(super) fn kill(&self) {
self.session_obj.emit_signal(&Closed);
self.state.screencasts.remove(self.session_obj.path());
match self.phase.set(ScreencastPhase::Terminated) {
ScreencastPhase::Init => {}
ScreencastPhase::SourcesSelected => {}
ScreencastPhase::Terminated => {}
ScreencastPhase::Selecting(s) => {
s.reply.err("Session has been terminated");
for (_, gui) in s.guis.lock().drain() {
gui.kill(false);
}
}
ScreencastPhase::Starting(s) => {
s.reply.err("Session has been terminated");
s.node.con.destroy_obj(s.node.deref());
s.dpy.screencasts.remove(self.session_obj.path());
}
ScreencastPhase::Started(s) => {
s.jay_screencast.con.remove_obj(s.jay_screencast.deref());
s.node.con.destroy_obj(s.node.deref());
s.dpy.screencasts.remove(self.session_obj.path());
}
}
}
fn dbus_select_sources(
self: &Rc<Self>,
_req: SelectSources,
reply: PendingReply<SelectSourcesReply<'static>>,
) {
match self.phase.get() {
ScreencastPhase::Init => {}
_ => {
self.kill();
reply.err("Sources have already been selected");
return;
}
}
self.phase.set(ScreencastPhase::SourcesSelected);
reply.ok(&SelectSourcesReply {
response: PORTAL_SUCCESS,
results: Default::default(),
});
}
fn dbus_start(self: &Rc<Self>, req: Start<'_>, reply: PendingReply<StartReply<'static>>) {
match self.phase.get() {
ScreencastPhase::SourcesSelected => {}
_ => {
self.kill();
reply.err("Session is not in the correct phase for starting");
return;
}
}
let request_obj = match self.state.dbus.add_object(req.handle.to_string()) {
Ok(r) => r,
Err(_) => {
self.kill();
reply.err("Request handle is not unique");
return;
}
};
{
use org::freedesktop::impl_::portal::request::*;
request_obj.add_method::<Close, _>({
let slf = self.clone();
move |_, pr| {
slf.kill();
pr.ok(&CloseReply);
}
});
}
let guis = CopyHashMap::new();
for dpy in self.state.displays.lock().values() {
if dpy.outputs.len() > 0 {
guis.set(dpy.id, SelectionGui::new(self, dpy));
}
}
if guis.is_empty() {
self.kill();
reply.err("There are no running displays");
return;
}
self.phase
.set(ScreencastPhase::Selecting(Rc::new(SelectingScreencast {
session: self.clone(),
request_obj: Rc::new(request_obj),
reply: Rc::new(reply),
guis,
output_selected: Cell::new(false),
})));
}
}
impl UsrJayScreencastOwner for StartedScreencast {
fn buffers(&self, buffers: Vec<DmaBuf>) {
if buffers.len() == 0 {
return;
}
let buffer = &buffers[0];
*self.port.supported_formats.borrow_mut() = Some(PwClientNodePortSupportedFormats {
media_type: Some(SPA_MEDIA_TYPE_video),
media_sub_type: Some(SPA_MEDIA_SUBTYPE_raw),
video_size: Some(PwPodRectangle {
width: buffer.width as _,
height: buffer.height as _,
}),
formats: vec![buffer.format],
modifiers: vec![buffer.modifier],
});
let bc = PwClientNodeBufferConfig {
num_buffers: buffers.len(),
planes: buffer.planes.len(),
stride: Some(buffer.planes[0].stride),
size: Some(buffer.planes[0].stride * buffer.height as u32),
align: 16,
data_type: SPA_DATA_DmaBuf,
};
self.port.buffer_config.set(Some(bc));
self.node.send_port_update(&self.port);
self.node.send_active(true);
*self.buffers.borrow_mut() = buffers;
}
fn ready(&self, ev: &Ready) {
let idx = ev.idx as usize;
unsafe {
let mut used = false;
if let Some(io) = self.port.io_buffers.lock().values().next() {
let io = io.write();
if io.status != SPA_STATUS_HAVE_DATA {
used = true;
if io.buffer_id != ev.idx {
if (io.buffer_id as usize) < self.buffers.borrow_mut().len() {
self.jay_screencast.release_buffer(io.buffer_id as usize);
}
}
io.buffer_id = ev.idx;
io.status = SPA_STATUS_HAVE_DATA;
}
}
if !used {
self.jay_screencast.release_buffer(idx);
}
{
let pbuffers = self.port.buffers.borrow_mut();
let buffers = self.buffers.borrow_mut();
if let Some(pbuffer) = pbuffers.get(idx) {
let buffer = &buffers[idx];
for (chunk, plane) in pbuffer.chunks.iter().zip(buffer.planes.iter()) {
let chunk = chunk.write();
chunk.flags = SpaChunkFlags::none();
chunk.offset = plane.offset;
chunk.stride = plane.stride;
chunk.size = plane.stride * buffer.height as u32;
}
if let Some(crop) = &pbuffer.meta_video_crop {
crop.write().region = spa_region {
position: spa_point { x: 0, y: 0 },
size: spa_rectangle {
width: buffer.width as _,
height: buffer.height as _,
},
};
}
}
}
}
if let Some(wfd) = self.port.node.transport_out.get() {
let _ = uapi::eventfd_write(wfd.raw(), 1);
}
}
fn destroyed(&self) {
self.session.kill();
}
}
pub(super) fn add_screencast_dbus_members(state_: &Rc<PortalState>, object: &DbusObject) {
use org::freedesktop::impl_::portal::screen_cast::*;
let state = state_.clone();
object.add_method::<CreateSession, _>(move |req, pr| {
dbus_create_session(&state, req, pr);
});
let state = state_.clone();
object.add_method::<SelectSources, _>(move |req, pr| {
dbus_select_sources(&state, req, pr);
});
let state = state_.clone();
object.add_method::<Start, _>(move |req, pr| {
dbus_start(&state, req, pr);
});
object.set_property::<AvailableSourceTypes>(Variant::U32(MONITOR.0));
object.set_property::<AvailableCursorModes>(Variant::U32(EMBEDDED.0));
object.set_property::<version>(Variant::U32(4));
}
fn dbus_create_session(
state: &Rc<PortalState>,
req: CreateSession,
reply: PendingReply<CreateSessionReply<'static>>,
) {
log::info!("Create Session {:#?}", req);
if state.screencasts.contains(req.session_handle.0.deref()) {
reply.err("Session already exists");
return;
}
let obj = match state.dbus.add_object(req.session_handle.0.to_string()) {
Ok(obj) => obj,
Err(_) => {
reply.err("Session path is not unique");
return;
}
};
let session = Rc::new(ScreencastSession {
_id: state.id(),
state: state.clone(),
app: req.app_id.to_string(),
session_obj: obj,
phase: CloneCell::new(ScreencastPhase::Init),
});
{
use org::freedesktop::impl_::portal::session::*;
let ses = session.clone();
session.session_obj.add_method::<Close, _>(move |_, pr| {
ses.kill();
pr.ok(&SessionCloseReply);
});
session.session_obj.set_property::<version>(Variant::U32(4));
}
state
.screencasts
.set(req.session_handle.0.to_string(), session);
reply.ok(&CreateSessionReply {
response: PORTAL_SUCCESS,
results: Default::default(),
});
}
fn dbus_select_sources(
state: &Rc<PortalState>,
req: SelectSources,
reply: PendingReply<SelectSourcesReply<'static>>,
) {
if let Some(s) = get_session(state, &reply, &req.session_handle.0) {
s.dbus_select_sources(req, reply);
}
}
fn dbus_start(state: &Rc<PortalState>, req: Start, reply: PendingReply<StartReply<'static>>) {
if let Some(s) = get_session(state, &reply, &req.session_handle.0) {
s.dbus_start(req, reply);
}
}
fn get_session<T>(
state: &Rc<PortalState>,
reply: &PendingReply<T>,
handle: &str,
) -> Option<Rc<ScreencastSession>> {
let res = state.screencasts.get(handle);
if res.is_none() {
let msg = format!("Screencast session `{}` does not exist", handle);
reply.err(&msg);
}
res
}

View file

@ -0,0 +1,182 @@
use {
crate::{
ifs::wl_seat::{wl_pointer::PRESSED, BTN_LEFT},
portal::{
ptl_display::{PortalDisplay, PortalOutput},
ptl_screencast::{ScreencastPhase, ScreencastSession, StartingScreencast},
ptr_gui::{
Align, Button, ButtonOwner, Flow, GuiElement, Label, Orientation, OverlayWindow,
OverlayWindowOwner,
},
},
theme::Color,
utils::copyhashmap::CopyHashMap,
},
std::rc::Rc,
};
const H_MARGIN: f32 = 30.0;
const V_MARGIN: f32 = 20.0;
pub struct SelectionGui {
screencast_session: Rc<ScreencastSession>,
dpy: Rc<PortalDisplay>,
surfaces: CopyHashMap<u32, Rc<SelectionGuiSurface>>,
}
pub struct SelectionGuiSurface {
gui: Rc<SelectionGui>,
output: Rc<PortalOutput>,
overlay: Rc<OverlayWindow>,
}
struct StaticButton {
surface: Rc<SelectionGuiSurface>,
role: ButtonRole,
}
#[derive(Copy, Clone, Eq, PartialEq)]
enum ButtonRole {
Accept,
Reject,
}
impl SelectionGui {
pub fn kill(&self, upwards: bool) {
for (_, surface) in self.surfaces.lock().drain() {
surface.overlay.data.kill(false);
}
if let ScreencastPhase::Selecting(s) = self.screencast_session.phase.get() {
s.guis.remove(&self.dpy.id);
if upwards && s.guis.is_empty() {
self.screencast_session.kill();
}
}
}
}
fn create_accept_gui(surface: &Rc<SelectionGuiSurface>) -> Rc<dyn GuiElement> {
let app = &surface.gui.screencast_session.app;
let text = if app.is_empty() {
format!("An application wants to capture the screen")
} else {
format!("`{}` wants to capture the screen", app)
};
let label = Rc::new(Label::default());
*label.text.borrow_mut() = text;
let accept_button = static_button(surface, ButtonRole::Accept, "Share This Output");
let reject_button = static_button(surface, ButtonRole::Reject, "Reject");
let buttons = [&accept_button, &reject_button];
for button in buttons {
button.border_color.set(Color::from_gray(100));
button.border.set(2.0);
button.padding.set(5.0);
}
accept_button.bg_color.set(Color::from_rgb(170, 200, 170));
accept_button
.bg_hover_color
.set(Color::from_rgb(170, 255, 170));
reject_button.bg_color.set(Color::from_rgb(200, 170, 170));
reject_button
.bg_hover_color
.set(Color::from_rgb(255, 170, 170));
let flow = Rc::new(Flow::default());
flow.orientation.set(Orientation::Vertical);
flow.cross_align.set(Align::Center);
flow.in_margin.set(V_MARGIN);
flow.cross_margin.set(H_MARGIN);
*flow.elements.borrow_mut() = vec![label, accept_button, reject_button];
flow
}
impl OverlayWindowOwner for SelectionGuiSurface {
fn kill(&self, upwards: bool) {
self.gui.dpy.windows.remove(&self.overlay.data.surface.id);
self.gui.surfaces.remove(&self.output.global_id);
if upwards && self.gui.surfaces.is_empty() {
self.gui.kill(true);
}
}
}
impl SelectionGui {
pub fn new(ss: &Rc<ScreencastSession>, dpy: &Rc<PortalDisplay>) -> Rc<Self> {
let gui = Rc::new(SelectionGui {
screencast_session: ss.clone(),
dpy: dpy.clone(),
surfaces: Default::default(),
});
for output in dpy.outputs.lock().values() {
let sgs = Rc::new(SelectionGuiSurface {
gui: gui.clone(),
output: output.clone(),
overlay: OverlayWindow::new(output),
});
let element = create_accept_gui(&sgs);
sgs.overlay.data.content.set(Some(element));
gui.dpy
.windows
.set(sgs.overlay.data.surface.id, sgs.overlay.data.clone());
gui.surfaces.set(output.global_id, sgs);
}
gui
}
}
impl ButtonOwner for StaticButton {
fn button(&self, button: u32, state: u32) {
if button != BTN_LEFT || state != PRESSED {
return;
}
match self.role {
ButtonRole::Accept => {
log::info!("User has accepted the request");
let selecting = match self.surface.gui.screencast_session.phase.get() {
ScreencastPhase::Selecting(selecting) => selecting,
_ => return,
};
for (_, gui) in selecting.guis.lock().drain() {
gui.kill(false);
}
let node = self.surface.gui.dpy.state.pw_con.create_client_node(&[
("media.class".to_string(), "Video/Source".to_string()),
("node.name".to_string(), "jay-desktop-portal".to_string()),
("node.driver".to_string(), "true".to_string()),
]);
let starting = Rc::new(StartingScreencast {
session: self.surface.gui.screencast_session.clone(),
request_obj: selecting.request_obj.clone(),
reply: selecting.reply.clone(),
node,
dpy: self.surface.gui.dpy.clone(),
output: self.surface.output.clone(),
});
self.surface
.gui
.screencast_session
.phase
.set(ScreencastPhase::Starting(starting.clone()));
starting.node.owner.set(Some(starting.clone()));
self.surface.gui.dpy.screencasts.set(
self.surface.gui.screencast_session.session_obj.path().to_owned(),
self.surface.gui.screencast_session.clone(),
);
}
ButtonRole::Reject => {
log::info!("User has rejected the screencast request");
self.surface.gui.screencast_session.kill();
}
}
}
}
fn static_button(surface: &Rc<SelectionGuiSurface>, role: ButtonRole, text: &str) -> Rc<Button> {
let button = Rc::new(Button::default());
let slf = Rc::new(StaticButton {
surface: surface.clone(),
role,
});
button.owner.set(Some(slf));
*button.text.borrow_mut() = text.to_string();
button
}

876
src/portal/ptr_gui.rs Normal file
View file

@ -0,0 +1,876 @@
use {
crate::{
async_engine::{Phase, SpawnedFuture},
cursor::KnownCursor,
fixed::Fixed,
format::ARGB8888,
ifs::zwlr_layer_shell_v1::OVERLAY,
portal::ptl_display::{PortalDisplay, PortalOutput, PortalSeat},
render::{Framebuffer, RenderContext, RendererBase, Texture},
text::{self, TextMeasurement},
theme::Color,
utils::{
asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap,
errorfmt::ErrorFmt, rc_eq::rc_eq,
},
video::{gbm::GBM_BO_USE_RENDERING, ModifiedFormat, INVALID_MODIFIER},
wire::{
wp_fractional_scale_v1::PreferredScale, zwlr_layer_surface_v1::Configure,
ZwpLinuxBufferParamsV1Id,
},
wl_usr::usr_ifs::{
usr_linux_buffer_params::{UsrLinuxBufferParams, UsrLinuxBufferParamsOwner},
usr_wl_buffer::{UsrWlBuffer, UsrWlBufferOwner},
usr_wl_surface::UsrWlSurface,
usr_wlr_layer_surface::{UsrWlrLayerSurface, UsrWlrLayerSurfaceOwner},
usr_wp_fractional_scale::{UsrWpFractionalScale, UsrWpFractionalScaleOwner},
usr_wp_viewport::UsrWpViewport,
},
},
ahash::AHashSet,
std::{
borrow::Cow,
cell::{Cell, RefCell},
ops::Deref,
rc::Rc,
},
};
#[derive(Default)]
pub struct GuiElementData {
pub x: Cell<f32>,
pub y: Cell<f32>,
pub width: Cell<f32>,
pub height: Cell<f32>,
}
pub trait GuiElement {
fn data(&self) -> &GuiElementData;
fn layout(
&self,
ctx: &Rc<RenderContext>,
scale: f32,
max_width: f32,
max_height: f32,
) -> (f32, f32);
fn render_at(&self, r: &mut RendererBase, x: f32, y: f32);
fn child_at(&self, x: f32, y: f32) -> Option<Rc<dyn GuiElement>>;
fn hover_cursor(&self) -> KnownCursor {
KnownCursor::Default
}
fn button(&self, seat: &PortalSeat, button: u32, state: u32) {
let _ = seat;
let _ = button;
let _ = state;
}
fn hover(&self, seat: &PortalSeat, hover: bool) -> bool {
let _ = seat;
let _ = hover;
false
}
fn destroy(&self) {}
}
#[derive(Copy, Clone, Debug, Default)]
pub struct ButtonExtents {
pub width: f32,
pub height: f32,
pub tex_off_x: f32,
pub tex_off_y: f32,
}
pub fn button_extents(
msmt: &TextMeasurement,
scale: f32,
padding: f32,
border: f32,
) -> ButtonExtents {
let above_baseline_height =
(msmt.baseline - msmt.ink_rect.y1().max(msmt.logical_rect.y1())) as f32 / scale;
let height = above_baseline_height + 2.0 * (padding + border);
let width = msmt.ink_rect.width() as f32 / scale + 2.0 * (padding + border);
let tex_off_x = padding + border;
// let tex_off_y = ((msmt.ink_rect.y1() - msmt.logical_rect.y1()) as f64 / scale).round() as i32 + padding + border;
let tex_off_y = padding + border;
ButtonExtents {
width,
height,
tex_off_x,
tex_off_y,
}
}
pub struct Button {
pub data: GuiElementData,
pub tex_off_x: Cell<f32>,
pub tex_off_y: Cell<f32>,
pub hover: RefCell<AHashSet<u32>>,
pub padding: Cell<f32>,
pub border: Cell<f32>,
pub border_color: Cell<Color>,
pub bg_color: Cell<Color>,
pub bg_hover_color: Cell<Color>,
pub text: RefCell<String>,
pub font: RefCell<Cow<'static, str>>,
pub tex: CloneCell<Option<Rc<Texture>>>,
pub owner: CloneCell<Option<Rc<dyn ButtonOwner>>>,
}
pub trait ButtonOwner {
fn button(&self, button: u32, state: u32);
}
impl Default for Button {
fn default() -> Self {
Self {
data: Default::default(),
tex_off_x: Cell::new(0.0),
tex_off_y: Cell::new(0.0),
hover: Default::default(),
padding: Default::default(),
border: Default::default(),
border_color: Cell::new(Color::from_gray(0)),
bg_color: Cell::new(Color::from_gray(255)),
bg_hover_color: Cell::new(Color::from_gray(255)),
text: Default::default(),
font: RefCell::new(DEFAULT_FONT.into()),
tex: Default::default(),
owner: Default::default(),
}
}
}
impl GuiElement for Button {
fn hover_cursor(&self) -> KnownCursor {
KnownCursor::Pointer
}
fn data(&self) -> &GuiElementData {
&self.data
}
fn layout(
&self,
ctx: &Rc<RenderContext>,
scale: f32,
_max_width: f32,
_max_height: f32,
) -> (f32, f32) {
let font = self.font.borrow_mut();
let text = self.text.borrow_mut();
let tex = text::render_fitting2(
ctx,
None,
&font,
&text,
Color::from_gray(0),
false,
Some(scale as _),
true,
)
.ok();
let (tex, msmt) = match tex {
Some((a, b)) => (Some(a), Some(b)),
_ => (None, None),
};
let extents = match msmt {
Some(m) => button_extents(&m, scale, self.padding.get(), self.border.get()),
_ => Default::default(),
};
self.tex.set(tex);
self.tex_off_x.set(extents.tex_off_x);
self.tex_off_y.set(extents.tex_off_y);
(extents.width, extents.height)
}
fn render_at(&self, r: &mut RendererBase, x1: f32, y1: f32) {
let x2 = x1 + self.data.width.get();
let y2 = y1 + self.data.height.get();
let border = self.border.get();
{
let rects = [
(x1, y1, x2, y1 + border),
(x1, y2 - border, x2, y2),
(x1, y1 + border, x1 + border, y2 - border),
(x2 - border, y1 + border, x2, y2 - border),
];
r.fill_boxes_f(&rects, &self.border_color.get());
}
{
let rects = [(x1 + border, y1 + border, x2 - border, y2 - border)];
let color = match self.hover.borrow_mut().is_empty() {
true => self.bg_color.get(),
false => self.bg_hover_color.get(),
};
r.fill_boxes_f(&rects, &color);
}
if let Some(tex) = self.tex.get() {
let (tx, ty) =
r.scale_point_f(x1 + self.tex_off_x.get(), y1 as f32 + self.tex_off_y.get());
r.render_texture(
&tex,
tx.round() as _,
ty.round() as _,
ARGB8888,
None,
None,
r.scale(),
);
}
}
fn child_at(&self, _x: f32, _y: f32) -> Option<Rc<dyn GuiElement>> {
None
}
fn hover(&self, seat: &PortalSeat, hover: bool) -> bool {
let ret;
let mut set = self.hover.borrow_mut();
if hover {
ret = set.is_empty();
set.insert(seat.global_id);
} else {
set.remove(&seat.global_id);
ret = set.is_empty();
}
ret
}
fn destroy(&self) {
self.owner.take();
}
fn button(&self, _seat: &PortalSeat, button: u32, state: u32) {
if let Some(owner) = self.owner.get() {
owner.button(button, state);
}
}
}
const DEFAULT_FONT: &str = "sans-serif 16";
pub struct Label {
pub data: GuiElementData,
pub font: RefCell<Cow<'static, str>>,
pub text: RefCell<String>,
pub tex: CloneCell<Option<Rc<Texture>>>,
}
impl Default for Label {
fn default() -> Self {
Self {
data: Default::default(),
font: RefCell::new(DEFAULT_FONT.into()),
text: RefCell::new("".to_string()),
tex: Default::default(),
}
}
}
impl GuiElement for Label {
fn data(&self) -> &GuiElementData {
&self.data
}
fn layout(
&self,
ctx: &Rc<RenderContext>,
scale: f32,
_max_width: f32,
_max_height: f32,
) -> (f32, f32) {
let text = self.text.borrow_mut();
let font = self.font.borrow_mut();
let tex = text::render_fitting2(
ctx,
None,
&font,
&text,
Color::from_gray(255),
false,
Some(scale as _),
false,
)
.ok();
let (tex, width, height) = match tex {
Some((t, _)) => (Some(t.clone()), t.width(), t.height()),
_ => (None, 0, 0),
};
self.tex.set(tex);
(width as f32 / scale, height as f32 / scale)
}
fn render_at(&self, r: &mut RendererBase, x: f32, y: f32) {
if let Some(tex) = self.tex.get() {
let (tx, ty) = r.scale_point_f(x, y);
r.render_texture(
&tex,
tx.round() as _,
ty.round() as _,
ARGB8888,
None,
None,
r.scale(),
);
}
}
fn child_at(&self, _x: f32, _y: f32) -> Option<Rc<dyn GuiElement>> {
None
}
}
#[derive(Copy, Clone, Eq, PartialEq, Default)]
pub enum Align {
#[allow(dead_code)]
Left,
#[default]
Center,
#[allow(dead_code)]
Right,
}
#[derive(Copy, Clone, Eq, PartialEq, Default)]
pub enum Orientation {
#[default]
Horizontal,
#[allow(dead_code)]
Vertical,
}
#[derive(Default)]
pub struct Flow {
pub data: GuiElementData,
pub in_margin: Cell<f32>,
pub cross_margin: Cell<f32>,
pub orientation: Cell<Orientation>,
pub cross_align: Cell<Align>,
pub elements: RefCell<Vec<Rc<dyn GuiElement>>>,
}
impl GuiElement for Flow {
fn data(&self) -> &GuiElementData {
&self.data
}
fn layout(
&self,
ctx: &Rc<RenderContext>,
scale: f32,
max_width: f32,
max_height: f32,
) -> (f32, f32) {
let elements = self.elements.borrow_mut();
let orientation = self.orientation.get();
let (max_in_size, _max_cross_size) = match orientation {
Orientation::Horizontal => (max_width, max_height),
Orientation::Vertical => (max_height, max_width),
};
let mut runs = vec![];
let mut run = vec![];
let cross_margin = self.cross_margin.get();
let in_margin = self.in_margin.get();
{
let mut run_cross_size: f32 = 0.0;
let mut in_pos = in_margin;
for element in elements.deref() {
let (w, h) = element.layout(ctx, scale, max_width, max_height);
let (cur_in_size, cur_cross_size) = match orientation {
Orientation::Horizontal => (w, h),
Orientation::Vertical => (h, w),
};
if in_pos + cur_in_size > max_in_size && run.len() > 0 {
runs.push((run, run_cross_size));
run = vec![];
in_pos = in_margin;
run_cross_size = 0.0;
}
run_cross_size = run_cross_size.max(cur_cross_size);
run.push((element, cur_in_size, cur_cross_size));
in_pos += cur_in_size + in_margin;
}
if run.len() > 0 {
runs.push((run, run_cross_size));
}
}
let mut max_in_pos: f32 = 0.0;
let mut cross_pos = cross_margin;
for (run, run_cross_size) in runs {
let mut in_pos = in_margin;
for (element, cur_in_size, cur_cross_size) in run {
let cur_cross_pos = cross_pos
+ match self.cross_align.get() {
Align::Left => 0.0,
Align::Center => (run_cross_size - cur_cross_size) / 2.0,
Align::Right => run_cross_size - cur_cross_size,
};
let (x, y, w, h) = match orientation {
Orientation::Horizontal => (in_pos, cur_cross_pos, cur_in_size, cur_cross_size),
Orientation::Vertical => (cur_cross_pos, in_pos, cur_cross_size, cur_in_size),
};
element.data().x.set(x);
element.data().y.set(y);
element.data().width.set(w);
element.data().height.set(h);
in_pos += in_margin + cur_in_size;
}
max_in_pos = max_in_pos.max(in_pos);
cross_pos += cross_margin + run_cross_size;
}
let (w, h) = match orientation {
Orientation::Horizontal => (max_in_pos, cross_pos),
Orientation::Vertical => (cross_pos, max_in_pos),
};
(w.min(max_width), h.min(max_height))
}
fn render_at(&self, r: &mut RendererBase, x: f32, y: f32) {
for element in self.elements.borrow_mut().deref() {
element.render_at(r, x + element.data().x.get(), y + element.data().y.get());
}
}
fn child_at(&self, x: f32, y: f32) -> Option<Rc<dyn GuiElement>> {
for child in self.elements.borrow_mut().deref() {
let data = child.data();
let x1 = data.x.get();
let y1 = data.y.get();
if x >= x1 && x - x1 < data.width.get() && y >= y1 && y - y1 < data.height.get() {
return Some(child.clone());
}
}
None
}
fn destroy(&self) {
for element in self.elements.borrow_mut().drain(..) {
element.destroy();
}
}
}
pub struct OverlayWindow {
pub layer_surface: Rc<UsrWlrLayerSurface>,
pub data: Rc<WindowData>,
pub owner: CloneCell<Option<Rc<dyn OverlayWindowOwner>>>,
}
pub trait OverlayWindowOwner {
fn kill(&self, upwards: bool);
}
pub struct WindowData {
pub frame_missed: Cell<bool>,
pub first_scale: Cell<bool>,
pub have_frame: Cell<bool>,
pub scale: Cell<Fixed>,
pub render_trigger: AsyncEvent,
pub render_task: Cell<Option<SpawnedFuture<()>>>,
pub dpy: Rc<PortalDisplay>,
pub content: CloneCell<Option<Rc<dyn GuiElement>>>,
pub surface: Rc<UsrWlSurface>,
pub viewport: Rc<UsrWpViewport>,
pub fractional_scale: Rc<UsrWpFractionalScale>,
pub bufs: RefCell<Vec<Rc<GuiBuffer>>>,
pending_bufs: CopyHashMap<ZwpLinuxBufferParamsV1Id, Rc<GuiBufferPending>>,
pub width: Cell<i32>,
pub height: Cell<i32>,
pub owner: CloneCell<Option<Rc<dyn WindowDataOwner>>>,
pub seats: CopyHashMap<u32, Rc<GuiWindowSeatState>>,
}
#[derive(Default)]
pub struct GuiWindowSeatState {
pub x: Cell<f32>,
pub y: Cell<f32>,
pub tree: RefCell<Vec<Rc<dyn GuiElement>>>,
pub cursor: Cell<Option<KnownCursor>>,
}
pub trait WindowDataOwner {
fn post_layout(&self);
fn kill(&self, upwards: bool);
}
impl WindowDataOwner for OverlayWindow {
fn post_layout(&self) {
self.layer_surface
.set_size(self.data.width.get(), self.data.height.get());
self.data.surface.commit();
}
fn kill(&self, upwards: bool) {
if let Some(owner) = self.owner.take() {
owner.kill(upwards);
}
self.layer_surface
.con
.remove_obj(self.layer_surface.deref());
}
}
const NUM_BUFFERS: usize = 2;
impl OverlayWindow {
pub fn new(output: &Rc<PortalOutput>) -> Rc<Self> {
let data = WindowData::new(&output.dpy);
let layer_surface = output
.dpy
.ls
.get_layer_surface(&data.surface, &output.wl, OVERLAY);
layer_surface.set_size(1, 1);
let slf = Rc::new(Self {
layer_surface,
data,
owner: Default::default(),
});
slf.data.owner.set(Some(slf.clone()));
slf.layer_surface.owner.set(Some(slf.clone()));
slf.data.surface.commit();
slf
}
}
impl WindowData {
pub fn schedule_render(&self) {
self.render_trigger.trigger();
}
pub fn new(dpy: &Rc<PortalDisplay>) -> Rc<Self> {
let surface = dpy.comp.create_surface();
let viewport = dpy.vp.get_viewport(&surface);
let fractional_scale = dpy.fsm.get_fractional_scale(&surface);
viewport.set_destination(1, 1);
let data = Rc::new(WindowData {
frame_missed: Cell::new(false),
first_scale: Cell::new(true),
have_frame: Cell::new(true),
bufs: Default::default(),
pending_bufs: Default::default(),
width: Cell::new(0),
height: Cell::new(0),
owner: Default::default(),
render_trigger: Default::default(),
render_task: Cell::new(None),
dpy: dpy.clone(),
content: Default::default(),
surface,
viewport,
scale: Cell::new(Fixed::from_int(1)),
fractional_scale,
seats: Default::default(),
});
data.render_task.set(Some(
dpy.state
.eng
.spawn2(Phase::Present, data.clone().render_task()),
));
data.fractional_scale.owner.set(Some(data.clone()));
data
}
pub fn layout(&self) {
let ctx = match self.dpy.render_ctx.get() {
Some(ctx) => ctx,
_ => return,
};
let scale = self.scale.get().to_f64() as f32;
let content = match self.content.get() {
Some(c) => c,
_ => return,
};
let (mut width, mut height) = content.layout(&ctx.ctx, scale, f32::INFINITY, f32::INFINITY);
content.data().width.set(width);
content.data().height.set(height);
width = width.max(1.0);
height = height.max(1.0);
self.width.set(width.round() as _);
self.height.set(height.round() as _);
self.viewport
.set_destination(width.round() as _, height.round() as _);
if let Some(owner) = self.owner.get() {
owner.post_layout();
}
}
async fn render_task(self: Rc<Self>) {
loop {
self.render_trigger.triggered().await;
self.render();
}
}
fn render(self: &Rc<Self>) {
self.frame_missed.set(true);
if !self.have_frame.get() {
return;
}
let bufs = self.bufs.borrow_mut();
let buf = 'get_buf: {
for buf in bufs.deref() {
if buf.free.get() {
break 'get_buf buf;
}
}
return;
};
self.frame_missed.set(false);
self.surface.frame({
let slf = self.clone();
move || {
slf.have_frame.set(true);
if slf.frame_missed.get() {
slf.schedule_render();
}
}
});
self.have_frame.set(false);
buf.free.set(false);
buf.fb.render_custom(self.scale.get(), |r| {
r.clear(&Color::from_gray(0));
if let Some(content) = self.content.get() {
content.render_at(r, 0.0, 0.0)
}
});
self.surface.attach(&buf.wl);
self.surface.commit();
}
pub fn kill(&self, upwards: bool) {
if let Some(owner) = self.owner.take() {
owner.kill(upwards);
}
self.render_task.take();
for (_, pb) in self.pending_bufs.lock().drain() {
pb.params.con.remove_obj(pb.params.deref());
}
for buf in self.bufs.borrow_mut().drain(..) {
buf.wl.con.remove_obj(buf.wl.deref());
}
self.fractional_scale
.con
.remove_obj(self.fractional_scale.deref());
self.viewport.con.remove_obj(self.viewport.deref());
self.surface.con.remove_obj(self.surface.deref());
if let Some(content) = self.content.take() {
content.destroy();
}
}
pub fn allocate_buffers(self: &Rc<Self>) {
{
for (_, buf) in self.pending_bufs.lock().drain() {
buf.params.con.remove_obj(buf.params.deref());
}
}
{
let mut bufs = self.bufs.borrow_mut();
for buf in bufs.drain(..) {
buf.wl.con.remove_obj(buf.wl.deref());
}
}
let ctx = match self.dpy.render_ctx.get() {
Some(ctx) => ctx,
_ => return,
};
let dmabuf = match self.dpy.dmabuf.get() {
Some(dmabuf) => dmabuf,
_ => return,
};
self.frame_missed.set(true);
let width = (self.width.get() as f64 * self.scale.get().to_f64()).round() as i32;
let height = (self.height.get() as f64 * self.scale.get().to_f64()).round() as i32;
for _ in 0..NUM_BUFFERS {
let format = ModifiedFormat {
format: ARGB8888,
modifier: INVALID_MODIFIER,
};
let bo = match ctx
.ctx
.gbm
.create_bo(width, height, &format, GBM_BO_USE_RENDERING)
{
Ok(b) => b,
Err(e) => {
log::error!("Could not allocate dmabuf: {}", ErrorFmt(e));
return;
}
};
let img = match ctx.ctx.dmabuf_img(bo.dmabuf()) {
Ok(b) => b,
Err(e) => {
log::error!("Could not import dmabuf into EGL: {}", ErrorFmt(e));
return;
}
};
let fb = match img.to_framebuffer() {
Ok(b) => b,
Err(e) => {
log::error!(
"Could not turns EGL image into framebuffer: {}",
ErrorFmt(e)
);
return;
}
};
let params = dmabuf.create_params();
params.create(bo.dmabuf());
let pending = Rc::new(GuiBufferPending {
window: self.clone(),
fb,
params,
size: (width, height),
});
pending.params.owner.set(Some(pending.clone()));
self.pending_bufs.set(pending.params.id, pending.clone());
}
}
fn tree_at(&self, tree: &mut Vec<Rc<dyn GuiElement>>, mut x: f32, mut y: f32) {
let mut element = match self.content.get() {
Some(e) => e,
_ => return,
};
tree.push(element.clone());
while let Some(c) = element.child_at(x, y) {
tree.push(c.clone());
x -= c.data().x.get();
y -= c.data().y.get();
element = c;
}
}
pub fn motion(&self, pseat: &PortalSeat, x: Fixed, y: Fixed, _enter: bool) {
let x = x.to_f64() as f32;
let y = y.to_f64() as f32;
let seat = self
.seats
.lock()
.entry(pseat.global_id)
.or_default()
.clone();
seat.x.set(x);
seat.y.set(y);
let mut tree = seat.tree.borrow_mut();
let old_element = tree.last().cloned();
self.tree_at(&mut tree, x, y);
let new_element = tree.last().cloned();
let element_changed = match (&old_element, &new_element) {
(Some(old), Some(new)) => !rc_eq(old, new),
(None, None) => false,
_ => true,
};
if element_changed {
if let Some(o) = &old_element {
o.hover(pseat, false);
}
if let Some(o) = &new_element {
o.hover(pseat, true);
}
}
if element_changed {
self.schedule_render();
}
let cursor = match &new_element {
Some(e) => e.hover_cursor(),
_ => KnownCursor::Default,
};
if seat.cursor.replace(Some(cursor)) != Some(cursor) {
pseat.jay_pointer.set_known_cursor(cursor);
}
}
pub fn button(&self, pseat: &PortalSeat, button: u32, state: u32) {
let seat = match self.seats.get(&pseat.global_id) {
Some(s) => s,
_ => return,
};
let element = seat.tree.borrow_mut().last().cloned();
if let Some(e) = element {
e.button(pseat, button, state);
}
}
}
pub struct GuiBuffer {
pub wl: Rc<UsrWlBuffer>,
pub window: Rc<WindowData>,
pub fb: Rc<Framebuffer>,
pub free: Cell<bool>,
pub size: (i32, i32),
}
struct GuiBufferPending {
pub window: Rc<WindowData>,
pub fb: Rc<Framebuffer>,
pub params: Rc<UsrLinuxBufferParams>,
pub size: (i32, i32),
}
impl UsrWlBufferOwner for GuiBuffer {
fn release(&self) {
self.free.set(true);
if self.window.frame_missed.get() {
self.window.schedule_render();
}
}
}
impl UsrWpFractionalScaleOwner for WindowData {
fn preferred_scale(self: Rc<Self>, ev: &PreferredScale) {
let mut layout = self.first_scale.replace(false);
layout |= self.scale.replace(ev.scale) != ev.scale;
if layout {
self.layout();
self.allocate_buffers();
}
}
}
impl UsrWlrLayerSurfaceOwner for OverlayWindow {
fn configure(&self, _ev: &Configure) {
self.data.schedule_render();
}
fn closed(&self) {
self.data.kill(true);
}
}
impl UsrLinuxBufferParamsOwner for GuiBufferPending {
fn created(&self, buffer: Rc<UsrWlBuffer>) {
buffer.con.add_object(buffer.clone());
let buf = Rc::new(GuiBuffer {
wl: buffer,
window: self.window.clone(),
fb: self.fb.clone(),
free: Cell::new(true),
size: self.size,
});
buf.wl.owner.set(Some(buf.clone()));
self.window.bufs.borrow_mut().push(buf);
self.params.con.remove_obj(self.params.deref());
self.window.pending_bufs.remove(&self.params.id);
if self.window.frame_missed.get() {
self.window.schedule_render();
}
}
fn failed(&self) {
self.window.kill(true);
}
}

View file

@ -1,4 +1,4 @@
pub use {context::*, framebuffer::*, image::*, renderer::*, texture::*};
pub use {context::*, framebuffer::*, image::*, renderer::*, renderer_base::*, texture::*};
mod context;
mod framebuffer;

View file

@ -114,6 +114,28 @@ impl Framebuffer {
});
}
pub fn render_custom(&self, scale: Fixed, f: impl FnOnce(&mut RendererBase)) {
let _ = self.ctx.ctx.with_current(|| {
unsafe {
glBindFramebuffer(GL_FRAMEBUFFER, self.gl.fbo);
glViewport(0, 0, self.gl.width, self.gl.height);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}
let mut renderer = RendererBase {
ctx: &self.ctx,
fb: &self.gl,
scaled: scale != 1,
scale,
scalef: scale.to_f64(),
};
f(&mut renderer);
unsafe {
glFlush();
}
Ok(())
});
}
pub fn render(
&self,
node: &dyn Node,

View file

@ -15,7 +15,7 @@ use {
texture::image_target,
},
renderer::context::RenderContext,
sys::{glDisable, glEnable, GL_BLEND},
sys::{glClear, glClearColor, glDisable, glEnable, GL_BLEND, GL_COLOR_BUFFER_BIT},
Texture,
},
theme::Color,
@ -33,6 +33,10 @@ pub struct RendererBase<'a> {
}
impl RendererBase<'_> {
pub fn scale(&self) -> Fixed {
self.scale
}
pub fn physical_extents(&self) -> Rect {
Rect::new_sized(0, 0, self.fb.width, self.fb.height).unwrap()
}
@ -45,6 +49,14 @@ impl RendererBase<'_> {
(x, y)
}
pub fn scale_point_f(&self, mut x: f32, mut y: f32) -> (f32, f32) {
if self.scaled {
x = (x as f64 * self.scalef) as _;
y = (y as f64 * self.scalef) as _;
}
(x, y)
}
pub fn scale_rect(&self, mut rect: Rect) -> Rect {
if self.scaled {
let x1 = (rect.x1() as f64 * self.scalef).round() as _;
@ -56,6 +68,25 @@ impl RendererBase<'_> {
rect
}
pub fn scale_rect_f(&self, mut rect: (f32, f32, f32, f32)) -> (f32, f32, f32, f32) {
if self.scaled {
let x1 = (rect.0 as f64 * self.scalef).round() as _;
let y1 = (rect.1 as f64 * self.scalef).round() as _;
let x2 = (rect.2 as f64 * self.scalef).round() as _;
let y2 = (rect.3 as f64 * self.scalef).round() as _;
rect = (x1, y1, x2, y2)
}
rect
}
fn xf_to_f(&self, x: f32) -> f32 {
2.0 * (x / self.fb.width as f32) - 1.0
}
fn yf_to_f(&self, y: f32) -> f32 {
2.0 * (y / self.fb.height as f32) - 1.0
}
fn x_to_f(&self, x: i32) -> f32 {
2.0 * (x as f32 / self.fb.width as f32) - 1.0
}
@ -64,6 +95,13 @@ impl RendererBase<'_> {
2.0 * (y as f32 / self.fb.height as f32) - 1.0
}
pub fn clear(&self, c: &Color) {
unsafe {
glClearColor(c.r, c.g, c.b, c.a);
glClear(GL_COLOR_BUFFER_BIT);
}
}
pub fn fill_boxes(&self, boxes: &[Rect], color: &Color) {
self.fill_boxes2(boxes, color, 0, 0);
}
@ -94,6 +132,36 @@ impl RendererBase<'_> {
self.fill_boxes3(&pos, color)
}
pub fn fill_boxes_f(&self, boxes: &[(f32, f32, f32, f32)], color: &Color) {
self.fill_boxes2_f(boxes, color, 0.0, 0.0);
}
pub fn fill_boxes2_f(&self, boxes: &[(f32, f32, f32, f32)], color: &Color, dx: f32, dy: f32) {
if boxes.is_empty() {
return;
}
let (dx, dy) = self.scale_point_f(dx, dy);
let mut pos = Vec::with_capacity(boxes.len() * 12);
for bx in boxes {
let (x1, y1, x2, y2) = self.scale_rect_f(*bx);
let x1 = self.xf_to_f(x1 + dx);
let y1 = self.yf_to_f(y1 + dy);
let x2 = self.xf_to_f(x2 + dx);
let y2 = self.yf_to_f(y2 + dy);
pos.extend_from_slice(&[
// triangle 1
x2, y1, // top right
x1, y1, // top left
x1, y2, // bottom left
// triangle 2
x2, y1, // top right
x1, y2, // bottom left
x2, y2, // bottom right
]);
}
self.fill_boxes3(&pos, color)
}
fn fill_boxes3(&self, boxes: &[f32], color: &Color) {
unsafe {
glUseProgram(self.ctx.fill_prog.prog);

View file

@ -17,6 +17,10 @@ fn to_u8(c: f32) -> u8 {
}
impl Color {
pub fn from_gray(g: u8) -> Self {
Self::from_rgb(g, g, g)
}
pub fn from_rgb(r: u8, g: u8, b: u8) -> Self {
Self {
r: to_f32(r),

View file

@ -50,7 +50,11 @@ impl<K: Eq + Hash, V> CopyHashMap<K, V> {
unsafe { self.map.get().deref().get(k).cloned() }
}
pub fn remove(&self, k: &K) -> Option<V> {
pub fn remove<Q: ?Sized>(&self, k: &Q) -> Option<V>
where
Q: Hash + Eq,
K: Borrow<Q>,
{
unsafe { self.map.get().deref_mut().remove(k) }
}

View file

@ -140,8 +140,8 @@ pub struct Drm {
impl Drm {
#[cfg_attr(not(feature = "it"), allow(dead_code))]
pub fn open_existing(fd: OwnedFd) -> Self {
Self { fd: Rc::new(fd) }
pub fn open_existing(fd: Rc<OwnedFd>) -> Self {
Self { fd }
}
pub fn reopen(fd: c::c_int, need_primary: bool) -> Result<Self, DrmError> {

View file

@ -1,3 +1,3 @@
#![allow(unused_imports)]
#![allow(unused_imports, non_camel_case_types)]
include!(concat!(env!("OUT_DIR"), "/wire_dbus.rs"));

View file

@ -1,5 +1,3 @@
#![allow(dead_code)]
pub mod usr_ifs;
pub mod usr_object;
@ -78,6 +76,7 @@ pub struct UsrCon {
incoming: Cell<Option<SpawnedFuture<()>>>,
outgoing: Cell<Option<SpawnedFuture<()>>>,
pub owner: CloneCell<Option<Rc<dyn UsrConOwner>>>,
dead: Cell<bool>,
}
pub trait UsrConOwner {
@ -126,6 +125,7 @@ impl UsrCon {
incoming: Default::default(),
outgoing: Default::default(),
owner: Default::default(),
dead: Cell::new(false),
});
slf.objects.set(
WL_DISPLAY_ID.into(),
@ -158,6 +158,7 @@ impl UsrCon {
}
pub fn kill(&self) {
self.dead.set(true);
for (_, obj) in self.objects.lock().drain() {
if let Some(obj) = obj {
obj.break_loops();
@ -186,8 +187,10 @@ impl UsrCon {
}
pub fn add_object(&self, obj: Rc<dyn UsrObject>) {
if !self.dead.get() {
self.objects.set(obj.id(), Some(obj));
}
}
pub fn get_registry(self: &Rc<Self>) -> Rc<UsrWlRegistry> {
let registry = Rc::new(UsrWlRegistry {
@ -199,7 +202,7 @@ impl UsrCon {
self_id: WL_DISPLAY_ID,
registry: registry.id,
});
self.objects.set(registry.id.into(), Some(registry.clone()));
self.add_object(registry.clone());
registry
}
@ -212,7 +215,7 @@ impl UsrCon {
self_id: WL_DISPLAY_ID,
callback: callback.id,
});
self.objects.set(callback.id.into(), Some(callback));
self.add_object(callback);
}
pub fn parse<'a, R: RequestParser<'a>>(
@ -233,6 +236,9 @@ impl UsrCon {
}
pub fn request<T: EventFormatter>(self: &Rc<Self>, event: T) {
if self.dead.get() {
return;
}
if log::log_enabled!(log::Level::Trace) {
log::trace!(
"Server {} <= {}@{}.{:?}",

View file

@ -82,6 +82,7 @@ impl UsrJayCompositor {
jo
}
#[allow(dead_code)]
pub fn watch_workspaces(&self) -> Rc<UsrJayWorkspaceWatcher> {
let ww = Rc::new(UsrJayWorkspaceWatcher {
id: self.con.id(),

View file

@ -67,6 +67,7 @@ impl UsrJayScreencast {
});
}
#[allow(dead_code)]
pub fn allow_workspace(&self, ws: &JayWorkspace) {
self.con.request(AllowWorkspace {
self_id: self.id,
@ -74,6 +75,7 @@ impl UsrJayScreencast {
});
}
#[allow(dead_code)]
pub fn touch_allowed_workspaces(&self) {
self.con
.request(TouchAllowedWorkspaces { self_id: self.id });

View file

@ -42,6 +42,7 @@ pub trait UsrWlPointerOwner {
}
impl UsrWlPointer {
#[allow(dead_code)]
pub fn set_cursor(&self, serial: u32, cursor: &UsrWlSurface, hot_x: i32, hot_y: i32) {
self.con.request(SetCursor {
self_id: self.id,

View file

@ -19,6 +19,7 @@ pub struct UsrWlShm {
}
impl UsrWlShm {
#[allow(dead_code)]
pub fn create_pool(&self, fd: &Rc<OwnedFd>, size: i32) -> Rc<UsrWlShmPool> {
let pool = Rc::new(UsrWlShmPool {
id: self.con.id(),

View file

@ -33,6 +33,7 @@ impl UsrWlrLayerSurface {
});
}
#[allow(dead_code)]
pub fn set_keyboard_interactivity(&self, ki: u32) {
self.con.request(SetKeyboardInteractivity {
self_id: self.id,
@ -40,6 +41,7 @@ impl UsrWlrLayerSurface {
});
}
#[allow(dead_code)]
pub fn set_layer(&self, layer: u32) {
self.con.request(SetLayer {
self_id: self.id,

View file

@ -13,6 +13,7 @@ pub struct UsrWpViewport {
}
impl UsrWpViewport {
#[allow(dead_code)]
pub fn set_source(&self, x: Fixed, y: Fixed, width: Fixed, height: Fixed) {
self.con.request(SetSource {
self_id: self.id,

View file

@ -43,6 +43,7 @@ pub trait UsrZwlrScreencopyFrameOwner {
}
impl UsrZwlrScreencopyFrame {
#[allow(dead_code)]
pub fn copy(&self, buffer: &UsrWlBuffer) {
self.con.request(Copy {
self_id: self.id,

View file

@ -17,6 +17,7 @@ pub struct UsrZwlrScreencopyManager {
}
impl UsrZwlrScreencopyManager {
#[allow(dead_code)]
pub fn capture_output(&self, output: &UsrWlOutput) -> Rc<UsrZwlrScreencopyFrame> {
let frame = Rc::new(UsrZwlrScreencopyFrame {
id: self.con.id(),

View file

@ -0,0 +1 @@
fn Close() { }

View file

@ -0,0 +1,34 @@
fn CreateSession(
handle: object_path,
session_handle: object_path,
app_id: string,
options: array(dict(string, variant)),
) {
response: u32,
results: array(dict(string, variant)),
}
fn SelectSources(
handle: object_path,
session_handle: object_path,
app_id: string,
options: array(dict(string, variant)),
) {
response: u32,
results: array(dict(string, variant)),
}
fn Start(
handle: object_path,
session_handle: object_path,
app_id: string,
parent_window: string,
options: array(dict(string, variant)),
) {
response: u32,
results: array(dict(string, variant)),
}
prop AvailableSourceTypes = u32
prop AvailableCursorModes = u32
prop version = u32

View file

@ -0,0 +1,9 @@
fn Close() {
}
sig Closed {
}
prop version = u32