2 releases
Uses new Rust 2024
new 0.1.1 | Apr 14, 2025 |
---|---|
0.1.0 | Apr 14, 2025 |
#321 in Rust patterns
560KB
12K
SLoC
wl-client
The wl-client crate provides a safe wrapper around libwayland. It should be used together with wl-client-builder to generate safe protocol wrappers.
Safety
libwayland is a mostly thread safe, reentrant, callback-based library. wl-client contains a large amount of unsafe code to make this not only safe but also convenient, e.g. by supporting scoped event handlers. wl-client contains ~200 unit tests that were created via manual mutation testing to achieve a nearly 100% test coverage. All of these tests are run through miri.
MSRV
The MSRV is max(1.85, stable - 3)
.
License
This project is licensed under either of
- Apache License, Version 2.0
- MIT License
at your option.
lib.rs
:
This crate provides safe wrappers around libwayland. It supports both blocking and
async operations with any async runtime. Both 'static
and 'scoped
, Send
and
!Send
event handlers are supported.
The most important types provided by this crate are
Libwayland
: A reference to a dynamically loadedlibwayland-client.so
.Connection
: A connection to a wayland compositor.Queue
: An event queue.
This crate does not itself provide type-safe wrappers for wayland protocol objects
(wl_display
, wl_keyboard
, etc). Instead, applications should use the
wl-client-builder
crate to generate these wrappers ahead of time or in build.rs
.
Example: Hello wayland
The code of this example can be found in the hello-wayland
example binary.
#
// Load the `libwayland-client.so` dynamic library.
let lib = Libwayland::open().unwrap();
// Connect to the default display determined by the `WAYLAND_DISPLAY` env var.
let con = lib.connect_to_default_display().unwrap();
// Create a new event queue with the name `hello-wayland`. This name will show up
// when debugging applications with `WAYLAND_DEBUG=1`.
let queue = con.create_queue(c"hello-wayland");
// Get a reference to the `wl_display` singleton. This type was generated with the
// `wl-client-builder` crate.
let display: WlDisplay = queue.display();
// Create a `wl_callback` object. The compositor will immediately respond with a
// `wl_callback.done` event.
let sync = display.sync();
// Set the event handler of the proxy.
proxy::set_event_handler(
&sync,
// When only handling a single event type, the following functional form can be
// used. In general, and when handling more than one event type, the event handler
// trait must be implemented. In this case, `WlCallbackEventHandler`.
WlCallback::on_done(|_, _| println!("Hello wayland!")),
);
// Perform a roundtrip to ensure that the `done` event has been dispatched.
queue.dispatch_roundtrip_blocking().unwrap();
Example: Getting a registry snapshot
The code of this example can be found in the get-registry
example binary.
#
struct Global {
pub name: u32,
pub interface: String,
pub version: u32,
}
fn get_registry_snapshot(queue: &Queue) -> (WlRegistry, Vec<Global>) {
// Create a new registry that will receive the globals and can later be used to
// bind them.
let registry = queue.display::<WlDisplay>().get_registry();
let globals = Mutex::new(vec![]);
// Since we don't care about registry events after this function returns, we can
// use a dispatch scope. The event handlers in this scope will not be called after
// the function returns.
queue.dispatch_scope_blocking(|scope| {
scope.set_event_handler(
®istry,
// Since we only want to create a snapshot, we don't care about
// global_remove events. This allows us to use the functional event handler
// form.
WlRegistry::on_global(|_, name, interface, version| {
globals.lock().push(Global {
name,
interface: interface.to_string(),
version,
});
}),
);
queue.dispatch_roundtrip_blocking().unwrap();
});
// The event handler will no longer be called after this function returns but
// the registry can still be used to bind globals.
(registry, globals.into_inner())
}
Example: Handling keyboard events
The code of this example can be found in the keyboard-events
example binary.
#
/// The state used to handle seat and keyboard events. In a real application this
/// would likely also contain a way to map keycodes to keysyms and to forward events
/// to the rest of the application.
struct Seat {
wl_seat: WlSeat,
wl_keyboard: RefCell<Option<WlKeyboard>>,
}
#[derive(Clone)]
struct SeatEventHandler(Rc<Seat>);
impl WlSeatEventHandler for SeatEventHandler {
fn capabilities(&self, _slf: &WlSeatRef, capabilities: WlSeatCapability) {
let kb = &mut *self.0.wl_keyboard.borrow_mut();
// When the seat loses/gains the keyboard capability, we need to
// destroy/create a wl_keyboard.
if capabilities.contains(WlSeatCapability::KEYBOARD) {
if kb.is_none() {
let wl_keyboard = self.0.wl_seat.get_keyboard();
// Since we're using `Rc` here, event handlers must be set with the
// `_local` function which allows `!Send` event handlers.
proxy::set_event_handler_local(&wl_keyboard, self.clone());
*kb = Some(wl_keyboard);
}
} else {
if let Some(kb) = kb.take() {
// The wl_keyboard.release request is only available since version 3.
// If it's not available, at least destroy the client-side object.
if proxy::version(&*kb) >= WlKeyboard::REQ__RELEASE__SINCE {
kb.release();
} else {
proxy::destroy(&kb);
}
}
}
}
}
// If more than one event type needs to be handled by an event handler, the convenient
// functional API cannot be used. Instead the application needs to implement the
// `*EventHandler` trait manually.
impl WlKeyboardEventHandler for SeatEventHandler {
fn key(&self,
_slf: &WlKeyboardRef,
_serial: u32,
_time: u32,
key: u32,
state: WlKeyboardKeyState,
) {
println!("key {key:} {state:?}");
}
fn modifiers(
&self,
_slf: &WlKeyboardRef,
_serial: u32,
mods_depressed: u32,
mods_latched: u32,
mods_locked: u32,
group: u32,
) {
println!("modifiers {mods_depressed:x}, {mods_latched:x}, {mods_locked:x}, {group}");
}
}
Example: Async roundtrip
The code of this example can be found in the async-dispatch
example binary.
#
let lib = Libwayland::open().unwrap();
let con = lib.connect_to_default_display().unwrap();
let queue = con.create_local_queue(c"async-roundtrip");
let registry = queue.display::<WlDisplay>().get_registry();
let num_globals = Cell::new(0);
queue
.dispatch_scope_async(async |scope| {
scope.set_event_handler_local(
®istry,
WlRegistry::on_global(|_, _, _, _| {
num_globals.set(num_globals.get() + 1);
}),
);
// This function can be used to perform an async roundtrip. It is
// compatible with any async runtime. This example also demonstrates
// that this works in combination with scoped event handlers.
queue.dispatch_roundtrip_async().await.unwrap();
})
.await;
println!("number of globals: {}", num_globals.get());
Example: Async waiting for events
The code of this example can be found in the async-wait
example binary.
#
let lib = Libwayland::open().unwrap();
let con = lib.connect_to_default_display().unwrap();
let queue = con.create_local_queue(c"async-wait");
let sync = queue.display::<WlDisplay>().sync();
proxy::set_event_handler(&sync, WlCallback::on_done(|_, _| println!("done!")));
loop {
// This future completes once there are events to dispatch in the queue.
queue.wait_for_events().await.unwrap();
queue.dispatch_pending().unwrap();
}
Example: Poll-based event loop integration
The code of this example can be found in the poll-integration
example binary.
#
let lib = Libwayland::open().unwrap();
let con = lib.connect_to_default_display().unwrap();
let queue = con.create_local_queue(c"poll-integration");
// The watcher exposes a file descriptor that will become readable when the queue
// has new events.
let watcher = queue.create_watcher().unwrap();
let token = Token(0);
let sync = queue.display::<WlDisplay>().sync();
proxy::set_event_handler(&sync, WlCallback::on_done(|_, _| println!("done!")));
let mut events = mio::Events::with_capacity(2);
let mut poll = mio::Poll::new().unwrap();
poll.registry()
.register(
&mut SourceFd(&watcher.as_raw_fd()),
token,
Interest::READABLE,
)
.unwrap();
loop {
// Flush requests before polling.
con.flush().unwrap();
poll.poll(&mut events, None).unwrap();
for event in events.iter() {
if event.token() == token {
queue.dispatch_pending().unwrap();
// Reset the watcher to clear the readability status.
watcher.reset().unwrap();
}
}
events.clear();
}
Dependencies
~0.8–8MB
~60K SLoC