1mod client;
8pub mod clipboard;
9mod memory;
10mod platform;
11#[cfg(feature = "server")]
12mod server;
13mod state;
14mod wire;
15mod worker;
16
17pub use client::{Client, Event};
18pub use memory::PixelBuffer;
19#[cfg(feature = "server")]
20pub use server::{NativeWindowServer, Registration, ServerEvent};
21pub use worker::WorkerWindow;
22
23use std::io;
24use std::path::{Path, PathBuf};
25use std::time::Duration;
26
27pub const PROTOCOL_NAME: &str = "org.knott.aen-window";
28pub const PROTOCOL_VERSION: u16 = 3;
29pub const MAX_DIMENSION: u32 = 8192;
30pub const MAX_FRAME_BYTES: usize = 32 * 1024 * 1024;
31pub const MAX_MEMORY_BYTES: usize = 128 * 1024 * 1024;
32pub const MAX_CLIENTS: usize = 32;
33pub const MAX_APP_ID_BYTES: usize = 64;
34pub const MAX_TITLE_BYTES: usize = 128;
35pub const MAX_TEXT_COMMIT_BYTES: usize = 256;
37pub const TRANSFER_TIMEOUT: Duration = Duration::from_secs(3);
38pub const MOD_SHIFT: u32 = 1;
39pub const MOD_CTRL: u32 = 2;
40pub const MOD_ALT: u32 = 4;
41pub const MOD_SUPER: u32 = 8;
42
43#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
46pub struct ClientOptions {
47 pub relative_pointer: bool,
50}
51impl ClientOptions {
52 pub(crate) const fn bits(self) -> u32 {
53 self.relative_pointer as u32
54 }
55 pub(crate) fn from_bits(bits: u32) -> io::Result<Self> {
56 if bits & !1 != 0 {
57 return Err(invalid("unknown native window options"));
58 }
59 Ok(Self {
60 relative_pointer: bits & 1 != 0,
61 })
62 }
63}
64
65#[derive(Clone, Debug, Eq, PartialEq)]
66pub struct Create {
67 pub app_id: String,
68 pub title: String,
69 pub preferred_width: u32,
70 pub preferred_height: u32,
71}
72impl Create {
73 pub fn validate(&self) -> io::Result<()> {
74 if self.app_id.is_empty()
75 || self.app_id.len() > MAX_APP_ID_BYTES
76 || !self
77 .app_id
78 .bytes()
79 .all(|b| b.is_ascii_alphanumeric() || b"._-".contains(&b))
80 || self.title.is_empty()
81 || self.title.len() > MAX_TITLE_BYTES
82 || self.title.chars().any(char::is_control)
83 {
84 return Err(invalid("invalid native window identity"));
85 }
86 frame_bytes(self.preferred_width, self.preferred_height)?;
87 Ok(())
88 }
89}
90
91#[derive(Clone, Copy, Debug, Eq, PartialEq)]
92pub struct Configure {
93 pub window_id: u64,
94 pub configure_serial: u64,
95 pub width: u32,
96 pub height: u32,
97 pub scale: u32,
98}
99impl Configure {
100 pub fn validate(&self) -> io::Result<()> {
101 if self.window_id == 0 || self.configure_serial == 0 || self.scale != 1 {
102 return Err(invalid("invalid native window configuration"));
103 }
104 frame_bytes(self.width, self.height)?;
105 Ok(())
106 }
107}
108
109#[derive(Clone, Debug)]
110pub struct Frame {
111 pub frame_serial: u64,
112 pub configure_serial: u64,
113 pub width: u32,
114 pub height: u32,
115 pub pixels: PixelBuffer,
118}
119pub type ServerFrame = Frame;
120impl Frame {
121 pub fn validate(&self) -> io::Result<()> {
122 if self.frame_serial == 0
123 || self.configure_serial == 0
124 || self.pixels.len() != frame_bytes(self.width, self.height)?
125 {
126 return Err(invalid("invalid native window frame"));
127 }
128 Ok(())
129 }
130}
131
132#[derive(Clone, Copy, Debug, Eq, PartialEq)]
133pub enum TouchPhase {
134 Down,
136 Motion,
138 Up,
140}
141
142#[derive(Clone, Debug, Eq, PartialEq)]
143pub struct Input {
144 pub frame_serial: u64,
145 pub event: InputEvent,
146}
147#[derive(Clone, Debug, Eq, PartialEq)]
148pub enum InputEvent {
149 Focus {
150 focused: bool,
151 },
152 Motion {
153 x: i32,
154 y: i32,
155 },
156 RelativeMotion {
160 dx: i32,
161 dy: i32,
162 time_usec: u64,
163 },
164 Button {
165 button: u32,
166 pressed: bool,
167 x: i32,
168 y: i32,
169 },
170 Axis {
171 horizontal: i32,
172 vertical: i32,
173 x: i32,
174 y: i32,
175 },
176 Leave,
177 Key {
178 keycode: u32,
179 keysym: u32,
180 pressed: bool,
181 repeat: bool,
182 modifiers: u32,
183 text: Option<char>,
184 },
185 TextCommit {
187 text: String,
188 },
189 Touch {
191 id: i32,
192 phase: TouchPhase,
193 x: i32,
194 y: i32,
195 },
196 TouchCancel,
198}
199impl Input {
200 pub fn validate(&self) -> io::Result<()> {
201 if self.frame_serial == 0 {
202 return Err(invalid("input requires an adopted frame"));
203 }
204 match &self.event {
205 InputEvent::Key {
206 keycode,
207 pressed,
208 repeat,
209 modifiers,
210 text,
211 ..
212 } if *keycode > 0xffff
213 || (*repeat && !*pressed)
214 || *modifiers & !15 != 0
215 || (*text).is_some_and(char::is_control)
216 || (!*pressed && (*text).is_some()) =>
217 {
218 Err(invalid("invalid native window key event"))
219 }
220 InputEvent::TextCommit { text }
221 if text.is_empty()
222 || text.len() > MAX_TEXT_COMMIT_BYTES
223 || text.chars().any(char::is_control) =>
224 {
225 Err(invalid("invalid native window text commit"))
226 }
227 InputEvent::Button { button, .. } if *button == 0 || *button > 0xffff => {
228 Err(invalid("invalid native window button"))
229 }
230 InputEvent::Touch { id, phase, x, y }
231 if *id < 0 || (*phase == TouchPhase::Up && (*x != 0 || *y != 0)) =>
232 {
233 Err(invalid("invalid native window touch event"))
234 }
235 _ => Ok(()),
236 }
237 }
238}
239
240pub fn frame_bytes(width: u32, height: u32) -> io::Result<usize> {
241 if width == 0 || height == 0 || width > MAX_DIMENSION || height > MAX_DIMENSION {
242 return Err(invalid("native window dimensions exceed limits"));
243 }
244 let bytes = (width as usize)
245 .checked_mul(height as usize)
246 .and_then(|n| n.checked_mul(4))
247 .filter(|n| *n <= MAX_FRAME_BYTES)
248 .ok_or_else(|| invalid("native window frame exceeds limit"))?;
249 Ok(bytes)
250}
251
252pub fn socket_for_shell(shell: &Path) -> PathBuf {
253 let mut path = shell.as_os_str().to_owned();
254 path.push(".windows");
255 path.into()
256}
257
258pub fn socket_from_env() -> io::Result<PathBuf> {
260 if let Some(path) = std::env::var_os("AEN_WINDOW_SOCKET") {
261 return Ok(path.into());
262 }
263 if let Some(path) = std::env::var_os("KNOTT_COMPOSITOR_SHELL_SOCKET") {
264 return Ok(socket_for_shell(Path::new(&path)));
265 }
266 let runtime = std::env::var_os("XDG_RUNTIME_DIR")
267 .ok_or_else(|| invalid("no native window endpoint is configured"))?;
268 Ok(socket_for_shell(
269 &PathBuf::from(runtime).join("knottx-shell.sock"),
270 ))
271}
272
273pub(crate) fn invalid(message: &'static str) -> io::Error {
274 io::Error::new(io::ErrorKind::InvalidData, message)
275}