@@ -2,81 +2,110 @@ use std::path::Path;
22
33use cosmic_randr_shell:: { AdaptiveSyncState , List } ;
44
5+ use crate :: EdidProduct ;
56use crate :: output:: comp:: OutputState ;
67
8+ /// Represents a currently connected output from the Wayland compositor
9+ #[ derive( Debug , Clone ) ]
10+ pub struct CurrentOutput {
11+ /// Connector name (e.g., "DP-5", "eDP-1")
12+ pub connector : String ,
13+ pub make : String ,
14+ pub model : String ,
15+ /// EDID product info for precise matching
16+ pub edid : Option < EdidProduct > ,
17+ }
18+
719pub struct CompList {
820 infos : Vec < super :: comp:: OutputInfo > ,
921 outputs : Vec < super :: comp:: OutputConfig > ,
1022}
1123
24+ /// Convert a saved OutputConfig to a randr Output, using the provided connector name
25+ fn config_to_randr_output (
26+ info : & super :: comp:: OutputInfo ,
27+ output : & super :: comp:: OutputConfig ,
28+ connector : String ,
29+ modes : & mut slotmap:: SlotMap < cosmic_randr_shell:: ModeKey , cosmic_randr_shell:: Mode > ,
30+ ) -> cosmic_randr_shell:: Output {
31+ let current = modes. insert ( cosmic_randr_shell:: Mode {
32+ size : ( output. mode . 0 . 0 as u32 , output. mode . 0 . 1 as u32 ) ,
33+ refresh_rate : output. mode . 1 . unwrap_or_default ( ) ,
34+ preferred : false ,
35+ } ) ;
36+
37+ cosmic_randr_shell:: Output {
38+ serial_number : info
39+ . edid
40+ . as_ref ( )
41+ . and_then ( |edid| edid. serial . map ( |s| s. to_string ( ) ) )
42+ . unwrap_or_default ( ) ,
43+ name : connector,
44+ enabled : !matches ! ( output. enabled, OutputState :: Disabled ) ,
45+ mirroring : match & output. enabled {
46+ OutputState :: Mirroring ( m) => Some ( m. clone ( ) ) ,
47+ _ => None ,
48+ } ,
49+ make : Some ( info. make . clone ( ) ) . filter ( |make| make != "Unknown" ) ,
50+ model : if info. model . as_str ( ) == "Unknown" {
51+ String :: new ( )
52+ } else {
53+ info. model . clone ( )
54+ } ,
55+ position : ( output. position . 0 as i32 , output. position . 1 as i32 ) ,
56+ scale : output. scale ,
57+ transform : Some ( match output. transform {
58+ crate :: output:: comp:: TransformDef :: Normal => cosmic_randr_shell:: Transform :: Normal ,
59+ crate :: output:: comp:: TransformDef :: _90 => cosmic_randr_shell:: Transform :: Rotate90 ,
60+ crate :: output:: comp:: TransformDef :: _180 => cosmic_randr_shell:: Transform :: Rotate180 ,
61+ crate :: output:: comp:: TransformDef :: _270 => cosmic_randr_shell:: Transform :: Rotate270 ,
62+ crate :: output:: comp:: TransformDef :: Flipped => cosmic_randr_shell:: Transform :: Flipped ,
63+ crate :: output:: comp:: TransformDef :: Flipped90 => {
64+ cosmic_randr_shell:: Transform :: Flipped90
65+ }
66+ crate :: output:: comp:: TransformDef :: Flipped180 => {
67+ cosmic_randr_shell:: Transform :: Flipped180
68+ }
69+ crate :: output:: comp:: TransformDef :: Flipped270 => {
70+ cosmic_randr_shell:: Transform :: Flipped270
71+ }
72+ } ) ,
73+ modes : vec ! [ current] ,
74+ current : Some ( current) ,
75+ adaptive_sync : Some ( match output. vrr {
76+ crate :: output:: comp:: AdaptiveSync :: Enabled => AdaptiveSyncState :: Auto ,
77+ crate :: output:: comp:: AdaptiveSync :: Disabled => AdaptiveSyncState :: Disabled ,
78+ crate :: output:: comp:: AdaptiveSync :: Force => AdaptiveSyncState :: Always ,
79+ } ) ,
80+ xwayland_primary : Some ( output. xwayland_primary ) ,
81+ physical : ( 0 , 0 ) ,
82+ adaptive_sync_availability : None ,
83+ }
84+ }
85+
86+ /// Check if a saved OutputInfo matches a current output by EDID or make/model
87+ fn info_matches_output ( info : & super :: comp:: OutputInfo , current : & CurrentOutput ) -> bool {
88+ // First try to match by EDID (most precise)
89+ if let ( Some ( saved_edid) , Some ( current_edid) ) = ( & info. edid , & current. edid ) {
90+ return saved_edid == current_edid;
91+ }
92+
93+ // Fall back to make/model matching
94+ info. make == current. make && info. model == current. model
95+ }
96+
1297impl From < CompList > for cosmic_randr_shell:: List {
1398 fn from ( CompList { infos, outputs } : CompList ) -> cosmic_randr_shell:: List {
1499 let mut list = cosmic_randr_shell:: List :: default ( ) ;
15100 for ( info, output) in infos. into_iter ( ) . zip ( outputs. into_iter ( ) ) {
16- let current = list. modes . insert ( cosmic_randr_shell:: Mode {
17- size : ( output. mode . 0 . 0 as u32 , output. mode . 0 . 1 as u32 ) ,
18- refresh_rate : output. mode . 1 . unwrap_or_default ( ) ,
19- // XXX not in config as far as i can tell
20- preferred : false ,
21- } ) ;
22- let modes = vec ! [ current] ;
23-
24- // for mode in output. {}
25- list. outputs . insert ( cosmic_randr_shell:: Output {
26- name : info. connector ,
27- enabled : !matches ! ( output. enabled, OutputState :: Disabled ) ,
28- mirroring : match output. enabled {
29- OutputState :: Mirroring ( m) => Some ( m) ,
30- _ => None ,
31- } ,
32- make : Some ( info. make ) . filter ( |make| make != "Unknown" ) ,
33- model : if info. model . as_str ( ) == "Unknown" {
34- String :: new ( )
35- } else {
36- info. model
37- } ,
38- position : ( output. position . 0 as i32 , output. position . 1 as i32 ) ,
39- scale : output. scale ,
40- transform : Some ( match output. transform {
41- crate :: output:: comp:: TransformDef :: Normal => {
42- cosmic_randr_shell:: Transform :: Normal
43- }
44- crate :: output:: comp:: TransformDef :: _90 => {
45- cosmic_randr_shell:: Transform :: Rotate90
46- }
47- crate :: output:: comp:: TransformDef :: _180 => {
48- cosmic_randr_shell:: Transform :: Rotate180
49- }
50- crate :: output:: comp:: TransformDef :: _270 => {
51- cosmic_randr_shell:: Transform :: Rotate270
52- }
53- crate :: output:: comp:: TransformDef :: Flipped => {
54- cosmic_randr_shell:: Transform :: Flipped
55- }
56- crate :: output:: comp:: TransformDef :: Flipped90 => {
57- cosmic_randr_shell:: Transform :: Flipped90
58- }
59- crate :: output:: comp:: TransformDef :: Flipped180 => {
60- cosmic_randr_shell:: Transform :: Flipped180
61- }
62- crate :: output:: comp:: TransformDef :: Flipped270 => {
63- cosmic_randr_shell:: Transform :: Flipped270
64- }
65- } ) ,
66- modes,
67- current : Some ( current) ,
68- adaptive_sync : Some ( match output. vrr {
69- crate :: output:: comp:: AdaptiveSync :: Enabled => AdaptiveSyncState :: Auto ,
70- crate :: output:: comp:: AdaptiveSync :: Disabled => AdaptiveSyncState :: Disabled ,
71- crate :: output:: comp:: AdaptiveSync :: Force => AdaptiveSyncState :: Always ,
72- } ) ,
73- xwayland_primary : Some ( output. xwayland_primary ) ,
74- // XXX no physical output size in the config
75- physical : ( 0 , 0 ) ,
76- adaptive_sync_availability : None ,
77- } ) ;
101+ // Use connector if available, otherwise use make/model as fallback for display
102+ let connector = info
103+ . connector
104+ . clone ( )
105+ . unwrap_or_else ( || format ! ( "{} {}" , info. make, info. model) ) ;
106+ let randr_output = config_to_randr_output ( & info, & output, connector, & mut list. modes ) ;
107+ list. outputs . insert ( randr_output) ;
78108 }
79-
80109 list
81110 }
82111}
@@ -92,3 +121,64 @@ pub fn load_outputs(path: Option<impl AsRef<Path>>) -> Vec<List> {
92121 } )
93122 . collect ( )
94123}
124+
125+ /// Given currently connected outputs, find the best matching saved config
126+ /// and return it with correct connector names filled in from the current outputs.
127+ ///
128+ /// This is the preferred way to get output config when you have access to
129+ /// live output information (e.g., in cosmic-greeter).
130+ pub fn get_matching_config (
131+ path : Option < impl AsRef < Path > > ,
132+ current_outputs : & [ CurrentOutput ] ,
133+ ) -> Option < List > {
134+ let output_config = crate :: output:: comp:: load_outputs ( path) ;
135+
136+ // Find the best matching saved config
137+ let mut best_match: Option < (
138+ & Vec < super :: comp:: OutputInfo > ,
139+ & Vec < super :: comp:: OutputConfig > ,
140+ ) > = None ;
141+
142+ for ( saved_infos, saved_configs) in output_config. config . iter ( ) {
143+ // Must have same number of outputs
144+ if saved_infos. len ( ) != current_outputs. len ( ) {
145+ continue ;
146+ }
147+
148+ // Check if all saved infos match a current output
149+ let all_match = saved_infos. iter ( ) . all ( |saved_info| {
150+ current_outputs
151+ . iter ( )
152+ . any ( |current| info_matches_output ( saved_info, current) )
153+ } ) ;
154+
155+ if all_match {
156+ // Prefer configs with more outputs (more specific match)
157+ if best_match. is_none_or ( |( infos, _) | infos. len ( ) < saved_infos. len ( ) ) {
158+ best_match = Some ( ( saved_infos, saved_configs) ) ;
159+ }
160+ }
161+ }
162+
163+ // Convert the matched config to a List with correct connector names
164+ let ( saved_infos, saved_configs) = best_match?;
165+
166+ let mut list = cosmic_randr_shell:: List :: default ( ) ;
167+
168+ for ( saved_info, saved_config) in saved_infos. iter ( ) . zip ( saved_configs. iter ( ) ) {
169+ // Find the matching current output to get the actual connector name
170+ let current = current_outputs
171+ . iter ( )
172+ . find ( |c| info_matches_output ( saved_info, c) ) ?;
173+
174+ let randr_output = config_to_randr_output (
175+ saved_info,
176+ saved_config,
177+ current. connector . clone ( ) ,
178+ & mut list. modes ,
179+ ) ;
180+ list. outputs . insert ( randr_output) ;
181+ }
182+
183+ Some ( list)
184+ }
0 commit comments