Skip to main content

ferroui_headless_vnc/
headless_vnc_framebuffer_source.rs

1//! Port of `HeadlessVncFramebufferSource.cs`: the framebuffer source of a
2//! VNC server session, which shows a headless window and passes the input
3//! of the client to it.
4//!
5//! The session calls the source on its own threads, while the window lives
6//! on the UI thread. As upstream, the input is posted to the UI thread at
7//! input priority. Upstream reads the last rendered frame of the window on
8//! the thread of the session; here the window can only be reached on the UI
9//! thread, so [`capture`](HeadlessVncFramebufferSource::capture) does its
10//! work there and waits for it (the frame itself is kept under a lock by
11//! the window, as upstream).
12//!
13//! One difference: the input events reach the window in the order the
14//! client sent them. Upstream posts each event on its own; the input
15//! extensions of a headless window run the jobs of the dispatcher before
16//! they deliver an event, so an event posted while another is delivered
17//! (a release right after a press, as a client that is not a person sends
18//! them) is delivered first, and the press after it. Here the events wait
19//! in a queue of the source, and each post delivers what is queued, in
20//! order, unless a delivery is running already.
21
22use crate::remote_viewing::vnc::server::VncServerSession;
23use crate::remote_viewing::vnc::{
24    ExtendedDesktopSizeStatus, IVncFramebufferSource, KeyChangedEventArgs, KeySym, VncFramebuffer,
25    VncPixelFormat,
26};
27use ferroui_base::input::{Key, MouseButton, PhysicalKey, RawInputModifiers};
28use ferroui_base::threading::{Dispatcher, DispatcherPriority};
29use ferroui_base::utilities::{NumberFormatInfo, ThreadBound};
30use ferroui_base::{PixelRect, Point, Ref, Vector};
31use ferroui_controls::Window;
32use ferroui_headless::HeadlessWindowExtensions;
33use std::cell::{Cell, RefCell};
34use std::collections::VecDeque;
35use std::sync::{Arc, Mutex, MutexGuard, PoisonError, Weak};
36
37bitflags::bitflags! {
38    /// The buttons of a pointer event of the protocol.
39    #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
40    struct VncButton: i32 {
41        const LEFT = 1;
42        const MIDDLE = 2;
43        const RIGHT = 4;
44        const SCROLL_UP = 8;
45        const SCROLL_DOWN = 16;
46    }
47}
48
49/// The buttons whose presses and releases are passed on.
50const CHECKED_BUTTONS: [VncButton; 3] = [VncButton::LEFT, VncButton::MIDDLE, VncButton::RIGHT];
51
52fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
53    mutex.lock().unwrap_or_else(PoisonError::into_inner)
54}
55
56/// An input event that waits for the UI thread.
57type PendingInput = Box<dyn FnOnce(&HeadlessVncFramebufferSource) + Send>;
58
59/// The framebuffer source of a VNC server session that shows a headless
60/// window.
61pub struct HeadlessVncFramebufferSource {
62    this: Weak<HeadlessVncFramebufferSource>,
63    /// `Window`: reached on the UI thread only.
64    window: ThreadBound<RefCell<Option<Ref<Window>>>>,
65    /// `_framebuffer`, under `_lock`.
66    framebuffer: Mutex<Arc<VncFramebuffer>>,
67    previous_buttons: Mutex<VncButton>,
68    key_state: Mutex<RawInputModifiers>,
69    /// The input events that wait for the UI thread, in the order they
70    /// came.
71    pending_input: Mutex<VecDeque<PendingInput>>,
72    /// Whether the UI thread delivers input events.
73    delivering_input: ThreadBound<Cell<bool>>,
74}
75
76impl HeadlessVncFramebufferSource {
77    /// Creates the source of `session` for `window`, and handles the
78    /// pointer and key events of the session.
79    ///
80    /// Called on the UI thread, which the window belongs to.
81    pub fn new(
82        session: &VncServerSession,
83        window: Ref<Window>,
84    ) -> Arc<HeadlessVncFramebufferSource> {
85        let source = Arc::new_cyclic(|this| HeadlessVncFramebufferSource {
86            this: this.clone(),
87            window: ThreadBound::new(RefCell::new(Some(window))),
88            framebuffer: Mutex::new(VncFramebuffer::new("FerroUI", 1, 1, VncPixelFormat::RGB32)),
89            previous_buttons: Mutex::new(VncButton::empty()),
90            key_state: Mutex::new(RawInputModifiers::NONE),
91            pending_input: Mutex::new(VecDeque::new()),
92            delivering_input: ThreadBound::new(Cell::new(false)),
93        });
94
95        let weak = Arc::downgrade(&source);
96        session.pointer_changed().add(move |_, args| {
97            let Some(this) = weak.upgrade() else {
98                return;
99            };
100            let pt = Point::new(f64::from(args.x()), f64::from(args.y()));
101
102            let buttons = VncButton::from_bits_retain(args.pressed_buttons());
103
104            fn translate_button(vnc_button: VncButton) -> MouseButton {
105                if vnc_button == VncButton::LEFT {
106                    MouseButton::Left
107                } else if vnc_button == VncButton::MIDDLE {
108                    MouseButton::Middle
109                } else if vnc_button == VncButton::RIGHT {
110                    MouseButton::Right
111                } else {
112                    MouseButton::None
113                }
114            }
115
116            let modifiers = RawInputModifiers::from_bits_retain((buttons.bits() & 7) << 4);
117
118            this.post_input(move |this| {
119                let window = this.window();
120                let Some(window) = window else {
121                    *lock(&this.previous_buttons) = buttons;
122                    return;
123                };
124                let previous_buttons = *lock(&this.previous_buttons);
125                window.mouse_move(pt, RawInputModifiers::NONE);
126                for btn in CHECKED_BUTTONS {
127                    if previous_buttons.contains(btn) && !buttons.contains(btn) {
128                        window.mouse_up(pt, translate_button(btn), modifiers);
129                    }
130                }
131
132                for btn in CHECKED_BUTTONS {
133                    if !previous_buttons.contains(btn) && buttons.contains(btn) {
134                        window.mouse_down(pt, translate_button(btn), modifiers);
135                    }
136                }
137
138                if buttons == VncButton::SCROLL_UP {
139                    window.mouse_wheel(pt, Vector::ONE, modifiers);
140                } else if buttons == VncButton::SCROLL_DOWN {
141                    window.mouse_wheel(pt, Vector::ONE.negate(), modifiers);
142                }
143
144                *lock(&this.previous_buttons) = buttons;
145            });
146        });
147
148        let weak = Arc::downgrade(&source);
149        session.key_changed().add(move |_, args| {
150            let Some(this) = weak.upgrade() else {
151                return;
152            };
153            let is_modifier_key = this.check_key_is_input_modifier(args);
154            if is_modifier_key {
155                return;
156            }
157
158            let (key, key_symbol) = Self::translate_key(args.keysym());
159            if key == Key::None {
160                return;
161            }
162
163            // We only care about text input on key up if not using Ctrl or Alt.
164            let key_state = *lock(&this.key_state);
165            let input_text = if args.pressed()
166                || key_state.contains(RawInputModifiers::CONTROL)
167                || key_state.contains(RawInputModifiers::ALT)
168            {
169                None
170            } else {
171                key_symbol.clone()
172            };
173
174            let pressed = args.pressed();
175            this.post_input(move |this| {
176                let Some(window) = this.window() else {
177                    return;
178                };
179                let key_state = *lock(&this.key_state);
180                if pressed {
181                    window.key_press(key, key_state, PhysicalKey::None, key_symbol.as_deref());
182                } else {
183                    window.key_release(key, key_state, PhysicalKey::None, key_symbol.as_deref());
184                }
185
186                if let Some(input_text) = &input_text {
187                    window.key_text_input(input_text);
188                }
189            });
190        });
191
192        source
193    }
194
195    /// Queues an input event and posts its delivery to the UI thread, at
196    /// input priority.
197    fn post_input(&self, input: impl FnOnce(&HeadlessVncFramebufferSource) + Send + 'static) {
198        lock(&self.pending_input).push_back(Box::new(input));
199        let this = self.arc();
200        Dispatcher::ui_thread().post(move || this.deliver_input(), DispatcherPriority::INPUT);
201    }
202
203    /// Delivers the queued input events in order, on the UI thread. A
204    /// delivery that starts while one runs (from the jobs the input
205    /// extensions of the window run) leaves the queue to it.
206    fn deliver_input(&self) {
207        struct Delivering<'a>(&'a Cell<bool>);
208        impl Drop for Delivering<'_> {
209            fn drop(&mut self) {
210                self.0.set(false);
211            }
212        }
213
214        let delivering = self.delivering_input.get();
215        if delivering.replace(true) {
216            return;
217        }
218        let _delivering = Delivering(delivering);
219
220        loop {
221            let input = lock(&self.pending_input).pop_front();
222            let Some(input) = input else {
223                return;
224            };
225            input(self);
226        }
227    }
228
229    fn arc(&self) -> Arc<HeadlessVncFramebufferSource> {
230        self.this
231            .upgrade()
232            .expect("the source is alive while it is used")
233    }
234
235    /// The window the source shows.
236    ///
237    /// # Panics
238    /// On a thread other than the UI thread.
239    pub fn window(&self) -> Option<Ref<Window>> {
240        self.window.get().borrow().clone()
241    }
242
243    /// Sets the window the source shows.
244    ///
245    /// # Panics
246    /// On a thread other than the UI thread.
247    pub fn set_window(&self, value: Option<Ref<Window>>) {
248        let old = self.window.get().replace(value);
249        drop(old);
250    }
251
252    /// The framebuffer the source captured last (`_framebuffer`).
253    pub fn framebuffer(&self) -> Arc<VncFramebuffer> {
254        lock(&self.framebuffer).clone()
255    }
256
257    /// Keeps the state of the modifier keys: whether the key of `args` is
258    /// one (Shift, Control, Alt), in which case it changes the state.
259    fn check_key_is_input_modifier(&self, args: &KeyChangedEventArgs) -> bool {
260        let toggle_modifier = match args.keysym() {
261            KeySym::ShiftLeft | KeySym::ShiftRight => Some(RawInputModifiers::SHIFT),
262            KeySym::ControlLeft | KeySym::ControlRight => Some(RawInputModifiers::CONTROL),
263            KeySym::AltLeft | KeySym::AltRight => Some(RawInputModifiers::ALT),
264            _ => None,
265        };
266
267        let Some(toggle_modifier) = toggle_modifier else {
268            return false;
269        };
270
271        let mut key_state = lock(&self.key_state);
272        if args.pressed() {
273            *key_state |= toggle_modifier;
274        } else {
275            *key_state &= !toggle_modifier;
276        }
277
278        true
279    }
280
281    /// The key of the framework and the text a key symbol stands for.
282    fn translate_key(key: KeySym) -> (Key, Option<String>) {
283        let (key, symbol): (Key, Option<&str>) = match key {
284            KeySym::Backspace => (Key::Back, Some("\u{8}")),
285            KeySym::Tab => (Key::Tab, Some("\t")),
286            KeySym::LineFeed => (Key::LineFeed, None),
287            KeySym::Clear => (Key::Clear, None),
288            KeySym::Return => (Key::Return, Some("\r")),
289            KeySym::Pause => (Key::Pause, None),
290            KeySym::Escape => (Key::Escape, Some("\u{1B}")),
291            KeySym::Delete => (Key::Delete, None),
292            KeySym::Home => (Key::Home, None),
293            KeySym::Left => (Key::Left, None),
294            KeySym::Up => (Key::Up, None),
295            KeySym::Right => (Key::Right, None),
296            KeySym::Down => (Key::Down, None),
297            KeySym::PageUp => (Key::PageUp, None),
298            KeySym::PageDown => (Key::PageDown, None),
299            KeySym::End => (Key::End, None),
300            KeySym::Begin => (Key::Home, None),
301            KeySym::Select => (Key::Select, None),
302            KeySym::Print => (Key::Print, None),
303            KeySym::Execute => (Key::Execute, None),
304            KeySym::Insert => (Key::Insert, None),
305            KeySym::Cancel => (Key::Cancel, None),
306            KeySym::Help => (Key::Help, None),
307            KeySym::Break => (Key::Pause, None),
308            KeySym::Num_Lock => (Key::NumLock, None),
309            KeySym::NumPadSpace => (Key::Space, None),
310            KeySym::NumPadTab => (Key::Tab, None),
311            KeySym::NumPadEnter => (Key::Enter, None),
312            KeySym::NumPadF1 => (Key::F1, None),
313            KeySym::NumPadF2 => (Key::F2, None),
314            KeySym::NumPadF3 => (Key::F3, None),
315            KeySym::NumPadF4 => (Key::F4, None),
316            KeySym::NumPadHome => (Key::Home, None),
317            KeySym::NumPadLeft => (Key::Left, None),
318            KeySym::NumPadUp => (Key::Up, None),
319            KeySym::NumPadRight => (Key::Right, None),
320            KeySym::NumPadDown => (Key::Down, None),
321            KeySym::NumPadPageUp => (Key::PageUp, None),
322            KeySym::NumPadPageDown => (Key::PageDown, None),
323            KeySym::NumPadEnd => (Key::End, None),
324            KeySym::NumPadBegin => (Key::Home, None),
325            KeySym::NumPadInsert => (Key::Insert, None),
326            KeySym::NumPadDelete => (Key::Delete, None),
327            KeySym::NumPadEqual => (Key::Enter, Some("=")),
328            KeySym::NumPadMultiply => (Key::Multiply, Some("*")),
329            KeySym::NumPadAdd => (Key::Add, Some("+")),
330            KeySym::NumPadSeparator => {
331                return (
332                    Key::Separator,
333                    Some(
334                        NumberFormatInfo::current_info()
335                            .number_group_separator()
336                            .to_owned(),
337                    ),
338                )
339            }
340            KeySym::NumPadSubtract => (Key::Subtract, Some("-")),
341            KeySym::NumPadDecimal => {
342                return (
343                    Key::Decimal,
344                    Some(
345                        NumberFormatInfo::current_info()
346                            .number_decimal_separator()
347                            .to_owned(),
348                    ),
349                )
350            }
351            KeySym::NumPadDivide => (Key::Divide, Some("/")),
352            KeySym::NumPad0 => (Key::NumPad0, Some("0")),
353            KeySym::NumPad1 => (Key::NumPad1, Some("1")),
354            KeySym::NumPad2 => (Key::NumPad2, Some("2")),
355            KeySym::NumPad3 => (Key::NumPad3, Some("3")),
356            KeySym::NumPad4 => (Key::NumPad4, Some("4")),
357            KeySym::NumPad5 => (Key::NumPad5, Some("5")),
358            KeySym::NumPad6 => (Key::NumPad6, Some("6")),
359            KeySym::NumPad7 => (Key::NumPad7, Some("7")),
360            KeySym::NumPad8 => (Key::NumPad8, Some("8")),
361            KeySym::NumPad9 => (Key::NumPad9, Some("9")),
362            KeySym::F1 => (Key::F1, None),
363            KeySym::F2 => (Key::F2, None),
364            KeySym::F3 => (Key::F3, None),
365            KeySym::F4 => (Key::F4, None),
366            KeySym::F5 => (Key::F5, None),
367            KeySym::F6 => (Key::F6, None),
368            KeySym::F7 => (Key::F7, None),
369            KeySym::F8 => (Key::F8, None),
370            KeySym::F9 => (Key::F9, None),
371            KeySym::F10 => (Key::F10, None),
372            KeySym::F11 => (Key::F11, None),
373            KeySym::F12 => (Key::F12, None),
374            KeySym::F13 => (Key::F13, None),
375            KeySym::F14 => (Key::F14, None),
376            KeySym::F15 => (Key::F15, None),
377            KeySym::F16 => (Key::F16, None),
378            KeySym::F17 => (Key::F17, None),
379            KeySym::F18 => (Key::F18, None),
380            KeySym::F19 => (Key::F19, None),
381            KeySym::F20 => (Key::F20, None),
382            KeySym::F21 => (Key::F21, None),
383            KeySym::F22 => (Key::F22, None),
384            KeySym::F23 => (Key::F23, None),
385            KeySym::F24 => (Key::F24, None),
386            KeySym::ShiftLeft => (Key::LeftShift, None),
387            KeySym::ShiftRight => (Key::RightShift, None),
388            KeySym::ControlLeft => (Key::LeftCtrl, None),
389            KeySym::ControlRight => (Key::RightCtrl, None),
390            KeySym::CapsLock => (Key::CapsLock, None),
391            KeySym::AltLeft => (Key::LeftAlt, None),
392            KeySym::AltRight => (Key::RightAlt, None),
393            KeySym::Space => (Key::Space, Some(" ")),
394            KeySym::Exclamation => (Key::D1, Some("!")),
395            KeySym::Quote => (Key::D2, Some("\"")),
396            KeySym::NumberSign => (Key::D3, Some("#")),
397            KeySym::Dollar => (Key::D4, Some("$")),
398            KeySym::Percent => (Key::D5, Some("%")),
399            KeySym::Ampersand => (Key::D7, Some("&")),
400            KeySym::Apostrophe => (Key::Oem3, Some("'")),
401            KeySym::ParenthesisLeft => (Key::D9, Some("(")),
402            KeySym::ParenthesisRight => (Key::D0, Some(")")),
403            KeySym::Asterisk => (Key::D8, Some("*")),
404            KeySym::Plus => (Key::OemPlus, Some("+")),
405            KeySym::Comma => (Key::OemComma, Some(",")),
406            KeySym::Minus => (Key::OemMinus, Some("-")),
407            KeySym::Period => (Key::OemPeriod, Some(".")),
408            KeySym::Slash => (Key::OemQuestion, Some("/")),
409            KeySym::D0 => (Key::D0, Some("0")),
410            KeySym::D1 => (Key::D1, Some("1")),
411            KeySym::D2 => (Key::D2, Some("2")),
412            KeySym::D3 => (Key::D3, Some("3")),
413            KeySym::D4 => (Key::D4, Some("4")),
414            KeySym::D5 => (Key::D5, Some("5")),
415            KeySym::D6 => (Key::D6, Some("6")),
416            KeySym::D7 => (Key::D7, Some("7")),
417            KeySym::D8 => (Key::D8, Some("8")),
418            KeySym::D9 => (Key::D9, Some("9")),
419            KeySym::Colon => (Key::OemSemicolon, Some(":")),
420            KeySym::Semicolon => (Key::OemSemicolon, Some(";")),
421            KeySym::Less => (Key::OemComma, Some("<")),
422            KeySym::Equal => (Key::OemPlus, Some("=")),
423            KeySym::Greater => (Key::OemPeriod, Some(">")),
424            KeySym::Question => (Key::OemQuestion, Some("?")),
425            KeySym::At => (Key::Oem3, Some("@")),
426            KeySym::A => (Key::A, Some("A")),
427            KeySym::B => (Key::B, Some("B")),
428            KeySym::C => (Key::C, Some("C")),
429            KeySym::D => (Key::D, Some("D")),
430            KeySym::E => (Key::E, Some("E")),
431            KeySym::F => (Key::F, Some("F")),
432            KeySym::G => (Key::G, Some("G")),
433            KeySym::H => (Key::H, Some("H")),
434            KeySym::I => (Key::I, Some("I")),
435            KeySym::J => (Key::J, Some("J")),
436            KeySym::K => (Key::K, Some("K")),
437            KeySym::L => (Key::L, Some("L")),
438            KeySym::M => (Key::M, Some("M")),
439            KeySym::N => (Key::N, Some("N")),
440            KeySym::O => (Key::O, Some("O")),
441            KeySym::P => (Key::P, Some("P")),
442            KeySym::Q => (Key::Q, Some("Q")),
443            KeySym::R => (Key::R, Some("R")),
444            KeySym::S => (Key::S, Some("S")),
445            KeySym::T => (Key::T, Some("T")),
446            KeySym::U => (Key::U, Some("U")),
447            KeySym::V => (Key::V, Some("V")),
448            KeySym::W => (Key::W, Some("W")),
449            KeySym::X => (Key::X, Some("X")),
450            KeySym::Y => (Key::Y, Some("Y")),
451            KeySym::Z => (Key::Z, Some("Z")),
452            KeySym::BracketLeft => (Key::OemOpenBrackets, Some("[")),
453            KeySym::Backslash => (Key::OemPipe, Some("\\")),
454            KeySym::Bracketright => (Key::OemCloseBrackets, Some("]")),
455            KeySym::Underscore => (Key::OemMinus, Some("_")),
456            KeySym::Grave => (Key::Oem8, Some("`")),
457            KeySym::a => (Key::A, Some("a")),
458            KeySym::b => (Key::B, Some("b")),
459            KeySym::c => (Key::C, Some("c")),
460            KeySym::d => (Key::D, Some("d")),
461            KeySym::e => (Key::E, Some("e")),
462            KeySym::f => (Key::F, Some("f")),
463            KeySym::g => (Key::G, Some("g")),
464            KeySym::h => (Key::H, Some("h")),
465            KeySym::i => (Key::I, Some("i")),
466            KeySym::j => (Key::J, Some("j")),
467            KeySym::k => (Key::K, Some("k")),
468            KeySym::l => (Key::L, Some("l")),
469            KeySym::m => (Key::M, Some("m")),
470            KeySym::n => (Key::M, Some("n")),
471            KeySym::o => (Key::O, Some("o")),
472            KeySym::p => (Key::P, Some("p")),
473            KeySym::q => (Key::Q, Some("q")),
474            KeySym::r => (Key::R, Some("r")),
475            KeySym::s => (Key::S, Some("s")),
476            KeySym::t => (Key::T, Some("t")),
477            KeySym::u => (Key::U, Some("u")),
478            KeySym::v => (Key::V, Some("v")),
479            KeySym::w => (Key::W, Some("w")),
480            KeySym::x => (Key::X, Some("x")),
481            KeySym::y => (Key::Y, Some("y")),
482            KeySym::z => (Key::Z, Some("z")),
483            KeySym::BraceLeft => (Key::OemOpenBrackets, Some("{")),
484            KeySym::Bar => (Key::OemPipe, Some("|")),
485            KeySym::BraceRight => (Key::OemCloseBrackets, Some("}")),
486            KeySym::AsciiTilde => (Key::OemTilde, Some("~")),
487            _ => (Key::None, None),
488        };
489
490        (key, symbol.map(str::to_owned))
491    }
492
493    /// `Capture` on the UI thread: copies the last rendered frame of the
494    /// window into the framebuffer, which is replaced when the size of the
495    /// frame changed.
496    fn capture_on_ui_thread(&self) -> Arc<VncFramebuffer> {
497        let mut framebuffer = lock(&self.framebuffer);
498        let Some(bmp) = self
499            .window()
500            .and_then(|window| window.get_last_rendered_frame())
501        else {
502            return framebuffer.clone();
503        };
504
505        let pixel_size = bmp.pixel_size();
506        if pixel_size.width != framebuffer.width() || pixel_size.height != framebuffer.height() {
507            *framebuffer = VncFramebuffer::new(
508                "FerroUI",
509                pixel_size.width,
510                pixel_size.height,
511                VncPixelFormat::RGB32,
512            );
513        }
514
515        {
516            let mut buffer = framebuffer.get_buffer();
517            bmp.copy_pixels(
518                PixelRect::from_size(pixel_size),
519                &mut buffer[..],
520                framebuffer.stride(),
521            );
522        }
523        bmp.dispose();
524
525        framebuffer.clone()
526    }
527}
528
529impl IVncFramebufferSource for HeadlessVncFramebufferSource {
530    fn supports_resizing(&self) -> bool {
531        true
532    }
533
534    fn capture(&self) -> Option<Arc<VncFramebuffer>> {
535        let this = self.arc();
536        match Dispatcher::ui_thread().invoke(move || this.capture_on_ui_thread()) {
537            Ok(framebuffer) => Some(framebuffer),
538            // The UI thread has shut down: the frame of before stays.
539            Err(_) => Some(self.framebuffer()),
540        }
541    }
542
543    fn set_desktop_size(&self, width: i32, height: i32) -> ExtendedDesktopSizeStatus {
544        let this = self.arc();
545        Dispatcher::ui_thread().post(
546            move || {
547                if let Some(window) = this.window() {
548                    window.set_width(f64::from(width));
549                    window.set_height(f64::from(height));
550                }
551            },
552            DispatcherPriority::NORMAL,
553        );
554        ExtendedDesktopSizeStatus::Success
555    }
556}
557
558#[cfg(test)]
559mod tests {
560    // Not from upstream.
561    use super::*;
562
563    #[test]
564    fn letters_digits_and_control_keys_translate_to_keys_and_text() {
565        assert_eq!(
566            (Key::A, Some("a".to_owned())),
567            HeadlessVncFramebufferSource::translate_key(KeySym::a)
568        );
569        assert_eq!(
570            (Key::A, Some("A".to_owned())),
571            HeadlessVncFramebufferSource::translate_key(KeySym::A)
572        );
573        assert_eq!(
574            (Key::D7, Some("7".to_owned())),
575            HeadlessVncFramebufferSource::translate_key(KeySym::D7)
576        );
577        assert_eq!(
578            (Key::Return, Some("\r".to_owned())),
579            HeadlessVncFramebufferSource::translate_key(KeySym::Return)
580        );
581        assert_eq!(
582            (Key::Left, None),
583            HeadlessVncFramebufferSource::translate_key(KeySym::Left)
584        );
585        assert_eq!(
586            (Key::Decimal, Some(".".to_owned())),
587            HeadlessVncFramebufferSource::translate_key(KeySym::NumPadDecimal)
588        );
589        assert_eq!(
590            (Key::None, None),
591            HeadlessVncFramebufferSource::translate_key(KeySym::VoidSymbol)
592        );
593        // As upstream: the small letter n is the key M.
594        assert_eq!(
595            (Key::M, Some("n".to_owned())),
596            HeadlessVncFramebufferSource::translate_key(KeySym::n)
597        );
598    }
599}