1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
|
// This example shows how to use the record extension.
//
// The short version is: You do not want to use the record extension.
//
// The long version is: It's ugly and here is how it works.
//
// If you want to learn more about the record extension, it is recommended to read
// https://www.x.org/releases/X11R7.6/doc/recordproto/record.html
//
// This example is based on
// https://github.com/nibrahim/showkeys/blob/master/tests/record-example.c, which is GPLv3 and
// contains no copyright information. According to the git history, it was written by
// Noufal Ibrahim <noufal@nibrahim.net.in> in 2011.
use std::convert::TryFrom;
use x11rb::connection::Connection;
use x11rb::connection::RequestConnection;
use x11rb::errors::ParseError;
use x11rb::protocol::record::{self, ConnectionExt as _};
use x11rb::protocol::xproto;
use x11rb::wrapper::ConnectionExt;
use x11rb::x11_utils::TryParse;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// From https://www.x.org/releases/X11R7.6/doc/recordproto/record.html:
// "The typical communication model for a recording client is to open two connections to the
// server and use one for RC control and the other for reading protocol data."
let (ctrl_conn, _) = connect(None)?;
let (data_conn, _) = connect(None)?;
// Check if the record extension is supported.
if ctrl_conn
.extension_information(record::X11_EXTENSION_NAME)?
.is_none()
{
eprintln!("The X11 server does not support the RECORD extension");
return Ok(());
}
let ver = ctrl_conn
.record_query_version(
record::X11_XML_VERSION.0 as _,
record::X11_XML_VERSION.1 as _,
)?
.reply()?;
println!(
"requested RECORD extension version {:?}, server supports {:?}",
record::X11_XML_VERSION,
(ver.major_version, ver.minor_version)
);
// Set up a recording context
let rc = ctrl_conn.generate_id()?;
let empty = record::Range8 { first: 0, last: 0 };
let empty_ext = record::ExtRange {
major: empty,
minor: record::Range16 { first: 0, last: 0 },
};
let range = record::Range {
core_requests: empty,
core_replies: empty,
ext_requests: empty_ext,
ext_replies: empty_ext,
delivered_events: empty,
device_events: record::Range8 {
// We want notification of core X11 events between key press and motion notify
first: xproto::KEY_PRESS_EVENT,
last: xproto::MOTION_NOTIFY_EVENT,
},
errors: empty,
client_started: false,
client_died: false,
};
ctrl_conn
.record_create_context(rc, 0, &[record::CS::ALL_CLIENTS.into()], &[range])?
.check()?;
// Apply a timeout if we are requested to do so.
match std::env::var("X11RB_EXAMPLE_TIMEOUT")
.ok()
.and_then(|str| str.parse().ok())
{
None => {}
Some(timeout) => {
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_secs(timeout));
ctrl_conn.record_disable_context(rc).unwrap();
ctrl_conn.sync().unwrap();
});
}
}
// The above check() makes sure that the server already handled the CreateContext request.
// Alternatively, we could do ctrl_conn.sync() here for the same effect.
// We now switch to using "the other" connection.
// FIXME: These constants should be added to the XML
const START_OF_DATA: u8 = 4;
const RECORD_FROM_SERVER: u8 = 0;
for reply in data_conn.record_enable_context(rc)? {
let reply = reply?;
if reply.client_swapped {
println!("Byte swapped clients are unsupported");
} else if reply.category == RECORD_FROM_SERVER {
let mut remaining = &reply.data[..];
let mut should_exit = false;
while !remaining.is_empty() {
let (r, exit) = print_reply_data(&reply.data)?;
remaining = r;
if exit {
should_exit = true;
}
}
if should_exit {
break;
}
} else if reply.category == START_OF_DATA {
println!("Press Escape to exit...");
} else {
println!("Got a reply with an unsupported category: {:?}", reply);
}
}
Ok(())
}
// Print a single reply data packet and return the remaining data. When escape is pressed, true is
// also returned to indicate that we should exit.
fn print_reply_data(data: &[u8]) -> Result<(&[u8], bool), ParseError> {
match data[0] {
xproto::KEY_PRESS_EVENT => {
let (event, remaining) = xproto::KeyPressEvent::try_parse(data)?;
println!("key press: {:?}", event);
Ok((remaining, false))
}
xproto::KEY_RELEASE_EVENT => {
let (event, remaining) = xproto::KeyReleaseEvent::try_parse(data)?;
println!("key release: {:?}", event);
Ok((remaining, event.detail == 9))
}
xproto::BUTTON_PRESS_EVENT => {
let (event, remaining) = xproto::ButtonPressEvent::try_parse(data)?;
println!("button press: {:?}", event);
Ok((remaining, false))
}
xproto::BUTTON_RELEASE_EVENT => {
let (event, remaining) = xproto::ButtonReleaseEvent::try_parse(data)?;
println!("button release: {:?}", event);
Ok((remaining, false))
}
xproto::MOTION_NOTIFY_EVENT => {
let (event, remaining) = xproto::MotionNotifyEvent::try_parse(data)?;
println!("motion notify: {:?}", event);
Ok((remaining, false))
}
0 => {
// This is a reply, we compute its length as follows
let (length, _) = u32::try_parse(&data[4..])?;
let length = usize::try_from(length).unwrap() * 4 + 32;
println!("unparsed reply: {:?}", &data[..length]);
Ok((&data[length..], false))
}
_ => {
// Error or event, they always have length 32
// TODO: What about XGE events?
println!("unparsed error/event: {:?}", &data[..32]);
Ok((&data[32..], false))
}
}
}
include!("integration_test_util/connect.rs");
|