all: split reusable components into workspace crates
This commit is contained in:
parent
2a079ed800
commit
657e7ce2f7
225 changed files with 7422 additions and 17602 deletions
|
|
@ -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
36
src/cli.rs
36
src/cli.rs
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>,
|
||||
}
|
||||
|
||||
|
|
|
|||
57
src/cmm.rs
57
src/cmm.rs
|
|
@ -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::*;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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],
|
||||
])
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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],
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<()>> {
|
||||
|
|
|
|||
124
src/config.rs
124
src/config.rs
|
|
@ -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, ©) {
|
||||
return Err(ConfigError::CopyConfigFile(e));
|
||||
}
|
||||
let unlink = UnlinkOnDrop(©);
|
||||
let lib = match unsafe { Library::new(©) } {
|
||||
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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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::*;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>;
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
pub mod critm_any_or_all;
|
||||
pub mod critm_constant;
|
||||
pub mod critm_exactly;
|
||||
pub mod critm_string;
|
||||
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
1303
src/edid.rs
1303
src/edid.rs
File diff suppressed because it is too large
Load diff
138
src/fixed.rs
138
src/fixed.rs
|
|
@ -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::*;
|
||||
|
|
|
|||
584
src/format.rs
584
src/format.rs
|
|
@ -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::*;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
));
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
pub mod pw_client;
|
||||
pub mod pw_client_node;
|
||||
pub mod pw_core;
|
||||
pub mod pw_registry;
|
||||
|
|
@ -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),
|
||||
}
|
||||
|
|
@ -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),
|
||||
}
|
||||
|
|
@ -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),
|
||||
}
|
||||
|
|
@ -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),
|
||||
}
|
||||
|
|
@ -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),
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
353
src/portal.rs
353
src/portal.rs
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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>>,
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
369
src/rect.rs
369
src/rect.rs
|
|
@ -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::*;
|
||||
|
|
|
|||
|
|
@ -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(®ion),
|
||||
BuilderOp::Sub => self.base.subtract(®ion),
|
||||
};
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
73
src/scale.rs
73
src/scale.rs
|
|
@ -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::*;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
35
src/state.rs
35
src/state.rs
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}))
|
||||
}
|
||||
120
src/time.rs
120
src/time.rs
|
|
@ -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::*;
|
||||
|
|
|
|||
|
|
@ -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)?;
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
|
|
|
|||
134
src/utils.rs
134
src/utils.rs
|
|
@ -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 {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
|
@ -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,);
|
||||
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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())) }
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue