tmai_core/
command_sender.rs1use anyhow::Result;
2use std::sync::Arc;
3
4use crate::hooks::registry::HookRegistry;
5use crate::ipc::server::IpcServer;
6use crate::pty::registry::PtyRegistry;
7use crate::runtime::RuntimeAdapter;
8use crate::state::SharedState;
9use crate::utils::keys::tmux_key_to_bytes;
10
11pub struct CommandSender {
19 ipc_server: Option<Arc<IpcServer>>,
20 runtime: Arc<dyn RuntimeAdapter>,
21 app_state: SharedState,
22 hook_registry: Option<HookRegistry>,
23 pty_registry: Option<Arc<PtyRegistry>>,
24}
25
26impl CommandSender {
27 pub fn new(
29 ipc_server: Option<Arc<IpcServer>>,
30 runtime: Arc<dyn RuntimeAdapter>,
31 app_state: SharedState,
32 ) -> Self {
33 Self {
34 ipc_server,
35 runtime,
36 app_state,
37 hook_registry: None,
38 pty_registry: None,
39 }
40 }
41
42 pub fn with_hook_registry(mut self, registry: HookRegistry) -> Self {
44 self.hook_registry = Some(registry);
45 self
46 }
47
48 pub fn with_pty_registry(mut self, registry: Arc<PtyRegistry>) -> Self {
50 self.pty_registry = Some(registry);
51 self
52 }
53
54 fn try_pty_session_write(&self, target: &str, data: &[u8]) -> bool {
56 if let Some(ref registry) = self.pty_registry {
57 if let Some(session) = registry.get(target) {
59 if session.is_running() {
60 return session.write_input(data).is_ok();
61 }
62 }
63 let session_id = {
65 let state = self.app_state.read();
66 state
67 .agents
68 .get(target)
69 .and_then(|a| a.pty_session_id.clone())
70 };
71 if let Some(sid) = session_id {
72 if let Some(session) = registry.get(&sid) {
73 if session.is_running() {
74 return session.write_input(data).is_ok();
75 }
76 }
77 }
78 }
79 false
80 }
81
82 pub fn send_keys(&self, target: &str, keys: &str) -> Result<()> {
84 let key_bytes = tmux_key_to_bytes(keys);
86 if self.try_pty_session_write(target, &key_bytes) {
87 return Ok(());
88 }
89 if let Some(ref ipc) = self.ipc_server {
91 if let Some(pane_id) = self.get_pane_id_for_target(target) {
92 if ipc.try_send_keys(&pane_id, keys, false) {
93 return Ok(());
94 }
95 }
96 }
97 if self.runtime.send_keys(target, keys).is_ok() {
99 return Ok(());
100 }
101 if let Some(pid) = self.resolve_pid_for_target(target) {
103 if pid > 0 {
104 return crate::pty_inject::inject_text(pid, keys);
105 }
106 }
107 anyhow::bail!("All send_keys tiers failed for target {}", target)
108 }
109
110 pub fn send_keys_literal(&self, target: &str, keys: &str) -> Result<()> {
112 if self.try_pty_session_write(target, keys.as_bytes()) {
114 return Ok(());
115 }
116 if let Some(ref ipc) = self.ipc_server {
118 if let Some(pane_id) = self.get_pane_id_for_target(target) {
119 if ipc.try_send_keys(&pane_id, keys, true) {
120 return Ok(());
121 }
122 }
123 }
124 if self.runtime.send_keys_literal(target, keys).is_ok() {
126 return Ok(());
127 }
128 if let Some(pid) = self.resolve_pid_for_target(target) {
130 if pid > 0 {
131 return crate::pty_inject::inject_text_literal(pid, keys);
132 }
133 }
134 anyhow::bail!("All send_keys_literal tiers failed for target {}", target)
135 }
136
137 pub fn send_text_and_enter(&self, target: &str, text: &str) -> Result<()> {
139 let mut data = text.as_bytes().to_vec();
141 data.push(b'\r');
142 if self.try_pty_session_write(target, &data) {
143 return Ok(());
144 }
145 if let Some(ref ipc) = self.ipc_server {
147 if let Some(pane_id) = self.get_pane_id_for_target(target) {
148 if ipc.try_send_keys_and_enter(&pane_id, text) {
149 return Ok(());
150 }
151 }
152 }
153 if self.runtime.send_text_and_enter(target, text).is_ok() {
155 return Ok(());
156 }
157 if let Some(pid) = self.resolve_pid_for_target(target) {
159 if pid > 0 {
160 return crate::pty_inject::inject_text_and_enter(pid, text);
161 }
162 }
163 anyhow::bail!("All send_text_and_enter tiers failed for target {}", target)
164 }
165
166 pub fn runtime(&self) -> &Arc<dyn RuntimeAdapter> {
168 &self.runtime
169 }
170
171 pub fn ipc_server(&self) -> Option<&Arc<IpcServer>> {
173 self.ipc_server.as_ref()
174 }
175
176 fn get_pane_id_for_target(&self, target: &str) -> Option<String> {
178 let state = self.app_state.read();
179 state.target_to_pane_id.get(target).cloned()
180 }
181
182 fn resolve_pid_for_target(&self, target: &str) -> Option<u32> {
184 if let Some(ref registry) = self.hook_registry {
186 let pane_id = {
187 let state = self.app_state.read();
188 state.target_to_pane_id.get(target).cloned()
189 };
190 if let Some(pane_id) = pane_id {
191 let reg = registry.read();
192 if let Some(hook_state) = reg.get(&pane_id) {
193 if let Some(pid) = hook_state.pid {
194 return Some(pid);
195 }
196 }
197 }
198 }
199
200 let state = self.app_state.read();
202 if let Some(agent) = state.agents.get(target) {
203 if agent.pid > 0 {
204 return Some(agent.pid);
205 }
206 }
207 for agent in state.agents.values() {
209 if agent.target == target && agent.pid > 0 {
210 return Some(agent.pid);
211 }
212 }
213 None
214 }
215}