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        /// Receives one or more peers only by broadcast, because its multicast
183        /// receive path is faulty.
184        STATUS_MCAST_FAULT = 1 << 26;
185        /// Powers its video processor down in power save. A device without it
186        /// refuses to enter power save.
187        POWER_SAVE = 1 << 27;
188        /// Tags its uplink by the VLAN configuration it reports, and takes one
189        /// written to it.
190        VLAN = 1 << 28;
191        /// Copies the overlay settings and logos of another unit when asked.
192        OSD_SYNC = 1 << 29;
193        /// Is downloading or installing a firmware or video processor
194        /// upgrade. [`STATUS_REBOOT`](Self::STATUS_REBOOT) follows when it
195        /// reboots to complete it.
196        STATUS_UPGRADING = 1 << 30;
197        /// Flips on every reboot, so a peer that sees it change knows the device restarted.
198        BOOT_BIT = 1 << 31;
199    }
200}
201
202bitmask! {
203    /// What a V2IP device's video processor supports, as the device reports it
204    /// in its configuration.
205    ///
206    /// Read-only, and a device's own: it fills the field in only on the frame
207    /// describing itself, and leaves it zero on one it sends to configure
208    /// another device. There is no write path.
209    ///
210    /// Bits are assigned by the video processor and only ever appended, so a
211    /// bit this library has no name for is a later capability rather than an
212    /// error. A device reports no features at all until its processor answers,
213    /// and an older processor answers with none of the optional commands, so an
214    /// empty mask is never reported as a capability set - see
215    /// [`crate::Remote::v2ip_features`], which reports it as unknown instead.
216    V2ipFpgaFeature: u64 {
217        /// Applies a DSCP marking to the streams it sources.
218        SOURCE_DSCP = 1 << 0;
219        /// Reports the audio format arriving at its sink.
220        SINK_AUDIO_FORMAT = 1 << 1;
221        /// Places a tiling window on its sink.
222        SINK_TILING_WINDOW = 1 << 2;
223        /// Reports the state of its sink's overlay.
224        SINK_OVERLAY_STATE = 1 << 3;
225        /// Reports its sink's state.
226        SINK_STATE = 1 << 4;
227        /// Reports information about the stream its sink receives.
228        SINK_STREAM_INFO = 1 << 5;
229        /// Draws a test pattern, tone and lip-sync flash on its sink's output.
230        SINK_TEST_PATTERN = 1 << 8;
231    }
232}
233
234bitmask! {
235    /// The device settings a V2IP configuration can carry, each on its own bit.
236    ///
237    /// The same bits serve as the settings a frame carries and as the values
238    /// of the on/off ones among them. `IR_PROFILE` to `POWER_SAVE_SCHEDULE`
239    /// carry a value elsewhere in the block and have no on/off value of their
240    /// own.
241    ///
242    /// A device reports every setting it has, so one it never reports is one
243    /// it does not have.
244    V2ipDeviceSetting {
245        /// Disables the decoder while the display is off.
246        SINK_CHECK_POWER = 1 << 0;
247        /// Disables the HDMI output while there is no signal.
248        SINK_OFF_NO_SIGNAL = 1 << 1;
249        /// Sends infrared modulated.
250        IR_TX_MODULATED = 1 << 2;
251        /// Lights the status LED.
252        STATUS_LED = 1 << 3;
253        /// Lights the network port LEDs.
254        NETWORK_LED = 1 << 4;
255        /// Runs the fan in quiet mode.
256        FAN_QUIET = 1 << 5;
257        /// Accepts CEC combo keys.
258        CEC_COMBO_KEYS = 1 << 6;
259        /// Accepts CEC combo keys for the device's own input.
260        CEC_COMBO_INPUT = 1 << 7;
261        /// The infrared profile of the device's global infrared port.
262        IR_PROFILE = 1 << 8;
263        /// The infrared profile of the output's infrared port.
264        IR_PROFILE_SINK = 1 << 9;
265        /// The infrared profiles stored on the device. Reported by the device
266        /// itself and never written.
267        IR_PROFILES = 1 << 10;
268        /// The minutes a device stays idle before it powers down by itself.
269        AUTO_POWER_SAVE = 1 << 11;
270        /// The daily windows in which the device powers down.
271        POWER_SAVE_SCHEDULE = 1 << 12;
272        /// The device's clock has been set, on/off. Reported by the device
273        /// itself and never written.
274        CLOCK_SET = 1 << 13;
275        /// The PTP mode the device runs.
276        PTP_MODE = 1 << 14;
277        /// The PTP domain the device runs in.
278        PTP_DOMAIN = 1 << 15;
279        /// The priority1 the device announces as a PTP grandmaster.
280        PTP_PRIORITY1 = 1 << 16;
281        /// Sends the source's audio as an AES67 flow, where the source allows.
282        AES67 = 1 << 17;
283        /// The power save level the device powers down to.
284        POWER_SAVE_LEVEL = 1 << 18;
285        /// The interval between the device's PTP announce messages.
286        PTP_ANNOUNCE = 1 << 19;
287        /// The interval between the device's PTP sync messages.
288        PTP_SYNC = 1 << 20;
289        /// The interval between the device's PTP delay requests.
290        PTP_DELAY_REQ = 1 << 21;
291        /// The announce intervals before the device drops a silent PTP master.
292        PTP_ANNOUNCE_TIMEOUT = 1 << 22;
293        /// The DSCP of the device's PTP messages.
294        PTP_DSCP = 1 << 23;
295        /// The minutes after which the device forgets a SAP announcement it
296        /// heard.
297        SAP_EXPIRE = 1 << 24;
298        /// Listens for SAP announcements.
299        SAP_LISTEN = 1 << 25;
300        /// Sends SAP announcements.
301        SAP_ANNOUNCE = 1 << 26;
302        /// The seconds between the device's SAP announcements.
303        SAP_INTERVAL = 1 << 27;
304        /// Never powers down by itself: automatic power save and the power
305        /// save schedule leave the device running, and switching this on
306        /// wakes a device either has powered down. A power save request
307        /// still powers it down. Writing
308        /// [`POWER_SAVE_LEVEL`](Self::POWER_SAVE_LEVEL) switches it off.
309        POWER_SAVE_DISABLED = 1 << 28;
310        /// The TTL of the device's streams, video, audio and ancillary
311        /// alike. Reported only by a device with an encoder.
312        STREAM_TTL = 1 << 29;
313        /// Connects to the cloud service. A device that does not report it
314        /// connects regardless.
315        CLOUD = 1 << 30;
316    }
317}
318
319wire_enum! {
320    /// How far a V2IP device powers down in power save, each level saving more
321    /// than the one before.
322    V2ipPowerSaveLevel: u8 {
323        /// No level.
324        NONE = 0;
325        /// Part of the video processing powers down.
326        REDUCED = 1;
327        /// All of the video processing powers down.
328        LOW = 2;
329        /// Everything but the network powers down.
330        OFF = 3;
331    }
332}
333
334bitmask! {
335    /// What a unit offers at one power save level.
336    UnitPowerSave: u8 {
337        /// The unit can power down to this level.
338        SUPPORTED = 1 << 0;
339        /// A signal on the HDMI input wakes the unit.
340        INPUT_WAKE = 1 << 1;
341        /// The display powering on wakes the unit, and the unit can power it
342        /// on.
343        OUTPUT_WAKE = 1 << 2;
344    }
345}
346
347wire_enum! {
348    /// How a V2IP device takes part in PTP.
349    V2ipPtpMode: u8 {
350        /// No PTP.
351        OFF = 0;
352        /// Follows a grandmaster, and never becomes one.
353        FOLLOWER = 1;
354        /// Follows a grandmaster, or becomes one when there is none.
355        AUTO = 2;
356    }
357}
358
359wire_enum! {
360    /// The test pattern a V2IP sink draws on its output.
361    V2ipTestPattern: u8 {
362        /// No pattern.
363        OFF = 0;
364        /// Colour bars.
365        BARS = 1;
366        /// One flat colour, the one the test card carries.
367        FLAT = 2;
368        /// A ramp.
369        RAMP = 3;
370        /// A grid.
371        GRID = 4;
372        /// A strip.
373        STRIP = 5;
374        /// A test card.
375        CARD = 6;
376    }
377}
378
379wire_enum! {
380    /// The test tone a V2IP sink plays on its output.
381    V2ipToneMode: u8 {
382        /// No tone.
383        OFF = 0;
384        /// A continuous tone.
385        CONTINUOUS = 1;
386        /// A channel ident.
387        IDENT = 2;
388        /// A line-up tone, which needs two channels or more.
389        LINEUP = 3;
390        /// A beep on each lip-sync mark.
391        BEEP = 4;
392    }
393}
394
395bitmask! {
396    /// What a V2IP sink reports about its test card.
397    V2ipTestcardFlag: u8 {
398        /// The sink can draw the test card.
399        SUPPORTED = 1 << 0;
400        /// The output shows the pattern.
401        SHOWING = 1 << 1;
402        /// The tone plays on the output.
403        PLAYING = 1 << 2;
404        /// A pattern change has yet to reach the video processor.
405        PATTERN_PENDING = 1 << 3;
406        /// A tone change has yet to reach the video processor.
407        TONE_PENDING = 1 << 4;
408        /// A lip-sync change has yet to reach the video processor.
409        SYNC_PENDING = 1 << 5;
410    }
411}
412
413bitmask! {
414    /// The flags of what a V2IP output plays from a session description.
415    V2ipSdpFlag: u8 {
416        /// The output plays a stream from a description.
417        ACTIVE = 1 << 0;
418        /// And nothing else, as no video is routed to it.
419        AUDIO_ONLY = 1 << 1;
420        /// The clock mode and state hold the video processor's answer.
421        CLOCK = 1 << 2;
422    }
423}
424
425wire_enum! {
426    /// The sample encoding of a stream a V2IP output plays from a description.
427    V2ipSdpEncoding: u8 {
428        /// None.
429        NONE = 0;
430        /// 16-bit linear PCM.
431        L16 = 1;
432        /// 24-bit linear PCM.
433        L24 = 2;
434        /// AM824.
435        AM824 = 3;
436    }
437}
438
439wire_enum! {
440    /// Where a described stream's media clock comes from.
441    V2ipSdpMediaClock: u8 {
442        /// None given.
443        NONE = 0;
444        /// Direct, at an offset from the reference clock.
445        DIRECT = 1;
446        /// The sender's.
447        SENDER = 2;
448    }
449}
450
451wire_enum! {
452    /// The reference clock a described stream names.
453    V2ipSdpRefClock: u8 {
454        /// None given.
455        NONE = 0;
456        /// PTP.
457        PTP = 1;
458        /// A traceable clock.
459        TRACEABLE = 2;
460        /// The sender's local MAC.
461        LOCAL_MAC = 3;
462    }
463}
464
465wire_enum! {
466    /// What a V2IP output's video processor times a described stream by.
467    V2ipSdpClockMode: u8 {
468        /// Arrival.
469        ARRIVAL = 0;
470        /// PTP.
471        PTP = 1;
472        /// The sender.
473        SENDER = 2;
474    }
475}
476
477wire_enum! {
478    /// How far a V2IP output's video processor has got with a described
479    /// stream's timing.
480    V2ipSdpClockState: u8 {
481        /// Idle.
482        IDLE = 0;
483        /// Running its servo.
484        SERVO = 1;
485        /// Aligning.
486        ALIGNING = 2;
487        /// Timed.
488        TIMED = 3;
489        /// Parked.
490        PARKED = 4;
491        /// Playing untimed.
492        UNTIMED = 5;
493        /// The timing is not supported.
494        UNSUPPORTED = 6;
495    }
496}
497
498bitmask! {
499    /// The flags of a unit's status report.
500    UnitFlag {
501        /// A changed setting takes effect after a reboot.
502        REBOOT_REQUIRED = 1 << 0;
503        /// A newer video processor image is available.
504        FPGA_UPGRADE = 1 << 1;
505        /// The IP configuration comes from DHCP.
506        DHCP = 1 << 2;
507        /// The IP configuration was changed through the mesh, and is reverted
508        /// unless the mesh controller confirms it in time.
509        NET_PENDING = 1 << 3;
510        /// The fan runs in quiet mode.
511        FAN_QUIET = 1 << 4;
512        /// Powered by an external supply rather than PoE.
513        EXTERNAL_PSU = 1 << 5;
514        /// The video processor runs with its IP configured. Without it the
515        /// encoder and decoder health carry nothing.
516        FPGA_CONFIGURED = 1 << 6;
517        /// The encoder is enabled.
518        ENCODER_ACTIVE = 1 << 7;
519        /// The decoder is enabled.
520        DECODER_ACTIVE = 1 << 8;
521        /// The decoder disables itself without a display.
522        AUTO_DISABLE = 1 << 9;
523        /// The decoder disabled itself without a display.
524        AUTO_DISABLE_ACTIVE = 1 << 10;
525        /// The HDMI output switches off without a signal.
526        OFF_NO_SIGNAL = 1 << 11;
527        /// The HDMI output switched off without a signal.
528        OFF_NO_SIGNAL_ACTIVE = 1 << 12;
529        /// Display power is checked before the decoder is enabled.
530        POWER_CHECK = 1 << 13;
531        /// The boot count is reported.
532        BOOT_COUNT = 1 << 14;
533        /// The fan speed is reported.
534        FAN_SPEED = 1 << 15;
535    }
536}
537
538wire_enum! {
539    /// What a unit command asks of its unit.
540    UnitCommandKind: u8 {
541        /// Clear the current system status.
542        CLEAR_STATUS = 0;
543        /// Upgrade the video processor to the latest image.
544        FPGA_UPGRADE = 1;
545        /// Change the IP configuration.
546        NET_CONFIG = 2;
547        /// Keep a pending IP configuration.
548        NET_CONFIRM = 3;
549    }
550}
551
552bitmask! {
553    /// The flags of a unit command.
554    UnitCommandFlag: u8 {
555        /// With [`UnitCommandKind::FPGA_UPGRADE`]: flash the image even if it
556        /// is running already.
557        FORCE = 1 << 0;
558        /// With [`UnitCommandKind::NET_CONFIG`]: use DHCP, ignoring the
559        /// addresses.
560        DHCP = 1 << 1;
561    }
562}
563
564bitmask! {
565    /// The flags of a V2IP device's PTP state.
566    V2ipPtpFlag: u8 {
567        /// The device runs PTP and the block holds its state. A block without
568        /// it holds nothing: the device has no PTP, or is in power save.
569        VALID = 1 << 0;
570        /// The device locked to a master since its PTP started, so its mesh
571        /// holds a time.
572        FOLLOWED = 1 << 1;
573    }
574}
575
576wire_enum! {
577    /// How far a V2IP device's PTP clock has locked to its master.
578    V2ipPtpLock: u8 {
579        /// Not locked.
580        UNLOCKED = 0;
581        /// Locking.
582        LOCKING = 1;
583        /// Locked.
584        LOCKED = 2;
585    }
586}
587
588bitmask! {
589    /// The flags of a V2IP device's VLAN configuration.
590    V2ipVlanFlag: u16 {
591        /// The block carries a configuration. A block without it carries
592        /// nothing, whatever its other bytes hold.
593        VALID = 1 << 0;
594        /// Untagged frames arriving on the uplink are dropped instead of
595        /// reaching the device.
596        TRUNK = 1 << 1;
597        /// Reported by the device: the configuration is applied, and reverted
598        /// unless the mesh controller confirms it. Never written.
599        PENDING = 1 << 2;
600        /// Sent by the mesh controller: a pending configuration it heard, which
601        /// the device then keeps.
602        CONFIRM = 1 << 3;
603        /// Reported by the device: it has an SFP port. Never written.
604        HAS_SFP = 1 << 4;
605    }
606}
607
608impl V2ipDeviceSetting {
609    /// The settings that are on or off, as opposed to carrying a value, and
610    /// that can be written.
611    pub const SWITCHES: Self = Self(
612        0xFF | Self::AES67.0
613            | Self::SAP_LISTEN.0
614            | Self::SAP_ANNOUNCE.0
615            | Self::POWER_SAVE_DISABLED.0
616            | Self::CLOUD.0,
617    );
618
619    /// The settings only a device reports about itself, which no write
620    /// carries.
621    pub const REPORTED_ONLY: Self = Self(Self::IR_PROFILES.0 | Self::CLOCK_SET.0);
622
623    /// The PTP settings.
624    pub const PTP_SETTINGS: Self =
625        Self(Self::PTP_MODE.0 | Self::PTP_DOMAIN.0 | Self::PTP_PRIORITY1.0);
626
627    /// The PTP message settings.
628    pub const PTP_MESSAGES: Self = Self(
629        Self::PTP_ANNOUNCE.0
630            | Self::PTP_SYNC.0
631            | Self::PTP_DELAY_REQ.0
632            | Self::PTP_ANNOUNCE_TIMEOUT.0
633            | Self::PTP_DSCP.0,
634    );
635
636    /// The settings whose values a frame carries in a block of their own
637    /// behind the rest, [`V2ipPtpMessages`](crate::V2ipPtpMessages): the PTP
638    /// message settings, the two SAP values and the stream TTL.
639    pub const SETTINGS_BLOCK: Self =
640        Self(Self::PTP_MESSAGES.0 | Self::SAP_EXPIRE.0 | Self::SAP_INTERVAL.0 | Self::STREAM_TTL.0);
641
642    /// These bits with every bit of `other` cleared.
643    pub(crate) const fn without(self, other: Self) -> Self {
644        Self(self.0 & !other.0)
645    }
646}
647
648bitmask! {
649    /// Capabilities of a single bay.
650    BayFeatures {
651        /// HDMI output.
652        HDMI_OUT = 1 << 0;
653        /// HDMI input.
654        HDMI_IN = 1 << 1;
655        /// Digital audio output.
656        AUDIO_DIG_OUT = 1 << 2;
657        /// Digital audio input.
658        AUDIO_DIG_IN = 1 << 3;
659        /// Analogue audio output.
660        AUDIO_ANA_OUT = 1 << 4;
661        /// Analogue audio input.
662        AUDIO_ANA_IN = 1 << 5;
663        /// Infrared input.
664        IR_IN = 1 << 6;
665        /// Infrared output.
666        IR_OUT = 1 << 7;
667        /// Amplified audio output.
668        AUDIO_AMP_OUT = 1 << 8;
669        /// Remote-control output.
670        RC_OUT = 1 << 9;
671        /// Remote-control input.
672        RC_IN = 1 << 10;
673        /// Dolby decoding.
674        DOLBY = 1 << 11;
675        /// Switches itself off when idle.
676        AUTO_OFF = 1 << 12;
677        /// Is a remote V2IP source.
678        V2IP_SOURCE_REMOTE = 1 << 13;
679        /// Is a remote V2IP sink.
680        V2IP_SINK_REMOTE = 1 << 14;
681        /// Is a local V2IP source.
682        V2IP_SOURCE_LOCAL = 1 << 15;
683        /// Is a local V2IP sink.
684        V2IP_SINK_LOCAL = 1 << 16;
685    }
686}
687
688bitmask! {
689    /// Live status flags of a single bay.
690    ///
691    /// Bits 16-19 and 22-23 are bit-fields rather than flags; read them with
692    /// [`BayStatus::rc_type`] and [`BayStatus::hdcp`].
693    BayStatus {
694        /// The bay reports a fault.
695        FAULT = 1 << 0;
696        /// The bay is hidden from the user interface.
697        HIDDEN = 1 << 1;
698        /// The amplifier zone the bay drives is powered on.
699        POWERED = 1 << 2;
700        /// A signal is present.
701        SIGNAL_DETECTED = 1 << 3;
702        /// Hot-plug detect is asserted.
703        HPD_DETECTED = 1 << 4;
704        /// The signal is scrambled.
705        SIGNAL_SCRAMBLE = 1 << 5;
706        /// An HDBaseT link is up.
707        HDBT_CONNECTED = 1 << 6;
708        /// A CEC device answered.
709        CEC_DETECTED = 1 << 7;
710        /// The attached device was powered on.
711        POWERED_ON = 1 << 8;
712        /// The attached device was powered off.
713        POWERED_OFF = 1 << 9;
714        /// Audio return over HDMI is active.
715        AUDIO_ARC_HDMI = 1 << 10;
716        /// Audio return over optical is active.
717        AUDIO_ARC_OPTIC = 1 << 11;
718        /// Audio return over analogue is active.
719        AUDIO_ARC_ANALOG = 1 << 12;
720        /// The bay is offline.
721        OFFLINE = 1 << 13;
722        /// The V2IP decoder is disabled.
723        DECODER_DISABLE = 1 << 14;
724        /// The V2IP encoder is disabled.
725        ENCODER_DISABLE = 1 << 15;
726        /// CEC is switched off for this bay.
727        CEC_DISABLED = 1 << 20;
728        /// The V2IP encoder reports an error.
729        ENCODER_ERROR = 1 << 21;
730        /// The bay's name was generated - a default, or taken from CEC or the
731        /// EDID - rather than set by a user. A device that predates this bit
732        /// never sets it, so a clear bit does not prove a user set the name.
733        AUTO_NAME = 1 << 24;
734    }
735}
736
737impl BayStatus {
738    const RC_TYPE_SHIFT: u32 = 16;
739    const RC_TYPE_MASK: u32 = 0xF << Self::RC_TYPE_SHIFT;
740    const HDCP_SHIFT: u32 = 22;
741    const HDCP_MASK: u32 = 0x3 << Self::HDCP_SHIFT;
742
743    /// Extracts the remote-control type carried in bits 16-19.
744    pub const fn rc_type(self) -> RcType {
745        RcType(((self.0 & Self::RC_TYPE_MASK) >> Self::RC_TYPE_SHIFT) as u8)
746    }
747
748    /// Extracts the HDCP version carried in bits 22-23.
749    pub const fn hdcp(self) -> u8 {
750        ((self.0 & Self::HDCP_MASK) >> Self::HDCP_SHIFT) as u8
751    }
752}
753
754bitmask! {
755    /// Media carried by a virtual link.
756    LinkFeature {
757        /// Video over HDMI.
758        VIDEO_HDMI = 1 << 0;
759        /// Audio over optical.
760        AUDIO_OPTICAL = 1 << 1;
761        /// Audio over analogue.
762        AUDIO_ANALOG = 1 << 2;
763        /// Infrared.
764        IR = 1 << 3;
765        /// Remote control.
766        RC = 1 << 4;
767    }
768}
769
770wire_enum! {
771    /// A remote-control action.
772    RcAction: u16 {
773        /// Toggle power.
774        POWER_TOGGLE = 0;
775        /// Power on.
776        POWER_ON = 1;
777        /// Power off.
778        POWER_OFF = 2;
779        /// Volume up.
780        VOLUME_UP = 3;
781        /// Volume down.
782        VOLUME_DOWN = 4;
783        /// Toggle mute.
784        VOLUME_MUTE = 5;
785    }
786}
787
788wire_enum! {
789    /// A remote-control key code (CEC or IR).
790    RcKey: u16 {
791        /// Digit 0.
792        NUM0 = 0;
793        /// Digit 1.
794        NUM1 = 1;
795        /// Digit 2.
796        NUM2 = 2;
797        /// Digit 3.
798        NUM3 = 3;
799        /// Digit 4.
800        NUM4 = 4;
801        /// Digit 5.
802        NUM5 = 5;
803        /// Digit 6.
804        NUM6 = 6;
805        /// Digit 7.
806        NUM7 = 7;
807        /// Digit 8.
808        NUM8 = 8;
809        /// Digit 9.
810        NUM9 = 9;
811        /// Confirm the highlighted item.
812        SELECT = 10;
813        /// Go back one step.
814        BACK = 11;
815        /// Navigate up.
816        UP = 12;
817        /// Navigate down.
818        DOWN = 13;
819        /// Navigate left.
820        LEFT = 14;
821        /// Navigate right.
822        RIGHT = 15;
823        /// Open the main menu.
824        MENU = 16;
825        /// Open the content menu.
826        CONTENT_MENU = 17;
827        /// Next channel.
828        CHANNEL_UP = 18;
829        /// Previous channel.
830        CHANNEL_DOWN = 19;
831        /// Start playback.
832        PLAY = 20;
833        /// Pause playback.
834        PAUSE = 21;
835        /// Stop playback.
836        STOP = 22;
837        /// Start recording.
838        RECORD = 23;
839        /// Fast forward.
840        FAST_FORWARD = 24;
841        /// Rewind.
842        REWIND = 25;
843        /// Red colour key.
844        RED = 26;
845        /// Green colour key.
846        GREEN = 27;
847        /// Yellow colour key.
848        YELLOW = 28;
849        /// Blue colour key.
850        BLUE = 29;
851        /// Open help.
852        HELP = 30;
853        /// Show information.
854        INFORMATION = 31;
855        /// Open teletext.
856        TEXT = 32;
857        /// Open the programme guide.
858        GUIDE = 33;
859        /// Open video on demand.
860        VIDEO_ON_DEMAND = 34;
861        /// Return to the previous channel.
862        PREVIOUS_CHANNEL = 80;
863        /// Toggle 3D mode.
864        MODE_3D = 81;
865        /// Toggle subtitles.
866        SUBTITLE = 82;
867        /// Select an audio track.
868        SOUND_SELECT = 83;
869        /// Select an input.
870        INPUT_SELECT = 84;
871        /// Eject the medium.
872        EJECT = 85;
873        /// Next chapter.
874        NEXT_CHAPTER = 86;
875        /// Previous chapter.
876        PREV_CHAPTER = 87;
877        /// Open interactive services.
878        INTERACTIVE = 128;
879        /// Open search.
880        SEARCH = 129;
881        /// Sky home key.
882        SKY = 130;
883        /// Base of the range carrying a raw CEC user-control code.
884        CUSTOM_CEC = 1280;
885        /// Base of the range carrying a raw Sky key code.
886        CUSTOM_SKY = 2048;
887    }
888}
889
890wire_enum! {
891    /// The remote-control protocol of a connected sink or source.
892    RcType: u8 {
893        /// Infrared.
894        IR = 0;
895        /// HDMI CEC.
896        CEC = 1;
897        /// Sky UK over IP.
898        SKY_UK = 2;
899        /// TiVo.
900        TIVO = 3;
901        /// Kodi.
902        KODI = 4;
903        /// Dish.
904        DISH = 5;
905        /// DirecTV.
906        DIRECTV = 6;
907        /// Another MX Remote device.
908        MX_REMOTE = 7;
909        /// Routed inside the device itself.
910        INTERNAL = 8;
911    }
912}
913
914impl fmt::Display for RcType {
915    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
916        let name = match *self {
917            Self::IR => "IR",
918            Self::CEC => "CEC",
919            Self::SKY_UK => "Sky",
920            Self::TIVO => "TiVo",
921            Self::KODI => "Kodi",
922            Self::DISH => "Dish",
923            Self::DIRECTV => "DirecTV",
924            Self::MX_REMOTE => "MX-Remote",
925            Self::INTERNAL => "Internal",
926            _ => "Unknown",
927        };
928        f.write_str(name)
929    }
930}
931
932wire_enum! {
933    /// An EDID preset selectable on an HDMI input.
934    EdidProfile: u16 {
935        /// 1080p with stereo audio.
936        STEREO_1080P = 0;
937        /// A fixed EDID stored on the device.
938        FIXED = 1;
939        /// 4K.
940        UHD_4K = 2;
941        /// 1080p with 5.1 audio.
942        SURROUND51_1080P = 3;
943        /// 720p.
944        HD_720P = 4;
945        /// 1080p with 7.1 audio.
946        SURROUND71_1080P = 5;
947        /// 4K with 7.1 audio.
948        SURROUND71_4K = 6;
949        /// 4K HDR with stereo audio.
950        HDR_STEREO_4K = 7;
951        /// 4K HDR with 7.1 audio.
952        HDR_SURROUND71_4K = 8;
953        /// 4K HDR, audio to the AVR only.
954        HDR_AVR_ONLY_4K = 9;
955        /// The lowest common denominator of the connected sinks.
956        LOWEST_COMMON = 10;
957        /// The lowest common denominator of every sink, connected or not.
958        LOWEST_COMMON_ALL = 11;
959        /// 4K HDR with Dolby Atmos.
960        HDR_ATMOS_4K = 12;
961        /// Copy the EDID of sink 1; the range runs to [`EdidProfile::SINK_32`].
962        SINK_1 = 101;
963        /// Copy the EDID of sink 32; the range starts at [`EdidProfile::SINK_1`].
964        SINK_32 = 132;
965        /// Base of the range carrying a user-supplied EDID.
966        CUSTOM_0 = 500;
967        /// The device reports no profile.
968        UNKNOWN = 0xFFF;
969    }
970}
971
972impl fmt::Display for EdidProfile {
973    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
974        let name = match *self {
975            Self::STEREO_1080P => "1080p stereo",
976            Self::FIXED => "fixed",
977            Self::UHD_4K => "4K",
978            Self::SURROUND51_1080P => "1080p 5.1",
979            Self::HD_720P => "720p",
980            Self::SURROUND71_1080P => "1080p 7.1",
981            Self::SURROUND71_4K => "4K 7.1",
982            Self::HDR_STEREO_4K => "4K HDR Stereo",
983            Self::HDR_SURROUND71_4K => "4K HDR 7.1",
984            Self::HDR_AVR_ONLY_4K => "4K HDR AVR",
985            Self::LOWEST_COMMON => "lowest common denominator",
986            Self::LOWEST_COMMON_ALL => "lowest common denominator (all sinks)",
987            Self::HDR_ATMOS_4K => "4K HDR Dolby Atmos",
988            _ => {
989                if self.0 >= Self::SINK_1.0 && self.0 <= Self::SINK_32.0 {
990                    return write!(f, "copy from sink #{}", self.0 - Self::SINK_1.0 + 1);
991                }
992                return write!(f, "custom #{}", self.0);
993            }
994        };
995        f.write_str(name)
996    }
997}
998
999wire_enum! {
1000    /// A firmware component.
1001    FirmwareType: u8 {
1002        /// The unit's own firmware.
1003        MCU = 0;
1004        /// The video processor's FPGA image.
1005        FPGA = 1;
1006        /// The video processor's RTOS image.
1007        RTOS = 2;
1008        /// The video processor's loader image.
1009        LOADER = 3;
1010        /// The unit's bootloader.
1011        BOOTLOADER = 4;
1012    }
1013}
1014
1015impl fmt::Display for FirmwareType {
1016    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1017        let name = match *self {
1018            Self::MCU => "MCU",
1019            Self::FPGA => "FPGA",
1020            Self::RTOS => "RTOS",
1021            Self::LOADER => "Loader",
1022            Self::BOOTLOADER => "Bootloader",
1023            _ => return write!(f, "firmware #{}", self.0),
1024        };
1025        f.write_str(name)
1026    }
1027}
1028
1029wire_enum! {
1030    /// The negotiated speed of a network port.
1031    UtpLinkSpeed: u8 {
1032        /// The device reports no speed.
1033        UNKNOWN = 0;
1034        /// 10Mbit/s.
1035        SPEED_10M = 1;
1036        /// 100Mbit/s.
1037        SPEED_100M = 2;
1038        /// 200Mbit/s.
1039        SPEED_200M = 3;
1040        /// 1Gbit/s.
1041        SPEED_1G = 4;
1042    }
1043}
1044
1045impl fmt::Display for UtpLinkSpeed {
1046    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1047        let name = match *self {
1048            Self::SPEED_10M => "10Mbit/s",
1049            Self::SPEED_100M => "100Mbit/s",
1050            Self::SPEED_200M => "200Mbit/s",
1051            Self::SPEED_1G => "1Gbit/s",
1052            _ => "Unknown",
1053        };
1054        f.write_str(name)
1055    }
1056}
1057
1058wire_enum! {
1059    /// The window layout of a multiviewer.
1060    MultiviewerViewMode: u8 {
1061        /// The device reports no layout.
1062        UNKNOWN = 0;
1063        /// One full-screen window.
1064        SINGLE = 1;
1065        /// Picture in picture.
1066        PIP = 2;
1067        /// Two windows, large.
1068        TWO_SCREEN_LARGE = 3;
1069        /// Two windows, small.
1070        TWO_SCREEN_SMALL = 4;
1071        /// Three windows, large.
1072        THREE_SCREEN_LARGE = 5;
1073        /// Three windows, small.
1074        THREE_SCREEN_SMALL = 6;
1075        /// Four windows, equal size.
1076        FOUR_SCREEN_EQUAL = 7;
1077        /// Four windows, small.
1078        FOUR_SCREEN_SMALL = 8;
1079    }
1080}
1081
1082wire_enum! {
1083    /// The corner a multiviewer places its picture-in-picture window in.
1084    MultiviewerPipPosition: u8 {
1085        /// The device reports no position.
1086        UNKNOWN = 0;
1087        /// Top left.
1088        LEFT_TOP = 1;
1089        /// Bottom left.
1090        LEFT_BOTTOM = 2;
1091        /// Top right.
1092        RIGHT_TOP = 3;
1093        /// Bottom right.
1094        RIGHT_BOTTOM = 4;
1095    }
1096}
1097
1098wire_enum! {
1099    /// The size of a multiviewer's picture-in-picture window.
1100    MultiviewerPipSize: u8 {
1101        /// The device reports no size.
1102        UNKNOWN = 0;
1103        /// Small.
1104        SMALL = 1;
1105        /// Medium.
1106        MEDIUM = 2;
1107        /// Large.
1108        LARGE = 3;
1109    }
1110}
1111
1112wire_enum! {
1113    /// The resolution and refresh rate a multiviewer drives its output at.
1114    MultiviewerOutputMode: u8 {
1115        /// The device reports no output mode.
1116        UNKNOWN = 0;
1117        /// 4096x2160p60.
1118        DCI4K_P60 = 1;
1119        /// 4096x2160p50.
1120        DCI4K_P50 = 2;
1121        /// 3840x2160p60.
1122        UHD_P60 = 3;
1123        /// 3840x2160p50.
1124        UHD_P50 = 4;
1125        /// 3840x2160p30.
1126        UHD_P30 = 5;
1127        /// 3840x2160p25.
1128        UHD_P25 = 6;
1129        /// 1920x1200p60, reduced blanking.
1130        WUXGA_P60_RB = 7;
1131        /// 1920x1080p60.
1132        HD1080_P60 = 8;
1133        /// 1920x1080p50.
1134        HD1080_P50 = 9;
1135        /// 1360x768p60.
1136        WXGA_P60 = 10;
1137        /// 1280x800p60.
1138        WXGA800_P60 = 11;
1139        /// 1280x720p60.
1140        HD720_P60 = 12;
1141        /// 1280x720p50.
1142        HD720_P50 = 13;
1143        /// 1024x768p60.
1144        XGA_P60 = 14;
1145    }
1146}
1147
1148wire_enum! {
1149    /// The HDCP version a multiviewer output negotiates.
1150    MultiviewerHdcpMode: u8 {
1151        /// The device reports no HDCP mode.
1152        UNKNOWN = 0;
1153        /// HDCP 1.4.
1154        V14 = 1;
1155        /// HDCP 2.2.
1156        V22 = 2;
1157        /// Content protection off.
1158        OFF = 3;
1159    }
1160}
1161
1162wire_enum! {
1163    /// The IT-content flag a multiviewer sets on its output.
1164    MultiviewerItcMode: u8 {
1165        /// The device reports no IT-content mode.
1166        UNKNOWN = 0;
1167        /// Video content.
1168        VIDEO = 1;
1169        /// PC content.
1170        PC = 2;
1171    }
1172}
1173
1174wire_enum! {
1175    /// The EDID template a multiviewer presents to its sources.
1176    ///
1177    /// A template's name is the largest resolution it advertises and the audio
1178    /// format it declares support for.
1179    MultiviewerEdidTemplate: u8 {
1180        /// The device reports no template.
1181        EDID_UNKNOWN = 0;
1182        /// 4K2K60 4:4:4, stereo 2.0.
1183        EDID_4K2K60_444_STEREO = 1;
1184        /// 4K2K60 4:4:4, Dolby/DTS 5.1.
1185        EDID_4K2K60_444_DOLBY_DTS_51 = 2;
1186        /// 4K2K60 4:4:4, HD audio 7.1.
1187        EDID_4K2K60_444_HD_AUDIO_71 = 3;
1188        /// 4K2K30 4:4:4, stereo 2.0.
1189        EDID_4K2K30_444_STEREO = 4;
1190        /// 4K2K30 4:4:4, Dolby/DTS 5.1.
1191        EDID_4K2K30_444_DOLBY_DTS_51 = 5;
1192        /// 4K2K30 4:4:4, HD audio 7.1.
1193        EDID_4K2K30_444_HD_AUDIO_71 = 6;
1194        /// 1080p, stereo 2.0.
1195        EDID_1080P_STEREO = 7;
1196        /// 1080p, Dolby/DTS 5.1.
1197        EDID_1080P_DOLBY_DTS_51 = 8;
1198        /// 1080p, HD audio 7.1.
1199        EDID_1080P_HD_AUDIO_71 = 9;
1200        /// 1920x1200, stereo 2.0.
1201        EDID_1920X1200_STEREO = 10;
1202        /// 1680x1050, stereo 2.0.
1203        EDID_1680X1050_STEREO = 11;
1204        /// 1600x1200, stereo 2.0.
1205        EDID_1600X1200_STEREO = 12;
1206        /// 1440x900, stereo 2.0.
1207        EDID_1440X900_STEREO = 13;
1208        /// 1360x768, stereo 2.0.
1209        EDID_1360X768_STEREO = 14;
1210        /// 1280x1024, stereo 2.0.
1211        EDID_1280X1024_STEREO = 15;
1212        /// 1024x768, stereo 2.0.
1213        EDID_1024X768_STEREO = 16;
1214        /// 720p, stereo 2.0.
1215        EDID_720P_STEREO = 17;
1216        /// Whatever the display connected to the HDMI output presents. The
1217        /// template a multiviewer leaves the factory with.
1218        EDID_COPY_OUTPUT = 18;
1219        /// The EDID loaded onto the device.
1220        EDID_CUSTOM = 19;
1221    }
1222}
1223
1224wire_enum! {
1225    /// The aspect ratio a multiviewer scales its windows to.
1226    MultiviewerAspectRatio: u8 {
1227        /// The device reports no aspect ratio.
1228        UNKNOWN = 0;
1229        /// Fill the window.
1230        FULL = 1;
1231        /// 16:9.
1232        RATIO_16_9 = 2;
1233    }
1234}
1235
1236wire_enum! {
1237    /// A multiviewer setting that is on, off, or not reported.
1238    MultiviewerBool: u8 {
1239        /// Off.
1240        OFF = 0;
1241        /// On.
1242        ON = 1;
1243        /// The device reports no value.
1244        UNKNOWN = 0xFF;
1245    }
1246}
1247
1248wire_enum! {
1249    /// One of a multiviewer's four inputs.
1250    ///
1251    /// The wire numbers the inputs from zero and this type from one, so that
1252    /// zero can mean "not reported" the way it does for every other
1253    /// multiviewer setting. So `to_wire` and `from_wire` carry this type's
1254    /// numbering rather than the wire's, and neither is the conversion to
1255    /// reach for when a raw multiviewer byte is what is in hand.
1256    MultiviewerSource: u8 {
1257        /// The device reports no source.
1258        UNKNOWN = 0;
1259        /// Input 1.
1260        INPUT_1 = 1;
1261        /// Input 2.
1262        INPUT_2 = 2;
1263        /// Input 3.
1264        INPUT_3 = 3;
1265        /// Input 4.
1266        INPUT_4 = 4;
1267    }
1268}
1269
1270impl MultiviewerSource {
1271    /// Reads a zero-based wire value, mapping anything past input 4 to
1272    /// [`MultiviewerSource::UNKNOWN`].
1273    ///
1274    /// The firmware spells "not known" as 0xFF, which lands past input 4 and
1275    /// so needs no case of its own.
1276    pub(crate) const fn from_zero_based(value: u8) -> Self {
1277        if value > 3 {
1278            Self::UNKNOWN
1279        } else {
1280            Self(value + 1)
1281        }
1282    }
1283
1284    /// The zero-based value the wire carries, or `None` for a source naming no
1285    /// input.
1286    ///
1287    /// A multiviewer reads zero as its first input, so there is no value that
1288    /// says "leave this alone": a request that cannot name an input has to be
1289    /// refused rather than sent.
1290    pub(crate) const fn to_zero_based(self) -> Option<u8> {
1291        match self.0 {
1292            1..=4 => Some(self.0 - 1),
1293            _ => None,
1294        }
1295    }
1296}
1297
1298impl MultiviewerBool {
1299    /// Reads a wire value, mapping anything but 0 and 1 to
1300    /// [`MultiviewerBool::UNKNOWN`].
1301    pub(crate) const fn from_wire_tristate(value: u8) -> Self {
1302        if value > 1 {
1303            Self::UNKNOWN
1304        } else {
1305            Self(value)
1306        }
1307    }
1308}
1309
1310wire_enum! {
1311    /// The colour space a V2IP output scales to.
1312    ///
1313    /// The field is four bits wide and only these four values are defined. A
1314    /// receiver passes the whole nibble to its validator, so a fifth value is
1315    /// dropped without a word rather than clamped to one of these.
1316    V2ipColourSpace: u8 {
1317        /// RGB.
1318        RGB = 0;
1319        /// YCbCr 4:4:4.
1320        YCBCR444 = 1;
1321        /// YCbCr 4:2:2.
1322        YCBCR422 = 2;
1323        /// YCbCr 4:2:0.
1324        YCBCR420 = 3;
1325    }
1326}
1327
1328wire_enum! {
1329    /// The 2-byte `mxr_signal_type` carried in scaling configs and bay signal
1330    /// reports.
1331    ///
1332    /// Byte 0 is the CTA-861 short video descriptor, 0 when the signal is not
1333    /// HDMI. Byte 1 packs `color:4` in the low nibble, then `non_int:1` and
1334    /// `bpp:3` above it.
1335    MxrSignalType: u16 {
1336        /// No signal format was reported.
1337        NONE = 0;
1338    }
1339}
1340
1341/// The bpp index a sender writes when it has no bit depth to report.
1342///
1343/// It sits outside the four indices that name a real depth, so an unset
1344/// format reads differently from every genuine one.
1345const SIG_BPP_UNSET: u16 = 5;
1346
1347impl MxrSignalType {
1348    /// The CTA-861 short video descriptor, 0 when the signal is not HDMI.
1349    pub const fn svd(self) -> u8 {
1350        (self.0 & 0xFF) as u8
1351    }
1352
1353    /// The colour space.
1354    pub const fn colour_space(self) -> u8 {
1355        ((self.0 >> 8) & 0xF) as u8
1356    }
1357
1358    /// Whether the frame rate carries a 1000/1001 clock.
1359    pub const fn is_non_integer(self) -> bool {
1360        self.0 & (1 << 12) != 0
1361    }
1362
1363    /// The raw bpp index as carried on the wire. The field is an index, not a
1364    /// bit depth; [`MxrSignalType::bpp`] converts it.
1365    pub const fn bpp_index(self) -> u8 {
1366        ((self.0 >> 13) & 0x7) as u8
1367    }
1368
1369    /// The bit depth the bpp index stands for, `None` when unknown or unset.
1370    pub const fn bpp(self) -> Option<u8> {
1371        match self.bpp_index() {
1372            1 => Some(8),
1373            2 => Some(10),
1374            3 => Some(12),
1375            4 => Some(16),
1376            _ => None,
1377        }
1378    }
1379
1380    /// Reports whether the word carries a signal format at all.
1381    ///
1382    /// A bay with nothing configured says so two ways. A sender that zeroes
1383    /// the word and stamps the unset bpp index leaves an index no real depth
1384    /// uses, and one that writes a plain zero leaves nothing at all. Neither
1385    /// is a format, and the svd and colour space beside them are not answers
1386    /// either: both read as zero, which is what this word says for "not HDMI"
1387    /// and "RGB" when it *is* set.
1388    pub const fn is_set(self) -> bool {
1389        self.0 != 0 && self.bpp_index() as u16 != SIG_BPP_UNSET
1390    }
1391
1392    /// Builds the word from the fields a scaling write consumes.
1393    ///
1394    /// `non_int` is left clear: the receiving struct carries the bit, and the
1395    /// apply path does not read it.
1396    ///
1397    /// Building rather than editing is the point. A sink with no mode
1398    /// configured reports the word with the unset bpp index in it, so a caller
1399    /// that read that word back and filled in an svd would send an index no
1400    /// depth uses - which the receiver decodes to zero and rejects without
1401    /// answering.
1402    pub(crate) const fn from_parts(svd: u8, colour: u8, bpp_index: u8) -> Self {
1403        Self((svd as u16) | (((colour & 0xF) as u16) << 8) | (((bpp_index & 0x7) as u16) << 13))
1404    }
1405
1406    /// The bpp index that stands for a bit depth, `None` for a depth no index
1407    /// names.
1408    ///
1409    /// Only the three depths a V2IP output stage accepts are here. Index 4
1410    /// names 16bpp, which [`MxrSignalType::bpp`] reads back from a device, but
1411    /// the output stage refuses it - so offering it as something to write would
1412    /// send a frame that is decoded cleanly and then dropped in silence.
1413    pub(crate) const fn bpp_index_for_depth(depth: u8) -> Option<u8> {
1414        match depth {
1415            8 => Some(1),
1416            10 => Some(2),
1417            12 => Some(3),
1418            _ => None,
1419        }
1420    }
1421}
1422
1423impl fmt::Display for MxrSignalType {
1424    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1425        if !self.is_set() {
1426            return f.write_str("unset");
1427        }
1428        match self.bpp() {
1429            Some(bpp) => write!(
1430                f,
1431                "svd {}, color {}, {}bpp",
1432                self.svd(),
1433                self.colour_space(),
1434                bpp
1435            ),
1436            None => write!(f, "svd {}, color {}", self.svd(), self.colour_space()),
1437        }
1438    }
1439}
1440
1441bitmask! {
1442    /// The kinds of overlay setting an overlay sync copies.
1443    OsdSyncWhat: u8 {
1444        /// Captions.
1445        CAPTIONS = 1 << 0;
1446        /// Which logo is shown, and where.
1447        LOGO = 1 << 1;
1448        /// Background colours.
1449        BACKGROUNDS = 1 << 2;
1450        /// The logo images in the four slots.
1451        LOGO_SLOTS = 1 << 3;
1452    }
1453}
1454
1455wire_enum! {
1456    /// How far a unit has got with taking an overlay sync.
1457    OsdSyncState: u8 {
1458        /// Taking it.
1459        TAKING = 0;
1460        /// Every setting written and committed.
1461        DONE = 1;
1462        /// Failed, for the reason the report gives.
1463        FAILED = 2;
1464        /// Kept for later, as the unit's video processor is off in power
1465        /// save.
1466        DEFERRED = 3;
1467    }
1468}
1469
1470wire_enum! {
1471    /// The output size an overlay setting applies at.
1472    OsdSyncSize: u8 {
1473        /// Large outputs, which every value but `SMALL` also means.
1474        LARGE = 1;
1475        /// Small outputs.
1476        SMALL = 2;
1477    }
1478}
1479
1480wire_enum! {
1481    /// How a caption's lines are aligned.
1482    OsdCaptionAlign: u8 {
1483        /// To the left.
1484        LEFT = 0;
1485        /// Centred.
1486        CENTRE = 1;
1487        /// To the right.
1488        RIGHT = 2;
1489        /// The layout's default.
1490        DEFAULT = 0xFF;
1491    }
1492}
1493
1494wire_enum! {
1495    /// How a caption's text is read.
1496    OsdCaptionFormat: u8 {
1497        /// As plain text.
1498        PLAIN = 0;
1499        /// As Markdown.
1500        MARKDOWN = 1;
1501        /// The layout's default.
1502        DEFAULT = 0xFF;
1503    }
1504}
1505
1506bitmask! {
1507    /// The caption fields an overlay setting gives. A field left out keeps
1508    /// what the unit has.
1509    OsdCaptionField: u16 {
1510        /// The font.
1511        FONT = 1 << 0;
1512        /// The size.
1513        SIZE = 1 << 1;
1514        /// The horizontal position.
1515        X = 1 << 2;
1516        /// The vertical position.
1517        Y = 1 << 3;
1518        /// The alignment.
1519        ALIGN = 1 << 4;
1520        /// The format.
1521        FORMAT = 1 << 5;
1522        /// The colour.
1523        COLOUR = 1 << 6;
1524        /// The wrap width.
1525        WRAP = 1 << 7;
1526        /// The text.
1527        TEXT = 1 << 8;
1528    }
1529}
1530
1531bitmask! {
1532    /// The logo fields an overlay setting gives. A field left out keeps what
1533    /// the unit has.
1534    OsdLogoField: u16 {
1535        /// Which logo.
1536        LOGO = 1 << 0;
1537        /// The horizontal position.
1538        X = 1 << 1;
1539        /// The vertical position.
1540        Y = 1 << 2;
1541    }
1542}