From 215e51b4eb64334b0bcce1f3c099e6be2608df9f Mon Sep 17 00:00:00 2001 From: arcnmx Date: Tue, 25 Aug 2020 22:08:29 -0700 Subject: [PATCH] No more xcb, support XInput2 raw events Closes #17 --- .travis.yml | 4 - Cargo.lock | 97 +++++- README.md | 2 - config/src/lib.rs | 3 + src/main.rs | 29 +- src/process.rs | 52 ++- x/Cargo.toml | 4 +- x/src/lib.rs | 871 ++++++++++++++++++++++++++++------------------ 8 files changed, 677 insertions(+), 385 deletions(-) diff --git a/.travis.yml b/.travis.yml index e461d46..2403f56 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,10 +8,6 @@ addons: apt: packages: - libudev-dev - - libxcb1-dev - - libxcb-dpms0-dev - - libxcb-xtest0-dev - - libxcb-xkb-dev cache: directories: - "$HOME/.cargo" diff --git a/Cargo.lock b/Cargo.lock index 5ee2e26..4cd5971 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -478,6 +478,16 @@ dependencies = [ "slab", ] +[[package]] +name = "gethostname" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e692e296bfac1d2533ef168d0b60ff5897b8b70a4009276834014dd8924cc028" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "gimli" version = "0.23.0" @@ -990,6 +1000,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "194d8e591e405d1eecf28819740abed6d719d1a2db87fc0bcdedee9a26d55560" +[[package]] +name = "roxmltree" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17dfc6c39f846bfc7d2ec442ad12055d79608d501380789b965d22f9354451f2" +dependencies = [ + "xmlparser", +] + [[package]] name = "rustc-demangle" version = "0.1.18" @@ -1093,13 +1112,15 @@ dependencies = [ name = "screenstub-x" version = "0.0.1" dependencies = [ + "enumflags2", "failure", "futures", "input-linux", "log", "screenstub-fd", "tokio", - "xcb", + "xproto", + "xserver", ] [[package]] @@ -1410,13 +1431,56 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "xcb" -version = "0.9.0" +name = "xcbgen" +version = "0.0.0" +source = "git+https://github.com/arcnmx/xproto-rs?branch=wip#fd3ba1383f0cd0c7b7079e352b81f07d9dd451e3" +dependencies = [ + "once_cell", + "roxmltree", +] + +[[package]] +name = "xcodegen" +version = "0.1.0" +source = "git+https://github.com/arcnmx/xproto-rs?branch=wip#fd3ba1383f0cd0c7b7079e352b81f07d9dd451e3" +dependencies = [ + "enumflags2", + "roxmltree", + "xcbgen", +] + +[[package]] +name = "xmlparser" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62056f63138b39116f82a540c983cc11f1c90cd70b3d492a70c25eaa50bd22a6" +checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8" + +[[package]] +name = "xproto" +version = "0.1.0" +source = "git+https://github.com/arcnmx/xproto-rs?branch=wip#fd3ba1383f0cd0c7b7079e352b81f07d9dd451e3" dependencies = [ - "libc", - "log", + "byteorder", + "bytes", + "enumflags2", + "xcodegen", + "zerocopy", +] + +[[package]] +name = "xserver" +version = "0.1.0" +source = "git+https://github.com/arcnmx/xproto-rs?branch=wip#fd3ba1383f0cd0c7b7079e352b81f07d9dd451e3" +dependencies = [ + "byteorder", + "bytes", + "enumflags2", + "futures", + "gethostname", + "tokio", + "tokio-util", + "xproto", + "zerocopy", ] [[package]] @@ -1427,3 +1491,24 @@ checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d" dependencies = [ "linked-hash-map", ] + +[[package]] +name = "zerocopy" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6580539ad917b7c026220c4b3f2c08d52ce54d6ce0dc491e66002e35388fab46" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d498dbd1fd7beb83c86709ae1c33ca50942889473473d287d56ce4770a18edfb" +dependencies = [ + "proc-macro2", + "syn", + "synstructure", +] diff --git a/README.md b/README.md index 782f90f..d29651d 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,6 @@ and can be installed and run like so: ### Dependencies - udev (Debian: libudev-dev) -- libxcb (Debian: libxcb1-dev libxcb-dpms0-dev libxcb-xtest0-dev libxcb-xkb-dev) -- python (build-time only to generate xcb bindings) ### Packages diff --git a/config/src/lib.rs b/config/src/lib.rs index d671f9a..68e61ba 100644 --- a/config/src/lib.rs +++ b/config/src/lib.rs @@ -256,6 +256,7 @@ impl ConfigInputEvent { #[serde(rename_all = "lowercase")] pub enum ConfigGrab { XCore, + XInput, XDevice { devices: Vec, }, @@ -276,6 +277,7 @@ impl ConfigGrab { pub fn mode(&self) -> ConfigGrabMode { match *self { ConfigGrab::XCore => ConfigGrabMode::XCore, + ConfigGrab::XInput => ConfigGrabMode::XInput, ConfigGrab::XDevice { .. } => ConfigGrabMode::XDevice, ConfigGrab::Evdev { .. } => ConfigGrabMode::Evdev, } @@ -294,6 +296,7 @@ pub enum ConfigGrabMode { Evdev, XDevice, XCore, + XInput, } impl Default for ConfigGrabMode { diff --git a/src/main.rs b/src/main.rs index b64743a..3dc30a2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -115,32 +115,13 @@ async fn main_result(spawner: &Arc) -> Result { match matches.subcommand() { ("x", Some(..)) => { + let (x_sender, mut x_receiver) = mpsc::channel(0x20); + let (mut xreq_sender, xreq_receiver) = mpsc::channel(0x08); let xinstance = screen.x_instance.unwrap_or("auto".into()); - - let (mut x_sender, mut x_receiver) = mpsc::channel(0x20); - let (mut xreq_sender, mut xreq_receiver) = mpsc::channel(0x08); - let x = x::XContext::xmain("screenstub", &xinstance, "screenstub")?; let xmain = tokio::spawn(async move { - let mut x = x.fuse(); - loop { - futures::select! { - req = xreq_receiver.next() => if let Some(req) = req { - let _ = x.send(req).await; - }, - event = x.next() => match event { - Some(Ok(event)) => { - let _ = x_sender.send(event).await; - }, - Some(Err(e)) => { - error!("X Error: {}: {:?}", e, e); - break - }, - None => { - break - }, - }, - complete => break, - } + let x = x::XContext::xmain("screenstub", &xinstance, "screenstub", xreq_receiver, x_sender); + if let Err(e) = x.await { + error!("X Error: {}: {:?}", e, e); } }).map_err(From::from); diff --git a/src/process.rs b/src/process.rs index 7488fff..d0bd828 100644 --- a/src/process.rs +++ b/src/process.rs @@ -158,7 +158,38 @@ impl Process { x_filter: Default::default(), is_mouse: false, }); - self.xreq(XRequest::Grab) + self.xreq(XRequest::Grab { + xcore: true, + motion: false, + }) + }, + ConfigGrab::XInput => { + let qemu = self.qemu.clone(); + let routing = self.routing; + let driver_relative = self.driver_relative; + let driver_absolute = self.driver_absolute; + let is_mouse = true; + let prev_is_mouse = self.is_mouse(); + + self.grabs.try_lock().unwrap().insert(mode, GrabHandle { + grab: None, + x_filter: Default::default(), + is_mouse, + }); + + let grab = self.xreq(XRequest::Grab { + xcore: true, + motion: true, + }); + async move { + grab.await?; + + if is_mouse && !prev_is_mouse { + Self::set_is_mouse_cmd(qemu, routing, driver_relative, driver_absolute, is_mouse).await?; + } + + Ok(()) + }.boxed() }, ConfigGrab::Evdev { exclusive, ref new_device_name, ref xcore_ignore, ref evdev_ignore, ref devices } => { let qemu = self.qemu.clone(); @@ -243,9 +274,22 @@ impl Process { fn ungrab(&self, grab: ConfigGrabMode) -> Pin> + Send>> { match grab { - ConfigGrabMode::XCore => { - self.grabs.try_lock().unwrap().remove(&grab); - self.xreq(XRequest::Ungrab) + ConfigGrabMode::XCore | ConfigGrabMode::XInput => { + let ungrab = self.xreq(XRequest::Ungrab); + let grab = self.grabs.try_lock().unwrap().remove(&grab); + if let Some(grab) = grab { + if grab.is_mouse && !self.is_mouse() { + let set = self.set_is_mouse(false); + async move { + set.await?; + ungrab.await + }.boxed() + } else { + ungrab + } + } else { + ungrab + } }, ConfigGrabMode::Evdev => { let grab = self.grabs.try_lock().unwrap().remove(&grab); diff --git a/x/Cargo.toml b/x/Cargo.toml index 2165d82..cbefcbe 100644 --- a/x/Cargo.toml +++ b/x/Cargo.toml @@ -8,6 +8,8 @@ screenstub-fd = { version = "^0.0.1", path = "../fd" } futures = { version = "^0.3.4", features = ["bilock", "unstable"] } tokio = { version = "^0.3.3", default-features = false, features = ["rt-multi-thread"] } failure = "^0.1.1" -xcb = { version = "^0.9.0", features = ["xtest", "xkb", "dpms"] } +xproto = { version = "^0.1.0", features = ["xtest", "xkb", "xinput", "dpms"], git = "https://github.com/arcnmx/xproto-rs", branch = "wip" } +xserver = { version = "^0.1.0", git = "https://github.com/arcnmx/xproto-rs", branch = "wip" } +enumflags2 = "^0.6.4" input-linux = "^0.4.0" log = "^0.4.1" diff --git a/x/src/lib.rs b/x/src/lib.rs index 351a523..dcd09f9 100644 --- a/x/src/lib.rs +++ b/x/src/lib.rs @@ -1,25 +1,39 @@ -pub extern crate xcb; - -use futures::{Sink, Stream, ready}; +use futures::{Sink, Stream, SinkExt, StreamExt}; use failure::{Error, format_err}; -use input_linux::{InputEvent, EventTime, KeyEvent, KeyState, Key, AbsoluteEvent, AbsoluteAxis, SynchronizeEvent}; -use tokio::io::unix::AsyncFd; -use tokio::io::Interest; -use std::task::{Poll, Context, Waker}; -use std::pin::Pin; +use input_linux::{InputEvent, EventTime, KeyEvent, KeyState, Key, AbsoluteEvent, AbsoluteAxis, RelativeEvent, RelativeAxis, SynchronizeEvent}; +use enumflags2::BitFlags; +use xproto::protocol::*; +use xproto::conversion::AsPrimitive; +use std::collections::BTreeMap; use log::{trace, warn, info}; use screenstub_fd::Fd; +pub fn iter_bits(mut v: u32) -> impl Iterator { + let mut index = 0; + std::iter::from_fn(move || { + if v == 0 { + None + } else { + let shift = v.trailing_zeros(); + v >>= shift + 1; + let res = index + shift; + index += shift + 1; + Some(res as usize) + } + }) +} + #[derive(Debug, Clone, Copy, Default)] struct XState { pub width: u16, pub height: u16, pub running: bool, + pub grabbed: bool, } #[derive(Debug)] pub struct XInputEvent { - time: xcb::Time, + time: u32, data: XInputEventData, } @@ -29,16 +43,20 @@ pub enum XInputEventData { x: i16, y: i16, }, + MouseRelative { + axis: RelativeAxis, + value: i32, + }, Button { pressed: bool, - button: xcb::Button, - state: u16, + button: u8, + state: BitFlags, }, Key { pressed: bool, - keycode: xcb::Keycode, - keysym: Option, - state: u16, + keycode: u8, + keysym: Option, + state: BitFlags, }, } @@ -54,145 +72,210 @@ pub enum XEvent { pub enum XRequest { Quit, UnstickHost, - Grab, + Grab { + xcore: bool, + motion: bool, + }, Ungrab, } -pub struct XContext { - conn: xcb::Connection, - fd: AsyncFd, - window: u32, +type XConnection = xserver::stream::XConnection>; +type XSink = xserver::stream::XSink>; - keys: xcb::GetKeyboardMappingReply, - mods: xcb::GetModifierMappingReply, +pub struct XContext { + sink: XSink, + window: xcore::Window, + ext_input: xcore::QueryExtensionReply, + ext_test: xcore::QueryExtensionReply, + ext_dpms: xcore::QueryExtensionReply, + ext_xkb: xcore::QueryExtensionReply, + setup: xcore::Setup, + + keys: xcore::GetKeyboardMappingReply, + mods: xcore::GetModifierMappingReply, + devices: BTreeMap, state: XState, - next_event: Option, - next_request: Option, + display: xserver::Display, + event_queue: Vec, - stop_waker: Option, - atom_wm_state: xcb::Atom, - atom_wm_protocols: xcb::Atom, - atom_wm_delete_window: xcb::Atom, - atom_net_wm_state: xcb::Atom, - atom_net_wm_state_fullscreen: xcb::Atom, + atom_wm_state: xcore::Atom, + atom_wm_protocols: xcore::Atom, + atom_wm_delete_window: xcore::Atom, + atom_net_wm_state: xcore::Atom, + atom_net_wm_state_fullscreen: xcore::Atom, } -unsafe impl Send for XContext { } +//unsafe impl Send for XContext { } impl XContext { - pub fn connect() -> Result { - let (conn, screen_num) = xcb::Connection::connect(None)?; - let fd = { - let fd = unsafe { xcb::ffi::base::xcb_get_file_descriptor(conn.get_raw_conn()) }; - AsyncFd::with_interest(fd.into(), Interest::READABLE) - }?; - let window = conn.generate_id(); - let (keys, mods) = { - let setup = conn.get_setup(); - let screen = setup.roots().nth(screen_num as usize).unwrap(); - - xcb::create_window(&conn, - xcb::COPY_FROM_PARENT as _, - window, - screen.root(), - 0, 0, - screen.width_in_pixels(), screen.height_in_pixels(), - 0, - xcb::WINDOW_CLASS_INPUT_OUTPUT as _, - screen.root_visual(), - &[ - (xcb::CW_BACK_PIXEL, screen.black_pixel()), - ( - xcb::CW_EVENT_MASK, - xcb::EVENT_MASK_VISIBILITY_CHANGE | xcb::EVENT_MASK_PROPERTY_CHANGE | - xcb::EVENT_MASK_KEY_PRESS | xcb::EVENT_MASK_KEY_RELEASE | - xcb::EVENT_MASK_BUTTON_PRESS | xcb::EVENT_MASK_BUTTON_RELEASE | - xcb::EVENT_MASK_POINTER_MOTION | xcb::EVENT_MASK_BUTTON_MOTION | - xcb::EVENT_MASK_STRUCTURE_NOTIFY | xcb::EVENT_MASK_FOCUS_CHANGE - ), - ] - ); - - ( - xcb::get_keyboard_mapping(&conn, setup.min_keycode(), setup.max_keycode() - setup.min_keycode()).get_reply()?, - xcb::get_modifier_mapping(&conn).get_reply()?, - ) - }; + pub async fn connect() -> Result<(XConnection, XSink, xserver::Display), Error> { + let display = xserver::Display::new(None)?; + let ((r, w), auth) = xserver::stream::open_display(&display).await?; + let (conn, sink) = xserver::stream::XConnection::connect(auth, r, w).await?; + + Ok((conn, sink, display)) + } + + async fn new(mut sink: XSink, display: xserver::Display, setup: xcore::Setup) -> Result { + let screen = setup.roots.get(display.screen as usize).unwrap(); + let window = sink.generate_id().await?; + let ext_input = sink.extension(ExtensionKind::Input).await.await? + .expect("XInput required"); + let ext_xkb = sink.extension(ExtensionKind::Xkb).await.await? + .expect("XKB required"); + let ext_test = sink.extension(ExtensionKind::Test).await.await? + .expect("XTest required"); + let ext_dpms = sink.extension(ExtensionKind::DPMS).await.await? + .expect("DPMS required"); + let _ = sink.execute(xinput::XIQueryVersionRequest { + major_opcode: ext_input.major_opcode, + major_version: 2, + minor_version: 3, + }).await.await?; + + let _ = sink.execute(xkb::UseExtensionRequest { + major_opcode: ext_xkb.major_opcode, + wanted_major: 1, + wanted_minor: 0, + }).await.await?; + + sink.execute(xcore::CreateWindowRequest { + depth: xcore::WindowClass::CopyFromParent.into(), + wid: window, + parent: screen.root, + x: 0, y: 0, + width: screen.width_in_pixels, height: screen.height_in_pixels, + border_width: 0, + class: xcore::WindowClass::InputOutput.into(), + visual: screen.root_visual, + value_list: xcore::CreateWindowRequestValueList { + back_pixel: Some(xcore::CreateWindowRequestValueListBackPixel { + background_pixel: screen.black_pixel, + }), + event_mask: Some(xcore::CreateWindowRequestValueListEventMask { + event_mask: (xcore::EventMask::VisibilityChange + | xcore::EventMask::KeyPress | xcore::EventMask::KeyRelease | xcore::EventMask::ButtonPress | xcore::EventMask::ButtonRelease | xcore::EventMask::PointerMotion | xcore::EventMask::ButtonMotion + | xcore::EventMask::PropertyChange + | xcore::EventMask::StructureNotify + | xcore::EventMask::FocusChange).into(), + }), + .. Default::default() + }, + }).await.await?; + + sink.execute(xinput::XISelectEventsRequest { + major_opcode: ext_input.major_opcode, + window, + masks: vec![ + xinput::EventMask { + deviceid: xinput::Device::All.into(), + mask: vec![xinput::XIEventMask::DeviceChanged.into()], + }, + ], + }).await.await?; + + sink.execute(xkb::PerClientFlagsRequest { + // Inhibit KeyRelease events normally generated by autorepeat + major_opcode: ext_xkb.major_opcode, + device_spec: xkb::ID::UseCoreKbd.into(), // according to xlib XkbSetDetectableAutoRepeat? + change: xkb::PerClientFlag::DetectableAutoRepeat.into(), + value: xkb::PerClientFlag::DetectableAutoRepeat.into(), + .. Default::default() + }).await.await?; + + let (keys, mods) = ( + sink.execute(xcore::GetKeyboardMappingRequest { + first_keycode: setup.min_keycode, + count: setup.max_keycode - setup.min_keycode, + }).await.await?, + sink.execute(xcore::GetModifierMappingRequest { }).await.await?, + ); Ok(Self { - atom_wm_state: xcb::intern_atom(&conn, true, "WM_STATE").get_reply()?.atom(), - atom_wm_protocols: xcb::intern_atom(&conn, true, "WM_PROTOCOLS").get_reply()?.atom(), - atom_wm_delete_window: xcb::intern_atom(&conn, true, "WM_DELETE_WINDOW").get_reply()?.atom(), - atom_net_wm_state: xcb::intern_atom(&conn, true, "_NET_WM_STATE").get_reply()?.atom(), - atom_net_wm_state_fullscreen: xcb::intern_atom(&conn, true, "_NET_WM_STATE_FULLSCREEN").get_reply()?.atom(), + atom_wm_state: sink.intern_atom("WM_STATE").await.await?, + atom_wm_protocols: sink.intern_atom("WM_PROTOCOLS").await.await?, + atom_wm_delete_window: sink.intern_atom("WM_DELETE_WINDOW").await.await?, + atom_net_wm_state: sink.intern_atom("_NET_WM_STATE").await.await?, + atom_net_wm_state_fullscreen: sink.intern_atom("_NET_WM_STATE_FULLSCREEN").await.await?, keys, mods, + setup, state: Default::default(), - next_event: None, - + devices: Default::default(), event_queue: Default::default(), - next_request: None, - stop_waker: None, + ext_input, + ext_test, + ext_xkb, + ext_dpms, + display, - conn, - fd, + sink, window, }) } - pub fn set_wm_name(&self, name: &str) -> Result<(), Error> { - // TODO: set _NET_WM_NAME instead? or both? - - xcb::change_property(&self.conn, - xcb::PROP_MODE_REPLACE as _, - self.window, - xcb::ATOM_WM_NAME, - xcb::ATOM_STRING, 8, - name.as_bytes() - ).request_check()?; + pub fn screen(&self) -> &xcore::Screen { + self.setup.roots.get(self.display.screen as usize).unwrap() + } - Ok(()) + pub async fn set_wm_name(&mut self, name: &str) -> Result<(), Error> { + // TODO: set _NET_WM_NAME instead? or both? + self.sink.execute(xcore::ChangePropertyRequest { + mode: xcore::PropMode::Replace, + window: self.window, + property: xcore::AtomEnum::WM_NAME.into(), + type_: xcore::AtomEnum::STRING.into(), + data: xcore::ChangePropertyRequestData::Data8(xcore::ChangePropertyRequestDataData8 { + data: name.into(), + }), + }).await.await.map(drop).map_err(From::from) } - pub fn set_wm_class(&self, instance: &str, class: &str) -> Result<(), Error> { + pub async fn set_wm_class(&mut self, instance: &str, class: &str) -> Result<(), Error> { // TODO: ensure neither class or instance contain nul byte let wm_class_string = format!("{}\0{}", instance, class); - xcb::change_property(&self.conn, - xcb::PROP_MODE_REPLACE as _, - self.window, - xcb::ATOM_WM_CLASS, - xcb::ATOM_STRING, 8, - wm_class_string.as_bytes() - ).request_check()?; - - Ok(()) + self.sink.execute(xcore::ChangePropertyRequest { + mode: xcore::PropMode::Replace, + window: self.window, + property: xcore::AtomEnum::WM_CLASS.into(), + type_: xcore::AtomEnum::STRING.into(), + data: xcore::ChangePropertyRequestData::Data8(xcore::ChangePropertyRequestDataData8 { + data: wm_class_string.into(), + }), + }).await.await.map(drop).map_err(From::from) } - pub fn map_window(&self) -> Result<(), Error> { - xcb::change_property(&self.conn, - xcb::PROP_MODE_REPLACE as _, - self.window, - self.atom_wm_protocols, - xcb::ATOM_ATOM, 32, - &[self.atom_wm_delete_window] - ).request_check()?; - - xcb::change_property(&self.conn, - xcb::PROP_MODE_APPEND as _, - self.window, - self.atom_net_wm_state, - xcb::ATOM_ATOM, 32, - &[self.atom_net_wm_state_fullscreen] - ).request_check()?; - - xcb::map_window(&self.conn, self.window); - - self.flush()?; - - xcb::grab_button(&self.conn, + pub async fn map_window(&mut self) -> Result<(), Error> { + self.sink.execute(xcore::ChangePropertyRequest { + mode: xcore::PropMode::Replace, + window: self.window, + property: self.atom_wm_protocols.into(), + type_: xcore::AtomEnum::ATOM.into(), + data: xcore::ChangePropertyRequestData::Data32(xcore::ChangePropertyRequestDataData32 { + data: vec![self.atom_wm_delete_window.into()], + }), + }).await.await?; + + self.sink.execute(xcore::ChangePropertyRequest { + mode: xcore::PropMode::Append, + window: self.window, + property: self.atom_net_wm_state.into(), + type_: xcore::AtomEnum::ATOM.into(), + data: xcore::ChangePropertyRequestData::Data32(xcore::ChangePropertyRequestDataData32 { + data: vec![self.atom_net_wm_state_fullscreen.into()], + }), + }).await.await?; + + self.update_valuators().await; + + self.sink.execute(xcore::MapWindowRequest { + window: self.window, + }).await.await?; + + /*xcb::grab_button(&self.conn, false, // owner_events? self.window, xcb::BUTTON_MASK_ANY as _, @@ -202,72 +285,18 @@ impl XContext { xcb::NONE, xcb::BUTTON_INDEX_ANY as _, xcb::MOD_MASK_ANY as _, - ).request_check()?; + ).request_check()?;*/ Ok(()) } - pub fn flush(&self) -> Result<(), xcb::ConnError> { - if self.conn.flush() { - Ok(()) - } else { - Err(self.connection_error().unwrap()) - } - } - - pub fn connection_error(&self) -> Option { - self.conn.has_error().err() - } - - pub fn connection(&self) -> &xcb::Connection { - &self.conn - } - - pub fn pump(&mut self) -> Result { - match self.next_event.take() { - Some(e) => Ok(e), - None => { - if let Some(event) = self.conn.wait_for_event() { - Ok(event) - } else { - Err(self.connection_error().unwrap().into()) - } - } - } - } - - pub fn poll(&mut self) -> Result, Error> { - match self.next_event.take() { - Some(e) => Ok(Some(e)), - None => { - if let Some(event) = self.conn.poll_for_event() { - Ok(Some(event)) - } else { - self.connection_error().map(|e| Err(e.into())).transpose() - } - } - } + pub fn keycode(&self, code: u8) -> u8 { + code - self.setup.min_keycode } - pub fn peek(&mut self) -> Option<&xcb::GenericEvent> { - if self.next_event.is_none() { - if let Some(event) = self.conn.poll_for_event() { - Some(self.next_event.get_or_insert(event)) - } else { - None - } - } else { - self.next_event.as_ref() - } - } - - pub fn keycode(&self, code: xcb::Keycode) -> xcb::Keycode { - code - self.conn.get_setup().min_keycode() - } - - pub fn keysym(&self, code: xcb::Keycode) -> Option { + pub fn keysym(&self, code: u8) -> Option { let modifier = 0; // TODO: ? - match self.keys.keysyms().get(code as usize * self.keys.keysyms_per_keycode() as usize + modifier).cloned() { + match self.keys.keysyms.get(code as usize * self.keys.keysyms_per_keycode as usize + modifier).cloned() { Some(0) => None, keysym => keysym, } @@ -277,29 +306,66 @@ impl XContext { log::trace!("XContext::stop()"); self.state.running = false; - if let Some(waker) = self.stop_waker.take() { - waker.wake(); - } } - pub fn xmain(name: &str, instance: &str, class: &str) -> Result { - let mut xcontext = Self::connect()?; + pub async fn xmain, O: Sink>(name: &str, instance: &str, class: &str, i: I, o: O) -> Result<(), Error> { + let (conn, sink, display) = Self::connect().await?; + let setup = conn.setup().clone(); + let mut conn = conn.fuse(); + + let (mut event_sender, mut event_receiver) = futures::channel::mpsc::unbounded(); + + let join = tokio::spawn(async move { + while let Some(e) = conn.next().await { + if event_sender.send(e).await.is_err() { + break + } + } + }); + + let mut xcontext = Self::new(sink, display, setup).await?; + + let i = i.fuse(); + futures::pin_mut!(i); + futures::pin_mut!(o); + xcontext.state.running = true; - xcontext.set_wm_name(name)?; - xcontext.set_wm_class(instance, class)?; - xcontext.map_window()?; - Ok(xcontext) + xcontext.set_wm_name(name).await?; + xcontext.set_wm_class(instance, class).await?; + xcontext.map_window().await?; + + while xcontext.state.running { + futures::select_biased! { + req = i.next() => match req { + None => break, + Some(req) => xcontext.process_request(&req).await?, + }, + e = event_receiver.next() => match e { + None => break, + Some(e) => xcontext.process_event(&e?).await?, + }, + } + + while let Some(e) = xcontext.event_queue_pop() { + let _ = o.send(e).await; // break if err? + } + } + + drop(event_receiver); + join.await?; + + Ok(()) } - fn handle_grab_status(&self, status: u8) -> Result<(), Error> { - if status == xcb::GRAB_STATUS_SUCCESS as _ { + fn handle_grab_status(&self, status: xcore::GrabStatus) -> Result<(), Error> { + if status == xcore::GrabStatus::Success as _ { Ok(()) } else { - Err(format_err!("X failed to grab with status code {}", status)) + Err(format_err!("X failed to grab with status {:?}", status)) } } - pub fn process_request(&mut self, request: &XRequest) -> Result<(), Error> { + pub async fn process_request(&mut self, request: &XRequest) -> Result<(), Error> { trace!("processing X request {:?}", request); Ok(match *request { @@ -307,112 +373,178 @@ impl XContext { self.stop(); }, XRequest::UnstickHost => { - let keys = xcb::query_keymap(&self.conn).get_reply()?; - let keys = keys.keys(); + let keys = self.sink.execute(xcore::QueryKeymapRequest { }).await.await?; let mut keycode = 0usize; - for &key in keys { - for i in 0..8 { + for &key in &keys.keys { + for i in 0u32..8 { if key & (1 << i) != 0 { - xcb::test::fake_input(&self.conn, - xcb::KEY_RELEASE, - keycode as _, - xcb::CURRENT_TIME, - xcb::NONE, 0, 0, - xcb::NONE as _ // can't find documentation for this device_id argument? - ).request_check()? + self.sink.execute(xtest::FakeInputRequest { + major_opcode: self.ext_test.major_opcode, + type_: xcore::KeyReleaseEvent::NUMBER as _, + detail: keycode as _, + time: xcore::Time::CurrentTime.into(), + root: xcore::WindowEnum::None.into(), + root_x: 0, + root_y: 0, + deviceid: 0, // apparently xcb::NONE, but 0 is Device::AllMaster or something? + }).await.await?; } keycode += 1; } } }, - XRequest::Grab => { - let status = xcb::grab_keyboard(&self.conn, - false, // owner_events, I don't quite understand how this works - self.window, - xcb::CURRENT_TIME, - xcb::GRAB_MODE_ASYNC as _, - xcb::GRAB_MODE_ASYNC as _, - ).get_reply()?.status(); - self.handle_grab_status(status)?; - let status = xcb::grab_pointer(&self.conn, - false, // owner_events, I don't quite understand how this works - self.window, - (xcb::EVENT_MASK_BUTTON_PRESS | xcb::EVENT_MASK_BUTTON_RELEASE | xcb::EVENT_MASK_POINTER_MOTION | xcb::EVENT_MASK_BUTTON_MOTION) as _, - xcb::GRAB_MODE_ASYNC as _, - xcb::GRAB_MODE_ASYNC as _, - self.window, // confine mouse to our window - xcb::NONE, - xcb::CURRENT_TIME, - ).get_reply()?.status(); - self.handle_grab_status(status)?; + XRequest::Grab { xcore, motion } => { + if xcore { + let status = self.sink.execute(xcore::GrabKeyboardRequest { + owner_events: false, // I don't quite understand how this works + grab_window: self.window, + time: xcore::Time::CurrentTime.into(), + pointer_mode: xcore::GrabMode::Async.into(), + keyboard_mode: xcore::GrabMode::Async.into(), + }).await.await?; + self.handle_grab_status(status.status)?; + + let status = self.sink.execute(xcore::GrabPointerRequest { + owner_events: false, // I don't quite understand how this works + grab_window: self.window, + event_mask: (xcore::EventMask::ButtonPress | xcore::EventMask::ButtonRelease | xcore::EventMask::PointerMotion | xcore::EventMask::ButtonMotion).as_(), + pointer_mode: xcore::GrabMode::Async.into(), + keyboard_mode: xcore::GrabMode::Async.into(), + confine_to: self.window.into(), + cursor: xcore::CursorEnum::None.into(), + time: xcore::Time::CurrentTime.into(), + }).await.await?; + self.handle_grab_status(status.status)?; + } + if motion { + self.update_grab(true).await?; + } }, XRequest::Ungrab => { - xcb::ungrab_keyboard(&self.conn, xcb::CURRENT_TIME).request_check()?; - xcb::ungrab_pointer(&self.conn, xcb::CURRENT_TIME).request_check()?; + self.sink.execute(xcore::UngrabKeyboardRequest { + time: xcore::Time::CurrentTime.into(), + }).await.await?; + self.sink.execute(xcore::UngrabPointerRequest { + time: xcore::Time::CurrentTime.into(), + }).await.await?; + self.update_grab(false).await?; }, }) } - fn process_event(&mut self, event: &xcb::GenericEvent) -> Result<(), xcb::GenericError> { - let kind = event.response_type() & !0x80; - trace!("processing X event {}", kind); + async fn update_grab(&mut self, grab: bool) -> Result<(), Error> { + self.sink.execute(xinput::XISelectEventsRequest { + major_opcode: self.ext_input.major_opcode, + window: self.screen().root, + masks: vec![ + xinput::EventMask { + deviceid: xinput::Device::All.into(), + mask: vec![ + if grab { + xinput::XIEventMask::RawMotion.into() + } else { + Default::default() + } + ], + }, + ], + }).await.await?; + self.state.grabbed = grab; + + // XI SetDeviceMode? - Ok(match kind { - xcb::VISIBILITY_NOTIFY => { - let event = unsafe { xcb::cast_event::(event) }; + Ok(()) + } + + async fn update_key_mappings(&mut self) -> Result<(), Error> { + let setup = &self.setup; + self.keys = self.sink.execute(xcore::GetKeyboardMappingRequest { + first_keycode: setup.min_keycode, + count: setup.max_keycode - setup.min_keycode, + }).await.await?; + + Ok(()) + } + async fn update_mappings(&mut self) -> Result<(), Error> { + self.update_key_mappings().await?; + self.mods = self.sink.execute(xcore::GetModifierMappingRequest { }).await.await?; + + Ok(()) + } + + async fn update_valuators(&mut self) -> Result<(), Error> { + self.devices = self.sink.execute(xinput::XIQueryDeviceRequest { + major_opcode: self.ext_input.major_opcode, + deviceid: xinput::Device::All.into(), + }).await.await?.infos + .into_iter().map(|info| (info.deviceid.value(), info)).collect(); + + Ok(()) + } + + fn valuator_info(&self, deviceid: xinput::DeviceId) -> Option<&xinput::XIDeviceInfo> { + self.devices.get(&deviceid) + } + + pub async fn process_event(&mut self, event: &ExtensionEvent) -> Result<(), Error> { + trace!("processing X event {:?}", event); + + Ok(match event { + ExtensionEvent::Core(xcore::Events::VisibilityNotify(event)) => { let dpms_blank = { - let power_level = xcb::dpms::info(&self.conn).get_reply() - .map(|info| info.power_level() as u32); + let info = self.sink.execute(dpms::InfoRequest { + major_opcode: self.ext_dpms.major_opcode, + }).await.await?; - power_level.unwrap_or(xcb::dpms::DPMS_MODE_ON) != xcb::dpms::DPMS_MODE_ON + info.power_level.get() != dpms::DPMSMode::On }; self.event_queue.push(if dpms_blank { XEvent::Visible(false) } else { - match event.state() as _ { - xcb::VISIBILITY_FULLY_OBSCURED => { - XEvent::Visible(false) - }, - xcb::VISIBILITY_UNOBSCURED => { - XEvent::Visible(true) - }, - state => { - warn!("unknown visibility {}", state); - return Ok(()) - }, + match event.state { + xcore::Visibility::FullyObscured => + XEvent::Visible(false), + xcore::Visibility::Unobscured => + XEvent::Visible(true), + xcore::Visibility::PartiallyObscured => + XEvent::Visible(true), // TODO: ?? } }); }, - xcb::CLIENT_MESSAGE => { - let event = unsafe { xcb::cast_event::(event) }; - - match event.data().data32().get(0) { - Some(&atom) if atom == self.atom_wm_delete_window => { - self.event_queue.push(XEvent::Close); - }, - Some(&atom) => { - let atom = xcb::get_atom_name(&self.conn, atom).get_reply(); - info!("unknown X client message {:?}", - atom.as_ref().map(|a| a.name()).unwrap_or("UNKNOWN") - ); - }, - None => { - warn!("empty client message"); - }, + ExtensionEvent::Core(xcore::Events::ClientMessage(event)) => { + let atom = match event.data { + xcore::ClientMessageEventData::Data32(d) => d.data[0], + _ => unimplemented!(), + }; + if atom == self.atom_wm_delete_window.into() { + self.event_queue.push(XEvent::Close); + } else { + let atom = self.sink.execute(xcore::GetAtomNameRequest { + atom: atom.into(), + }).await.await?; + info!("unknown X client message {:?}", atom.name); } }, - xcb::PROPERTY_NOTIFY => { - let event = unsafe { xcb::cast_event::(event) }; - - match event.atom() { + ExtensionEvent::Core(xcore::Events::PropertyNotify(event)) => { + match event.atom { atom if atom == self.atom_wm_state => { - let r = xcb::get_property(&self.conn, false, event.window(), event.atom(), 0, 0, 1).get_reply()?; - let x = r.value::(); + let r = self.sink.execute(xcore::GetPropertyRequest { + delete: false, + window: event.window, + property: atom, + type_: xcore::GetPropertyType::Any.into(), + long_offset: 0, + long_length: 0, + }).await.await?; + let x = match &r.value { + xcore::GetPropertyReplyValue::Data32(d) => d.data.get(0), + _ => None, + }; let window_state_withdrawn = 0; // 1 is back but unobscured also works so ?? let window_state_iconic = 3; - match x.get(0) { + match x { Some(&state) if state == window_state_withdrawn || state == window_state_iconic => { self.event_queue.push(XEvent::Visible(false)); }, @@ -425,113 +557,161 @@ impl XContext { } }, atom => { - let atom = xcb::get_atom_name(&self.conn, atom).get_reply(); - info!("unknown property notify {:?}", - atom.as_ref().map(|a| a.name()).unwrap_or("UNKNOWN") - ); + let atom = self.sink.execute(xcore::GetAtomNameRequest { + atom: atom.into(), + }).await.await?; + info!("unknown property notify {:?}", atom.name); }, } }, - xcb::FOCUS_OUT | xcb::FOCUS_IN => { - self.event_queue.push(XEvent::Focus(kind == xcb::FOCUS_IN)); + ExtensionEvent::Core(xcore::Events::MappingNotify(event)) => { + self.update_mappings().await?; }, - xcb::KEY_PRESS | xcb::KEY_RELEASE => { - let event = unsafe { xcb::cast_event::(event) }; - - // filter out autorepeat events - let peek = if let Some(peek) = self.peek() { - let peek_kind = peek.response_type() & !0x80; - match peek_kind { - xcb::KEY_PRESS | xcb::KEY_RELEASE => { - let peek_event = unsafe { xcb::cast_event::(peek) }; - Some((peek_kind, peek_event.time(), peek_event.detail())) - }, - _ => None, - } - } else { - None + ExtensionEvent::Core(xcore::Events::ConfigureNotify(event)) => { + self.state.width = event.width; + self.state.height = event.height; + }, + ExtensionEvent::Core(xcore::Events::FocusOut(..)) => { + self.event_queue.push(XEvent::Focus(false)); + }, + ExtensionEvent::Core(xcore::Events::FocusIn(..)) => { + self.event_queue.push(XEvent::Focus(true)); + }, + ExtensionEvent::Core(e @ xcore::Events::ButtonPress(..)) | ExtensionEvent::Core(e @ xcore::Events::ButtonRelease(..)) => { + let (pressed, event) = match e { + xcore::Events::ButtonPress(event) => (true, event), + xcore::Events::ButtonRelease(event) => (false, &event.0), + _ => unsafe { core::hint::unreachable_unchecked() }, }; - if let Some((peek_kind, peek_time, peek_detail)) = peek { - if peek_kind != kind && peek_time == event.time() && event.detail() == peek_detail { - // TODO: I think this only matters on release? - // repeat - return Ok(()) - } - } - - let keycode = self.keycode(event.detail()); - let keysym = self.keysym(keycode); - let event = XInputEvent { - time: event.time(), - data: XInputEventData::Key { - pressed: kind == xcb::KEY_PRESS, - keycode, - keysym: if keysym == Some(0) { None } else { keysym }, - state: event.state(), + time: event.time, + data: XInputEventData::Button { + pressed, + button: event.detail, + state: event.state.get(), }, }; self.convert_x_events(&event) }, - xcb::BUTTON_PRESS | xcb::BUTTON_RELEASE => { - let event = unsafe { xcb::cast_event::(event) }; + ExtensionEvent::Core(xcore::Events::MotionNotify(event)) => { + if self.state.grabbed { + // TODO: proper filtering + return Ok(()) + } let event = XInputEvent { - time: event.time(), - data: XInputEventData::Button { - pressed: kind == xcb::BUTTON_PRESS, - button: event.detail(), - state: event.state(), + time: event.time, + data: XInputEventData::Mouse { + x: event.event_x, + y: event.event_y, }, }; self.convert_x_events(&event) }, - xcb::MOTION_NOTIFY => { - let event = unsafe { xcb::cast_event::(event) }; + ExtensionEvent::Core(e @ xcore::Events::KeyPress(..)) | ExtensionEvent::Core(e @ xcore::Events::KeyRelease(..)) => { + let (pressed, event) = match e { + xcore::Events::KeyPress(event) => (true, event), + xcore::Events::KeyRelease(event) => (false, &event.0), + _ => unsafe { core::hint::unreachable_unchecked() }, + }; + + let keycode = self.keycode(event.detail); + let keysym = self.keysym(keycode); + let event = XInputEvent { - time: event.time(), - data: XInputEventData::Mouse { - x: event.event_x(), - y: event.event_y(), + time: event.time, + data: XInputEventData::Key { + pressed, + keycode, + keysym: if keysym == Some(0) { None } else { keysym }, + state: event.state.into(), }, }; self.convert_x_events(&event) }, - xcb::MAPPING_NOTIFY => { - let setup = self.conn.get_setup(); - self.keys = xcb::get_keyboard_mapping(&self.conn, setup.min_keycode(), setup.max_keycode() - setup.min_keycode()).get_reply()?; - self.mods = xcb::get_modifier_mapping(&self.conn).get_reply()?; + ExtensionEvent::Input(xinput::Events::RawMotion(event)) => { + let event = &event.0; + let axis_info = self.valuator_info(event.deviceid.value()) + .ok_or_else(|| format_err!("XInput device unknown for event: {:?}", event))?; + // TODO: could be relative or abs? + let valuators: BTreeMap<_, _> = axis_info.classes.iter().filter_map(|class| match class.data { + xinput::DeviceClassData::Valuator(val) => Some((val.number as usize, val)), + _ => None, + }).collect(); // TODO: do we need to index by val.number? + // TODO: figure out which axis are scroll wheels via ScrollClass - there are multiple entries per valuator? + let mut values = event.axisvalues.iter().zip(&event.axisvalues_raw); + for &valuator_mask in &event.valuator_mask { + for axis in iter_bits(valuator_mask)/*.zip(&mut values)*/ { + let (value, value_raw) = values.next().unwrap(); + let valuator = match valuators.get(&axis) { + Some(val) => val, + _ => continue, + }; + let &xinput::Fp3232 { integral, frac } = value_raw; + let value = (integral as i64) << 32; + let value = if integral < 0 { + value - frac as i64 + } else { + value + frac as i64 + }; + let event = XInputEvent { + time: event.time.value(), + data: match valuator.mode.get() { + xinput::ValuatorMode::Relative => XInputEventData::MouseRelative { + axis: match axis { + // TODO: match by label instead? Are these indexes fixed? + 0 => RelativeAxis::X, + 1 => RelativeAxis::Y, + 2 => RelativeAxis::Wheel, + 3 => RelativeAxis::HorizontalWheel, + _ => continue, + }, + value: (value >> 32) as i32, + }, + xinput::ValuatorMode::Absolute => continue /*XInputEventData::Mouse { + axis: match axis { + 0 => RelativeAxis::X, + 1 => RelativeAxis::Y, + }, + value: (value >> 2) as i32, + }*/, + }, + }; + self.convert_x_events(&event) + } + } }, - xcb::CONFIGURE_NOTIFY => { - let event = unsafe { xcb::cast_event::(event) }; - self.state.width = event.width(); - self.state.height = event.height(); + ExtensionEvent::Input(xinput::Events::DevicePresenceNotify(event)) => { + self.update_valuators().await?; }, - _ => { - info!("unknown X event {}", event.response_type()); + event => { + info!("unknown X event {:?}", event); }, }) } - fn x_button(button: xcb::Button) -> Option { - match button as _ { - xcb::BUTTON_INDEX_1 => Some(Key::ButtonLeft), - xcb::BUTTON_INDEX_2 => Some(Key::ButtonMiddle), - xcb::BUTTON_INDEX_3 => Some(Key::ButtonRight), - xcb::BUTTON_INDEX_4 => Some(Key::ButtonGearUp), - xcb::BUTTON_INDEX_5 => Some(Key::ButtonWheel), // Key::ButtonGearDown - // also map Key::ButtonSide, Key::ButtonExtra? qemu input-linux doesn't support fwd/back, but virtio probably does + fn x_button(button: u8) -> Option { + match button { + 1 => Some(Key::ButtonLeft), + 2 => Some(Key::ButtonMiddle), + 3 => Some(Key::ButtonRight), + 4 => Some(Key::ButtonGearUp), // or wheel y axis + 5 => Some(Key::ButtonWheel), // Key::ButtonGearDown, or wheel y axis + 6 => Some(Key::ButtonBack), // or wheel x axis + 7 => Some(Key::ButtonForward), // or wheel x axis + 8 => Some(Key::ButtonSide), + 9 => Some(Key::ButtonExtra), _ => None, } } - fn x_keycode(key: xcb::Keycode) -> Option { + fn x_keycode(key: u8) -> Option { match Key::from_code(key as _) { Ok(code) => Some(code), Err(..) => None, } } - fn x_keysym(_key: xcb::Keysym) -> Option { + fn x_keysym(_key: u32) -> Option { unimplemented!() } @@ -564,11 +744,14 @@ impl XContext { new as i32 * 0x8000 / dim as i32, )).map(|e| XEvent::Input(e.into()))); }, + XInputEventData::MouseRelative { axis, value } => { + self.event_queue.push(XEvent::Input(RelativeEvent::new(time, axis, value).into())); + }, XInputEventData::Button { pressed, button, state: _ } => { if let Some(button) = Self::x_button(button) { self.event_queue.push(XEvent::Input(Self::key_event(time, button, pressed).into())); } else { - warn!("unknown X button {}", button); + warn!("unknown X button {:?}", button); } }, XInputEventData::Key { pressed, keycode, keysym, state: _ } => { @@ -591,7 +774,7 @@ impl XContext { } } -impl Stream for XContext { +/*impl Stream for XContext { type Item = Result; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { @@ -673,4 +856,4 @@ impl Sink for XContext { } Poll::Ready(Ok(())) } -} +}*/