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(
654 &self,
655 device: DeviceUid,
656 endpoint: u16,
657 volume: u32,
658 ) -> Result<(), ControlError> {
659 self.audio_endpoint(device, audio_sub::VOLUME, endpoint, volume)
660 }
661
662 fn audio_endpoint(
663 &self,
664 device: DeviceUid,
665 sub: u16,
666 endpoint: u16,
667 value: u32,
668 ) -> Result<(), ControlError> {
669 self.shared.command(move |state| {
670 let device = device_of(state, device)?;
671 let mut payload = audio_cmd_header(sub, device.uid);
672 payload.extend_from_slice(&audio_param(endpoint, value));
673 Ok(Command::new(
674 Addressee::device(device),
675 op::V2IP_AUDIO,
676 payload,
677 ))
678 })
679 }
680
681 pub fn select_audio_endpoint_input(
683 &self,
684 sink: DeviceUid,
685 sink_endpoint: u16,
686 source: DeviceUid,
687 source_endpoint: u16,
688 ) -> Result<(), ControlError> {
689 self.shared.command(move |state| {
690 let device = device_of(state, sink)?;
691 Ok(Command::new(
692 Addressee::device(device),
693 op::V2IP_AUDIO,
694 build_audio_select_input(sink, sink_endpoint, source, source_endpoint),
695 ))
696 })
697 }
698
699 pub fn subscribe_v2ip_stats(
713 &self,
714 device: DeviceUid,
715 subscribe: bool,
716 ) -> Result<(), ControlError> {
717 self.shared.command(move |state| {
718 let device = device_of(state, device)?;
719 Ok(Command::new(
720 Addressee::device(device),
721 op::V2IP_STATS,
722 build_stats_request(device.uid, subscribe),
723 ))
724 })
725 }
726
727 pub fn request_edid(&self, device: DeviceUid, output: bool) -> Result<(), ControlError> {
741 self.shared.command(move |state| {
742 let device = device_of(state, device)?;
743 Ok(Command::new(
744 Addressee::device(device),
745 op::DEV_EDID,
746 build_edid_request(device.uid, output),
747 ))
748 })
749 }
750
751 pub fn request_signal_status(&self, device: Option<DeviceUid>) -> Result<(), ControlError> {
758 let Some(device) = device else {
759 self.shared
760 .send(&Addressee::Broadcast, op::BAY_SIGNAL_STATUS, &[])?;
761 return Ok(());
762 };
763 self.shared.command(move |state| {
764 let device = device_of(state, device)?;
765 Ok(Command::new(
766 Addressee::device(device),
767 op::BAY_SIGNAL_STATUS,
768 build_target_only(device.uid),
769 ))
770 })
771 }
772
773 pub fn reboot(&self, device: DeviceUid) -> Result<(), ControlError> {
778 self.shared.command(move |state| {
779 let d = device_of(state, device)?;
780 Ok(Command::new(
781 Addressee::device(d),
782 op::SYS_REBOOT,
783 build_target_only(d.uid),
784 )
785 .then(move |state, _| {
786 if let Some(d) = state.device_mut(device) {
787 d.rebooting = true;
788 }
789 }))
790 })
791 }
792
793 pub fn send_monitoring_pulse(&self) -> Result<(), ControlError> {
796 self.shared
797 .send(&Addressee::Broadcast, op::SYS_MONITORING_PULSE, &[])?;
798 Ok(())
799 }
800
801 pub fn preview_video_wall(
810 &self,
811 sink: DeviceUid,
812 window: VideoWallWindow,
813 ) -> Result<(), ControlError> {
814 self.set_video_wall(sink, window, VideoWallOp::PREVIEW)
815 }
816
817 pub fn store_video_wall(
832 &self,
833 sink: DeviceUid,
834 window: VideoWallWindow,
835 ) -> Result<(), ControlError> {
836 self.set_video_wall(sink, window, VideoWallOp::STORE)
837 }
838
839 pub fn revert_video_wall(&self, sink: DeviceUid) -> Result<(), ControlError> {
844 self.set_video_wall(sink, VIDEO_WALL_CLEARED, VideoWallOp::REVERT)
845 }
846
847 fn set_video_wall(
861 &self,
862 sink: DeviceUid,
863 window: VideoWallWindow,
864 op: VideoWallOp,
865 ) -> Result<(), ControlError> {
866 if op != VideoWallOp::REVERT {
867 window.validate().map_err(ControlError::InvalidRequest)?;
868 }
869 self.shared.command(move |state| {
870 let device = device_of(state, sink)?;
871 Ok(Command::new(
872 Addressee::device(device),
873 op::V2IP_VIDEO_WALL,
874 build_video_wall(device.uid, window, op),
875 ))
876 })
877 }
878
879 pub fn set_multiviewer_view_mode(
883 &self,
884 device: DeviceUid,
885 mode: MultiviewerViewMode,
886 ) -> Result<(), ControlError> {
887 let mode = mv_setting(mode.to_wire(), 8, "the multiviewer has no such view mode")?;
888 self.multiviewer(device, mv_sub::VIEW_MODE, &[mode])
889 }
890
891 pub fn set_multiviewer_video_source(
901 &self,
902 device: DeviceUid,
903 screen: u8,
904 source: MultiviewerSource,
905 ) -> Result<(), ControlError> {
906 let source = source_index(source, "the source names no multiviewer input")?;
907 self.shared.command(|state| {
908 let target = multiviewer_of(state, device)?;
909 let windows = target
910 .multiviewer
911 .as_ref()
912 .and_then(MultiviewerStatus::window_count)
913 .unwrap_or(1);
914 if screen >= windows {
915 return Err(ControlError::InvalidRequest(
916 "the window is not one the multiviewer is showing",
917 ));
918 }
919 Ok(mv_command(target, mv_sub::VIDEO_SOURCE, &[screen, source]))
920 })
921 }
922
923 pub fn set_multiviewer_audio_source(
925 &self,
926 device: DeviceUid,
927 source: MultiviewerSource,
928 ) -> Result<(), ControlError> {
929 let source = source_index(source, "the audio source names no multiviewer input")?;
930 self.multiviewer(device, mv_sub::AUDIO_SOURCE, &[source])
931 }
932
933 pub fn set_multiviewer_audio_volume(
941 &self,
942 device: DeviceUid,
943 volume: u8,
944 muted: bool,
945 ) -> Result<(), ControlError> {
946 if volume > 100 {
947 return Err(ControlError::InvalidRequest(
948 "a multiviewer volume is a percentage",
949 ));
950 }
951 self.multiviewer(device, mv_sub::AUDIO_VOLUME, &[volume, u8::from(muted)])
952 }
953
954 pub fn set_multiviewer_edid_template(
956 &self,
957 device: DeviceUid,
958 template: MultiviewerEdidTemplate,
959 ) -> Result<(), ControlError> {
960 let template = mv_setting(
961 template.to_wire(),
962 19,
963 "the multiviewer has no such EDID template",
964 )?;
965 self.multiviewer(device, mv_sub::EDID_TEMPLATE, &[template])
966 }
967
968 pub fn set_multiviewer_remote_control(
970 &self,
971 device: DeviceUid,
972 source: MultiviewerSource,
973 ) -> Result<(), ControlError> {
974 let source = source_index(
975 source,
976 "the remote-control source names no multiviewer input",
977 )?;
978 self.multiviewer(device, mv_sub::ROUTE_RC, &[source])
979 }
980
981 pub fn set_multiviewer_pip_size(
983 &self,
984 device: DeviceUid,
985 size: MultiviewerPipSize,
986 ) -> Result<(), ControlError> {
987 let size = mv_setting(
988 size.to_wire(),
989 3,
990 "the multiviewer has no such picture-in-picture size",
991 )?;
992 self.multiviewer(device, mv_sub::PIP_SIZE, &[size])
993 }
994
995 pub fn set_multiviewer_pip_position(
997 &self,
998 device: DeviceUid,
999 position: MultiviewerPipPosition,
1000 ) -> Result<(), ControlError> {
1001 let position = mv_setting(
1002 position.to_wire(),
1003 4,
1004 "the multiviewer has no such picture-in-picture position",
1005 )?;
1006 self.multiviewer(device, mv_sub::PIP_POSITION, &[position])
1007 }
1008
1009 pub fn set_multiviewer_aspect_ratio(
1011 &self,
1012 device: DeviceUid,
1013 aspect: MultiviewerAspectRatio,
1014 ) -> Result<(), ControlError> {
1015 let aspect = mv_setting(
1016 aspect.to_wire(),
1017 2,
1018 "the multiviewer has no such aspect ratio",
1019 )?;
1020 self.multiviewer(device, mv_sub::ASPECT, &[aspect])
1021 }
1022
1023 pub fn set_multiviewer_auto_switch(
1025 &self,
1026 device: DeviceUid,
1027 enable: bool,
1028 ) -> Result<(), ControlError> {
1029 self.multiviewer(device, mv_sub::AUTO_SWITCH, &[u8::from(enable)])
1030 }
1031
1032 pub fn set_multiviewer_output_mode(
1034 &self,
1035 device: DeviceUid,
1036 mode: MultiviewerOutputMode,
1037 ) -> Result<(), ControlError> {
1038 let mode = mv_setting(
1039 mode.to_wire(),
1040 14,
1041 "the multiviewer has no such output mode",
1042 )?;
1043 self.multiviewer(device, mv_sub::OUTPUT_MODE, &[mode])
1044 }
1045
1046 pub fn set_multiviewer_output_itc(
1048 &self,
1049 device: DeviceUid,
1050 mode: MultiviewerItcMode,
1051 ) -> Result<(), ControlError> {
1052 let mode = mv_setting(
1053 mode.to_wire(),
1054 2,
1055 "the multiviewer has no such IT-content mode",
1056 )?;
1057 self.multiviewer(device, mv_sub::OUTPUT_ITC_MODE, &[mode])
1058 }
1059
1060 pub fn set_multiviewer_hdcp_mode(
1062 &self,
1063 device: DeviceUid,
1064 mode: MultiviewerHdcpMode,
1065 ) -> Result<(), ControlError> {
1066 let mode = mv_setting(mode.to_wire(), 3, "the multiviewer has no such HDCP mode")?;
1067 self.multiviewer(device, mv_sub::HDCP_MODE, &[mode])
1068 }
1069
1070 pub fn set_multiviewer_input_source(
1084 &self,
1085 device: DeviceUid,
1086 input: u8,
1087 source: DeviceUid,
1088 ) -> Result<(), ControlError> {
1089 if usize::from(input) >= MULTIVIEWER_INPUTS {
1090 return Err(ControlError::InvalidRequest(
1091 "the multiviewer has no such input",
1092 ));
1093 }
1094 let mut args = Vec::with_capacity(24);
1095 args.extend_from_slice(source.as_bytes());
1096 args.push(input);
1097 args.extend_from_slice(&[0; 7]);
1100 self.multiviewer(device, mv_sub::CONFIG_SOURCE, &args)
1101 }
1102
1103 pub fn multiviewer_auto_route(&self, device: DeviceUid) -> Result<(), ControlError> {
1105 self.multiviewer(device, mv_sub::AUTO_ROUTE, &[])
1106 }
1107
1108 fn multiviewer(&self, device: DeviceUid, sub: u8, args: &[u8]) -> Result<(), ControlError> {
1109 self.shared
1110 .command(|state| Ok(mv_command(multiviewer_of(state, device)?, sub, args)))
1111 }
1112}