Skip to content

Instantly share code, notes, and snippets.

@psychon
Created May 15, 2020 16:17
Show Gist options
  • Save psychon/8c12a9b201b56c72c44a2a832ae1dce4 to your computer and use it in GitHub Desktop.
Save psychon/8c12a9b201b56c72c44a2a832ae1dce4 to your computer and use it in GitHub Desktop.
use std::os::unix::net::UnixStream;
use std::os::unix::io::AsRawFd;
use std::io::{Read, Result as IOResult, Write};
use std::error::Error;
use std::convert::TryFrom;
use nix::poll::{PollFd, PollFlags, poll};
use x11rb::protocol::xproto::{self, SetupRequest, Setup};
use x11rb::x11_utils::Serialize;
struct Stream {
stream: UnixStream,
data_read: Vec<u8>,
data_write: Vec<u8>,
}
impl Stream {
// Write some data to the stream
pub fn write(&mut self, data: &[u8]) {
self.data_write.extend(data);
}
// Flush the stream
pub fn flush(&mut self) -> IOResult<()> {
while !self.data_write.is_empty() {
let mut fds = [PollFd::new(self.stream.as_raw_fd(), PollFlags::POLLIN | PollFlags::POLLOUT)];
let res = poll(&mut fds, -1)
// errno still contains the error code
.map_err(|_| std::io::Error::last_os_error())?;
// There can be no timeout since we specified no timeout. There can also be no error since that
// was handled above. Thus, the FD must now be writable.
assert!(res == 1);
let revents = fds[0].revents().unwrap();
if revents.contains(PollFlags::POLLIN) {
self.do_read()?;
}
if revents.contains(PollFlags::POLLOUT) {
let written = self.stream.write(&self.data_write)?;
self.data_write.drain(0..written);
}
}
Ok(())
}
// Internal function: Read more data from the other end
fn read_more(&mut self) -> IOResult<()> {
let mut fds = [PollFd::new(self.stream.as_raw_fd(), PollFlags::POLLIN)];
let res = poll(&mut fds, -1)
// errno still contains the error code
.map_err(|_| std::io::Error::last_os_error())?;
// There can be no timeout since we specified no timeout. There can also be no error since that
// was handled above. Thus, the FD must now be writable.
assert!(res == 1);
assert!(fds[0].revents().unwrap().contains(PollFlags::POLLIN));
self.do_read()
}
// Internal function: The stream is readable and should now be read.
// The data ends up in our internal buffer.
fn do_read(&mut self) -> IOResult<()> {
let mut data = [0; 1024];
let length = self.stream.read(&mut data)?;
self.data_read.extend(&data[..length]);
Ok(())
}
// Read a packet with the given length.
// This blocks until enough data is available.
fn read_length(&mut self, length: usize) -> IOResult<Vec<u8>> {
while self.data_read.len() < length {
self.read_more()?;
}
Ok(self.data_read.drain(..length).collect())
}
}
fn connect() -> IOResult<Stream> {
let stream = UnixStream::connect("/tmp/.X11-unix/X0")?;
stream.set_nonblocking(true)?;
let stream = Stream {
stream,
data_read: Vec::new(),
data_write: Vec::new(),
};
Ok(stream)
}
// Send an (empty) X11 SetupRequest and read the Setup response
fn do_setup(stream: &mut Stream) -> Result<Setup, Box<dyn Error>> {
let request = SetupRequest {
byte_order: 0x6c, // little endian
protocol_major_version: 11,
protocol_minor_version: 0,
authorization_protocol_name: Vec::new(),
authorization_protocol_data: Vec::new(),
};
stream.write(&request.serialize());
stream.flush()?;
// Read the response
while stream.data_read.len() < 8 {
stream.read_more()?;
}
let length = 8 + usize::from(u16::from_ne_bytes([stream.data_read[6], stream.data_read[7]])) * 4;
let setup = stream.read_length(length)?;
Ok(Setup::try_from(&setup[..])?)
}
fn watch_root(stream: &mut Stream, setup: &Setup) -> IOResult<()> {
use xproto::EventMask;
let root = &setup.roots[0].root;
let events = EventMask::PropertyChange | EventMask::FocusChange | EventMask::PointerMotion;
// Send a ChangeWindowAttributes request
stream.write(&[2, 0, 4, 0]);
stream.write(&root.to_ne_bytes());
stream.write(&u32::from(xproto::CW::EventMask).to_ne_bytes());
stream.write(&events.to_ne_bytes());
stream.flush()?;
Ok(())
}
fn main() -> Result<(), Box<dyn Error>> {
let mut stream = connect()?;
println!("Hello, world!");
let setup = do_setup(&mut stream)?;
println!("Got setup");
//println!("{:?}", setup);
watch_root(&mut stream, &setup)?;
loop {
let event = stream.read_length(32)?;
let response_type = event[0] & 0x7f;
match response_type {
0 => match event[1] {
xproto::ACCESS_ERROR => println!("{:?}", xproto::AccessError::try_from(&event[..])?),
xproto::ALLOC_ERROR => println!("{:?}", xproto::AllocError::try_from(&event[..])?),
xproto::ATOM_ERROR => println!("{:?}", xproto::AtomError::try_from(&event[..])?),
xproto::COLORMAP_ERROR => println!("{:?}", xproto::ColormapError::try_from(&event[..])?),
xproto::CURSOR_ERROR => println!("{:?}", xproto::CursorError::try_from(&event[..])?),
xproto::DRAWABLE_ERROR => println!("{:?}", xproto::DrawableError::try_from(&event[..])?),
xproto::FONT_ERROR => println!("{:?}", xproto::FontError::try_from(&event[..])?),
xproto::G_CONTEXT_ERROR => println!("{:?}", xproto::GContextError::try_from(&event[..])?),
xproto::ID_CHOICE_ERROR => println!("{:?}", xproto::IDChoiceError::try_from(&event[..])?),
xproto::IMPLEMENTATION_ERROR => println!("{:?}", xproto::ImplementationError::try_from(&event[..])?),
xproto::LENGTH_ERROR => println!("{:?}", xproto::LengthError::try_from(&event[..])?),
xproto::MATCH_ERROR => println!("{:?}", xproto::MatchError::try_from(&event[..])?),
xproto::NAME_ERROR => println!("{:?}", xproto::NameError::try_from(&event[..])?),
xproto::PIXMAP_ERROR => println!("{:?}", xproto::PixmapError::try_from(&event[..])?),
xproto::REQUEST_ERROR => println!("{:?}", xproto::RequestError::try_from(&event[..])?),
xproto::VALUE_ERROR => println!("{:?}", xproto::ValueError::try_from(&event[..])?),
xproto::WINDOW_ERROR => println!("{:?}", xproto::WindowError::try_from(&event[..])?),
_ => println!("(unknown error) {:?}", event),
},
xproto::BUTTON_PRESS_EVENT => println!("{:?}", xproto::ButtonPressEvent::try_from(&event[..])?),
xproto::BUTTON_RELEASE_EVENT => println!("{:?}", xproto::ButtonReleaseEvent::try_from(&event[..])?),
xproto::CIRCULATE_NOTIFY_EVENT => println!("{:?}", xproto::CirculateNotifyEvent::try_from(&event[..])?),
xproto::CIRCULATE_REQUEST_EVENT => println!("{:?}", xproto::CirculateRequestEvent::try_from(&event[..])?),
xproto::CLIENT_MESSAGE_EVENT => println!("{:?}", xproto::ClientMessageEvent::try_from(&event[..])?),
xproto::COLORMAP_NOTIFY_EVENT => println!("{:?}", xproto::ColormapNotifyEvent::try_from(&event[..])?),
xproto::CONFIGURE_NOTIFY_EVENT => println!("{:?}", xproto::ConfigureNotifyEvent::try_from(&event[..])?),
xproto::CONFIGURE_REQUEST_EVENT => println!("{:?}", xproto::ConfigureRequestEvent::try_from(&event[..])?),
xproto::CREATE_NOTIFY_EVENT => println!("{:?}", xproto::CreateNotifyEvent::try_from(&event[..])?),
xproto::DESTROY_NOTIFY_EVENT => println!("{:?}", xproto::DestroyNotifyEvent::try_from(&event[..])?),
xproto::ENTER_NOTIFY_EVENT => println!("{:?}", xproto::EnterNotifyEvent::try_from(&event[..])?),
xproto::EXPOSE_EVENT => println!("{:?}", xproto::ExposeEvent::try_from(&event[..])?),
xproto::FOCUS_IN_EVENT => println!("{:?}", xproto::FocusInEvent::try_from(&event[..])?),
xproto::FOCUS_OUT_EVENT => println!("{:?}", xproto::FocusOutEvent::try_from(&event[..])?),
xproto::GRAPHICS_EXPOSURE_EVENT => println!("{:?}", xproto::GraphicsExposureEvent::try_from(&event[..])?),
xproto::GRAVITY_NOTIFY_EVENT => println!("{:?}", xproto::GravityNotifyEvent::try_from(&event[..])?),
xproto::KEY_PRESS_EVENT => println!("{:?}", xproto::KeyPressEvent::try_from(&event[..])?),
xproto::KEY_RELEASE_EVENT => println!("{:?}", xproto::KeyReleaseEvent::try_from(&event[..])?),
xproto::KEYMAP_NOTIFY_EVENT => println!("{:?}", xproto::KeymapNotifyEvent::try_from(&event[..])?),
xproto::LEAVE_NOTIFY_EVENT => println!("{:?}", xproto::LeaveNotifyEvent::try_from(&event[..])?),
xproto::MAP_NOTIFY_EVENT => println!("{:?}", xproto::MapNotifyEvent::try_from(&event[..])?),
xproto::MAP_REQUEST_EVENT => println!("{:?}", xproto::MapRequestEvent::try_from(&event[..])?),
xproto::MAPPING_NOTIFY_EVENT => println!("{:?}", xproto::MappingNotifyEvent::try_from(&event[..])?),
xproto::MOTION_NOTIFY_EVENT => println!("{:?}", xproto::MotionNotifyEvent::try_from(&event[..])?),
xproto::NO_EXPOSURE_EVENT => println!("{:?}", xproto::NoExposureEvent::try_from(&event[..])?),
xproto::PROPERTY_NOTIFY_EVENT => println!("{:?}", xproto::PropertyNotifyEvent::try_from(&event[..])?),
xproto::REPARENT_NOTIFY_EVENT => println!("{:?}", xproto::ReparentNotifyEvent::try_from(&event[..])?),
xproto::RESIZE_REQUEST_EVENT => println!("{:?}", xproto::ResizeRequestEvent::try_from(&event[..])?),
xproto::SELECTION_CLEAR_EVENT => println!("{:?}", xproto::SelectionClearEvent::try_from(&event[..])?),
xproto::SELECTION_NOTIFY_EVENT => println!("{:?}", xproto::SelectionNotifyEvent::try_from(&event[..])?),
xproto::SELECTION_REQUEST_EVENT => println!("{:?}", xproto::SelectionRequestEvent::try_from(&event[..])?),
xproto::UNMAP_NOTIFY_EVENT => println!("{:?}", xproto::UnmapNotifyEvent::try_from(&event[..])?),
xproto::VISIBILITY_NOTIFY_EVENT => println!("{:?}", xproto::VisibilityNotifyEvent::try_from(&event[..])?),
_ => println!("(unknown) {:?}", event),
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment