From 2ced50f3a7ade8b6ce46e992f66a2758d742327d Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 3 Jun 2022 11:49:23 +0200 Subject: [PATCH] cli: add seat-test --- src/cli.rs | 15 +- src/cli/seat_test.rs | 235 ++++++++++++++++++++++++++++++ src/compositor.rs | 1 + src/ifs.rs | 1 + src/ifs/jay_compositor.rs | 38 ++++- src/ifs/jay_seat_events.rs | 152 +++++++++++++++++++ src/ifs/wl_seat/event_handling.rs | 34 ++++- src/ifs/wl_seat/pointer_owner.rs | 3 + src/state.rs | 12 +- wire/jay_compositor.txt | 13 ++ wire/jay_seat_events.txt | 62 ++++++++ 11 files changed, 559 insertions(+), 7 deletions(-) create mode 100644 src/cli/seat_test.rs create mode 100644 src/ifs/jay_seat_events.rs create mode 100644 wire/jay_seat_events.txt diff --git a/src/cli.rs b/src/cli.rs index 01479ec1..677c7434 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -4,6 +4,7 @@ mod log; mod quit; mod run_privileged; pub mod screenshot; +mod seat_test; mod set_log_level; mod unlock; @@ -48,8 +49,10 @@ pub enum Cmd { Screenshot(ScreenshotArgs), /// Inspect/modify the idle (screensaver) settings. Idle(IdleArgs), - /// Run a privileged program + /// Run a privileged program. RunPrivileged(RunPrivilegedArgs), + /// Tests the events produced by a seat. + SeatTest(SeatTestArgs), #[cfg(feature = "it")] RunTests, } @@ -149,6 +152,15 @@ pub struct SetLogArgs { level: CliLogLevel, } +#[derive(Args, Debug)] +pub struct SeatTestArgs { + /// Test all seats. + #[clap(long, short = 'a')] + all: bool, + /// The seat to test. + seat: Option, +} + #[derive(ArgEnum, Debug, Copy, Clone, Hash, Eq, PartialEq)] pub enum CliBackend { X11, @@ -201,6 +213,7 @@ pub fn main() { Cmd::Idle(a) => idle::main(cli.global, a), Cmd::Unlock => unlock::main(cli.global), Cmd::RunPrivileged(a) => run_privileged::main(cli.global, a), + Cmd::SeatTest(a) => seat_test::main(cli.global, a), #[cfg(feature = "it")] Cmd::RunTests => crate::it::run_tests(), } diff --git a/src/cli/seat_test.rs b/src/cli/seat_test.rs new file mode 100644 index 00000000..6e3d2715 --- /dev/null +++ b/src/cli/seat_test.rs @@ -0,0 +1,235 @@ +use { + crate::{ + cli::{GlobalArgs, SeatTestArgs}, + ifs::wl_seat::wl_pointer::{PendingScroll, CONTINUOUS, FINGER, WHEEL}, + tools::tool_client::{Handle, ToolClient}, + wire::{ + jay_compositor::{GetSeats, Seat, SeatEvents}, + jay_seat_events::{ + Axis120, AxisFrame, AxisPx, AxisSource, AxisStop, Button, Key, Modifiers, + PointerAbs, PointerRel, + }, + }, + }, + ahash::AHashMap, + std::{cell::RefCell, future::pending, ops::Deref, rc::Rc}, +}; + +pub fn main(global: GlobalArgs, args: SeatTestArgs) { + let tc = ToolClient::new(global.log_level.into()); + let screenshot = Rc::new(SeatTest { + tc: tc.clone(), + args, + names: Default::default(), + }); + tc.run(run(screenshot)); +} + +struct SeatTest { + tc: Rc, + args: SeatTestArgs, + names: RefCell>>, +} + +impl SeatTest { + fn name(&self, seat: u32) -> Rc { + match self.names.borrow_mut().get(&seat) { + Some(n) => n.clone(), + _ => Rc::new("unknown".to_string()), + } + } +} + +async fn run(seat_test: Rc) { + let tc = &seat_test.tc; + let comp = tc.jay_compositor().await; + tc.send(GetSeats { self_id: comp }); + Seat::handle(&tc, comp, seat_test.clone(), |st, seat| { + st.names + .borrow_mut() + .insert(seat.id, Rc::new(seat.name.to_string())); + }); + tc.round_trip().await; + let all = seat_test.args.all; + let mut seat = 0; + if !all { + seat = choose_seat(&seat_test); + } + let se = tc.id(); + tc.send(SeatEvents { + self_id: comp, + id: se, + }); + let st = seat_test.clone(); + Key::handle(tc, se, (), move |_, ev| { + if all || ev.seat == seat { + if all { + print!("Seat: {}, ", st.name(ev.seat)); + } + println!( + "Time: {:.4}, Key: {}, State: {}", + time(ev.time_usec), + ev.key, + ev.state + ); + } + }); + let st = seat_test.clone(); + Modifiers::handle(tc, se, (), move |_, ev| { + if all || ev.seat == seat { + if all { + print!("Seat: {}, ", st.name(ev.seat)); + } + println!("Modifiers: {:08b}, Group: {}", ev.modifiers, ev.group); + } + }); + let st = seat_test.clone(); + PointerAbs::handle(tc, se, (), move |_, ev| { + if all || ev.seat == seat { + if all { + print!("Seat: {}, ", st.name(ev.seat)); + } + println!( + "Time: {:.4}, Pointer: {}x{}", + time(ev.time_usec), + ev.x, + ev.y + ); + } + }); + let st = seat_test.clone(); + PointerRel::handle(tc, se, (), move |_, ev| { + if all || ev.seat == seat { + if all { + print!("Seat: {}, ", st.name(ev.seat)); + } + println!( + "Time: {:.4}, Pointer: {:+.4}x{:+.4}, Rel: {:+.4}x{:+.4}, Unaccelerated: {:+.4}x{:+.4}", + time(ev.time_usec), + ev.x, + ev.y, + ev.dx, + ev.dy, + ev.dx_unaccelerated, + ev.dy_unaccelerated + ); + } + }); + let st = seat_test.clone(); + Button::handle(tc, se, (), move |_, ev| { + if all || ev.seat == seat { + if all { + print!("Seat: {:.4}, ", st.name(ev.seat)); + } + println!( + "Time: {}, Button: {}, State: {}", + time(ev.time_usec), + ev.button, + ev.state + ); + } + }); + let ps = Rc::new(PendingScroll::default()); + AxisSource::handle(tc, se, ps.clone(), move |ps, ev| { + ps.source.set(Some(ev.source)); + }); + AxisPx::handle(tc, se, ps.clone(), move |ps, ev| { + ps.px[ev.axis as usize].set(Some(ev.dist)); + }); + AxisStop::handle(tc, se, ps.clone(), move |ps, ev| { + ps.stop[ev.axis as usize].set(true); + }); + Axis120::handle(tc, se, ps.clone(), move |ps, ev| { + ps.v120[ev.axis as usize].set(Some(ev.dist)); + }); + let st = seat_test.clone(); + AxisFrame::handle(tc, se, ps.clone(), move |ps, ev| { + let source = ps.source.take(); + let px_x = ps.px[0].take(); + let px_y = ps.px[1].take(); + let stop_x = ps.stop[0].take(); + let stop_y = ps.stop[1].take(); + let v120_x = ps.v120[0].take(); + let v120_y = ps.v120[1].take(); + if all || ev.seat == seat { + if all { + print!("Seat: {}, ", st.name(ev.seat)); + } + let mut need_comma = false; + macro_rules! comma { + () => { + if std::mem::take(&mut need_comma) { + print!(", "); + } + }; + } + print!("Time: {:.4}, ", time(ev.time_usec)); + if let Some(source) = source { + let source = match source { + WHEEL => "wheel", + FINGER => "finger", + CONTINUOUS => "continuous", + _ => "unknown", + }; + print!("Source: {}", source); + need_comma = true; + } + for (axis, px, steps, stop) in [ + ("horizontal", px_x, v120_x, stop_x), + ("vertical", px_y, v120_y, stop_y), + ] { + if px.is_some() || steps.is_some() || stop { + comma!(); + print!("Axis {}: ", axis); + } + if let Some(dist) = px { + print!("{:+.4}px", dist); + need_comma = true; + } + if let Some(dist) = steps { + comma!(); + print!("steps: {:+}/120", dist); + need_comma = true; + } + if stop { + comma!(); + print!("stop"); + need_comma = true; + } + } + println!(); + } + }); + pending::<()>().await; +} + +fn time(time_usec: u64) -> f64 { + time_usec as f64 / 1_000_000f64 +} + +fn choose_seat(st: &SeatTest) -> u32 { + let seat_name = match &st.args.seat { + Some(s) => s.clone(), + _ => { + let mut seats: Vec<_> = st.names.borrow_mut().values().cloned().collect(); + seats.sort(); + eprintln!("Seats:"); + for seat in seats { + eprintln!(" - {}", seat); + } + eprint!("Name a seat to test: "); + let mut name = String::new(); + if let Err(e) = std::io::stdin().read_line(&mut name) { + fatal!("Could not read from stdin: {}", e); + } + name + } + }; + let seat_name = seat_name.trim(); + for seat in st.names.borrow_mut().deref() { + if seat.1.as_str() == seat_name { + return *seat.0; + } + } + fatal!("Unknown seat `{}`", seat_name); +} diff --git a/src/compositor.rs b/src/compositor.rs index 40882be7..6894f49d 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -194,6 +194,7 @@ fn start_compositor2( scales, cursor_sizes: Default::default(), hardware_tick_cursor: Default::default(), + testers: Default::default(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); diff --git a/src/ifs.rs b/src/ifs.rs index ef546dae..611347a9 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -5,6 +5,7 @@ pub mod jay_compositor; pub mod jay_idle; pub mod jay_log_file; pub mod jay_screenshot; +pub mod jay_seat_events; pub mod org_kde_kwin_server_decoration; pub mod org_kde_kwin_server_decoration_manager; pub mod wl_buffer; diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs index bb3c3a1a..01dc6b59 100644 --- a/src/ifs/jay_compositor.rs +++ b/src/ifs/jay_compositor.rs @@ -3,7 +3,10 @@ use { cli::CliLogLevel, client::{Client, ClientError}, globals::{Global, GlobalName}, - ifs::{jay_idle::JayIdle, jay_log_file::JayLogFile, jay_screenshot::JayScreenshot}, + ifs::{ + jay_idle::JayIdle, jay_log_file::JayLogFile, jay_screenshot::JayScreenshot, + jay_seat_events::JaySeatEvents, + }, leaks::Tracker, object::Object, screenshoter::take_screenshot, @@ -193,6 +196,35 @@ impl JayCompositor { self.client.symmetric_delete.set(true); Ok(()) } + + fn get_seats(&self, parser: MsgParser<'_, '_>) -> Result<(), JayCompositorError> { + let _req: GetSeats = self.client.parse(self, parser)?; + for seat in self.client.state.globals.seats.lock().values() { + self.client.event(Seat { + self_id: self.id, + id: seat.id().raw(), + name: seat.seat_name(), + }) + } + Ok(()) + } + + fn seat_events(&self, parser: MsgParser<'_, '_>) -> Result<(), JayCompositorError> { + let req: SeatEvents = self.client.parse(self, parser)?; + let se = Rc::new(JaySeatEvents { + id: req.id, + client: self.client.clone(), + tracker: Default::default(), + }); + track!(self.client, se); + self.client.add_client_obj(&se)?; + self.client + .state + .testers + .borrow_mut() + .insert((self.client.id, req.id), se); + Ok(()) + } } object_base! { @@ -207,11 +239,13 @@ object_base! { GET_CLIENT_ID => get_client_id, ENABLE_SYMMETRIC_DELETE => enable_symmetric_delete, UNLOCK => unlock, + GET_SEATS => get_seats, + SEAT_EVENTS => seat_events, } impl Object for JayCompositor { fn num_requests(&self) -> u32 { - UNLOCK + 1 + SEAT_EVENTS + 1 } } diff --git a/src/ifs/jay_seat_events.rs b/src/ifs/jay_seat_events.rs new file mode 100644 index 00000000..e4759f12 --- /dev/null +++ b/src/ifs/jay_seat_events.rs @@ -0,0 +1,152 @@ +use { + crate::{ + backend::{self, KeyState}, + client::Client, + fixed::Fixed, + ifs::wl_seat::{wl_pointer::PendingScroll, SeatId}, + leaks::Tracker, + object::Object, + state::DeviceHandlerData, + wire::{jay_seat_events::*, JaySeatEventsId}, + xkbcommon::ModifierState, + }, + std::rc::Rc, +}; + +pub struct JaySeatEvents { + pub id: JaySeatEventsId, + pub client: Rc, + pub tracker: Tracker, +} + +impl JaySeatEvents { + pub fn send_modifiers(&self, seat: SeatId, mods: &ModifierState) { + self.client.event(Modifiers { + self_id: self.id, + seat: seat.raw(), + modifiers: mods.mods_effective, + group: mods.group, + }); + } + + pub fn send_key(&self, seat: SeatId, time_usec: u64, key: u32, state: KeyState) { + self.client.event(Key { + self_id: self.id, + seat: seat.raw(), + time_usec, + key, + state: state as u32, + }); + } + + pub fn send_pointer_abs(&self, seat: SeatId, time_usec: u64, x: Fixed, y: Fixed) { + self.client.event(PointerAbs { + self_id: self.id, + seat: seat.raw(), + time_usec, + x, + y, + }); + } + + pub fn send_pointer_rel( + &self, + seat: SeatId, + time_usec: u64, + x: Fixed, + y: Fixed, + dx: Fixed, + dy: Fixed, + dx_unaccelerated: Fixed, + dy_unaccelerated: Fixed, + ) { + self.client.event(PointerRel { + self_id: self.id, + seat: seat.raw(), + time_usec, + x, + y, + dx, + dy, + dx_unaccelerated, + dy_unaccelerated, + }); + } + + pub fn send_button(&self, seat: SeatId, time_usec: u64, button: u32, state: KeyState) { + self.client.event(Button { + self_id: self.id, + seat: seat.raw(), + time_usec, + button, + state: state as u32, + }); + } + + pub fn send_axis( + &self, + seat: SeatId, + time_usec: u64, + dev: &DeviceHandlerData, + ps: &PendingScroll, + ) { + if let Some(source) = ps.source.get() { + self.client.event(AxisSource { + self_id: self.id, + source, + }); + } + for axis in 0..1 { + if let Some(dist) = ps.v120[axis].get() { + self.client.event(Axis120 { + self_id: self.id, + dist, + axis: axis as _, + }); + let px = (dist as f64 / backend::AXIS_120 as f64) * dev.px_per_scroll_wheel.get(); + self.client.event(AxisPx { + self_id: self.id, + dist: Fixed::from_f64(px), + axis: axis as _, + }); + } else if let Some(dist) = ps.px[axis].get() { + self.client.event(AxisPx { + self_id: self.id, + dist, + axis: axis as _, + }); + } + if ps.stop[axis].get() { + self.client.event(AxisStop { + self_id: self.id, + axis: axis as _, + }); + } + } + self.client.event(AxisFrame { + self_id: self.id, + seat: seat.raw(), + time_usec, + }); + } +} + +object_base! { + JaySeatEvents; +} + +impl Object for JaySeatEvents { + fn num_requests(&self) -> u32 { + 0 + } + + fn break_loops(&self) { + self.client + .state + .testers + .borrow_mut() + .remove(&(self.client.id, self.id)); + } +} + +simple_add_obj!(JaySeatEvents); diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index 63fb8562..5c3b49ac 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -191,7 +191,7 @@ impl WlSeatGlobal { time_usec, button, state, - } => self.pointer_owner.button(self, time_usec, button, state), + } => self.button_event(time_usec, button, state), InputEvent::AxisSource { source } => self.pointer_owner.axis_source(source), InputEvent::Axis120 { dist, axis } => self.pointer_owner.axis_120(dist, axis), @@ -216,6 +216,9 @@ impl WlSeatGlobal { let pos = output.node.global.pos.get(); x += Fixed::from_int(pos.x1()); y += Fixed::from_int(pos.y1()); + self.state.for_each_seat_tester(|t| { + t.send_pointer_abs(self.id, time_usec, x, y); + }); self.set_new_position(time_usec, x, y); } @@ -238,6 +241,18 @@ impl WlSeatGlobal { let (mut x, mut y) = self.pos.get(); x += dx; y += dy; + self.state.for_each_seat_tester(|t| { + t.send_pointer_rel( + self.id, + time_usec, + x, + y, + dx, + dy, + dx_unaccelerated, + dy_unaccelerated, + ); + }); let output = self.output.get(); let pos = output.global.pos.get(); let mut x_int = x.round_down(); @@ -268,10 +283,17 @@ impl WlSeatGlobal { self.set_new_position(time_usec, x, y); } - fn key_event(&self, time_usec: u64, key: u32, state: KeyState) { + fn button_event(self: &Rc, time_usec: u64, button: u32, state: KeyState) { + self.state.for_each_seat_tester(|t| { + t.send_button(self.id, time_usec, button, state); + }); + self.pointer_owner.button(self, time_usec, button, state); + } + + fn key_event(&self, time_usec: u64, key: u32, key_state: KeyState) { let (state, xkb_dir) = { let mut pk = self.pressed_keys.borrow_mut(); - match state { + match key_state { KeyState::Released => { if !pk.remove(&key) { return; @@ -304,6 +326,9 @@ impl WlSeatGlobal { } new_mods = kb_state.update(key, xkb_dir); } + self.state.for_each_seat_tester(|t| { + t.send_key(self.id, time_usec, key, key_state); + }); let node = self.keyboard_node.get(); if shortcuts.is_empty() { node.node_on_key(self, time_usec, key, state); @@ -313,6 +338,9 @@ impl WlSeatGlobal { } } if let Some(mods) = new_mods { + self.state.for_each_seat_tester(|t| { + t.send_modifiers(self.id, &mods); + }); node.node_on_mods(self, mods); } } diff --git a/src/ifs/wl_seat/pointer_owner.rs b/src/ifs/wl_seat/pointer_owner.rs index 0008cbbe..7396991e 100644 --- a/src/ifs/wl_seat/pointer_owner.rs +++ b/src/ifs/wl_seat/pointer_owner.rs @@ -55,6 +55,9 @@ impl PointerOwnerHolder { pub fn frame(&self, dev: &DeviceHandlerData, seat: &Rc, time_usec: u64) { self.pending_scroll.time_usec.set(time_usec); let pending = self.pending_scroll.take(); + seat.state.for_each_seat_tester(|t| { + t.send_axis(seat.id, time_usec, dev, &pending); + }); if let Some(node) = self.owner.get().axis_node(seat) { node.node_on_axis_event(dev, seat, &pending); } diff --git a/src/state.rs b/src/state.rs index be1c8ea5..d2b249df 100644 --- a/src/state.rs +++ b/src/state.rs @@ -8,7 +8,7 @@ use { }, backends::dummy::DummyBackend, cli::RunArgs, - client::{Client, Clients, SerialRange, NUM_CACHED_SERIAL_RANGES}, + client::{Client, ClientId, Clients, SerialRange, NUM_CACHED_SERIAL_RANGES}, config::ConfigProxy, cursor::{Cursor, ServerCursors}, dbus::Dbus, @@ -17,6 +17,7 @@ use { globals::{Globals, GlobalsError, WaylandGlobal}, ifs::{ ext_session_lock_v1::ExtSessionLockV1, + jay_seat_events::JaySeatEvents, wl_drm::WlDrmGlobal, wl_seat::{SeatIds, WlSeatGlobal}, wl_surface::{ @@ -41,6 +42,7 @@ use { queue::AsyncQueue, refcounted::RefCounted, run_toplevel::RunToplevel, }, wheel::Wheel, + wire::JaySeatEventsId, xkbcommon::{XkbContext, XkbKeymap}, xwayland::{self, XWaylandEvent}, }, @@ -114,6 +116,7 @@ pub struct State { pub scales: RefCounted, pub cursor_sizes: RefCounted, pub hardware_tick_cursor: AsyncQueue>>, + pub testers: RefCell>>, } // impl Drop for State { @@ -622,4 +625,11 @@ impl State { }; seat.update_hardware_cursor(); } + + pub fn for_each_seat_tester(&self, f: F) { + let testers = self.testers.borrow_mut(); + for tester in testers.values() { + f(tester); + } + } } diff --git a/wire/jay_compositor.txt b/wire/jay_compositor.txt index a6d1ef3b..36d913a5 100644 --- a/wire/jay_compositor.txt +++ b/wire/jay_compositor.txt @@ -35,8 +35,21 @@ msg unlock = 8 { } +msg get_seats = 9 { + +} + +msg seat_events = 10 { + id: id(jay_seat_events), +} + # events msg client_id = 0 { client_id: pod(u64), } + +msg seat = 1 { + id: u32, + name: str, +} diff --git a/wire/jay_seat_events.txt b/wire/jay_seat_events.txt new file mode 100644 index 00000000..43c27c82 --- /dev/null +++ b/wire/jay_seat_events.txt @@ -0,0 +1,62 @@ +# events + +msg key = 0 { + seat: u32, + time_usec: pod(u64), + key: u32, + state: u32, +} + +msg pointer_abs = 1 { + seat: u32, + time_usec: pod(u64), + x: fixed, + y: fixed, +} + +msg pointer_rel = 2 { + seat: u32, + time_usec: pod(u64), + x: fixed, + y: fixed, + dx: fixed, + dy: fixed, + dx_unaccelerated: fixed, + dy_unaccelerated: fixed, +} + +msg button = 3 { + seat: u32, + time_usec: pod(u64), + button: u32, + state: u32, +} + +msg axis_source = 5 { + source: u32, +} + +msg axis_px = 6 { + dist: fixed, + axis: u32, +} + +msg axis_stop = 7 { + axis: u32, +} + +msg axis_120 = 8 { + dist: i32, + axis: u32, +} + +msg axis_frame = 9 { + seat: u32, + time_usec: pod(u64), +} + +msg modifiers = 10 { + seat: u32, + modifiers: u32, + group: u32, +}