add leave event to make entering a client more reliable (#50)

Instead of relying on release events not getting lost, every event now signals the opponent
to release its pointer grab.

There is one case that requires a Leave event:

Consider a Sending client A and receiving Client B.

If B enters the dead-zone of client A, it will send an enter event towards A but before
A receives the Release event, it may still send additional events towards B that should not cause
B to immediately revert to Receiving state again.

Therefore B puts itself into AwaitingLeave state until it receives a Leave event coming from A.
A responds to the Enter event coming from B with a leave event, to signify that it will no longer
send any events and releases it's pointer.

To guard against packet loss of the leave events, B sends additional enter events while it is in AwaitingLeave
mode until it receives a Leave event at some point.

This is still not resilient against possible packet reordering in UDP but in the (rare) case where a leave event arrives before some other event coming from A, the user would simply need to move the pointer into the dead-zone again.
This commit is contained in:
Ferdinand Schober
2023-12-17 17:38:06 +01:00
committed by GitHub
parent 02d1b33e45
commit 735434438f
4 changed files with 87 additions and 52 deletions

View File

@@ -209,9 +209,7 @@ impl EventConsumer for MacOSConsumer {
}
KeyboardEvent::Modifiers { .. } => {}
},
Event::Release() => {}
Event::Ping() => {}
Event::Pong() => {}
_ => ()
}
}

View File

