Skip to content

Commit 9068062

Browse files
committed
calloop-wayland-source version 0.1.0
0 parents  commit 9068062

File tree

7 files changed

+357
-0
lines changed

7 files changed

+357
-0
lines changed

.github/workflows/ci.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches: [main]
7+
8+
env:
9+
RUST_BACKTRACE: 1
10+
CARGO_INCREMENTAL: 0
11+
RUSTFLAGS: "-Cdebuginfo=0 --deny=warnings"
12+
RUSTDOCFLAGS: "--deny=warnings"
13+
14+
jobs:
15+
fmt:
16+
name: Check Formatting
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v3
20+
- uses: hecrj/setup-rust-action@v1
21+
with:
22+
rust-version: nightly
23+
components: rustfmt
24+
- name: Check Formatting
25+
run: cargo +nightly fmt --all -- --check
26+
27+
tests:
28+
name: Tests
29+
runs-on: ubuntu-latest
30+
strategy:
31+
matrix:
32+
rust_version: ["1.65", stable, nightly]
33+
34+
steps:
35+
- uses: actions/checkout@v3
36+
37+
- uses: hecrj/setup-rust-action@v1
38+
with:
39+
rust-version: ${{ matrix.rust_version }}
40+
41+
- name: Check documentation
42+
run: cargo doc --features=log --no-deps --document-private-items
43+
44+
- name: Run tests
45+
run: cargo test --verbose --features=log

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/target
2+
/Cargo.lock

Cargo.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
name = "calloop-wayland-source"
3+
description = "A wayland-rs client event source for callloop"
4+
version = "0.1.0"
5+
edition = "2021"
6+
authors = ["Kirill Chibisov <[email protected]>"]
7+
license = "MIT"
8+
readme = "README.md"
9+
keywords = ["wayland", "windowing"]
10+
rust-version = "1.65.0"
11+
12+
[dependencies]
13+
wayland-client = "0.30.2"
14+
wayland-backend = "0.1.0"
15+
calloop = "0.10.0"
16+
log = { version = "0.4.19", optional = true }
17+
rustix = { version = "0.38.4", default-features = false, features = ["std"] }

