use std::sync::{Arc, Mutex, mpsc};
use std::time::Instant;
use std::collections::{HashMap, HashSet, VecDeque};
use crossterm::event::{KeyCode, KeyModifiers};
use portable_pty::MasterPty;
use ratatui::prelude::Rect;
use chrono::Local;
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Clone, Debug)]
pub enum ControlNotification {
Output { pane_id: usize, data: String },
WindowAdd { window_id: usize },
WindowClose { window_id: usize },
WindowRenamed { window_id: usize, name: String },
WindowPaneChanged { window_id: usize, pane_id: usize },
LayoutChange { window_id: usize, layout: String },
SessionChanged { session_id: usize, name: String },
SessionRenamed { name: String },
SessionWindowChanged { session_id: usize, window_id: usize },
SessionsChanged,
PaneModeChanged { pane_id: usize },
ClientDetached { client: String },
Continue { pane_id: usize },
Pause { pane_id: usize },
ExtendedOutput { pane_id: usize, age_ms: u64, data: String },
SubscriptionChanged {
name: String,
session_id: usize,
window_id: usize,
window_index: usize,
pane_id: usize,
value: String,
},
Exit { reason: Option<String> },
PasteBufferChanged { name: String },
PasteBufferDeleted { name: String },
ClientSessionChanged { client: String, session_id: usize, name: String },
Message { text: String },
}
pub struct ControlClient {
pub client_id: u64,
pub cmd_counter: u64,
pub echo_enabled: bool,
pub notification_tx: mpsc::SyncSender<ControlNotification>,
pub paused_panes: HashSet<usize>,
pub subscriptions: HashMap<String, (String, String)>,
pub subscription_values: HashMap<String, String>,
pub subscription_last_check: HashMap<String, Instant>,
pub pause_after_secs: Option<u64>,
pub output_paused_panes: HashSet<usize>,
pub pane_last_output: HashMap<usize, Instant>,
}
#[derive(Clone, Debug)]
pub struct ClientInfo {
pub id: u64,
pub width: u16,
pub height: u16,
pub connected_at: std::time::Instant,
pub last_activity: std::time::Instant,
pub tty_name: String,
pub is_control: bool,
}
pub struct Pane {
pub master: Box<dyn MasterPty>,
pub writer: Box<dyn std::io::Write + Send>,
pub child: Box<dyn portable_pty::Child>,
pub term: Arc<Mutex<vt100::Parser>>,
pub last_rows: u16,
pub last_cols: u16,
pub id: usize,
pub title: String,
pub title_locked: bool,
pub child_pid: Option<u32>,
pub data_version: std::sync::Arc<std::sync::atomic::AtomicU64>,
pub last_title_check: Instant,
pub last_infer_title: Instant,
pub dead: bool,
pub vt_bridge_cache: Option<(Instant, bool)>,
pub vti_mode_cache: Option<(Instant, bool)>,
pub mouse_input_cache: Option<(Instant, bool)>,
pub cursor_shape: std::sync::Arc<std::sync::atomic::AtomicU8>,
pub bell_pending: std::sync::Arc<std::sync::atomic::AtomicBool>,
pub copy_state: Option<CopyModeState>,
pub pane_style: Option<String>,
pub squelch_until: Option<Instant>,
pub output_ring: Arc<Mutex<VecDeque<u8>>>,
}
pub struct WarmPane {
pub master: Box<dyn MasterPty>,
pub writer: Box<dyn std::io::Write + Send>,
pub child: Box<dyn portable_pty::Child>,
pub term: Arc<Mutex<vt100::Parser>>,
pub data_version: std::sync::Arc<std::sync::atomic::AtomicU64>,
pub cursor_shape: std::sync::Arc<std::sync::atomic::AtomicU8>,
pub bell_pending: std::sync::Arc<std::sync::atomic::AtomicBool>,
pub child_pid: Option<u32>,
pub pane_id: usize,
pub rows: u16,
pub cols: u16,
pub output_ring: Arc<Mutex<VecDeque<u8>>>,
}
pub struct ForwardedPane {
pub master: Box<dyn MasterPty>,
pub child: Box<dyn portable_pty::Child>,
pub listener_port: u16,
pub pid: Option<u32>,
pub title: String,
pub rows: u16,
pub cols: u16,
pub shutdown: Arc<std::sync::atomic::AtomicBool>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum LayoutKind { Horizontal, Vertical }
pub enum Node {
Leaf(Pane),
Split { kind: LayoutKind, sizes: Vec<u16>, children: Vec<Node> },
}
pub struct Window {
pub root: Node,
pub active_path: Vec<usize>,
pub name: String,
pub id: usize,
pub activity_flag: bool,
pub bell_flag: bool,
pub silence_flag: bool,
pub last_output_time: std::time::Instant,
pub last_seen_version: u64,
pub manual_rename: bool,
pub layout_index: usize,
pub pane_mru: Vec<usize>,
pub zoom_saved: Option<Vec<(Vec<usize>, Vec<u16>)>>,
pub linked_from: Option<usize>,
}
#[derive(Clone)]
pub struct MenuItem {
pub name: String,
pub key: Option<char>,
pub command: String,
pub is_separator: bool,
}
#[derive(Clone)]
pub struct Menu {
pub title: String,
pub items: Vec<MenuItem>,
pub selected: usize,
pub x: Option<i16>,
pub y: Option<i16>,
}
#[derive(Clone)]
pub struct Hook {
pub name: String,
pub command: String,
}
pub struct PipePaneState {
pub pane_id: usize,
pub process: Option<std::process::Child>,
pub stdin: bool,
pub stdout: bool,
}
pub struct WaitChannel {
pub locked: bool,
pub waiters: Vec<mpsc::Sender<()>>,
}
pub enum Mode {
Passthrough,
Prefix { armed_at: Instant },
CommandPrompt { input: String, cursor: usize },
WindowChooser { selected: usize, tree: Vec<crate::session::TreeEntry> },
RenamePrompt { input: String },
RenameSessionPrompt { input: String },
CopyMode,
PaneChooser { opened_at: Instant },
MenuMode { menu: Menu },
PopupMode {
command: String,
output: String,
process: Option<std::process::Child>,
width: u16,
height: u16,
close_on_exit: bool,
popup_pane: Option<Pane>,
scroll_offset: u16,
},
ConfirmMode {
prompt: String,
command: String,
input: String,
},
CopySearch {
input: String,
forward: bool,
},
ClockMode,
BufferChooser { selected: usize },
WindowIndexPrompt { input: String },
CustomizeMode {
options: Vec<(String, String, String)>,
selected: usize,
scroll_offset: usize,
editing: bool,
edit_buffer: String,
edit_cursor: usize,
filter: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SelectionMode { Char, Line, Rect }
#[derive(Clone)]
pub struct CopyModeState {
pub anchor: Option<(u16, u16)>,
pub anchor_scroll_offset: usize,
pub pos: Option<(u16, u16)>,
pub scroll_offset: usize,
pub selection_mode: SelectionMode,
pub search_query: String,
pub count: Option<usize>,
pub search_matches: Vec<(u16, u16, u16)>,
pub search_idx: usize,
pub search_forward: bool,
pub find_char_pending: Option<u8>,
pub text_object_pending: Option<u8>,
pub register_pending: bool,
pub register: Option<char>,
pub in_search: bool,
pub search_input: String,
pub search_input_forward: bool,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FocusDir { Left, Right, Up, Down }
pub struct AppState {
pub windows: Vec<Window>,
pub active_idx: usize,
pub mode: Mode,
pub escape_time_ms: u64,
pub repeat_time_ms: u64,
pub prefix_repeating: bool,
pub prefix_key: (KeyCode, KeyModifiers),
pub prefix2_key: Option<(KeyCode, KeyModifiers)>,
pub prediction_dimming: bool,
pub allow_predictions: bool,
pub drag: Option<DragState>,
pub last_window_area: Rect,
pub mouse_enabled: bool,
pub scroll_enter_copy_mode: bool,
pub pwsh_mouse_selection: bool,
pub mouse_selection: bool,
pub paste_detection: bool,
pub choose_tree_preview: bool,
pub paste_buffers: Vec<String>,
pub named_buffers: std::collections::HashMap<String, String>,
pub paste_next_index: u32,
pub status_left: String,
pub status_right: String,
pub window_base_index: usize,
pub copy_anchor: Option<(u16,u16)>,
pub copy_anchor_scroll_offset: usize,
pub copy_pos: Option<(u16,u16)>,
pub copy_mouse_down_cell: Option<(u16,u16)>,
pub copy_scroll_offset: usize,
pub copy_selection_mode: SelectionMode,
pub copy_search_query: String, pub copy_count: Option<usize>, pub copy_search_matches: Vec<(u16, u16, u16)>,
pub copy_search_idx: usize,
pub copy_search_forward: bool,
pub copy_find_char_pending: Option<u8>,
pub copy_text_object_pending: Option<u8>,
pub copy_register_pending: bool,
pub copy_register: Option<char>,
pub named_registers: std::collections::HashMap<char, String>,
pub display_map: Vec<(usize, Vec<usize>)>,
pub key_tables: std::collections::HashMap<String, Vec<Bind>>,
pub current_key_table: Option<String>,
pub control_rx: Option<mpsc::Receiver<CtrlReq>>,
pub control_port: Option<u16>,
pub session_key: String,
pub run_shell_rx: Option<mpsc::Receiver<(String, String)>>,
pub run_shell_tx: Option<mpsc::Sender<(String, String)>>,
pub session_name: String,
pub session_id: usize,
pub socket_name: Option<String>,
pub attached_clients: usize,
pub client_sizes: std::collections::HashMap<u64, (u16, u16)>,
pub latest_client_id: Option<u64>,
pub client_registry: std::collections::HashMap<u64, ClientInfo>,
pub created_at: chrono::DateTime<Local>,
pub next_win_id: usize,
pub next_pane_id: usize,
pub client_prefix_active: bool,
pub sync_input: bool,
pub hooks: std::collections::HashMap<String, Vec<String>>,
pub wait_channels: std::collections::HashMap<String, WaitChannel>,
pub pipe_panes: Vec<PipePaneState>,
pub last_window_idx: usize,
pub last_pane_path: Vec<usize>,
pub tab_positions: Vec<(usize, u16, u16)>,
pub history_limit: usize,
pub display_time_ms: u64,
pub display_panes_time_ms: u64,
pub pane_base_index: usize,
pub focus_events: bool,
pub mode_keys: String,
pub status_visible: bool,
pub status_position: String,
pub status_style: String,
pub default_shell: String,
pub word_separators: String,
pub renumber_windows: bool,
pub automatic_rename: bool,
pub allow_rename: bool,
pub allow_set_title: bool,
pub monitor_activity: bool,
pub visual_activity: bool,
pub activity_action: String,
pub silence_action: String,
pub remain_on_exit: bool,
pub destroy_unattached: bool,
pub exit_empty: bool,
pub aggressive_resize: bool,
pub set_titles: bool,
pub set_titles_string: String,
pub update_environment: Vec<String>,
pub environment: std::collections::HashMap<String, String>,
pub user_options: std::collections::HashMap<String, String>,
pub user_set_options: std::collections::HashSet<String>,
pub pane_border_style: String,
pub pane_active_border_style: String,
pub pane_border_hover_style: String,
pub window_status_format: String,
pub window_status_current_format: String,
pub window_status_separator: String,
pub window_status_style: String,
pub window_status_current_style: String,
pub window_status_activity_style: String,
pub window_status_bell_style: String,
pub window_status_last_style: String,
pub message_style: String,
pub message_command_style: String,
pub mode_style: String,
pub status_left_style: String,
pub status_right_style: String,
pub marked_pane: Option<(usize, usize)>,
pub monitor_silence: u64,
pub bell_action: String,
pub visual_bell: bool,
pub command_history: Vec<String>,
pub command_history_idx: usize,
pub command_vi_normal: bool,
pub status_interval: u64,
pub last_status_interval_fire: std::time::Instant,
pub status_justify: String,
pub main_pane_width: u16,
pub main_pane_height: u16,
pub status_left_length: usize,
pub status_right_length: usize,
pub status_lines: usize,
pub status_format: Vec<String>,
pub window_size: String,
pub allow_passthrough: String,
pub copy_command: String,
pub command_aliases: std::collections::HashMap<String, String>,
pub set_clipboard: String,
pub clipboard_osc52: Option<String>,
pub bell_forward: bool,
pub env_shim: bool,
pub claude_code_fix_tty: bool,
pub claude_code_force_interactive: bool,
pub last_hover_pos: Option<(u16, u16)>,
pub last_mouse_x: u16,
pub last_mouse_y: u16,
pub status_message: Option<(String, std::time::Instant, Option<u64>)>,
pub warm_enabled: bool,
pub warm_pane: Option<WarmPane>,
pub pending_plugin_scripts: Vec<String>,
pub control_clients: HashMap<u64, ControlClient>,
pub session_group: Option<String>,
pub defaults_suppressed: bool,
pub forwarded_panes: HashMap<u64, ForwardedPane>,
pub next_forward_id: u64,
}
impl AppState {
pub fn new(session_name: String) -> Self {
Self {
windows: Vec::new(),
active_idx: 0,
mode: Mode::Passthrough,
escape_time_ms: 500,
repeat_time_ms: 500,
prefix_repeating: false,
prefix_key: (crossterm::event::KeyCode::Char('b'), crossterm::event::KeyModifiers::CONTROL),
prefix2_key: None,
prediction_dimming: std::env::var("PSMUX_DIM_PREDICTIONS")
.map(|v| v == "1" || v.to_lowercase() == "true")
.unwrap_or(false),
allow_predictions: false,
drag: None,
last_window_area: Rect { x: 0, y: 0, width: 120, height: 30 },
mouse_enabled: true,
scroll_enter_copy_mode: true,
pwsh_mouse_selection: false,
mouse_selection: true,
paste_detection: true,
choose_tree_preview: false,
paste_buffers: Vec::new(),
named_buffers: std::collections::HashMap::new(),
paste_next_index: 0,
status_left: "[#S] ".to_string(),
status_right: "#{?window_bigger,[#{window_offset_x}#,#{window_offset_y}] ,}\"#{=21:pane_title}\" %H:%M %d-%b-%y".to_string(),
window_base_index: 0,
copy_anchor: None,
copy_anchor_scroll_offset: 0,
copy_pos: None,
copy_mouse_down_cell: None,
copy_scroll_offset: 0,
copy_selection_mode: SelectionMode::Char,
copy_count: None,
copy_search_query: String::new(),
copy_search_matches: Vec::new(),
copy_search_idx: 0,
copy_search_forward: true,
copy_find_char_pending: None,
copy_text_object_pending: None,
copy_register_pending: false,
copy_register: None,
named_registers: std::collections::HashMap::new(),
display_map: Vec::new(),
key_tables: std::collections::HashMap::new(),
current_key_table: None,
control_rx: None,
control_port: None,
session_key: String::new(),
run_shell_rx: None,
run_shell_tx: None,
session_name,
session_id: {
static NEXT_SESSION_ID: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
NEXT_SESSION_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
},
socket_name: None,
attached_clients: 0,
client_sizes: std::collections::HashMap::new(),
latest_client_id: None,
client_registry: std::collections::HashMap::new(),
created_at: Local::now(),
next_win_id: 1,
next_pane_id: 1,
client_prefix_active: false,
sync_input: false,
hooks: std::collections::HashMap::new(),
wait_channels: std::collections::HashMap::new(),
pipe_panes: Vec::new(),
last_window_idx: 0,
last_pane_path: Vec::new(),
tab_positions: Vec::new(),
history_limit: 2000,
display_time_ms: 750,
display_panes_time_ms: 1000,
pane_base_index: 0,
focus_events: false,
mode_keys: "emacs".to_string(),
status_visible: true,
status_position: "bottom".to_string(),
status_style: "bg=green,fg=black".to_string(),
default_shell: String::new(),
word_separators: " -_@".to_string(),
renumber_windows: false,
automatic_rename: true,
allow_rename: true,
allow_set_title: false,
monitor_activity: false,
visual_activity: false,
activity_action: "other".to_string(),
silence_action: "other".to_string(),
remain_on_exit: false,
destroy_unattached: false,
exit_empty: true,
aggressive_resize: false,
set_titles: false,
set_titles_string: String::new(),
update_environment: vec![
"DISPLAY".to_string(),
"KRB5CCNAME".to_string(),
"SSH_ASKPASS".to_string(),
"SSH_AUTH_SOCK".to_string(),
"SSH_AGENT_PID".to_string(),
"SSH_CONNECTION".to_string(),
"WINDOWID".to_string(),
"XAUTHORITY".to_string(),
],
environment: std::collections::HashMap::new(),
user_options: std::collections::HashMap::new(),
user_set_options: std::collections::HashSet::new(),
pane_border_style: String::new(),
pane_active_border_style: "fg=green".to_string(),
pane_border_hover_style: "fg=yellow".to_string(),
window_status_format: "#I:#W#{?window_flags,#{window_flags}, }".to_string(),
window_status_current_format: "#I:#W#{?window_flags,#{window_flags}, }".to_string(),
window_status_separator: " ".to_string(),
window_status_style: String::new(),
window_status_current_style: String::new(),
window_status_activity_style: "reverse".to_string(),
window_status_bell_style: "reverse".to_string(),
window_status_last_style: String::new(),
message_style: "bg=yellow,fg=black".to_string(),
message_command_style: "bg=black,fg=yellow".to_string(),
mode_style: "bg=yellow,fg=black".to_string(),
status_left_style: String::new(),
status_right_style: String::new(),
marked_pane: None,
monitor_silence: 0,
bell_action: "any".to_string(),
visual_bell: false,
command_history: Vec::new(),
command_history_idx: 0,
command_vi_normal: false,
status_interval: 15,
last_status_interval_fire: std::time::Instant::now(),
status_justify: "left".to_string(),
main_pane_width: 0,
main_pane_height: 0,
status_left_length: 10,
status_right_length: 40,
status_lines: 1,
status_format: Vec::new(),
window_size: "latest".to_string(),
allow_passthrough: "off".to_string(),
copy_command: String::new(),
command_aliases: std::collections::HashMap::new(),
set_clipboard: "on".to_string(),
clipboard_osc52: None,
bell_forward: false,
env_shim: true,
claude_code_fix_tty: true,
claude_code_force_interactive: true,
last_hover_pos: None,
last_mouse_x: 0,
last_mouse_y: 0,
status_message: None,
warm_enabled: std::env::var("PSMUX_NO_WARM").map(|v| v != "1" && v != "true").unwrap_or(true),
warm_pane: None,
pending_plugin_scripts: Vec::new(),
control_clients: HashMap::new(),
session_group: None,
defaults_suppressed: false,
forwarded_panes: HashMap::new(),
next_forward_id: 1,
}
}
pub fn port_file_base(&self) -> String {
if let Some(ref sn) = self.socket_name {
format!("{}__{}", sn, self.session_name)
} else {
self.session_name.clone()
}
}
}
pub struct DragState {
pub split_path: Vec<usize>,
pub kind: LayoutKind,
pub index: usize,
pub start_x: u16,
pub start_y: u16,
pub left_initial: u16,
pub _right_initial: u16,
pub total_pixels: u16,
}
#[derive(Clone)]
pub enum Action {
DisplayPanes,
MoveFocus(FocusDir),
Command(String),
CommandChain(Vec<String>),
NewWindow,
SplitHorizontal,
SplitVertical,
KillPane,
NextWindow,
PrevWindow,
CopyMode,
Paste,
Detach,
RenameWindow,
WindowChooser,
SessionChooser,
ZoomPane,
SwitchTable(String),
}
#[derive(Clone)]
pub struct Bind { pub key: (KeyCode, KeyModifiers), pub action: Action, pub repeat: bool }
pub enum CtrlReq {
NewWindow(Option<String>, Option<String>, bool, Option<String>), NewWindowPrint(Option<String>, Option<String>, bool, Option<String>, Option<String>, mpsc::Sender<String>), SplitWindow(LayoutKind, Option<String>, bool, Option<String>, Option<(u16, bool)>, mpsc::Sender<String>), SplitWindowPrint(LayoutKind, Option<String>, bool, Option<String>, Option<(u16, bool)>, Option<String>, mpsc::Sender<String>), KillPane,
KillPaneById(usize),
CapturePane(mpsc::Sender<String>),
CapturePaneStyled(mpsc::Sender<String>, Option<i32>, Option<i32>),
FocusWindow(usize),
FocusWindowById(usize),
FocusWindowByName(String),
FocusWindowTemp(usize),
FocusWindowByIdTemp(usize),
FocusWindowByNameTemp(String),
FocusPane(usize),
FocusPaneByIndex(usize),
FocusPaneTemp(usize),
FocusPaneByIndexTemp(usize),
SessionInfo(mpsc::Sender<String>),
SessionInfoFormat(mpsc::Sender<String>, String),
CapturePaneRange(mpsc::Sender<String>, Option<i32>, Option<i32>),
ClientAttach(u64),
ClientDetach(u64),
DumpLayout(mpsc::Sender<String>),
DumpState(mpsc::Sender<String>, bool), SendText(String),
SendKey(String),
SendPaste(String),
ZoomPane,
PrefixBegin,
PrefixEnd,
CopyEnter,
CopyEnterPageUp,
CopyMove(i16, i16),
CopyAnchor,
CopyYank,
CopyRectToggle,
ClientSize(u64, u16, u16),
FocusPaneCmd(usize),
FocusWindowCmd(usize),
MouseDown(u64,u16,u16),
MouseDownRight(u64,u16,u16),
MouseDownMiddle(u64,u16,u16),
MouseDrag(u64,u16,u16),
MouseUp(u64,u16,u16),
MouseUpRight(u64,u16,u16),
MouseUpMiddle(u64,u16,u16),
MouseMove(u64,u16,u16),
ScrollUp(u64,u16, u16),
ScrollDown(u64,u16, u16),
PaneMouse(u64, usize, u8, i16, i16, bool),
PaneScroll(u64, usize, bool),
SplitSetSizes(u64, Vec<usize>, Vec<u16>),
SplitResizeDone(u64),
NextWindow,
PrevWindow,
RenameWindow(String),
ListWindows(mpsc::Sender<String>),
ListWindowsTmux(mpsc::Sender<String>),
ListWindowsFormat(mpsc::Sender<String>, String),
ListTree(mpsc::Sender<String>),
WindowLayout(usize, mpsc::Sender<String>),
WindowDump(usize, mpsc::Sender<String>),
ToggleSync,
SetPaneTitle(String),
SetPaneStyle(String),
SendKeys(String, bool),
SendKeysX(String), SelectPane(String, bool),
SelectWindow(usize),
ListPanes(mpsc::Sender<String>),
ListPanesFormat(mpsc::Sender<String>, String),
ListAllPanes(mpsc::Sender<String>),
ListAllPanesFormat(mpsc::Sender<String>, String),
KillWindow,
KillSession,
HasSession(mpsc::Sender<bool>),
RenameSession(String),
ClaimSession(String, Option<String>, mpsc::Sender<String>),
SwapPane(String),
ResizePane(String, u16),
SetBuffer(String),
SetNamedBuffer(String, String),
ListBuffers(mpsc::Sender<String>),
ListBuffersFormat(mpsc::Sender<String>, String),
ShowBuffer(mpsc::Sender<String>),
ShowBufferAt(mpsc::Sender<String>, usize),
ShowNamedBuffer(mpsc::Sender<String>, String),
DeleteBuffer,
DeleteBufferAt(usize),
DeleteNamedBuffer(String),
PasteBufferAt(usize),
DisplayMessage(mpsc::Sender<String>, String, Option<usize>, bool, Option<u64>), LastWindow,
LastPane,
RotateWindow(bool),
DisplayPanes,
DisplayPaneSelect(usize),
BreakPane,
JoinPane {
src_win: Option<usize>,
src_pane: Option<usize>,
target_win: Option<usize>,
target_pane: Option<usize>,
horizontal: bool,
},
RespawnPane(Option<String>, bool), BindKey(String, String, String, bool), UnbindKey(String, Option<String>), UnbindAll,
UnbindAllInTable(String),
ListKeys(mpsc::Sender<String>),
SetOption(String, String),
SetOptionQuiet(String, String, bool), SetOptionUnset(String), SetOptionAppend(String, String), SetOptionOnlyIfUnset(String, String), ShowOptions(mpsc::Sender<String>),
ShowWindowOptions(mpsc::Sender<String>),
SourceFile(String),
MoveWindow(Option<usize>),
SwapWindow(usize),
LinkWindow(Option<usize>, Option<usize>),
UnlinkWindow,
SetSessionGroup(String),
FindWindow(mpsc::Sender<String>, String),
MovePane {
src_win: Option<usize>,
src_pane: Option<usize>,
target_win: Option<usize>,
target_pane: Option<usize>,
horizontal: bool,
},
PaneForwardExtract(usize, usize, mpsc::Sender<String>),
PaneForwardInject {
source_session: String,
source_addr: String,
source_key: String,
forward_id: u64,
fwd_port: u16,
pid: u32,
title: String,
rows: u16,
cols: u16,
screen_b64: String,
target_win: Option<usize>,
target_pane: Option<usize>,
horizontal: bool,
},
PaneForwardResize(u64, u16, u16),
PaneForwardStatus(u64, mpsc::Sender<String>),
PaneForwardKill(u64),
PipePane(String, bool, bool, bool),
SelectLayout(String),
NextLayout,
ListClients(mpsc::Sender<String>),
ListClientsFormat(mpsc::Sender<String>, String),
ForceDetachClient(u64),
SwitchClient(String, char),
LockClient,
RefreshClient,
ControlSubscribe {
client_id: u64,
name: String,
target: String,
format: String,
},
ControlUnsubscribe {
client_id: u64,
name: String,
},
ControlSetPauseAfter {
client_id: u64,
pause_after_secs: Option<u64>,
},
ControlContinuePane {
client_id: u64,
pane_id: usize,
},
SuspendClient,
CopyModePageUp,
ClearHistory,
SaveBuffer(String),
LoadBuffer(String),
SetEnvironment(String, String),
UnsetEnvironment(String),
ShowEnvironment(mpsc::Sender<String>),
SetHook(String, String),
AppendHook(String, String),
ShowHooks(mpsc::Sender<String>),
RemoveHook(String),
KillServer,
WaitFor(String, WaitForOp),
DisplayMenu(String, Option<i16>, Option<i16>),
DisplayMenuDirect(Menu),
DisplayPopup(String, String, String, bool, Option<String>),
ConfirmBefore(String, String),
ClockMode,
ResizePaneAbsolute(String, u16),
ResizePanePercent(String, u8), ShowOptionValue(mpsc::Sender<String>, String),
ShowWindowOptionValue(mpsc::Sender<String>, String),
ChooseBuffer(mpsc::Sender<String>),
ServerInfo(mpsc::Sender<String>),
SendPrefix,
PrevLayout,
SwitchClientTable(String),
ListCommands(mpsc::Sender<String>),
ResizeWindow(String, u16),
RespawnWindow,
FocusIn,
FocusOut,
CommandPrompt(String),
ShowMessages(mpsc::Sender<String>),
PopupInput(Vec<u8>),
OverlayClose,
ConfirmRespond(bool),
MenuSelect(usize),
MenuNavigate(i32),
ShowTextPopup(String, String),
StatusMessage(String),
ClearPromptHistory,
ShowPromptHistory(bool),
ControlRegister {
client_id: u64,
echo: bool,
notif_tx: mpsc::SyncSender<ControlNotification>,
},
ControlDeregister {
client_id: u64,
},
CustomizeMode,
CustomizeNavigate(i32),
CustomizeEdit,
CustomizeEditUpdate(String),
CustomizeEditConfirm,
CustomizeEditCancel,
CustomizeResetDefault,
CustomizeFilter(String),
RunCommand(String, mpsc::Sender<String>),
}
pub static PTY_DATA_READY: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
static PERSISTENT_STREAMS: std::sync::Mutex<Vec<(u64, std::net::TcpStream)>> = std::sync::Mutex::new(Vec::new());
pub fn register_persistent_stream(client_id: u64, stream: &std::net::TcpStream) {
if let Ok(cloned) = stream.try_clone() {
if let Ok(mut v) = PERSISTENT_STREAMS.lock() {
v.push((client_id, cloned));
}
}
}
pub fn shutdown_persistent_streams() {
if let Ok(mut v) = PERSISTENT_STREAMS.lock() {
for (_, s) in v.drain(..) {
let _ = s.shutdown(std::net::Shutdown::Both);
}
}
}
pub fn shutdown_client_stream(client_id: u64) {
if let Ok(mut v) = PERSISTENT_STREAMS.lock() {
v.retain(|(cid, s)| {
if *cid == client_id {
let _ = s.shutdown(std::net::Shutdown::Both);
false
} else {
true
}
});
}
if let Ok(mut v) = FRAME_PUSH_CHANNELS.lock() {
v.retain(|(cid, _)| *cid != client_id);
}
remove_directive_channel(client_id);
}
const FRAME_CHANNEL_CAPACITY: usize = 16;
pub type FrameChannel = std::sync::Arc<FrameChannelInner>;
pub struct FrameChannelInner {
pub tx: std::sync::mpsc::SyncSender<String>,
pub rx: std::sync::Mutex<std::sync::mpsc::Receiver<String>>,
}
static FRAME_PUSH_CHANNELS: std::sync::Mutex<Vec<(u64, std::sync::mpsc::SyncSender<String>)>> =
std::sync::Mutex::new(Vec::new());
pub fn register_frame_channel(client_id: u64) -> FrameChannel {
let (tx, rx) = std::sync::mpsc::sync_channel::<String>(FRAME_CHANNEL_CAPACITY);
if let Ok(mut v) = FRAME_PUSH_CHANNELS.lock() {
v.push((client_id, tx.clone()));
}
std::sync::Arc::new(FrameChannelInner {
tx,
rx: std::sync::Mutex::new(rx),
})
}
pub fn push_frame(frame: &str) {
if let Ok(mut channels) = FRAME_PUSH_CHANNELS.lock() {
channels.retain(|(_, tx)| {
match tx.try_send(frame.to_string()) {
Ok(()) => true,
Err(std::sync::mpsc::TrySendError::Full(_)) => {
true
}
Err(std::sync::mpsc::TrySendError::Disconnected(_)) => false,
}
});
}
}
pub fn has_frame_receivers() -> bool {
FRAME_PUSH_CHANNELS.lock().map_or(false, |v| !v.is_empty())
}
static DIRECTIVE_CHANNELS: std::sync::Mutex<Vec<(u64, std::sync::mpsc::Sender<String>)>> =
std::sync::Mutex::new(Vec::new());
pub fn register_directive_channel(client_id: u64) -> std::sync::mpsc::Receiver<String> {
let (tx, rx) = std::sync::mpsc::channel::<String>();
if let Ok(mut v) = DIRECTIVE_CHANNELS.lock() {
v.push((client_id, tx));
}
rx
}
pub fn send_directive_to_client(client_id: u64, directive: &str) -> bool {
if let Ok(channels) = DIRECTIVE_CHANNELS.lock() {
for (cid, tx) in channels.iter() {
if *cid == client_id {
return tx.send(directive.to_string()).is_ok();
}
}
}
false
}
pub fn send_directive_to_all_clients(directive: &str) {
if let Ok(channels) = DIRECTIVE_CHANNELS.lock() {
for (_, tx) in channels.iter() {
let _ = tx.send(directive.to_string());
}
}
}
pub fn remove_directive_channel(client_id: u64) {
if let Ok(mut v) = DIRECTIVE_CHANNELS.lock() {
v.retain(|(cid, _)| *cid != client_id);
}
}
static NEXT_CONTROL_CLIENT_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
pub fn next_control_client_id() -> u64 {
NEXT_CONTROL_CLIENT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
}
#[derive(Clone, Copy)]
pub enum WaitForOp {
Wait,
Lock,
Signal,
Unlock,
}
#[derive(Debug, Clone, Default)]
pub struct ParsedTarget {
pub session: Option<String>,
pub window: Option<usize>,
pub window_name: Option<String>,
pub pane: Option<usize>,
pub pane_is_id: bool,
pub window_is_id: bool,
}