1use std::str::FromStr;
12
13use futures::FutureExt;
14use pinnacle_api_defs::pinnacle::{
15 output::{
16 self,
17 v1::{
18 FocusRequest, GetEnabledRequest, GetFocusStackWindowIdsRequest, GetFocusedRequest,
19 GetInfoRequest, GetLocRequest, GetLogicalSizeRequest, GetModesRequest,
20 GetOutputsInDirRequest, GetPhysicalSizeRequest, GetPoweredRequest, GetRequest,
21 GetScaleRequest, GetTagIdsRequest, GetTransformRequest, SetLocRequest, SetModeRequest,
22 SetModelineRequest, SetPoweredRequest, SetScaleRequest, SetTransformRequest,
23 SetVrrRequest,
24 },
25 },
26 util::v1::{AbsOrRel, SetOrToggle},
27};
28
29use crate::{
30 BlockOnTokio,
31 client::Client,
32 signal::{OutputSignal, SignalHandle},
33 tag::TagHandle,
34 util::{Batch, Direction, Point, Size},
35 window::WindowHandle,
36};
37
38pub fn get_all() -> impl Iterator<Item = OutputHandle> {
49 get_all_async().block_on_tokio()
50}
51
52pub async fn get_all_async() -> impl Iterator<Item = OutputHandle> {
54 Client::output()
55 .get(GetRequest {})
56 .await
57 .unwrap()
58 .into_inner()
59 .output_names
60 .into_iter()
61 .map(|name| OutputHandle { name })
62}
63
64pub fn get_all_enabled() -> impl Iterator<Item = OutputHandle> {
77 get_all_enabled_async().block_on_tokio()
78}
79
80pub async fn get_all_enabled_async() -> impl Iterator<Item = OutputHandle> {
82 get_all_async()
83 .await
84 .batch_filter(|op| op.enabled_async().boxed(), |enabled| enabled)
85}
86
87pub fn get_by_name(name: impl ToString) -> Option<OutputHandle> {
91 get_by_name_async(name).block_on_tokio()
92}
93
94pub async fn get_by_name_async(name: impl ToString) -> Option<OutputHandle> {
96 get_all_async().await.find(|op| op.name == name.to_string())
97}
98
99pub fn get_focused() -> Option<OutputHandle> {
103 get_focused_async().block_on_tokio()
104}
105
106pub async fn get_focused_async() -> Option<OutputHandle> {
108 get_all_async()
109 .await
110 .batch_find(|op| op.focused_async().boxed(), |focused| *focused)
111}
112
113pub fn for_each_output(mut for_each: impl FnMut(&OutputHandle) + Send + 'static) {
135 for output in get_all() {
136 for_each(&output);
137 }
138
139 Client::signal_state()
140 .output_connect
141 .add_callback(Box::new(for_each));
142}
143
144pub fn connect_signal(signal: OutputSignal) -> SignalHandle {
156 let mut signal_state = Client::signal_state();
157
158 match signal {
159 OutputSignal::Connect(f) => signal_state.output_connect.add_callback(f),
160 OutputSignal::Disconnect(f) => signal_state.output_disconnect.add_callback(f),
161 OutputSignal::Resize(f) => signal_state.output_resize.add_callback(f),
162 OutputSignal::Move(f) => signal_state.output_move.add_callback(f),
163 OutputSignal::PointerEnter(f) => signal_state.output_pointer_enter.add_callback(f),
164 OutputSignal::PointerLeave(f) => signal_state.output_pointer_leave.add_callback(f),
165 OutputSignal::Focused(f) => signal_state.output_focused.add_callback(f),
166 }
167}
168
169#[derive(Clone, Debug, PartialEq, Eq, Hash)]
173pub struct OutputHandle {
174 pub(crate) name: String,
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
179pub enum Alignment {
180 TopAlignLeft,
182 TopAlignCenter,
184 TopAlignRight,
186 BottomAlignLeft,
188 BottomAlignCenter,
190 BottomAlignRight,
192 LeftAlignTop,
194 LeftAlignCenter,
196 LeftAlignBottom,
198 RightAlignTop,
200 RightAlignCenter,
202 RightAlignBottom,
204}
205
206#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
210#[repr(i32)]
211pub enum Transform {
212 #[default]
214 Normal,
215 _90,
217 _180,
219 _270,
221 Flipped,
223 Flipped90,
225 Flipped180,
227 Flipped270,
229}
230
231impl TryFrom<output::v1::Transform> for Transform {
232 type Error = ();
233
234 fn try_from(value: output::v1::Transform) -> Result<Self, Self::Error> {
235 match value {
236 output::v1::Transform::Unspecified => Err(()),
237 output::v1::Transform::Normal => Ok(Transform::Normal),
238 output::v1::Transform::Transform90 => Ok(Transform::_90),
239 output::v1::Transform::Transform180 => Ok(Transform::_180),
240 output::v1::Transform::Transform270 => Ok(Transform::_270),
241 output::v1::Transform::Flipped => Ok(Transform::Flipped),
242 output::v1::Transform::Flipped90 => Ok(Transform::Flipped90),
243 output::v1::Transform::Flipped180 => Ok(Transform::Flipped180),
244 output::v1::Transform::Flipped270 => Ok(Transform::Flipped270),
245 }
246 }
247}
248
249impl From<Transform> for output::v1::Transform {
250 fn from(value: Transform) -> Self {
251 match value {
252 Transform::Normal => output::v1::Transform::Normal,
253 Transform::_90 => output::v1::Transform::Transform90,
254 Transform::_180 => output::v1::Transform::Transform180,
255 Transform::_270 => output::v1::Transform::Transform270,
256 Transform::Flipped => output::v1::Transform::Flipped,
257 Transform::Flipped90 => output::v1::Transform::Flipped90,
258 Transform::Flipped180 => output::v1::Transform::Flipped180,
259 Transform::Flipped270 => output::v1::Transform::Flipped270,
260 }
261 }
262}
263
264#[doc(alias = "AdaptiveSync")]
266#[doc(alias = "VariableRefreshRate")]
267#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
268pub enum Vrr {
269 #[default]
271 Off,
272 AlwaysOn,
274 OnDemand,
277}
278
279impl OutputHandle {
280 pub fn from_name(name: impl ToString) -> Self {
282 Self {
283 name: name.to_string(),
284 }
285 }
286
287 pub fn set_loc(&self, x: i32, y: i32) {
322 Client::output()
323 .set_loc(SetLocRequest {
324 output_name: self.name(),
325 x,
326 y,
327 })
328 .block_on_tokio()
329 .unwrap();
330 }
331
332 pub fn set_loc_adj_to(&self, other: &OutputHandle, alignment: Alignment) {
373 let (self_size, other_loc, other_size) = async {
374 tokio::join!(
375 self.logical_size_async(),
376 other.loc_async(),
377 other.logical_size_async()
378 )
379 }
380 .block_on_tokio();
381
382 let attempt_set_loc = || -> Option<()> {
384 let other_x = other_loc?.x;
385 let other_y = other_loc?.y;
386 let other_width = other_size?.w as i32;
387 let other_height = other_size?.h as i32;
388
389 let self_width = self_size?.w as i32;
390 let self_height = self_size?.h as i32;
391
392 use Alignment::*;
393
394 let x: i32;
395 let y: i32;
396
397 if let TopAlignLeft | TopAlignCenter | TopAlignRight | BottomAlignLeft
398 | BottomAlignCenter | BottomAlignRight = alignment
399 {
400 if let TopAlignLeft | TopAlignCenter | TopAlignRight = alignment {
401 y = other_y - self_height;
402 } else {
403 y = other_y + other_height;
405 }
406
407 match alignment {
408 TopAlignLeft | BottomAlignLeft => x = other_x,
409 TopAlignCenter | BottomAlignCenter => {
410 x = other_x + (other_width - self_width) / 2;
411 }
412 TopAlignRight | BottomAlignRight => x = other_x + (other_width - self_width),
413 _ => unreachable!(),
414 }
415 } else {
416 if let LeftAlignTop | LeftAlignCenter | LeftAlignBottom = alignment {
417 x = other_x - self_width;
418 } else {
419 x = other_x + other_width;
420 }
421
422 match alignment {
423 LeftAlignTop | RightAlignTop => y = other_y,
424 LeftAlignCenter | RightAlignCenter => {
425 y = other_y + (other_height - self_height) / 2;
426 }
427 LeftAlignBottom | RightAlignBottom => {
428 y = other_y + (other_height - self_height);
429 }
430 _ => unreachable!(),
431 }
432 }
433
434 self.set_loc(x, y);
435
436 Some(())
437 };
438
439 attempt_set_loc();
440 }
441
442 pub fn set_mode(&self, width: u32, height: u32, refresh_rate_mhz: impl Into<Option<u32>>) {
464 Client::output()
465 .set_mode(SetModeRequest {
466 output_name: self.name(),
467 size: Some(pinnacle_api_defs::pinnacle::util::v1::Size { width, height }),
468 refresh_rate_mhz: refresh_rate_mhz.into(),
469 custom: false,
470 })
471 .block_on_tokio()
472 .unwrap();
473 }
474
475 pub fn set_custom_mode(
494 &self,
495 width: u32,
496 height: u32,
497 refresh_rate_mhz: impl Into<Option<u32>>,
498 ) {
499 Client::output()
500 .set_mode(SetModeRequest {
501 output_name: self.name(),
502 size: Some(pinnacle_api_defs::pinnacle::util::v1::Size { width, height }),
503 refresh_rate_mhz: refresh_rate_mhz.into(),
504 custom: true,
505 })
506 .block_on_tokio()
507 .unwrap();
508 }
509
510 pub fn set_modeline(&self, modeline: Modeline) {
528 Client::output()
529 .set_modeline(SetModelineRequest {
530 output_name: self.name(),
531 modeline: Some(modeline.into()),
532 })
533 .block_on_tokio()
534 .unwrap();
535 }
536
537 pub fn set_scale(&self, scale: f32) {
539 Client::output()
540 .set_scale(SetScaleRequest {
541 output_name: self.name(),
542 scale,
543 abs_or_rel: AbsOrRel::Absolute.into(),
544 })
545 .block_on_tokio()
546 .unwrap();
547 }
548
549 pub fn change_scale(&self, change_by: f32) {
562 Client::output()
563 .set_scale(SetScaleRequest {
564 output_name: self.name(),
565 scale: change_by,
566 abs_or_rel: AbsOrRel::Relative.into(),
567 })
568 .block_on_tokio()
569 .unwrap();
570 }
571
572 pub fn set_transform(&self, transform: Transform) {
586 Client::output()
587 .set_transform(SetTransformRequest {
588 output_name: self.name(),
589 transform: output::v1::Transform::from(transform).into(),
590 })
591 .block_on_tokio()
592 .unwrap();
593 }
594
595 pub fn set_powered(&self, powered: bool) {
600 Client::output()
601 .set_powered(SetPoweredRequest {
602 output_name: self.name(),
603 set_or_toggle: match powered {
604 true => SetOrToggle::Set,
605 false => SetOrToggle::Unset,
606 }
607 .into(),
608 })
609 .block_on_tokio()
610 .unwrap();
611 }
612
613 pub fn toggle_powered(&self) {
618 Client::output()
619 .set_powered(SetPoweredRequest {
620 output_name: self.name(),
621 set_or_toggle: SetOrToggle::Toggle.into(),
622 })
623 .block_on_tokio()
624 .unwrap();
625 }
626
627 #[doc(alias = "set_adaptive_sync")]
631 #[doc(alias = "set_variable_refresh_rate")]
632 pub fn set_vrr(&self, vrr: Vrr) {
633 Client::output()
634 .set_vrr(SetVrrRequest {
635 output_name: self.name(),
636 vrr: match vrr {
637 Vrr::Off => output::v1::Vrr::Off,
638 Vrr::AlwaysOn => output::v1::Vrr::AlwaysOn,
639 Vrr::OnDemand => output::v1::Vrr::OnDemand,
640 } as i32,
641 })
642 .block_on_tokio()
643 .unwrap();
644 }
645
646 pub fn focus(&self) {
648 Client::output()
649 .focus(FocusRequest {
650 output_name: self.name(),
651 })
652 .block_on_tokio()
653 .unwrap();
654 }
655
656 pub fn make(&self) -> String {
658 self.make_async().block_on_tokio()
659 }
660
661 pub async fn make_async(&self) -> String {
663 Client::output()
664 .get_info(GetInfoRequest {
665 output_name: self.name(),
666 })
667 .await
668 .unwrap()
669 .into_inner()
670 .make
671 }
672
673 pub fn model(&self) -> String {
675 self.model_async().block_on_tokio()
676 }
677
678 pub async fn model_async(&self) -> String {
680 Client::output()
681 .get_info(GetInfoRequest {
682 output_name: self.name(),
683 })
684 .await
685 .unwrap()
686 .into_inner()
687 .model
688 }
689
690 pub fn serial(&self) -> String {
692 self.serial_async().block_on_tokio()
693 }
694
695 pub async fn serial_async(&self) -> String {
697 Client::output()
698 .get_info(GetInfoRequest {
699 output_name: self.name(),
700 })
701 .await
702 .unwrap()
703 .into_inner()
704 .serial
705 }
706
707 pub fn loc(&self) -> Option<Point> {
711 self.loc_async().block_on_tokio()
712 }
713
714 pub async fn loc_async(&self) -> Option<Point> {
716 Client::output()
717 .get_loc(GetLocRequest {
718 output_name: self.name(),
719 })
720 .await
721 .unwrap()
722 .into_inner()
723 .loc
724 .map(|loc| Point { x: loc.x, y: loc.y })
725 }
726
727 pub fn logical_size(&self) -> Option<Size> {
736 self.logical_size_async().block_on_tokio()
737 }
738
739 pub async fn logical_size_async(&self) -> Option<Size> {
741 Client::output()
742 .get_logical_size(GetLogicalSizeRequest {
743 output_name: self.name(),
744 })
745 .await
746 .unwrap()
747 .into_inner()
748 .logical_size
749 .map(|size| Size {
750 w: size.width,
751 h: size.height,
752 })
753 }
754
755 pub fn current_mode(&self) -> Option<Mode> {
759 self.current_mode_async().block_on_tokio()
760 }
761
762 pub async fn current_mode_async(&self) -> Option<Mode> {
764 Client::output()
765 .get_modes(GetModesRequest {
766 output_name: self.name(),
767 })
768 .await
769 .unwrap()
770 .into_inner()
771 .current_mode
772 .map(|mode| Mode {
773 size: Size {
774 w: mode.size.expect("mode should have a size").width,
775 h: mode.size.expect("mode should have a size").height,
776 },
777 refresh_rate_mhz: mode.refresh_rate_mhz,
778 })
779 }
780
781 pub fn preferred_mode(&self) -> Option<Mode> {
785 self.preferred_mode_async().block_on_tokio()
786 }
787
788 pub async fn preferred_mode_async(&self) -> Option<Mode> {
790 Client::output()
791 .get_modes(GetModesRequest {
792 output_name: self.name(),
793 })
794 .await
795 .unwrap()
796 .into_inner()
797 .preferred_mode
798 .map(|mode| Mode {
799 size: Size {
800 w: mode.size.expect("mode should have a size").width,
801 h: mode.size.expect("mode should have a size").height,
802 },
803 refresh_rate_mhz: mode.refresh_rate_mhz,
804 })
805 }
806
807 pub fn modes(&self) -> impl Iterator<Item = Mode> + use<> {
809 self.modes_async().block_on_tokio()
810 }
811
812 pub async fn modes_async(&self) -> impl Iterator<Item = Mode> + use<> {
814 Client::output()
815 .get_modes(GetModesRequest {
816 output_name: self.name(),
817 })
818 .await
819 .unwrap()
820 .into_inner()
821 .modes
822 .into_iter()
823 .map(|mode| Mode {
824 size: Size {
825 w: mode.size.expect("mode should have a size").width,
826 h: mode.size.expect("mode should have a size").height,
827 },
828 refresh_rate_mhz: mode.refresh_rate_mhz,
829 })
830 }
831
832 pub fn physical_size(&self) -> Size {
836 self.physical_size_async().block_on_tokio()
837 }
838
839 pub async fn physical_size_async(&self) -> Size {
841 Client::output()
842 .get_physical_size(GetPhysicalSizeRequest {
843 output_name: self.name(),
844 })
845 .await
846 .unwrap()
847 .into_inner()
848 .physical_size
849 .map(|size| Size {
850 w: size.width,
851 h: size.height,
852 })
853 .unwrap_or_default()
854 }
855
856 pub fn focused(&self) -> bool {
860 self.focused_async().block_on_tokio()
861 }
862
863 pub async fn focused_async(&self) -> bool {
865 Client::output()
866 .get_focused(GetFocusedRequest {
867 output_name: self.name(),
868 })
869 .await
870 .unwrap()
871 .into_inner()
872 .focused
873 }
874
875 pub fn tags(&self) -> impl Iterator<Item = TagHandle> + use<> {
877 self.tags_async().block_on_tokio()
878 }
879
880 pub async fn tags_async(&self) -> impl Iterator<Item = TagHandle> + use<> {
882 Client::output()
883 .get_tag_ids(GetTagIdsRequest {
884 output_name: self.name(),
885 })
886 .await
887 .unwrap()
888 .into_inner()
889 .tag_ids
890 .into_iter()
891 .map(|id| TagHandle { id })
892 }
893
894 pub fn active_tags(&self) -> impl Iterator<Item = TagHandle> + use<> {
896 self.active_tags_async().block_on_tokio()
897 }
898
899 pub async fn active_tags_async(&self) -> impl Iterator<Item = TagHandle> + use<> {
901 self.tags_async()
902 .await
903 .batch_filter(|tag| tag.active_async().boxed(), |is_active| is_active)
904 }
905
906 pub fn inactive_tags(&self) -> impl Iterator<Item = TagHandle> + use<> {
908 self.inactive_tags_async().block_on_tokio()
909 }
910
911 pub async fn inactive_tags_async(&self) -> impl Iterator<Item = TagHandle> + use<> {
913 self.tags_async()
914 .await
915 .batch_filter(|tag| tag.active_async().boxed(), |is_active| !is_active)
916 }
917
918 pub fn scale(&self) -> f32 {
920 self.scale_async().block_on_tokio()
921 }
922
923 pub async fn scale_async(&self) -> f32 {
925 Client::output()
926 .get_scale(GetScaleRequest {
927 output_name: self.name(),
928 })
929 .await
930 .unwrap()
931 .into_inner()
932 .scale
933 }
934
935 pub fn transform(&self) -> Transform {
937 self.transform_async().block_on_tokio()
938 }
939
940 pub async fn transform_async(&self) -> Transform {
942 Client::output()
943 .get_transform(GetTransformRequest {
944 output_name: self.name(),
945 })
946 .await
947 .unwrap()
948 .into_inner()
949 .transform()
950 .try_into()
951 .unwrap_or_default()
952 }
953
954 pub fn keyboard_focus_stack(&self) -> impl Iterator<Item = WindowHandle> + use<> {
964 self.keyboard_focus_stack_async().block_on_tokio()
965 }
966
967 pub async fn keyboard_focus_stack_async(&self) -> impl Iterator<Item = WindowHandle> + use<> {
969 Client::output()
970 .get_focus_stack_window_ids(GetFocusStackWindowIdsRequest {
971 output_name: self.name(),
972 })
973 .await
974 .unwrap()
975 .into_inner()
976 .window_ids
977 .into_iter()
978 .map(|id| WindowHandle { id })
979 }
980
981 pub fn keyboard_focus_stack_visible(&self) -> impl Iterator<Item = WindowHandle> + use<> {
990 self.keyboard_focus_stack_visible_async().block_on_tokio()
991 }
992
993 pub async fn keyboard_focus_stack_visible_async(
995 &self,
996 ) -> impl Iterator<Item = WindowHandle> + use<> {
997 self.keyboard_focus_stack_async()
998 .await
999 .batch_filter(|win| win.is_on_active_tag_async().boxed(), |is_on| is_on)
1000 }
1001
1002 pub fn enabled(&self) -> bool {
1004 self.enabled_async().block_on_tokio()
1005 }
1006
1007 pub async fn enabled_async(&self) -> bool {
1009 Client::output()
1010 .get_enabled(GetEnabledRequest {
1011 output_name: self.name(),
1012 })
1013 .await
1014 .unwrap()
1015 .into_inner()
1016 .enabled
1017 }
1018
1019 pub fn powered(&self) -> bool {
1023 self.powered_async().block_on_tokio()
1024 }
1025
1026 pub async fn powered_async(&self) -> bool {
1028 Client::output()
1029 .get_powered(GetPoweredRequest {
1030 output_name: self.name(),
1031 })
1032 .await
1033 .unwrap()
1034 .into_inner()
1035 .powered
1036 }
1037
1038 pub fn in_direction(&self, direction: Direction) -> impl Iterator<Item = OutputHandle> + use<> {
1040 self.in_direction_async(direction).block_on_tokio()
1041 }
1042
1043 pub async fn in_direction_async(
1045 &self,
1046 direction: Direction,
1047 ) -> impl Iterator<Item = OutputHandle> + use<> {
1048 let output_name = self.name();
1049
1050 let mut request = GetOutputsInDirRequest {
1051 output_name,
1052 dir: Default::default(),
1053 };
1054
1055 request.set_dir(match direction {
1056 Direction::Left => pinnacle_api_defs::pinnacle::util::v1::Dir::Left,
1057 Direction::Right => pinnacle_api_defs::pinnacle::util::v1::Dir::Right,
1058 Direction::Up => pinnacle_api_defs::pinnacle::util::v1::Dir::Up,
1059 Direction::Down => pinnacle_api_defs::pinnacle::util::v1::Dir::Down,
1060 });
1061
1062 let response = Client::output()
1063 .get_outputs_in_dir(request)
1064 .await
1065 .unwrap()
1066 .into_inner();
1067
1068 response
1069 .output_names
1070 .into_iter()
1071 .map(OutputHandle::from_name)
1072 }
1073
1074 pub fn name(&self) -> String {
1076 self.name.to_string()
1077 }
1078}
1079
1080#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
1082pub struct Mode {
1083 pub size: Size,
1085 pub refresh_rate_mhz: u32,
1089}
1090
1091#[allow(missing_docs)]
1093#[derive(Copy, Clone, Debug, PartialEq, Default)]
1094pub struct Modeline {
1095 pub clock: f32,
1096 pub hdisplay: u32,
1097 pub hsync_start: u32,
1098 pub hsync_end: u32,
1099 pub htotal: u32,
1100 pub vdisplay: u32,
1101 pub vsync_start: u32,
1102 pub vsync_end: u32,
1103 pub vtotal: u32,
1104 pub hsync: bool,
1105 pub vsync: bool,
1106}
1107
1108impl From<Modeline> for output::v1::Modeline {
1109 fn from(modeline: Modeline) -> Self {
1110 output::v1::Modeline {
1111 clock: modeline.clock,
1112 hdisplay: modeline.hdisplay,
1113 hsync_start: modeline.hsync_start,
1114 hsync_end: modeline.hsync_end,
1115 htotal: modeline.htotal,
1116 vdisplay: modeline.vdisplay,
1117 vsync_start: modeline.vsync_start,
1118 vsync_end: modeline.vsync_end,
1119 vtotal: modeline.vtotal,
1120 hsync: modeline.hsync,
1121 vsync: modeline.vsync,
1122 }
1123 }
1124}
1125
1126#[derive(Debug)]
1128pub struct ParseModelineError(ParseModelineErrorKind);
1129
1130#[derive(Debug)]
1131enum ParseModelineErrorKind {
1132 NoClock,
1133 InvalidClock,
1134 NoHdisplay,
1135 InvalidHdisplay,
1136 NoHsyncStart,
1137 InvalidHsyncStart,
1138 NoHsyncEnd,
1139 InvalidHsyncEnd,
1140 NoHtotal,
1141 InvalidHtotal,
1142 NoVdisplay,
1143 InvalidVdisplay,
1144 NoVsyncStart,
1145 InvalidVsyncStart,
1146 NoVsyncEnd,
1147 InvalidVsyncEnd,
1148 NoVtotal,
1149 InvalidVtotal,
1150 NoHsync,
1151 InvalidHsync,
1152 NoVsync,
1153 InvalidVsync,
1154}
1155
1156impl std::fmt::Display for ParseModelineError {
1157 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1158 std::fmt::Debug::fmt(&self.0, f)
1159 }
1160}
1161
1162impl From<ParseModelineErrorKind> for ParseModelineError {
1163 fn from(value: ParseModelineErrorKind) -> Self {
1164 Self(value)
1165 }
1166}
1167
1168impl FromStr for Modeline {
1169 type Err = ParseModelineError;
1170
1171 fn from_str(s: &str) -> Result<Self, Self::Err> {
1173 let mut args = s.split_whitespace();
1174
1175 let clock = args
1176 .next()
1177 .ok_or(ParseModelineErrorKind::NoClock)?
1178 .parse()
1179 .map_err(|_| ParseModelineErrorKind::InvalidClock)?;
1180 let hdisplay = args
1181 .next()
1182 .ok_or(ParseModelineErrorKind::NoHdisplay)?
1183 .parse()
1184 .map_err(|_| ParseModelineErrorKind::InvalidHdisplay)?;
1185 let hsync_start = args
1186 .next()
1187 .ok_or(ParseModelineErrorKind::NoHsyncStart)?
1188 .parse()
1189 .map_err(|_| ParseModelineErrorKind::InvalidHsyncStart)?;
1190 let hsync_end = args
1191 .next()
1192 .ok_or(ParseModelineErrorKind::NoHsyncEnd)?
1193 .parse()
1194 .map_err(|_| ParseModelineErrorKind::InvalidHsyncEnd)?;
1195 let htotal = args
1196 .next()
1197 .ok_or(ParseModelineErrorKind::NoHtotal)?
1198 .parse()
1199 .map_err(|_| ParseModelineErrorKind::InvalidHtotal)?;
1200 let vdisplay = args
1201 .next()
1202 .ok_or(ParseModelineErrorKind::NoVdisplay)?
1203 .parse()
1204 .map_err(|_| ParseModelineErrorKind::InvalidVdisplay)?;
1205 let vsync_start = args
1206 .next()
1207 .ok_or(ParseModelineErrorKind::NoVsyncStart)?
1208 .parse()
1209 .map_err(|_| ParseModelineErrorKind::InvalidVsyncStart)?;
1210 let vsync_end = args
1211 .next()
1212 .ok_or(ParseModelineErrorKind::NoVsyncEnd)?
1213 .parse()
1214 .map_err(|_| ParseModelineErrorKind::InvalidVsyncEnd)?;
1215 let vtotal = args
1216 .next()
1217 .ok_or(ParseModelineErrorKind::NoVtotal)?
1218 .parse()
1219 .map_err(|_| ParseModelineErrorKind::InvalidVtotal)?;
1220
1221 let hsync = match args
1222 .next()
1223 .ok_or(ParseModelineErrorKind::NoHsync)?
1224 .to_lowercase()
1225 .as_str()
1226 {
1227 "+hsync" => true,
1228 "-hsync" => false,
1229 _ => Err(ParseModelineErrorKind::InvalidHsync)?,
1230 };
1231 let vsync = match args
1232 .next()
1233 .ok_or(ParseModelineErrorKind::NoVsync)?
1234 .to_lowercase()
1235 .as_str()
1236 {
1237 "+vsync" => true,
1238 "-vsync" => false,
1239 _ => Err(ParseModelineErrorKind::InvalidVsync)?,
1240 };
1241
1242 Ok(Modeline {
1243 clock,
1244 hdisplay,
1245 hsync_start,
1246 hsync_end,
1247 htotal,
1248 vdisplay,
1249 vsync_start,
1250 vsync_end,
1251 vtotal,
1252 hsync,
1253 vsync,
1254 })
1255 }
1256}