Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
// This program shows error handling. It causes some X11 errors and shows where they end up.
//
// This program also serves as a (bad) integration test. To verify that the expected behaviour
// occurs, it needs to do some extra work. In particular, all calls to .sequence_number() are not
// needed for the example, but only for the test. Where needed, extra comments indicate how the
// short version of each case would look.
extern crate x11rb;
use x11rb::connection::Connection;
use x11rb::errors::ReplyError;
use x11rb::protocol::xproto::ConnectionExt as _;
use x11rb::protocol::Event;
use x11rb::wrapper::ConnectionExt as _;
const INVALID_WINDOW: u32 = 0;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (conn, _) = connect(None).unwrap();
// For requests with responses, there are four possibilities:
// We can just normally get the response or error to the request via reply()
let res = conn.get_geometry(INVALID_WINDOW)?.reply();
assert!(res.is_err());
// We can decide that we do not care about the response and also do not care about errors via
// discard_reply_and_errors()
conn.get_geometry(INVALID_WINDOW)?
.discard_reply_and_errors();
// Errors can show up as 'events' in wait_for_event() via reply_unchecked()
let cookie = conn.get_geometry(INVALID_WINDOW)?;
let seq1 = cookie.sequence_number();
let res = cookie.reply_unchecked()?;
assert!(res.is_none());
// The short version of the above would be:
// let res = conn.get_geomtry(INVALID_WINDOW)?.reply_unchecked()?;
// Errors can show up as 'events' in wait_for_event() by just dropping the cookie
let cookie = conn.get_geometry(INVALID_WINDOW)?;
let seq2 = cookie.sequence_number();
drop(cookie);
// The short version of the above would be:
// drop(conn.get_geometry(INVALID_WINDOW)?);
// For requests without responses, there are three possibilities
// We can check for errors explicitly
match conn.destroy_window(INVALID_WINDOW)?.check() {
Err(ReplyError::X11Error(_)) => {}
e => panic!("{e:?} unexpected"),
}
// We can silently ignore the error
conn.destroy_window(INVALID_WINDOW)?.ignore_error();
// An error can be handled as an event.
let cookie = conn.destroy_window(INVALID_WINDOW)?;
let seq3 = cookie.sequence_number();
drop(cookie);
// The short version of the above would be:
// drop(conn.destroy_window(INVALID_WINDOW)?);
// Synchronise with the server so that all errors are already received.
conn.sync()?;
// Now check if the things above really caused errors. This is the part that is supposed to
// turn this example into a (bad) integration test.
for &seq in &[seq1, seq2, seq3] {
let (event, seq2) = conn.wait_for_event_with_sequence()?;
match event {
Event::Error(_) => {}
event => panic!("Unexpectedly got {event:?} instead of an X11 error"),
}
assert_eq!(seq, seq2);
}
assert!(conn.poll_for_event()?.is_none());
println!("Done");
Ok(())
}
include!("integration_test_util/connect.rs");

289
vendor/x11rb/examples/display_ppm.rs vendored Normal file
View File

@@ -0,0 +1,289 @@
// This example reads a .ppm file and displays the image on screen. It shows how to work
// with images.
use x11rb::connection::Connection;
use x11rb::errors::ReplyOrIdError;
use x11rb::image::{ColorComponent, Image, PixelLayout};
use x11rb::protocol::xproto::{
AtomEnum, ConnectionExt, CreateGCAux, CreateWindowAux, PropMode, Screen, VisualClass, Visualid,
Window, WindowClass,
};
use x11rb::protocol::Event;
use x11rb::wrapper::ConnectionExt as _;
x11rb::atom_manager! {
Atoms: AtomsCookie {
WM_PROTOCOLS,
WM_DELETE_WINDOW,
}
}
/// Create a window with the given image as background.
fn create_window(
conn: &impl Connection,
screen: &Screen,
atoms: &Atoms,
image: &Image,
) -> Result<Window, ReplyOrIdError> {
let win_id = conn.generate_id()?;
let pixmap_id = conn.generate_id()?;
let gc_id = conn.generate_id()?;
conn.create_gc(
gc_id,
screen.root,
&CreateGCAux::default().graphics_exposures(0),
)?;
conn.create_pixmap(
screen.root_depth,
pixmap_id,
screen.root,
image.width(),
image.height(),
)?;
image.put(conn, pixmap_id, gc_id, 0, 0)?;
conn.free_gc(gc_id)?;
conn.create_window(
screen.root_depth,
win_id,
screen.root,
0,
0,
image.width(),
image.height(),
0,
WindowClass::INPUT_OUTPUT,
0,
&CreateWindowAux::default().background_pixmap(pixmap_id),
)?;
conn.free_pixmap(pixmap_id)?;
conn.change_property32(
PropMode::REPLACE,
win_id,
atoms.WM_PROTOCOLS,
AtomEnum::ATOM,
&[atoms.WM_DELETE_WINDOW],
)?;
Ok(win_id)
}
/// Check that the given visual is "as expected" (pixel values are 0xRRGGBB with RR/GG/BB being the
/// colors). Otherwise, this exits the process.
fn check_visual(screen: &Screen, id: Visualid) -> PixelLayout {
// Find the information about the visual and at the same time check its depth.
let visual_info = screen
.allowed_depths
.iter()
.filter_map(|depth| {
let info = depth.visuals.iter().find(|depth| depth.visual_id == id);
info.map(|info| (depth.depth, info))
})
.next();
let (depth, visual_type) = match visual_info {
Some(info) => info,
None => {
eprintln!("Did not find the root visual's description?!");
std::process::exit(1);
}
};
// Check that the pixels have red/green/blue components that we can set directly.
match visual_type.class {
VisualClass::TRUE_COLOR | VisualClass::DIRECT_COLOR => {}
_ => {
eprintln!("The root visual is not true / direct color, but {visual_type:?}");
std::process::exit(1);
}
}
let result = PixelLayout::from_visual_type(*visual_type)
.expect("The server sent a malformed visual type");
assert_eq!(result.depth(), depth);
result
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load the image
let image = match std::env::args_os().nth(1) {
None => {
eprintln!(
"Expected a file name of a PPM as argument, using a built-in default image instead"
);
ppm_parser::parse_ppm_bytes(&BUILTIN_IMAGE)?
}
Some(arg) => ppm_parser::parse_ppm_file(&arg)?,
};
let (conn, screen_num) = connect(None)?;
// The following is only needed for start_timeout_thread(), which is used for 'tests'
let conn1 = std::sync::Arc::new(conn);
let conn = &*conn1;
let screen = &conn.setup().roots[screen_num];
let pixel_layout = check_visual(screen, screen.root_visual);
// Convert the image from the PPM format into the server's native format.
let ppm_layout = PixelLayout::new(
ColorComponent::new(8, 16)?,
ColorComponent::new(8, 8)?,
ColorComponent::new(8, 0)?,
);
let image = image.reencode(ppm_layout, pixel_layout, conn.setup())?;
let atoms = Atoms::new(conn)?.reply()?;
let win_id = create_window(conn, screen, &atoms, &image)?;
conn.map_window(win_id)?;
util::start_timeout_thread(conn1.clone(), win_id);
conn.flush()?;
loop {
let event = conn.wait_for_event().unwrap();
match event {
Event::ClientMessage(event) => {
let data = event.data.as_data32();
if event.format == 32 && event.window == win_id && data[0] == atoms.WM_DELETE_WINDOW
{
println!("Window was asked to close");
return Ok(());
}
}
Event::Error(err) => println!("Got an unexpected error: {err:?}"),
ev => println!("Got an unknown event: {ev:?}"),
}
}
}
mod ppm_parser {
use std::ffi::OsStr;
use std::io::{Error as IOError, ErrorKind, Read, Result as IOResult};
use x11rb::image::{BitsPerPixel, Image, ImageOrder, ScanlinePad};
fn make_io_error(text: &'static str) -> IOError {
IOError::new(ErrorKind::Other, text)
}
/// Read until the next b'\n'.
fn read_to_end_of_line(input: &mut impl Read) -> IOResult<()> {
let mut byte = [0; 1];
loop {
input.read_exact(&mut byte)?;
if byte[0] == b'\n' {
return Ok(());
}
}
}
/// Read a decimal number from the input.
fn read_decimal(input: &mut impl Read) -> IOResult<u16> {
let mut byte = [0; 1];
// Skip leading whitespace and comments
loop {
input.read_exact(&mut byte)?;
match byte[0] {
b' ' | b'\t' | b'\r' => {}
// Comment, skip a whole line
b'#' => read_to_end_of_line(input)?,
_ => break,
}
}
// Now comes a number
if !byte[0].is_ascii_digit() {
return Err(make_io_error("Failed parsing a number"));
}
let mut result: u16 = 0;
while byte[0].is_ascii_digit() {
let value = u16::from(byte[0] - b'0');
result = result
.checked_mul(10)
.map(|result| result + value)
.ok_or_else(|| make_io_error("Overflow while parsing number"))?;
input.read_exact(&mut byte)?;
}
// After the number, there should be some whitespace.
if byte[0].is_ascii_whitespace() {
Ok(result)
} else {
Err(make_io_error("Unexpected character in header"))
}
}
fn parse_ppm(input: &mut impl Read) -> IOResult<Image<'static>> {
let mut header = [0; 2];
input.read_exact(&mut header)?;
if header != *b"P6" {
return Err(make_io_error("Incorrect file header"));
}
read_to_end_of_line(input)?;
let width = read_decimal(input)?;
let height = read_decimal(input)?;
let max = read_decimal(input)?;
if max != 255 {
eprintln!("Image declares a max pixel value of {max}, but I expected 255.");
eprintln!("Something will happen...?");
}
let mut image = Image::allocate(
width,
height,
ScanlinePad::Pad8,
24,
BitsPerPixel::B24,
ImageOrder::MsbFirst,
);
input.read_exact(image.data_mut())?;
Ok(image)
}
pub fn parse_ppm_bytes(bytes: &[u8]) -> IOResult<Image<'static>> {
use std::io::Cursor;
parse_ppm(&mut Cursor::new(bytes))
}
pub fn parse_ppm_file(file_name: &OsStr) -> IOResult<Image<'static>> {
use std::fs::File;
use std::io::BufReader;
parse_ppm(&mut BufReader::new(File::open(file_name)?))
}
}
// Simple builtin PPM that is used if none is provided on the command line
#[rustfmt::skip]
const BUILTIN_IMAGE: [u8; 35] = [
b'P', b'6', b'\n',
// width and height
b'4', b' ', b'2', b'\n',
b'2', b'5', b'5', b'\n',
// Black pixel
0x00, 0x00, 0x00,
// red pixel
0xff, 0x00, 0x00,
// green pixel
0x00, 0xff, 0x00,
// blue pixel
0x00, 0x00, 0xff,
// white pixel
0xff, 0xff, 0xff,
// cyan pixel
0x00, 0xff, 0xff,
// magenta pixel
0xff, 0x00, 0xff,
// yellow pixel
0xff, 0xff, 0x00,
];
include!("integration_test_util/connect.rs");
include!("integration_test_util/util.rs");

