-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update dependencies and add an example #1
Changes from 9 commits
5cce320
1e33943
8e1db02
ebeb344
8cdd7dd
1bd5909
2f79656
54f4d0d
ee6b605
b73f07d
8a4034e
ab4b6cc
d3d1cf6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,7 +31,7 @@ | |
use std::io; | ||
use std::os::unix::io::{AsRawFd, RawFd}; | ||
|
||
use calloop::generic::Generic; | ||
use calloop::generic::{FdWrapper, Generic}; | ||
use calloop::{ | ||
EventSource, InsertError, Interest, LoopHandle, Mode, Poll, PostAction, Readiness, | ||
RegistrationToken, Token, TokenFactory, | ||
|
@@ -58,18 +58,42 @@ use std::eprintln as log_error; | |
#[derive(Debug)] | ||
pub struct WaylandSource<D> { | ||
queue: EventQueue<D>, | ||
fd: Generic<RawFd>, | ||
fd: Generic<FdWrapper<RawFd>>, | ||
read_guard: Option<ReadEventsGuard>, | ||
/// Calloop's before_will_sleep method allows | ||
/// skipping the sleeping by returning a `Token`. | ||
/// We cannot produce this on the fly, so store it here instead | ||
fake_token: Option<Token>, | ||
// Some calloop event handlers don't support error handling, so we have to store the error | ||
// for a short time until we reach a method which allows it | ||
stored_error: Result<(), io::Error>, | ||
} | ||
|
||
impl<D> WaylandSource<D> { | ||
/// Wrap an [`EventQueue`] as a [`WaylandSource`]. | ||
pub fn new(queue: EventQueue<D>) -> Result<WaylandSource<D>, WaylandError> { | ||
/// | ||
/// If this returns None, that means that acquiring a read guard failed. | ||
/// See [EventQueue::prepare_read] for details | ||
/// This guard is only used to get the wayland file descriptor | ||
#[must_use] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
pub fn new(queue: EventQueue<D>) -> Option<WaylandSource<D>> { | ||
DJMcNab marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let guard = queue.prepare_read()?; | ||
let fd = Generic::new(guard.connection_fd().as_raw_fd(), Interest::READ, Mode::Level); | ||
let fd = Generic::new( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, what's the reason we try to acquire the guard when creating the source? I know that it was like that before, but what is the reason, @elinorbgr ? We won't do any polling here, so it's a bit weird, just to check that the FD is alive? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this repository, it's entirely because There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed, though Smithay/wayland-rs#656 is adding the necessary APIs There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, Smithay/wayland-rs#656 lets us do this in a clean and safe way with simply https://github.com/Smithay/client-toolkit/pull/403/files#diff-8d59229f4c6aea492f0cbceaafc305fcade8dbe513b86e6466feed5a0b2b3107 has a version doing that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We'd need to wait for a patch release, but I'd rather do so. |
||
// Safety: `connection_fd` returns the wayland socket fd, | ||
// and EventQueue (eventually) owns this socket | ||
// fd is only used in calloop, which guarantees | ||
// that the source is unregistered before dropping it, so the | ||
// fd cannot be used after being freed | ||
// Wayland-backend should probably document some guarantees here to make this sound, | ||
// but we know that they are unlikely to make the queue have a different socket/fd | ||
// - there is no public API to do so | ||
unsafe { FdWrapper::new(guard.connection_fd().as_raw_fd()) }, | ||
Interest::READ, | ||
Mode::Level, | ||
); | ||
drop(guard); | ||
|
||
Ok(WaylandSource { queue, fd, read_guard: None }) | ||
Some(WaylandSource { queue, fd, read_guard: None, fake_token: None, stored_error: Ok(()) }) | ||
} | ||
|
||
/// Access the underlying event queue | ||
|
@@ -104,58 +128,43 @@ impl<D> EventSource for WaylandSource<D> { | |
type Metadata = EventQueue<D>; | ||
type Ret = Result<usize, DispatchError>; | ||
|
||
const NEEDS_EXTRA_LIFECYCLE_EVENTS: bool = true; | ||
|
||
fn process_events<F>( | ||
&mut self, | ||
readiness: Readiness, | ||
token: Token, | ||
_: Readiness, | ||
_: Token, | ||
mut callback: F, | ||
) -> Result<PostAction, Self::Error> | ||
where | ||
F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret, | ||
{ | ||
debug_assert!(self.read_guard.is_none()); | ||
|
||
let queue = &mut self.queue; | ||
let read_guard = &mut self.read_guard; | ||
// Take the stored error | ||
std::mem::replace(&mut self.stored_error, Ok(()))?; | ||
|
||
let action = self.fd.process_events(readiness, token, |_, _| { | ||
// 1. read events from the socket if any are available | ||
if let Some(guard) = read_guard.take() { | ||
// might be None if some other thread read events before us, concurrently | ||
if let Err(WaylandError::Io(err)) = guard.read() { | ||
if err.kind() != io::ErrorKind::WouldBlock { | ||
return Err(err); | ||
} | ||
} | ||
} | ||
// Because our polling is based on a `Generic` source, in theory we might want | ||
// to to call the process_events handler on fd. However, | ||
// we know that Generic's `process_events` call is a no-op, so we can just | ||
// handle the event ourselves | ||
|
||
// 2. dispatch any pending events in the queue | ||
// This is done to ensure we are not waiting for messages that are already in | ||
// the buffer. | ||
Self::loop_callback_pending(queue, &mut callback)?; | ||
*read_guard = Some(Self::prepare_read(queue)?); | ||
|
||
// 3. Once dispatching is finished, flush the responses to the compositor | ||
if let Err(WaylandError::Io(e)) = queue.flush() { | ||
if e.kind() != io::ErrorKind::WouldBlock { | ||
// in case of error, forward it and fast-exit | ||
return Err(e); | ||
} | ||
// WouldBlock error means the compositor could not process all | ||
// our messages quickly. Either it is slowed | ||
// down or we are a spammer. Should not really | ||
// happen, if it does we do nothing and will flush again later | ||
} | ||
// Dispatch any pending events in the queue | ||
Self::loop_callback_pending(queue, &mut callback)?; | ||
|
||
Ok(PostAction::Continue) | ||
})?; | ||
// Once dispatching is finished, flush the responses to the compositor | ||
flush_queue(queue)?; | ||
|
||
Ok(action) | ||
Ok(PostAction::Continue) | ||
} | ||
|
||
fn register( | ||
&mut self, | ||
poll: &mut Poll, | ||
token_factory: &mut TokenFactory, | ||
) -> calloop::Result<()> { | ||
self.fake_token = Some(token_factory.token()); | ||
self.fd.register(poll, token_factory) | ||
} | ||
|
||
|
@@ -171,37 +180,60 @@ impl<D> EventSource for WaylandSource<D> { | |
self.fd.unregister(poll) | ||
} | ||
|
||
fn pre_run<F>(&mut self, mut callback: F) -> calloop::Result<()> | ||
where | ||
F: FnMut((), &mut Self::Metadata) -> Self::Ret, | ||
{ | ||
fn before_sleep(&mut self) -> calloop::Result<Option<(Readiness, Token)>> { | ||
debug_assert!(self.read_guard.is_none()); | ||
|
||
// flush the display before starting to poll | ||
if let Err(WaylandError::Io(err)) = self.queue.flush() { | ||
if err.kind() != io::ErrorKind::WouldBlock { | ||
// in case of error, don't prepare a read, if the error is persistent, it'll | ||
// trigger in other wayland methods anyway | ||
log_error!("Error trying to flush the wayland display: {}", err); | ||
return Err(err.into()); | ||
} | ||
} | ||
flush_queue(&mut self.queue)?; | ||
|
||
// ensure we are not waiting for messages that are already in the buffer. | ||
Self::loop_callback_pending(&mut self.queue, &mut callback)?; | ||
self.read_guard = Some(Self::prepare_read(&mut self.queue)?); | ||
self.read_guard = self.queue.prepare_read(); | ||
match self.read_guard { | ||
Some(_) => Ok(None), | ||
// If getting the guard failed, that means that there are | ||
// events in the queue, and so we need to handle the events instantly | ||
// rather than waiting on an event in polling. We tell calloop this | ||
// by returning Some here. Note that the readiness value is | ||
// never used, so we just need some marker | ||
None => Ok(Some((Readiness::EMPTY, self.fake_token.unwrap()))), | ||
} | ||
} | ||
|
||
Ok(()) | ||
fn before_handle_events(&mut self, events: calloop::EventIterator<'_>) { | ||
// It's important that the guard isn't held whilst process_events calls occur | ||
// This can use arbitrary user-provided code, which may want to use the wayland | ||
// socket For example, creating a Vulkan surface needs access to the | ||
// connection | ||
let guard = self.read_guard.take(); | ||
if events.count() > 0 { | ||
// Read events from the socket if any are available | ||
if let Some(guard) = guard { | ||
if let Err(WaylandError::Io(err)) = guard.read() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can probably merge these two and probably, but I don't really care. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't really see how without let chains, which I don't think are stable yet There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've suggested in the diff how to merge them. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. It's not the code style I would use, but I'll apply it anyway. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean, it's less nested and that's how usually written. More readable code should be preferred. |
||
// If some other thread read events before us, concurrently, that's an expected | ||
// case, so this error isn't an issue. Other error kinds do need to be returned, | ||
// however | ||
if err.kind() != io::ErrorKind::WouldBlock { | ||
// before_handle_events doesn't allow returning errors | ||
// For now, cache it in self until process_events is called | ||
self.stored_error = Err(err); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
fn post_run<F>(&mut self, _: F) -> calloop::Result<()> | ||
where | ||
F: FnMut((), &mut Self::Metadata) -> Self::Ret, | ||
{ | ||
// Drop implementation of ReadEventsGuard will do cleanup | ||
self.read_guard.take(); | ||
Ok(()) | ||
fn flush_queue<D>(queue: &mut EventQueue<D>) -> Result<(), calloop::Error> { | ||
if let Err(WaylandError::Io(err)) = queue.flush() { | ||
if err.kind() != io::ErrorKind::WouldBlock { | ||
// in case of error, forward it and fast-exit | ||
log_error!("Error trying to flush the wayland display: {}", err); | ||
return Err(err.into()); | ||
} | ||
// WouldBlock error means the compositor could not process all | ||
// our messages quickly. Either it is slowed | ||
// down or we are a spammer. Should not really | ||
// happen, if it does we do nothing and will flush again later | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment still seems a bit weird, should be above the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was the existing style, but I'll move it up |
||
} | ||
Ok(()) | ||
} | ||
|
||
impl<D> WaylandSource<D> { | ||
|
@@ -237,16 +269,4 @@ impl<D> WaylandSource<D> { | |
} | ||
} | ||
} | ||
|
||
fn prepare_read(queue: &mut EventQueue<D>) -> io::Result<ReadEventsGuard> { | ||
queue.prepare_read().map_err(|err| match err { | ||
WaylandError::Io(err) => err, | ||
|
||
WaylandError::Protocol(err) => { | ||
log_error!("Protocol error received on display: {}", err); | ||
|
||
Errno::PROTO.into() | ||
}, | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should state what and why it's here.