diff options
Diffstat (limited to 'src/client/shell_runtime.rs')
| -rw-r--r-- | src/client/shell_runtime.rs | 114 |
1 files changed, 90 insertions, 24 deletions
diff --git a/src/client/shell_runtime.rs b/src/client/shell_runtime.rs index 00bb1406..2e8d1c72 100644 --- a/src/client/shell_runtime.rs +++ b/src/client/shell_runtime.rs @@ -6,7 +6,7 @@ pub(super) fn dispatch_client_shell_actions( endpoints: &mut endpoint::EndpointRegistry, mut shell: Option<&mut shell::ClientShellState>, detached_process_children: &mut Vec<std::process::Child>, - event_tx: &tokio::sync::mpsc::Sender<ClientLoopEvent>, + scheduled_activation: &mut Option<ClientLoopEvent>, ) -> Result<(Vec<crossterm::event::MouseEvent>, bool), ClientError> { let mut replay_mouse = Vec::new(); let mut repaint = false; @@ -32,7 +32,7 @@ pub(super) fn dispatch_client_shell_actions( endpoint_id, target, } => { - let _ = event_tx.try_send(ClientLoopEvent::ActivateEndpoint { + *scheduled_activation = Some(ClientLoopEvent::ActivateEndpoint { endpoint_id, target, force: false, @@ -169,6 +169,41 @@ fn install_pending_activation( *pending = Some(activation); } +fn local_activation_metadata_ready( + state: &ClientState, + endpoints: &endpoint::EndpointRegistry, +) -> bool { + endpoints + .connection(&endpoint::ClientEndpointId::Local) + .is_some_and(|connection| { + state.shell.as_ref().is_some_and(|shell| { + shell + .endpoint_snapshot_identity( + &endpoint::ClientEndpointId::Local, + connection.generation, + ) + .is_some() + }) + }) +} + +pub(super) fn take_ready_local_activation( + state: &mut ClientState, + endpoints: &endpoint::EndpointRegistry, +) -> Option<ClientLoopEvent> { + if !local_activation_metadata_ready(state, endpoints) { + return None; + } + state + .deferred_local_activation + .take() + .map(|intent| ClientLoopEvent::ActivateEndpoint { + endpoint_id: intent.endpoint_id, + target: intent.target, + force: false, + }) +} + pub(super) fn begin_endpoint_activation( state: &mut ClientState, endpoints: &mut endpoint::EndpointRegistry, @@ -179,26 +214,46 @@ pub(super) fn begin_endpoint_activation( target: Option<shell::ClientEndpointFocusTarget>, force: bool, now: std::time::Instant, - event_tx: &tokio::sync::mpsc::Sender<ClientLoopEvent>, + scheduled_activation: &mut Option<ClientLoopEvent>, ) -> Result<(), ClientError> { - if let Some(activation) = pending.as_mut() { - if activation.can_retarget(&endpoint_id) { - let retarget_error = activation.retarget(target, endpoints).err(); - if let Some(error) = retarget_error { - rollback_endpoint_activation(state, endpoints, pending, error, false); - } - } else { - // Once rollback starts, even a request for the original target is a new intent. It - // replaces the retained successor instead of mutating the transaction being retired. - let outcome = activation.supersede(endpoint_id, target, endpoints); - if let endpoint::ActivationRollback::Unavailable(message) = outcome { - *pending = None; - present_handoff_unavailable(state, message); - } + state.deferred_local_activation = None; + if endpoint_id.is_local() && !local_activation_metadata_ready(state, endpoints) { + state.deferred_local_activation = Some(endpoint::EndpointActivationIntent { + endpoint_id, + target, + }); + if let Some(shell) = state.shell.as_mut() { + shell.receive_endpoint_unavailable( + "Local is reconnecting; selection will resume when it is ready".into(), + ); } return Ok(()); } - let already_active = !force + let replace_pending = endpoint_id.is_local() + && pending + .as_ref() + .is_some_and(|activation| !activation.can_retarget(&endpoint_id)); + if !replace_pending { + if let Some(activation) = pending.as_mut() { + if activation.can_retarget(&endpoint_id) { + let retarget_error = activation.retarget(target, endpoints).err(); + if let Some(error) = retarget_error { + rollback_endpoint_activation(state, endpoints, pending, error, false); + } + } else { + // Once rollback starts, even a request for the original target is a new intent. + // Retain it until restoration finishes; Local can instead abandon this handoff. + let outcome = activation.supersede(endpoint_id, target, endpoints); + if let endpoint::ActivationRollback::Unavailable(message) = outcome { + *pending = None; + present_handoff_unavailable(state, message); + } + } + return Ok(()); + } + } + let already_active = !replace_pending + && !force && endpoints.active_id() == &endpoint_id && endpoints .connection(&endpoint_id) @@ -212,7 +267,7 @@ pub(super) fn begin_endpoint_activation( endpoints, Some(shell), &mut state.detached_process_children, - event_tx, + scheduled_activation, )?; if repaint { if let Some(frame) = shell.compose(state.reported_size.0, state.reported_size.1) { @@ -233,7 +288,7 @@ pub(super) fn begin_endpoint_activation( state.reported_cell_size.1, state.pixel_geometry_exact, ); - match endpoint::PendingEndpointActivation::begin( + match endpoint::PendingEndpointActivation::prepare( shell, endpoints, endpoint_id.clone(), @@ -241,7 +296,17 @@ pub(super) fn begin_endpoint_activation( resize, *next_surface_serial, now, - ) { + ) + .and_then(|activation| { + // Preserve the old transaction if Local fails preflight. After retiring it, + // all send failures belong to the prepared replacement's rollback path. + if replace_pending { + if let Some(previous) = pending.take() { + previous.abandon(endpoints); + } + } + activation.start(endpoints) + }) { Ok(activation) => install_pending_activation( state, endpoint_commands, @@ -388,7 +453,8 @@ pub(super) fn complete_endpoint_activation( if let Some(frame) = frame { state.present_frame(frame); } - if let Some(intent) = successor { + // A Local selection made while reconnecting is newer than this transaction's successor. + if let Some(intent) = successor.filter(|_| state.deferred_local_activation.is_none()) { return Ok(Some(ClientLoopEvent::ActivateEndpoint { endpoint_id: intent.endpoint_id, target: intent.target, @@ -619,7 +685,7 @@ pub(super) fn finish_client_shell_input( pending_activation: &mut Option<endpoint::PendingEndpointActivation>, endpoint_commands: &mut endpoint_commands::EndpointCommands, prefix_input_source: &mut impl crate::platform::PrefixInputSource, - event_tx: &tokio::sync::mpsc::Sender<ClientLoopEvent>, + scheduled_activation: &mut Option<ClientLoopEvent>, ) -> Result<bool, ClientError> { apply_client_shell_input_source_changes(state, prefix_input_source); if outcome.detach { @@ -658,7 +724,7 @@ pub(super) fn finish_client_shell_input( endpoints, state.shell.as_mut(), &mut state.detached_process_children, - event_tx, + scheduled_activation, )?; let frame = if dispatch_repaint { state |