LICENSE.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2023 Kirill Chibisov
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in
11+
all copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# calloop-wayland-source
2+
3+
Use [`EventQueue`] from the [`wayland-client`](https://crates.io/crates/wayland-client)
4+
with the [`calloop`](https://crates.io/crates/calloop).
5+
6+
[`EventQueue`]: https://docs.rs/wayland-client/0.30/wayland_client/struct.EventQueue.html

rustfmt.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
format_code_in_doc_comments = true
2+
match_block_trailing_comma = true
3+
condense_wildcard_suffixes = true
4+
use_field_init_shorthand = true
5+
normalize_doc_attributes = true
6+
overflow_delimited_expr = true
7+
imports_granularity = "Module"
8+
use_small_heuristics = "Max"
9+
normalize_comments = true
10+
reorder_impl_items = true
11+
use_try_shorthand = true
12+
newline_style = "Unix"
13+
format_strings = true
14+
wrap_comments = true
15+
comment_width = 80
16+
edition = "2021"

src/lib.rs

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
//! Utilities for using an [`EventQueue`] from wayland-client with an event loop
4+
//! that performs polling with [`calloop`](https://crates.io/crates/calloop).
5+
//!
6+
//! # Example
7+
//!
8+
//! ```no_run,rust
9+
//! use calloop::EventLoop;
10+
//! use calloop_wayland_source::WaylandSource;
11+
//! use wayland_client::{Connection, QueueHandle};
12+
//!
13+
//! // Create a Wayland connection and a queue.
14+
//! let connection = Connection::connect_to_env().unwrap();
15+
//! let event_queue = connection.new_event_queue();
16+
//! let queue_handle = event_queue.handle();
17+
//!
18+
//! // Create the calloop event loop to drive everytihng.
19+
//! let mut event_loop: EventLoop<()> = EventLoop::try_new().unwrap();
20+
//! let loop_handle = event_loop.handle();
21+
//!
22+
//! // Insert the wayland source into the calloop's event loop.
23+
//! WaylandSource::new(event_queue).unwrap().insert(loop_handle).unwrap();
24+
//!
25+
//! // This will start dispatching the event loop and processing pending wayland requests.
26+
//! while let Ok(_) = event_loop.dispatch(None, &mut ()) {
27+
//! // Your logic here.
28+
//! }
29+
//! ```
30+
31+
use std::io;
32+
use std::os::unix::io::{AsRawFd, RawFd};
33+
34+
use calloop::generic::Generic;
35+
use calloop::{
36+
EventSource, InsertError, Interest, LoopHandle, Mode, Poll, PostAction, Readiness,
37+
RegistrationToken, Token, TokenFactory,
38+
};
39+
use rustix::io::Errno;
40+
use wayland_backend::client::{ReadEventsGuard, WaylandError};
41+
use wayland_client::{DispatchError, EventQueue};
42+
43+
#[cfg(feature = "log")]
44+
use log::error as log_error;
45+
#[cfg(not(feature = "log"))]
46+
use std::eprintln as log_error;
47+
48+
/// An adapter to insert an [`EventQueue`] into a calloop
49+
/// [`EventLoop`](calloop::EventLoop).
50+
///
51+
/// This type implements [`EventSource`] which generates an event whenever
52+
/// events on the event queue need to be dispatched. The event queue available
53+
/// in the callback calloop registers may be used to dispatch pending
54+
/// events using [`EventQueue::dispatch_pending`].
55+
///
56+
/// [`WaylandSource::insert`] can be used to insert this source into an event
57+
/// loop and automatically dispatch pending events on the event queue.
58+
#[derive(Debug)]
59+
pub struct WaylandSource<D> {
60+
queue: EventQueue<D>,
61+
fd: Generic<RawFd>,
62+
read_guard: Option<ReadEventsGuard>,
63+
}
64+
65+
impl<D> WaylandSource<D> {
66+
/// Wrap an [`EventQueue`] as a [`WaylandSource`].
67+
pub fn new(queue: EventQueue<D>) -> Result<WaylandSource<D>, WaylandError> {
68+
let guard = queue.prepare_read()?;
69+
let fd = Generic::new(guard.connection_fd().as_raw_fd(), Interest::READ, Mode::Level);
70+
drop(guard);
71+
72+
Ok(WaylandSource { queue, fd, read_guard: None })
73+
}
74+
75+
/// Access the underlying event queue
76+
///
77+
/// Note that you should be careful when interacting with it if you invoke
78+
/// methods that interact with the wayland socket (such as `dispatch()`
79+
/// or `prepare_read()`). These may interfere with the proper waking up
80+
/// of this event source in the event loop.
81+
pub fn queue(&mut self) -> &mut EventQueue<D> {
82+
&mut self.queue
83+
}
84+
85+
/// Insert this source into the given event loop.
86+
///
87+
/// This adapter will pass the event loop's shared data as the `D` type for
88+
/// the event loop.
89+
pub fn insert(self, handle: LoopHandle<D>) -> Result<RegistrationToken, InsertError<Self>>
90+
where
91+
D: 'static,
92+
{
93+
handle.insert_source(self, |_, queue, data| queue.dispatch_pending(data))
94+
}
95+
}
96+
97+
impl<D> EventSource for WaylandSource<D> {
98+
type Error = calloop::Error;
99+
type Event = ();
100+
/// The underlying event queue.
101+
///
102+
/// You should call [`EventQueue::dispatch_pending`] inside your callback
103+
/// using this queue.
104+
type Metadata = EventQueue<D>;
105+
type Ret = Result<usize, DispatchError>;
106+
107+
fn process_events<F>(
108+
&mut self,
109+
readiness: Readiness,
110+
token: Token,
111+
mut callback: F,
112+
) -> Result<PostAction, Self::Error>
113+
where
114+
F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret,
115+
{
116+
let queue = &mut self.queue;
117+
let read_guard = &mut self.read_guard;
118+
119+
let action = self.fd.process_events(readiness, token, |_, _| {
120+
// 1. read events from the socket if any are available
121+
if let Some(guard) = read_guard.take() {
122+
// might be None if some other thread read events before us, concurrently
123+
if let Err(WaylandError::Io(err)) = guard.read() {
124+
if err.kind() != io::ErrorKind::WouldBlock {
125+
return Err(err);
126+
}
127+
}
128+
}
129+
130+
// 2. dispatch any pending events in the queue
131+
// This is done to ensure we are not waiting for messages that are already in
132+
// the buffer.
133+
Self::loop_callback_pending(queue, &mut callback)?;
134+
*read_guard = Some(Self::prepare_read(queue)?);
135+
136+
// 3. Once dispatching is finished, flush the responses to the compositor
137+
if let Err(WaylandError::Io(e)) = queue.flush() {
138+
if e.kind() != io::ErrorKind::WouldBlock {
139+
// in case of error, forward it and fast-exit
140+
return Err(e);
141+
}
142+
// WouldBlock error means the compositor could not process all
143+
// our messages quickly. Either it is slowed
144+
// down or we are a spammer. Should not really
145+
// happen, if it does we do nothing and will flush again later
146+
}
147+
148+
Ok(PostAction::Continue)
149+
})?;
150+
151+
Ok(action)
152+
}
153+
154+
fn register(
155+
&mut self,
156+
poll: &mut Poll,
157+
token_factory: &mut TokenFactory,
158+
) -> calloop::Result<()> {
159+
self.fd.register(poll, token_factory)
160+
}
161+
162+
fn reregister(
163+
&mut self,
164+
poll: &mut Poll,
165+
token_factory: &mut TokenFactory,
166+
) -> calloop::Result<()> {
167+
self.fd.reregister(poll, token_factory)
168+
}
169+
170+
fn unregister(&mut self, poll: &mut Poll) -> calloop::Result<()> {
171+
self.fd.unregister(poll)
172+
}
173+
174+
fn pre_run<F>(&mut self, mut callback: F) -> calloop::Result<()>
175+
where
176+
F: FnMut((), &mut Self::Metadata) -> Self::Ret,
177+
{
178+
debug_assert!(self.read_guard.is_none());
179+
180+
// flush the display before starting to poll
181+
if let Err(WaylandError::Io(err)) = self.queue.flush() {
182+
if err.kind() != io::ErrorKind::WouldBlock {
183+
// in case of error, don't prepare a read, if the error is persistent, it'll
184+
// trigger in other wayland methods anyway
185+
log_error!("Error trying to flush the wayland display: {}", err);
186+
return Err(err.into());
187+
}
188+
}
189+
190+
// ensure we are not waiting for messages that are already in the buffer.
191+
Self::loop_callback_pending(&mut self.queue, &mut callback)?;
192+
self.read_guard = Some(Self::prepare_read(&mut self.queue)?);
193+
194+
Ok(())
195+
}
196+
197+
fn post_run<F>(&mut self, _: F) -> calloop::Result<()>
198+
where
199+
F: FnMut((), &mut Self::Metadata) -> Self::Ret,
200+
{
201+
// Drop implementation of ReadEventsGuard will do cleanup
202+
self.read_guard.take();
203+
Ok(())
204+
}
205+
}
206+
207+
impl<D> WaylandSource<D> {
208+
/// Loop over the callback until all pending messages have been dispatched.
209+
fn loop_callback_pending<F>(queue: &mut EventQueue<D>, callback: &mut F) -> io::Result<()>
210+
where
211+
F: FnMut((), &mut EventQueue<D>) -> Result<usize, DispatchError>,
212+
{
213+
// Loop on the callback until no pending events are left.
214+
loop {
215+
match callback((), queue) {
216+
// No more pending events.
217+
Ok(0) => break Ok(()),
218+
Ok(_) => continue,
219+
Err(DispatchError::Backend(WaylandError::Io(err))) => {
220+
return Err(err);
221+
},
222+
Err(DispatchError::Backend(WaylandError::Protocol(err))) => {
223+
log_error!("Protocol error received on display: {}", err);
224+
225+
break Err(Errno::PROTO.into());
226+
},
227+
Err(DispatchError::BadMessage { interface, sender_id, opcode }) => {
228+
log_error!(
229+
"Bad message on interface \"{}\": (sender_id: {}, opcode: {})",
230+
interface,
231+
sender_id,
232+
opcode,
233+
);
234+
235+
break Err(Errno::PROTO.into());
236+
},
237+
}
238+
}
239+
}
240+
241+
fn prepare_read(queue: &mut EventQueue<D>) -> io::Result<ReadEventsGuard> {
242+
queue.prepare_read().map_err(|err| match err {
243+
WaylandError::Io(err) => err,
244+
245+
WaylandError::Protocol(err) => {
246+
log_error!("Protocol error received on display: {}", err);
247+
248+
Errno::PROTO.into()
249+
},
250+
})
251+
}
252+
}

0 commit comments

Comments
 (0)