1use crate::server::spice::wire::{self, image_type, msg, Cursor};
19use anyhow::{ensure, Result};
20use serde_json::{json, Value};
21use std::collections::{BTreeMap, HashMap, VecDeque};
22use tokio::time::Instant;
23
24pub const MAX_SIDE: u32 = 4096;
26pub const MAX_PIXELS: u64 = 4096 * 2160;
28pub const MAX_CACHE_BYTES: usize = 64 << 20;
30pub const MAX_CLIP_RECTS: u32 = 1024;
32pub const MAX_MONITORS: u16 = 16;
34
35#[derive(Debug, Clone, PartialEq, Eq)]
37pub enum Change {
38 SurfaceCreated {
39 width: u32,
40 height: u32,
41 format: u32,
42 },
43}
44
45#[derive(Clone)]
47struct Image {
48 width: u32,
49 height: u32,
50 bgrx: Vec<u8>,
51}
52
53#[derive(Clone, Copy, Debug)]
54struct Rect {
55 top: i64,
56 left: i64,
57 bottom: i64,
58 right: i64,
59}
60
61impl Rect {
62 fn read(c: &mut Cursor) -> Result<Self> {
63 Ok(Rect {
64 top: c.i32("rect top")? as i64,
65 left: c.i32("rect left")? as i64,
66 bottom: c.i32("rect bottom")? as i64,
67 right: c.i32("rect right")? as i64,
68 })
69 }
70 fn width(&self) -> i64 {
71 self.right - self.left
72 }
73 fn height(&self) -> i64 {
74 self.bottom - self.top
75 }
76 fn intersect(&self, o: &Rect) -> Rect {
77 Rect {
78 top: self.top.max(o.top),
79 left: self.left.max(o.left),
80 bottom: self.bottom.min(o.bottom),
81 right: self.right.min(o.right),
82 }
83 }
84 fn is_empty(&self) -> bool {
85 self.width() <= 0 || self.height() <= 0
86 }
87}
88
89struct Surface {
90 id: u32,
91 width: u32,
92 height: u32,
93 format: u32,
94 bgrx: Vec<u8>,
95}
96
97#[derive(Default)]
99struct Cache {
100 images: HashMap<u64, Image>,
101 order: VecDeque<u64>,
102 bytes: usize,
103}
104
105impl Cache {
106 fn insert(&mut self, id: u64, image: Image) {
107 let size = image.bgrx.len();
108 if size > MAX_CACHE_BYTES {
109 return;
110 }
111 if let Some(old) = self.images.remove(&id) {
112 self.bytes -= old.bgrx.len();
113 self.order.retain(|i| *i != id);
114 }
115 while self.bytes + size > MAX_CACHE_BYTES {
116 let Some(oldest) = self.order.pop_front() else {
117 break;
118 };
119 if let Some(old) = self.images.remove(&oldest) {
120 self.bytes -= old.bgrx.len();
121 }
122 }
123 self.bytes += size;
124 self.order.push_back(id);
125 self.images.insert(id, image);
126 }
127 fn remove(&mut self, id: u64) {
128 if let Some(old) = self.images.remove(&id) {
129 self.bytes -= old.bgrx.len();
130 self.order.retain(|i| *i != id);
131 }
132 }
133 fn clear(&mut self) {
134 *self = Cache::default();
135 }
136}
137
138pub struct DisplayModel {
139 primary: Option<Surface>,
140 other_surfaces: u32,
141 ops: BTreeMap<&'static str, u64>,
142 images: BTreeMap<&'static str, u64>,
143 undecoded: u64,
144 updates: u64,
145 marked: bool,
146 monitors: Vec<Value>,
147 cache: Cache,
148 pub last_update: Instant,
149}
150
151impl Default for DisplayModel {
152 fn default() -> Self {
153 Self {
154 primary: None,
155 other_surfaces: 0,
156 ops: BTreeMap::new(),
157 images: BTreeMap::new(),
158 undecoded: 0,
159 updates: 0,
160 marked: false,
161 monitors: Vec::new(),
162 cache: Cache::default(),
163 last_update: Instant::now(),
164 }
165 }
166}
167
168fn op_name(t: u16) -> &'static str {
169 match t {
170 msg::DISPLAY_MODE => "mode",
171 msg::DISPLAY_MARK => "mark",
172 msg::DISPLAY_RESET => "reset",
173 msg::DISPLAY_COPY_BITS => "copy_bits",
174 msg::DISPLAY_INVAL_LIST => "inval_list",
175 msg::DISPLAY_INVAL_ALL_PIXMAPS => "inval_all_pixmaps",
176 msg::DISPLAY_INVAL_PALETTE => "inval_palette",
177 msg::DISPLAY_INVAL_ALL_PALETTES => "inval_all_palettes",
178 msg::DISPLAY_STREAM_CREATE => "stream_create",
179 msg::DISPLAY_STREAM_DATA => "stream_data",
180 msg::DISPLAY_STREAM_CLIP => "stream_clip",
181 msg::DISPLAY_STREAM_DESTROY => "stream_destroy",
182 msg::DISPLAY_STREAM_DESTROY_ALL => "stream_destroy_all",
183 msg::DISPLAY_DRAW_FILL => "fill",
184 msg::DISPLAY_DRAW_OPAQUE => "opaque",
185 msg::DISPLAY_DRAW_COPY => "copy",
186 msg::DISPLAY_DRAW_BLEND => "blend",
187 msg::DISPLAY_DRAW_BLACKNESS => "blackness",
188 msg::DISPLAY_DRAW_WHITENESS => "whiteness",
189 msg::DISPLAY_DRAW_INVERS => "invers",
190 msg::DISPLAY_DRAW_ROP3 => "rop3",
191 msg::DISPLAY_DRAW_STROKE => "stroke",
192 msg::DISPLAY_DRAW_TEXT => "text",
193 msg::DISPLAY_DRAW_TRANSPARENT => "transparent",
194 msg::DISPLAY_DRAW_ALPHA_BLEND => "alpha_blend",
195 msg::DISPLAY_SURFACE_CREATE => "surface_create",
196 msg::DISPLAY_SURFACE_DESTROY => "surface_destroy",
197 msg::DISPLAY_STREAM_DATA_SIZED => "stream_data_sized",
198 msg::DISPLAY_MONITORS_CONFIG => "monitors_config",
199 msg::DISPLAY_DRAW_COMPOSITE => "composite",
200 _ => "other",
201 }
202}
203
204fn check_size(what: &str, width: u32, height: u32) -> Result<()> {
205 ensure!(
206 (1..=MAX_SIDE).contains(&width) && (1..=MAX_SIDE).contains(&height),
207 "{what} of {width}x{height}: this client accepts 1 to {MAX_SIDE} pixels a side"
208 );
209 ensure!(
210 u64::from(width) * u64::from(height) <= MAX_PIXELS,
211 "{what} of {width}x{height} is above the {MAX_PIXELS} pixel limit"
212 );
213 Ok(())
214}
215
216impl DisplayModel {
217 pub fn handle(&mut self, msg_type: u16, body: &[u8]) -> Result<Option<Change>> {
220 let name = op_name(msg_type);
221 if name == "other" {
222 return Ok(None);
223 }
224 *self.ops.entry(name).or_default() += 1;
225 let mut c = Cursor::new(body);
226 match msg_type {
227 msg::DISPLAY_SURFACE_CREATE => {
228 let id = c.u32("surface id")?;
229 let width = c.u32("surface width")?;
230 let height = c.u32("surface height")?;
231 let format = c.u32("surface format")?;
232 let flags = c.u32("surface flags")?;
233 if flags & wire::SURFACE_FLAGS_PRIMARY == 0 {
234 self.other_surfaces += 1;
235 return Ok(None);
236 }
237 check_size("a primary surface", width, height)?;
238 self.primary = Some(Surface {
239 id,
240 width,
241 height,
242 format,
243 bgrx: vec![0; width as usize * height as usize * 4],
244 });
245 self.touch();
246 return Ok(Some(Change::SurfaceCreated {
247 width,
248 height,
249 format,
250 }));
251 }
252 msg::DISPLAY_SURFACE_DESTROY => {
253 let id = c.u32("surface id")?;
254 if self.primary.as_ref().is_some_and(|s| s.id == id) {
255 self.primary = None;
256 self.marked = false;
257 } else {
258 self.other_surfaces = self.other_surfaces.saturating_sub(1);
259 }
260 }
261 msg::DISPLAY_MARK => self.marked = true,
262 msg::DISPLAY_RESET => {
263 self.primary = None;
264 self.marked = false;
265 }
266 msg::DISPLAY_INVAL_ALL_PIXMAPS => self.cache.clear(),
267 msg::DISPLAY_INVAL_LIST => {
268 let n = c.u16("resource count")?;
269 for _ in 0..n {
270 let _kind = c.u8("resource type")?;
271 let id = c.u64("resource id")?;
272 self.cache.remove(id);
273 }
274 }
275 msg::DISPLAY_MONITORS_CONFIG => {
276 let n = c.u16("monitor count")?;
277 let _max = c.u16("max monitors")?;
278 ensure!(n <= MAX_MONITORS, "{n} monitors; at most {MAX_MONITORS}");
279 self.monitors.clear();
280 for _ in 0..n {
281 let head = json!({
282 "id": c.u32("head id")?,
283 "surface_id": c.u32("head surface")?,
284 "width": c.u32("head width")?,
285 "height": c.u32("head height")?,
286 "x": c.u32("head x")?,
287 "y": c.u32("head y")?,
288 });
289 let _flags = c.u32("head flags")?;
290 self.monitors.push(head);
291 }
292 }
293 msg::DISPLAY_DRAW_FILL
294 | msg::DISPLAY_DRAW_COPY
295 | msg::DISPLAY_DRAW_BLACKNESS
296 | msg::DISPLAY_DRAW_WHITENESS
297 | msg::DISPLAY_DRAW_INVERS
298 | msg::DISPLAY_COPY_BITS
299 | msg::DISPLAY_DRAW_OPAQUE
300 | msg::DISPLAY_DRAW_BLEND
301 | msg::DISPLAY_DRAW_ROP3
302 | msg::DISPLAY_DRAW_STROKE
303 | msg::DISPLAY_DRAW_TEXT
304 | msg::DISPLAY_DRAW_TRANSPARENT
305 | msg::DISPLAY_DRAW_ALPHA_BLEND
306 | msg::DISPLAY_DRAW_COMPOSITE => {
307 self.touch();
308 let applied = self.draw(msg_type, body)?;
309 if !applied {
310 self.undecoded += 1;
311 }
312 }
313 msg::DISPLAY_STREAM_DATA | msg::DISPLAY_STREAM_DATA_SIZED => {
314 self.touch();
315 self.undecoded += 1;
316 }
317 _ => {}
318 }
319 Ok(None)
320 }
321
322 fn touch(&mut self) {
323 self.updates += 1;
324 self.last_update = Instant::now();
325 }
326
327 fn draw(&mut self, msg_type: u16, body: &[u8]) -> Result<bool> {
330 let mut c = Cursor::new(body);
331 let surface_id = c.u32("surface id")?;
332 let bbox = Rect::read(&mut c)?;
333 let clip_type = c.u8("clip type")?;
334 let clips = if clip_type == 1 {
335 let n = c.u32("clip rect count")?;
336 ensure!(
337 n <= MAX_CLIP_RECTS,
338 "{n} clip rects; at most {MAX_CLIP_RECTS}"
339 );
340 ensure!(
341 (n as usize) * 16 <= c.remaining(),
342 "{n} clip rects do not fit in the message"
343 );
344 (0..n)
345 .map(|_| Rect::read(&mut c))
346 .collect::<Result<Vec<_>>>()?
347 } else {
348 vec![bbox]
349 };
350 let Some(primary) = self.primary.as_ref() else {
351 return Ok(false);
352 };
353 if primary.id != surface_id {
354 return Ok(true);
356 }
357 if primary.format != wire::SURFACE_FMT_32_XRGB && primary.format != 96 {
358 return Ok(false);
359 }
360 let bounds = Rect {
361 top: 0,
362 left: 0,
363 bottom: primary.height as i64,
364 right: primary.width as i64,
365 };
366 let regions: Vec<Rect> = clips
367 .iter()
368 .map(|r| r.intersect(&bbox).intersect(&bounds))
369 .filter(|r| !r.is_empty())
370 .collect();
371 match msg_type {
372 msg::DISPLAY_DRAW_FILL => {
373 let brush = c.u8("brush type")?;
374 if brush != 1 {
375 return Ok(false);
376 }
377 let color = c.u32("brush color")?;
378 let rop = c.u16("rop")?;
379 let mask = Self::read_mask(&mut c)?;
380 if rop != wire::ROPD_OP_PUT || mask {
381 return Ok(false);
382 }
383 let px = [color as u8, (color >> 8) as u8, (color >> 16) as u8, 0];
384 self.fill(®ions, |_| px);
385 Ok(true)
386 }
387 msg::DISPLAY_DRAW_BLACKNESS | msg::DISPLAY_DRAW_WHITENESS => {
388 if Self::read_mask(&mut c)? {
389 return Ok(false);
390 }
391 let v = if msg_type == msg::DISPLAY_DRAW_BLACKNESS {
392 0
393 } else {
394 255
395 };
396 self.fill(®ions, |_| [v, v, v, 0]);
397 Ok(true)
398 }
399 msg::DISPLAY_DRAW_INVERS => {
400 if Self::read_mask(&mut c)? {
401 return Ok(false);
402 }
403 self.fill(®ions, |p| [!p[0], !p[1], !p[2], 0]);
404 Ok(true)
405 }
406 msg::DISPLAY_COPY_BITS => {
407 let sx = c.i32("source x")? as i64;
408 let sy = c.i32("source y")? as i64;
409 let snapshot = self.primary.as_ref().map(|s| s.bgrx.clone());
410 let Some(src) = snapshot else {
411 return Ok(false);
412 };
413 let (w, h) = (bounds.right, bounds.bottom);
414 self.copy(®ions, |x, y| {
415 let (u, v) = (sx + (x - bbox.left), sy + (y - bbox.top));
416 if u < 0 || v < 0 || u >= w || v >= h {
417 return None;
418 }
419 let i = ((v * w + u) * 4) as usize;
420 Some([src[i], src[i + 1], src[i + 2], 0])
421 });
422 Ok(true)
423 }
424 msg::DISPLAY_DRAW_COPY => {
425 let image_at = c.u32("image offset")?;
426 let src = Rect::read(&mut c)?;
427 let rop = c.u16("rop")?;
428 let _scale = c.u8("scale mode")?;
429 let mask = Self::read_mask(&mut c)?;
430 let image = self.image(body, image_at)?;
431 let Some(image) = image else {
432 return Ok(false);
433 };
434 if rop != wire::ROPD_OP_PUT || mask || src.is_empty() || bbox.is_empty() {
435 return Ok(false);
436 }
437 let (sw, sh) = (src.width(), src.height());
438 let (bw, bh) = (bbox.width(), bbox.height());
439 let (iw, ih) = (image.width as i64, image.height as i64);
440 self.copy(®ions, |x, y| {
441 let u = src.left + (x - bbox.left) * sw / bw;
442 let v = src.top + (y - bbox.top) * sh / bh;
443 if u < 0 || v < 0 || u >= iw || v >= ih {
444 return None;
445 }
446 let i = ((v * iw + u) * 4) as usize;
447 Some([image.bgrx[i], image.bgrx[i + 1], image.bgrx[i + 2], 0])
448 });
449 Ok(true)
450 }
451 _ => {
452 if matches!(
456 msg_type,
457 msg::DISPLAY_DRAW_OPAQUE
458 | msg::DISPLAY_DRAW_BLEND
459 | msg::DISPLAY_DRAW_ROP3
460 | msg::DISPLAY_DRAW_TRANSPARENT
461 ) {
462 let at = c.u32("image offset")?;
463 let _ = self.image(body, at)?;
464 }
465 Ok(false)
466 }
467 }
468 }
469
470 fn read_mask(c: &mut Cursor) -> Result<bool> {
472 let _flags = c.u8("mask flags")?;
473 let _x = c.i32("mask x")?;
474 let _y = c.i32("mask y")?;
475 Ok(c.u32("mask image")? != 0)
476 }
477
478 fn fill(&mut self, regions: &[Rect], f: impl Fn([u8; 4]) -> [u8; 4]) {
479 let Some(s) = self.primary.as_mut() else {
480 return;
481 };
482 let w = s.width as i64;
483 for r in regions {
484 for y in r.top..r.bottom {
485 for x in r.left..r.right {
486 let i = ((y * w + x) * 4) as usize;
487 let old = [s.bgrx[i], s.bgrx[i + 1], s.bgrx[i + 2], s.bgrx[i + 3]];
488 s.bgrx[i..i + 4].copy_from_slice(&f(old));
489 }
490 }
491 }
492 }
493
494 fn copy(&mut self, regions: &[Rect], src: impl Fn(i64, i64) -> Option<[u8; 4]>) {
495 let Some(s) = self.primary.as_mut() else {
496 return;
497 };
498 let w = s.width as i64;
499 for r in regions {
500 for y in r.top..r.bottom {
501 for x in r.left..r.right {
502 if let Some(px) = src(x, y) {
503 let i = ((y * w + x) * 4) as usize;
504 s.bgrx[i..i + 4].copy_from_slice(&px);
505 }
506 }
507 }
508 }
509 }
510
511 fn image(&mut self, body: &[u8], offset: u32) -> Result<Option<Image>> {
514 if offset == 0 {
515 return Ok(None);
516 }
517 let mut c = Cursor::at(body, offset as usize);
518 let id = c.u64("image id")?;
519 let kind = c.u8("image type")?;
520 let flags = c.u8("image flags")?;
521 let width = c.u32("image width")?;
522 let height = c.u32("image height")?;
523 *self.images.entry(image_type::name(kind)).or_default() += 1;
524 let image = match kind {
525 image_type::BITMAP => {
526 check_size("an image", width, height)?;
527 let format = c.u8("bitmap format")?;
528 let bflags = c.u8("bitmap flags")?;
529 let x = c.u32("bitmap width")?;
530 let y = c.u32("bitmap height")?;
531 let stride = c.u32("bitmap stride")?;
532 if bflags & wire::BITMAP_FLAGS_PAL_FROM_CACHE != 0 {
533 c.u64("palette id")?;
534 } else {
535 c.u32("palette offset")?;
536 }
537 let bpp = match format {
538 wire::BITMAP_FMT_32BIT | wire::BITMAP_FMT_RGBA => 4,
539 wire::BITMAP_FMT_24BIT => 3,
540 _ => return Ok(None),
541 };
542 ensure!(
543 x == width && y == height,
544 "a {x}x{y} bitmap in a {width}x{height} image"
545 );
546 ensure!(
547 u64::from(stride) >= u64::from(x) * bpp as u64,
548 "a bitmap stride of {stride} for {x} pixels of {bpp} bytes"
549 );
550 let size = u64::from(stride) * u64::from(y);
551 ensure!(
552 size <= c.remaining() as u64,
553 "a bitmap of {size} bytes in a message with {} left",
554 c.remaining()
555 );
556 let data = c.bytes(size as usize, "bitmap data")?;
557 let top_down = bflags & wire::BITMAP_FLAGS_TOP_DOWN != 0;
558 let mut bgrx = Vec::with_capacity(x as usize * y as usize * 4);
559 for row in 0..y as usize {
560 let r = if top_down { row } else { y as usize - 1 - row };
561 let line = &data[r * stride as usize..];
562 for px in 0..x as usize {
563 let p = &line[px * bpp..px * bpp + bpp];
564 bgrx.extend_from_slice(&[p[0], p[1], p[2], 0]);
565 }
566 }
567 Some(Image {
568 width,
569 height,
570 bgrx,
571 })
572 }
573 image_type::FROM_CACHE | image_type::FROM_CACHE_LOSSLESS => {
574 self.cache.images.get(&id).cloned()
575 }
576 _ => None,
577 };
578 if let Some(img) = &image {
579 if flags & wire::IMAGE_FLAGS_CACHE_ME != 0 {
580 self.cache.insert(id, img.clone());
581 }
582 }
583 Ok(image)
584 }
585
586 pub fn primary_size(&self) -> Option<(u32, u32)> {
587 self.primary.as_ref().map(|s| (s.width, s.height))
588 }
589
590 pub fn describe(&self, grid: usize) -> Value {
592 let mut out = match &self.primary {
593 Some(s) => describe_bgrx(&s.bgrx, s.width as usize, s.height as usize, grid),
594 None => json!({"width": 0, "height": 0}),
595 };
596 out["surface"] = json!(self.primary.is_some());
597 out["marked"] = json!(self.marked);
598 out["updates"] = json!(self.updates);
599 out["draw_ops"] = json!(self.ops);
600 out["images"] = json!(self.images);
601 out["undecoded"] = json!(self.undecoded);
602 out["exact"] = json!(self.undecoded == 0);
603 out["offscreen_surfaces"] = json!(self.other_surfaces);
604 out["monitors"] = json!(self.monitors);
605 out
606 }
607}
608
609fn hex([r, g, b]: [u8; 3]) -> String {
610 format!("#{r:02x}{g:02x}{b:02x}")
611}
612
613pub fn describe_bgrx(bgrx: &[u8], width: usize, height: usize, grid: usize) -> Value {
617 let grid = grid.clamp(1, width.max(1)).clamp(1, height.max(1));
618 let mut counts: HashMap<[u8; 3], u64> = HashMap::new();
619 let mut cells: Vec<HashMap<[u8; 3], u64>> = vec![HashMap::new(); grid * grid];
620 let mut checksum: u64 = 0xcbf2_9ce4_8422_2325;
621 for y in 0..height {
622 let gy = y * grid / height;
623 for x in 0..width {
624 let i = (y * width + x) * 4;
625 let px = [bgrx[i + 2], bgrx[i + 1], bgrx[i]];
626 for b in px {
627 checksum ^= u64::from(b);
628 checksum = checksum.wrapping_mul(0x0100_0000_01b3);
629 }
630 *counts.entry(px).or_default() += 1;
631 *cells[gy * grid + x * grid / width].entry(px).or_default() += 1;
632 }
633 }
634 let (dominant, n) = most_common(&counts);
635 let rows: Vec<Vec<String>> = cells
636 .chunks(grid)
637 .map(|row| row.iter().map(|c| hex(most_common(c).0)).collect())
638 .collect();
639 json!({
640 "width": width,
641 "height": height,
642 "dominant_color": hex(dominant),
643 "dominant_share": ((n as f64 / (width * height).max(1) as f64) * 1000.0).round() / 1000.0,
644 "distinct_colors": counts.len(),
645 "grid": rows,
646 "checksum": format!("{checksum:016x}"),
647 })
648}
649
650fn most_common(counts: &HashMap<[u8; 3], u64>) -> ([u8; 3], u64) {
651 counts
652 .iter()
653 .max_by_key(|&(px, n)| (*n, std::cmp::Reverse(*px)))
654 .map(|(px, n)| (*px, *n))
655 .unwrap_or(([0, 0, 0], 0))
656}
657
658pub fn pixel_hex(model: &DisplayModel, x: u32, y: u32) -> Option<String> {
660 let s = model.primary.as_ref()?;
661 if x >= s.width || y >= s.height {
662 return None;
663 }
664 let i = ((y * s.width + x) * 4) as usize;
665 Some(hex([s.bgrx[i + 2], s.bgrx[i + 1], s.bgrx[i]]))
666}