Skip to content

Commit 6a01219

Browse files
feat: add get_matching_config for current outputs
This code is moved from cosmic-greeter to avoid needing to duplicate the matching logic in multiple places
1 parent 2a548f7 commit 6a01219

3 files changed

Lines changed: 160 additions & 77 deletions

File tree

Cargo.lock

Lines changed: 3 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cosmic-comp-config/Cargo.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ cosmic-randr-shell = { git = "https://github.com/pop-os/cosmic-randr/", optional
99
input = "0.9.1"
1010
libdisplay-info = { version = "0.3.0", optional = true }
1111
serde = { version = "1", features = ["derive"] }
12+
slotmap = { version = "1.0.7", optional = true }
1213
ron = { version = "0.12", optional = true }
1314
tracing = { version = "0.1.44", features = [
14-
"max_level_debug",
15-
"release_max_level_info",
15+
"max_level_debug",
16+
"release_max_level_info",
1617
], optional = true }
1718

1819
[features]
1920
default = []
2021
output = ["ron", "tracing"]
21-
randr = ["cosmic-randr-shell", "output"]
22+
randr = ["cosmic-randr-shell", "slotmap", "output"]

cosmic-comp-config/src/output/randr.rs

Lines changed: 153 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2,81 +2,110 @@ use std::path::Path;
22

33
use cosmic_randr_shell::{AdaptiveSyncState, List};
44

5+
use crate::EdidProduct;
56
use 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+
719
pub 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+
1297
impl 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

Comments
 (0)