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, V2ipOutputMode,
34 V2ipRoute, V2ipRouteTarget, V2ipScalingSettings, V2ipStreamSources, VideoWallOp,
35 VideoWallWindow, VolumeMuteStatus, MULTIVIEWER_INPUTS, SCALING_FLAG_AUTO_SCALING,
36 SCALING_FLAG_MODE_VALID, SCALING_FLAG_OPTIONS_VALID, VIDEO_WALL_CLEARED,
37};
38use crate::wire::{
39 audio_cmd_header, audio_param, audio_sub, build_amp_zone_settings, build_audio_select_input,
40 build_bay_hide, build_edid_profile, build_edid_request, build_rc_action, build_rc_key,
41 build_set_bay_name, build_set_volume, build_stats_request, build_target_only,
42 build_v2ip_manual_source_switch, build_v2ip_scaling, build_v2ip_source_switch,
43 build_video_wall, mv_cmd_payload, mv_sub, op, Addressee, BayUid, DeviceUid, EdidProfile,
44 MultiviewerAspectRatio, MultiviewerEdidTemplate, MultiviewerHdcpMode, MultiviewerItcMode,
45 MultiviewerOutputMode, MultiviewerPipPosition, MultiviewerPipSize, MultiviewerSource,
46 MultiviewerViewMode, MxrSignalType, Opcode, RcAction, RcKey, SendError, StreamAddr,
47 V2ipStreams, DEVICE_NAME_LEN, V2IP_PORT_ANC, V2IP_PORT_AUDIO, V2IP_PORT_VIDEO,
48};
49
50use super::{Remote, Shared};
51
52#[derive(Debug)]
54#[non_exhaustive]
55pub enum ControlError {
56 UnknownDevice(DeviceUid),
58 UnknownBay(BayUid),
60 UnknownSource(String),
62 Unsupported(&'static str),
64 InvalidRequest(&'static str),
71 NotReported(&'static str),
76 Send(SendError),
78}
79
80impl fmt::Display for ControlError {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 match self {
83 Self::UnknownDevice(uid) => write!(f, "no device {uid}"),
84 Self::UnknownBay(uid) => write!(f, "no bay {uid}"),
85 Self::UnknownSource(name) => write!(f, "no source named {name:?}"),
86 Self::Unsupported(what) => f.write_str(what),
87 Self::InvalidRequest(what) => f.write_str(what),
88 Self::NotReported(what) => write!(f, "{what} has not been reported"),
89 Self::Send(e) => write!(f, "{e}"),
90 }
91 }
92}
93
94impl std::error::Error for ControlError {
95 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
96 match self {
97 Self::Send(e) => Some(e),
98 _ => None,
99 }
100 }
101}
102
103impl From<SendError> for ControlError {
104 fn from(e: SendError) -> Self {
105 Self::Send(e)
106 }
107}
108
109type WriteBack = Box<dyn FnOnce(&mut State, &mut Vec<Event>) + Send>;
116
117struct Command {
119 to: Addressee,
120 opcode: Opcode,
121 payload: Vec<u8>,
122 write_back: Option<WriteBack>,
123}
124
125impl Command {
126 fn new(to: Addressee, opcode: Opcode, payload: Vec<u8>) -> Self {
127 Self {
128 to,
129 opcode,
130 payload,
131 write_back: None,
132 }
133 }
134
135 fn then(mut self, f: impl FnOnce(&mut State, &mut Vec<Event>) + Send + 'static) -> Self {
137 self.write_back = Some(Box::new(f));
138 self
139 }
140}
141
142impl Shared {
143 fn command(
146 &self,
147 prepare: impl FnOnce(&State) -> Result<Command, ControlError>,
148 ) -> Result<(), ControlError> {
149 let command = self.read(prepare)?;
150 self.send(&command.to, command.opcode, &command.payload)?;
151 if let Some(write_back) = command.write_back {
152 self.mutate(|state, ev| write_back(state, ev));
153 }
154 Ok(())
155 }
156}
157
158fn device_of(state: &State, uid: DeviceUid) -> Result<&Device, ControlError> {
159 state.device(uid).ok_or(ControlError::UnknownDevice(uid))
160}
161
162fn multiviewer_of(state: &State, uid: DeviceUid) -> Result<&Device, ControlError> {
164 let device = device_of(state, uid)?;
165 if !device.is_multiviewer() {
166 return Err(ControlError::Unsupported("the device is not a multiviewer"));
167 }
168 Ok(device)
169}
170
171fn mv_command(device: &Device, sub: u8, args: &[u8]) -> Command {
173 Command::new(
174 Addressee::device(device),
175 op::V2IP_MULTIVIEWER,
176 mv_cmd_payload(device.uid, sub, args),
177 )
178}
179
180fn source_index(source: MultiviewerSource, what: &'static str) -> Result<u8, ControlError> {
186 source
187 .to_zero_based()
188 .ok_or(ControlError::InvalidRequest(what))
189}
190
191fn mv_setting(value: u8, highest: u8, what: &'static str) -> Result<u8, ControlError> {
198 if (1..=highest).contains(&value) {
199 Ok(value)
200 } else {
201 Err(ControlError::InvalidRequest(what))
202 }
203}
204
205fn bay_of(state: &State, uid: BayUid) -> Result<(&Device, &Bay), ControlError> {
206 let device = device_of(state, uid.device)?;
207 let bay = device.bay(uid.port).ok_or(ControlError::UnknownBay(uid))?;
208 Ok((device, bay))
209}
210
211fn source_streams(device: &Device, port: u16) -> Result<&V2ipStreamSources, ControlError> {
213 let source = device
214 .bay(port)
215 .ok_or(ControlError::UnknownBay(BayUid::new(device.uid, port)))?;
216 device
217 .v2ip_source_for(source)
218 .ok_or(ControlError::NotReported("the source's stream addresses"))
219}
220
221fn v2ip_sink(state: &State, uid: BayUid) -> Result<(&Device, &Bay), ControlError> {
223 let (device, bay) = bay_of(state, uid)?;
224 if !bay.is_v2ip_sink() {
225 return Err(ControlError::Unsupported("routing needs a V2IP sink"));
226 }
227 Ok((device, bay))
228}
229
230fn stream_addr(target: V2ipRouteTarget, standard_port: u16) -> StreamAddr {
236 if target.ip.is_unspecified() {
237 return StreamAddr::default();
238 }
239 StreamAddr {
240 ip: target.ip,
241 port: target.port_or(standard_port),
242 }
243}
244
245fn stored_name(name: &str) -> String {
248 let bytes = name.as_bytes();
249 String::from_utf8_lossy(bytes.get(..DEVICE_NAME_LEN).unwrap_or(bytes)).into_owned()
250}
251
252impl Remote {
253 pub fn select_video_source(&self, sink: BayUid, source_port: u16) -> Result<(), ControlError> {
257 self.shared.command(|state| {
258 let (device, bay) = v2ip_sink(state, sink)?;
259 if !bay.is_output() {
260 return Err(ControlError::Unsupported("not an output bay"));
261 }
262 let streams = source_streams(device, source_port)?;
263 Ok(Command::new(
264 Addressee::device(device),
265 op::V2IP_SOURCE_SWITCH,
266 build_v2ip_source_switch(device.uid, streams.video.ip, Ipv4Addr::UNSPECIFIED),
267 ))
268 })
269 }
270
271 pub fn select_audio_source(&self, sink: BayUid, source_port: u16) -> Result<(), ControlError> {
273 self.shared.command(|state| {
274 let (device, _) = v2ip_sink(state, sink)?;
275 let streams = source_streams(device, source_port)?;
276 Ok(Command::new(
277 Addressee::device(device),
278 op::V2IP_SOURCE_SWITCH,
279 build_v2ip_source_switch(device.uid, Ipv4Addr::UNSPECIFIED, streams.audio.ip),
280 ))
281 })
282 }
283
284 pub fn select_video_source_by_name(
287 &self,
288 sink: BayUid,
289 name: &str,
290 ) -> Result<(), ControlError> {
291 self.select_video_source(sink, self.source_port(sink, name)?)
292 }
293
294 pub fn select_audio_source_addr(
300 &self,
301 sink: BayUid,
302 audio_ip: Ipv4Addr,
303 audio_port: Option<u16>,
304 format: Option<V2ipAudioFormat>,
305 ) -> Result<(), ControlError> {
306 self.shared.command(move |state| {
307 let (device, _) = v2ip_sink(state, sink)?;
308 let streams = V2ipStreams {
309 audio: StreamAddr {
310 ip: audio_ip,
311 port: audio_port.unwrap_or(V2IP_PORT_AUDIO),
312 },
313 ..V2ipStreams::default()
314 };
315 Ok(Command::new(
316 Addressee::device(device),
317 op::V2IP_MANUAL_SRC_SWITCH,
318 build_v2ip_manual_source_switch(device.uid, streams, format),
319 ))
320 })
321 }
322
323 pub fn select_source_addr(
341 &self,
342 sink: BayUid,
343 route: V2ipRoute,
344 format: Option<V2ipAudioFormat>,
345 ) -> Result<(), ControlError> {
346 let streams = V2ipStreams {
347 video: stream_addr(route.video, V2IP_PORT_VIDEO),
348 audio: stream_addr(route.audio, V2IP_PORT_AUDIO),
349 anc: stream_addr(route.anc, V2IP_PORT_ANC),
350 };
351 let format = format.unwrap_or(V2ipAudioFormat::STANDARD);
352 self.shared.command(move |state| {
353 let (device, _) = v2ip_sink(state, sink)?;
354 Ok(Command::new(
355 Addressee::device(device),
356 op::V2IP_MANUAL_SRC_SWITCH,
357 build_v2ip_manual_source_switch(device.uid, streams, Some(format)),
358 ))
359 })
360 }
361
362 pub fn select_audio_source_by_name(
368 &self,
369 sink: BayUid,
370 name: &str,
371 format: Option<V2ipAudioFormat>,
372 ) -> Result<(), ControlError> {
373 let port = self.source_port(sink, name)?;
374 let Some(format) = format else {
375 return self.select_audio_source(sink, port);
376 };
377 self.shared.command(move |state| {
378 let (device, _) = v2ip_sink(state, sink)?;
379 let audio = source_streams(device, port)?.audio;
380 let streams = V2ipStreams {
381 audio: StreamAddr {
382 ip: audio.ip,
383 port: audio.port,
384 },
385 ..V2ipStreams::default()
386 };
387 Ok(Command::new(
388 Addressee::device(device),
389 op::V2IP_MANUAL_SRC_SWITCH,
390 build_v2ip_manual_source_switch(device.uid, streams, Some(format)),
391 ))
392 })
393 }
394
395 fn source_port(&self, sink: BayUid, name: &str) -> Result<u16, ControlError> {
397 self.shared.read(|state| {
398 let (device, _) = bay_of(state, sink)?;
399 device
400 .bay_by_user_name(name)
401 .map(|b| b.port)
402 .ok_or_else(|| ControlError::UnknownSource(name.to_owned()))
403 })
404 }
405
406 pub fn set_bay_name(&self, bay: BayUid, name: &str) -> Result<(), ControlError> {
410 let name = stored_name(name);
411 self.shared.command(move |state| {
412 let (device, _) = bay_of(state, bay)?;
413 let payload = build_set_bay_name(device.uid, bay.port, &name);
414 Ok(
415 Command::new(Addressee::device(device), op::CHANGE_BAY_NAME, payload).then(
416 move |state, ev| {
417 if let Some(b) = state.bay_mut(bay) {
418 b.set_user_name(name, ev);
419 }
420 },
421 ),
422 )
423 })
424 }
425
426 pub fn set_bay_hidden(&self, bay: BayUid, hidden: bool) -> Result<(), ControlError> {
428 self.shared.command(move |state| {
429 let (device, _) = bay_of(state, bay)?;
430 Ok(Command::new(
431 Addressee::device(device),
432 op::BAY_HIDE,
433 build_bay_hide(device.uid, bay.port, hidden),
434 )
435 .then(move |state, ev| {
436 if let Some(b) = state.bay_mut(bay) {
437 let status = if hidden {
438 HiddenStatus::Hidden
439 } else {
440 HiddenStatus::Visible
441 };
442 b.apply_hidden(status, ev);
443 }
444 }))
445 })
446 }
447
448 pub fn select_edid_profile(
450 &self,
451 bay: BayUid,
452 profile: EdidProfile,
453 ) -> Result<(), ControlError> {
454 self.shared.command(move |state| {
455 let (device, _) = bay_of(state, bay)?;
456 Ok(Command::new(
457 Addressee::device(device),
458 op::BAY_EDID_PROFILE,
459 build_edid_profile(device.uid, profile),
460 )
461 .then(move |state, ev| {
462 if let Some(b) = state.bay_mut(bay) {
463 b.set_edid_profile(profile, ev);
464 }
465 }))
466 })
467 }
468
469 pub fn send_action(&self, bay: BayUid, action: RcAction) -> Result<(), ControlError> {
471 self.shared.command(move |state| {
472 let (device, _) = bay_of(state, bay)?;
473 Ok(Command::new(
474 Addressee::device(device),
475 op::RC_TX_ACTION,
476 build_rc_action(device.uid, bay.port, action),
477 ))
478 })
479 }
480
481 pub fn send_key(&self, bay: BayUid, key: RcKey) -> Result<(), ControlError> {
488 self.shared.command(move |state| {
489 let (device, _) = bay_of(state, bay)?;
490 Ok(Command::new(
491 Addressee::device(device),
492 op::RC_TX_KEY,
493 build_rc_key(device.uid, bay.port, key),
494 ))
495 })
496 }
497
498 pub fn power_on(&self, bay: BayUid) -> Result<(), ControlError> {
500 self.set_power(bay, RcAction::POWER_ON, PowerStatus::On)
501 }
502
503 pub fn power_off(&self, bay: BayUid) -> Result<(), ControlError> {
505 self.set_power(bay, RcAction::POWER_OFF, PowerStatus::Off)
506 }
507
508 fn set_power(
509 &self,
510 bay: BayUid,
511 action: RcAction,
512 power: PowerStatus,
513 ) -> Result<(), ControlError> {
514 self.shared.command(move |state| {
515 let (device, _) = bay_of(state, bay)?;
516 Ok(Command::new(
517 Addressee::device(device),
518 op::RC_TX_ACTION,
519 build_rc_action(device.uid, bay.port, action),
520 )
521 .then(move |state, ev| {
522 if let Some(b) = state.bay_mut(bay) {
523 b.set_power_status(power, ev);
524 }
525 }))
526 })
527 }
528
529 pub fn set_volume(
540 &self,
541 bay: BayUid,
542 volume: u8,
543 muted: Option<bool>,
544 ) -> Result<(), ControlError> {
545 let volume = volume.min(100);
546 let wanted = VolumeMuteStatus {
547 volume_left: Some(volume),
548 volume_right: Some(volume),
549 muted_left: muted,
550 muted_right: muted,
551 };
552 self.shared.command(move |state| {
553 let target = state.volume_bay(bay);
557 let (device, b) = bay_of(state, target)?;
558 if !b.has_volume_control() {
559 return Err(ControlError::Unsupported("the bay has no volume control"));
560 }
561 Ok(Command::new(
562 Addressee::device(device),
563 op::AUDIO_SET_VOLUME,
564 build_set_volume(device.uid, target.port, wanted),
565 )
566 .then(move |state, ev| {
567 if let Some(device) = state.device_mut(target.device) {
568 device.apply_bay_volume(target.port, wanted, ev);
569 }
570 }))
571 })
572 }
573
574 pub fn volume_up(&self, bay: BayUid) -> Result<(), ControlError> {
576 self.set_volume(bay, self.current_volume(bay)?.saturating_add(1), None)
577 }
578
579 pub fn volume_down(&self, bay: BayUid) -> Result<(), ControlError> {
581 self.set_volume(bay, self.current_volume(bay)?.saturating_sub(1), None)
582 }
583
584 pub fn set_muted(&self, bay: BayUid, muted: bool) -> Result<(), ControlError> {
586 self.set_volume(bay, self.current_volume(bay)?, Some(muted))
587 }
588
589 fn current_volume(&self, bay: BayUid) -> Result<u8, ControlError> {
591 self.shared.read(|state| {
592 let (_, b) = bay_of(state, state.volume_bay(bay))?;
593 b.audio_volume
594 .map(|v| v.volume())
595 .ok_or(ControlError::NotReported("the bay's volume"))
596 })
597 }
598
599 pub fn set_amp_zone_settings(
601 &self,
602 bay: BayUid,
603 settings: AmpZoneSettings,
604 ) -> Result<(), ControlError> {
605 self.shared.command(move |state| {
606 let (device, _) = bay_of(state, bay)?;
607 Ok(Command::new(
608 Addressee::device(device),
609 op::AMP_ZONE_SETTINGS,
610 build_amp_zone_settings(device.uid, bay.port, &settings),
611 )
612 .then(move |state, ev| {
613 if let Some(b) = state.bay_mut(bay) {
614 b.set_amp_settings(settings, ev);
615 }
616 }))
617 })
618 }
619
620 pub fn set_audio_endpoint_muted(
624 &self,
625 device: DeviceUid,
626 endpoint: u16,
627 muted: bool,
628 ) -> Result<(), ControlError> {
629 self.audio_endpoint(device, audio_sub::MUTE, endpoint, u32::from(muted))
630 }
631
632 pub fn set_audio_endpoint_trigger(
634 &self,
635 device: DeviceUid,
636 endpoint: u16,
637 active: bool,
638 ) -> Result<(), ControlError> {
639 self.audio_endpoint(device, audio_sub::TRIGGER, endpoint, u32::from(active))
640 }
641
642 pub fn set_audio_endpoint_volume(
655 &self,
656 device: DeviceUid,
657 endpoint: u16,
658 volume: u32,
659 ) -> Result<(), ControlError> {
660 self.audio_endpoint(device, audio_sub::VOLUME, endpoint, volume)
661 }
662
663 fn audio_endpoint(
664 &self,
665 device: DeviceUid,
666 sub: u16,
667 endpoint: u16,
668 value: u32,
669 ) -> Result<(), ControlError> {
670 self.shared.command(move |state| {
671 let device = device_of(state, device)?;
672 let mut payload = audio_cmd_header(sub, device.uid);
673 payload.extend_from_slice(&audio_param(endpoint, value));
674 Ok(Command::new(
675 Addressee::device(device),
676 op::V2IP_AUDIO,
677 payload,
678 ))
679 })
680 }
681
682 pub fn select_audio_endpoint_input(
684 &self,
685 sink: DeviceUid,
686 sink_endpoint: u16,
687 source: DeviceUid,
688 source_endpoint: u16,
689 ) -> Result<(), ControlError> {
690 self.shared.command(move |state| {
691 let device = device_of(state, sink)?;
692 Ok(Command::new(
693 Addressee::device(device),
694 op::V2IP_AUDIO,
695 build_audio_select_input(sink, sink_endpoint, source, source_endpoint),
696 ))
697 })
698 }
699
700 pub fn subscribe_v2ip_stats(
714 &self,
715 device: DeviceUid,
716 subscribe: bool,
717 ) -> Result<(), ControlError> {
718 self.shared.command(move |state| {
719 let device = device_of(state, device)?;
720 Ok(Command::new(
721 Addressee::device(device),
722 op::V2IP_STATS,
723 build_stats_request(device.uid, subscribe),
724 ))
725 })
726 }
727
728 pub fn request_edid(&self, device: DeviceUid, output: bool) -> Result<(), ControlError> {
742 self.shared.command(move |state| {
743 let device = device_of(state, device)?;
744 Ok(Command::new(
745 Addressee::device(device),
746 op::DEV_EDID,
747 build_edid_request(device.uid, output),
748 ))
749 })
750 }
751
752 pub fn request_signal_status(&self, device: Option<DeviceUid>) -> Result<(), ControlError> {
759 let Some(device) = device else {
760 self.shared
761 .send(&Addressee::Broadcast, op::BAY_SIGNAL_STATUS, &[])?;
762 return Ok(());
763 };
764 self.shared.command(move |state| {
765 let device = device_of(state, device)?;
766 Ok(Command::new(
767 Addressee::device(device),
768 op::BAY_SIGNAL_STATUS,
769 build_target_only(device.uid),
770 ))
771 })
772 }
773
774 pub fn reboot(&self, device: DeviceUid) -> Result<(), ControlError> {
779 self.shared.command(move |state| {
780 let d = device_of(state, device)?;
781 Ok(Command::new(
782 Addressee::device(d),
783 op::SYS_REBOOT,
784 build_target_only(d.uid),
785 )
786 .then(move |state, _| {
787 if let Some(d) = state.device_mut(device) {
788 d.rebooting = true;
789 }
790 }))
791 })
792 }
793
794 pub fn send_monitoring_pulse(&self) -> Result<(), ControlError> {
797 self.shared
798 .send(&Addressee::Broadcast, op::SYS_MONITORING_PULSE, &[])?;
799 Ok(())
800 }
801
802 pub fn set_v2ip_auto_scaling(
815 &self,
816 device: DeviceUid,
817 enabled: bool,
818 ) -> Result<(), ControlError> {
819 let written = if enabled {
820 SCALING_FLAG_OPTIONS_VALID | SCALING_FLAG_AUTO_SCALING
821 } else {
822 SCALING_FLAG_OPTIONS_VALID
823 };
824 self.set_v2ip_scaling(device, MxrSignalType::NONE, 0, written, move |cached| {
825 V2ipScalingSettings {
826 flags: (cached.flags & !SCALING_FLAG_AUTO_SCALING)
827 | SCALING_FLAG_OPTIONS_VALID
828 | (written & SCALING_FLAG_AUTO_SCALING),
829 ..cached
830 }
831 })
832 }
833
834 pub fn set_v2ip_output_mode(
850 &self,
851 device: DeviceUid,
852 mode: V2ipOutputMode,
853 ) -> Result<(), ControlError> {
854 mode.validate().map_err(ControlError::InvalidRequest)?;
855 let signal = mode.to_signal_type();
856 let refresh = mode.refresh;
857 self.set_v2ip_scaling(
858 device,
859 signal,
860 refresh,
861 SCALING_FLAG_MODE_VALID,
862 move |cached| V2ipScalingSettings {
863 mode: signal,
864 refresh,
865 flags: cached.flags | SCALING_FLAG_MODE_VALID,
866 },
867 )
868 }
869
870 pub fn clear_v2ip_output_mode(&self, device: DeviceUid) -> Result<(), ControlError> {
881 self.set_v2ip_scaling(
885 device,
886 MxrSignalType::NONE,
887 0,
888 SCALING_FLAG_MODE_VALID,
889 |cached| V2ipScalingSettings {
890 mode: MxrSignalType::NONE,
891 refresh: 0,
892 flags: cached.flags & !SCALING_FLAG_MODE_VALID,
893 },
894 )
895 }
896
897 fn set_v2ip_scaling(
906 &self,
907 device: DeviceUid,
908 mode: MxrSignalType,
909 refresh: u16,
910 written: u8,
911 applied: impl FnOnce(V2ipScalingSettings) -> V2ipScalingSettings + Send + 'static,
912 ) -> Result<(), ControlError> {
913 self.shared.command(move |state| {
914 let d = device_of(state, device)?;
915 if !d.is_v2ip_sink() {
916 return Err(ControlError::Unsupported(
917 "scaling settings need a V2IP sink",
918 ));
919 }
920 Ok(Command::new(
921 Addressee::device(d),
922 op::V2IP_DEVICE_CFG,
923 build_v2ip_scaling(d.uid, mode, refresh, written),
924 )
925 .then(move |state, ev| {
926 if let Some(d) = state.device_mut(device) {
927 let cached = d.v2ip_scaling();
928 d.set_v2ip_scaling(applied(cached), ev);
929 }
930 }))
931 })
932 }
933
934 pub fn preview_video_wall(
943 &self,
944 sink: DeviceUid,
945 window: VideoWallWindow,
946 ) -> Result<(), ControlError> {
947 self.set_video_wall(sink, window, VideoWallOp::PREVIEW)
948 }
949
950 pub fn store_video_wall(
965 &self,
966 sink: DeviceUid,
967 window: VideoWallWindow,
968 ) -> Result<(), ControlError> {
969 self.set_video_wall(sink, window, VideoWallOp::STORE)
970 }
971
972 pub fn revert_video_wall(&self, sink: DeviceUid) -> Result<(), ControlError> {
977 self.set_video_wall(sink, VIDEO_WALL_CLEARED, VideoWallOp::REVERT)
978 }
979
980 fn set_video_wall(
994 &self,
995 sink: DeviceUid,
996 window: VideoWallWindow,
997 op: VideoWallOp,
998 ) -> Result<(), ControlError> {
999 if op != VideoWallOp::REVERT {
1000 window.validate().map_err(ControlError::InvalidRequest)?;
1001 }
1002 self.shared.command(move |state| {
1003 let device = device_of(state, sink)?;
1004 Ok(Command::new(
1005 Addressee::device(device),
1006 op::V2IP_VIDEO_WALL,
1007 build_video_wall(device.uid, window, op),
1008 ))
1009 })
1010 }
1011
1012 pub fn set_multiviewer_view_mode(
1016 &self,
1017 device: DeviceUid,
1018 mode: MultiviewerViewMode,
1019 ) -> Result<(), ControlError> {
1020 let mode = mv_setting(mode.to_wire(), 8, "the multiviewer has no such view mode")?;
1021 self.multiviewer(device, mv_sub::VIEW_MODE, &[mode])
1022 }
1023
1024 pub fn set_multiviewer_video_source(
1034 &self,
1035 device: DeviceUid,
1036 screen: u8,
1037 source: MultiviewerSource,
1038 ) -> Result<(), ControlError> {
1039 let source = source_index(source, "the source names no multiviewer input")?;
1040 self.shared.command(|state| {
1041 let target = multiviewer_of(state, device)?;
1042 let windows = target
1043 .multiviewer
1044 .as_ref()
1045 .and_then(MultiviewerStatus::window_count)
1046 .unwrap_or(1);
1047 if screen >= windows {
1048 return Err(ControlError::InvalidRequest(
1049 "the window is not one the multiviewer is showing",
1050 ));
1051 }
1052 Ok(mv_command(target, mv_sub::VIDEO_SOURCE, &[screen, source]))
1053 })
1054 }
1055
1056 pub fn set_multiviewer_audio_source(
1058 &self,
1059 device: DeviceUid,
1060 source: MultiviewerSource,
1061 ) -> Result<(), ControlError> {
1062 let source = source_index(source, "the audio source names no multiviewer input")?;
1063 self.multiviewer(device, mv_sub::AUDIO_SOURCE, &[source])
1064 }
1065
1066 pub fn set_multiviewer_audio_volume(
1074 &self,
1075 device: DeviceUid,
1076 volume: u8,
1077 muted: bool,
1078 ) -> Result<(), ControlError> {
1079 if volume > 100 {
1080 return Err(ControlError::InvalidRequest(
1081 "a multiviewer volume is a percentage",
1082 ));
1083 }
1084 self.multiviewer(device, mv_sub::AUDIO_VOLUME, &[volume, u8::from(muted)])
1085 }
1086
1087 pub fn set_multiviewer_edid_template(
1089 &self,
1090 device: DeviceUid,
1091 template: MultiviewerEdidTemplate,
1092 ) -> Result<(), ControlError> {
1093 let template = mv_setting(
1094 template.to_wire(),
1095 19,
1096 "the multiviewer has no such EDID template",
1097 )?;
1098 self.multiviewer(device, mv_sub::EDID_TEMPLATE, &[template])
1099 }
1100
1101 pub fn set_multiviewer_remote_control(
1103 &self,
1104 device: DeviceUid,
1105 source: MultiviewerSource,
1106 ) -> Result<(), ControlError> {
1107 let source = source_index(
1108 source,
1109 "the remote-control source names no multiviewer input",
1110 )?;
1111 self.multiviewer(device, mv_sub::ROUTE_RC, &[source])
1112 }
1113
1114 pub fn set_multiviewer_pip_size(
1116 &self,
1117 device: DeviceUid,
1118 size: MultiviewerPipSize,
1119 ) -> Result<(), ControlError> {
1120 let size = mv_setting(
1121 size.to_wire(),
1122 3,
1123 "the multiviewer has no such picture-in-picture size",
1124 )?;
1125 self.multiviewer(device, mv_sub::PIP_SIZE, &[size])
1126 }
1127
1128 pub fn set_multiviewer_pip_position(
1130 &self,
1131 device: DeviceUid,
1132 position: MultiviewerPipPosition,
1133 ) -> Result<(), ControlError> {
1134 let position = mv_setting(
1135 position.to_wire(),
1136 4,
1137 "the multiviewer has no such picture-in-picture position",
1138 )?;
1139 self.multiviewer(device, mv_sub::PIP_POSITION, &[position])
1140 }
1141
1142 pub fn set_multiviewer_aspect_ratio(
1144 &self,
1145 device: DeviceUid,
1146 aspect: MultiviewerAspectRatio,
1147 ) -> Result<(), ControlError> {
1148 let aspect = mv_setting(
1149 aspect.to_wire(),
1150 2,
1151 "the multiviewer has no such aspect ratio",
1152 )?;
1153 self.multiviewer(device, mv_sub::ASPECT, &[aspect])
1154 }
1155
1156 pub fn set_multiviewer_auto_switch(
1158 &self,
1159 device: DeviceUid,
1160 enable: bool,
1161 ) -> Result<(), ControlError> {
1162 self.multiviewer(device, mv_sub::AUTO_SWITCH, &[u8::from(enable)])
1163 }
1164
1165 pub fn set_multiviewer_output_mode(
1167 &self,
1168 device: DeviceUid,
1169 mode: MultiviewerOutputMode,
1170 ) -> Result<(), ControlError> {
1171 let mode = mv_setting(
1172 mode.to_wire(),
1173 14,
1174 "the multiviewer has no such output mode",
1175 )?;
1176 self.multiviewer(device, mv_sub::OUTPUT_MODE, &[mode])
1177 }
1178
1179 pub fn set_multiviewer_output_itc(
1181 &self,
1182 device: DeviceUid,
1183 mode: MultiviewerItcMode,
1184 ) -> Result<(), ControlError> {
1185 let mode = mv_setting(
1186 mode.to_wire(),
1187 2,
1188 "the multiviewer has no such IT-content mode",
1189 )?;
1190 self.multiviewer(device, mv_sub::OUTPUT_ITC_MODE, &[mode])
1191 }
1192
1193 pub fn set_multiviewer_hdcp_mode(
1195 &self,
1196 device: DeviceUid,
1197 mode: MultiviewerHdcpMode,
1198 ) -> Result<(), ControlError> {
1199 let mode = mv_setting(mode.to_wire(), 3, "the multiviewer has no such HDCP mode")?;
1200 self.multiviewer(device, mv_sub::HDCP_MODE, &[mode])
1201 }
1202
1203 pub fn set_multiviewer_input_source(
1217 &self,
1218 device: DeviceUid,
1219 input: u8,
1220 source: DeviceUid,
1221 ) -> Result<(), ControlError> {
1222 if usize::from(input) >= MULTIVIEWER_INPUTS {
1223 return Err(ControlError::InvalidRequest(
1224 "the multiviewer has no such input",
1225 ));
1226 }
1227 let mut args = Vec::with_capacity(24);
1228 args.extend_from_slice(source.as_bytes());
1229 args.push(input);
1230 args.extend_from_slice(&[0; 7]);
1233 self.multiviewer(device, mv_sub::CONFIG_SOURCE, &args)
1234 }
1235
1236 pub fn multiviewer_auto_route(&self, device: DeviceUid) -> Result<(), ControlError> {
1238 self.multiviewer(device, mv_sub::AUTO_ROUTE, &[])
1239 }
1240
1241 fn multiviewer(&self, device: DeviceUid, sub: u8, args: &[u8]) -> Result<(), ControlError> {
1242 self.shared
1243 .command(|state| Ok(mv_command(multiviewer_of(state, device)?, sub, args)))
1244 }
1245}