1
0
Fork 0
forked from wry/wry

all: bring back xdg portals

This commit is contained in:
kossLAN 2026-06-06 19:16:45 -04:00
parent d920e554cf
commit e61f042d8b
No known key found for this signature in database
107 changed files with 14293 additions and 55 deletions

View file

@ -274,15 +274,47 @@ fn write_message<W: Write>(f: &mut W, obj: &str, message: &Message) -> Result<()
Ok(())
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum RequestHandlerDirection {
Request,
Event,
}
fn write_request_handler<W: Write>(
f: &mut W,
camel_obj_name: &str,
messages: &[Lined<Message>],
direction: RequestHandlerDirection,
) -> Result<()> {
let snake_direction;
let camel_direction;
let parent;
let parser;
let error;
let param;
writeln!(f)?;
match direction {
RequestHandlerDirection::Request => {
snake_direction = "request";
camel_direction = "Request";
parent = "crate::object::Object";
parser = "crate::client::Client";
error = "crate::client::ClientError";
param = "req";
}
RequestHandlerDirection::Event => {
snake_direction = "event";
camel_direction = "Event";
parent = "crate::wl_usr::usr_object::UsrObject";
parser = "crate::wl_usr::UsrCon";
error = "crate::wl_usr::UsrConError";
param = "ev";
writeln!(f, " #[allow(clippy::allow_attributes, dead_code)]")?;
}
}
writeln!(
f,
" pub trait {camel_obj_name}RequestHandler: crate::object::Object + Sized {{"
" pub trait {camel_obj_name}{camel_direction}Handler: {parent} + Sized {{"
)?;
writeln!(f, " type Error: std::error::Error;")?;
for message in messages {
@ -294,24 +326,24 @@ fn write_request_handler<W: Write>(
writeln!(f)?;
writeln!(
f,
" fn {}(&self, req: {}{lt}, _slf: &Rc<Self>) -> Result<(), Self::Error>;",
" fn {}(&self, {param}: {}{lt}, _slf: &Rc<Self>) -> Result<(), Self::Error>;",
msg.safe_name, msg.camel_name
)?;
}
writeln!(f)?;
writeln!(f, " #[inline(always)]")?;
writeln!(f, " fn handle_request_impl(")?;
writeln!(f, " fn handle_{snake_direction}_impl(")?;
writeln!(f, " self: Rc<Self>,")?;
writeln!(f, " client: &crate::client::Client,")?;
writeln!(f, " client: &{parser},")?;
writeln!(f, " req: u32,")?;
writeln!(
f,
" parser: crate::utils::buffd::MsgParser<'_, '_>,"
)?;
writeln!(f, " ) -> Result<(), crate::client::ClientError> {{")?;
writeln!(f, " ) -> Result<(), {error}> {{")?;
if messages.is_empty() {
writeln!(f, " #![allow(unused_variables)]")?;
writeln!(f, " Err(crate::client::ClientError::InvalidMethod)")?;
writeln!(f, " Err({error}::InvalidMethod)")?;
} else {
writeln!(f, " let method;")?;
writeln!(
@ -347,10 +379,10 @@ fn write_request_handler<W: Write>(
}
writeln!(
f,
" _ => return Err(crate::client::ClientError::InvalidMethod),"
" _ => return Err({error}::InvalidMethod),"
)?;
writeln!(f, " }};")?;
writeln!(f, " Err(crate::client::ClientError::MethodError {{")?;
writeln!(f, " Err({error}::MethodError {{")?;
writeln!(f, " interface: {camel_obj_name},")?;
writeln!(f, " id: self.id(),")?;
writeln!(f, " method,")?;
@ -394,6 +426,13 @@ fn write_file<W: Write>(
f,
&camel_obj_name,
&messages.requests,
RequestHandlerDirection::Request,
)?;
write_request_handler(
f,
&camel_obj_name,
&messages.events,
RequestHandlerDirection::Event,
)?;
writeln!(f, "}}")?;
Ok(())

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

@ -0,0 +1,6 @@
[preferred]
default=gtk
org.freedesktop.impl.portal.ScreenCast=jay
org.freedesktop.impl.portal.RemoteDesktop=jay
org.freedesktop.impl.portal.Inhibit=none
org.freedesktop.impl.portal.FileChooser=gtk4

3
etc/jay.portal Normal file
View file

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

View file

@ -32,6 +32,7 @@ use {
},
compositor::start_compositor,
logger::LogLevel,
portal,
pr_caps::drop_all_pr_caps,
},
clap::{Args, Parser, Subcommand, ValueEnum, ValueHint},
@ -87,6 +88,8 @@ pub enum Cmd {
Dpms(DpmsArgs),
/// Tests the events produced by a seat.
SeatTest(SeatTestArgs),
/// Run the desktop portal.
Portal,
/// Inspect/modify graphics card and connector settings.
Randr(RandrArgs),
/// Inspect/modify input settings.
@ -243,6 +246,7 @@ pub fn main() {
Cmd::Dpms(a) => dpms::main(cli.global, a),
Cmd::Unlock => unlock::main(cli.global),
Cmd::SeatTest(a) => seat_test::main(cli.global, a),
Cmd::Portal => portal::run_freestanding(cli.global),
Cmd::Randr(a) => randr::main(cli.global, a),
Cmd::Input(a) => input::main(cli.global, a),
Cmd::DamageTracking(a) => damage_tracking::main(cli.global, a),

View file

@ -41,9 +41,8 @@ use {
};
pub use {
error::{ClientError, ParserError},
objects::MIN_SERVER_ID,
};
#[cfg(feature = "it")]
pub use objects::MIN_SERVER_ID;
mod error;
mod objects;

View file

@ -6,10 +6,6 @@ use {
wp_image_description_reference_v1::WpImageDescriptionReferenceV1,
wp_image_description_v1::WpImageDescriptionV1,
},
ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
ext_image_capture_source_v1::ExtImageCaptureSourceV1,
ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1,
head_management::jay_head_error_v1::JayHeadErrorV1,
data_transfer::{
data_control::{
ext_data_control_source_v1::ExtDataControlSourceV1,
@ -18,7 +14,12 @@ use {
wl_data_source::WlDataSource,
zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1,
},
ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
ext_image_capture_source_v1::ExtImageCaptureSourceV1,
ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1,
head_management::jay_head_error_v1::JayHeadErrorV1,
jay_output::JayOutput,
jay_screencast::JayScreencast,
jay_toplevel::JayToplevel,
jay_workspace::JayWorkspace,
wl_buffer::WlBuffer,
@ -48,9 +49,9 @@ use {
wire::{
ExtDataControlSourceV1Id, ExtForeignToplevelHandleV1Id, ExtImageCaptureSourceV1Id,
ExtImageCopyCaptureSessionV1Id, ExtWorkspaceGroupHandleV1Id, JayHeadErrorV1Id,
JayOutputId, JayToplevelId, JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId,
WlPointerId, WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId,
WpDrmLeaseConnectorV1Id, WpImageDescriptionReferenceV1Id,
JayOutputId, JayScreencastId, JayToplevelId, JayWorkspaceId, WlBufferId,
WlDataSourceId, WlOutputId, WlPointerId, WlRegionId, WlRegistryId, WlSeatId,
WlSurfaceId, WpDrmLeaseConnectorV1Id, WpImageDescriptionReferenceV1Id,
WpImageDescriptionV1Id, WpLinuxDrmSyncobjTimelineV1Id, XdgPopupId, XdgPositionerId,
XdgSurfaceId, XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id,
ZwlrOutputHeadV1Id, ZwlrOutputModeV1Id, ZwpPrimarySelectionSourceV1Id,
@ -79,6 +80,7 @@ pub struct Objects {
pub pointers: CopyHashMap<WlPointerId, Rc<WlPointer>>,
pub xdg_wm_bases: CopyHashMap<XdgWmBaseId, Rc<XdgWmBase>>,
pub seats: CopyHashMap<WlSeatId, Rc<WlSeat>>,
pub screencasts: CopyHashMap<JayScreencastId, Rc<JayScreencast>>,
pub timelines: CopyHashMap<WpLinuxDrmSyncobjTimelineV1Id, Rc<WpLinuxDrmSyncobjTimelineV1>>,
pub zwlr_data_sources: CopyHashMap<ZwlrDataControlSourceV1Id, Rc<ZwlrDataControlSourceV1>>,
pub zwlr_output_heads: CopyHashMap<ZwlrOutputHeadV1Id, Rc<ZwlrOutputHeadV1>>,
@ -125,6 +127,7 @@ impl Objects {
pointers: Default::default(),
xdg_wm_bases: Default::default(),
seats: Default::default(),
screencasts: Default::default(),
timelines: Default::default(),
zwlr_data_sources: Default::default(),
zwlr_output_heads: Default::default(),
@ -173,6 +176,7 @@ impl Objects {
self.xdg_wm_bases.clear();
self.seats.clear();
self.pointers.clear();
self.screencasts.clear();
self.timelines.clear();
self.zwlr_data_sources.clear();
self.jay_toplevels.clear();

View file

@ -35,6 +35,7 @@ use {
head_management::{
HeadManagers, HeadState, jay_head_manager_session_v1::handle_jay_head_manager_done,
},
jay_screencast::{perform_screencast_realloc, perform_toplevel_screencasts},
wl_output::{BlendSpace, OutputId, PersistentOutputState, WlOutputGlobal},
wl_seat::{handle_position_hint_requests, handle_warp_mouse_to_focus},
wl_surface::{
@ -49,6 +50,7 @@ use {
leaks,
logger::Logger,
output_schedule::create_output_schedule,
portal::{self, PortalStartup},
pr_caps::{PrCapsThread, pr_caps},
scale::Scale,
sighand::{self, SighandError},
@ -115,10 +117,19 @@ pub fn start_compositor(global: GlobalArgs, args: RunArgs) {
None
};
let forker = create_forker(reaper_pid);
let portal = portal::run_from_compositor(global.log_level);
enable_profiler();
let logger = Logger::install_compositor(global.log_level);
let portal = match portal {
Ok(p) => Some(p),
Err(e) => {
log::error!("Could not spawn portal: {}", ErrorFmt(e));
None
}
};
let res = start_compositor2(
Some(forker),
portal,
Some(logger.clone()),
args,
None,
@ -137,7 +148,7 @@ pub fn start_compositor(global: GlobalArgs, args: RunArgs) {
#[cfg(feature = "it")]
pub fn start_compositor_for_test(future: TestFuture) -> Result<(), CompositorError> {
let res = start_compositor2(None, None, RunArgs::default(), Some(future), None);
let res = start_compositor2(None, None, None, RunArgs::default(), Some(future), None);
leaks::log_leaked();
res
}
@ -179,6 +190,7 @@ pub type TestFuture = Box<dyn Fn(&Rc<State>) -> Box<dyn Future<Output = ()>>>;
fn start_compositor2(
forker: Option<Rc<ForkerProxy>>,
portal: Option<PortalStartup>,
logger: Option<Arc<Logger>>,
run_args: RunArgs,
test_future: Option<TestFuture>,
@ -246,6 +258,8 @@ fn start_compositor2(
pending_output_render_data: Default::default(),
pending_float_layout: Default::default(),
pending_input_popup_positioning: Default::default(),
pending_toplevel_screencasts: Default::default(),
pending_screencast_reallocs_or_reconfigures: Default::default(),
pending_placeholder_render_textures: Default::default(),
pending_container_tab_render_textures: Default::default(),
dbus: Dbus::new(&engine, &ring, &run_toplevel),
@ -401,6 +415,13 @@ fn start_compositor2(
forker.setenv(key.as_bytes(), val.as_bytes());
}
}
let mut _portal = None;
if let (Some(portal), Some(logger)) = (portal, &logger) {
_portal = Some(engine.spawn(
"portal",
portal.spawn(engine.clone(), ring.clone(), logger.clone()),
));
}
let _compositor = engine.spawn("compositor", start_compositor3(state.clone(), test_future));
ring.run()?;
state.clear();
@ -560,6 +581,16 @@ fn start_global_event_handlers(state: &Rc<State>) -> Vec<SpawnedFuture<()>> {
Phase::PostLayout,
handle_xdg_surface_configure_events(state.clone()),
),
eng.spawn2(
"toplevel screencast present",
Phase::Present,
perform_toplevel_screencasts(state.clone()),
),
eng.spawn2(
"screencast realloc",
Phase::PostLayout,
perform_screencast_realloc(state.clone()),
),
eng.spawn(
"lazy event sources",
handle_lazy_event_sources(state.clone()),
@ -749,6 +780,7 @@ fn create_dummy_output(state: &Rc<State>) {
hardware_cursor: Default::default(),
update_render_data_scheduled: Cell::new(false),
hardware_cursor_needs_render: Cell::new(false),
screencasts: Default::default(),
screencopies: Default::default(),
title_visible: Cell::new(false),
schedule,

View file

@ -16,12 +16,13 @@ use {
},
ahash::AHashMap,
std::{
borrow::Cow,
borrow::{Borrow, Cow},
cell::{Cell, RefCell},
fmt::Debug,
future::Future,
marker::PhantomData,
mem,
ops::Deref,
pin::Pin,
rc::Rc,
task::{Context, Poll, Waker},
@ -174,6 +175,24 @@ pub struct DbusSocket {
headers: RefCell<VecStorage<(u8, Variant<'static>)>>,
run_toplevel: Rc<RunToplevel>,
signal_handlers: RefCell<AHashMap<(&'static str, &'static str), InterfaceSignalHandlers>>,
objects: CopyHashMap<Cow<'static, str>, Rc<DbusObjectData>>,
}
#[derive(Hash, Eq, PartialEq)]
struct MemberHandlerOwnedKey {
key: MemberHandlerKey<'static>,
}
#[derive(Hash, Eq, PartialEq)]
struct MemberHandlerKey<'a> {
interface: &'a str,
member: &'a str,
}
impl<'a> Borrow<MemberHandlerKey<'a>> for MemberHandlerOwnedKey {
fn borrow(&self) -> &MemberHandlerKey<'a> {
&self.key
}
}
const HDR_PATH: u8 = 1;
@ -197,6 +216,20 @@ const NO_AUTO_START: u8 = 0x2;
#[expect(dead_code)]
const ALLOW_INTERACTIVE_AUTHORIZATION: u8 = 0x4;
#[expect(dead_code)]
pub const DBUS_NAME_FLAG_ALLOW_REPLACEMENT: u32 = 0x1;
#[expect(dead_code)]
pub const DBUS_NAME_FLAG_REPLACE_EXISTING: u32 = 0x2;
pub const DBUS_NAME_FLAG_DO_NOT_QUEUE: u32 = 0x4;
pub const DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER: u32 = 1;
#[expect(dead_code)]
pub const DBUS_REQUEST_NAME_REPLY_IN_QUEUE: u32 = 2;
#[expect(dead_code)]
pub const DBUS_REQUEST_NAME_REPLY_EXISTS: u32 = 3;
#[expect(dead_code)]
pub const DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER: u32 = 4;
pub const BUS_DEST: &str = "org.freedesktop.DBus";
pub const BUS_PATH: &str = "/org/freedesktop/DBus";
@ -399,3 +432,308 @@ struct InterfaceSignalHandlers {
unconditional: Option<Rc<dyn SignalHandlerApi>>,
conditional: AHashMap<String, Rc<dyn SignalHandlerApi>>,
}
struct DbusObjectData {
path: Cow<'static, str>,
methods: CopyHashMap<MemberHandlerOwnedKey, Rc<dyn MethodHandlerApi>>,
properties: CopyHashMap<MemberHandlerOwnedKey, Rc<dyn PropertyHandlerApi>>,
}
pub struct DbusObject {
socket: Rc<DbusSocket>,
data: Rc<DbusObjectData>,
}
impl Drop for DbusObject {
fn drop(&mut self) {
self.socket.objects.remove(&self.data.path);
}
}
impl DbusObject {
pub fn add_method<T, F>(&self, handler: F)
where
T: MethodCall<'static>,
F: for<'a> Fn(T::Generic<'a>, PendingReply<T::Reply>) + 'static,
{
let rhd = Rc::new(MethodHandlerData {
handler,
_phantom: Default::default(),
});
let key = MemberHandlerOwnedKey {
key: MemberHandlerKey {
interface: T::INTERFACE,
member: T::MEMBER,
},
};
self.data.methods.set(key, rhd);
}
pub fn set_property<T>(&self, value: Variant<'static>)
where
T: Property + 'static,
{
self.emit_signal(
&crate::wire_dbus::org::freedesktop::dbus::properties::PropertiesChanged {
interface_name: T::INTERFACE.into(),
changed_properties: Cow::Borrowed(&[DictEntry {
key: T::PROPERTY.into(),
value: borrow_variant(&value),
}]),
invalidated_properties: Default::default(),
},
);
let phd = Rc::new(PropertyHandlerData::<T> {
data: value,
_phantom: Default::default(),
});
let key = MemberHandlerOwnedKey {
key: MemberHandlerKey {
interface: T::INTERFACE,
member: T::PROPERTY,
},
};
self.data.properties.set(key, phd);
}
pub fn emit_signal<'a, T: Signal<'a>>(&self, signal: &T) {
self.socket.emit_signal(&self.data.path, signal);
}
pub fn path(&self) -> &str {
&self.data.path
}
}
trait PropertyHandlerApi {
fn interface(&self) -> &'static str;
fn member(&self) -> &'static str;
fn value<'a>(&'a self) -> Variant<'a>;
}
struct PropertyHandlerData<T> {
data: Variant<'static>,
_phantom: PhantomData<T>,
}
impl<T> PropertyHandlerApi for PropertyHandlerData<T>
where
T: Property,
{
fn interface(&self) -> &'static str {
T::INTERFACE
}
fn member(&self) -> &'static str {
T::PROPERTY
}
fn value<'a>(&'a self) -> Variant<'a> {
borrow_variant(&self.data)
}
}
fn borrow_variant<'a>(value: &'a Variant<'static>) -> Variant<'a> {
match value {
Variant::U8(v) => Variant::U8(*v),
Variant::Bool(v) => Variant::Bool(*v),
Variant::I16(v) => Variant::I16(*v),
Variant::U16(v) => Variant::U16(*v),
Variant::I32(v) => Variant::I32(*v),
Variant::U32(v) => Variant::U32(*v),
Variant::I64(v) => Variant::I64(*v),
Variant::U64(v) => Variant::U64(*v),
Variant::F64(v) => Variant::F64(*v),
Variant::String(v) => Variant::String(Cow::Borrowed(v.as_ref())),
Variant::ObjectPath(v) => Variant::ObjectPath(ObjectPath(Cow::Borrowed(v.0.as_ref()))),
Variant::Signature(v) => Variant::Signature(Signature(Cow::Borrowed(v.0.as_ref()))),
Variant::Variant(v) => Variant::Variant(Box::new(borrow_variant(v))),
Variant::Fd(v) => Variant::Fd(v.clone()),
Variant::Array(ty, values) => {
Variant::Array(ty.clone(), values.iter().map(borrow_variant).collect())
}
Variant::DictEntry(k, v) => {
Variant::DictEntry(Box::new(borrow_variant(k)), Box::new(borrow_variant(v)))
}
Variant::Struct(values) => Variant::Struct(values.iter().map(borrow_variant).collect()),
}
}
pub struct PendingReply<T> {
reply_expected: bool,
socket: Rc<DbusSocket>,
destination: String,
serial: u32,
_phantom: PhantomData<T>,
}
impl<T> PendingReply<T> {
#[expect(dead_code)]
pub fn reply_expected(&self) -> bool {
self.reply_expected
}
pub fn err(&self, msg: &str) {
if self.reply_expected {
self.socket.send_error(&self.destination, self.serial, msg);
}
}
}
impl<T> PendingReply<T>
where
T: Message<'static>,
{
pub fn ok<'a>(&self, msg: &T::Generic<'a>) {
if self.reply_expected {
self.socket.send_reply(&self.destination, self.serial, msg);
}
}
#[expect(dead_code)]
pub fn complete<'a>(&self, res: Result<&T::Generic<'a>, &str>) {
match res {
Ok(m) => self.ok(m),
Err(e) => self.err(e),
}
}
}
trait MethodHandlerApi {
fn signature(&self) -> &'static str;
fn handle(
&self,
object: &DbusObjectData,
socket: &Rc<DbusSocket>,
dest: &str,
serial: u32,
reply_expected: bool,
parser: &mut Parser,
) -> Result<(), DbusError>;
}
struct MethodHandlerData<T, F> {
handler: F,
_phantom: PhantomData<T>,
}
impl<T, F> MethodHandlerApi for MethodHandlerData<T, F>
where
T: MethodCall<'static>,
F: for<'a> Fn(T::Generic<'a>, PendingReply<T::Reply>) + 'static,
{
fn signature(&self) -> &'static str {
T::SIGNATURE
}
fn handle<'a>(
&self,
_object: &DbusObjectData,
socket: &Rc<DbusSocket>,
dest: &str,
serial: u32,
reply_expected: bool,
parser: &mut Parser<'a>,
) -> Result<(), DbusError> {
let msg = T::Generic::<'a>::unmarshal(parser)?;
let pr = PendingReply {
reply_expected,
socket: socket.clone(),
destination: dest.to_string(),
serial,
_phantom: Default::default(),
};
(self.handler)(msg, pr);
Ok(())
}
}
struct PropertyGetHandlerProxy;
impl MethodHandlerApi for PropertyGetHandlerProxy {
fn signature(&self) -> &'static str {
crate::wire_dbus::org::freedesktop::dbus::properties::Get::SIGNATURE
}
fn handle<'a>(
&self,
object: &DbusObjectData,
socket: &Rc<DbusSocket>,
dest: &str,
serial: u32,
reply_expected: bool,
parser: &mut Parser<'a>,
) -> Result<(), DbusError> {
if !reply_expected {
return Ok(());
}
let msg = crate::wire_dbus::org::freedesktop::dbus::properties::Get::unmarshal(parser)?;
let key = MemberHandlerKey {
interface: msg.interface_name.deref(),
member: msg.property_name.deref(),
};
match object.properties.get(&key) {
Some(h) => socket.send_reply(
dest,
serial,
&crate::wire_dbus::org::freedesktop::dbus::properties::GetReply {
value: h.value(),
},
),
_ => socket.send_error(dest, serial, "Property does not exist"),
};
Ok(())
}
}
struct PropertyGetAllHandlerProxy;
impl MethodHandlerApi for PropertyGetAllHandlerProxy {
fn signature(&self) -> &'static str {
crate::wire_dbus::org::freedesktop::dbus::properties::GetAll::SIGNATURE
}
fn handle<'a>(
&self,
object: &DbusObjectData,
socket: &Rc<DbusSocket>,
dest: &str,
serial: u32,
reply_expected: bool,
parser: &mut Parser<'a>,
) -> Result<(), DbusError> {
if !reply_expected {
return Ok(());
}
let msg = crate::wire_dbus::org::freedesktop::dbus::properties::GetAll::unmarshal(parser)?;
let all_props = object.properties.lock();
let mut props = vec![];
for property in all_props.values() {
if property.interface() == msg.interface_name {
props.push(DictEntry {
key: property.member().into(),
value: property.value(),
});
}
}
socket.send_reply(
dest,
serial,
&crate::wire_dbus::org::freedesktop::dbus::properties::GetAllReply {
props: props.into(),
},
);
Ok(())
}
}
pub mod prelude {
pub use {
super::{
DbusError, DbusType, Formatter, Message, MethodCall, Parser, Property, Signal,
types::{Bool, DictEntry, ObjectPath, Variant},
},
std::{borrow::Cow, rc::Rc},
uapi::OwnedFd,
};
}

View file

@ -68,6 +68,7 @@ async fn connect(
headers: Default::default(),
run_toplevel: run_toplevel.clone(),
signal_handlers: Default::default(),
objects: Default::default(),
});
let skt = socket.clone();
socket.call(

View file

@ -6,7 +6,7 @@ use {
crate::{
dbus::{
CallError, DbusError, DbusSocket, Headers, MSG_ERROR, MSG_METHOD_CALL,
MSG_METHOD_RETURN, MSG_SIGNAL, Parser,
MSG_METHOD_RETURN, MSG_SIGNAL, MemberHandlerKey, NO_REPLY_EXPECTED, Parser,
},
utils::{
bufio::BufIoIncoming,
@ -61,6 +61,7 @@ impl Incoming {
return Err(DbusError::InvalidEndianess);
}
let msg_ty = msg_buf[1];
let flags = msg_buf[2];
let protocol = msg_buf[3];
if protocol != 1 {
return Err(DbusError::InvalidProtocol);
@ -83,16 +84,57 @@ impl Incoming {
return Err(DbusError::TooFewFds);
}
let fds: Vec<_> = self.incoming.fds.drain(..unix_fds).collect();
let mut parser =
Parser::new_at(msg_buf, FIXED_HEADER_SIZE + dyn_header_len as usize, &fds);
let mut parser = Parser::new_at(msg_buf, FIXED_HEADER_SIZE + dyn_header_len as usize, &fds);
match msg_ty {
MSG_METHOD_CALL => {
let sender = match &headers.sender {
Some(s) => s,
let (sender, interface, member, path) = match (
&headers.sender,
&headers.interface,
&headers.member,
&headers.path,
) {
(Some(s), Some(i), Some(m), Some(p)) => (s, i, m, p),
_ => return Err(DbusError::MissingMethodCallHeaders),
};
self.socket
.send_error(sender.deref(), serial, "Object does not exist");
if let Some(object) = self.socket.objects.get(path.deref()) {
let key = MemberHandlerKey {
interface: interface.deref(),
member: member.deref(),
};
if let Some(handler) = object.methods.get(&key) {
let sig = headers.signature.as_deref().unwrap_or("");
if sig != handler.signature() {
let msg = format!(
"Method call has an invalid signature: expected: {}, actual: {}",
handler.signature(),
sig,
);
self.socket.send_error(sender.deref(), serial, &msg);
} else {
let reply_expected = flags & NO_REPLY_EXPECTED == 0;
if let Err(e) = handler.handle(
&object,
&self.socket,
sender,
serial,
reply_expected,
&mut parser,
) {
log::error!(
"{}: Could not handle method call: {}",
self.socket.bus_name,
ErrorFmt(e)
);
}
}
} else {
self.socket
.send_error(sender.deref(), serial, "Method does not exist");
}
} else {
self.socket
.send_error(sender.deref(), serial, "Object does not exist");
}
}
MSG_METHOD_RETURN | MSG_ERROR => {
let serial = match headers.reply_serial {

View file

@ -1,12 +1,13 @@
use {
crate::{
dbus::{
AsyncProperty, AsyncReply, AsyncReplySlot, BUS_DEST, BUS_PATH, DbusError, DbusSocket,
DbusType, ErrorMessage, Formatter, HDR_DESTINATION, HDR_ERROR_NAME, HDR_INTERFACE,
HDR_MEMBER, HDR_PATH, HDR_REPLY_SERIAL, HDR_SIGNATURE, HDR_UNIX_FDS, Headers,
InterfaceSignalHandlers, MSG_ERROR, MSG_METHOD_CALL, Message,
MethodCall, NO_REPLY_EXPECTED, Parser, Property, Reply, ReplyHandler, Signal,
SignalHandler, SignalHandlerApi, SignalHandlerData,
AsyncProperty, AsyncReply, AsyncReplySlot, BUS_DEST, BUS_PATH, DbusError, DbusObject,
DbusObjectData, DbusSocket, DbusType, ErrorMessage, Formatter, HDR_DESTINATION,
HDR_ERROR_NAME, HDR_INTERFACE, HDR_MEMBER, HDR_PATH, HDR_REPLY_SERIAL, HDR_SIGNATURE,
HDR_UNIX_FDS, Headers, InterfaceSignalHandlers, MSG_ERROR, MSG_METHOD_CALL,
MSG_METHOD_RETURN, MSG_SIGNAL, Message, MethodCall, NO_REPLY_EXPECTED, Parser,
Property, PropertyGetAllHandlerProxy, PropertyGetHandlerProxy, Reply, ReplyHandler,
Signal, SignalHandler, SignalHandlerApi, SignalHandlerData,
property::Get,
types::{ObjectPath, Signature, Variant},
},
@ -14,8 +15,8 @@ use {
wire_dbus::org,
},
std::{
cell::Cell, collections::hash_map::Entry, fmt::Write, marker::PhantomData, mem,
ops::DerefMut, rc::Rc,
borrow::Cow, cell::Cell, collections::hash_map::Entry, fmt::Write, marker::PhantomData,
mem, ops::DerefMut, rc::Rc,
},
uapi::c,
};
@ -132,6 +133,46 @@ impl DbusSocket {
}
}
pub fn add_object(
self: &Rc<Self>,
object: impl Into<Cow<'static, str>>,
) -> Result<DbusObject, DbusError> {
let object = object.into();
let data = Rc::new(DbusObjectData {
path: object.clone(),
methods: Default::default(),
properties: Default::default(),
});
match self.objects.lock().entry(object) {
Entry::Occupied(_) => Err(DbusError::AlreadyHandled),
Entry::Vacant(v) => {
v.insert(data.clone());
data.methods.set(
crate::dbus::MemberHandlerOwnedKey {
key: crate::dbus::MemberHandlerKey {
interface: org::freedesktop::dbus::properties::Get::INTERFACE,
member: org::freedesktop::dbus::properties::Get::MEMBER,
},
},
Rc::new(PropertyGetHandlerProxy),
);
data.methods.set(
crate::dbus::MemberHandlerOwnedKey {
key: crate::dbus::MemberHandlerKey {
interface: org::freedesktop::dbus::properties::GetAll::INTERFACE,
member: org::freedesktop::dbus::properties::GetAll::MEMBER,
},
},
Rc::new(PropertyGetAllHandlerProxy),
);
Ok(DbusObject {
socket: self.clone(),
data,
})
}
}
}
pub fn handle_signal<T, F>(
self: &Rc<Self>,
sender: Option<&str>,
@ -250,6 +291,23 @@ impl DbusSocket {
serial
}
pub fn emit_signal<'a, T: Signal<'a>>(&self, path: &str, msg: &T) -> u32 {
let (msg, serial) = self.format_signal(path, msg);
self.bufio.send(msg);
serial
}
pub fn send_reply<'a, T: Message<'a>>(
&self,
destination: &str,
reply_serial: u32,
msg: &T,
) -> u32 {
let (msg, serial) = self.format_reply(destination, reply_serial, msg);
self.bufio.send(msg);
serial
}
fn send_call<'a, T: Message<'a>>(
&self,
path: &str,
@ -277,6 +335,29 @@ impl DbusSocket {
)
}
fn format_signal<'a, T: Signal<'a>>(&self, path: &str, msg: &T) -> (BufIoMessage, u32) {
self.format_generic(MSG_SIGNAL, Some(path), None, None, 0, msg, None, true, true)
}
fn format_reply<'a, T: Message<'a>>(
&self,
destination: &str,
reply_serial: u32,
msg: &T,
) -> (BufIoMessage, u32) {
self.format_generic(
MSG_METHOD_RETURN,
None,
Some(reply_serial),
Some(destination),
0,
msg,
None,
true,
true,
)
}
fn format_call<'a, T: Message<'a>>(
&self,
path: &str,

View file

@ -1 +1,57 @@
pub use jay_formats::*;
use {
crate::pipewire::pw_pod::{
SPA_VIDEO_FORMAT_ABGR_210LE, SPA_VIDEO_FORMAT_ARGB_210LE, SPA_VIDEO_FORMAT_BGR,
SPA_VIDEO_FORMAT_BGR15, SPA_VIDEO_FORMAT_BGR16, SPA_VIDEO_FORMAT_BGRA,
SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_GRAY8, SPA_VIDEO_FORMAT_RGB,
SPA_VIDEO_FORMAT_RGB16, SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_RGBx,
SPA_VIDEO_FORMAT_UNKNOWN, SPA_VIDEO_FORMAT_xBGR_210LE, SPA_VIDEO_FORMAT_xRGB_210LE,
SpaVideoFormat,
},
ahash::AHashMap,
std::sync::LazyLock,
};
static PW_FORMATS_MAP: LazyLock<AHashMap<SpaVideoFormat, &'static Format>> = LazyLock::new(|| {
let mut map = AHashMap::new();
for format in FORMATS {
let pw = pw_format(format);
if pw != SPA_VIDEO_FORMAT_UNKNOWN {
assert!(map.insert(pw, format).is_none());
}
}
map
});
pub fn pw_formats() -> &'static AHashMap<SpaVideoFormat, &'static Format> {
&PW_FORMATS_MAP
}
pub fn pipewire_format(format: &'static Format) -> SpaVideoFormat {
pw_format(format)
}
const fn fourcc_code(a: char, b: char, c: char, d: char) -> u32 {
(a as u32) | ((b as u32) << 8) | ((c as u32) << 16) | ((d as u32) << 24)
}
fn pw_format(format: &Format) -> SpaVideoFormat {
match format.drm {
drm if drm == fourcc_code('A', 'R', '2', '4') => SPA_VIDEO_FORMAT_BGRA,
drm if drm == fourcc_code('X', 'R', '2', '4') => SPA_VIDEO_FORMAT_BGRx,
drm if drm == fourcc_code('A', 'B', '2', '4') => SPA_VIDEO_FORMAT_RGBA,
drm if drm == fourcc_code('X', 'B', '2', '4') => SPA_VIDEO_FORMAT_RGBx,
drm if drm == fourcc_code('R', '8', ' ', ' ') => SPA_VIDEO_FORMAT_GRAY8,
drm if drm == fourcc_code('R', 'G', '2', '4') => SPA_VIDEO_FORMAT_BGR,
drm if drm == fourcc_code('B', 'G', '2', '4') => SPA_VIDEO_FORMAT_RGB,
drm if drm == fourcc_code('R', 'G', '1', '6') => SPA_VIDEO_FORMAT_BGR16,
drm if drm == fourcc_code('B', 'G', '1', '6') => SPA_VIDEO_FORMAT_RGB16,
drm if drm == fourcc_code('X', 'R', '1', '5') => SPA_VIDEO_FORMAT_BGR15,
drm if drm == fourcc_code('A', 'R', '3', '0') => SPA_VIDEO_FORMAT_ARGB_210LE,
drm if drm == fourcc_code('X', 'R', '3', '0') => SPA_VIDEO_FORMAT_xRGB_210LE,
drm if drm == fourcc_code('A', 'B', '3', '0') => SPA_VIDEO_FORMAT_ABGR_210LE,
drm if drm == fourcc_code('X', 'B', '3', '0') => SPA_VIDEO_FORMAT_xBGR_210LE,
_ => SPA_VIDEO_FORMAT_UNKNOWN,
}
}

View file

@ -570,6 +570,33 @@ impl dyn GfxFramebuffer {
)
}
pub fn render_custom(
self: &Rc<Self>,
acquire_sync: AcquireSync,
release_sync: ReleaseSync,
cd: &Rc<ColorDescription>,
scale: Scale,
clear: Option<&Color>,
clear_cd: &Rc<LinearColorDescription>,
blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
blend_cd: &Rc<ColorDescription>,
f: &mut dyn FnMut(&mut RendererBase),
) -> Result<Option<FdSync>, GfxError> {
let mut ops = vec![];
let mut renderer = self.renderer_base(&mut ops, scale, Transform::None);
f(&mut renderer);
self.render(
acquire_sync,
release_sync,
cd,
&ops,
clear,
clear_cd,
blend_buffer,
blend_cd,
)
}
pub fn create_render_pass(
&self,
node: &dyn Node,
@ -930,6 +957,44 @@ pub struct GfxFormat {
pub supports_shm: bool,
}
impl GfxFormat {
pub fn cross_intersect(&self, other: &GfxFormat) -> GfxFormat {
assert_eq!(self.format, other.format);
let other_write_modifiers: IndexSet<_> = other.write_modifiers.keys().copied().collect();
GfxFormat {
format: self.format,
read_modifiers: self
.read_modifiers
.intersection(&other_write_modifiers)
.copied()
.collect(),
write_modifiers: self
.write_modifiers
.iter()
.filter(|(m, _)| other.read_modifiers.contains(*m))
.map(|(&k, v)| (k, v.clone()))
.collect(),
supports_shm: self.supports_shm && other.supports_shm,
}
}
}
pub fn cross_intersect_formats(
local: &AHashMap<u32, GfxFormat>,
remote: &AHashMap<u32, GfxFormat>,
) -> AHashMap<u32, GfxFormat> {
let mut res = AHashMap::new();
for lf in local.values() {
if let Some(rf) = remote.get(&lf.format.drm) {
let f = lf.cross_intersect(rf);
if !f.read_modifiers.is_empty() || !f.write_modifiers.is_empty() || f.supports_shm {
res.insert(f.format.drm, f);
}
}
}
res
}
#[derive(Error)]
#[error(transparent)]
pub struct GfxError(pub Box<dyn Error + Send>);

View file

@ -28,6 +28,7 @@ pub mod jay_popup_ext_manager_v1;
pub mod jay_randr;
pub mod jay_reexec;
pub mod jay_render_ctx;
pub mod jay_screencast;
pub mod jay_screenshot;
pub mod jay_seat_events;
pub mod jay_select_toplevel;

View file

@ -15,6 +15,7 @@ use {
jay_randr::JayRandr,
jay_reexec::JayReexec,
jay_render_ctx::JayRenderCtx,
jay_screencast::JayScreencast,
jay_screenshot::JayScreenshot,
jay_seat_events::JaySeatEvents,
jay_select_toplevel::{JaySelectToplevel, JayToplevelSelector},
@ -41,7 +42,9 @@ use {
thiserror::Error,
};
pub const CREATE_EI_SESSION_SINCE: Version = Version(5);
pub const SCREENSHOT_SPLITUP_SINCE: Version = Version(6);
pub const GET_TOPLEVEL_SINCE: Version = Version(12);
pub struct JayCompositorGlobal {
name: GlobalName,
@ -92,6 +95,7 @@ pub struct Cap;
impl Cap {
pub const NONE: u16 = 0;
pub const WINDOW_CAPTURE: u16 = 1;
pub const SELECT_WORKSPACE: u16 = 2;
}
@ -99,7 +103,7 @@ impl JayCompositor {
fn send_capabilities(&self) {
self.client.event(Capabilities {
self_id: self.id,
cap: &[Cap::NONE, Cap::SELECT_WORKSPACE],
cap: &[Cap::NONE, Cap::WINDOW_CAPTURE, Cap::SELECT_WORKSPACE],
});
}
@ -342,6 +346,13 @@ impl JayCompositorRequestHandler for JayCompositor {
Ok(())
}
fn create_screencast(&self, req: CreateScreencast, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let sc = Rc::new_cyclic(|slf| JayScreencast::new(req.id, &self.client, slf, self.version));
track!(self.client, sc);
self.client.add_client_obj(&sc)?;
Ok(())
}
fn select_toplevel(&self, req: SelectToplevel, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let obj = JaySelectToplevel::new(&self.client, req.id, self.version);
track!(self.client, obj);

796
src/ifs/jay_screencast.rs Normal file
View file

@ -0,0 +1,796 @@
use {
crate::{
allocator::{AllocatorError, BO_USE_LINEAR, BO_USE_RENDERING, BufferObject},
client::{Client, ClientError},
cmm::cmm_description::ColorDescription,
format::XRGB8888,
gfx_api::{
AcquireSync, BufferResv, GfxContext, GfxError, GfxFramebuffer, GfxTexture, ReleaseSync,
},
ifs::{jay_output::JayOutput, jay_toplevel::JayToplevel, wl_buffer::WlBufferStorage},
leaks::Tracker,
object::{Object, Version},
scale::Scale,
state::State,
tree::{
LatchListener, OutputNode, ToplevelNode, Transform, WorkspaceNode, WorkspaceNodeId,
},
utils::{
clonecell::{CloneCell, UnsafeCellCloneSafe},
errorfmt::ErrorFmt,
event_listener::EventListener,
numcell::NumCell,
option_ext::OptionExt,
},
video::{INVALID_MODIFIER, LINEAR_MODIFIER, dmabuf::DmaBuf},
wire::{JayScreencastId, jay_screencast::*},
},
ahash::AHashSet,
std::{
cell::{Cell, RefCell},
ops::DerefMut,
rc::{Rc, Weak},
},
thiserror::Error,
};
pub async fn perform_toplevel_screencasts(state: Rc<State>) {
loop {
let screencast = state.pending_toplevel_screencasts.pop().await;
screencast.perform_toplevel_screencast();
}
}
pub async fn perform_screencast_realloc(state: Rc<State>) {
loop {
let screencast = state
.pending_screencast_reallocs_or_reconfigures
.pop()
.await;
screencast.realloc_or_reconfigure_scheduled.set(false);
match state.render_ctx.get() {
None => screencast.do_destroy(),
Some(ctx) => {
if let Err(e) = screencast.realloc(&ctx) {
screencast.client.error(e);
}
}
}
}
}
pub const CLIENT_BUFFERS_SINCE: Version = Version(7);
pub struct JayScreencast {
pub id: JayScreencastId,
pub version: Version,
pub client: Rc<Client>,
pub tracker: Tracker<Self>,
config_serial: NumCell<u32>,
config_acked: Cell<bool>,
buffers_serial: NumCell<u32>,
buffers_acked: Cell<bool>,
buffers: RefCell<Vec<ScreencastBuffer>>,
missed_frame: Cell<bool>,
target: CloneCell<Option<Target>>,
destroyed: Cell<bool>,
running: Cell<bool>,
show_all: Cell<bool>,
show_workspaces: RefCell<AHashSet<WorkspaceNodeId>>,
linear: Cell<bool>,
pending: Pending,
need_realloc_or_reconfigure: Cell<bool>,
realloc_or_reconfigure_scheduled: Cell<bool>,
latch_listener: EventListener<dyn LatchListener>,
}
#[derive(Clone)]
enum Target {
Output(Rc<OutputNode>),
Toplevel(Rc<dyn ToplevelNode>),
}
impl LatchListener for JayScreencast {
fn after_latch(self: Rc<Self>, _on: &OutputNode, _tearing: bool) {
self.schedule_toplevel_screencast();
}
}
unsafe impl UnsafeCellCloneSafe for Target {}
enum PendingTarget {
Output(Rc<JayOutput>),
Toplevel(Rc<JayToplevel>),
}
#[derive(Default)]
struct Pending {
linear: Cell<Option<bool>>,
running: Cell<Option<bool>>,
target: Cell<Option<Option<PendingTarget>>>,
show_all: Cell<Option<bool>>,
show_workspaces: RefCell<Option<AHashSet<WorkspaceNodeId>>>,
clear_buffers: Cell<bool>,
buffers: RefCell<Vec<Rc<dyn GfxFramebuffer>>>,
}
struct ScreencastBuffer {
_bo: Option<Rc<dyn BufferObject>>,
dmabuf: Option<DmaBuf>,
fb: Rc<dyn GfxFramebuffer>,
free: bool,
}
impl JayScreencast {
pub fn shows_ws(&self, ws: &WorkspaceNode) -> bool {
if self.show_all.get() {
return true;
}
for &id in &*self.show_workspaces.borrow() {
if id == ws.id {
return true;
}
}
false
}
pub fn new(
id: JayScreencastId,
client: &Rc<Client>,
slf: &Weak<Self>,
version: Version,
) -> Self {
Self {
id,
version,
client: client.clone(),
tracker: Default::default(),
config_serial: Default::default(),
config_acked: Cell::new(true),
buffers_serial: Default::default(),
buffers_acked: Cell::new(true),
buffers: Default::default(),
missed_frame: Cell::new(false),
target: Default::default(),
destroyed: Cell::new(false),
running: Cell::new(false),
show_all: Cell::new(false),
show_workspaces: Default::default(),
linear: Cell::new(false),
pending: Default::default(),
need_realloc_or_reconfigure: Cell::new(false),
realloc_or_reconfigure_scheduled: Cell::new(false),
latch_listener: EventListener::new(slf.clone()),
}
}
fn schedule_toplevel_screencast(self: &Rc<Self>) {
if !self.running.get() {
return;
}
self.client
.state
.pending_toplevel_screencasts
.push(self.clone());
}
fn perform_toplevel_screencast(&self) {
if self.destroyed.get() || !self.running.get() {
return;
}
let Some(target) = self.target.get() else {
return;
};
let Target::Toplevel(tl) = target else {
log::warn!("Tried to perform window screencast for output screencast");
return;
};
let scale = match tl.tl_data().workspace.get() {
None => Scale::default(),
Some(w) => w.output.get().global.persistent.scale.get(),
};
let mut buffer = self.buffers.borrow_mut();
for (idx, buffer) in buffer.deref_mut().iter_mut().enumerate() {
if buffer.free {
let res = buffer.fb.render_node(
AcquireSync::Implicit,
ReleaseSync::Implicit,
self.client.state.color_manager.srgb_gamma22(),
&*tl,
&self.client.state,
Some(tl.node_absolute_position()),
scale,
true,
true,
false,
false,
Transform::None,
None,
self.client.state.color_manager.srgb_linear(),
);
match res {
Ok(_) => {
self.client.event(Ready {
self_id: self.id,
idx: idx as _,
});
buffer.free = false;
return;
}
Err(e) => {
log::error!("Could not perform window copy: {}", ErrorFmt(e));
break;
}
}
}
}
self.missed_frame.set(true);
self.client.event(MissedFrame { self_id: self.id })
}
fn send_buffers(&self) {
self.buffers_acked.set(false);
let serial = self.buffers_serial.fetch_add(1) + 1;
let buffers = self.buffers.borrow_mut();
for buffer in buffers.iter() {
let Some(dmabuf) = &buffer.dmabuf else {
log::error!("Trying to send buffers but buffers are client allocated");
self.do_destroy();
return;
};
for plane in &dmabuf.planes {
self.client.event(Plane {
self_id: self.id,
fd: plane.fd.clone(),
offset: plane.offset,
stride: plane.stride,
});
}
self.client.event(Buffer {
self_id: self.id,
format: dmabuf.format.drm,
modifier: dmabuf.modifier,
width: dmabuf.width,
height: dmabuf.height,
});
}
self.client.event(BuffersDone {
self_id: self.id,
serial,
});
}
fn send_config(&self) {
self.need_realloc_or_reconfigure.set(false);
self.config_acked.set(false);
let serial = self.config_serial.fetch_add(1) + 1;
if let Some(target) = self.target.get() {
let (width, height) = target_size(Some(&target));
if self.version >= CLIENT_BUFFERS_SINCE {
self.client.event(ConfigSize {
self_id: self.id,
width,
height,
});
}
if let Target::Output(output) = target {
self.client.event(ConfigOutput {
self_id: self.id,
linear_id: output.id.raw(),
});
}
}
self.client.event(ConfigAllowAllWorkspaces {
self_id: self.id,
allow_all: self.show_all.get() as _,
});
for &ws in self.show_workspaces.borrow_mut().iter() {
self.client.event(ConfigAllowWorkspace {
self_id: self.id,
linear_id: ws.raw(),
});
}
self.client.event(ConfigUseLinearBuffers {
self_id: self.id,
use_linear: self.linear.get() as _,
});
self.client.event(ConfigRunning {
self_id: self.id,
running: self.running.get() as _,
});
self.client.event(ConfigDone {
self_id: self.id,
serial,
});
}
pub fn copy_texture(
&self,
on: &OutputNode,
texture: &Rc<dyn GfxTexture>,
cd: &Rc<ColorDescription>,
resv: Option<&Rc<dyn BufferResv>>,
acquire_sync: &AcquireSync,
release_sync: ReleaseSync,
render_hardware_cursors: bool,
x_off: i32,
y_off: i32,
size: Option<(i32, i32)>,
) {
if !self.running.get() {
return;
}
if !self.show_all.get() {
let ws = match on.workspace.get() {
Some(ws) => ws,
_ => return,
};
if !self.show_workspaces.borrow_mut().contains(&ws.id) {
return;
}
}
let mut buffer = self.buffers.borrow_mut();
for (idx, buffer) in buffer.deref_mut().iter_mut().enumerate() {
if buffer.free {
let res = self.client.state.perform_screencopy(
texture,
resv,
acquire_sync,
release_sync,
cd,
&buffer.fb,
AcquireSync::Implicit,
ReleaseSync::Implicit,
Transform::None,
self.client.state.color_manager.srgb_gamma22(),
on.global.pos.get(),
render_hardware_cursors,
x_off,
y_off,
size,
on.global.persistent.transform.get(),
on.global.persistent.scale.get(),
);
match res {
Ok(_) => {
self.client.event(Ready {
self_id: self.id,
idx: idx as _,
});
buffer.free = false;
return;
}
Err(e) => {
log::error!("Could not perform screencopy: {}", ErrorFmt(e));
break;
}
}
}
}
self.missed_frame.set(true);
self.client.event(MissedFrame { self_id: self.id })
}
fn detach(&self) {
self.latch_listener.detach();
if let Some(target) = self.target.take() {
match target {
Target::Output(output) => {
output.remove_screencast(self);
}
Target::Toplevel(tl) => {
let data = tl.tl_data();
data.jay_screencasts.remove(&(self.client.id, self.id));
}
}
}
}
pub fn do_destroy(&self) {
self.detach();
self.buffers.borrow_mut().clear();
self.destroyed.set(true);
self.client.event(Destroyed { self_id: self.id });
}
pub fn schedule_realloc_or_reconfigure(self: &Rc<Self>) {
self.need_realloc_or_reconfigure.set(true);
if !self.realloc_or_reconfigure_scheduled.replace(true) {
self.client
.state
.pending_screencast_reallocs_or_reconfigures
.push(self.clone());
}
}
fn realloc(&self, ctx: &Rc<dyn GfxContext>) -> Result<(), JayScreencastError> {
if self.destroyed.get() {
return Ok(());
}
if self.version < CLIENT_BUFFERS_SINCE {
if self.buffers_acked.get() {
return self.do_realloc(ctx);
}
} else {
if self.config_acked.get() {
self.send_config();
}
}
Ok(())
}
fn do_realloc(&self, ctx: &Rc<dyn GfxContext>) -> Result<(), JayScreencastError> {
self.need_realloc_or_reconfigure.set(false);
let mut buffers = vec![];
let formats = ctx.formats();
let format = match formats.get(&XRGB8888.drm) {
Some(f) => f,
_ => return Err(JayScreencastError::XRGB8888),
};
if let Some(target) = self.target.get() {
let (width, height) = target_size(Some(&target));
let num = 3;
for _ in 0..num {
if width == 0 || height == 0 {
continue;
}
let mut usage = BO_USE_RENDERING;
let modifiers = match self.linear.get() {
true if format.write_modifiers.contains_key(&LINEAR_MODIFIER) => {
vec![LINEAR_MODIFIER]
}
true if format.write_modifiers.contains_key(&INVALID_MODIFIER) => {
usage |= BO_USE_LINEAR;
vec![INVALID_MODIFIER]
}
true => return Err(JayScreencastError::Modifier),
false if format.write_modifiers.is_empty() => {
return Err(JayScreencastError::XRGB8888Writing);
}
false => format.write_modifiers.keys().copied().collect(),
};
let buffer = ctx.allocator().create_bo(
&self.client.state.dma_buf_ids,
width,
height,
format.format,
&modifiers,
usage,
)?;
let fb = ctx.clone().dmabuf_img(buffer.dmabuf())?.to_framebuffer()?;
buffers.push(ScreencastBuffer {
dmabuf: Some(buffer.dmabuf().clone()),
_bo: Some(buffer),
fb,
free: true,
});
}
}
*self.buffers.borrow_mut() = buffers;
self.send_buffers();
self.damage();
Ok(())
}
fn damage(&self) {
if let Some(target) = self.target.get() {
let rect = match target {
Target::Output(o) => o.global.pos.get(),
Target::Toplevel(t) => {
if !t.node_visible() {
return;
}
t.node_absolute_position()
}
};
self.client.state.damage(rect);
}
}
pub fn update_latch_listener(&self) {
let Some(Target::Toplevel(tl)) = self.target.get() else {
return;
};
let data = tl.tl_data();
if data.visible.get() {
self.latch_listener.attach(&data.output().latch_event);
} else {
self.latch_listener.detach();
}
}
}
impl JayScreencastRequestHandler for JayScreencast {
type Error = JayScreencastError;
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.detach();
self.client.remove_obj(self)?;
Ok(())
}
fn set_output(&self, req: SetOutput, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let output = if req.output.is_some() {
Some(PendingTarget::Output(self.client.lookup(req.output)?))
} else {
None
};
if self.destroyed.get() {
return Ok(());
}
self.pending.target.set(Some(output));
Ok(())
}
fn set_allow_all_workspaces(
&self,
req: SetAllowAllWorkspaces,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
if self.destroyed.get() {
return Ok(());
}
self.pending.show_all.set(Some(req.allow_all != 0));
Ok(())
}
fn allow_workspace(&self, req: AllowWorkspace, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let ws = self.client.lookup(req.workspace)?;
if self.destroyed.get() {
return Ok(());
}
let mut sw = self.pending.show_workspaces.borrow_mut();
let sw = sw.get_or_insert_default_ext();
if let Some(ws) = ws.workspace.get() {
sw.insert(ws.id);
}
Ok(())
}
fn touch_allowed_workspaces(
&self,
_req: TouchAllowedWorkspaces,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
if self.destroyed.get() {
return Ok(());
}
self.pending
.show_workspaces
.borrow_mut()
.get_or_insert_default_ext();
Ok(())
}
fn set_use_linear_buffers(
&self,
req: SetUseLinearBuffers,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
if self.destroyed.get() {
return Ok(());
}
self.pending.linear.set(Some(req.use_linear != 0));
Ok(())
}
fn set_running(&self, req: SetRunning, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if self.destroyed.get() {
return Ok(());
}
self.pending.running.set(Some(req.running != 0));
Ok(())
}
fn configure(&self, _req: Configure, slf: &Rc<Self>) -> Result<(), Self::Error> {
if self.destroyed.get() {
return Ok(());
}
let mut need_realloc_or_reconfigure = false;
if let Some(target) = self.pending.target.take() {
self.detach();
let mut new_target = None;
if let Some(new) = target {
match new {
PendingTarget::Output(o) => {
let Some(o) = o.output.node() else {
self.do_destroy();
return Ok(());
};
o.add_screencast(slf);
new_target = Some(Target::Output(o));
}
PendingTarget::Toplevel(t) => {
if t.destroyed.get() {
self.do_destroy();
return Ok(());
}
let t = t.toplevel.clone();
let data = t.tl_data();
data.jay_screencasts
.set((self.client.id, self.id), slf.clone());
if data.visible.get() {
self.latch_listener.attach(&data.output().latch_event);
}
new_target = Some(Target::Toplevel(t));
}
}
}
if target_size(new_target.as_ref()) != target_size(self.target.get().as_ref()) {
need_realloc_or_reconfigure = true;
}
self.target.set(new_target);
}
if let Some(linear) = self.pending.linear.take() {
if self.linear.replace(linear) != linear {
need_realloc_or_reconfigure = true;
}
}
if self.pending.clear_buffers.take() {
self.buffers.borrow_mut().clear();
}
for buffer in self.pending.buffers.borrow_mut().drain(..) {
self.buffers.borrow_mut().push(ScreencastBuffer {
_bo: None,
dmabuf: None,
fb: buffer,
free: true,
});
}
let mut capture_rules_changed = false;
if let Some(show_all) = self.pending.show_all.take() {
self.show_all.set(show_all);
capture_rules_changed = true;
}
if let Some(new_workspaces) = self.pending.show_workspaces.borrow_mut().take() {
*self.show_workspaces.borrow_mut() = new_workspaces;
capture_rules_changed = true;
}
if let Some(running) = self.pending.running.take() {
self.running.set(running);
}
if need_realloc_or_reconfigure {
slf.schedule_realloc_or_reconfigure();
}
if capture_rules_changed && let Some(Target::Output(o)) = self.target.get() {
o.screencast_changed();
}
if self.running.get() {
self.damage();
}
Ok(())
}
fn ack_buffers(&self, req: AckBuffers, slf: &Rc<Self>) -> Result<(), Self::Error> {
if self.destroyed.get() {
return Ok(());
}
if req.serial == self.buffers_serial.get() {
self.buffers_acked.set(true);
if self.need_realloc_or_reconfigure.get() {
slf.schedule_realloc_or_reconfigure();
}
}
Ok(())
}
fn ack_config(&self, req: AckConfig, slf: &Rc<Self>) -> Result<(), Self::Error> {
if self.destroyed.get() {
return Ok(());
}
if req.serial == self.config_serial.get() {
self.config_acked.set(true);
if self.need_realloc_or_reconfigure.get() {
slf.schedule_realloc_or_reconfigure();
}
}
Ok(())
}
fn release_buffer(&self, req: ReleaseBuffer, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if self.destroyed.get() || !self.buffers_acked.get() {
return Ok(());
}
let idx = req.idx as usize;
if idx >= self.buffers.borrow_mut().len() {
return Err(JayScreencastError::OutOfBounds(req.idx));
}
self.buffers.borrow_mut()[idx].free = true;
if self.missed_frame.replace(false) {
self.damage();
}
Ok(())
}
fn set_toplevel(&self, req: SetToplevel, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let toplevel = if req.id.is_some() {
Some(PendingTarget::Toplevel(self.client.lookup(req.id)?))
} else {
None
};
if self.destroyed.get() {
return Ok(());
}
self.pending.target.set(Some(toplevel));
Ok(())
}
fn clear_buffers(&self, _req: ClearBuffers, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if self.destroyed.get() {
return Ok(());
}
self.pending.clear_buffers.set(true);
Ok(())
}
fn add_buffer(&self, req: AddBuffer, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if self.destroyed.get() {
return Ok(());
}
let buffer = self.client.lookup(req.buffer)?;
if let Some(WlBufferStorage::Dmabuf { img, .. }) = &*buffer.storage.borrow() {
match img.clone().to_framebuffer() {
Ok(fb) => self.pending.buffers.borrow_mut().push(fb),
Err(e) => {
log::warn!(
"Could not turn GfxImage into GfxFramebuffer: {}",
ErrorFmt(e)
);
self.do_destroy();
}
}
return Ok(());
}
Err(JayScreencastError::NotDmabuf)
}
}
object_base! {
self = JayScreencast;
version = self.version;
}
impl Object for JayScreencast {
fn break_loops(&self) {
self.detach();
}
}
dedicated_add_obj!(JayScreencast, JayScreencastId, screencasts);
#[derive(Debug, Error)]
pub enum JayScreencastError {
#[error(transparent)]
ClientError(Box<ClientError>),
#[error("Buffer index {0} is out-of-bounds")]
OutOfBounds(u32),
#[error(transparent)]
AllocatorError(#[from] AllocatorError),
#[error(transparent)]
GfxError(#[from] GfxError),
#[error("Render context does not support XRGB8888 format")]
XRGB8888,
#[error("Render context does not support XRGB8888 format for rendering")]
XRGB8888Writing,
#[error("Render context supports neither linear or invalid modifier")]
Modifier,
#[error("Buffer is not a dmabuf")]
NotDmabuf,
}
efrom!(JayScreencastError, ClientError);
fn target_size(target: Option<&Target>) -> (i32, i32) {
if let Some(target) = target {
return match target {
Target::Output(o) => o.global.pixel_size(),
Target::Toplevel(t) => t.tl_data().desired_pixel_size(),
};
}
(0, 0)
}

View file

@ -11,6 +11,33 @@ macro_rules! efrom {
};
}
macro_rules! usr_object_base {
($self:ident = $oname:ident = $iname:ident; version = $version:expr;) => {
impl crate::wl_usr::usr_object::UsrObjectBase for $oname {
fn id(&$self) -> crate::object::ObjectId {
$self.id.into()
}
fn version(&$self) -> crate::object::Version {
$version
}
fn handle_event(
$self: std::rc::Rc<Self>,
con: &crate::wl_usr::UsrCon,
event: u32,
parser: crate::utils::buffd::MsgParser<'_, '_>,
) -> Result<(), crate::wl_usr::UsrConError> {
$self.handle_event_impl(con, event, parser)
}
fn interface(&$self) -> crate::object::Interface {
crate::wire::$iname
}
}
};
}
macro_rules! object_base {
($self:ident = $oname:ident; version = $version:expr;) => {
impl crate::object::ObjectBase for $oname {
@ -171,6 +198,40 @@ macro_rules! linear_ids {
};
}
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 {
#[expect(dead_code)]
pub fn raw(&self) -> $ty {
self.0
}
#[expect(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)
}
}
};
}
#[expect(unused_macros)]
macro_rules! bitor {
($name:ident) => {
@ -637,6 +698,90 @@ macro_rules! ei_object_base {
};
}
macro_rules! pw_opcodes {
($name:ident; $($var:ident = $val:expr,)*) => {
#[derive(Copy, Clone, Debug)]
pub enum $name {
$(
$var,
)*
}
#[allow(clippy::allow_attributes, dead_code)]
impl $name {
pub fn from_id(id: u8) -> Option<Self> {
let v = match id {
$($val => Self::$var,)*
_ => return None,
};
Some(v)
}
pub fn name(self) -> &'static str {
match self {
$(Self::$var => stringify!($var),)*
}
}
}
impl crate::pipewire::pw_object::PwOpcode for $name {
fn id(&self) -> u8 {
match self {
$(Self::$var => $val,)*
}
}
}
}
}
macro_rules! pw_object_base {
($name:ident, $if:expr, $events:ident; $($event:ident => $method:ident,)*) => {
impl crate::pipewire::pw_object::PwObjectBase for $name {
fn data(&self) -> &crate::pipewire::pw_object::PwObjectData {
&self.data
}
fn interface(&self) -> &str {
$if
}
fn handle_msg(
self: std::rc::Rc<Self>,
opcode: u8,
parser: crate::pipewire::pw_parser::PwParser<'_>,
) -> Result<(), crate::pipewire::pw_object::PwObjectError> {
match $events::from_id(opcode) {
None => Err(crate::pipewire::pw_object::PwObjectError {
interface: $if,
source: crate::pipewire::pw_object::PwObjectErrorType::UnknownEvent(opcode),
}),
Some(m) => {
let (res, method) = match m {
$(
$events::$event => (self.$method(parser), stringify!($event)),
)*
};
match res {
Ok(_) => Ok(()),
Err(source) => Err(crate::pipewire::pw_object::PwObjectError {
interface: $if,
source: crate::pipewire::pw_object::PwObjectErrorType::EventError {
method,
source: Box::new(source),
},
}),
}
}
}
}
fn event_name(&self, opcode: u8) -> Option<&'static str> {
$events::from_id(opcode).map(|o| o.name())
}
}
}
}
macro_rules! logical_to_client_wire_scale {
($client:expr, $($field:expr),+ $(,)?) => {
#[expect(clippy::allow_attributes)]

View file

@ -92,6 +92,8 @@ mod logind;
mod object;
mod output_schedule;
mod pango;
mod pipewire;
mod portal;
mod pr_caps;
mod rect;
mod renderer;
@ -119,6 +121,7 @@ mod wire;
mod wire_dbus;
mod wire_ei;
mod wire_xcon;
mod wl_usr;
mod xcon;
mod xwayland;

7
src/pipewire.rs Normal file
View file

@ -0,0 +1,7 @@
pub mod pw_con;
pub mod pw_formatter;
pub mod pw_ifs;
pub mod pw_mem;
pub mod pw_object;
pub mod pw_parser;
pub mod pw_pod;

453
src/pipewire/pw_con.rs Normal file
View file

@ -0,0 +1,453 @@
use {
crate::{
async_engine::{AsyncEngine, SpawnedFuture},
io_uring::{IoUring, IoUringError},
pipewire::{
pw_formatter::{PwFormatter, format},
pw_ifs::{
pw_client::{PwClient, PwClientMethods},
pw_client_node::{
PW_CLIENT_NODE_FACTORY, PW_CLIENT_NODE_INTERFACE, PW_CLIENT_NODE_VERSION,
PwClientNode,
},
pw_core::{PW_CORE_VERSION, PwCore, PwCoreMethods},
pw_registry::{PW_REGISTRY_VERSION, PwRegistry},
},
pw_mem::PwMemPool,
pw_object::{PwObject, PwObjectData, PwObjectError, PwOpcode},
pw_parser::{PwParser, PwParserError},
},
utils::{
bitfield::Bitfield,
bufio::{BufIo, BufIoError, BufIoIncoming, BufIoMessage},
clonecell::CloneCell,
copyhashmap::CopyHashMap,
errorfmt::ErrorFmt,
hash_map_ext::HashMapExt,
numcell::NumCell,
oserror::{OsError, OsErrorExt2},
xrd::xrd,
},
},
std::{
cell::{Cell, RefCell},
fmt::Display,
io::Write,
rc::{Rc, Weak},
},
thiserror::Error,
uapi::{OwnedFd, c},
};
#[derive(Debug, Error)]
pub enum PwConError {
#[error("Could not create a unix socket")]
CreateSocket(#[source] OsError),
#[error("Could not connect to the pipewire daemon")]
ConnectSocket(#[source] IoUringError),
#[error(transparent)]
BufIoError(#[from] BufIoError),
#[error("Server did not sent a required fd")]
MissingFd,
#[error("XDG_RUNTIME_DIR is not set")]
XrdNotSet,
#[error(transparent)]
PwObjectError(#[from] PwObjectError),
#[error(transparent)]
PwParserError(#[from] PwParserError),
}
pub struct PwConHolder {
pub con: Rc<PwCon>,
outgoing: Cell<Option<SpawnedFuture<()>>>,
incoming: Cell<Option<SpawnedFuture<()>>>,
}
pub struct PwCon {
send_seq: NumCell<u32>,
pub io: Rc<BufIo>,
holder: CloneCell<Weak<PwConHolder>>,
dead: Cell<bool>,
pub objects: CopyHashMap<u32, Rc<dyn PwObject>>,
pub ids: RefCell<Bitfield>,
pub mem: PwMemPool,
pub ring: Rc<IoUring>,
pub eng: Rc<AsyncEngine>,
pub owner: CloneCell<Option<Rc<dyn PwConOwner>>>,
registry_generation: Cell<u64>,
ack_registry_generation: Cell<u64>,
}
pub trait PwConOwner {
fn killed(&self) {}
}
impl PwCon {
pub fn create_client_node(self: &Rc<Self>, props: &[(String, String)]) -> Rc<PwClientNode> {
let node = Rc::new(PwClientNode {
data: self.proxy_data(),
con: self.clone(),
ios: Default::default(),
owner: CloneCell::new(None),
ports: Default::default(),
port_out_free: RefCell::new(Default::default()),
port_in_free: RefCell::new(Default::default()),
activation: Default::default(),
transport_in: Cell::new(None),
transport_out: Default::default(),
activations: Default::default(),
});
if !self.dead.get() {
self.objects.set(node.data.id, node.clone());
}
self.create_object(
PW_CLIENT_NODE_FACTORY,
PW_CLIENT_NODE_INTERFACE,
PW_CLIENT_NODE_VERSION,
props,
node.data.id,
);
node.send_update();
node
}
pub fn destroy_obj(&self, obj: &impl PwObject) {
obj.break_loops();
self.send2(0, "core", PwCoreMethods::Destroy, |f| {
f.write_struct(|f| {
f.write_uint(obj.data().id);
});
});
self.objects.remove(&obj.data().id);
}
pub fn kill(&self) {
for obj in self.objects.lock().drain_values() {
obj.break_loops();
}
self.io.shutdown();
self.dead.set(true);
if let Some(con) = self.holder.get().upgrade() {
con.outgoing.take();
con.incoming.take();
}
if let Some(owner) = self.owner.take() {
owner.killed();
}
}
pub fn id(&self) -> u32 {
self.ids.borrow_mut().acquire()
}
pub fn proxy_data(&self) -> PwObjectData {
PwObjectData {
id: self.id(),
bound_id: Cell::new(None),
sync_id: Default::default(),
}
}
pub fn send<P, O, F>(&self, proxy: &P, opcode: O, f: F)
where
P: PwObject,
O: PwOpcode,
F: FnOnce(&mut PwFormatter),
{
self.send2(proxy.data().id, proxy.interface(), opcode, f);
}
pub fn send2<O, F>(&self, id: u32, interface: &str, opcode: O, f: F)
where
O: PwOpcode,
F: FnOnce(&mut PwFormatter),
{
if self.dead.get() {
return;
}
let mut buf = self.io.buf();
let mut fds = vec![];
format(
&mut buf,
&mut fds,
id,
opcode.id(),
self.send_seq.fetch_add(1),
|fmt| {
f(fmt);
if self.ack_registry_generation.get() != self.registry_generation.get() {
let generation = self.registry_generation.get();
fmt.write_struct(|f| {
f.write_id(FOOTER_REGISTRY_GENERATION);
f.write_struct(|f| {
f.write_ulong(generation);
});
});
self.ack_registry_generation.set(generation);
}
},
);
if log::log_enabled!(log::Level::Trace) {
log::trace!("CALL {}@{}: `{:?}`:", interface, id, opcode);
let mut parser = PwParser::new(&buf[16..buf.len()], &fds);
while parser.len() > 0 {
log::trace!("{:#?}", parser.read_pod().unwrap());
}
}
self.io.send(BufIoMessage {
fds,
buf: buf.unwrap(),
});
}
#[expect(dead_code)]
pub fn sync<P: PwObject>(&self, p: &P) {
let seq = p.data().sync_id.fetch_add(1) + 1;
self.send2(0, "core", PwCoreMethods::Sync, |f| {
f.write_struct(|f| {
f.write_uint(p.data().id);
f.write_uint(seq);
});
});
}
pub fn send_hello(&self) {
self.send2(0, "core", PwCoreMethods::Hello, |f| {
f.write_struct(|f| f.write_int(PW_CORE_VERSION));
});
}
#[expect(dead_code)]
pub fn get_registry(self: &Rc<Self>) -> Rc<PwRegistry> {
let registry = Rc::new(PwRegistry {
data: self.proxy_data(),
_con: self.clone(),
});
if !self.dead.get() {
self.objects.set(registry.data.id, registry.clone());
}
self.send2(0, "core", PwCoreMethods::GetRegistry, |f| {
f.write_struct(|f| {
f.write_int(PW_REGISTRY_VERSION);
f.write_uint(registry.data.id);
});
});
registry
}
pub fn create_object(
&self,
factory: &str,
ty: &str,
version: i32,
props: &[(String, String)],
new_id: u32,
) {
self.send2(0, "core", PwCoreMethods::CreateObject, |f| {
f.write_struct(|f| {
f.write_string(factory);
f.write_string(ty);
f.write_int(version);
f.write_struct(|f| {
f.write_int(props.len() as _);
for (key, val) in props {
f.write_string(key);
f.write_string(val);
}
});
f.write_uint(new_id);
});
});
}
pub fn send_properties(&self) {
self.send2(1, "client", PwClientMethods::UpdateProperties, |f| {
f.write_struct(|f| {
f.write_struct(|f| {
f.write_int(1);
f.write_string("application.name");
f.write_string("jay-portal");
});
});
});
}
async fn handle_outgoing(self: Rc<Self>) {
if let Err(e) = self.io.clone().outgoing().await {
log::error!("{}", ErrorFmt(e));
}
self.kill();
}
async fn handle_incoming(self: Rc<Self>) {
let incoming = Incoming {
incoming: self.io.clone().incoming(),
con: self.clone(),
buf: vec![],
fds: vec![],
};
incoming.run().await;
}
}
impl Drop for PwConHolder {
fn drop(&mut self) {
self.con.owner.take();
self.con.kill();
}
}
impl PwConHolder {
pub async fn new(eng: &Rc<AsyncEngine>, ring: &Rc<IoUring>) -> Result<Rc<Self>, PwConError> {
let fd = uapi::socket(c::AF_UNIX, c::SOCK_STREAM | c::SOCK_CLOEXEC, 0)
.map(Rc::new)
.map_os_err(PwConError::CreateSocket)?;
let mut addr = c::sockaddr_un {
sun_family: c::AF_UNIX as _,
..uapi::pod_zeroed()
};
let xrd = match xrd() {
Some(xrd) => xrd,
_ => return Err(PwConError::XrdNotSet),
};
{
let mut path = uapi::as_bytes_mut(&mut addr.sun_path[..]);
let _ = write!(path, "{}/pipewire-0", xrd);
}
if let Err(e) = ring.connect(&fd, &addr).await {
return Err(PwConError::ConnectSocket(e));
}
let io = Rc::new(BufIo::new(&fd, ring));
let data = Rc::new(PwCon {
send_seq: Default::default(),
io,
holder: Default::default(),
dead: Cell::new(false),
objects: Default::default(),
ids: Default::default(),
mem: Default::default(),
ring: ring.clone(),
eng: eng.clone(),
owner: Default::default(),
registry_generation: Cell::new(0),
ack_registry_generation: Cell::new(0),
});
let core = Rc::new(PwCore {
data: data.proxy_data(),
con: data.clone(),
});
let client = Rc::new(PwClient {
data: data.proxy_data(),
_con: data.clone(),
});
data.objects.set(0, core.clone());
data.objects.set(1, client.clone());
data.send_hello();
data.send_properties();
let con = Rc::new(PwConHolder {
outgoing: Cell::new(Some(
eng.spawn("pw outgoing", data.clone().handle_outgoing()),
)),
incoming: Cell::new(Some(
eng.spawn("pw incoming", data.clone().handle_incoming()),
)),
con: data,
});
con.con.holder.set(Rc::downgrade(&con));
Ok(con)
}
}
struct Incoming {
con: Rc<PwCon>,
incoming: BufIoIncoming,
buf: Vec<u8>,
fds: Vec<Rc<OwnedFd>>,
}
impl Incoming {
async fn run(mut self) {
loop {
if let Err(e) = self.handle_msg().await {
log::error!("Could not handle incoming message: {}", ErrorFmt(e));
self.con.kill();
return;
}
}
}
async fn handle_msg(&mut self) -> Result<(), PwConError> {
self.buf.clear();
self.incoming.fill_msg_buf(16, &mut self.buf).await?;
let id: u32 = uapi::pod_read(&self.buf[0..4]).unwrap();
let p2: u32 = uapi::pod_read(&self.buf[4..8]).unwrap();
let opcode = (p2 >> 24) as u8;
let size = (p2 & 0xff_ffff) as usize;
let _seq: u32 = uapi::pod_read(&self.buf[8..12]).unwrap();
let n_fds: u32 = uapi::pod_read(&self.buf[12..16]).unwrap();
for _ in 0..n_fds {
match self.incoming.fds.pop_front() {
Some(fd) => self.fds.push(fd),
_ => return Err(PwConError::MissingFd),
}
}
self.buf.clear();
self.incoming.fill_msg_buf(size, &mut self.buf).await?;
if let Err(e) = self.handle_msg_data(id, opcode) {
log::warn!("Could not handle incoming message: {}", ErrorFmt(e));
}
self.fds.clear();
Ok(())
}
fn handle_msg_data(&self, id: u32, opcode: u8) -> Result<(), PwConError> {
let parser = PwParser::new(&self.buf, &self.fds);
{
let mut parser = parser;
parser.skip()?;
if parser.len() > 0 {
let s1 = parser.read_struct()?;
let mut p2 = s1.fields;
while p2.len() > 0 {
let opcode = p2.read_id()?;
let s2 = p2.read_struct()?;
if opcode == FOOTER_REGISTRY_GENERATION {
let mut p3 = s2.fields;
let generation = p3.read_ulong()?;
self.con.registry_generation.set(generation);
log::debug!("registry generation = {}", generation);
} else {
log::warn!("Unknown message footer: {}", opcode);
}
}
}
}
if let Some(obj) = self.con.objects.get(&id) {
'log: {
if log::log_enabled!(log::Level::Trace) {
let s;
let op: &dyn Display = match obj.event_name(opcode) {
Some(e) => {
s = e;
if e == "Done" && obj.interface() == "core" {
break 'log;
}
&s
}
_ => &opcode,
};
log::trace!("EVENT {}@{}: `{}`:", obj.interface(), obj.data().id, op);
let mut parser = parser;
while parser.len() > 0 {
log::trace!("{:#?}", parser.read_pod().unwrap());
}
}
}
obj.handle_msg(opcode, parser)?;
}
Ok(())
}
}
const FOOTER_REGISTRY_GENERATION: u32 = 0;

View file

@ -0,0 +1,312 @@
use {
crate::{
pipewire::pw_pod::{
PW_TYPE_Array, PW_TYPE_Bitmap, PW_TYPE_Bool, PW_TYPE_Bytes, PW_TYPE_Choice,
PW_TYPE_Double, PW_TYPE_Fd, PW_TYPE_Float, PW_TYPE_Fraction, PW_TYPE_Id, PW_TYPE_Int,
PW_TYPE_Long, PW_TYPE_None, PW_TYPE_Object, PW_TYPE_Rectangle, PW_TYPE_String,
PW_TYPE_Struct, PwChoiceType, PwPodObjectType, PwPodType, PwPropFlag,
},
utils::buf::DynamicBuf,
},
std::rc::Rc,
uapi::OwnedFd,
};
pub struct PwFormatter<'a> {
data: &'a mut DynamicBuf,
fds: &'a mut Vec<Rc<OwnedFd>>,
array: bool,
first: bool,
}
impl<'a> PwFormatter<'a> {
pub fn write_bool(&mut self, b: bool) {
if !self.array || self.first {
self.data.extend_from_slice(uapi::as_bytes(&4u32));
self.data.extend_from_slice(uapi::as_bytes(&PW_TYPE_Bool.0));
}
self.data.extend_from_slice(uapi::as_bytes(&(b as u32)));
if !self.array {
self.data.extend_from_slice(uapi::as_bytes(&0u32));
}
self.first = false;
}
pub fn write_id(&mut self, id: u32) {
if !self.array || self.first {
self.data.extend_from_slice(uapi::as_bytes(&4u32));
self.data.extend_from_slice(uapi::as_bytes(&PW_TYPE_Id.0));
}
self.data.extend_from_slice(uapi::as_bytes(&id));
if !self.array {
self.data.extend_from_slice(uapi::as_bytes(&0u32));
}
self.first = false;
}
pub fn write_object<F>(&mut self, ty: PwPodObjectType, id: u32, f: F)
where
F: FnOnce(&mut PwObjectFormatter),
{
let start = self.data.len();
self.data.extend_from_slice(uapi::as_bytes(&0u32));
self.data
.extend_from_slice(uapi::as_bytes(&PW_TYPE_Object.0));
self.data.extend_from_slice(uapi::as_bytes(&ty.0));
self.data.extend_from_slice(uapi::as_bytes(&id));
let mut fmt = PwObjectFormatter {
data: self.data,
fds: self.fds,
};
f(&mut fmt);
let len = (self.data.len() - start - 8) as u32;
self.data[start..start + 4].copy_from_slice(uapi::as_bytes(&len));
}
pub fn write_uint(&mut self, int: u32) {
self.write_int(int as _)
}
pub fn write_int(&mut self, int: i32) {
if !self.array || self.first {
self.data.extend_from_slice(uapi::as_bytes(&4u32));
self.data.extend_from_slice(uapi::as_bytes(&PW_TYPE_Int.0));
}
self.data.extend_from_slice(uapi::as_bytes(&int));
if !self.array {
self.data.extend_from_slice(uapi::as_bytes(&0u32));
}
self.first = false;
}
pub fn write_ulong(&mut self, long: u64) {
self.write_long(long as _)
}
pub fn write_long(&mut self, long: i64) {
if !self.array || self.first {
self.data.extend_from_slice(uapi::as_bytes(&8u32));
self.data.extend_from_slice(uapi::as_bytes(&PW_TYPE_Long.0));
}
self.data.extend_from_slice(uapi::as_bytes(&long));
self.first = false;
}
#[expect(dead_code)]
pub fn write_float(&mut self, float: f32) {
if !self.array || self.first {
self.data.extend_from_slice(uapi::as_bytes(&4u32));
self.data
.extend_from_slice(uapi::as_bytes(&PW_TYPE_Float.0));
}
self.data.extend_from_slice(uapi::as_bytes(&float));
if !self.array {
self.data.extend_from_slice(uapi::as_bytes(&0u32));
}
self.first = false;
}
#[expect(dead_code)]
pub fn write_double(&mut self, double: f64) {
if !self.array || self.first {
self.data.extend_from_slice(uapi::as_bytes(&8u32));
self.data
.extend_from_slice(uapi::as_bytes(&PW_TYPE_Double.0));
}
self.data.extend_from_slice(uapi::as_bytes(&double));
}
pub fn write_string<S: AsRef<[u8]> + ?Sized>(&mut self, s: &S) {
let s = s.as_ref();
self.data
.extend_from_slice(uapi::as_bytes(&(s.len() as u32 + 1)));
self.data
.extend_from_slice(uapi::as_bytes(&PW_TYPE_String.0));
self.data.extend_from_slice(s);
self.data.push(0);
self.pad();
}
#[expect(dead_code)]
pub fn write_bytes(&mut self, s: &[u8]) {
self.data
.extend_from_slice(uapi::as_bytes(&(s.len() as u32)));
self.data
.extend_from_slice(uapi::as_bytes(&PW_TYPE_Bytes.0));
self.data.extend_from_slice(s);
self.pad();
}
pub fn write_rectangle(&mut self, width: u32, height: u32) {
if !self.array || self.first {
self.data.extend_from_slice(uapi::as_bytes(&8u32));
self.data
.extend_from_slice(uapi::as_bytes(&PW_TYPE_Rectangle.0));
}
self.data.extend_from_slice(uapi::as_bytes(&width));
self.data.extend_from_slice(uapi::as_bytes(&height));
self.first = false;
}
#[expect(dead_code)]
pub fn write_fraction(&mut self, num: i32, denom: i32) {
if !self.array || self.first {
self.data.extend_from_slice(uapi::as_bytes(&8u32));
self.data
.extend_from_slice(uapi::as_bytes(&PW_TYPE_Fraction.0));
}
self.data.extend_from_slice(uapi::as_bytes(&num));
self.data.extend_from_slice(uapi::as_bytes(&denom));
self.first = false;
}
pub fn write_none(&mut self) {
if !self.array || self.first {
self.data.extend_from_slice(uapi::as_bytes(&0u32));
self.data.extend_from_slice(uapi::as_bytes(&PW_TYPE_None.0));
}
self.first = false;
}
#[expect(dead_code)]
pub fn write_bitmap(&mut self, s: &[u8]) {
self.data
.extend_from_slice(uapi::as_bytes(&(s.len() as u32)));
self.data
.extend_from_slice(uapi::as_bytes(&PW_TYPE_Bitmap.0));
self.data.extend_from_slice(s);
self.pad();
}
pub fn write_fd(&mut self, fd: &Rc<OwnedFd>) {
let pos = self.fds.len() as u64;
self.fds.push(fd.clone());
if !self.array || self.first {
self.data.extend_from_slice(uapi::as_bytes(&8u32));
self.data.extend_from_slice(uapi::as_bytes(&PW_TYPE_Fd.0));
}
self.data.extend_from_slice(uapi::as_bytes(&pos));
self.first = false;
}
pub fn write_struct<F>(&mut self, f: F)
where
F: FnOnce(&mut PwFormatter),
{
self.write_compound(PW_TYPE_Struct, |fmt| {
let mut fmt = PwFormatter {
data: fmt.data,
fds: fmt.fds,
array: false,
first: false,
};
f(&mut fmt);
});
}
#[expect(dead_code)]
pub fn write_array<F>(&mut self, f: F)
where
F: FnOnce(&mut PwFormatter),
{
self.write_compound(PW_TYPE_Array, |fmt| {
fmt.write_array_body(f);
});
self.pad();
}
fn write_array_body<F>(&mut self, f: F)
where
F: FnOnce(&mut PwFormatter),
{
let mut fmt = PwFormatter {
data: self.data,
fds: self.fds,
array: true,
first: true,
};
f(&mut fmt);
if fmt.first {
fmt.write_none();
}
}
pub fn write_choice<F>(&mut self, ty: PwChoiceType, flags: u32, f: F)
where
F: FnOnce(&mut PwFormatter),
{
self.write_compound(PW_TYPE_Choice, |fmt| {
fmt.data.extend_from_slice(uapi::as_bytes(&ty.0));
fmt.data.extend_from_slice(uapi::as_bytes(&flags));
fmt.write_array_body(f);
});
self.pad();
}
fn write_compound<F>(&mut self, ty: PwPodType, f: F)
where
F: FnOnce(&mut PwFormatter),
{
let start = self.data.len();
self.data.extend_from_slice(uapi::as_bytes(&0u32));
self.data.extend_from_slice(uapi::as_bytes(&ty.0));
f(self);
let len = (self.data.len() - start - 8) as u32;
self.data[start..start + 4].copy_from_slice(uapi::as_bytes(&len));
}
fn pad(&mut self) {
let todo = self.data.len().wrapping_neg() & 7;
self.data.extend_from_slice(&uapi::as_bytes(&0u64)[..todo]);
}
}
pub struct PwObjectFormatter<'a> {
data: &'a mut DynamicBuf,
fds: &'a mut Vec<Rc<OwnedFd>>,
}
impl<'a> PwObjectFormatter<'a> {
pub fn write_property<F>(&mut self, key: u32, flags: PwPropFlag, f: F)
where
F: FnOnce(&mut PwFormatter),
{
self.data.extend_from_slice(uapi::as_bytes(&key));
self.data.extend_from_slice(uapi::as_bytes(&flags.0));
let mut fmt = PwFormatter {
data: self.data,
fds: self.fds,
array: false,
first: false,
};
f(&mut fmt);
}
}
pub fn format<F>(
buf: &mut DynamicBuf,
fds: &mut Vec<Rc<OwnedFd>>,
id: u32,
opcode: u8,
seq: u32,
f: F,
) where
F: FnOnce(&mut PwFormatter),
{
buf.clear();
buf.extend_from_slice(uapi::as_bytes(&id));
buf.extend_from_slice(uapi::as_bytes(&0u32));
buf.extend_from_slice(uapi::as_bytes(&seq));
buf.extend_from_slice(uapi::as_bytes(&0u32));
let mut fmt = PwFormatter {
data: buf,
fds,
array: false,
first: false,
};
f(&mut fmt);
let p2 = (buf.len() - 16) as u32 | ((opcode as u32) << 24);
buf[4..8].copy_from_slice(uapi::as_bytes(&p2));
let nfds = fds.len() as u32;
buf[12..16].copy_from_slice(uapi::as_bytes(&nfds));
}

4
src/pipewire/pw_ifs.rs Normal file
View file

@ -0,0 +1,4 @@
pub mod pw_client;
pub mod pw_client_node;
pub mod pw_core;
pub mod pw_registry;

View file

@ -0,0 +1,61 @@
use {
crate::pipewire::{
pw_con::PwCon,
pw_object::{PwObject, PwObjectData},
pw_parser::{PwParser, PwParserError},
},
std::rc::Rc,
thiserror::Error,
};
pw_opcodes! {
PwClientMethods;
Error = 1,
UpdateProperties = 2,
GetPermissions = 3,
UpdatePermissions = 4,
}
pw_opcodes! {
PwClientEvents;
Info = 0,
Permissions = 1,
}
pub struct PwClient {
pub data: PwObjectData,
pub _con: Rc<PwCon>,
}
impl PwClient {
fn handle_info(&self, mut p: PwParser<'_>) -> Result<(), PwClientError> {
let s1 = p.read_struct()?;
let mut p2 = s1.fields;
let _id = p2.read_int()?;
let _change_mask = p2.read_long()?;
let props = p2.read_dict_struct()?;
log::debug!("Pipewire properties: {:#?}", props);
Ok(())
}
fn handle_permissions(&self, _p: PwParser<'_>) -> Result<(), PwClientError> {
Ok(())
}
}
pw_object_base! {
PwClient, "client", PwClientEvents;
Info => handle_info,
Permissions => handle_permissions,
}
impl PwObject for PwClient {}
#[derive(Debug, Error)]
pub enum PwClientError {
#[error(transparent)]
PwParserError(#[from] PwParserError),
}

View file

@ -0,0 +1,892 @@
#![allow(non_upper_case_globals)]
use {
crate::{
async_engine::SpawnedFuture,
format::{Format, pipewire_format, pw_formats},
pipewire::{
pw_con::PwCon,
pw_mem::{PwMemError, PwMemMap, PwMemSlice, PwMemTyped},
pw_object::{PwObject, PwObjectData},
pw_parser::{PwParser, PwParserError},
pw_pod::{
PW_CHOICE_Enum, PW_CHOICE_Flags, PW_NODE_ACTIVATION_FINISHED,
PW_NODE_ACTIVATION_NOT_TRIGGERED, PW_NODE_ACTIVATION_TRIGGERED, PW_OBJECT_Format,
PW_OBJECT_ParamBuffers, PW_OBJECT_ParamMeta, PW_PROP_DONT_FIXATE, PW_TYPE_Long,
PwIoType, PwPod, PwPodFraction, PwPodObject, PwPodRectangle, PwPropFlag,
SPA_DATA_DmaBuf, SPA_DATA_FLAG_READABLE, SPA_DATA_MemFd, SPA_DATA_MemPtr,
SPA_DIRECTION_INPUT, SPA_DIRECTION_OUTPUT, SPA_FORMAT_VIDEO_format,
SPA_FORMAT_VIDEO_framerate, SPA_FORMAT_VIDEO_modifier, SPA_FORMAT_VIDEO_size,
SPA_FORMAT_mediaSubtype, SPA_FORMAT_mediaType, SPA_IO_Buffers, SPA_META_Bitmap,
SPA_META_Busy, SPA_META_Control, SPA_META_Cursor, SPA_META_Header,
SPA_META_VideoCrop, SPA_META_VideoDamage, SPA_NODE_BUFFERS_FLAG_ALLOC,
SPA_NODE_COMMAND_Pause, SPA_NODE_COMMAND_Start, SPA_NODE_COMMAND_Suspend,
SPA_PARAM_BUFFERS_blocks, SPA_PARAM_BUFFERS_buffers, SPA_PARAM_BUFFERS_dataType,
SPA_PARAM_Buffers, SPA_PARAM_EnumFormat, SPA_PARAM_Format, SPA_PARAM_INFO,
SPA_PARAM_INFO_READ, SPA_PARAM_INFO_SERIAL, SPA_PARAM_META_size,
SPA_PARAM_META_type, SPA_PARAM_Meta, SPA_PORT_FLAG,
SPA_PORT_FLAG_CAN_ALLOC_BUFFERS, SpaDataFlags, SpaDataType, SpaDirection,
SpaIoType, SpaMediaSubtype, SpaMediaType, SpaMetaType, SpaNodeBuffersFlags,
SpaNodeCommand, SpaParamType, SpaVideoFormat, pw_node_activation, spa_chunk,
spa_io_buffers, spa_meta_bitmap, spa_meta_busy, spa_meta_cursor, spa_meta_header,
spa_meta_region,
},
},
utils::{
bitfield::Bitfield, buf::TypedBuf, clonecell::CloneCell, copyhashmap::CopyHashMap,
errorfmt::ErrorFmt, option_ext::OptionExt,
},
video::{Modifier, dmabuf::DmaBuf},
},
std::{
cell::{Cell, RefCell},
rc::Rc,
sync::atomic::Ordering::{Relaxed, Release},
},
thiserror::Error,
uapi::OwnedFd,
};
pw_opcodes! {
PwClientNodeMethods;
GetNode = 1,
Update = 2,
PortUpdate = 3,
SetActive = 4,
Event = 5,
PortBuffers = 6,
}
pw_opcodes! {
PwClientNodeEvents;
Transport = 0,
SetParam = 1,
SetIo = 2,
Event = 3,
Command = 4,
AddPort = 5,
RemovePort = 6,
PortSetParam = 7,
PortUseBuffers = 8,
PortSetIo = 9,
SetActivation = 10,
PortSetMixInfo = 11,
}
pub trait PwClientNodeOwner {
fn port_format_changed(&self, port: &Rc<PwClientNodePort>) {
let _ = port;
}
fn use_buffers(self: Rc<Self>, port: &Rc<PwClientNodePort>) {
let _ = port;
}
fn start(self: Rc<Self>) {}
fn pause(self: Rc<Self>) {}
fn suspend(self: Rc<Self>) {}
fn bound_id(&self, id: u32) {
let _ = id;
}
}
bitflags! {
PwClientNodePortChanges: u32;
CHANGED_SUPPORTED_PARAMS = 1 << 0,
}
bitflags! {
PwClientNodePortSupportedMetas: u32;
SUPPORTED_META_HEADER = 1 << 0,
SUPPORTED_META_BUSY = 1 << 1,
SUPPORTED_META_VIDEO_CROP = 1 << 2,
}
pub struct PwClientNodePort {
pub node: Rc<PwClientNode>,
pub direction: SpaDirection,
pub id: u32,
pub _destroyed: Cell<bool>,
pub negotiated_format: RefCell<PwClientNodePortFormat>,
pub supported_formats: RefCell<PwClientNodePortSupportedFormats>,
pub supported_metas: Cell<PwClientNodePortSupportedMetas>,
pub can_alloc_buffers: Cell<bool>,
pub buffers: RefCell<Vec<Rc<PwClientNodeBuffer>>>,
pub buffer_config: RefCell<PwClientNodeBufferConfig>,
pub io_buffers: CloneCell<Option<Rc<PwMemTyped<spa_io_buffers>>>>,
pub serial: Cell<bool>,
}
#[derive(Copy, Clone, Debug, Default)]
pub struct PwClientNodeBufferConfig {
pub num_buffers: Option<usize>,
pub planes: Option<usize>,
pub data_type: SpaDataType,
}
pub struct PwClientNodeBuffer {
pub _meta_header: Option<Rc<PwMemTyped<spa_meta_header>>>,
pub _meta_busy: Option<Rc<PwMemTyped<spa_meta_busy>>>,
pub meta_video_crop: Option<Rc<PwMemTyped<spa_meta_region>>>,
pub chunks: Vec<Rc<PwMemTyped<spa_chunk>>>,
pub _slices: Vec<Rc<PwMemSlice>>,
}
#[derive(Clone, Debug)]
pub struct PwClientNodePortSupportedFormat {
pub format: &'static Format,
pub modifiers: Vec<u64>,
}
#[derive(Clone, Debug, Default)]
pub struct PwClientNodePortSupportedFormats {
pub media_type: Option<SpaMediaType>,
pub media_sub_type: Option<SpaMediaSubtype>,
pub video_size: Option<PwPodRectangle>,
pub formats: Vec<PwClientNodePortSupportedFormat>,
}
#[derive(Clone, Debug, Default)]
pub struct PwClientNodePortFormat {
pub media_type: Option<SpaMediaType>,
pub media_sub_type: Option<SpaMediaSubtype>,
pub video_size: Option<PwPodRectangle>,
pub format: Option<&'static Format>,
pub modifiers: Option<Vec<Modifier>>,
pub framerate: Option<PwPodFraction>,
}
pub struct PwClientNode {
pub data: PwObjectData,
pub con: Rc<PwCon>,
pub ios: CopyHashMap<PwIoType, Rc<PwMemMap>>,
pub owner: CloneCell<Option<Rc<dyn PwClientNodeOwner>>>,
pub ports: CopyHashMap<(SpaDirection, u32), Rc<PwClientNodePort>>,
pub port_out_free: RefCell<Bitfield>,
pub port_in_free: RefCell<Bitfield>,
pub activation: CloneCell<Option<Rc<PwMemTyped<pw_node_activation>>>>,
pub transport_in: Cell<Option<SpawnedFuture<()>>>,
pub transport_out: CloneCell<Option<Rc<OwnedFd>>>,
pub activations: CopyHashMap<u32, Rc<PwNodeActivation>>,
}
pub struct PwNodeActivation {
pub activation: Rc<PwMemTyped<pw_node_activation>>,
pub fd: Rc<OwnedFd>,
}
// pub struct PwNodeBuffer {
// pub width: i32,
// pub height: i32,
// pub stride: i32,
// pub offset: i32,
// pub fd: Rc<OwnedFd>,
// }
pub const PW_CLIENT_NODE_FACTORY: &str = "client-node";
pub const PW_CLIENT_NODE_INTERFACE: &str = "PipeWire:Interface:ClientNode";
pub const PW_CLIENT_NODE_VERSION: i32 = 4;
#[expect(dead_code)]
const PW_CLIENT_NODE_UPDATE_PARAMS: u32 = 1 << 0;
const PW_CLIENT_NODE_UPDATE_INFO: u32 = 1 << 1;
const SPA_NODE_CHANGE_MASK_FLAGS: u64 = 1 << 0;
#[expect(dead_code)]
const SPA_NODE_CHANGE_MASK_PROPS: u64 = 1 << 1;
const SPA_NODE_CHANGE_MASK_PARAMS: u64 = 1 << 2;
const PW_CLIENT_NODE_PORT_UPDATE_PARAMS: u32 = 1 << 0;
const PW_CLIENT_NODE_PORT_UPDATE_INFO: u32 = 1 << 1;
const SPA_PORT_CHANGE_MASK_FLAGS: u64 = 1 << 0;
const SPA_PORT_CHANGE_MASK_RATE: u64 = 1 << 1;
#[expect(dead_code)]
const SPA_PORT_CHANGE_MASK_PROPS: u64 = 1 << 2;
const SPA_PORT_CHANGE_MASK_PARAMS: u64 = 1 << 3;
impl PwClientNode {
pub fn send_update(&self) {
self.con.send(self, PwClientNodeMethods::Update, |f| {
f.write_struct(|f| {
f.write_uint(PW_CLIENT_NODE_UPDATE_INFO);
f.write_uint(0);
f.write_struct(|f| {
f.write_uint(0);
f.write_uint(1);
f.write_ulong(SPA_NODE_CHANGE_MASK_PARAMS | SPA_NODE_CHANGE_MASK_FLAGS);
f.write_ulong(0);
f.write_uint(0);
f.write_uint(0);
});
});
});
}
pub fn send_active(&self, active: bool) {
self.con.send(self, PwClientNodeMethods::SetActive, |f| {
f.write_struct(|f| {
f.write_bool(active);
});
});
}
pub fn create_port(
self: &Rc<Self>,
output: bool,
supported_formats: PwClientNodePortSupportedFormats,
num_buffers: Option<usize>,
) -> Rc<PwClientNodePort> {
let (ids, direction) = match output {
true => (&self.port_out_free, SPA_DIRECTION_OUTPUT),
false => (&self.port_in_free, SPA_DIRECTION_INPUT),
};
let port = Rc::new(PwClientNodePort {
node: self.clone(),
direction,
id: ids.borrow_mut().acquire(),
_destroyed: Cell::new(false),
negotiated_format: Default::default(),
supported_formats: RefCell::new(supported_formats),
supported_metas: Cell::new(PwClientNodePortSupportedMetas::none()),
can_alloc_buffers: Cell::new(false),
buffers: RefCell::new(vec![]),
buffer_config: RefCell::new(PwClientNodeBufferConfig {
num_buffers,
planes: None,
data_type: SPA_DATA_DmaBuf,
}),
io_buffers: Default::default(),
serial: Cell::new(false),
});
self.ports.set((direction, port.id), port.clone());
port
}
pub fn send_port_output_buffers(&self, port: &PwClientNodePort, buffers: &[DmaBuf]) {
self.con.send(self, PwClientNodeMethods::PortBuffers, |f| {
f.write_struct(|f| {
// direction
f.write_uint(port.direction.0);
// id
f.write_uint(port.id);
// mix_id
f.write_int(-1);
// n_buffers
f.write_uint(buffers.len() as _);
for buffer in buffers {
// n_datas
f.write_uint(buffer.planes.len() as _);
for plane in &buffer.planes {
// type
f.write_id(SPA_DATA_DmaBuf.0);
// fd
f.write_fd(&plane.fd);
// flags
f.write_uint(SPA_DATA_FLAG_READABLE.0);
// offset
f.write_uint(plane.offset);
// size
f.write_uint(plane.stride * buffer.height as u32);
}
}
});
});
}
pub fn send_port_update(&self, port: &PwClientNodePort, fixate: bool) {
port.serial.set(!port.serial.get());
let serial = match port.serial.get() {
true => SPA_PARAM_INFO_SERIAL,
false => SPA_PARAM_INFO::none(),
};
self.con.send(self, PwClientNodeMethods::PortUpdate, |f| {
f.write_struct(|f| {
// direction
f.write_uint(port.direction.0);
// id
f.write_uint(port.id);
// change flags
f.write_uint(PW_CLIENT_NODE_PORT_UPDATE_PARAMS | PW_CLIENT_NODE_PORT_UPDATE_INFO);
let sm = port.supported_metas.get();
let mut metas = vec![];
if sm.contains(SUPPORTED_META_HEADER) {
metas.push((SPA_META_Header, size_of::<spa_meta_header>()));
}
if sm.contains(SUPPORTED_META_BUSY) {
metas.push((SPA_META_Busy, size_of::<spa_meta_busy>()));
}
if sm.contains(SUPPORTED_META_VIDEO_CROP) {
metas.push((SPA_META_VideoCrop, size_of::<spa_meta_region>()));
}
let sf = &*port.supported_formats.borrow();
let num_formats = sf.formats.len() as u32;
let bc = &*port.buffer_config.borrow();
let num_params = metas.len() as u32 + num_formats + 1;
// num params
f.write_uint(num_params);
for format in &sf.formats {
f.write_object(PW_OBJECT_Format, SPA_PARAM_EnumFormat.0, |f| {
if let Some(mt) = sf.media_type {
f.write_property(SPA_FORMAT_mediaType.0, PwPropFlag::none(), |f| {
f.write_id(mt.0);
});
}
if let Some(mst) = sf.media_sub_type {
f.write_property(SPA_FORMAT_mediaSubtype.0, PwPropFlag::none(), |f| {
f.write_id(mst.0);
});
}
f.write_property(SPA_FORMAT_VIDEO_format.0, PwPropFlag::none(), |f| {
f.write_choice(PW_CHOICE_Enum, 0, |f| {
f.write_id(pipewire_format(format.format).0);
f.write_id(pipewire_format(format.format).0);
});
});
f.write_property(
SPA_FORMAT_VIDEO_modifier.0,
if fixate {
PwPropFlag::none()
} else {
PW_PROP_DONT_FIXATE
},
|f| {
f.write_choice(PW_CHOICE_Enum, 0, |f| {
f.write_ulong(format.modifiers[0]);
for modifier in &format.modifiers {
f.write_ulong(*modifier);
}
});
},
);
if let Some(vs) = sf.video_size {
f.write_property(SPA_FORMAT_VIDEO_size.0, PwPropFlag::none(), |f| {
f.write_choice(PW_CHOICE_Enum, 0, |f| {
f.write_rectangle(vs.width, vs.height);
f.write_rectangle(vs.width, vs.height);
});
});
}
});
}
f.write_object(PW_OBJECT_ParamBuffers, SPA_PARAM_Buffers.0, |f| {
if let Some(num_buffers) = bc.num_buffers {
f.write_property(SPA_PARAM_BUFFERS_buffers.0, PwPropFlag::none(), |f| {
f.write_uint(num_buffers as _);
});
}
if let Some(planes) = bc.planes {
f.write_property(SPA_PARAM_BUFFERS_blocks.0, PwPropFlag::none(), |f| {
f.write_uint(planes as _);
});
}
f.write_property(SPA_PARAM_BUFFERS_dataType.0, PwPropFlag::none(), |f| {
f.write_choice(PW_CHOICE_Flags, 0, |f| {
f.write_uint(1 << bc.data_type.0);
});
});
});
for (key, size) in metas {
f.write_object(PW_OBJECT_ParamMeta, SPA_PARAM_Meta.0, |f| {
f.write_property(SPA_PARAM_META_type.0, PwPropFlag::none(), |f| {
f.write_id(key.0);
});
f.write_property(SPA_PARAM_META_size.0, PwPropFlag::none(), |f| {
f.write_uint(size as u32);
});
});
}
f.write_struct(|f| {
// change mask
f.write_ulong(
SPA_PORT_CHANGE_MASK_FLAGS
// | SPA_PORT_CHANGE_MASK_PROPS
| SPA_PORT_CHANGE_MASK_PARAMS
| SPA_PORT_CHANGE_MASK_RATE,
);
let mut flags = SPA_PORT_FLAG::none();
if port.can_alloc_buffers.get() {
flags = SPA_PORT_FLAG_CAN_ALLOC_BUFFERS;
}
// flags
f.write_ulong(flags.0);
// rate num
f.write_int(0);
// rate denom
f.write_int(1);
// num props
f.write_int(0);
let num_params = 3;
// num params
f.write_uint(num_params);
f.write_id(SPA_PARAM_EnumFormat.0);
f.write_uint((SPA_PARAM_INFO_READ | serial).0);
f.write_id(SPA_PARAM_Buffers.0);
f.write_uint((SPA_PARAM_INFO_READ | serial).0);
f.write_id(SPA_PARAM_Meta.0);
f.write_uint(SPA_PARAM_INFO_READ.0);
});
});
});
}
fn handle_set_param(&self, _p: PwParser<'_>) -> Result<(), PwClientNodeError> {
Ok(())
}
fn handle_set_io(&self, mut p: PwParser<'_>) -> Result<(), PwClientNodeError> {
let s = p.read_struct()?;
let mut p2 = s.fields;
let id = PwIoType(p2.read_id()?);
let memid = p2.read_uint()?;
let offset = p2.read_uint()?;
let size = p2.read_uint()?;
log::debug!("set io {:?}", id);
if memid == !0 {
self.ios.remove(&id);
} else {
let map = match self.con.mem.map(memid, offset, size) {
Ok(m) => m,
Err(e) => {
log::error!("Could not map memory from the pool: {}", ErrorFmt(e));
return Ok(());
}
};
self.ios.set(id, map);
}
Ok(())
}
fn handle_event(&self, _p: PwParser<'_>) -> Result<(), PwClientNodeError> {
Ok(())
}
fn handle_command(self: &Rc<Self>, mut p: PwParser<'_>) -> Result<(), PwClientNodeError> {
let s1 = p.read_struct()?;
let mut p1 = s1.fields;
let obj = p1.read_object()?;
match SpaNodeCommand(obj.id) {
SPA_NODE_COMMAND_Start => {
if let Some(owner) = self.owner.get() {
owner.start();
}
}
SPA_NODE_COMMAND_Pause => {
if let Some(owner) = self.owner.get() {
owner.pause();
}
}
SPA_NODE_COMMAND_Suspend => {
if let Some(owner) = self.owner.get() {
owner.suspend();
}
}
v => {
log::warn!("Unhandled node command {:?}", v);
}
}
Ok(())
}
fn handle_add_port(&self, _p: PwParser<'_>) -> Result<(), PwClientNodeError> {
Ok(())
}
fn handle_remove_port(&self, _p: PwParser<'_>) -> Result<(), PwClientNodeError> {
Ok(())
}
fn port_set_format(
&self,
port: &Rc<PwClientNodePort>,
obj: Option<PwPodObject<'_>>,
) -> Result<(), PwClientNodeError> {
let mut obj = match obj {
Some(obj) => obj,
_ => {
port.negotiated_format.take();
return Ok(());
}
};
let mut format = PwClientNodePortFormat::default();
if let Some(mt) = obj.get_param(SPA_FORMAT_mediaType.0)? {
format.media_type = Some(SpaMediaType(mt.pod.get_id()?));
}
if let Some(mt) = obj.get_param(SPA_FORMAT_mediaSubtype.0)? {
format.media_sub_type = Some(SpaMediaSubtype(mt.pod.get_id()?));
}
if let Some(mt) = obj.get_param(SPA_FORMAT_VIDEO_size.0)? {
format.video_size = Some(mt.pod.get_rectangle()?);
}
if let Some(mt) = obj.get_param(SPA_FORMAT_VIDEO_format.0)?
&& let Some(fmt) = pw_formats().get(&SpaVideoFormat(mt.pod.get_id()?))
{
format.format = Some(*fmt);
}
if let Some(mt) = obj.get_param(SPA_FORMAT_VIDEO_modifier.0)?
&& let PwPod::Choice(mods) = mt.pod
{
let mut p1 = mods.elements.elements;
p1.read_pod_body_packed(PW_TYPE_Long, 8)?;
while p1.len() > 0 {
let modifier = p1.read_pod_body_packed(PW_TYPE_Long, 8)?;
if let PwPod::Long(modifier) = modifier {
format
.modifiers
.get_or_insert_default_ext()
.push(modifier as u64);
}
}
}
if let Some(mt) = obj.get_param(SPA_FORMAT_VIDEO_framerate.0)? {
format.framerate = Some(mt.pod.get_fraction()?);
}
*port.negotiated_format.borrow_mut() = format;
Ok(())
}
fn handle_port_set_param(&self, mut p: PwParser<'_>) -> Result<(), PwClientNodeError> {
let s1 = p.read_struct()?;
let mut p1 = s1.fields;
let direction = SpaDirection(p1.read_uint()?);
let port_id = p1.read_uint()?;
let id = SpaParamType(p1.read_id()?);
let _flags = p1.read_int()?;
let obj = p1.read_object_opt()?;
let port = self.get_port(direction, port_id)?;
match id {
SPA_PARAM_Format => {
self.port_set_format(&port, obj)?;
if let Some(owner) = self.owner.get() {
owner.port_format_changed(&port);
}
}
_ => {
log::warn!(
"port_set_param: Ignoring unexpected port parameter {:?}",
id
);
}
}
Ok(())
}
fn handle_port_use_buffers(&self, mut p: PwParser<'_>) -> Result<(), PwClientNodeError> {
let s1 = p.read_struct()?;
let mut p1 = s1.fields;
let direction = SpaDirection(p1.read_uint()?);
let port_id = p1.read_uint()?;
let _mix_id = p1.read_int()?;
let buffer_flags = SpaNodeBuffersFlags(p1.read_uint()?);
let n_buffers = p1.read_uint()?;
let port = self.get_port(direction, port_id)?;
let mut res = vec![];
for _ in 0..n_buffers {
let mem_id = p1.read_uint()?;
let offset = p1.read_uint()?;
let size = p1.read_uint()?;
let n_metas = p1.read_uint()?;
let mut meta_header = Default::default();
let mut meta_video_crop = Default::default();
let mut meta_busy = Default::default();
let mut chunks = vec![];
let mut slices = vec![];
let mem = self.con.mem.map(mem_id, offset, size)?;
log::debug!(" mem_id={}, offset={}, size={}", mem_id, offset, size);
log::debug!(" n_metas={}", n_metas);
let mut offset = 0;
for _ in 0..n_metas {
let ty = SpaMetaType(p1.read_id()?);
let size = p1.read_uint()? as usize;
match ty {
SPA_META_Header => {
let header = mem.typed_at::<spa_meta_header>(offset);
meta_header = Some(header);
}
SPA_META_VideoCrop => {
let crop = mem.typed_at::<spa_meta_region>(offset);
meta_video_crop = Some(crop);
}
SPA_META_VideoDamage => {
let _video_damage = mem.typed_at::<spa_meta_region>(offset);
}
SPA_META_Bitmap => {
let _bitmap = mem.typed_at::<spa_meta_bitmap>(offset);
}
SPA_META_Cursor => {
let _cursor = mem.typed_at::<spa_meta_cursor>(offset);
}
SPA_META_Control => {}
SPA_META_Busy => {
let busy = mem.typed_at::<spa_meta_busy>(offset);
meta_busy = Some(busy);
}
_ => {}
}
offset += (size + 7) & !7;
}
let n_datas = p1.read_uint()?;
log::debug!(" offset = {}, n_datas={}", offset, n_datas);
for _ in 0..n_datas {
let ty = SpaDataType(p1.read_id()?);
let data_id = p1.read_uint()?;
let _flags = SpaDataFlags(p1.read_uint()?);
let mapoffset = p1.read_uint()?;
let maxsize = p1.read_uint()?;
chunks.push(mem.typed_at(offset));
offset += size_of::<spa_chunk>();
if !buffer_flags.contains(SPA_NODE_BUFFERS_FLAG_ALLOC) {
if ty == SPA_DATA_MemPtr {
let offset = data_id as usize;
slices.push(mem.slice(offset..offset + maxsize as usize));
} else if ty == SPA_DATA_MemFd {
let mem = self.con.mem.map(data_id, mapoffset, maxsize)?;
slices.push(mem.slice(0..maxsize as usize));
}
}
}
res.push(Rc::new(PwClientNodeBuffer {
_meta_header: meta_header,
_meta_busy: meta_busy,
meta_video_crop,
chunks,
_slices: slices,
}));
}
*port.buffers.borrow_mut() = res;
if let Some(owner) = self.owner.get() {
owner.use_buffers(&port);
}
Ok(())
}
fn handle_port_set_io(&self, mut p: PwParser<'_>) -> Result<(), PwClientNodeError> {
let s = p.read_struct()?;
let mut p2 = s.fields;
let direction = SpaDirection(p2.read_uint()?);
let port_id = p2.read_uint()?;
let mix_id = p2.read_uint()?;
let id = SpaIoType(p2.read_id()?);
let mem_id = p2.read_uint()?;
let offset = p2.read_uint()?;
let size = p2.read_uint()?;
let port = self.get_port(direction, port_id)?;
match id {
SPA_IO_Buffers if mix_id == 0 => {
if mem_id == !0 {
port.io_buffers.take();
} else {
let io_buffers = self
.con
.mem
.map(mem_id, offset, size)?
.typed::<spa_io_buffers>();
unsafe {
io_buffers.read().buffer_id.store(!0, Relaxed);
io_buffers.read().status.store(0, Relaxed);
}
port.io_buffers.set(Some(io_buffers));
}
}
_ => {}
}
Ok(())
}
fn handle_transport(self: &Rc<Self>, mut p: PwParser<'_>) -> Result<(), PwClientNodeError> {
let s = p.read_struct()?;
let mut p2 = s.fields;
let readfd = p2.read_fd()?;
let writefd = p2.read_fd()?;
let memid = p2.read_uint()?;
let offset = p2.read_uint()?;
let size = p2.read_uint()?;
let map = match self.con.mem.map(memid, offset, size) {
Ok(m) => m,
Err(e) => {
log::error!("Could not map memory from the pool: {}", ErrorFmt(e));
return Ok(());
}
};
let typed = map.typed::<pw_node_activation>();
self.activation.set(Some(typed.clone()));
self.transport_in.set(Some(
self.con
.eng
.spawn("pw transport in", self.clone().transport_in(typed, readfd)),
));
self.transport_out.set(Some(writefd));
Ok(())
}
fn handle_set_activation(
self: &Rc<Self>,
mut p: PwParser<'_>,
) -> Result<(), PwClientNodeError> {
let s = p.read_struct()?;
let mut p2 = s.fields;
let node = p2.read_uint()?;
let signalfd = p2.read_fd_opt()?;
if let Some(signalfd) = signalfd {
let memid = p2.read_uint()?;
let offset = p2.read_uint()?;
let size = p2.read_uint()?;
let map = match self.con.mem.map(memid, offset, size) {
Ok(m) => m,
Err(e) => {
log::error!("Could not map memory from the pool: {}", ErrorFmt(e));
return Ok(());
}
};
let typed = map.typed::<pw_node_activation>();
self.activations.set(
node,
Rc::new(PwNodeActivation {
activation: typed,
fd: signalfd,
}),
);
} else {
self.activations.remove(&node);
}
Ok(())
}
fn get_port(
&self,
direction: SpaDirection,
port_id: u32,
) -> Result<Rc<PwClientNodePort>, PwClientNodeError> {
match self.ports.get(&(direction, port_id)) {
Some(p) => Ok(p),
_ => Err(PwClientNodeError::UnknownPort(direction, port_id)),
}
}
fn handle_port_set_mix_info(&self, mut p: PwParser<'_>) -> Result<(), PwClientNodeError> {
let s1 = p.read_struct()?;
let mut p1 = s1.fields;
let direction = SpaDirection(p1.read_uint()?);
let port_id = p1.read_uint()?;
let mix_id = p1.read_int()?;
let peer_id = p1.read_int()?;
let dict = p1.read_dict_struct()?;
let _port = self.get_port(direction, port_id)?;
log::debug!(
"mix info: mix_id={}, peer_id={}, dict={:#?}",
mix_id,
peer_id,
dict
);
Ok(())
}
async fn transport_in(
self: Rc<Self>,
_activation: Rc<PwMemTyped<pw_node_activation>>,
fd: Rc<OwnedFd>,
) {
let mut buf = TypedBuf::<u64>::new();
loop {
if let Err(e) = self.con.ring.read(&fd, buf.buf()).await {
log::error!("Could not read from eventfd: {}", ErrorFmt(e));
return;
}
if let Some(activation) = self.activation.get() {
let activation = unsafe { activation.read() };
activation
.status
.store(PW_NODE_ACTIVATION_FINISHED.0, Relaxed);
}
}
}
pub fn drive(&self) {
for activation in self.activations.lock().values() {
let a = unsafe { activation.activation.read() };
let required = a.state[0].required.load(Relaxed);
a.state[0].pending.store(required - 1, Relaxed);
if required == 1 {
a.status.store(PW_NODE_ACTIVATION_TRIGGERED.0, Release);
let _ = uapi::eventfd_write(activation.fd.raw(), 1);
} else {
a.status.store(PW_NODE_ACTIVATION_NOT_TRIGGERED.0, Release);
}
}
}
}
pw_object_base! {
PwClientNode, "client-node", PwClientNodeEvents;
Transport => handle_transport,
SetParam => handle_set_param,
SetIo => handle_set_io,
Event => handle_event,
Command => handle_command,
AddPort => handle_add_port,
RemovePort => handle_remove_port,
PortSetParam => handle_port_set_param,
PortUseBuffers => handle_port_use_buffers,
PortSetIo => handle_port_set_io,
SetActivation => handle_set_activation,
PortSetMixInfo => handle_port_set_mix_info,
}
impl PwObject for PwClientNode {
fn bound_id(&self, id: u32) {
if let Some(owner) = self.owner.get() {
owner.bound_id(id);
}
}
fn break_loops(&self) {
self.owner.take();
self.ports.clear();
self.transport_in.take();
self.transport_out.take();
}
}
#[derive(Debug, Error)]
pub enum PwClientNodeError {
#[error(transparent)]
PwParserError(#[from] PwParserError),
#[error(transparent)]
PwMemError(#[from] PwMemError),
#[error("Unknown port {0:?}@{1}")]
UnknownPort(SpaDirection, u32),
}

View file

@ -0,0 +1,186 @@
#![allow(non_upper_case_globals)]
use {
crate::{
pipewire::{
pw_con::PwCon,
pw_mem::{PwMem, PwMemType},
pw_object::{PwObject, PwObjectData},
pw_parser::{PwParser, PwParserError},
pw_pod::{SPA_DATA_DmaBuf, SPA_DATA_MemFd, SpaDataType},
},
utils::bitflags::BitflagsExt,
},
std::rc::Rc,
thiserror::Error,
};
pub struct PwCore {
pub data: PwObjectData,
pub con: Rc<PwCon>,
}
pw_opcodes! {
PwCoreMethods;
Hello = 1,
Sync = 2,
Pong = 3,
Error = 4,
GetRegistry = 5,
CreateObject = 6,
Destroy = 7,
}
pw_opcodes! {
PwCoreEvents;
Info = 0,
Done = 1,
Ping = 2,
Error = 3,
RemoveId = 4,
BoundId = 5,
AddMem = 6,
RemoveMem = 7,
}
pub const PW_CORE_VERSION: i32 = 3;
impl PwCore {
pub fn handle_info(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> {
let s1 = p1.read_struct()?;
let mut p2 = s1.fields;
let id = p2.read_int()?;
let cookie = p2.read_int()?;
let user_name = p2.read_string()?;
let host_name = p2.read_string()?;
let version_name = p2.read_string()?;
let name = p2.read_string()?;
let change_mask = p2.read_long()?;
let dict = p2.read_dict_struct()?;
log::info!(
"info: id={id}, cookie={cookie}, user_name={user_name}, host_name={host_name}, version_name={version_name}, name={name}, change_mask={change_mask}"
);
log::info!("dict: {:#?}", dict);
Ok(())
}
pub fn handle_done(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> {
let s1 = p1.read_struct()?;
let mut p2 = s1.fields;
let id = p2.read_uint()?;
let seq = p2.read_uint()?;
if let Some(obj) = self.con.objects.get(&id)
&& obj.data().sync_id.get() <= seq
{
obj.data().sync_id.set(seq);
obj.done();
}
Ok(())
}
pub fn handle_ping(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> {
let s1 = p1.read_struct()?;
let mut p2 = s1.fields;
let id = p2.read_int()?;
let seq = p2.read_int()?;
self.con.send(self, PwCoreMethods::Pong, |f| {
f.write_struct(|f| {
f.write_int(id);
f.write_int(seq);
});
});
Ok(())
}
pub fn handle_error(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> {
let s1 = p1.read_struct()?;
let mut p2 = s1.fields;
let id = p2.read_int()?;
let seq = p2.read_int()?;
let res = p2.read_int()?;
let error = p2.read_string()?;
log::info!("error: id={id}, seq={seq}, res={res}, error={error}");
Ok(())
}
pub fn handle_remove_id(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> {
let s1 = p1.read_struct()?;
let mut p2 = s1.fields;
let id = p2.read_uint()?;
self.con.objects.remove(&id);
self.con.ids.borrow_mut().release(id);
Ok(())
}
pub fn handle_bound_id(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> {
let s1 = p1.read_struct()?;
let mut p2 = s1.fields;
let id = p2.read_uint()?;
let bound_id = p2.read_uint()?;
if let Some(obj) = self.con.objects.get(&id) {
obj.data().bound_id.set(Some(bound_id));
obj.bound_id(bound_id);
}
Ok(())
}
pub fn handle_add_mem(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> {
let s1 = p1.read_struct()?;
let mut p2 = s1.fields;
let id = p2.read_uint()?;
let ty = SpaDataType(p2.read_id()?);
let fd = p2.read_fd()?;
let flags = p2.read_int()?;
let read = flags.contains(1);
let write = flags.contains(2);
let ty = match ty {
SPA_DATA_MemFd => PwMemType::MemFd,
SPA_DATA_DmaBuf => PwMemType::DmaBuf,
_ => {
log::error!("Ignoring unknown mem type {:?}", ty);
return Ok(());
}
};
self.con.mem.mems.set(
id,
Rc::new(PwMem {
_ty: ty,
read,
write,
fd,
}),
);
Ok(())
}
pub fn handle_remove_mem(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> {
let s1 = p1.read_struct()?;
let mut p2 = s1.fields;
let id = p2.read_uint()?;
self.con.mem.mems.remove(&id);
Ok(())
}
}
pw_object_base! {
PwCore, "core", PwCoreEvents;
Info => handle_info,
Done => handle_done,
Ping => handle_ping,
Error => handle_error,
RemoveId => handle_remove_id,
BoundId => handle_bound_id,
AddMem => handle_add_mem,
RemoveMem => handle_remove_mem,
}
impl PwObject for PwCore {}
#[derive(Debug, Error)]
pub enum PwCoreError {
#[error(transparent)]
PwParserError(#[from] PwParserError),
}

View file

@ -0,0 +1,48 @@
use {
crate::pipewire::{
pw_con::PwCon,
pw_object::{PwObject, PwObjectData},
pw_parser::{PwParser, PwParserError},
},
std::rc::Rc,
thiserror::Error,
};
pub const PW_REGISTRY_VERSION: i32 = 3;
pw_opcodes! {
PwRegistryEvents;
Global = 0,
GlobalRemove = 1,
}
pub struct PwRegistry {
pub data: PwObjectData,
pub _con: Rc<PwCon>,
}
impl PwRegistry {
fn handle_global(&self, _p: PwParser<'_>) -> Result<(), PwRegistryError> {
Ok(())
}
fn handle_global_remove(&self, _p: PwParser<'_>) -> Result<(), PwRegistryError> {
Ok(())
}
}
pw_object_base! {
PwRegistry, "registry", PwRegistryEvents;
Global => handle_global,
GlobalRemove => handle_global_remove,
}
impl PwObject for PwRegistry {}
#[derive(Debug, Error)]
pub enum PwRegistryError {
#[error(transparent)]
PwParserError(#[from] PwParserError),
}

155
src/pipewire/pw_mem.rs Normal file
View file

@ -0,0 +1,155 @@
use {
crate::utils::{
copyhashmap::CopyHashMap,
mmap::{Mmapped, mmap},
oserror::OsError,
page_size::page_size,
ptr_ext::{MutPtrExt, PtrExt},
},
std::{marker::PhantomData, ops::Range, rc::Rc},
thiserror::Error,
uapi::{OwnedFd, Pod, c},
};
#[derive(Default)]
pub struct PwMemPool {
pub mems: CopyHashMap<u32, Rc<PwMem>>,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum PwMemType {
MemFd,
DmaBuf,
}
pub struct PwMem {
pub _ty: PwMemType,
pub read: bool,
pub write: bool,
pub fd: Rc<OwnedFd>,
}
pub struct PwMemMap {
pub _mem: Rc<PwMem>,
pub range: Range<usize>,
pub map: Mmapped,
}
pub struct PwMemTyped<T> {
mem: Rc<PwMemMap>,
offset: usize,
_phantom: PhantomData<T>,
}
#[expect(dead_code)]
pub struct PwMemSlice {
mem: Rc<PwMemMap>,
range: Range<usize>,
}
impl PwMemPool {
pub fn map(&self, memid: u32, offset: u32, size: u32) -> Result<Rc<PwMemMap>, PwMemError> {
match self.mems.get(&memid) {
Some(m) => m.map(offset, size),
_ => Err(PwMemError::MemidDoesNotExist(memid)),
}
}
}
impl PwMem {
pub fn map(self: &Rc<Self>, offset: u32, size: u32) -> Result<Rc<PwMemMap>, PwMemError> {
let mask = page_size() - 1;
let offset = offset as usize;
let size = size as usize;
let start = offset & !mask;
let dist = offset - start;
let len = (size + dist + mask) & !mask;
let range = dist..dist + size;
let mut prot = 0;
if self.read {
prot |= c::PROT_READ;
}
if self.write {
prot |= c::PROT_WRITE;
}
let map = match mmap(len as _, prot, c::MAP_SHARED, self.fd.raw(), start as _) {
Ok(m) => m,
Err(e) => return Err(PwMemError::MmapFailed(e)),
};
Ok(Rc::new(PwMemMap {
_mem: self.clone(),
range,
map,
}))
}
}
impl PwMemMap {
#[expect(dead_code)]
pub unsafe fn read<T: Pod>(&self) -> &T {
self.check::<T>(0);
unsafe { (self.map.ptr.cast::<u8>().add(self.range.start) as *const T).deref() }
}
#[expect(dead_code)]
pub unsafe fn write<T: Pod>(&self) -> &mut T {
self.check::<T>(0);
unsafe { (self.map.ptr.cast::<u8>().add(self.range.start) as *mut T).deref_mut() }
}
#[expect(dead_code)]
pub unsafe fn bytes_mut(&self) -> &mut [u8] {
unsafe {
std::slice::from_raw_parts_mut(
self.map.ptr.cast::<u8>().add(self.range.start) as _,
self.range.len(),
)
}
}
fn check<T>(&self, offset: usize) {
assert!(offset <= self.range.len());
assert!(size_of::<T>() <= self.range.len() - offset);
assert_eq!((align_of::<T>() - 1) & (self.range.start + offset), 0);
}
pub fn typed<T: Pod>(self: &Rc<Self>) -> Rc<PwMemTyped<T>> {
self.typed_at(0)
}
pub fn typed_at<T: Pod>(self: &Rc<Self>, offset: usize) -> Rc<PwMemTyped<T>> {
self.check::<T>(offset);
Rc::new(PwMemTyped {
mem: self.clone(),
offset: self.range.start + offset,
_phantom: Default::default(),
})
}
pub fn slice(self: &Rc<Self>, range: Range<usize>) -> Rc<PwMemSlice> {
assert!(range.start <= self.range.len());
assert!(range.len() <= self.range.len() - range.start);
Rc::new(PwMemSlice {
mem: self.clone(),
range: self.range.start + range.start..self.range.start + range.end,
})
}
}
impl<T: Pod> PwMemTyped<T> {
pub unsafe fn read(&self) -> &T {
unsafe { (self.mem.map.ptr.cast::<u8>().add(self.offset) as *const T).deref() }
}
pub unsafe fn write(&self) -> &mut T {
unsafe { (self.mem.map.ptr.cast::<u8>().add(self.offset) as *mut T).deref_mut() }
}
}
#[derive(Debug, Error)]
pub enum PwMemError {
#[error("mmap failed")]
MmapFailed(#[source] OsError),
#[error("memid {0} does not exist")]
MemidDoesNotExist(u32),
}

52
src/pipewire/pw_object.rs Normal file
View file

@ -0,0 +1,52 @@
use {
crate::{pipewire::pw_parser::PwParser, utils::numcell::NumCell},
std::{cell::Cell, fmt::Debug, rc::Rc},
thiserror::Error,
};
pub trait PwObjectBase {
fn data(&self) -> &PwObjectData;
fn interface(&self) -> &str;
fn handle_msg(self: Rc<Self>, opcode: u8, parser: PwParser<'_>) -> Result<(), PwObjectError>;
fn event_name(&self, opcode: u8) -> Option<&'static str>;
}
pub trait PwObject: PwObjectBase {
fn bound_id(&self, id: u32) {
let _ = id;
}
fn done(&self) {}
fn break_loops(&self) {}
}
pub struct PwObjectData {
pub id: u32,
pub bound_id: Cell<Option<u32>>,
pub sync_id: NumCell<u32>,
}
#[derive(Debug, Error)]
#[error("An error occurred in a `{interface}`")]
pub struct PwObjectError {
pub interface: &'static str,
#[source]
pub source: PwObjectErrorType,
}
#[derive(Debug, Error)]
pub enum PwObjectErrorType {
#[error("Unknown event {0}")]
UnknownEvent(u8),
#[error("An error occurred in event `{method}`")]
EventError {
method: &'static str,
#[source]
source: Box<dyn std::error::Error>,
},
}
pub trait PwOpcode: Debug {
fn id(&self) -> u8;
}

312
src/pipewire/pw_parser.rs Normal file
View file

@ -0,0 +1,312 @@
#![allow(non_upper_case_globals)]
use {
crate::pipewire::pw_pod::{
PW_CHOICE_None, PW_TYPE_Array, PW_TYPE_Bitmap, PW_TYPE_Bool, PW_TYPE_Bytes, PW_TYPE_Choice,
PW_TYPE_Double, PW_TYPE_Fd, PW_TYPE_Float, PW_TYPE_Fraction, PW_TYPE_Id, PW_TYPE_Int,
PW_TYPE_Long, PW_TYPE_None, PW_TYPE_Object, PW_TYPE_Pod, PW_TYPE_Pointer,
PW_TYPE_Rectangle, PW_TYPE_Sequence, PW_TYPE_String, PW_TYPE_Struct, PwChoiceType,
PwControlType, PwPod, PwPodArray, PwPodChoice, PwPodControl, PwPodFraction, PwPodObject,
PwPodObjectType, PwPodPointer, PwPodRectangle, PwPodSequence, PwPodStruct, PwPodType,
PwPointerType, PwProp, PwPropFlag,
},
ahash::AHashMap,
bstr::{BStr, BString, ByteSlice},
std::{fmt::Debug, mem::MaybeUninit, rc::Rc},
thiserror::Error,
uapi::{OwnedFd, Pod},
};
#[derive(Debug, Error)]
pub enum PwParserError {
#[error("Unexpected EOF")]
UnexpectedEof,
#[error("Message references an FD that is out of bounds")]
MissingFd,
#[error("Array element type has size of 0")]
ZeroSizedArrayElementType,
#[error("Unknown POD type: {0:?}")]
UnknownType(PwPodType),
#[error("Unexpected POD type: Expected {0:?}, got {1:?}")]
UnexpectedPodType(PwPodType, PwPodType),
}
#[derive(Copy, Clone)]
pub struct PwParser<'a> {
data: &'a [u8],
fds: &'a [Rc<OwnedFd>],
pos: usize,
}
impl<'a> PwParser<'a> {
pub fn new(data: &'a [u8], fds: &'a [Rc<OwnedFd>]) -> Self {
Self { data, fds, pos: 0 }
}
pub fn reset(&mut self) {
self.pos = 0;
}
fn read_raw<T: Pod>(&mut self, offset: usize) -> Result<T, PwParserError> {
if self.pos + offset + size_of::<T>() <= self.data.len() {
unsafe {
let mut res = MaybeUninit::uninit();
let src = self.data[self.pos + offset..].as_ptr();
std::ptr::copy_nonoverlapping(src, res.as_mut_ptr() as _, size_of::<T>());
Ok(res.assume_init())
}
} else {
Err(PwParserError::UnexpectedEof)
}
}
pub fn len(&self) -> usize {
self.data.len() - self.pos
}
pub fn pos(&self) -> usize {
self.pos
}
fn read_array(&mut self, offset: usize, len: usize) -> Result<PwPodArray<'a>, PwParserError> {
let child_len = self.read_raw::<u32>(offset)? as usize;
if child_len == 0 {
return Err(PwParserError::ZeroSizedArrayElementType);
}
let ty = PwPodType(self.read_raw(offset + 4)?);
Ok(PwPodArray {
ty,
child_len,
n_elements: (len - 8) / child_len,
elements: PwParser::new(
&self.data[self.pos + offset + 8..self.pos + offset + len],
self.fds,
),
})
}
pub fn read_dict_struct(&mut self) -> Result<AHashMap<BString, BString>, PwParserError> {
let s2 = self.read_struct()?;
let mut p3 = s2.fields;
let num_dict_entries = p3.read_int()?;
let mut de = AHashMap::new();
for _ in 0..num_dict_entries {
de.insert(p3.read_string()?.to_owned(), p3.read_string()?.to_owned());
}
Ok(de)
}
pub fn read_struct(&mut self) -> Result<PwPodStruct<'a>, PwParserError> {
match self.read_pod()? {
PwPod::Struct(s) => Ok(s),
v => Err(PwParserError::UnexpectedPodType(PW_TYPE_Struct, v.ty())),
}
}
pub fn read_uint(&mut self) -> Result<u32, PwParserError> {
self.read_int().map(|v| v as u32)
}
pub fn read_int(&mut self) -> Result<i32, PwParserError> {
match self.read_value()? {
PwPod::Int(s) => Ok(s),
v => Err(PwParserError::UnexpectedPodType(PW_TYPE_Int, v.ty())),
}
}
pub fn read_object_opt(&mut self) -> Result<Option<PwPodObject<'_>>, PwParserError> {
match self.read_pod()? {
PwPod::Object(p) => Ok(Some(p)),
PwPod::None => Ok(None),
v => Err(PwParserError::UnexpectedPodType(PW_TYPE_Object, v.ty())),
}
}
pub fn read_object(&mut self) -> Result<PwPodObject<'_>, PwParserError> {
match self.read_object_opt()? {
Some(p) => Ok(p),
_ => Err(PwParserError::UnexpectedPodType(
PW_TYPE_Object,
PW_TYPE_None,
)),
}
}
pub fn read_id(&mut self) -> Result<u32, PwParserError> {
match self.read_value()? {
PwPod::Id(s) => Ok(s),
v => Err(PwParserError::UnexpectedPodType(PW_TYPE_Id, v.ty())),
}
}
pub fn read_fd_opt(&mut self) -> Result<Option<Rc<OwnedFd>>, PwParserError> {
match self.read_pod()? {
PwPod::Fd(idx) if idx == !0 => Ok(None),
PwPod::Fd(idx) => match self.fds.get(idx as usize) {
Some(fd) => Ok(Some(fd.clone())),
_ => Err(PwParserError::MissingFd),
},
v => Err(PwParserError::UnexpectedPodType(PW_TYPE_Id, v.ty())),
}
}
pub fn read_fd(&mut self) -> Result<Rc<OwnedFd>, PwParserError> {
match self.read_fd_opt()? {
Some(fd) => Ok(fd),
_ => Err(PwParserError::MissingFd),
}
}
pub fn read_ulong(&mut self) -> Result<u64, PwParserError> {
self.read_long().map(|l| l as _)
}
pub fn read_long(&mut self) -> Result<i64, PwParserError> {
match self.read_value()? {
PwPod::Long(s) => Ok(s),
v => Err(PwParserError::UnexpectedPodType(PW_TYPE_Long, v.ty())),
}
}
pub fn read_string(&mut self) -> Result<&'a BStr, PwParserError> {
match self.read_value()? {
PwPod::String(s) => Ok(s),
v => Err(PwParserError::UnexpectedPodType(PW_TYPE_String, v.ty())),
}
}
pub fn read_value(&mut self) -> Result<PwPod<'a>, PwParserError> {
let mut v = self.read_pod();
if let Ok(PwPod::Choice(v)) = &mut v
&& v.ty == PW_CHOICE_None
&& v.elements.n_elements > 0
{
return v
.elements
.elements
.read_pod_body_packed(v.elements.ty, v.elements.child_len);
}
v
}
pub fn read_pod(&mut self) -> Result<PwPod<'a>, PwParserError> {
let len = self.read_raw::<u32>(0)? as usize;
let ty = PwPodType(self.read_raw(4)?);
self.pos += 8;
self.read_pod_body(ty, len)
}
pub fn read_pod_body_packed(
&mut self,
ty: PwPodType,
len: usize,
) -> Result<PwPod<'a>, PwParserError> {
self.read_pod_body2(ty, len, true)
}
pub fn read_pod_body(&mut self, ty: PwPodType, len: usize) -> Result<PwPod<'a>, PwParserError> {
self.read_pod_body2(ty, len, false)
}
fn read_pod_body2(
&mut self,
ty: PwPodType,
len: usize,
packed: bool,
) -> Result<PwPod<'a>, PwParserError> {
let size = if packed { len } else { (len + 7) & !7 };
if self.len() < size {
return Err(PwParserError::UnexpectedEof);
}
let val = match ty {
PW_TYPE_None => PwPod::None,
PW_TYPE_Bool => PwPod::Bool(self.read_raw::<i32>(0)? != 0),
PW_TYPE_Id => PwPod::Id(self.read_raw(0)?),
PW_TYPE_Int => PwPod::Int(self.read_raw(0)?),
PW_TYPE_Long => PwPod::Long(self.read_raw(0)?),
PW_TYPE_Float => PwPod::Float(self.read_raw(0)?),
PW_TYPE_Double => PwPod::Double(self.read_raw(0)?),
PW_TYPE_String => {
let s = if len == 0 {
&[][..]
} else {
&self.data[self.pos..self.pos + len - 1]
};
PwPod::String(s.as_bstr())
}
PW_TYPE_Bytes => PwPod::Bytes(&self.data[self.pos..self.pos + len]),
PW_TYPE_Rectangle => PwPod::Rectangle(PwPodRectangle {
width: self.read_raw(0)?,
height: self.read_raw(4)?,
}),
PW_TYPE_Fraction => PwPod::Fraction(PwPodFraction {
num: self.read_raw(0)?,
denom: self.read_raw(4)?,
}),
PW_TYPE_Bitmap => PwPod::Bitmap(&self.data[self.pos..self.pos + len]),
PW_TYPE_Array => PwPod::Array(self.read_array(0, len)?),
PW_TYPE_Struct => PwPod::Struct(PwPodStruct {
fields: PwParser::new(&self.data[self.pos..self.pos + len], self.fds),
}),
PW_TYPE_Object => PwPod::Object(PwPodObject {
ty: PwPodObjectType(self.read_raw(0)?),
id: self.read_raw(4)?,
probs: PwParser::new(&self.data[self.pos + 8..self.pos + len], self.fds),
}),
PW_TYPE_Sequence => PwPod::Sequence(PwPodSequence {
unit: self.read_raw(0)?,
controls: PwParser::new(&self.data[self.pos + 8..self.pos + len], self.fds),
}),
PW_TYPE_Pointer => PwPod::Pointer(PwPodPointer {
_ty: PwPointerType(self.read_raw(0)?),
_value: self.read_raw(8)?,
}),
PW_TYPE_Fd => PwPod::Fd(self.read_raw(0)?),
PW_TYPE_Choice => PwPod::Choice(PwPodChoice {
ty: PwChoiceType(self.read_raw(0)?),
flags: self.read_raw(4)?,
elements: self.read_array(8, len - 8)?,
}),
PW_TYPE_Pod => {
let pos = self.pos;
let pod = self.read_pod()?;
self.pos = pos;
pod
}
_ => return Err(PwParserError::UnknownType(ty)),
};
self.pos += size;
Ok(val)
}
pub fn read_prop(&mut self) -> Result<PwProp<'a>, PwParserError> {
let key = self.read_raw(0)?;
let flags = PwPropFlag(self.read_raw(4)?);
self.pos += 8;
Ok(PwProp {
key,
flags,
pod: self.read_pod()?,
})
}
pub fn read_control(&mut self) -> Result<PwPodControl<'a>, PwParserError> {
let offset = self.read_raw(0)?;
let ty = PwControlType(self.read_raw(4)?);
self.pos += 8;
Ok(PwPodControl {
_offset: offset,
_ty: ty,
_value: self.read_pod()?,
})
}
pub fn skip(&mut self) -> Result<(), PwParserError> {
let size = self.read_raw::<u32>(0)? as usize;
if self.len() < size + 8 {
return Err(PwParserError::UnexpectedEof);
}
self.pos += size + 8;
Ok(())
}
}

1451
src/pipewire/pw_pod.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,464 @@
use {
crate::{
pipewire::{
pw_parser::PwParser,
pw_pod::{
PW_COMMAND_Node, PW_OBJECT_Format, PW_OBJECT_ParamBuffers, PW_OBJECT_ParamIO,
PW_OBJECT_ParamLatency, PW_OBJECT_ParamMeta, PW_OBJECT_ParamPortConfig,
PW_OBJECT_ParamProcessLatency, PW_OBJECT_ParamProfile, PW_OBJECT_ParamRoute,
PW_OBJECT_Profiler, PW_OBJECT_PropInfo, PW_OBJECT_Props, PW_TYPE_Id, PwPod,
PwPodArray, PwPodObject, PwPodObjectType, PwPodSequence, PwPodStruct, PwPodType,
PwProp, SPA_FORMAT_AUDIO_bitorder, SPA_FORMAT_AUDIO_format,
SPA_FORMAT_AUDIO_iec958Codec, SPA_FORMAT_AUDIO_position,
SPA_FORMAT_VIDEO_H264_alignment, SPA_FORMAT_VIDEO_H264_streamFormat,
SPA_FORMAT_VIDEO_chromaSite, SPA_FORMAT_VIDEO_colorMatrix,
SPA_FORMAT_VIDEO_colorPrimaries, SPA_FORMAT_VIDEO_colorRange,
SPA_FORMAT_VIDEO_format, SPA_FORMAT_VIDEO_interlaceMode,
SPA_FORMAT_VIDEO_multiviewFlags, SPA_FORMAT_VIDEO_multiviewMode,
SPA_FORMAT_VIDEO_transferFunction, SPA_FORMAT_mediaSubtype, SPA_FORMAT_mediaType,
SPA_PARAM_BUFFERS_dataType, SPA_PARAM_IO_id, SPA_PARAM_META_type,
SPA_PARAM_PORT_CONFIG_direction, SPA_PARAM_PORT_CONFIG_mode,
SPA_PARAM_PROFILE_available, SPA_PARAM_ROUTE_available, SPA_PARAM_ROUTE_direction,
SPA_PROP_channelMap, SPA_PROP_iec958Codecs, SpaAudioChannel, SpaAudioFormat,
SpaAudioIec958Codec, SpaDataTypes, SpaDirection, SpaFormat, SpaH264Alignment,
SpaH264StreamFormat, SpaIoType, SpaMediaSubtype, SpaMediaType, SpaMetaType,
SpaNodeCommand, SpaParamAvailability, SpaParamBitorder, SpaParamBuffers,
SpaParamIo, SpaParamLatency, SpaParamMeta, SpaParamPortConfig,
SpaParamPortConfigMode, SpaParamProcessLatency, SpaParamProfile, SpaParamRoute,
SpaParamType, SpaProfiler, SpaProp, SpaPropInfo, SpaVideoChromaSite,
SpaVideoColorMatrix, SpaVideoColorPrimaries, SpaVideoColorRange, SpaVideoFormat,
SpaVideoInterlaceMode, SpaVideoMultiviewFlags, SpaVideoMultiviewMode,
SpaVideoTransferFunction,
},
},
utils::errorfmt::ErrorFmt,
},
std::{
fmt,
fmt::{Debug, DebugList, Formatter, Write},
},
};
trait PwPodObjectDebugger: Sync {
fn debug_property(&self, fmt: &mut Formatter<'_>, value: PwProp<'_>) -> std::fmt::Result;
fn id_name(&self, id: u32) -> Option<&'static str>;
}
struct PwPodObjectDebuggerSimple<F, G, H> {
key_name: F,
debug_pod: G,
id_name: H,
}
impl<F, G, H> PwPodObjectDebugger for PwPodObjectDebuggerSimple<F, G, H>
where
F: Fn(u32) -> Option<&'static str> + Sync,
G: Fn(u32, &mut Formatter<'_>, PwPod<'_>) -> std::fmt::Result + Sync,
H: Fn(u32) -> Option<&'static str> + Sync,
{
fn debug_property(&self, fmt: &mut Formatter<'_>, value: PwProp<'_>) -> std::fmt::Result {
let mut s = fmt.debug_struct("PwProp");
match (self.key_name)(value.key) {
Some(n) => s.field("key", &n),
_ => s.field("key", &value.key),
};
s.field("flags", &value.flags)
.field(
"pod",
&fmt::from_fn(|f| (self.debug_pod)(value.key, f, value.pod)),
)
.finish()
}
fn id_name(&self, id: u32) -> Option<&'static str> {
(self.id_name)(id)
}
}
fn choice_debug<F>(fmt: &mut Formatter<'_>, p: PwPod<'_>, ty: PwPodType, f: F) -> std::fmt::Result
where
F: Fn(&mut Formatter<'_>, PwPod<'_>) -> std::fmt::Result,
{
match p {
PwPod::Choice(c) if c.elements.ty == ty => fmt
.debug_struct("choice")
.field("ty", &c.ty)
.field("flags", &c.flags)
.field(
"elements",
&fmt::from_fn(|fmt| {
array_body_debug(fmt, c.elements, |l, p| {
match p.read_pod_body_packed(ty, c.elements.child_len) {
Ok(p) => {
l.entry(&fmt::from_fn(|fmt| f(fmt, p)));
true
}
Err(e) => {
let e = ErrorFmt(e);
l.entry(&fmt::from_fn(|fmt| {
write!(fmt, "Could not read choice element: {}", e)
}));
false
}
}
})
}),
)
.finish(),
_ if p.ty() == ty => f(fmt, p),
_ => p.fmt(fmt),
}
}
fn id_debug<F>(fmt: &mut Formatter<'_>, p: PwPod<'_>, f: F) -> std::fmt::Result
where
F: Fn(&mut Formatter<'_>, u32) -> std::fmt::Result,
{
choice_debug(fmt, p, PW_TYPE_Id, |fmt, p| match p {
PwPod::Id(id) => f(fmt, id),
_ => p.fmt(fmt),
})
}
fn array_body_debug<F>(fmt: &mut Formatter<'_>, mut a: PwPodArray<'_>, f: F) -> std::fmt::Result
where
F: Fn(&mut DebugList, &mut PwParser<'_>) -> bool,
{
let mut l = fmt.debug_list();
for _ in 0..a.n_elements {
if !f(&mut l, &mut a.elements) {
break;
}
}
l.finish()
}
fn array_debug<F>(fmt: &mut Formatter<'_>, p: PwPod<'_>, ty: PwPodType, f: F) -> std::fmt::Result
where
F: Fn(&mut DebugList, &mut PwParser<'_>) -> bool,
{
match p {
PwPod::Array(a) if a.ty == ty => array_body_debug(fmt, a, f),
_ => p.fmt(fmt),
}
}
fn array_id_debug<F, T>(fmt: &mut Formatter<'_>, p: PwPod<'_>, f: F) -> std::fmt::Result
where
F: Fn(&mut DebugList, u32) -> T,
{
array_debug(fmt, p, PW_TYPE_Id, |l, p| match p.read_id() {
Ok(a) => {
f(l, a);
true
}
Err(e) => {
let e = ErrorFmt(e);
l.entry(&fmt::from_fn(|f| write!(f, "Could not read id: {}", e)));
false
}
})
}
fn object_id_name(id: u32) -> Option<&'static str> {
SpaParamType(id).name()
}
fn command_id_name(id: u32) -> Option<&'static str> {
SpaNodeCommand(id).name()
}
static PROP_INFO_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple {
key_name: |key| SpaPropInfo(key).name(),
debug_pod: |_, f: &mut Formatter<'_>, p: PwPod<'_>| p.fmt(f),
id_name: object_id_name,
};
static PROPS_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple {
key_name: |key| SpaProp(key).name(),
id_name: object_id_name,
debug_pod: |key, f: &mut Formatter<'_>, p: PwPod<'_>| match SpaProp(key) {
SPA_PROP_channelMap => array_id_debug(f, p, |l, a| {
l.entry(&SpaAudioChannel(a));
}),
SPA_PROP_iec958Codecs => array_id_debug(f, p, |l, a| {
l.entry(&SpaAudioIec958Codec(a));
}),
_ => p.fmt(f),
},
};
static FORMAT_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple {
key_name: |key| SpaFormat(key).name(),
id_name: object_id_name,
debug_pod: |key, f: &mut Formatter<'_>, p: PwPod<'_>| match SpaFormat(key) {
SPA_FORMAT_mediaType => id_debug(f, p, |f, a| SpaMediaType(a).fmt(f)),
SPA_FORMAT_mediaSubtype => id_debug(f, p, |f, a| SpaMediaSubtype(a).fmt(f)),
SPA_FORMAT_AUDIO_format => id_debug(f, p, |f, a| SpaAudioFormat(a).fmt(f)),
SPA_FORMAT_AUDIO_position => array_id_debug(f, p, |l, a| {
l.entry(&SpaAudioChannel(a));
}),
SPA_FORMAT_AUDIO_iec958Codec => id_debug(f, p, |f, a| SpaAudioIec958Codec(a).fmt(f)),
SPA_FORMAT_AUDIO_bitorder => id_debug(f, p, |f, a| SpaParamBitorder(a).fmt(f)),
SPA_FORMAT_VIDEO_format => id_debug(f, p, |f, a| SpaVideoFormat(a).fmt(f)),
SPA_FORMAT_VIDEO_interlaceMode => id_debug(f, p, |f, a| SpaVideoInterlaceMode(a).fmt(f)),
SPA_FORMAT_VIDEO_multiviewMode => id_debug(f, p, |f, a| SpaVideoMultiviewMode(a).fmt(f)),
SPA_FORMAT_VIDEO_multiviewFlags => id_debug(f, p, |f, a| SpaVideoMultiviewFlags(a).fmt(f)),
SPA_FORMAT_VIDEO_chromaSite => id_debug(f, p, |f, a| SpaVideoChromaSite(a).fmt(f)),
SPA_FORMAT_VIDEO_colorRange => id_debug(f, p, |f, a| SpaVideoColorRange(a).fmt(f)),
SPA_FORMAT_VIDEO_colorMatrix => id_debug(f, p, |f, a| SpaVideoColorMatrix(a).fmt(f)),
SPA_FORMAT_VIDEO_transferFunction => {
id_debug(f, p, |f, a| SpaVideoTransferFunction(a).fmt(f))
}
SPA_FORMAT_VIDEO_colorPrimaries => id_debug(f, p, |f, a| SpaVideoColorPrimaries(a).fmt(f)),
SPA_FORMAT_VIDEO_H264_streamFormat => id_debug(f, p, |f, a| SpaH264StreamFormat(a).fmt(f)),
SPA_FORMAT_VIDEO_H264_alignment => id_debug(f, p, |f, a| SpaH264Alignment(a).fmt(f)),
_ => p.fmt(f),
},
};
static PARAM_BUFFERS_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple {
key_name: |key| SpaParamBuffers(key).name(),
id_name: object_id_name,
debug_pod: |key, f: &mut Formatter<'_>, p: PwPod<'_>| match SpaParamBuffers(key) {
SPA_PARAM_BUFFERS_dataType => match p {
PwPod::Int(v) => SpaDataTypes(v as _).fmt(f),
_ => p.fmt(f),
},
_ => p.fmt(f),
},
};
static PARAM_META_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple {
key_name: |key| SpaParamMeta(key).name(),
id_name: object_id_name,
debug_pod: |key, f: &mut Formatter<'_>, p: PwPod<'_>| match SpaParamMeta(key) {
SPA_PARAM_META_type => id_debug(f, p, |f, b| SpaMetaType(b).fmt(f)),
_ => p.fmt(f),
},
};
static PARAM_IO_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple {
key_name: |key| SpaParamIo(key).name(),
id_name: object_id_name,
debug_pod: |key, f: &mut Formatter<'_>, p: PwPod<'_>| match SpaParamIo(key) {
SPA_PARAM_IO_id => id_debug(f, p, |f, b| SpaIoType(b).fmt(f)),
_ => p.fmt(f),
},
};
static PARAM_PROFILE_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple {
key_name: |key| SpaParamProfile(key).name(),
id_name: object_id_name,
debug_pod: |key, f: &mut Formatter<'_>, p: PwPod<'_>| match SpaParamProfile(key) {
SPA_PARAM_PROFILE_available => id_debug(f, p, |f, b| SpaParamAvailability(b).fmt(f)),
_ => p.fmt(f),
},
};
static PARAM_PORT_CONFIG_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple {
key_name: |key| SpaParamPortConfig(key).name(),
id_name: object_id_name,
debug_pod: |key, f: &mut Formatter<'_>, p: PwPod<'_>| match SpaParamPortConfig(key) {
SPA_PARAM_PORT_CONFIG_direction => id_debug(f, p, |f, b| SpaDirection(b).fmt(f)),
SPA_PARAM_PORT_CONFIG_mode => id_debug(f, p, |f, b| SpaParamPortConfigMode(b).fmt(f)),
_ => p.fmt(f),
},
};
static PARAM_ROUTE_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple {
key_name: |key| SpaParamRoute(key).name(),
id_name: object_id_name,
debug_pod: |key, f: &mut Formatter<'_>, p: PwPod<'_>| match SpaParamRoute(key) {
SPA_PARAM_ROUTE_direction => id_debug(f, p, |f, b| SpaDirection(b).fmt(f)),
SPA_PARAM_ROUTE_available => id_debug(f, p, |f, b| SpaParamAvailability(b).fmt(f)),
_ => p.fmt(f),
},
};
static PROFILER_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple {
key_name: |key| SpaProfiler(key).name(),
id_name: object_id_name,
debug_pod: |_, f: &mut Formatter<'_>, p: PwPod<'_>| p.fmt(f),
};
static PARAM_LATENCY_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple {
key_name: |key| SpaParamLatency(key).name(),
id_name: object_id_name,
debug_pod: |_, f: &mut Formatter<'_>, p: PwPod<'_>| p.fmt(f),
};
static PARAM_PROCESS_LATENCY_DEBUGGER: &'static dyn PwPodObjectDebugger =
&PwPodObjectDebuggerSimple {
key_name: |key| SpaParamProcessLatency(key).name(),
id_name: object_id_name,
debug_pod: |_, f: &mut Formatter<'_>, p: PwPod<'_>| p.fmt(f),
};
static COMMAND_NODE_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple {
key_name: |key| SpaNodeCommand(key).name(),
id_name: command_id_name,
debug_pod: |_, f: &mut Formatter<'_>, p: PwPod<'_>| p.fmt(f),
};
fn object_debugger(obj: PwPodObjectType) -> Option<&'static dyn PwPodObjectDebugger> {
let res: &dyn PwPodObjectDebugger = match obj {
PW_OBJECT_PropInfo => PROP_INFO_DEBUGGER,
PW_OBJECT_Props => PROPS_DEBUGGER,
PW_OBJECT_Format => FORMAT_DEBUGGER,
PW_OBJECT_ParamBuffers => PARAM_BUFFERS_DEBUGGER,
PW_OBJECT_ParamMeta => PARAM_META_DEBUGGER,
PW_OBJECT_ParamIO => PARAM_IO_DEBUGGER,
PW_OBJECT_ParamProfile => PARAM_PROFILE_DEBUGGER,
PW_OBJECT_ParamPortConfig => PARAM_PORT_CONFIG_DEBUGGER,
PW_OBJECT_ParamRoute => PARAM_ROUTE_DEBUGGER,
PW_OBJECT_Profiler => PROFILER_DEBUGGER,
PW_OBJECT_ParamLatency => PARAM_LATENCY_DEBUGGER,
PW_OBJECT_ParamProcessLatency => PARAM_PROCESS_LATENCY_DEBUGGER,
PW_COMMAND_Node => COMMAND_NODE_DEBUGGER,
_ => return None,
};
Some(res)
}
impl<'a> Debug for PwPodObject<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let debugger = object_debugger(self.ty);
let mut s = f.debug_struct("object");
s.field("type", &self.ty);
let name;
let mut id: &dyn Debug = &self.id;
if let Some(d) = debugger
&& let Some(n) = d.id_name(self.id)
{
name = n;
id = &name;
}
s.field("id", id);
s.field(
"props",
&fmt::from_fn(|f| {
let mut l = f.debug_list();
let mut parser = self.probs;
while parser.len() > 0 {
match parser.read_prop() {
Ok(p) => match debugger {
Some(d) => l.entry(&fmt::from_fn(|fmt| d.debug_property(fmt, p))),
_ => l.entry(&p),
},
Err(e) => {
let e = ErrorFmt(e);
l.entry(&fmt::from_fn(|f| {
write!(f, "Could not read object property: {}", &e)
}));
break;
}
};
}
l.finish()
}),
);
s.finish()
}
}
impl<'a> Debug for PwPodSequence<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut s = f.debug_struct("sequence");
s.field("unit", &self.unit);
s.field(
"controls",
&fmt::from_fn(|f| {
let mut l = f.debug_list();
let mut parser = self.controls;
while parser.len() > 0 {
match parser.read_control() {
Ok(c) => l.entry(&c),
Err(e) => {
let e = ErrorFmt(e);
l.entry(&fmt::from_fn(|f| {
write!(f, "Could not read control element: {}", &e)
}));
break;
}
};
}
l.finish()
}),
);
s.finish()
}
}
impl<'a> Debug for PwPodStruct<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut parser = self.fields;
let mut s = f.debug_struct("struct");
let mut field = String::new();
for i in 0.. {
if parser.len() == 0 {
break;
}
field.clear();
let _ = write!(&mut field, "\"{}\"", i);
match parser.read_pod() {
Ok(p) => s.field(&field, &p),
Err(e) => {
let e = ErrorFmt(e);
s.field(
&field,
&fmt::from_fn(|f| write!(f, "Could not parse struct field: {}", &e)),
);
break;
}
};
}
s.finish()
}
}
impl<'a> Debug for PwPodArray<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut list = f.debug_list();
let mut parser = self.elements;
for _ in 0..self.n_elements {
match parser.read_pod_body_packed(self.ty, self.child_len) {
Ok(e) => list.entry(&e),
Err(e) => {
let e = ErrorFmt(e);
list.entry(&fmt::from_fn(|f| {
write!(f, "Could not parse array element: {}", &e)
}));
break;
}
};
}
list.finish()
}
}
impl<'a> Debug for PwPod<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
PwPod::None => write!(f, "None"),
PwPod::Bool(b) => write!(f, "{}", b),
PwPod::Id(id) => write!(f, "id({})", id),
PwPod::Int(i) => write!(f, "int({})", i),
PwPod::Long(l) => write!(f, "long({})", l),
PwPod::Float(v) => write!(f, "float({})", v),
PwPod::Double(d) => write!(f, "double({})", d),
PwPod::String(s) => write!(f, "string({:?})", s),
PwPod::Bytes(b) => write!(f, "bytes(len = {})", b.len()),
PwPod::Rectangle(r) => write!(f, "rectangle({}x{})", r.width, r.height),
PwPod::Fraction(v) => write!(f, "fraction({}/{})", v.num, v.denom),
PwPod::Bitmap(b) => write!(f, "bitmap(len = {})", b.len()),
PwPod::Array(a) => a.fmt(f),
PwPod::Struct(s) => s.fmt(f),
PwPod::Object(o) => o.fmt(f),
PwPod::Sequence(s) => s.fmt(f),
PwPod::Pointer(p) => p.fmt(f),
PwPod::Fd(v) => write!(f, "fd({})", v),
PwPod::Choice(c) => c.fmt(f),
}
}
}

352
src/portal.rs Normal file
View file

@ -0,0 +1,352 @@
mod ptl_display;
mod ptl_remote_desktop;
mod ptl_render_ctx;
mod ptl_screencast;
mod ptl_session;
mod ptl_text;
mod ptr_gui;
use {
crate::{
async_engine::AsyncEngine,
cli::GlobalArgs,
cmm::cmm_manager::ColorManager,
dbus::{
BUS_DEST, BUS_PATH, DBUS_NAME_FLAG_DO_NOT_QUEUE, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER,
Dbus, DbusSocket,
},
eventfd_cache::EventfdCache,
forker::ForkerError,
io_uring::IoUring,
logger::{LogLevel, Logger},
pipewire::pw_con::{PwCon, PwConHolder, PwConOwner},
portal::{
ptl_display::{PortalDisplay, PortalDisplayId, watch_displays},
ptl_remote_desktop::add_remote_desktop_dbus_members,
ptl_render_ctx::PortalRenderCtx,
ptl_screencast::add_screencast_dbus_members,
ptl_session::PortalSession,
},
utils::{
clone3::{Forked, fork_with_pidfd},
copyhashmap::CopyHashMap,
errorfmt::ErrorFmt,
line_logger::log_lines,
numcell::NumCell,
oserror::{OsError, OsErrorExt},
pipe::{Pipe, pipe},
process_name::set_process_name,
run_toplevel::RunToplevel,
xrd::xrd,
},
version::VERSION,
video::dmabuf::DmaBufIds,
wheel::Wheel,
wire_dbus::org,
},
std::{
ffi::OsStr,
io::{BufReader, BufWriter},
os::unix::{ffi::OsStrExt, process::CommandExt},
process::{Command, exit},
rc::{Rc, Weak},
sync::Arc,
},
thiserror::Error,
uapi::{OwnedFd, WEXITSTATUS, c, getpid},
};
const PORTAL_SUCCESS: u32 = 0;
#[expect(dead_code)]
const PORTAL_CANCELLED: u32 = 1;
#[expect(dead_code)]
const PORTAL_ENDED: u32 = 2;
pub fn run_freestanding(global: GlobalArgs) {
let logger = Logger::install_stderr(global.log_level);
run(logger, true);
}
#[derive(Debug, Error)]
pub enum PortalError {
#[error("Could not create pipe")]
CreatePipe(#[source] OsError),
#[error("Could not fork")]
Fork(#[source] ForkerError),
}
pub struct PortalStartup {
logs: Rc<OwnedFd>,
pid: c::pid_t,
pidfd: Rc<OwnedFd>,
}
impl PortalStartup {
pub async fn spawn(self, eng: Rc<AsyncEngine>, ring: Rc<IoUring>, logger: Arc<Logger>) {
let f1 = eng.spawn("check portal exit code", {
let ring = ring.clone();
async move {
if let Err(e) = ring.readable(&self.pidfd).await {
log::error!(
"Could not wait for portal pidfd to become readable: {}",
ErrorFmt(e)
);
return;
}
let (_, status) = match uapi::waitpid(self.pid, 0).to_os_error() {
Ok(r) => r,
Err(e) => {
log::error!(
"Could not retrieve exit status of portal ({}): {}",
self.pid,
ErrorFmt(e),
);
return;
}
};
let status = WEXITSTATUS(status);
if status != 0 {
log::error!("Portal exited with non-0 exit code: {status}");
}
}
});
let f2 = eng.spawn("portal logger", {
let ring = ring.clone();
let logger = logger.clone();
async move {
let res = log_lines(&ring, &self.logs, |left, right| {
logger.write_raw(left);
logger.write_raw(right);
logger.write_raw(b" (portal)\n");
})
.await;
if let Err(e) = res {
log::error!("Could not read portal logs: {}", ErrorFmt(e));
}
}
});
f1.await;
f2.await;
}
}
pub fn run_from_compositor(level: LogLevel) -> Result<PortalStartup, PortalError> {
let Pipe { read, write } = match pipe() {
Ok(p) => p,
Err(e) => return Err(PortalError::CreatePipe(e)),
};
let fork = match fork_with_pidfd(false) {
Ok(f) => f,
Err(e) => return Err(PortalError::Fork(e)),
};
match fork {
Forked::Parent { pidfd, pid } => Ok(PortalStartup {
logs: Rc::new(read),
pid,
pidfd: Rc::new(pidfd),
}),
Forked::Child { .. } => {
drop(read);
let logger = Logger::install_pipe(write, level);
run(logger, false);
}
}
}
fn run(logger: Arc<Logger>, freestanding: bool) -> ! {
let Pipe { read, write } = match pipe() {
Ok(p) => p,
Err(e) => {
fatal!("Could not create a pipe: {}", ErrorFmt(e));
}
};
let fork = match fork_with_pidfd(false) {
Ok(f) => f,
Err(e) => {
fatal!("Could not fork: {}", ErrorFmt(e));
}
};
let Forked::Parent { pid, .. } = fork else {
drop(read);
run2(logger, write);
exit(0);
};
drop(write);
let read = BufReader::new(read);
let Ok(log_file) = bincode::deserialize_from::<_, Vec<u8>>(read) else {
let (_, status) = match uapi::waitpid(pid, 0).to_os_error() {
Ok(r) => r,
Err(e) => {
fatal!(
"Could not retrieve exit status of portal ({pid}): {}",
ErrorFmt(e),
);
}
};
exit(WEXITSTATUS(status));
};
if freestanding {
let e = Command::new("tail")
.arg("-f")
.arg("-n")
.arg("+1")
.arg(OsStr::from_bytes(&log_file))
.exec();
fatal!("Could not exec `tail`: {}", ErrorFmt(e));
}
exit(0);
}
fn run2(logger: Arc<Logger>, path_sink: OwnedFd) {
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 _f = eng.spawn(
"portal",
run_async(eng.clone(), ring.clone(), logger, path_sink),
);
if let Err(e) = ring.run() {
fatal!("The IO-uring returned an error: {}", ErrorFmt(e));
}
}
async fn run_async(
eng: Rc<AsyncEngine>,
ring: Rc<IoUring>,
logger: Arc<Logger>,
path_sink: OwnedFd,
) {
let (_rtl_future, rtl) = RunToplevel::install(&eng);
let dbus = Dbus::new(&eng, &ring, &rtl);
let dbus = init_dbus_session(&dbus, logger, path_sink).await;
let xrd = match xrd() {
Some(xrd) => xrd,
_ => {
fatal!("XDG_RUNTIME_DIR is not set");
}
};
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).await {
Ok(p) => Some(p),
Err(e) => {
log::error!("Could not connect to pipewire: {}", ErrorFmt(e));
None
}
};
let eventfd_cache = EventfdCache::new(&ring, &eng);
let state = Rc::new(PortalState {
xrd,
ring,
eventfd_cache,
eng,
wheel,
displays: Default::default(),
dbus,
sessions: Default::default(),
next_id: NumCell::new(1),
render_ctxs: Default::default(),
dma_buf_ids: Default::default(),
pw_con: pw_con.as_ref().map(|c| c.con.clone()),
color_manager: ColorManager::new(),
});
if let Some(pw_con) = &pw_con {
pw_con.con.owner.set(Some(state.clone()));
}
let _root = {
let obj = state
.dbus
.add_object("/org/freedesktop/portal/desktop")
.unwrap();
if let Some(pw_con) = &pw_con {
add_screencast_dbus_members(&state, &pw_con.con, &obj);
}
add_remote_desktop_dbus_members(&state, &obj);
obj
};
watch_displays(state.clone()).await;
}
const UNIQUE_NAME: &str = "org.freedesktop.impl.portal.desktop.jay";
async fn init_dbus_session(dbus: &Dbus, logger: Arc<Logger>, path_sink: OwnedFd) -> Rc<DbusSocket> {
let session = match dbus.session().await {
Ok(s) => s,
Err(e) => {
fatal!("Could not connect to dbus session daemon: {}", ErrorFmt(e));
}
};
let rv = session
.call_async(
BUS_DEST,
BUS_PATH,
org::freedesktop::dbus::RequestName {
name: UNIQUE_NAME.into(),
flags: DBUS_NAME_FLAG_DO_NOT_QUEUE,
},
)
.await;
match rv {
Ok(r) if r.get().rv == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER => {
log::info!("Acquired unique name {}", UNIQUE_NAME);
let log_file = logger.redirect("portal");
log::info!("version = {VERSION}");
let sink = BufWriter::new(path_sink);
if let Err(e) = bincode::serialize_into(sink, log_file.as_bytes()) {
log::error!("Could not send log file to parent: {}", ErrorFmt(e));
}
if let Err(e) = uapi::setsid().to_os_error() {
log::error!("setsid failed: {}", ErrorFmt(e));
}
log::info!("pid = {}", getpid());
set_process_name("jay portal");
session
}
Ok(_) => {
log::info!("Portal is already running");
exit(0);
}
Err(e) => {
fatal!(
"Could not communicate with the session bus: {}",
ErrorFmt(e)
);
}
}
}
struct PortalState {
xrd: String,
ring: Rc<IoUring>,
eventfd_cache: Rc<EventfdCache>,
eng: Rc<AsyncEngine>,
wheel: Rc<Wheel>,
displays: CopyHashMap<PortalDisplayId, Rc<PortalDisplay>>,
dbus: Rc<DbusSocket>,
sessions: CopyHashMap<String, Rc<PortalSession>>,
next_id: NumCell<u32>,
render_ctxs: CopyHashMap<c::dev_t, Weak<PortalRenderCtx>>,
dma_buf_ids: Rc<DmaBufIds>,
pw_con: Option<Rc<PwCon>>,
color_manager: Rc<ColorManager>,
}
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");
}
}

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

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

View file

@ -0,0 +1,328 @@
mod remote_desktop_gui;
use {
crate::{
dbus::{DbusObject, PendingReply, prelude::Variant},
ifs::jay_compositor::CREATE_EI_SESSION_SINCE,
portal::{
PORTAL_SUCCESS, PortalState,
ptl_display::{PortalDisplay, PortalDisplayId},
ptl_remote_desktop::remote_desktop_gui::SelectionGui,
ptl_screencast::ScreencastPhase,
ptl_session::{PortalSession, PortalSessionReply},
},
utils::{
clonecell::{CloneCell, UnsafeCellCloneSafe},
copyhashmap::CopyHashMap,
},
wire_dbus::{
org,
org::freedesktop::impl_::portal::{
remote_desktop::{
ConnectToEIS, ConnectToEISReply, CreateSession, CreateSessionReply,
SelectDevices, SelectDevicesReply, Start, StartReply,
},
session::CloseReply as SessionCloseReply,
},
},
wl_usr::usr_ifs::usr_jay_ei_session::{UsrJayEiSession, UsrJayEiSessionOwner},
},
std::{cell::Cell, ops::Deref, rc::Rc},
uapi::OwnedFd,
};
#[derive(Clone)]
pub enum RemoteDesktopPhase {
Init,
DevicesSelected,
Selecting(Rc<SelectingDisplay>),
Starting(Rc<StartingRemoteDesktop>),
Started(Rc<StartedRemoteDesktop>),
Terminated,
}
unsafe impl UnsafeCellCloneSafe for RemoteDesktopPhase {}
pub struct SelectingDisplay {
pub session: Rc<PortalSession>,
pub request_obj: Rc<DbusObject>,
pub guis: CopyHashMap<PortalDisplayId, Rc<SelectionGui>>,
}
pub struct StartingRemoteDesktop {
pub session: Rc<PortalSession>,
pub request_obj: Rc<DbusObject>,
pub dpy: Rc<PortalDisplay>,
pub ei_session: Rc<UsrJayEiSession>,
}
pub struct StartedRemoteDesktop {
pub session: Rc<PortalSession>,
pub dpy: Rc<PortalDisplay>,
pub ei_session: Rc<UsrJayEiSession>,
pub ei_fd: Cell<Option<Rc<OwnedFd>>>,
}
bitflags! {
DeviceTypes: u32;
KEYBOARD = 1,
POINTER = 2,
TOUCHSCREEN = 4,
}
impl UsrJayEiSessionOwner for StartingRemoteDesktop {
fn created(&self, fd: &Rc<OwnedFd>) {
let started = Rc::new(StartedRemoteDesktop {
session: self.session.clone(),
dpy: self.dpy.clone(),
ei_session: self.ei_session.clone(),
ei_fd: Cell::new(Some(fd.clone())),
});
self.session
.rd_phase
.set(RemoteDesktopPhase::Started(started.clone()));
started.ei_session.owner.set(Some(started.clone()));
if let ScreencastPhase::SourcesSelected(s) = self.session.sc_phase.get() {
self.session.screencast_restore(
&self.request_obj,
s.restore_data.take(),
Some(self.dpy.clone()),
);
} else {
self.session.send_start_reply(None, None, None);
}
}
fn failed(&self, reason: &str) {
log::error!("Could not create session: {}", reason);
self.session.reply_err(reason);
self.session.kill();
}
}
impl SelectingDisplay {
pub fn starting(&self, dpy: &Rc<PortalDisplay>) {
let builder = dpy.jc.create_ei_session();
builder.set_app_id(&self.session.app);
let ei_session = builder.commit();
let starting = Rc::new(StartingRemoteDesktop {
session: self.session.clone(),
request_obj: self.request_obj.clone(),
dpy: dpy.clone(),
ei_session,
});
self.session
.rd_phase
.set(RemoteDesktopPhase::Starting(starting.clone()));
starting.ei_session.owner.set(Some(starting.clone()));
dpy.sessions.set(
self.session.session_obj.path().to_owned(),
self.session.clone(),
);
}
}
impl PortalSession {
fn dbus_select_devices(
self: &Rc<Self>,
_req: SelectDevices,
reply: PendingReply<SelectDevicesReply<'static>>,
) {
match self.rd_phase.get() {
RemoteDesktopPhase::Init => {}
_ => {
self.kill();
reply.err("Devices have already been selected");
return;
}
}
self.rd_phase.set(RemoteDesktopPhase::DevicesSelected);
reply.ok(&SelectDevicesReply {
response: PORTAL_SUCCESS,
results: Default::default(),
});
}
fn dbus_start_remote_desktop(
self: &Rc<Self>,
req: Start<'_>,
reply: PendingReply<StartReply<'static>>,
) {
match self.rd_phase.get() {
RemoteDesktopPhase::DevicesSelected => {}
_ => {
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 && dpy.jc.version >= CREATE_EI_SESSION_SINCE {
guis.set(dpy.id, SelectionGui::new(self, dpy));
}
}
if guis.is_empty() {
self.kill();
reply.err("There are no running displays");
return;
}
self.start_reply
.set(Some(PortalSessionReply::RemoteDesktop(reply)));
self.rd_phase
.set(RemoteDesktopPhase::Selecting(Rc::new(SelectingDisplay {
session: self.clone(),
request_obj: Rc::new(request_obj),
guis,
})));
}
fn dbus_connect_to_eis(
self: &Rc<Self>,
_req: ConnectToEIS,
reply: PendingReply<ConnectToEISReply>,
) {
let RemoteDesktopPhase::Started(started) = self.rd_phase.get() else {
self.kill();
reply.err("Sources have already been selected");
return;
};
let Some(fd) = started.ei_fd.take() else {
self.kill();
reply.err("EI file descriptor has already been consumed");
return;
};
reply.ok(&ConnectToEISReply { fd });
}
}
impl UsrJayEiSessionOwner for StartedRemoteDesktop {
fn destroyed(&self) {
self.session.kill();
}
}
pub(super) fn add_remote_desktop_dbus_members(state_: &Rc<PortalState>, object: &DbusObject) {
use org::freedesktop::impl_::portal::remote_desktop::*;
let state = state_.clone();
object.add_method::<CreateSession, _>(move |req, pr| {
dbus_create_session(&state, req, pr);
});
let state = state_.clone();
object.add_method::<SelectDevices, _>(move |req, pr| {
dbus_select_devices(&state, req, pr);
});
let state = state_.clone();
object.add_method::<Start, _>(move |req, pr| {
dbus_start(&state, req, pr);
});
let state = state_.clone();
object.add_method::<ConnectToEIS, _>(move |req, pr| {
dbus_connect_to_eis(&state, req, pr);
});
object.set_property::<AvailableDeviceTypes>(Variant::U32(DeviceTypes::all().0));
object.set_property::<version>(Variant::U32(2));
}
fn dbus_create_session(
state: &Rc<PortalState>,
req: CreateSession,
reply: PendingReply<CreateSessionReply<'static>>,
) {
log::info!("Create remote desktop session {:#?}", req);
if state.sessions.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(PortalSession {
_id: state.id(),
state: state.clone(),
pw_con: state.pw_con.clone(),
app: req.app_id.to_string(),
session_obj: obj,
sc_phase: CloneCell::new(ScreencastPhase::Init),
rd_phase: CloneCell::new(RemoteDesktopPhase::Init),
start_reply: Default::default(),
});
{
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(2));
}
state
.sessions
.set(req.session_handle.0.to_string(), session);
reply.ok(&CreateSessionReply {
response: PORTAL_SUCCESS,
results: Default::default(),
});
}
fn dbus_select_devices(
state: &Rc<PortalState>,
req: SelectDevices,
reply: PendingReply<SelectDevicesReply<'static>>,
) {
if let Some(s) = get_session(state, &reply, &req.session_handle.0) {
s.dbus_select_devices(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_remote_desktop(req, reply);
}
}
fn dbus_connect_to_eis(
state: &Rc<PortalState>,
req: ConnectToEIS,
reply: PendingReply<ConnectToEISReply>,
) {
if let Some(s) = get_session(state, &reply, &req.session_handle.0) {
s.dbus_connect_to_eis(req, reply);
}
}
fn get_session<T>(
state: &Rc<PortalState>,
reply: &PendingReply<T>,
handle: &str,
) -> Option<Rc<PortalSession>> {
let res = state.sessions.get(handle);
if res.is_none() {
let msg = format!("Remote desktop session `{}` does not exist", handle);
reply.err(&msg);
}
res
}

View file

@ -0,0 +1,160 @@
use {
crate::{
globals::GlobalName,
ifs::wl_seat::{BTN_LEFT, wl_pointer::PRESSED},
portal::{
ptl_display::{PortalDisplay, PortalOutput, PortalSeat},
ptl_remote_desktop::{PortalSession, RemoteDesktopPhase},
ptr_gui::{
Align, Button, ButtonOwner, Flow, GuiElement, Label, Orientation, OverlayWindow,
OverlayWindowOwner,
},
},
theme::Color,
utils::{copyhashmap::CopyHashMap, hash_map_ext::HashMapExt},
},
std::rc::Rc,
};
const H_MARGIN: f32 = 30.0;
const V_MARGIN: f32 = 20.0;
pub struct SelectionGui {
remote_desktop_session: Rc<PortalSession>,
dpy: Rc<PortalDisplay>,
surfaces: CopyHashMap<GlobalName, 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_values() {
surface.overlay.data.kill(false);
}
if let RemoteDesktopPhase::Selecting(s) = self.remote_desktop_session.rd_phase.get() {
s.guis.remove(&self.dpy.id);
if upwards && s.guis.is_empty() {
self.remote_desktop_session.kill();
}
}
}
}
fn create_accept_gui(surface: &Rc<SelectionGuiSurface>) -> Rc<dyn GuiElement> {
let app = &surface.gui.remote_desktop_session.app;
let text = if app.is_empty() {
format!("An application wants to generate/monitor input")
} else {
format!("`{}` wants to generate/monitor input", app)
};
let label = Rc::new(Label::default());
*label.text.borrow_mut() = text;
let accept_button = static_button(surface, ButtonRole::Accept, "Allow");
let reject_button = static_button(surface, ButtonRole::Reject, "Reject");
for button in [&accept_button, &reject_button] {
button.border_color.set(Color::from_gray_srgb(100));
button.border.set(2.0);
button.padding.set(5.0);
}
accept_button.bg_color.set(Color::from_srgb(170, 200, 170));
accept_button
.bg_hover_color
.set(Color::from_srgb(170, 255, 170));
reject_button.bg_color.set(Color::from_srgb(200, 170, 170));
reject_button
.bg_hover_color
.set(Color::from_srgb(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<PortalSession>, dpy: &Rc<PortalDisplay>) -> Rc<Self> {
let gui = Rc::new(SelectionGui {
remote_desktop_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, _seat: &PortalSeat, 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.remote_desktop_session.rd_phase.get() {
RemoteDesktopPhase::Selecting(selecting) => selecting,
_ => return,
};
for gui in selecting.guis.lock().drain_values() {
gui.kill(false);
}
selecting.starting(&self.surface.output.dpy);
}
ButtonRole::Reject => {
log::info!("User has rejected the remote desktop request");
self.surface.gui.remote_desktop_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
}

View file

@ -0,0 +1,17 @@
use {
crate::gfx_api::{GfxContext, GfxFormat},
ahash::AHashMap,
std::rc::Rc,
uapi::c,
};
pub struct PortalRenderCtx {
pub _dev_id: c::dev_t,
pub ctx: Rc<dyn GfxContext>,
}
pub struct PortalServerRenderCtx {
pub ctx: Rc<PortalRenderCtx>,
pub usable_formats: Rc<AHashMap<u32, GfxFormat>>,
pub server_formats: Option<AHashMap<u32, GfxFormat>>,
}

View file

@ -0,0 +1,967 @@
mod screencast_gui;
use {
crate::{
allocator::{AllocatorError, BO_USE_RENDERING, BufferObject, BufferUsage},
dbus::{DbusObject, DictEntry, PendingReply, prelude::Variant},
format::{Format, XRGB8888, pipewire_format},
ifs::{jay_compositor::GET_TOPLEVEL_SINCE, jay_screencast::CLIENT_BUFFERS_SINCE},
pipewire::{
pw_con::PwCon,
pw_ifs::pw_client_node::{
PwClientNode, PwClientNodeBufferConfig, PwClientNodeOwner, PwClientNodePort,
PwClientNodePortSupportedFormat, PwClientNodePortSupportedFormats,
SUPPORTED_META_VIDEO_CROP,
},
pw_pod::{
PwPodRectangle, SPA_DATA_DmaBuf, SPA_MEDIA_SUBTYPE_raw, SPA_MEDIA_TYPE_video,
SPA_STATUS_HAVE_DATA, SPA_VIDEO_FORMAT_UNKNOWN, SpaChunkFlags, spa_point,
spa_rectangle, spa_region,
},
},
portal::{
PORTAL_SUCCESS, PortalState,
ptl_display::{PortalDisplay, PortalDisplayId, PortalOutput},
ptl_remote_desktop::RemoteDesktopPhase,
ptl_screencast::screencast_gui::SelectionGui,
ptl_session::{PortalSession, PortalSessionReply},
},
utils::{
clonecell::{CloneCell, UnsafeCellCloneSafe},
copyhashmap::CopyHashMap,
errorfmt::ErrorFmt,
opaque::Opaque,
},
video::{LINEAR_MODIFIER, Modifier, 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,
},
},
wl_usr::usr_ifs::{
usr_jay_screencast::{
UsrJayScreencast, UsrJayScreencastOwner, UsrJayScreencastServerConfig,
},
usr_jay_select_toplevel::UsrJaySelectToplevel,
usr_jay_select_workspace::UsrJaySelectWorkspace,
usr_jay_toplevel::UsrJayToplevel,
usr_jay_workspace::UsrJayWorkspace,
usr_linux_buffer_params::{UsrLinuxBufferParams, UsrLinuxBufferParamsOwner},
usr_wl_buffer::UsrWlBuffer,
},
},
serde::{Deserialize, Serialize},
std::{
borrow::Cow,
cell::{Cell, RefCell},
ops::Deref,
rc::Rc,
sync::atomic::Ordering::{Acquire, Relaxed, Release},
},
thiserror::Error,
};
#[derive(Clone)]
pub enum ScreencastPhase {
Init,
SourcesSelected(Rc<SourcesSelectedScreencast>),
Selecting(Rc<SelectingScreencast>),
SelectingWindow(Rc<SelectingWindowScreencast>),
SelectingWorkspace(Rc<SelectingWorkspaceScreencast>),
Starting(Rc<StartingScreencast>),
Started(Rc<StartedScreencast>),
Terminated,
}
unsafe impl UnsafeCellCloneSafe for ScreencastPhase {}
pub struct SourcesSelectedScreencast {
pub restore_data: Cell<Option<Result<RestoreData, RestoreError>>>,
}
#[derive(Clone)]
pub struct SelectingScreencastCore {
pub session: Rc<PortalSession>,
pub request_obj: Rc<DbusObject>,
}
pub struct SelectingScreencast {
pub core: SelectingScreencastCore,
pub guis: CopyHashMap<PortalDisplayId, Rc<SelectionGui>>,
pub restore_data: Cell<Option<RestoreData>>,
}
pub struct SelectingWindowScreencast {
pub core: SelectingScreencastCore,
pub dpy: Rc<PortalDisplay>,
pub selector: Rc<UsrJaySelectToplevel>,
pub restoring: bool,
}
pub struct SelectingWorkspaceScreencast {
pub core: SelectingScreencastCore,
pub dpy: Rc<PortalDisplay>,
pub selector: Rc<UsrJaySelectWorkspace>,
}
pub struct StartingScreencast {
pub session: Rc<PortalSession>,
pub _request_obj: Rc<DbusObject>,
pub node: Rc<PwClientNode>,
pub dpy: Rc<PortalDisplay>,
pub target: ScreencastTarget,
}
pub enum ScreencastTarget {
Output(Rc<PortalOutput>),
Workspace(Rc<PortalOutput>, Rc<UsrJayWorkspace>, bool),
Toplevel(Rc<UsrJayToplevel>),
}
pub struct StartedScreencast {
pub session: Rc<PortalSession>,
pub node: Rc<PwClientNode>,
pub port: Rc<PwClientNodePort>,
pub buffer_objects: RefCell<Vec<Rc<dyn BufferObject>>>,
pub buffers: RefCell<Vec<DmaBuf>>,
pub pending_buffers: RefCell<Vec<Rc<UsrLinuxBufferParams>>>,
pub buffers_valid: Cell<bool>,
pub dpy: Rc<PortalDisplay>,
pub jay_screencast: Rc<UsrJayScreencast>,
pub port_buffer_valid: Cell<bool>,
pub fixated: Cell<bool>,
pub format: Cell<&'static Format>,
pub modifier: Cell<Modifier>,
pub width: Cell<i32>,
pub height: Cell<i32>,
}
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 output = match &self.target {
ScreencastTarget::Output(o) => Some(o),
ScreencastTarget::Workspace(o, _, _) => Some(o),
ScreencastTarget::Toplevel(_) => None,
};
let mapping_id = output.and_then(|o| o.wl.name.borrow().clone());
self.session.send_start_reply(
Some(node_id),
create_restore_data(&self.dpy, &self.target),
mapping_id.as_deref(),
);
}
let mut supported_formats = PwClientNodePortSupportedFormats {
media_type: Some(SPA_MEDIA_TYPE_video),
media_sub_type: Some(SPA_MEDIA_SUBTYPE_raw),
video_size: None,
formats: vec![PwClientNodePortSupportedFormat {
format: XRGB8888,
modifiers: vec![LINEAR_MODIFIER],
}],
};
init_supported_formats(&mut supported_formats, &self.dpy);
let jsc_version = self.dpy.jc.version;
let num_buffers = (jsc_version >= CLIENT_BUFFERS_SINCE).then_some(3);
let port = self.node.create_port(true, supported_formats, num_buffers);
port.can_alloc_buffers.set(true);
port.supported_metas.set(SUPPORTED_META_VIDEO_CROP);
let jsc = self.dpy.jc.create_screencast();
match &self.target {
ScreencastTarget::Output(o) => {
jsc.set_output(&o.jay);
jsc.set_allow_all_workspaces(true);
}
ScreencastTarget::Workspace(o, ws, _) => {
jsc.set_output(&o.jay);
jsc.allow_workspace(ws);
}
ScreencastTarget::Toplevel(t) => jsc.set_toplevel(t),
}
jsc.set_use_linear_buffers(true);
jsc.configure();
match &self.target {
ScreencastTarget::Output(_) => {}
ScreencastTarget::Workspace(_, w, true) => {
self.dpy.con.remove_obj(&**w);
}
ScreencastTarget::Workspace(_, _, false) => {}
ScreencastTarget::Toplevel(t) => {
self.dpy.con.remove_obj(&**t);
}
}
let started = Rc::new(StartedScreencast {
session: self.session.clone(),
node: self.node.clone(),
port,
buffer_objects: Default::default(),
buffers: Default::default(),
pending_buffers: Default::default(),
buffers_valid: Cell::new(false),
dpy: self.dpy.clone(),
jay_screencast: jsc,
port_buffer_valid: Cell::new(false),
fixated: Cell::new(jsc_version < CLIENT_BUFFERS_SINCE),
format: Cell::new(XRGB8888),
modifier: Cell::new(LINEAR_MODIFIER),
width: Cell::new(1),
height: Cell::new(1),
});
self.session
.sc_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>) {
let format = &*port.negotiated_format.borrow();
if self.fixated.get() {
return;
}
let (Some(fmt), Some(modifiers)) = (format.format, &format.modifiers) else {
return;
};
let modifier;
let planes;
match self.allocate_buffer(fmt, modifiers) {
Ok(bo) => {
let dmabuf = bo.dmabuf();
modifier = dmabuf.modifier;
planes = dmabuf.planes.len();
}
Err(e) => {
log::error!("Could not allocate buffer: {}", ErrorFmt(e));
self.session.kill();
return;
}
};
log::debug!(
"Negotiated format {} with modifier 0x{modifier:08x} at size {}x{}",
fmt.name,
self.width.get(),
self.height.get(),
);
self.port.supported_formats.borrow_mut().formats = vec![PwClientNodePortSupportedFormat {
format: fmt,
modifiers: vec![modifier],
}];
self.port.buffer_config.borrow_mut().planes = Some(planes);
self.node.send_port_update(&self.port, true);
self.format.set(fmt);
self.modifier.set(modifier);
self.fixated.set(true);
}
fn use_buffers(self: Rc<Self>, port: &Rc<PwClientNodePort>) {
if self.jay_screencast.version < CLIENT_BUFFERS_SINCE {
self.node
.send_port_output_buffers(port, &self.buffers.borrow_mut());
self.buffers_valid.set(true);
return;
}
self.buffers_valid.set(false);
self.port_buffer_valid.set(false);
let Some(dmabuf) = self.dpy.dmabuf.get() else {
log::error!("Display does not support dmabuf");
self.session.kill();
return;
};
self.jay_screencast.clear_buffers();
self.jay_screencast.configure();
self.buffer_objects.borrow_mut().clear();
self.buffers.borrow_mut().clear();
for buffer in self.pending_buffers.borrow_mut().drain(..) {
self.dpy.con.remove_obj(&*buffer);
}
for _ in 0..self.port.buffers.borrow().len() {
let res = self.allocate_buffer(self.format.get(), &[self.modifier.get()]);
match res {
Ok(b) => {
let params = dmabuf.create_params();
params.create(&b.dmabuf());
params.owner.set(Some(self.clone()));
self.buffers.borrow_mut().push(b.dmabuf().clone());
self.buffer_objects.borrow_mut().push(b);
self.pending_buffers.borrow_mut().push(params);
}
Err(e) => {
log::error!("Could not allocate buffer: {}", ErrorFmt(e));
self.session.kill();
return;
}
}
}
self.node
.send_port_output_buffers(&self.port, &self.buffers.borrow());
}
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();
}
}
#[derive(Debug, Error)]
enum BufferAllocationError {
#[error("Display has no render context")]
NoRenderContext,
#[error(transparent)]
Allocator(#[from] AllocatorError),
}
impl StartedScreencast {
fn allocate_buffer(
&self,
format: &'static Format,
modifiers: &[Modifier],
) -> Result<Rc<dyn BufferObject>, BufferAllocationError> {
let Some(ctx) = self.dpy.render_ctx.get() else {
return Err(BufferAllocationError::NoRenderContext);
};
let mut usage = BO_USE_RENDERING;
if let Some(sf) = &ctx.server_formats
&& let Some(format) = sf.get(&format.drm)
{
let no_render_usage = modifiers.iter().all(|m| {
format
.write_modifiers
.get(m)
.map(|w| !w.needs_render_usage)
.unwrap_or(false)
});
if no_render_usage {
usage = BufferUsage::none();
}
}
let buffer = ctx.ctx.ctx.allocator().create_bo(
&self.dpy.state.dma_buf_ids,
self.width.get(),
self.height.get(),
format,
modifiers,
usage,
)?;
Ok(buffer)
}
}
impl SelectingScreencastCore {
pub fn starting(&self, dpy: &Rc<PortalDisplay>, target: ScreencastTarget) {
let Some(pw_con) = &self.session.pw_con else {
self.session.kill();
return;
};
let node = 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.session.clone(),
_request_obj: self.request_obj.clone(),
node,
dpy: dpy.clone(),
target,
});
self.session
.sc_phase
.set(ScreencastPhase::Starting(starting.clone()));
starting.node.owner.set(Some(starting.clone()));
dpy.sessions.set(
self.session.session_obj.path().to_owned(),
self.session.clone(),
);
}
}
impl PortalSession {
fn dbus_select_sources(
self: &Rc<Self>,
req: SelectSources,
reply: PendingReply<SelectSourcesReply<'static>>,
) {
match self.sc_phase.get() {
ScreencastPhase::Init => {}
_ => {
self.kill();
reply.err("Sources have already been selected");
return;
}
}
self.sc_phase.set(ScreencastPhase::SourcesSelected(Rc::new(
SourcesSelectedScreencast {
restore_data: Cell::new(get_restore_data(&req)),
},
)));
reply.ok(&SelectSourcesReply {
response: PORTAL_SUCCESS,
results: Default::default(),
});
}
fn dbus_start_screencast(
self: &Rc<Self>,
req: Start<'_>,
reply: PendingReply<StartReply<'static>>,
) {
let restore_data = match self.sc_phase.get() {
ScreencastPhase::SourcesSelected(s) => s.restore_data.take(),
_ => {
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) => Rc::new(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);
}
});
}
self.start_reply
.set(Some(PortalSessionReply::ScreenCast(reply)));
self.screencast_restore(&request_obj, restore_data, None);
}
fn start_interactive_selection(
self: &Rc<Self>,
request_obj: &Rc<DbusObject>,
restore_data: Option<RestoreData>,
) {
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, restore_data.is_some()));
}
}
if guis.is_empty() {
self.kill();
self.reply_err("There are no running displays");
return;
}
self.sc_phase
.set(ScreencastPhase::Selecting(Rc::new(SelectingScreencast {
core: SelectingScreencastCore {
session: self.clone(),
request_obj: request_obj.clone(),
},
guis,
restore_data: Cell::new(restore_data),
})));
}
pub fn screencast_restore(
self: &Rc<Self>,
request_obj: &Rc<DbusObject>,
restore_data: Option<Result<RestoreData, RestoreError>>,
display: Option<Rc<PortalDisplay>>,
) {
if let Some(rd) = restore_data {
if let Err(e) = self.try_restore(&request_obj, rd, display) {
log::error!("Could not restore session: {}", ErrorFmt(e));
} else {
return;
}
}
self.start_interactive_selection(&request_obj, None);
}
fn try_restore(
self: &Rc<Self>,
request_obj: &Rc<DbusObject>,
restore_data: Result<RestoreData, RestoreError>,
display: Option<Rc<PortalDisplay>>,
) -> Result<(), RestoreError> {
let rd = restore_data?;
let dpy = if let Some(dpy) = display {
dpy
} else {
let dpy = self
.state
.displays
.lock()
.values()
.find(|d| d.unique_id == rd.display)
.cloned();
match dpy {
Some(dpy) => dpy,
_ => {
if self.state.displays.len() == 0 {
return Err(RestoreError::UnknownDisplay);
} else if self.state.displays.len() == 1 {
self.state.displays.lock().values().next().unwrap().clone()
} else {
self.start_interactive_selection(&request_obj, Some(rd));
return Ok(());
}
}
}
};
let start = |target: ScreencastTarget| {
SelectingScreencastCore {
session: self.clone(),
request_obj: request_obj.clone(),
}
.starting(&dpy, target);
};
match &rd.ty {
RestoreDataType::Output(d) => {
let output = dpy
.outputs
.lock()
.values()
.find(|o| o.wl.name.borrow().as_ref() == Some(&d.name))
.cloned();
let Some(output) = output else {
return Err(RestoreError::UnknownOutput);
};
start(ScreencastTarget::Output(output));
}
RestoreDataType::Workspace(ws) => {
let ws = dpy
.workspaces
.lock()
.values()
.find(|w| w.name.borrow().as_ref() == Some(&ws.name))
.cloned();
let Some(ws) = ws else {
return Err(RestoreError::UnknownWorkspace);
};
let Some(output) = dpy.outputs.get(&ws.output.get()) else {
return Err(RestoreError::UnknownOutput);
};
start(ScreencastTarget::Workspace(output, ws, false));
}
RestoreDataType::Toplevel(d) => {
if dpy.jc.version < GET_TOPLEVEL_SINCE {
return Err(RestoreError::GetToplevel);
}
let selector = dpy.jc.get_toplevel(&d.id);
let selecting = Rc::new(SelectingWindowScreencast {
core: SelectingScreencastCore {
session: self.clone(),
request_obj: request_obj.clone(),
},
dpy: dpy.clone(),
selector: selector.clone(),
restoring: true,
});
selector.owner.set(Some(selecting.clone()));
self.sc_phase
.set(ScreencastPhase::SelectingWindow(selecting));
}
}
Ok(())
}
}
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() = 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![PwClientNodePortSupportedFormat {
format: buffer.format,
modifiers: vec![buffer.modifier],
}],
};
*self.port.buffer_config.borrow_mut() = PwClientNodeBufferConfig {
num_buffers: Some(buffers.len()),
planes: Some(buffer.planes.len()),
data_type: SPA_DATA_DmaBuf,
};
self.node.send_port_update(&self.port, true);
self.node.send_active(true);
self.fixated.set(true);
*self.buffers.borrow_mut() = buffers;
self.buffers_valid.set(false);
}
fn ready(&self, ev: &Ready) {
let idx = ev.idx as usize;
let buffers = &*self.buffers.borrow();
let pbuffers = self.port.buffers.borrow();
let buffer = &buffers[idx];
let discard_buffer = || {
self.jay_screencast.release_buffer(idx);
};
if !self.buffers_valid.get() {
return;
}
let Some(io) = self.port.io_buffers.get() else {
discard_buffer();
return;
};
let Some(pbuffer) = pbuffers.get(idx) else {
discard_buffer();
return;
};
let io = unsafe { io.read() };
if io.status.load(Acquire) == SPA_STATUS_HAVE_DATA.0 {
discard_buffer();
return;
}
for (chunk, plane) in pbuffer.chunks.iter().zip(buffer.planes.iter()) {
let chunk = unsafe { 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 {
unsafe { crop.write() }.region = spa_region {
position: spa_point { x: 0, y: 0 },
size: spa_rectangle {
width: buffer.width as _,
height: buffer.height as _,
},
};
}
let buffer_id = io.buffer_id.load(Relaxed) as usize;
if self.port_buffer_valid.get() {
if buffer_id != idx {
if buffer_id < buffers.len() {
self.jay_screencast.release_buffer(buffer_id);
}
}
}
io.buffer_id.store(ev.idx, Relaxed);
io.status.store(SPA_STATUS_HAVE_DATA.0, Release);
self.port_buffer_valid.set(true);
self.port.node.drive();
}
fn destroyed(&self) {
self.session.kill();
}
fn config(&self, config: UsrJayScreencastServerConfig) {
let mut changed = false;
let width = config.width.max(1);
let height = config.height.max(1);
changed |= self.width.replace(width) != width;
changed |= self.height.replace(height) != height;
self.port.supported_formats.borrow_mut().video_size = Some(PwPodRectangle {
width: self.width.get() as _,
height: self.height.get() as _,
});
if changed && self.dpy.jc.version >= CLIENT_BUFFERS_SINCE {
self.fixated.set(false);
init_supported_formats(&mut self.port.supported_formats.borrow_mut(), &self.dpy);
}
self.node.send_port_update(&self.port, self.fixated.get());
self.node.send_active(true);
}
}
fn init_supported_formats(
supported_formats: &mut PwClientNodePortSupportedFormats,
dpy: &PortalDisplay,
) {
let Some(ctx) = dpy.render_ctx.get() else {
return;
};
let Some(server_formats) = &ctx.server_formats else {
return;
};
supported_formats.formats.clear();
for format in server_formats.values() {
if format.write_modifiers.is_empty() {
continue;
}
if pipewire_format(format.format) == SPA_VIDEO_FORMAT_UNKNOWN {
continue;
}
let ptl_format = PwClientNodePortSupportedFormat {
format: format.format,
modifiers: format.write_modifiers.keys().copied().collect(),
};
supported_formats.formats.push(ptl_format);
}
}
impl UsrLinuxBufferParamsOwner for StartedScreencast {
fn created(&self, buffer: Rc<UsrWlBuffer>) {
self.buffers_valid.set(true);
self.jay_screencast.add_buffer(&buffer);
self.jay_screencast.configure();
self.dpy.con.remove_obj(&*buffer);
}
fn failed(&self) {
log::error!("Buffer import failed");
self.session.kill();
}
}
pub(super) fn add_screencast_dbus_members(
state_: &Rc<PortalState>,
pw_con: &Rc<PwCon>,
object: &DbusObject,
) {
use org::freedesktop::impl_::portal::screen_cast::*;
let state = state_.clone();
let pw_con = pw_con.clone();
object.add_method::<CreateSession, _>(move |req, pr| {
dbus_create_session(&state, &pw_con, 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(5));
}
fn dbus_create_session(
state: &Rc<PortalState>,
pw_con: &Rc<PwCon>,
req: CreateSession,
reply: PendingReply<CreateSessionReply<'static>>,
) {
log::info!("Create Session {:#?}", req);
if state.sessions.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(PortalSession {
_id: state.id(),
state: state.clone(),
pw_con: Some(pw_con.clone()),
app: req.app_id.to_string(),
session_obj: obj,
sc_phase: CloneCell::new(ScreencastPhase::Init),
rd_phase: CloneCell::new(RemoteDesktopPhase::Init),
start_reply: Default::default(),
});
{
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
.sessions
.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_screencast(req, reply);
}
}
fn get_session<T>(
state: &Rc<PortalState>,
reply: &PendingReply<T>,
handle: &str,
) -> Option<Rc<PortalSession>> {
let res = state.sessions.get(handle);
if res.is_none() {
let msg = format!("Screencast session `{}` does not exist", handle);
reply.err(&msg);
}
res
}
fn create_restore_data(dpy: &PortalDisplay, rd: &ScreencastTarget) -> Option<Variant<'static>> {
let rd = RestoreData {
display: dpy.unique_id,
ty: match rd {
ScreencastTarget::Output(o) => RestoreDataType::Output(RestoreDataOutput {
name: o.wl.name.borrow().clone()?,
}),
ScreencastTarget::Workspace(_, w, _) => {
RestoreDataType::Workspace(RestoreDataWorkspace {
name: w.name.borrow().clone()?,
})
}
ScreencastTarget::Toplevel(tl) => RestoreDataType::Toplevel(RestoreDataToplevel {
id: tl.toplevel_id.borrow().clone()?,
}),
},
};
Some(Variant::Struct(vec![
Variant::String("Jay".into()),
Variant::U32(1),
Variant::Variant(Box::new(Variant::String(
serde_json::to_string(&rd).unwrap().into(),
))),
]))
}
#[derive(Debug, Error)]
pub enum RestoreError {
#[error("DBus restore data is not a struct")]
NotAStruct,
#[error("DBus restore data is not a struct with 3 fields")]
NotLen3,
#[error("DBus restore data first field is not a string")]
FirstNotString,
#[error("DBus restore data second field is not a u32")]
SecondNotU32,
#[error("DBus restore data third field is not a variant")]
ThirdNotVariant,
#[error("DBus restore data third field is not a string")]
ThirdNotString,
#[error("DBus restore data is not for Jay")]
NotJay,
#[error("DBus restore data is not version 1")]
NotVersion1,
#[error("DBus restore data could not be deserialized")]
Parse(#[source] serde_json::Error),
#[error("The display no longer exists")]
UnknownDisplay,
#[error("The output no longer exists")]
UnknownOutput,
#[error("The workspace no longer exists")]
UnknownWorkspace,
#[error("The display does not support toplevel restoration")]
GetToplevel,
}
fn get_restore_data(req: &SelectSources) -> Option<Result<RestoreData, RestoreError>> {
let restore_data = req.options.iter().find(|n| n.key == "restore_data")?;
Some(get_restore_data_(restore_data))
}
fn get_restore_data_(
restore_data: &DictEntry<Cow<str>, Variant>,
) -> Result<RestoreData, RestoreError> {
let Variant::Struct(s) = &restore_data.value else {
return Err(RestoreError::NotAStruct);
};
if s.len() != 3 {
return Err(RestoreError::NotLen3);
}
let Variant::String(compositor) = &s[0] else {
return Err(RestoreError::FirstNotString);
};
let Variant::U32(version) = &s[1] else {
return Err(RestoreError::SecondNotU32);
};
let Variant::Variant(restore_data) = &s[2] else {
return Err(RestoreError::ThirdNotVariant);
};
let Variant::String(restore_data) = &**restore_data else {
return Err(RestoreError::ThirdNotString);
};
if compositor != "Jay" {
return Err(RestoreError::NotJay);
}
if *version != 1 {
return Err(RestoreError::NotVersion1);
}
serde_json::from_str(restore_data).map_err(RestoreError::Parse)
}
#[derive(Serialize, Deserialize)]
pub struct RestoreData {
display: Opaque,
ty: RestoreDataType,
}
#[derive(Serialize, Deserialize)]
enum RestoreDataType {
Output(RestoreDataOutput),
Workspace(RestoreDataWorkspace),
Toplevel(RestoreDataToplevel),
}
#[derive(Serialize, Deserialize)]
struct RestoreDataOutput {
name: String,
}
#[derive(Serialize, Deserialize)]
struct RestoreDataWorkspace {
name: String,
}
#[derive(Serialize, Deserialize)]
struct RestoreDataToplevel {
id: String,
}

View file

@ -0,0 +1,297 @@
use {
crate::{
globals::GlobalName,
ifs::wl_seat::{BTN_LEFT, wl_pointer::PRESSED},
portal::{
ptl_display::{PortalDisplay, PortalOutput, PortalSeat},
ptl_screencast::{
PortalSession, ScreencastPhase, ScreencastTarget, SelectingWindowScreencast,
SelectingWorkspaceScreencast,
},
ptr_gui::{
Align, Button, ButtonOwner, Flow, GuiElement, Label, Orientation, OverlayWindow,
OverlayWindowOwner,
},
},
theme::Color,
utils::{copyhashmap::CopyHashMap, hash_map_ext::HashMapExt},
wl_usr::usr_ifs::{
usr_jay_select_toplevel::UsrJaySelectToplevelOwner,
usr_jay_select_workspace::UsrJaySelectWorkspaceOwner, usr_jay_toplevel::UsrJayToplevel,
usr_jay_workspace::UsrJayWorkspace,
},
},
std::rc::Rc,
};
const H_MARGIN: f32 = 30.0;
const V_MARGIN: f32 = 20.0;
pub struct SelectionGui {
screencast_session: Rc<PortalSession>,
dpy: Rc<PortalDisplay>,
surfaces: CopyHashMap<GlobalName, 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 {
Restore,
Accept,
SelectWorkspace,
SelectWindow,
Reject,
}
impl SelectionGui {
pub fn kill(&self, upwards: bool) {
for surface in self.surfaces.lock().drain_values() {
surface.overlay.data.kill(false);
}
if let ScreencastPhase::Selecting(s) = self.screencast_session.sc_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>, for_restore: bool) -> 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 restore_button = static_button(surface, ButtonRole::Restore, "Restore Session");
let accept_button = static_button(surface, ButtonRole::Accept, "Share This Output");
let workspace_button = static_button(surface, ButtonRole::SelectWorkspace, "Share A Workspace");
let window_button = static_button(surface, ButtonRole::SelectWindow, "Share A Window");
let reject_button = static_button(surface, ButtonRole::Reject, "Reject");
for button in [
&restore_button,
&accept_button,
&workspace_button,
&window_button,
&reject_button,
] {
button.border_color.set(Color::from_gray_srgb(100));
button.border.set(2.0);
button.padding.set(5.0);
}
restore_button.bg_color.set(Color::from_srgb(170, 170, 200));
restore_button
.bg_hover_color
.set(Color::from_srgb(170, 170, 255));
for button in [&accept_button, &workspace_button, &window_button] {
button.bg_color.set(Color::from_srgb(170, 200, 170));
button.bg_hover_color.set(Color::from_srgb(170, 255, 170));
}
reject_button.bg_color.set(Color::from_srgb(200, 170, 170));
reject_button
.bg_hover_color
.set(Color::from_srgb(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);
let mut elements: Vec<Rc<dyn GuiElement>> = vec![label];
if for_restore {
elements.push(restore_button);
}
elements.push(accept_button);
if surface.gui.dpy.jc.caps.select_workspace.get() {
elements.push(workspace_button);
}
if surface.gui.dpy.jc.caps.window_capture.get() {
elements.push(window_button);
}
elements.push(reject_button);
*flow.elements.borrow_mut() = elements;
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<PortalSession>, dpy: &Rc<PortalDisplay>, for_restore: bool) -> 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, for_restore);
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, seat: &PortalSeat, button: u32, state: u32) {
if button != BTN_LEFT || state != PRESSED {
return;
}
match self.role {
ButtonRole::Restore
| ButtonRole::Accept
| ButtonRole::SelectWorkspace
| ButtonRole::SelectWindow => {
log::info!("User has accepted the request");
let selecting = match self.surface.gui.screencast_session.sc_phase.get() {
ScreencastPhase::Selecting(selecting) => selecting,
_ => return,
};
for gui in selecting.guis.lock().drain_values() {
gui.kill(false);
}
let dpy = &self.surface.output.dpy;
if self.role == ButtonRole::Restore {
selecting.core.session.screencast_restore(
&selecting.core.request_obj,
selecting.restore_data.take().map(Ok),
Some(self.surface.gui.dpy.clone()),
);
} else if self.role == ButtonRole::Accept {
selecting
.core
.starting(dpy, ScreencastTarget::Output(self.surface.output.clone()));
} else if self.role == ButtonRole::SelectWorkspace {
let selector = dpy.jc.select_workspace(&seat.wl);
let selecting = Rc::new(SelectingWorkspaceScreencast {
core: selecting.core.clone(),
dpy: dpy.clone(),
selector: selector.clone(),
});
selector.owner.set(Some(selecting.clone()));
self.surface
.gui
.screencast_session
.sc_phase
.set(ScreencastPhase::SelectingWorkspace(selecting));
} else {
let selector = dpy.jc.select_toplevel(&seat.wl);
let selecting = Rc::new(SelectingWindowScreencast {
core: selecting.core.clone(),
dpy: dpy.clone(),
selector: selector.clone(),
restoring: false,
});
selector.owner.set(Some(selecting.clone()));
self.surface
.gui
.screencast_session
.sc_phase
.set(ScreencastPhase::SelectingWindow(selecting));
}
}
ButtonRole::Reject => {
log::info!("User has rejected the screencast request");
self.surface.gui.screencast_session.kill();
}
}
}
}
impl UsrJaySelectToplevelOwner for SelectingWindowScreencast {
fn done(&self, tl: Option<Rc<UsrJayToplevel>>) {
let Some(tl) = tl else {
if self.restoring {
log::warn!("Could not restore session because toplevel no longer exists");
self.core
.session
.start_interactive_selection(&self.core.request_obj, None);
return;
}
log::info!("User has aborted the selection");
self.core.session.kill();
return;
};
match self.core.session.sc_phase.get() {
ScreencastPhase::SelectingWindow(s) => {
self.dpy.con.remove_obj(&*s.selector);
}
_ => {
self.dpy.con.remove_obj(&*tl);
return;
}
}
log::info!("User has selected a window");
self.core
.starting(&self.dpy, ScreencastTarget::Toplevel(tl));
}
}
impl UsrJaySelectWorkspaceOwner for SelectingWorkspaceScreencast {
fn done(&self, output: GlobalName, ws: Option<Rc<UsrJayWorkspace>>) {
let Some(ws) = ws else {
log::info!("User has aborted the selection");
self.core.session.kill();
return;
};
match self.core.session.sc_phase.get() {
ScreencastPhase::SelectingWorkspace(s) => {
self.dpy.con.remove_obj(&*s.selector);
}
_ => {
self.dpy.con.remove_obj(&*ws);
return;
}
}
log::info!("User has selected a workspace");
let output = match self.dpy.outputs.get(&output) {
Some(o) => o,
_ => {
log::warn!("Workspace does not belong to any known output");
self.dpy.con.remove_obj(&*ws);
self.core.session.kill();
return;
}
};
self.core
.starting(&self.dpy, ScreencastTarget::Workspace(output, ws, true));
}
}
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
}

169
src/portal/ptl_session.rs Normal file
View file

@ -0,0 +1,169 @@
use {
crate::{
dbus::{DbusObject, DictEntry, DynamicType, FALSE, PendingReply, prelude::Variant},
pipewire::pw_con::PwCon,
portal::{
PORTAL_SUCCESS, PortalState,
ptl_remote_desktop::{DeviceTypes, RemoteDesktopPhase},
ptl_screencast::{ScreencastPhase, ScreencastTarget},
},
utils::{clonecell::CloneCell, hash_map_ext::HashMapExt},
wire_dbus::org::freedesktop::impl_::portal::{
remote_desktop::StartReply as RdStartReply, screen_cast::StartReply as ScStartReply,
session::Closed,
},
},
std::{borrow::Cow, cell::Cell, ops::Deref, rc::Rc},
};
shared_ids!(SessionId);
pub struct PortalSession {
pub _id: SessionId,
pub state: Rc<PortalState>,
pub pw_con: Option<Rc<PwCon>>,
pub app: String,
pub session_obj: DbusObject,
pub sc_phase: CloneCell<ScreencastPhase>,
pub rd_phase: CloneCell<RemoteDesktopPhase>,
pub start_reply: Cell<Option<PortalSessionReply>>,
}
pub enum PortalSessionReply {
RemoteDesktop(PendingReply<RdStartReply<'static>>),
ScreenCast(PendingReply<ScStartReply<'static>>),
}
impl PortalSession {
pub(super) fn kill(&self) {
self.session_obj.emit_signal(&Closed);
self.state.sessions.remove(self.session_obj.path());
self.reply_err("Session has been terminated");
match self.rd_phase.set(RemoteDesktopPhase::Terminated) {
RemoteDesktopPhase::Init => {}
RemoteDesktopPhase::DevicesSelected => {}
RemoteDesktopPhase::Terminated => {}
RemoteDesktopPhase::Selecting(s) => {
for gui in s.guis.lock().drain_values() {
gui.kill(false);
}
}
RemoteDesktopPhase::Starting(s) => {
s.ei_session.con.remove_obj(s.ei_session.deref());
s.dpy.sessions.remove(self.session_obj.path());
}
RemoteDesktopPhase::Started(s) => {
s.ei_session.con.remove_obj(s.ei_session.deref());
s.dpy.sessions.remove(self.session_obj.path());
}
}
match self.sc_phase.set(ScreencastPhase::Terminated) {
ScreencastPhase::Init => {}
ScreencastPhase::SourcesSelected(_) => {}
ScreencastPhase::Terminated => {}
ScreencastPhase::Selecting(s) => {
for gui in s.guis.lock().drain_values() {
gui.kill(false);
}
}
ScreencastPhase::SelectingWindow(s) => {
s.dpy.con.remove_obj(&*s.selector);
}
ScreencastPhase::SelectingWorkspace(s) => {
s.dpy.con.remove_obj(&*s.selector);
}
ScreencastPhase::Starting(s) => {
s.node.con.destroy_obj(s.node.deref());
s.dpy.sessions.remove(self.session_obj.path());
match &s.target {
ScreencastTarget::Output(_) => {}
ScreencastTarget::Workspace(_, w, true) => {
s.dpy.con.remove_obj(&**w);
}
ScreencastTarget::Workspace(_, _, false) => {}
ScreencastTarget::Toplevel(t) => {
s.dpy.con.remove_obj(&**t);
}
}
}
ScreencastPhase::Started(s) => {
s.jay_screencast.con.remove_obj(s.jay_screencast.deref());
s.node.con.destroy_obj(s.node.deref());
s.dpy.sessions.remove(self.session_obj.path());
for buffer in s.pending_buffers.borrow_mut().drain(..) {
s.dpy.con.remove_obj(&*buffer);
}
}
}
}
pub(super) fn send_start_reply(
&self,
pw_node_id: Option<u32>,
restore_data: Option<Variant<'static>>,
mapping_id: Option<&str>,
) {
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 mut streams = vec![];
if let Some(node_id) = pw_node_id {
streams = vec![Variant::U32(node_id), Variant::Array(inner_type, vec![])];
}
let mut variants = vec![
DictEntry {
key: "devices".into(),
value: Variant::U32(DeviceTypes::all().0),
},
DictEntry {
key: "clipboard_enabled".into(),
value: Variant::Bool(FALSE),
},
DictEntry {
key: "streams".into(),
value: Variant::Array(kt, streams),
},
];
if let Some(rd) = restore_data {
variants.push(DictEntry {
key: "restore_data".into(),
value: rd,
});
}
if let Some(mapping_id) = mapping_id {
variants.push(DictEntry {
key: "mapping_id".into(),
value: Variant::String(mapping_id.into()),
});
}
if let Some(reply) = self.start_reply.take() {
match reply {
PortalSessionReply::RemoteDesktop(reply) => {
reply.ok(&RdStartReply {
response: PORTAL_SUCCESS,
results: Cow::Borrowed(&variants),
});
}
PortalSessionReply::ScreenCast(reply) => {
reply.ok(&ScStartReply {
response: PORTAL_SUCCESS,
results: Cow::Borrowed(&variants),
});
}
}
}
}
pub(super) fn reply_err(&self, err: &str) {
if let Some(reply) = self.start_reply.take() {
match reply {
PortalSessionReply::RemoteDesktop(r) => r.err(err),
PortalSessionReply::ScreenCast(r) => r.err(err),
}
}
}
}

102
src/portal/ptl_text.rs Normal file
View file

@ -0,0 +1,102 @@
use {
crate::{
cmm::cmm_eotf::Eotf,
format::ARGB8888,
gfx_api::{GfxContext, GfxTexture},
pango::{
CairoContext, CairoImageSurface, PangoCairoContext, PangoFontDescription, PangoLayout,
consts::{CAIRO_FORMAT_ARGB32, CAIRO_OPERATOR_SOURCE},
},
rect::Rect,
theme::Color,
},
std::{ops::Neg, rc::Rc, sync::Arc},
};
struct Data {
image: Rc<CairoImageSurface>,
cctx: Rc<CairoContext>,
_pctx: Rc<PangoCairoContext>,
_fd: PangoFontDescription,
layout: PangoLayout,
}
fn create_data(font: &str, width: i32, height: i32, scale: Option<f64>) -> Option<Data> {
let image = CairoImageSurface::new_image_surface(CAIRO_FORMAT_ARGB32, width, height).ok()?;
let cctx = image.create_context().ok()?;
let pctx = cctx.create_pango_context().ok()?;
let mut fd = PangoFontDescription::from_string(font);
if let Some(scale) = scale {
fd.set_size((fd.size() as f64 * scale).round() as _);
}
let layout = pctx.create_layout().ok()?;
layout.set_font_description(&fd);
Some(Data {
image,
cctx,
_pctx: pctx,
_fd: fd,
layout,
})
}
fn measure(font: &str, text: &str, scale: Option<f64>, full: bool) -> Option<TextMeasurement> {
let data = create_data(font, 1, 1, scale)?;
data.layout.set_text(text);
let mut res = TextMeasurement::default();
res.ink_rect = data.layout.inc_pixel_rect();
if full {
res.logical_rect = data.layout.logical_pixel_rect();
res.baseline = data.layout.pixel_baseline();
}
Some(res)
}
#[derive(Debug, Copy, Clone, Default)]
pub struct TextMeasurement {
pub ink_rect: Rect,
pub logical_rect: Rect,
pub baseline: i32,
}
pub fn render(
ctx: &Rc<dyn GfxContext>,
height: Option<i32>,
font: &Arc<String>,
text: &str,
color: Color,
scale: Option<f64>,
include_measurements: bool,
) -> Option<(Rc<dyn GfxTexture>, TextMeasurement)> {
let measurement = measure(font, text, scale, include_measurements)?;
let y = match height {
Some(_) => None,
_ => Some(measurement.ink_rect.y1().neg()),
};
let x = measurement.ink_rect.x1().neg();
let width = measurement.ink_rect.width();
let height = height.unwrap_or(measurement.ink_rect.height());
let data = create_data(font, width, height, scale)?;
data.layout.set_text(text);
let font_height = data.layout.pixel_size().1;
let [r, g, b, a] = color.to_array(Eotf::Gamma22);
data.cctx.set_operator(CAIRO_OPERATOR_SOURCE);
data.cctx.set_source_rgba(r as _, g as _, b as _, a as _);
let y = y.unwrap_or((height - font_height) / 2);
data.cctx.move_to(x as f64, y as f64);
data.layout.show_layout();
data.image.flush();
let bytes = data.image.data().ok()?;
ctx.clone()
.shmem_texture(
None,
bytes,
ARGB8888,
width,
height,
data.image.stride(),
None,
)
.ok()
.map(|t| (t as _, measurement))
}

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

@ -0,0 +1,947 @@
use {
crate::{
allocator::{BO_USE_RENDERING, BufferObject, BufferUsage},
async_engine::{Phase, SpawnedFuture},
cmm::{cmm_manager::ColorManager, cmm_render_intent::RenderIntent},
cursor::KnownCursor,
fixed::Fixed,
format::ARGB8888,
gfx_api::{
AcquireSync, AlphaMode, GfxContext, GfxFramebuffer, GfxTexture, ReleaseSync,
needs_render_usage,
},
globals::GlobalName,
ifs::zwlr_layer_shell_v1::OVERLAY,
portal::{
ptl_display::{PortalDisplay, PortalOutput, PortalSeat},
ptl_text::{self, TextMeasurement},
},
renderer::renderer_base::RendererBase,
scale::Scale,
theme::Color,
utils::{
asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap,
errorfmt::ErrorFmt, hash_map_ext::HashMapExt, rc_eq::rc_eq,
},
wire::{
ZwpLinuxBufferParamsV1Id, wp_fractional_scale_v1::PreferredScale,
zwlr_layer_surface_v1::Configure,
},
wl_usr::usr_ifs::{
usr_linux_buffer_params::{UsrLinuxBufferParams, UsrLinuxBufferParamsOwner},
usr_wl_buffer::{UsrWlBuffer, UsrWlBufferOwner},
usr_wl_callback::UsrWlCallbackOwner,
usr_wl_surface::UsrWlSurface,
usr_wlr_layer_surface::{UsrWlrLayerSurface, UsrWlrLayerSurfaceOwner},
usr_wp_fractional_scale::{UsrWpFractionalScale, UsrWpFractionalScaleOwner},
usr_wp_viewport::UsrWpViewport,
},
},
ahash::AHashSet,
std::{
cell::{Cell, RefCell},
ops::Deref,
rc::Rc,
sync::Arc,
},
};
#[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<dyn GfxContext>,
scale: f32,
max_width: f32,
max_height: f32,
) -> (f32, f32);
fn render_at(&self, color_manager: &ColorManager, 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<GlobalName>>,
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: Arc<String>,
pub tex: CloneCell<Option<Rc<dyn GfxTexture>>>,
pub owner: CloneCell<Option<Rc<dyn ButtonOwner>>>,
}
pub trait ButtonOwner {
fn button(&self, seat: &PortalSeat, 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_srgb(0)),
bg_color: Cell::new(Color::from_gray_srgb(255)),
bg_hover_color: Cell::new(Color::from_gray_srgb(255)),
text: Default::default(),
font: Arc::new(DEFAULT_FONT.to_string()),
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<dyn GfxContext>,
scale: f32,
_max_width: f32,
_max_height: f32,
) -> (f32, f32) {
let text = self.text.borrow_mut();
let tex = ptl_text::render(
ctx,
None,
&self.font,
&text,
Color::from_gray_srgb(0),
Some(scale as _),
true,
);
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, color_manager: &ColorManager, r: &mut RendererBase, x1: f32, y1: f32) {
let srgb_srgb = color_manager.srgb_gamma22();
let srgb = &srgb_srgb.linear;
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(),
srgb,
RenderIntent::Perceptual,
);
}
{
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, srgb, RenderIntent::Perceptual);
}
if let Some(tex) = self.tex.get() {
let (tx, ty) = r.scale_point_f(x1 + self.tex_off_x.get(), y1 + self.tex_off_y.get());
r.render_texture(
&tex,
None,
tx.round() as _,
ty.round() as _,
None,
None,
r.scale(),
None,
None,
AcquireSync::None,
ReleaseSync::None,
false,
srgb_srgb,
RenderIntent::Perceptual,
AlphaMode::PremultipliedElectrical,
);
}
}
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(seat, button, state);
}
}
}
const DEFAULT_FONT: &str = "sans-serif 16";
pub struct Label {
pub data: GuiElementData,
pub font: Arc<String>,
pub text: RefCell<String>,
pub tex: CloneCell<Option<Rc<dyn GfxTexture>>>,
}
impl Default for Label {
fn default() -> Self {
Self {
data: Default::default(),
font: Arc::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<dyn GfxContext>,
scale: f32,
_max_width: f32,
_max_height: f32,
) -> (f32, f32) {
let text = self.text.borrow_mut();
let tex = ptl_text::render(
ctx,
None,
&self.font,
&text,
Color::from_gray_srgb(255),
Some(scale as _),
false,
);
let (tex, width, height) = match tex {
Some((t, _)) => {
let (width, height) = t.size();
(Some(t.clone()), width, height)
}
_ => (None, 0, 0),
};
self.tex.set(tex);
(width as f32 / scale, height as f32 / scale)
}
fn render_at(&self, color_manager: &ColorManager, 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,
None,
tx.round() as _,
ty.round() as _,
None,
None,
r.scale(),
None,
None,
AcquireSync::None,
ReleaseSync::None,
false,
color_manager.srgb_gamma22(),
RenderIntent::Perceptual,
AlphaMode::PremultipliedElectrical,
);
}
}
fn child_at(&self, _x: f32, _y: f32) -> Option<Rc<dyn GuiElement>> {
None
}
}
#[derive(Copy, Clone, Eq, PartialEq, Default)]
pub enum Align {
#[expect(dead_code)]
Left,
#[default]
Center,
#[expect(dead_code)]
Right,
}
#[derive(Copy, Clone, Eq, PartialEq, Default)]
pub enum Orientation {
#[default]
Horizontal,
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<dyn GfxContext>,
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, color_manager: &ColorManager, r: &mut RendererBase, x: f32, y: f32) {
for element in self.elements.borrow_mut().deref() {
element.render_at(
color_manager,
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<Scale>,
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<GlobalName, 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(Scale::from_int(1)),
fractional_scale,
seats: Default::default(),
});
data.render_task.set(Some(dpy.state.eng.spawn2(
"render",
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.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;
};
let res = buf.fb.render_custom(
AcquireSync::Implicit,
ReleaseSync::Implicit,
self.dpy.state.color_manager.srgb_gamma22(),
self.scale.get(),
Some(&Color::from_gray_srgb(0)),
&self.dpy.state.color_manager.srgb_gamma22().linear,
None,
self.dpy.state.color_manager.srgb_linear(),
&mut |r| {
if let Some(content) = self.content.get() {
content.render_at(&self.dpy.state.color_manager, r, 0.0, 0.0)
}
},
);
if let Err(e) = res {
log::error!("Could not render frame: {}", ErrorFmt(e));
return;
}
self.frame_missed.set(false);
self.surface.frame().owner.set(Some(self.clone()));
self.have_frame.set(false);
buf.free.set(false);
self.surface.attach(&buf.wl);
self.surface.damage();
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_values() {
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_values() {
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;
let formats = &ctx.usable_formats;
let format = match formats.get(&ARGB8888.drm) {
None => {
log::error!("Render context does not support ARGB8888 format");
return;
}
Some(f) => f,
};
if format.write_modifiers.is_empty() {
log::error!("Render context cannot render to ARGB8888 format");
return;
}
let modifiers: Vec<_> = format.write_modifiers.keys().copied().collect();
let mut usage = BO_USE_RENDERING;
if !needs_render_usage(format.write_modifiers.values()) {
usage = BufferUsage::none();
}
for _ in 0..NUM_BUFFERS {
let bo = match ctx.ctx.ctx.allocator().create_bo(
&self.dpy.state.dma_buf_ids,
width,
height,
ARGB8888,
&modifiers,
usage,
) {
Ok(b) => b,
Err(e) => {
log::error!("Could not allocate dmabuf: {}", ErrorFmt(e));
return;
}
};
let img = match ctx.ctx.ctx.clone().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 {
bo: Cell::new(Some(bo)),
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<dyn GfxFramebuffer>,
pub _bo: Option<Rc<dyn BufferObject>>,
pub free: Cell<bool>,
pub _size: (i32, i32),
}
struct GuiBufferPending {
pub bo: Cell<Option<Rc<dyn BufferObject>>>,
pub window: Rc<WindowData>,
pub fb: Rc<dyn GfxFramebuffer>,
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);
let scale = Scale::from_wl(ev.scale);
layout |= self.scale.replace(scale) != scale;
if layout {
self.layout();
self.allocate_buffers();
}
}
}
impl UsrWlCallbackOwner for WindowData {
fn done(self: Rc<Self>) {
self.have_frame.set(true);
if self.frame_missed.get() {
self.schedule_render();
}
}
}
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(),
_bo: self.bo.take(),
free: Cell::new(true),
_size: self.size,
});
buf.wl.owner.set(Some(buf.clone()));
self.window.bufs.borrow_mut().push(buf);
self.params.con.remove_obj(self.params.deref());
self.window.pending_bufs.remove(&self.params.id);
if self.window.frame_missed.get() {
self.window.schedule_render();
}
}
fn failed(&self) {
self.window.kill(true);
}
}

View file

@ -27,6 +27,10 @@ pub struct RendererBase<'a> {
}
impl RendererBase<'_> {
pub fn scale(&self) -> Scale {
self.scale
}
pub fn scale_point(&self, mut x: i32, mut y: i32) -> (i32, i32) {
if self.scaled {
[x, y] = self.scale.pixel_size([x, y]);
@ -34,6 +38,14 @@ impl RendererBase<'_> {
(x, y)
}
pub fn scale_point_f(&self, mut x: f32, mut y: f32) -> (f32, f32) {
if self.scaled {
x = (x as f64 * self.scalef) as _;
y = (y as f64 * self.scalef) as _;
}
(x, y)
}
pub fn scale_rect(&self, mut rect: Rect) -> Rect {
if self.scaled {
let [x1, y1, x2, y2] =
@ -44,6 +56,17 @@ impl RendererBase<'_> {
rect
}
pub fn scale_rect_f(&self, mut rect: (f32, f32, f32, f32)) -> (f32, f32, f32, f32) {
if self.scaled {
let x1 = (rect.0 as f64 * self.scalef).round() as _;
let y1 = (rect.1 as f64 * self.scalef).round() as _;
let x2 = (rect.2 as f64 * self.scalef).round() as _;
let y2 = (rect.3 as f64 * self.scalef).round() as _;
rect = (x1, y1, x2, y2);
}
rect
}
pub fn fill_scaled_boxes(
&mut self,
boxes: &[Rect],
@ -115,6 +138,49 @@ impl RendererBase<'_> {
}
}
pub fn fill_boxes_f(
&mut self,
boxes: &[(f32, f32, f32, f32)],
color: &Color,
cd: &Rc<LinearColorDescription>,
render_intent: RenderIntent,
) {
self.fill_boxes2_f(boxes, color, cd, render_intent, 0.0, 0.0);
}
pub fn fill_boxes2_f(
&mut self,
boxes: &[(f32, f32, f32, f32)],
color: &Color,
cd: &Rc<LinearColorDescription>,
render_intent: RenderIntent,
dx: f32,
dy: f32,
) {
if boxes.is_empty() || *color == Color::TRANSPARENT {
return;
}
let (dx, dy) = self.scale_point_f(dx, dy);
for bx in boxes {
let (x1, y1, x2, y2) = self.scale_rect_f(*bx);
self.ops.push(GfxApiOpt::FillRect(FillRect {
rect: FramebufferRect::new(
x1 + dx,
y1 + dy,
x2 + dx,
y2 + dy,
self.transform,
self.fb_width,
self.fb_height,
),
color: *color,
alpha: None,
render_intent,
cd: cd.clone(),
}));
}
}
pub fn render_texture(
&mut self,
texture: &Rc<dyn GfxTexture>,

View file

@ -1,8 +1,8 @@
mod animations;
mod connectors;
mod idle;
mod rendering;
mod render_context;
mod rendering;
mod settings;
mod tree_ops;
mod xwayland;
@ -48,29 +48,25 @@ use {
globals::{Globals, GlobalsError, RemovableWaylandGlobal, WaylandGlobal},
icons::Icons,
ifs::{
data_transfer::{DataOfferIds, DataSourceIds, data_control::DataControlDeviceIds},
ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1,
ext_session_lock_v1::ExtSessionLockV1,
head_management::{
HeadNames, jay_head_manager_session_v1::{HeadManagerEvent, JayHeadManagerSessionV1},
},
data_transfer::{
DataOfferIds, DataSourceIds, data_control::DataControlDeviceIds,
HeadNames,
jay_head_manager_session_v1::{HeadManagerEvent, JayHeadManagerSessionV1},
},
jay_render_ctx::JayRenderCtx,
jay_screencast::JayScreencast,
jay_seat_events::JaySeatEvents,
jay_workspace_watcher::JayWorkspaceWatcher,
wl_buffer::WlBuffer,
wl_output::{OutputGlobalOpt, OutputId, PersistentOutputState},
wl_seat::{
PhysicalKeyboardId, PhysicalKeyboardIds, PositionHintRequest, SeatIds,
WlSeatGlobal,
PhysicalKeyboardId, PhysicalKeyboardIds, PositionHintRequest, SeatIds, WlSeatGlobal,
},
wl_surface::{
NoneSurfaceExt,
tray::TrayItemIds,
wl_subsurface::SubsurfaceIds,
xdg_surface::XdgSurfaceConfigureEvent,
zwp_idle_inhibitor_v1::IdleInhibitorIds,
NoneSurfaceExt, tray::TrayItemIds, wl_subsurface::SubsurfaceIds,
xdg_surface::XdgSurfaceConfigureEvent, zwp_idle_inhibitor_v1::IdleInhibitorIds,
zwp_input_popup_surface_v2::ZwpInputPopupSurfaceV2,
},
wlr_output_manager::WlrOutputManagerState,
@ -115,9 +111,8 @@ use {
virtual_output::VirtualOutputs,
wheel::Wheel,
wire::{
ExtForeignToplevelListV1Id, JayHeadManagerSessionV1Id,
JayRenderCtxId, JaySeatEventsId, JayWorkspaceWatcherId, ZwlrForeignToplevelManagerV1Id,
ZwpLinuxDmabufFeedbackV1Id,
ExtForeignToplevelListV1Id, JayHeadManagerSessionV1Id, JayRenderCtxId, JaySeatEventsId,
JayWorkspaceWatcherId, ZwlrForeignToplevelManagerV1Id, ZwpLinuxDmabufFeedbackV1Id,
},
},
ahash::AHashMap,
@ -172,6 +167,8 @@ pub struct State {
pub pending_output_render_data: AsyncQueue<Rc<OutputNode>>,
pub pending_float_layout: AsyncQueue<Rc<FloatNode>>,
pub pending_input_popup_positioning: AsyncQueue<Rc<ZwpInputPopupSurfaceV2>>,
pub pending_toplevel_screencasts: AsyncQueue<Rc<JayScreencast>>,
pub pending_screencast_reallocs_or_reconfigures: AsyncQueue<Rc<JayScreencast>>,
pub pending_placeholder_render_textures: AsyncQueue<Rc<PlaceholderNode>>,
pub pending_container_tab_render_textures: AsyncQueue<Rc<ContainerNode>>,
pub dbus: Dbus,
@ -689,5 +686,4 @@ impl State {
};
ctx.supports_color_management()
}
}

View file

@ -218,6 +218,7 @@ impl ConnectorHandler {
lock_surface: Default::default(),
hardware_cursor: Default::default(),
jay_outputs: Default::default(),
screencasts: Default::default(),
update_render_data_scheduled: Cell::new(false),
hardware_cursor_needs_render: Cell::new(false),
screencopies: Default::default(),
@ -340,6 +341,10 @@ impl ConnectorHandler {
for jo in on.jay_outputs.lock().drain_values() {
jo.send_destroyed();
}
let screencasts: Vec<_> = on.screencasts.lock().values().cloned().collect();
for sc in screencasts {
sc.do_destroy();
}
for sc in on.screencopies.lock().drain_values() {
sc.send_failed();
}

View file

@ -24,6 +24,7 @@ use {
ifs::{
ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1,
jay_output::JayOutput,
jay_screencast::JayScreencast,
wl_output::{BlendSpace, WlOutputGlobal},
wl_seat::{
BTN_LEFT, NodeSeatState, SeatId, WlSeatGlobal,
@ -72,7 +73,7 @@ use {
scroller::Scroller,
},
wire::{
ExtImageCopyCaptureSessionV1Id, JayOutputId, ZwlrScreencopyFrameV1Id,
ExtImageCopyCaptureSessionV1Id, JayOutputId, JayScreencastId, ZwlrScreencopyFrameV1Id,
},
},
numeric_sort::cmp,
@ -115,6 +116,7 @@ pub struct OutputNode {
pub hardware_cursor: CloneCell<Option<Rc<dyn HardwareCursor>>>,
pub hardware_cursor_needs_render: Cell<bool>,
pub update_render_data_scheduled: Cell<bool>,
pub screencasts: CopyHashMap<(ClientId, JayScreencastId), Rc<JayScreencast>>,
pub screencopies: CopyHashMap<(ClientId, ZwlrScreencopyFrameV1Id), Rc<ZwlrScreencopyFrameV1>>,
pub title_visible: Cell<bool>,
pub schedule: Rc<OutputSchedule>,
@ -285,6 +287,7 @@ impl OutputNode {
}
self.lock_surface.take();
self.jay_outputs.clear();
self.screencasts.clear();
self.screencopies.clear();
self.ext_copy_sessions.clear();
self.ext_workspace_groups.clear();

View file

@ -3,7 +3,7 @@ use {
crate::{
cmm::cmm_description::ColorDescription,
gfx_api::{AcquireSync, BufferResv, GfxTexture, ReleaseSync},
ifs::wl_buffer::WlBufferStorage,
ifs::{jay_screencast::JayScreencast, wl_buffer::WlBufferStorage},
utils::{errorfmt::ErrorFmt, hash_map_ext::HashMapExt},
},
std::{ops::Deref, rc::Rc},
@ -16,6 +16,20 @@ impl OutputNode {
}
}
pub fn screencast_changed(&self) {
self.captures_changed();
}
pub fn add_screencast(&self, sc: &Rc<JayScreencast>) {
self.screencasts.set((sc.client.id, sc.id), sc.clone());
self.captures_changed();
}
pub fn remove_screencast(&self, sc: &JayScreencast) {
self.screencasts.remove(&(sc.client.id, sc.id));
self.captures_changed();
}
pub fn perform_screencopies(
&self,
tex: &Rc<dyn GfxTexture>,
@ -44,6 +58,20 @@ impl OutputNode {
y_off,
size,
);
for sc in self.screencasts.lock().values() {
sc.copy_texture(
self,
tex,
cd,
resv,
acquire_sync,
release_sync,
render_hardware_cursor,
x_off,
y_off,
size,
);
}
for sc in self.ext_copy_sessions.lock().values() {
sc.copy_texture(
self,

View file

@ -20,6 +20,7 @@ use {
ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1,
ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1,
jay_screencast::JayScreencast,
jay_toplevel::JayToplevel,
wl_seat::{NodeSeatState, SeatId, collect_kb_foci, collect_kb_foci2},
wl_surface::{
@ -43,8 +44,8 @@ use {
rc_eq::rc_eq, threshold_counter::ThresholdCounter,
},
wire::{
ExtForeignToplevelHandleV1Id, ExtImageCopyCaptureSessionV1Id, JayToplevelId,
XxForeignToplevelGeometryTrackerV1Id, ZwlrForeignToplevelHandleV1Id,
ExtForeignToplevelHandleV1Id, ExtImageCopyCaptureSessionV1Id, JayScreencastId,
JayToplevelId, XxForeignToplevelGeometryTrackerV1Id, ZwlrForeignToplevelHandleV1Id,
},
},
jay_config::{window, window::WindowType},
@ -173,6 +174,9 @@ impl<T: ToplevelNodeBase> ToplevelNode for T {
fn tl_workspace_output_changed(&self, prev: &Rc<OutputNode>, new: &Rc<OutputNode>) {
let data = self.tl_data();
for sc in data.jay_screencasts.lock().values() {
sc.update_latch_listener();
}
for sc in data.ext_copy_sessions.lock().values() {
sc.update_latch_listener();
}
@ -240,6 +244,9 @@ impl<T: ToplevelNodeBase> ToplevelNode for T {
data.spawn_in_pending.set(false);
}
if prev.size() != rect.size() {
for sc in data.jay_screencasts.lock().values() {
sc.schedule_realloc_or_reconfigure();
}
for sc in data.ext_copy_sessions.lock().values() {
sc.buffer_size_changed();
}
@ -536,6 +543,7 @@ pub struct ToplevelData {
CopyHashMap<(ClientId, ZwlrForeignToplevelHandleV1Id), Rc<ZwlrForeignToplevelHandleV1>>,
pub render_highlight: NumCell<u32>,
pub jay_toplevels: CopyHashMap<(ClientId, JayToplevelId), Rc<JayToplevel>>,
pub jay_screencasts: CopyHashMap<(ClientId, JayScreencastId), Rc<JayScreencast>>,
pub ext_copy_sessions:
CopyHashMap<(ClientId, ExtImageCopyCaptureSessionV1Id), Rc<ExtImageCopyCaptureSessionV1>>,
pub slf: Weak<dyn ToplevelNode>,
@ -595,6 +603,7 @@ impl ToplevelData {
manager_handles: Default::default(),
render_highlight: Default::default(),
jay_toplevels: Default::default(),
jay_screencasts: Default::default(),
ext_copy_sessions: Default::default(),
slf: slf.clone(),
destroyed: Default::default(),
@ -671,6 +680,9 @@ impl ToplevelData {
for jay_tl in self.jay_toplevels.lock().drain_values() {
jay_tl.destroy();
}
for screencast in self.jay_screencasts.lock().drain_values() {
screencast.do_destroy();
}
for capture in self.ext_copy_sessions.lock().drain_values() {
capture.stop();
}
@ -991,6 +1003,9 @@ impl ToplevelData {
self.property_changed(TL_CHANGED_VISIBLE);
}
self.seat_state.set_visible(node, visible);
for sc in self.jay_screencasts.lock().values() {
sc.update_latch_listener();
}
for sc in self.ext_copy_sessions.lock().values() {
sc.update_latch_listener();
}

View file

@ -119,6 +119,12 @@ impl WorkspaceNode {
if !self.may_capture.get() {
break 'update;
}
for sc in output.screencasts.lock().values() {
if sc.shows_ws(self) {
has_capture = true;
break 'update;
}
}
if output.screencopies.is_not_empty() {
has_capture = true;
}

370
src/wl_usr.rs Normal file
View file

@ -0,0 +1,370 @@
pub mod usr_ifs;
pub mod usr_object;
use {
crate::{
async_engine::{AsyncEngine, SpawnedFuture},
client::{EventFormatter, MIN_SERVER_ID, RequestParser},
io_uring::{IoUring, IoUringError},
object::{Interface, ObjectId, Version, WL_DISPLAY_ID},
utils::{
asyncevent::AsyncEvent,
bitfield::Bitfield,
buffd::{
BufFdError, BufFdOut, MsgFormatter, MsgParser, MsgParserError, OutBuffer,
OutBufferSwapchain, WlBufFdIn, WlMessage,
},
clonecell::CloneCell,
copyhashmap::CopyHashMap,
errorfmt::ErrorFmt,
hash_map_ext::HashMapExt,
oserror::{OsError, OsErrorExt2},
},
video::dmabuf::DmaBufIds,
wheel::Wheel,
wire::wl_display,
wl_usr::{
usr_ifs::{
usr_wl_callback::UsrWlCallback, usr_wl_display::UsrWlDisplay,
usr_wl_registry::UsrWlRegistry,
},
usr_object::UsrObject,
},
},
std::{
cell::{Cell, RefCell},
collections::VecDeque,
error::Error,
mem,
rc::Rc,
},
thiserror::Error,
uapi::{OwnedFd, c},
};
#[derive(Debug, Error)]
pub enum UsrConError {
#[error("Could not create a socket")]
CreateSocket(#[source] OsError),
#[error("The socket path is too long")]
SocketPathTooLong,
#[error("Could not connect to the compositor")]
Connect(#[source] IoUringError),
#[error(transparent)]
BufFdError(#[from] BufFdError),
#[error("Could not read from the compositor")]
Read(#[source] BufFdError),
#[error("Could not write to the compositor")]
Write(#[source] BufFdError),
#[error("Server sent an event for object {0} that does not exist")]
MissingObject(ObjectId),
#[error("Could not process a `{}#{}.{}` event", .interface.name(), .id, .method)]
MethodError {
interface: Interface,
id: ObjectId,
method: &'static str,
#[source]
error: Box<dyn Error + 'static>,
},
#[error("Client tried to invoke a non-existent method")]
InvalidMethod,
}
pub struct UsrCon {
pub ring: Rc<IoUring>,
pub _wheel: Rc<Wheel>,
pub eng: Rc<AsyncEngine>,
pub server_id: u32,
obj_ids: RefCell<Bitfield>,
objects: CopyHashMap<ObjectId, Option<Rc<dyn UsrObject>>>,
swapchain: Rc<RefCell<OutBufferSwapchain>>,
flush_request: AsyncEvent,
incoming: Cell<Option<SpawnedFuture<()>>>,
outgoing: Cell<Option<SpawnedFuture<()>>>,
pub owner: CloneCell<Option<Rc<dyn UsrConOwner>>>,
dead: Cell<bool>,
dma_buf_ids: Rc<DmaBufIds>,
}
pub trait UsrConOwner {
fn killed(&self);
}
impl UsrCon {
pub async fn new(
ring: &Rc<IoUring>,
wheel: &Rc<Wheel>,
eng: &Rc<AsyncEngine>,
dma_buf_ids: &Rc<DmaBufIds>,
path: &str,
server_id: u32,
) -> Result<Rc<Self>, UsrConError> {
let socket = uapi::socket(c::AF_UNIX, c::SOCK_STREAM | c::SOCK_CLOEXEC, 0)
.map(Rc::new)
.map_os_err(UsrConError::CreateSocket)?;
let mut addr: c::sockaddr_un = uapi::pod_zeroed();
addr.sun_family = c::AF_UNIX as _;
if path.len() >= addr.sun_path.len() {
return Err(UsrConError::SocketPathTooLong);
}
let sun_path = uapi::as_bytes_mut(&mut addr.sun_path[..]);
sun_path[..path.len()].copy_from_slice(path.as_bytes());
sun_path[path.len()] = 0;
if let Err(e) = ring.connect(&socket, &addr).await {
return Err(UsrConError::Connect(e));
}
Ok(Self::from_socket(
ring,
wheel,
eng,
dma_buf_ids,
&socket,
server_id,
))
}
pub fn from_socket(
ring: &Rc<IoUring>,
wheel: &Rc<Wheel>,
eng: &Rc<AsyncEngine>,
dma_buf_ids: &Rc<DmaBufIds>,
socket: &Rc<OwnedFd>,
server_id: u32,
) -> Rc<Self> {
let mut obj_ids = Bitfield::default();
obj_ids.take(0);
obj_ids.take(1);
let slf = Rc::new(Self {
ring: ring.clone(),
_wheel: wheel.clone(),
eng: eng.clone(),
server_id,
obj_ids: RefCell::new(obj_ids),
objects: Default::default(),
swapchain: Default::default(),
flush_request: Default::default(),
incoming: Default::default(),
outgoing: Default::default(),
owner: Default::default(),
dead: Cell::new(false),
dma_buf_ids: dma_buf_ids.clone(),
});
slf.objects.set(
WL_DISPLAY_ID.into(),
Some(Rc::new(UsrWlDisplay {
id: WL_DISPLAY_ID,
con: slf.clone(),
version: Version(1),
})),
);
slf.incoming.set(Some(
slf.eng.spawn(
"wl_usr incoming",
Incoming {
con: slf.clone(),
buf: WlBufFdIn::new(socket, &slf.ring),
}
.run(),
),
));
slf.outgoing.set(Some(
slf.eng.spawn(
"wl_usr outgoing",
Outgoing {
con: slf.clone(),
buf: BufFdOut::new(socket, &slf.ring),
buffers: Default::default(),
}
.run(),
),
));
slf
}
pub fn kill(&self) {
self.dead.set(true);
for obj in self.objects.lock().drain_values() {
if let Some(obj) = obj {
obj.break_loops();
}
}
self.incoming.take();
self.outgoing.take();
if let Some(owner) = self.owner.take() {
owner.killed();
}
}
pub fn release_id(&self, id: u32) {
self.obj_ids.borrow_mut().release(id);
self.objects.remove(&ObjectId::from_raw(id));
}
pub fn remove_obj(&self, obj: &impl UsrObject) {
obj.destroy();
obj.break_loops();
if obj.id().raw() >= MIN_SERVER_ID {
self.objects.remove(&obj.id());
} else {
self.objects.set(obj.id(), None);
}
}
pub fn add_object(&self, obj: Rc<dyn UsrObject>) {
if !self.dead.get() {
self.objects.set(obj.id(), Some(obj));
}
}
pub fn get_registry(self: &Rc<Self>) -> Rc<UsrWlRegistry> {
let registry = Rc::new(UsrWlRegistry {
id: self.id(),
con: self.clone(),
owner: Default::default(),
version: Version(1),
});
self.request(wl_display::GetRegistry {
self_id: WL_DISPLAY_ID,
registry: registry.id,
});
self.add_object(registry.clone());
registry
}
pub fn sync<F>(self: &Rc<Self>, handler: F)
where
F: FnOnce() + 'static,
{
let callback = Rc::new(UsrWlCallback::new(self));
callback.owner.set(Some(Rc::new(Cell::new(Some(handler)))));
self.request(wl_display::Sync {
self_id: WL_DISPLAY_ID,
callback: callback.id,
});
self.add_object(callback);
}
pub fn parse<'a, R: RequestParser<'a>>(
&self,
obj: &impl UsrObject,
mut parser: MsgParser<'_, 'a>,
) -> Result<R, MsgParserError> {
let res = R::parse(&mut parser)?;
log::trace!(
"Server {} -> {}@{}.{:?}",
self.server_id,
obj.interface().name(),
obj.id(),
res
);
Ok(res)
}
pub fn request<T: EventFormatter>(self: &Rc<Self>, event: T) {
if self.dead.get() {
return;
}
if log::log_enabled!(log::Level::Trace) {
log::trace!(
"Server {} <= {}@{}.{:?}",
self.server_id,
event.interface().name(),
event.id(),
event,
);
}
let mut fds = vec![];
let mut swapchain = self.swapchain.borrow_mut();
let mut fmt = MsgFormatter::new(&mut swapchain.cur, &mut fds);
event.format(&mut fmt);
fmt.write_len();
if swapchain.cur.is_full() {
swapchain.commit();
}
self.flush_request.trigger();
}
pub fn id<T: From<ObjectId>>(&self) -> T {
let id = self.obj_ids.borrow_mut().acquire();
ObjectId::from_raw(id).into()
}
}
struct Outgoing {
con: Rc<UsrCon>,
buf: BufFdOut,
buffers: VecDeque<OutBuffer>,
}
impl Outgoing {
async fn run(mut self) {
loop {
self.con.flush_request.triggered().await;
if let Err(e) = self.flush().await {
log::error!(
"Server {}: Could not process an outgoing message: {}",
self.con.server_id,
ErrorFmt(e)
);
self.con.kill();
return;
}
}
}
async fn flush(&mut self) -> Result<(), UsrConError> {
{
let mut swapchain = self.con.swapchain.borrow_mut();
swapchain.commit();
mem::swap(&mut swapchain.pending, &mut self.buffers);
}
while let Some(mut cur) = self.buffers.pop_front() {
if let Err(e) = self.buf.flush_no_timeout(&mut cur).await {
return Err(UsrConError::Write(e));
}
self.con.swapchain.borrow_mut().free.push(cur);
}
Ok(())
}
}
struct Incoming {
con: Rc<UsrCon>,
buf: WlBufFdIn,
}
impl Incoming {
async fn run(mut self) {
loop {
if let Err(e) = self.handle_msg().await {
log::error!(
"Server {}: Could not process an incoming message: {}",
self.con.server_id,
ErrorFmt(e)
);
self.con.kill();
return;
}
}
}
async fn handle_msg(&mut self) -> Result<(), UsrConError> {
let WlMessage {
obj_id,
message,
body,
fds,
} = self.buf.read_message().await.map_err(UsrConError::Read)?;
if let Some(obj) = self.con.objects.get(&obj_id) {
if let Some(obj) = obj {
let parser = MsgParser::new(fds, body);
obj.handle_event(&self.con, message, parser)?;
}
} else if obj_id.raw() < MIN_SERVER_ID {
return Err(UsrConError::MissingObject(obj_id));
} else {
// ignore events for server-created objects that were never added to the state
}
Ok(())
}
}

48
src/wl_usr/usr_ifs.rs Normal file
View file

@ -0,0 +1,48 @@
pub mod usr_jay_compositor;
pub mod usr_jay_ei_session;
pub mod usr_jay_ei_session_builder;
pub mod usr_jay_output;
pub mod usr_jay_pointer;
pub mod usr_jay_render_ctx;
pub mod usr_jay_screencast;
pub mod usr_jay_select_toplevel;
pub mod usr_jay_select_workspace;
pub mod usr_jay_sync_file_release;
pub mod usr_jay_sync_file_surface;
pub mod usr_jay_toplevel;
pub mod usr_jay_workspace;
pub mod usr_jay_workspace_watcher;
pub mod usr_linux_buffer_params;
pub mod usr_linux_dmabuf;
pub mod usr_wl_buffer;
pub mod usr_wl_callback;
pub mod usr_wl_compositor;
pub mod usr_wl_data_device;
pub mod usr_wl_data_device_manager;
pub mod usr_wl_data_offer;
pub mod usr_wl_data_source;
pub mod usr_wl_display;
pub mod usr_wl_keyboard;
pub mod usr_wl_output;
pub mod usr_wl_pointer;
pub mod usr_wl_registry;
pub mod usr_wl_seat;
pub mod usr_wl_shm;
pub mod usr_wl_shm_pool;
pub mod usr_wl_surface;
pub mod usr_wlr_layer_shell;
pub mod usr_wlr_layer_surface;
pub mod usr_wp_cursor_shape_device_v1;
pub mod usr_wp_cursor_shape_manager_v1;
pub mod usr_wp_fractional_scale;
pub mod usr_wp_fractional_scale_manager;
pub mod usr_wp_viewport;
pub mod usr_wp_viewporter;
pub mod usr_xdg_surface;
pub mod usr_xdg_toplevel;
pub mod usr_xdg_wm_base;
pub mod usr_zwlr_screencopy_frame;
pub mod usr_zwlr_screencopy_manager;
pub mod usr_zwp_linux_buffer_params_v1;
pub mod usr_zwp_linux_dmabuf_v1;
pub mod usr_zwp_primary_selection_device_manager;

View file

@ -0,0 +1,239 @@
use {
crate::{
ifs::jay_compositor::Cap,
object::Version,
utils::clonecell::CloneCell,
wire::{JayCompositorId, jay_compositor::*},
wl_usr::{
UsrCon,
usr_ifs::{
usr_jay_ei_session_builder::UsrJayEiSessionBuilder, usr_jay_output::UsrJayOutput,
usr_jay_pointer::UsrJayPointer, usr_jay_render_ctx::UsrJayRenderCtx,
usr_jay_screencast::UsrJayScreencast,
usr_jay_select_toplevel::UsrJaySelectToplevel,
usr_jay_select_workspace::UsrJaySelectWorkspace,
usr_jay_workspace_watcher::UsrJayWorkspaceWatcher, usr_wl_output::UsrWlOutput,
usr_wl_seat::UsrWlSeat,
},
usr_object::UsrObject,
},
},
std::{cell::Cell, convert::Infallible, rc::Rc},
};
pub struct UsrJayCompositor {
pub id: JayCompositorId,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrJayCompositorOwner>>>,
pub caps: UsrJayCompositorCaps,
pub version: Version,
}
#[derive(Default)]
pub struct UsrJayCompositorCaps {
pub window_capture: Cell<bool>,
pub select_workspace: Cell<bool>,
}
pub trait UsrJayCompositorOwner {
fn client_id(&self, ev: ClientId) {
let _ = ev;
}
fn seat(&self, ev: Seat) {
let _ = ev;
}
}
impl UsrJayCompositor {
pub fn get_render_context(&self) -> Rc<UsrJayRenderCtx> {
let rc = Rc::new(UsrJayRenderCtx {
id: self.con.id(),
con: self.con.clone(),
owner: Default::default(),
version: self.version,
formats: Default::default(),
});
self.con.request(GetRenderCtx {
self_id: self.id,
id: rc.id,
});
self.con.add_object(rc.clone());
rc
}
pub fn create_screencast(&self) -> Rc<UsrJayScreencast> {
let sc = Rc::new(UsrJayScreencast {
id: self.con.id(),
con: self.con.clone(),
owner: Default::default(),
version: self.version,
pending_buffers: Default::default(),
pending_planes: Default::default(),
pending_config: Default::default(),
});
self.con.request(CreateScreencast {
self_id: self.id,
id: sc.id,
});
self.con.add_object(sc.clone());
sc
}
pub fn get_output(&self, output: &UsrWlOutput) -> Rc<UsrJayOutput> {
let jo = Rc::new(UsrJayOutput {
id: self.con.id(),
con: self.con.clone(),
owner: Default::default(),
version: self.version,
});
self.con.request(GetOutput {
self_id: self.id,
id: jo.id,
output: output.id,
});
self.con.add_object(jo.clone());
jo
}
pub fn watch_workspaces(&self) -> Rc<UsrJayWorkspaceWatcher> {
let ww = Rc::new(UsrJayWorkspaceWatcher {
id: self.con.id(),
con: self.con.clone(),
owner: Default::default(),
version: self.version,
});
self.con.request(WatchWorkspaces {
self_id: self.id,
id: ww.id,
});
self.con.add_object(ww.clone());
ww
}
pub fn get_pointer(&self, seat: &UsrWlSeat) -> Rc<UsrJayPointer> {
let jp = Rc::new(UsrJayPointer {
id: self.con.id(),
con: self.con.clone(),
version: self.version,
});
self.con.add_object(jp.clone());
self.con.request(GetPointer {
self_id: self.id,
id: jp.id,
seat: seat.id,
});
jp
}
pub fn select_toplevel(&self, seat: &UsrWlSeat) -> Rc<UsrJaySelectToplevel> {
let sc = Rc::new(UsrJaySelectToplevel {
id: self.con.id(),
con: self.con.clone(),
owner: Default::default(),
version: self.version,
});
self.con.request(SelectToplevel {
self_id: self.id,
id: sc.id,
seat: seat.id,
});
self.con.add_object(sc.clone());
sc
}
pub fn get_toplevel(&self, id: &str) -> Rc<UsrJaySelectToplevel> {
let sc = Rc::new(UsrJaySelectToplevel {
id: self.con.id(),
con: self.con.clone(),
owner: Default::default(),
version: self.version,
});
self.con.request(GetToplevel {
self_id: self.id,
id: sc.id,
toplevel_id: id,
});
self.con.add_object(sc.clone());
sc
}
pub fn select_workspace(&self, seat: &UsrWlSeat) -> Rc<UsrJaySelectWorkspace> {
let sc = Rc::new(UsrJaySelectWorkspace {
id: self.con.id(),
con: self.con.clone(),
owner: Default::default(),
version: self.version,
});
self.con.request(SelectWorkspace {
self_id: self.id,
id: sc.id,
seat: seat.id,
});
self.con.add_object(sc.clone());
sc
}
pub fn create_ei_session(&self) -> Rc<UsrJayEiSessionBuilder> {
let obj = Rc::new(UsrJayEiSessionBuilder {
id: self.con.id(),
con: self.con.clone(),
version: self.version,
});
self.con.request(CreateEiSession {
self_id: self.id,
id: obj.id,
});
self.con.add_object(obj.clone());
obj
}
}
impl JayCompositorEventHandler for UsrJayCompositor {
type Error = Infallible;
fn client_id(&self, ev: ClientId, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.client_id(ev);
}
Ok(())
}
fn seat(&self, ev: Seat<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.seat(ev);
}
Ok(())
}
fn capabilities(&self, ev: Capabilities<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
for &cap in ev.cap {
match cap {
Cap::NONE => {}
Cap::WINDOW_CAPTURE => self.caps.window_capture.set(true),
Cap::SELECT_WORKSPACE => self.caps.select_workspace.set(true),
_ => {}
}
}
Ok(())
}
fn pid(&self, _ev: Pid, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
}
usr_object_base! {
self = UsrJayCompositor = JayCompositor;
version = self.version;
}
impl UsrObject for UsrJayCompositor {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
fn break_loops(&self) {
self.owner.take();
}
}

View file

@ -0,0 +1,72 @@
use {
crate::{
object::Version,
utils::clonecell::CloneCell,
wire::{
JayEiSessionId,
jay_ei_session::{Created, Destroyed, Failed, JayEiSessionEventHandler, Release},
},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
uapi::OwnedFd,
};
pub struct UsrJayEiSession {
pub id: JayEiSessionId,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrJayEiSessionOwner>>>,
pub version: Version,
}
pub trait UsrJayEiSessionOwner {
fn destroyed(&self) {}
fn created(&self, fd: &Rc<OwnedFd>) {
let _ = fd;
}
fn failed(&self, reason: &str) {
let _ = reason;
}
}
impl JayEiSessionEventHandler for UsrJayEiSession {
type Error = Infallible;
fn destroyed(&self, _ev: Destroyed, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.destroyed();
}
Ok(())
}
fn created(&self, ev: Created, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.created(&ev.fd);
}
Ok(())
}
fn failed(&self, ev: Failed<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.failed(ev.reason);
}
Ok(())
}
}
usr_object_base! {
self = UsrJayEiSession = JayEiSession;
version = self.version;
}
impl UsrObject for UsrJayEiSession {
fn destroy(&self) {
self.con.request(Release { self_id: self.id });
}
fn break_loops(&self) {
self.owner.take();
}
}

View file

@ -0,0 +1,57 @@
use {
crate::{
object::Version,
wire::{
JayEiSessionBuilderId,
jay_ei_session_builder::{Commit, JayEiSessionBuilderEventHandler, SetAppId},
},
wl_usr::{UsrCon, usr_ifs::usr_jay_ei_session::UsrJayEiSession, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrJayEiSessionBuilder {
pub id: JayEiSessionBuilderId,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl UsrJayEiSessionBuilder {
pub fn set_app_id(&self, app_id: &str) {
self.con.request(SetAppId {
self_id: self.id,
app_id,
});
}
pub fn commit(&self) -> Rc<UsrJayEiSession> {
let obj = Rc::new(UsrJayEiSession {
id: self.con.id(),
con: self.con.clone(),
owner: Default::default(),
version: self.version,
});
self.con.add_object(obj.clone());
self.con.request(Commit {
self_id: self.id,
id: obj.id,
});
self.con.remove_obj(self);
obj
}
}
impl JayEiSessionBuilderEventHandler for UsrJayEiSessionBuilder {
type Error = Infallible;
}
usr_object_base! {
self = UsrJayEiSessionBuilder = JayEiSessionBuilder;
version = self.version;
}
impl UsrObject for UsrJayEiSessionBuilder {
fn destroy(&self) {
// nothing
}
}

View file

@ -0,0 +1,61 @@
use {
crate::{
object::Version,
utils::clonecell::CloneCell,
wire::{JayOutputId, jay_output::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrJayOutput {
pub id: JayOutputId,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrJayOutputOwner>>>,
pub version: Version,
}
pub trait UsrJayOutputOwner {
fn linear_id(self: Rc<Self>, ev: &LinearId) {
let _ = ev;
}
fn destroyed(&self) {}
}
impl JayOutputEventHandler for UsrJayOutput {
type Error = Infallible;
fn linear_id(&self, ev: LinearId, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.linear_id(&ev);
}
Ok(())
}
fn unused(&self, _ev: Unused, _slf: &Rc<Self>) -> Result<(), Self::Error> {
unimplemented!();
}
fn destroyed(&self, _ev: Destroyed, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.destroyed();
}
Ok(())
}
}
usr_object_base! {
self = UsrJayOutput = JayOutput;
version = self.version;
}
impl UsrObject for UsrJayOutput {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
fn break_loops(&self) {
self.owner.set(None);
}
}

View file

@ -0,0 +1,39 @@
use {
crate::{
cursor::KnownCursor,
object::Version,
wire::{JayPointerId, jay_pointer::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrJayPointer {
pub id: JayPointerId,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl UsrJayPointer {
pub fn set_known_cursor(&self, cursor: KnownCursor) {
self.con.request(SetKnownCursor {
self_id: self.id,
idx: cursor as usize as _,
});
}
}
impl JayPointerEventHandler for UsrJayPointer {
type Error = Infallible;
}
usr_object_base! {
self = UsrJayPointer = JayPointer;
version = self.version;
}
impl UsrObject for UsrJayPointer {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
}

View file

@ -0,0 +1,109 @@
use {
crate::{
format::formats,
gfx_api::{GfxFormat, GfxWriteModifier},
ifs::jay_render_ctx::FORMATS_SINCE,
object::Version,
utils::clonecell::CloneCell,
video::Modifier,
wire::{JayRenderCtxId, jay_render_ctx::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
ahash::AHashMap,
std::{cell::RefCell, convert::Infallible, rc::Rc},
uapi::OwnedFd,
};
pub struct UsrJayRenderCtx {
pub id: JayRenderCtxId,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrJayRenderCtxOwner>>>,
pub version: Version,
pub formats: RefCell<AHashMap<u32, GfxFormat>>,
}
pub trait UsrJayRenderCtxOwner {
fn no_device(&self) {}
fn device(&self, fd: Rc<OwnedFd>, server_formats: Option<AHashMap<u32, GfxFormat>>) {
let _ = fd;
let _ = server_formats;
}
}
impl UsrJayRenderCtx {
fn add_write_modifier(&self, format: u32, modifier: Modifier, needs_render_usage: bool) {
if let Some(format) = self.formats.borrow_mut().get_mut(&format) {
format
.write_modifiers
.insert(modifier, GfxWriteModifier { needs_render_usage });
}
}
}
impl JayRenderCtxEventHandler for UsrJayRenderCtx {
type Error = Infallible;
fn no_device(&self, _ev: NoDevice, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.formats.take();
if let Some(owner) = self.owner.get() {
owner.no_device();
}
Ok(())
}
fn device(&self, ev: Device, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let formats = self.formats.take();
let formats = (self.version >= FORMATS_SINCE).then_some(formats);
if let Some(owner) = self.owner.get() {
owner.device(ev.fd, formats);
}
Ok(())
}
fn read_modifier(&self, ev: ReadModifier, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(format) = self.formats.borrow_mut().get_mut(&ev.format) {
format.read_modifiers.insert(ev.modifier);
}
Ok(())
}
fn write_modifier(&self, ev: WriteModifier, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.add_write_modifier(ev.format, ev.modifier, true);
Ok(())
}
fn format(&self, ev: Format, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(format) = formats().get(&ev.format) {
self.formats.borrow_mut().insert(
ev.format,
GfxFormat {
format,
read_modifiers: Default::default(),
write_modifiers: Default::default(),
supports_shm: false,
},
);
}
Ok(())
}
fn write_modifier2(&self, ev: WriteModifier2, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.add_write_modifier(ev.format, ev.modifier, ev.needs_render_usage != 0);
Ok(())
}
}
usr_object_base! {
self = UsrJayRenderCtx = JayRenderCtx;
version = self.version;
}
impl UsrObject for UsrJayRenderCtx {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
fn break_loops(&self) {
self.owner.take();
}
}

View file

@ -0,0 +1,272 @@
use {
crate::{
format::formats,
object::Version,
utils::clonecell::CloneCell,
video::dmabuf::{DmaBuf, DmaBufPlane, PlaneVec},
wire::{JayScreencastId, jay_screencast::*},
wl_usr::{
UsrCon,
usr_ifs::{
usr_jay_output::UsrJayOutput, usr_jay_toplevel::UsrJayToplevel,
usr_jay_workspace::UsrJayWorkspace, usr_wl_buffer::UsrWlBuffer,
},
usr_object::UsrObject,
},
},
std::{cell::RefCell, mem, ops::DerefMut, rc::Rc},
thiserror::Error,
};
pub struct UsrJayScreencast {
pub id: JayScreencastId,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrJayScreencastOwner>>>,
pub version: Version,
pub pending_buffers: RefCell<Vec<DmaBuf>>,
pub pending_planes: RefCell<PlaneVec<DmaBufPlane>>,
pub pending_config: RefCell<UsrJayScreencastServerConfig>,
}
#[derive(Default)]
pub struct UsrJayScreencastServerConfig {
pub output: Option<u32>,
pub show_all: bool,
pub running: bool,
pub use_linear_buffers: bool,
pub allowed_workspaces: Vec<u32>,
pub width: i32,
pub height: i32,
}
pub trait UsrJayScreencastOwner {
fn buffers(&self, buffers: Vec<DmaBuf>) {
let _ = buffers;
}
fn ready(&self, ev: &Ready) {
let _ = ev;
}
fn destroyed(&self) {}
fn missed_frame(&self) {}
fn config(&self, config: UsrJayScreencastServerConfig) {
let _ = config;
}
}
impl UsrJayScreencast {
pub fn set_output(&self, output: &UsrJayOutput) {
self.con.request(SetOutput {
self_id: self.id,
output: output.id,
});
}
pub fn set_toplevel(&self, tl: &UsrJayToplevel) {
self.con.request(SetToplevel {
self_id: self.id,
id: tl.id,
});
}
pub fn set_allow_all_workspaces(&self, allow_all: bool) {
self.con.request(SetAllowAllWorkspaces {
self_id: self.id,
allow_all: allow_all as _,
});
}
pub fn allow_workspace(&self, ws: &UsrJayWorkspace) {
self.con.request(AllowWorkspace {
self_id: self.id,
workspace: ws.id,
});
}
#[expect(dead_code)]
pub fn touch_allowed_workspaces(&self) {
self.con
.request(TouchAllowedWorkspaces { self_id: self.id });
}
pub fn set_use_linear_buffers(&self, linear: bool) {
self.con.request(SetUseLinearBuffers {
self_id: self.id,
use_linear: linear as _,
});
}
pub fn set_running(&self, running: bool) {
self.con.request(SetRunning {
self_id: self.id,
running: running as _,
});
}
pub fn configure(&self) {
self.con.request(Configure { self_id: self.id });
}
pub fn release_buffer(&self, idx: usize) {
self.con.request(ReleaseBuffer {
self_id: self.id,
idx: idx as _,
});
}
pub fn clear_buffers(&self) {
self.con.request(ClearBuffers { self_id: self.id });
}
pub fn add_buffer(&self, buffer: &UsrWlBuffer) {
self.con.request(AddBuffer {
self_id: self.id,
buffer: buffer.id,
});
}
}
impl JayScreencastEventHandler for UsrJayScreencast {
type Error = UsrJayScreencastError;
fn plane(&self, ev: Plane, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.pending_planes.borrow_mut().push(DmaBufPlane {
offset: ev.offset,
stride: ev.stride,
fd: ev.fd,
});
Ok(())
}
fn buffer(&self, ev: Buffer, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let format = match formats().get(&ev.format) {
Some(f) => f,
_ => return Err(UsrJayScreencastError::UnknownFormat(ev.format)),
};
self.pending_buffers.borrow_mut().push(DmaBuf {
id: self.con.dma_buf_ids.next(),
width: ev.width,
height: ev.height,
format,
modifier: ev.modifier,
planes: mem::take(self.pending_planes.borrow_mut().deref_mut()),
is_disjoint: Default::default(),
});
Ok(())
}
fn buffers_done(&self, ev: BuffersDone, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.buffers(mem::take(self.pending_buffers.borrow_mut().deref_mut()));
}
self.con.request(AckBuffers {
self_id: self.id,
serial: ev.serial,
});
Ok(())
}
fn ready(&self, ev: Ready, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.ready(&ev);
}
Ok(())
}
fn destroyed(&self, _ev: Destroyed, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.destroyed();
}
Ok(())
}
fn missed_frame(&self, _ev: MissedFrame, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.missed_frame();
}
Ok(())
}
fn config_output(&self, ev: ConfigOutput, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.pending_config.borrow_mut().output = Some(ev.linear_id);
Ok(())
}
fn config_allow_all_workspaces(
&self,
ev: ConfigAllowAllWorkspaces,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
self.pending_config.borrow_mut().show_all = ev.allow_all != 0;
Ok(())
}
fn config_allow_workspace(
&self,
ev: ConfigAllowWorkspace,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
self.pending_config
.borrow_mut()
.allowed_workspaces
.push(ev.linear_id);
Ok(())
}
fn config_use_linear_buffers(
&self,
ev: ConfigUseLinearBuffers,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
self.pending_config.borrow_mut().use_linear_buffers = ev.use_linear != 0;
Ok(())
}
fn config_running(&self, ev: ConfigRunning, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.pending_config.borrow_mut().running = ev.running != 0;
Ok(())
}
fn config_done(&self, ev: ConfigDone, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.config(mem::take(self.pending_config.borrow_mut().deref_mut()));
}
self.con.request(AckConfig {
self_id: self.id,
serial: ev.serial,
});
Ok(())
}
fn config_size(&self, ev: ConfigSize, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.pending_config.borrow_mut().width = ev.width;
self.pending_config.borrow_mut().height = ev.height;
Ok(())
}
}
usr_object_base! {
self = UsrJayScreencast = JayScreencast;
version = self.version;
}
impl UsrObject for UsrJayScreencast {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
fn break_loops(&self) {
self.owner.take();
}
}
#[derive(Debug, Error)]
pub enum UsrJayScreencastError {
#[error("The server sent an unknown format {0}")]
UnknownFormat(u32),
}

View file

@ -0,0 +1,90 @@
use {
crate::{
ifs::jay_toplevel::ID_SINCE,
object::Version,
utils::clonecell::CloneCell,
wire::{JaySelectToplevelId, jay_select_toplevel::*},
wl_usr::{
UsrCon,
usr_ifs::usr_jay_toplevel::{UsrJayToplevel, UsrJayToplevelOwner},
usr_object::UsrObject,
},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrJaySelectToplevel {
pub id: JaySelectToplevelId,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrJaySelectToplevelOwner>>>,
pub version: Version,
}
impl UsrJaySelectToplevel {
fn send(&self, tl: Option<Rc<UsrJayToplevel>>) {
if let Some(owner) = self.owner.get() {
owner.done(tl);
} else {
if let Some(tl) = tl {
self.con.remove_obj(&*tl);
}
}
self.con.remove_obj(self);
}
}
pub trait UsrJaySelectToplevelOwner {
fn done(&self, toplevel: Option<Rc<UsrJayToplevel>>);
}
impl JaySelectToplevelEventHandler for UsrJaySelectToplevel {
type Error = Infallible;
fn done(&self, ev: Done, slf: &Rc<Self>) -> Result<(), Self::Error> {
let tl = if ev.id.is_none() {
None
} else {
let tl = Rc::new(UsrJayToplevel {
id: ev.id,
con: self.con.clone(),
owner: Default::default(),
version: self.version,
toplevel_id: Default::default(),
});
self.con.add_object(tl.clone());
Some(tl)
};
'send: {
if self.version >= ID_SINCE
&& let Some(tl) = tl
{
tl.owner.set(Some(slf.clone()));
break 'send;
}
self.send(tl);
}
Ok(())
}
}
impl UsrJayToplevelOwner for UsrJaySelectToplevel {
fn done(&self, tl: &Rc<UsrJayToplevel>) {
tl.owner.take();
self.send(Some(tl.clone()));
}
}
usr_object_base! {
self = UsrJaySelectToplevel = JaySelectToplevel;
version = self.version;
}
impl UsrObject for UsrJaySelectToplevel {
fn destroy(&self) {
// nothing
}
fn break_loops(&self) {
self.owner.take();
}
}

View file

@ -0,0 +1,78 @@
use {
crate::{
globals::GlobalName,
object::Version,
utils::clonecell::CloneCell,
wire::{JaySelectWorkspaceId, jay_select_workspace::*},
wl_usr::{
UsrCon,
usr_ifs::usr_jay_workspace::{UsrJayWorkspace, UsrJayWorkspaceOwner},
usr_object::UsrObject,
},
},
std::{cell::Cell, convert::Infallible, rc::Rc},
};
pub struct UsrJaySelectWorkspace {
pub id: JaySelectWorkspaceId,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrJaySelectWorkspaceOwner>>>,
pub version: Version,
}
pub trait UsrJaySelectWorkspaceOwner {
fn done(&self, output: GlobalName, ws: Option<Rc<UsrJayWorkspace>>);
}
impl JaySelectWorkspaceEventHandler for UsrJaySelectWorkspace {
type Error = Infallible;
fn cancelled(&self, _ev: Cancelled, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.done(GlobalName::from_raw(0), None);
}
self.con.remove_obj(self);
Ok(())
}
fn selected(&self, ev: Selected, slf: &Rc<Self>) -> Result<(), Self::Error> {
let tl = Rc::new(UsrJayWorkspace {
id: ev.id,
con: self.con.clone(),
owner: Default::default(),
version: self.version,
linear_id: Default::default(),
output: Cell::new(GlobalName::from_raw(0)),
name: Default::default(),
});
self.con.add_object(tl.clone());
tl.owner.set(Some(slf.clone()));
Ok(())
}
}
impl UsrJayWorkspaceOwner for UsrJaySelectWorkspace {
fn done(&self, ws: &Rc<UsrJayWorkspace>) {
ws.owner.take();
match self.owner.get() {
Some(owner) => owner.done(ws.output.get(), Some(ws.clone())),
_ => self.con.remove_obj(&**ws),
}
self.con.remove_obj(self);
}
}
usr_object_base! {
self = UsrJaySelectWorkspace = JaySelectWorkspace;
version = self.version;
}
impl UsrObject for UsrJaySelectWorkspace {
fn destroy(&self) {
// nothing
}
fn break_loops(&self) {
self.owner.take();
}
}

View file

@ -0,0 +1,60 @@
use {
crate::{
gfx_api::SyncFile,
object::Version,
utils::clonecell::CloneCell,
wire::{JaySyncFileReleaseId, jay_sync_file_release::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
uapi::OwnedFd,
};
pub struct UsrJaySyncFileRelease {
pub id: JaySyncFileReleaseId,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrJaySyncFileReleaseOwner>>>,
pub version: Version,
}
pub trait UsrJaySyncFileReleaseOwner {
fn release(&self, sync_file: Option<SyncFile>);
}
impl UsrJaySyncFileRelease {
fn release(&self, sf: Option<Rc<OwnedFd>>) {
if let Some(owner) = self.owner.get() {
owner.release(sf.map(SyncFile));
}
self.con.remove_obj(self);
}
}
impl JaySyncFileReleaseEventHandler for UsrJaySyncFileRelease {
type Error = Infallible;
fn release_immediate(&self, _ev: ReleaseImmediate, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.release(None);
Ok(())
}
fn release_async(&self, ev: ReleaseAsync, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.release(Some(ev.sync_file));
Ok(())
}
}
usr_object_base! {
self = UsrJaySyncFileRelease = JaySyncFileRelease;
version = self.version;
}
impl UsrObject for UsrJaySyncFileRelease {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
fn break_loops(&self) {
self.owner.take();
}
}

View file

@ -0,0 +1,29 @@
use {
crate::{
object::Version,
wire::{JaySyncFileSurfaceId, jay_sync_file_surface::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrJaySyncFileSurface {
pub id: JaySyncFileSurfaceId,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl JaySyncFileSurfaceEventHandler for UsrJaySyncFileSurface {
type Error = Infallible;
}
usr_object_base! {
self = UsrJaySyncFileSurface = JaySyncFileSurface;
version = self.version;
}
impl UsrObject for UsrJaySyncFileSurface {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
}

View file

@ -0,0 +1,64 @@
use {
crate::{
object::Version,
utils::clonecell::CloneCell,
wire::{JayToplevelId, jay_toplevel::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{cell::RefCell, convert::Infallible, rc::Rc},
};
pub struct UsrJayToplevel {
pub id: JayToplevelId,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrJayToplevelOwner>>>,
pub version: Version,
pub toplevel_id: RefCell<Option<String>>,
}
pub trait UsrJayToplevelOwner {
fn destroyed(&self) {}
fn done(&self, tl: &Rc<UsrJayToplevel>);
}
impl JayToplevelEventHandler for UsrJayToplevel {
type Error = Infallible;
fn destroyed(&self, _ev: Destroyed, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.destroyed();
}
Ok(())
}
fn id_(&self, ev: Id<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
*self.toplevel_id.borrow_mut() = Some(ev.id.to_string());
Ok(())
}
fn client_id(&self, _ev: ClientId, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
fn done(&self, _ev: Done, slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.done(slf);
}
Ok(())
}
}
usr_object_base! {
self = UsrJayToplevel = JayToplevel;
version = self.version;
}
impl UsrObject for UsrJayToplevel {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
fn break_loops(&self) {
self.owner.set(None);
}
}

View file

@ -0,0 +1,100 @@
use {
crate::{
globals::GlobalName,
object::Version,
utils::clonecell::CloneCell,
wire::{JayWorkspaceId, jay_workspace::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{
cell::{Cell, RefCell},
convert::Infallible,
rc::Rc,
},
};
pub struct UsrJayWorkspace {
pub id: JayWorkspaceId,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrJayWorkspaceOwner>>>,
pub version: Version,
pub linear_id: Cell<u32>,
pub output: Cell<GlobalName>,
pub name: RefCell<Option<String>>,
}
pub trait UsrJayWorkspaceOwner {
fn destroyed(&self, ws: &UsrJayWorkspace) {
let _ = ws;
}
fn done(&self, ws: &Rc<UsrJayWorkspace>) {
let _ = ws;
}
fn output(self: Rc<Self>, ev: &Output) {
let _ = ev;
}
fn visible(&self, visible: bool) {
let _ = visible;
}
}
impl JayWorkspaceEventHandler for UsrJayWorkspace {
type Error = Infallible;
fn linear_id(&self, ev: LinearId, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.linear_id.set(ev.linear_id);
Ok(())
}
fn name(&self, ev: Name<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
*self.name.borrow_mut() = Some(ev.name.to_string());
Ok(())
}
fn destroyed(&self, _ev: Destroyed, slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.destroyed(slf);
}
Ok(())
}
fn done(&self, _ev: Done, slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.done(slf);
}
Ok(())
}
fn output(&self, ev: Output, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.output.set(GlobalName::from_raw(ev.global_name));
if let Some(owner) = self.owner.get() {
owner.output(&ev);
}
Ok(())
}
fn visible(&self, ev: Visible, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.visible(ev.visible != 0);
}
Ok(())
}
}
usr_object_base! {
self = UsrJayWorkspace = JayWorkspace;
version = self.version;
}
impl UsrObject for UsrJayWorkspace {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
fn break_loops(&self) {
self.owner.set(None);
}
}

View file

@ -0,0 +1,62 @@
use {
crate::{
globals::GlobalName,
object::Version,
utils::clonecell::CloneCell,
wire::{JayWorkspaceWatcherId, jay_workspace_watcher::*},
wl_usr::{UsrCon, usr_ifs::usr_jay_workspace::UsrJayWorkspace, usr_object::UsrObject},
},
std::{cell::Cell, convert::Infallible, ops::Deref, rc::Rc},
};
pub struct UsrJayWorkspaceWatcher {
pub id: JayWorkspaceWatcherId,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrJayWorkspaceWatcherOwner>>>,
pub version: Version,
}
pub trait UsrJayWorkspaceWatcherOwner {
fn new(self: Rc<Self>, ev: Rc<UsrJayWorkspace>, linear_id: u32) {
let _ = linear_id;
ev.con.remove_obj(ev.deref());
}
}
impl JayWorkspaceWatcherEventHandler for UsrJayWorkspaceWatcher {
type Error = Infallible;
fn new(&self, ev: New, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let jw = Rc::new(UsrJayWorkspace {
id: ev.id,
con: self.con.clone(),
owner: Default::default(),
version: self.version,
linear_id: Default::default(),
output: Cell::new(GlobalName::from_raw(0)),
name: Default::default(),
});
self.con.add_object(jw.clone());
if let Some(owner) = self.owner.get() {
owner.new(jw, ev.linear_id);
} else {
self.con.remove_obj(jw.deref());
}
Ok(())
}
}
usr_object_base! {
self = UsrJayWorkspaceWatcher = JayWorkspaceWatcher;
version = self.version;
}
impl UsrObject for UsrJayWorkspaceWatcher {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
fn break_loops(&self) {
self.owner.set(None);
}
}

View file

@ -0,0 +1,89 @@
use {
crate::{
object::Version,
utils::clonecell::CloneCell,
video::dmabuf::DmaBuf,
wire::{ZwpLinuxBufferParamsV1Id, zwp_linux_buffer_params_v1::*},
wl_usr::{UsrCon, usr_ifs::usr_wl_buffer::UsrWlBuffer, usr_object::UsrObject},
},
std::{convert::Infallible, ops::Deref, rc::Rc},
};
pub struct UsrLinuxBufferParams {
pub id: ZwpLinuxBufferParamsV1Id,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrLinuxBufferParamsOwner>>>,
pub version: Version,
}
pub trait UsrLinuxBufferParamsOwner {
fn created(&self, buffer: Rc<UsrWlBuffer>) {
buffer.con.remove_obj(buffer.deref());
}
fn failed(&self) {}
}
impl UsrLinuxBufferParams {
pub fn create(&self, buf: &DmaBuf) {
for (idx, plane) in buf.planes.iter().enumerate() {
self.con.request(Add {
self_id: self.id,
fd: plane.fd.clone(),
plane_idx: idx as _,
offset: plane.offset,
stride: plane.stride,
modifier: buf.modifier,
});
}
self.con.request(Create {
self_id: self.id,
width: buf.width,
height: buf.height,
format: buf.format.drm,
flags: 0,
});
}
}
impl ZwpLinuxBufferParamsV1EventHandler for UsrLinuxBufferParams {
type Error = Infallible;
fn created(&self, ev: Created, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let buffer = Rc::new(UsrWlBuffer {
id: ev.buffer,
con: self.con.clone(),
owner: Default::default(),
version: self.version,
});
self.con.add_object(buffer.clone());
if let Some(owner) = self.owner.get() {
owner.created(buffer);
} else {
self.con.remove_obj(buffer.deref());
}
Ok(())
}
fn failed(&self, _ev: Failed, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.failed();
}
Ok(())
}
}
usr_object_base! {
self = UsrLinuxBufferParams = ZwpLinuxBufferParamsV1;
version = self.version;
}
impl UsrObject for UsrLinuxBufferParams {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
fn break_loops(&self) {
self.owner.take();
}
}

View file

@ -0,0 +1,78 @@
use {
crate::{
format::{Format, formats},
object::Version,
utils::clonecell::CloneCell,
wire::{
ZwpLinuxDmabufV1Id,
zwp_linux_dmabuf_v1::{self, *},
},
wl_usr::{
UsrCon, usr_ifs::usr_linux_buffer_params::UsrLinuxBufferParams, usr_object::UsrObject,
},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrLinuxDmabuf {
pub id: ZwpLinuxDmabufV1Id,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrLinuxDmabufOwner>>>,
pub version: Version,
}
pub trait UsrLinuxDmabufOwner {
fn modifier(&self, format: &'static Format, modifier: u64) {
let _ = format;
let _ = modifier;
}
}
impl UsrLinuxDmabuf {
pub fn create_params(&self) -> Rc<UsrLinuxBufferParams> {
let params = Rc::new(UsrLinuxBufferParams {
id: self.con.id(),
con: self.con.clone(),
owner: Default::default(),
version: self.version,
});
self.con.request(CreateParams {
self_id: self.id,
params_id: params.id,
});
self.con.add_object(params.clone());
params
}
}
impl ZwpLinuxDmabufV1EventHandler for UsrLinuxDmabuf {
type Error = Infallible;
fn format(&self, _ev: zwp_linux_dmabuf_v1::Format, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
fn modifier(&self, ev: Modifier, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get()
&& let Some(format) = formats().get(&ev.format)
{
owner.modifier(format, ev.modifier);
}
Ok(())
}
}
usr_object_base! {
self = UsrLinuxDmabuf = ZwpLinuxDmabufV1;
version = self.version;
}
impl UsrObject for UsrLinuxDmabuf {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
fn break_loops(&self) {
self.owner.take();
}
}

View file

@ -0,0 +1,46 @@
use {
crate::{
object::Version,
utils::clonecell::CloneCell,
wire::{WlBufferId, wl_buffer::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrWlBuffer {
pub id: WlBufferId,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrWlBufferOwner>>>,
pub version: Version,
}
pub trait UsrWlBufferOwner {
fn release(&self) {}
}
impl WlBufferEventHandler for UsrWlBuffer {
type Error = Infallible;
fn release(&self, _ev: Release, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.release();
}
Ok(())
}
}
usr_object_base! {
self = UsrWlBuffer = WlBuffer;
version = self.version;
}
impl UsrObject for UsrWlBuffer {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
fn break_loops(&self) {
self.owner.take();
}
}

View file

@ -0,0 +1,68 @@
use {
crate::{
object::Version,
wire::{WlCallbackId, wl_callback::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{cell::Cell, convert::Infallible, rc::Rc},
};
pub struct UsrWlCallback {
pub id: WlCallbackId,
pub con: Rc<UsrCon>,
pub owner: Cell<Option<Rc<dyn UsrWlCallbackOwner>>>,
pub version: Version,
}
pub trait UsrWlCallbackOwner {
fn done(self: Rc<Self>);
}
impl<T> UsrWlCallbackOwner for Cell<Option<T>>
where
T: FnOnce() + 'static,
{
fn done(self: Rc<Self>) {
if let Some(slf) = self.take() {
slf();
}
}
}
impl UsrWlCallback {
pub fn new(con: &Rc<UsrCon>) -> Self {
Self {
id: con.id(),
con: con.clone(),
owner: Default::default(),
version: Version(1),
}
}
}
impl WlCallbackEventHandler for UsrWlCallback {
type Error = Infallible;
fn done(&self, _ev: Done, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(handler) = self.owner.take() {
handler.done();
}
self.con.remove_obj(self);
Ok(())
}
}
usr_object_base! {
self = UsrWlCallback = WlCallback;
version = self.version;
}
impl UsrObject for UsrWlCallback {
fn destroy(&self) {
// nothing
}
fn break_loops(&self) {
self.owner.take();
}
}

View file

@ -0,0 +1,48 @@
use {
crate::{
object::Version,
wire::{
WlCompositorId,
wl_compositor::{CreateSurface, WlCompositorEventHandler},
},
wl_usr::{UsrCon, usr_ifs::usr_wl_surface::UsrWlSurface, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrWlCompositor {
pub id: WlCompositorId,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl UsrWlCompositor {
pub fn create_surface(&self) -> Rc<UsrWlSurface> {
let sfc = Rc::new(UsrWlSurface {
id: self.con.id(),
con: self.con.clone(),
version: self.version,
});
self.con.request(CreateSurface {
self_id: self.id,
id: sfc.id,
});
self.con.add_object(sfc.clone());
sfc
}
}
impl WlCompositorEventHandler for UsrWlCompositor {
type Error = Infallible;
}
usr_object_base! {
self = UsrWlCompositor = WlCompositor;
version = self.version;
}
impl UsrObject for UsrWlCompositor {
fn destroy(&self) {
// nothing
}
}

View file

@ -0,0 +1,84 @@
use {
crate::{
object::Version,
utils::clonecell::CloneCell,
wire::{WlDataDeviceId, wl_data_device::*},
wl_usr::{UsrCon, usr_ifs::usr_wl_data_offer::UsrWlDataOffer, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrWlDataDevice {
pub id: WlDataDeviceId,
pub con: Rc<UsrCon>,
pub version: Version,
pub offer: CloneCell<Option<Rc<UsrWlDataOffer>>>,
pub selection: CloneCell<Option<Rc<UsrWlDataOffer>>>,
}
impl WlDataDeviceEventHandler for UsrWlDataDevice {
type Error = Infallible;
fn data_offer(&self, ev: DataOffer, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let obj = Rc::new(UsrWlDataOffer {
id: ev.id,
con: self.con.clone(),
version: self.version,
mime_types: Default::default(),
});
self.con.add_object(obj.clone());
if let Some(offer) = self.offer.set(Some(obj)) {
self.con.remove_obj(&*offer);
}
Ok(())
}
fn enter(&self, ev: Enter, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let _ = ev;
Ok(())
}
fn leave(&self, ev: Leave, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let _ = ev;
Ok(())
}
fn motion(&self, ev: Motion, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let _ = ev;
Ok(())
}
fn drop_(&self, ev: Drop, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let _ = ev;
Ok(())
}
fn selection(&self, ev: Selection, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.selection.take();
if let Some(offer) = self.offer.get()
&& offer.id == ev.id
{
self.selection.set(Some(offer));
}
Ok(())
}
}
usr_object_base! {
self = UsrWlDataDevice = WlDataDevice;
version = self.version;
}
impl UsrObject for UsrWlDataDevice {
fn destroy(&self) {
if let Some(offer) = self.offer.take() {
self.con.remove_obj(&*offer);
}
self.con.request(Release { self_id: self.id });
}
fn break_loops(&self) {
self.selection.take();
self.offer.take();
}
}

View file

@ -0,0 +1,29 @@
use {
crate::{
object::Version,
wire::{WlDataDeviceManagerId, wl_data_device_manager::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrWlDataDeviceManager {
pub id: WlDataDeviceManagerId,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl WlDataDeviceManagerEventHandler for UsrWlDataDeviceManager {
type Error = Infallible;
}
usr_object_base! {
self = UsrWlDataDeviceManager = WlDataDeviceManager;
version = self.version;
}
impl UsrObject for UsrWlDataDeviceManager {
fn destroy(&self) {
self.con.request(Release { self_id: self.id });
}
}

View file

@ -0,0 +1,46 @@
use {
crate::{
object::Version,
wire::{WlDataOfferId, wl_data_offer::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
ahash::AHashSet,
std::{cell::RefCell, convert::Infallible, rc::Rc},
};
pub struct UsrWlDataOffer {
pub id: WlDataOfferId,
pub con: Rc<UsrCon>,
pub version: Version,
pub mime_types: RefCell<AHashSet<String>>,
}
impl WlDataOfferEventHandler for UsrWlDataOffer {
type Error = Infallible;
fn offer(&self, ev: Offer<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.mime_types
.borrow_mut()
.insert(ev.mime_type.to_string());
Ok(())
}
fn source_actions(&self, _ev: SourceActions, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
fn action(&self, _ev: Action, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
}
usr_object_base! {
self = UsrWlDataOffer = WlDataOffer;
version = self.version;
}
impl UsrObject for UsrWlDataOffer {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
}

View file

@ -0,0 +1,72 @@
use {
crate::{
object::Version,
utils::clonecell::CloneCell,
wire::{WlDataSourceId, wl_data_source::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
uapi::OwnedFd,
};
pub struct UsrWlDataSource {
pub id: WlDataSourceId,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrWlDataSourceOwner>>>,
pub version: Version,
}
pub trait UsrWlDataSourceOwner {
fn send(&self, mime_type: &str, fd: Rc<OwnedFd>);
}
impl WlDataSourceEventHandler for UsrWlDataSource {
type Error = Infallible;
fn target(&self, ev: Target<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let _ = ev;
Ok(())
}
fn send(&self, ev: Send<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.send(ev.mime_type, ev.fd);
}
Ok(())
}
fn cancelled(&self, ev: Cancelled, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let _ = ev;
Ok(())
}
fn dnd_drop_performed(&self, ev: DndDropPerformed, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let _ = ev;
Ok(())
}
fn dnd_finished(&self, ev: DndFinished, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let _ = ev;
Ok(())
}
fn action(&self, ev: Action, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let _ = ev;
Ok(())
}
}
usr_object_base! {
self = UsrWlDataSource = WlDataSource;
version = self.version;
}
impl UsrObject for UsrWlDataSource {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
fn break_loops(&self) {
self.owner.take();
}
}

View file

@ -0,0 +1,44 @@
use {
crate::{
object::Version,
wire::{WlDisplayId, wl_display::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::rc::Rc,
};
pub struct UsrWlDisplay {
pub id: WlDisplayId,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl WlDisplayEventHandler for UsrWlDisplay {
type Error = UsrWlDisplayError;
fn error(&self, ev: Error<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Err(UsrWlDisplayError::ServerError(ev.message.to_owned()))
}
fn delete_id(&self, ev: DeleteId, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.con.release_id(ev.id);
Ok(())
}
}
#[derive(Debug, thiserror::Error)]
pub enum UsrWlDisplayError {
#[error("The server emitted an error: {0}")]
ServerError(String),
}
usr_object_base! {
self = UsrWlDisplay = WlDisplay;
version = self.version;
}
impl UsrObject for UsrWlDisplay {
fn destroy(&self) {
// nothing
}
}

View file

@ -0,0 +1,148 @@
use {
crate::{
ifs::wl_seat::wl_keyboard,
object::Version,
utils::{clonecell::CloneCell, mmap::mmap, oserror::OsError},
wire::{WlKeyboardId, WlSurfaceId, wl_keyboard::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
kbvm::{
Components, Keycode, ModifierMask,
lookup::{Lookup, LookupTable},
xkb::{
Context,
diagnostic::{Diagnostic, WriteToLog},
},
},
std::{cell::RefCell, rc::Rc},
thiserror::Error,
uapi::c,
};
pub struct UsrWlKeyboard {
pub id: WlKeyboardId,
pub con: Rc<UsrCon>,
pub keyboard: RefCell<Option<Keyboard>>,
pub owner: CloneCell<Option<Rc<dyn UsrWlKeyboardOwner>>>,
pub version: Version,
}
pub struct Keyboard {
lookup: LookupTable,
components: Components,
}
pub trait UsrWlKeyboardOwner {
fn focus(self: Rc<Self>, surface: WlSurfaceId, serial: u32);
fn unfocus(self: Rc<Self>);
fn modifiers(self: Rc<Self>, mods: ModifierMask);
fn down(self: Rc<Self>, lookup: Lookup<'_>, serial: u32);
fn repeat(self: Rc<Self>, lookup: Lookup<'_>, serial: u32);
fn up(self: Rc<Self>, lookup: Lookup<'_>, serial: u32);
}
impl WlKeyboardEventHandler for UsrWlKeyboard {
type Error = UsrWlKeyboardError;
fn keymap(&self, ev: Keymap, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let map = mmap(ev.size as _, c::PROT_READ, c::MAP_PRIVATE, ev.fd.raw(), 0)
.map_err(UsrWlKeyboardError::MapKeymap)?;
let mut builder = Context::builder();
builder.enable_default_includes(false);
builder.enable_environment(false);
let keymap = builder
.build()
.keymap_from_bytes(WriteToLog, None, unsafe { &*map.ptr })
.map_err(UsrWlKeyboardError::ParseKeymap)?;
let lookup = keymap.to_builder().build_lookup_table();
let keyboard = Keyboard {
lookup,
components: Default::default(),
};
self.keyboard.replace(Some(keyboard));
Ok(())
}
fn enter(&self, ev: Enter<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let Some(owner) = self.owner.get() else {
return Ok(());
};
owner.focus(ev.surface, ev.serial);
Ok(())
}
fn leave(&self, _ev: Leave, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let Some(owner) = self.owner.get() else {
return Ok(());
};
owner.unfocus();
Ok(())
}
fn key(&self, ev: Key, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let Some(kb) = &*self.keyboard.borrow() else {
return Ok(());
};
let Some(owner) = self.owner.get() else {
return Ok(());
};
let kc = Keycode::from_evdev(ev.key);
let lookup = kb
.lookup
.lookup(kb.components.group, kb.components.mods, kc);
if ev.state == wl_keyboard::PRESSED {
owner.down(lookup, ev.serial);
} else if ev.state == wl_keyboard::REPEATED {
owner.repeat(lookup, ev.serial);
} else if ev.state == wl_keyboard::RELEASED {
owner.up(lookup, ev.serial);
}
Ok(())
}
fn modifiers(&self, ev: Modifiers, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let Some(kb) = &mut *self.keyboard.borrow_mut() else {
return Ok(());
};
kb.components.mods_pressed.0 = ev.mods_depressed;
kb.components.mods_latched.0 = ev.mods_latched;
kb.components.mods_locked.0 = ev.mods_locked;
kb.components.group_locked.0 = ev.group;
let old = kb.components.mods;
kb.components.update_effective();
let new = kb.components.mods;
if old != new
&& let Some(owner) = self.owner.get()
{
owner.modifiers(new);
}
Ok(())
}
fn repeat_info(&self, _ev: RepeatInfo, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
}
usr_object_base! {
self = UsrWlKeyboard = WlKeyboard;
version = self.version;
}
impl UsrObject for UsrWlKeyboard {
fn destroy(&self) {
self.con.request(Release { self_id: self.id });
}
fn break_loops(&self) {
self.owner.take();
}
}
#[derive(Debug, Error)]
pub enum UsrWlKeyboardError {
#[error("Could not map the keymap")]
MapKeymap(#[source] OsError),
#[error("Could not parse the keymap")]
ParseKeymap(#[source] Diagnostic),
}

View file

@ -0,0 +1,103 @@
use {
crate::{
object::Version,
utils::clonecell::CloneCell,
wire::{WlOutputId, wl_output::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{cell::RefCell, convert::Infallible, rc::Rc},
};
pub struct UsrWlOutput {
pub id: WlOutputId,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrWlOutputOwner>>>,
pub version: Version,
pub name: RefCell<Option<String>>,
}
pub trait UsrWlOutputOwner {
fn geometry(&self, ev: &Geometry) {
let _ = ev;
}
fn mode(&self, ev: &Mode) {
let _ = ev;
}
fn done(&self) {}
fn scale(&self, ev: &Scale) {
let _ = ev;
}
fn name(&self, ev: &Name) {
let _ = ev;
}
fn description(&self, ev: &Description) {
let _ = ev;
}
}
impl WlOutputEventHandler for UsrWlOutput {
type Error = Infallible;
fn geometry(&self, ev: Geometry<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.geometry(&ev);
}
Ok(())
}
fn mode(&self, ev: Mode, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.mode(&ev);
}
Ok(())
}
fn done(&self, _ev: Done, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.done();
}
Ok(())
}
fn scale(&self, ev: Scale, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.scale(&ev);
}
Ok(())
}
fn name(&self, ev: Name<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
*self.name.borrow_mut() = Some(ev.name.to_string());
if let Some(owner) = self.owner.get() {
owner.name(&ev);
}
Ok(())
}
fn description(&self, ev: Description<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.description(&ev);
}
Ok(())
}
}
usr_object_base! {
self = UsrWlOutput = WlOutput;
version = self.version;
}
impl UsrObject for UsrWlOutput {
fn destroy(&self) {
self.con.request(Release { self_id: self.id });
}
fn break_loops(&self) {
self.owner.set(None);
}
}

View file

@ -0,0 +1,143 @@
use {
crate::{
ifs::wl_seat::wl_pointer::PendingScroll,
object::Version,
utils::clonecell::CloneCell,
wire::{WlPointerId, wl_pointer::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{cell::Cell, convert::Infallible, rc::Rc},
};
pub struct UsrWlPointer {
pub id: WlPointerId,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrWlPointerOwner>>>,
pub any_scroll_events: Cell<bool>,
pub pending_scroll: PendingScroll,
pub version: Version,
}
pub trait UsrWlPointerOwner {
fn enter(self: Rc<Self>, ev: &Enter) {
let _ = ev;
}
fn leave(self: Rc<Self>, ev: &Leave) {
let _ = ev;
}
fn motion(self: Rc<Self>, ev: &Motion) {
let _ = ev;
}
fn button(self: Rc<Self>, ev: &Button) {
let _ = ev;
}
fn scroll(self: Rc<Self>, ps: &PendingScroll) {
let _ = ps;
}
}
impl WlPointerEventHandler for UsrWlPointer {
type Error = Infallible;
fn enter(&self, ev: Enter, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.enter(&ev);
}
Ok(())
}
fn leave(&self, ev: Leave, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.leave(&ev);
}
Ok(())
}
fn motion(&self, ev: Motion, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.motion(&ev);
}
Ok(())
}
fn button(&self, ev: Button, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.button(&ev);
}
Ok(())
}
fn axis(&self, ev: Axis, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.pending_scroll.time_usec.set(ev.time as u64 * 1000);
if ev.axis < 2 {
self.pending_scroll.px[ev.axis as usize].set(Some(ev.value));
}
self.any_scroll_events.set(true);
Ok(())
}
fn frame(&self, _ev: Frame, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if self.any_scroll_events.take() {
let pe = self.pending_scroll.take();
if let Some(owner) = self.owner.get() {
owner.scroll(&pe);
}
}
Ok(())
}
fn axis_source(&self, ev: AxisSource, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.pending_scroll.source.set(Some(ev.axis_source));
self.any_scroll_events.set(true);
Ok(())
}
fn axis_stop(&self, ev: AxisStop, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.pending_scroll.time_usec.set(ev.time as u64 * 1000);
if ev.axis < 2 {
self.pending_scroll.stop[ev.axis as usize].set(true);
}
self.any_scroll_events.set(true);
Ok(())
}
fn axis_discrete(&self, _ev: AxisDiscrete, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.any_scroll_events.set(true);
Ok(())
}
fn axis_value120(&self, ev: AxisValue120, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if ev.axis < 2 {
self.pending_scroll.v120[ev.axis as usize].set(Some(ev.value120));
}
self.any_scroll_events.set(true);
Ok(())
}
fn axis_relative_direction(
&self,
_ev: AxisRelativeDirection,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
Ok(())
}
}
usr_object_base! {
self = UsrWlPointer = WlPointer;
version = self.version;
}
impl UsrObject for UsrWlPointer {
fn destroy(&self) {
self.con.request(Release { self_id: self.id });
}
fn break_loops(&self) {
self.owner.take();
}
}

View file

@ -0,0 +1,74 @@
use {
crate::{
globals::GlobalName,
object::Version,
utils::clonecell::CloneCell,
wire::{WlRegistryId, wl_registry::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrWlRegistry {
pub id: WlRegistryId,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrWlRegistryOwner>>>,
pub version: Version,
}
pub trait UsrWlRegistryOwner {
fn global(self: Rc<Self>, name: GlobalName, interface: &str, version: u32) {
let _ = name;
let _ = interface;
let _ = version;
}
fn global_remove(&self, name: GlobalName) {
let _ = name;
}
}
impl UsrWlRegistry {
pub fn bind(&self, name: GlobalName, obj: &dyn UsrObject) {
self.con.request(Bind {
self_id: self.id,
name: name.raw(),
interface: obj.interface().name(),
version: obj.version().0,
id: obj.id(),
});
}
}
impl WlRegistryEventHandler for UsrWlRegistry {
type Error = Infallible;
fn global(&self, ev: Global<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.global(GlobalName::from_raw(ev.name), ev.interface, ev.version);
}
Ok(())
}
fn global_remove(&self, ev: GlobalRemove, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.global_remove(GlobalName::from_raw(ev.name));
}
Ok(())
}
}
usr_object_base! {
self = UsrWlRegistry = WlRegistry;
version = self.version;
}
impl UsrObject for UsrWlRegistry {
fn destroy(&self) {
// nothing
}
fn break_loops(&self) {
self.owner.set(None);
}
}

View file

@ -0,0 +1,78 @@
use {
crate::{
object::Version,
utils::clonecell::CloneCell,
wire::{WlSeatId, wl_seat::*},
wl_usr::{UsrCon, usr_ifs::usr_wl_pointer::UsrWlPointer, usr_object::UsrObject},
},
std::{cell::Cell, convert::Infallible, rc::Rc},
};
pub struct UsrWlSeat {
pub id: WlSeatId,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrWlSeatOwner>>>,
pub version: Version,
}
pub trait UsrWlSeatOwner {
fn capabilities(self: Rc<Self>, value: u32) {
let _ = value;
}
fn name(&self, name: &str) {
let _ = name;
}
}
impl UsrWlSeat {
pub fn get_pointer(&self) -> Rc<UsrWlPointer> {
let ptr = Rc::new(UsrWlPointer {
id: self.con.id(),
con: self.con.clone(),
owner: Default::default(),
any_scroll_events: Cell::new(false),
pending_scroll: Default::default(),
version: self.version,
});
self.con.add_object(ptr.clone());
self.con.request(GetPointer {
self_id: self.id,
id: ptr.id,
});
ptr
}
}
impl WlSeatEventHandler for UsrWlSeat {
type Error = Infallible;
fn capabilities(&self, ev: Capabilities, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.capabilities(ev.capabilities);
}
Ok(())
}
fn name(&self, ev: Name<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.name(ev.name);
}
Ok(())
}
}
usr_object_base! {
self = UsrWlSeat = WlSeat;
version = self.version;
}
impl UsrObject for UsrWlSeat {
fn destroy(&self) {
self.con.request(Release { self_id: self.id });
}
fn break_loops(&self) {
self.owner.take();
}
}

View file

@ -0,0 +1,60 @@
use {
crate::{
format::{formats, map_wayland_format_id},
object::Version,
utils::copyhashmap::CopyHashMap,
wire::{WlShmId, wl_shm::*},
wl_usr::{UsrCon, usr_ifs::usr_wl_shm_pool::UsrWlShmPool, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
uapi::OwnedFd,
};
pub struct UsrWlShm {
pub id: WlShmId,
pub con: Rc<UsrCon>,
pub formats: CopyHashMap<u32, &'static crate::format::Format>,
pub version: Version,
}
impl UsrWlShm {
#[expect(dead_code)]
pub fn create_pool(&self, fd: &Rc<OwnedFd>, size: i32) -> Rc<UsrWlShmPool> {
let pool = Rc::new(UsrWlShmPool {
id: self.con.id(),
con: self.con.clone(),
version: self.version,
});
self.con.request(CreatePool {
self_id: self.id,
id: pool.id,
fd: fd.clone(),
size,
});
self.con.add_object(pool.clone());
pool
}
}
impl WlShmEventHandler for UsrWlShm {
type Error = Infallible;
fn format(&self, ev: Format, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let format = map_wayland_format_id(ev.format);
if let Some(format) = formats().get(&format) {
self.formats.set(format.drm, *format);
}
Ok(())
}
}
usr_object_base! {
self = UsrWlShm = WlShm;
version = self.version;
}
impl UsrObject for UsrWlShm {
fn destroy(&self) {
// nothing
}
}

View file

@ -0,0 +1,39 @@
use {
crate::{
object::Version,
wire::{WlShmPoolId, wl_shm_pool::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrWlShmPool {
pub id: WlShmPoolId,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl UsrWlShmPool {
#[expect(dead_code)]
pub fn resize(&self, size: i32) {
self.con.request(Resize {
self_id: self.id,
size,
});
}
}
impl WlShmPoolEventHandler for UsrWlShmPool {
type Error = Infallible;
}
usr_object_base! {
self = UsrWlShmPool = WlShmPool;
version = self.version;
}
impl UsrObject for UsrWlShmPool {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
}

View file

@ -0,0 +1,92 @@
use {
crate::{
object::Version,
wire::{WlSurfaceId, wl_surface::*},
wl_usr::{
UsrCon,
usr_ifs::{usr_wl_buffer::UsrWlBuffer, usr_wl_callback::UsrWlCallback},
usr_object::UsrObject,
},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrWlSurface {
pub id: WlSurfaceId,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl UsrWlSurface {
pub fn attach(&self, buffer: &UsrWlBuffer) {
self.con.request(Attach {
self_id: self.id,
buffer: buffer.id,
x: 0,
y: 0,
});
}
pub fn damage(&self) {
self.con.request(DamageBuffer {
self_id: self.id,
x: 0,
y: 0,
width: i32::MAX,
height: i32::MAX,
});
}
pub fn frame(&self) -> Rc<UsrWlCallback> {
let cb = Rc::new(UsrWlCallback::new(&self.con));
self.con.request(Frame {
self_id: self.id,
callback: cb.id,
});
self.con.add_object(cb.clone());
cb
}
pub fn commit(&self) {
self.con.request(Commit { self_id: self.id });
}
}
impl WlSurfaceEventHandler for UsrWlSurface {
type Error = Infallible;
fn enter(&self, _ev: Enter, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
fn leave(&self, _ev: Leave, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
fn preferred_buffer_scale(
&self,
_ev: PreferredBufferScale,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
Ok(())
}
fn preferred_buffer_transform(
&self,
_ev: PreferredBufferTransform,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
Ok(())
}
}
usr_object_base! {
self = UsrWlSurface = WlSurface;
version = self.version;
}
impl UsrObject for UsrWlSurface {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
}

View file

@ -0,0 +1,62 @@
use {
crate::{
object::Version,
wire::{ZwlrLayerShellV1Id, zwlr_layer_shell_v1::*},
wl_usr::{
UsrCon,
usr_ifs::{
usr_wl_output::UsrWlOutput, usr_wl_surface::UsrWlSurface,
usr_wlr_layer_surface::UsrWlrLayerSurface,
},
usr_object::UsrObject,
},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrWlrLayerShell {
pub id: ZwlrLayerShellV1Id,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl UsrWlrLayerShell {
pub fn get_layer_surface(
&self,
surface: &UsrWlSurface,
output: &UsrWlOutput,
layer: u32,
) -> Rc<UsrWlrLayerSurface> {
let sfc = Rc::new(UsrWlrLayerSurface {
id: self.con.id(),
con: self.con.clone(),
owner: Default::default(),
version: self.version,
});
self.con.add_object(sfc.clone());
self.con.request(GetLayerSurface {
self_id: self.id,
id: sfc.id,
surface: surface.id,
output: output.id,
layer,
namespace: "",
});
sfc
}
}
impl ZwlrLayerShellV1EventHandler for UsrWlrLayerShell {
type Error = Infallible;
}
usr_object_base! {
self = UsrWlrLayerShell = ZwlrLayerShellV1;
version = self.version;
}
impl UsrObject for UsrWlrLayerShell {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id })
}
}

View file

@ -0,0 +1,87 @@
use {
crate::{
object::Version,
utils::clonecell::CloneCell,
wire::{ZwlrLayerSurfaceV1Id, zwlr_layer_surface_v1::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrWlrLayerSurface {
pub id: ZwlrLayerSurfaceV1Id,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrWlrLayerSurfaceOwner>>>,
pub version: Version,
}
pub trait UsrWlrLayerSurfaceOwner {
fn configure(&self, ev: &Configure) {
let _ = ev;
}
fn closed(&self) {}
}
impl UsrWlrLayerSurface {
pub fn set_size(&self, width: i32, height: i32) {
self.con.request(SetSize {
self_id: self.id,
width: width as _,
height: height as _,
});
}
#[expect(dead_code)]
pub fn set_keyboard_interactivity(&self, ki: u32) {
self.con.request(SetKeyboardInteractivity {
self_id: self.id,
keyboard_interactivity: ki,
});
}
#[expect(dead_code)]
pub fn set_layer(&self, layer: u32) {
self.con.request(SetLayer {
self_id: self.id,
layer,
});
}
}
impl ZwlrLayerSurfaceV1EventHandler for UsrWlrLayerSurface {
type Error = Infallible;
fn configure(&self, ev: Configure, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.configure(&ev);
}
self.con.request(AckConfigure {
self_id: self.id,
serial: ev.serial,
});
Ok(())
}
fn closed(&self, _ev: Closed, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.closed();
}
Ok(())
}
}
usr_object_base! {
self = UsrWlrLayerSurface = ZwlrLayerSurfaceV1;
version = self.version;
}
impl UsrObject for UsrWlrLayerSurface {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
fn break_loops(&self) {
self.owner.take();
}
}

View file

@ -0,0 +1,29 @@
use {
crate::{
object::Version,
wire::{WpCursorShapeDeviceV1Id, wp_cursor_shape_device_v1::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrWpCursorShapeDeviceV1 {
pub id: WpCursorShapeDeviceV1Id,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl WpCursorShapeDeviceV1EventHandler for UsrWpCursorShapeDeviceV1 {
type Error = Infallible;
}
usr_object_base! {
self = UsrWpCursorShapeDeviceV1 = WpCursorShapeDeviceV1;
version = self.version;
}
impl UsrObject for UsrWpCursorShapeDeviceV1 {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id })
}
}

View file

@ -0,0 +1,29 @@
use {
crate::{
object::Version,
wire::{WpCursorShapeManagerV1Id, wp_cursor_shape_manager_v1::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrWpCursorShapeManagerV1 {
pub id: WpCursorShapeManagerV1Id,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl WpCursorShapeManagerV1EventHandler for UsrWpCursorShapeManagerV1 {
type Error = Infallible;
}
usr_object_base! {
self = UsrWpCursorShapeManagerV1 = WpCursorShapeManagerV1;
version = self.version;
}
impl UsrObject for UsrWpCursorShapeManagerV1 {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id })
}
}

View file

@ -0,0 +1,48 @@
use {
crate::{
object::Version,
utils::clonecell::CloneCell,
wire::{WpFractionalScaleV1Id, wp_fractional_scale_v1::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrWpFractionalScale {
pub id: WpFractionalScaleV1Id,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrWpFractionalScaleOwner>>>,
pub version: Version,
}
pub trait UsrWpFractionalScaleOwner {
fn preferred_scale(self: Rc<Self>, ev: &PreferredScale) {
let _ = ev;
}
}
impl WpFractionalScaleV1EventHandler for UsrWpFractionalScale {
type Error = Infallible;
fn preferred_scale(&self, ev: PreferredScale, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.preferred_scale(&ev);
}
Ok(())
}
}
usr_object_base! {
self = UsrWpFractionalScale = WpFractionalScaleV1;
version = self.version;
}
impl UsrObject for UsrWpFractionalScale {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
fn break_loops(&self) {
self.owner.take();
}
}

View file

@ -0,0 +1,53 @@
use {
crate::{
object::Version,
wire::{WpFractionalScaleManagerV1Id, wp_fractional_scale_manager_v1::*},
wl_usr::{
UsrCon,
usr_ifs::{
usr_wl_surface::UsrWlSurface, usr_wp_fractional_scale::UsrWpFractionalScale,
},
usr_object::UsrObject,
},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrWpFractionalScaleManager {
pub id: WpFractionalScaleManagerV1Id,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl UsrWpFractionalScaleManager {
pub fn get_fractional_scale(&self, surface: &UsrWlSurface) -> Rc<UsrWpFractionalScale> {
let fs = Rc::new(UsrWpFractionalScale {
id: self.con.id(),
con: self.con.clone(),
owner: Default::default(),
version: self.version,
});
self.con.add_object(fs.clone());
self.con.request(GetFractionalScale {
self_id: self.id,
id: fs.id,
surface: surface.id,
});
fs
}
}
impl WpFractionalScaleManagerV1EventHandler for UsrWpFractionalScaleManager {
type Error = Infallible;
}
usr_object_base! {
self = UsrWpFractionalScaleManager = WpFractionalScaleManagerV1;
version = self.version;
}
impl UsrObject for UsrWpFractionalScaleManager {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id })
}
}

View file

@ -0,0 +1,51 @@
use {
crate::{
fixed::Fixed,
object::Version,
wire::{WpViewportId, wp_viewport::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrWpViewport {
pub id: WpViewportId,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl UsrWpViewport {
#[expect(dead_code)]
pub fn set_source(&self, x: Fixed, y: Fixed, width: Fixed, height: Fixed) {
self.con.request(SetSource {
self_id: self.id,
x,
y,
width,
height,
});
}
pub fn set_destination(&self, width: i32, height: i32) {
self.con.request(SetDestination {
self_id: self.id,
width,
height,
});
}
}
impl WpViewportEventHandler for UsrWpViewport {
type Error = Infallible;
}
usr_object_base! {
self = UsrWpViewport = WpViewport;
version = self.version;
}
impl UsrObject for UsrWpViewport {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
}

View file

@ -0,0 +1,50 @@
use {
crate::{
object::Version,
wire::{WpViewporterId, wp_viewporter::*},
wl_usr::{
UsrCon,
usr_ifs::{usr_wl_surface::UsrWlSurface, usr_wp_viewport::UsrWpViewport},
usr_object::UsrObject,
},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrWpViewporter {
pub id: WpViewporterId,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl UsrWpViewporter {
pub fn get_viewport(&self, surface: &UsrWlSurface) -> Rc<UsrWpViewport> {
let wv = Rc::new(UsrWpViewport {
id: self.con.id(),
con: self.con.clone(),
version: self.version,
});
self.con.add_object(wv.clone());
self.con.request(GetViewport {
self_id: self.id,
id: wv.id,
surface: surface.id,
});
wv
}
}
impl WpViewporterEventHandler for UsrWpViewporter {
type Error = Infallible;
}
usr_object_base! {
self = UsrWpViewporter = WpViewporter;
version = self.version;
}
impl UsrObject for UsrWpViewporter {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
}

View file

@ -0,0 +1,52 @@
use {
crate::{
object::Version,
utils::clonecell::CloneCell,
wire::{XdgSurfaceId, xdg_surface::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrXdgSurface {
pub id: XdgSurfaceId,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrXdgSurfaceOwner>>>,
pub version: Version,
}
pub trait UsrXdgSurfaceOwner {
fn configure(&self) {
// nothing
}
}
impl XdgSurfaceEventHandler for UsrXdgSurface {
type Error = Infallible;
fn configure(&self, ev: Configure, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.con.request(AckConfigure {
self_id: self.id,
serial: ev.serial,
});
if let Some(owner) = self.owner.get() {
owner.configure();
}
Ok(())
}
}
usr_object_base! {
self = UsrXdgSurface = XdgSurface;
version = self.version;
}
impl UsrObject for UsrXdgSurface {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id })
}
fn break_loops(&self) {
self.owner.take();
}
}

View file

@ -0,0 +1,68 @@
use {
crate::{
object::Version,
utils::clonecell::CloneCell,
wire::{XdgToplevelId, xdg_toplevel::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrXdgToplevel {
pub id: XdgToplevelId,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrXdgToplevelOwner>>>,
pub version: Version,
}
pub trait UsrXdgToplevelOwner {
fn configure(&self, width: i32, height: i32) {
let _ = width;
let _ = height;
}
fn close(&self) {
// nothing
}
}
impl XdgToplevelEventHandler for UsrXdgToplevel {
type Error = Infallible;
fn configure(&self, ev: Configure<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.configure(ev.width, ev.height);
}
Ok(())
}
fn close(&self, _ev: Close, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.close();
}
Ok(())
}
fn configure_bounds(&self, _ev: ConfigureBounds, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
fn wm_capabilities(&self, _ev: WmCapabilities<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
}
usr_object_base! {
self = UsrXdgToplevel = XdgToplevel;
version = self.version;
}
impl UsrObject for UsrXdgToplevel {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id })
}
fn break_loops(&self) {
self.owner.take();
}
}

View file

@ -0,0 +1,37 @@
use {
crate::{
object::Version,
wire::{XdgWmBaseId, xdg_wm_base::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrXdgWmBase {
pub id: XdgWmBaseId,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl XdgWmBaseEventHandler for UsrXdgWmBase {
type Error = Infallible;
fn ping(&self, ev: Ping, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.con.request(Pong {
self_id: self.id,
serial: ev.serial,
});
Ok(())
}
}
usr_object_base! {
self = UsrXdgWmBase = XdgWmBase;
version = self.version;
}
impl UsrObject for UsrXdgWmBase {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id })
}
}

View file

@ -0,0 +1,128 @@
use {
crate::{
object::Version,
utils::clonecell::CloneCell,
wire::{ZwlrScreencopyFrameV1Id, zwlr_screencopy_frame_v1::*},
wl_usr::{UsrCon, usr_ifs::usr_wl_buffer::UsrWlBuffer, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrZwlrScreencopyFrame {
pub id: ZwlrScreencopyFrameV1Id,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrZwlrScreencopyFrameOwner>>>,
pub version: Version,
}
pub trait UsrZwlrScreencopyFrameOwner {
fn buffer(&self, buffer: &Buffer) {
let _ = buffer;
}
fn flags(&self, flags: &Flags) {
let _ = flags;
}
fn ready(&self, ready: &Ready) {
let _ = ready;
}
fn failed(&self) {}
fn damage(&self, damage: &Damage) {
let _ = damage;
}
fn linux_dmabuf(&self, dmabuf: &LinuxDmabuf) {
let _ = dmabuf;
}
fn buffer_done(&self) {}
}
impl UsrZwlrScreencopyFrame {
#[expect(dead_code)]
pub fn copy(&self, buffer: &UsrWlBuffer) {
self.con.request(Copy {
self_id: self.id,
buffer: buffer.id,
});
}
#[expect(dead_code)]
pub fn copy_with_damage(&self, buffer: &UsrWlBuffer) {
self.con.request(CopyWithDamage {
self_id: self.id,
buffer: buffer.id,
});
}
}
impl ZwlrScreencopyFrameV1EventHandler for UsrZwlrScreencopyFrame {
type Error = Infallible;
fn buffer(&self, ev: Buffer, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.buffer(&ev);
}
Ok(())
}
fn flags(&self, ev: Flags, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.flags(&ev);
}
Ok(())
}
fn ready(&self, ev: Ready, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.ready(&ev);
}
Ok(())
}
fn failed(&self, _ev: Failed, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.failed();
}
Ok(())
}
fn damage(&self, ev: Damage, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.damage(&ev);
}
Ok(())
}
fn linux_dmabuf(&self, ev: LinuxDmabuf, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.linux_dmabuf(&ev);
}
Ok(())
}
fn buffer_done(&self, _ev: BufferDone, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.buffer_done();
}
Ok(())
}
}
usr_object_base! {
self = UsrZwlrScreencopyFrame = ZwlrScreencopyFrameV1;
version = self.version;
}
impl UsrObject for UsrZwlrScreencopyFrame {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
fn break_loops(&self) {
self.owner.take();
}
}

View file

@ -0,0 +1,55 @@
use {
crate::{
object::Version,
wire::{ZwlrScreencopyManagerV1Id, zwlr_screencopy_manager_v1::*},
wl_usr::{
UsrCon,
usr_ifs::{
usr_wl_output::UsrWlOutput, usr_zwlr_screencopy_frame::UsrZwlrScreencopyFrame,
},
usr_object::UsrObject,
},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrZwlrScreencopyManager {
pub id: ZwlrScreencopyManagerV1Id,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl UsrZwlrScreencopyManager {
#[expect(dead_code)]
pub fn capture_output(&self, output: &UsrWlOutput) -> Rc<UsrZwlrScreencopyFrame> {
let frame = Rc::new(UsrZwlrScreencopyFrame {
id: self.con.id(),
con: self.con.clone(),
owner: Default::default(),
version: self.version,
});
self.con.request(CaptureOutput {
self_id: self.id,
frame: frame.id,
overlay_cursor: 0,
output: output.id,
});
self.con.add_object(frame.clone());
frame
}
}
impl ZwlrScreencopyManagerV1EventHandler for UsrZwlrScreencopyManager {
type Error = Infallible;
}
usr_object_base! {
self = UsrZwlrScreencopyManager = ZwlrScreencopyManagerV1;
version = self.version;
}
impl UsrObject for UsrZwlrScreencopyManager {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
}

View file

@ -0,0 +1,37 @@
use {
crate::{
object::Version,
wire::{ZwpLinuxBufferParamsV1Id, zwp_linux_buffer_params_v1::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrZwpLinuxBufferParamsV1 {
pub id: ZwpLinuxBufferParamsV1Id,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl ZwpLinuxBufferParamsV1EventHandler for UsrZwpLinuxBufferParamsV1 {
type Error = Infallible;
fn created(&self, _ev: Created, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
fn failed(&self, _ev: Failed, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
}
usr_object_base! {
self = UsrZwpLinuxBufferParamsV1 = ZwpLinuxBufferParamsV1;
version = self.version;
}
impl UsrObject for UsrZwpLinuxBufferParamsV1 {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id })
}
}

View file

@ -0,0 +1,37 @@
use {
crate::{
object::Version,
wire::{ZwpLinuxDmabufV1Id, zwp_linux_dmabuf_v1::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrZwpLinuxDmabufV1 {
pub id: ZwpLinuxDmabufV1Id,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl ZwpLinuxDmabufV1EventHandler for UsrZwpLinuxDmabufV1 {
type Error = Infallible;
fn format(&self, _ev: Format, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
fn modifier(&self, _ev: Modifier, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
}
usr_object_base! {
self = UsrZwpLinuxDmabufV1 = ZwpLinuxDmabufV1;
version = self.version;
}
impl UsrObject for UsrZwpLinuxDmabufV1 {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id })
}
}

View file

@ -0,0 +1,31 @@
use {
crate::{
object::Version,
wire::{ZwpPrimarySelectionDeviceManagerV1Id, zwp_primary_selection_device_manager_v1::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrZwpPrimarySelectionDeviceManagerV1 {
pub id: ZwpPrimarySelectionDeviceManagerV1Id,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl UsrZwpPrimarySelectionDeviceManagerV1 {}
impl ZwpPrimarySelectionDeviceManagerV1EventHandler for UsrZwpPrimarySelectionDeviceManagerV1 {
type Error = Infallible;
}
usr_object_base! {
self = UsrZwpPrimarySelectionDeviceManagerV1 = ZwpPrimarySelectionDeviceManagerV1;
version = self.version;
}
impl UsrObject for UsrZwpPrimarySelectionDeviceManagerV1 {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id })
}
}

26
src/wl_usr/usr_object.rs Normal file
View file

@ -0,0 +1,26 @@
use {
crate::{
object::{Interface, ObjectId, Version},
utils::buffd::MsgParser,
wl_usr::{UsrCon, UsrConError},
},
std::rc::Rc,
};
pub trait UsrObjectBase {
fn id(&self) -> ObjectId;
fn handle_event(
self: Rc<Self>,
con: &UsrCon,
event: u32,
parser: MsgParser<'_, '_>,
) -> Result<(), UsrConError>;
fn interface(&self) -> Interface;
fn version(&self) -> Version;
}
pub trait UsrObject: UsrObjectBase + 'static {
fn destroy(&self);
fn break_loops(&self) {}
}

View file

@ -0,0 +1,16 @@
fn Get(interface_name: string, property_name: string) {
value: variant,
}
# fn Set(interface_name: string, property_name: string, value: variant) {
# }
fn GetAll(interface_name: string) {
props: array(dict(string, variant)),
}
sig PropertiesChanged {
interface_name: string,
changed_properties: array(dict(string, variant)),
invalidated_properties: array(string),
}

Some files were not shown because too many files have changed in this diff Show more