1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#![allow(dead_code)]
use super::protocol::{
ClientMsg, DaemonMsg, WindowInfo, read_message, try_read_message, write_message,
};
use super::socket;
use std::io;
use std::os::unix::net::UnixStream;
use std::time::Duration;
/// Client connection to the persist daemon
pub struct PersistClient {
stream: UnixStream,
read_buf: Vec<u8>,
}
/// Result of attempting to connect to or start a daemon
pub enum ConnectResult {
/// Successfully connected to an existing daemon
Connected(PersistClient, Vec<WindowInfo>),
/// Connection was denied (another client is attached)
Denied(String),
/// No daemon is running
NoDaemon,
}
impl PersistClient {
/// Try to connect to an existing daemon.
/// If `force` is true, sends ForceAttach to kick any existing client.
pub fn connect(cols: u16, rows: u16, force: bool) -> io::Result<ConnectResult> {
let sock_path = socket::socket_path()?;
if !sock_path.exists() {
return Ok(ConnectResult::NoDaemon);
}
// Try to connect
let mut stream = match UnixStream::connect(&sock_path) {
Ok(s) => s,
Err(e) => {
if e.kind() == io::ErrorKind::ConnectionRefused
|| e.kind() == io::ErrorKind::NotFound
{
// Socket exists but no one is listening - stale
socket::cleanup_files();
return Ok(ConnectResult::NoDaemon);
}
return Err(e);
}
};
// Set timeout for handshake
stream.set_read_timeout(Some(Duration::from_secs(5)))?;
stream.set_write_timeout(Some(Duration::from_secs(5)))?;
// Send Attach or ForceAttach message
let msg = if force {
ClientMsg::ForceAttach { cols, rows }
} else {
ClientMsg::Attach { cols, rows }
};
write_message(&mut stream, &msg)?;
// Read response
let response: DaemonMsg = read_message(&mut stream)?;
match response {
DaemonMsg::AttachOk { windows } => {
// Switch to non-blocking for ongoing communication
stream.set_nonblocking(true)?;
stream.set_read_timeout(None)?;
stream.set_write_timeout(None)?;
let client = PersistClient {
stream,
read_buf: Vec::new(),
};
Ok(ConnectResult::Connected(client, windows))
}
DaemonMsg::AttachDenied { reason } => Ok(ConnectResult::Denied(reason)),
_ => Err(io::Error::new(
io::ErrorKind::InvalidData,
"unexpected response from daemon",
)),
}
}
/// Send a message to the daemon
pub fn send(&mut self, msg: &ClientMsg) -> io::Result<()> {
// Temporarily set blocking for writes
self.stream.set_nonblocking(false)?;
let result = write_message(&mut self.stream, msg);
// If restoring non-blocking fails, return error so caller knows
// the socket is in a bad state rather than silently continuing
self.stream.set_nonblocking(true)?;
result
}
/// Try to receive a message from the daemon (non-blocking)
pub fn try_recv(&mut self) -> io::Result<Option<DaemonMsg>> {
try_read_message(&mut self.stream, &mut self.read_buf)
}
/// Send PTY input for a specific window
pub fn send_pty_input(&mut self, window_id: u32, data: &[u8]) -> io::Result<()> {
self.send(&ClientMsg::PtyInput {
window_id,
data: data.to_vec(),
})
}
/// Request creation of a new window
pub fn request_create_window(
&mut self,
x: u16,
y: u16,
width: u16,
height: u16,
title: String,
command: Option<String>,
) -> io::Result<()> {
self.send(&ClientMsg::CreateWindow {
x,
y,
width,
height,
title,
command,
})
}
/// Request closing a window
pub fn request_close_window(&mut self, window_id: u32) -> io::Result<()> {
self.send(&ClientMsg::CloseWindow { window_id })
}
/// Notify daemon of window geometry change (move/resize)
pub fn send_update_geometry(
&mut self,
window_id: u32,
x: u16,
y: u16,
width: u16,
height: u16,
) -> io::Result<()> {
self.send(&ClientMsg::UpdateWindowGeometry {
window_id,
x,
y,
width,
height,
})
}
/// Notify daemon of PTY resize
pub fn send_resize_pty(&mut self, window_id: u32, cols: u16, rows: u16) -> io::Result<()> {
self.send(&ClientMsg::ResizePty {
window_id,
cols,
rows,
})
}
/// Send detach message (graceful disconnect)
pub fn detach(&mut self) -> io::Result<()> {
self.send(&ClientMsg::Detach)
}
/// Send shutdown message (kill daemon)
pub fn shutdown(&mut self) -> io::Result<()> {
self.send(&ClientMsg::Shutdown)
}
}