portal: add a desktop portal
This commit is contained in:
parent
323a6ed953
commit
a162055f1d
38 changed files with 2389 additions and 27 deletions
4
etc/jay.portal
Normal file
4
etc/jay.portal
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[portal]
|
||||
DBusName=org.freedesktop.impl.portal.desktop.jay
|
||||
Interfaces=org.freedesktop.impl.portal.ScreenCast;
|
||||
UseIn=jay
|
||||
4
etc/org.freedesktop.impl.portal.desktop.jay.service
Normal file
4
etc/org.freedesktop.impl.portal.desktop.jay.service
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[D-BUS Service]
|
||||
Name=org.freedesktop.impl.portal.desktop.jay
|
||||
Exec=/bin/false
|
||||
SystemdService=xdg-desktop-portal-jay.service
|
||||
7
etc/xdg-desktop-portal-jay.service
Normal file
7
etc/xdg-desktop-portal-jay.service
Normal 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
|
||||
|
|
@ -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(),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ mod logind;
|
|||
mod object;
|
||||
mod pango;
|
||||
mod pipewire;
|
||||
mod portal;
|
||||
mod rect;
|
||||
mod render;
|
||||
mod screenshoter;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
pub mod pw_con;
|
||||
pub mod pw_formatter;
|
||||
pub mod pw_ifs;
|
||||
|
|
|
|||
|
|
@ -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
161
src/portal.rs
Normal 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
486
src/portal/ptl_display.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
6
src/portal/ptl_render_ctx.rs
Normal file
6
src/portal/ptl_render_ctx.rs
Normal 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>,
|
||||
}
|
||||
450
src/portal/ptl_screencast.rs
Normal file
450
src/portal/ptl_screencast.rs
Normal 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
|
||||
}
|
||||
182
src/portal/ptl_screencast/screencast_gui.rs
Normal file
182
src/portal/ptl_screencast/screencast_gui.rs
Normal 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
876
src/portal/ptr_gui.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
pub use {context::*, framebuffer::*, image::*, renderer::*, texture::*};
|
||||
pub use {context::*, framebuffer::*, image::*, renderer::*, renderer_base::*, texture::*};
|
||||
|
||||
mod context;
|
||||
mod framebuffer;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
#![allow(unused_imports)]
|
||||
#![allow(unused_imports, non_camel_case_types)]
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/wire_dbus.rs"));
|
||||
|
|
|
|||
|
|
@ -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,7 +187,9 @@ impl UsrCon {
|
|||
}
|
||||
|
||||
pub fn add_object(&self, obj: Rc<dyn UsrObject>) {
|
||||
self.objects.set(obj.id(), Some(obj));
|
||||
if !self.dead.get() {
|
||||
self.objects.set(obj.id(), Some(obj));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_registry(self: &Rc<Self>) -> Rc<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 {} <= {}@{}.{:?}",
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
1
wire-dbus/org.freedesktop.impl.portal.Request.txt
Normal file
1
wire-dbus/org.freedesktop.impl.portal.Request.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
fn Close() { }
|
||||
34
wire-dbus/org.freedesktop.impl.portal.ScreenCast.txt
Normal file
34
wire-dbus/org.freedesktop.impl.portal.ScreenCast.txt
Normal 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
|
||||
9
wire-dbus/org.freedesktop.impl.portal.Session.txt
Normal file
9
wire-dbus/org.freedesktop.impl.portal.Session.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
fn Close() {
|
||||
|
||||
}
|
||||
|
||||
sig Closed {
|
||||
|
||||
}
|
||||
|
||||
prop version = u32
|
||||
Loading…
Add table
Add a link
Reference in a new issue