From 735434438fb02674fbeaac020eca60a18c162ac3 Mon Sep 17 00:00:00 2001 From: Ferdinand Schober Date: Sun, 17 Dec 2023 17:38:06 +0100 Subject: [PATCH] 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. --- src/backend/consumer/macos.rs | 4 +- src/backend/producer/wayland.rs | 2 +- src/event.rs | 30 ++++++++-- src/server.rs | 101 +++++++++++++++++++------------- 4 files changed, 86 insertions(+), 51 deletions(-) diff --git a/src/backend/consumer/macos.rs b/src/backend/consumer/macos.rs index 76d0fdfc..2bec5467 100644 --- a/src/backend/consumer/macos.rs +++ b/src/backend/consumer/macos.rs @@ -209,9 +209,7 @@ impl EventConsumer for MacOSConsumer { } KeyboardEvent::Modifiers { .. } => {} }, - Event::Release() => {} - Event::Ping() => {} - Event::Pong() => {} + _ => () } } diff --git a/src/backend/producer/wayland.rs b/src/backend/producer/wayland.rs index 8e483ac3..0d032427 100644 --- a/src/backend/producer/wayland.rs +++ b/src/backend/producer/wayland.rs @@ -662,7 +662,7 @@ impl Dispatch 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(); diff --git a/src/event.rs b/src/event.rs index 4a65a01b..90869184 100644 --- a/src/event.rs +++ b/src/event.rs @@ -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 { 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> 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 { diff --git a/src/server.rs b/src/server.rs index afa5e4b5..29947dae 100644 --- a/src/server.rs +++ b/src/server.rs @@ -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; + // 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 { @@ -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); - } - } } }