1
0
Fork 0
forked from wry/wry

all: split reusable components into workspace crates

This commit is contained in:
kossLAN 2026-05-29 09:14:53 -04:00
parent 2a079ed800
commit 657e7ce2f7
No known key found for this signature in database
225 changed files with 7422 additions and 17602 deletions

View file

@ -1,7 +1,6 @@
use {
crate::{
async_engine::SpawnedFuture,
client::ClientCaps,
security_context_acceptor::AcceptorMetadata,
state::State,
utils::{
@ -46,63 +45,49 @@ struct AllocatedSocket {
name: String,
// /run/user/1000/wayland-x
path: Ustring,
insecure: Rc<OwnedFd>,
socket: Rc<OwnedFd>,
// /run/user/1000/wayland-x.lock
lock_path: Ustring,
_lock_fd: OwnedFd,
// /run/user/1000/wayland-x.jay
secure_path: Ustring,
secure: Rc<OwnedFd>,
}
impl Drop for AllocatedSocket {
fn drop(&mut self) {
let _ = uapi::unlink(&self.path);
let _ = uapi::unlink(&self.lock_path);
let _ = uapi::unlink(&self.secure_path);
}
}
fn bind_socket(
insecure: &Rc<OwnedFd>,
secure: &Rc<OwnedFd>,
xrd: &str,
id: u32,
) -> Result<AllocatedSocket, AcceptorError> {
fn bind_socket(socket: &Rc<OwnedFd>, xrd: &str, id: u32) -> Result<AllocatedSocket, AcceptorError> {
let mut addr: c::sockaddr_un = uapi::pod_zeroed();
addr.sun_family = c::AF_UNIX as _;
let name = format!("wayland-{}", id);
let path = format_ustr!("{}/{}", xrd, name);
let jay_path = format_ustr!("{}.jay", path.display());
let lock_path = format_ustr!("{}.lock", path.display());
if jay_path.len() + 1 > addr.sun_path.len() {
if path.len() + 1 > addr.sun_path.len() {
return Err(AcceptorError::XrdTooLong(xrd.to_string()));
}
let lock_fd = uapi::open(&*lock_path, c::O_CREAT | c::O_CLOEXEC | c::O_RDWR, 0o644)
.map_os_err(AcceptorError::OpenLockFile)?;
uapi::flock(lock_fd.raw(), c::LOCK_EX | c::LOCK_NB).map_os_err(AcceptorError::LockLockFile)?;
for (name, fd) in [(&path, insecure), (&jay_path, secure)] {
match uapi::lstat(name).to_os_error() {
Ok(_) => {
log::info!("Unlinking {}", name.display());
let _ = uapi::unlink(name);
}
Err(OsError(c::ENOENT)) => {}
Err(e) => return Err(AcceptorError::SocketStat(e)),
match uapi::lstat(&path).to_os_error() {
Ok(_) => {
log::info!("Unlinking {}", path.display());
let _ = uapi::unlink(&path);
}
let sun_path = uapi::as_bytes_mut(&mut addr.sun_path[..]);
sun_path[..name.len()].copy_from_slice(name.as_bytes());
sun_path[name.len()] = 0;
uapi::bind(fd.raw(), &addr).map_os_err(AcceptorError::BindFailed)?;
Err(OsError(c::ENOENT)) => {}
Err(e) => return Err(AcceptorError::SocketStat(e)),
}
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;
uapi::bind(socket.raw(), &addr).map_os_err(AcceptorError::BindFailed)?;
Ok(AllocatedSocket {
name,
path,
insecure: insecure.clone(),
socket: socket.clone(),
lock_path,
_lock_fd: lock_fd,
secure_path: jay_path,
secure: secure.clone(),
})
}
@ -111,17 +96,11 @@ fn allocate_socket() -> Result<AllocatedSocket, AcceptorError> {
Some(d) => d,
_ => return Err(AcceptorError::XrdNotSet),
};
let mut fds = [None, None];
for fd in &mut fds {
let socket = uapi::socket(c::AF_UNIX, c::SOCK_STREAM | c::SOCK_CLOEXEC, 0)
.map(Rc::new)
.map_os_err(AcceptorError::SocketFailed)?;
*fd = Some(socket);
}
let unsecure = fds[0].take().unwrap();
let secure = fds[1].take().unwrap();
let socket = uapi::socket(c::AF_UNIX, c::SOCK_STREAM | c::SOCK_CLOEXEC, 0)
.map(Rc::new)
.map_os_err(AcceptorError::SocketFailed)?;
for i in 1..1000 {
match bind_socket(&unsecure, &secure, &xrd, i) {
match bind_socket(&socket, &xrd, i) {
Ok(s) => return Ok(s),
Err(e) => {
log::warn!("Cannot use the wayland-{} socket: {}", i, ErrorFmt(e));
@ -137,19 +116,12 @@ impl Acceptor {
) -> Result<(Rc<Acceptor>, Vec<SpawnedFuture<()>>), AcceptorError> {
let socket = allocate_socket()?;
log::info!("bound to socket {}", socket.path.display());
for fd in [&socket.secure, &socket.insecure] {
uapi::listen(fd.raw(), 4096).map_os_err(AcceptorError::ListenFailed)?;
}
uapi::listen(socket.socket.raw(), 4096).map_os_err(AcceptorError::ListenFailed)?;
let acc = Rc::new(Acceptor { socket });
let futures = vec![
state.eng.spawn(
"secure acceptor",
accept(acc.socket.secure.clone(), state.clone(), true),
),
state.eng.spawn(
"insecure acceptor",
accept(acc.socket.insecure.clone(), state.clone(), false),
),
state
.eng
.spawn("client acceptor", accept(acc.socket.socket.clone(), state.clone())),
];
state.acceptor.set(Some(acc.clone()));
Ok((acc, futures))
@ -160,16 +132,13 @@ impl Acceptor {
}
#[cfg_attr(not(feature = "it"), expect(dead_code))]
pub fn secure_path(&self) -> &Ustr {
self.socket.secure_path.as_ustr()
pub fn socket_path(&self) -> &Ustr {
self.socket.path.as_ustr()
}
}
async fn accept(fd: Rc<OwnedFd>, state: Rc<State>, secure: bool) {
let metadata = Rc::new(AcceptorMetadata {
secure,
..Default::default()
});
async fn accept(fd: Rc<OwnedFd>, state: Rc<State>) {
let metadata = Rc::new(AcceptorMetadata::default());
loop {
let fd = match state.ring.accept(&fd, c::SOCK_CLOEXEC).await {
Ok(fd) => fd,
@ -181,7 +150,7 @@ async fn accept(fd: Rc<OwnedFd>, state: Rc<State>, secure: bool) {
let id = state.clients.id();
if let Err(e) = state
.clients
.spawn(id, &state, fd, ClientCaps::all(), false, &metadata)
.spawn(id, &state, fd, &metadata)
{
log::error!("Could not spawn a client: {}", ErrorFmt(e));
break;

File diff suppressed because it is too large Load diff

View file

@ -14,8 +14,6 @@ mod pid;
mod quit;
mod randr;
mod reexec;
mod run_privileged;
mod run_tagged;
pub mod screenshot;
mod seat_test;
mod set_log_level;
@ -29,15 +27,13 @@ use {
cli::{
clients::ClientsArgs, color_management::ColorManagementArgs, config::ConfigArgs,
damage_tracking::DamageTrackingArgs, idle::IdleCmd, input::InputArgs,
json::VERBOSE_JSON, randr::RandrArgs, reexec::ReexecArgs, run_tagged::RunTaggedArgs,
tree::TreeArgs, xwayland::XwaylandArgs,
json::VERBOSE_JSON, randr::RandrArgs, reexec::ReexecArgs, tree::TreeArgs,
xwayland::XwaylandArgs,
},
compositor::{LogLevel, start_compositor},
format::{Format, ref_formats},
portal,
pr_caps::drop_all_pr_caps,
},
clap::{Args, Parser, Subcommand, ValueEnum, ValueHint, builder::PossibleValue},
clap::{Args, Parser, Subcommand, ValueEnum, ValueHint},
clap_complete::Shell,
std::sync::atomic::Ordering::Relaxed,
};
@ -88,14 +84,8 @@ pub enum Cmd {
Idle(IdleArgs),
/// Turn monitors on or off.
Dpms(DpmsArgs),
/// Run a privileged program.
RunPrivileged(RunPrivilegedArgs),
/// Run a program with a connection tag.
RunTagged(RunTaggedArgs),
/// Tests the events produced by a seat.
SeatTest(SeatTestArgs),
/// Run the desktop portal.
Portal,
/// Inspect/modify graphics card and connector settings.
Randr(RandrArgs),
/// Inspect/modify input settings.
@ -147,13 +137,6 @@ pub enum DpmsState {
Off,
}
#[derive(Args, Debug)]
pub struct RunPrivilegedArgs {
/// The program to run
#[clap(required = true, trailing_var_arg = true, value_hint = ValueHint::CommandWithArguments)]
pub program: Vec<String>,
}
#[derive(ValueEnum, Debug, Copy, Clone, Hash, Default, PartialEq)]
pub enum ScreenshotFormat {
/// The PNG image format.
@ -240,16 +223,6 @@ pub struct GenerateArgs {
shell: Shell,
}
impl ValueEnum for &'static Format {
fn value_variants<'a>() -> &'a [Self] {
ref_formats()
}
fn to_possible_value(&self) -> Option<PossibleValue> {
Some(PossibleValue::new(self.name))
}
}
pub fn main() {
let cli = Jay::parse();
if not_matches!(cli.command, Cmd::Run(_)) {
@ -268,10 +241,7 @@ pub fn main() {
Cmd::Idle(a) => idle::main(cli.global, a),
Cmd::Dpms(a) => dpms::main(cli.global, a),
Cmd::Unlock => unlock::main(cli.global),
Cmd::RunPrivileged(a) => run_privileged::main(cli.global, a),
Cmd::RunTagged(a) => run_tagged::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::Input(a) => input::main(cli.global, a),
Cmd::DamageTracking(a) => damage_tracking::main(cli.global, a),

View file

@ -167,7 +167,6 @@ pub struct Client {
pub is_xwayland: bool,
pub comm: Option<String>,
pub exe: Option<String>,
pub tag: Option<String>,
}
pub async fn handle_client_query(
@ -212,9 +211,6 @@ pub async fn handle_client_query(
Exe::handle(tl, id, c.clone(), |c, event| {
last!(c).exe = Some(event.exe.to_string());
});
Tag::handle(tl, id, c.clone(), |c, event| {
last!(c).tag = Some(event.tag.to_string());
});
tl.round_trip().await;
mem::take(&mut *c.borrow_mut())
.into_iter()
@ -253,7 +249,6 @@ impl ClientPrinter<'_> {
bol!(is_xwayland, "xwayland");
opt!(comm, "comm");
opt!(exe, "exe");
opt!(tag, "tag");
}
}
@ -269,6 +264,5 @@ pub fn make_json_client(client: &Client) -> JsonClient<'_> {
is_xwayland: client.is_xwayland,
comm: client.comm.as_deref(),
exe: client.exe.as_deref(),
tag: client.tag.as_deref(),
}
}

View file

@ -66,8 +66,6 @@ pub struct JsonClient<'a> {
pub comm: Option<&'a str>,
#[serde(skip_serializing_if = "is_none")]
pub exe: Option<&'a str>,
#[serde(skip_serializing_if = "is_none")]
pub tag: Option<&'a str>,
}
#[derive(Serialize)]

View file

@ -1,35 +0,0 @@
use {
crate::{
cli::{GlobalArgs, RunPrivilegedArgs},
compositor::WAYLAND_DISPLAY,
logger::Logger,
utils::{errorfmt::ErrorFmt, oserror::OsErrorExt, xrd::xrd},
},
std::path::PathBuf,
uapi::UstrPtr,
};
pub fn main(global: GlobalArgs, args: RunPrivilegedArgs) {
Logger::install_stderr(global.log_level);
if let Some(xrd) = xrd() {
let mut wd = match std::env::var(WAYLAND_DISPLAY) {
Ok(v) => v,
_ => fatal!("{} is not set", WAYLAND_DISPLAY),
};
wd.push_str(".jay");
let mut path = PathBuf::from(xrd);
path.push(&wd);
if path.exists() {
unsafe {
std::env::set_var(WAYLAND_DISPLAY, &wd);
}
}
}
let mut argv = UstrPtr::new();
for arg in &args.program {
argv.push(arg.as_str());
}
let program = args.program[0].as_str();
let res = uapi::execvp(program, &argv).to_os_error().unwrap_err();
fatal!("Could not execute `{}`: {}", program, ErrorFmt(res));
}

View file

@ -1,70 +0,0 @@
use {
crate::{
cli::GlobalArgs,
compositor::WAYLAND_DISPLAY,
tools::tool_client::{Handle, ToolClient, with_tool_client},
utils::{errorfmt::ErrorFmt, oserror::OsErrorExt},
wire::{jay_acceptor_request, jay_compositor},
},
clap::{Args, ValueHint},
std::{cell::Cell, env, rc::Rc},
uapi::UstrPtr,
};
#[derive(Args, Debug)]
pub struct RunTaggedArgs {
/// Specifies a tag to apply to all spawned wayland connections.
tag: String,
/// The program to run.
#[clap(required = true, trailing_var_arg = true, value_hint = ValueHint::CommandWithArguments)]
pub program: Vec<String>,
}
pub fn main(global: GlobalArgs, run_tagged_args: RunTaggedArgs) {
with_tool_client(global.log_level, |tc| async move {
let run_tagged = Rc::new(RunTagged { tc: tc.clone() });
run_tagged.run(run_tagged_args).await;
});
}
struct RunTagged {
tc: Rc<ToolClient>,
}
impl RunTagged {
async fn run(&self, args: RunTaggedArgs) {
let tc = &self.tc;
let comp = tc.jay_compositor().await;
let req = tc.id();
tc.send(jay_compositor::GetTaggedAcceptor {
self_id: comp,
id: req,
tag: &args.tag,
});
let res = Rc::new(Cell::new(None));
jay_acceptor_request::Done::handle(&tc, req, res.clone(), |res, ev| {
res.set(Some(Ok(ev.name.to_owned())));
});
jay_acceptor_request::Failed::handle(&tc, req, res.clone(), |res, ev| {
res.set(Some(Err(ev.msg.to_owned())));
});
tc.round_trip().await;
match res.take().unwrap() {
Ok(n) => {
unsafe {
env::set_var(WAYLAND_DISPLAY, &n);
}
let mut argv = UstrPtr::new();
for arg in &args.program {
argv.push(arg.as_str());
}
let program = args.program[0].as_str();
let res = uapi::execvp(program, &argv).to_os_error().unwrap_err();
fatal!("Could not execute `{}`: {}", program, ErrorFmt(res));
}
Err(msg) => {
fatal!("Could not create acceptor: {}", msg);
}
}
}
}

View file

@ -25,7 +25,6 @@ use {
pending_serial::PendingSerial,
pid_info::{PidInfo, get_pid_info, get_socket_creds},
pidfd_send_signal::pidfd_send_signal,
static_text::StaticText,
},
wire::WlRegistryId,
},
@ -71,35 +70,6 @@ bitflags! {
CAP_FOREIGN_TOPLEVEL_GEOMETRY_TRACKING = 1 << 16,
}
impl StaticText for ClientCapsEnum {
fn text(&self) -> &'static str {
match self {
ClientCapsEnum::CAP_DATA_CONTROL_MANAGER => "data-control",
ClientCapsEnum::CAP_VIRTUAL_KEYBOARD_MANAGER => "virtual-keyboard",
ClientCapsEnum::CAP_FOREIGN_TOPLEVEL_LIST => "foreign-toplevel-list",
ClientCapsEnum::CAP_IDLE_NOTIFIER => "idle-notifier",
ClientCapsEnum::CAP_SESSION_LOCK_MANAGER => "session-lock",
ClientCapsEnum::CAP_JAY_COMPOSITOR => "jay-compositor",
ClientCapsEnum::CAP_LAYER_SHELL => "layer-shell",
ClientCapsEnum::CAP_SCREENCOPY_MANAGER => "screencopy",
ClientCapsEnum::CAP_SEAT_MANAGER => "seat-manager",
ClientCapsEnum::CAP_DRM_LEASE => "drm-lease",
ClientCapsEnum::CAP_INPUT_METHOD => "input-method",
ClientCapsEnum::CAP_WORKSPACE => "workspace-manager",
ClientCapsEnum::CAP_FOREIGN_TOPLEVEL_MANAGER => "foreign-toplevel-manager",
ClientCapsEnum::CAP_HEAD_MANAGER => "head-manager",
ClientCapsEnum::CAP_GAMMA_CONTROL_MANAGER => "gamma-control-manager",
ClientCapsEnum::CAP_VIRTUAL_POINTER_MANAGER => "virtual-pointer",
ClientCapsEnum::CAP_FOREIGN_TOPLEVEL_GEOMETRY_TRACKING => {
"foreign-toplevel-geometry-tracking"
}
}
}
}
pub const CAPS_DEFAULT: ClientCaps = ClientCaps(CAP_LAYER_SHELL.0 | CAP_DRM_LEASE.0);
pub const CAPS_DEFAULT_SANDBOXED: ClientCaps = ClientCaps(CAP_DRM_LEASE.0);
#[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub struct ClientId(u64);
@ -156,24 +126,12 @@ impl Clients {
id: ClientId,
global: &Rc<State>,
socket: Rc<OwnedFd>,
bounding_caps: ClientCaps,
set_bounding_caps_for_children: bool,
acceptor: &Rc<AcceptorMetadata>,
) -> Result<(), ClientError> {
let Some((uid, pid)) = get_socket_creds(&socket) else {
return Ok(());
};
self.spawn2(
id,
global,
socket,
uid,
pid,
bounding_caps,
set_bounding_caps_for_children,
false,
acceptor,
)?;
self.spawn2(id, global, socket, uid, pid, false, acceptor)?;
Ok(())
}
@ -184,15 +142,9 @@ impl Clients {
socket: Rc<OwnedFd>,
uid: c::uid_t,
pid: c::pid_t,
bounding_caps: ClientCaps,
set_bounding_caps_for_children: bool,
is_xwayland: bool,
acceptor: &Rc<AcceptorMetadata>,
) -> Result<Rc<Client>, ClientError> {
let effective_caps = match acceptor.sandboxed {
true => CAPS_DEFAULT_SANDBOXED,
false => CAPS_DEFAULT,
};
let data = Rc::new_cyclic(|slf| Client {
id,
state: global.clone(),
@ -204,8 +156,6 @@ impl Clients {
shutdown: Default::default(),
tracker: Default::default(),
is_xwayland,
effective_caps: Cell::new(effective_caps & bounding_caps),
bounding_caps_for_children: Cell::new(bounding_caps),
last_enter_serial: Default::default(),
pid_info: get_pid_info(uid, pid),
serials: Default::default(),
@ -226,10 +176,6 @@ impl Clients {
acceptor: acceptor.clone(),
});
track!(data, data);
global.update_capabilities(&data, bounding_caps, set_bounding_caps_for_children);
if acceptor.secure || is_xwayland {
data.effective_caps.set(ClientCaps::all());
}
let display = Rc::new(WlDisplay::new(&data));
track!(data, display);
data.objects.display.set(Some(display.clone()));
@ -239,13 +185,12 @@ impl Clients {
data: data.clone(),
};
log::info!(
"Client {} connected, pid: {}, uid: {}, fd: {}, comm: {:?}, caps: {:?}",
"Client {} connected, pid: {}, uid: {}, fd: {}, comm: {:?}",
id,
pid,
uid,
client.data.socket.raw(),
data.pid_info.comm,
data.effective_caps.get(),
);
client.data.property_changed(CL_CHANGED_NEW);
self.clients.borrow_mut().insert(client.data.id, client);
@ -274,9 +219,8 @@ impl Clients {
{
let clients = self.clients.borrow();
for client in clients.values() {
if client.data.effective_caps.get().contains(required_caps)
&& (!xwayland_only || client.data.is_xwayland)
{
let _ = required_caps;
if !xwayland_only || client.data.is_xwayland {
f(&client.data);
}
}
@ -336,8 +280,6 @@ pub struct Client {
shutdown: AsyncEvent,
pub tracker: Tracker<Client>,
pub is_xwayland: bool,
pub effective_caps: Cell<ClientCaps>,
pub bounding_caps_for_children: Cell<ClientCaps>,
pub last_enter_serial: Cell<Option<u64>>,
pub pid_info: PidInfo,
pub serials: RefCell<VecDeque<SerialRange>>,
@ -349,7 +291,7 @@ pub struct Client {
pub wire_scale: Cell<Option<i32>>,
pub focus_stealing_serial: Cell<Option<u64>>,
pub changed_properties: Cell<ClMatcherChange>,
pub destroyed: CopyHashMap<CritMatcherId, Weak<dyn CritDestroyListener<Rc<Self>>>>,
pub destroyed: CopyHashMap<CritMatcherId, Weak<dyn CritDestroyListener<Self>>>,
pub acceptor: Rc<AcceptorMetadata>,
}

View file

@ -1,9 +1,48 @@
pub mod cmm_description;
pub mod cmm_eotf;
pub mod cmm_luminance;
pub mod cmm_manager;
pub mod cmm_primaries;
pub mod cmm_render_intent;
#[cfg(test)]
mod cmm_tests;
pub mod cmm_transform;
pub mod cmm_description {
pub use jay_cmm::cmm_description::*;
}
pub mod cmm_eotf {
pub use jay_cmm::cmm_eotf::*;
}
pub mod cmm_luminance {
pub use jay_cmm::cmm_luminance::*;
}
pub mod cmm_manager {
pub use jay_cmm::cmm_manager::*;
}
pub mod cmm_primaries {
pub use jay_cmm::cmm_primaries::*;
}
pub mod cmm_render_intent {
use crate::{
ifs::color_management::{
ABSOLUTE_NO_ADAPTATION_SINCE, RENDER_INTENT_ABSOLUTE_NO_ADAPTATION,
RENDER_INTENT_PERCEPTUAL, RENDER_INTENT_RELATIVE, RENDER_INTENT_RELATIVE_BPC,
},
object::Version,
};
pub use jay_cmm::cmm_render_intent::*;
pub fn from_wayland(intent: u32, version: Version) -> Option<RenderIntent> {
let res = match intent {
RENDER_INTENT_PERCEPTUAL => RenderIntent::Perceptual,
RENDER_INTENT_RELATIVE => RenderIntent::Relative,
RENDER_INTENT_RELATIVE_BPC => RenderIntent::RelativeBpc,
RENDER_INTENT_ABSOLUTE_NO_ADAPTATION if version >= ABSOLUTE_NO_ADAPTATION_SINCE => {
RenderIntent::AbsoluteNoAdaptation
}
_ => return None,
};
Some(res)
}
}
pub mod cmm_transform {
pub use jay_cmm::cmm_transform::*;
}

View file

@ -1,89 +0,0 @@
use {
crate::{
cmm::{
cmm_eotf::Eotf,
cmm_luminance::{Luminance, TargetLuminance, white_balance},
cmm_manager::Shared,
cmm_primaries::{NamedPrimaries, Primaries},
cmm_render_intent::RenderIntent,
cmm_transform::{ColorMatrix, Local, Xyz, bradford_adjustment},
},
utils::ordered_float::F64,
},
std::rc::Rc,
};
linear_ids!(LinearColorDescriptionIds, LinearColorDescriptionId, u64);
linear_ids!(ColorDescriptionIds, ColorDescriptionId, u64);
#[derive(Debug)]
pub struct LinearColorDescription {
pub id: LinearColorDescriptionId,
pub primaries: Primaries,
pub xyz_from_local: ColorMatrix<Xyz, Local>,
pub local_from_xyz: ColorMatrix<Local, Xyz>,
pub luminance: Luminance,
pub target_primaries: Primaries,
pub target_luminance: TargetLuminance,
pub max_cll: Option<F64>,
pub max_fall: Option<F64>,
pub(super) shared: Rc<Shared>,
}
#[derive(Debug)]
pub struct ColorDescription {
pub id: ColorDescriptionId,
pub linear: Rc<LinearColorDescription>,
pub named_primaries: Option<NamedPrimaries>,
pub eotf: Eotf,
pub(super) shared: Rc<Shared>,
}
impl LinearColorDescription {
pub fn color_transform(&self, target: &Self, intent: RenderIntent) -> ColorMatrix {
let mut mat = target.local_from_xyz;
if self.luminance != target.luminance {
mat *= white_balance(
&self.luminance,
&target.luminance,
target.primaries.wp,
intent,
);
}
if self.primaries.wp != target.primaries.wp && intent.bradford_adjustment() {
mat *= bradford_adjustment(self.primaries.wp, target.primaries.wp);
}
mat * self.xyz_from_local
}
pub fn embeds_into(&self, target: &Self) -> bool {
if self.id == target.id {
return true;
}
if self.primaries != target.primaries {
return false;
}
if self.luminance != target.luminance {
return false;
}
true
}
}
impl ColorDescription {
pub fn embeds_into(&self, target: &Self) -> bool {
self.eotf == target.eotf && self.linear.embeds_into(&target.linear)
}
}
impl Drop for LinearColorDescription {
fn drop(&mut self) {
self.shared.dead_linear.fetch_add(1);
}
}
impl Drop for ColorDescription {
fn drop(&mut self) {
self.shared.dead_complete.fetch_add(1);
}
}

View file

@ -1,60 +0,0 @@
use crate::utils::ordered_float::F32;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Eotf {
Linear,
St2084Pq,
Bt1886(F32),
Gamma22,
Gamma24,
Gamma28,
St240,
Log100,
Log316,
St428,
Pow(EotfPow),
CompoundPower24,
}
const MUL: u32 = 10_000;
const MUL_F32: f32 = MUL as f32;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct EotfPow(pub u32);
impl EotfPow {
pub const MIN: Self = Self(10_000);
pub const LINEAR: Self = Self(10_000);
pub const GAMMA22: Self = Self(22_000);
pub const GAMMA24: Self = Self(24_000);
pub const GAMMA28: Self = Self(28_000);
pub const MAX: Self = Self(100_000);
pub fn eotf_f32(self) -> f32 {
self.0 as f32 / MUL_F32
}
pub fn inv_eotf_f32(self) -> f32 {
MUL_F32 / self.0 as f32
}
}
pub fn bt1886_eotf_args(c: F32) -> [f32; 4] {
let c = c.0;
let gamma = 1.0 / 2.4;
let a1 = 1.0 / (1.0 - c);
let a2 = 1.0 - c.powf(gamma);
let a3 = c.powf(gamma);
let a4 = c;
[a1, a2, a3, a4]
}
pub fn bt1886_inv_eotf_args(c: F32) -> [f32; 4] {
let c = c.0;
let gamma = 1.0 / 2.4;
let a1 = 1.0 / (1.0 - c.powf(gamma));
let a2 = 1.0 - c;
let a3 = c;
let a4 = c.powf(gamma);
[a1, a2, a3, a4]
}

View file

@ -1,97 +0,0 @@
use crate::{
cmm::{
cmm_render_intent::RenderIntent,
cmm_transform::{ColorMatrix, Xyz},
},
utils::ordered_float::F64,
};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct Luminance {
pub min: F64,
pub max: F64,
pub white: F64,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct TargetLuminance {
pub min: F64,
pub max: F64,
}
impl Luminance {
pub const SRGB: Self = Self {
min: F64(0.2),
max: F64(80.0),
white: F64(80.0),
};
pub const BT1886: Self = Self {
min: F64(0.01),
max: F64(100.0),
white: F64(100.0),
};
pub const ST2084_PQ: Self = Self {
min: F64(0.0),
max: F64(10000.0),
white: F64(203.0),
};
#[expect(dead_code)]
pub const HLG: Self = Self {
min: F64(0.005),
max: F64(1000.0),
white: F64(203.0),
};
pub const WINDOWS_SCRGB: Self = Self {
min: Self::ST2084_PQ.min,
max: Self::ST2084_PQ.max,
// This causes the white balance formula (with target ST2084_PQ) to simplify to
// `Y * 80 / 10000`, meaning that sRGB pure white maps to a luminance of
// 80 cd/m^2.
white: F64(Self::ST2084_PQ.white.0 / 80.0 * Self::ST2084_PQ.max.0),
};
}
impl Luminance {
pub fn to_target(&self) -> TargetLuminance {
TargetLuminance {
min: self.min,
max: self.max,
}
}
}
impl Default for Luminance {
fn default() -> Self {
Self::SRGB
}
}
#[expect(non_snake_case)]
pub fn white_balance(
from: &Luminance,
to: &Luminance,
w_to: (F64, F64),
intent: RenderIntent,
) -> ColorMatrix<Xyz, Xyz> {
let a = ((from.max - from.min) / (to.max - to.min) * (to.white - to.min)
/ (from.white - from.min))
.0;
let d = match intent.black_point_compensation() {
true => 0.0,
false => ((from.min - to.min) / (to.max - to.min)).0,
};
let s = a - d;
let (F64(x_to), F64(y_to)) = w_to;
let X_to = x_to / y_to;
let Y_to = 1.0;
let Z_to = (1.0 - x_to - y_to) / y_to;
ColorMatrix::new([
[s, 0.0, 0.0, d * X_to],
[0.0, s, 0.0, d * Y_to],
[0.0, 0.0, s, d * Z_to],
])
}

View file

@ -1,253 +0,0 @@
use {
crate::{
cmm::{
cmm_description::{
ColorDescription, ColorDescriptionIds, LinearColorDescription,
LinearColorDescriptionId, LinearColorDescriptionIds,
},
cmm_eotf::Eotf,
cmm_luminance::{Luminance, TargetLuminance},
cmm_primaries::{NamedPrimaries, Primaries},
},
utils::{copyhashmap::CopyHashMap, numcell::NumCell, ordered_float::F64},
},
std::rc::{Rc, Weak},
};
pub struct ColorManager {
linear_ids: LinearColorDescriptionIds,
linear_descriptions: CopyHashMap<LinearDescriptionKey, Weak<LinearColorDescription>>,
complete_descriptions: CopyHashMap<CompleteDescriptionKey, Weak<ColorDescription>>,
shared: Rc<Shared>,
srgb_gamma22: Rc<ColorDescription>,
srgb_linear: Rc<ColorDescription>,
windows_scrgb: Rc<ColorDescription>,
}
#[derive(Debug, Default)]
pub(super) struct Shared {
pub(super) dead_linear: NumCell<usize>,
pub(super) dead_complete: NumCell<usize>,
pub(super) complete_ids: ColorDescriptionIds,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
struct LinearDescriptionKey {
primaries: Primaries,
luminance: Luminance,
target_primaries: Primaries,
target_luminance: TargetLuminance,
max_cll: Option<F64>,
max_fall: Option<F64>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
struct CompleteDescriptionKey {
linear: LinearColorDescriptionId,
named_primaries: Option<NamedPrimaries>,
eotf: Eotf,
}
impl ColorManager {
pub fn new() -> Rc<Self> {
let linear_ids = LinearColorDescriptionIds::default();
let linear_descriptions = CopyHashMap::default();
let complete_descriptions = CopyHashMap::default();
let shared = Rc::new(Shared::default());
let _ = shared.complete_ids.next();
let srgb_gamma22 = get_description(
&shared,
&linear_descriptions,
&complete_descriptions,
&linear_ids,
Some(NamedPrimaries::Srgb),
Primaries::SRGB,
Luminance::SRGB,
Eotf::Gamma22,
Primaries::SRGB,
Luminance::SRGB.to_target(),
None,
None,
);
let srgb_linear = get_description2(
&shared,
&srgb_gamma22.linear,
&complete_descriptions,
Some(NamedPrimaries::Srgb),
Eotf::Linear,
);
let windows_scrgb = get_description(
&shared,
&linear_descriptions,
&complete_descriptions,
&linear_ids,
Some(NamedPrimaries::Srgb),
Primaries::SRGB,
Luminance::WINDOWS_SCRGB,
Eotf::Linear,
Primaries::BT2020,
Luminance::ST2084_PQ.to_target(),
None,
None,
);
Rc::new(Self {
linear_ids,
linear_descriptions,
complete_descriptions,
shared,
srgb_gamma22,
srgb_linear,
windows_scrgb,
})
}
pub fn srgb_gamma22(&self) -> &Rc<ColorDescription> {
&self.srgb_gamma22
}
pub fn srgb_linear(&self) -> &Rc<ColorDescription> {
&self.srgb_linear
}
pub fn windows_scrgb(&self) -> &Rc<ColorDescription> {
&self.windows_scrgb
}
pub fn get_description(
self: &Rc<Self>,
named_primaries: Option<NamedPrimaries>,
primaries: Primaries,
luminance: Luminance,
eotf: Eotf,
target_primaries: Primaries,
target_luminance: TargetLuminance,
max_cll: Option<F64>,
max_fall: Option<F64>,
) -> Rc<ColorDescription> {
get_description(
&self.shared,
&self.linear_descriptions,
&self.complete_descriptions,
&self.linear_ids,
named_primaries,
primaries,
luminance,
eotf,
target_primaries,
target_luminance,
max_cll,
max_fall,
)
}
pub fn get_with_tf(
self: &Rc<Self>,
cd: &Rc<ColorDescription>,
eotf: Eotf,
) -> Rc<ColorDescription> {
get_description2(
&self.shared,
&cd.linear,
&self.complete_descriptions,
cd.named_primaries,
eotf,
)
}
}
fn get_description(
shared: &Rc<Shared>,
linear_descriptions: &CopyHashMap<LinearDescriptionKey, Weak<LinearColorDescription>>,
complete_descriptions: &CopyHashMap<CompleteDescriptionKey, Weak<ColorDescription>>,
linear_ids: &LinearColorDescriptionIds,
named_primaries: Option<NamedPrimaries>,
primaries: Primaries,
luminance: Luminance,
eotf: Eotf,
target_primaries: Primaries,
target_luminance: TargetLuminance,
max_cll: Option<F64>,
max_fall: Option<F64>,
) -> Rc<ColorDescription> {
macro_rules! gc {
($d:ident, $i:expr) => {
if $d.len() > 16 && $i.get() * 2 > $d.len() {
$d.lock().retain(|_, d| d.strong_count() > 0);
$i.set(0);
}
};
}
gc!(linear_descriptions, &shared.dead_linear);
gc!(complete_descriptions, &shared.dead_complete);
let key = LinearDescriptionKey {
primaries,
luminance,
target_primaries,
target_luminance,
max_cll,
max_fall,
};
if let Some(d) = linear_descriptions.get(&key) {
if let Some(d) = d.upgrade() {
return get_description2(shared, &d, complete_descriptions, named_primaries, eotf);
}
shared.dead_linear.fetch_sub(1);
}
let (xyz_from_local, local_from_xyz) = primaries.matrices();
let d = Rc::new(LinearColorDescription {
id: linear_ids.next(),
primaries,
xyz_from_local,
local_from_xyz,
luminance,
target_primaries,
target_luminance,
max_cll,
max_fall,
shared: shared.clone(),
});
linear_descriptions.set(key, Rc::downgrade(&d));
let key = CompleteDescriptionKey {
linear: d.id,
named_primaries,
eotf,
};
let d = Rc::new(ColorDescription {
id: shared.complete_ids.next(),
linear: d,
named_primaries,
eotf,
shared: shared.clone(),
});
complete_descriptions.set(key, Rc::downgrade(&d));
d
}
fn get_description2(
shared: &Rc<Shared>,
ld: &Rc<LinearColorDescription>,
complete_descriptions: &CopyHashMap<CompleteDescriptionKey, Weak<ColorDescription>>,
named_primaries: Option<NamedPrimaries>,
eotf: Eotf,
) -> Rc<ColorDescription> {
let key = CompleteDescriptionKey {
linear: ld.id,
named_primaries,
eotf,
};
if let Some(d) = complete_descriptions.get(&key) {
if let Some(d) = d.upgrade() {
return d;
}
shared.dead_complete.fetch_sub(1);
}
let d = Rc::new(ColorDescription {
id: shared.complete_ids.next(),
linear: ld.clone(),
named_primaries,
eotf,
shared: shared.clone(),
});
complete_descriptions.set(key, Rc::downgrade(&d));
d
}

View file

@ -1,111 +0,0 @@
use {crate::utils::ordered_float::F64, std::hash::Hash};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum NamedPrimaries {
Srgb,
PalM,
Pal,
Ntsc,
GenericFilm,
Bt2020,
Cie1931Xyz,
DciP3,
DisplayP3,
AdobeRgb,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct Primaries {
pub r: (F64, F64),
pub g: (F64, F64),
pub b: (F64, F64),
pub wp: (F64, F64),
}
impl Primaries {
pub const SRGB: Self = Self {
r: (F64(0.64), F64(0.33)),
g: (F64(0.3), F64(0.6)),
b: (F64(0.15), F64(0.06)),
wp: (F64(0.3127), F64(0.3290)),
};
pub const PAL_M: Self = Self {
r: (F64(0.67), F64(0.33)),
g: (F64(0.21), F64(0.71)),
b: (F64(0.14), F64(0.08)),
wp: (F64(0.310), F64(0.316)),
};
pub const PAL: Self = Self {
r: (F64(0.64), F64(0.33)),
g: (F64(0.29), F64(0.60)),
b: (F64(0.15), F64(0.06)),
wp: (F64(0.3127), F64(0.3290)),
};
pub const NTSC: Self = Self {
r: (F64(0.630), F64(0.340)),
g: (F64(0.310), F64(0.595)),
b: (F64(0.155), F64(0.070)),
wp: (F64(0.3127), F64(0.3290)),
};
pub const GENERIC_FILM: Self = Self {
r: (F64(0.681), F64(0.319)),
g: (F64(0.243), F64(0.692)),
b: (F64(0.145), F64(0.049)),
wp: (F64(0.310), F64(0.316)),
};
pub const BT2020: Self = Self {
r: (F64(0.708), F64(0.292)),
g: (F64(0.170), F64(0.797)),
b: (F64(0.131), F64(0.046)),
wp: (F64(0.3127), F64(0.3290)),
};
pub const CIE1931_XYZ: Self = Self {
r: (F64(1.0), F64(0.0)),
g: (F64(0.0), F64(1.0)),
b: (F64(0.0), F64(0.0)),
wp: (F64(1.0 / 3.0), F64(1.0 / 3.0)),
};
pub const DCI_P3: Self = Self {
r: (F64(0.680), F64(0.320)),
g: (F64(0.265), F64(0.690)),
b: (F64(0.150), F64(0.060)),
wp: (F64(0.314), F64(0.351)),
};
pub const DISPLAY_P3: Self = Self {
r: (F64(0.680), F64(0.320)),
g: (F64(0.265), F64(0.690)),
b: (F64(0.150), F64(0.060)),
wp: (F64(0.3127), F64(0.3290)),
};
pub const ADOBE_RGB: Self = Self {
r: (F64(0.64), F64(0.33)),
g: (F64(0.21), F64(0.71)),
b: (F64(0.15), F64(0.06)),
wp: (F64(0.3127), F64(0.3290)),
};
}
impl NamedPrimaries {
pub const fn primaries(self) -> Primaries {
match self {
NamedPrimaries::Srgb => Primaries::SRGB,
NamedPrimaries::PalM => Primaries::PAL_M,
NamedPrimaries::Pal => Primaries::PAL,
NamedPrimaries::Ntsc => Primaries::NTSC,
NamedPrimaries::GenericFilm => Primaries::GENERIC_FILM,
NamedPrimaries::Bt2020 => Primaries::BT2020,
NamedPrimaries::Cie1931Xyz => Primaries::CIE1931_XYZ,
NamedPrimaries::DciP3 => Primaries::DCI_P3,
NamedPrimaries::DisplayP3 => Primaries::DISPLAY_P3,
NamedPrimaries::AdobeRgb => Primaries::ADOBE_RGB,
}
}
}

View file

@ -1,49 +0,0 @@
use crate::{
ifs::color_management::{
ABSOLUTE_NO_ADAPTATION_SINCE, RENDER_INTENT_ABSOLUTE_NO_ADAPTATION,
RENDER_INTENT_PERCEPTUAL, RENDER_INTENT_RELATIVE, RENDER_INTENT_RELATIVE_BPC,
},
object::Version,
};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)]
pub enum RenderIntent {
#[default]
Perceptual,
Relative,
RelativeBpc,
AbsoluteNoAdaptation,
}
impl RenderIntent {
pub fn from_wayland(intent: u32, version: Version) -> Option<Self> {
let res = match intent {
RENDER_INTENT_PERCEPTUAL => Self::Perceptual,
RENDER_INTENT_RELATIVE => Self::Relative,
RENDER_INTENT_RELATIVE_BPC => Self::RelativeBpc,
RENDER_INTENT_ABSOLUTE_NO_ADAPTATION if version >= ABSOLUTE_NO_ADAPTATION_SINCE => {
Self::AbsoluteNoAdaptation
}
_ => return None,
};
Some(res)
}
pub fn black_point_compensation(self) -> bool {
match self {
RenderIntent::Perceptual => true,
RenderIntent::RelativeBpc => true,
RenderIntent::Relative => false,
RenderIntent::AbsoluteNoAdaptation => false,
}
}
pub fn bradford_adjustment(self) -> bool {
match self {
RenderIntent::Perceptual => true,
RenderIntent::RelativeBpc => true,
RenderIntent::Relative => true,
RenderIntent::AbsoluteNoAdaptation => false,
}
}
}

View file

@ -1,214 +0,0 @@
mod matrices {
use crate::{cmm::cmm_primaries::Primaries, utils::ordered_float::F64};
fn check(primaries: Primaries, expected: [[f64; 4]; 3]) {
let (ltg, gtl) = primaries.matrices();
println!("{:#?}", ltg);
assert!((ltg.0[0][0].0 - expected[0][0]).abs() < 0.001);
assert!((ltg.0[0][1].0 - expected[0][1]).abs() < 0.001);
assert!((ltg.0[0][2].0 - expected[0][2]).abs() < 0.001);
assert!((ltg.0[0][3].0 - expected[0][3]).abs() < 0.001);
assert!((ltg.0[1][0].0 - expected[1][0]).abs() < 0.001);
assert!((ltg.0[1][1].0 - expected[1][1]).abs() < 0.001);
assert!((ltg.0[1][2].0 - expected[1][2]).abs() < 0.001);
assert!((ltg.0[1][3].0 - expected[1][3]).abs() < 0.001);
assert!((ltg.0[2][0].0 - expected[2][0]).abs() < 0.001);
assert!((ltg.0[2][1].0 - expected[2][1]).abs() < 0.001);
assert!((ltg.0[2][2].0 - expected[2][2]).abs() < 0.001);
assert!((ltg.0[2][3].0 - expected[2][3]).abs() < 0.001);
let roundtrip = gtl * ltg;
assert!((roundtrip.0[0][0].0 - 1.0).abs() < 0.001);
assert!((roundtrip.0[0][1].0 - 0.0).abs() < 0.001);
assert!((roundtrip.0[0][2].0 - 0.0).abs() < 0.001);
assert!((roundtrip.0[0][3].0 - 0.0).abs() < 0.001);
assert!((roundtrip.0[1][0].0 - 0.0).abs() < 0.001);
assert!((roundtrip.0[1][1].0 - 1.0).abs() < 0.001);
assert!((roundtrip.0[1][2].0 - 0.0).abs() < 0.001);
assert!((roundtrip.0[1][3].0 - 0.0).abs() < 0.001);
assert!((roundtrip.0[2][0].0 - 0.0).abs() < 0.001);
assert!((roundtrip.0[2][1].0 - 0.0).abs() < 0.001);
assert!((roundtrip.0[2][2].0 - 1.0).abs() < 0.001);
assert!((roundtrip.0[2][3].0 - 0.0).abs() < 0.001);
}
#[test]
fn srgb() {
check(
Primaries::SRGB,
[
[0.4124564, 0.3575761, 0.1804375, 0.0],
[0.2126729, 0.7151522, 0.0721750, 0.0],
[0.0193339, 0.1191920, 0.9503041, 0.0],
],
);
}
#[test]
fn cie1931_xyz() {
check(
Primaries::CIE1931_XYZ,
[
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
],
);
}
#[test]
fn adobe_rgb() {
check(
Primaries::ADOBE_RGB,
[
[0.5767309, 0.1855540, 0.1881852, 0.0],
[0.2973769, 0.6273491, 0.0752741, 0.0],
[0.0270343, 0.0706872, 0.9911085, 0.0],
],
);
}
#[test]
fn apple_rgb() {
check(
Primaries {
r: (F64(0.625), F64(0.34)),
g: (F64(0.28), F64(0.595)),
b: (F64(0.155), F64(0.07)),
wp: (F64(0.31271), F64(0.32902)),
},
[
[0.4497288, 0.3162486, 0.1844926, 0.0],
[0.2446525, 0.6720283, 0.0833192, 0.0],
[0.0251848, 0.1411824, 0.9224628, 0.0],
],
);
}
#[test]
fn bt2020() {
check(
Primaries::BT2020,
[
[0.636958, 0.144617, 0.168881, 0.0],
[0.262700, 0.677998, 0.059302, 0.0],
[0.000000, 0.028073, 1.060985, 0.0],
],
);
}
#[test]
fn pal() {
check(
Primaries::PAL,
[
[0.4306190, 0.3415419, 0.1783091, 0.0],
[0.2220379, 0.7066384, 0.0713236, 0.0],
[0.0201853, 0.1295504, 0.9390944, 0.0],
],
);
}
#[test]
fn dci_p3() {
check(
Primaries::DCI_P3,
[
[0.445170, 0.277134, 0.172283, 0.0],
[0.209492, 0.721595, 0.068913, 0.0],
[-0.000000, 0.047061, 0.907355, 0.0],
],
);
}
#[test]
fn display_p3() {
check(
Primaries::DISPLAY_P3,
[
[0.486571, 0.265668, 0.198217, 0.0],
[0.228975, 0.691739, 0.079287, 0.0],
[-0.000000, 0.045113, 1.043944, 0.0],
],
);
}
}
mod transforms {
use crate::cmm::{
cmm_eotf::Eotf, cmm_luminance::Luminance, cmm_manager::ColorManager,
cmm_primaries::Primaries, cmm_render_intent::RenderIntent,
};
fn check(p1: Primaries, p2: Primaries, expected: [[f64; 4]; 3]) {
let manager = ColorManager::new();
let d = |p| {
manager.get_description(
None,
p,
Luminance::SRGB,
Eotf::Linear,
p,
Luminance::SRGB.to_target(),
None,
None,
)
};
let d1 = d(p1);
let d2 = d(p2);
let m = d1
.linear
.color_transform(&d2.linear, RenderIntent::Perceptual);
println!("{:#?}", m);
assert!((m.0[0][0].0 - expected[0][0]).abs() < 0.001);
assert!((m.0[0][1].0 - expected[0][1]).abs() < 0.001);
assert!((m.0[0][2].0 - expected[0][2]).abs() < 0.001);
assert!((m.0[0][3].0 - expected[0][3]).abs() < 0.001);
assert!((m.0[1][0].0 - expected[1][0]).abs() < 0.001);
assert!((m.0[1][1].0 - expected[1][1]).abs() < 0.001);
assert!((m.0[1][2].0 - expected[1][2]).abs() < 0.001);
assert!((m.0[1][3].0 - expected[1][3]).abs() < 0.001);
assert!((m.0[2][0].0 - expected[2][0]).abs() < 0.001);
assert!((m.0[2][1].0 - expected[2][1]).abs() < 0.001);
assert!((m.0[2][2].0 - expected[2][2]).abs() < 0.001);
assert!((m.0[2][3].0 - expected[2][3]).abs() < 0.001);
}
#[test]
fn srgb_to_bt2020() {
check(
Primaries::SRGB,
Primaries::BT2020,
[
[0.627404, 0.329283, 0.043313, 0.0],
[0.069097, 0.919540, 0.011362, 0.0],
[0.016391, 0.088013, 0.895595, 0.0],
],
)
}
#[test]
fn bt2020_to_srgb() {
check(
Primaries::BT2020,
Primaries::SRGB,
[
[1.660491, -0.587641, -0.072850, 0.0],
[-0.124550, 1.132900, -0.008349, 0.0],
[-0.018151, -0.100579, 1.118730, 0.0],
],
)
}
#[test]
fn srgb_to_dci_p3() {
check(
Primaries::SRGB,
Primaries::DCI_P3,
[
[0.868580, 0.128919, 0.002501, 0.0],
[0.034540, 0.961811, 0.003648, 0.0],
[0.016771, 0.071040, 0.912189, 0.0],
],
)
}
}

View file

@ -1,262 +0,0 @@
use {
crate::{
cmm::{cmm_eotf::Eotf, cmm_primaries::Primaries},
gfx_api::AlphaMode,
theme::Color,
utils::ordered_float::F64,
},
std::{
fmt,
fmt::{Debug, Formatter},
hash::{Hash, Hasher},
marker::PhantomData,
ops::{Mul, MulAssign},
},
};
pub struct ColorMatrix<To = Local, From = Local>(pub [[F64; 4]; 3], PhantomData<(To, From)>);
#[derive(Copy, Clone)]
pub struct Local;
#[derive(Copy, Clone)]
pub struct Xyz;
#[derive(Copy, Clone)]
pub struct Bradford;
impl<T, U> Copy for ColorMatrix<T, U> {}
impl<T, U> Clone for ColorMatrix<T, U> {
fn clone(&self) -> Self {
*self
}
}
impl<T, U> PartialEq<Self> for ColorMatrix<T, U> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<T, U> Eq for ColorMatrix<T, U> {}
impl<T, U> Hash for ColorMatrix<T, U> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl<T, U> Debug for ColorMatrix<T, U> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ColorMatrix")
.field(&format_matrix(&self.0))
.finish()
}
}
fn format_matrix<'a>(m: &'a [[F64; 4]; 3]) -> impl Debug + use<'a> {
fmt::from_fn(move |f| {
let iter = m
.iter()
.copied()
.chain(Some([F64(0.0), F64(0.0), F64(0.0), F64(1.0)]))
.enumerate();
if f.alternate() {
for (idx, row) in iter {
if idx > 0 {
f.write_str("\n")?;
}
write!(
f,
"{:7.4} {:7.4} {:7.4} {:7.4}",
row[0], row[1], row[2], row[3]
)?;
}
} else {
f.write_str("[")?;
for (idx, row) in iter {
if idx > 0 {
f.write_str(", ")?;
}
write!(
f,
"[{:.4}, {:.4}, {:.4}, {:.4}]",
row[0], row[1], row[2], row[3]
)?;
}
f.write_str("]")?;
}
Ok(())
})
}
impl<T, U, V> Mul<ColorMatrix<U, T>> for ColorMatrix<V, U> {
type Output = ColorMatrix<V, T>;
fn mul(self, rhs: ColorMatrix<U, T>) -> Self::Output {
let a = &self.0;
let b = &rhs.0;
macro_rules! mul {
($ar:expr, $bc:expr) => {
a[$ar][0] * b[0][$bc] + a[$ar][1] * b[1][$bc] + a[$ar][2] * b[2][$bc]
};
}
let m = [
[mul!(0, 0), mul!(0, 1), mul!(0, 2), mul!(0, 3) + a[0][3]],
[mul!(1, 0), mul!(1, 1), mul!(1, 2), mul!(1, 3) + a[1][3]],
[mul!(2, 0), mul!(2, 1), mul!(2, 2), mul!(2, 3) + a[2][3]],
];
ColorMatrix(m, PhantomData)
}
}
impl<U, V> MulAssign<ColorMatrix<U, U>> for ColorMatrix<V, U> {
fn mul_assign(&mut self, rhs: ColorMatrix<U, U>) {
*self = *self * rhs;
}
}
impl<T, U> Mul<[f64; 3]> for ColorMatrix<T, U> {
type Output = [f64; 3];
fn mul(self, rhs: [f64; 3]) -> Self::Output {
let a = &self.0;
macro_rules! mul {
($ar:expr) => {
a[$ar][0].0 * rhs[0] + a[$ar][1].0 * rhs[1] + a[$ar][2].0 * rhs[2]
};
}
[mul!(0), mul!(1), mul!(2)]
}
}
impl<T, U> Mul<Color> for ColorMatrix<T, U> {
type Output = Color;
fn mul(self, rhs: Color) -> Self::Output {
let mut rgba = rhs.to_array(Eotf::Linear);
let a = rgba[3];
if a < 1.0 && a > 0.0 {
for c in &mut rgba[..3] {
*c /= a;
}
}
let [r, g, b] = self * [rgba[0] as f64, rgba[1] as f64, rgba[2] as f64];
Color::new(
Eotf::Linear,
AlphaMode::Straight,
r as f32,
g as f32,
b as f32,
a,
)
}
}
impl<T, U> ColorMatrix<T, U> {
pub const fn new(m: [[f64; 4]; 3]) -> Self {
let m = [
[F64(m[0][0]), F64(m[0][1]), F64(m[0][2]), F64(m[0][3])],
[F64(m[1][0]), F64(m[1][1]), F64(m[1][2]), F64(m[1][3])],
[F64(m[2][0]), F64(m[2][1]), F64(m[2][2]), F64(m[2][3])],
];
Self(m, PhantomData)
}
pub const fn to_f32(&self) -> [[f32; 4]; 4] {
let m = &self.0;
macro_rules! map {
($r:expr, $c:expr) => {
m[$r][$c].0 as f32
};
}
[
[map!(0, 0), map!(0, 1), map!(0, 2), map!(0, 3)],
[map!(1, 0), map!(1, 1), map!(1, 2), map!(1, 3)],
[map!(2, 0), map!(2, 1), map!(2, 2), map!(2, 3)],
[0.0, 0.0, 0.0, 1.0],
]
}
}
impl ColorMatrix<Bradford, Xyz> {
const BFD: Self = Self::new([
[0.8951, 0.2664, -0.1614, 0.0],
[-0.7502, 1.7135, 0.0367, 0.0],
[0.0389, -0.0685, 1.0296, 0.0],
]);
}
impl ColorMatrix<Xyz, Bradford> {
const BFD_INV: Self = Self::new([
[0.9870, -0.1471, 0.1600, 0.0],
[0.4323, 0.5184, 0.0493, 0.0],
[-0.0085, 0.04, 0.9685, 0.0],
]);
}
#[expect(non_snake_case)]
pub fn bradford_adjustment(w_from: (F64, F64), w_to: (F64, F64)) -> ColorMatrix<Xyz, Xyz> {
let (F64(x_from), F64(y_from)) = w_from;
let (F64(x_to), F64(y_to)) = w_to;
let X_from = x_from / y_from;
let Z_from = (1.0 - x_from - y_from) / y_from;
let X_to = x_to / y_to;
let Z_to = (1.0 - x_to - y_to) / y_to;
let [R_from, G_from, B_from] = ColorMatrix::BFD * [X_from, 1.0, Z_from];
let [R_to, G_to, B_to] = ColorMatrix::BFD * [X_to, 1.0, Z_to];
let adj = ColorMatrix::new([
[R_to / R_from, 0.0, 0.0, 0.0],
[0.0, G_to / G_from, 0.0, 0.0],
[0.0, 0.0, B_to / B_from, 0.0],
]);
ColorMatrix::BFD_INV * adj * ColorMatrix::BFD
}
impl Primaries {
#[expect(non_snake_case)]
pub const fn matrices(&self) -> (ColorMatrix<Xyz, Local>, ColorMatrix<Local, Xyz>) {
let (F64(xw), F64(yw)) = self.wp;
let Xw = xw / yw;
let Zw = (1.0 - xw - yw) / yw;
let (F64(xr), F64(yr)) = self.r;
let (F64(xg), F64(yg)) = self.g;
let (F64(xb), F64(yb)) = self.b;
let zr = 1.0 - xr - yr;
let zg = 1.0 - xg - yg;
let zb = 1.0 - xb - yb;
let srx = yg * zb - zg * yb;
let sry = zg * xb - xg * zb;
let srz = xg * yb - yg * xb;
let sgx = zr * yb - yr * zb;
let sgz = yr * xb - xr * yb;
let sgy = xr * zb - zr * xb;
let sbx = yr * zg - zr * yg;
let sby = zr * xg - xr * zg;
let sbz = xr * yg - yr * xg;
let det = srz + sgz + sbz;
let sr = srx * Xw + sry + srz * Zw;
let sg = sgx * Xw + sgy + sgz * Zw;
let sb = sbx * Xw + sby + sbz * Zw;
let det_inv = 1.0 / det;
let sr_inv = 1.0 / sr;
let sg_inv = 1.0 / sg;
let sb_inv = 1.0 / sb;
let srp = sr * det_inv;
let sgp = sg * det_inv;
let sbp = sb * det_inv;
let XYZ_from_local = [
[srp * xr, sgp * xg, sbp * xb, 0.0],
[srp * yr, sgp * yg, sbp * yb, 0.0],
[srp * zr, sgp * zg, sbp * zb, 0.0],
];
let local_from_XYZ = [
[srx * sr_inv, sry * sr_inv, srz * sr_inv, 0.0],
[sgx * sg_inv, sgy * sg_inv, sgz * sg_inv, 0.0],
[sbx * sb_inv, sby * sb_inv, sbz * sb_inv, 0.0],
];
(
ColorMatrix::new(XYZ_from_local),
ColorMatrix::new(local_from_XYZ),
)
}
}

View file

@ -50,7 +50,6 @@ use {
leaks,
logger::Logger,
output_schedule::OutputSchedule,
portal::{self, PortalStartup},
pr_caps::{PrCapsThread, pr_caps},
scale::Scale,
sighand::{self, SighandError},
@ -121,19 +120,10 @@ pub fn start_compositor(global: GlobalArgs, args: RunArgs) {
None
};
let forker = create_forker(reaper_pid);
let portal = portal::run_from_compositor(global.log_level);
enable_profiler();
let logger = Logger::install_compositor(global.log_level);
let portal = match portal {
Ok(p) => Some(p),
Err(e) => {
log::error!("Could not spawn portal: {}", ErrorFmt(e));
None
}
};
let res = start_compositor2(
Some(forker),
portal,
Some(logger.clone()),
args,
None,
@ -152,7 +142,7 @@ pub fn start_compositor(global: GlobalArgs, args: RunArgs) {
#[cfg(feature = "it")]
pub fn start_compositor_for_test(future: TestFuture) -> Result<(), CompositorError> {
let res = start_compositor2(None, None, None, RunArgs::default(), Some(future), None);
let res = start_compositor2(None, None, RunArgs::default(), Some(future), None);
leaks::log_leaked();
res
}
@ -194,7 +184,6 @@ pub type TestFuture = Box<dyn Fn(&Rc<State>) -> Box<dyn Future<Output = ()>>>;
fn start_compositor2(
forker: Option<Rc<ForkerProxy>>,
portal: Option<PortalStartup>,
logger: Option<Arc<Logger>>,
run_args: RunArgs,
test_future: Option<TestFuture>,
@ -308,7 +297,6 @@ fn start_compositor2(
idle_inhibitor_ids: Default::default(),
run_toplevel,
config_dir: explicit_config_dir.or_else(config_dir),
config_file_id: NumCell::new(1),
tracker: Default::default(),
data_offer_ids: Default::default(),
data_source_ids: Default::default(),
@ -342,7 +330,6 @@ fn start_compositor2(
keyboard_state_ids: Default::default(),
physical_keyboard_ids: Default::default(),
security_context_acceptors: Default::default(),
tagged_acceptors: Default::default(),
cursor_user_group_ids: Default::default(),
cursor_user_ids: Default::default(),
cursor_user_groups: Default::default(),
@ -420,13 +407,6 @@ fn start_compositor2(
forker.setenv(key.as_bytes(), val.as_bytes());
}
}
let mut _portal = None;
if let (Some(portal), Some(logger)) = (portal, &logger) {
_portal = Some(engine.spawn(
"portal",
portal.spawn(engine.clone(), ring.clone(), logger.clone()),
));
}
let _compositor = engine.spawn("compositor", start_compositor3(state.clone(), test_future));
ring.run()?;
state.clear();
@ -487,14 +467,7 @@ fn load_config(
if for_test {
return ConfigProxy::for_test(state);
}
match ConfigProxy::from_config_dir(state) {
Ok(c) => c,
Err(e) => {
log::warn!("Could not load config.so: {}", ErrorFmt(e));
log::warn!("Using default config");
ConfigProxy::default(state)
}
}
ConfigProxy::default(state)
}
fn start_global_event_handlers(state: &Rc<State>) -> Vec<SpawnedFuture<()>> {

View file

@ -5,18 +5,14 @@ use crate::it::test_config::TEST_CONFIG_ENTRY;
use {
crate::{
backend::{ConnectorId, DrmDeviceId, InputDeviceId},
client::{Client, ClientCaps},
config::handler::ConfigProxyHandler,
ifs::wl_seat::SeatId,
state::State,
tree::{TileState, ToplevelData, ToplevelIdentifier},
utils::{
clonecell::CloneCell,
nice::{JAY_NO_REALTIME, dont_allow_config_so},
numcell::NumCell,
ptr_ext::PtrExt,
unlink_on_drop::UnlinkOnDrop,
xrd::xrd,
},
},
bincode::Options,
@ -30,27 +26,9 @@ use {
video::{Connector, DrmDevice},
window::{self},
},
libloading::Library,
std::{cell::Cell, io, mem, path::Path, ptr, rc::Rc},
thiserror::Error,
std::{cell::Cell, mem, ptr, rc::Rc},
};
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("Could not load the config library")]
CouldNotLoadLibrary(#[source] libloading::Error),
#[error("Config library does not contain the entry symbol")]
LibraryDoesNotContainEntry(#[source] libloading::Error),
#[error("Could not determine the config directory")]
ConfigDirNotSet,
#[error("Could not copy the config file")]
CopyConfigFile(#[source] io::Error),
#[error("XDG_RUNTIME_DIR is not set")]
XrdNotSet,
#[error("Custom config.so is not permitted")]
NotPermitted,
}
pub struct ConfigProxy {
handler: CloneCell<Option<Rc<ConfigProxyHandler>>>,
}
@ -181,16 +159,6 @@ impl ConfigProxy {
self.handler.get()?.initial_tile_state(data)
}
pub fn update_capabilities(
&self,
data: &Rc<Client>,
bounding_caps: ClientCaps,
set_bounding_caps: bool,
) {
if let Some(handler) = self.handler.get() {
handler.update_capabilities(data, bounding_caps, set_bounding_caps);
}
}
}
impl Drop for ConfigProxy {
@ -215,18 +183,11 @@ unsafe extern "C" fn default_client_init(
}
impl ConfigProxy {
fn new(
lib: Option<Library>,
entry: &ConfigEntry,
state: &Rc<State>,
path: Option<String>,
) -> Self {
fn new(entry: &ConfigEntry, state: &Rc<State>) -> Self {
let version = entry.version.min(VERSION);
let data = Rc::new(ConfigProxyHandler {
path,
client_data: Cell::new(ptr::null()),
dropped: Cell::new(false),
_lib: lib,
_version: version,
unref: entry.unref,
handle_msg: entry.handle_msg,
@ -249,8 +210,6 @@ impl ConfigProxy {
client_matchers: Default::default(),
client_matcher_cache: Default::default(),
client_matcher_leafs: Default::default(),
client_matcher_capabilities: Default::default(),
client_matcher_bounding_capabilities: Default::default(),
window_matcher_ids: NumCell::new(1),
window_matchers: Default::default(),
window_matcher_cache: Default::default(),
@ -291,75 +250,12 @@ impl ConfigProxy {
unref: jay_config::_private::client::unref,
handle_msg: jay_config::_private::client::handle_msg,
};
Self::new(None, &entry, state, None)
Self::new(&entry, state)
}
#[cfg(feature = "it")]
pub fn for_test(state: &Rc<State>) -> Self {
Self::new(None, &TEST_CONFIG_ENTRY, state, None)
}
pub fn from_config_dir(state: &Rc<State>) -> Result<Self, ConfigError> {
if dont_allow_config_so() {
if have_config_so(state.config_dir.as_deref()) {
log::warn!("Not loading config.so because");
log::warn!(" 1. Jay was started with CAP_SYS_NICE");
log::warn!(" 2. Jay was not started with {}=1", JAY_NO_REALTIME);
log::warn!(" 3. The scheduler was elevated to SCHED_RR");
log::warn!(
" 4. Jay was not compiled with {}=1",
jay_allow_realtime_config_so!(),
);
}
return Err(ConfigError::NotPermitted);
}
let dir = match state.config_dir.as_deref() {
Some(d) => d,
_ => return Err(ConfigError::ConfigDirNotSet),
};
let file = format!("{}/{CONFIG_SO}", dir);
unsafe { Self::from_file(&file, state) }
}
pub unsafe fn from_file(path: &str, state: &Rc<State>) -> Result<Self, ConfigError> {
// Here we have to do a bit of a dance to support reloading. glibc will
// never load a library twice unless it has been unloaded in between.
// glibc identifies libraries by their file path and by their inode
// number. If either of those match, glibc considers the libraries
// identical. If the inode has not changed then this is not a problem
// for us since we don't want glibc to do any unnecessary work.
// However, if the user has created a new config with a new inode, then
// glibc will still not reload the library if we try to load it from
// the canonical location ~/.config/jay/config.so since it already has
// a library with that path loaded. To work around this, create a
// temporary copy with an incrementing number and load the library
// from there.
let xrd = match xrd() {
Some(x) => x,
_ => return Err(ConfigError::XrdNotSet),
};
let copy = format!(
"{}/.jay_config.so.{}.{}",
xrd,
uapi::getpid(),
state.config_file_id.fetch_add(1)
);
let _ = uapi::unlink(copy.as_str());
if let Err(e) = std::fs::copy(path, &copy) {
return Err(ConfigError::CopyConfigFile(e));
}
let unlink = UnlinkOnDrop(&copy);
let lib = match unsafe { Library::new(&copy) } {
Ok(l) => l,
Err(e) => return Err(ConfigError::CouldNotLoadLibrary(e)),
};
let entry = unsafe { lib.get::<&'static ConfigEntry>(b"JAY_CONFIG_ENTRY_V1\0") };
let entry = match entry {
Ok(e) => *e,
Err(e) => return Err(ConfigError::LibraryDoesNotContainEntry(e)),
};
mem::forget(unlink);
Ok(Self::new(Some(lib), entry, state, Some(copy)))
Self::new(&TEST_CONFIG_ENTRY, state)
}
}
@ -388,15 +284,3 @@ pub struct InvokedShortcut {
pub effective_mods: Modifiers,
pub sym: KeySym,
}
const CONFIG_SO: &str = "config.so";
pub fn have_config_so(config_dir: Option<&str>) -> bool {
let Some(dir) = config_dir else {
return false;
};
let mut dir = dir.to_owned();
dir.push_str("/");
dir.push_str(CONFIG_SO);
Path::new(&dir).exists()
}

View file

@ -6,9 +6,9 @@ use {
InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceId,
transaction::BackendConnectorTransactionError,
},
client::{CAP_JAY_COMPOSITOR, Client, ClientCaps, ClientId},
client::{Client, ClientId},
cmm::cmm_eotf::Eotf,
compositor::{MAX_EXTENTS, WAYLAND_DISPLAY},
compositor::MAX_EXTENTS,
criteria::{
CritLiteralOrRegex, CritMgrExt, CritTarget, CritUpstreamNode,
clm::ClmLeafMatcher,
@ -25,7 +25,6 @@ use {
output_schedule::map_cursor_hz,
scale::Scale,
state::{ConnectorData, DeviceHandlerData, DrmDevData, OutputData, State},
tagged_acceptor::TaggedAcceptorError,
theme::{ThemeColor, ThemeSized},
tree::{
ContainerSplit, OutputNode, TearingMode, TileState, ToplevelData, ToplevelIdentifier,
@ -37,7 +36,7 @@ use {
copyhashmap::CopyHashMap,
errorfmt::ErrorFmt,
numcell::NumCell,
oserror::{OsError, OsErrorExt},
oserror::OsErrorExt,
stack::Stack,
timer::{TimerError, TimerFd},
},
@ -50,7 +49,7 @@ use {
ipc::{ClientMessage, Response, ServerMessage, WorkspaceSource},
},
Axis, Direction, Workspace,
client::{Client as ConfigClient, ClientCapabilities, ClientMatcher},
client::{Client as ConfigClient, ClientMatcher},
input::{
FallbackOutputMode, FocusFollowsMouseMode, InputDevice, LayerDirection, Seat, Timeline,
acceleration::{ACCEL_PROFILE_ADAPTIVE, ACCEL_PROFILE_FLAT, AccelProfile},
@ -76,7 +75,6 @@ use {
xwayland::XScalingMode,
},
kbvm::Keycode,
libloading::Library,
log::Level,
regex::Regex,
std::{
@ -92,10 +90,8 @@ use {
};
pub(super) struct ConfigProxyHandler {
pub path: Option<String>,
pub client_data: Cell<*const u8>,
pub dropped: Cell<bool>,
pub _lib: Option<Library>,
pub _version: u32,
pub unref: unsafe extern "C" fn(data: *const u8),
pub handle_msg: unsafe extern "C" fn(data: *const u8, msg: *const u8, size: usize),
@ -121,24 +117,9 @@ pub(super) struct ConfigProxyHandler {
pub client_matcher_ids: NumCell<u64>,
pub client_matchers:
CopyHashMap<ClientMatcher, Rc<CachedCriterion<ClientCriterionIpc, Rc<Client>>>>,
pub client_matcher_cache: CriterionCache<ClientCriterionIpc, Rc<Client>>,
CopyHashMap<ClientMatcher, Rc<CachedCriterion<ClientCriterionIpc, Client>>>,
pub client_matcher_cache: CriterionCache<ClientCriterionIpc, Client>,
pub client_matcher_leafs: CopyHashMap<ClientMatcher, Rc<ClmLeafMatcher>>,
pub client_matcher_capabilities: CopyHashMap<
ClientMatcher,
(
Rc<CachedCriterion<ClientCriterionIpc, Rc<Client>>>,
ClientCaps,
),
>,
pub client_matcher_bounding_capabilities: CopyHashMap<
ClientMatcher,
(
Rc<CachedCriterion<ClientCriterionIpc, Rc<Client>>>,
ClientCaps,
),
>,
pub window_matcher_ids: NumCell<u64>,
pub window_matchers:
CopyHashMap<WindowMatcher, Rc<CachedCriterion<WindowCriterionIpc, ToplevelData>>>,
@ -218,11 +199,6 @@ impl ConfigProxyHandler {
self.window_matcher_leafs.clear();
self.window_matchers.clear();
if let Some(path) = &self.path
&& let Err(e) = uapi::unlink(path.as_str())
{
log::error!("Could not unlink {}: {}", path, ErrorFmt(OsError(e.0)));
}
}
pub fn send(&self, msg: &ServerMessage) {
@ -1185,19 +1161,6 @@ impl ConfigProxyHandler {
self.state.set_color_management_enabled(enabled);
}
fn handle_get_socket_path(&self) {
match self.state.acceptor.get() {
Some(a) => {
self.respond(Response::GetSocketPath {
path: a.socket_name().to_string(),
});
}
_ => {
log::warn!("There is no acceptor");
}
}
}
fn handle_connector_connected(&self, connector: Connector) -> Result<(), CphError> {
let connector = self.get_connector(connector)?;
self.respond(Response::ConnectorConnected {
@ -1937,18 +1900,9 @@ impl ConfigProxyHandler {
&self,
prog: &str,
args: Vec<String>,
mut env: Vec<(String, String)>,
env: Vec<(String, String)>,
fds: Vec<(i32, i32)>,
tag: Option<&str>,
) -> Result<(), CphError> {
if let Some(tag) = tag {
let display = self
.state
.tagged_acceptors
.get(&self.state, tag)
.map_err(CphError::CreateTaggedAcceptor)?;
env.push((WAYLAND_DISPLAY.to_string(), display.to_string()));
}
let fds: Vec<_> = fds
.into_iter()
.map(|(a, b)| (a, Rc::new(OwnedFd::new(b))))
@ -2125,7 +2079,7 @@ impl ConfigProxyHandler {
fn get_client_matcher(
&self,
matcher: ClientMatcher,
) -> Result<Rc<CachedCriterion<ClientCriterionIpc, Rc<Client>>>, CphError> {
) -> Result<Rc<CachedCriterion<ClientCriterionIpc, Client>>, CphError> {
self.client_matchers
.get(&matcher)
.ok_or(CphError::ClientMatcherDoesNotExist(matcher))
@ -2226,7 +2180,6 @@ impl ConfigProxyHandler {
}
ClientCriterionStringField::Comm => mgr.comm(needle),
ClientCriterionStringField::Exe => mgr.exe(needle),
ClientCriterionStringField::Tag => mgr.tag(needle),
}
}
ClientCriterionIpc::Sandboxed => mgr.sandboxed(),
@ -2249,8 +2202,6 @@ impl ConfigProxyHandler {
fn handle_destroy_client_matcher(&self, matcher: ClientMatcher) {
self.client_matchers.remove(&matcher);
self.client_matcher_leafs.remove(&matcher);
self.client_matcher_capabilities.remove(&matcher);
self.client_matcher_bounding_capabilities.remove(&matcher);
}
fn handle_enable_client_matcher_events(
@ -2862,28 +2813,6 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_set_client_matcher_capabilities(
&self,
matcher: ClientMatcher,
caps: ClientCapabilities,
) -> Result<(), CphError> {
let m = self.get_client_matcher(matcher)?;
self.client_matcher_capabilities
.set(matcher, (m, caps.to_client_caps()));
Ok(())
}
fn handle_set_client_matcher_bounding_capabilities(
&self,
matcher: ClientMatcher,
caps: ClientCapabilities,
) -> Result<(), CphError> {
let m = self.get_client_matcher(matcher)?;
self.client_matcher_bounding_capabilities
.set(matcher, (m, caps.to_client_caps()));
Ok(())
}
pub fn handle_request(self: &Rc<Self>, msg: &[u8]) {
if let Err(e) = self.handle_request_(msg) {
log::error!("Could not handle client request: {}", ErrorFmt(e));
@ -2946,7 +2875,7 @@ impl ConfigProxyHandler {
ClientMessage::GetSeats => self.handle_get_seats(),
ClientMessage::RemoveSeat { .. } => {}
ClientMessage::Run { prog, args, env } => {
self.handle_run(prog, args, env, vec![], None).wrn("run")?
self.handle_run(prog, args, env, vec![]).wrn("run")?
}
ClientMessage::GrabKb { kb, grab } => self.handle_grab(kb, grab).wrn("grab")?,
ClientMessage::SetColor { colorable, color } => {
@ -3154,7 +3083,7 @@ impl ConfigProxyHandler {
args,
env,
fds,
} => self.handle_run(prog, args, env, fds, None).wrn("run")?,
} => self.handle_run(prog, args, env, fds).wrn("run")?,
ClientMessage::DisableDefaultSeat => self.state.create_default_seat.set(false),
ClientMessage::DestroyKeymap { keymap } => self.handle_destroy_keymap(keymap),
ClientMessage::GetConnectorName { connector } => self
@ -3207,7 +3136,6 @@ impl ConfigProxyHandler {
ClientMessage::SetExplicitSyncEnabled { enabled } => {
self.handle_set_explicit_sync_enabled(enabled)
}
ClientMessage::GetSocketPath => self.handle_get_socket_path(),
ClientMessage::DeviceSetKeymap { device, keymap } => self
.handle_set_device_keymap(device, keymap)
.wrn("set_device_keymap")?,
@ -3508,12 +3436,6 @@ impl ConfigProxyHandler {
.wrn("connector_set_blend_space")?,
ClientMessage::SetBarFont { font } => self.handle_set_bar_font(font),
ClientMessage::SetTitleFont { font } => self.handle_set_title_font(font),
ClientMessage::SetClientMatcherCapabilities { matcher, caps } => self
.handle_set_client_matcher_capabilities(matcher, caps)
.wrn("set_client_matcher_capabilities")?,
ClientMessage::SetClientMatcherBoundingCapabilities { matcher, caps } => self
.handle_set_client_matcher_bounding_capabilities(matcher, caps)
.wrn("set_client_matcher_bounding_capabilities")?,
ClientMessage::ShowWorkspaceOn {
seat,
workspace,
@ -3559,13 +3481,6 @@ impl ConfigProxyHandler {
ClientMessage::SetXWaylandEnabled { enabled } => self
.handle_set_x_wayland_enabled(enabled)
.wrn("set_x_wayland_enabled")?,
ClientMessage::Run3 {
prog,
args,
env,
fds,
tag,
} => self.handle_run(prog, args, env, fds, tag).wrn("run")?,
ClientMessage::ConnectorSupportsArbitraryModes { connector } => self
.handle_connector_supports_arbitrary_modes(connector)
.wrn("connector_supports_arbitrary_modes")?,
@ -3638,41 +3553,6 @@ impl ConfigProxyHandler {
None
}
pub fn update_capabilities(
&self,
data: &Rc<Client>,
bounding_caps: ClientCaps,
set_bounding_caps: bool,
) {
let mut have_caps = false;
let mut have_bounding_caps = false;
let mut caps = ClientCaps::none();
let mut new_bounding_caps = ClientCaps::none();
for (matcher, state) in self.client_matcher_capabilities.lock().values() {
if matcher.node.pull(data) {
have_caps = true;
caps |= *state;
}
}
for (matcher, state) in self.client_matcher_bounding_capabilities.lock().values() {
if matcher.node.pull(data) {
have_bounding_caps = true;
new_bounding_caps |= *state;
}
}
if have_caps {
caps &= bounding_caps;
data.effective_caps.set(caps);
}
if !have_bounding_caps && set_bounding_caps {
have_bounding_caps = true;
new_bounding_caps = data.effective_caps.get();
}
if have_bounding_caps {
new_bounding_caps &= bounding_caps;
data.bounding_caps_for_children.set(new_bounding_caps);
}
}
}
#[derive(Debug, Error)]
@ -3771,8 +3651,6 @@ enum CphError {
UnknownFallbackOutputMode(FallbackOutputMode),
#[error("Unknown tile state {0:?}")]
UnknownTileState(ConfigTileState),
#[error("Could not create a tagged acceptor")]
CreateTaggedAcceptor(#[source] TaggedAcceptorError),
}
trait WithRequestName {
@ -3784,13 +3662,3 @@ impl WithRequestName for Result<(), CphError> {
self.map_err(move |e| CphError::FailedRequest(request, Box::new(e)))
}
}
trait ClientCapabilitiesExt {
fn to_client_caps(self) -> ClientCaps;
}
impl ClientCapabilitiesExt for ClientCapabilities {
fn to_client_caps(self) -> ClientCaps {
ClientCaps(self.0 as u32) & !CAP_JAY_COMPOSITOR & ClientCaps::all()
}
}

View file

@ -1,96 +1,4 @@
pub mod clm;
mod crit_graph;
pub mod crit_leaf;
mod crit_matchers;
mod crit_per_target_data;
pub mod tlm;
use {
crate::{
criteria::{
crit_graph::{CritMgr, CritMiddle, CritRoot, CritRootCriterion, CritRootFixed},
crit_leaf::CritLeafMatcher,
crit_matchers::{critm_any_or_all::CritMatchAnyOrAll, critm_exactly::CritMatchExactly},
},
utils::copyhashmap::CopyHashMap,
},
linearize::StaticMap,
regex::Regex,
std::rc::{Rc, Weak},
};
pub use {
crit_graph::{CritTarget, CritUpstreamNode},
crit_per_target_data::CritDestroyListener,
};
linear_ids!(CritMatcherIds, CritMatcherId, u64);
type RootMatcherMap<Target, T> = CopyHashMap<CritMatcherId, Weak<CritRoot<Target, T>>>;
type FixedRootMatcher<Target, T> = StaticMap<bool, Rc<CritRoot<Target, CritRootFixed<Target, T>>>>;
#[derive(Clone)]
pub enum CritLiteralOrRegex {
Literal(String),
Regex(Regex),
}
impl CritLiteralOrRegex {
fn matches(&self, string: &str) -> bool {
match self {
CritLiteralOrRegex::Literal(p) => string == p,
CritLiteralOrRegex::Regex(r) => r.is_match(string),
}
}
}
pub trait CritMgrExt: CritMgr {
fn list(
&self,
upstream: &[Rc<dyn CritUpstreamNode<Self::Target>>],
all: bool,
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
if upstream.is_empty() {
return self.match_constant()[all].clone();
}
CritMiddle::new(self, upstream, CritMatchAnyOrAll::new(upstream, all))
}
fn exactly(
&self,
upstream: &[Rc<dyn CritUpstreamNode<Self::Target>>],
num: usize,
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
if num > upstream.len() {
return self.match_constant()[false].clone();
}
if num == 0 {
let upstream: Vec<_> = upstream.iter().map(|u| u.not(self)).collect();
return self.list(&upstream, true);
}
CritMiddle::new(self, upstream, CritMatchExactly::new(upstream, num))
}
fn leaf(
&self,
upstream: &Rc<dyn CritUpstreamNode<Self::Target>>,
on_match: impl Fn(<Self::Target as CritTarget>::LeafData) -> Box<dyn FnOnce()> + 'static,
) -> Rc<CritLeafMatcher<Self::Target>> {
CritLeafMatcher::new(self, upstream, on_match)
}
fn not(
&self,
upstream: &Rc<dyn CritUpstreamNode<Self::Target>>,
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
upstream.not(self)
}
fn root<T>(&self, criterion: T) -> Rc<dyn CritUpstreamNode<Self::Target>>
where
T: CritRootCriterion<Self::Target>,
{
CritRoot::new(self.roots(), self.id(), criterion)
}
}
impl<T> CritMgrExt for T where T: CritMgr {}
pub use jay_criteria::*;

View file

@ -13,7 +13,7 @@ use {
clmm_sandboxed::ClmMatchSandboxed,
clmm_string::{
ClmMatchComm, ClmMatchExe, ClmMatchSandboxAppId, ClmMatchSandboxEngine,
ClmMatchSandboxInstanceId, ClmMatchTag,
ClmMatchSandboxInstanceId,
},
clmm_uid::ClmMatchUid,
},
@ -39,19 +39,19 @@ bitflags! {
CL_CHANGED_NEW,
}
type ClmFixedRootMatcher<T> = FixedRootMatcher<Rc<Client>, T>;
type ClmFixedRootMatcher<T> = FixedRootMatcher<Client, T>;
pub struct ClMatcherManager {
ids: Rc<CritMatcherIds>,
changes: AsyncQueue<Rc<Client>>,
leaf_events: Rc<AsyncQueue<CritLeafEvent<Rc<Client>>>>,
constant: ClmFixedRootMatcher<CritMatchConstant<Rc<Client>>>,
leaf_events: Rc<AsyncQueue<CritLeafEvent<Client>>>,
constant: ClmFixedRootMatcher<CritMatchConstant<Client>>,
sandboxed: ClmFixedRootMatcher<ClmMatchSandboxed>,
is_xwayland: ClmFixedRootMatcher<ClmMatchIsXwayland>,
matchers: Rc<RootMatchers>,
}
type ClmRootMatcherMap<T> = RootMatcherMap<Rc<Client>, T>;
type ClmRootMatcherMap<T> = RootMatcherMap<Client, T>;
#[derive(Default)]
pub struct RootMatchers {
@ -62,7 +62,6 @@ pub struct RootMatchers {
pid: ClmRootMatcherMap<ClmMatchPid>,
comm: ClmRootMatcherMap<ClmMatchComm>,
exe: ClmRootMatcherMap<ClmMatchExe>,
tag: ClmRootMatcherMap<ClmMatchTag>,
id: ClmRootMatcherMap<ClmMatchId>,
}
@ -75,7 +74,6 @@ impl RootMatchers {
self.pid.clear();
self.comm.clear();
self.exe.clear();
self.tag.clear();
self.id.clear();
}
}
@ -98,8 +96,8 @@ pub async fn handle_cl_leaf_events(state: Rc<State>) {
}
}
pub type ClmUpstreamNode = dyn CritUpstreamNode<Rc<Client>>;
pub type ClmLeafMatcher = CritLeafMatcher<Rc<Client>>;
pub type ClmUpstreamNode = dyn CritUpstreamNode<Client>;
pub type ClmLeafMatcher = CritLeafMatcher<Client>;
impl ClMatcherManager {
pub fn new(ids: &Rc<CritMatcherIds>) -> Self {
@ -146,6 +144,7 @@ impl ClMatcherManager {
}
fn update_matches(&self, data: &Rc<Client>) {
let data = data.as_ref();
let mut changed = data.changed_properties.take();
if changed.contains(CL_CHANGED_DESTROYED) {
for destroyed in data.destroyed.lock().drain_values() {
@ -186,7 +185,6 @@ impl ClMatcherManager {
unconditional!(pid);
unconditional!(comm);
unconditional!(exe);
unconditional!(tag);
unconditional!(id);
fixed!(sandboxed);
fixed!(is_xwayland);
@ -230,20 +228,29 @@ impl ClMatcherManager {
self.root(ClmMatchExe::new(string))
}
pub fn tag(&self, string: CritLiteralOrRegex) -> Rc<ClmUpstreamNode> {
self.root(ClmMatchTag::new(string))
}
}
impl CritTarget for Rc<Client> {
pub struct ClientTargetOwner {
client: Rc<Client>,
}
pub struct WeakClientTargetOwner {
state: Weak<State>,
id: ClientId,
}
impl CritTarget for Client {
type Id = ClientId;
type Mgr = ClMatcherManager;
type RootMatchers = RootMatchers;
type LeafData = ClientId;
type Owner = Weak<Client>;
type Owner = WeakClientTargetOwner;
fn owner(&self) -> Self::Owner {
Rc::downgrade(self)
WeakClientTargetOwner {
state: Rc::downgrade(&self.state),
id: self.id,
}
}
fn id(&self) -> Self::Id {
@ -259,25 +266,27 @@ impl CritTarget for Rc<Client> {
}
}
impl CritTargetOwner for Rc<Client> {
type Target = Rc<Client>;
impl CritTargetOwner for ClientTargetOwner {
type Target = Client;
fn data(&self) -> &Self::Target {
self
&self.client
}
}
impl WeakCritTargetOwner for Weak<Client> {
type Target = Rc<Client>;
type Owner = Rc<Client>;
impl WeakCritTargetOwner for WeakClientTargetOwner {
type Target = Client;
type Owner = ClientTargetOwner;
fn upgrade(&self) -> Option<Self::Owner> {
self.upgrade()
let state = self.state.upgrade()?;
let client = state.clients.get(self.id).ok()?;
Some(ClientTargetOwner { client })
}
}
impl CritMgr for ClMatcherManager {
type Target = Rc<Client>;
type Target = Client;
fn id(&self) -> CritMatcherId {
self.ids.next()

View file

@ -1,6 +1,6 @@
macro_rules! fixed_root_criterion {
($ty:ty, $field:ident) => {
impl crate::criteria::crit_graph::CritFixedRootCriterionBase<Rc<crate::client::Client>>
impl crate::criteria::crit_graph::CritFixedRootCriterionBase<crate::client::Client>
for $ty
{
fn constant(&self) -> bool {
@ -10,7 +10,7 @@ macro_rules! fixed_root_criterion {
fn not<'a>(
&self,
mgr: &'a crate::criteria::clm::ClMatcherManager,
) -> &'a crate::criteria::FixedRootMatcher<Rc<crate::client::Client>, Self> {
) -> &'a crate::criteria::FixedRootMatcher<crate::client::Client, Self> {
&mgr.$field
}
}

View file

@ -3,17 +3,16 @@ use {
client::{Client, ClientId},
criteria::{RootMatcherMap, clm::RootMatchers, crit_graph::CritRootCriterion},
},
std::rc::Rc,
};
pub struct ClmMatchId(pub ClientId);
impl CritRootCriterion<Rc<Client>> for ClmMatchId {
fn matches(&self, data: &Rc<Client>) -> bool {
impl CritRootCriterion<Client> for ClmMatchId {
fn matches(&self, data: &Client) -> bool {
data.id == self.0
}
fn nodes(roots: &RootMatchers) -> Option<&RootMatcherMap<Rc<Client>, Self>> {
fn nodes(roots: &RootMatchers) -> Option<&RootMatcherMap<Client, Self>> {
Some(&roots.id)
}
}

View file

@ -1,14 +1,13 @@
use {
crate::{client::Client, criteria::crit_graph::CritFixedRootCriterion},
std::rc::Rc,
};
pub struct ClmMatchIsXwayland(pub bool);
fixed_root_criterion!(ClmMatchIsXwayland, is_xwayland);
impl CritFixedRootCriterion<Rc<Client>> for ClmMatchIsXwayland {
fn matches(&self, data: &Rc<Client>) -> bool {
impl CritFixedRootCriterion<Client> for ClmMatchIsXwayland {
fn matches(&self, data: &Client) -> bool {
data.is_xwayland
}
}

View file

@ -3,18 +3,17 @@ use {
client::Client,
criteria::{RootMatcherMap, clm::RootMatchers, crit_graph::CritRootCriterion},
},
std::rc::Rc,
uapi::c,
};
pub struct ClmMatchPid(pub c::pid_t);
impl CritRootCriterion<Rc<Client>> for ClmMatchPid {
fn matches(&self, data: &Rc<Client>) -> bool {
impl CritRootCriterion<Client> for ClmMatchPid {
fn matches(&self, data: &Client) -> bool {
data.pid_info.pid == self.0
}
fn nodes(roots: &RootMatchers) -> Option<&RootMatcherMap<Rc<Client>, Self>> {
fn nodes(roots: &RootMatchers) -> Option<&RootMatcherMap<Client, Self>> {
Some(&roots.pid)
}
}

View file

@ -1,14 +1,13 @@
use {
crate::{client::Client, criteria::crit_graph::CritFixedRootCriterion},
std::rc::Rc,
};
pub struct ClmMatchSandboxed(pub bool);
fixed_root_criterion!(ClmMatchSandboxed, sandboxed);
impl CritFixedRootCriterion<Rc<Client>> for ClmMatchSandboxed {
fn matches(&self, data: &Rc<Client>) -> bool {
impl CritFixedRootCriterion<Client> for ClmMatchSandboxed {
fn matches(&self, data: &Client) -> bool {
data.acceptor.sandboxed
}
}

View file

@ -7,15 +7,14 @@ use {
},
security_context_acceptor::AcceptorMetadata,
},
std::{marker::PhantomData, rc::Rc},
std::marker::PhantomData,
};
pub type ClmMatchString<T> = CritMatchString<Rc<Client>, T>;
pub type ClmMatchString<T> = CritMatchString<Client, T>;
pub type ClmMatchSandboxEngine = ClmMatchString<AcceptorMetadataAccess<SandboxEngineField>>;
pub type ClmMatchSandboxAppId = ClmMatchString<AcceptorMetadataAccess<SandboxAppIdField>>;
pub type ClmMatchSandboxInstanceId = ClmMatchString<AcceptorMetadataAccess<SandboxInstanceIdField>>;
pub type ClmMatchTag = ClmMatchString<AcceptorMetadataAccess<TagField>>;
pub type ClmMatchComm = ClmMatchString<CommAccess>;
pub type ClmMatchExe = ClmMatchString<ExeAccess>;
@ -33,13 +32,12 @@ trait AcceptorMetadataField: Sized + 'static {
pub struct SandboxEngineField;
pub struct SandboxAppIdField;
pub struct SandboxInstanceIdField;
pub struct TagField;
impl<T> StringAccess<Rc<Client>> for AcceptorMetadataAccess<T>
impl<T> StringAccess<Client> for AcceptorMetadataAccess<T>
where
T: AcceptorMetadataField,
{
fn with_string(data: &Rc<Client>, f: impl FnOnce(&str) -> bool) -> bool {
fn with_string(data: &Client, f: impl FnOnce(&str) -> bool) -> bool {
f(T::field(&data.acceptor).as_deref().unwrap_or_default())
}
@ -84,20 +82,8 @@ impl AcceptorMetadataField for SandboxInstanceIdField {
}
}
impl AcceptorMetadataField for TagField {
fn field(meta: &AcceptorMetadata) -> &Option<String> {
&meta.tag
}
fn nodes(
roots: &RootMatchers,
) -> &ClmRootMatcherMap<ClmMatchString<AcceptorMetadataAccess<Self>>> {
&roots.tag
}
}
impl StringAccess<Rc<Client>> for CommAccess {
fn with_string(data: &Rc<Client>, f: impl FnOnce(&str) -> bool) -> bool {
impl StringAccess<Client> for CommAccess {
fn with_string(data: &Client, f: impl FnOnce(&str) -> bool) -> bool {
f(&data.pid_info.comm)
}
@ -106,8 +92,8 @@ impl StringAccess<Rc<Client>> for CommAccess {
}
}
impl StringAccess<Rc<Client>> for ExeAccess {
fn with_string(data: &Rc<Client>, f: impl FnOnce(&str) -> bool) -> bool {
impl StringAccess<Client> for ExeAccess {
fn with_string(data: &Client, f: impl FnOnce(&str) -> bool) -> bool {
f(&data.pid_info.exe)
}

View file

@ -3,18 +3,17 @@ use {
client::Client,
criteria::{RootMatcherMap, clm::RootMatchers, crit_graph::CritRootCriterion},
},
std::rc::Rc,
uapi::c,
};
pub struct ClmMatchUid(pub c::uid_t);
impl CritRootCriterion<Rc<Client>> for ClmMatchUid {
fn matches(&self, data: &Rc<Client>) -> bool {
impl CritRootCriterion<Client> for ClmMatchUid {
fn matches(&self, data: &Client) -> bool {
data.pid_info.uid == self.0
}
fn nodes(roots: &RootMatchers) -> Option<&RootMatcherMap<Rc<Client>, Self>> {
fn nodes(roots: &RootMatchers) -> Option<&RootMatcherMap<Client, Self>> {
Some(&roots.uid)
}
}

View file

@ -1,18 +0,0 @@
mod crit_downstream;
mod crit_middle;
mod crit_root;
mod crit_target;
mod crit_upstream;
pub use {
crit_downstream::{CritDownstream, CritDownstreamData},
crit_middle::{CritMiddle, CritMiddleCriterion},
crit_root::{
CritFixedRootCriterion, CritFixedRootCriterionBase, CritRoot, CritRootCriterion,
CritRootFixed,
},
crit_target::{CritMgr, CritTarget, CritTargetOwner, WeakCritTargetOwner},
crit_upstream::{
CritUpstreamData, CritUpstreamNode, CritUpstreamNodeBase, CritUpstreamNodeData,
},
};

View file

@ -1,52 +0,0 @@
use {
crate::criteria::{
CritMatcherId,
crit_graph::{CritTarget, crit_upstream::CritUpstreamNode},
},
std::rc::Rc,
};
pub struct CritDownstreamData<Target>
where
Target: CritTarget,
{
id: CritMatcherId,
pub(super) upstream: Vec<Rc<dyn CritUpstreamNode<Target>>>,
}
pub trait CritDownstream<Target>: 'static {
fn update_matched(self: Rc<Self>, target: &Target, matched: bool);
}
impl<Target> CritDownstreamData<Target>
where
Target: CritTarget,
{
pub fn new(id: CritMatcherId, upstream: &[Rc<dyn CritUpstreamNode<Target>>]) -> Self {
Self {
id,
upstream: upstream.to_vec(),
}
}
pub fn attach(&self, slf: &Rc<impl CritDownstream<Target>>) {
for upstream in &self.upstream {
upstream.attach(self.id, slf.clone() as _);
}
}
pub fn not(&self, mgr: &Target::Mgr) -> Vec<Rc<dyn CritUpstreamNode<Target>>> {
self.upstream.iter().map(|n| n.not(mgr)).collect()
}
}
impl<Target> Drop for CritDownstreamData<Target>
where
Target: CritTarget,
{
fn drop(&mut self) {
for el in &self.upstream {
el.detach(self.id);
}
}
}

View file

@ -1,111 +0,0 @@
use {
crate::criteria::{
CritUpstreamNode,
crit_graph::{
CritDownstream, CritDownstreamData, CritTarget, CritUpstreamData,
crit_target::CritMgr,
crit_upstream::{CritUpstreamNodeBase, CritUpstreamNodeData},
},
crit_per_target_data::{CritDestroyListenerBase, CritPerTargetData},
},
std::rc::Rc,
};
pub struct CritMiddle<Target, T>
where
Target: CritTarget,
T: CritMiddleCriterion<Target>,
{
upstream: CritDownstreamData<Target>,
downstream: CritUpstreamData<Target, CritMiddleData<T::Data>>,
criterion: T,
}
#[derive(Default)]
pub struct CritMiddleData<T> {
matches: usize,
data: T,
}
pub trait CritMiddleCriterion<Target>: Sized + 'static {
type Data: Default;
type Not: CritMiddleCriterion<Target>;
fn update_matched(&self, target: &Target, node: &mut Self::Data, matched: bool) -> bool;
fn pull(&self, upstream: &[Rc<dyn CritUpstreamNode<Target>>], target: &Target) -> bool;
fn not(&self) -> Self::Not;
}
impl<Target, T> CritMiddle<Target, T>
where
Target: CritTarget,
T: CritMiddleCriterion<Target>,
{
pub fn new(
mgr: &Target::Mgr,
upstream: &[Rc<dyn CritUpstreamNode<Target>>],
criterion: T,
) -> Rc<Self> {
let id = mgr.id();
let slf = Rc::new_cyclic(|slf| Self {
upstream: CritDownstreamData::new(id, upstream),
downstream: CritUpstreamData::new(slf, id),
criterion,
});
slf.upstream.attach(&slf);
slf
}
}
impl<Target, T> CritDownstream<Target> for CritMiddle<Target, T>
where
Target: CritTarget,
T: CritMiddleCriterion<Target>,
{
fn update_matched(self: Rc<Self>, target: &Target, matched: bool) {
let mut node = self.downstream.get_or_create(target);
let change = self
.criterion
.update_matched(target, &mut node.data, matched);
let matches = match matched {
true => node.matches + 1,
false => node.matches - 1,
};
node.matches = matches;
self.downstream
.update_matched(target, node, change, matches == 0);
}
}
impl<Target, T> CritUpstreamNodeBase<Target> for CritMiddle<Target, T>
where
Target: CritTarget,
T: CritMiddleCriterion<Target>,
{
type Data = CritMiddleData<T::Data>;
fn data(&self) -> &CritUpstreamData<Target, Self::Data> {
&self.downstream
}
fn not(&self, mgr: &Target::Mgr) -> Rc<dyn CritUpstreamNode<Target>> {
let upstream = self.upstream.not(mgr);
CritMiddle::new(mgr, &upstream, self.criterion.not())
}
fn pull(&self, target: &Target) -> bool {
self.criterion.pull(&self.upstream.upstream, target)
}
}
impl<Target, T> CritDestroyListenerBase<Target> for CritMiddle<Target, T>
where
Target: CritTarget,
T: CritMiddleCriterion<Target>,
{
type Data = CritUpstreamNodeData<Target, CritMiddleData<T::Data>>;
fn data(&self) -> &CritPerTargetData<Target, Self::Data> {
&self.downstream.nodes
}
}

View file

@ -1,175 +0,0 @@
use {
crate::criteria::{
CritMatcherId, CritUpstreamNode, FixedRootMatcher, RootMatcherMap,
crit_graph::{
CritTarget, CritUpstreamData,
crit_target::CritMgr,
crit_upstream::{CritUpstreamNodeBase, CritUpstreamNodeData},
},
crit_per_target_data::{CritDestroyListenerBase, CritPerTargetData},
},
std::{marker::PhantomData, rc::Rc},
};
pub struct CritRoot<Target, T>
where
Target: CritTarget,
T: CritRootCriterion<Target>,
{
id: CritMatcherId,
downstream: CritUpstreamData<Target, ()>,
not: bool,
criterion: Rc<T>,
roots: Rc<Target::RootMatchers>,
}
pub struct CritRootFixed<Target, Crit>(pub Crit, pub PhantomData<fn(&Target)>);
pub trait CritRootCriterion<Target>: Sized + 'static
where
Target: CritTarget,
{
fn matches(&self, data: &Target) -> bool;
fn nodes(roots: &Target::RootMatchers) -> Option<&RootMatcherMap<Target, Self>> {
let _ = roots;
None
}
fn not(&self, mgr: &Target::Mgr) -> Option<Rc<dyn CritUpstreamNode<Target>>> {
let _ = mgr;
None
}
}
pub trait CritFixedRootCriterionBase<Target>: Sized + 'static
where
Target: CritTarget,
{
fn constant(&self) -> bool;
fn not<'a>(&self, mgr: &'a Target::Mgr) -> &'a FixedRootMatcher<Target, Self>
where
Self: CritFixedRootCriterion<Target>;
}
pub trait CritFixedRootCriterion<Target>: CritFixedRootCriterionBase<Target>
where
Target: CritTarget,
{
const COMPARE: bool = true;
fn matches(&self, data: &Target) -> bool;
}
impl<Target, T> CritRootCriterion<Target> for CritRootFixed<Target, T>
where
Target: CritTarget,
T: CritFixedRootCriterion<Target>,
{
fn matches(&self, data: &Target) -> bool {
let mut res = self.0.matches(data);
if T::COMPARE {
res = res == self.0.constant();
}
res
}
fn not(&self, mgr: &Target::Mgr) -> Option<Rc<dyn CritUpstreamNode<Target>>> {
Some(self.0.not(mgr)[!self.0.constant()].clone())
}
}
impl<Target, T> CritUpstreamNodeBase<Target> for CritRoot<Target, T>
where
Target: CritTarget,
T: CritRootCriterion<Target>,
{
type Data = ();
fn data(&self) -> &CritUpstreamData<Target, Self::Data> {
&self.downstream
}
fn not(&self, mgr: &Target::Mgr) -> Rc<dyn CritUpstreamNode<Target>> {
if let Some(node) = self.criterion.not(mgr) {
return node;
}
let id = mgr.id();
Self::new_(&self.roots, id, self.criterion.clone(), !self.not)
}
fn pull(&self, target: &Target) -> bool {
self.criterion.matches(target) ^ self.not
}
}
impl<Target, T> CritRoot<Target, T>
where
Target: CritTarget,
T: CritRootCriterion<Target>,
{
pub fn new(roots: &Rc<Target::RootMatchers>, id: CritMatcherId, criterion: T) -> Rc<Self> {
Self::new_(roots, id, Rc::new(criterion), false)
}
fn new_(
roots: &Rc<Target::RootMatchers>,
id: CritMatcherId,
criterion: Rc<T>,
not: bool,
) -> Rc<Self> {
let slf = Rc::new_cyclic(|slf| Self {
id,
downstream: CritUpstreamData::new(slf, id),
not,
criterion,
roots: roots.clone(),
});
if let Some(nodes) = T::nodes(roots) {
nodes.set(id, Rc::downgrade(&slf));
}
slf
}
pub fn clear(&self) {
self.downstream.clear();
}
pub fn handle(&self, target: &Target) {
let new = self.criterion.matches(target) ^ self.not;
let node = match new {
true => self.downstream.get_or_create(target),
false => match self.downstream.get(target) {
Some(n) => n,
None => return,
},
};
self.downstream.update_matched(target, node, new, !new);
}
pub fn has_downstream(&self) -> bool {
self.downstream.has_downstream()
}
}
impl<Target, T> CritDestroyListenerBase<Target> for CritRoot<Target, T>
where
Target: CritTarget,
T: CritRootCriterion<Target>,
{
type Data = CritUpstreamNodeData<Target, ()>;
fn data(&self) -> &CritPerTargetData<Target, Self::Data> {
&self.downstream.nodes
}
}
impl<Target, T> Drop for CritRoot<Target, T>
where
Target: CritTarget,
T: CritRootCriterion<Target>,
{
fn drop(&mut self) {
if let Some(nodes) = T::nodes(&self.roots) {
nodes.remove(&self.id);
}
}
}

View file

@ -1,48 +0,0 @@
use {
crate::{
criteria::{
CritDestroyListener, CritMatcherId, FixedRootMatcher, crit_leaf::CritLeafEvent,
crit_matchers::critm_constant::CritMatchConstant,
},
utils::{copyhashmap::CopyHashMap, queue::AsyncQueue},
},
std::{
hash::Hash,
rc::{Rc, Weak},
},
};
pub trait CritMgr: 'static {
type Target: CritTarget<Mgr = Self>;
fn id(&self) -> CritMatcherId;
fn leaf_events(&self) -> &Rc<AsyncQueue<CritLeafEvent<Self::Target>>>;
fn match_constant(&self) -> &FixedRootMatcher<Self::Target, CritMatchConstant<Self::Target>>;
fn roots(&self) -> &Rc<<Self::Target as CritTarget>::RootMatchers>;
}
pub trait CritTarget: 'static {
type Id: Copy + Hash + Eq;
type Mgr: CritMgr<Target = Self>;
type RootMatchers;
type LeafData: Copy + Eq;
type Owner: WeakCritTargetOwner<Target = Self>;
fn owner(&self) -> Self::Owner;
fn id(&self) -> Self::Id;
fn destroyed(&self) -> &CopyHashMap<CritMatcherId, Weak<dyn CritDestroyListener<Self>>>;
fn leaf_data(&self) -> Self::LeafData;
}
pub trait CritTargetOwner: 'static {
type Target: CritTarget;
fn data(&self) -> &Self::Target;
}
pub trait WeakCritTargetOwner: 'static {
type Target: CritTarget;
type Owner: CritTargetOwner<Target = Self::Target>;
fn upgrade(&self) -> Option<Self::Owner>;
}

View file

@ -1,179 +0,0 @@
use {
crate::{
criteria::{
CritDestroyListener, CritMatcherId,
crit_graph::{
WeakCritTargetOwner,
crit_downstream::CritDownstream,
crit_target::{CritTarget, CritTargetOwner},
},
crit_per_target_data::CritPerTargetData,
},
utils::copyhashmap::CopyHashMap,
},
std::{
cell::RefMut,
mem,
ops::{Deref, DerefMut},
rc::{Rc, Weak},
},
};
pub struct CritUpstreamData<Target, T>
where
Target: CritTarget,
{
downstream: CopyHashMap<CritMatcherId, Weak<dyn CritDownstream<Target>>>,
pub nodes: CritPerTargetData<Target, CritUpstreamNodeData<Target, T>>,
}
pub struct CritUpstreamNodeData<Target, T>
where
Target: CritTarget,
{
matched: bool,
tl: Target::Owner,
data: T,
}
pub trait CritUpstreamNodeBase<Target>: 'static
where
Target: CritTarget,
{
type Data;
fn data(&self) -> &CritUpstreamData<Target, Self::Data>;
fn not(&self, mgr: &Target::Mgr) -> Rc<dyn CritUpstreamNode<Target>>;
fn pull(&self, target: &Target) -> bool;
}
pub trait CritUpstreamNode<Target>: 'static
where
Target: CritTarget,
{
fn attach(&self, id: CritMatcherId, downstream: Rc<dyn CritDownstream<Target>>);
fn detach(&self, id: CritMatcherId);
fn not(&self, mgr: &Target::Mgr) -> Rc<dyn CritUpstreamNode<Target>>;
fn pull(&self, target: &Target) -> bool;
fn get(&self, target: &Target) -> bool;
}
impl<Target, T> CritUpstreamNode<Target> for T
where
Target: CritTarget,
T: CritUpstreamNodeBase<Target>,
{
fn attach(&self, id: CritMatcherId, downstream: Rc<dyn CritDownstream<Target>>) {
let data = self.data();
for n in data.nodes.borrow_mut().values_mut() {
if !n.matched {
continue;
}
let Some(target) = n.tl.upgrade() else {
continue;
};
downstream.clone().update_matched(target.data(), true);
}
data.downstream.set(id, Rc::downgrade(&downstream));
}
fn detach(&self, id: CritMatcherId) {
self.data().downstream.remove(&id);
}
fn not(&self, mgr: &Target::Mgr) -> Rc<dyn CritUpstreamNode<Target>> {
<T as CritUpstreamNodeBase<Target>>::not(self, mgr)
}
fn pull(&self, target: &Target) -> bool {
<T as CritUpstreamNodeBase<Target>>::pull(self, target)
}
fn get(&self, target: &Target) -> bool {
<T as CritUpstreamNodeBase<Target>>::data(self).matched(target)
}
}
impl<Target, T> Deref for CritUpstreamNodeData<Target, T>
where
Target: CritTarget,
{
type Target = T;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<Target, T> DerefMut for CritUpstreamNodeData<Target, T>
where
Target: CritTarget,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.data
}
}
impl<Target, T> CritUpstreamData<Target, T>
where
Target: CritTarget,
{
pub fn new(slf: &Weak<impl CritDestroyListener<Target>>, id: CritMatcherId) -> Self {
Self {
downstream: Default::default(),
nodes: CritPerTargetData::new(slf, id),
}
}
pub fn clear(&self) {
self.nodes.clear()
}
pub fn update_matched(
&self,
target: &Target,
mut node: RefMut<CritUpstreamNodeData<Target, T>>,
matched: bool,
remove: bool,
) {
let unchanged = mem::replace(&mut node.matched, matched) == matched;
drop(node);
if remove {
self.nodes.remove(target.id());
}
if unchanged {
return;
}
for el in self.downstream.lock().values() {
if let Some(el) = el.upgrade() {
el.update_matched(target, matched);
}
}
}
pub fn get_or_create(&self, target: &Target) -> RefMut<'_, CritUpstreamNodeData<Target, T>>
where
T: Default,
{
self.nodes.get_or_create(target, || CritUpstreamNodeData {
matched: false,
tl: target.owner(),
data: Default::default(),
})
}
pub fn get(&self, target: &Target) -> Option<RefMut<'_, CritUpstreamNodeData<Target, T>>> {
self.nodes.get(target)
}
pub fn has_downstream(&self) -> bool {
self.downstream.is_not_empty()
}
pub fn matched(&self, target: &Target) -> bool {
let Some(node) = self.nodes.get(target) else {
return false;
};
node.matched
}
}

View file

@ -1,156 +0,0 @@
use {
crate::{
criteria::{
CritUpstreamNode,
crit_graph::{CritDownstream, CritDownstreamData, CritMgr, CritTarget},
crit_per_target_data::{CritDestroyListenerBase, CritPerTargetData},
},
utils::{cell_ext::CellExt, queue::AsyncQueue},
},
std::{
cell::Cell,
rc::{Rc, Weak},
slice,
},
};
pub struct CritLeafMatcher<Target>
where
Target: CritTarget,
{
upstream: CritDownstreamData<Target>,
on_match: Box<dyn Fn(Target::LeafData) -> Box<dyn FnOnce()>>,
targets: CritPerTargetData<Target, NodeHolder<Target>>,
events: Rc<AsyncQueue<CritLeafEvent<Target>>>,
}
pub(in crate::criteria) struct NodeHolder<Target>
where
Target: CritTarget,
{
node: Rc<Node<Target>>,
}
struct Node<Target>
where
Target: CritTarget,
{
leaf: Weak<CritLeafMatcher<Target>>,
target_id: Target::Id,
needs_event: Cell<bool>,
new_data: Cell<Option<Target::LeafData>>,
data: Cell<Option<Target::LeafData>>,
on_unmatch: Cell<Option<Box<dyn FnOnce()>>>,
}
pub struct CritLeafEvent<Target>
where
Target: CritTarget,
{
node: Rc<Node<Target>>,
}
impl<Target> CritDownstream<Target> for CritLeafMatcher<Target>
where
Target: CritTarget,
{
fn update_matched(self: Rc<Self>, data: &Target, matched: bool) {
let node = &self
.targets
.get_or_create(data, || {
let node = Rc::new(Node {
leaf: Rc::downgrade(&self),
target_id: data.id(),
needs_event: Cell::new(true),
new_data: Cell::new(None),
data: Cell::new(None),
on_unmatch: Cell::new(None),
});
NodeHolder { node: node.clone() }
})
.node;
self.push_event(node, matched.then_some(data.leaf_data()));
}
}
impl<Target> CritLeafMatcher<Target>
where
Target: CritTarget,
{
pub(in crate::criteria) fn new(
mgr: &Target::Mgr,
upstream: &Rc<dyn CritUpstreamNode<Target>>,
on_match: impl Fn(Target::LeafData) -> Box<dyn FnOnce()> + 'static,
) -> Rc<Self> {
let id = mgr.id();
let slf = Rc::new_cyclic(|slf| Self {
targets: CritPerTargetData::new(slf, id),
on_match: Box::new(on_match),
events: mgr.leaf_events().clone(),
upstream: CritDownstreamData::new(id, slice::from_ref(upstream)),
});
slf.upstream.attach(&slf);
slf
}
fn push_event(&self, node: &Rc<Node<Target>>, new_data: Option<Target::LeafData>) {
node.new_data.set(new_data);
if node.needs_event.replace(false) {
self.events.push(CritLeafEvent { node: node.clone() });
}
}
}
impl<Target> CritLeafEvent<Target>
where
Target: CritTarget,
{
pub fn run(self) {
let n = self.node;
n.needs_event.set(true);
if n.new_data != n.data
&& let Some(on_unmatch) = n.on_unmatch.take()
{
if n.leaf.strong_count() == 0 {
return;
}
on_unmatch();
}
n.data.set(n.new_data.get());
if n.data.is_some() != n.on_unmatch.is_some() {
let Some(leaf) = n.leaf.upgrade() else {
return;
};
if let Some(id) = n.data.get() {
n.on_unmatch.set(Some((leaf.on_match)(id)));
} else {
if let Some(on_unmatch) = n.on_unmatch.take() {
on_unmatch();
}
leaf.targets.remove(n.target_id);
}
}
}
}
impl<Target> Drop for NodeHolder<Target>
where
Target: CritTarget,
{
fn drop(&mut self) {
if let Some(leaf) = self.node.leaf.upgrade() {
leaf.push_event(&self.node, None);
}
}
}
impl<Target> CritDestroyListenerBase<Target> for CritLeafMatcher<Target>
where
Target: CritTarget,
{
type Data = NodeHolder<Target>;
fn data(&self) -> &CritPerTargetData<Target, Self::Data> {
&self.targets
}
}

View file

@ -1,4 +0,0 @@
pub mod critm_any_or_all;
pub mod critm_constant;
pub mod critm_exactly;
pub mod critm_string;

View file

@ -1,73 +0,0 @@
use {
crate::criteria::crit_graph::{CritMiddleCriterion, CritTarget, CritUpstreamNode},
std::{marker::PhantomData, rc::Rc},
};
pub struct CritMatchAnyOrAll<Target>
where
Target: CritTarget,
{
all: bool,
total: usize,
_phantom: PhantomData<fn(&Target)>,
}
impl<Target> CritMatchAnyOrAll<Target>
where
Target: CritTarget,
{
pub fn new(upstream: &[Rc<dyn CritUpstreamNode<Target>>], all: bool) -> Self {
Self {
all,
total: upstream.len(),
_phantom: Default::default(),
}
}
}
impl<Target> CritMiddleCriterion<Target> for CritMatchAnyOrAll<Target>
where
Target: CritTarget,
{
type Data = usize;
type Not = Self;
fn update_matched(&self, _data: &Target, node: &mut usize, matched: bool) -> bool {
if matched {
*node += 1;
} else {
*node -= 1;
}
if self.all {
*node == self.total
} else {
*node > 0
}
}
fn pull(&self, upstream: &[Rc<dyn CritUpstreamNode<Target>>], node: &Target) -> bool {
for upstream in upstream {
if upstream.pull(node) {
if !self.all {
return true;
}
} else {
if self.all {
return false;
}
}
}
self.all
}
fn not(&self) -> Self
where
Self: Sized,
{
Self {
all: !self.all,
total: self.total,
_phantom: Default::default(),
}
}
}

View file

@ -1,58 +0,0 @@
use {
crate::criteria::{
CritMatcherIds, FixedRootMatcher,
crit_graph::{
CritFixedRootCriterion, CritFixedRootCriterionBase, CritMgr, CritRoot, CritRootFixed,
CritTarget,
},
},
linearize::static_map,
std::{marker::PhantomData, rc::Rc},
};
pub struct CritMatchConstant<Target>(pub bool, pub PhantomData<fn(&Target)>);
impl<Target> CritMatchConstant<Target>
where
Target: CritTarget,
{
pub fn create(
roots: &Rc<Target::RootMatchers>,
ids: &CritMatcherIds,
) -> FixedRootMatcher<Target, CritMatchConstant<Target>> {
static_map! {
v => CritRoot::new(
roots,
ids.next(),
CritRootFixed(Self(v, PhantomData), PhantomData),
),
}
}
}
impl<Target> CritFixedRootCriterionBase<Target> for CritMatchConstant<Target>
where
Target: CritTarget,
{
fn constant(&self) -> bool {
self.0
}
fn not<'a>(&self, mgr: &'a Target::Mgr) -> &'a FixedRootMatcher<Target, Self>
where
Self: CritFixedRootCriterion<Target>,
{
mgr.match_constant()
}
}
impl<Target> CritFixedRootCriterion<Target> for CritMatchConstant<Target>
where
Target: CritTarget,
{
const COMPARE: bool = false;
fn matches(&self, _data: &Target) -> bool {
self.0
}
}

View file

@ -1,61 +0,0 @@
use {
crate::criteria::crit_graph::{CritMiddleCriterion, CritTarget, CritUpstreamNode},
std::{marker::PhantomData, rc::Rc},
};
pub struct CritMatchExactly<Target> {
total: usize,
num: usize,
not: bool,
_phantom: PhantomData<fn(&Target)>,
}
impl<Target> CritMatchExactly<Target> {
pub fn new(upstream: &[Rc<dyn CritUpstreamNode<Target>>], num: usize) -> Self {
Self {
total: upstream.len(),
num,
not: false,
_phantom: Default::default(),
}
}
}
impl<Target> CritMiddleCriterion<Target> for CritMatchExactly<Target>
where
Target: CritTarget,
{
type Data = usize;
type Not = Self;
fn update_matched(&self, _data: &Target, node: &mut usize, matched: bool) -> bool {
if matched {
*node += 1;
} else {
*node -= 1;
}
(*node == self.num) ^ self.not
}
fn pull(&self, upstream: &[Rc<dyn CritUpstreamNode<Target>>], node: &Target) -> bool {
let mut n = 0;
for upstream in upstream {
if upstream.pull(node) {
n += 1;
}
}
(n == self.num) ^ self.not
}
fn not(&self) -> Self
where
Self: Sized,
{
Self {
total: self.total,
num: self.total - self.num,
not: !self.not,
_phantom: Default::default(),
}
}
}

View file

@ -1,45 +0,0 @@
use {
crate::criteria::{
CritLiteralOrRegex, RootMatcherMap,
crit_graph::{CritRootCriterion, CritTarget},
},
std::marker::PhantomData,
};
pub struct CritMatchString<Target, A> {
string: CritLiteralOrRegex,
_phantom: PhantomData<(fn(&Target), A)>,
}
pub trait StringAccess<Target>: Sized + 'static
where
Target: CritTarget,
{
fn with_string(data: &Target, f: impl FnOnce(&str) -> bool) -> bool;
fn nodes(
roots: &Target::RootMatchers,
) -> &RootMatcherMap<Target, CritMatchString<Target, Self>>;
}
impl<Target, A> CritMatchString<Target, A> {
pub fn new(string: CritLiteralOrRegex) -> Self {
Self {
string,
_phantom: Default::default(),
}
}
}
impl<Target, A> CritRootCriterion<Target> for CritMatchString<Target, A>
where
Target: CritTarget,
A: StringAccess<Target>,
{
fn matches(&self, data: &Target) -> bool {
A::with_string(data, |s| self.string.matches(s))
}
fn nodes(roots: &Target::RootMatchers) -> Option<&RootMatcherMap<Target, Self>> {
Some(A::nodes(roots))
}
}

View file

@ -1,139 +0,0 @@
use {
crate::criteria::{
CritMatcherId,
crit_graph::{CritTarget, CritTargetOwner, WeakCritTargetOwner},
},
ahash::AHashMap,
std::{
cell::{RefCell, RefMut},
ops::{Deref, DerefMut},
rc::Weak,
},
};
pub struct CritPerTargetData<Target, T>
where
Target: CritTarget,
{
id: CritMatcherId,
slf: Weak<dyn CritDestroyListener<Target>>,
data: RefCell<AHashMap<Target::Id, PerTreeNodeData<Target, T>>>,
}
pub struct PerTreeNodeData<Target, T>
where
Target: CritTarget,
{
node: Target::Owner,
data: T,
}
pub(super) trait CritDestroyListenerBase<Target>: 'static
where
Target: CritTarget,
{
type Data;
fn data(&self) -> &CritPerTargetData<Target, Self::Data>;
}
pub trait CritDestroyListener<Target>: 'static
where
Target: CritTarget,
{
fn destroyed(&self, target_id: Target::Id);
}
impl<Target, T> CritPerTargetData<Target, T>
where
Target: CritTarget,
{
pub fn new(slf: &Weak<impl CritDestroyListener<Target>>, id: CritMatcherId) -> Self {
Self {
id,
slf: slf.clone() as _,
data: Default::default(),
}
}
pub fn clear(&self) {
self.data.borrow_mut().clear();
}
pub fn get_or_create(&self, target: &Target, default: impl FnOnce() -> T) -> RefMut<'_, T> {
RefMut::map(self.data.borrow_mut(), |d| {
&mut d
.entry(target.id())
.or_insert_with(|| {
target.destroyed().set(self.id, self.slf.clone());
PerTreeNodeData {
node: target.owner(),
data: default(),
}
})
.data
})
}
pub fn get(&self, target: &Target) -> Option<RefMut<'_, T>> {
RefMut::filter_map(self.data.borrow_mut(), |d| {
d.get_mut(&target.id()).map(|d| &mut d.data)
})
.ok()
}
pub fn remove(&self, target_id: Target::Id) {
if let Some(node) = self.data.borrow_mut().remove(&target_id)
&& let Some(node) = node.node.upgrade()
{
node.data().destroyed().remove(&self.id);
}
}
pub fn borrow_mut(&self) -> RefMut<'_, AHashMap<Target::Id, PerTreeNodeData<Target, T>>> {
self.data.borrow_mut()
}
}
impl<Target, T> Drop for CritPerTargetData<Target, T>
where
Target: CritTarget,
{
fn drop(&mut self) {
for d in self.data.borrow().values() {
if let Some(n) = d.node.upgrade() {
n.data().destroyed().remove(&self.id);
}
}
}
}
impl<Target, T> CritDestroyListener<Target> for T
where
Target: CritTarget,
T: CritDestroyListenerBase<Target>,
{
fn destroyed(&self, target_id: Target::Id) {
let _v = self.data().data.borrow_mut().remove(&target_id);
}
}
impl<Target, T> Deref for PerTreeNodeData<Target, T>
where
Target: CritTarget,
{
type Target = T;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<Target, T> DerefMut for PerTreeNodeData<Target, T>
where
Target: CritTarget,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.data
}
}

View file

@ -408,10 +408,10 @@ impl CritTarget for ToplevelData {
type Mgr = TlMatcherManager;
type RootMatchers = RootMatchers;
type LeafData = ToplevelIdentifier;
type Owner = Weak<dyn ToplevelNode>;
type Owner = WeakToplevelTargetOwner;
fn owner(&self) -> Self::Owner {
self.slf.clone()
WeakToplevelTargetOwner(self.slf.clone())
}
fn id(&self) -> Self::Id {
@ -427,20 +427,24 @@ impl CritTarget for ToplevelData {
}
}
impl CritTargetOwner for Rc<dyn ToplevelNode> {
pub struct ToplevelTargetOwner(Rc<dyn ToplevelNode>);
pub struct WeakToplevelTargetOwner(Weak<dyn ToplevelNode>);
impl CritTargetOwner for ToplevelTargetOwner {
type Target = ToplevelData;
fn data(&self) -> &Self::Target {
self.tl_data()
self.0.tl_data()
}
}
impl WeakCritTargetOwner for Weak<dyn ToplevelNode> {
impl WeakCritTargetOwner for WeakToplevelTargetOwner {
type Target = ToplevelData;
type Owner = Rc<dyn ToplevelNode>;
type Owner = ToplevelTargetOwner;
fn upgrade(&self) -> Option<Self::Owner> {
self.upgrade()
self.0.upgrade().map(ToplevelTargetOwner)
}
}

View file

@ -21,7 +21,7 @@ pub struct TlmMatchClient {
id: CritMatcherId,
state: Rc<State>,
node: Rc<ClmUpstreamNode>,
upstream: CritDownstreamData<Rc<Client>>,
upstream: CritDownstreamData<Client>,
downstream: CritUpstreamData<ToplevelData, ()>,
}
@ -73,8 +73,8 @@ impl CritUpstreamNodeBase<ToplevelData> for TlmMatchClient {
}
}
impl CritDownstream<Rc<Client>> for TlmMatchClient {
fn update_matched(self: Rc<Self>, target: &Rc<Client>, matched: bool) {
impl CritDownstream<Client> for TlmMatchClient {
fn update_matched(self: Rc<Self>, target: &Client, matched: bool) {
let handle = |data: &ToplevelData| {
let node = match matched {
true => self.downstream.get_or_create(data),

File diff suppressed because it is too large Load diff

View file

@ -1,137 +1 @@
use std::{
cmp::Ordering,
fmt::{Debug, Display, Formatter},
ops::{Add, AddAssign, Div, Mul, Sub, SubAssign},
};
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
#[repr(transparent)]
pub struct Fixed(pub i32);
impl Fixed {
pub const EPSILON: Self = Fixed(1);
pub fn is_integer(self) -> bool {
self.0 & 255 == 0
}
pub fn from_f64(f: f64) -> Self {
Self((f * 256.0) as i32)
}
pub fn from_f32(f: f32) -> Self {
Self::from_f64(f as f64)
}
pub fn to_f64(self) -> f64 {
self.0 as f64 / 256.0
}
pub fn to_f32(self) -> f32 {
self.0 as f32 / 256.0
}
pub fn from_1616(i: i32) -> Self {
Self(i >> 8)
}
pub fn to_int(self) -> i32 {
self.0 >> 8
}
pub fn from_int(i: i32) -> Self {
Self(i << 8)
}
pub fn round_down(self) -> i32 {
self.0 >> 8
}
pub fn apply_fract(self, i: i32) -> Self {
Self((i << 8) | (self.0 & 255))
}
}
impl PartialEq<i32> for Fixed {
fn eq(&self, other: &i32) -> bool {
self.0 == *other << 8
}
}
impl PartialOrd<i32> for Fixed {
fn partial_cmp(&self, other: &i32) -> Option<Ordering> {
self.0.partial_cmp(&(*other << 8))
}
}
impl Debug for Fixed {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&self.to_f64(), f)
}
}
impl Display for Fixed {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.to_f64(), f)
}
}
impl Sub for Fixed {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self(self.0 - rhs.0)
}
}
impl Add for Fixed {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self(self.0 + rhs.0)
}
}
impl Sub<i32> for Fixed {
type Output = Self;
fn sub(self, rhs: i32) -> Self::Output {
Self(self.0 - (rhs << 8))
}
}
impl Add<i32> for Fixed {
type Output = Self;
fn add(self, rhs: i32) -> Self::Output {
Self(self.0 + (rhs << 8))
}
}
impl Mul<i32> for Fixed {
type Output = Self;
fn mul(self, rhs: i32) -> Self::Output {
Self(self.0 * rhs)
}
}
impl Div<i32> for Fixed {
type Output = Self;
fn div(self, rhs: i32) -> Self::Output {
Self(self.0 / rhs)
}
}
impl AddAssign for Fixed {
fn add_assign(&mut self, rhs: Self) {
self.0 += rhs.0;
}
}
impl SubAssign for Fixed {
fn sub_assign(&mut self, rhs: Self) {
self.0 -= rhs.0;
}
}
pub use jay_units::fixed::*;

View file

@ -1,583 +1 @@
use {
crate::{
gfx_apis::gl::sys::{GL_BGRA_EXT, GL_RGBA, GL_RGBA8, GL_UNSIGNED_BYTE, GLenum, GLint},
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,
ash::vk,
jay_config::video::Format as ConfigFormat,
std::{
fmt::{self, Debug, Write},
sync::LazyLock,
},
};
#[derive(Copy, Clone, Debug)]
pub struct FormatShmInfo {
pub gl_format: GLint,
pub gl_internal_format: GLenum,
pub gl_type: GLint,
}
#[derive(Copy, Clone, Debug)]
pub struct Format {
pub name: &'static str,
pub vk_format: vk::Format,
pub drm: u32,
pub wl_id: Option<u32>,
pub external_only_guess: bool,
pub has_alpha: bool,
pub pipewire: SpaVideoFormat,
pub opaque: Option<&'static Format>,
pub shm_info: Option<FormatShmInfo>,
pub config: ConfigFormat,
pub bpp: u32,
}
const fn default(config: ConfigFormat) -> Format {
Format {
name: "",
vk_format: vk::Format::UNDEFINED,
drm: 0,
wl_id: None,
external_only_guess: false,
has_alpha: false,
pipewire: SPA_VIDEO_FORMAT_UNKNOWN,
opaque: None,
shm_info: None,
config,
bpp: 4,
}
}
impl PartialEq for Format {
fn eq(&self, other: &Self) -> bool {
self.drm == other.drm
}
}
impl Eq for Format {}
static FORMATS_MAP: LazyLock<AHashMap<u32, &'static Format>> = LazyLock::new(|| {
let mut map = AHashMap::new();
for format in FORMATS {
assert!(map.insert(format.drm, format).is_none());
}
map
});
static PW_FORMATS_MAP: LazyLock<AHashMap<SpaVideoFormat, &'static Format>> = LazyLock::new(|| {
let mut map = AHashMap::new();
for format in FORMATS {
if format.pipewire != SPA_VIDEO_FORMAT_UNKNOWN {
assert!(map.insert(format.pipewire, format).is_none());
}
}
map
});
static FORMATS_REFS: LazyLock<Vec<&'static Format>> = LazyLock::new(|| FORMATS.iter().collect());
static FORMATS_NAMES: LazyLock<AHashMap<&'static str, &'static Format>> = LazyLock::new(|| {
let mut map = AHashMap::new();
for format in FORMATS {
assert!(map.insert(format.name, format).is_none());
}
map
});
static FORMATS_CONFIG: LazyLock<AHashMap<ConfigFormat, &'static Format>> = LazyLock::new(|| {
let mut map = AHashMap::new();
for format in FORMATS {
assert!(map.insert(format.config, format).is_none());
}
map
});
#[test]
fn formats_dont_panic() {
formats();
pw_formats();
named_formats();
config_formats();
}
pub fn formats() -> &'static AHashMap<u32, &'static Format> {
&FORMATS_MAP
}
pub fn pw_formats() -> &'static AHashMap<SpaVideoFormat, &'static Format> {
&PW_FORMATS_MAP
}
pub fn ref_formats() -> &'static [&'static Format] {
&FORMATS_REFS
}
pub fn named_formats() -> &'static AHashMap<&'static str, &'static Format> {
&FORMATS_NAMES
}
pub fn config_formats() -> &'static AHashMap<ConfigFormat, &'static Format> {
&FORMATS_CONFIG
}
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)
}
#[expect(dead_code)]
pub fn debug(fourcc: u32) -> impl Debug {
fmt::from_fn(move |fmt| {
fmt.write_char(fourcc as u8 as char)?;
fmt.write_char((fourcc >> 8) as u8 as char)?;
fmt.write_char((fourcc >> 16) as u8 as char)?;
fmt.write_char((fourcc >> 24) as u8 as char)?;
Ok(())
})
}
const ARGB8888_ID: u32 = 0;
const ARGB8888_DRM: u32 = fourcc_code('A', 'R', '2', '4');
const XRGB8888_ID: u32 = 1;
const XRGB8888_DRM: u32 = fourcc_code('X', 'R', '2', '4');
pub fn map_wayland_format_id(id: u32) -> u32 {
match id {
ARGB8888_ID => ARGB8888_DRM,
XRGB8888_ID => XRGB8888_DRM,
_ => id,
}
}
pub static ARGB8888: &Format = &Format {
name: "argb8888",
shm_info: Some(FormatShmInfo {
gl_format: GL_BGRA_EXT,
gl_internal_format: GL_RGBA8,
gl_type: GL_UNSIGNED_BYTE,
}),
vk_format: vk::Format::B8G8R8A8_UNORM,
bpp: 4,
drm: ARGB8888_DRM,
wl_id: Some(ARGB8888_ID),
external_only_guess: false,
has_alpha: true,
pipewire: SPA_VIDEO_FORMAT_BGRA,
opaque: Some(XRGB8888),
config: ConfigFormat::ARGB8888,
};
pub static XRGB8888: &Format = &Format {
name: "xrgb8888",
shm_info: Some(FormatShmInfo {
gl_format: GL_BGRA_EXT,
gl_internal_format: GL_RGBA8,
gl_type: GL_UNSIGNED_BYTE,
}),
vk_format: vk::Format::B8G8R8A8_UNORM,
bpp: 4,
drm: XRGB8888_DRM,
wl_id: Some(XRGB8888_ID),
external_only_guess: false,
has_alpha: false,
pipewire: SPA_VIDEO_FORMAT_BGRx,
opaque: None,
config: ConfigFormat::XRGB8888,
};
static ABGR8888: &Format = &Format {
name: "abgr8888",
shm_info: Some(FormatShmInfo {
gl_format: GL_RGBA,
gl_internal_format: GL_RGBA8,
gl_type: GL_UNSIGNED_BYTE,
}),
vk_format: vk::Format::R8G8B8A8_UNORM,
bpp: 4,
drm: fourcc_code('A', 'B', '2', '4'),
wl_id: None,
external_only_guess: false,
has_alpha: true,
pipewire: SPA_VIDEO_FORMAT_RGBA,
opaque: Some(XBGR8888),
config: ConfigFormat::ABGR8888,
};
static XBGR8888: &Format = &Format {
name: "xbgr8888",
shm_info: Some(FormatShmInfo {
gl_format: GL_RGBA,
gl_internal_format: GL_RGBA8,
gl_type: GL_UNSIGNED_BYTE,
}),
vk_format: vk::Format::R8G8B8A8_UNORM,
bpp: 4,
drm: fourcc_code('X', 'B', '2', '4'),
wl_id: None,
external_only_guess: false,
has_alpha: false,
pipewire: SPA_VIDEO_FORMAT_RGBx,
opaque: None,
config: ConfigFormat::XBGR8888,
};
static R8: &Format = &Format {
name: "r8",
vk_format: vk::Format::R8_UNORM,
bpp: 1,
drm: fourcc_code('R', '8', ' ', ' '),
pipewire: SPA_VIDEO_FORMAT_GRAY8,
..default(ConfigFormat::R8)
};
static GR88: &Format = &Format {
name: "gr88",
vk_format: vk::Format::R8G8_UNORM,
bpp: 2,
drm: fourcc_code('G', 'R', '8', '8'),
..default(ConfigFormat::GR88)
};
static RGB888: &Format = &Format {
name: "rgb888",
vk_format: vk::Format::B8G8R8_UNORM,
bpp: 3,
drm: fourcc_code('R', 'G', '2', '4'),
pipewire: SPA_VIDEO_FORMAT_BGR,
..default(ConfigFormat::RGB888)
};
static BGR888: &Format = &Format {
name: "bgr888",
vk_format: vk::Format::R8G8B8_UNORM,
bpp: 3,
drm: fourcc_code('B', 'G', '2', '4'),
pipewire: SPA_VIDEO_FORMAT_RGB,
..default(ConfigFormat::BGR888)
};
static RGBA4444: &Format = &Format {
name: "rgba4444",
vk_format: vk::Format::R4G4B4A4_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('R', 'A', '1', '2'),
has_alpha: true,
opaque: Some(RGBX4444),
..default(ConfigFormat::RGBA4444)
};
static RGBX4444: &Format = &Format {
name: "rgbx4444",
vk_format: vk::Format::R4G4B4A4_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('R', 'X', '1', '2'),
..default(ConfigFormat::RGBX4444)
};
static BGRA4444: &Format = &Format {
name: "bgra4444",
vk_format: vk::Format::B4G4R4A4_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('B', 'A', '1', '2'),
has_alpha: true,
opaque: Some(BGRX4444),
..default(ConfigFormat::BGRA4444)
};
static BGRX4444: &Format = &Format {
name: "bgrx4444",
vk_format: vk::Format::B4G4R4A4_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('B', 'X', '1', '2'),
..default(ConfigFormat::BGRX4444)
};
static RGB565: &Format = &Format {
name: "rgb565",
vk_format: vk::Format::R5G6B5_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('R', 'G', '1', '6'),
pipewire: SPA_VIDEO_FORMAT_BGR16,
..default(ConfigFormat::RGB565)
};
static BGR565: &Format = &Format {
name: "bgr565",
vk_format: vk::Format::B5G6R5_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('B', 'G', '1', '6'),
pipewire: SPA_VIDEO_FORMAT_RGB16,
..default(ConfigFormat::BGR565)
};
static RGBA5551: &Format = &Format {
name: "rgba5551",
vk_format: vk::Format::R5G5B5A1_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('R', 'A', '1', '5'),
has_alpha: true,
opaque: Some(RGBX5551),
..default(ConfigFormat::RGBA5551)
};
static RGBX5551: &Format = &Format {
name: "rgbx5551",
vk_format: vk::Format::R5G5B5A1_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('R', 'X', '1', '5'),
..default(ConfigFormat::RGBX5551)
};
static BGRA5551: &Format = &Format {
name: "bgra5551",
vk_format: vk::Format::B5G5R5A1_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('B', 'A', '1', '5'),
has_alpha: true,
opaque: Some(BGRX5551),
..default(ConfigFormat::BGRA5551)
};
static BGRX5551: &Format = &Format {
name: "bgrx5551",
vk_format: vk::Format::B5G5R5A1_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('B', 'X', '1', '5'),
..default(ConfigFormat::BGRX5551)
};
static ARGB1555: &Format = &Format {
name: "argb1555",
vk_format: vk::Format::A1R5G5B5_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('A', 'R', '1', '5'),
has_alpha: true,
opaque: Some(XRGB1555),
..default(ConfigFormat::ARGB1555)
};
static XRGB1555: &Format = &Format {
name: "xrgb1555",
vk_format: vk::Format::A1R5G5B5_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('X', 'R', '1', '5'),
pipewire: SPA_VIDEO_FORMAT_BGR15,
..default(ConfigFormat::XRGB1555)
};
static ARGB2101010: &Format = &Format {
name: "argb2101010",
vk_format: vk::Format::A2R10G10B10_UNORM_PACK32,
bpp: 4,
drm: fourcc_code('A', 'R', '3', '0'),
has_alpha: true,
opaque: Some(XRGB2101010),
pipewire: SPA_VIDEO_FORMAT_ARGB_210LE,
..default(ConfigFormat::ARGB2101010)
};
static XRGB2101010: &Format = &Format {
name: "xrgb2101010",
vk_format: vk::Format::A2R10G10B10_UNORM_PACK32,
bpp: 4,
drm: fourcc_code('X', 'R', '3', '0'),
pipewire: SPA_VIDEO_FORMAT_xRGB_210LE,
..default(ConfigFormat::XRGB2101010)
};
static ABGR2101010: &Format = &Format {
name: "abgr2101010",
vk_format: vk::Format::A2B10G10R10_UNORM_PACK32,
bpp: 4,
drm: fourcc_code('A', 'B', '3', '0'),
has_alpha: true,
opaque: Some(XBGR2101010),
pipewire: SPA_VIDEO_FORMAT_ABGR_210LE,
..default(ConfigFormat::ABGR2101010)
};
static XBGR2101010: &Format = &Format {
name: "xbgr2101010",
vk_format: vk::Format::A2B10G10R10_UNORM_PACK32,
bpp: 4,
drm: fourcc_code('X', 'B', '3', '0'),
pipewire: SPA_VIDEO_FORMAT_xBGR_210LE,
..default(ConfigFormat::XBGR2101010)
};
static ABGR16161616: &Format = &Format {
name: "abgr16161616",
vk_format: vk::Format::R16G16B16A16_UNORM,
bpp: 8,
drm: fourcc_code('A', 'B', '4', '8'),
has_alpha: true,
opaque: Some(XBGR16161616),
..default(ConfigFormat::ABGR16161616)
};
static XBGR16161616: &Format = &Format {
name: "xbgr16161616",
vk_format: vk::Format::R16G16B16A16_UNORM,
bpp: 8,
drm: fourcc_code('X', 'B', '4', '8'),
..default(ConfigFormat::XBGR16161616)
};
pub static ABGR16161616F: &Format = &Format {
name: "abgr16161616f",
vk_format: vk::Format::R16G16B16A16_SFLOAT,
bpp: 8,
drm: fourcc_code('A', 'B', '4', 'H'),
has_alpha: true,
opaque: Some(XBGR16161616F),
..default(ConfigFormat::ABGR16161616F)
};
static XBGR16161616F: &Format = &Format {
name: "xbgr16161616f",
vk_format: vk::Format::R16G16B16A16_SFLOAT,
bpp: 8,
drm: fourcc_code('X', 'B', '4', 'H'),
..default(ConfigFormat::XBGR16161616F)
};
static BGR161616: &Format = &Format {
name: "bgr161616",
vk_format: vk::Format::R16G16B16_UNORM,
bpp: 6,
drm: fourcc_code('B', 'G', '4', '8'),
..default(ConfigFormat::BGR161616)
};
static R16F: &Format = &Format {
name: "r16f",
vk_format: vk::Format::R16_SFLOAT,
bpp: 2,
drm: fourcc_code('R', ' ', ' ', 'H'),
..default(ConfigFormat::R16F)
};
static GR1616F: &Format = &Format {
name: "gr1616f",
vk_format: vk::Format::R16G16_SFLOAT,
bpp: 4,
drm: fourcc_code('G', 'R', ' ', 'H'),
..default(ConfigFormat::GR1616F)
};
static BGR161616F: &Format = &Format {
name: "bgr161616f",
vk_format: vk::Format::R16G16B16_SFLOAT,
bpp: 6,
drm: fourcc_code('B', 'G', 'R', 'H'),
..default(ConfigFormat::BGR161616F)
};
static R32F: &Format = &Format {
name: "r32f",
vk_format: vk::Format::R32_SFLOAT,
bpp: 4,
drm: fourcc_code('R', ' ', ' ', 'F'),
..default(ConfigFormat::R32F)
};
static GR3232F: &Format = &Format {
name: "gr3232f",
vk_format: vk::Format::R32G32_SFLOAT,
bpp: 8,
drm: fourcc_code('G', 'R', ' ', 'F'),
..default(ConfigFormat::GR3232F)
};
static BGR323232F: &Format = &Format {
name: "bgr323232f",
vk_format: vk::Format::R32G32B32_SFLOAT,
bpp: 12,
drm: fourcc_code('B', 'G', 'R', 'F'),
..default(ConfigFormat::BGR323232F)
};
static ABGR32323232F: &Format = &Format {
name: "abgr32323232f",
vk_format: vk::Format::R32G32B32A32_SFLOAT,
bpp: 16,
drm: fourcc_code('A', 'B', '8', 'F'),
has_alpha: true,
..default(ConfigFormat::ABGR32323232F)
};
pub static FORMATS: &[Format] = &[
*ARGB8888,
*XRGB8888,
*ABGR8888,
*XBGR8888,
*R8,
*GR88,
*RGB888,
*BGR888,
#[cfg(target_endian = "little")]
*RGBA4444,
#[cfg(target_endian = "little")]
*RGBX4444,
#[cfg(target_endian = "little")]
*BGRA4444,
#[cfg(target_endian = "little")]
*BGRX4444,
#[cfg(target_endian = "little")]
*RGB565,
#[cfg(target_endian = "little")]
*BGR565,
#[cfg(target_endian = "little")]
*RGBA5551,
#[cfg(target_endian = "little")]
*RGBX5551,
#[cfg(target_endian = "little")]
*BGRA5551,
#[cfg(target_endian = "little")]
*BGRX5551,
#[cfg(target_endian = "little")]
*ARGB1555,
#[cfg(target_endian = "little")]
*XRGB1555,
#[cfg(target_endian = "little")]
*ARGB2101010,
#[cfg(target_endian = "little")]
*XRGB2101010,
#[cfg(target_endian = "little")]
*ABGR2101010,
#[cfg(target_endian = "little")]
*XBGR2101010,
#[cfg(target_endian = "little")]
*ABGR16161616,
#[cfg(target_endian = "little")]
*XBGR16161616,
#[cfg(target_endian = "little")]
*ABGR16161616F,
#[cfg(target_endian = "little")]
*XBGR16161616F,
#[cfg(target_endian = "little")]
*BGR161616,
#[cfg(target_endian = "little")]
*R16F,
#[cfg(target_endian = "little")]
*GR1616F,
#[cfg(target_endian = "little")]
*BGR161616F,
#[cfg(target_endian = "little")]
*R32F,
#[cfg(target_endian = "little")]
*GR3232F,
#[cfg(target_endian = "little")]
*BGR323232F,
#[cfg(target_endian = "little")]
*ABGR32323232F,
];
pub use jay_formats::*;

View file

@ -13,9 +13,6 @@ pub type GLuint = c::c_uint;
egl_transparent!(GLeglImageOES);
pub const GL_RGBA: GLint = 0x1908;
pub const GL_RGBA8: GLenum = 0x8058;
pub const GL_BGRA_EXT: GLint = 0x80E1;
pub const GL_CLAMP_TO_EDGE: GLint = 0x812F;
pub const GL_COLOR_ATTACHMENT0: GLenum = 0x8CE0;
pub const GL_COLOR_BUFFER_BIT: GLbitfield = 0x00004000;
@ -40,7 +37,6 @@ pub const GL_TEXTURE_WRAP_T: GLenum = 0x2803;
pub const GL_TRIANGLE_STRIP: GLenum = 0x0005;
pub const GL_TRIANGLES: GLenum = 0x0004;
pub const GL_UNPACK_ROW_LENGTH_EXT: GLenum = 0x0CF2;
pub const GL_UNSIGNED_BYTE: GLint = 0x1401;
pub const GL_VERTEX_SHADER: GLenum = 0x8B31;
pub const GL_BLEND: GLenum = 0x0BE2;
pub const GL_ONE: GLenum = 1;

View file

@ -2871,7 +2871,7 @@ impl ColorTransforms {
mut color: Color,
) -> Color {
if let Some(ct) = self.get_or_create(src, dst, intent) {
color = ct.matrix * color;
color = apply_color_matrix(ct.matrix, color);
};
color
}
@ -2896,6 +2896,25 @@ impl ColorTransforms {
}
}
fn apply_color_matrix(matrix: ColorMatrix, color: Color) -> Color {
let mut rgba = color.to_array(Eotf::Linear);
let a = rgba[3];
if a < 1.0 && a > 0.0 {
for c in &mut rgba[..3] {
*c /= a;
}
}
let [r, g, b] = matrix * [rgba[0] as f64, rgba[1] as f64, rgba[2] as f64];
Color::new(
Eotf::Linear,
AlphaMode::Straight,
r as f32,
g as f32,
b as f32,
a,
)
}
#[derive(Default)]
struct EotfArgsCache {
map: AHashMap<(EotfCacheKey, bool), EotfArg>,

View file

@ -156,7 +156,8 @@ pub trait Global: GlobalBase {
true
}
fn permitted(&self, caps: ClientCaps, xwayland: bool) -> bool {
caps.contains(self.required_caps()) && (xwayland || !self.xwayland_only())
let _ = caps;
xwayland || !self.xwayland_only()
}
fn not_permitted(&self, caps: ClientCaps, xwayland: bool) -> bool {
!self.permitted(caps, xwayland)
@ -345,7 +346,7 @@ impl Globals {
}
pub fn notify_all(&self, registry: &Rc<WlRegistry>) {
let caps = registry.client.effective_caps.get();
let caps = ClientCaps::all();
let xwayland = registry.client.is_xwayland;
let globals = self.registry.lock();
macro_rules! emit {
@ -429,7 +430,7 @@ impl Globals {
}
for client in state.clients.clients.borrow().values() {
let client = &client.data;
let caps = client.effective_caps.get();
let caps = ClientCaps::all();
let xwayland = client.is_xwayland;
for global in &singletons {
if global.permitted(caps, xwayland) {

View file

@ -13,7 +13,6 @@ pub mod head_management;
pub mod hyprland_focus_grab_manager_v1;
pub mod hyprland_focus_grab_v1;
pub mod ipc;
pub mod jay_acceptor_request;
pub mod jay_client_query;
pub mod jay_color_management;
pub mod jay_compositor;

View file

@ -1,60 +0,0 @@
use {
crate::{
client::{Client, ClientError},
leaks::Tracker,
object::{Object, Version},
utils::errorfmt::ErrorFmt,
wire::{JayAcceptorRequestId, jay_acceptor_request::*},
},
std::{error::Error, rc::Rc},
thiserror::Error,
};
pub struct JayAcceptorRequest {
pub id: JayAcceptorRequestId,
pub client: Rc<Client>,
pub tracker: Tracker<Self>,
pub version: Version,
}
impl JayAcceptorRequest {
pub fn send_done(&self, name: &str) {
self.client.event(Done {
self_id: self.id,
name,
});
}
pub fn send_failed(&self, err: impl Error) {
let msg = &ErrorFmt(err).to_string();
self.client.event(Failed {
self_id: self.id,
msg,
});
}
}
impl JayAcceptorRequestRequestHandler for JayAcceptorRequest {
type Error = JayAcceptorRequestError;
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.client.remove_obj(self)?;
Ok(())
}
}
object_base! {
self = JayAcceptorRequest;
version = self.version;
}
impl Object for JayAcceptorRequest {}
simple_add_obj!(JayAcceptorRequest);
#[derive(Debug, Error)]
pub enum JayAcceptorRequestError {
#[error(transparent)]
ClientError(Box<ClientError>),
}
efrom!(JayAcceptorRequestError, ClientError);

View file

@ -9,7 +9,7 @@ use {
jay_client_query::{
AddAll, AddId, Comm, Destroy, Done, End, Exe, Execute, IsXwayland,
JayClientQueryRequestHandler, Pid, SandboxAppId, SandboxEngine, SandboxInstanceId,
Sandboxed, Start, Tag, Uid,
Sandboxed, Start, Uid,
},
},
},
@ -26,8 +26,6 @@ pub struct JayClientQuery {
all: Cell<bool>,
}
const TAG_SINCE: Version = Version(25);
impl JayClientQuery {
pub fn new(client: &Rc<Client>, id: JayClientQueryId, version: Version) -> Self {
Self {
@ -97,14 +95,6 @@ impl JayClientQueryRequestHandler for JayClientQuery {
instance_id,
});
}
if self.version >= TAG_SINCE
&& let Some(tag) = &client.acceptor.tag
{
self.client.event(Tag {
self_id: self.id,
tag,
});
}
self.client.event(End { self_id: self.id });
};
if self.all.get() {

View file

@ -5,7 +5,6 @@ use {
compositor::LogLevel,
globals::{Global, GlobalName},
ifs::{
jay_acceptor_request::JayAcceptorRequest,
jay_client_query::JayClientQuery,
jay_color_management::JayColorManagement,
jay_ei_session_builder::JayEiSessionBuilder,
@ -503,27 +502,6 @@ impl JayCompositorRequestHandler for JayCompositor {
Ok(())
}
fn get_tagged_acceptor(
&self,
req: GetTaggedAcceptor<'_>,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
let obj = Rc::new(JayAcceptorRequest {
id: req.id,
client: self.client.clone(),
tracker: Default::default(),
version: self.version,
});
track!(self.client, obj);
self.client.add_client_obj(&obj)?;
let state = &self.client.state;
match state.tagged_acceptors.get(state, req.tag) {
Ok(d) => obj.send_done(&d),
Err(e) => obj.send_failed(e),
}
Ok(())
}
fn get_sync_file_surface(
&self,
req: GetSyncFileSurface,

View file

@ -1,6 +1,6 @@
use {
crate::{
client::Client,
client::{Client, ClientCaps},
globals::{Global, GlobalName, GlobalsError, Singleton},
leaks::Tracker,
object::{Interface, Object, Version},
@ -61,11 +61,7 @@ impl WlRegistryRequestHandler for WlRegistry {
fn bind(&self, bind: Bind, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let name = GlobalName::from_raw(bind.name);
let globals = &self.client.state.globals;
let global = globals.get(
name,
self.client.effective_caps.get(),
self.client.is_xwayland,
)?;
let global = globals.get(name, ClientCaps::all(), self.client.is_xwayland)?;
if global.interface().name() != bind.interface {
return Err(WlRegistryError::InvalidInterface(InterfaceError {
name: global.name(),

View file

@ -1,7 +1,7 @@
use {
crate::{
client::{Client, ClientError},
cmm::cmm_render_intent::RenderIntent,
cmm::cmm_render_intent,
ifs::wl_surface::WlSurface,
leaks::Tracker,
object::{Object, Version},
@ -52,7 +52,7 @@ impl WpColorManagementSurfaceV1RequestHandler for WpColorManagementSurfaceV1 {
req: SetImageDescription,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
let Some(intent) = RenderIntent::from_wayland(req.render_intent, self.version) else {
let Some(intent) = cmm_render_intent::from_wayland(req.render_intent, self.version) else {
return Err(WpColorManagementSurfaceV1Error::UnsupportedRenderIntent(
req.render_intent,
));

View file

@ -87,7 +87,6 @@ impl WpSecurityContextV1RequestHandler for WpSecurityContextV1 {
self.instance_id.take(),
&self.listen_fd,
&self.close_fd,
self.client.bounding_caps_for_children.get(),
);
Ok(())
}

View file

@ -126,7 +126,7 @@ fn run_test(it_run: &ItRun, test: &'static dyn TestCase, cfg: Rc<TestConfig>) {
let mut addr: c::sockaddr_un = uapi::pod_zeroed();
addr.sun_family = c::AF_UNIX as _;
let acceptor = state.acceptor.get().unwrap();
let path = acceptor.secure_path();
let path = acceptor.socket_path();
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;

View file

@ -370,6 +370,7 @@ macro_rules! dedicated_add_global {
};
}
#[expect(unused_macros)]
macro_rules! assert_size_eq {
($t:ty, $u:ty) => {{
struct AssertEqSize<T, U>(std::marker::PhantomData<T>, std::marker::PhantomData<U>);
@ -401,6 +402,7 @@ macro_rules! assert_size_le {
}};
}
#[expect(unused_macros)]
macro_rules! assert_align_eq {
($t:ty, $u:ty) => {{
struct AssertEqAlign<T, U>(std::marker::PhantomData<T>, std::marker::PhantomData<U>);
@ -632,86 +634,6 @@ macro_rules! bitflags {
};
}
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! ei_id {
($name:ident) => {
#[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)]
@ -829,12 +751,6 @@ macro_rules! not_matches {
};
}
macro_rules! jay_allow_realtime_config_so {
() => {
"JAY_ALLOW_REALTIME_CONFIG_SO"
};
}
#[allow(clippy::allow_attributes, unused_macros)]
macro_rules! dbg {
($val:expr) => {

View file

@ -89,8 +89,6 @@ mod logind;
mod object;
mod output_schedule;
mod pango;
mod pipewire;
mod portal;
mod pr_caps;
mod rect;
mod renderer;
@ -99,7 +97,6 @@ mod screenshoter;
mod security_context_acceptor;
mod sighand;
mod state;
mod tagged_acceptor;
mod tasks;
mod text;
mod theme;

View file

@ -1,7 +0,0 @@
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;

View file

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

View file

@ -1,312 +0,0 @@
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));
}

View file

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

View file

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

View file

@ -1,892 +0,0 @@
#![allow(non_upper_case_globals)]
use {
crate::{
async_engine::SpawnedFuture,
format::{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(format.format.pipewire.0);
f.write_id(format.format.pipewire.0);
});
});
f.write_property(
SPA_FORMAT_VIDEO_modifier.0,
if fixate {
PwPropFlag::none()
} else {
PW_PROP_DONT_FIXATE
},
|f| {
f.write_choice(PW_CHOICE_Enum, 0, |f| {
f.write_ulong(format.modifiers[0]);
for modifier in &format.modifiers {
f.write_ulong(*modifier);
}
});
},
);
if let Some(vs) = sf.video_size {
f.write_property(SPA_FORMAT_VIDEO_size.0, PwPropFlag::none(), |f| {
f.write_choice(PW_CHOICE_Enum, 0, |f| {
f.write_rectangle(vs.width, vs.height);
f.write_rectangle(vs.width, vs.height);
});
});
}
});
}
f.write_object(PW_OBJECT_ParamBuffers, SPA_PARAM_Buffers.0, |f| {
if let Some(num_buffers) = bc.num_buffers {
f.write_property(SPA_PARAM_BUFFERS_buffers.0, PwPropFlag::none(), |f| {
f.write_uint(num_buffers as _);
});
}
if let Some(planes) = bc.planes {
f.write_property(SPA_PARAM_BUFFERS_blocks.0, PwPropFlag::none(), |f| {
f.write_uint(planes as _);
});
}
f.write_property(SPA_PARAM_BUFFERS_dataType.0, PwPropFlag::none(), |f| {
f.write_choice(PW_CHOICE_Flags, 0, |f| {
f.write_uint(1 << bc.data_type.0);
});
});
});
for (key, size) in metas {
f.write_object(PW_OBJECT_ParamMeta, SPA_PARAM_Meta.0, |f| {
f.write_property(SPA_PARAM_META_type.0, PwPropFlag::none(), |f| {
f.write_id(key.0);
});
f.write_property(SPA_PARAM_META_size.0, PwPropFlag::none(), |f| {
f.write_uint(size as u32);
});
});
}
f.write_struct(|f| {
// change mask
f.write_ulong(
SPA_PORT_CHANGE_MASK_FLAGS
// | SPA_PORT_CHANGE_MASK_PROPS
| SPA_PORT_CHANGE_MASK_PARAMS
| SPA_PORT_CHANGE_MASK_RATE,
);
let mut flags = SPA_PORT_FLAG::none();
if port.can_alloc_buffers.get() {
flags = SPA_PORT_FLAG_CAN_ALLOC_BUFFERS;
}
// flags
f.write_ulong(flags.0);
// rate num
f.write_int(0);
// rate denom
f.write_int(1);
// num props
f.write_int(0);
let num_params = 3;
// num params
f.write_uint(num_params);
f.write_id(SPA_PARAM_EnumFormat.0);
f.write_uint((SPA_PARAM_INFO_READ | serial).0);
f.write_id(SPA_PARAM_Buffers.0);
f.write_uint((SPA_PARAM_INFO_READ | serial).0);
f.write_id(SPA_PARAM_Meta.0);
f.write_uint(SPA_PARAM_INFO_READ.0);
});
});
});
}
fn handle_set_param(&self, _p: PwParser<'_>) -> Result<(), PwClientNodeError> {
Ok(())
}
fn handle_set_io(&self, mut p: PwParser<'_>) -> Result<(), PwClientNodeError> {
let s = p.read_struct()?;
let mut p2 = s.fields;
let id = PwIoType(p2.read_id()?);
let memid = p2.read_uint()?;
let offset = p2.read_uint()?;
let size = p2.read_uint()?;
log::debug!("set io {:?}", id);
if memid == !0 {
self.ios.remove(&id);
} else {
let map = match self.con.mem.map(memid, offset, size) {
Ok(m) => m,
Err(e) => {
log::error!("Could not map memory from the pool: {}", ErrorFmt(e));
return Ok(());
}
};
self.ios.set(id, map);
}
Ok(())
}
fn handle_event(&self, _p: PwParser<'_>) -> Result<(), PwClientNodeError> {
Ok(())
}
fn handle_command(self: &Rc<Self>, mut p: PwParser<'_>) -> Result<(), PwClientNodeError> {
let s1 = p.read_struct()?;
let mut p1 = s1.fields;
let obj = p1.read_object()?;
match SpaNodeCommand(obj.id) {
SPA_NODE_COMMAND_Start => {
if let Some(owner) = self.owner.get() {
owner.start();
}
}
SPA_NODE_COMMAND_Pause => {
if let Some(owner) = self.owner.get() {
owner.pause();
}
}
SPA_NODE_COMMAND_Suspend => {
if let Some(owner) = self.owner.get() {
owner.suspend();
}
}
v => {
log::warn!("Unhandled node command {:?}", v);
}
}
Ok(())
}
fn handle_add_port(&self, _p: PwParser<'_>) -> Result<(), PwClientNodeError> {
Ok(())
}
fn handle_remove_port(&self, _p: PwParser<'_>) -> Result<(), PwClientNodeError> {
Ok(())
}
fn port_set_format(
&self,
port: &Rc<PwClientNodePort>,
obj: Option<PwPodObject<'_>>,
) -> Result<(), PwClientNodeError> {
let mut obj = match obj {
Some(obj) => obj,
_ => {
port.negotiated_format.take();
return Ok(());
}
};
let mut format = PwClientNodePortFormat::default();
if let Some(mt) = obj.get_param(SPA_FORMAT_mediaType.0)? {
format.media_type = Some(SpaMediaType(mt.pod.get_id()?));
}
if let Some(mt) = obj.get_param(SPA_FORMAT_mediaSubtype.0)? {
format.media_sub_type = Some(SpaMediaSubtype(mt.pod.get_id()?));
}
if let Some(mt) = obj.get_param(SPA_FORMAT_VIDEO_size.0)? {
format.video_size = Some(mt.pod.get_rectangle()?);
}
if let Some(mt) = obj.get_param(SPA_FORMAT_VIDEO_format.0)?
&& let Some(fmt) = pw_formats().get(&SpaVideoFormat(mt.pod.get_id()?))
{
format.format = Some(*fmt);
}
if let Some(mt) = obj.get_param(SPA_FORMAT_VIDEO_modifier.0)?
&& let PwPod::Choice(mods) = mt.pod
{
let mut p1 = mods.elements.elements;
p1.read_pod_body_packed(PW_TYPE_Long, 8)?;
while p1.len() > 0 {
let modifier = p1.read_pod_body_packed(PW_TYPE_Long, 8)?;
if let PwPod::Long(modifier) = modifier {
format
.modifiers
.get_or_insert_default_ext()
.push(modifier as u64);
}
}
}
if let Some(mt) = obj.get_param(SPA_FORMAT_VIDEO_framerate.0)? {
format.framerate = Some(mt.pod.get_fraction()?);
}
*port.negotiated_format.borrow_mut() = format;
Ok(())
}
fn handle_port_set_param(&self, mut p: PwParser<'_>) -> Result<(), PwClientNodeError> {
let s1 = p.read_struct()?;
let mut p1 = s1.fields;
let direction = SpaDirection(p1.read_uint()?);
let port_id = p1.read_uint()?;
let id = SpaParamType(p1.read_id()?);
let _flags = p1.read_int()?;
let obj = p1.read_object_opt()?;
let port = self.get_port(direction, port_id)?;
match id {
SPA_PARAM_Format => {
self.port_set_format(&port, obj)?;
if let Some(owner) = self.owner.get() {
owner.port_format_changed(&port);
}
}
_ => {
log::warn!(
"port_set_param: Ignoring unexpected port parameter {:?}",
id
);
}
}
Ok(())
}
fn handle_port_use_buffers(&self, mut p: PwParser<'_>) -> Result<(), PwClientNodeError> {
let s1 = p.read_struct()?;
let mut p1 = s1.fields;
let direction = SpaDirection(p1.read_uint()?);
let port_id = p1.read_uint()?;
let _mix_id = p1.read_int()?;
let buffer_flags = SpaNodeBuffersFlags(p1.read_uint()?);
let n_buffers = p1.read_uint()?;
let port = self.get_port(direction, port_id)?;
let mut res = vec![];
for _ in 0..n_buffers {
let mem_id = p1.read_uint()?;
let offset = p1.read_uint()?;
let size = p1.read_uint()?;
let n_metas = p1.read_uint()?;
let mut meta_header = Default::default();
let mut meta_video_crop = Default::default();
let mut meta_busy = Default::default();
let mut chunks = vec![];
let mut slices = vec![];
let mem = self.con.mem.map(mem_id, offset, size)?;
log::debug!(" mem_id={}, offset={}, size={}", mem_id, offset, size);
log::debug!(" n_metas={}", n_metas);
let mut offset = 0;
for _ in 0..n_metas {
let ty = SpaMetaType(p1.read_id()?);
let size = p1.read_uint()? as usize;
match ty {
SPA_META_Header => {
let header = mem.typed_at::<spa_meta_header>(offset);
meta_header = Some(header);
}
SPA_META_VideoCrop => {
let crop = mem.typed_at::<spa_meta_region>(offset);
meta_video_crop = Some(crop);
}
SPA_META_VideoDamage => {
let _video_damage = mem.typed_at::<spa_meta_region>(offset);
}
SPA_META_Bitmap => {
let _bitmap = mem.typed_at::<spa_meta_bitmap>(offset);
}
SPA_META_Cursor => {
let _cursor = mem.typed_at::<spa_meta_cursor>(offset);
}
SPA_META_Control => {}
SPA_META_Busy => {
let busy = mem.typed_at::<spa_meta_busy>(offset);
meta_busy = Some(busy);
}
_ => {}
}
offset += (size + 7) & !7;
}
let n_datas = p1.read_uint()?;
log::debug!(" offset = {}, n_datas={}", offset, n_datas);
for _ in 0..n_datas {
let ty = SpaDataType(p1.read_id()?);
let data_id = p1.read_uint()?;
let _flags = SpaDataFlags(p1.read_uint()?);
let mapoffset = p1.read_uint()?;
let maxsize = p1.read_uint()?;
chunks.push(mem.typed_at(offset));
offset += size_of::<spa_chunk>();
if !buffer_flags.contains(SPA_NODE_BUFFERS_FLAG_ALLOC) {
if ty == SPA_DATA_MemPtr {
let offset = data_id as usize;
slices.push(mem.slice(offset..offset + maxsize as usize));
} else if ty == SPA_DATA_MemFd {
let mem = self.con.mem.map(data_id, mapoffset, maxsize)?;
slices.push(mem.slice(0..maxsize as usize));
}
}
}
res.push(Rc::new(PwClientNodeBuffer {
_meta_header: meta_header,
_meta_busy: meta_busy,
meta_video_crop,
chunks,
_slices: slices,
}));
}
*port.buffers.borrow_mut() = res;
if let Some(owner) = self.owner.get() {
owner.use_buffers(&port);
}
Ok(())
}
fn handle_port_set_io(&self, mut p: PwParser<'_>) -> Result<(), PwClientNodeError> {
let s = p.read_struct()?;
let mut p2 = s.fields;
let direction = SpaDirection(p2.read_uint()?);
let port_id = p2.read_uint()?;
let mix_id = p2.read_uint()?;
let id = SpaIoType(p2.read_id()?);
let mem_id = p2.read_uint()?;
let offset = p2.read_uint()?;
let size = p2.read_uint()?;
let port = self.get_port(direction, port_id)?;
match id {
SPA_IO_Buffers if mix_id == 0 => {
if mem_id == !0 {
port.io_buffers.take();
} else {
let io_buffers = self
.con
.mem
.map(mem_id, offset, size)?
.typed::<spa_io_buffers>();
unsafe {
io_buffers.read().buffer_id.store(!0, Relaxed);
io_buffers.read().status.store(0, Relaxed);
}
port.io_buffers.set(Some(io_buffers));
}
}
_ => {}
}
Ok(())
}
fn handle_transport(self: &Rc<Self>, mut p: PwParser<'_>) -> Result<(), PwClientNodeError> {
let s = p.read_struct()?;
let mut p2 = s.fields;
let readfd = p2.read_fd()?;
let writefd = p2.read_fd()?;
let memid = p2.read_uint()?;
let offset = p2.read_uint()?;
let size = p2.read_uint()?;
let map = match self.con.mem.map(memid, offset, size) {
Ok(m) => m,
Err(e) => {
log::error!("Could not map memory from the pool: {}", ErrorFmt(e));
return Ok(());
}
};
let typed = map.typed::<pw_node_activation>();
self.activation.set(Some(typed.clone()));
self.transport_in.set(Some(
self.con
.eng
.spawn("pw transport in", self.clone().transport_in(typed, readfd)),
));
self.transport_out.set(Some(writefd));
Ok(())
}
fn handle_set_activation(
self: &Rc<Self>,
mut p: PwParser<'_>,
) -> Result<(), PwClientNodeError> {
let s = p.read_struct()?;
let mut p2 = s.fields;
let node = p2.read_uint()?;
let signalfd = p2.read_fd_opt()?;
if let Some(signalfd) = signalfd {
let memid = p2.read_uint()?;
let offset = p2.read_uint()?;
let size = p2.read_uint()?;
let map = match self.con.mem.map(memid, offset, size) {
Ok(m) => m,
Err(e) => {
log::error!("Could not map memory from the pool: {}", ErrorFmt(e));
return Ok(());
}
};
let typed = map.typed::<pw_node_activation>();
self.activations.set(
node,
Rc::new(PwNodeActivation {
activation: typed,
fd: signalfd,
}),
);
} else {
self.activations.remove(&node);
}
Ok(())
}
fn get_port(
&self,
direction: SpaDirection,
port_id: u32,
) -> Result<Rc<PwClientNodePort>, PwClientNodeError> {
match self.ports.get(&(direction, port_id)) {
Some(p) => Ok(p),
_ => Err(PwClientNodeError::UnknownPort(direction, port_id)),
}
}
fn handle_port_set_mix_info(&self, mut p: PwParser<'_>) -> Result<(), PwClientNodeError> {
let s1 = p.read_struct()?;
let mut p1 = s1.fields;
let direction = SpaDirection(p1.read_uint()?);
let port_id = p1.read_uint()?;
let mix_id = p1.read_int()?;
let peer_id = p1.read_int()?;
let dict = p1.read_dict_struct()?;
let _port = self.get_port(direction, port_id)?;
log::debug!(
"mix info: mix_id={}, peer_id={}, dict={:#?}",
mix_id,
peer_id,
dict
);
Ok(())
}
async fn transport_in(
self: Rc<Self>,
_activation: Rc<PwMemTyped<pw_node_activation>>,
fd: Rc<OwnedFd>,
) {
let mut buf = TypedBuf::<u64>::new();
loop {
if let Err(e) = self.con.ring.read(&fd, buf.buf()).await {
log::error!("Could not read from eventfd: {}", ErrorFmt(e));
return;
}
if let Some(activation) = self.activation.get() {
let activation = unsafe { activation.read() };
activation
.status
.store(PW_NODE_ACTIVATION_FINISHED.0, Relaxed);
}
}
}
pub fn drive(&self) {
for activation in self.activations.lock().values() {
let a = unsafe { activation.activation.read() };
let required = a.state[0].required.load(Relaxed);
a.state[0].pending.store(required - 1, Relaxed);
if required == 1 {
a.status.store(PW_NODE_ACTIVATION_TRIGGERED.0, Release);
let _ = uapi::eventfd_write(activation.fd.raw(), 1);
} else {
a.status.store(PW_NODE_ACTIVATION_NOT_TRIGGERED.0, Release);
}
}
}
}
pw_object_base! {
PwClientNode, "client-node", PwClientNodeEvents;
Transport => handle_transport,
SetParam => handle_set_param,
SetIo => handle_set_io,
Event => handle_event,
Command => handle_command,
AddPort => handle_add_port,
RemovePort => handle_remove_port,
PortSetParam => handle_port_set_param,
PortUseBuffers => handle_port_use_buffers,
PortSetIo => handle_port_set_io,
SetActivation => handle_set_activation,
PortSetMixInfo => handle_port_set_mix_info,
}
impl PwObject for PwClientNode {
fn bound_id(&self, id: u32) {
if let Some(owner) = self.owner.get() {
owner.bound_id(id);
}
}
fn break_loops(&self) {
self.owner.take();
self.ports.clear();
self.transport_in.take();
self.transport_out.take();
}
}
#[derive(Debug, Error)]
pub enum PwClientNodeError {
#[error(transparent)]
PwParserError(#[from] PwParserError),
#[error(transparent)]
PwMemError(#[from] PwMemError),
#[error("Unknown port {0:?}@{1}")]
UnknownPort(SpaDirection, u32),
}

View file

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

View file

@ -1,48 +0,0 @@
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),
}

View file

@ -1,155 +0,0 @@
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),
}

View file

@ -1,52 +0,0 @@
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;
}

View file

@ -1,312 +0,0 @@
#![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(())
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,464 +0,0 @@
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),
}
}
}

View file

@ -1,353 +0,0 @@
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,
compositor::LogLevel,
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::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");
}
}

View file

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

View file

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

View file

@ -1,160 +0,0 @@
use {
crate::{
globals::GlobalName,
ifs::wl_seat::{BTN_LEFT, wl_pointer::PRESSED},
portal::{
ptl_display::{PortalDisplay, PortalOutput, PortalSeat},
ptl_remote_desktop::{PortalSession, RemoteDesktopPhase},
ptr_gui::{
Align, Button, ButtonOwner, Flow, GuiElement, Label, Orientation, OverlayWindow,
OverlayWindowOwner,
},
},
theme::Color,
utils::{copyhashmap::CopyHashMap, hash_map_ext::HashMapExt},
},
std::rc::Rc,
};
const H_MARGIN: f32 = 30.0;
const V_MARGIN: f32 = 20.0;
pub struct SelectionGui {
remote_desktop_session: Rc<PortalSession>,
dpy: Rc<PortalDisplay>,
surfaces: CopyHashMap<GlobalName, Rc<SelectionGuiSurface>>,
}
pub struct SelectionGuiSurface {
gui: Rc<SelectionGui>,
output: Rc<PortalOutput>,
overlay: Rc<OverlayWindow>,
}
struct StaticButton {
surface: Rc<SelectionGuiSurface>,
role: ButtonRole,
}
#[derive(Copy, Clone, Eq, PartialEq)]
enum ButtonRole {
Accept,
Reject,
}
impl SelectionGui {
pub fn kill(&self, upwards: bool) {
for surface in self.surfaces.lock().drain_values() {
surface.overlay.data.kill(false);
}
if let RemoteDesktopPhase::Selecting(s) = self.remote_desktop_session.rd_phase.get() {
s.guis.remove(&self.dpy.id);
if upwards && s.guis.is_empty() {
self.remote_desktop_session.kill();
}
}
}
}
fn create_accept_gui(surface: &Rc<SelectionGuiSurface>) -> Rc<dyn GuiElement> {
let app = &surface.gui.remote_desktop_session.app;
let text = if app.is_empty() {
format!("An application wants to generate/monitor input")
} else {
format!("`{}` wants to generate/monitor input", app)
};
let label = Rc::new(Label::default());
*label.text.borrow_mut() = text;
let accept_button = static_button(surface, ButtonRole::Accept, "Allow");
let reject_button = static_button(surface, ButtonRole::Reject, "Reject");
for button in [&accept_button, &reject_button] {
button.border_color.set(Color::from_gray_srgb(100));
button.border.set(2.0);
button.padding.set(5.0);
}
accept_button.bg_color.set(Color::from_srgb(170, 200, 170));
accept_button
.bg_hover_color
.set(Color::from_srgb(170, 255, 170));
reject_button.bg_color.set(Color::from_srgb(200, 170, 170));
reject_button
.bg_hover_color
.set(Color::from_srgb(255, 170, 170));
let flow = Rc::new(Flow::default());
flow.orientation.set(Orientation::Vertical);
flow.cross_align.set(Align::Center);
flow.in_margin.set(V_MARGIN);
flow.cross_margin.set(H_MARGIN);
*flow.elements.borrow_mut() = vec![label, accept_button, reject_button];
flow
}
impl OverlayWindowOwner for SelectionGuiSurface {
fn kill(&self, upwards: bool) {
self.gui.dpy.windows.remove(&self.overlay.data.surface.id);
self.gui.surfaces.remove(&self.output.global_id);
if upwards && self.gui.surfaces.is_empty() {
self.gui.kill(true);
}
}
}
impl SelectionGui {
pub fn new(ss: &Rc<PortalSession>, dpy: &Rc<PortalDisplay>) -> Rc<Self> {
let gui = Rc::new(SelectionGui {
remote_desktop_session: ss.clone(),
dpy: dpy.clone(),
surfaces: Default::default(),
});
for output in dpy.outputs.lock().values() {
let sgs = Rc::new(SelectionGuiSurface {
gui: gui.clone(),
output: output.clone(),
overlay: OverlayWindow::new(output),
});
let element = create_accept_gui(&sgs);
sgs.overlay.data.content.set(Some(element));
gui.dpy
.windows
.set(sgs.overlay.data.surface.id, sgs.overlay.data.clone());
gui.surfaces.set(output.global_id, sgs);
}
gui
}
}
impl ButtonOwner for StaticButton {
fn button(&self, _seat: &PortalSeat, button: u32, state: u32) {
if button != BTN_LEFT || state != PRESSED {
return;
}
match self.role {
ButtonRole::Accept => {
log::info!("User has accepted the request");
let selecting = match self.surface.gui.remote_desktop_session.rd_phase.get() {
RemoteDesktopPhase::Selecting(selecting) => selecting,
_ => return,
};
for gui in selecting.guis.lock().drain_values() {
gui.kill(false);
}
selecting.starting(&self.surface.output.dpy);
}
ButtonRole::Reject => {
log::info!("User has rejected the remote desktop request");
self.surface.gui.remote_desktop_session.kill();
}
}
}
}
fn static_button(surface: &Rc<SelectionGuiSurface>, role: ButtonRole, text: &str) -> Rc<Button> {
let button = Rc::new(Button::default());
let slf = Rc::new(StaticButton {
surface: surface.clone(),
role,
});
button.owner.set(Some(slf));
*button.text.borrow_mut() = text.to_string();
button
}

View file

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

View file

@ -1,967 +0,0 @@
mod screencast_gui;
use {
crate::{
allocator::{AllocatorError, BO_USE_RENDERING, BufferObject, BufferUsage},
dbus::{DbusObject, DictEntry, PendingReply, prelude::Variant},
format::{Format, XRGB8888},
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 format.format.pipewire == SPA_VIDEO_FORMAT_UNKNOWN {
continue;
}
let ptl_format = PwClientNodePortSupportedFormat {
format: format.format,
modifiers: format.write_modifiers.keys().copied().collect(),
};
supported_formats.formats.push(ptl_format);
}
}
impl UsrLinuxBufferParamsOwner for StartedScreencast {
fn created(&self, buffer: Rc<UsrWlBuffer>) {
self.buffers_valid.set(true);
self.jay_screencast.add_buffer(&buffer);
self.jay_screencast.configure();
self.dpy.con.remove_obj(&*buffer);
}
fn failed(&self) {
log::error!("Buffer import failed");
self.session.kill();
}
}
pub(super) fn add_screencast_dbus_members(
state_: &Rc<PortalState>,
pw_con: &Rc<PwCon>,
object: &DbusObject,
) {
use org::freedesktop::impl_::portal::screen_cast::*;
let state = state_.clone();
let pw_con = pw_con.clone();
object.add_method::<CreateSession, _>(move |req, pr| {
dbus_create_session(&state, &pw_con, req, pr);
});
let state = state_.clone();
object.add_method::<SelectSources, _>(move |req, pr| {
dbus_select_sources(&state, req, pr);
});
let state = state_.clone();
object.add_method::<Start, _>(move |req, pr| {
dbus_start(&state, req, pr);
});
object.set_property::<AvailableSourceTypes>(Variant::U32(MONITOR.0));
object.set_property::<AvailableCursorModes>(Variant::U32(EMBEDDED.0));
object.set_property::<version>(Variant::U32(5));
}
fn dbus_create_session(
state: &Rc<PortalState>,
pw_con: &Rc<PwCon>,
req: CreateSession,
reply: PendingReply<CreateSessionReply<'static>>,
) {
log::info!("Create Session {:#?}", req);
if state.sessions.contains(req.session_handle.0.deref()) {
reply.err("Session already exists");
return;
}
let obj = match state.dbus.add_object(req.session_handle.0.to_string()) {
Ok(obj) => obj,
Err(_) => {
reply.err("Session path is not unique");
return;
}
};
let session = Rc::new(PortalSession {
_id: state.id(),
state: state.clone(),
pw_con: Some(pw_con.clone()),
app: req.app_id.to_string(),
session_obj: obj,
sc_phase: CloneCell::new(ScreencastPhase::Init),
rd_phase: CloneCell::new(RemoteDesktopPhase::Init),
start_reply: Default::default(),
});
{
use org::freedesktop::impl_::portal::session::*;
let ses = session.clone();
session.session_obj.add_method::<Close, _>(move |_, pr| {
ses.kill();
pr.ok(&SessionCloseReply);
});
session.session_obj.set_property::<version>(Variant::U32(4));
}
state
.sessions
.set(req.session_handle.0.to_string(), session);
reply.ok(&CreateSessionReply {
response: PORTAL_SUCCESS,
results: Default::default(),
});
}
fn dbus_select_sources(
state: &Rc<PortalState>,
req: SelectSources,
reply: PendingReply<SelectSourcesReply<'static>>,
) {
if let Some(s) = get_session(state, &reply, &req.session_handle.0) {
s.dbus_select_sources(req, reply);
}
}
fn dbus_start(state: &Rc<PortalState>, req: Start, reply: PendingReply<StartReply<'static>>) {
if let Some(s) = get_session(state, &reply, &req.session_handle.0) {
s.dbus_start_screencast(req, reply);
}
}
fn get_session<T>(
state: &Rc<PortalState>,
reply: &PendingReply<T>,
handle: &str,
) -> Option<Rc<PortalSession>> {
let res = state.sessions.get(handle);
if res.is_none() {
let msg = format!("Screencast session `{}` does not exist", handle);
reply.err(&msg);
}
res
}
fn create_restore_data(dpy: &PortalDisplay, rd: &ScreencastTarget) -> Option<Variant<'static>> {
let rd = RestoreData {
display: dpy.unique_id,
ty: match rd {
ScreencastTarget::Output(o) => RestoreDataType::Output(RestoreDataOutput {
name: o.wl.name.borrow().clone()?,
}),
ScreencastTarget::Workspace(_, w, _) => {
RestoreDataType::Workspace(RestoreDataWorkspace {
name: w.name.borrow().clone()?,
})
}
ScreencastTarget::Toplevel(tl) => RestoreDataType::Toplevel(RestoreDataToplevel {
id: tl.toplevel_id.borrow().clone()?,
}),
},
};
Some(Variant::Struct(vec![
Variant::String("Jay".into()),
Variant::U32(1),
Variant::Variant(Box::new(Variant::String(
serde_json::to_string(&rd).unwrap().into(),
))),
]))
}
#[derive(Debug, Error)]
pub enum RestoreError {
#[error("DBus restore data is not a struct")]
NotAStruct,
#[error("DBus restore data is not a struct with 3 fields")]
NotLen3,
#[error("DBus restore data first field is not a string")]
FirstNotString,
#[error("DBus restore data second field is not a u32")]
SecondNotU32,
#[error("DBus restore data third field is not a variant")]
ThirdNotVariant,
#[error("DBus restore data third field is not a string")]
ThirdNotString,
#[error("DBus restore data is not for Jay")]
NotJay,
#[error("DBus restore data is not version 1")]
NotVersion1,
#[error("DBus restore data could not be deserialized")]
Parse(#[source] serde_json::Error),
#[error("The display no longer exists")]
UnknownDisplay,
#[error("The output no longer exists")]
UnknownOutput,
#[error("The workspace no longer exists")]
UnknownWorkspace,
#[error("The display does not support toplevel restoration")]
GetToplevel,
}
fn get_restore_data(req: &SelectSources) -> Option<Result<RestoreData, RestoreError>> {
let restore_data = req.options.iter().find(|n| n.key == "restore_data")?;
Some(get_restore_data_(restore_data))
}
fn get_restore_data_(
restore_data: &DictEntry<Cow<str>, Variant>,
) -> Result<RestoreData, RestoreError> {
let Variant::Struct(s) = &restore_data.value else {
return Err(RestoreError::NotAStruct);
};
if s.len() != 3 {
return Err(RestoreError::NotLen3);
}
let Variant::String(compositor) = &s[0] else {
return Err(RestoreError::FirstNotString);
};
let Variant::U32(version) = &s[1] else {
return Err(RestoreError::SecondNotU32);
};
let Variant::Variant(restore_data) = &s[2] else {
return Err(RestoreError::ThirdNotVariant);
};
let Variant::String(restore_data) = &**restore_data else {
return Err(RestoreError::ThirdNotString);
};
if compositor != "Jay" {
return Err(RestoreError::NotJay);
}
if *version != 1 {
return Err(RestoreError::NotVersion1);
}
serde_json::from_str(restore_data).map_err(RestoreError::Parse)
}
#[derive(Serialize, Deserialize)]
pub struct RestoreData {
display: Opaque,
ty: RestoreDataType,
}
#[derive(Serialize, Deserialize)]
enum RestoreDataType {
Output(RestoreDataOutput),
Workspace(RestoreDataWorkspace),
Toplevel(RestoreDataToplevel),
}
#[derive(Serialize, Deserialize)]
struct RestoreDataOutput {
name: String,
}
#[derive(Serialize, Deserialize)]
struct RestoreDataWorkspace {
name: String,
}
#[derive(Serialize, Deserialize)]
struct RestoreDataToplevel {
id: String,
}

View file

@ -1,297 +0,0 @@
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
}

View file

@ -1,169 +0,0 @@
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),
}
}
}
}

View file

@ -1,102 +0,0 @@
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))
}

View file

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

View file

@ -1,368 +1 @@
mod region;
#[cfg(test)]
mod tests;
pub use region::{DamageQueue, RegionBuilder};
use {
jay_algorithms::rect::{NoTag, RectRaw, Tag},
smallvec::SmallVec,
std::fmt::{Debug, Formatter},
};
#[derive(Copy, Clone, Eq, PartialEq, Default)]
#[repr(transparent)]
pub struct Rect<T = NoTag>
where
T: Tag,
{
raw: RectRaw<T>,
}
#[derive(Clone, Eq, PartialEq, Debug, Default)]
pub struct Region<T = NoTag>
where
T: Tag,
{
rects: SmallVec<[RectRaw<T>; 1]>,
extents: Rect,
}
impl Debug for Rect {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&self.raw, f)
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
pub struct RectOverflow {
pub left: i32,
pub right: i32,
pub top: i32,
pub bottom: i32,
}
impl RectOverflow {
pub fn is_contained(&self) -> bool {
self.left <= 0 && self.right <= 0 && self.top <= 0 && self.bottom <= 0
}
pub fn x_overflow(&self) -> bool {
self.left > 0 || self.right > 0
}
pub fn y_overflow(&self) -> bool {
self.top > 0 || self.bottom > 0
}
}
impl<T> Rect<T>
where
T: Tag,
{
pub fn untag(&self) -> Rect {
Rect {
raw: RectRaw {
x1: self.raw.x1,
y1: self.raw.y1,
x2: self.raw.x2,
y2: self.raw.y2,
tag: NoTag,
},
}
}
}
impl Rect {
pub fn new_empty(x: i32, y: i32) -> Self {
Self {
raw: RectRaw {
x1: x,
y1: y,
x2: x,
y2: y,
tag: NoTag,
},
}
}
pub fn new(x1: i32, y1: i32, x2: i32, y2: i32) -> Option<Self> {
if x2 < x1 || y2 < y1 {
return None;
}
Some(Self {
raw: RectRaw {
x1,
y1,
x2,
y2,
tag: NoTag,
},
})
}
#[cfg_attr(not(test), expect(dead_code))]
fn new_unchecked_danger(x1: i32, y1: i32, x2: i32, y2: i32) -> Self {
Self {
raw: RectRaw {
x1,
y1,
x2,
y2,
tag: NoTag,
},
}
}
pub fn new_sized(x1: i32, y1: i32, width: i32, height: i32) -> Option<Self> {
if width < 0 || height < 0 {
return None;
}
Self::new(x1, y1, x1 + width, y1 + height)
}
pub fn new_saturating(x1: i32, y1: i32, x2: i32, y2: i32) -> Self {
Self {
raw: RectRaw {
x1,
y1,
x2: x2.max(x1),
y2: y2.max(y1),
tag: NoTag,
},
}
}
pub fn new_sized_saturating(x1: i32, y1: i32, width: i32, height: i32) -> Self {
Self {
raw: RectRaw {
x1,
y1,
x2: x1.saturating_add(width.max(0)),
y2: y1.saturating_add(height.max(0)),
tag: NoTag,
},
}
}
pub fn union(&self, other: Self) -> Self {
Self {
raw: RectRaw {
x1: self.raw.x1.min(other.raw.x1),
y1: self.raw.y1.min(other.raw.y1),
x2: self.raw.x2.max(other.raw.x2),
y2: self.raw.y2.max(other.raw.y2),
tag: NoTag,
},
}
}
pub fn intersect(&self, other: Self) -> Self {
let x1 = self.raw.x1.max(other.raw.x1);
let y1 = self.raw.y1.max(other.raw.y1);
let x2 = self.raw.x2.min(other.raw.x2).max(x1);
let y2 = self.raw.y2.min(other.raw.y2).max(y1);
Self {
raw: RectRaw {
x1,
y1,
x2,
y2,
tag: NoTag,
},
}
}
pub fn with_size_saturating(&self, width: i32, height: i32) -> Self {
Self::new_sized_saturating(self.raw.x1, self.raw.y1, width, height)
}
pub fn with_tag(&self, tag: u32) -> Rect<u32> {
Rect {
raw: RectRaw {
x1: self.raw.x1,
y1: self.raw.y1,
x2: self.raw.x2,
y2: self.raw.y2,
tag,
},
}
}
}
impl<T> Rect<T>
where
T: Tag,
{
#[cfg_attr(not(test), expect(dead_code))]
fn new_unchecked_danger_tagged(x1: i32, y1: i32, x2: i32, y2: i32, tag: T) -> Self {
Self {
raw: RectRaw {
x1,
y1,
x2,
y2,
tag,
},
}
}
pub fn intersects(&self, other: &Self) -> bool {
self.raw.x1 < other.raw.x2
&& other.raw.x1 < self.raw.x2
&& self.raw.y1 < other.raw.y2
&& other.raw.y1 < self.raw.y2
}
pub fn contains(&self, x: i32, y: i32) -> bool {
self.raw.x1 <= x && self.raw.y1 <= y && self.raw.x2 > x && self.raw.y2 > y
}
pub fn not_contains(&self, x: i32, y: i32) -> bool {
!self.contains(x, y)
}
pub fn dist_squared(&self, x: i32, y: i32) -> i128 {
let x = x as i64;
let y = y as i64;
let x1 = self.raw.x1 as i64;
let x2 = self.raw.x2 as i64;
let y1 = self.raw.y1 as i64;
let y2 = self.raw.y2 as i64;
let mut dx = 0;
if x1 > x {
dx = x1 - x;
} else if x2 < x {
dx = x - x2;
}
let mut dy = 0;
if y1 > y {
dy = y1 - y;
} else if y2 < y {
dy = y - y2;
}
let dx = dx as i128;
let dy = dy as i128;
dx * dx + dy * dy
}
#[expect(dead_code)]
pub fn contains_rect<U>(&self, rect: &Rect<U>) -> bool
where
U: Tag,
{
self.raw.x1 <= rect.raw.x1
&& self.raw.y1 <= rect.raw.x1
&& rect.raw.x2 <= self.raw.x2
&& rect.raw.y2 <= self.raw.y2
}
pub fn get_overflow<U>(&self, child: &Rect<U>) -> RectOverflow
where
U: Tag,
{
RectOverflow {
left: self.raw.x1 - child.raw.x1,
right: child.raw.x2 - self.raw.x2,
top: self.raw.y1 - child.raw.y1,
bottom: child.raw.y2 - self.raw.y2,
}
}
pub fn is_empty(&self) -> bool {
self.raw.x1 == self.raw.x2 || self.raw.y1 == self.raw.y2
}
#[expect(dead_code)]
pub fn is_not_empty(&self) -> bool {
!self.is_empty()
}
#[expect(dead_code)]
pub fn to_origin(&self) -> Self {
Self {
raw: RectRaw {
x1: 0,
y1: 0,
x2: self.raw.x2 - self.raw.x1,
y2: self.raw.y2 - self.raw.y1,
tag: self.raw.tag,
},
}
}
pub fn move_(&self, dx: i32, dy: i32) -> Self {
Self {
raw: RectRaw {
x1: self.raw.x1.saturating_add(dx),
y1: self.raw.y1.saturating_add(dy),
x2: self.raw.x2.saturating_add(dx),
y2: self.raw.y2.saturating_add(dy),
tag: self.raw.tag,
},
}
}
pub fn at_point(&self, x1: i32, y1: i32) -> Self {
Self {
raw: RectRaw {
x1,
y1,
x2: x1 + self.raw.x2 - self.raw.x1,
y2: y1 + self.raw.y2 - self.raw.y1,
tag: self.raw.tag,
},
}
}
pub fn translate(&self, x: i32, y: i32) -> (i32, i32) {
(x.wrapping_sub(self.raw.x1), y.wrapping_sub(self.raw.y1))
}
pub fn translate_inv(&self, x: i32, y: i32) -> (i32, i32) {
(x.wrapping_add(self.raw.x1), y.wrapping_add(self.raw.y1))
}
pub fn x1(&self) -> i32 {
self.raw.x1
}
pub fn x2(&self) -> i32 {
self.raw.x2
}
pub fn y1(&self) -> i32 {
self.raw.y1
}
pub fn y2(&self) -> i32 {
self.raw.y2
}
pub fn width(&self) -> i32 {
self.raw.x2 - self.raw.x1
}
pub fn height(&self) -> i32 {
self.raw.y2 - self.raw.y1
}
pub fn position(&self) -> (i32, i32) {
(self.raw.x1, self.raw.y1)
}
pub fn size(&self) -> (i32, i32) {
(self.width(), self.height())
}
pub fn center(&self) -> (i32, i32) {
(
self.raw.x1 + self.width() / 2,
self.raw.y1 + self.height() / 2,
)
}
pub fn tag(&self) -> T {
self.raw.tag
}
}
pub use jay_geometry::*;

View file

@ -1,346 +0,0 @@
use {
crate::{
rect::{Rect, Region},
utils::{
array,
ptr_ext::{MutPtrExt, PtrExt},
},
},
jay_algorithms::rect::{
RectRaw, Tag,
region::{
extents, intersect, intersect_tagged, rects_to_bands, rects_to_bands_tagged, subtract,
union,
},
},
smallvec::SmallVec,
std::{
borrow::Cow,
cell::UnsafeCell,
fmt::{Debug, Formatter},
mem,
ops::Deref,
rc::Rc,
},
};
thread_local! {
static EMPTY: Rc<Region> =
Rc::new(Region {
rects: Default::default(),
extents: Default::default(),
});
}
impl Region {
pub fn empty() -> Rc<Self> {
EMPTY.with(|e| e.clone())
}
pub fn from_rects(rects: &[Rect]) -> Rc<Self> {
if rects.is_empty() {
return Self::empty();
}
Rc::new(Self::from_rects2(rects))
}
pub fn from_rects2(rects: &[Rect]) -> Self {
if rects.is_empty() {
return Self::default();
}
if rects.len() == 1 {
return Self::new(rects[0]);
}
let rects = rects_to_bands(unsafe { mem::transmute::<&[Rect], &[RectRaw]>(rects) });
Self {
extents: Rect {
raw: extents(&rects),
},
rects,
}
}
pub fn union(self: &Rc<Self>, other: &Rc<Self>) -> Rc<Self> {
if self.extents.is_empty() {
return other.clone();
}
if other.extents.is_empty() {
return self.clone();
}
let rects = union(&self.rects, &other.rects);
Rc::new(Self {
rects,
extents: self.extents.union(other.extents),
})
}
pub fn union_cow<'a>(&'a self, other: &'a Self) -> Cow<'a, Region> {
if self.extents.is_empty() {
return Cow::Borrowed(other);
}
if other.extents.is_empty() {
return Cow::Borrowed(self);
}
let rects = union(&self.rects, &other.rects);
Cow::Owned(Self {
rects,
extents: self.extents.union(other.extents),
})
}
pub fn subtract(self: &Rc<Self>, other: &Rc<Self>) -> Rc<Self> {
if self.extents.is_empty() || other.extents.is_empty() {
return self.clone();
}
let rects = subtract(&self.rects, &other.rects);
Rc::new(Self {
extents: Rect {
raw: extents(&rects),
},
rects,
})
}
pub fn subtract_cow<'a>(&'a self, other: &Self) -> Cow<'a, Self> {
if self.extents.is_empty() || other.extents.is_empty() {
return Cow::Borrowed(self);
}
let rects = subtract(&self.rects, &other.rects);
Cow::Owned(Self {
extents: Rect {
raw: extents(&rects),
},
rects,
})
}
pub fn intersect(&self, other: &Region) -> Self {
if self.is_empty() || other.is_empty() {
return Self::default();
}
let rects = intersect(&self.rects, &other.rects);
Self {
extents: Rect {
raw: extents(&rects),
},
rects,
}
}
}
impl Region<u32> {
pub fn from_rects_tagged(rects: &[Rect<u32>]) -> Self {
if rects.is_empty() {
return Self::default();
}
if rects.len() == 1 {
let mut rect = rects[0];
rect.raw.tag = rect.raw.tag.constrain();
return Self::new(rect);
}
let rects = rects_to_bands_tagged(unsafe {
mem::transmute::<&[Rect<u32>], &[RectRaw<u32>]>(rects)
});
Self {
extents: Rect {
raw: extents(&rects),
},
rects,
}
}
pub fn intersect_tagged(&self, other: &Region) -> Self {
if self.is_empty() || other.is_empty() {
return Self::default();
}
let rects = intersect_tagged(&self.rects, &other.rects);
Self {
extents: Rect {
raw: extents(&rects),
},
rects,
}
}
}
impl<T> Region<T>
where
T: Tag,
{
pub fn new(rect: Rect<T>) -> Self {
let mut rects = SmallVec::new();
rects.push(rect.raw);
Self {
rects,
extents: rect.untag(),
}
}
#[cfg_attr(not(feature = "it"), expect(dead_code))]
pub fn extents(&self) -> Rect {
self.extents
}
pub fn rects(&self) -> &[Rect<T>] {
unsafe { mem::transmute::<&[RectRaw<T>], &[Rect<T>]>(&self.rects[..]) }
}
pub fn contains(&self, x: i32, y: i32) -> bool {
if !self.extents.contains(x, y) {
return false;
}
for r in self.deref() {
if r.contains(x, y) {
return true;
}
}
false
}
pub fn contains_rect(&self, rect: &Rect) -> bool {
self.contains_rect2(rect, |r| *r)
}
pub fn contains_rect2(&self, rect: &Rect, map: impl Fn(&Rect<T>) -> Rect<T>) -> bool {
if rect.is_empty() {
return true;
}
let mut y1 = rect.y1();
for r in self.rects() {
let r = map(r);
if r.y2() <= y1 || r.x2() <= rect.x1() {
continue;
}
if r.y1() > y1 || r.x1() > rect.x1() || r.x2() < rect.x2() {
return false;
}
y1 = r.y2();
if y1 >= rect.y2() {
return true;
}
}
false
}
}
impl<T> Deref for Region<T>
where
T: Tag,
{
type Target = [Rect<T>];
fn deref(&self) -> &Self::Target {
unsafe { mem::transmute::<&[RectRaw<T>], _>(&self.rects) }
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
enum BuilderOp {
#[default]
Add,
Sub,
}
#[derive(Debug)]
pub struct RegionBuilder {
base: Rc<Region>,
op: BuilderOp,
pending: Vec<Rect>,
}
impl Default for RegionBuilder {
fn default() -> Self {
Self {
base: Region::empty(),
op: Default::default(),
pending: Default::default(),
}
}
}
impl RegionBuilder {
pub fn add(&mut self, rect: Rect) {
self.set_op(BuilderOp::Add);
self.pending.push(rect);
}
pub fn sub(&mut self, rect: Rect) {
self.set_op(BuilderOp::Sub);
self.pending.push(rect);
}
pub fn get(&mut self) -> Rc<Region> {
self.flush();
self.base.clone()
}
#[expect(dead_code)]
pub fn clear(&mut self) {
self.pending.clear();
self.base = Region::empty();
}
fn set_op(&mut self, op: BuilderOp) {
if self.op != op {
self.flush();
self.op = op;
}
}
fn flush(&mut self) {
if self.pending.is_empty() {
return;
}
let region = Region::from_rects(&self.pending);
self.base = match self.op {
BuilderOp::Add => self.base.union(&region),
BuilderOp::Sub => self.base.subtract(&region),
};
self.pending.clear();
}
}
pub struct DamageQueue {
this: usize,
datas: Rc<UnsafeCell<Vec<Vec<Rect>>>>,
}
impl Debug for DamageQueue {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DamageQueue").finish_non_exhaustive()
}
}
impl DamageQueue {
pub fn new<const N: usize>() -> [DamageQueue; N] {
let datas = Rc::new(UnsafeCell::new(vec![vec!(); N]));
array::from_fn(|this| DamageQueue {
this,
datas: datas.clone(),
})
}
pub fn damage(&self, rects: &[Rect]) {
let datas = unsafe { self.datas.get().deref_mut() };
for data in datas {
data.extend(rects);
}
}
pub fn clear(&self) {
let data = unsafe { &mut self.datas.get().deref_mut()[self.this] };
data.clear();
}
pub fn clear_all(&self) {
let datas = unsafe { self.datas.get().deref_mut() };
for data in datas {
data.clear();
}
}
pub fn get(&self) -> Region {
let data = unsafe { &self.datas.get().deref()[self.this] };
Region::from_rects2(data)
}
}

View file

@ -1,715 +0,0 @@
use {
crate::rect::{Rect, Region},
jay_algorithms::rect::{NoTag, RectRaw},
};
#[test]
fn union1() {
let r1 = Region::new(Rect::new(0, 0, 10, 10).unwrap());
let r2_ = Region::new(Rect::new(5, 5, 15, 15).unwrap());
let r2 = Region::new(Rect::new(10, 10, 20, 20).unwrap());
let r3 = r1.union_cow(&r2);
let r3 = r3.union_cow(&r2_);
assert_eq!(r3.extents, Rect::new(0, 0, 20, 20).unwrap());
assert_eq!(
&r3.rects[..],
&[
Rect::new(0, 0, 10, 5).unwrap().raw,
Rect::new(0, 5, 15, 10).unwrap().raw,
Rect::new(5, 10, 20, 15).unwrap().raw,
Rect::new(10, 15, 20, 20).unwrap().raw,
]
);
}
#[test]
fn union2() {
let r1 = Region::new(Rect::new(0, 0, 10, 10).unwrap());
let r2 = Region::new(Rect::new(0, 10, 10, 20).unwrap());
let r3 = r1.union_cow(&r2);
assert_eq!(r3.extents, Rect::new(0, 0, 10, 20).unwrap());
assert_eq!(&r3.rects[..], &[Rect::new(0, 0, 10, 20).unwrap().raw,]);
}
#[test]
fn subtract1() {
let r1 = Region::new(Rect::new(0, 0, 20, 20).unwrap());
let r2 = Region::new(Rect::new(5, 5, 15, 15).unwrap());
let r3 = r1.subtract_cow(&r2);
assert_eq!(r3.extents, Rect::new(0, 0, 20, 20).unwrap());
assert_eq!(
&r3.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 20,
y2: 5,
tag: NoTag,
},
RectRaw {
x1: 0,
y1: 5,
x2: 5,
y2: 15,
tag: NoTag,
},
RectRaw {
x1: 15,
y1: 5,
x2: 20,
y2: 15,
tag: NoTag,
},
RectRaw {
x1: 0,
y1: 15,
x2: 20,
y2: 20,
tag: NoTag,
},
]
);
}
#[test]
fn rects_to_bands() {
let rects = [
Rect::new_unchecked_danger(0, 0, 10, 10),
Rect::new_unchecked_danger(5, 0, 30, 10),
Rect::new_unchecked_danger(30, 5, 50, 15),
];
let r = Region::from_rects(&rects[..]);
// println!("{:#?}", r.rects);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 30,
y2: 5,
tag: NoTag,
},
RectRaw {
x1: 0,
y1: 5,
x2: 50,
y2: 10,
tag: NoTag,
},
RectRaw {
x1: 30,
y1: 10,
x2: 50,
y2: 15,
tag: NoTag,
},
]
);
}
#[test]
fn rects_to_bands2() {
let rects = [
Rect::new_unchecked_danger(0, 0, 10, 10),
Rect::new_unchecked_danger(0, 10, 10, 20),
];
let r = Region::from_rects(&rects[..]);
// println!("{:#?}", r.rects);
assert_eq!(&r.rects[..], &[Rect::new(0, 0, 10, 20).unwrap().raw,]);
}
#[test]
fn rects_to_bands_tagged1() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 200, 200, 1),
Rect::new_unchecked_danger_tagged(50, 50, 150, 150, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 200,
y2: 50,
tag: 1,
},
RectRaw {
x1: 0,
y1: 50,
x2: 50,
y2: 150,
tag: 1,
},
RectRaw {
x1: 50,
y1: 50,
x2: 150,
y2: 150,
tag: 0,
},
RectRaw {
x1: 150,
y1: 50,
x2: 200,
y2: 150,
tag: 1,
},
RectRaw {
x1: 0,
y1: 150,
x2: 200,
y2: 200,
tag: 1,
},
],
);
}
#[test]
fn rects_to_bands_tagged2() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 200, 200, 1),
Rect::new_unchecked_danger_tagged(50, 50, 150, 150, 0),
Rect::new_unchecked_danger_tagged(60, 60, 140, 140, 2),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 200,
y2: 50,
tag: 1,
},
RectRaw {
x1: 0,
y1: 50,
x2: 50,
y2: 150,
tag: 1,
},
RectRaw {
x1: 50,
y1: 50,
x2: 150,
y2: 150,
tag: 0,
},
RectRaw {
x1: 150,
y1: 50,
x2: 200,
y2: 150,
tag: 1,
},
RectRaw {
x1: 0,
y1: 150,
x2: 200,
y2: 200,
tag: 1,
},
],
);
}
#[test]
fn rects_to_bands_tagged3() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 200, 200, 2),
Rect::new_unchecked_danger_tagged(50, 50, 150, 150, 1),
Rect::new_unchecked_danger_tagged(60, 60, 140, 140, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 200,
y2: 50,
tag: 0,
},
RectRaw {
x1: 0,
y1: 50,
x2: 50,
y2: 60,
tag: 0,
},
RectRaw {
x1: 50,
y1: 50,
x2: 150,
y2: 60,
tag: 1,
},
RectRaw {
x1: 150,
y1: 50,
x2: 200,
y2: 60,
tag: 0,
},
RectRaw {
x1: 0,
y1: 60,
x2: 50,
y2: 140,
tag: 0,
},
RectRaw {
x1: 50,
y1: 60,
x2: 60,
y2: 140,
tag: 1,
},
RectRaw {
x1: 60,
y1: 60,
x2: 140,
y2: 140,
tag: 0,
},
RectRaw {
x1: 140,
y1: 60,
x2: 150,
y2: 140,
tag: 1,
},
RectRaw {
x1: 150,
y1: 60,
x2: 200,
y2: 140,
tag: 0,
},
RectRaw {
x1: 0,
y1: 140,
x2: 50,
y2: 150,
tag: 0,
},
RectRaw {
x1: 50,
y1: 140,
x2: 150,
y2: 150,
tag: 1,
},
RectRaw {
x1: 150,
y1: 140,
x2: 200,
y2: 150,
tag: 0,
},
RectRaw {
x1: 0,
y1: 150,
x2: 200,
y2: 200,
tag: 0,
},
],
);
}
#[test]
fn rects_to_bands_tagged4() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1),
Rect::new_unchecked_danger_tagged(100, 0, 200, 200, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 1,
},
RectRaw {
x1: 100,
y1: 0,
x2: 200,
y2: 100,
tag: 0,
},
RectRaw {
x1: 100,
y1: 100,
x2: 200,
y2: 200,
tag: 0,
},
],
);
}
#[test]
fn rects_to_bands_tagged5() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 200, 100, 1),
Rect::new_unchecked_danger_tagged(100, 0, 200, 100, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 1,
},
RectRaw {
x1: 100,
y1: 0,
x2: 200,
y2: 100,
tag: 0,
},
],
);
}
#[test]
fn rects_to_bands_tagged6() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 200, 100, 1),
Rect::new_unchecked_danger_tagged(100, 0, 300, 100, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 1,
},
RectRaw {
x1: 100,
y1: 0,
x2: 300,
y2: 100,
tag: 0,
},
],
);
}
#[test]
fn rects_to_bands_tagged7() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 200, 100, 0),
Rect::new_unchecked_danger_tagged(100, 0, 300, 200, 1),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 200,
y2: 100,
tag: 0,
},
RectRaw {
x1: 200,
y1: 0,
x2: 300,
y2: 100,
tag: 1,
},
RectRaw {
x1: 100,
y1: 100,
x2: 300,
y2: 200,
tag: 1,
},
],
);
}
#[test]
fn rects_to_bands_tagged8() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1),
Rect::new_unchecked_danger_tagged(100, 0, 200, 100, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 1,
},
RectRaw {
x1: 100,
y1: 0,
x2: 200,
y2: 100,
tag: 0,
},
],
);
}
#[test]
fn rects_to_bands_tagged9() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1),
Rect::new_unchecked_danger_tagged(100, 0, 200, 100, 1),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[RectRaw {
x1: 0,
y1: 0,
x2: 200,
y2: 100,
tag: 1,
},],
);
}
#[test]
fn rects_to_bands_tagged10() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1),
Rect::new_unchecked_danger_tagged(0, 100, 100, 200, 1),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 200,
tag: 1,
},],
);
}
#[test]
fn rects_to_bands_tagged11() {
let rects = [Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 11)];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 1,
},],
);
}
#[test]
fn rects_to_bands_tagged12() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 11),
Rect::new_unchecked_danger_tagged(200, 0, 300, 100, 10),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 1,
},
RectRaw {
x1: 200,
y1: 0,
x2: 300,
y2: 100,
tag: 0,
},
],
);
}
#[test]
fn rects_to_bands_tagged13() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1),
Rect::new_unchecked_danger_tagged(0, 100, 100, 200, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 1,
},
RectRaw {
x1: 0,
y1: 100,
x2: 100,
y2: 200,
tag: 0,
},
],
);
}
#[test]
fn rects_to_bands_tagged14() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1),
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 0,
},],
);
}
#[test]
fn intersect1() {
let rects = [Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 0)];
let r1 = Region::from_rects_tagged(&rects[..]);
let rects = [Rect::new_unchecked_danger(100, 100, 200, 200)];
let r2 = Region::from_rects2(&rects[..]);
let r3 = r1.intersect_tagged(&r2);
assert_eq!(&r3.rects[..], &[],);
}
#[test]
fn intersect2() {
let rects = [Rect::new_unchecked_danger_tagged(0, 0, 200, 200, 0)];
let r1 = Region::from_rects_tagged(&rects[..]);
let rects = [Rect::new_unchecked_danger(50, 50, 150, 150)];
let r2 = Region::from_rects2(&rects[..]);
let r3 = r1.intersect_tagged(&r2);
assert_eq!(
&r3.rects[..],
&[RectRaw {
x1: 50,
y1: 50,
x2: 150,
y2: 150,
tag: 0,
}],
);
}
#[test]
fn intersect3() {
macro_rules! t {
($l:expr, $r:expr, $t:expr) => {
Rect::new_unchecked_danger_tagged($l, 0, $r, 1, $t)
};
}
macro_rules! u {
($l:expr, $r:expr) => {
Rect::new_unchecked_danger($l, 0, $r, 1)
};
}
macro_rules! r {
($l:expr, $r:expr, $t:expr) => {
RectRaw {
x1: $l,
y1: 0,
x2: $r,
y2: 1,
tag: $t,
}
};
}
let rects = [
t!(0, 100, 0),
t!(110, 130, 1),
t!(140, 160, 2),
t!(170, 180, 0),
];
let r1 = Region::from_rects_tagged(&rects[..]);
let rects = [
u!(10, 20),
u!(50, 60),
u!(70, 100),
u!(120, 150),
u!(170, 180),
];
let r2 = Region::from_rects2(&rects[..]);
let r3 = r1.intersect_tagged(&r2);
assert_eq!(
&r3.rects[..],
&[
r!(10, 20, 0),
r!(50, 60, 0),
r!(70, 100, 0),
r!(120, 130, 1),
r!(140, 150, 0),
r!(170, 180, 0),
],
);
}
#[test]
fn new_saturating() {
let r1 = Rect::new_sized_saturating(10, 10, -5, -5);
assert!(r1.is_empty());
assert_eq!(r1.x1(), 10);
assert_eq!(r1.x2(), 10);
let r2 = Rect::new_saturating(100, 100, 50, 50);
assert!(r2.is_empty());
assert_eq!(r2.x1(), 100);
assert_eq!(r2.x2(), 100);
let r3 = Rect::new_sized_saturating(i32::MAX - 10, 0, 100, 10);
assert_eq!(r3.x2(), i32::MAX);
}
#[test]
fn dist_squared() {
let r1 = Rect::new_sized_saturating(i32::MIN, i32::MIN, 0, 0);
assert_eq!(
r1.dist_squared(i32::MAX, i32::MAX),
(1 << 65) + 2 - (1 << 34),
);
}

