1use std::fmt;
28use std::net::Ipv4Addr;
29
30use crate::event::Event;
31use crate::state::{Bay, Device, State};
32use crate::types::{
33 AmpZoneSettings, HiddenStatus, MultiviewerStatus, PowerStatus, V2ipAudioFormat, V2ipRoute,
34 V2ipRouteTarget, V2ipStreamSources, VideoWallOp, VideoWallWindow, VolumeMuteStatus,
35 MULTIVIEWER_INPUTS, VIDEO_WALL_CLEARED,
36};
37use crate::wire::{
38 audio_cmd_header, audio_param, audio_sub, build_amp_zone_settings, build_audio_select_input,
39 build_bay_hide, build_edid_profile, build_edid_request, build_rc_action, build_rc_key,
40 build_set_bay_name, build_set_volume, build_stats_request, build_target_only,
41 build_v2ip_manual_source_switch, build_v2ip_source_switch, build_video_wall, mv_cmd_payload,
42 mv_sub, op, Addressee, BayUid, DeviceUid, EdidProfile, MultiviewerAspectRatio,
43 MultiviewerEdidTemplate, MultiviewerHdcpMode, MultiviewerItcMode, MultiviewerOutputMode,
44 MultiviewerPipPosition, MultiviewerPipSize, MultiviewerSource, MultiviewerViewMode, Opcode,
45 RcAction, RcKey, SendError, StreamAddr, V2ipStreams, DEVICE_NAME_LEN, V2IP_PORT_ANC,
46 V2IP_PORT_AUDIO, V2IP_PORT_VIDEO,
47};
48
49use super::{Remote, Shared};
50
51#[derive(Debug)]
53#[non_exhaustive]
54pub enum ControlError {
55 UnknownDevice(DeviceUid),
57 UnknownBay(BayUid),
59 UnknownSource(String),
61 Unsupported(&'static str),
63 InvalidRequest(&'static str),
70 NotReported(&'static str),
75 Send(SendError),
77}
78
79impl fmt::Display for ControlError {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 match self {
82 Self::UnknownDevice(uid) => write!(f, "no device {uid}"),
83 Self::UnknownBay(uid) => write!(f, "no bay {uid}"),
84 Self::UnknownSource(name) => write!(f, "no source named {name:?}"),
85 Self::Unsupported(what) => f.write_str(what),
86 Self::InvalidRequest(what) => f.write_str(what),
87 Self::NotReported(what) => write!(f, "{what} has not been reported"),
88 Self::Send(e) => write!(f, "{e}"),
89 }
90 }
91}
92
93impl std::error::Error for ControlError {
94 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
95 match self {
96 Self::Send(e) => Some(e),
97 _ => None,
98 }
99 }
100}
101
102impl From<SendError> for ControlError {
103 fn from(e: SendError) -> Self {
104 Self::Send(e)
105 }
106}
107
108type WriteBack = Box<dyn FnOnce(&mut State, &mut Vec<Event>) + Send>;
115
116struct Command {
118 to: Addressee,
119 opcode: Opcode,
120 payload: Vec<u8>,
121 write_back: Option<WriteBack>,
122}
123
124impl Command {
125 fn new(to: Addressee, opcode: Opcode, payload: Vec<u8>) -> Self {
126 Self {
127 to,
128 opcode,
129 payload,
130 write_back: None,
131 }
132 }
133
134 fn then(mut self, f: impl FnOnce(&mut State, &mut Vec<Event>) + Send + 'static) -> Self {
136 self.write_back = Some(Box::new(f));
137 self
138 }
139}
140
141impl Shared {
142 fn command(
145 &self,
146 prepare: impl FnOnce(&State) -> Result<Command, ControlError>,
147 ) -> Result<(), ControlError> {
148 let command = self.read(prepare)?;
149 self.send(&command.to, command.opcode, &command.payload)?;
150 if let Some(write_back) = command.write_back {
151 self.mutate(|state, ev| write_back(state, ev));
152 }
153 Ok(())
154 }
155}
156
157fn device_of(state: &State, uid: DeviceUid) -> Result<&Device, ControlError> {
158 state.device(uid).ok_or(ControlError::UnknownDevice(uid))
159}
160
161fn multiviewer_of(state: &State, uid: DeviceUid) -> Result<&Device, ControlError> {
163 let device = device_of(state, uid)?;
164 if !device.is_multiviewer() {
165 return Err(ControlError::Unsupported("the device is not a multiviewer"));
166 }
167 Ok(device)
168}
169
170fn mv_command(device: &Device, sub: u8, args: &[u8]) -> Command {
172 Command::new(
173 Addressee::device(device),
174 op::V2IP_MULTIVIEWER,
175 mv_cmd_payload(device.uid, sub, args),
176 )
177}
178
179fn source_index(source: MultiviewerSource, what: &'static str) -> Result<u8, ControlError> {
185 source
186 .to_zero_based()
187 .ok_or(ControlError::InvalidRequest(what))
188}
189
190fn mv_setting(value: u8, highest: u8, what: &'static str) -> Result<u8, ControlError> {
197 if (1..=highest).contains(&value) {
198 Ok(value)
199 } else {
200 Err(ControlError::InvalidRequest(what))
201 }
202}
203
204fn bay_of(state: &State, uid: BayUid) -> Result<(&Device, &Bay), ControlError> {
205 let device = device_of(state, uid.device)?;
206 let bay = device.bay(uid.port).ok_or(ControlError::UnknownBay(uid))?;
207 Ok((device, bay))
208}
209
210fn source_streams(device: &Device, port: u16) -> Result<&V2ipStreamSources, ControlError> {
212 let source = device
213 .bay(port)
214 .ok_or(ControlError::UnknownBay(BayUid::new(device.uid, port)))?;
215 device
216 .v2ip_source_for(source)
217 .ok_or(ControlError::NotReported("the source's stream addresses"))
218}
219
220fn v2ip_sink(state: &State, uid: BayUid) -> Result<(&Device, &Bay), ControlError> {
222 let (device, bay) = bay_of(state, uid)?;
223 if !bay.is_v2ip_sink() {
224 return Err(ControlError::Unsupported("routing needs a V2IP sink"));
225 }
226 Ok((device, bay))
227}
228
229fn stream_addr(target: V2ipRouteTarget, standard_port: u16) -> StreamAddr {
235 if target.ip.is_unspecified() {
236 return StreamAddr::default();
237 }
238 StreamAddr {
239 ip: target.ip,
240 port: target.port_or(standard_port),
241 }
242}
243
244fn stored_name(name: &str) -> String {
247 let bytes = name.as_bytes();
248 String::from_utf8_lossy(bytes.get(..DEVICE_NAME_LEN).unwrap_or(bytes)).into_owned()
249}
250
251impl Remote {
252 pub fn select_video_source(&self, sink: BayUid, source_port: u16) -> Result<(), ControlError> {
256 self.shared.command(|state| {
257 let (device, bay) = v2ip_sink(state, sink)?;
258 if !bay.is_output() {
259 return Err(ControlError::Unsupported("not an output bay"));
260 }
261 let streams = source_streams(device, source_port)?;
262 Ok(Command::new(
263 Addressee::device(device),
264 op::V2IP_SOURCE_SWITCH,
265 build_v2ip_source_switch(device.uid, streams.video.ip, Ipv4Addr::UNSPECIFIED),
266 ))
267 })
268 }
269
270 pub fn select_audio_source(&self, sink: BayUid, source_port: u16) -> Result<(), ControlError> {
272 self.shared.command(|state| {
273 let (device, _) = v2ip_sink(state, sink)?;
274 let streams = source_streams(device, source_port)?;
275 Ok(Command::new(
276 Addressee::device(device),
277 op::V2IP_SOURCE_SWITCH,
278 build_v2ip_source_switch(device.uid, Ipv4Addr::UNSPECIFIED, streams.audio.ip),
279 ))
280 })
281 }
282
283 pub fn select_video_source_by_name(
286 &self,
287 sink: BayUid,
288 name: &str,
289 ) -> Result<(), ControlError> {
290 self.select_video_source(sink, self.source_port(sink, name)?)
291 }
292
293 pub fn select_audio_source_addr(
299 &self,
300 sink: BayUid,
301 audio_ip: Ipv4Addr,
302 audio_port: Option<u16>,
303 format: Option<V2ipAudioFormat>,
304 ) -> Result<(), ControlError> {
305 self.shared.command(move |state| {
306 let (device, _) = v2ip_sink(state, sink)?;
307 let streams = V2ipStreams {
308 audio: StreamAddr {
309 ip: audio_ip,
310 port: audio_port.unwrap_or(V2IP_PORT_AUDIO),
311 },
312 ..V2ipStreams::default()
313 };
314 Ok(Command::new(
315 Addressee::device(device),
316 op::V2IP_MANUAL_SRC_SWITCH,
317 build_v2ip_manual_source_switch(device.uid, streams, format),
318 ))
319 })
320 }
321
322 pub fn select_source_addr(
340 &self,
341 sink: BayUid,
342 route: V2ipRoute,
343 format: Option<V2ipAudioFormat>,
344 ) -> Result<(), ControlError> {
345 let streams = V2ipStreams {
346 video: stream_addr(route.video, V2IP_PORT_VIDEO),
347 audio: stream_addr(route.audio, V2IP_PORT_AUDIO),
348 anc: stream_addr(route.anc, V2IP_PORT_ANC),
349 };
350 let format = format.unwrap_or(V2ipAudioFormat::STANDARD);
351 self.shared.command(move |state| {
352 let (device, _) = v2ip_sink(state, sink)?;
353 Ok(Command::new(
354 Addressee::device(device),
355 op::V2IP_MANUAL_SRC_SWITCH,
356 build_v2ip_manual_source_switch(device.uid, streams, Some(format)),
357 ))
358 })
359 }
360
361 pub fn select_audio_source_by_name(
367 &self,
368 sink: BayUid,
369 name: &str,
370 format: Option<V2ipAudioFormat>,
371 ) -> Result<(), ControlError> {
372 let port = self.source_port(sink, name)?;
373 let Some(format) = format else {
374 return self.select_audio_source(sink, port);
375 };
376 self.shared.command(move |state| {
377 let (device, _) = v2ip_sink(state, sink)?;
378 let audio = source_streams(device, port)?.audio;
379 let streams = V2ipStreams {
380 audio: StreamAddr {
381 ip: audio.ip,
382 port: audio.port,
383 },
384 ..V2ipStreams::default()
385 };
386 Ok(Command::new(
387 Addressee::device(device),
388 op::V2IP_MANUAL_SRC_SWITCH,
389 build_v2ip_manual_source_switch(device.uid, streams, Some(format)),
390 ))
391 })
392 }
393
394 fn source_port(&self, sink: BayUid, name: &str) -> Result<u16, ControlError> {
396 self.shared.read(|state| {
397 let (device, _) = bay_of(state, sink)?;
398 device
399 .bay_by_user_name(name)
400 .map(|b| b.port)
401 .ok_or_else(|| ControlError::UnknownSource(name.to_owned()))
402 })
403 }
404
405 pub fn set_bay_name(&self, bay: BayUid, name: &str) -> Result<(), ControlError> {
409 let name = stored_name(name);
410 self.shared.command(move |state| {
411 let (device, _) = bay_of(state, bay)?;
412 let payload = build_set_bay_name(device.uid, bay.port, &name);
413 Ok(
414 Command::new(Addressee::device(device), op::CHANGE_BAY_NAME, payload).then(
415 move |state, ev| {
416 if let Some(b) = state.bay_mut(bay) {
417 b.set_user_name(name, ev);
418 }
419 },
420 ),
421 )
422 })
423 }
424
425 pub fn set_bay_hidden(&self, bay: BayUid, hidden: bool) -> Result<(), ControlError> {
427 self.shared.command(move |state| {
428 let (device, _) = bay_of(state, bay)?;
429 Ok(Command::new(
430 Addressee::device(device),
431 op::BAY_HIDE,
432 build_bay_hide(device.uid, bay.port, hidden),
433 )
434 .then(move |state, ev| {
435 if let Some(b) = state.bay_mut(bay) {
436 let status = if hidden {
437 HiddenStatus::Hidden
438 } else {
439 HiddenStatus::Visible
440 };
441 b.apply_hidden(status, ev);
442 }
443 }))
444 })
445 }
446
447 pub fn select_edid_profile(
449 &self,
450 bay: BayUid,
451 profile: EdidProfile,
452 ) -> Result<(), ControlError> {
453 self.shared.command(move |state| {
454 let (device, _) = bay_of(state, bay)?;
455 Ok(Command::new(
456 Addressee::device(device),
457 op::BAY_EDID_PROFILE,
458 build_edid_profile(device.uid, profile),
459 )
460 .then(move |state, ev| {
461 if let Some(b) = state.bay_mut(bay) {
462 b.set_edid_profile(profile, ev);
463 }
464 }))
465 })
466 }
467
468 pub fn send_action(&self, bay: BayUid, action: RcAction) -> Result<(), ControlError> {
470 self.shared.command(move |state| {
471 let (device, _) = bay_of(state, bay)?;
472 Ok(Command::new(
473 Addressee::device(device),
474 op::RC_TX_ACTION,
475 build_rc_action(device.uid, bay.port, action),
476 ))
477 })
478 }
479
480 pub fn send_key(&self, bay: BayUid, key: RcKey) -> Result<(), ControlError> {
487 self.shared.command(move |state| {
488 let (device, _) = bay_of(state, bay)?;
489 Ok(Command::new(
490 Addressee::device(device),
491 op::RC_TX_KEY,
492 build_rc_key(device.uid, bay.port, key),
493 ))
494 })
495 }
496
497 pub fn power_on(&self, bay: BayUid) -> Result<(), ControlError> {
499 self.set_power(bay, RcAction::POWER_ON, PowerStatus::On)
500 }
501
502 pub fn power_off(&self, bay: BayUid) -> Result<(), ControlError> {
504 self.set_power(bay, RcAction::POWER_OFF, PowerStatus::Off)
505 }
506
507 fn set_power(
508 &self,
509 bay: BayUid,
510 action: RcAction,
511 power: PowerStatus,
512 ) -> Result<(), ControlError> {
513 self.shared.command(move |state| {
514 let (device, _) = bay_of(state, bay)?;
515 Ok(Command::new(
516 Addressee::device(device),
517 op::RC_TX_ACTION,
518 build_rc_action(device.uid, bay.port, action),
519 )
520 .then(move |state, ev| {
521 if let Some(b) = state.bay_mut(bay) {
522 b.set_power_status(power, ev);
523 }
524 }))
525 })
526 }
527
528 pub fn set_volume(
539 &self,
540 bay: BayUid,
541 volume: u8,
542 muted: Option<bool>,
543 ) -> Result<(), ControlError> {
544 let volume = volume.min(100);
545 let wanted = VolumeMuteStatus {
546 volume_left: Some(volume),
547 volume_right: Some(volume),
548 muted_left: muted,
549 muted_right: muted,
550 };
551 self.shared.command(move |state| {
552 let target = state.volume_bay(bay);
556 let (device, b) = bay_of(state, target)?;
557 if !b.has_volume_control() {
558 return Err(ControlError::Unsupported("the bay has no volume control"));
559 }
560 Ok(Command::new(
561 Addressee::device(device),
562 op::AUDIO_SET_VOLUME,
563 build_set_volume(device.uid, target.port, wanted),
564 )
565 .then(move |state, ev| {
566 if let Some(device) = state.device_mut(target.device) {
567 device.apply_bay_volume(target.port, wanted, ev);
568 }
569 }))
570 })
571 }
572
573 pub fn volume_up(&self, bay: BayUid) -> Result<(), ControlError> {
575 self.set_volume(bay, self.current_volume(bay)?.saturating_add(1), None)
576 }
577
578 pub fn volume_down(&self, bay: BayUid) -> Result<(), ControlError> {
580 self.set_volume(bay, self.current_volume(bay)?.saturating_sub(1), None)
581 }
582
583 pub fn set_muted(&self, bay: BayUid, muted: bool) -> Result<(), ControlError> {
585 self.set_volume(bay, self.current_volume(bay)?, Some(muted))
586 }
587
588 fn current_volume(&self, bay: BayUid) -> Result<u8, ControlError> {
590 self.shared.read(|state| {
591 let (_, b) = bay_of(state, state.volume_bay(bay))?;
592 b.audio_volume
593 .map(|v| v.volume())
594 .ok_or(ControlError::NotReported("the bay's volume"))
595 })
596 }
597
598 pub fn set_amp_zone_settings(
600 &self,
601 bay: BayUid,
602 settings: AmpZoneSettings,
603 ) -> Result<(), ControlError> {
604 self.shared.command(move |state| {
605 let (device, _) = bay_of(state, bay)?;
606 Ok(Command::new(
607 Addressee::device(device),
608 op::AMP_ZONE_SETTINGS,
609 build_amp_zone_settings(device.uid, bay.port, &settings),
610 )
611 .then(move |state, ev| {
612 if let Some(b) = state.bay_mut(bay) {
613 b.set_amp_settings(settings, ev);
614 }
615 }))
616 })
617 }
618
619 pub fn set_audio_endpoint_muted(
623 &self,
624 device: DeviceUid,
625 endpoint: u16,
626 muted: bool,
627 ) -> Result<(), ControlError> {
628 self.audio_endpoint(device, audio_sub::MUTE, endpoint, u32::from(muted))
629 }
630
631 pub fn set_audio_endpoint_trigger(
633 &self,
634 device: DeviceUid,
635 endpoint: u16,
636 active: bool,
637 ) -> Result<(), ControlError> {
638 self.audio_endpoint(device, audio_sub::TRIGGER, endpoint, u32::from(active))
639 }
640
641 pub fn set_audio_endpoint_volume(
643 &self,
644 device: DeviceUid,
645 endpoint: u16,
646 volume: u32,
647 ) -> Result<(), ControlError> {
648 self.audio_endpoint(device, audio_sub::VOLUME, endpoint, volume)
649 }
650
651 fn audio_endpoint(
652 &self,
653 device: DeviceUid,
654 sub: u16,
655 endpoint: u16,
656 value: u32,
657 ) -> Result<(), ControlError> {
658 self.shared.command(move |state| {
659 let device = device_of(state, device)?;
660 let mut payload = audio_cmd_header(sub, device.uid);
661 payload.extend_from_slice(&audio_param(endpoint, value));
662 Ok(Command::new(
663 Addressee::device(device),
664 op::V2IP_AUDIO,
665 payload,
666 ))
667 })
668 }
669
670 pub fn select_audio_endpoint_input(
672 &self,
673 sink: DeviceUid,
674 sink_endpoint: u16,
675 source: DeviceUid,
676 source_endpoint: u16,
677 ) -> Result<(), ControlError> {
678 self.shared.command(move |state| {
679 let device = device_of(state, sink)?;
680 Ok(Command::new(
681 Addressee::device(device),
682 op::V2IP_AUDIO,
683 build_audio_select_input(sink, sink_endpoint, source, source_endpoint),
684 ))
685 })
686 }
687
688 pub fn subscribe_v2ip_stats(
692 &self,
693 device: DeviceUid,
694 subscribe: bool,
695 ) -> Result<(), ControlError> {
696 self.shared.command(move |state| {
697 let device = device_of(state, device)?;
698 Ok(Command::new(
699 Addressee::device(device),
700 op::V2IP_STATS,
701 build_stats_request(device.uid, subscribe),
702 ))
703 })
704 }
705
706 pub fn request_edid(&self, device: DeviceUid, output: bool) -> Result<(), ControlError> {
720 self.shared.command(move |state| {
721 let device = device_of(state, device)?;
722 Ok(Command::new(
723 Addressee::device(device),
724 op::DEV_EDID,
725 build_edid_request(device.uid, output),
726 ))
727 })
728 }
729
730 pub fn request_signal_status(&self, device: Option<DeviceUid>) -> Result<(), ControlError> {
737 let Some(device) = device else {
738 self.shared
739 .send(&Addressee::Broadcast, op::BAY_SIGNAL_STATUS, &[])?;
740 return Ok(());
741 };
742 self.shared.command(move |state| {
743 let device = device_of(state, device)?;
744 Ok(Command::new(
745 Addressee::device(device),
746 op::BAY_SIGNAL_STATUS,
747 build_target_only(device.uid),
748 ))
749 })
750 }
751
752 pub fn reboot(&self, device: DeviceUid) -> Result<(), ControlError> {
757 self.shared.command(move |state| {
758 let d = device_of(state, device)?;
759 Ok(Command::new(
760 Addressee::device(d),
761 op::SYS_REBOOT,
762 build_target_only(d.uid),
763 )
764 .then(move |state, _| {
765 if let Some(d) = state.device_mut(device) {
766 d.rebooting = true;
767 }
768 }))
769 })
770 }
771
772 pub fn send_monitoring_pulse(&self) -> Result<(), ControlError> {
775 self.shared
776 .send(&Addressee::Broadcast, op::SYS_MONITORING_PULSE, &[])?;
777 Ok(())
778 }
779
780 pub fn preview_video_wall(
789 &self,
790 sink: DeviceUid,
791 window: VideoWallWindow,
792 ) -> Result<(), ControlError> {
793 self.set_video_wall(sink, window, VideoWallOp::PREVIEW)
794 }
795
796 pub fn store_video_wall(
811 &self,
812 sink: DeviceUid,
813 window: VideoWallWindow,
814 ) -> Result<(), ControlError> {
815 self.set_video_wall(sink, window, VideoWallOp::STORE)
816 }
817
818 pub fn revert_video_wall(&self, sink: DeviceUid) -> Result<(), ControlError> {
823 self.set_video_wall(sink, VIDEO_WALL_CLEARED, VideoWallOp::REVERT)
824 }
825
826 fn set_video_wall(
831 &self,
832 sink: DeviceUid,
833 window: VideoWallWindow,
834 op: VideoWallOp,
835 ) -> Result<(), ControlError> {
836 if op != VideoWallOp::REVERT {
837 window.validate().map_err(ControlError::InvalidRequest)?;
838 }
839 self.shared.command(move |state| {
840 let device = device_of(state, sink)?;
841 Ok(Command::new(
842 Addressee::device(device),
843 op::V2IP_VIDEO_WALL,
844 build_video_wall(device.uid, window, op),
845 ))
846 })
847 }
848
849 pub fn set_multiviewer_view_mode(
853 &self,
854 device: DeviceUid,
855 mode: MultiviewerViewMode,
856 ) -> Result<(), ControlError> {
857 let mode = mv_setting(mode.to_wire(), 8, "the multiviewer has no such view mode")?;
858 self.multiviewer(device, mv_sub::VIEW_MODE, &[mode])
859 }
860
861 pub fn set_multiviewer_video_source(
871 &self,
872 device: DeviceUid,
873 screen: u8,
874 source: MultiviewerSource,
875 ) -> Result<(), ControlError> {
876 let source = source_index(source, "the source names no multiviewer input")?;
877 self.shared.command(|state| {
878 let target = multiviewer_of(state, device)?;
879 let windows = target
880 .multiviewer
881 .as_ref()
882 .and_then(MultiviewerStatus::window_count)
883 .unwrap_or(1);
884 if screen >= windows {
885 return Err(ControlError::InvalidRequest(
886 "the window is not one the multiviewer is showing",
887 ));
888 }
889 Ok(mv_command(target, mv_sub::VIDEO_SOURCE, &[screen, source]))
890 })
891 }
892
893 pub fn set_multiviewer_audio_source(
895 &self,
896 device: DeviceUid,
897 source: MultiviewerSource,
898 ) -> Result<(), ControlError> {
899 let source = source_index(source, "the audio source names no multiviewer input")?;
900 self.multiviewer(device, mv_sub::AUDIO_SOURCE, &[source])
901 }
902
903 pub fn set_multiviewer_audio_volume(
911 &self,
912 device: DeviceUid,
913 volume: u8,
914 muted: bool,
915 ) -> Result<(), ControlError> {
916 if volume > 100 {
917 return Err(ControlError::InvalidRequest(
918 "a multiviewer volume is a percentage",
919 ));
920 }
921 self.multiviewer(device, mv_sub::AUDIO_VOLUME, &[volume, u8::from(muted)])
922 }
923
924 pub fn set_multiviewer_edid_template(
926 &self,
927 device: DeviceUid,
928 template: MultiviewerEdidTemplate,
929 ) -> Result<(), ControlError> {
930 let template = mv_setting(
931 template.to_wire(),
932 19,
933 "the multiviewer has no such EDID template",
934 )?;
935 self.multiviewer(device, mv_sub::EDID_TEMPLATE, &[template])
936 }
937
938 pub fn set_multiviewer_remote_control(
940 &self,
941 device: DeviceUid,
942 source: MultiviewerSource,
943 ) -> Result<(), ControlError> {
944 let source = source_index(
945 source,
946 "the remote-control source names no multiviewer input",
947 )?;
948 self.multiviewer(device, mv_sub::ROUTE_RC, &[source])
949 }
950
951 pub fn set_multiviewer_pip_size(
953 &self,
954 device: DeviceUid,
955 size: MultiviewerPipSize,
956 ) -> Result<(), ControlError> {
957 let size = mv_setting(
958 size.to_wire(),
959 3,
960 "the multiviewer has no such picture-in-picture size",
961 )?;
962 self.multiviewer(device, mv_sub::PIP_SIZE, &[size])
963 }
964
965 pub fn set_multiviewer_pip_position(
967 &self,
968 device: DeviceUid,
969 position: MultiviewerPipPosition,
970 ) -> Result<(), ControlError> {
971 let position = mv_setting(
972 position.to_wire(),
973 4,
974 "the multiviewer has no such picture-in-picture position",
975 )?;
976 self.multiviewer(device, mv_sub::PIP_POSITION, &[position])
977 }
978
979 pub fn set_multiviewer_aspect_ratio(
981 &self,
982 device: DeviceUid,
983 aspect: MultiviewerAspectRatio,
984 ) -> Result<(), ControlError> {
985 let aspect = mv_setting(
986 aspect.to_wire(),
987 2,
988 "the multiviewer has no such aspect ratio",
989 )?;
990 self.multiviewer(device, mv_sub::ASPECT, &[aspect])
991 }
992
993 pub fn set_multiviewer_auto_switch(
995 &self,
996 device: DeviceUid,
997 enable: bool,
998 ) -> Result<(), ControlError> {
999 self.multiviewer(device, mv_sub::AUTO_SWITCH, &[u8::from(enable)])
1000 }
1001
1002 pub fn set_multiviewer_output_mode(
1004 &self,
1005 device: DeviceUid,
1006 mode: MultiviewerOutputMode,
1007 ) -> Result<(), ControlError> {
1008 let mode = mv_setting(
1009 mode.to_wire(),
1010 14,
1011 "the multiviewer has no such output mode",
1012 )?;
1013 self.multiviewer(device, mv_sub::OUTPUT_MODE, &[mode])
1014 }
1015
1016 pub fn set_multiviewer_output_itc(
1018 &self,
1019 device: DeviceUid,
1020 mode: MultiviewerItcMode,
1021 ) -> Result<(), ControlError> {
1022 let mode = mv_setting(
1023 mode.to_wire(),
1024 2,
1025 "the multiviewer has no such IT-content mode",
1026 )?;
1027 self.multiviewer(device, mv_sub::OUTPUT_ITC_MODE, &[mode])
1028 }
1029
1030 pub fn set_multiviewer_hdcp_mode(
1032 &self,
1033 device: DeviceUid,
1034 mode: MultiviewerHdcpMode,
1035 ) -> Result<(), ControlError> {
1036 let mode = mv_setting(mode.to_wire(), 3, "the multiviewer has no such HDCP mode")?;
1037 self.multiviewer(device, mv_sub::HDCP_MODE, &[mode])
1038 }
1039
1040 pub fn set_multiviewer_input_source(
1054 &self,
1055 device: DeviceUid,
1056 input: u8,
1057 source: DeviceUid,
1058 ) -> Result<(), ControlError> {
1059 if usize::from(input) >= MULTIVIEWER_INPUTS {
1060 return Err(ControlError::InvalidRequest(
1061 "the multiviewer has no such input",
1062 ));
1063 }
1064 let mut args = Vec::with_capacity(24);
1065 args.extend_from_slice(source.as_bytes());
1066 args.push(input);
1067 args.extend_from_slice(&[0; 7]);
1070 self.multiviewer(device, mv_sub::CONFIG_SOURCE, &args)
1071 }
1072
1073 pub fn multiviewer_auto_route(&self, device: DeviceUid) -> Result<(), ControlError> {
1075 self.multiviewer(device, mv_sub::AUTO_ROUTE, &[])
1076 }
1077
1078 fn multiviewer(&self, device: DeviceUid, sub: u8, args: &[u8]) -> Result<(), ControlError> {
1079 self.shared
1080 .command(|state| Ok(mv_command(multiviewer_of(state, device)?, sub, args)))
1081 }
1082}