all: bring back xdg portals
This commit is contained in:
parent
d920e554cf
commit
e61f042d8b
107 changed files with 14293 additions and 55 deletions
|
|
@ -274,15 +274,47 @@ fn write_message<W: Write>(f: &mut W, obj: &str, message: &Message) -> Result<()
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
enum RequestHandlerDirection {
|
||||||
|
Request,
|
||||||
|
Event,
|
||||||
|
}
|
||||||
|
|
||||||
fn write_request_handler<W: Write>(
|
fn write_request_handler<W: Write>(
|
||||||
f: &mut W,
|
f: &mut W,
|
||||||
camel_obj_name: &str,
|
camel_obj_name: &str,
|
||||||
messages: &[Lined<Message>],
|
messages: &[Lined<Message>],
|
||||||
|
direction: RequestHandlerDirection,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
let snake_direction;
|
||||||
|
let camel_direction;
|
||||||
|
let parent;
|
||||||
|
let parser;
|
||||||
|
let error;
|
||||||
|
let param;
|
||||||
writeln!(f)?;
|
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!(
|
writeln!(
|
||||||
f,
|
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;")?;
|
writeln!(f, " type Error: std::error::Error;")?;
|
||||||
for message in messages {
|
for message in messages {
|
||||||
|
|
@ -294,24 +326,24 @@ fn write_request_handler<W: Write>(
|
||||||
writeln!(f)?;
|
writeln!(f)?;
|
||||||
writeln!(
|
writeln!(
|
||||||
f,
|
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
|
msg.safe_name, msg.camel_name
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
writeln!(f)?;
|
writeln!(f)?;
|
||||||
writeln!(f, " #[inline(always)]")?;
|
writeln!(f, " #[inline(always)]")?;
|
||||||
writeln!(f, " fn handle_request_impl(")?;
|
writeln!(f, " fn handle_{snake_direction}_impl(")?;
|
||||||
writeln!(f, " self: Rc<Self>,")?;
|
writeln!(f, " self: Rc<Self>,")?;
|
||||||
writeln!(f, " client: &crate::client::Client,")?;
|
writeln!(f, " client: &{parser},")?;
|
||||||
writeln!(f, " req: u32,")?;
|
writeln!(f, " req: u32,")?;
|
||||||
writeln!(
|
writeln!(
|
||||||
f,
|
f,
|
||||||
" parser: crate::utils::buffd::MsgParser<'_, '_>,"
|
" parser: crate::utils::buffd::MsgParser<'_, '_>,"
|
||||||
)?;
|
)?;
|
||||||
writeln!(f, " ) -> Result<(), crate::client::ClientError> {{")?;
|
writeln!(f, " ) -> Result<(), {error}> {{")?;
|
||||||
if messages.is_empty() {
|
if messages.is_empty() {
|
||||||
writeln!(f, " #![allow(unused_variables)]")?;
|
writeln!(f, " #![allow(unused_variables)]")?;
|
||||||
writeln!(f, " Err(crate::client::ClientError::InvalidMethod)")?;
|
writeln!(f, " Err({error}::InvalidMethod)")?;
|
||||||
} else {
|
} else {
|
||||||
writeln!(f, " let method;")?;
|
writeln!(f, " let method;")?;
|
||||||
writeln!(
|
writeln!(
|
||||||
|
|
@ -347,10 +379,10 @@ fn write_request_handler<W: Write>(
|
||||||
}
|
}
|
||||||
writeln!(
|
writeln!(
|
||||||
f,
|
f,
|
||||||
" _ => return Err(crate::client::ClientError::InvalidMethod),"
|
" _ => return Err({error}::InvalidMethod),"
|
||||||
)?;
|
)?;
|
||||||
writeln!(f, " }};")?;
|
writeln!(f, " }};")?;
|
||||||
writeln!(f, " Err(crate::client::ClientError::MethodError {{")?;
|
writeln!(f, " Err({error}::MethodError {{")?;
|
||||||
writeln!(f, " interface: {camel_obj_name},")?;
|
writeln!(f, " interface: {camel_obj_name},")?;
|
||||||
writeln!(f, " id: self.id(),")?;
|
writeln!(f, " id: self.id(),")?;
|
||||||
writeln!(f, " method,")?;
|
writeln!(f, " method,")?;
|
||||||
|
|
@ -394,6 +426,13 @@ fn write_file<W: Write>(
|
||||||
f,
|
f,
|
||||||
&camel_obj_name,
|
&camel_obj_name,
|
||||||
&messages.requests,
|
&messages.requests,
|
||||||
|
RequestHandlerDirection::Request,
|
||||||
|
)?;
|
||||||
|
write_request_handler(
|
||||||
|
f,
|
||||||
|
&camel_obj_name,
|
||||||
|
&messages.events,
|
||||||
|
RequestHandlerDirection::Event,
|
||||||
)?;
|
)?;
|
||||||
writeln!(f, "}}")?;
|
writeln!(f, "}}")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
6
etc/jay-portals.conf
Normal file
6
etc/jay-portals.conf
Normal 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
3
etc/jay.portal
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[portal]
|
||||||
|
DBusName=org.freedesktop.impl.portal.desktop.jay
|
||||||
|
Interfaces=org.freedesktop.impl.portal.ScreenCast;org.freedesktop.impl.portal.RemoteDesktop;
|
||||||
|
|
@ -32,6 +32,7 @@ use {
|
||||||
},
|
},
|
||||||
compositor::start_compositor,
|
compositor::start_compositor,
|
||||||
logger::LogLevel,
|
logger::LogLevel,
|
||||||
|
portal,
|
||||||
pr_caps::drop_all_pr_caps,
|
pr_caps::drop_all_pr_caps,
|
||||||
},
|
},
|
||||||
clap::{Args, Parser, Subcommand, ValueEnum, ValueHint},
|
clap::{Args, Parser, Subcommand, ValueEnum, ValueHint},
|
||||||
|
|
@ -87,6 +88,8 @@ pub enum Cmd {
|
||||||
Dpms(DpmsArgs),
|
Dpms(DpmsArgs),
|
||||||
/// Tests the events produced by a seat.
|
/// Tests the events produced by a seat.
|
||||||
SeatTest(SeatTestArgs),
|
SeatTest(SeatTestArgs),
|
||||||
|
/// Run the desktop portal.
|
||||||
|
Portal,
|
||||||
/// Inspect/modify graphics card and connector settings.
|
/// Inspect/modify graphics card and connector settings.
|
||||||
Randr(RandrArgs),
|
Randr(RandrArgs),
|
||||||
/// Inspect/modify input settings.
|
/// Inspect/modify input settings.
|
||||||
|
|
@ -243,6 +246,7 @@ pub fn main() {
|
||||||
Cmd::Dpms(a) => dpms::main(cli.global, a),
|
Cmd::Dpms(a) => dpms::main(cli.global, a),
|
||||||
Cmd::Unlock => unlock::main(cli.global),
|
Cmd::Unlock => unlock::main(cli.global),
|
||||||
Cmd::SeatTest(a) => seat_test::main(cli.global, a),
|
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::Randr(a) => randr::main(cli.global, a),
|
||||||
Cmd::Input(a) => input::main(cli.global, a),
|
Cmd::Input(a) => input::main(cli.global, a),
|
||||||
Cmd::DamageTracking(a) => damage_tracking::main(cli.global, a),
|
Cmd::DamageTracking(a) => damage_tracking::main(cli.global, a),
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,8 @@ use {
|
||||||
};
|
};
|
||||||
pub use {
|
pub use {
|
||||||
error::{ClientError, ParserError},
|
error::{ClientError, ParserError},
|
||||||
|
objects::MIN_SERVER_ID,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "it")]
|
|
||||||
pub use objects::MIN_SERVER_ID;
|
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
mod objects;
|
mod objects;
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,6 @@ use {
|
||||||
wp_image_description_reference_v1::WpImageDescriptionReferenceV1,
|
wp_image_description_reference_v1::WpImageDescriptionReferenceV1,
|
||||||
wp_image_description_v1::WpImageDescriptionV1,
|
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_transfer::{
|
||||||
data_control::{
|
data_control::{
|
||||||
ext_data_control_source_v1::ExtDataControlSourceV1,
|
ext_data_control_source_v1::ExtDataControlSourceV1,
|
||||||
|
|
@ -18,7 +14,12 @@ use {
|
||||||
wl_data_source::WlDataSource,
|
wl_data_source::WlDataSource,
|
||||||
zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1,
|
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_output::JayOutput,
|
||||||
|
jay_screencast::JayScreencast,
|
||||||
jay_toplevel::JayToplevel,
|
jay_toplevel::JayToplevel,
|
||||||
jay_workspace::JayWorkspace,
|
jay_workspace::JayWorkspace,
|
||||||
wl_buffer::WlBuffer,
|
wl_buffer::WlBuffer,
|
||||||
|
|
@ -48,9 +49,9 @@ use {
|
||||||
wire::{
|
wire::{
|
||||||
ExtDataControlSourceV1Id, ExtForeignToplevelHandleV1Id, ExtImageCaptureSourceV1Id,
|
ExtDataControlSourceV1Id, ExtForeignToplevelHandleV1Id, ExtImageCaptureSourceV1Id,
|
||||||
ExtImageCopyCaptureSessionV1Id, ExtWorkspaceGroupHandleV1Id, JayHeadErrorV1Id,
|
ExtImageCopyCaptureSessionV1Id, ExtWorkspaceGroupHandleV1Id, JayHeadErrorV1Id,
|
||||||
JayOutputId, JayToplevelId, JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId,
|
JayOutputId, JayScreencastId, JayToplevelId, JayWorkspaceId, WlBufferId,
|
||||||
WlPointerId, WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId,
|
WlDataSourceId, WlOutputId, WlPointerId, WlRegionId, WlRegistryId, WlSeatId,
|
||||||
WpDrmLeaseConnectorV1Id, WpImageDescriptionReferenceV1Id,
|
WlSurfaceId, WpDrmLeaseConnectorV1Id, WpImageDescriptionReferenceV1Id,
|
||||||
WpImageDescriptionV1Id, WpLinuxDrmSyncobjTimelineV1Id, XdgPopupId, XdgPositionerId,
|
WpImageDescriptionV1Id, WpLinuxDrmSyncobjTimelineV1Id, XdgPopupId, XdgPositionerId,
|
||||||
XdgSurfaceId, XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id,
|
XdgSurfaceId, XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id,
|
||||||
ZwlrOutputHeadV1Id, ZwlrOutputModeV1Id, ZwpPrimarySelectionSourceV1Id,
|
ZwlrOutputHeadV1Id, ZwlrOutputModeV1Id, ZwpPrimarySelectionSourceV1Id,
|
||||||
|
|
@ -79,6 +80,7 @@ pub struct Objects {
|
||||||
pub pointers: CopyHashMap<WlPointerId, Rc<WlPointer>>,
|
pub pointers: CopyHashMap<WlPointerId, Rc<WlPointer>>,
|
||||||
pub xdg_wm_bases: CopyHashMap<XdgWmBaseId, Rc<XdgWmBase>>,
|
pub xdg_wm_bases: CopyHashMap<XdgWmBaseId, Rc<XdgWmBase>>,
|
||||||
pub seats: CopyHashMap<WlSeatId, Rc<WlSeat>>,
|
pub seats: CopyHashMap<WlSeatId, Rc<WlSeat>>,
|
||||||
|
pub screencasts: CopyHashMap<JayScreencastId, Rc<JayScreencast>>,
|
||||||
pub timelines: CopyHashMap<WpLinuxDrmSyncobjTimelineV1Id, Rc<WpLinuxDrmSyncobjTimelineV1>>,
|
pub timelines: CopyHashMap<WpLinuxDrmSyncobjTimelineV1Id, Rc<WpLinuxDrmSyncobjTimelineV1>>,
|
||||||
pub zwlr_data_sources: CopyHashMap<ZwlrDataControlSourceV1Id, Rc<ZwlrDataControlSourceV1>>,
|
pub zwlr_data_sources: CopyHashMap<ZwlrDataControlSourceV1Id, Rc<ZwlrDataControlSourceV1>>,
|
||||||
pub zwlr_output_heads: CopyHashMap<ZwlrOutputHeadV1Id, Rc<ZwlrOutputHeadV1>>,
|
pub zwlr_output_heads: CopyHashMap<ZwlrOutputHeadV1Id, Rc<ZwlrOutputHeadV1>>,
|
||||||
|
|
@ -125,6 +127,7 @@ impl Objects {
|
||||||
pointers: Default::default(),
|
pointers: Default::default(),
|
||||||
xdg_wm_bases: Default::default(),
|
xdg_wm_bases: Default::default(),
|
||||||
seats: Default::default(),
|
seats: Default::default(),
|
||||||
|
screencasts: Default::default(),
|
||||||
timelines: Default::default(),
|
timelines: Default::default(),
|
||||||
zwlr_data_sources: Default::default(),
|
zwlr_data_sources: Default::default(),
|
||||||
zwlr_output_heads: Default::default(),
|
zwlr_output_heads: Default::default(),
|
||||||
|
|
@ -173,6 +176,7 @@ impl Objects {
|
||||||
self.xdg_wm_bases.clear();
|
self.xdg_wm_bases.clear();
|
||||||
self.seats.clear();
|
self.seats.clear();
|
||||||
self.pointers.clear();
|
self.pointers.clear();
|
||||||
|
self.screencasts.clear();
|
||||||
self.timelines.clear();
|
self.timelines.clear();
|
||||||
self.zwlr_data_sources.clear();
|
self.zwlr_data_sources.clear();
|
||||||
self.jay_toplevels.clear();
|
self.jay_toplevels.clear();
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ use {
|
||||||
head_management::{
|
head_management::{
|
||||||
HeadManagers, HeadState, jay_head_manager_session_v1::handle_jay_head_manager_done,
|
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_output::{BlendSpace, OutputId, PersistentOutputState, WlOutputGlobal},
|
||||||
wl_seat::{handle_position_hint_requests, handle_warp_mouse_to_focus},
|
wl_seat::{handle_position_hint_requests, handle_warp_mouse_to_focus},
|
||||||
wl_surface::{
|
wl_surface::{
|
||||||
|
|
@ -49,6 +50,7 @@ use {
|
||||||
leaks,
|
leaks,
|
||||||
logger::Logger,
|
logger::Logger,
|
||||||
output_schedule::create_output_schedule,
|
output_schedule::create_output_schedule,
|
||||||
|
portal::{self, PortalStartup},
|
||||||
pr_caps::{PrCapsThread, pr_caps},
|
pr_caps::{PrCapsThread, pr_caps},
|
||||||
scale::Scale,
|
scale::Scale,
|
||||||
sighand::{self, SighandError},
|
sighand::{self, SighandError},
|
||||||
|
|
@ -115,10 +117,19 @@ pub fn start_compositor(global: GlobalArgs, args: RunArgs) {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
let forker = create_forker(reaper_pid);
|
let forker = create_forker(reaper_pid);
|
||||||
|
let portal = portal::run_from_compositor(global.log_level);
|
||||||
enable_profiler();
|
enable_profiler();
|
||||||
let logger = Logger::install_compositor(global.log_level);
|
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(
|
let res = start_compositor2(
|
||||||
Some(forker),
|
Some(forker),
|
||||||
|
portal,
|
||||||
Some(logger.clone()),
|
Some(logger.clone()),
|
||||||
args,
|
args,
|
||||||
None,
|
None,
|
||||||
|
|
@ -137,7 +148,7 @@ pub fn start_compositor(global: GlobalArgs, args: RunArgs) {
|
||||||
|
|
||||||
#[cfg(feature = "it")]
|
#[cfg(feature = "it")]
|
||||||
pub fn start_compositor_for_test(future: TestFuture) -> Result<(), CompositorError> {
|
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();
|
leaks::log_leaked();
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
@ -179,6 +190,7 @@ pub type TestFuture = Box<dyn Fn(&Rc<State>) -> Box<dyn Future<Output = ()>>>;
|
||||||
|
|
||||||
fn start_compositor2(
|
fn start_compositor2(
|
||||||
forker: Option<Rc<ForkerProxy>>,
|
forker: Option<Rc<ForkerProxy>>,
|
||||||
|
portal: Option<PortalStartup>,
|
||||||
logger: Option<Arc<Logger>>,
|
logger: Option<Arc<Logger>>,
|
||||||
run_args: RunArgs,
|
run_args: RunArgs,
|
||||||
test_future: Option<TestFuture>,
|
test_future: Option<TestFuture>,
|
||||||
|
|
@ -246,6 +258,8 @@ fn start_compositor2(
|
||||||
pending_output_render_data: Default::default(),
|
pending_output_render_data: Default::default(),
|
||||||
pending_float_layout: Default::default(),
|
pending_float_layout: Default::default(),
|
||||||
pending_input_popup_positioning: 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_placeholder_render_textures: Default::default(),
|
||||||
pending_container_tab_render_textures: Default::default(),
|
pending_container_tab_render_textures: Default::default(),
|
||||||
dbus: Dbus::new(&engine, &ring, &run_toplevel),
|
dbus: Dbus::new(&engine, &ring, &run_toplevel),
|
||||||
|
|
@ -401,6 +415,13 @@ fn start_compositor2(
|
||||||
forker.setenv(key.as_bytes(), val.as_bytes());
|
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));
|
let _compositor = engine.spawn("compositor", start_compositor3(state.clone(), test_future));
|
||||||
ring.run()?;
|
ring.run()?;
|
||||||
state.clear();
|
state.clear();
|
||||||
|
|
@ -560,6 +581,16 @@ fn start_global_event_handlers(state: &Rc<State>) -> Vec<SpawnedFuture<()>> {
|
||||||
Phase::PostLayout,
|
Phase::PostLayout,
|
||||||
handle_xdg_surface_configure_events(state.clone()),
|
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(
|
eng.spawn(
|
||||||
"lazy event sources",
|
"lazy event sources",
|
||||||
handle_lazy_event_sources(state.clone()),
|
handle_lazy_event_sources(state.clone()),
|
||||||
|
|
@ -749,6 +780,7 @@ fn create_dummy_output(state: &Rc<State>) {
|
||||||
hardware_cursor: Default::default(),
|
hardware_cursor: Default::default(),
|
||||||
update_render_data_scheduled: Cell::new(false),
|
update_render_data_scheduled: Cell::new(false),
|
||||||
hardware_cursor_needs_render: Cell::new(false),
|
hardware_cursor_needs_render: Cell::new(false),
|
||||||
|
screencasts: Default::default(),
|
||||||
screencopies: Default::default(),
|
screencopies: Default::default(),
|
||||||
title_visible: Cell::new(false),
|
title_visible: Cell::new(false),
|
||||||
schedule,
|
schedule,
|
||||||
|
|
|
||||||
340
src/dbus.rs
340
src/dbus.rs
|
|
@ -16,12 +16,13 @@ use {
|
||||||
},
|
},
|
||||||
ahash::AHashMap,
|
ahash::AHashMap,
|
||||||
std::{
|
std::{
|
||||||
borrow::Cow,
|
borrow::{Borrow, Cow},
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
future::Future,
|
future::Future,
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
mem,
|
mem,
|
||||||
|
ops::Deref,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
task::{Context, Poll, Waker},
|
task::{Context, Poll, Waker},
|
||||||
|
|
@ -174,6 +175,24 @@ pub struct DbusSocket {
|
||||||
headers: RefCell<VecStorage<(u8, Variant<'static>)>>,
|
headers: RefCell<VecStorage<(u8, Variant<'static>)>>,
|
||||||
run_toplevel: Rc<RunToplevel>,
|
run_toplevel: Rc<RunToplevel>,
|
||||||
signal_handlers: RefCell<AHashMap<(&'static str, &'static str), InterfaceSignalHandlers>>,
|
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;
|
const HDR_PATH: u8 = 1;
|
||||||
|
|
@ -197,6 +216,20 @@ const NO_AUTO_START: u8 = 0x2;
|
||||||
#[expect(dead_code)]
|
#[expect(dead_code)]
|
||||||
const ALLOW_INTERACTIVE_AUTHORIZATION: u8 = 0x4;
|
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_DEST: &str = "org.freedesktop.DBus";
|
||||||
pub const BUS_PATH: &str = "/org/freedesktop/DBus";
|
pub const BUS_PATH: &str = "/org/freedesktop/DBus";
|
||||||
|
|
||||||
|
|
@ -399,3 +432,308 @@ struct InterfaceSignalHandlers {
|
||||||
unconditional: Option<Rc<dyn SignalHandlerApi>>,
|
unconditional: Option<Rc<dyn SignalHandlerApi>>,
|
||||||
conditional: AHashMap<String, 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ async fn connect(
|
||||||
headers: Default::default(),
|
headers: Default::default(),
|
||||||
run_toplevel: run_toplevel.clone(),
|
run_toplevel: run_toplevel.clone(),
|
||||||
signal_handlers: Default::default(),
|
signal_handlers: Default::default(),
|
||||||
|
objects: Default::default(),
|
||||||
});
|
});
|
||||||
let skt = socket.clone();
|
let skt = socket.clone();
|
||||||
socket.call(
|
socket.call(
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use {
|
||||||
crate::{
|
crate::{
|
||||||
dbus::{
|
dbus::{
|
||||||
CallError, DbusError, DbusSocket, Headers, MSG_ERROR, MSG_METHOD_CALL,
|
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::{
|
utils::{
|
||||||
bufio::BufIoIncoming,
|
bufio::BufIoIncoming,
|
||||||
|
|
@ -61,6 +61,7 @@ impl Incoming {
|
||||||
return Err(DbusError::InvalidEndianess);
|
return Err(DbusError::InvalidEndianess);
|
||||||
}
|
}
|
||||||
let msg_ty = msg_buf[1];
|
let msg_ty = msg_buf[1];
|
||||||
|
let flags = msg_buf[2];
|
||||||
let protocol = msg_buf[3];
|
let protocol = msg_buf[3];
|
||||||
if protocol != 1 {
|
if protocol != 1 {
|
||||||
return Err(DbusError::InvalidProtocol);
|
return Err(DbusError::InvalidProtocol);
|
||||||
|
|
@ -83,17 +84,58 @@ impl Incoming {
|
||||||
return Err(DbusError::TooFewFds);
|
return Err(DbusError::TooFewFds);
|
||||||
}
|
}
|
||||||
let fds: Vec<_> = self.incoming.fds.drain(..unix_fds).collect();
|
let fds: Vec<_> = self.incoming.fds.drain(..unix_fds).collect();
|
||||||
let mut parser =
|
let mut parser = Parser::new_at(msg_buf, FIXED_HEADER_SIZE + dyn_header_len as usize, &fds);
|
||||||
Parser::new_at(msg_buf, FIXED_HEADER_SIZE + dyn_header_len as usize, &fds);
|
|
||||||
match msg_ty {
|
match msg_ty {
|
||||||
MSG_METHOD_CALL => {
|
MSG_METHOD_CALL => {
|
||||||
let sender = match &headers.sender {
|
let (sender, interface, member, path) = match (
|
||||||
Some(s) => s,
|
&headers.sender,
|
||||||
|
&headers.interface,
|
||||||
|
&headers.member,
|
||||||
|
&headers.path,
|
||||||
|
) {
|
||||||
|
(Some(s), Some(i), Some(m), Some(p)) => (s, i, m, p),
|
||||||
_ => return Err(DbusError::MissingMethodCallHeaders),
|
_ => return Err(DbusError::MissingMethodCallHeaders),
|
||||||
};
|
};
|
||||||
|
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
|
self.socket
|
||||||
.send_error(sender.deref(), serial, "Object does not exist");
|
.send_error(sender.deref(), serial, "Object does not exist");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
MSG_METHOD_RETURN | MSG_ERROR => {
|
MSG_METHOD_RETURN | MSG_ERROR => {
|
||||||
let serial = match headers.reply_serial {
|
let serial = match headers.reply_serial {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
dbus::{
|
dbus::{
|
||||||
AsyncProperty, AsyncReply, AsyncReplySlot, BUS_DEST, BUS_PATH, DbusError, DbusSocket,
|
AsyncProperty, AsyncReply, AsyncReplySlot, BUS_DEST, BUS_PATH, DbusError, DbusObject,
|
||||||
DbusType, ErrorMessage, Formatter, HDR_DESTINATION, HDR_ERROR_NAME, HDR_INTERFACE,
|
DbusObjectData, DbusSocket, DbusType, ErrorMessage, Formatter, HDR_DESTINATION,
|
||||||
HDR_MEMBER, HDR_PATH, HDR_REPLY_SERIAL, HDR_SIGNATURE, HDR_UNIX_FDS, Headers,
|
HDR_ERROR_NAME, HDR_INTERFACE, HDR_MEMBER, HDR_PATH, HDR_REPLY_SERIAL, HDR_SIGNATURE,
|
||||||
InterfaceSignalHandlers, MSG_ERROR, MSG_METHOD_CALL, Message,
|
HDR_UNIX_FDS, Headers, InterfaceSignalHandlers, MSG_ERROR, MSG_METHOD_CALL,
|
||||||
MethodCall, NO_REPLY_EXPECTED, Parser, Property, Reply, ReplyHandler, Signal,
|
MSG_METHOD_RETURN, MSG_SIGNAL, Message, MethodCall, NO_REPLY_EXPECTED, Parser,
|
||||||
SignalHandler, SignalHandlerApi, SignalHandlerData,
|
Property, PropertyGetAllHandlerProxy, PropertyGetHandlerProxy, Reply, ReplyHandler,
|
||||||
|
Signal, SignalHandler, SignalHandlerApi, SignalHandlerData,
|
||||||
property::Get,
|
property::Get,
|
||||||
types::{ObjectPath, Signature, Variant},
|
types::{ObjectPath, Signature, Variant},
|
||||||
},
|
},
|
||||||
|
|
@ -14,8 +15,8 @@ use {
|
||||||
wire_dbus::org,
|
wire_dbus::org,
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
cell::Cell, collections::hash_map::Entry, fmt::Write, marker::PhantomData, mem,
|
borrow::Cow, cell::Cell, collections::hash_map::Entry, fmt::Write, marker::PhantomData,
|
||||||
ops::DerefMut, rc::Rc,
|
mem, ops::DerefMut, rc::Rc,
|
||||||
},
|
},
|
||||||
uapi::c,
|
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>(
|
pub fn handle_signal<T, F>(
|
||||||
self: &Rc<Self>,
|
self: &Rc<Self>,
|
||||||
sender: Option<&str>,
|
sender: Option<&str>,
|
||||||
|
|
@ -250,6 +291,23 @@ impl DbusSocket {
|
||||||
serial
|
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>>(
|
fn send_call<'a, T: Message<'a>>(
|
||||||
&self,
|
&self,
|
||||||
path: &str,
|
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>>(
|
fn format_call<'a, T: Message<'a>>(
|
||||||
&self,
|
&self,
|
||||||
path: &str,
|
path: &str,
|
||||||
|
|
|
||||||
|
|
@ -1 +1,57 @@
|
||||||
pub use jay_formats::*;
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(
|
pub fn create_render_pass(
|
||||||
&self,
|
&self,
|
||||||
node: &dyn Node,
|
node: &dyn Node,
|
||||||
|
|
@ -930,6 +957,44 @@ pub struct GfxFormat {
|
||||||
pub supports_shm: bool,
|
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)]
|
#[derive(Error)]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
pub struct GfxError(pub Box<dyn Error + Send>);
|
pub struct GfxError(pub Box<dyn Error + Send>);
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ pub mod jay_popup_ext_manager_v1;
|
||||||
pub mod jay_randr;
|
pub mod jay_randr;
|
||||||
pub mod jay_reexec;
|
pub mod jay_reexec;
|
||||||
pub mod jay_render_ctx;
|
pub mod jay_render_ctx;
|
||||||
|
pub mod jay_screencast;
|
||||||
pub mod jay_screenshot;
|
pub mod jay_screenshot;
|
||||||
pub mod jay_seat_events;
|
pub mod jay_seat_events;
|
||||||
pub mod jay_select_toplevel;
|
pub mod jay_select_toplevel;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ use {
|
||||||
jay_randr::JayRandr,
|
jay_randr::JayRandr,
|
||||||
jay_reexec::JayReexec,
|
jay_reexec::JayReexec,
|
||||||
jay_render_ctx::JayRenderCtx,
|
jay_render_ctx::JayRenderCtx,
|
||||||
|
jay_screencast::JayScreencast,
|
||||||
jay_screenshot::JayScreenshot,
|
jay_screenshot::JayScreenshot,
|
||||||
jay_seat_events::JaySeatEvents,
|
jay_seat_events::JaySeatEvents,
|
||||||
jay_select_toplevel::{JaySelectToplevel, JayToplevelSelector},
|
jay_select_toplevel::{JaySelectToplevel, JayToplevelSelector},
|
||||||
|
|
@ -41,7 +42,9 @@ use {
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const CREATE_EI_SESSION_SINCE: Version = Version(5);
|
||||||
pub const SCREENSHOT_SPLITUP_SINCE: Version = Version(6);
|
pub const SCREENSHOT_SPLITUP_SINCE: Version = Version(6);
|
||||||
|
pub const GET_TOPLEVEL_SINCE: Version = Version(12);
|
||||||
|
|
||||||
pub struct JayCompositorGlobal {
|
pub struct JayCompositorGlobal {
|
||||||
name: GlobalName,
|
name: GlobalName,
|
||||||
|
|
@ -92,6 +95,7 @@ pub struct Cap;
|
||||||
|
|
||||||
impl Cap {
|
impl Cap {
|
||||||
pub const NONE: u16 = 0;
|
pub const NONE: u16 = 0;
|
||||||
|
pub const WINDOW_CAPTURE: u16 = 1;
|
||||||
pub const SELECT_WORKSPACE: u16 = 2;
|
pub const SELECT_WORKSPACE: u16 = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,7 +103,7 @@ impl JayCompositor {
|
||||||
fn send_capabilities(&self) {
|
fn send_capabilities(&self) {
|
||||||
self.client.event(Capabilities {
|
self.client.event(Capabilities {
|
||||||
self_id: self.id,
|
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(())
|
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> {
|
fn select_toplevel(&self, req: SelectToplevel, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
let obj = JaySelectToplevel::new(&self.client, req.id, self.version);
|
let obj = JaySelectToplevel::new(&self.client, req.id, self.version);
|
||||||
track!(self.client, obj);
|
track!(self.client, obj);
|
||||||
|
|
|
||||||
796
src/ifs/jay_screencast.rs
Normal file
796
src/ifs/jay_screencast.rs
Normal 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)
|
||||||
|
}
|
||||||
145
src/macros.rs
145
src/macros.rs
|
|
@ -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 {
|
macro_rules! object_base {
|
||||||
($self:ident = $oname:ident; version = $version:expr;) => {
|
($self:ident = $oname:ident; version = $version:expr;) => {
|
||||||
impl crate::object::ObjectBase for $oname {
|
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)]
|
#[expect(unused_macros)]
|
||||||
macro_rules! bitor {
|
macro_rules! bitor {
|
||||||
($name:ident) => {
|
($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 {
|
macro_rules! logical_to_client_wire_scale {
|
||||||
($client:expr, $($field:expr),+ $(,)?) => {
|
($client:expr, $($field:expr),+ $(,)?) => {
|
||||||
#[expect(clippy::allow_attributes)]
|
#[expect(clippy::allow_attributes)]
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,8 @@ mod logind;
|
||||||
mod object;
|
mod object;
|
||||||
mod output_schedule;
|
mod output_schedule;
|
||||||
mod pango;
|
mod pango;
|
||||||
|
mod pipewire;
|
||||||
|
mod portal;
|
||||||
mod pr_caps;
|
mod pr_caps;
|
||||||
mod rect;
|
mod rect;
|
||||||
mod renderer;
|
mod renderer;
|
||||||
|
|
@ -119,6 +121,7 @@ mod wire;
|
||||||
mod wire_dbus;
|
mod wire_dbus;
|
||||||
mod wire_ei;
|
mod wire_ei;
|
||||||
mod wire_xcon;
|
mod wire_xcon;
|
||||||
|
mod wl_usr;
|
||||||
mod xcon;
|
mod xcon;
|
||||||
mod xwayland;
|
mod xwayland;
|
||||||
|
|
||||||
|
|
|
||||||
7
src/pipewire.rs
Normal file
7
src/pipewire.rs
Normal 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
453
src/pipewire/pw_con.rs
Normal 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;
|
||||||
312
src/pipewire/pw_formatter.rs
Normal file
312
src/pipewire/pw_formatter.rs
Normal 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
4
src/pipewire/pw_ifs.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
pub mod pw_client;
|
||||||
|
pub mod pw_client_node;
|
||||||
|
pub mod pw_core;
|
||||||
|
pub mod pw_registry;
|
||||||
61
src/pipewire/pw_ifs/pw_client.rs
Normal file
61
src/pipewire/pw_ifs/pw_client.rs
Normal 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),
|
||||||
|
}
|
||||||
892
src/pipewire/pw_ifs/pw_client_node.rs
Normal file
892
src/pipewire/pw_ifs/pw_client_node.rs
Normal 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),
|
||||||
|
}
|
||||||
186
src/pipewire/pw_ifs/pw_core.rs
Normal file
186
src/pipewire/pw_ifs/pw_core.rs
Normal 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),
|
||||||
|
}
|
||||||
48
src/pipewire/pw_ifs/pw_registry.rs
Normal file
48
src/pipewire/pw_ifs/pw_registry.rs
Normal 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
155
src/pipewire/pw_mem.rs
Normal 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
52
src/pipewire/pw_object.rs
Normal 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
312
src/pipewire/pw_parser.rs
Normal 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
1451
src/pipewire/pw_pod.rs
Normal file
File diff suppressed because it is too large
Load diff
464
src/pipewire/pw_pod/pw_debug.rs
Normal file
464
src/pipewire/pw_pod/pw_debug.rs
Normal 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
352
src/portal.rs
Normal 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
562
src/portal/ptl_display.rs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
328
src/portal/ptl_remote_desktop.rs
Normal file
328
src/portal/ptl_remote_desktop.rs
Normal 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
|
||||||
|
}
|
||||||
160
src/portal/ptl_remote_desktop/remote_desktop_gui.rs
Normal file
160
src/portal/ptl_remote_desktop/remote_desktop_gui.rs
Normal 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
|
||||||
|
}
|
||||||
17
src/portal/ptl_render_ctx.rs
Normal file
17
src/portal/ptl_render_ctx.rs
Normal 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>>,
|
||||||
|
}
|
||||||
967
src/portal/ptl_screencast.rs
Normal file
967
src/portal/ptl_screencast.rs
Normal 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,
|
||||||
|
}
|
||||||
297
src/portal/ptl_screencast/screencast_gui.rs
Normal file
297
src/portal/ptl_screencast/screencast_gui.rs
Normal 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
169
src/portal/ptl_session.rs
Normal 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
102
src/portal/ptl_text.rs
Normal 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
947
src/portal/ptr_gui.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,6 +27,10 @@ pub struct RendererBase<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RendererBase<'_> {
|
impl RendererBase<'_> {
|
||||||
|
pub fn scale(&self) -> Scale {
|
||||||
|
self.scale
|
||||||
|
}
|
||||||
|
|
||||||
pub fn scale_point(&self, mut x: i32, mut y: i32) -> (i32, i32) {
|
pub fn scale_point(&self, mut x: i32, mut y: i32) -> (i32, i32) {
|
||||||
if self.scaled {
|
if self.scaled {
|
||||||
[x, y] = self.scale.pixel_size([x, y]);
|
[x, y] = self.scale.pixel_size([x, y]);
|
||||||
|
|
@ -34,6 +38,14 @@ impl RendererBase<'_> {
|
||||||
(x, y)
|
(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn scale_point_f(&self, mut x: f32, mut y: f32) -> (f32, f32) {
|
||||||
|
if self.scaled {
|
||||||
|
x = (x as f64 * self.scalef) as _;
|
||||||
|
y = (y as f64 * self.scalef) as _;
|
||||||
|
}
|
||||||
|
(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn scale_rect(&self, mut rect: Rect) -> Rect {
|
pub fn scale_rect(&self, mut rect: Rect) -> Rect {
|
||||||
if self.scaled {
|
if self.scaled {
|
||||||
let [x1, y1, x2, y2] =
|
let [x1, y1, x2, y2] =
|
||||||
|
|
@ -44,6 +56,17 @@ impl RendererBase<'_> {
|
||||||
rect
|
rect
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn scale_rect_f(&self, mut rect: (f32, f32, f32, f32)) -> (f32, f32, f32, f32) {
|
||||||
|
if self.scaled {
|
||||||
|
let x1 = (rect.0 as f64 * self.scalef).round() as _;
|
||||||
|
let y1 = (rect.1 as f64 * self.scalef).round() as _;
|
||||||
|
let x2 = (rect.2 as f64 * self.scalef).round() as _;
|
||||||
|
let y2 = (rect.3 as f64 * self.scalef).round() as _;
|
||||||
|
rect = (x1, y1, x2, y2);
|
||||||
|
}
|
||||||
|
rect
|
||||||
|
}
|
||||||
|
|
||||||
pub fn fill_scaled_boxes(
|
pub fn fill_scaled_boxes(
|
||||||
&mut self,
|
&mut self,
|
||||||
boxes: &[Rect],
|
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(
|
pub fn render_texture(
|
||||||
&mut self,
|
&mut self,
|
||||||
texture: &Rc<dyn GfxTexture>,
|
texture: &Rc<dyn GfxTexture>,
|
||||||
|
|
|
||||||
28
src/state.rs
28
src/state.rs
|
|
@ -1,8 +1,8 @@
|
||||||
mod animations;
|
mod animations;
|
||||||
mod connectors;
|
mod connectors;
|
||||||
mod idle;
|
mod idle;
|
||||||
mod rendering;
|
|
||||||
mod render_context;
|
mod render_context;
|
||||||
|
mod rendering;
|
||||||
mod settings;
|
mod settings;
|
||||||
mod tree_ops;
|
mod tree_ops;
|
||||||
mod xwayland;
|
mod xwayland;
|
||||||
|
|
@ -48,29 +48,25 @@ use {
|
||||||
globals::{Globals, GlobalsError, RemovableWaylandGlobal, WaylandGlobal},
|
globals::{Globals, GlobalsError, RemovableWaylandGlobal, WaylandGlobal},
|
||||||
icons::Icons,
|
icons::Icons,
|
||||||
ifs::{
|
ifs::{
|
||||||
|
data_transfer::{DataOfferIds, DataSourceIds, data_control::DataControlDeviceIds},
|
||||||
ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1,
|
ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1,
|
||||||
ext_session_lock_v1::ExtSessionLockV1,
|
ext_session_lock_v1::ExtSessionLockV1,
|
||||||
head_management::{
|
head_management::{
|
||||||
HeadNames, jay_head_manager_session_v1::{HeadManagerEvent, JayHeadManagerSessionV1},
|
HeadNames,
|
||||||
},
|
jay_head_manager_session_v1::{HeadManagerEvent, JayHeadManagerSessionV1},
|
||||||
data_transfer::{
|
|
||||||
DataOfferIds, DataSourceIds, data_control::DataControlDeviceIds,
|
|
||||||
},
|
},
|
||||||
jay_render_ctx::JayRenderCtx,
|
jay_render_ctx::JayRenderCtx,
|
||||||
|
jay_screencast::JayScreencast,
|
||||||
jay_seat_events::JaySeatEvents,
|
jay_seat_events::JaySeatEvents,
|
||||||
jay_workspace_watcher::JayWorkspaceWatcher,
|
jay_workspace_watcher::JayWorkspaceWatcher,
|
||||||
wl_buffer::WlBuffer,
|
wl_buffer::WlBuffer,
|
||||||
wl_output::{OutputGlobalOpt, OutputId, PersistentOutputState},
|
wl_output::{OutputGlobalOpt, OutputId, PersistentOutputState},
|
||||||
wl_seat::{
|
wl_seat::{
|
||||||
PhysicalKeyboardId, PhysicalKeyboardIds, PositionHintRequest, SeatIds,
|
PhysicalKeyboardId, PhysicalKeyboardIds, PositionHintRequest, SeatIds, WlSeatGlobal,
|
||||||
WlSeatGlobal,
|
|
||||||
},
|
},
|
||||||
wl_surface::{
|
wl_surface::{
|
||||||
NoneSurfaceExt,
|
NoneSurfaceExt, tray::TrayItemIds, wl_subsurface::SubsurfaceIds,
|
||||||
tray::TrayItemIds,
|
xdg_surface::XdgSurfaceConfigureEvent, zwp_idle_inhibitor_v1::IdleInhibitorIds,
|
||||||
wl_subsurface::SubsurfaceIds,
|
|
||||||
xdg_surface::XdgSurfaceConfigureEvent,
|
|
||||||
zwp_idle_inhibitor_v1::IdleInhibitorIds,
|
|
||||||
zwp_input_popup_surface_v2::ZwpInputPopupSurfaceV2,
|
zwp_input_popup_surface_v2::ZwpInputPopupSurfaceV2,
|
||||||
},
|
},
|
||||||
wlr_output_manager::WlrOutputManagerState,
|
wlr_output_manager::WlrOutputManagerState,
|
||||||
|
|
@ -115,9 +111,8 @@ use {
|
||||||
virtual_output::VirtualOutputs,
|
virtual_output::VirtualOutputs,
|
||||||
wheel::Wheel,
|
wheel::Wheel,
|
||||||
wire::{
|
wire::{
|
||||||
ExtForeignToplevelListV1Id, JayHeadManagerSessionV1Id,
|
ExtForeignToplevelListV1Id, JayHeadManagerSessionV1Id, JayRenderCtxId, JaySeatEventsId,
|
||||||
JayRenderCtxId, JaySeatEventsId, JayWorkspaceWatcherId, ZwlrForeignToplevelManagerV1Id,
|
JayWorkspaceWatcherId, ZwlrForeignToplevelManagerV1Id, ZwpLinuxDmabufFeedbackV1Id,
|
||||||
ZwpLinuxDmabufFeedbackV1Id,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ahash::AHashMap,
|
ahash::AHashMap,
|
||||||
|
|
@ -172,6 +167,8 @@ pub struct State {
|
||||||
pub pending_output_render_data: AsyncQueue<Rc<OutputNode>>,
|
pub pending_output_render_data: AsyncQueue<Rc<OutputNode>>,
|
||||||
pub pending_float_layout: AsyncQueue<Rc<FloatNode>>,
|
pub pending_float_layout: AsyncQueue<Rc<FloatNode>>,
|
||||||
pub pending_input_popup_positioning: AsyncQueue<Rc<ZwpInputPopupSurfaceV2>>,
|
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_placeholder_render_textures: AsyncQueue<Rc<PlaceholderNode>>,
|
||||||
pub pending_container_tab_render_textures: AsyncQueue<Rc<ContainerNode>>,
|
pub pending_container_tab_render_textures: AsyncQueue<Rc<ContainerNode>>,
|
||||||
pub dbus: Dbus,
|
pub dbus: Dbus,
|
||||||
|
|
@ -689,5 +686,4 @@ impl State {
|
||||||
};
|
};
|
||||||
ctx.supports_color_management()
|
ctx.supports_color_management()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -218,6 +218,7 @@ impl ConnectorHandler {
|
||||||
lock_surface: Default::default(),
|
lock_surface: Default::default(),
|
||||||
hardware_cursor: Default::default(),
|
hardware_cursor: Default::default(),
|
||||||
jay_outputs: Default::default(),
|
jay_outputs: Default::default(),
|
||||||
|
screencasts: Default::default(),
|
||||||
update_render_data_scheduled: Cell::new(false),
|
update_render_data_scheduled: Cell::new(false),
|
||||||
hardware_cursor_needs_render: Cell::new(false),
|
hardware_cursor_needs_render: Cell::new(false),
|
||||||
screencopies: Default::default(),
|
screencopies: Default::default(),
|
||||||
|
|
@ -340,6 +341,10 @@ impl ConnectorHandler {
|
||||||
for jo in on.jay_outputs.lock().drain_values() {
|
for jo in on.jay_outputs.lock().drain_values() {
|
||||||
jo.send_destroyed();
|
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() {
|
for sc in on.screencopies.lock().drain_values() {
|
||||||
sc.send_failed();
|
sc.send_failed();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ use {
|
||||||
ifs::{
|
ifs::{
|
||||||
ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1,
|
ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1,
|
||||||
jay_output::JayOutput,
|
jay_output::JayOutput,
|
||||||
|
jay_screencast::JayScreencast,
|
||||||
wl_output::{BlendSpace, WlOutputGlobal},
|
wl_output::{BlendSpace, WlOutputGlobal},
|
||||||
wl_seat::{
|
wl_seat::{
|
||||||
BTN_LEFT, NodeSeatState, SeatId, WlSeatGlobal,
|
BTN_LEFT, NodeSeatState, SeatId, WlSeatGlobal,
|
||||||
|
|
@ -72,7 +73,7 @@ use {
|
||||||
scroller::Scroller,
|
scroller::Scroller,
|
||||||
},
|
},
|
||||||
wire::{
|
wire::{
|
||||||
ExtImageCopyCaptureSessionV1Id, JayOutputId, ZwlrScreencopyFrameV1Id,
|
ExtImageCopyCaptureSessionV1Id, JayOutputId, JayScreencastId, ZwlrScreencopyFrameV1Id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
numeric_sort::cmp,
|
numeric_sort::cmp,
|
||||||
|
|
@ -115,6 +116,7 @@ pub struct OutputNode {
|
||||||
pub hardware_cursor: CloneCell<Option<Rc<dyn HardwareCursor>>>,
|
pub hardware_cursor: CloneCell<Option<Rc<dyn HardwareCursor>>>,
|
||||||
pub hardware_cursor_needs_render: Cell<bool>,
|
pub hardware_cursor_needs_render: Cell<bool>,
|
||||||
pub update_render_data_scheduled: Cell<bool>,
|
pub update_render_data_scheduled: Cell<bool>,
|
||||||
|
pub screencasts: CopyHashMap<(ClientId, JayScreencastId), Rc<JayScreencast>>,
|
||||||
pub screencopies: CopyHashMap<(ClientId, ZwlrScreencopyFrameV1Id), Rc<ZwlrScreencopyFrameV1>>,
|
pub screencopies: CopyHashMap<(ClientId, ZwlrScreencopyFrameV1Id), Rc<ZwlrScreencopyFrameV1>>,
|
||||||
pub title_visible: Cell<bool>,
|
pub title_visible: Cell<bool>,
|
||||||
pub schedule: Rc<OutputSchedule>,
|
pub schedule: Rc<OutputSchedule>,
|
||||||
|
|
@ -285,6 +287,7 @@ impl OutputNode {
|
||||||
}
|
}
|
||||||
self.lock_surface.take();
|
self.lock_surface.take();
|
||||||
self.jay_outputs.clear();
|
self.jay_outputs.clear();
|
||||||
|
self.screencasts.clear();
|
||||||
self.screencopies.clear();
|
self.screencopies.clear();
|
||||||
self.ext_copy_sessions.clear();
|
self.ext_copy_sessions.clear();
|
||||||
self.ext_workspace_groups.clear();
|
self.ext_workspace_groups.clear();
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use {
|
||||||
crate::{
|
crate::{
|
||||||
cmm::cmm_description::ColorDescription,
|
cmm::cmm_description::ColorDescription,
|
||||||
gfx_api::{AcquireSync, BufferResv, GfxTexture, ReleaseSync},
|
gfx_api::{AcquireSync, BufferResv, GfxTexture, ReleaseSync},
|
||||||
ifs::wl_buffer::WlBufferStorage,
|
ifs::{jay_screencast::JayScreencast, wl_buffer::WlBufferStorage},
|
||||||
utils::{errorfmt::ErrorFmt, hash_map_ext::HashMapExt},
|
utils::{errorfmt::ErrorFmt, hash_map_ext::HashMapExt},
|
||||||
},
|
},
|
||||||
std::{ops::Deref, rc::Rc},
|
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(
|
pub fn perform_screencopies(
|
||||||
&self,
|
&self,
|
||||||
tex: &Rc<dyn GfxTexture>,
|
tex: &Rc<dyn GfxTexture>,
|
||||||
|
|
@ -44,6 +58,20 @@ impl OutputNode {
|
||||||
y_off,
|
y_off,
|
||||||
size,
|
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() {
|
for sc in self.ext_copy_sessions.lock().values() {
|
||||||
sc.copy_texture(
|
sc.copy_texture(
|
||||||
self,
|
self,
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ use {
|
||||||
ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
|
ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
|
||||||
ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1,
|
ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1,
|
||||||
ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1,
|
ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1,
|
||||||
|
jay_screencast::JayScreencast,
|
||||||
jay_toplevel::JayToplevel,
|
jay_toplevel::JayToplevel,
|
||||||
wl_seat::{NodeSeatState, SeatId, collect_kb_foci, collect_kb_foci2},
|
wl_seat::{NodeSeatState, SeatId, collect_kb_foci, collect_kb_foci2},
|
||||||
wl_surface::{
|
wl_surface::{
|
||||||
|
|
@ -43,8 +44,8 @@ use {
|
||||||
rc_eq::rc_eq, threshold_counter::ThresholdCounter,
|
rc_eq::rc_eq, threshold_counter::ThresholdCounter,
|
||||||
},
|
},
|
||||||
wire::{
|
wire::{
|
||||||
ExtForeignToplevelHandleV1Id, ExtImageCopyCaptureSessionV1Id, JayToplevelId,
|
ExtForeignToplevelHandleV1Id, ExtImageCopyCaptureSessionV1Id, JayScreencastId,
|
||||||
XxForeignToplevelGeometryTrackerV1Id, ZwlrForeignToplevelHandleV1Id,
|
JayToplevelId, XxForeignToplevelGeometryTrackerV1Id, ZwlrForeignToplevelHandleV1Id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
jay_config::{window, window::WindowType},
|
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>) {
|
fn tl_workspace_output_changed(&self, prev: &Rc<OutputNode>, new: &Rc<OutputNode>) {
|
||||||
let data = self.tl_data();
|
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() {
|
for sc in data.ext_copy_sessions.lock().values() {
|
||||||
sc.update_latch_listener();
|
sc.update_latch_listener();
|
||||||
}
|
}
|
||||||
|
|
@ -240,6 +244,9 @@ impl<T: ToplevelNodeBase> ToplevelNode for T {
|
||||||
data.spawn_in_pending.set(false);
|
data.spawn_in_pending.set(false);
|
||||||
}
|
}
|
||||||
if prev.size() != rect.size() {
|
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() {
|
for sc in data.ext_copy_sessions.lock().values() {
|
||||||
sc.buffer_size_changed();
|
sc.buffer_size_changed();
|
||||||
}
|
}
|
||||||
|
|
@ -536,6 +543,7 @@ pub struct ToplevelData {
|
||||||
CopyHashMap<(ClientId, ZwlrForeignToplevelHandleV1Id), Rc<ZwlrForeignToplevelHandleV1>>,
|
CopyHashMap<(ClientId, ZwlrForeignToplevelHandleV1Id), Rc<ZwlrForeignToplevelHandleV1>>,
|
||||||
pub render_highlight: NumCell<u32>,
|
pub render_highlight: NumCell<u32>,
|
||||||
pub jay_toplevels: CopyHashMap<(ClientId, JayToplevelId), Rc<JayToplevel>>,
|
pub jay_toplevels: CopyHashMap<(ClientId, JayToplevelId), Rc<JayToplevel>>,
|
||||||
|
pub jay_screencasts: CopyHashMap<(ClientId, JayScreencastId), Rc<JayScreencast>>,
|
||||||
pub ext_copy_sessions:
|
pub ext_copy_sessions:
|
||||||
CopyHashMap<(ClientId, ExtImageCopyCaptureSessionV1Id), Rc<ExtImageCopyCaptureSessionV1>>,
|
CopyHashMap<(ClientId, ExtImageCopyCaptureSessionV1Id), Rc<ExtImageCopyCaptureSessionV1>>,
|
||||||
pub slf: Weak<dyn ToplevelNode>,
|
pub slf: Weak<dyn ToplevelNode>,
|
||||||
|
|
@ -595,6 +603,7 @@ impl ToplevelData {
|
||||||
manager_handles: Default::default(),
|
manager_handles: Default::default(),
|
||||||
render_highlight: Default::default(),
|
render_highlight: Default::default(),
|
||||||
jay_toplevels: Default::default(),
|
jay_toplevels: Default::default(),
|
||||||
|
jay_screencasts: Default::default(),
|
||||||
ext_copy_sessions: Default::default(),
|
ext_copy_sessions: Default::default(),
|
||||||
slf: slf.clone(),
|
slf: slf.clone(),
|
||||||
destroyed: Default::default(),
|
destroyed: Default::default(),
|
||||||
|
|
@ -671,6 +680,9 @@ impl ToplevelData {
|
||||||
for jay_tl in self.jay_toplevels.lock().drain_values() {
|
for jay_tl in self.jay_toplevels.lock().drain_values() {
|
||||||
jay_tl.destroy();
|
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() {
|
for capture in self.ext_copy_sessions.lock().drain_values() {
|
||||||
capture.stop();
|
capture.stop();
|
||||||
}
|
}
|
||||||
|
|
@ -991,6 +1003,9 @@ impl ToplevelData {
|
||||||
self.property_changed(TL_CHANGED_VISIBLE);
|
self.property_changed(TL_CHANGED_VISIBLE);
|
||||||
}
|
}
|
||||||
self.seat_state.set_visible(node, 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() {
|
for sc in self.ext_copy_sessions.lock().values() {
|
||||||
sc.update_latch_listener();
|
sc.update_latch_listener();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,12 @@ impl WorkspaceNode {
|
||||||
if !self.may_capture.get() {
|
if !self.may_capture.get() {
|
||||||
break 'update;
|
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() {
|
if output.screencopies.is_not_empty() {
|
||||||
has_capture = true;
|
has_capture = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
370
src/wl_usr.rs
Normal file
370
src/wl_usr.rs
Normal 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
48
src/wl_usr/usr_ifs.rs
Normal 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;
|
||||||
239
src/wl_usr/usr_ifs/usr_jay_compositor.rs
Normal file
239
src/wl_usr/usr_ifs/usr_jay_compositor.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
72
src/wl_usr/usr_ifs/usr_jay_ei_session.rs
Normal file
72
src/wl_usr/usr_ifs/usr_jay_ei_session.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
57
src/wl_usr/usr_ifs/usr_jay_ei_session_builder.rs
Normal file
57
src/wl_usr/usr_ifs/usr_jay_ei_session_builder.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/wl_usr/usr_ifs/usr_jay_output.rs
Normal file
61
src/wl_usr/usr_ifs/usr_jay_output.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/wl_usr/usr_ifs/usr_jay_pointer.rs
Normal file
39
src/wl_usr/usr_ifs/usr_jay_pointer.rs
Normal 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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
109
src/wl_usr/usr_ifs/usr_jay_render_ctx.rs
Normal file
109
src/wl_usr/usr_ifs/usr_jay_render_ctx.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
272
src/wl_usr/usr_ifs/usr_jay_screencast.rs
Normal file
272
src/wl_usr/usr_ifs/usr_jay_screencast.rs
Normal 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),
|
||||||
|
}
|
||||||
90
src/wl_usr/usr_ifs/usr_jay_select_toplevel.rs
Normal file
90
src/wl_usr/usr_ifs/usr_jay_select_toplevel.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
78
src/wl_usr/usr_ifs/usr_jay_select_workspace.rs
Normal file
78
src/wl_usr/usr_ifs/usr_jay_select_workspace.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/wl_usr/usr_ifs/usr_jay_sync_file_release.rs
Normal file
60
src/wl_usr/usr_ifs/usr_jay_sync_file_release.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/wl_usr/usr_ifs/usr_jay_sync_file_surface.rs
Normal file
29
src/wl_usr/usr_ifs/usr_jay_sync_file_surface.rs
Normal 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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
64
src/wl_usr/usr_ifs/usr_jay_toplevel.rs
Normal file
64
src/wl_usr/usr_ifs/usr_jay_toplevel.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
100
src/wl_usr/usr_ifs/usr_jay_workspace.rs
Normal file
100
src/wl_usr/usr_ifs/usr_jay_workspace.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
62
src/wl_usr/usr_ifs/usr_jay_workspace_watcher.rs
Normal file
62
src/wl_usr/usr_ifs/usr_jay_workspace_watcher.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
89
src/wl_usr/usr_ifs/usr_linux_buffer_params.rs
Normal file
89
src/wl_usr/usr_ifs/usr_linux_buffer_params.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
78
src/wl_usr/usr_ifs/usr_linux_dmabuf.rs
Normal file
78
src/wl_usr/usr_ifs/usr_linux_dmabuf.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/wl_usr/usr_ifs/usr_wl_buffer.rs
Normal file
46
src/wl_usr/usr_ifs/usr_wl_buffer.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
68
src/wl_usr/usr_ifs/usr_wl_callback.rs
Normal file
68
src/wl_usr/usr_ifs/usr_wl_callback.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/wl_usr/usr_ifs/usr_wl_compositor.rs
Normal file
48
src/wl_usr/usr_ifs/usr_wl_compositor.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
84
src/wl_usr/usr_ifs/usr_wl_data_device.rs
Normal file
84
src/wl_usr/usr_ifs/usr_wl_data_device.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/wl_usr/usr_ifs/usr_wl_data_device_manager.rs
Normal file
29
src/wl_usr/usr_ifs/usr_wl_data_device_manager.rs
Normal 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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/wl_usr/usr_ifs/usr_wl_data_offer.rs
Normal file
46
src/wl_usr/usr_ifs/usr_wl_data_offer.rs
Normal 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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
72
src/wl_usr/usr_ifs/usr_wl_data_source.rs
Normal file
72
src/wl_usr/usr_ifs/usr_wl_data_source.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/wl_usr/usr_ifs/usr_wl_display.rs
Normal file
44
src/wl_usr/usr_ifs/usr_wl_display.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
148
src/wl_usr/usr_ifs/usr_wl_keyboard.rs
Normal file
148
src/wl_usr/usr_ifs/usr_wl_keyboard.rs
Normal 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),
|
||||||
|
}
|
||||||
103
src/wl_usr/usr_ifs/usr_wl_output.rs
Normal file
103
src/wl_usr/usr_ifs/usr_wl_output.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
143
src/wl_usr/usr_ifs/usr_wl_pointer.rs
Normal file
143
src/wl_usr/usr_ifs/usr_wl_pointer.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/wl_usr/usr_ifs/usr_wl_registry.rs
Normal file
74
src/wl_usr/usr_ifs/usr_wl_registry.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
78
src/wl_usr/usr_ifs/usr_wl_seat.rs
Normal file
78
src/wl_usr/usr_ifs/usr_wl_seat.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/wl_usr/usr_ifs/usr_wl_shm.rs
Normal file
60
src/wl_usr/usr_ifs/usr_wl_shm.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/wl_usr/usr_ifs/usr_wl_shm_pool.rs
Normal file
39
src/wl_usr/usr_ifs/usr_wl_shm_pool.rs
Normal 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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
92
src/wl_usr/usr_ifs/usr_wl_surface.rs
Normal file
92
src/wl_usr/usr_ifs/usr_wl_surface.rs
Normal 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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
62
src/wl_usr/usr_ifs/usr_wlr_layer_shell.rs
Normal file
62
src/wl_usr/usr_ifs/usr_wlr_layer_shell.rs
Normal 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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
87
src/wl_usr/usr_ifs/usr_wlr_layer_surface.rs
Normal file
87
src/wl_usr/usr_ifs/usr_wlr_layer_surface.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/wl_usr/usr_ifs/usr_wp_cursor_shape_device_v1.rs
Normal file
29
src/wl_usr/usr_ifs/usr_wp_cursor_shape_device_v1.rs
Normal 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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/wl_usr/usr_ifs/usr_wp_cursor_shape_manager_v1.rs
Normal file
29
src/wl_usr/usr_ifs/usr_wp_cursor_shape_manager_v1.rs
Normal 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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/wl_usr/usr_ifs/usr_wp_fractional_scale.rs
Normal file
48
src/wl_usr/usr_ifs/usr_wp_fractional_scale.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/wl_usr/usr_ifs/usr_wp_fractional_scale_manager.rs
Normal file
53
src/wl_usr/usr_ifs/usr_wp_fractional_scale_manager.rs
Normal 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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/wl_usr/usr_ifs/usr_wp_viewport.rs
Normal file
51
src/wl_usr/usr_ifs/usr_wp_viewport.rs
Normal 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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/wl_usr/usr_ifs/usr_wp_viewporter.rs
Normal file
50
src/wl_usr/usr_ifs/usr_wp_viewporter.rs
Normal 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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/wl_usr/usr_ifs/usr_xdg_surface.rs
Normal file
52
src/wl_usr/usr_ifs/usr_xdg_surface.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
68
src/wl_usr/usr_ifs/usr_xdg_toplevel.rs
Normal file
68
src/wl_usr/usr_ifs/usr_xdg_toplevel.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/wl_usr/usr_ifs/usr_xdg_wm_base.rs
Normal file
37
src/wl_usr/usr_ifs/usr_xdg_wm_base.rs
Normal 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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
128
src/wl_usr/usr_ifs/usr_zwlr_screencopy_frame.rs
Normal file
128
src/wl_usr/usr_ifs/usr_zwlr_screencopy_frame.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/wl_usr/usr_ifs/usr_zwlr_screencopy_manager.rs
Normal file
55
src/wl_usr/usr_ifs/usr_zwlr_screencopy_manager.rs
Normal 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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/wl_usr/usr_ifs/usr_zwp_linux_buffer_params_v1.rs
Normal file
37
src/wl_usr/usr_ifs/usr_zwp_linux_buffer_params_v1.rs
Normal 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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/wl_usr/usr_ifs/usr_zwp_linux_dmabuf_v1.rs
Normal file
37
src/wl_usr/usr_ifs/usr_zwp_linux_dmabuf_v1.rs
Normal 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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
26
src/wl_usr/usr_object.rs
Normal 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) {}
|
||||||
|
}
|
||||||
16
wire-dbus/org.freedesktop.DBus.Properties.txt
Normal file
16
wire-dbus/org.freedesktop.DBus.Properties.txt
Normal 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
Loading…
Add table
Add a link
Reference in a new issue