use crate::config::Favorites;
use crate::ddc::{self, DisplayHandle, Monitor, MonitorCapabilities, VcpValue};
use anyhow::Result;
pub struct CapabilityEntry {
pub feature_code: u8,
pub name: String,
pub is_continuous: bool,
pub choices: Vec<(u8, String)>,
pub value: Option<VcpValue>,
pub value_fetched: bool,
pub favorite: bool,
}
pub struct MonitorEntry {
pub monitor: Monitor,
pub capabilities: MonitorCapabilities,
pub features: Vec<CapabilityEntry>,
pub features_loaded: bool,
handle: Option<DisplayHandle>,
}
impl MonitorEntry {
pub fn label(&self) -> String {
format!(
"{} {} (#{})",
self.monitor.mfg_id, self.monitor.model_name, self.monitor.dispno
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Focus {
Monitors,
Capabilities,
}
pub enum Mode {
Normal,
EditContinuous { value: u16, max: u16 },
EditChoice { index: usize },
}
pub struct App {
pub monitors: Vec<MonitorEntry>,
pub selected_monitor: usize,
pub selected_capability: usize,
pub focus: Focus,
pub favorites: Favorites,
pub mode: Mode,
pub status: Option<String>,
pub should_quit: bool,
}
impl App {
pub fn new(monitors: Vec<Monitor>, favorites: Favorites) -> Result<Self> {
let mut entries = Vec::with_capacity(monitors.len());
for monitor in monitors {
let dh = DisplayHandle::open(&monitor)?;
let capabilities = ddc::query_capabilities(&dh)?;
entries.push(MonitorEntry {
monitor,
capabilities,
features: Vec::new(),
features_loaded: false,
handle: None,
});
}
let mut app = Self {
monitors: entries,
selected_monitor: 0,
selected_capability: 0,
focus: Focus::Monitors,
favorites,
mode: Mode::Normal,
status: None,
should_quit: false,
};
for idx in 0..app.monitors.len() {
if let Err(e) = app.ensure_features_loaded(idx) {
app.status = Some(format!(
"failed to load capabilities for {}: {e}",
app.monitors[idx].label()
));
}
}
Ok(app)
}
fn ensure_features_loaded(&mut self, idx: usize) -> Result<()> {
if self.monitors[idx].features_loaded {
return Ok(());
}
let entry = &mut self.monitors[idx];
let monitor_key = entry.monitor.cache_key();
let handle = match &entry.handle {
Some(_) => entry.handle.as_ref().unwrap(),
None => {
entry.handle = Some(DisplayHandle::open(&entry.monitor)?);
entry.handle.as_ref().unwrap()
}
};
let mut features = Vec::with_capacity(entry.capabilities.features.len());
for def in &entry.capabilities.features {
let meta = match ddc::feature_metadata(handle, def.feature_code) {
Ok(m) => m,
Err(_) => continue,
};
let choices = if def.values.is_empty() {
meta.sl_values.clone()
} else {
meta.sl_values
.iter()
.filter(|(code, _)| def.values.contains(code))
.cloned()
.collect()
};
features.push(CapabilityEntry {
feature_code: def.feature_code,
name: meta.name,
is_continuous: meta.is_continuous,
choices,
value: None,
value_fetched: false,
favorite: self.favorites.is_favorite(&monitor_key, def.feature_code),
});
}
let entry = &mut self.monitors[idx];
entry.features = features;
entry.features_loaded = true;
self.sort_features(idx);
Ok(())
}
pub fn has_pending_value_fetch(&self) -> bool {
self.monitors
.iter()
.any(|m| m.features.iter().any(|f| !f.value_fetched))
}
pub fn step_value_fetch(&mut self) {
let selected = self.selected_monitor;
let order =
std::iter::once(selected).chain((0..self.monitors.len()).filter(|&i| i != selected));
for idx in order {
let entry = &self.monitors[idx];
if !entry.features_loaded {
continue;
}
let Some(pos) = entry.features.iter().position(|f| !f.value_fetched) else {
continue;
};
let Some(handle) = entry.handle.as_ref() else {
continue;
};
let code = entry.features[pos].feature_code;
let value = ddc::get_vcp_value(handle, code).ok();
let feature = &mut self.monitors[idx].features[pos];
feature.value = value;
feature.value_fetched = true;
return;
}
}
fn sort_features(&mut self, idx: usize) {
self.monitors[idx]
.features
.sort_by_key(|f| (!f.favorite, f.feature_code));
}
pub fn selected_monitor_entry(&self) -> &MonitorEntry {
&self.monitors[self.selected_monitor]
}
pub fn selected_capability_entry(&self) -> Option<&CapabilityEntry> {
self.monitors[self.selected_monitor]
.features
.get(self.selected_capability)
}
pub fn move_monitor(&mut self, delta: i32) {
if self.monitors.is_empty() {
return;
}
let len = self.monitors.len() as i32;
let new_idx = (self.selected_monitor as i32 + delta).rem_euclid(len) as usize;
self.selected_monitor = new_idx;
self.selected_capability = 0;
if let Err(e) = self.ensure_features_loaded(new_idx) {
self.status = Some(format!("failed to load capabilities: {e}"));
}
}
pub fn move_capability(&mut self, delta: i32) {
let len = self.monitors[self.selected_monitor].features.len();
if len == 0 {
return;
}
let new_idx = (self.selected_capability as i32 + delta).rem_euclid(len as i32) as usize;
self.selected_capability = new_idx;
}
pub fn toggle_focus(&mut self) {
self.focus = match self.focus {
Focus::Monitors => Focus::Capabilities,
Focus::Capabilities => Focus::Monitors,
};
}
pub fn toggle_favorite(&mut self) {
let idx = self.selected_monitor;
let Some(entry) = self.monitors[idx].features.get(self.selected_capability) else {
return;
};
let code = entry.feature_code;
let monitor_key = self.monitors[idx].monitor.cache_key();
self.favorites.toggle(&monitor_key, code);
if let Err(e) = self.favorites.save() {
self.status = Some(format!("failed to save favorites: {e}"));
}
let selected_code = code;
for f in &mut self.monitors[idx].features {
if f.feature_code == code {
f.favorite = self.favorites.is_favorite(&monitor_key, code);
}
}
self.sort_features(idx);
if let Some(pos) = self.monitors[idx]
.features
.iter()
.position(|f| f.feature_code == selected_code)
{
self.selected_capability = pos;
}
}
pub fn start_edit(&mut self) {
let Some(entry) = self.selected_capability_entry() else {
return;
};
if entry.is_continuous {
let Some(v) = entry.value else { return };
self.mode = Mode::EditContinuous {
value: v.current,
max: v.max,
};
} else if !entry.choices.is_empty() {
let current = entry.value.map(|v| v.current as u8);
let index = current
.and_then(|cur| entry.choices.iter().position(|(code, _)| *code == cur))
.unwrap_or(0);
self.mode = Mode::EditChoice { index };
}
}
pub fn cancel_edit(&mut self) {
self.mode = Mode::Normal;
}
pub fn adjust_edit(&mut self, delta: i32) {
let choice_len = self
.selected_capability_entry()
.map(|e| e.choices.len())
.unwrap_or(0);
match &mut self.mode {
Mode::EditContinuous { value, max } => {
let new_value = (*value as i32 + delta).clamp(0, *max as i32);
*value = new_value as u16;
}
Mode::EditChoice { index } => {
if choice_len > 0 {
*index = (*index as i32 + delta).clamp(0, choice_len as i32 - 1) as usize;
}
}
Mode::Normal => {}
}
}
pub fn refresh_current_values(&mut self) {
let idx = self.selected_monitor;
let entry = &mut self.monitors[idx];
let Some(handle) = entry.handle.as_ref() else {
return;
};
let mut failures = 0;
for feature in &mut entry.features {
feature.value_fetched = true;
match ddc::get_vcp_value(handle, feature.feature_code) {
Ok(v) => feature.value = Some(v),
Err(_) => failures += 1,
}
}
self.status = Some(if failures == 0 {
"Refreshed values".to_string()
} else {
format!("Refreshed values ({failures} feature(s) failed to read)")
});
}
pub fn confirm_edit(&mut self) {
let idx = self.selected_monitor;
let cap_idx = self.selected_capability;
let new_value = match &self.mode {
Mode::EditContinuous { value, .. } => Some(*value),
Mode::EditChoice { index } => self.monitors[idx]
.features
.get(cap_idx)
.and_then(|f| f.choices.get(*index))
.map(|(code, _)| *code as u16),
Mode::Normal => None,
};
self.mode = Mode::Normal;
let Some(new_value) = new_value else { return };
let entry = &mut self.monitors[idx];
let Some(handle) = entry.handle.as_ref() else {
return;
};
let Some(feature) = entry.features.get_mut(cap_idx) else {
return;
};
match ddc::set_vcp_value(handle, feature.feature_code, new_value) {
Ok(()) => {
if let Some(v) = feature.value.as_mut() {
v.current = new_value;
}
}
Err(e) => {
self.status = Some(format!("failed to set value: {e}"));
}
}
}
}