88
vendor/x11rb/examples/generic_events.rs vendored Normal file
View File

@@ -0,0 +1,88 @@
// This example tests support for generic events (XGE). It generates a window and uses the PRESENT
// extension to cause an XGE event to be sent.
use std::process::exit;
use x11rb::connection::{Connection as _, RequestConnection as _};
use x11rb::protocol::xproto::{
ConfigureWindowAux, ConnectionExt as _, CreateWindowAux, WindowClass,
};
use x11rb::protocol::{present, Event};
use x11rb::COPY_DEPTH_FROM_PARENT;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (conn, screen_num) = connect(None)?;
let screen = &conn.setup().roots[screen_num];
if conn
.extension_information(present::X11_EXTENSION_NAME)?
.is_none()
{
eprintln!("Present extension is not supported");
exit(1);
}
// Create a window
let win_id = conn.generate_id()?;
let win_aux = CreateWindowAux::new().background_pixel(screen.white_pixel);
conn.create_window(
COPY_DEPTH_FROM_PARENT,
win_id,
screen.root,
0,
0,
10,
10,
0,
WindowClass::INPUT_OUTPUT,
0,
&win_aux,
)?;
// Ask for present ConfigureNotify events
let event_id = conn.generate_id()?;
present::select_input(
&conn,
event_id,
win_id,
present::EventMask::CONFIGURE_NOTIFY,
)?;
// Cause an event
conn.configure_window(win_id, &ConfigureWindowAux::new().width(20))?;
// Wait for the event
conn.flush()?;
let event = conn.wait_for_event()?;
// Now check that the event really is what we wanted to get
let event = match event {
Event::PresentConfigureNotify(event) => event,
other => panic!("Unexpected event {other:?}"),
};
println!(
"Got a Present ConfigureNotify event for event ID 0x{:x} and window 0x{:x}.",
event.event, event.window
);
println!(
"x={}, y={}, width={}, height={}, off_x={}, off_y={}, pixmap_width={}, pixmap_height={}, \
pixmap_flags={:x}",
event.x,
event.y,
event.width,
event.height,
event.off_x,
event.off_y,
event.pixmap_width,
event.pixmap_height,
event.pixmap_flags,
);
assert_eq!(
(20, 10, 0),
(event.pixmap_width, event.pixmap_height, event.pixmap_flags)
);
Ok(())
}
include!("integration_test_util/connect.rs");

277
vendor/x11rb/examples/hypnomoire.rs vendored Normal file
View File

