rmux-server 0.10.0

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
use rmux_proto::{ErrorResponse, PaneTarget, Response, RmuxError, SendKeysResponse, SessionName};

use super::super::RequestHandler;
use super::pane_io_encoding::{
    prepare_pane_console_input_write, synchronized_input_targets,
    tokens_emulate_windows_cmd_select_all, tokens_route_windows_control_as_pty_bytes,
    windows_console_input_for_target_tokens, windows_console_input_for_token,
    write_windows_console_input_action_to_target_io, PaneConsoleInputWrite,
    WindowsConsoleInputAction,
};
use super::{
    encode_tokens_for_target, prepare_pane_input_write, write_bytes_to_targets, PaneInputLiveness,
    PaneInputWrite,
};
use crate::limits::bounded_repeat_count;
use crate::pane_terminals::HandlerState;

pub(super) enum PreparedWindowsConsoleInputStep {
    Bytes {
        writes: Vec<PaneInputWrite>,
        bytes: Vec<u8>,
    },
    Console {
        writes: Vec<PaneConsoleInputWrite>,
        action: WindowsConsoleInputAction,
        wrote_bytes: bool,
    },
}

pub(super) fn prepare_windows_console_input_sequence(
    state: &mut HandlerState,
    target: &PaneTarget,
    tokens: &[String],
    repeat_count: Option<usize>,
) -> Result<Option<Vec<PreparedWindowsConsoleInputStep>>, RmuxError> {
    prepare_windows_console_input_sequence_for_scope(
        state,
        target,
        tokens,
        repeat_count,
        WindowsConsoleInputScope::Synchronized,
    )
}

pub(super) fn prepare_single_pane_windows_console_input_sequence(
    state: &mut HandlerState,
    target: &PaneTarget,
    tokens: &[String],
    repeat_count: Option<usize>,
) -> Result<Option<Vec<PreparedWindowsConsoleInputStep>>, RmuxError> {
    prepare_windows_console_input_sequence_for_scope(
        state,
        target,
        tokens,
        repeat_count,
        WindowsConsoleInputScope::SinglePane,
    )
}

#[derive(Clone, Copy)]
enum WindowsConsoleInputScope {
    Synchronized,
    SinglePane,
}

fn prepare_windows_console_input_sequence_for_scope(
    state: &mut HandlerState,
    target: &PaneTarget,
    tokens: &[String],
    repeat_count: Option<usize>,
    scope: WindowsConsoleInputScope,
) -> Result<Option<Vec<PreparedWindowsConsoleInputStep>>, RmuxError> {
    if tokens.is_empty() || !tokens_contain_windows_console_input(tokens) {
        return Ok(None);
    }

    let repeat_count = bounded_repeat_count(repeat_count);
    let mut steps = Vec::with_capacity(tokens.len().saturating_mul(repeat_count));
    for _ in 0..repeat_count {
        for token in tokens {
            for input_target in input_targets_for_scope(state, target, scope)? {
                let single_token = [token.clone()];
                if !tokens_emulate_windows_cmd_select_all(state, &input_target, &single_token) {
                    if let Some((action, console_bytes)) = windows_console_input_for_target_tokens(
                        state,
                        &input_target,
                        &single_token,
                        1,
                    ) {
                        if tokens_route_windows_control_as_pty_bytes(
                            state,
                            &input_target,
                            &single_token,
                        ) {
                            let write = prepare_pane_input_write(
                                state,
                                &input_target,
                                &console_bytes,
                                PaneInputLiveness::TolerateDead,
                            )?;
                            steps.push(PreparedWindowsConsoleInputStep::Bytes {
                                writes: vec![write],
                                bytes: console_bytes,
                            });
                        } else {
                            let wrote_bytes = !console_bytes.is_empty();
                            let write = prepare_pane_console_input_write(
                                state,
                                &input_target,
                                &console_bytes,
                                action,
                            )?;
                            steps.push(PreparedWindowsConsoleInputStep::Console {
                                writes: vec![write],
                                action,
                                wrote_bytes,
                            });
                        }
                        continue;
                    }
                }

                let bytes = encode_tokens_for_target(state, &input_target, &single_token)?;
                let write = prepare_pane_input_write(
                    state,
                    &input_target,
                    &bytes,
                    PaneInputLiveness::TolerateDead,
                )?;
                steps.push(PreparedWindowsConsoleInputStep::Bytes {
                    writes: vec![write],
                    bytes,
                });
            }
        }
    }

    Ok(Some(steps))
}

fn tokens_contain_windows_console_input(tokens: &[String]) -> bool {
    tokens
        .iter()
        .any(|token| windows_console_input_for_token(token, 1).is_some())
}

fn input_targets_for_scope(
    state: &HandlerState,
    target: &PaneTarget,
    scope: WindowsConsoleInputScope,
) -> Result<Vec<PaneTarget>, RmuxError> {
    match scope {
        WindowsConsoleInputScope::Synchronized => synchronized_input_targets(state, target),
        WindowsConsoleInputScope::SinglePane => Ok(vec![target.clone()]),
    }
}

impl RequestHandler {
    pub(super) async fn write_windows_console_input_sequence_and_mark_interactive(
        &self,
        steps: Vec<PreparedWindowsConsoleInputStep>,
        key_count: usize,
    ) -> Response {
        let mut interactive_sessions = Vec::new();
        for step in steps {
            match step {
                PreparedWindowsConsoleInputStep::Bytes { writes, bytes } => {
                    if !bytes.is_empty() {
                        push_unique_sessions(
                            &mut interactive_sessions,
                            input_write_sessions(&writes),
                        );
                    }
                    let response = write_bytes_to_targets(writes, bytes, key_count).await;
                    if !matches!(response, Response::SendKeys(_)) {
                        return response;
                    }
                }
                PreparedWindowsConsoleInputStep::Console {
                    writes,
                    action,
                    wrote_bytes,
                } => {
                    if wrote_bytes {
                        push_unique_sessions(
                            &mut interactive_sessions,
                            console_input_write_sessions(&writes),
                        );
                    }
                    for write in writes {
                        if let Err(error) =
                            write_windows_console_input_action_to_target_io(write, action).await
                        {
                            return Response::Error(ErrorResponse { error });
                        }
                    }
                }
            }
        }
        for session_name in interactive_sessions {
            self.mark_attached_session_interactive_input(&session_name)
                .await;
        }
        Response::SendKeys(SendKeysResponse { key_count })
    }
}

fn input_write_sessions(writes: &[PaneInputWrite]) -> Vec<SessionName> {
    let mut sessions = Vec::new();
    for write in writes {
        let session_name = write.session_name();
        if !sessions.iter().any(|existing| existing == session_name) {
            sessions.push(session_name.clone());
        }
    }
    sessions
}

fn console_input_write_sessions(writes: &[PaneConsoleInputWrite]) -> Vec<SessionName> {
    let mut sessions = Vec::new();
    for write in writes {
        let session_name = write.session_name();
        if !sessions.iter().any(|existing| existing == session_name) {
            sessions.push(session_name.clone());
        }
    }
    sessions
}

fn push_unique_sessions(sessions: &mut Vec<SessionName>, new_sessions: Vec<SessionName>) {
    for session_name in new_sessions {
        if !sessions.iter().any(|existing| existing == &session_name) {
            sessions.push(session_name);
        }
    }
}