aboutsummaryrefslogtreecommitdiff
path: root/src/client/shell_runtime.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/shell_runtime.rs')
-rw-r--r--src/client/shell_runtime.rs114
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