@@ -0,0 +1,277 @@
// This is a (rough) port of hypnomoire from the xcb-demo repository.
// The original file has Copyright (C) 2001-2002 Bart Massey and Jamey Sharp and is licensed under
// a MIT license.
extern crate x11rb;
use std::f64::consts::PI;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use x11rb::connection::Connection;
use x11rb::errors::{ReplyError, ReplyOrIdError};
use x11rb::protocol::xproto::*;
use x11rb::protocol::Event;
use x11rb::wrapper::ConnectionExt as _;
use x11rb::COPY_DEPTH_FROM_PARENT;
/// Lag angle for the follow line
const LAG: f64 = 0.3;
/// Frames per second
const FRAME_RATE: u64 = 10;
/// Number of windows to show
const WINS: usize = 3;
#[derive(Default)]
struct WindowState {
window: Window,
pixmap: Pixmap,
angle_velocity: f64,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (conn, screen_num) = connect(None)?;
let conn = Arc::new(conn);
let screen = &conn.setup().roots[screen_num];
let white = conn.generate_id()?;
let black = conn.generate_id()?;
conn.create_gc(
white,
screen.root,
&CreateGCAux::new()
.graphics_exposures(0)
.foreground(screen.white_pixel),
)?;
conn.create_gc(
black,
screen.root,
&CreateGCAux::new()
.graphics_exposures(0)
.foreground(screen.black_pixel),
)?;
let windows: Vec<_> = (0..WINS)
.map(|_| Arc::new(Mutex::new(WindowState::default())))
.collect();
for win in windows.iter() {
let conn2 = Arc::clone(&conn);
let win = Arc::clone(win);
thread::spawn(move || run(conn2, win, screen_num, white, black));
}
event_thread(conn, windows, white)?;
Ok(())
}
fn run<C: Connection>(
conn: Arc<C>,
window_state: Arc<Mutex<WindowState>>,
screen_num: usize,
white: Gcontext,
black: Gcontext,
) -> Result<(), ReplyOrIdError> {
let wm_protocols = conn.intern_atom(false, b"WM_PROTOCOLS")?;
let wm_delete_window = conn.intern_atom(false, b"WM_DELETE_WINDOW")?;
let wm_protocols = wm_protocols.reply()?.atom;
let wm_delete_window = wm_delete_window.reply()?.atom;
let screen = &conn.setup().roots[screen_num];
let default_size = 300;
let pixmap = conn.generate_id()?;
let window = conn.generate_id()?;
{
let mut guard = window_state.lock().unwrap();
guard.pixmap = pixmap;
guard.window = window;
guard.angle_velocity = 0.05;
}
conn.create_window(
COPY_DEPTH_FROM_PARENT,
window,
screen.root,
0, // x
0, // y
default_size, // width
default_size, // height
0,
WindowClass::INPUT_OUTPUT,
screen.root_visual,
&CreateWindowAux::new()
.background_pixel(screen.white_pixel)
.event_mask(
EventMask::BUTTON_RELEASE | EventMask::EXPOSURE | EventMask::STRUCTURE_NOTIFY,
)
.do_not_propogate_mask(EventMask::BUTTON_PRESS),
)?;
conn.change_property32(
PropMode::REPLACE,
window,
wm_protocols,
AtomEnum::ATOM,
&[wm_delete_window],
)?;
conn.map_window(window)?;
conn.create_pixmap(
screen.root_depth,
pixmap,
window,
default_size,
default_size,
)?;
let rect = Rectangle {
x: 0,
y: 0,
width: default_size,
height: default_size,
};
conn.poly_fill_rectangle(pixmap, white, &[rect])?;
let mut theta: f64 = 0.0;
let radius = f64::from(default_size) + 1.0;
loop {
let guard = window_state.lock().unwrap();
let (center_x, center_y) = (default_size as i16 / 2, default_size as i16 / 2);
let (sin, cos) = theta.sin_cos();
let (x, y) = ((radius * cos) as i16, (radius * sin) as i16);
let lines = [
Point {
x: center_x,
y: center_y,
},
Point { x, y },
];
conn.poly_line(CoordMode::PREVIOUS, pixmap, black, &lines)?;
let (sin, cos) = (theta + LAG).sin_cos();
let (x, y) = ((radius * cos) as i16, (radius * sin) as i16);
let lines = [
Point {
x: center_x,
y: center_y,
},
Point { x, y },
];
conn.poly_line(CoordMode::PREVIOUS, pixmap, white, &lines)?;
conn.copy_area(
pixmap,
window,
white,
0,
0,
0,
0,
default_size,
default_size,
)?;
conn.flush()?;
theta += guard.angle_velocity;
while theta > 2.0 * PI {
theta -= 2.0 * PI;
}
while theta < 0.0 {
theta += 2.0 * PI;
}
drop(guard);
thread::sleep(Duration::from_micros(1_000_000 / FRAME_RATE));
}
}
fn event_thread<C>(
conn_arc: Arc<C>,
windows: Vec<Arc<Mutex<WindowState>>>,
white: Gcontext,
) -> Result<(), ReplyError>
where
C: Connection + Send + Sync + 'static,
{
let mut first_window_mapped = false;
let conn = &*conn_arc;
loop {
let event = conn.wait_for_event()?;
match event {
Event::Expose(event) => {
if let Some(state) = find_window_by_id(&windows, event.window) {
let state = state.lock().unwrap();
conn.copy_area(
state.pixmap,
state.window,
white,
event.x as _,
event.y as _,
event.x as _,
event.y as _,
event.width,
event.height,
)?;
if event.count == 0 {
conn.flush()?;
}
} else {
eprintln!("Expose on unknown window!");
}
}
Event::ButtonRelease(event) => {
if let Some(state) = find_window_by_id(&windows, event.event) {
let mut state = state.lock().unwrap();
match event.detail {
// Button 1 is left mouse button
1 => state.angle_velocity *= -1.0,
// Buttons 4 and 5 are mouse wheel
4 => state.angle_velocity += 0.001,
5 => state.angle_velocity -= 0.001,
_ => {}
}
} else {
eprintln!("ButtonRelease on unknown window!");
}
}
Event::MapNotify(event) => {
if !first_window_mapped {
first_window_mapped = true;
util::start_timeout_thread(conn_arc.clone(), event.window);
}
}
Event::ClientMessage(_) => {
// We simply assume that this is a message to close. Since we are the main thread,
// everything closes when we exit.
return Ok(());
}
Event::Error(err) => {
eprintln!("Got an X11 error: {err:?}");
}
_ => {}
}
}
}
fn find_window_by_id(
windows: &[Arc<Mutex<WindowState>>],
window: Window,
) -> Option<&Arc<Mutex<WindowState>>> {
windows.iter().find(|state| {
state
.lock()
.map(|state| state.window == window)
.unwrap_or(false)
})
}
include!("integration_test_util/util.rs");
include!("integration_test_util/connect.rs");

View File

@@ -0,0 +1,24 @@
/// Establish a new connection to an X11 server.
///
/// Returns a `XCBConnection` if `allow-unsafe-code`, otherwise returns a `RustConnection`.
/// This function is meant to test code with both connection types. Production code
/// usually wants to use `x11rb::connect`, `x11rb::rust_connection::RustConnection::connect`
/// or `x11rb::xcb_ffi::XCBConnection::connect`.
pub fn connect(
dpy_name: Option<&str>,
) -> Result<(impl x11rb::connection::Connection + Send + Sync, usize), x11rb::errors::ConnectError>
{
#[cfg(feature = "allow-unsafe-code")]
{
let dpy_name = dpy_name
.map(std::ffi::CString::new)
.transpose()
.map_err(|_| x11rb::errors::DisplayParsingError::Unknown)?;
let dpy_name = dpy_name.as_deref();
x11rb::xcb_ffi::XCBConnection::connect(dpy_name)
}
#[cfg(not(feature = "allow-unsafe-code"))]
{
x11rb::rust_connection::RustConnection::connect(dpy_name)
}
}

View File

@@ -0,0 +1,55 @@
// As far as I can see, I cannot easily share code between different examples. The following code
// is used by several examples to react to the $X11RB_EXAMPLE_TIMEOUT variable. This code is
// include!()d in the examples
mod util {
use std::env;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use x11rb::connection::Connection;
use x11rb::protocol::xproto::{
ClientMessageEvent, ConnectionExt as _, EventMask, Window,
};
pub fn start_timeout_thread<C>(conn: Arc<C>, window: Window)
where
C: Connection + Send + Sync + 'static,
{
let timeout = match env::var("X11RB_EXAMPLE_TIMEOUT")
.ok()
.and_then(|str| str.parse().ok())
{
None => return,
Some(timeout) => timeout,
};
thread::spawn(move || {
let wm_protocols = conn.intern_atom(false, b"WM_PROTOCOLS").unwrap();
let wm_delete_window = conn.intern_atom(false, b"WM_DELETE_WINDOW").unwrap();
thread::sleep(Duration::from_secs(timeout));
let event = ClientMessageEvent::new(
32,
window,
wm_protocols.reply().unwrap().atom,
[wm_delete_window.reply().unwrap().atom, 0, 0, 0, 0],
);
if let Err(err) = conn.send_event(false, window, EventMask::NO_EVENT, event) {
eprintln!("Error while sending event: {err:?}");
}
if let Err(err) = conn.send_event(
false,
window,
EventMask::SUBSTRUCTURE_REDIRECT,
event,
) {
eprintln!("Error while sending event: {err:?}");
}
conn.flush().unwrap();
});
}
}

50
vendor/x11rb/examples/list_fonts.rs vendored Normal file
View File

@@ -0,0 +1,50 @@
// This program should produce output identical to `xlsfonts -lu`.
extern crate x11rb;
use x11rb::protocol::xproto::{ConnectionExt, FontDraw};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (conn, _) = connect(None)?;
println!("DIR MIN MAX EXIST DFLT PROP ASC DESC NAME");
for reply in conn.list_fonts_with_info(u16::MAX, b"*")? {
let reply = reply?;
let dir = if reply.draw_direction == FontDraw::LEFT_TO_RIGHT {
"-->"
} else if reply.draw_direction == FontDraw::RIGHT_TO_LEFT {
"<--"
} else {
"???"
};
let (min, max, indicator) = if reply.min_byte1 == 0 && reply.max_byte1 == 0 {
(reply.min_char_or_byte2, reply.max_char_or_byte2, ' ')
} else {
(u16::from(reply.min_byte1), u16::from(reply.max_byte1), '*')
};
let all = if reply.all_chars_exist { "all" } else { "some" };
let name = String::from_utf8_lossy(&reply.name);
println!(
"{} {}{:3} {}{:3} {:>5} {:4} {:4} {:3} {:4} {}",
dir,
indicator,
min,
indicator,
max,
all,
reply.default_char,
reply.properties.len(),
reply.font_ascent,
reply.font_descent,
name
);
}
Ok(())
}
include!("integration_test_util/connect.rs");

171
vendor/x11rb/examples/record.rs vendored Normal file
View File

@@ -0,0 +1,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");

175
vendor/x11rb/examples/shared_memory.rs vendored Normal file
View File

