Skip to content

Commit

Permalink
add leave event to make entering a client more reliable (#50)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
feschber authored Dec 17, 2023
1 parent 02d1b33 commit 7354344
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 51 deletions.
4 changes: 1 addition & 3 deletions src/backend/consumer/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,7 @@ impl EventConsumer for MacOSConsumer {
}
KeyboardEvent::Modifiers { .. } => {}
},
Event::Release() => {}
Event::Ping() => {}
Event::Pong() => {}
_ => ()
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/backend/producer/wayland.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
30 changes: 24 additions & 6 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}

Expand Down Expand Up @@ -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"),
}
Expand All @@ -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,
}
Expand Down Expand Up @@ -155,7 +170,8 @@ enum KeyboardEventType {
enum EventType {
Pointer,
Keyboard,
Release,
Enter,
Leave,
Ping,
Pong,
}
Expand Down Expand Up @@ -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![],
};
Expand Down Expand Up @@ -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 {
Expand Down
101 changes: 60 additions & 41 deletions src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ use crate::{
enum State {
Sending,
Receiving,
AwaitingLeave,
}

pub struct Server {
Expand Down Expand Up @@ -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;
// 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);
}
return;
}
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;
}

// 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 {
log::error!("udp send: {}", e);
}
// 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 {
Expand Down Expand Up @@ -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);
}
}
}
}

Expand Down

0 comments on commit 7354344

Please sign in to comment.