1use crate::backend::{MuxBackend, MuxError, Pane, PaneSize, Session, SplitType};
4use async_trait::async_trait;
5use chrono::Utc;
6use futures_util::{SinkExt, StreamExt};
7use serde_json::{json, Value};
8use std::collections::HashMap;
9use std::sync::Arc;
10use tokio::sync::RwLock;
11use tokio_tungstenite::connect_async;
12use tracing::{debug, info};
13
14pub struct MuxdBackend {
16 url: String,
17 sessions: Arc<RwLock<HashMap<String, Session>>>,
18 panes: Arc<RwLock<HashMap<String, Pane>>>,
19}
20
21impl MuxdBackend {
22 pub fn new(url: String) -> Self {
23 Self {
24 url,
25 sessions: Arc::new(RwLock::new(HashMap::new())),
26 panes: Arc::new(RwLock::new(HashMap::new())),
27 }
28 }
29
30 async fn send_command(&self, command: Value) -> Result<Value, MuxError> {
32 let (mut ws, _) = connect_async(&self.url)
33 .await
34 .map_err(|e| MuxError::ConnectionError(format!("Failed to connect to muxd: {e}")))?;
35
36 ws.send(tokio_tungstenite::tungstenite::Message::Text(
38 command.to_string().into(),
39 ))
40 .await
41 .map_err(|e| MuxError::ConnectionError(format!("Failed to send command: {e}")))?;
42
43 if let Some(Ok(msg)) = ws.next().await {
45 match msg {
46 tokio_tungstenite::tungstenite::Message::Text(text) => serde_json::from_str(&text)
47 .map_err(|e| MuxError::ParseError(format!("Invalid response: {e}"))),
48 _ => Err(MuxError::ParseError("Expected text response".to_string())),
49 }
50 } else {
51 Err(MuxError::ConnectionError(
52 "No response from muxd".to_string(),
53 ))
54 }
55 }
56}
57
58#[async_trait]
59impl MuxBackend for MuxdBackend {
60 async fn create_session(&self, name: &str) -> Result<String, MuxError> {
61 info!("Creating muxd session: {}", name);
62
63 let command = json!({
64 "type": "create_session",
65 "name": name
66 });
67
68 let response = self.send_command(command).await?;
69
70 if let Some(session_id) = response.get("session_id").and_then(|v| v.as_str()) {
71 let session = Session {
72 id: session_id.to_string(),
73 name: name.to_string(),
74 created_at: Utc::now(),
75 window_count: 1,
76 attached: false,
77 };
78
79 self.sessions
80 .write()
81 .await
82 .insert(session_id.to_string(), session);
83 Ok(session_id.to_string())
84 } else {
85 Err(MuxError::ParseError(
86 "Invalid create_session response".to_string(),
87 ))
88 }
89 }
90
91 async fn create_pane(&self, session_id: &str, split: SplitType) -> Result<String, MuxError> {
92 debug!(
93 "Creating pane in muxd session: {} with split: {:?}",
94 session_id, split
95 );
96
97 let command = json!({
98 "type": "create_pane",
99 "session_id": session_id,
100 "split": match split {
101 SplitType::Horizontal => "horizontal",
102 SplitType::Vertical => "vertical",
103 SplitType::None => "none",
104 }
105 });
106
107 let response = self.send_command(command).await?;
108
109 if let Some(pane_id) = response.get("pane_id").and_then(|v| v.as_str()) {
110 let pane = Pane {
111 id: pane_id.to_string(),
112 session_id: session_id.to_string(),
113 index: 0,
114 title: String::new(),
115 active: true,
116 size: PaneSize {
117 width: 80,
118 height: 24,
119 },
120 };
121
122 self.panes.write().await.insert(pane_id.to_string(), pane);
123 Ok(pane_id.to_string())
124 } else {
125 Err(MuxError::ParseError(
126 "Invalid create_pane response".to_string(),
127 ))
128 }
129 }
130
131 async fn send_keys(&self, pane_id: &str, keys: &str) -> Result<(), MuxError> {
132 debug!("Sending keys to muxd pane {}: {}", pane_id, keys);
133
134 let command = json!({
135 "type": "send_keys",
136 "pane_id": pane_id,
137 "keys": keys
138 });
139
140 self.send_command(command).await?;
141 Ok(())
142 }
143
144 async fn capture_pane(&self, pane_id: &str) -> Result<String, MuxError> {
145 debug!("Capturing muxd pane: {}", pane_id);
146
147 let command = json!({
148 "type": "capture_pane",
149 "pane_id": pane_id
150 });
151
152 let response = self.send_command(command).await?;
153
154 response
155 .get("content")
156 .and_then(|v| v.as_str())
157 .map(|s| s.to_string())
158 .ok_or_else(|| MuxError::ParseError("Invalid capture_pane response".to_string()))
159 }
160
161 async fn list_sessions(&self) -> Result<Vec<Session>, MuxError> {
162 debug!("Listing muxd sessions");
163
164 let command = json!({
165 "type": "list_sessions"
166 });
167
168 let response = self.send_command(command).await?;
169
170 if let Some(sessions_data) = response.get("sessions").and_then(|v| v.as_array()) {
171 let sessions = sessions_data
172 .iter()
173 .filter_map(|v| {
174 let id = v.get("id")?.as_str()?;
175 let name = v.get("name")?.as_str()?;
176 Some(Session {
177 id: id.to_string(),
178 name: name.to_string(),
179 created_at: Utc::now(),
180 window_count: v.get("window_count").and_then(|v| v.as_u64()).unwrap_or(0)
181 as usize,
182 attached: v.get("attached").and_then(|v| v.as_bool()).unwrap_or(false),
183 })
184 })
185 .collect();
186 Ok(sessions)
187 } else {
188 Err(MuxError::ParseError(
189 "Invalid list_sessions response".to_string(),
190 ))
191 }
192 }
193
194 async fn kill_session(&self, session_id: &str) -> Result<(), MuxError> {
195 info!("Killing muxd session: {}", session_id);
196
197 let command = json!({
198 "type": "kill_session",
199 "session_id": session_id
200 });
201
202 self.send_command(command).await?;
203 self.sessions.write().await.remove(session_id);
204 Ok(())
205 }
206
207 async fn kill_pane(&self, pane_id: &str) -> Result<(), MuxError> {
208 debug!("Killing muxd pane: {}", pane_id);
209
210 let command = json!({
211 "type": "kill_pane",
212 "pane_id": pane_id
213 });
214
215 self.send_command(command).await?;
216 self.panes.write().await.remove(pane_id);
217 Ok(())
218 }
219
220 async fn resize_pane(&self, pane_id: &str, size: PaneSize) -> Result<(), MuxError> {
221 debug!(
222 "Resizing muxd pane {} to {}x{}",
223 pane_id, size.width, size.height
224 );
225
226 let command = json!({
227 "type": "resize_pane",
228 "pane_id": pane_id,
229 "width": size.width,
230 "height": size.height
231 });
232
233 self.send_command(command).await?;
234
235 if let Some(pane) = self.panes.write().await.get_mut(pane_id) {
236 pane.size = size;
237 }
238
239 Ok(())
240 }
241
242 async fn select_pane(&self, pane_id: &str) -> Result<(), MuxError> {
243 debug!("Selecting muxd pane: {}", pane_id);
244
245 let command = json!({
246 "type": "select_pane",
247 "pane_id": pane_id
248 });
249
250 self.send_command(command).await?;
251 Ok(())
252 }
253
254 async fn list_panes(&self, session_id: &str) -> Result<Vec<Pane>, MuxError> {
255 debug!("Listing panes for muxd session: {}", session_id);
256
257 let command = json!({
258 "type": "list_panes",
259 "session_id": session_id
260 });
261
262 let response = self.send_command(command).await?;
263
264 if let Some(panes_data) = response.get("panes").and_then(|v| v.as_array()) {
265 let panes = panes_data
266 .iter()
267 .filter_map(|v| {
268 let id = v.get("id")?.as_str()?;
269 let index = v.get("index")?.as_u64()? as u32;
270 Some(Pane {
271 id: id.to_string(),
272 session_id: session_id.to_string(),
273 index,
274 title: v
275 .get("title")
276 .and_then(|v| v.as_str())
277 .unwrap_or("")
278 .to_string(),
279 active: v.get("active").and_then(|v| v.as_bool()).unwrap_or(false),
280 size: PaneSize {
281 width: v.get("width").and_then(|v| v.as_u64()).unwrap_or(80) as u32,
282 height: v.get("height").and_then(|v| v.as_u64()).unwrap_or(24) as u32,
283 },
284 })
285 })
286 .collect();
287 Ok(panes)
288 } else {
289 Err(MuxError::ParseError(
290 "Invalid list_panes response".to_string(),
291 ))
292 }
293 }
294
295 async fn attach_session(&self, session_id: &str) -> Result<(), MuxError> {
296 info!("Attaching to muxd session: {}", session_id);
297
298 let command = json!({
299 "type": "attach_session",
300 "session_id": session_id
301 });
302
303 self.send_command(command).await?;
304
305 if let Some(session) = self.sessions.write().await.get_mut(session_id) {
306 session.attached = true;
307 }
308
309 Ok(())
310 }
311
312 async fn detach_session(&self, session_id: &str) -> Result<(), MuxError> {
313 info!("Detaching from muxd session: {}", session_id);
314
315 let command = json!({
316 "type": "detach_session",
317 "session_id": session_id
318 });
319
320 self.send_command(command).await?;
321
322 if let Some(session) = self.sessions.write().await.get_mut(session_id) {
323 session.attached = false;
324 }
325
326 Ok(())
327 }
328}