@@ -662,7 +662,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for State {
.iter()
.find(|(w, _c)| w.surface == surface)
.unwrap();
app.pending_events.push_back((*client, Event::Release()));
app.pending_events.push_back((*client, Event::Enter()));
}
wl_pointer::Event::Leave { .. } => {
app.ungrab();

View File

@@ -47,10 +47,23 @@ pub enum KeyboardEvent {
#[derive(Debug, Clone, Copy)]
pub enum Event {
/// pointer event (motion / button / axis)
Pointer(PointerEvent),
/// keyboard events (key / modifiers)
Keyboard(KeyboardEvent),
Release(),
/// enter event: request to enter a client.
/// The client must release the pointer if it is grabbed
/// and reply with a leave event, as soon as its ready to
/// receive events
Enter(),
/// leave event: this client is now ready to receive events and will
/// not send any events after until it sends an enter event
Leave(),
/// ping a client, to see if it is still alive. A client that does
/// not respond with a pong event will be assumed to be offline.
Ping(),
/// response to a ping event: this event signals that a client
/// is still alive but must otherwise be ignored
Pong(),
}
@@ -103,7 +116,8 @@ impl Display for Event {
match self {
Event::Pointer(p) => write!(f, "{}", p),
Event::Keyboard(k) => write!(f, "{}", k),
Event::Release() => write!(f, "release"),
Event::Enter() => write!(f, "enter"),
Event::Leave() => write!(f, "leave"),
Event::Ping() => write!(f, "ping"),
Event::Pong() => write!(f, "pong"),
}
@@ -115,7 +129,8 @@ impl Event {
match self {
Self::Pointer(_) => EventType::Pointer,
Self::Keyboard(_) => EventType::Keyboard,
Self::Release() => EventType::Release,
Self::Enter() => EventType::Enter,
Self::Leave() => EventType::Leave,
Self::Ping() => EventType::Ping,
Self::Pong() => EventType::Pong,
}
@@ -155,7 +170,8 @@ enum KeyboardEventType {
enum EventType {
Pointer,
Keyboard,
Release,
Enter,
Leave,
Ping,
Pong,
}
@@ -196,7 +212,8 @@ impl From<&Event> for Vec<u8> {
let event_data = match event {
Event::Pointer(p) => p.into(),
Event::Keyboard(k) => k.into(),
Event::Release() => vec![],
Event::Enter() => vec![],
Event::Leave() => vec![],
Event::Ping() => vec![],
Event::Pong() => vec![],
};
@@ -224,7 +241,8 @@ impl TryFrom<Vec<u8>> for Event {
match event_id {
i if i == (EventType::Pointer as u8) => Ok(Event::Pointer(value.try_into()?)),
i if i == (EventType::Keyboard as u8) => Ok(Event::Keyboard(value.try_into()?)),
i if i == (EventType::Release as u8) => Ok(Event::Release()),
i if i == (EventType::Enter as u8) => Ok(Event::Enter()),
i if i == (EventType::Leave as u8) => Ok(Event::Leave()),
i if i == (EventType::Ping as u8) => Ok(Event::Ping()),
i if i == (EventType::Pong as u8) => Ok(Event::Pong()),
_ => Err(Box::new(ProtocolError {

View File

@@ -36,6 +36,7 @@ use crate::{
enum State {
Sending,
Receiving,
AwaitingLeave,
}
pub struct Server {
@@ -308,63 +309,91 @@ impl Server {
state.client.active_addr = Some(addr);
match (event, addr) {
(Event::Pong(), _) => {} // ignore pong events
(Event::Pong(), _) => { /* ignore pong events */ }
(Event::Ping(), addr) => {
if let Err(e) = send_event(&self.socket, Event::Pong(), addr).await {
log::error!("udp send: {}", e);
}
}
(event, addr) => {
// device is sending events => release pointer if captured
if self.state == State::Sending {
// Even in sending state, we can still receive events
// from the client we entered until (our) release event
// arrives there.
//
// Therefore we must wait until we receive
// another release event to actually release the pointer.
if let Event::Release() = event {
log::debug!("releasing pointer ...");
self.producer.release();
self.state = State::Receiving;
}
return;
}
// consume event
self.consumer.consume(event, handle).await;
log::trace!("{event:?} => consumer");
// let the server know we are still alive once every second
let last_replied = state.last_replied;
if last_replied.is_none()
|| last_replied.is_some()
&& last_replied.unwrap().elapsed() > Duration::from_secs(1)
{
state.last_replied = Some(Instant::now());
if let Err(e) = send_event(&self.socket, Event::Pong(), addr).await {
// tell clients that we are ready to receive events
if let Event::Enter() = event {
if let Err(e) = send_event(&self.socket, Event::Leave(), addr).await {
log::error!("udp send: {}", e);
}
}
match self.state {
State::Sending => {
if let Event::Leave() = event {
// ignore additional leave events that may
// have been sent for redundancy
} else {
// upon receiving any event, we go back to receiving mode
self.producer.release();
self.state = State::Receiving;
}
},
State::Receiving => {
// consume event
self.consumer.consume(event, handle).await;
log::trace!("{event:?} => consumer");
},
State::AwaitingLeave => {
// we just entered the deadzone of a client, so
// we need to ignore events that may still
// be on the way until a leave event occurs
// telling us the client registered the enter
if let Event::Leave() = event {
self.state = State::Sending;
}
// entering a client that is waiting for a leave
// event should still be possible
if let Event::Enter() = event {
self.state = State::Receiving;
}
},
}
},
}
// let the server know we are still alive once every second
if state.last_replied.is_none()
|| state.last_replied.is_some()
&& state.last_replied.unwrap().elapsed() > Duration::from_secs(1)
{
state.last_replied = Some(Instant::now());
if let Err(e) = send_event(&self.socket, Event::Pong(), addr).await {
log::error!("udp send: {}", e);
}
}
}
async fn handle_producer_event(&mut self, c: ClientHandle, e: Event) {
log::trace!("producer: ({c}) {e:?}");
// we are sending events
self.state = State::Sending;
// get client state for handle
let state = match self.client_manager.get_mut(c) {
Some(state) => state,
None => {
// should not happen
log::warn!("unknown client!");
self.producer.release();
self.state = State::Receiving;
return;
}
};
// if we just entered the client we want to send additional enter events until
// we get a leave event
if let State::Receiving | State::AwaitingLeave = self.state {
self.state = State::AwaitingLeave;
if let Some(addr) = state.client.active_addr {
if let Err(e) = send_event(&self.socket, Event::Enter(), addr).await {
log::error!("udp send: {}", e);
}
}
}
// otherwise we should have an address to
// transmit events to the corrensponding client
if let Some(addr) = state.client.active_addr {
@@ -414,16 +443,6 @@ impl Server {
log::error!("udp send: {}", e);
}
}
// we can not assume the client is in receiving mode because a
// client can always change mode from receiving to sending
// by restarting.
// To prevent both clients from being in sending mode,
// we send a release event.
if let Err(e) = send_event(&self.socket, Event::Release(), *addr).await {
if e.kind() != ErrorKind::WouldBlock {
log::error!("udp send: {}", e);
}
}
}
}