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;
|
mod unlock;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
crate::compositor::start_compositor,
|
crate::{compositor::start_compositor, portal},
|
||||||
::log::Level,
|
::log::Level,
|
||||||
clap::{ArgEnum, Args, Parser, Subcommand},
|
clap::{ArgEnum, Args, Parser, Subcommand},
|
||||||
clap_complete::Shell,
|
clap_complete::Shell,
|
||||||
|
|
@ -53,6 +53,8 @@ pub enum Cmd {
|
||||||
RunPrivileged(RunPrivilegedArgs),
|
RunPrivileged(RunPrivilegedArgs),
|
||||||
/// Tests the events produced by a seat.
|
/// Tests the events produced by a seat.
|
||||||
SeatTest(SeatTestArgs),
|
SeatTest(SeatTestArgs),
|
||||||
|
/// Run the desktop portal.
|
||||||
|
Portal,
|
||||||
#[cfg(feature = "it")]
|
#[cfg(feature = "it")]
|
||||||
RunTests,
|
RunTests,
|
||||||
}
|
}
|
||||||
|
|
@ -214,6 +216,7 @@ pub fn main() {
|
||||||
Cmd::Unlock => unlock::main(cli.global),
|
Cmd::Unlock => unlock::main(cli.global),
|
||||||
Cmd::RunPrivileged(a) => run_privileged::main(cli.global, a),
|
Cmd::RunPrivileged(a) => run_privileged::main(cli.global, a),
|
||||||
Cmd::SeatTest(a) => seat_test::main(cli.global, a),
|
Cmd::SeatTest(a) => seat_test::main(cli.global, a),
|
||||||
|
Cmd::Portal => portal::run(cli.global),
|
||||||
#[cfg(feature = "it")]
|
#[cfg(feature = "it")]
|
||||||
Cmd::RunTests => crate::it::run_tests(),
|
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;
|
pub const DBUS_NAME_FLAG_ALLOW_REPLACEMENT: u32 = 0x1;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub const DBUS_NAME_FLAG_REPLACE_EXISTING: u32 = 0x2;
|
pub const DBUS_NAME_FLAG_REPLACE_EXISTING: u32 = 0x2;
|
||||||
#[allow(dead_code)]
|
|
||||||
pub const DBUS_NAME_FLAG_DO_NOT_QUEUE: u32 = 0x4;
|
pub const DBUS_NAME_FLAG_DO_NOT_QUEUE: u32 = 0x4;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub const DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER: u32 = 1;
|
pub const DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER: u32 = 1;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub const DBUS_REQUEST_NAME_REPLY_IN_QUEUE: u32 = 2;
|
pub const DBUS_REQUEST_NAME_REPLY_IN_QUEUE: u32 = 2;
|
||||||
|
|
@ -604,7 +602,6 @@ impl Drop for DbusObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DbusObject {
|
impl DbusObject {
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn add_method<T, F>(&self, handler: F)
|
pub fn add_method<T, F>(&self, handler: F)
|
||||||
where
|
where
|
||||||
T: MethodCall<'static>,
|
T: MethodCall<'static>,
|
||||||
|
|
@ -623,7 +620,6 @@ impl DbusObject {
|
||||||
self.data.methods.set(key, rhd);
|
self.data.methods.set(key, rhd);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn set_property<T>(&self, value: Variant<'static>)
|
pub fn set_property<T>(&self, value: Variant<'static>)
|
||||||
where
|
where
|
||||||
T: Property + 'static,
|
T: Property + 'static,
|
||||||
|
|
@ -649,12 +645,10 @@ impl DbusObject {
|
||||||
self.data.properties.set(key, phd);
|
self.data.properties.set(key, phd);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn emit_signal<'a, T: Signal<'a>>(&self, signal: &T) {
|
pub fn emit_signal<'a, T: Signal<'a>>(&self, signal: &T) {
|
||||||
self.socket.emit_signal(&self.data.path, signal);
|
self.socket.emit_signal(&self.data.path, signal);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn path(&self) -> &str {
|
pub fn path(&self) -> &str {
|
||||||
&self.data.path
|
&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) {
|
pub fn call_noreply<'a, T: MethodCall<'a>>(&self, destination: &str, path: &str, msg: T) {
|
||||||
if !self.dead.get() {
|
if !self.dead.get() {
|
||||||
self.send_call(path, destination, NO_REPLY_EXPECTED, &msg);
|
self.send_call(path, destination, NO_REPLY_EXPECTED, &msg);
|
||||||
|
|
@ -135,7 +134,6 @@ impl DbusSocket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn add_object(
|
pub fn add_object(
|
||||||
self: &Rc<Self>,
|
self: &Rc<Self>,
|
||||||
object: impl Into<Cow<'static, str>>,
|
object: impl Into<Cow<'static, str>>,
|
||||||
|
|
@ -158,7 +156,6 @@ impl DbusSocket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn handle_signal<T, F>(
|
pub fn handle_signal<T, F>(
|
||||||
self: &Rc<Self>,
|
self: &Rc<Self>,
|
||||||
sender: Option<&str>,
|
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 {
|
pub fn emit_signal<'a, T: Signal<'a>>(&self, path: &str, msg: &T) -> u32 {
|
||||||
let (msg, serial) = self.format_signal(path, msg);
|
let (msg, serial) = self.format_signal(path, msg);
|
||||||
self.bufio.send(msg);
|
self.bufio.send(msg);
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ use {
|
||||||
uapi::{c, Errno, OwnedFd},
|
uapi::{c, Errno, OwnedFd},
|
||||||
};
|
};
|
||||||
|
|
||||||
const POINTER: u32 = 1;
|
pub const POINTER: u32 = 1;
|
||||||
const KEYBOARD: u32 = 2;
|
const KEYBOARD: u32 = 2;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
const TOUCH: u32 = 4;
|
const TOUCH: u32 = 4;
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ use {
|
||||||
const ROLE: u32 = 0;
|
const ROLE: u32 = 0;
|
||||||
|
|
||||||
pub(super) const RELEASED: 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 VERTICAL_SCROLL: u32 = 0;
|
||||||
pub const HORIZONTAL_SCROLL: u32 = 1;
|
pub const HORIZONTAL_SCROLL: u32 = 1;
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,7 @@ impl TestBackend {
|
||||||
return Err(TestBackendError::NoDrmNode);
|
return Err(TestBackendError::NoDrmNode);
|
||||||
};
|
};
|
||||||
let file = match uapi::open(node.as_path(), c::O_RDWR | c::O_CLOEXEC, 0) {
|
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) => {
|
Err(e) => {
|
||||||
return Err(TestBackendError::OpenDrmNode(
|
return Err(TestBackendError::OpenDrmNode(
|
||||||
node.as_os_str().as_bytes().as_bstr().to_string(),
|
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 {
|
macro_rules! linear_ids {
|
||||||
($ids:ident, $id:ident) => {
|
($ids:ident, $id:ident) => {
|
||||||
linear_ids!($ids, $id, u32);
|
linear_ids!($ids, $id, u32);
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ mod logind;
|
||||||
mod object;
|
mod object;
|
||||||
mod pango;
|
mod pango;
|
||||||
mod pipewire;
|
mod pipewire;
|
||||||
|
mod portal;
|
||||||
mod rect;
|
mod rect;
|
||||||
mod render;
|
mod render;
|
||||||
mod screenshoter;
|
mod screenshoter;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
pub mod pw_con;
|
pub mod pw_con;
|
||||||
pub mod pw_formatter;
|
pub mod pw_formatter;
|
||||||
pub mod pw_ifs;
|
pub mod pw_ifs;
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ pub struct PwMemTyped<T> {
|
||||||
_phantom: PhantomData<T>,
|
_phantom: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub struct PwMemSlice {
|
pub struct PwMemSlice {
|
||||||
mem: Rc<PwMemMap>,
|
mem: Rc<PwMemMap>,
|
||||||
range: Range<usize>,
|
range: Range<usize>,
|
||||||
|
|
@ -134,6 +135,7 @@ impl PwMemMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Pod> PwMemTyped<T> {
|
impl<T: Pod> PwMemTyped<T> {
|
||||||
|
#[allow(dead_code)]
|
||||||
pub unsafe fn read(&self) -> &T {
|
pub unsafe fn read(&self) -> &T {
|
||||||
(self.mem.map.ptr.cast::<u8>().add(self.offset) as *const T).deref()
|
(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 context;
|
||||||
mod framebuffer;
|
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(
|
pub fn render(
|
||||||
&self,
|
&self,
|
||||||
node: &dyn Node,
|
node: &dyn Node,
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ use {
|
||||||
texture::image_target,
|
texture::image_target,
|
||||||
},
|
},
|
||||||
renderer::context::RenderContext,
|
renderer::context::RenderContext,
|
||||||
sys::{glDisable, glEnable, GL_BLEND},
|
sys::{glClear, glClearColor, glDisable, glEnable, GL_BLEND, GL_COLOR_BUFFER_BIT},
|
||||||
Texture,
|
Texture,
|
||||||
},
|
},
|
||||||
theme::Color,
|
theme::Color,
|
||||||
|
|
@ -33,6 +33,10 @@ pub struct RendererBase<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RendererBase<'_> {
|
impl RendererBase<'_> {
|
||||||
|
pub fn scale(&self) -> Fixed {
|
||||||
|
self.scale
|
||||||
|
}
|
||||||
|
|
||||||
pub fn physical_extents(&self) -> Rect {
|
pub fn physical_extents(&self) -> Rect {
|
||||||
Rect::new_sized(0, 0, self.fb.width, self.fb.height).unwrap()
|
Rect::new_sized(0, 0, self.fb.width, self.fb.height).unwrap()
|
||||||
}
|
}
|
||||||
|
|
@ -45,6 +49,14 @@ impl RendererBase<'_> {
|
||||||
(x, y)
|
(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 {
|
pub fn scale_rect(&self, mut rect: Rect) -> Rect {
|
||||||
if self.scaled {
|
if self.scaled {
|
||||||
let x1 = (rect.x1() as f64 * self.scalef).round() as _;
|
let x1 = (rect.x1() as f64 * self.scalef).round() as _;
|
||||||
|
|
@ -56,6 +68,25 @@ impl RendererBase<'_> {
|
||||||
rect
|
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 {
|
fn x_to_f(&self, x: i32) -> f32 {
|
||||||
2.0 * (x as f32 / self.fb.width as f32) - 1.0
|
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
|
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) {
|
pub fn fill_boxes(&self, boxes: &[Rect], color: &Color) {
|
||||||
self.fill_boxes2(boxes, color, 0, 0);
|
self.fill_boxes2(boxes, color, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
@ -94,6 +132,36 @@ impl RendererBase<'_> {
|
||||||
self.fill_boxes3(&pos, color)
|
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) {
|
fn fill_boxes3(&self, boxes: &[f32], color: &Color) {
|
||||||
unsafe {
|
unsafe {
|
||||||
glUseProgram(self.ctx.fill_prog.prog);
|
glUseProgram(self.ctx.fill_prog.prog);
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,10 @@ fn to_u8(c: f32) -> u8 {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Color {
|
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 {
|
pub fn from_rgb(r: u8, g: u8, b: u8) -> Self {
|
||||||
Self {
|
Self {
|
||||||
r: to_f32(r),
|
r: to_f32(r),
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,11 @@ impl<K: Eq + Hash, V> CopyHashMap<K, V> {
|
||||||
unsafe { self.map.get().deref().get(k).cloned() }
|
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) }
|
unsafe { self.map.get().deref_mut().remove(k) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -140,8 +140,8 @@ pub struct Drm {
|
||||||
|
|
||||||
impl Drm {
|
impl Drm {
|
||||||
#[cfg_attr(not(feature = "it"), allow(dead_code))]
|
#[cfg_attr(not(feature = "it"), allow(dead_code))]
|
||||||
pub fn open_existing(fd: OwnedFd) -> Self {
|
pub fn open_existing(fd: Rc<OwnedFd>) -> Self {
|
||||||
Self { fd: Rc::new(fd) }
|
Self { fd }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reopen(fd: c::c_int, need_primary: bool) -> Result<Self, DrmError> {
|
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"));
|
include!(concat!(env!("OUT_DIR"), "/wire_dbus.rs"));
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
pub mod usr_ifs;
|
pub mod usr_ifs;
|
||||||
pub mod usr_object;
|
pub mod usr_object;
|
||||||
|
|
||||||
|
|
@ -78,6 +76,7 @@ pub struct UsrCon {
|
||||||
incoming: Cell<Option<SpawnedFuture<()>>>,
|
incoming: Cell<Option<SpawnedFuture<()>>>,
|
||||||
outgoing: Cell<Option<SpawnedFuture<()>>>,
|
outgoing: Cell<Option<SpawnedFuture<()>>>,
|
||||||
pub owner: CloneCell<Option<Rc<dyn UsrConOwner>>>,
|
pub owner: CloneCell<Option<Rc<dyn UsrConOwner>>>,
|
||||||
|
dead: Cell<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait UsrConOwner {
|
pub trait UsrConOwner {
|
||||||
|
|
@ -126,6 +125,7 @@ impl UsrCon {
|
||||||
incoming: Default::default(),
|
incoming: Default::default(),
|
||||||
outgoing: Default::default(),
|
outgoing: Default::default(),
|
||||||
owner: Default::default(),
|
owner: Default::default(),
|
||||||
|
dead: Cell::new(false),
|
||||||
});
|
});
|
||||||
slf.objects.set(
|
slf.objects.set(
|
||||||
WL_DISPLAY_ID.into(),
|
WL_DISPLAY_ID.into(),
|
||||||
|
|
@ -158,6 +158,7 @@ impl UsrCon {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kill(&self) {
|
pub fn kill(&self) {
|
||||||
|
self.dead.set(true);
|
||||||
for (_, obj) in self.objects.lock().drain() {
|
for (_, obj) in self.objects.lock().drain() {
|
||||||
if let Some(obj) = obj {
|
if let Some(obj) = obj {
|
||||||
obj.break_loops();
|
obj.break_loops();
|
||||||
|
|
@ -186,7 +187,9 @@ impl UsrCon {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_object(&self, obj: Rc<dyn UsrObject>) {
|
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> {
|
pub fn get_registry(self: &Rc<Self>) -> Rc<UsrWlRegistry> {
|
||||||
|
|
@ -199,7 +202,7 @@ impl UsrCon {
|
||||||
self_id: WL_DISPLAY_ID,
|
self_id: WL_DISPLAY_ID,
|
||||||
registry: registry.id,
|
registry: registry.id,
|
||||||
});
|
});
|
||||||
self.objects.set(registry.id.into(), Some(registry.clone()));
|
self.add_object(registry.clone());
|
||||||
registry
|
registry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,7 +215,7 @@ impl UsrCon {
|
||||||
self_id: WL_DISPLAY_ID,
|
self_id: WL_DISPLAY_ID,
|
||||||
callback: callback.id,
|
callback: callback.id,
|
||||||
});
|
});
|
||||||
self.objects.set(callback.id.into(), Some(callback));
|
self.add_object(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse<'a, R: RequestParser<'a>>(
|
pub fn parse<'a, R: RequestParser<'a>>(
|
||||||
|
|
@ -233,6 +236,9 @@ impl UsrCon {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request<T: EventFormatter>(self: &Rc<Self>, event: T) {
|
pub fn request<T: EventFormatter>(self: &Rc<Self>, event: T) {
|
||||||
|
if self.dead.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if log::log_enabled!(log::Level::Trace) {
|
if log::log_enabled!(log::Level::Trace) {
|
||||||
log::trace!(
|
log::trace!(
|
||||||
"Server {} <= {}@{}.{:?}",
|
"Server {} <= {}@{}.{:?}",
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ impl UsrJayCompositor {
|
||||||
jo
|
jo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn watch_workspaces(&self) -> Rc<UsrJayWorkspaceWatcher> {
|
pub fn watch_workspaces(&self) -> Rc<UsrJayWorkspaceWatcher> {
|
||||||
let ww = Rc::new(UsrJayWorkspaceWatcher {
|
let ww = Rc::new(UsrJayWorkspaceWatcher {
|
||||||
id: self.con.id(),
|
id: self.con.id(),
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ impl UsrJayScreencast {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn allow_workspace(&self, ws: &JayWorkspace) {
|
pub fn allow_workspace(&self, ws: &JayWorkspace) {
|
||||||
self.con.request(AllowWorkspace {
|
self.con.request(AllowWorkspace {
|
||||||
self_id: self.id,
|
self_id: self.id,
|
||||||
|
|
@ -74,6 +75,7 @@ impl UsrJayScreencast {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn touch_allowed_workspaces(&self) {
|
pub fn touch_allowed_workspaces(&self) {
|
||||||
self.con
|
self.con
|
||||||
.request(TouchAllowedWorkspaces { self_id: self.id });
|
.request(TouchAllowedWorkspaces { self_id: self.id });
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ pub trait UsrWlPointerOwner {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UsrWlPointer {
|
impl UsrWlPointer {
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn set_cursor(&self, serial: u32, cursor: &UsrWlSurface, hot_x: i32, hot_y: i32) {
|
pub fn set_cursor(&self, serial: u32, cursor: &UsrWlSurface, hot_x: i32, hot_y: i32) {
|
||||||
self.con.request(SetCursor {
|
self.con.request(SetCursor {
|
||||||
self_id: self.id,
|
self_id: self.id,
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ pub struct UsrWlShm {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UsrWlShm {
|
impl UsrWlShm {
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn create_pool(&self, fd: &Rc<OwnedFd>, size: i32) -> Rc<UsrWlShmPool> {
|
pub fn create_pool(&self, fd: &Rc<OwnedFd>, size: i32) -> Rc<UsrWlShmPool> {
|
||||||
let pool = Rc::new(UsrWlShmPool {
|
let pool = Rc::new(UsrWlShmPool {
|
||||||
id: self.con.id(),
|
id: self.con.id(),
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ impl UsrWlrLayerSurface {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn set_keyboard_interactivity(&self, ki: u32) {
|
pub fn set_keyboard_interactivity(&self, ki: u32) {
|
||||||
self.con.request(SetKeyboardInteractivity {
|
self.con.request(SetKeyboardInteractivity {
|
||||||
self_id: self.id,
|
self_id: self.id,
|
||||||
|
|
@ -40,6 +41,7 @@ impl UsrWlrLayerSurface {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn set_layer(&self, layer: u32) {
|
pub fn set_layer(&self, layer: u32) {
|
||||||
self.con.request(SetLayer {
|
self.con.request(SetLayer {
|
||||||
self_id: self.id,
|
self_id: self.id,
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ pub struct UsrWpViewport {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UsrWpViewport {
|
impl UsrWpViewport {
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn set_source(&self, x: Fixed, y: Fixed, width: Fixed, height: Fixed) {
|
pub fn set_source(&self, x: Fixed, y: Fixed, width: Fixed, height: Fixed) {
|
||||||
self.con.request(SetSource {
|
self.con.request(SetSource {
|
||||||
self_id: self.id,
|
self_id: self.id,
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ pub trait UsrZwlrScreencopyFrameOwner {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UsrZwlrScreencopyFrame {
|
impl UsrZwlrScreencopyFrame {
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn copy(&self, buffer: &UsrWlBuffer) {
|
pub fn copy(&self, buffer: &UsrWlBuffer) {
|
||||||
self.con.request(Copy {
|
self.con.request(Copy {
|
||||||
self_id: self.id,
|
self_id: self.id,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ pub struct UsrZwlrScreencopyManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UsrZwlrScreencopyManager {
|
impl UsrZwlrScreencopyManager {
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn capture_output(&self, output: &UsrWlOutput) -> Rc<UsrZwlrScreencopyFrame> {
|
pub fn capture_output(&self, output: &UsrWlOutput) -> Rc<UsrZwlrScreencopyFrame> {
|
||||||
let frame = Rc::new(UsrZwlrScreencopyFrame {
|
let frame = Rc::new(UsrZwlrScreencopyFrame {
|
||||||
id: self.con.id(),
|
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