View file

@ -1,72 +1 @@
use std::fmt::{Debug, Display, Formatter};
pub const SCALE_BASE: u32 = 120;
const BASE64: i64 = SCALE_BASE as i64;
pub const SCALE_BASEF: f64 = SCALE_BASE as f64;
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[repr(transparent)]
pub struct Scale(u32);
impl Default for Scale {
fn default() -> Self {
Scale::from_int(1)
}
}
impl Scale {
pub const fn from_int(f: u32) -> Self {
Self(f.saturating_mul(SCALE_BASE))
}
pub fn from_f64(f: f64) -> Self {
Self((f * SCALE_BASEF).round() as u32)
}
pub fn from_f64_as_float(f: f64) -> Self {
Self(((f * (SCALE_BASEF / 15.0)).round() as u32).saturating_mul(15))
}
pub fn to_f64(self) -> f64 {
self.0 as f64 / SCALE_BASEF
}
pub fn round_up(self) -> u32 {
self.0.saturating_add(SCALE_BASE - 1) / SCALE_BASE
}
pub const fn from_wl(wl: u32) -> Self {
Self(wl)
}
pub fn to_wl(self) -> u32 {
self.0
}
#[inline(always)]
pub fn pixel_size<const N: usize>(self, v: [i32; N]) -> [i32; N] {
if self == Scale::default() {
return v;
}
let scale = self.0 as i64;
v.map(|v| ((v as i64 * scale + v.signum() as i64 * BASE64 / 2) / BASE64) as i32)
}
}
impl PartialEq<u32> for Scale {
fn eq(&self, other: &u32) -> bool {
self.0 == other * SCALE_BASE
}
}
impl Debug for Scale {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&self.to_f64(), f)
}
}
impl Display for Scale {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.to_f64(), f)
}
}
pub use jay_units::scale::*;

