Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ struct ConnectionInner {
handshake_resp: HandshakeResp,
/// The last serial number used in an event by the server.
serial: AtomicU32,
state: Mutex<EiConnectionState>,
}

/// High-level client-side wrapper for `ei_connection`.
Expand Down Expand Up @@ -105,6 +106,16 @@ impl Connection {
pub fn serial(&self) -> u32 {
self.0.serial.load(Ordering::Relaxed)
}

/// Returns the current lifecycle state of this connection.
///
/// # Panics
///
/// Will panic if an internal Mutex is poisoned.
#[must_use]
pub fn state(&self) -> EiConnectionState {
*self.0.state.lock().unwrap()
}
}

/// Utility that converts low-level protocol-level events into high-level events defined in this
Expand Down Expand Up @@ -138,6 +149,7 @@ impl EiEventConverter {
context: context.clone(),
serial: AtomicU32::new(handshake_resp.serial),
handshake_resp,
state: Mutex::new(EiConnectionState::Connected),
})),
}
}
Expand Down Expand Up @@ -170,6 +182,10 @@ impl EiEventConverter {
/// Handles a low-level protocol-level [`ei::Event`], possibly converting it into a high-level
/// [`EiEvent`].
///
/// # Panics
///
/// Will panic if an internal Mutex is poisoned.
///
/// # Errors
///
/// The errors returned are protocol violations.
Expand All @@ -191,6 +207,7 @@ impl EiEventConverter {
proto_seat: seat,
name: None,
capability_map: CapabilityMap::default(),
state: Mutex::new(EiSeatState::New),
},
);
}
Expand All @@ -206,6 +223,7 @@ impl EiEventConverter {
reason,
explanation,
} => {
*self.connection.0.state.lock().unwrap() = EiConnectionState::Disconnected;
self.queue_event(EiEvent::Disconnected(Disconnected {
last_serial,
reason,
Expand Down Expand Up @@ -251,6 +269,7 @@ impl EiEventConverter {
.pending_seats
.remove(&seat)
.ok_or(EventError::SeatSetupEventAfterDone)?;
*seat.state.lock().unwrap() = EiSeatState::Done;
let seat = Seat(Arc::new(seat));
self.seats.insert(seat.0.proto_seat.clone(), seat.clone());
self.queue_event(EiEvent::SeatAdded(SeatAdded { seat }));
Expand All @@ -272,13 +291,15 @@ impl EiEventConverter {
regions: Vec::new(),
next_region_mapping_id: None,
keymap: None,
state: Mutex::new(EiDeviceState::Paused),
},
);
}
ei::seat::Event::Destroyed { serial } => {
self.connection.update_serial(serial);
self.pending_seats.remove(&seat);
if let Some(seat) = self.seats.remove(&seat) {
*seat.0.state.lock().unwrap() = EiSeatState::Removed;
self.queue_event(EiEvent::SeatRemoved(SeatRemoved { seat }));
}
}
Expand Down Expand Up @@ -357,6 +378,7 @@ impl EiEventConverter {
.devices
.get(&device)
.ok_or(EventError::DeviceEventBeforeDone)?;
*device.0.state.lock().unwrap() = EiDeviceState::Resumed;
self.queue_event(EiEvent::DeviceResumed(DeviceResumed {
device: device.clone(),
serial,
Expand All @@ -368,6 +390,7 @@ impl EiEventConverter {
.devices
.get(&device)
.ok_or(EventError::DeviceEventBeforeDone)?;
*device.0.state.lock().unwrap() = EiDeviceState::Paused;
self.queue_event(EiEvent::DevicePaused(DevicePaused {
device: device.clone(),
serial,
Expand All @@ -379,6 +402,7 @@ impl EiEventConverter {
.devices
.get(&device)
.ok_or(EventError::DeviceEventBeforeDone)?;
*device.0.state.lock().unwrap() = EiDeviceState::Emulating;
self.queue_event(EiEvent::DeviceStartEmulating(DeviceStartEmulating {
device: device.clone(),
serial,
Expand All @@ -391,6 +415,7 @@ impl EiEventConverter {
.devices
.get(&device)
.ok_or(EventError::DeviceEventBeforeDone)?;
*device.0.state.lock().unwrap() = EiDeviceState::Resumed;
self.queue_event(EiEvent::DeviceStopEmulating(DeviceStopEmulating {
device: device.clone(),
serial,
Expand Down Expand Up @@ -419,6 +444,7 @@ impl EiEventConverter {
self.connection.update_serial(serial);
self.pending_devices.remove(&device);
if let Some(device) = self.devices.remove(&device) {
*device.0.state.lock().unwrap() = EiDeviceState::RemovedFromServer;
for (_, obj) in device.0.interfaces.lock().unwrap().drain() {
self.device_for_interface.remove(&obj);
}
Expand Down Expand Up @@ -807,6 +833,54 @@ impl DeviceCapability {
}
}

/// Lifecycle state of a device, client side.
///
/// Mirrors libei's internal `ei_device_state` so the high-level layer can track and (later)
/// validate device lifecycle the same way libei does. States the client converter never observes
/// are omitted: the pre-`done` `NEW`/`AWAITING_READY` phases (a device is only exposed after its
/// `done` event), and `REMOVED_FROM_CLIENT`/`DEAD` (the converter does not model client-initiated
/// release).
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum EiDeviceState {
/// Input cannot flow. A newly delivered device starts here.
Paused,
/// Input may flow, but emulation has not started.
Resumed,
/// The device is actively emulating input (between `start_emulating` and `stop_emulating`).
Emulating,
/// The server destroyed the device.
RemovedFromServer,
}

/// Lifecycle state of a seat, client side.
///
/// Mirrors libei's `ei_seat_state`.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum EiSeatState {
/// The seat has been added but its capability advertisement is not yet complete.
New,
/// The seat is fully advertised and bindable.
Done,
/// The seat has been removed.
Removed,
}

/// Lifecycle state of a connection, client side.
///
/// Reduced from libei's `ei_state` to the phases observable after the handshake completes,
/// since the converter only runs post-handshake. Pre-handshake validation is handled
/// separately in the handshake path.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum EiConnectionState {
/// The connection is active.
Connected,
/// The connection is fully disconnected.
Disconnected,
}

/// Lookup table from [`DeviceCapability`] to a protocol capability.
#[derive(Clone, Copy, PartialEq, Eq, Default)]
struct CapabilityMap([u64; BitFlags::<DeviceCapability>::ALL.bits_c().count_ones() as usize]);
Expand All @@ -827,6 +901,7 @@ struct SeatInner {
proto_seat: ei::Seat,
name: Option<String>,
capability_map: CapabilityMap,
state: Mutex<EiSeatState>,
}

/// High-level client-side wrapper for `ei_seat`.
Expand Down Expand Up @@ -864,6 +939,16 @@ impl Seat {
self.0.name.as_deref()
}

/// Returns the current lifecycle state of this seat.
///
/// # Panics
///
/// Will panic if an internal Mutex is poisoned.
#[must_use]
pub fn state(&self) -> EiSeatState {
*self.0.state.lock().unwrap()
}

// TODO has_capability

/// Binds to a selection of the advertised capabilities received through
Expand Down Expand Up @@ -892,6 +977,7 @@ struct DeviceInner {
next_region_mapping_id: Option<String>,
// Only defined device with `ei_keyboard` interface
keymap: Option<Keymap>,
state: Mutex<EiDeviceState>,
}

/// High-level client-side wrapper for `ei_device`.
Expand Down Expand Up @@ -952,6 +1038,16 @@ impl Device {
self.0.keymap.as_ref()
}

/// Returns the current lifecycle state of this device.
///
/// # Panics
///
/// Will panic if an internal Mutex is poisoned.
#[must_use]
pub fn state(&self) -> EiDeviceState {
*self.0.state.lock().unwrap()
}

/// Returns an interface proxy if it is implemented for this device.
///
/// Interfaces of devices are implemented, such that there is one `ei_device` object and
Expand Down
Loading
Loading