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(
702 &self,
703 device: DeviceUid,
704 subscribe: bool,
705 ) -> Result<(), ControlError> {
706 self.shared.command(move |state| {
707 let device = device_of(state, device)?;
708 Ok(Command::new(
709 Addressee::device(device),
710 op::V2IP_STATS,
711 build_stats_request(device.uid, subscribe),
712 ))
713 })
714 }
715
716 pub fn request_edid(&self, device: DeviceUid, output: bool) -> Result<(), ControlError> {
730 self.shared.command(move |state| {
731 let device = device_of(state, device)?;
732 Ok(Command::new(
733 Addressee::device(device),
734 op::DEV_EDID,
735 build_edid_request(device.uid, output),
736 ))
737 })
738 }
739
740 pub fn request_signal_status(&self, device: Option<DeviceUid>) -> Result<(), ControlError> {
747 let Some(device) = device else {
748 self.shared
749 .send(&Addressee::Broadcast, op::BAY_SIGNAL_STATUS, &[])?;
750 return Ok(());
751 };
752 self.shared.command(move |state| {
753 let device = device_of(state, device)?;
754 Ok(Command::new(
755 Addressee::device(device),
756 op::BAY_SIGNAL_STATUS,
757 build_target_only(device.uid),
758 ))
759 })
760 }
761
762 pub fn reboot(&self, device: DeviceUid) -> Result<(), ControlError> {
767 self.shared.command(move |state| {
768 let d = device_of(state, device)?;
769 Ok(Command::new(
770 Addressee::device(d),
771 op::SYS_REBOOT,
772 build_target_only(d.uid),
773 )
774 .then(move |state, _| {
775 if let Some(d) = state.device_mut(device) {
776 d.rebooting = true;
777 }
778 }))
779 })
780 }
781
782 pub fn send_monitoring_pulse(&self) -> Result<(), ControlError> {
785 self.shared
786 .send(&Addressee::Broadcast, op::SYS_MONITORING_PULSE, &[])?;
787 Ok(())
788 }
789
790 pub fn preview_video_wall(
799 &self,
800 sink: DeviceUid,
801 window: VideoWallWindow,
802 ) -> Result<(), ControlError> {
803 self.set_video_wall(sink, window, VideoWallOp::PREVIEW)
804 }
805
806 pub fn store_video_wall(
821 &self,
822 sink: DeviceUid,
823 window: VideoWallWindow,
824 ) -> Result<(), ControlError> {
825 self.set_video_wall(sink, window, VideoWallOp::STORE)
826 }
827
828 pub fn revert_video_wall(&self, sink: DeviceUid) -> Result<(), ControlError> {
833 self.set_video_wall(sink, VIDEO_WALL_CLEARED, VideoWallOp::REVERT)
834 }
835
836 fn set_video_wall(
841 &self,
842 sink: DeviceUid,
843 window: VideoWallWindow,
844 op: VideoWallOp,
845 ) -> Result<(), ControlError> {
846 if op != VideoWallOp::REVERT {
847 window.validate().map_err(ControlError::InvalidRequest)?;
848 }
849 self.shared.command(move |state| {
850 let device = device_of(state, sink)?;
851 Ok(Command::new(
852 Addressee::device(device),
853 op::V2IP_VIDEO_WALL,
854 build_video_wall(device.uid, window, op),
855 ))
856 })
857 }
858
859 pub fn set_multiviewer_view_mode(
863 &self,
864 device: DeviceUid,
865 mode: MultiviewerViewMode,
866 ) -> Result<(), ControlError> {
867 let mode = mv_setting(mode.to_wire(), 8, "the multiviewer has no such view mode")?;
868 self.multiviewer(device, mv_sub::VIEW_MODE, &[mode])
869 }
870
871 pub fn set_multiviewer_video_source(
881 &self,
882 device: DeviceUid,
883 screen: u8,
884 source: MultiviewerSource,
885 ) -> Result<(), ControlError> {
886 let source = source_index(source, "the source names no multiviewer input")?;
887 self.shared.command(|state| {
888 let target = multiviewer_of(state, device)?;
889 let windows = target
890 .multiviewer
891 .as_ref()
892 .and_then(MultiviewerStatus::window_count)
893 .unwrap_or(1);
894 if screen >= windows {
895 return Err(ControlError::InvalidRequest(
896 "the window is not one the multiviewer is showing",
897 ));
898 }
899 Ok(mv_command(target, mv_sub::VIDEO_SOURCE, &[screen, source]))
900 })
901 }
902
903 pub fn set_multiviewer_audio_source(
905 &self,
906 device: DeviceUid,
907 source: MultiviewerSource,
908 ) -> Result<(), ControlError> {
909 let source = source_index(source, "the audio source names no multiviewer input")?;
910 self.multiviewer(device, mv_sub::AUDIO_SOURCE, &[source])
911 }
912
913 pub fn set_multiviewer_audio_volume(
921 &self,
922 device: DeviceUid,
923 volume: u8,
924 muted: bool,
925 ) -> Result<(), ControlError> {
926 if volume > 100 {
927 return Err(ControlError::InvalidRequest(
928 "a multiviewer volume is a percentage",
929 ));
930 }
931 self.multiviewer(device, mv_sub::AUDIO_VOLUME, &[volume, u8::from(muted)])
932 }
933
934 pub fn set_multiviewer_edid_template(
936 &self,
937 device: DeviceUid,
938 template: MultiviewerEdidTemplate,
939 ) -> Result<(), ControlError> {
940 let template = mv_setting(
941 template.to_wire(),
942 19,
943 "the multiviewer has no such EDID template",
944 )?;
945 self.multiviewer(device, mv_sub::EDID_TEMPLATE, &[template])
946 }
947
948 pub fn set_multiviewer_remote_control(
950 &self,
951 device: DeviceUid,
952 source: MultiviewerSource,
953 ) -> Result<(), ControlError> {
954 let source = source_index(
955 source,
956 "the remote-control source names no multiviewer input",
957 )?;
958 self.multiviewer(device, mv_sub::ROUTE_RC, &[source])
959 }
960
961 pub fn set_multiviewer_pip_size(
963 &self,
964 device: DeviceUid,
965 size: MultiviewerPipSize,
966 ) -> Result<(), ControlError> {
967 let size = mv_setting(
968 size.to_wire(),
969 3,
970 "the multiviewer has no such picture-in-picture size",
971 )?;
972 self.multiviewer(device, mv_sub::PIP_SIZE, &[size])
973 }
974
975 pub fn set_multiviewer_pip_position(
977 &self,
978 device: DeviceUid,
979 position: MultiviewerPipPosition,
980 ) -> Result<(), ControlError> {
981 let position = mv_setting(
982 position.to_wire(),
983 4,
984 "the multiviewer has no such picture-in-picture position",
985 )?;
986 self.multiviewer(device, mv_sub::PIP_POSITION, &[position])
987 }
988
989 pub fn set_multiviewer_aspect_ratio(
991 &self,
992 device: DeviceUid,
993 aspect: MultiviewerAspectRatio,
994 ) -> Result<(), ControlError> {
995 let aspect = mv_setting(
996 aspect.to_wire(),
997 2,
998 "the multiviewer has no such aspect ratio",
999 )?;
1000 self.multiviewer(device, mv_sub::ASPECT, &[aspect])
1001 }
1002
1003 pub fn set_multiviewer_auto_switch(
1005 &self,
1006 device: DeviceUid,
1007 enable: bool,
1008 ) -> Result<(), ControlError> {
1009 self.multiviewer(device, mv_sub::AUTO_SWITCH, &[u8::from(enable)])
1010 }
1011
1012 pub fn set_multiviewer_output_mode(
1014 &self,
1015 device: DeviceUid,
1016 mode: MultiviewerOutputMode,
1017 ) -> Result<(), ControlError> {
1018 let mode = mv_setting(
1019 mode.to_wire(),
1020 14,
1021 "the multiviewer has no such output mode",
1022 )?;
1023 self.multiviewer(device, mv_sub::OUTPUT_MODE, &[mode])
1024 }
1025
1026 pub fn set_multiviewer_output_itc(
1028 &self,
1029 device: DeviceUid,
1030 mode: MultiviewerItcMode,
1031 ) -> Result<(), ControlError> {
1032 let mode = mv_setting(
1033 mode.to_wire(),
1034 2,
1035 "the multiviewer has no such IT-content mode",
1036 )?;
1037 self.multiviewer(device, mv_sub::OUTPUT_ITC_MODE, &[mode])
1038 }
1039
1040 pub fn set_multiviewer_hdcp_mode(
1042 &self,
1043 device: DeviceUid,
1044 mode: MultiviewerHdcpMode,
1045 ) -> Result<(), ControlError> {
1046 let mode = mv_setting(mode.to_wire(), 3, "the multiviewer has no such HDCP mode")?;
1047 self.multiviewer(device, mv_sub::HDCP_MODE, &[mode])
1048 }
1049
1050 pub fn set_multiviewer_input_source(
1064 &self,
1065 device: DeviceUid,
1066 input: u8,
1067 source: DeviceUid,
1068 ) -> Result<(), ControlError> {
1069 if usize::from(input) >= MULTIVIEWER_INPUTS {
1070 return Err(ControlError::InvalidRequest(
1071 "the multiviewer has no such input",
1072 ));
1073 }
1074 let mut args = Vec::with_capacity(24);
1075 args.extend_from_slice(source.as_bytes());
1076 args.push(input);
1077 args.extend_from_slice(&[0; 7]);
1080 self.multiviewer(device, mv_sub::CONFIG_SOURCE, &args)
1081 }
1082
1083 pub fn multiviewer_auto_route(&self, device: DeviceUid) -> Result<(), ControlError> {
1085 self.multiviewer(device, mv_sub::AUTO_ROUTE, &[])
1086 }
1087
1088 fn multiviewer(&self, device: DeviceUid, sub: u8, args: &[u8]) -> Result<(), ControlError> {
1089 self.shared
1090 .command(|state| Ok(mv_command(multiviewer_of(state, device)?, sub, args)))
1091 }
1092}