Skip to main content

mx_remote/wire/
enums.rs

1// Author: Lars Op den Kamp (lars@opdenkamp-it.nl)
2// Copyright (c) 2026 Op den Kamp IT Solutions
3
4//! Wire enumerations and bitmasks.
5//!
6//! Each type is a newtype over the integer that travels on the wire, with
7//! named constants rather than a closed set of variants. A value this library
8//! has no name for reaches the caller as it arrived: zero is a valid value for
9//! most of these, so a confidently wrong reading is worse than an unrecognised
10//! one.
11
12use core::fmt;
13use core::ops::{BitAnd, BitOr, BitOrAssign};
14
15/// Declares a bitmask newtype with the given named bit constants.
16///
17/// The representation defaults to `u32`; give it explicitly as `Name: u64` for
18/// a mask whose wire field is wider.
19macro_rules! bitmask {
20    (
21        $(#[$meta:meta])*
22        $name:ident { $( $(#[$cmeta:meta])* $cname:ident = $value:expr; )* }
23    ) => {
24        bitmask! {
25            $(#[$meta])*
26            $name: u32 { $( $(#[$cmeta])* $cname = $value; )* }
27        }
28    };
29    (
30        $(#[$meta:meta])*
31        $name:ident: $repr:ty { $( $(#[$cmeta:meta])* $cname:ident = $value:expr; )* }
32    ) => {
33        $(#[$meta])*
34        #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
35        pub struct $name($repr);
36
37        impl $name {
38            /// No bits set.
39            pub const NONE: Self = Self(0);
40
41            $( $(#[$cmeta])* pub const $cname: Self = Self($value); )*
42
43            /// Wraps a raw wire value, including bits this library has no name for.
44            pub const fn from_bits(bits: $repr) -> Self {
45                Self(bits)
46            }
47
48            /// Returns the raw wire value.
49            pub const fn bits(self) -> $repr {
50                self.0
51            }
52
53            /// Reports whether every bit in `other` is set.
54            pub const fn has(self, other: Self) -> bool {
55                self.0 & other.0 == other.0
56            }
57
58            /// Reports whether no bit is set.
59            pub const fn is_empty(self) -> bool {
60                self.0 == 0
61            }
62        }
63
64        impl BitOr for $name {
65            type Output = Self;
66            fn bitor(self, rhs: Self) -> Self {
67                Self(self.0 | rhs.0)
68            }
69        }
70
71        impl BitOrAssign for $name {
72            fn bitor_assign(&mut self, rhs: Self) {
73                self.0 |= rhs.0;
74            }
75        }
76
77        impl BitAnd for $name {
78            type Output = Self;
79            fn bitand(self, rhs: Self) -> Self {
80                Self(self.0 & rhs.0)
81            }
82        }
83
84        impl fmt::Display for $name {
85            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86                // Two hex digits per byte of the wire field, plus "0x".
87                write!(
88                    f,
89                    "{:#0width$x}",
90                    self.0,
91                    width = core::mem::size_of::<$repr>() * 2 + 2
92                )
93            }
94        }
95    };
96}
97
98/// Declares an enumeration newtype over `$repr` with the given named constants.
99macro_rules! wire_enum {
100    (
101        $(#[$meta:meta])*
102        $name:ident: $repr:ty { $( $(#[$cmeta:meta])* $cname:ident = $value:expr; )* }
103    ) => {
104        $(#[$meta])*
105        #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
106        pub struct $name($repr);
107
108        impl $name {
109            $( $(#[$cmeta])* pub const $cname: Self = Self($value); )*
110
111            /// Wraps a raw wire value, including one this library has no name for.
112            pub const fn from_wire(value: $repr) -> Self {
113                Self(value)
114            }
115
116            /// Returns the raw wire value.
117            pub const fn to_wire(self) -> $repr {
118                self.0
119            }
120        }
121    };
122}
123
124bitmask! {
125    /// Capabilities a device reports in its hello frame.
126    DeviceFeature {
127        /// Receives infrared.
128        IR_RX = 1 << 0;
129        /// Transmits infrared.
130        IR_TX = 1 << 1;
131        /// Speaks CEC.
132        CEC = 1 << 2;
133        /// Acts as a V2IP stream source.
134        V2IP_SOURCE = 1 << 3;
135        /// Acts as a V2IP stream sink.
136        V2IP_SINK = 1 << 4;
137        /// Routes video.
138        VIDEO_ROUTING = 1 << 5;
139        /// Routes audio.
140        AUDIO_ROUTING = 1 << 6;
141        /// Controls volume.
142        VOLUME_CONTROL = 1 << 7;
143        /// Supports audio return.
144        AUDIO_RETURN = 1 << 8;
145        /// Passes remote-control commands through.
146        REMOTE_CONTROL = 1 << 9;
147        /// Installer setup has been completed.
148        SETUP_COMPLETED = 1 << 10;
149        /// Is the master of its mesh.
150        MESH_MASTER = 1 << 11;
151        /// Has a notification pending.
152        STATUS_NOTIFY = 1 << 12;
153        /// Has a warning pending.
154        STATUS_WARNING = 1 << 13;
155        /// Has an error pending.
156        STATUS_ERROR = 1 << 14;
157        /// Is about to reboot.
158        STATUS_REBOOT = 1 << 15;
159        /// Is a member of a mesh.
160        MESH_MEMBER = 1 << 16;
161        /// Is an audio amplifier.
162        AUDIO_AMPLIFIER = 1 << 17;
163        /// Is still booting.
164        BOOTING = 1 << 18;
165        /// Is a management client rather than a device.
166        MANAGER = 1 << 19;
167        /// Is in power-save mode.
168        STATUS_POWER_SAVE = 1 << 20;
169        /// Supports meshing.
170        MESH = 1 << 21;
171        /// Is a multiviewer.
172        MULTIVIEWER = 1 << 22;
173        /// Has crashed since it last booted.
174        STATUS_CRASHED = 1 << 23;
175        /// Supports video walls.
176        VIDEO_WALL = 1 << 24;
177        /// Initialises the configuration it broadcasts.
178        ///
179        /// Firmware without this bit sends a device configuration built over
180        /// uninitialised memory, so fields it did not mean to write carry junk.
181        CONFIG_INITIALISED = 1 << 25;
182        /// Set while the device is in its boot loader.
183        BOOT_BIT = 1 << 31;
184    }
185}
186
187bitmask! {
188    /// What a V2IP device's video processor supports, as the device reports it
189    /// in its configuration.
190    ///
191    /// Read-only, and a device's own: it fills the field in only on the frame
192    /// describing itself, and leaves it zero on one it sends to configure
193    /// another device. There is no write path.
194    ///
195    /// Bits are assigned by the video processor and only ever appended, so a
196    /// bit this library has no name for is a later capability rather than an
197    /// error. A device reports no features at all until its processor answers,
198    /// and an older processor answers with none of the optional commands, so an
199    /// empty mask is never reported as a capability set - see
200    /// [`crate::Remote::v2ip_features`], which reports it as unknown instead.
201    V2ipFpgaFeature: u64 {
202        /// Applies a DSCP marking to the streams it sources.
203        SOURCE_DSCP = 1 << 0;
204        /// Reports the audio format arriving at its sink.
205        SINK_AUDIO_FORMAT = 1 << 1;
206        /// Places a tiling window on its sink.
207        SINK_TILING_WINDOW = 1 << 2;
208        /// Reports the state of its sink's overlay.
209        SINK_OVERLAY_STATE = 1 << 3;
210        /// Reports its sink's state.
211        SINK_STATE = 1 << 4;
212        /// Reports information about the stream its sink receives.
213        SINK_STREAM_INFO = 1 << 5;
214    }
215}
216
217bitmask! {
218    /// The device settings a V2IP configuration can carry, each on its own bit.
219    ///
220    /// The same bits serve as the settings a frame carries and as the values
221    /// of the on/off ones among them. `IR_PROFILE` to `POWER_SAVE_SCHEDULE`
222    /// carry a value elsewhere in the block and have no on/off value of their
223    /// own.
224    ///
225    /// A device reports every setting it has, so one it never reports is one
226    /// it does not have.
227    V2ipDeviceSetting {
228        /// Disables the decoder while the display is off.
229        SINK_CHECK_POWER = 1 << 0;
230        /// Disables the HDMI output while there is no signal.
231        SINK_OFF_NO_SIGNAL = 1 << 1;
232        /// Sends infrared modulated.
233        IR_TX_MODULATED = 1 << 2;
234        /// Lights the status LED.
235        STATUS_LED = 1 << 3;
236        /// Lights the network port LEDs.
237        NETWORK_LED = 1 << 4;
238        /// Runs the fan in quiet mode.
239        FAN_QUIET = 1 << 5;
240        /// Accepts CEC combo keys.
241        CEC_COMBO_KEYS = 1 << 6;
242        /// Accepts CEC combo keys for the device's own input.
243        CEC_COMBO_INPUT = 1 << 7;
244        /// The infrared profile of the device's global infrared port.
245        IR_PROFILE = 1 << 8;
246        /// The infrared profile of the output's infrared port.
247        IR_PROFILE_SINK = 1 << 9;
248        /// The infrared profiles stored on the device. Reported by the device
249        /// itself and never written.
250        IR_PROFILES = 1 << 10;
251        /// The minutes a device stays idle before it powers down by itself.
252        AUTO_POWER_SAVE = 1 << 11;
253        /// The daily windows in which the device powers down.
254        POWER_SAVE_SCHEDULE = 1 << 12;
255        /// The device's clock has been set, on/off. Reported by the device
256        /// itself and never written.
257        CLOCK_SET = 1 << 13;
258    }
259}
260
261impl V2ipDeviceSetting {
262    /// The settings that are on or off, as opposed to carrying a value, and
263    /// that can be written.
264    pub const SWITCHES: Self = Self(0xFF);
265
266    /// The settings only a device reports about itself, which no write
267    /// carries.
268    pub const REPORTED_ONLY: Self = Self(Self::IR_PROFILES.0 | Self::CLOCK_SET.0);
269
270    /// These bits with every bit of `other` cleared.
271    pub(crate) const fn without(self, other: Self) -> Self {
272        Self(self.0 & !other.0)
273    }
274}
275
276bitmask! {
277    /// Capabilities of a single bay.
278    BayFeatures {
279        /// HDMI output.
280        HDMI_OUT = 1 << 0;
281        /// HDMI input.
282        HDMI_IN = 1 << 1;
283        /// Digital audio output.
284        AUDIO_DIG_OUT = 1 << 2;
285        /// Digital audio input.
286        AUDIO_DIG_IN = 1 << 3;
287        /// Analogue audio output.
288        AUDIO_ANA_OUT = 1 << 4;
289        /// Analogue audio input.
290        AUDIO_ANA_IN = 1 << 5;
291        /// Infrared input.
292        IR_IN = 1 << 6;
293        /// Infrared output.
294        IR_OUT = 1 << 7;
295        /// Amplified audio output.
296        AUDIO_AMP_OUT = 1 << 8;
297        /// Remote-control output.
298        RC_OUT = 1 << 9;
299        /// Remote-control input.
300        RC_IN = 1 << 10;
301        /// Dolby decoding.
302        DOLBY = 1 << 11;
303        /// Switches itself off when idle.
304        AUTO_OFF = 1 << 12;
305        /// Is a remote V2IP source.
306        V2IP_SOURCE_REMOTE = 1 << 13;
307        /// Is a remote V2IP sink.
308        V2IP_SINK_REMOTE = 1 << 14;
309        /// Is a local V2IP source.
310        V2IP_SOURCE_LOCAL = 1 << 15;
311        /// Is a local V2IP sink.
312        V2IP_SINK_LOCAL = 1 << 16;
313    }
314}
315
316bitmask! {
317    /// Live status flags of a single bay.
318    ///
319    /// Bits 16-19 and 22-23 are bit-fields rather than flags; read them with
320    /// [`BayStatus::rc_type`] and [`BayStatus::hdcp`].
321    BayStatus {
322        /// The bay reports a fault.
323        FAULT = 1 << 0;
324        /// The bay is hidden from the user interface.
325        HIDDEN = 1 << 1;
326        /// The bay has power.
327        POWERED = 1 << 2;
328        /// A signal is present.
329        SIGNAL_DETECTED = 1 << 3;
330        /// Hot-plug detect is asserted.
331        HPD_DETECTED = 1 << 4;
332        /// The signal is scrambled.
333        SIGNAL_SCRAMBLE = 1 << 5;
334        /// An HDBaseT link is up.
335        HDBT_CONNECTED = 1 << 6;
336        /// A CEC device answered.
337        CEC_DETECTED = 1 << 7;
338        /// The attached device was powered on.
339        POWERED_ON = 1 << 8;
340        /// The attached device was powered off.
341        POWERED_OFF = 1 << 9;
342        /// Audio return over HDMI is active.
343        AUDIO_ARC_HDMI = 1 << 10;
344        /// Audio return over optical is active.
345        AUDIO_ARC_OPTIC = 1 << 11;
346        /// Audio return over analogue is active.
347        AUDIO_ARC_ANALOG = 1 << 12;
348        /// The bay is offline.
349        OFFLINE = 1 << 13;
350        /// The V2IP decoder is disabled.
351        DECODER_DISABLE = 1 << 14;
352        /// The V2IP encoder is disabled.
353        ENCODER_DISABLE = 1 << 15;
354        /// CEC is switched off for this bay.
355        CEC_DISABLED = 1 << 20;
356        /// The V2IP encoder reports an error.
357        ENCODER_ERROR = 1 << 21;
358    }
359}
360
361impl BayStatus {
362    const RC_TYPE_SHIFT: u32 = 16;
363    const RC_TYPE_MASK: u32 = 0xF << Self::RC_TYPE_SHIFT;
364    const HDCP_SHIFT: u32 = 22;
365    const HDCP_MASK: u32 = 0x3 << Self::HDCP_SHIFT;
366
367    /// Extracts the remote-control type carried in bits 16-19.
368    pub const fn rc_type(self) -> RcType {
369        RcType(((self.0 & Self::RC_TYPE_MASK) >> Self::RC_TYPE_SHIFT) as u8)
370    }
371
372    /// Extracts the HDCP version carried in bits 22-23.
373    pub const fn hdcp(self) -> u8 {
374        ((self.0 & Self::HDCP_MASK) >> Self::HDCP_SHIFT) as u8
375    }
376}
377
378bitmask! {
379    /// Media carried by a virtual link.
380    LinkFeature {
381        /// Video over HDMI.
382        VIDEO_HDMI = 1 << 0;
383        /// Audio over optical.
384        AUDIO_OPTICAL = 1 << 1;
385        /// Audio over analogue.
386        AUDIO_ANALOG = 1 << 2;
387        /// Infrared.
388        IR = 1 << 3;
389        /// Remote control.
390        RC = 1 << 4;
391    }
392}
393
394wire_enum! {
395    /// A remote-control action.
396    RcAction: u16 {
397        /// Toggle power.
398        POWER_TOGGLE = 0;
399        /// Power on.
400        POWER_ON = 1;
401        /// Power off.
402        POWER_OFF = 2;
403        /// Volume down.
404        VOLUME_DOWN = 3;
405        /// Volume up.
406        VOLUME_UP = 4;
407        /// Toggle mute.
408        VOLUME_MUTE = 5;
409    }
410}
411
412wire_enum! {
413    /// A remote-control key code (CEC or IR).
414    RcKey: u16 {
415        /// Digit 0.
416        NUM0 = 0;
417        /// Digit 1.
418        NUM1 = 1;
419        /// Digit 2.
420        NUM2 = 2;
421        /// Digit 3.
422        NUM3 = 3;
423        /// Digit 4.
424        NUM4 = 4;
425        /// Digit 5.
426        NUM5 = 5;
427        /// Digit 6.
428        NUM6 = 6;
429        /// Digit 7.
430        NUM7 = 7;
431        /// Digit 8.
432        NUM8 = 8;
433        /// Digit 9.
434        NUM9 = 9;
435        /// Confirm the highlighted item.
436        SELECT = 10;
437        /// Go back one step.
438        BACK = 11;
439        /// Navigate up.
440        UP = 12;
441        /// Navigate down.
442        DOWN = 13;
443        /// Navigate left.
444        LEFT = 14;
445        /// Navigate right.
446        RIGHT = 15;
447        /// Open the main menu.
448        MENU = 16;
449        /// Open the content menu.
450        CONTENT_MENU = 17;
451        /// Next channel.
452        CHANNEL_UP = 18;
453        /// Previous channel.
454        CHANNEL_DOWN = 19;
455        /// Start playback.
456        PLAY = 20;
457        /// Pause playback.
458        PAUSE = 21;
459        /// Stop playback.
460        STOP = 22;
461        /// Start recording.
462        RECORD = 23;
463        /// Fast forward.
464        FAST_FORWARD = 24;
465        /// Rewind.
466        REWIND = 25;
467        /// Red colour key.
468        RED = 26;
469        /// Green colour key.
470        GREEN = 27;
471        /// Yellow colour key.
472        YELLOW = 28;
473        /// Blue colour key.
474        BLUE = 29;
475        /// Open help.
476        HELP = 30;
477        /// Show information.
478        INFORMATION = 31;
479        /// Open teletext.
480        TEXT = 32;
481        /// Open the programme guide.
482        GUIDE = 33;
483        /// Open video on demand.
484        VIDEO_ON_DEMAND = 34;
485        /// Return to the previous channel.
486        PREVIOUS_CHANNEL = 80;
487        /// Toggle 3D mode.
488        MODE_3D = 81;
489        /// Toggle subtitles.
490        SUBTITLE = 82;
491        /// Select an audio track.
492        SOUND_SELECT = 83;
493        /// Select an input.
494        INPUT_SELECT = 84;
495        /// Eject the medium.
496        EJECT = 85;
497        /// Next chapter.
498        NEXT_CHAPTER = 86;
499        /// Previous chapter.
500        PREV_CHAPTER = 87;
501        /// Open interactive services.
502        INTERACTIVE = 128;
503        /// Open search.
504        SEARCH = 129;
505        /// Sky home key.
506        SKY = 130;
507        /// Base of the range carrying a raw CEC user-control code.
508        CUSTOM_CEC = 1280;
509        /// Base of the range carrying a raw Sky key code.
510        CUSTOM_SKY = 2048;
511    }
512}
513
514wire_enum! {
515    /// The remote-control protocol of a connected sink or source.
516    RcType: u8 {
517        /// Infrared.
518        IR = 0;
519        /// HDMI CEC.
520        CEC = 1;
521        /// Sky UK over IP.
522        SKY_UK = 2;
523        /// TiVo.
524        TIVO = 3;
525        /// Kodi.
526        KODI = 4;
527        /// Dish.
528        DISH = 5;
529        /// DirecTV.
530        DIRECTV = 6;
531        /// Another MX Remote device.
532        MX_REMOTE = 7;
533    }
534}
535
536impl fmt::Display for RcType {
537    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
538        let name = match *self {
539            Self::IR => "IR",
540            Self::CEC => "CEC",
541            Self::SKY_UK => "Sky",
542            Self::TIVO => "TiVo",
543            Self::KODI => "Kodi",
544            Self::DISH => "Dish",
545            Self::DIRECTV => "DirecTV",
546            Self::MX_REMOTE => "MX-Remote",
547            _ => "Unknown",
548        };
549        f.write_str(name)
550    }
551}
552
553wire_enum! {
554    /// An EDID preset selectable on an HDMI input.
555    EdidProfile: u16 {
556        /// 1080p with stereo audio.
557        STEREO_1080P = 0;
558        /// A fixed EDID stored on the device.
559        FIXED = 1;
560        /// 4K.
561        UHD_4K = 2;
562        /// 1080p with 5.1 audio.
563        SURROUND51_1080P = 3;
564        /// 720p.
565        HD_720P = 4;
566        /// 1080p with 7.1 audio.
567        SURROUND71_1080P = 5;
568        /// 4K with 7.1 audio.
569        SURROUND71_4K = 6;
570        /// 4K HDR with stereo audio.
571        HDR_STEREO_4K = 7;
572        /// 4K HDR with 7.1 audio.
573        HDR_SURROUND71_4K = 8;
574        /// 4K HDR, audio to the AVR only.
575        HDR_AVR_ONLY_4K = 9;
576        /// The lowest common denominator of the connected sinks.
577        LOWEST_COMMON = 10;
578        /// The lowest common denominator of every sink, connected or not.
579        LOWEST_COMMON_ALL = 11;
580        /// 4K HDR with Dolby Atmos.
581        HDR_ATMOS_4K = 12;
582        /// Copy the EDID of sink 1; the range runs to [`EdidProfile::SINK_32`].
583        SINK_1 = 101;
584        /// Copy the EDID of sink 32; the range starts at [`EdidProfile::SINK_1`].
585        SINK_32 = 132;
586        /// Base of the range carrying a user-supplied EDID.
587        CUSTOM_0 = 500;
588        /// The device reports no profile.
589        UNKNOWN = 0xFFF;
590    }
591}
592
593impl fmt::Display for EdidProfile {
594    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
595        let name = match *self {
596            Self::STEREO_1080P => "1080p stereo",
597            Self::FIXED => "fixed",
598            Self::UHD_4K => "4K",
599            Self::SURROUND51_1080P => "1080p 5.1",
600            Self::HD_720P => "720p",
601            Self::SURROUND71_1080P => "1080p 7.1",
602            Self::SURROUND71_4K => "4K 7.1",
603            Self::HDR_STEREO_4K => "4K HDR Stereo",
604            Self::HDR_SURROUND71_4K => "4K HDR 7.1",
605            Self::HDR_AVR_ONLY_4K => "4K HDR AVR",
606            Self::LOWEST_COMMON => "lowest common denominator",
607            Self::LOWEST_COMMON_ALL => "lowest common denominator (all sinks)",
608            Self::HDR_ATMOS_4K => "4K HDR Dolby Atmos",
609            _ => {
610                if self.0 >= Self::SINK_1.0 && self.0 <= Self::SINK_32.0 {
611                    return write!(f, "copy from sink #{}", self.0 - Self::SINK_1.0 + 1);
612                }
613                return write!(f, "custom #{}", self.0);
614            }
615        };
616        f.write_str(name)
617    }
618}
619
620wire_enum! {
621    /// A firmware component.
622    FirmwareType: u8 {
623        /// The component is not known.
624        UNKNOWN = 0;
625        /// The FPGA bitstream.
626        FPGA = 1;
627        /// The Linux system image.
628        LINUX = 2;
629        /// A loadable overlay.
630        LOADING_OVERLAY = 3;
631    }
632}
633
634impl fmt::Display for FirmwareType {
635    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
636        let name = match *self {
637            Self::FPGA => "FPGA",
638            Self::LINUX => "Linux",
639            Self::LOADING_OVERLAY => "Loading Overlay",
640            _ => "Unknown",
641        };
642        f.write_str(name)
643    }
644}
645
646wire_enum! {
647    /// The negotiated speed of a network port.
648    UtpLinkSpeed: u8 {
649        /// The device reports no speed.
650        UNKNOWN = 0;
651        /// 10Mbit/s.
652        SPEED_10M = 1;
653        /// 100Mbit/s.
654        SPEED_100M = 2;
655        /// 200Mbit/s.
656        SPEED_200M = 3;
657        /// 1Gbit/s.
658        SPEED_1G = 4;
659    }
660}
661
662impl fmt::Display for UtpLinkSpeed {
663    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
664        let name = match *self {
665            Self::SPEED_10M => "10Mbit/s",
666            Self::SPEED_100M => "100Mbit/s",
667            Self::SPEED_200M => "200Mbit/s",
668            Self::SPEED_1G => "1Gbit/s",
669            _ => "Unknown",
670        };
671        f.write_str(name)
672    }
673}
674
675wire_enum! {
676    /// The window layout of a multiviewer.
677    MultiviewerViewMode: u8 {
678        /// The device reports no layout.
679        UNKNOWN = 0;
680        /// One full-screen window.
681        SINGLE = 1;
682        /// Picture in picture.
683        PIP = 2;
684        /// Two windows, large.
685        TWO_SCREEN_LARGE = 3;
686        /// Two windows, small.
687        TWO_SCREEN_SMALL = 4;
688        /// Three windows, large.
689        THREE_SCREEN_LARGE = 5;
690        /// Three windows, small.
691        THREE_SCREEN_SMALL = 6;
692        /// Four windows, equal size.
693        FOUR_SCREEN_EQUAL = 7;
694        /// Four windows, small.
695        FOUR_SCREEN_SMALL = 8;
696    }
697}
698
699wire_enum! {
700    /// The corner a multiviewer places its picture-in-picture window in.
701    MultiviewerPipPosition: u8 {
702        /// The device reports no position.
703        UNKNOWN = 0;
704        /// Top left.
705        LEFT_TOP = 1;
706        /// Bottom left.
707        LEFT_BOTTOM = 2;
708        /// Top right.
709        RIGHT_TOP = 3;
710        /// Bottom right.
711        RIGHT_BOTTOM = 4;
712    }
713}
714
715wire_enum! {
716    /// The size of a multiviewer's picture-in-picture window.
717    MultiviewerPipSize: u8 {
718        /// The device reports no size.
719        UNKNOWN = 0;
720        /// Small.
721        SMALL = 1;
722        /// Medium.
723        MEDIUM = 2;
724        /// Large.
725        LARGE = 3;
726    }
727}
728
729wire_enum! {
730    /// The resolution and refresh rate a multiviewer drives its output at.
731    MultiviewerOutputMode: u8 {
732        /// The device reports no output mode.
733        UNKNOWN = 0;
734        /// 4096x2160p60.
735        DCI4K_P60 = 1;
736        /// 4096x2160p50.
737        DCI4K_P50 = 2;
738        /// 3840x2160p60.
739        UHD_P60 = 3;
740        /// 3840x2160p50.
741        UHD_P50 = 4;
742        /// 3840x2160p30.
743        UHD_P30 = 5;
744        /// 3840x2160p25.
745        UHD_P25 = 6;
746        /// 1920x1200p60, reduced blanking.
747        WUXGA_P60_RB = 7;
748        /// 1920x1080p60.
749        HD1080_P60 = 8;
750        /// 1920x1080p50.
751        HD1080_P50 = 9;
752        /// 1360x768p60.
753        WXGA_P60 = 10;
754        /// 1280x800p60.
755        WXGA800_P60 = 11;
756        /// 1280x720p60.
757        HD720_P60 = 12;
758        /// 1280x720p50.
759        HD720_P50 = 13;
760        /// 1024x768p60.
761        XGA_P60 = 14;
762    }
763}
764
765wire_enum! {
766    /// The HDCP version a multiviewer output negotiates.
767    MultiviewerHdcpMode: u8 {
768        /// The device reports no HDCP mode.
769        UNKNOWN = 0;
770        /// HDCP 1.4.
771        V14 = 1;
772        /// HDCP 2.2.
773        V22 = 2;
774        /// Content protection off.
775        OFF = 3;
776    }
777}
778
779wire_enum! {
780    /// The IT-content flag a multiviewer sets on its output.
781    MultiviewerItcMode: u8 {
782        /// The device reports no IT-content mode.
783        UNKNOWN = 0;
784        /// Video content.
785        VIDEO = 1;
786        /// PC content.
787        PC = 2;
788    }
789}
790
791wire_enum! {
792    /// The EDID template a multiviewer presents to its sources.
793    ///
794    /// A template's name is the largest resolution it advertises and the audio
795    /// format it declares support for.
796    MultiviewerEdidTemplate: u8 {
797        /// The device reports no template.
798        EDID_UNKNOWN = 0;
799        /// 4K2K60 4:4:4, stereo 2.0.
800        EDID_4K2K60_444_STEREO = 1;
801        /// 4K2K60 4:4:4, Dolby/DTS 5.1.
802        EDID_4K2K60_444_DOLBY_DTS_51 = 2;
803        /// 4K2K60 4:4:4, HD audio 7.1.
804        EDID_4K2K60_444_HD_AUDIO_71 = 3;
805        /// 4K2K30 4:4:4, stereo 2.0.
806        EDID_4K2K30_444_STEREO = 4;
807        /// 4K2K30 4:4:4, Dolby/DTS 5.1.
808        EDID_4K2K30_444_DOLBY_DTS_51 = 5;
809        /// 4K2K30 4:4:4, HD audio 7.1.
810        EDID_4K2K30_444_HD_AUDIO_71 = 6;
811        /// 1080p, stereo 2.0.
812        EDID_1080P_STEREO = 7;
813        /// 1080p, Dolby/DTS 5.1.
814        EDID_1080P_DOLBY_DTS_51 = 8;
815        /// 1080p, HD audio 7.1.
816        EDID_1080P_HD_AUDIO_71 = 9;
817        /// 1920x1200, stereo 2.0.
818        EDID_1920X1200_STEREO = 10;
819        /// 1680x1050, stereo 2.0.
820        EDID_1680X1050_STEREO = 11;
821        /// 1600x1200, stereo 2.0.
822        EDID_1600X1200_STEREO = 12;
823        /// 1440x900, stereo 2.0.
824        EDID_1440X900_STEREO = 13;
825        /// 1360x768, stereo 2.0.
826        EDID_1360X768_STEREO = 14;
827        /// 1280x1024, stereo 2.0.
828        EDID_1280X1024_STEREO = 15;
829        /// 1024x768, stereo 2.0.
830        EDID_1024X768_STEREO = 16;
831        /// 720p, stereo 2.0.
832        EDID_720P_STEREO = 17;
833        /// Whatever the display connected to the HDMI output presents. The
834        /// template a multiviewer leaves the factory with.
835        EDID_COPY_OUTPUT = 18;
836        /// The EDID loaded onto the device.
837        EDID_CUSTOM = 19;
838    }
839}
840
841wire_enum! {
842    /// The aspect ratio a multiviewer scales its windows to.
843    MultiviewerAspectRatio: u8 {
844        /// The device reports no aspect ratio.
845        UNKNOWN = 0;
846        /// Fill the window.
847        FULL = 1;
848        /// 16:9.
849        RATIO_16_9 = 2;
850    }
851}
852
853wire_enum! {
854    /// A multiviewer setting that is on, off, or not reported.
855    MultiviewerBool: u8 {
856        /// Off.
857        OFF = 0;
858        /// On.
859        ON = 1;
860        /// The device reports no value.
861        UNKNOWN = 0xFF;
862    }
863}
864
865wire_enum! {
866    /// One of a multiviewer's four inputs.
867    ///
868    /// The wire numbers the inputs from zero and this type from one, so that
869    /// zero can mean "not reported" the way it does for every other
870    /// multiviewer setting. So `to_wire` and `from_wire` carry this type's
871    /// numbering rather than the wire's, and neither is the conversion to
872    /// reach for when a raw multiviewer byte is what is in hand.
873    MultiviewerSource: u8 {
874        /// The device reports no source.
875        UNKNOWN = 0;
876        /// Input 1.
877        INPUT_1 = 1;
878        /// Input 2.
879        INPUT_2 = 2;
880        /// Input 3.
881        INPUT_3 = 3;
882        /// Input 4.
883        INPUT_4 = 4;
884    }
885}
886
887impl MultiviewerSource {
888    /// Reads a zero-based wire value, mapping anything past input 4 to
889    /// [`MultiviewerSource::UNKNOWN`].
890    ///
891    /// The firmware spells "not known" as 0xFF, which lands past input 4 and
892    /// so needs no case of its own.
893    pub(crate) const fn from_zero_based(value: u8) -> Self {
894        if value > 3 {
895            Self::UNKNOWN
896        } else {
897            Self(value + 1)
898        }
899    }
900
901    /// The zero-based value the wire carries, or `None` for a source naming no
902    /// input.
903    ///
904    /// A multiviewer reads zero as its first input, so there is no value that
905    /// says "leave this alone": a request that cannot name an input has to be
906    /// refused rather than sent.
907    pub(crate) const fn to_zero_based(self) -> Option<u8> {
908        match self.0 {
909            1..=4 => Some(self.0 - 1),
910            _ => None,
911        }
912    }
913}
914
915impl MultiviewerBool {
916    /// Reads a wire value, mapping anything but 0 and 1 to
917    /// [`MultiviewerBool::UNKNOWN`].
918    pub(crate) const fn from_wire_tristate(value: u8) -> Self {
919        if value > 1 {
920            Self::UNKNOWN
921        } else {
922            Self(value)
923        }
924    }
925}
926
927wire_enum! {
928    /// The colour space a V2IP output scales to.
929    ///
930    /// The field is four bits wide and only these four values are defined. A
931    /// receiver passes the whole nibble to its validator, so a fifth value is
932    /// dropped without a word rather than clamped to one of these.
933    V2ipColourSpace: u8 {
934        /// RGB.
935        RGB = 0;
936        /// YCbCr 4:4:4.
937        YCBCR444 = 1;
938        /// YCbCr 4:2:2.
939        YCBCR422 = 2;
940        /// YCbCr 4:2:0.
941        YCBCR420 = 3;
942    }
943}
944
945wire_enum! {
946    /// The 2-byte `mxr_signal_type` carried in scaling configs and bay signal
947    /// reports.
948    ///
949    /// Byte 0 is the CTA-861 short video descriptor, 0 when the signal is not
950    /// HDMI. Byte 1 packs `color:4` in the low nibble, then `non_int:1` and
951    /// `bpp:3` above it.
952    MxrSignalType: u16 {
953        /// No signal format was reported.
954        NONE = 0;
955    }
956}
957
958/// The bpp index a sender writes when it has no bit depth to report.
959///
960/// It sits outside the four indices that name a real depth, so an unset
961/// format reads differently from every genuine one.
962const SIG_BPP_UNSET: u16 = 5;
963
964impl MxrSignalType {
965    /// The CTA-861 short video descriptor, 0 when the signal is not HDMI.
966    pub const fn svd(self) -> u8 {
967        (self.0 & 0xFF) as u8
968    }
969
970    /// The colour space.
971    pub const fn colour_space(self) -> u8 {
972        ((self.0 >> 8) & 0xF) as u8
973    }
974
975    /// Whether the frame rate carries a 1000/1001 clock.
976    pub const fn is_non_integer(self) -> bool {
977        self.0 & (1 << 12) != 0
978    }
979
980    /// The raw bpp index as carried on the wire. The field is an index, not a
981    /// bit depth; [`MxrSignalType::bpp`] converts it.
982    pub const fn bpp_index(self) -> u8 {
983        ((self.0 >> 13) & 0x7) as u8
984    }
985
986    /// The bit depth the bpp index stands for, `None` when unknown or unset.
987    pub const fn bpp(self) -> Option<u8> {
988        match self.bpp_index() {
989            1 => Some(8),
990            2 => Some(10),
991            3 => Some(12),
992            4 => Some(16),
993            _ => None,
994        }
995    }
996
997    /// Reports whether the word carries a signal format at all.
998    ///
999    /// A bay with nothing configured says so two ways. A sender that zeroes
1000    /// the word and stamps the unset bpp index leaves an index no real depth
1001    /// uses, and one that writes a plain zero leaves nothing at all. Neither
1002    /// is a format, and the svd and colour space beside them are not answers
1003    /// either: both read as zero, which is what this word says for "not HDMI"
1004    /// and "RGB" when it *is* set.
1005    pub const fn is_set(self) -> bool {
1006        self.0 != 0 && self.bpp_index() as u16 != SIG_BPP_UNSET
1007    }
1008
1009    /// Builds the word from the fields a scaling write consumes.
1010    ///
1011    /// `non_int` is left clear: the receiving struct carries the bit, and the
1012    /// apply path does not read it.
1013    ///
1014    /// Building rather than editing is the point. A sink with no mode
1015    /// configured reports the word with the unset bpp index in it, so a caller
1016    /// that read that word back and filled in an svd would send an index no
1017    /// depth uses - which the receiver decodes to zero and rejects without
1018    /// answering.
1019    pub(crate) const fn from_parts(svd: u8, colour: u8, bpp_index: u8) -> Self {
1020        Self((svd as u16) | (((colour & 0xF) as u16) << 8) | (((bpp_index & 0x7) as u16) << 13))
1021    }
1022
1023    /// The bpp index that stands for a bit depth, `None` for a depth no index
1024    /// names.
1025    ///
1026    /// Only the three depths a V2IP output stage accepts are here. Index 4
1027    /// names 16bpp, which [`MxrSignalType::bpp`] reads back from a device, but
1028    /// the output stage refuses it - so offering it as something to write would
1029    /// send a frame that is decoded cleanly and then dropped in silence.
1030    pub(crate) const fn bpp_index_for_depth(depth: u8) -> Option<u8> {
1031        match depth {
1032            8 => Some(1),
1033            10 => Some(2),
1034            12 => Some(3),
1035            _ => None,
1036        }
1037    }
1038}
1039
1040impl fmt::Display for MxrSignalType {
1041    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1042        if !self.is_set() {
1043            return f.write_str("unset");
1044        }
1045        match self.bpp() {
1046            Some(bpp) => write!(
1047                f,
1048                "svd {}, color {}, {}bpp",
1049                self.svd(),
1050                self.colour_space(),
1051                bpp
1052            ),
1053            None => write!(f, "svd {}, color {}", self.svd(), self.colour_space()),
1054        }
1055    }
1056}