@@ -0,0 +1,175 @@
extern crate x11rb;
use std::fs::{remove_file, File, OpenOptions};
use std::io::{Result as IOResult, Write};
use std::os::unix::io::AsRawFd;
use std::ptr::null_mut;
use libc::{mmap, MAP_FAILED, MAP_SHARED, PROT_READ, PROT_WRITE};
use rustix::fd::OwnedFd;
use x11rb::connection::Connection;
use x11rb::errors::{ConnectionError, ReplyError, ReplyOrIdError};
use x11rb::protocol::shm::{self, ConnectionExt as _};
use x11rb::protocol::xproto::{self, ImageFormat, PixmapWrapper};
const TEMP_FILE_CONTENT: [u8; 8] = [0x00, 0x01, 0x02, 0x03, 0xff, 0xfe, 0xfd, 0xfc];
/// Get the supported SHM version from the X11 server
fn check_shm_version<C: Connection>(conn: &C) -> Result<Option<(u16, u16)>, ReplyError> {
if conn
.extension_information(shm::X11_EXTENSION_NAME)?
.is_none()
{
return Ok(None);
}
let shm_version = conn.shm_query_version()?.reply()?;
Ok(Some((shm_version.major_version, shm_version.minor_version)))
}
/// Get the bytes describing the first pixel at the given offset of the given shared memory segment
/// (interpreted in the screen's root_visual)
fn get_shared_memory_content_at_offset<C: Connection>(
conn: &C,
screen: &xproto::Screen,
shmseg: shm::Seg,
offset: u32,
) -> Result<Vec<u8>, ReplyOrIdError> {
let width = match screen.root_depth {
24 => 1,
16 => 2,
8 => 4,
_ => panic!("I do not know how to handle depth {}", screen.root_depth),
};
let pixmap = conn.generate_id()?;
conn.shm_create_pixmap(
pixmap,
screen.root,
width,
1,
screen.root_depth,
shmseg,
offset,
)?;
let pixmap = PixmapWrapper::for_pixmap(conn, pixmap);
let image = xproto::get_image(
conn,
ImageFormat::Z_PIXMAP,
pixmap.pixmap(),
0,
0,
width,
1,
!0,
)?
.reply()?;
Ok(image.data)
}
fn use_shared_mem<C: Connection>(
conn: &C,
screen_num: usize,
shmseg: shm::Seg,
) -> Result<(), ReplyOrIdError> {
let screen = &conn.setup().roots[screen_num];
let content = get_shared_memory_content_at_offset(conn, screen, shmseg, 0)?;
assert_eq!(content[..], TEMP_FILE_CONTENT[0..4]);
let content = get_shared_memory_content_at_offset(conn, screen, shmseg, 4)?;
assert_eq!(content[..], TEMP_FILE_CONTENT[4..8]);
Ok(())
}
/// Make a temporary file
fn make_file() -> IOResult<File> {
let file_name = "shared_memory.bin";
let mut file = OpenOptions::new()
.create(true)
.read(true)
.write(true)
.truncate(true)
.open(file_name)?;
file.write_all(&TEMP_FILE_CONTENT)?;
remove_file(file_name)?;
Ok(file)
}
fn send_fd<C: Connection>(conn: &C, screen_num: usize, file: File) -> Result<(), ReplyOrIdError> {
let shmseg = conn.generate_id()?;
conn.shm_attach_fd(shmseg, OwnedFd::from(file), false)?;
use_shared_mem(conn, screen_num, shmseg)?;
conn.shm_detach(shmseg)?;
Ok(())
}
fn receive_fd<C: Connection>(conn: &C, screen_num: usize) -> Result<(), ReplyOrIdError> {
let shmseg = conn.generate_id()?;
let segment_size = TEMP_FILE_CONTENT.len() as _;
let reply = conn
.shm_create_segment(shmseg, segment_size, false)?
.reply()?;
let shm::CreateSegmentReply { shm_fd, .. } = reply;
let addr = unsafe {
mmap(
null_mut(),
segment_size as _,
PROT_READ | PROT_WRITE,
MAP_SHARED,
shm_fd.as_raw_fd(),
0,
)
};
if addr == MAP_FAILED {
conn.shm_detach(shmseg)?;
return Err(ConnectionError::InsufficientMemory.into());
}
unsafe {
let slice = std::slice::from_raw_parts_mut(addr as *mut u8, segment_size as _);
slice.copy_from_slice(&TEMP_FILE_CONTENT);
}
use_shared_mem(conn, screen_num, shmseg)?;
conn.shm_detach(shmseg)?;
// Let's ignore the munmap() that we should do here
Ok(())
}
fn main() {
let file = make_file().expect("Failed to create temporary file for FD passing");
match connect(None) {
Err(err) => eprintln!("Failed to connect to the X11 server: {err}"),
Ok((conn, screen_num)) => {
// Check for SHM 1.2 support (needed for fd passing)
if let Some((major, minor)) = check_shm_version(&conn).unwrap() {
if major < 1 || (major == 1 && minor < 2) {
eprintln!(
"X11 server supports version {major}.{minor} of the SHM extension, but version 1.2 \
is needed"
);
return;
}
} else {
eprintln!("X11 server does not support SHM extension");
return;
}
println!("Trying to send an FD");
send_fd(&conn, screen_num, file).unwrap();
println!("Trying to receive an FD");
receive_fd(&conn, screen_num).unwrap();
}
}
}
include!("integration_test_util/connect.rs");

173
vendor/x11rb/examples/simple_window.rs vendored Normal file
View File

@@ -0,0 +1,173 @@
extern crate x11rb;
use x11rb::connection::Connection;
use x11rb::cursor::Handle as CursorHandle;
use x11rb::protocol::xproto::*;
use x11rb::protocol::Event;
use x11rb::resource_manager::new_from_default;
use x11rb::wrapper::ConnectionExt as _;
x11rb::atom_manager! {
pub Atoms: AtomsCookie {
WM_PROTOCOLS,
WM_DELETE_WINDOW,
_NET_WM_NAME,
UTF8_STRING,
}
}
fn init_tracing() {
use tracing_subscriber::prelude::*;
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.with(tracing_subscriber::EnvFilter::from_default_env())
.init();
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
init_tracing();
let (conn, screen_num) = x11rb::connect(None)?;
// The following is only needed for start_timeout_thread(), which is used for 'tests'
let conn1 = std::sync::Arc::new(conn);
let conn = &*conn1;
let screen = &conn.setup().roots[screen_num];
let win_id = conn.generate_id()?;
let gc_id = conn.generate_id()?;
let resource_db = new_from_default(conn)?;
let cursor_handle = CursorHandle::new(conn, screen_num, &resource_db)?;
let atoms = Atoms::new(conn)?.reply()?;
let cursor_handle = cursor_handle.reply()?;
let win_aux = CreateWindowAux::new()
.event_mask(EventMask::EXPOSURE | EventMask::STRUCTURE_NOTIFY | EventMask::NO_EVENT)
.background_pixel(screen.white_pixel)
.win_gravity(Gravity::NORTH_WEST)
// Just because, we set the cursor to "wait"
.cursor(cursor_handle.load_cursor(conn, "wait")?);
let gc_aux = CreateGCAux::new().foreground(screen.black_pixel);
let (mut width, mut height) = (100, 100);
conn.create_window(
screen.root_depth,
win_id,
screen.root,
0,
0,
width,
height,
0,
WindowClass::INPUT_OUTPUT,
0,
&win_aux,
)?;
util::start_timeout_thread(conn1.clone(), win_id);
let title = "Simple Window";
conn.change_property8(
PropMode::REPLACE,
win_id,
AtomEnum::WM_NAME,
AtomEnum::STRING,
title.as_bytes(),
)?;
conn.change_property8(
PropMode::REPLACE,
win_id,
atoms._NET_WM_NAME,
atoms.UTF8_STRING,
title.as_bytes(),
)?;
conn.change_property32(
PropMode::REPLACE,
win_id,
atoms.WM_PROTOCOLS,
AtomEnum::ATOM,
&[atoms.WM_DELETE_WINDOW],
)?;
conn.change_property8(
PropMode::REPLACE,
win_id,
AtomEnum::WM_CLASS,
AtomEnum::STRING,
b"simple_window\0simple_window\0",
)?;
conn.change_property8(
PropMode::REPLACE,
win_id,
AtomEnum::WM_CLIENT_MACHINE,
AtomEnum::STRING,
// Text encoding in X11 is complicated. Let's use UTF-8 and hope for the best.
gethostname::gethostname()
.to_str()
.unwrap_or("[Invalid]")
.as_bytes(),
)?;
let reply = conn
.get_property(false, win_id, AtomEnum::WM_NAME, AtomEnum::STRING, 0, 1024)?
.reply()?;
assert_eq!(reply.value, title.as_bytes());
conn.create_gc(gc_id, win_id, &gc_aux)?;
conn.map_window(win_id)?;
conn.flush()?;
loop {
let event = conn.wait_for_event()?;
tracing::debug!("Got event {event:?}");
match event {
Event::Expose(event) => {
if event.count == 0 {
// There already is a white background because we set background_pixel to white
// when creating the window.
let (width, height): (i16, i16) = (width as _, height as _);
let points = [
Point {
x: width,
y: height,
},
Point { x: -10, y: -10 },
Point {
x: -10,
y: height + 10,
},
Point {
x: width + 10,
y: -10,
},
];
conn.poly_line(CoordMode::ORIGIN, win_id, gc_id, &points)?;
conn.flush()?;
}
}
Event::ConfigureNotify(event) => {
width = event.width;
height = event.height;
}
Event::ClientMessage(event) => {
let data = event.data.as_data32();
if event.format == 32 && event.window == win_id && data[0] == atoms.WM_DELETE_WINDOW
{
tracing::info!("Window was asked to close");
return Ok(());
}
}
Event::Error(err) => {
tracing::error!("Got an unexpected error: {err:?}")
}
event => tracing::info!("Got an unhandled event: {event:?}"),
}
}
}
include!("integration_test_util/util.rs");