View file

@ -1,7 +1,6 @@
use {
crate::{
async_engine::SpawnedFuture,
client::ClientCaps,
state::State,
utils::{copyhashmap::CopyHashMap, errorfmt::ErrorFmt, hash_map_ext::HashMapExt},
},
@ -27,19 +26,16 @@ struct Acceptor {
metadata: Rc<AcceptorMetadata>,
listen_fd: Rc<OwnedFd>,
close_fd: Rc<OwnedFd>,
bounding_caps: ClientCaps,
listen_future: Cell<Option<SpawnedFuture<()>>>,
close_future: Cell<Option<SpawnedFuture<()>>>,
}
#[derive(Default)]
pub struct AcceptorMetadata {
pub secure: bool,
pub sandboxed: bool,
pub sandbox_engine: Option<String>,
pub app_id: Option<String>,
pub instance_id: Option<String>,
pub tag: Option<String>,
}
impl SecurityContextAcceptors {
@ -57,22 +53,18 @@ impl SecurityContextAcceptors {
instance_id: Option<String>,
listen_fd: &Rc<OwnedFd>,
close_fd: &Rc<OwnedFd>,
bounding_caps: ClientCaps,
) {
let acceptor = Rc::new(Acceptor {
id: self.ids.next(),
state: state.clone(),
metadata: Rc::new(AcceptorMetadata {
secure: false,
sandboxed: true,
sandbox_engine,
app_id,
instance_id,
tag: None,
}),
listen_fd: listen_fd.clone(),
close_fd: close_fd.clone(),
bounding_caps,
listen_future: Cell::new(None),
close_future: Cell::new(None),
});
@ -113,10 +105,7 @@ impl Acceptor {
}
};
let id = s.clients.id();
if let Err(e) = s
.clients
.spawn(id, s, fd, self.bounding_caps, true, &self.metadata)
{
if let Err(e) = s.clients.spawn(id, s, fd, &self.metadata) {
log::error!("Could not spawn a client: {}", ErrorFmt(e));
break;
}

View file

@ -23,7 +23,7 @@ use {
},
backends::dummy::DummyBackend,
cli::RunArgs,
client::{Client, ClientCaps, ClientId, Clients, NUM_CACHED_SERIAL_RANGES, SerialRange},
client::{Client, ClientId, Clients, NUM_CACHED_SERIAL_RANGES, SerialRange},
clientmem::ClientMemOffset,
cmm::{
cmm_description::ColorDescription, cmm_manager::ColorManager,
@ -109,7 +109,6 @@ use {
renderer::Renderer,
scale::Scale,
security_context_acceptor::SecurityContextAcceptors,
tagged_acceptor::TaggedAcceptors,
theme::{BarPosition, Color, Theme, ThemeColor, ThemeSized},
time::Time,
tree::{
@ -315,7 +314,6 @@ pub struct State {
pub serial: NumCell<u64>,
pub run_toplevel: Rc<RunToplevel>,
pub config_dir: Option<String>,
pub config_file_id: NumCell<u64>,
pub tracker: Tracker<Self>,
pub data_offer_ids: DataOfferIds,
pub data_source_ids: DataSourceIds,
@ -346,7 +344,6 @@ pub struct State {
pub keyboard_state_ids: KeyboardStateIds,
pub physical_keyboard_ids: PhysicalKeyboardIds,
pub security_context_acceptors: SecurityContextAcceptors,
pub tagged_acceptors: TaggedAcceptors,
pub cursor_user_group_ids: CursorUserGroupIds,
pub cursor_user_ids: CursorUserIds,
pub cursor_user_groups: CopyHashMap<CursorUserGroupId, Rc<CursorUserGroup>>,
@ -1253,7 +1250,6 @@ impl State {
self.workspace_watchers.clear();
self.toplevel_lists.clear();
self.security_context_acceptors.clear();
self.tagged_acceptors.clear();
self.slow_clients.clear();
for h in self.input_device_handlers.borrow_mut().drain_values() {
h.async_event.clear();
@ -2298,17 +2294,6 @@ impl State {
}
}
pub fn update_capabilities(
&self,
data: &Rc<Client>,
bounding_caps: ClientCaps,
set_bounding_caps: bool,
) {
if let Some(config) = self.config.get() {
config.update_capabilities(data, bounding_caps, set_bounding_caps);
}
}
pub fn find_output_in_direction(
&self,
source_output: &OutputNode,
@ -2480,13 +2465,7 @@ impl State {
pub fn reload_config(self: &Rc<Self>) {
log::info!("Reloading config");
let config = match ConfigProxy::from_config_dir(self) {
Ok(c) => c,
Err(e) => {
log::error!("Cannot reload config: {}", ErrorFmt(e));
return;
}
};
let config = ConfigProxy::default(self);
if let Some(config) = self.config.take() {
config.destroy();
for seat in self.globals.seats.lock().values() {
@ -2781,31 +2760,31 @@ mod tests {
#[test]
fn layout_animation_candidates_coalesce_duplicate_nodes() {
let source = MultiphaseHierarchyPosition {
parent: Some(NodeId(10)),
parent: Some(NodeId(10).into()),
depth: 2,
sibling_index: Some(1),
..Default::default()
};
let intermediate = MultiphaseHierarchyPosition {
parent: Some(NodeId(11)),
parent: Some(NodeId(11).into()),
depth: 1,
sibling_index: Some(0),
..Default::default()
};
let target = MultiphaseHierarchyPosition {
parent: Some(NodeId(12)),
parent: Some(NodeId(12).into()),
depth: 0,
sibling_index: Some(2),
..Default::default()
};
let second_source = MultiphaseHierarchyPosition {
parent: Some(NodeId(20)),
parent: Some(NodeId(20).into()),
depth: 1,
sibling_index: Some(0),
..Default::default()
};
let second_target = MultiphaseHierarchyPosition {
parent: Some(NodeId(20)),
parent: Some(NodeId(20).into()),
depth: 1,
sibling_index: Some(1),
..Default::default()

View file

@ -1,199 +0,0 @@
use {
crate::{
async_engine::SpawnedFuture,
client::ClientCaps,
security_context_acceptor::AcceptorMetadata,
state::State,
utils::{
errorfmt::ErrorFmt,
numcell::NumCell,
oserror::{OsError, OsErrorExt, OsErrorExt2},
xrd::xrd,
},
},
ahash::AHashMap,
std::{
cell::{Cell, RefCell},
rc::Rc,
},
thiserror::Error,
uapi::{OwnedFd, Ustring, c, format_ustr},
};
#[derive(Debug, Error)]
pub enum TaggedAcceptorError {
#[error("XDG_RUNTIME_DIR is not set")]
XrdNotSet,
#[error("XDG_RUNTIME_DIR ({0:?}) is too long to form a unix socket address")]
XrdTooLong(String),
#[error("Could not create a wayland socket")]
SocketFailed(#[source] OsError),
#[error("Could not stat the existing socket")]
SocketStat(#[source] OsError),
#[error("Could not start listening for incoming connections")]
ListenFailed(#[source] OsError),
#[error("Could not open the lock file")]
OpenLockFile(#[source] OsError),
#[error("Could not lock the lock file")]
LockLockFile(#[source] OsError),
#[error("Could not bind the socket to an address")]
BindFailed(#[source] OsError),
}
#[derive(Default)]
pub struct TaggedAcceptors {
acceptors: RefCell<AHashMap<String, Rc<Acceptor>>>,
next_name: NumCell<u64>,
}
struct Acceptor {
socket: AllocatedSocket,
tag: String,
state: Rc<State>,
metadata: Rc<AcceptorMetadata>,
future: Cell<Option<SpawnedFuture<()>>>,
}
impl TaggedAcceptors {
pub fn clear(&self) {
let acceptors = self.acceptors.take();
for (_, acceptor) in acceptors {
acceptor.kill();
}
}
pub fn get(&self, state: &Rc<State>, tag: &str) -> Result<Rc<String>, TaggedAcceptorError> {
let acceptors = &mut *self.acceptors.borrow_mut();
if let Some(acceptor) = acceptors.get(tag) {
return Ok(acceptor.socket.name.clone());
}
let acceptor = Rc::new(Acceptor {
socket: self.allocate_socket()?,
tag: tag.to_owned(),
state: state.clone(),
metadata: Rc::new(AcceptorMetadata {
secure: false,
sandboxed: false,
sandbox_engine: Default::default(),
app_id: Default::default(),
instance_id: Default::default(),
tag: Some(tag.to_owned()),
}),
future: Default::default(),
});
log::info!("Creating tagged acceptor `{tag}`");
acceptor.future.set(Some(
state.eng.spawn("tagged accept", acceptor.clone().accept()),
));
acceptors.insert(tag.to_owned(), acceptor.clone());
Ok(acceptor.socket.name.clone())
}
fn allocate_socket(&self) -> Result<AllocatedSocket, TaggedAcceptorError> {
let xrd = xrd().ok_or(TaggedAcceptorError::XrdNotSet)?;
let socket = uapi::socket(c::AF_UNIX, c::SOCK_STREAM | c::SOCK_CLOEXEC, 0)
.map(Rc::new)
.map_os_err(TaggedAcceptorError::SocketFailed)?;
loop {
let i = self.next_name.fetch_add(1) + 1000;
if let Some(s) = bind_socket(&socket, &xrd, i)? {
return Ok(s);
}
}
}
}
impl Acceptor {
fn kill(&self) {
log::info!("Destroying tagged acceptor `{}`", self.tag);
self.future.take();
self.state
.tagged_acceptors
.acceptors
.borrow_mut()
.remove(&self.tag);
}
async fn accept(self: Rc<Self>) {
let s = &self.state;
loop {
let fd = match s.ring.accept(&self.socket.socket, c::SOCK_CLOEXEC).await {
Ok(fd) => fd,
Err(e) => {
log::error!("Could not accept a client: {}", ErrorFmt(e));
break;
}
};
let id = s.clients.id();
if let Err(e) = s
.clients
.spawn(id, s, fd, ClientCaps::all(), false, &self.metadata)
{
log::error!("Could not spawn a client: {}", ErrorFmt(e));
break;
}
}
self.kill();
}
}
struct AllocatedSocket {
// wayland-x
name: Rc<String>,
// /run/user/1000/wayland-x
path: Ustring,
socket: Rc<OwnedFd>,
// /run/user/1000/wayland-x.lock
lock_path: Ustring,
_lock_fd: OwnedFd,
}
impl Drop for AllocatedSocket {
fn drop(&mut self) {
let _ = uapi::unlink(&self.path);
let _ = uapi::unlink(&self.lock_path);
}
}
fn bind_socket(
fd: &Rc<OwnedFd>,
xrd: &str,
id: u64,
) -> Result<Option<AllocatedSocket>, TaggedAcceptorError> {
let mut addr: c::sockaddr_un = uapi::pod_zeroed();
addr.sun_family = c::AF_UNIX as _;
let name = Rc::new(format!("wayland-{}", id));
let path = format_ustr!("{}/{}", xrd, name);
let lock_path = format_ustr!("{}.lock", path.display());
if path.len() + 1 > addr.sun_path.len() {
return Err(TaggedAcceptorError::XrdTooLong(xrd.to_string()));
}
let lock_fd = uapi::open(&*lock_path, c::O_CREAT | c::O_CLOEXEC | c::O_RDWR, 0o644)
.map_os_err(TaggedAcceptorError::OpenLockFile)?;
if let Err(e) = uapi::flock(lock_fd.raw(), c::LOCK_EX | c::LOCK_NB).to_os_error() {
if e.0 == c::EWOULDBLOCK {
return Ok(None);
}
return Err(TaggedAcceptorError::LockLockFile(e));
}
match uapi::lstat(&path).to_os_error() {
Ok(_) => {
log::info!("Unlinking {}", path.display());
let _ = uapi::unlink(&path);
}
Err(OsError(c::ENOENT)) => {}
Err(e) => return Err(TaggedAcceptorError::SocketStat(e)),
}
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;
uapi::bind(fd.raw(), &addr).map_os_err(TaggedAcceptorError::BindFailed)?;
uapi::listen(fd.raw(), 4096).map_os_err(TaggedAcceptorError::ListenFailed)?;
Ok(Some(AllocatedSocket {
name,
path,
socket: fd.clone(),
lock_path,
_lock_fd: lock_fd,
}))
}

View file

@ -1,119 +1 @@
use {
std::{
cmp::Ordering,
fmt::{Debug, Formatter},
ops::{Add, Sub},
time::Duration,
},
uapi::c,
};
#[derive(Copy, Clone)]
pub struct Time(pub c::timespec);
impl Debug for Time {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Time")
.field("tv_sec", &self.0.tv_sec)
.field("tv_nsec", &self.0.tv_nsec)
.finish()
}
}
impl Time {
pub fn now_unchecked() -> Time {
let mut time = uapi::pod_zeroed();
let _ = uapi::clock_gettime(c::CLOCK_MONOTONIC, &mut time);
Self(time)
}
pub fn round_to_ms(self) -> Time {
if self.0.tv_nsec > 999_000_000 {
Time(c::timespec {
tv_sec: self.0.tv_sec + 1,
tv_nsec: 0,
})
} else {
Time(c::timespec {
tv_sec: self.0.tv_sec,
tv_nsec: (self.0.tv_nsec + 999_999) / 1_000_000 * 1_000_000,
})
}
}
pub fn nsec(self) -> u64 {
let sec = self.0.tv_sec as u64 * 1_000_000_000;
let nsec = self.0.tv_nsec as u64;
sec + nsec
}
pub fn usec(self) -> u64 {
let sec = self.0.tv_sec as u64 * 1_000_000;
let nsec = self.0.tv_nsec as u64 / 1_000;
sec + nsec
}
pub fn msec(self) -> u64 {
let sec = self.0.tv_sec as u64 * 1_000;
let nsec = self.0.tv_nsec as u64 / 1_000_000;
sec + nsec
}
pub fn elapsed(self) -> Duration {
let now = Self::now_unchecked();
now - self
}
}
impl Eq for Time {}
impl PartialEq for Time {
fn eq(&self, other: &Self) -> bool {
self.0.tv_sec == other.0.tv_sec && self.0.tv_nsec == other.0.tv_nsec
}
}
impl Ord for Time {
fn cmp(&self, other: &Self) -> Ordering {
self.0
.tv_sec
.cmp(&other.0.tv_sec)
.then_with(|| self.0.tv_nsec.cmp(&other.0.tv_nsec))
}
}
impl PartialOrd for Time {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Sub<Time> for Time {
type Output = Duration;
fn sub(self, rhs: Time) -> Self::Output {
let sec = self.0.tv_sec - rhs.0.tv_sec;
let nsec = self.0.tv_nsec - rhs.0.tv_nsec;
Duration::from_nanos((sec as i64 * 1_000_000_000 + nsec as i64) as u64)
}
}
impl Add<Duration> for Time {
type Output = Self;
fn add(mut self, rhs: Duration) -> Self::Output {
let secs = (rhs.as_nanos() / 1_000_000_000) as c::time_t;
let nsecs = (rhs.as_nanos() % 1_000_000_000) as c::c_long;
self.0.tv_sec += secs;
self.0.tv_nsec += nsecs;
if self.0.tv_nsec > 999_999_999 {
self.0.tv_sec += 1;
self.0.tv_nsec -= 1_000_000_000;
}
self
}
}
pub fn usec_to_msec(usec: u64) -> u32 {
(usec / 1000) as u32
}
pub use jay_time::*;

View file

@ -28,7 +28,6 @@ use {
},
},
ahash::AHashMap,
isnt::std_1::primitive::IsntSliceExt,
std::{
cell::{Cell, RefCell},
collections::VecDeque,
@ -149,11 +148,7 @@ impl ToolClient {
Ok(d) => d,
Err(_) => return Err(ToolClientError::WaylandDisplayNotSet),
};
let mut path = format_ustr!("{}/{}", xrd, wd);
let suffix = b".jay";
if path.not_ends_with(suffix) {
path.push(suffix.as_slice());
}
let path = format_ustr!("{}/{}", xrd, wd);
let socket = uapi::socket(c::AF_UNIX, c::SOCK_STREAM | c::SOCK_CLOEXEC, 0)
.map(Rc::new)
.map_os_err(ToolClientError::CreateSocket)?;

View file

@ -340,7 +340,7 @@ pub trait ToplevelNodeBase: Node {
return Default::default();
};
let mut position = MultiphaseHierarchyPosition {
parent: Some(parent.node_id()),
parent: Some(parent.node_id().into()),
..Default::default()
};
populate_multiphase_ancestor_splits(&mut position, Some(parent.clone()));

View file

@ -1,69 +1,89 @@
pub mod array;
pub mod array_to_tuple;
pub mod asyncevent;
pub mod atomic_enum;
pub mod binary_search_map;
macro_rules! reexport_utils {
($($name:ident,)*) => {
$(
pub mod $name {
#[allow(unused_imports)]
pub use jay_utils::$name::*;
}
)*
};
}
reexport_utils! {
array,
array_to_tuple,
asyncevent,
atomic_enum,
binary_search_map,
bitfield,
bitflags,
buf,
cell_ext,
compat,
copyhashmap,
double_buffered,
errorfmt,
fdcloser,
free_list,
geometric_decay,
hash_map_ext,
log_on_drop,
mmap,
nice,
nonblock,
num_cpus,
numcell,
on_change,
on_drop_event,
once,
opaque,
opaque_cell,
opt,
option_ext,
ordered_float,
oserror,
page_size,
pid_info,
pidfd_send_signal,
pipe,
process_name,
ptr_ext,
queue,
rc_eq,
refcounted,
smallmap,
stack,
static_text,
string_ext,
syncqueue,
threshold_counter,
tri,
unlink_on_drop,
vec_ext,
vecdeque_ext,
vecset,
vecstorage,
windows,
xrd,
}
pub mod bindings;
pub mod bitfield;
pub mod bitflags;
pub mod buf;
pub mod buffd;
pub mod bufio;
pub mod cell_ext;
pub mod clone3;
pub mod clonecell;
pub mod compat;
pub mod copyhashmap;
pub mod double_buffered;
pub mod double_click_state;
pub mod errorfmt;
pub mod event_listener;
pub mod fdcloser;
pub mod free_list;
pub mod geometric_decay;
pub mod hash_map_ext;
pub mod line_logger;
pub mod linkedlist;
pub mod log_on_drop;
pub mod mmap;
pub mod nice;
pub mod nonblock;
pub mod num_cpus;
pub mod numcell;
pub mod line_logger;
pub mod object_drop_queue;
pub mod on_change;
pub mod on_drop_event;
pub mod once;
pub mod opaque;
pub mod opaque_cell;
pub mod opt;
pub mod option_ext;
pub mod ordered_float;
pub mod oserror;
pub mod page_size;
pub mod pending_serial;
pub mod pid_info;
pub mod pidfd_send_signal;
pub mod pipe;
pub mod process_name;
pub mod ptr_ext;
pub mod queue;
pub mod rc_eq;
pub mod refcounted;
pub mod run_toplevel;
pub mod scroller;
pub mod smallmap;
pub mod stack;
pub mod static_text;
pub mod string_ext;
pub mod syncqueue;
pub mod threshold_counter;
pub mod timer;
pub mod tri;
pub mod unlink_on_drop;
pub mod vec_ext;
pub mod vecdeque_ext;
pub mod vecset;
pub mod vecstorage;
pub mod windows;
pub mod xrd;
pub mod clonecell {
pub use jay_utils::clonecell::*;
unsafe impl<T> UnsafeCellCloneSafe for crate::utils::linkedlist::NodeRef<T> {}
unsafe impl UnsafeCellCloneSafe for crate::tree::NodeId {}
}

View file

@ -1,11 +0,0 @@
pub fn from_fn<F, T, const N: usize>(mut cb: F) -> [T; N]
where
F: FnMut(usize) -> T,
{
let mut idx = 0;
[(); N].map(|_| {
let res = cb(idx);
idx += 1;
res
})
}

View file

@ -1,28 +0,0 @@
pub trait ArrayToTuple {
type Tuple;
fn to_tuple(self) -> Self::Tuple;
}
macro_rules! ignore {
($t:tt) => {
T
};
}
macro_rules! array_to_tuple {
($n:expr, $($field:ident,)*) => {
impl<T> ArrayToTuple for [T; $n] {
type Tuple = ($(ignore!($field),)*);
fn to_tuple(self) -> Self::Tuple {
let [$($field,)*] = self;
#[allow(clippy::allow_attributes)]
#[allow(clippy::unused_unit)]
($($field,)*)
}
}
};
}
array_to_tuple!(2, t1, t2,);

View file

@ -1,60 +0,0 @@
use {
crate::utils::numcell::NumCell,
std::{
cell::Cell,
fmt::{Debug, Formatter},
future::Future,
pin::Pin,
task::{Context, Poll, Waker},
},
};
#[derive(Default)]
pub struct AsyncEvent {
triggers: NumCell<u32>,
waker: Cell<Option<Waker>>,
}
impl Debug for AsyncEvent {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncEvent")
.field("triggers", &self.triggers.get())
.finish_non_exhaustive()
}
}
impl AsyncEvent {
pub fn clear(&self) {
self.triggers.set(0);
self.waker.take();
}
pub fn trigger(&self) {
if self.triggers.fetch_add(1) == 0
&& let Some(waker) = self.waker.take()
{
waker.wake();
}
}
pub fn triggered(&self) -> AsyncEventTriggered<'_> {
AsyncEventTriggered { ae: self }
}
}
pub struct AsyncEventTriggered<'a> {
ae: &'a AsyncEvent,
}
impl<'a> Future for AsyncEventTriggered<'a> {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.ae.triggers.replace(0) == 0 {
self.ae.waker.set(Some(cx.waker().clone()));
Poll::Pending
} else {
Poll::Ready(())
}
}
}

View file

@ -1,42 +0,0 @@
use {
linearize::Linearize,
std::{
marker::PhantomData,
sync::atomic::{AtomicUsize, Ordering},
},
};
pub struct AtomicEnum<T> {
v: AtomicUsize,
_phantom: PhantomData<T>,
}
impl<T> Default for AtomicEnum<T>
where
T: Default + Linearize + Copy,
{
fn default() -> Self {
Self::new(T::default())
}
}
impl<T> AtomicEnum<T>
where
T: Linearize + Copy,
{
pub fn new(t: T) -> Self {
Self {
v: AtomicUsize::new(t.linearize()),
_phantom: Default::default(),
}
}
#[allow(dead_code)]
pub fn load(&self, ordering: Ordering) -> T {
unsafe { T::from_linear_unchecked(self.v.load(ordering)) }
}
pub fn store(&self, t: T, ordering: Ordering) {
self.v.store(t.linearize(), ordering);
}
}

View file

@ -1,219 +0,0 @@
use {
crate::utils::ptr_ext::{MutPtrExt, PtrExt},
smallvec::SmallVec,
std::{
fmt::{Debug, Formatter},
mem,
},
};
pub struct BinarySearchMap<K, V, const N: usize> {
m: SmallVec<[(K, V); N]>,
}
impl<K: Debug, V: Debug, const N: usize> Debug for BinarySearchMap<K, V, N> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_map()
.entries(self.m.iter().map(|e| (&e.0, &e.1)))
.finish()
}
}
impl<K, V, const N: usize> Default for BinarySearchMap<K, V, N> {
fn default() -> Self {
Self {
m: Default::default(),
}
}
}
impl<K, V, const N: usize> BinarySearchMap<K, V, N> {
pub fn new_with(k: K, v: V) -> Self {
let mut sv = SmallVec::new();
sv.push((k, v));
Self { m: sv }
}
pub fn new() -> Self {
Self {
m: SmallVec::new_const(),
}
}
pub fn len(&self) -> usize {
self.m.len()
}
fn pos(&self, k: &K) -> Result<usize, usize>
where
K: Ord + Eq,
{
self.m.binary_search_by(|(c, _)| c.cmp(k))
}
pub fn contains(&self, k: &K) -> bool
where
K: Ord + Eq,
{
self.pos(k).is_ok()
}
pub fn not_contains(&self, k: &K) -> bool
where
K: Ord + Eq,
{
!self.contains(k)
}
pub fn insert(&mut self, k: K, v: V) -> Option<V>
where
K: Ord + Eq,
{
match self.pos(&k) {
Ok(p) => Some(mem::replace(&mut self.m[p], (k, v)).1),
Err(p) => {
self.m.insert(p, (k, v));
None
}
}
}
pub fn get(&self, k: &K) -> Option<&V>
where
K: Ord + Eq,
{
self.pos(k).ok().map(|p| &self.m[p].1)
}
pub fn get_mut(&mut self, k: &K) -> Option<&mut V>
where
K: Ord + Eq,
{
self.pos(k).ok().map(|p| &mut self.m[p].1)
}
pub fn get_or_default_mut(&mut self, k: K) -> &mut V
where
K: Ord + Eq,
V: Default,
{
self.get_or_insert_with(k, || V::default())
}
pub fn get_or_insert_with<F>(&mut self, k: K, f: F) -> &mut V
where
K: Ord + Eq,
F: FnOnce() -> V,
{
let p = match self.pos(&k) {
Ok(p) => return &mut self.m[p].1,
Err(p) => p,
};
self.m.insert(p, (k, f()));
&mut self.m[p].1
}
pub fn is_empty(&self) -> bool {
self.m.is_empty()
}
pub fn remove(&mut self, k: &K) -> Option<V>
where
K: Ord + Eq,
{
if let Ok(p) = self.pos(k) {
return Some(self.m.remove(p).1);
}
None
}
pub fn clear(&mut self) {
let _v = mem::replace(&mut self.m, SmallVec::new());
}
pub fn take(&mut self) -> SmallVec<[(K, V); N]> {
mem::take(&mut self.m)
}
pub fn iter<'a>(&'a self) -> BinarySearchMapIter<'a, K, V, N> {
BinarySearchMapIter { pos: 0, map: self }
}
pub fn values<'a>(&'a self) -> impl Iterator<Item = &'a V> + 'a {
self.iter().map(|(_, v)| v)
}
pub fn iter_mut<'a>(&'a mut self) -> BinarySearchMapMutIterMut<'a, K, V, N> {
BinarySearchMapMutIterMut { pos: 0, map: self }
}
pub fn values_mut<'a>(&'a mut self) -> impl Iterator<Item = &'a mut V> + 'a {
self.iter_mut().map(|(_, v)| v)
}
pub fn remove_if<F: FnMut(&K, &V) -> bool>(&mut self, mut f: F) {
let mut i = 0;
while i < self.m.len() {
let (k, v) = &self.m[i];
if f(k, v) {
self.m.remove(i);
} else {
i += 1;
}
}
}
}
impl<'a, K: Copy, V, const N: usize> IntoIterator for &'a BinarySearchMap<K, V, N> {
type Item = (&'a K, &'a V);
type IntoIter = BinarySearchMapIter<'a, K, V, N>;
fn into_iter(self) -> Self::IntoIter {
BinarySearchMapIter { pos: 0, map: self }
}
}
impl<'a, K: Copy, V, const N: usize> IntoIterator for &'a mut BinarySearchMap<K, V, N> {
type Item = (&'a K, &'a mut V);
type IntoIter = BinarySearchMapMutIterMut<'a, K, V, N>;
fn into_iter(self) -> Self::IntoIter {
BinarySearchMapMutIterMut { pos: 0, map: self }
}
}
pub struct BinarySearchMapIter<'a, K, V, const N: usize> {
pos: usize,
map: &'a BinarySearchMap<K, V, N>,
}
impl<'a, K, V, const N: usize> Iterator for BinarySearchMapIter<'a, K, V, N> {
type Item = (&'a K, &'a V);
fn next(&mut self) -> Option<Self::Item> {
if self.pos >= self.map.m.len() {
return None;
}
let (k, v) = &self.map.m[self.pos];
self.pos += 1;
Some((k, v))
}
}
pub struct BinarySearchMapMutIterMut<'a, K, V, const N: usize> {
pos: usize,
map: &'a mut BinarySearchMap<K, V, N>,
}
impl<'a, K, V, const N: usize> Iterator for BinarySearchMapMutIterMut<'a, K, V, N> {
type Item = (&'a K, &'a mut V);
fn next(&mut self) -> Option<Self::Item> {
if self.pos >= self.map.m.len() {
return None;
}
let (k, v) = &mut self.map.m[self.pos];
self.pos += 1;
unsafe { Some(((k as *const K).deref(), (v as *mut V).deref_mut())) }
}
}

View file

@ -1,37 +0,0 @@
#![allow(dead_code)]
const SEG_SIZE: usize = usize::BITS as usize;
#[derive(Default)]
pub struct Bitfield {
vals: Vec<usize>,
}
impl Bitfield {
pub fn take(&mut self, val: u32) {
let idx = val as usize / SEG_SIZE;
let pos = val as usize % SEG_SIZE;
while self.vals.len() <= idx {
self.vals.push(!0);
}
self.vals[idx] &= !(1 << pos);
}
pub fn acquire(&mut self) -> u32 {
for (idx, n) in self.vals.iter_mut().enumerate() {
if *n != 0 {
let pos = n.trailing_zeros();
*n &= !(1 << pos);
return (idx * SEG_SIZE) as u32 + pos;
}
}
self.vals.push(!1);
((self.vals.len() - 1) * SEG_SIZE) as u32
}
pub fn release(&mut self, val: u32) {
let idx = val as usize / SEG_SIZE;
let pos = val as usize % SEG_SIZE;
self.vals[idx] |= 1 << pos;
}
}

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