Skip to content
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

Merged
merged 13 commits into from
Sep 20, 2023
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ keywords = ["wayland", "windowing"]
rust-version = "1.65.0"

[dependencies]
wayland-client = "0.30.2"
wayland-backend = "0.1.0"
calloop = "0.10.0"
wayland-client = "0.31.0"
wayland-backend = "0.3.0"
calloop = "0.12.0"
log = { version = "0.4.19", optional = true }
rustix = { version = "0.38.4", default-features = false, features = ["std"] }
113 changes: 66 additions & 47 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -58,18 +58,35 @@ 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>,
fake_token: Option<Token>,
Copy link
Member

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.

stored_error: Option<io::Error>,
DJMcNab marked this conversation as resolved.
Show resolved Hide resolved
}

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
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(
Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this repository, it's entirely because wayland-backend chooses not to expose the fd through any mechanism other than the guard, as far as I can tell. The fd should still be valid any time the event queue (and hence the Connection) is around, although we're not given that guarantee.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, though Smithay/wayland-rs#656 is adding the necessary APIs

Copy link
Member

@ids1024 ids1024 Sep 18, 2023

Choose a reason for hiding this comment

The 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 let queue = Generic::new(queue, Interest::READ, Mode::Level);. Then using self.queue.file to access the contained EventQueue.

https://github.com/Smithay/client-toolkit/pull/403/files#diff-8d59229f4c6aea492f0cbceaafc305fcade8dbe513b86e6466feed5a0b2b3107 has a version doing that.

Copy link
Member

Choose a reason for hiding this comment

The 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 this is unlikely to change
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the last part is not clear, what is unlikely to change?

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: None })
}

/// Access the underlying event queue
Expand Down Expand Up @@ -104,6 +121,8 @@ 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,
Expand All @@ -114,24 +133,15 @@ impl<D> EventSource for WaylandSource<D> {
F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret,
{
let queue = &mut self.queue;
let read_guard = &mut self.read_guard;

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);
}
}
}
if let Some(err) = self.stored_error.take() {
return Err(err)?;
}

let mut callback = || {
// 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() {
Expand All @@ -146,7 +156,11 @@ impl<D> EventSource for WaylandSource<D> {
}

Ok(PostAction::Continue)
})?;
};
if Some(token) == self.fake_token {
callback()?;
}
let action = self.fd.process_events(readiness, token, |_, _| callback())?;
DJMcNab marked this conversation as resolved.
Show resolved Hide resolved

Ok(action)
}
Expand All @@ -156,6 +170,7 @@ impl<D> EventSource for WaylandSource<D> {
poll: &mut Poll,
token_factory: &mut TokenFactory,
) -> calloop::Result<()> {
self.fake_token = Some(token_factory.token());
self.fd.register(poll, token_factory)
}

Expand All @@ -171,10 +186,7 @@ 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
Expand All @@ -187,20 +199,39 @@ impl<D> EventSource for WaylandSource<D> {
}
}

// 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)?);

Ok(())
// TODO: ensure we are not waiting for messages that are already in the buffer.
// is this needed? I think in the case that this would occur, getting the guard
// would fail anyway
// Self::loop_callback_pending(&mut self.queue, &mut callback)?;
self.read_guard = self.queue.prepare_read();
DJMcNab marked this conversation as resolved.
Show resolved Hide resolved
match self.read_guard {
// If getting the guard failed
Some(_) => Ok(None),
// The readiness value is never used, we just need some marker
// If getting the guard failed, we need to process the events 'instantly'
// tell calloop this
None => Ok(Some((Readiness::EMPTY, self.fake_token.unwrap()))),
}
}

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 before_handle_events(&mut self, events: calloop::EventIterator<'_>) {
let guard = self.read_guard.take();
if events.count() > 0 {
// 1. read events from the socket if any are available
// If none are available, that means
if let Some(guard) = guard {
// might be None if some other thread read events before us, concurrently
if let Err(WaylandError::Io(err)) = guard.read() {
elinorbgr marked this conversation as resolved.
Show resolved Hide resolved
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 = Some(err);
}
}
}
}
// Otherwise, drop the guard if we have it, as we don't want to do any
// reading when we didn't get any events
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's always dropped? There's no otherwise.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well it's always dropped, sure. But it's not always dropped by us - if the read call happens, that takes ownership and drops it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we call the read it still sort of dropped by us. The comment is a bit misleading imho, because read should take ownership, no?

}
}

Expand Down Expand Up @@ -237,16 +268,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()
},
})
}
}