View File

@@ -0,0 +1,434 @@
// A very simple reparenting window manager.
// This WM does NOT follow ICCCM!
extern crate x11rb;
use std::cmp::Reverse;
use std::collections::{BinaryHeap, HashSet};
use std::process::exit;
use x11rb::connection::Connection;
use x11rb::errors::{ReplyError, ReplyOrIdError};
use x11rb::protocol::xproto::*;
use x11rb::protocol::{ErrorKind, Event};
use x11rb::{COPY_DEPTH_FROM_PARENT, CURRENT_TIME};
const TITLEBAR_HEIGHT: u16 = 20;
const DRAG_BUTTON: Button = 1;
/// The state of a single window that we manage
#[derive(Debug)]
struct WindowState {
window: Window,
frame_window: Window,
x: i16,
y: i16,
width: u16,
}
impl WindowState {
fn new(window: Window, frame_window: Window, geom: &GetGeometryReply) -> WindowState {
WindowState {
window,
frame_window,
x: geom.x,
y: geom.y,
width: geom.width,
}
}
fn close_x_position(&self) -> i16 {
std::cmp::max(0, self.width - TITLEBAR_HEIGHT) as _
}
}
/// The state of the full WM
#[derive(Debug)]
struct WmState<'a, C: Connection> {
conn: &'a C,
screen_num: usize,
black_gc: Gcontext,
windows: Vec<WindowState>,
pending_expose: HashSet<Window>,
wm_protocols: Atom,
wm_delete_window: Atom,
sequences_to_ignore: BinaryHeap<Reverse<u16>>,
// If this is Some, we are currently dragging the given window with the given offset relative
// to the mouse.
drag_window: Option<(Window, (i16, i16))>,
}
impl<'a, C: Connection> WmState<'a, C> {
fn new(conn: &'a C, screen_num: usize) -> Result<WmState<'a, C>, ReplyOrIdError> {
let screen = &conn.setup().roots[screen_num];
let black_gc = conn.generate_id()?;
let font = conn.generate_id()?;
conn.open_font(font, b"9x15")?;
let gc_aux = CreateGCAux::new()
.graphics_exposures(0)
.background(screen.white_pixel)
.foreground(screen.black_pixel)
.font(font);
conn.create_gc(black_gc, screen.root, &gc_aux)?;
conn.close_font(font)?;
let wm_protocols = conn.intern_atom(false, b"WM_PROTOCOLS")?;
let wm_delete_window = conn.intern_atom(false, b"WM_DELETE_WINDOW")?;
Ok(WmState {
conn,
screen_num,
black_gc,
windows: Vec::default(),
pending_expose: HashSet::default(),
wm_protocols: wm_protocols.reply()?.atom,
wm_delete_window: wm_delete_window.reply()?.atom,
sequences_to_ignore: Default::default(),
drag_window: None,
})
}
/// Scan for already existing windows and manage them
fn scan_windows(&mut self) -> Result<(), ReplyOrIdError> {
// Get the already existing top-level windows.
let screen = &self.conn.setup().roots[self.screen_num];
let tree_reply = self.conn.query_tree(screen.root)?.reply()?;
// For each window, request its attributes and geometry *now*
let mut cookies = Vec::with_capacity(tree_reply.children.len());
for win in tree_reply.children {
let attr = self.conn.get_window_attributes(win)?;
let geom = self.conn.get_geometry(win)?;
cookies.push((win, attr, geom));
}
// Get the replies and manage windows
for (win, attr, geom) in cookies {
if let (Ok(attr), Ok(geom)) = (attr.reply(), geom.reply()) {
if !attr.override_redirect && attr.map_state != MapState::UNMAPPED {
self.manage_window(win, &geom)?;
}
} else {
// Just skip this window
}
}
Ok(())
}
/// Add a new window that should be managed by the WM
fn manage_window(
&mut self,
win: Window,
geom: &GetGeometryReply,
) -> Result<(), ReplyOrIdError> {
println!("Managing window {win:?}");
let screen = &self.conn.setup().roots[self.screen_num];
assert!(self.find_window_by_id(win).is_none());
let frame_win = self.conn.generate_id()?;
let win_aux = CreateWindowAux::new()
.event_mask(
EventMask::EXPOSURE
| EventMask::SUBSTRUCTURE_NOTIFY
| EventMask::BUTTON_PRESS
| EventMask::BUTTON_RELEASE
| EventMask::POINTER_MOTION
| EventMask::ENTER_WINDOW,
)
.background_pixel(screen.white_pixel);
self.conn.create_window(
COPY_DEPTH_FROM_PARENT,
frame_win,
screen.root,
geom.x,
geom.y,
geom.width,
geom.height + TITLEBAR_HEIGHT,
1,
WindowClass::INPUT_OUTPUT,
0,
&win_aux,
)?;
self.conn.grab_server()?;
self.conn.change_save_set(SetMode::INSERT, win)?;
let cookie = self
.conn
.reparent_window(win, frame_win, 0, TITLEBAR_HEIGHT as _)?;
self.conn.map_window(win)?;
self.conn.map_window(frame_win)?;
self.conn.ungrab_server()?;
self.windows.push(WindowState::new(win, frame_win, geom));
// Ignore all events caused by reparent_window(). All those events have the sequence number
// of the reparent_window() request, thus remember its sequence number. The
// grab_server()/ungrab_server() is done so that the server does not handle other clients
// in-between, which could cause other events to get the same sequence number.
self.sequences_to_ignore
.push(Reverse(cookie.sequence_number() as u16));
Ok(())
}
/// Draw the titlebar of a window
fn redraw_titlebar(&self, state: &WindowState) -> Result<(), ReplyError> {
let close_x = state.close_x_position();
self.conn.poly_line(
CoordMode::ORIGIN,
state.frame_window,
self.black_gc,
&[
Point { x: close_x, y: 0 },
Point {
x: state.width as _,
y: TITLEBAR_HEIGHT as _,
},
],
)?;
self.conn.poly_line(
CoordMode::ORIGIN,
state.frame_window,
self.black_gc,
&[
Point {
x: close_x,
y: TITLEBAR_HEIGHT as _,
},
Point {
x: state.width as _,
y: 0,
},
],
)?;
let reply = self
.conn
.get_property(
false,
state.window,
AtomEnum::WM_NAME,
AtomEnum::STRING,
0,
u32::MAX,
)?
.reply()?;
self.conn
.image_text8(state.frame_window, self.black_gc, 1, 10, &reply.value)?;
Ok(())
}
/// Do all pending work that was queued while handling some events
fn refresh(&mut self) {
while let Some(&win) = self.pending_expose.iter().next() {
self.pending_expose.remove(&win);
if let Some(state) = self.find_window_by_id(win) {
if let Err(err) = self.redraw_titlebar(state) {
eprintln!(
"Error while redrawing window {:x?}: {:?}",
state.window, err
);
}
}
}
}
fn find_window_by_id(&self, win: Window) -> Option<&WindowState> {
self.windows
.iter()
.find(|state| state.window == win || state.frame_window == win)
}
fn find_window_by_id_mut(&mut self, win: Window) -> Option<&mut WindowState> {
self.windows
.iter_mut()
.find(|state| state.window == win || state.frame_window == win)
}
/// Handle the given event
fn handle_event(&mut self, event: Event) -> Result<(), ReplyOrIdError> {
let mut should_ignore = false;
if let Some(seqno) = event.wire_sequence_number() {
// Check sequences_to_ignore and remove entries with old (=smaller) numbers.
while let Some(&Reverse(to_ignore)) = self.sequences_to_ignore.peek() {
// Sequence numbers can wrap around, so we cannot simply check for
// "to_ignore <= seqno". This is equivalent to "to_ignore - seqno <= 0", which is what we
// check instead. Since sequence numbers are unsigned, we need a trick: We decide
// that values from [MAX/2, MAX] count as "<= 0" and the rest doesn't.
if to_ignore.wrapping_sub(seqno) <= u16::MAX / 2 {
// If the two sequence numbers are equal, this event should be ignored.
should_ignore = to_ignore == seqno;
break;
}
self.sequences_to_ignore.pop();
}
}
println!("Got event {event:?}");
if should_ignore {
println!(" [ignored]");
return Ok(());
}
match event {
Event::UnmapNotify(event) => self.handle_unmap_notify(event),
Event::ConfigureRequest(event) => self.handle_configure_request(event)?,
Event::MapRequest(event) => self.handle_map_request(event)?,
Event::Expose(event) => self.handle_expose(event),
Event::EnterNotify(event) => self.handle_enter(event)?,
Event::ButtonPress(event) => self.handle_button_press(event),
Event::ButtonRelease(event) => self.handle_button_release(event)?,
Event::MotionNotify(event) => self.handle_motion_notify(event)?,
_ => {}
}
Ok(())
}
fn handle_unmap_notify(&mut self, event: UnmapNotifyEvent) {
let root = self.conn.setup().roots[self.screen_num].root;
let conn = self.conn;
self.windows.retain(|state| {
if state.window != event.window {
return true;
}
conn.change_save_set(SetMode::DELETE, state.window).unwrap();
conn.reparent_window(state.window, root, state.x, state.y)
.unwrap();
conn.destroy_window(state.frame_window).unwrap();
false
});
}
fn handle_configure_request(&mut self, event: ConfigureRequestEvent) -> Result<(), ReplyError> {
if let Some(state) = self.find_window_by_id_mut(event.window) {
let _ = state;
unimplemented!();
}
// Allow clients to change everything, except sibling / stack mode
let aux = ConfigureWindowAux::from_configure_request(&event)
.sibling(None)
.stack_mode(None);
println!("Configure: {aux:?}");
self.conn.configure_window(event.window, &aux)?;
Ok(())
}
fn handle_map_request(&mut self, event: MapRequestEvent) -> Result<(), ReplyOrIdError> {
self.manage_window(
event.window,
&self.conn.get_geometry(event.window)?.reply()?,
)
}
fn handle_expose(&mut self, event: ExposeEvent) {
self.pending_expose.insert(event.window);
}
fn handle_enter(&mut self, event: EnterNotifyEvent) -> Result<(), ReplyError> {
if let Some(state) = self.find_window_by_id(event.event) {
// Set the input focus (ignoring ICCCM's WM_PROTOCOLS / WM_TAKE_FOCUS)
self.conn
.set_input_focus(InputFocus::PARENT, state.window, CURRENT_TIME)?;
// Also raise the window to the top of the stacking order
self.conn.configure_window(
state.frame_window,
&ConfigureWindowAux::new().stack_mode(StackMode::ABOVE),
)?;
}
Ok(())
}
fn handle_button_press(&mut self, event: ButtonPressEvent) {
if event.detail != DRAG_BUTTON || u16::from(event.state) != 0 {
return;
}
if let Some(state) = self.find_window_by_id(event.event) {
if self.drag_window.is_none() && event.event_x < state.close_x_position() {
let (x, y) = (-event.event_x, -event.event_y);
self.drag_window = Some((state.frame_window, (x, y)));
}
}
}
fn handle_button_release(&mut self, event: ButtonReleaseEvent) -> Result<(), ReplyError> {
if event.detail == DRAG_BUTTON {
self.drag_window = None;
}
if let Some(state) = self.find_window_by_id(event.event) {
if event.event_x >= state.close_x_position() {
let event = ClientMessageEvent::new(
32,
state.window,
self.wm_protocols,
[self.wm_delete_window, 0, 0, 0, 0],
);
self.conn
.send_event(false, state.window, EventMask::NO_EVENT, event)?;
}
}
Ok(())
}
fn handle_motion_notify(&mut self, event: MotionNotifyEvent) -> Result<(), ReplyError> {
if let Some((win, (x, y))) = self.drag_window {
let (x, y) = (x + event.root_x, y + event.root_y);
// Sigh, X11 and its mixing up i16 and i32
let (x, y) = (x as i32, y as i32);
self.conn
.configure_window(win, &ConfigureWindowAux::new().x(x).y(y))?;
}
Ok(())
}
}
fn become_wm<C: Connection>(conn: &C, screen: &Screen) -> Result<(), ReplyError> {
// Try to become the window manager. This causes an error if there is already another WM.
let change = ChangeWindowAttributesAux::default()
.event_mask(EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY);
let res = conn.change_window_attributes(screen.root, &change)?.check();
if let Err(ReplyError::X11Error(ref error)) = res {
if error.error_kind == ErrorKind::Access {
eprintln!("Another WM is already running.");
exit(1);
} else {
res
}
} else {
res
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (conn, screen_num) = connect(None)?;
// The following is only needed for start_timeout_thread(), which is used for 'tests'
let conn1 = std::sync::Arc::new(conn);
let conn = &*conn1;
let screen = &conn.setup().roots[screen_num];
become_wm(conn, screen)?;
let mut wm_state = WmState::new(conn, screen_num)?;
wm_state.scan_windows()?;
util::start_timeout_thread(conn1.clone(), screen.root);
loop {
wm_state.refresh();
conn.flush()?;
let event = conn.wait_for_event()?;
let mut event_option = Some(event);
while let Some(event) = event_option {
if let Event::ClientMessage(_) = event {
// This is start_timeout_thread() signaling us to close (most likely).
return Ok(());
}
wm_state.handle_event(event)?;
event_option = conn.poll_for_event()?;
}
}
}
include!("integration_test_util/connect.rs");
include!("integration_test_util/util.rs");

2861
vendor/x11rb/examples/tutorial.rs vendored Normal file

File diff suppressed because it is too large Load Diff

282
vendor/x11rb/examples/xclock_utc.rs vendored Normal file
View File

@@ -0,0 +1,282 @@
use std::num::NonZeroUsize;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use polling::{Event as PollingEvent, Poller};
use x11rb::atom_manager;
use x11rb::connection::Connection;
use x11rb::errors::{ConnectionError, ReplyOrIdError};
use x11rb::protocol::xproto::*;
use x11rb::protocol::Event;
use x11rb::rust_connection::RustConnection;
use x11rb::wrapper::ConnectionExt as _;
atom_manager! {
pub Atoms: AtomsCookie {
UTF8_STRING,
WM_DELETE_WINDOW,
WM_PROTOCOLS,
_NET_WM_NAME,
}
}
fn create_window(
conn: &impl Connection,
screen: &Screen,
atoms: &Atoms,
(width, height): (u16, u16),
) -> Result<Window, ReplyOrIdError> {
let win_id = conn.generate_id()?;
let win_aux =
CreateWindowAux::new().event_mask(EventMask::EXPOSURE | EventMask::STRUCTURE_NOTIFY);
conn.create_window(
screen.root_depth,
win_id,
screen.root,
0,
0,
width,
height,
0,
WindowClass::INPUT_OUTPUT,
0,
&win_aux,
)?;
let title = "xclock";
conn.change_property8(
PropMode::REPLACE,
win_id,
AtomEnum::WM_NAME,
AtomEnum::STRING,
title.as_bytes(),
)?;
conn.change_property8(
PropMode::REPLACE,
win_id,
atoms._NET_WM_NAME,
atoms.UTF8_STRING,
title.as_bytes(),
)?;
conn.change_property32(
PropMode::REPLACE,
win_id,
atoms.WM_PROTOCOLS,
AtomEnum::ATOM,
&[atoms.WM_DELETE_WINDOW],
)?;
conn.map_window(win_id)?;
Ok(win_id)
}
fn redraw(
conn: &impl Connection,
screen: &Screen,
win_id: Window,
gc_id: Gcontext,
(width, height): (u16, u16),
) -> Result<(), ConnectionError> {
let (hour, minute, second) = get_time();
let center = ((width as f32) / 2.0, (height as f32) / 2.0);
let size = (width.min(height) as f32) / 2.0;
// Transform a value between 0 and 60 to a position on the clock (relative to the center)
let minute_to_outer_position = |minute: f32| {
let angle = (30.0 - minute) * 2.0 * std::f32::consts::PI / 60.0;
let (sin, cos) = angle.sin_cos();
(size * sin, size * cos)
};
// Create a line segment
fn create_line(center: (f32, f32), from: (f32, f32), to: (f32, f32)) -> Segment {
Segment {
x1: (center.0 + from.0).round() as _,
y1: (center.1 + from.1).round() as _,
x2: (center.0 + to.0).round() as _,
y2: (center.1 + to.1).round() as _,
}
}
// Draw the background
conn.change_gc(gc_id, &ChangeGCAux::new().foreground(screen.white_pixel))?;
conn.poly_fill_rectangle(
win_id,
gc_id,
&[Rectangle {
x: 0,
y: 0,
width,
height,
}],
)?;
conn.change_gc(gc_id, &ChangeGCAux::new().foreground(screen.black_pixel))?;
// Get a list of lines for the clock's face
let mut lines = (0..60)
.map(|minute| {
let outer = minute_to_outer_position(minute as _);
let length_factor = if minute % 5 == 0 { 0.8 } else { 0.9 };
create_line(
center,
outer,
(outer.0 * length_factor, outer.1 * length_factor),
)
})
.collect::<Vec<_>>();
// ... and also the hand for seconds
lines.push(create_line(
center,
(0.0, 0.0),
minute_to_outer_position(second as _),
));
// Draw everything
conn.poly_segment(win_id, gc_id, &lines)?;
// Now draw the hands
let point = |pos: (f32, f32), factor: f32| Point {
x: (center.0 + pos.0 * factor).round() as _,
y: (center.1 + pos.1 * factor).round() as _,
};
let hour_to_60 = (hour % 12) as f32 * 60.0 / 12.0;
for &(position, hand_length, hand_width) in
&[(hour_to_60, 0.6, 0.08), (minute as f32, 0.8, 0.05)]
{
let outer = minute_to_outer_position(position);
let ortho1 = (outer.1, -outer.0);
let ortho2 = (-outer.1, outer.0);
let opposite = (-outer.0, -outer.1);
let polygon = [
point(ortho1, hand_width),
point(opposite, hand_width),
point(ortho2, hand_width),
point(outer, hand_length),
];
conn.fill_poly(
win_id,
gc_id,
PolyShape::COMPLEX,
CoordMode::ORIGIN,
&polygon,
)?;
}
Ok(())
}
fn poll_with_timeout(
poller: &Poller,
conn: &RustConnection,
timeout: Duration,
) -> Result<(), Box<dyn std::error::Error>> {
// Add interest in the connection's stream.
unsafe {
// SAFETY: The guard bellow guarantees that the source will be removed
// from the poller.
poller.add(conn.stream(), PollingEvent::readable(1))?;
}
// Remove it if we time out.
let _guard = CallOnDrop(|| {
poller.delete(conn.stream()).ok();
});
// Wait for events.
let mut event = polling::Events::with_capacity(NonZeroUsize::new(1).unwrap());
let target = Instant::now() + timeout;
loop {
let remaining = target.saturating_duration_since(Instant::now());
poller.wait(&mut event, Some(remaining))?;
// If we received an event, we're done.
if event.iter().any(|event| event.readable) {
return Ok(());
}
// If our timeout expired, we're done.
if Instant::now() >= target {
// We do not really care about the result of poll. Either there was a timeout, in which case we
// try to handle events (there are none) and then redraw. Or there was an event, in which case
// we handle it and then still redraw.
return Ok(());
}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (conn, screen_num) = RustConnection::connect(None)?;
// The following is only needed for start_timeout_thread(), which is used for 'tests'
let conn1 = std::sync::Arc::new(conn);
let conn = &*conn1;
let poller = Poller::new()?;
let screen = &conn.setup().roots[screen_num];
let atoms = Atoms::new(conn)?.reply()?;
let (mut width, mut height) = (100, 100);
let win_id = create_window(conn, screen, &atoms, (width, height))?;
let gc_id = conn.generate_id().unwrap();
conn.create_gc(gc_id, win_id, &CreateGCAux::default())?;
util::start_timeout_thread(conn1.clone(), win_id);
conn.flush()?;
loop {
poll_with_timeout(&poller, conn, Duration::from_millis(1_000))?;
while let Some(event) = conn.poll_for_event()? {
println!("{event:?})");
match event {
Event::ConfigureNotify(event) => {
width = event.width;
height = event.height;
}
Event::ClientMessage(event) => {
let data = event.data.as_data32();
if event.format == 32
&& event.window == win_id
&& data[0] == atoms.WM_DELETE_WINDOW
{
println!("Window was asked to close");
return Ok(());
}
}
Event::Error(_) => println!("Got an unexpected error"),
_ => println!("Got an unknown event"),
}
}
redraw(conn, screen, win_id, gc_id, (width, height))?;
conn.flush()?;
}
}
/// Get the current time as (hour, minute, second)
fn get_time() -> (u8, u8, u8) {
let total_secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let (second, total_minutes) = (total_secs % 60, total_secs / 60);
let (minute, total_hours) = (total_minutes % 60, total_minutes / 60);
let hour = total_hours % 24;
// This is in UTC. Getting local time is complicated and not important for us.
(hour as _, minute as _, second as _)
}
include!("integration_test_util/util.rs");
struct CallOnDrop<F: FnMut()>(F);
impl<F: FnMut()> Drop for CallOnDrop<F> {
fn drop(&mut self) {
(self.0)();
}
}

372
vendor/x11rb/examples/xeyes.rs vendored Normal file
View File

@@ -0,0 +1,372 @@
// This is (quite loosely) based on the original xeyes
extern crate x11rb;
use x11rb::connection::{Connection, RequestConnection as _};
use x11rb::errors::{ConnectionError, ReplyOrIdError};
use x11rb::protocol::shape::{self, ConnectionExt as _};
use x11rb::protocol::xproto::*;
use x11rb::protocol::Event;
use x11rb::wrapper::ConnectionExt as _;
use x11rb::COPY_DEPTH_FROM_PARENT;
const PUPIL_SIZE: i16 = 50;
const EYE_SIZE: i16 = 50;
// Draw the big background of the eyes
fn draw_eyes<C: Connection>(
conn: &C,
win_id: Window,
black: Gcontext,
white: Gcontext,
window_size: (u16, u16),
) -> Result<(), ConnectionError> {
// Draw the black outlines
let mut arc1 = Arc {
x: 0,
y: 0,
width: window_size.0 / 2,
height: window_size.1,
angle1: 0,
angle2: 360 * 64,
};
let mut arc2 = arc1;
arc2.x = arc2.width as _;
conn.poly_fill_arc(win_id, black, &[arc1, arc2])?;
// Draw the white inner part
for arc in [&mut arc1, &mut arc2].iter_mut() {
arc.x += EYE_SIZE;
arc.y += EYE_SIZE;
arc.width -= 2 * EYE_SIZE as u16;
arc.height -= 2 * EYE_SIZE as u16;
}
conn.poly_fill_arc(win_id, white, &[arc1, arc2])?;
Ok(())
}
// Draw the pupils inside the eye
fn draw_pupils<C: Connection>(
conn: &C,
win_id: Window,
gc: Gcontext,
((x1, y1), (x2, y2)): ((i16, i16), (i16, i16)),
) -> Result<(), ConnectionError> {
// Transform center to top left corner
let (x1, y1) = (x1 - PUPIL_SIZE / 2, y1 - PUPIL_SIZE / 2);
let (x2, y2) = (x2 - PUPIL_SIZE / 2, y2 - PUPIL_SIZE / 2);
let arc1 = Arc {
x: x1,
y: y1,
width: PUPIL_SIZE as _,
height: PUPIL_SIZE as _,
angle1: 90 * 64,
angle2: 360 * 64,
};
let mut arc2 = arc1;
arc2.x = x2;
arc2.y = y2;
// Do the drawing
conn.poly_fill_arc(win_id, gc, &[arc1, arc2])?;
Ok(())
}
// Given two points, return their squared distance
fn distance_squared(p1: (f64, f64), p2: (f64, f64)) -> f64 {
let dx = p1.0 - p2.0;
let dy = p1.1 - p2.1;
dx * dx + dy * dy
}
// Compute the position of a pupil inside of the given area.
fn compute_pupil(area: (i16, i16, i16, i16), mouse: (i16, i16)) -> (i16, i16) {
// What is the center of the eye?
let center_x = area.0 + area.2 / 2;
let center_y = area.1 + area.3 / 2;
let (w, h) = (f64::from(area.2) / 2.0, f64::from(area.3) / 2.0);
// Is the mouse exactly on the center?
if (center_x, center_y) == mouse {
return mouse;
}
let center = (f64::from(center_x), f64::from(center_y));
let mouse = (f64::from(mouse.0), f64::from(mouse.1));
// Calculate the offset of the mouse position from the center
let diff = (mouse.0 - center.0, mouse.1 - center.1);
// An eclipse is described by this equation, where the angle 'a' is varied over all values, but
// does not actually describe the angle from the center due to the different scaling in x and y
// direction.
//
// x = w * cos(a)
// y = h * sin(a)
//
// With tan(a) = sin(a)/cos(a), we get
//
// tan(a) * x = w * sin(a) => sin(a) = tan(a) * x / w
// y = h * sin(a) => sin(a) = y / h
//
// and thus
//
// tan(a) * x / w = y / h
//
// which we can rearrange to
//
// tan(a) = (y * w) / (x * h)
//
// And thus, the angle we are looking for is:
//
// a = arctan((y * w) / (x * h))
//
// However, due to tan() being the way it is, we actually need:
let angle = (diff.1 * w).atan2(diff.0 * h);
// Now compute the corresponding point on the ellipse (relative to the center)
let (cx, cy) = (w * angle.cos(), h * angle.sin());
// ...and also compute the actual point
let (x, y) = ((center.0 + cx) as _, (center.1 + cy) as _);
// Return the point that is closer to the center
if distance_squared(center, mouse) < distance_squared(center, (x, y)) {
(mouse.0 as _, mouse.1 as _)
} else {
(x as _, y as _)
}
}
// Compute the position of both pupils.
fn compute_pupils(window_size: (u16, u16), mouse_position: (i16, i16)) -> ((i16, i16), (i16, i16)) {
let border = PUPIL_SIZE + EYE_SIZE;
let half_width = window_size.0 as i16 / 2;
let width = half_width - 2 * border;
let height = window_size.1 as i16 - 2 * border;
(
compute_pupil((border, border, width, height), mouse_position),
compute_pupil((border + half_width, border, width, height), mouse_position),
)
}
fn shape_window<C: Connection>(
conn: &C,
win_id: Window,
window_size: (u16, u16),
) -> Result<(), ReplyOrIdError> {
// Create a pixmap for the shape
let pixmap = PixmapWrapper::create_pixmap(conn, 1, win_id, window_size.0, window_size.1)?;
// Fill the pixmap with what will indicate "transparent"
let gc = create_gc_with_foreground(conn, pixmap.pixmap(), 0)?;
let rect = Rectangle {
x: 0,
y: 0,
width: window_size.0,
height: window_size.1,
};
conn.poly_fill_rectangle(pixmap.pixmap(), gc.gcontext(), &[rect])?;
// Draw the eyes as "not transparent"
let values = ChangeGCAux::new().foreground(1);
conn.change_gc(gc.gcontext(), &values)?;
draw_eyes(
conn,
pixmap.pixmap(),
gc.gcontext(),
gc.gcontext(),
window_size,
)?;
// Set the shape of the window
conn.shape_mask(shape::SO::SET, shape::SK::BOUNDING, win_id, 0, 0, &pixmap)?;
Ok(())
}
fn setup_window<C: Connection>(
conn: &C,
screen: &Screen,
window_size: (u16, u16),
wm_protocols: Atom,
wm_delete_window: Atom,
) -> Result<Window, ReplyOrIdError> {
let win_id = conn.generate_id()?;
let win_aux = CreateWindowAux::new()
.event_mask(EventMask::EXPOSURE | EventMask::STRUCTURE_NOTIFY | EventMask::POINTER_MOTION)
.background_pixel(screen.white_pixel);
conn.create_window(
COPY_DEPTH_FROM_PARENT,
win_id,
screen.root,
0,
0,
window_size.0,
window_size.1,
0,
WindowClass::INPUT_OUTPUT,
0,
&win_aux,
)?;
let title = "xeyes";
conn.change_property8(
PropMode::REPLACE,
win_id,
AtomEnum::WM_NAME,
AtomEnum::STRING,
title.as_bytes(),
)?;
conn.change_property32(
PropMode::REPLACE,
win_id,
wm_protocols,
AtomEnum::ATOM,
&[wm_delete_window],
)?;
conn.map_window(win_id)?;
Ok(win_id)
}
fn create_gc_with_foreground<C: Connection>(
conn: C,
win_id: Window,
foreground: u32,
) -> Result<GcontextWrapper<C>, ReplyOrIdError> {
GcontextWrapper::create_gc(
conn,
win_id,
&CreateGCAux::new()
.graphics_exposures(0)
.foreground(foreground),
)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (conn, screen_num) = connect(None).expect("Failed to connect to the X11 server");
// The following is only needed for start_timeout_thread(), which is used for 'tests'
let conn = std::sync::Arc::new(conn);
let screen = &conn.setup().roots[screen_num];
let wm_protocols = conn.intern_atom(false, b"WM_PROTOCOLS")?;
let wm_delete_window = conn.intern_atom(false, b"WM_DELETE_WINDOW")?;
let mut window_size = (700, 500);
let has_shape = conn
.extension_information(shape::X11_EXTENSION_NAME)
.expect("failed to get extension information")
.is_some();
let (wm_protocols, wm_delete_window) =
(wm_protocols.reply()?.atom, wm_delete_window.reply()?.atom);
let win_id = setup_window(&conn, screen, window_size, wm_protocols, wm_delete_window)?;
let mut pixmap = PixmapWrapper::create_pixmap(
conn.clone(),
screen.root_depth,
win_id,
window_size.0,
window_size.1,
)?;
let black_gc = create_gc_with_foreground(&conn, win_id, screen.black_pixel)?;
let white_gc = create_gc_with_foreground(&conn, win_id, screen.white_pixel)?;
conn.flush()?;
let mut need_repaint = false;
let mut need_reshape = false;
let mut mouse_position = (0, 0);
util::start_timeout_thread(conn.clone(), win_id);
loop {
let event = conn.wait_for_event()?;
let mut event_option = Some(event);
while let Some(event) = event_option {
match event {
Event::Expose(event) => {
if event.count == 0 {
need_repaint = true;
}
}
Event::ConfigureNotify(event) => {
window_size = (event.width, event.height);
pixmap = PixmapWrapper::create_pixmap(
conn.clone(),
screen.root_depth,
win_id,
window_size.0,
window_size.1,
)?;
need_reshape = true;
}
Event::MotionNotify(event) => {
mouse_position = (event.event_x, event.event_y);
need_repaint = true;
}
Event::MapNotify(_) => {
need_reshape = true;
}
Event::ClientMessage(event) => {
let data = event.data.as_data32();
if event.format == 32 && event.window == win_id && data[0] == wm_delete_window {
println!("Window was asked to close");
return Ok(());
}
}
Event::Error(error) => {
println!("Unknown error {error:?}");
}
event => {
println!("Unknown event {event:?}");
}
}
event_option = conn.poll_for_event()?;
}
if need_reshape && has_shape {
shape_window(&conn, win_id, window_size)?;
need_reshape = false;
}
if need_repaint {
// Draw new pupils
let pos = compute_pupils(window_size, mouse_position);
draw_eyes(
&conn,
pixmap.pixmap(),
black_gc.gcontext(),
white_gc.gcontext(),
window_size,
)?;
draw_pupils(&conn, pixmap.pixmap(), black_gc.gcontext(), pos)?;
// Copy drawing from pixmap to window
conn.copy_area(
pixmap.pixmap(),
win_id,
white_gc.gcontext(),
0,
0,
0,
0,
window_size.0,
window_size.1,
)?;
conn.flush()?;
need_repaint = false;
}
}
}
include!("integration_test_util/connect.rs");
include!("integration_test_util/util.rs");