autocommit 2022-05-01 17:23:55 CEST
This commit is contained in:
parent
4373ed05bf
commit
e1d5bf0e5d
39 changed files with 1772 additions and 57 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -2,3 +2,4 @@
|
||||||
!.gitignore
|
!.gitignore
|
||||||
!/.cargo
|
!/.cargo
|
||||||
/target
|
/target
|
||||||
|
/testruns
|
||||||
|
|
|
||||||
|
|
@ -54,3 +54,4 @@ opt-level = 3
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
rc_tracking = []
|
rc_tracking = []
|
||||||
|
it = []
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use {
|
||||||
},
|
},
|
||||||
std::rc::Rc,
|
std::rc::Rc,
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
uapi::{c, format_ustr, Errno, OwnedFd, Ustring},
|
uapi::{c, format_ustr, Errno, OwnedFd, Ustr, Ustring},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
|
@ -48,7 +48,7 @@ pub struct Acceptor {
|
||||||
|
|
||||||
struct AllocatedSocket {
|
struct AllocatedSocket {
|
||||||
// wayland-x
|
// wayland-x
|
||||||
name: Ustring,
|
name: String,
|
||||||
// /run/user/1000/wayland-x
|
// /run/user/1000/wayland-x
|
||||||
path: Ustring,
|
path: Ustring,
|
||||||
insecure: Rc<OwnedFd>,
|
insecure: Rc<OwnedFd>,
|
||||||
|
|
@ -56,6 +56,8 @@ struct AllocatedSocket {
|
||||||
lock_path: Ustring,
|
lock_path: Ustring,
|
||||||
_lock_fd: OwnedFd,
|
_lock_fd: OwnedFd,
|
||||||
// /run/user/1000/wayland-x.jay
|
// /run/user/1000/wayland-x.jay
|
||||||
|
#[cfg_attr(not(feature = "it"), allow(dead_code))]
|
||||||
|
secure_path: Ustring,
|
||||||
secure: Rc<OwnedFd>,
|
secure: Rc<OwnedFd>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,8 +76,8 @@ fn bind_socket(
|
||||||
) -> Result<AllocatedSocket, AcceptorError> {
|
) -> Result<AllocatedSocket, AcceptorError> {
|
||||||
let mut addr: c::sockaddr_un = uapi::pod_zeroed();
|
let mut addr: c::sockaddr_un = uapi::pod_zeroed();
|
||||||
addr.sun_family = c::AF_UNIX as _;
|
addr.sun_family = c::AF_UNIX as _;
|
||||||
let name = format_ustr!("wayland-{}", id);
|
let name = format!("wayland-{}", id);
|
||||||
let path = format_ustr!("{}/{}", xrd, name.display());
|
let path = format_ustr!("{}/{}", xrd, name);
|
||||||
let jay_path = format_ustr!("{}.jay", path.display());
|
let jay_path = format_ustr!("{}.jay", path.display());
|
||||||
let lock_path = format_ustr!("{}.lock", path.display());
|
let lock_path = format_ustr!("{}.lock", path.display());
|
||||||
if jay_path.len() + 1 > addr.sun_path.len() {
|
if jay_path.len() + 1 > addr.sun_path.len() {
|
||||||
|
|
@ -110,6 +112,7 @@ fn bind_socket(
|
||||||
insecure: insecure.clone(),
|
insecure: insecure.clone(),
|
||||||
lock_path,
|
lock_path,
|
||||||
_lock_fd: lock_fd,
|
_lock_fd: lock_fd,
|
||||||
|
secure_path: jay_path,
|
||||||
secure: secure.clone(),
|
secure: secure.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -145,7 +148,7 @@ fn allocate_socket() -> Result<AllocatedSocket, AcceptorError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Acceptor {
|
impl Acceptor {
|
||||||
pub fn install(state: &Rc<State>) -> Result<Rc<String>, AcceptorError> {
|
pub fn install(state: &Rc<State>) -> Result<Rc<Acceptor>, AcceptorError> {
|
||||||
let socket = allocate_socket()?;
|
let socket = allocate_socket()?;
|
||||||
log::info!("bound to socket {}", socket.path.display());
|
log::info!("bound to socket {}", socket.path.display());
|
||||||
for fd in [&socket.secure, &socket.insecure] {
|
for fd in [&socket.secure, &socket.insecure] {
|
||||||
|
|
@ -155,7 +158,6 @@ impl Acceptor {
|
||||||
}
|
}
|
||||||
let id1 = state.el.id();
|
let id1 = state.el.id();
|
||||||
let id2 = state.el.id();
|
let id2 = state.el.id();
|
||||||
let name = socket.name.to_owned();
|
|
||||||
let acc = Rc::new(Acceptor {
|
let acc = Rc::new(Acceptor {
|
||||||
ids: [id1, id2],
|
ids: [id1, id2],
|
||||||
socket,
|
socket,
|
||||||
|
|
@ -169,10 +171,18 @@ impl Acceptor {
|
||||||
)?;
|
)?;
|
||||||
state
|
state
|
||||||
.el
|
.el
|
||||||
.insert(id2, Some(acc.socket.secure.raw()), c::EPOLLIN, acc)?;
|
.insert(id2, Some(acc.socket.secure.raw()), c::EPOLLIN, acc.clone())?;
|
||||||
let name = Rc::new(name.display().to_string());
|
state.acceptor.set(Some(acc.clone()));
|
||||||
state.socket_path.set(name.clone());
|
Ok(acc)
|
||||||
Ok(name)
|
}
|
||||||
|
|
||||||
|
pub fn socket_name(&self) -> &str {
|
||||||
|
&self.socket.name
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(not(feature = "it"), allow(dead_code))]
|
||||||
|
pub fn secure_path(&self) -> &Ustr {
|
||||||
|
self.socket.secure_path.as_ustr()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use {
|
||||||
video::drm::ConnectorType,
|
video::drm::ConnectorType,
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
|
any::Any,
|
||||||
error::Error,
|
error::Error,
|
||||||
fmt::{Debug, Display, Formatter},
|
fmt::{Debug, Display, Formatter},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
|
|
@ -17,6 +18,7 @@ linear_ids!(InputDeviceIds, InputDeviceId);
|
||||||
|
|
||||||
pub trait Backend {
|
pub trait Backend {
|
||||||
fn run(self: Rc<Self>) -> SpawnedFuture<Result<(), Box<dyn Error>>>;
|
fn run(self: Rc<Self>) -> SpawnedFuture<Result<(), Box<dyn Error>>>;
|
||||||
|
fn into_any(self: Rc<Self>) -> Rc<dyn Any>;
|
||||||
|
|
||||||
fn switch_to(&self, vtnr: u32) {
|
fn switch_to(&self, vtnr: u32) {
|
||||||
let _ = vtnr;
|
let _ = vtnr;
|
||||||
|
|
@ -30,7 +32,7 @@ pub trait Backend {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_freestanding(&self) -> bool {
|
fn import_environment(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,6 +59,7 @@ pub struct MonitorInfo {
|
||||||
pub height_mm: i32,
|
pub height_mm: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct ConnectorKernelId {
|
pub struct ConnectorKernelId {
|
||||||
pub ty: ConnectorType,
|
pub ty: ConnectorType,
|
||||||
pub idx: u32,
|
pub idx: u32,
|
||||||
|
|
@ -84,6 +87,8 @@ pub enum ConnectorEvent {
|
||||||
ModeChanged(Mode),
|
ModeChanged(Mode),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type TransformMatrix = [[f64; 2]; 2];
|
||||||
|
|
||||||
pub trait InputDevice {
|
pub trait InputDevice {
|
||||||
fn id(&self) -> InputDeviceId;
|
fn id(&self) -> InputDeviceId;
|
||||||
fn removed(&self) -> bool;
|
fn removed(&self) -> bool;
|
||||||
|
|
@ -94,11 +99,11 @@ pub trait InputDevice {
|
||||||
fn set_left_handed(&self, left_handed: bool);
|
fn set_left_handed(&self, left_handed: bool);
|
||||||
fn set_accel_profile(&self, profile: InputDeviceAccelProfile);
|
fn set_accel_profile(&self, profile: InputDeviceAccelProfile);
|
||||||
fn set_accel_speed(&self, speed: f64);
|
fn set_accel_speed(&self, speed: f64);
|
||||||
fn set_transform_matrix(&self, matrix: [[f64; 2]; 2]);
|
fn set_transform_matrix(&self, matrix: TransformMatrix);
|
||||||
fn name(&self) -> Rc<String>;
|
fn name(&self) -> Rc<String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
|
||||||
pub enum InputDeviceCapability {
|
pub enum InputDeviceCapability {
|
||||||
Keyboard,
|
Keyboard,
|
||||||
Pointer,
|
Pointer,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use {
|
||||||
backend::{Backend, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId},
|
backend::{Backend, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId},
|
||||||
video::drm::ConnectorType,
|
video::drm::ConnectorType,
|
||||||
},
|
},
|
||||||
std::{error::Error, rc::Rc},
|
std::{any::Any, error::Error, rc::Rc},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct DummyBackend;
|
pub struct DummyBackend;
|
||||||
|
|
@ -13,6 +13,10 @@ impl Backend for DummyBackend {
|
||||||
fn run(self: Rc<Self>) -> SpawnedFuture<Result<(), Box<dyn Error>>> {
|
fn run(self: Rc<Self>) -> SpawnedFuture<Result<(), Box<dyn Error>>> {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DummyOutput {
|
pub struct DummyOutput {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use {
|
||||||
async_engine::{AsyncError, AsyncFd, SpawnedFuture},
|
async_engine::{AsyncError, AsyncFd, SpawnedFuture},
|
||||||
backend::{
|
backend::{
|
||||||
Backend, BackendEvent, InputDevice, InputDeviceAccelProfile, InputDeviceCapability,
|
Backend, BackendEvent, InputDevice, InputDeviceAccelProfile, InputDeviceCapability,
|
||||||
InputDeviceId, InputEvent, KeyState,
|
InputDeviceId, InputEvent, KeyState, TransformMatrix,
|
||||||
},
|
},
|
||||||
backends::metal::video::{MetalDrmDevice, PendingDrmDevice},
|
backends::metal::video::{MetalDrmDevice, PendingDrmDevice},
|
||||||
dbus::{DbusError, SignalHandler},
|
dbus::{DbusError, SignalHandler},
|
||||||
|
|
@ -40,6 +40,7 @@ use {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
|
any::Any,
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
error::Error,
|
error::Error,
|
||||||
ffi::{CStr, CString},
|
ffi::{CStr, CString},
|
||||||
|
|
@ -148,6 +149,10 @@ impl Backend for MetalBackend {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn switch_to(&self, vtnr: u32) {
|
fn switch_to(&self, vtnr: u32) {
|
||||||
self.session.switch_to(vtnr, move |res| {
|
self.session.switch_to(vtnr, move |res| {
|
||||||
if let Err(e) = res {
|
if let Err(e) = res {
|
||||||
|
|
@ -188,7 +193,7 @@ impl Backend for MetalBackend {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_freestanding(&self) -> bool {
|
fn import_environment(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -288,7 +293,7 @@ struct MetalInputDevice {
|
||||||
left_handed: Cell<Option<bool>>,
|
left_handed: Cell<Option<bool>>,
|
||||||
accel_profile: Cell<Option<AccelProfile>>,
|
accel_profile: Cell<Option<AccelProfile>>,
|
||||||
accel_speed: Cell<Option<f64>>,
|
accel_speed: Cell<Option<f64>>,
|
||||||
transform_matrix: Cell<Option<[[f64; 2]; 2]>>,
|
transform_matrix: Cell<Option<TransformMatrix>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for MetalInputDevice {
|
impl Drop for MetalInputDevice {
|
||||||
|
|
@ -419,7 +424,7 @@ impl InputDevice for MetalInputDevice {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_transform_matrix(&self, matrix: [[f64; 2]; 2]) {
|
fn set_transform_matrix(&self, matrix: TransformMatrix) {
|
||||||
self.transform_matrix.set(Some(matrix));
|
self.transform_matrix.set(Some(matrix));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use {
|
||||||
backend::{
|
backend::{
|
||||||
AxisSource, Backend, BackendEvent, Connector, ConnectorEvent, ConnectorId,
|
AxisSource, Backend, BackendEvent, Connector, ConnectorEvent, ConnectorId,
|
||||||
ConnectorKernelId, InputDevice, InputDeviceAccelProfile, InputDeviceCapability,
|
ConnectorKernelId, InputDevice, InputDeviceAccelProfile, InputDeviceCapability,
|
||||||
InputDeviceId, InputEvent, KeyState, Mode, MonitorInfo, ScrollAxis,
|
InputDeviceId, InputEvent, KeyState, Mode, MonitorInfo, ScrollAxis, TransformMatrix,
|
||||||
},
|
},
|
||||||
fixed::Fixed,
|
fixed::Fixed,
|
||||||
format::XRGB8888,
|
format::XRGB8888,
|
||||||
|
|
@ -48,6 +48,7 @@ use {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
|
any::Any,
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
|
|
@ -242,6 +243,10 @@ impl Backend for XBackend {
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct XBackend {
|
pub struct XBackend {
|
||||||
|
|
@ -1054,7 +1059,7 @@ impl InputDevice for XSeatKeyboard {
|
||||||
let _ = speed;
|
let _ = speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_transform_matrix(&self, matrix: [[f64; 2]; 2]) {
|
fn set_transform_matrix(&self, matrix: TransformMatrix) {
|
||||||
let _ = matrix;
|
let _ = matrix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1103,7 +1108,7 @@ impl InputDevice for XSeatMouse {
|
||||||
let _ = speed;
|
let _ = speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_transform_matrix(&self, matrix: [[f64; 2]; 2]) {
|
fn set_transform_matrix(&self, matrix: TransformMatrix) {
|
||||||
let _ = matrix;
|
let _ = matrix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,8 @@ pub enum Cmd {
|
||||||
Screenshot(ScreenshotArgs),
|
Screenshot(ScreenshotArgs),
|
||||||
/// Inspect/modify the idle (screensaver) settings.
|
/// Inspect/modify the idle (screensaver) settings.
|
||||||
Idle(IdleArgs),
|
Idle(IdleArgs),
|
||||||
|
#[cfg(feature = "it")]
|
||||||
|
RunTests,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
|
|
@ -101,7 +103,7 @@ pub struct ScreenshotArgs {
|
||||||
pub filename: Option<String>,
|
pub filename: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug, Default)]
|
||||||
pub struct RunArgs {
|
pub struct RunArgs {
|
||||||
/// The backends to try.
|
/// The backends to try.
|
||||||
///
|
///
|
||||||
|
|
@ -184,5 +186,7 @@ pub fn main() {
|
||||||
Cmd::SetLogLevel(a) => set_log_level::main(cli.global, a),
|
Cmd::SetLogLevel(a) => set_log_level::main(cli.global, a),
|
||||||
Cmd::Screenshot(a) => screenshot::main(cli.global, a),
|
Cmd::Screenshot(a) => screenshot::main(cli.global, a),
|
||||||
Cmd::Idle(a) => idle::main(cli.global, a),
|
Cmd::Idle(a) => idle::main(cli.global, a),
|
||||||
|
#[cfg(feature = "it")]
|
||||||
|
Cmd::RunTests => crate::it::run_tests(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,11 @@ impl ClientId {
|
||||||
pub fn raw(self) -> u64 {
|
pub fn raw(self) -> u64 {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(not(feature = "it"), allow(dead_code))]
|
||||||
|
pub fn from_raw(val: u64) -> Self {
|
||||||
|
Self(val)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ClientId {
|
impl Display for ClientId {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
#[cfg(feature = "it")]
|
||||||
|
use crate::it::test_backend::TestBackend;
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
acceptor::{Acceptor, AcceptorError},
|
acceptor::{Acceptor, AcceptorError},
|
||||||
|
|
@ -36,7 +38,7 @@ use {
|
||||||
},
|
},
|
||||||
ahash::AHashSet,
|
ahash::AHashSet,
|
||||||
forker::ForkerProxy,
|
forker::ForkerProxy,
|
||||||
std::{cell::Cell, ops::Deref, rc::Rc, sync::Arc, time::Duration},
|
std::{cell::Cell, future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration},
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
uapi::c,
|
uapi::c,
|
||||||
};
|
};
|
||||||
|
|
@ -44,12 +46,9 @@ use {
|
||||||
pub const MAX_EXTENTS: i32 = (1 << 22) - 1;
|
pub const MAX_EXTENTS: i32 = (1 << 22) - 1;
|
||||||
|
|
||||||
pub fn start_compositor(global: GlobalArgs, args: RunArgs) {
|
pub fn start_compositor(global: GlobalArgs, args: RunArgs) {
|
||||||
let forker = match ForkerProxy::create() {
|
let forker = create_forker();
|
||||||
Ok(f) => Rc::new(f),
|
|
||||||
Err(e) => fatal!("Could not create a forker process: {}", ErrorFmt(e)),
|
|
||||||
};
|
|
||||||
let logger = Logger::install_compositor(global.log_level.into());
|
let logger = Logger::install_compositor(global.log_level.into());
|
||||||
if let Err(e) = start_compositor2(forker, logger.clone(), args) {
|
if let Err(e) = start_compositor2(forker, Some(logger.clone()), args, None) {
|
||||||
let e = ErrorFmt(e);
|
let e = ErrorFmt(e);
|
||||||
log::error!("A fatal error occurred: {}", e);
|
log::error!("A fatal error occurred: {}", e);
|
||||||
eprintln!("A fatal error occurred: {}", e);
|
eprintln!("A fatal error occurred: {}", e);
|
||||||
|
|
@ -59,8 +58,21 @@ pub fn start_compositor(global: GlobalArgs, args: RunArgs) {
|
||||||
log::info!("Exit");
|
log::info!("Exit");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "it")]
|
||||||
|
pub fn start_compositor_for_test(future: TestFuture) -> Result<(), CompositorError> {
|
||||||
|
let forker = create_forker();
|
||||||
|
start_compositor2(forker, None, RunArgs::default(), Some(future))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_forker() -> Rc<ForkerProxy> {
|
||||||
|
match ForkerProxy::create() {
|
||||||
|
Ok(f) => Rc::new(f),
|
||||||
|
Err(e) => fatal!("Could not create a forker process: {}", ErrorFmt(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
enum MainError {
|
pub enum CompositorError {
|
||||||
#[error("The client acceptor caused an error")]
|
#[error("The client acceptor caused an error")]
|
||||||
AcceptorError(#[from] AcceptorError),
|
AcceptorError(#[from] AcceptorError),
|
||||||
#[error("The event loop caused an error")]
|
#[error("The event loop caused an error")]
|
||||||
|
|
@ -86,11 +98,14 @@ const STATIC_VARS: &[(&str, &str)] = &[
|
||||||
("_JAVA_AWT_WM_NONREPARENTING", "1"),
|
("_JAVA_AWT_WM_NONREPARENTING", "1"),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pub type TestFuture = Box<dyn Fn(&Rc<State>) -> Box<dyn Future<Output = ()>>>;
|
||||||
|
|
||||||
fn start_compositor2(
|
fn start_compositor2(
|
||||||
forker: Rc<ForkerProxy>,
|
forker: Rc<ForkerProxy>,
|
||||||
logger: Arc<Logger>,
|
logger: Option<Arc<Logger>>,
|
||||||
run_args: RunArgs,
|
run_args: RunArgs,
|
||||||
) -> Result<(), MainError> {
|
test_future: Option<TestFuture>,
|
||||||
|
) -> Result<(), CompositorError> {
|
||||||
log::info!("pid = {}", uapi::getpid());
|
log::info!("pid = {}", uapi::getpid());
|
||||||
init_fd_limit();
|
init_fd_limit();
|
||||||
leaks::init();
|
leaks::init();
|
||||||
|
|
@ -155,19 +170,22 @@ fn start_compositor2(
|
||||||
handler: Default::default(),
|
handler: Default::default(),
|
||||||
queue: Default::default(),
|
queue: Default::default(),
|
||||||
},
|
},
|
||||||
socket_path: Default::default(),
|
acceptor: Default::default(),
|
||||||
serial: Default::default(),
|
serial: Default::default(),
|
||||||
idle_inhibitor_ids: Default::default(),
|
idle_inhibitor_ids: Default::default(),
|
||||||
run_toplevel,
|
run_toplevel,
|
||||||
});
|
});
|
||||||
create_dummy_output(&state);
|
create_dummy_output(&state);
|
||||||
let socket_path = Acceptor::install(&state)?;
|
let acceptor = Acceptor::install(&state)?;
|
||||||
forker.install(&state);
|
forker.install(&state);
|
||||||
forker.setenv(WAYLAND_DISPLAY.as_bytes(), socket_path.as_bytes());
|
forker.setenv(
|
||||||
|
WAYLAND_DISPLAY.as_bytes(),
|
||||||
|
acceptor.socket_name().as_bytes(),
|
||||||
|
);
|
||||||
for (key, val) in STATIC_VARS {
|
for (key, val) in STATIC_VARS {
|
||||||
forker.setenv(key.as_bytes(), val.as_bytes());
|
forker.setenv(key.as_bytes(), val.as_bytes());
|
||||||
}
|
}
|
||||||
let _compositor = engine.spawn(start_compositor3(state.clone()));
|
let _compositor = engine.spawn(start_compositor3(state.clone(), test_future));
|
||||||
el.run()?;
|
el.run()?;
|
||||||
state.xwayland.handler.borrow_mut().take();
|
state.xwayland.handler.borrow_mut().take();
|
||||||
state.clients.clear();
|
state.clients.clear();
|
||||||
|
|
@ -178,8 +196,8 @@ fn start_compositor2(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn start_compositor3(state: Rc<State>) {
|
async fn start_compositor3(state: Rc<State>, test_future: Option<TestFuture>) {
|
||||||
let backend = match create_backend(&state).await {
|
let backend = match create_backend(&state, test_future).await {
|
||||||
Some(b) => b,
|
Some(b) => b,
|
||||||
_ => {
|
_ => {
|
||||||
log::error!("Could not create a backend");
|
log::error!("Could not create a backend");
|
||||||
|
|
@ -190,8 +208,10 @@ async fn start_compositor3(state: Rc<State>) {
|
||||||
state.backend.set(backend.clone());
|
state.backend.set(backend.clone());
|
||||||
state.globals.add_singletons(&backend);
|
state.globals.add_singletons(&backend);
|
||||||
|
|
||||||
if backend.is_freestanding() {
|
if backend.import_environment() {
|
||||||
import_environment(&state, WAYLAND_DISPLAY, &state.socket_path.get());
|
if let Some(acc) = state.acceptor.get() {
|
||||||
|
import_environment(&state, WAYLAND_DISPLAY, acc.socket_name());
|
||||||
|
}
|
||||||
for (key, val) in STATIC_VARS {
|
for (key, val) in STATIC_VARS {
|
||||||
import_environment(&state, key, val);
|
import_environment(&state, key, val);
|
||||||
}
|
}
|
||||||
|
|
@ -228,7 +248,17 @@ fn start_global_event_handlers(
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_backend(state: &Rc<State>) -> Option<Rc<dyn Backend>> {
|
async fn create_backend(
|
||||||
|
state: &Rc<State>,
|
||||||
|
#[allow(unused_variables)] test_future: Option<TestFuture>,
|
||||||
|
) -> Option<Rc<dyn Backend>> {
|
||||||
|
#[cfg(feature = "it")]
|
||||||
|
if let Some(tf) = test_future {
|
||||||
|
return Some(Rc::new(TestBackend {
|
||||||
|
state: state.clone(),
|
||||||
|
test_future: tf,
|
||||||
|
}));
|
||||||
|
}
|
||||||
let mut backends = &state.run_args.backends[..];
|
let mut backends = &state.run_args.backends[..];
|
||||||
if backends.is_empty() {
|
if backends.is_empty() {
|
||||||
backends = &[CliBackend::X11, CliBackend::Metal];
|
backends = &[CliBackend::X11, CliBackend::Metal];
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ use {
|
||||||
},
|
},
|
||||||
wire::{jay_compositor::*, JayCompositorId},
|
wire::{jay_compositor::*, JayCompositorId},
|
||||||
},
|
},
|
||||||
|
bstr::ByteSlice,
|
||||||
log::Level,
|
log::Level,
|
||||||
std::{ops::Deref, rc::Rc},
|
std::{ops::Deref, rc::Rc},
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
|
|
@ -80,7 +81,11 @@ impl JayCompositor {
|
||||||
let log_file = Rc::new(JayLogFile::new(req.id, &self.client));
|
let log_file = Rc::new(JayLogFile::new(req.id, &self.client));
|
||||||
track!(self.client, log_file);
|
track!(self.client, log_file);
|
||||||
self.client.add_client_obj(&log_file)?;
|
self.client.add_client_obj(&log_file)?;
|
||||||
log_file.send_path(self.client.state.logger.path());
|
let path = match &self.client.state.logger {
|
||||||
|
Some(logger) => logger.path(),
|
||||||
|
_ => "".as_bytes().as_bstr(),
|
||||||
|
};
|
||||||
|
log_file.send_path(path);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,7 +111,9 @@ impl JayCompositor {
|
||||||
TRACE => Level::Trace,
|
TRACE => Level::Trace,
|
||||||
_ => return Err(JayCompositorError::UnknownLogLevel(req.level)),
|
_ => return Err(JayCompositorError::UnknownLogLevel(req.level)),
|
||||||
};
|
};
|
||||||
self.client.state.logger.set_level(level);
|
if let Some(logger) = &self.client.state.logger {
|
||||||
|
logger.set_level(level);
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -152,6 +159,15 @@ impl JayCompositor {
|
||||||
self.client.add_client_obj(&idle)?;
|
self.client.add_client_obj(&idle)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_client_id(&self, parser: MsgParser<'_, '_>) -> Result<(), JayCompositorError> {
|
||||||
|
let _req: GetClientId = self.client.parse(self, parser)?;
|
||||||
|
self.client.event(ClientId {
|
||||||
|
self_id: self.id,
|
||||||
|
client_id: self.client.id.raw(),
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object_base! {
|
object_base! {
|
||||||
|
|
@ -163,11 +179,12 @@ object_base! {
|
||||||
SET_LOG_LEVEL => set_log_level,
|
SET_LOG_LEVEL => set_log_level,
|
||||||
TAKE_SCREENSHOT => take_screenshot,
|
TAKE_SCREENSHOT => take_screenshot,
|
||||||
GET_IDLE => get_idle,
|
GET_IDLE => get_idle,
|
||||||
|
GET_CLIENT_ID => get_client_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Object for JayCompositor {
|
impl Object for JayCompositor {
|
||||||
fn num_requests(&self) -> u32 {
|
fn num_requests(&self) -> u32 {
|
||||||
GET_IDLE + 1
|
GET_CLIENT_ID + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
113
src/it.rs
Normal file
113
src/it.rs
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
#![cfg(feature = "it")]
|
||||||
|
|
||||||
|
use {
|
||||||
|
crate::it::{test_backend::TestBackend, testrun::TestRun, tests::TestCase},
|
||||||
|
ahash::AHashMap,
|
||||||
|
isnt::std_1::collections::IsntHashMapExt,
|
||||||
|
log::Level,
|
||||||
|
std::{
|
||||||
|
cell::{Cell, RefCell},
|
||||||
|
future::pending,
|
||||||
|
pin::Pin,
|
||||||
|
rc::Rc,
|
||||||
|
time::SystemTime,
|
||||||
|
},
|
||||||
|
uapi::c,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod test_error;
|
||||||
|
#[macro_use]
|
||||||
|
mod test_object;
|
||||||
|
pub mod test_backend;
|
||||||
|
mod test_client;
|
||||||
|
mod test_ifs;
|
||||||
|
mod test_logger;
|
||||||
|
mod test_mem;
|
||||||
|
mod test_transport;
|
||||||
|
mod testrun;
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
pub fn run_tests() {
|
||||||
|
test_logger::install();
|
||||||
|
test_logger::set_level(Level::Trace);
|
||||||
|
let it_run = ItRun {
|
||||||
|
path: format!(
|
||||||
|
"{}/testruns/{}",
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
humantime::format_rfc3339_millis(SystemTime::now())
|
||||||
|
),
|
||||||
|
failed: Default::default(),
|
||||||
|
};
|
||||||
|
for test in tests::tests() {
|
||||||
|
run_test(&it_run, test);
|
||||||
|
}
|
||||||
|
let failed = it_run.failed.borrow_mut();
|
||||||
|
if failed.is_not_empty() {
|
||||||
|
let mut failed: Vec<_> = failed.iter().collect();
|
||||||
|
failed.sort_by_key(|f| f.0);
|
||||||
|
log::error!("The following tests failed:");
|
||||||
|
for (name, errors) in failed {
|
||||||
|
log::error!(" {}:", name);
|
||||||
|
for error in errors {
|
||||||
|
log::error!(" {}", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fatal!("Some tests failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ItRun {
|
||||||
|
path: String,
|
||||||
|
failed: RefCell<AHashMap<&'static str, Vec<String>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_test(it_run: &ItRun, test: &'static dyn TestCase) {
|
||||||
|
let dir = format!("{}/{}", it_run.path, test.name());
|
||||||
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
|
let log_path = format!("{}/log", dir);
|
||||||
|
let log_file = Rc::new(uapi::open(log_path.as_str(), c::O_WRONLY | c::O_CREAT, 0o644).unwrap());
|
||||||
|
test_logger::set_file(log_file);
|
||||||
|
let errors = Rc::new(Cell::new(Vec::new()));
|
||||||
|
let errors2 = errors.clone();
|
||||||
|
let res = crate::compositor::start_compositor_for_test(Box::new(move |state| {
|
||||||
|
let state = state.clone();
|
||||||
|
let server_addr = {
|
||||||
|
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 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;
|
||||||
|
addr
|
||||||
|
};
|
||||||
|
let backend: Rc<TestBackend> = state.backend.get().into_any().downcast().unwrap();
|
||||||
|
let testrun = Rc::new(TestRun {
|
||||||
|
state: state.clone(),
|
||||||
|
backend,
|
||||||
|
errors: Default::default(),
|
||||||
|
server_addr,
|
||||||
|
});
|
||||||
|
let errors = errors2.clone();
|
||||||
|
Box::new(async move {
|
||||||
|
let future: Pin<_> = test.run(testrun.clone()).into();
|
||||||
|
if let Err(e) = future.await {
|
||||||
|
testrun.errors.push(e.to_string());
|
||||||
|
}
|
||||||
|
errors.set(testrun.errors.take());
|
||||||
|
state.el.stop();
|
||||||
|
pending().await
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
let errors = errors.take();
|
||||||
|
if errors.len() > 0 {
|
||||||
|
log::error!("The following errors occurred:");
|
||||||
|
for e in &errors {
|
||||||
|
log::error!(" {}", e);
|
||||||
|
}
|
||||||
|
it_run.failed.borrow_mut().insert(test.name(), errors);
|
||||||
|
}
|
||||||
|
test_logger::unset_file();
|
||||||
|
let _ = res;
|
||||||
|
}
|
||||||
136
src/it/test_backend.rs
Normal file
136
src/it/test_backend.rs
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
async_engine::SpawnedFuture,
|
||||||
|
backend::{
|
||||||
|
Backend, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, InputDevice,
|
||||||
|
InputDeviceAccelProfile, InputDeviceCapability, InputDeviceId, InputEvent,
|
||||||
|
TransformMatrix,
|
||||||
|
},
|
||||||
|
compositor::TestFuture,
|
||||||
|
state::State,
|
||||||
|
utils::{clonecell::CloneCell, copyhashmap::CopyHashMap, syncqueue::SyncQueue},
|
||||||
|
},
|
||||||
|
std::{any::Any, cell::Cell, error::Error, pin::Pin, rc::Rc},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct TestBackend {
|
||||||
|
pub state: Rc<State>,
|
||||||
|
pub test_future: TestFuture,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Backend for TestBackend {
|
||||||
|
fn run(self: Rc<Self>) -> SpawnedFuture<Result<(), Box<dyn Error>>> {
|
||||||
|
let future = (self.test_future)(&self.state);
|
||||||
|
self.state.eng.spawn(async move {
|
||||||
|
let future: Pin<_> = future.into();
|
||||||
|
future.await;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn switch_to(&self, vtnr: u32) {
|
||||||
|
let _ = vtnr;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_idle(&self, _idle: bool) {}
|
||||||
|
|
||||||
|
fn supports_idle(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supports_presentation_feedback(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TestConnector {
|
||||||
|
pub id: ConnectorId,
|
||||||
|
pub kernel_id: ConnectorKernelId,
|
||||||
|
pub events: SyncQueue<ConnectorEvent>,
|
||||||
|
pub on_change: CloneCell<Option<Rc<dyn Fn()>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Connector for TestConnector {
|
||||||
|
fn id(&self) -> ConnectorId {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kernel_id(&self) -> ConnectorKernelId {
|
||||||
|
self.kernel_id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&self) -> Option<ConnectorEvent> {
|
||||||
|
self.events.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_change(&self, cb: Rc<dyn Fn()>) {
|
||||||
|
self.on_change.set(Some(cb));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn damage(&self) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TestInputDevice {
|
||||||
|
pub id: InputDeviceId,
|
||||||
|
pub remove: Cell<bool>,
|
||||||
|
pub events: SyncQueue<InputEvent>,
|
||||||
|
pub on_change: CloneCell<Option<Rc<dyn Fn()>>>,
|
||||||
|
pub capabilities: CopyHashMap<InputDeviceCapability, ()>,
|
||||||
|
pub transform_matrix: Cell<TransformMatrix>,
|
||||||
|
pub name: Rc<String>,
|
||||||
|
pub accel_speed: Cell<f64>,
|
||||||
|
pub accel_profile: Cell<InputDeviceAccelProfile>,
|
||||||
|
pub left_handed: Cell<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputDevice for TestInputDevice {
|
||||||
|
fn id(&self) -> InputDeviceId {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn removed(&self) -> bool {
|
||||||
|
self.remove.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&self) -> Option<InputEvent> {
|
||||||
|
self.events.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_change(&self, cb: Rc<dyn Fn()>) {
|
||||||
|
self.on_change.set(Some(cb));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn grab(&self, _grab: bool) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_capability(&self, cap: InputDeviceCapability) -> bool {
|
||||||
|
self.capabilities.contains(&cap)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_left_handed(&self, left_handed: bool) {
|
||||||
|
self.left_handed.set(left_handed);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_accel_profile(&self, profile: InputDeviceAccelProfile) {
|
||||||
|
self.accel_profile.set(profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_accel_speed(&self, speed: f64) {
|
||||||
|
self.accel_speed.set(speed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_transform_matrix(&self, matrix: TransformMatrix) {
|
||||||
|
self.transform_matrix.set(matrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> Rc<String> {
|
||||||
|
self.name.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/it/test_client.rs
Normal file
34
src/it/test_client.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
client::Client,
|
||||||
|
it::{
|
||||||
|
test_ifs::{
|
||||||
|
test_compositor::TestCompositor, test_jay_compositor::TestJayCompositor,
|
||||||
|
test_registry::TestRegistry, test_shm::TestShm,
|
||||||
|
},
|
||||||
|
test_transport::TestTransport,
|
||||||
|
testrun::TestRun,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
std::rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct TestClient {
|
||||||
|
pub run: Rc<TestRun>,
|
||||||
|
pub server: Rc<Client>,
|
||||||
|
pub transport: Rc<TestTransport>,
|
||||||
|
pub registry: Rc<TestRegistry>,
|
||||||
|
pub jc: Rc<TestJayCompositor>,
|
||||||
|
pub comp: Rc<TestCompositor>,
|
||||||
|
pub shm: Rc<TestShm>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestClient {
|
||||||
|
pub fn error(&self, msg: &str) {
|
||||||
|
self.transport.error(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn sync(self: &Rc<Self>) {
|
||||||
|
self.transport.sync().await
|
||||||
|
}
|
||||||
|
}
|
||||||
136
src/it/test_error.rs
Normal file
136
src/it/test_error.rs
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
use {
|
||||||
|
crate::utils::errorfmt::ErrorFmt,
|
||||||
|
std::{
|
||||||
|
error::Error,
|
||||||
|
fmt::{Debug, Display, Formatter},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct TestError {
|
||||||
|
error: Box<dyn Error + 'static>,
|
||||||
|
source: Option<Box<TestError>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestError {
|
||||||
|
pub fn new<D: Display + 'static>(d: D) -> Self {
|
||||||
|
Self {
|
||||||
|
error: Box::new(DisplayError { msg: d }),
|
||||||
|
source: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DisplayError<T: Display> {
|
||||||
|
msg: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Display> Debug for DisplayError<T> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
Display::fmt(&self.msg, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Display> Display for DisplayError<T> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
Display::fmt(&self.msg, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Display> Error for DisplayError<T> {}
|
||||||
|
|
||||||
|
impl Debug for TestError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("TestError")
|
||||||
|
.field("error", &self.error)
|
||||||
|
.field("source", &self.source)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for TestError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let mut e_prev = self;
|
||||||
|
let mut e_opt = Some(self);
|
||||||
|
let mut first = true;
|
||||||
|
while let Some(e) = e_opt {
|
||||||
|
if first {
|
||||||
|
write!(f, "{}", e.error)?;
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
write!(f, ": {}", e.error)?;
|
||||||
|
}
|
||||||
|
e_prev = e;
|
||||||
|
e_opt = e.source.as_deref();
|
||||||
|
}
|
||||||
|
if let Some(e) = e_prev.error.source() {
|
||||||
|
write!(f, ": ")?;
|
||||||
|
ErrorFmt(e).fmt(f)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Error + 'static> From<T> for TestError {
|
||||||
|
fn from(error: T) -> Self {
|
||||||
|
Self {
|
||||||
|
error: Box::new(error),
|
||||||
|
source: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait TestErrorExt {
|
||||||
|
type Context;
|
||||||
|
|
||||||
|
fn with_context<T, F>(self, f: F) -> Self::Context
|
||||||
|
where
|
||||||
|
T: Display + 'static,
|
||||||
|
F: FnOnce() -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E> TestErrorExt for Result<T, E>
|
||||||
|
where
|
||||||
|
E: StdError,
|
||||||
|
{
|
||||||
|
type Context = Result<T, TestError>;
|
||||||
|
|
||||||
|
fn with_context<D, F>(self, f: F) -> Self::Context
|
||||||
|
where
|
||||||
|
D: Display + 'static,
|
||||||
|
F: FnOnce() -> D,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Ok(v) => Ok(v),
|
||||||
|
Err(e) => Err(e.with_context(f())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait StdError: 'static {
|
||||||
|
fn with_context<D: Display + 'static>(self, d: D) -> TestError;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Error + 'static> StdError for E {
|
||||||
|
fn with_context<D: Display + 'static>(self, d: D) -> TestError {
|
||||||
|
TestError {
|
||||||
|
error: Box::new(DisplayError { msg: d }),
|
||||||
|
source: Some(Box::new(self.into())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StdError for TestError {
|
||||||
|
fn with_context<D: Display + 'static>(self, d: D) -> TestError {
|
||||||
|
TestError {
|
||||||
|
error: Box::new(DisplayError { msg: d }),
|
||||||
|
source: Some(Box::new(self)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! bail {
|
||||||
|
($($tt:tt)*) => {{
|
||||||
|
let msg = format!($($tt)*);
|
||||||
|
return Err(crate::it::test_error::TestError::new(msg));
|
||||||
|
}}
|
||||||
|
}
|
||||||
8
src/it/test_ifs.rs
Normal file
8
src/it/test_ifs.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
pub mod test_callback;
|
||||||
|
pub mod test_compositor;
|
||||||
|
pub mod test_display;
|
||||||
|
pub mod test_jay_compositor;
|
||||||
|
pub mod test_registry;
|
||||||
|
pub mod test_shm;
|
||||||
|
pub mod test_shm_buffer;
|
||||||
|
pub mod test_shm_pool;
|
||||||
45
src/it/test_ifs/test_callback.rs
Normal file
45
src/it/test_ifs/test_callback.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
it::{
|
||||||
|
test_error::TestError, test_object::TestObject, test_transport::TestTransport,
|
||||||
|
testrun::ParseFull,
|
||||||
|
},
|
||||||
|
utils::buffd::MsgParser,
|
||||||
|
wire::{wl_callback::*, WlCallbackId},
|
||||||
|
},
|
||||||
|
std::{cell::Cell, rc::Rc},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct TestCallback {
|
||||||
|
pub id: WlCallbackId,
|
||||||
|
pub transport: Rc<TestTransport>,
|
||||||
|
pub handler: Cell<Option<Box<dyn FnOnce()>>>,
|
||||||
|
pub done: Cell<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestCallback {
|
||||||
|
fn handle_done(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
|
||||||
|
let _ev = Done::parse_full(parser)?;
|
||||||
|
self.dispatch();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispatch(&self) {
|
||||||
|
self.done.set(true);
|
||||||
|
if let Some(handler) = self.handler.take() {
|
||||||
|
handler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test_object! {
|
||||||
|
TestCallback, WlCallback;
|
||||||
|
|
||||||
|
DONE => handle_done,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestObject for TestCallback {
|
||||||
|
fn on_remove(&self, _transport: &TestTransport) {
|
||||||
|
self.dispatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/it/test_ifs/test_compositor.rs
Normal file
18
src/it/test_ifs/test_compositor.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
it::{test_object::TestObject, test_transport::TestTransport},
|
||||||
|
wire::WlCompositorId,
|
||||||
|
},
|
||||||
|
std::rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct TestCompositor {
|
||||||
|
pub id: WlCompositorId,
|
||||||
|
pub transport: Rc<TestTransport>,
|
||||||
|
}
|
||||||
|
|
||||||
|
test_object! {
|
||||||
|
TestCompositor, WlCompositor;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestObject for TestCompositor {}
|
||||||
55
src/it/test_ifs/test_display.rs
Normal file
55
src/it/test_ifs/test_display.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
it::{
|
||||||
|
test_error::TestError, test_object::TestObject, test_transport::TestTransport,
|
||||||
|
testrun::ParseFull,
|
||||||
|
},
|
||||||
|
object::ObjectId,
|
||||||
|
utils::buffd::MsgParser,
|
||||||
|
wire::{wl_display::*, WlDisplayId},
|
||||||
|
},
|
||||||
|
std::rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct TestDisplay {
|
||||||
|
pub transport: Rc<TestTransport>,
|
||||||
|
pub id: WlDisplayId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestDisplay {
|
||||||
|
fn handle_error(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
|
||||||
|
let ev = Error::parse_full(parser)?;
|
||||||
|
let msg = format!("Compositor sent an error: {}", ev.message);
|
||||||
|
self.transport.error(&msg);
|
||||||
|
self.transport.kill();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_delete_id(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
|
||||||
|
let ev = DeleteId::parse_full(parser)?;
|
||||||
|
match self.transport.objects.remove(&ObjectId::from_raw(ev.id)) {
|
||||||
|
None => {
|
||||||
|
let msg = format!(
|
||||||
|
"Compositor sent delete_id for object {} which does not exist",
|
||||||
|
ev.id
|
||||||
|
);
|
||||||
|
self.transport.error(&msg);
|
||||||
|
self.transport.kill();
|
||||||
|
}
|
||||||
|
Some(obj) => {
|
||||||
|
obj.on_remove(&self.transport);
|
||||||
|
self.transport.obj_ids.borrow_mut().release(ev.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test_object! {
|
||||||
|
TestDisplay, WlDisplay;
|
||||||
|
|
||||||
|
ERROR => handle_error,
|
||||||
|
DELETE_ID => handle_delete_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestObject for TestDisplay {}
|
||||||
48
src/it/test_ifs/test_jay_compositor.rs
Normal file
48
src/it/test_ifs/test_jay_compositor.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
client::ClientId,
|
||||||
|
it::{
|
||||||
|
test_error::TestError, test_object::TestObject, test_transport::TestTransport,
|
||||||
|
testrun::ParseFull,
|
||||||
|
},
|
||||||
|
utils::buffd::MsgParser,
|
||||||
|
wire::{
|
||||||
|
jay_compositor::{self, *},
|
||||||
|
JayCompositorId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
std::{cell::Cell, rc::Rc},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct TestJayCompositor {
|
||||||
|
pub id: JayCompositorId,
|
||||||
|
pub transport: Rc<TestTransport>,
|
||||||
|
pub client_id: Cell<Option<ClientId>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestJayCompositor {
|
||||||
|
pub async fn get_client_id(&self) -> Result<ClientId, TestError> {
|
||||||
|
if self.client_id.get().is_none() {
|
||||||
|
self.transport.send(GetClientId { self_id: self.id });
|
||||||
|
}
|
||||||
|
self.transport.sync().await;
|
||||||
|
match self.client_id.get() {
|
||||||
|
Some(c) => Ok(c),
|
||||||
|
_ => bail!("Compositor did not send a client id"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_client_id(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
|
||||||
|
let ev = jay_compositor::ClientId::parse_full(parser)?;
|
||||||
|
self.client_id.set(Some(ClientId::from_raw(ev.client_id)));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test_object! {
|
||||||
|
TestJayCompositor, JayCompositor;
|
||||||
|
|
||||||
|
CLIENT_ID => handle_client_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestObject for TestJayCompositor {}
|
||||||
185
src/it/test_ifs/test_registry.rs
Normal file
185
src/it/test_ifs/test_registry.rs
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
it::{
|
||||||
|
test_error::TestError,
|
||||||
|
test_ifs::{
|
||||||
|
test_compositor::TestCompositor, test_jay_compositor::TestJayCompositor,
|
||||||
|
test_shm::TestShm,
|
||||||
|
},
|
||||||
|
test_object::TestObject,
|
||||||
|
test_transport::TestTransport,
|
||||||
|
testrun::ParseFull,
|
||||||
|
},
|
||||||
|
utils::{buffd::MsgParser, clonecell::CloneCell, copyhashmap::CopyHashMap},
|
||||||
|
wire::{wl_registry::*, WlRegistryId},
|
||||||
|
},
|
||||||
|
std::{cell::Cell, rc::Rc},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct TestGlobal {
|
||||||
|
pub name: u32,
|
||||||
|
pub interface: String,
|
||||||
|
pub version: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TestRegistrySingletons {
|
||||||
|
pub jay_compositor: u32,
|
||||||
|
pub wl_compositor: u32,
|
||||||
|
pub wl_shm: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TestRegistry {
|
||||||
|
pub id: WlRegistryId,
|
||||||
|
pub transport: Rc<TestTransport>,
|
||||||
|
pub globals: CopyHashMap<u32, Rc<TestGlobal>>,
|
||||||
|
pub singletons: CloneCell<Option<Rc<TestRegistrySingletons>>>,
|
||||||
|
pub jay_compositor: CloneCell<Option<Rc<TestJayCompositor>>>,
|
||||||
|
pub compositor: CloneCell<Option<Rc<TestCompositor>>>,
|
||||||
|
pub shm: CloneCell<Option<Rc<TestShm>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! singleton {
|
||||||
|
($field:expr) => {
|
||||||
|
if let Some(s) = $field.get() {
|
||||||
|
return Ok(s);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestRegistry {
|
||||||
|
pub async fn get_singletons(&self) -> Result<Rc<TestRegistrySingletons>, TestError> {
|
||||||
|
singleton!(self.singletons);
|
||||||
|
self.transport.sync().await;
|
||||||
|
singleton!(self.singletons);
|
||||||
|
let mut jay_compositor = 0;
|
||||||
|
let mut wl_compositor = 0;
|
||||||
|
let mut wl_shm = 0;
|
||||||
|
for global in self.globals.lock().values() {
|
||||||
|
match global.interface.as_str() {
|
||||||
|
"jay_compositor" => jay_compositor = global.name,
|
||||||
|
"wl_compositor" => wl_compositor = global.name,
|
||||||
|
"wl_shm" => wl_shm = global.name,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
macro_rules! singleton {
|
||||||
|
($($name:ident,)*) => {
|
||||||
|
TestRegistrySingletons {
|
||||||
|
$(
|
||||||
|
$name: {
|
||||||
|
if $name == 0 {
|
||||||
|
bail!("Compositor did not send {} singleton", stringify!($name));
|
||||||
|
}
|
||||||
|
$name
|
||||||
|
},
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let singletons = Rc::new(singleton! {
|
||||||
|
jay_compositor,
|
||||||
|
wl_compositor,
|
||||||
|
wl_shm,
|
||||||
|
});
|
||||||
|
self.singletons.set(Some(singletons.clone()));
|
||||||
|
Ok(singletons)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_jay_compositor(&self) -> Result<Rc<TestJayCompositor>, TestError> {
|
||||||
|
singleton!(self.jay_compositor);
|
||||||
|
let singletons = self.get_singletons().await?;
|
||||||
|
singleton!(self.jay_compositor);
|
||||||
|
let jc = Rc::new(TestJayCompositor {
|
||||||
|
id: self.transport.id(),
|
||||||
|
transport: self.transport.clone(),
|
||||||
|
client_id: Default::default(),
|
||||||
|
});
|
||||||
|
self.bind(&jc, singletons.jay_compositor, 1)?;
|
||||||
|
self.jay_compositor.set(Some(jc.clone()));
|
||||||
|
Ok(jc)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_compositor(&self) -> Result<Rc<TestCompositor>, TestError> {
|
||||||
|
singleton!(self.compositor);
|
||||||
|
let singletons = self.get_singletons().await?;
|
||||||
|
singleton!(self.compositor);
|
||||||
|
let jc = Rc::new(TestCompositor {
|
||||||
|
id: self.transport.id(),
|
||||||
|
transport: self.transport.clone(),
|
||||||
|
});
|
||||||
|
self.bind(&jc, singletons.wl_compositor, 4)?;
|
||||||
|
self.compositor.set(Some(jc.clone()));
|
||||||
|
Ok(jc)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_shm(&self) -> Result<Rc<TestShm>, TestError> {
|
||||||
|
singleton!(self.shm);
|
||||||
|
let singletons = self.get_singletons().await?;
|
||||||
|
singleton!(self.shm);
|
||||||
|
let jc = Rc::new(TestShm {
|
||||||
|
id: self.transport.id(),
|
||||||
|
transport: self.transport.clone(),
|
||||||
|
formats: Default::default(),
|
||||||
|
formats_awaited: Cell::new(false),
|
||||||
|
});
|
||||||
|
self.bind(&jc, singletons.wl_shm, 1)?;
|
||||||
|
self.shm.set(Some(jc.clone()));
|
||||||
|
Ok(jc)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bind<O: TestObject>(
|
||||||
|
&self,
|
||||||
|
obj: &Rc<O>,
|
||||||
|
name: u32,
|
||||||
|
version: u32,
|
||||||
|
) -> Result<(), TestError> {
|
||||||
|
self.transport.send(Bind {
|
||||||
|
self_id: self.id,
|
||||||
|
name,
|
||||||
|
interface: obj.interface().name(),
|
||||||
|
version,
|
||||||
|
id: obj.id().into(),
|
||||||
|
});
|
||||||
|
self.transport.add_obj(obj.clone())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_global(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
|
||||||
|
let ev = Global::parse_full(parser)?;
|
||||||
|
let prev = self.globals.set(
|
||||||
|
ev.name,
|
||||||
|
Rc::new(TestGlobal {
|
||||||
|
name: ev.name,
|
||||||
|
interface: ev.interface.to_string(),
|
||||||
|
version: ev.version,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
if prev.is_some() {
|
||||||
|
self.transport.error(&format!(
|
||||||
|
"Compositor sent global {} multiple times",
|
||||||
|
ev.name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_global_remove(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
|
||||||
|
let ev = GlobalRemove::parse_full(parser)?;
|
||||||
|
if self.globals.remove(&ev.name).is_none() {
|
||||||
|
self.transport.error(&format!(
|
||||||
|
"Compositor sent global_remove for {} which does not exist",
|
||||||
|
ev.name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test_object! {
|
||||||
|
TestRegistry, WlRegistry;
|
||||||
|
|
||||||
|
GLOBAL => handle_global,
|
||||||
|
GLOBAL_REMOVE => handle_global_remove,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestObject for TestRegistry {}
|
||||||
59
src/it/test_ifs/test_shm.rs
Normal file
59
src/it/test_ifs/test_shm.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
it::{
|
||||||
|
test_error::TestError, test_ifs::test_shm_pool::TestShmPool, test_mem::TestMem,
|
||||||
|
test_object::TestObject, test_transport::TestTransport, testrun::ParseFull,
|
||||||
|
},
|
||||||
|
utils::{buffd::MsgParser, clonecell::CloneCell, copyhashmap::CopyHashMap},
|
||||||
|
wire::{wl_shm::*, WlShmId},
|
||||||
|
},
|
||||||
|
std::{cell::Cell, rc::Rc},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct TestShm {
|
||||||
|
pub id: WlShmId,
|
||||||
|
pub transport: Rc<TestTransport>,
|
||||||
|
pub formats: CopyHashMap<u32, ()>,
|
||||||
|
pub formats_awaited: Cell<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestShm {
|
||||||
|
pub async fn formats(&self) -> &CopyHashMap<u32, ()> {
|
||||||
|
if !self.formats_awaited.replace(true) {
|
||||||
|
self.transport.sync().await;
|
||||||
|
}
|
||||||
|
&self.formats
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_pool(&self, size: usize) -> Result<Rc<TestShmPool>, TestError> {
|
||||||
|
let mem = TestMem::new(size)?;
|
||||||
|
let pool = Rc::new(TestShmPool {
|
||||||
|
id: self.transport.id(),
|
||||||
|
transport: self.transport.clone(),
|
||||||
|
mem: CloneCell::new(mem.clone()),
|
||||||
|
destroyed: Cell::new(false),
|
||||||
|
});
|
||||||
|
self.transport.send(CreatePool {
|
||||||
|
self_id: self.id,
|
||||||
|
id: pool.id,
|
||||||
|
fd: mem.fd.clone(),
|
||||||
|
size: size as _,
|
||||||
|
});
|
||||||
|
self.transport.add_obj(pool.clone())?;
|
||||||
|
Ok(pool)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_format(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
|
||||||
|
let ev = Format::parse_full(parser)?;
|
||||||
|
self.formats.set(ev.format, ());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test_object! {
|
||||||
|
TestShm, WlShm;
|
||||||
|
|
||||||
|
FORMAT => handle_format,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestObject for TestShm {}
|
||||||
61
src/it/test_ifs/test_shm_buffer.rs
Normal file
61
src/it/test_ifs/test_shm_buffer.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
it::{
|
||||||
|
test_error::TestError, test_mem::TestMem, test_object::TestObject,
|
||||||
|
test_transport::TestTransport, testrun::ParseFull,
|
||||||
|
},
|
||||||
|
utils::buffd::MsgParser,
|
||||||
|
wire::{wl_buffer::*, WlBufferId},
|
||||||
|
},
|
||||||
|
std::{
|
||||||
|
cell::Cell,
|
||||||
|
ops::{Deref, Range},
|
||||||
|
rc::Rc,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct TestShmBuffer {
|
||||||
|
pub id: WlBufferId,
|
||||||
|
pub transport: Rc<TestTransport>,
|
||||||
|
pub range: Range<usize>,
|
||||||
|
pub mem: Rc<TestMem>,
|
||||||
|
pub released: Cell<bool>,
|
||||||
|
pub destroyed: Cell<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestShmBuffer {
|
||||||
|
pub fn destroy(&self) {
|
||||||
|
if self.destroyed.replace(true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.transport.send(Destroy { self_id: self.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_release(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
|
||||||
|
let _ev = Release::parse_full(parser)?;
|
||||||
|
self.released.set(true);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for TestShmBuffer {
|
||||||
|
type Target = [Cell<u8>];
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.mem[self.range.clone()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TestShmBuffer {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test_object! {
|
||||||
|
TestShmBuffer, WlBuffer;
|
||||||
|
|
||||||
|
RELEASE => handle_release,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestObject for TestShmBuffer {}
|
||||||
86
src/it/test_ifs/test_shm_pool.rs
Normal file
86
src/it/test_ifs/test_shm_pool.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
format::Format,
|
||||||
|
it::{
|
||||||
|
test_error::TestError, test_ifs::test_shm_buffer::TestShmBuffer, test_mem::TestMem,
|
||||||
|
test_object::TestObject, test_transport::TestTransport,
|
||||||
|
},
|
||||||
|
utils::clonecell::CloneCell,
|
||||||
|
wire::{wl_shm_pool::*, WlShmPoolId},
|
||||||
|
},
|
||||||
|
std::{cell::Cell, rc::Rc},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct TestShmPool {
|
||||||
|
pub id: WlShmPoolId,
|
||||||
|
pub transport: Rc<TestTransport>,
|
||||||
|
pub mem: CloneCell<Rc<TestMem>>,
|
||||||
|
pub destroyed: Cell<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestShmPool {
|
||||||
|
pub fn create_buffer(
|
||||||
|
&self,
|
||||||
|
offset: i32,
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
stride: i32,
|
||||||
|
format: &Format,
|
||||||
|
) -> Result<Rc<TestShmBuffer>, TestError> {
|
||||||
|
let size = (height * stride) as usize;
|
||||||
|
let start = offset as usize;
|
||||||
|
let end = start + size;
|
||||||
|
let mem = self.mem.get();
|
||||||
|
if end > mem.len() {
|
||||||
|
bail!("Out-of-bounds buffer");
|
||||||
|
}
|
||||||
|
let buffer = Rc::new(TestShmBuffer {
|
||||||
|
id: self.transport.id(),
|
||||||
|
transport: self.transport.clone(),
|
||||||
|
range: start..end,
|
||||||
|
mem,
|
||||||
|
released: Cell::new(true),
|
||||||
|
destroyed: Cell::new(false),
|
||||||
|
});
|
||||||
|
self.transport.add_obj(buffer.clone())?;
|
||||||
|
self.transport.send(CreateBuffer {
|
||||||
|
self_id: self.id,
|
||||||
|
id: buffer.id,
|
||||||
|
offset,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
stride,
|
||||||
|
format: format.wl_id.unwrap_or(format.drm),
|
||||||
|
});
|
||||||
|
Ok(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resize(&self, size: usize) -> Result<(), TestError> {
|
||||||
|
let mem = self.mem.get().grow(size)?;
|
||||||
|
self.mem.set(mem);
|
||||||
|
self.transport.send(Resize {
|
||||||
|
self_id: self.id,
|
||||||
|
size: size as _,
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(&self) {
|
||||||
|
if self.destroyed.replace(true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.transport.send(Destroy { self_id: self.id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TestShmPool {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test_object! {
|
||||||
|
TestShmPool, WlShmPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestObject for TestShmPool {}
|
||||||
73
src/it/test_logger.rs
Normal file
73
src/it/test_logger.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
use {
|
||||||
|
crate::utils::clonecell::CloneCell,
|
||||||
|
log::{Level, LevelFilter, Log, Metadata, Record},
|
||||||
|
std::{cell::Cell, fmt::Write as FmtWrite, io::Write, rc::Rc, time::SystemTime},
|
||||||
|
uapi::{Fd, OwnedFd},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[thread_local]
|
||||||
|
static LEVEL: Cell<Level> = Cell::new(Level::Info);
|
||||||
|
|
||||||
|
#[thread_local]
|
||||||
|
static FILE: CloneCell<Option<Rc<OwnedFd>>> = CloneCell::new(None);
|
||||||
|
|
||||||
|
pub fn install() {
|
||||||
|
log::set_logger(&Logger).unwrap();
|
||||||
|
log::set_max_level(LevelFilter::Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_level(level: Level) {
|
||||||
|
LEVEL.set(level);
|
||||||
|
log::set_max_level(level.to_level_filter());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_file(file: Rc<OwnedFd>) {
|
||||||
|
FILE.set(Some(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unset_file() {
|
||||||
|
FILE.set(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Logger;
|
||||||
|
|
||||||
|
impl Log for Logger {
|
||||||
|
fn enabled(&self, metadata: &Metadata) -> bool {
|
||||||
|
metadata.level() <= LEVEL.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log(&self, record: &Record) {
|
||||||
|
if record.level() > LEVEL.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut buf = String::new();
|
||||||
|
let now = SystemTime::now();
|
||||||
|
let _ = if let Some(mp) = record.module_path() {
|
||||||
|
writeln!(
|
||||||
|
buf,
|
||||||
|
"[{} {:5} {}] {}",
|
||||||
|
humantime::format_rfc3339_millis(now),
|
||||||
|
record.level(),
|
||||||
|
mp,
|
||||||
|
record.args(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
writeln!(
|
||||||
|
buf,
|
||||||
|
"[{} {:5}] {}",
|
||||||
|
humantime::format_rfc3339_millis(now),
|
||||||
|
record.level(),
|
||||||
|
record.args(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let mut fd = match FILE.get() {
|
||||||
|
Some(f) => f.borrow(),
|
||||||
|
_ => Fd::new(2),
|
||||||
|
};
|
||||||
|
let _ = fd.write_all(buf.as_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&self) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
71
src/it/test_mem.rs
Normal file
71
src/it/test_mem.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
it::test_error::TestError,
|
||||||
|
utils::{oserror::OsError, ptr_ext::PtrExt},
|
||||||
|
},
|
||||||
|
std::{cell::Cell, ops::Deref, ptr, rc::Rc},
|
||||||
|
uapi::{c, OwnedFd},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct TestMem {
|
||||||
|
pub fd: Rc<OwnedFd>,
|
||||||
|
slice: *const [Cell<u8>],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestMem {
|
||||||
|
pub fn new(size: usize) -> Result<Rc<Self>, TestError> {
|
||||||
|
let fd = uapi::memfd_create("test_pool", c::MFD_CLOEXEC | c::MFD_ALLOW_SEALING)?;
|
||||||
|
uapi::fcntl_add_seals(fd.raw(), c::F_SEAL_SHRINK)?;
|
||||||
|
uapi::ftruncate(fd.raw(), size as _)?;
|
||||||
|
let slice = map(fd.raw(), size)?;
|
||||||
|
Ok(Rc::new(Self {
|
||||||
|
fd: Rc::new(fd),
|
||||||
|
slice,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn grow(&self, size: usize) -> Result<Rc<Self>, TestError> {
|
||||||
|
let cur_len = uapi::fstat(self.fd.raw())?;
|
||||||
|
if size > cur_len.st_size as _ {
|
||||||
|
uapi::ftruncate(self.fd.raw(), size as _)?;
|
||||||
|
}
|
||||||
|
let slice = map(self.fd.raw(), size)?;
|
||||||
|
Ok(Rc::new(Self {
|
||||||
|
fd: self.fd.clone(),
|
||||||
|
slice,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for TestMem {
|
||||||
|
type Target = [Cell<u8>];
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
unsafe { &*self.slice }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map(fd: c::c_int, size: usize) -> Result<*const [Cell<u8>], TestError> {
|
||||||
|
unsafe {
|
||||||
|
let res = c::mmap(
|
||||||
|
ptr::null_mut(),
|
||||||
|
size as _,
|
||||||
|
c::PROT_READ | c::PROT_WRITE,
|
||||||
|
c::MAP_SHARED,
|
||||||
|
fd,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
if res == c::MAP_FAILED {
|
||||||
|
bail!("Could not map memory: {}", OsError::default());
|
||||||
|
}
|
||||||
|
Ok(std::slice::from_raw_parts(res as _, size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TestMem {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
c::munmap(self.slice.deref().as_ptr() as _, self.slice.deref().len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
54
src/it/test_object.rs
Normal file
54
src/it/test_object.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
it::{test_error::TestError, test_transport::TestTransport},
|
||||||
|
object::{Interface, ObjectId},
|
||||||
|
utils::buffd::MsgParser,
|
||||||
|
},
|
||||||
|
std::rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
|
macro_rules! test_object {
|
||||||
|
($oname:ident, $ifname:ident; $($code:ident => $f:ident,)*) => {
|
||||||
|
impl crate::it::test_object::TestObjectBase for $oname {
|
||||||
|
fn id(&self) -> crate::object::ObjectId {
|
||||||
|
self.id.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused_variables, unreachable_code)]
|
||||||
|
fn handle_request(
|
||||||
|
self: std::rc::Rc<Self>,
|
||||||
|
request: u32,
|
||||||
|
parser: crate::utils::buffd::MsgParser<'_, '_>,
|
||||||
|
) -> Result<(), crate::it::test_error::TestError> {
|
||||||
|
use crate::it::test_error::TestErrorExt;
|
||||||
|
let res: Result<(), crate::it::test_error::TestError> = match request {
|
||||||
|
$(
|
||||||
|
$code => $oname::$f(&self, parser).with_context(|| format!("While handling a `{}` event", stringify!($f))),
|
||||||
|
)*
|
||||||
|
_ => Err(crate::it::test_error::TestError::new("Unknown event {}")),
|
||||||
|
};
|
||||||
|
res.with_context(|| format!("In object {} of type `{}`", self.id(), self.interface().name()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interface(&self) -> crate::object::Interface {
|
||||||
|
crate::wire::$ifname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait TestObjectBase: 'static {
|
||||||
|
fn id(&self) -> ObjectId;
|
||||||
|
fn handle_request(
|
||||||
|
self: Rc<Self>,
|
||||||
|
request: u32,
|
||||||
|
parser: MsgParser<'_, '_>,
|
||||||
|
) -> Result<(), TestError>;
|
||||||
|
fn interface(&self) -> Interface;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait TestObject: TestObjectBase {
|
||||||
|
fn on_remove(&self, transport: &TestTransport) {
|
||||||
|
let _ = transport;
|
||||||
|
}
|
||||||
|
}
|
||||||
254
src/it/test_transport.rs
Normal file
254
src/it/test_transport.rs
Normal file
|
|
@ -0,0 +1,254 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
async_engine::{AsyncFd, SpawnedFuture},
|
||||||
|
client::{ClientId, EventFormatter},
|
||||||
|
it::{
|
||||||
|
test_error::{StdError, TestError},
|
||||||
|
test_ifs::{test_callback::TestCallback, test_registry::TestRegistry},
|
||||||
|
test_object::TestObject,
|
||||||
|
testrun::TestRun,
|
||||||
|
},
|
||||||
|
object::{ObjectId, WL_DISPLAY_ID},
|
||||||
|
utils::{
|
||||||
|
asyncevent::AsyncEvent,
|
||||||
|
bitfield::Bitfield,
|
||||||
|
buffd::{BufFdIn, BufFdOut, MsgFormatter, MsgParser, OutBuffer, OutBufferSwapchain},
|
||||||
|
copyhashmap::CopyHashMap,
|
||||||
|
stack::Stack,
|
||||||
|
vec_ext::VecExt,
|
||||||
|
},
|
||||||
|
wire::wl_display,
|
||||||
|
},
|
||||||
|
std::{
|
||||||
|
cell::{Cell, RefCell},
|
||||||
|
collections::VecDeque,
|
||||||
|
future::Future,
|
||||||
|
mem,
|
||||||
|
rc::Rc,
|
||||||
|
task::Poll,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct TestTransport {
|
||||||
|
pub run: Rc<TestRun>,
|
||||||
|
pub fd: AsyncFd,
|
||||||
|
pub client_id: Cell<ClientId>,
|
||||||
|
pub bufs: Stack<Vec<u32>>,
|
||||||
|
pub swapchain: Rc<RefCell<OutBufferSwapchain>>,
|
||||||
|
pub flush_request: AsyncEvent,
|
||||||
|
pub incoming: Cell<Option<SpawnedFuture<()>>>,
|
||||||
|
pub outgoing: Cell<Option<SpawnedFuture<()>>>,
|
||||||
|
pub objects: CopyHashMap<ObjectId, Rc<dyn TestObject>>,
|
||||||
|
pub obj_ids: RefCell<Bitfield>,
|
||||||
|
pub killed: Cell<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestTransport {
|
||||||
|
pub fn get_registry(self: &Rc<Self>) -> Rc<TestRegistry> {
|
||||||
|
let reg = Rc::new(TestRegistry {
|
||||||
|
id: self.id(),
|
||||||
|
transport: self.clone(),
|
||||||
|
globals: Default::default(),
|
||||||
|
singletons: Default::default(),
|
||||||
|
jay_compositor: Default::default(),
|
||||||
|
compositor: Default::default(),
|
||||||
|
shm: Default::default(),
|
||||||
|
});
|
||||||
|
self.send(wl_display::GetRegistry {
|
||||||
|
self_id: WL_DISPLAY_ID,
|
||||||
|
registry: reg.id,
|
||||||
|
});
|
||||||
|
let _ = self.add_obj(reg.clone());
|
||||||
|
reg
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_obj(&self, obj: Rc<dyn TestObject>) -> Result<(), TestError> {
|
||||||
|
if self.killed.get() {
|
||||||
|
bail!("Transport has already been killed");
|
||||||
|
}
|
||||||
|
let id = obj.id();
|
||||||
|
if self.objects.set(id, obj).is_some() {
|
||||||
|
bail!("There already is an object with id {}", id);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kill(&self) {
|
||||||
|
self.outgoing.take();
|
||||||
|
self.incoming.take();
|
||||||
|
for (_, object) in self.objects.lock().drain() {
|
||||||
|
object.on_remove(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sync(self: &Rc<Self>) -> impl Future<Output = ()> {
|
||||||
|
let cb = Rc::new(TestCallback {
|
||||||
|
id: self.id(),
|
||||||
|
transport: self.clone(),
|
||||||
|
handler: Cell::new(None),
|
||||||
|
done: Cell::new(self.killed.get()),
|
||||||
|
});
|
||||||
|
self.send(wl_display::Sync {
|
||||||
|
self_id: WL_DISPLAY_ID,
|
||||||
|
callback: cb.id,
|
||||||
|
});
|
||||||
|
let _ = self.add_obj(cb.clone());
|
||||||
|
futures_util::future::poll_fn(move |ctx| {
|
||||||
|
if cb.done.get() {
|
||||||
|
Poll::Ready(())
|
||||||
|
} else {
|
||||||
|
let waker = ctx.waker().clone();
|
||||||
|
cb.handler.set(Some(Box::new(move || waker.wake())));
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id<T: From<ObjectId>>(&self) -> T {
|
||||||
|
ObjectId::from_raw(self.obj_ids.borrow_mut().acquire()).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn error(&self, msg: &str) {
|
||||||
|
let msg = format!("In client {}: {}", self.client_id.get(), msg);
|
||||||
|
self.run.errors.push(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(self: &Rc<Self>) {
|
||||||
|
self.incoming.set(Some(
|
||||||
|
self.run.state.eng.spawn(
|
||||||
|
Incoming {
|
||||||
|
tc: self.clone(),
|
||||||
|
buf: BufFdIn::new(self.fd.clone()),
|
||||||
|
}
|
||||||
|
.run(),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
self.outgoing.set(Some(
|
||||||
|
self.run.state.eng.spawn(
|
||||||
|
Outgoing {
|
||||||
|
tc: self.clone(),
|
||||||
|
buf: BufFdOut::new(self.fd.clone()),
|
||||||
|
buffers: Default::default(),
|
||||||
|
}
|
||||||
|
.run(),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send<M: EventFormatter>(&self, msg: M) {
|
||||||
|
if self.killed.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut fds = vec![];
|
||||||
|
let mut swapchain = self.swapchain.borrow_mut();
|
||||||
|
let mut fmt = MsgFormatter::new(&mut swapchain.cur, &mut fds);
|
||||||
|
msg.format(&mut fmt);
|
||||||
|
fmt.write_len();
|
||||||
|
if swapchain.cur.is_full() {
|
||||||
|
swapchain.commit();
|
||||||
|
}
|
||||||
|
self.flush_request.trigger();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Outgoing {
|
||||||
|
tc: Rc<TestTransport>,
|
||||||
|
buf: BufFdOut,
|
||||||
|
buffers: VecDeque<OutBuffer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Outgoing {
|
||||||
|
async fn run(mut self: Self) {
|
||||||
|
loop {
|
||||||
|
self.tc.flush_request.triggered().await;
|
||||||
|
if let Err(e) = self.flush().await {
|
||||||
|
let msg = format!(
|
||||||
|
"Could not process an outgoing message for client {}: {}",
|
||||||
|
self.tc.client_id.get(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
log::error!("{}", msg);
|
||||||
|
self.tc.run.errors.push(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn flush(&mut self) -> Result<(), TestError> {
|
||||||
|
{
|
||||||
|
let mut swapchain = self.tc.swapchain.borrow_mut();
|
||||||
|
swapchain.commit();
|
||||||
|
mem::swap(&mut swapchain.pending, &mut self.buffers);
|
||||||
|
}
|
||||||
|
while let Some(mut cur) = self.buffers.pop_front() {
|
||||||
|
if let Err(e) = self.buf.flush_no_timeout(&mut cur).await {
|
||||||
|
return Err(e.with_context("Could not write to wayland socket"));
|
||||||
|
}
|
||||||
|
self.tc.swapchain.borrow_mut().free.push(cur);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Incoming {
|
||||||
|
tc: Rc<TestTransport>,
|
||||||
|
buf: BufFdIn,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Incoming {
|
||||||
|
async fn run(mut self: Self) {
|
||||||
|
loop {
|
||||||
|
if let Err(e) = self.handle_msg().await {
|
||||||
|
let msg = format!(
|
||||||
|
"Could not process an incoming message for client {}: {}",
|
||||||
|
self.tc.client_id.get(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
log::error!("{}", msg);
|
||||||
|
self.tc.run.errors.push(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.tc.kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_msg(&mut self) -> Result<(), TestError> {
|
||||||
|
let mut hdr = [0u32, 0];
|
||||||
|
if let Err(e) = self.buf.read_full(&mut hdr[..]).await {
|
||||||
|
return Err(e.with_context("Could not read from wayland socket"));
|
||||||
|
}
|
||||||
|
let obj_id = ObjectId::from_raw(hdr[0]);
|
||||||
|
let len = (hdr[1] >> 16) as usize;
|
||||||
|
let request = hdr[1] & 0xffff;
|
||||||
|
if len < 8 {
|
||||||
|
bail!("Message size is < 8");
|
||||||
|
}
|
||||||
|
if len % 4 != 0 {
|
||||||
|
bail!("Message size is not a multiple of 4");
|
||||||
|
}
|
||||||
|
let len = len / 4 - 2;
|
||||||
|
let mut data_buf = self.tc.bufs.pop().unwrap_or_default();
|
||||||
|
data_buf.clear();
|
||||||
|
data_buf.reserve(len);
|
||||||
|
let unused = data_buf.split_at_spare_mut_ext().1;
|
||||||
|
if let Err(e) = self.buf.read_full(&mut unused[..len]).await {
|
||||||
|
return Err(e.with_context("Could not read from wayland socket"));
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
data_buf.set_len(len);
|
||||||
|
}
|
||||||
|
let object = match self.tc.objects.get(&obj_id) {
|
||||||
|
Some(obj) => obj,
|
||||||
|
_ => bail!(
|
||||||
|
"Compositor sent a message for object {} which does not exist",
|
||||||
|
obj_id
|
||||||
|
),
|
||||||
|
};
|
||||||
|
let parser = MsgParser::new(&mut self.buf, &data_buf);
|
||||||
|
object.handle_request(request, parser)?;
|
||||||
|
if data_buf.capacity() > 0 {
|
||||||
|
self.tc.bufs.push(data_buf);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
100
src/it/testrun.rs
Normal file
100
src/it/testrun.rs
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
client::{ClientId, RequestParser},
|
||||||
|
it::{
|
||||||
|
test_backend::TestBackend,
|
||||||
|
test_client::TestClient,
|
||||||
|
test_error::{TestError, TestErrorExt},
|
||||||
|
test_ifs::test_display::TestDisplay,
|
||||||
|
test_transport::TestTransport,
|
||||||
|
},
|
||||||
|
object::WL_DISPLAY_ID,
|
||||||
|
state::State,
|
||||||
|
utils::{bitfield::Bitfield, buffd::MsgParser, oserror::OsErrorExt, stack::Stack},
|
||||||
|
},
|
||||||
|
std::{
|
||||||
|
cell::{Cell, RefCell},
|
||||||
|
rc::Rc,
|
||||||
|
},
|
||||||
|
uapi::c,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct TestRun {
|
||||||
|
pub state: Rc<State>,
|
||||||
|
pub backend: Rc<TestBackend>,
|
||||||
|
pub errors: Stack<String>,
|
||||||
|
pub server_addr: c::sockaddr_un,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestRun {
|
||||||
|
pub async fn create_client(self: &Rc<Self>) -> Result<Rc<TestClient>, TestError> {
|
||||||
|
self.create_client2()
|
||||||
|
.await
|
||||||
|
.with_context(|| "Could not create a client")
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_client2(self: &Rc<Self>) -> Result<Rc<TestClient>, TestError> {
|
||||||
|
let socket = uapi::socket(
|
||||||
|
c::AF_UNIX,
|
||||||
|
c::SOCK_STREAM | c::SOCK_CLOEXEC | c::SOCK_NONBLOCK,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.to_os_error()
|
||||||
|
.with_context(|| "Could not create a unix socket")?;
|
||||||
|
let socket = Rc::new(socket);
|
||||||
|
uapi::connect(socket.raw(), &self.server_addr)
|
||||||
|
.to_os_error()
|
||||||
|
.with_context(|| "Could not connect to the compositor")?;
|
||||||
|
let fd = self
|
||||||
|
.state
|
||||||
|
.eng
|
||||||
|
.fd(&socket)
|
||||||
|
.with_context(|| "Could not create an async fd")?;
|
||||||
|
let mut obj_ids = Bitfield::default();
|
||||||
|
obj_ids.take(0);
|
||||||
|
obj_ids.take(1);
|
||||||
|
let transport = Rc::new(TestTransport {
|
||||||
|
run: self.clone(),
|
||||||
|
fd,
|
||||||
|
client_id: Cell::new(ClientId::from_raw(0)),
|
||||||
|
bufs: Default::default(),
|
||||||
|
swapchain: Default::default(),
|
||||||
|
flush_request: Default::default(),
|
||||||
|
incoming: Default::default(),
|
||||||
|
outgoing: Default::default(),
|
||||||
|
objects: Default::default(),
|
||||||
|
obj_ids: RefCell::new(obj_ids),
|
||||||
|
killed: Cell::new(false),
|
||||||
|
});
|
||||||
|
transport.add_obj(Rc::new(TestDisplay {
|
||||||
|
transport: transport.clone(),
|
||||||
|
id: WL_DISPLAY_ID,
|
||||||
|
}))?;
|
||||||
|
transport.init();
|
||||||
|
let registry = transport.get_registry();
|
||||||
|
let jc = registry.get_jay_compositor().await?;
|
||||||
|
let client_id = jc.get_client_id().await?;
|
||||||
|
let client = self.state.clients.get(client_id)?;
|
||||||
|
Ok(Rc::new(TestClient {
|
||||||
|
run: self.clone(),
|
||||||
|
server: client,
|
||||||
|
transport,
|
||||||
|
jc,
|
||||||
|
comp: registry.get_compositor().await?,
|
||||||
|
shm: registry.get_shm().await?,
|
||||||
|
registry,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ParseFull<'a>: Sized {
|
||||||
|
fn parse_full(parser: MsgParser<'_, 'a>) -> Result<Self, TestError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: RequestParser<'a>> ParseFull<'a> for T {
|
||||||
|
fn parse_full(mut parser: MsgParser<'_, 'a>) -> Result<Self, TestError> {
|
||||||
|
let res = T::parse(&mut parser)?;
|
||||||
|
parser.eof()?;
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/it/tests.rs
Normal file
47
src/it/tests.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
use {
|
||||||
|
crate::it::{test_error::TestError, testrun::TestRun},
|
||||||
|
std::{future::Future, rc::Rc},
|
||||||
|
};
|
||||||
|
|
||||||
|
macro_rules! testcase {
|
||||||
|
() => {
|
||||||
|
pub struct Test;
|
||||||
|
|
||||||
|
impl crate::it::tests::TestCase for Test {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
module_path!().strip_prefix("jay::it::tests::").unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
testrun: std::rc::Rc<crate::it::testrun::TestRun>,
|
||||||
|
) -> Box<dyn std::future::Future<Output = Result<(), TestError>>> {
|
||||||
|
Box::new(test(testrun))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! tassert {
|
||||||
|
($cond:expr) => {
|
||||||
|
if !$cond {
|
||||||
|
bail!(
|
||||||
|
"Assert `{}` failed ({}:{})",
|
||||||
|
stringify!($cond),
|
||||||
|
file!(),
|
||||||
|
line!()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mod t0001_shm_formats;
|
||||||
|
|
||||||
|
pub trait TestCase {
|
||||||
|
fn name(&self) -> &'static str;
|
||||||
|
fn run(&self, testrun: Rc<TestRun>) -> Box<dyn Future<Output = Result<(), TestError>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tests() -> Vec<&'static dyn TestCase> {
|
||||||
|
vec![&t0001_shm_formats::Test]
|
||||||
|
}
|
||||||
18
src/it/tests/t0001_shm_formats.rs
Normal file
18
src/it/tests/t0001_shm_formats.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
format::{ARGB8888, XRGB8888},
|
||||||
|
it::{test_error::TestError, testrun::TestRun},
|
||||||
|
},
|
||||||
|
std::rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
|
testcase!();
|
||||||
|
|
||||||
|
/// Test that wl_shm supports the required formats
|
||||||
|
async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
|
||||||
|
let client = run.create_client().await?;
|
||||||
|
let formats = client.shm.formats().await;
|
||||||
|
tassert!(formats.contains(&XRGB8888.wl_id.unwrap()));
|
||||||
|
tassert!(formats.contains(&ARGB8888.wl_id.unwrap()));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -52,6 +52,7 @@ mod forker;
|
||||||
mod format;
|
mod format;
|
||||||
mod globals;
|
mod globals;
|
||||||
mod ifs;
|
mod ifs;
|
||||||
|
mod it;
|
||||||
mod libinput;
|
mod libinput;
|
||||||
mod logger;
|
mod logger;
|
||||||
mod logind;
|
mod logind;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
|
acceptor::Acceptor,
|
||||||
async_engine::{AsyncEngine, SpawnedFuture},
|
async_engine::{AsyncEngine, SpawnedFuture},
|
||||||
backend::{
|
backend::{
|
||||||
Backend, BackendEvent, Connector, ConnectorId, ConnectorIds, InputDevice,
|
Backend, BackendEvent, Connector, ConnectorId, ConnectorIds, InputDevice,
|
||||||
|
|
@ -83,14 +84,14 @@ pub struct State {
|
||||||
pub pending_float_titles: AsyncQueue<Rc<FloatNode>>,
|
pub pending_float_titles: AsyncQueue<Rc<FloatNode>>,
|
||||||
pub dbus: Dbus,
|
pub dbus: Dbus,
|
||||||
pub fdcloser: Arc<FdCloser>,
|
pub fdcloser: Arc<FdCloser>,
|
||||||
pub logger: Arc<Logger>,
|
pub logger: Option<Arc<Logger>>,
|
||||||
pub connectors: CopyHashMap<ConnectorId, Rc<ConnectorData>>,
|
pub connectors: CopyHashMap<ConnectorId, Rc<ConnectorData>>,
|
||||||
pub outputs: CopyHashMap<ConnectorId, Rc<OutputData>>,
|
pub outputs: CopyHashMap<ConnectorId, Rc<OutputData>>,
|
||||||
pub status: CloneCell<Rc<String>>,
|
pub status: CloneCell<Rc<String>>,
|
||||||
pub idle: IdleState,
|
pub idle: IdleState,
|
||||||
pub run_args: RunArgs,
|
pub run_args: RunArgs,
|
||||||
pub xwayland: XWaylandState,
|
pub xwayland: XWaylandState,
|
||||||
pub socket_path: CloneCell<Rc<String>>,
|
pub acceptor: CloneCell<Option<Rc<Acceptor>>>,
|
||||||
pub serial: NumCell<Wrapping<u32>>,
|
pub serial: NumCell<Wrapping<u32>>,
|
||||||
pub run_toplevel: Rc<RunToplevel>,
|
pub run_toplevel: Rc<RunToplevel>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ impl Bitfield {
|
||||||
let idx = val as usize / SEG_SIZE;
|
let idx = val as usize / SEG_SIZE;
|
||||||
let pos = val as usize % SEG_SIZE;
|
let pos = val as usize % SEG_SIZE;
|
||||||
while self.vals.len() <= idx {
|
while self.vals.len() <= idx {
|
||||||
self.vals.push(0);
|
self.vals.push(!0);
|
||||||
}
|
}
|
||||||
self.vals[idx] &= !(1 << pos);
|
self.vals[idx] &= !(1 << pos);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct CloneCell<T: UnsafeCellCloneSafe> {
|
pub struct CloneCell<T> {
|
||||||
data: UnsafeCell<T>,
|
data: UnsafeCell<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,19 +26,22 @@ impl<T: UnsafeCellCloneSafe> Clone for CloneCell<T> {
|
||||||
|
|
||||||
impl<T: UnsafeCellCloneSafe + Debug> Debug for CloneCell<T> {
|
impl<T: UnsafeCellCloneSafe + Debug> Debug for CloneCell<T> {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
unsafe { self.data.get().deref().fmt(f) }
|
self.get().fmt(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: UnsafeCellCloneSafe> CloneCell<T> {
|
impl<T> CloneCell<T> {
|
||||||
pub fn new(t: T) -> Self {
|
pub const fn new(t: T) -> Self {
|
||||||
Self {
|
Self {
|
||||||
data: UnsafeCell::new(t),
|
data: UnsafeCell::new(t),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn get(&self) -> T {
|
pub fn get(&self) -> T
|
||||||
|
where
|
||||||
|
T: UnsafeCellCloneSafe,
|
||||||
|
{
|
||||||
unsafe { self.data.get().deref().clone() }
|
unsafe { self.data.get().deref().clone() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,7 +55,7 @@ impl<T: UnsafeCellCloneSafe> CloneCell<T> {
|
||||||
where
|
where
|
||||||
T: Default,
|
T: Default,
|
||||||
{
|
{
|
||||||
unsafe { mem::take(self.data.get().deref_mut()) }
|
self.set(T::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,8 @@ impl<K: Eq + Hash, V> CopyHashMap<K, V> {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set(&self, k: K, v: V) {
|
pub fn set(&self, k: K, v: V) -> Option<V> {
|
||||||
unsafe {
|
unsafe { self.map.get().deref_mut().insert(k, v) }
|
||||||
self.map.get().deref_mut().insert(k, v);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<V>
|
pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<V>
|
||||||
|
|
|
||||||
|
|
@ -204,3 +204,17 @@ impl Display for OsError {
|
||||||
write!(f, "{} (os error {})", msg, self.0)
|
write!(f, "{} (os error {})", msg, self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait OsErrorExt {
|
||||||
|
type Container;
|
||||||
|
|
||||||
|
fn to_os_error(self) -> Self::Container;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> OsErrorExt for Result<T, Errno> {
|
||||||
|
type Container = Result<T, OsError>;
|
||||||
|
|
||||||
|
fn to_os_error(self) -> Self::Container {
|
||||||
|
self.map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ pub async fn manage(state: Rc<State>) {
|
||||||
forker.setenv(DISPLAY.as_bytes(), display.as_bytes());
|
forker.setenv(DISPLAY.as_bytes(), display.as_bytes());
|
||||||
log::info!("Allocated display :{} for Xwayland", xsocket.id);
|
log::info!("Allocated display :{} for Xwayland", xsocket.id);
|
||||||
log::info!("Waiting for connection attempt");
|
log::info!("Waiting for connection attempt");
|
||||||
if state.backend.get().is_freestanding() {
|
if state.backend.get().import_environment() {
|
||||||
import_environment(&state, DISPLAY, &display);
|
import_environment(&state, DISPLAY, &display);
|
||||||
}
|
}
|
||||||
let res = XWaylandError::tria(async {
|
let res = XWaylandError::tria(async {
|
||||||
|
|
|
||||||
|
|
@ -22,3 +22,13 @@ msg take_screenshot = 4 {
|
||||||
msg get_idle = 5 {
|
msg get_idle = 5 {
|
||||||
id: id(jay_idle),
|
id: id(jay_idle),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
msg get_client_id = 6 {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# events
|
||||||
|
|
||||||
|
msg client_id = 0 {
|
||||||
|
client_id: pod(u64),
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue