use rmux_core::{key_code_lookup_bits, key_string_lookup_key, key_string_lookup_string};
use rmux_proto::{
ErrorResponse, OptionName, PaneTarget, Response, RmuxError, SendKeysResponse,
SendPrefixResponse,
};
use super::super::RequestHandler;
#[cfg(windows)]
use super::pane_io_encoding::{
prepare_attached_pane_console_input_writes, tokens_emulate_windows_cmd_select_all,
tokens_route_windows_control_as_pty_bytes, windows_console_input_for_target_tokens,
write_windows_console_input_action_to_target_io, PaneConsoleInputWrite,
WindowsConsoleInputAction,
};
#[cfg(windows)]
use super::pane_windows_console_sequence::prepare_windows_console_input_sequence;
use super::{
encode_key_for_target, encode_mouse_for_target, encode_tokens_for_target,
expand_send_key_tokens, pane_id_for_input_target, prepare_pane_input_write,
prepare_synchronized_pane_input_writes, resolve_input_target, write_bytes_to_targets,
PaneInputLiveness, PaneInputWrite,
};
use crate::keys::{parse_key_code, resolve_hex_key};
use crate::limits::bounded_repeat_count;
#[path = "send_keys/session_targets.rs"]
mod session_targets;
#[cfg(windows)]
use session_targets::console_input_write_sessions;
use session_targets::input_write_sessions;
impl RequestHandler {
pub(in crate::handler) async fn handle_send_keys(
&self,
request: rmux_proto::SendKeysRequest,
) -> Response {
let key_count = request.keys.len();
{
let state = self.state.lock().await;
if let Err(error) = pane_id_for_input_target(&state, &request.target) {
return Response::Error(ErrorResponse { error });
}
}
if key_count == 0 {
return Response::SendKeys(SendKeysResponse { key_count });
}
let keys = if request.keys.is_empty() {
request.keys
} else {
match self
.consume_copy_mode_key_tokens(std::process::id(), &request.target, &request.keys)
.await
{
Ok(keys) => keys,
Err(error) => return Response::Error(ErrorResponse { error }),
}
};
if key_count > 0 && keys.is_empty() {
return Response::SendKeys(SendKeysResponse { key_count });
}
let prepared = {
let mut state = self.state.lock().await;
let resolved = match encode_tokens_for_target(&state, &request.target, &keys) {
Ok(resolved) => resolved,
Err(error) => return Response::Error(ErrorResponse { error }),
};
#[cfg(windows)]
if !tokens_emulate_windows_cmd_select_all(&state, &request.target, &keys) {
match prepare_windows_console_input_sequence(
&mut state,
&request.target,
&keys,
None,
) {
Ok(Some(steps)) => {
drop(state);
return self
.write_windows_console_input_sequence_and_mark_interactive(
steps, key_count,
)
.await;
}
Ok(None) => {}
Err(error) => return Response::Error(ErrorResponse { error }),
}
if let Some((action, bytes)) =
windows_console_input_for_target_tokens(&state, &request.target, &keys, 1)
{
if tokens_route_windows_control_as_pty_bytes(&state, &request.target, &keys) {
let writes = match prepare_synchronized_pane_input_writes(
&mut state,
&request.target,
&bytes,
) {
Ok(writes) => writes,
Err(error) => return Response::Error(ErrorResponse { error }),
};
drop(state);
return self
.write_pane_input_and_mark_interactive(writes, bytes, key_count)
.await;
}
let writes = match prepare_attached_pane_console_input_writes(
&mut state,
&request.target,
&bytes,
action,
) {
Ok(writes) => writes,
Err(error) => return Response::Error(ErrorResponse { error }),
};
drop(state);
return self
.write_windows_console_input_and_mark_interactive(
writes,
action,
!bytes.is_empty(),
key_count,
)
.await;
}
}
let writes = match prepare_synchronized_pane_input_writes(
&mut state,
&request.target,
&resolved,
) {
Ok(writes) => writes,
Err(error) => return Response::Error(ErrorResponse { error }),
};
(writes, resolved)
};
self.write_pane_input_and_mark_interactive(prepared.0, prepared.1, key_count)
.await
}
#[async_recursion::async_recursion]
pub(in crate::handler) async fn handle_send_keys_ext(
&self,
requester_pid: u32,
request: rmux_proto::SendKeysExtRequest,
) -> Response {
self.handle_send_keys_ext_inner(requester_pid, request, None)
.await
}
pub(in crate::handler) async fn handle_send_keys_ext2(
&self,
requester_pid: u32,
request: rmux_proto::SendKeysExt2Request,
) -> Response {
let target_client = request.target_client;
let request = rmux_proto::SendKeysExtRequest {
target: request.target,
keys: request.keys,
expand_formats: request.expand_formats,
hex: request.hex,
literal: request.literal,
dispatch_key_table: request.dispatch_key_table,
copy_mode_command: request.copy_mode_command,
forward_mouse_event: request.forward_mouse_event,
reset_terminal: request.reset_terminal,
repeat_count: request.repeat_count,
};
self.handle_send_keys_ext_inner(requester_pid, request, target_client)
.await
}
#[async_recursion::async_recursion]
async fn handle_send_keys_ext_inner(
&self,
requester_pid: u32,
request: rmux_proto::SendKeysExtRequest,
target_client: Option<String>,
) -> Response {
let target_attach_pid = match target_client.as_deref() {
Some(target_client) => match self
.find_target_attach_client_pid(requester_pid, target_client, "send-keys")
.await
{
Ok(Some(attach_pid)) => Some(attach_pid),
Ok(None) => {
return Response::SendKeys(SendKeysResponse { key_count: 0 });
}
Err(error) => return Response::Error(ErrorResponse { error }),
},
None => None,
};
let attached_session = {
let active_attach = self.active_attach.lock().await;
active_attach.current_session_candidate(target_attach_pid.unwrap_or(requester_pid))
};
let target = {
let state = self.state.lock().await;
let target = match resolve_input_target(
&state,
request.target.as_ref(),
attached_session.as_ref(),
) {
Ok(target) => target,
Err(error) => return Response::Error(ErrorResponse { error }),
};
if let Err(error) = super::super::require_expected_pane_identity(&state, &target) {
return Response::Error(ErrorResponse { error });
}
target
};
let tokens = {
let state = self.state.lock().await;
match expand_send_key_tokens(&state, &target, &request.keys, request.expand_formats) {
Ok(tokens) => tokens,
Err(error) => return Response::Error(ErrorResponse { error }),
}
};
if request.copy_mode_command {
return Box::pin(self.handle_send_keys_copy_mode(
target_attach_pid.unwrap_or(requester_pid),
&request,
target,
&tokens,
))
.await;
}
if request.dispatch_key_table {
let attach_pid = match target_attach_pid {
Some(attach_pid) => attach_pid,
None => match self
.resolve_attached_client_pid(requester_pid, "send-keys")
.await
{
Ok(attach_pid) => attach_pid,
Err(error) => return Response::Error(ErrorResponse { error }),
},
};
let effective_requester_pid = target_attach_pid.unwrap_or(requester_pid);
let repeat_count = bounded_repeat_count(request.repeat_count);
for token in &tokens {
let key = if request.hex {
resolve_hex_key(token).map(u64::from)
} else {
parse_key_code(token)
};
let Some(key) = key else {
return Response::Error(ErrorResponse {
error: RmuxError::Server(format!("unknown key: {token}")),
});
};
for _ in 0..repeat_count {
if let Err(error) = Box::pin(self.dispatch_attached_key(
attach_pid,
effective_requester_pid,
&target,
key,
))
.await
{
return Response::Error(ErrorResponse { error });
}
}
}
return Response::SendKeys(SendKeysResponse {
key_count: tokens.len(),
});
}
if request.forward_mouse_event {
match self.exit_clock_mode(&target).await {
Ok(true) => {
return Response::SendKeys(SendKeysResponse { key_count: 0 });
}
Ok(false) => {}
Err(error) => return Response::Error(ErrorResponse { error }),
}
let attach_pid = match target_attach_pid {
Some(attach_pid) => attach_pid,
None => match self
.resolve_attached_client_pid(requester_pid, "send-keys")
.await
{
Ok(attach_pid) => attach_pid,
Err(error) => return Response::Error(ErrorResponse { error }),
},
};
let mouse_event = {
let active_attach = self.active_attach.lock().await;
active_attach
.by_pid
.get(&attach_pid)
.and_then(|active| active.mouse.current_event.clone())
};
let Some(mouse_event) = mouse_event else {
return Response::SendKeys(SendKeysResponse { key_count: 0 });
};
let mut state = self.state.lock().await;
let bytes = match encode_mouse_for_target(&state, &target, &mouse_event) {
Ok(bytes) => bytes,
Err(error) => return Response::Error(ErrorResponse { error }),
};
let write = match prepare_pane_input_write(
&mut state,
&target,
&bytes,
PaneInputLiveness::TolerateDead,
) {
Ok(write) => write,
Err(error) => return Response::Error(ErrorResponse { error }),
};
drop(state);
return self
.write_pane_input_and_mark_interactive(vec![write], bytes, 0)
.await;
}
let tokens =
if !tokens.is_empty() && !request.hex && !request.literal && !request.reset_terminal {
match self
.consume_copy_mode_key_tokens(
target_attach_pid.unwrap_or(requester_pid),
&target,
&tokens,
)
.await
{
Ok(tokens) => tokens,
Err(error) => return Response::Error(ErrorResponse { error }),
}
} else {
tokens
};
if !request.keys.is_empty() && tokens.is_empty() {
return Response::SendKeys(SendKeysResponse {
key_count: request.keys.len(),
});
}
if request.keys.is_empty() && !request.reset_terminal {
return Response::SendKeys(SendKeysResponse { key_count: 0 });
}
let prepared = {
let mut state = self.state.lock().await;
if request.reset_terminal {
if let Err(error) = state.reset_pane_terminal_state(&target) {
return Response::Error(ErrorResponse { error });
}
}
#[cfg(windows)]
if !request.hex
&& !request.literal
&& !request.reset_terminal
&& !tokens_emulate_windows_cmd_select_all(&state, &target, &tokens)
{
match prepare_windows_console_input_sequence(
&mut state,
&target,
&tokens,
request.repeat_count,
) {
Ok(Some(steps)) => {
drop(state);
return self
.write_windows_console_input_sequence_and_mark_interactive(
steps,
request.keys.len(),
)
.await;
}
Ok(None) => {}
Err(error) => return Response::Error(ErrorResponse { error }),
}
if let Some((action, bytes)) = windows_console_input_for_target_tokens(
&state,
&target,
&tokens,
bounded_repeat_count(request.repeat_count),
) {
let route_raw =
tokens_route_windows_control_as_pty_bytes(&state, &target, &tokens);
if route_raw {
let writes = match prepare_synchronized_pane_input_writes(
&mut state, &target, &bytes,
) {
Ok(writes) => writes,
Err(error) => return Response::Error(ErrorResponse { error }),
};
drop(state);
return self
.write_pane_input_and_mark_interactive(
writes,
bytes,
request.keys.len(),
)
.await;
}
let writes = match prepare_attached_pane_console_input_writes(
&mut state, &target, &bytes, action,
) {
Ok(writes) => writes,
Err(error) => return Response::Error(ErrorResponse { error }),
};
drop(state);
return self
.write_windows_console_input_and_mark_interactive(
writes,
action,
!bytes.is_empty(),
request.keys.len(),
)
.await;
}
}
let mut bytes = Vec::new();
if request.hex {
for token in &tokens {
let Some(byte) = resolve_hex_key(token) else {
return Response::Error(ErrorResponse {
error: RmuxError::Server(format!("invalid hex byte: {token}")),
});
};
bytes.push(byte);
}
} else if request.literal {
for token in &tokens {
bytes.extend_from_slice(token.as_bytes());
}
} else {
match encode_tokens_for_target(&state, &target, &tokens) {
Ok(encoded) => bytes.extend_from_slice(&encoded),
Err(error) => return Response::Error(ErrorResponse { error }),
}
}
let repeat_count = bounded_repeat_count(request.repeat_count);
let repeated = bytes.repeat(repeat_count);
let writes =
match prepare_synchronized_pane_input_writes(&mut state, &target, &repeated) {
Ok(writes) => writes,
Err(error) => return Response::Error(ErrorResponse { error }),
};
(writes, repeated)
};
self.write_pane_input_and_mark_interactive(prepared.0, prepared.1, request.keys.len())
.await
}
async fn consume_copy_mode_key_tokens(
&self,
requester_pid: u32,
target: &PaneTarget,
tokens: &[String],
) -> Result<Vec<String>, RmuxError> {
let mut remaining = Vec::new();
for (index, token) in tokens.iter().enumerate() {
if !self.target_is_in_copy_mode(target).await? {
remaining.extend(tokens[index..].iter().cloned());
break;
}
let Some(key) = parse_key_code(token) else {
remaining.extend(tokens[index..].iter().cloned());
break;
};
let handled = self
.handle_detached_copy_mode_key_code(requester_pid, target.clone(), key)
.await?;
if !handled {
remaining.extend(tokens[index..].iter().cloned());
break;
}
}
Ok(remaining)
}
pub(in crate::handler) async fn handle_send_prefix(
&self,
requester_pid: u32,
request: rmux_proto::SendPrefixRequest,
) -> Response {
let attached_session = {
let active_attach = self.active_attach.lock().await;
active_attach.current_session_candidate(requester_pid)
};
let target = {
let state = self.state.lock().await;
match resolve_input_target(&state, request.target.as_ref(), attached_session.as_ref()) {
Ok(target) => target,
Err(error) => return Response::Error(ErrorResponse { error }),
}
};
let (writes, encoded, canonical_key) = {
let mut state = self.state.lock().await;
let option = if request.secondary {
OptionName::Prefix2
} else {
OptionName::Prefix
};
let Some(value) = state.options.resolve(Some(target.session_name()), option) else {
return Response::Error(ErrorResponse {
error: RmuxError::Server("prefix key is not configured".to_owned()),
});
};
let Some(key) = key_string_lookup_string(value) else {
return Response::Error(ErrorResponse {
error: RmuxError::Server(format!("unknown key: {value}")),
});
};
let canonical_key = key_string_lookup_key(key_code_lookup_bits(key), false);
let encoded = match encode_key_for_target(&state, &target, key) {
Ok(Some(encoded)) => encoded,
Ok(None) => {
return Response::Error(ErrorResponse {
error: RmuxError::Server(format!(
"key {} cannot be sent to a pane",
canonical_key
)),
});
}
Err(error) => return Response::Error(ErrorResponse { error }),
};
let writes = match prepare_synchronized_pane_input_writes(&mut state, &target, &encoded)
{
Ok(writes) => writes,
Err(error) => return Response::Error(ErrorResponse { error }),
};
(writes, encoded, canonical_key)
};
match self
.write_pane_input_and_mark_interactive(writes, encoded, 1)
.await
{
Response::SendKeys(_) => Response::SendPrefix(SendPrefixResponse {
target: Some(target),
key: canonical_key,
key_count: 1,
}),
other => other,
}
}
async fn write_pane_input_and_mark_interactive(
&self,
writes: Vec<PaneInputWrite>,
bytes: Vec<u8>,
key_count: usize,
) -> Response {
let wrote_bytes = !bytes.is_empty();
let sessions = input_write_sessions(&writes);
let response = write_bytes_to_targets(writes, bytes, key_count).await;
if wrote_bytes && matches!(response, Response::SendKeys(_)) {
for session_name in sessions {
self.mark_attached_session_interactive_input(&session_name)
.await;
}
}
response
}
#[cfg(windows)]
async fn write_windows_console_input_and_mark_interactive(
&self,
writes: Vec<PaneConsoleInputWrite>,
action: WindowsConsoleInputAction,
wrote_bytes: bool,
key_count: usize,
) -> Response {
let 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 });
}
}
if wrote_bytes {
for session_name in sessions {
self.mark_attached_session_interactive_input(&session_name)
.await;
}
}
Response::SendKeys(SendKeysResponse { key_count })
}
}