diff --git a/Cargo.toml b/Cargo.toml index 3489fb8..84bc6c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ default-members = ["cosmic-settings"] resolver = "2" [workspace.package] -rust-version = "1.75.0" +rust-version = "1.79.0" [workspace.dependencies] cosmic-randr = { git = "https://github.com/pop-os/cosmic-randr" } diff --git a/cosmic-settings/src/pages/display/mod.rs b/cosmic-settings/src/pages/display/mod.rs index 0be88b4..235014a 100644 --- a/cosmic-settings/src/pages/display/mod.rs +++ b/cosmic-settings/src/pages/display/mod.rs @@ -16,7 +16,7 @@ use cosmic::{command, Apply, Command, Element}; use cosmic_randr_shell::{List, Output, OutputKey, Transform}; use cosmic_settings_page::{self as page, section, Section}; use slab::Slab; -use slotmap::{Key, SlotMap}; +use slotmap::{Key, SecondaryMap, SlotMap}; use std::{collections::BTreeMap, process::ExitStatus, sync::Arc}; /// Display color depth options @@ -29,10 +29,10 @@ pub struct ColorDepth(usize); // } /// Display mirroring options -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum Mirroring { Disable, - ProjectToAll, + // ProjectToAll, Project(OutputKey), Mirror(OutputKey), } @@ -102,6 +102,7 @@ impl From for app::Message { #[derive(Clone, Copy, Debug, PartialEq)] enum Randr { + Mirror(OutputKey), Position(i32, i32), RefreshRate(u32), Resolution(u32, u32), @@ -114,16 +115,19 @@ enum Randr { pub struct Page { list: List, display_tabs: segmented_button::SingleSelectModel, + mirror_map: SecondaryMap, + mirror_menu: widget::dropdown::multi::Model, active_display: OutputKey, background_service: Option>, config: Config, cache: ViewCache, // context: Option, - display_arrangement_scrollable: cosmic::widget::Id, + display_arrangement_scrollable: widget::Id, /// The setting to revert to if the next dialog page is cancelled. dialog: Option, /// the instant the setting was changed. dialog_countdown: usize, + show_display_options: bool, } impl Default for Page { @@ -131,14 +135,17 @@ impl Default for Page { Self { list: List::default(), display_tabs: segmented_button::SingleSelectModel::default(), + mirror_map: SecondaryMap::new(), + mirror_menu: widget::dropdown::multi::model(), active_display: OutputKey::default(), background_service: None, config: Config::default(), cache: ViewCache::default(), // context: None, - display_arrangement_scrollable: cosmic::widget::Id::unique(), + display_arrangement_scrollable: widget::Id::unique(), dialog: None, dialog_countdown: 0, + show_display_options: true, } } } @@ -377,10 +384,23 @@ impl Page { Message::DisplayToggle(enable) => return self.toggle_display(enable), Message::Mirroring(mirroring) => match mirroring { - Mirroring::Disable => (), - Mirroring::Mirror(_target_display) => (), - Mirroring::Project(_target_display) => (), - Mirroring::ProjectToAll => (), + Mirroring::Disable => return self.toggle_display(true), + + Mirroring::Mirror(from_display) => { + let Some(output) = self.list.outputs.get(self.active_display) else { + return Command::none(); + }; + + return self.exec_randr(output, Randr::Mirror(from_display)); + } + + Mirroring::Project(to_display) => { + let Some(output) = self.list.outputs.get(to_display) else { + return Command::none(); + }; + + return self.exec_randr(output, Randr::Mirror(self.active_display)); + } // Mirroring::ProjectToAll => (), }, // Message::NightLight(night_light) => {} @@ -448,6 +468,7 @@ impl Page { self.active_display = OutputKey::null(); self.display_tabs.clear(); + self.mirror_map.clear(); self.list = list; let sorted_outputs = self @@ -462,6 +483,15 @@ impl Page { continue; }; + if let Some(mirroring_from) = output.mirroring.as_deref() { + for (other_id, other_output) in &self.list.outputs { + if other_output.name == mirroring_from { + self.mirror_map.insert(id, other_id); + break; + } + } + } + let text = crate::utils::display_name(&output.name, output.physical); if text == active_display_name { @@ -577,6 +607,61 @@ impl Page { cache_rates(&mut self.cache.refresh_rates, rates); } } + + self.mirror_menu.clear(); + + self.mirror_menu.insert(widget::dropdown::multi::list( + None, + vec![(fl!("mirroring", "dont"), Mirroring::Disable)], + )); + + self.mirror_menu.insert(widget::dropdown::multi::list( + None, + self.list + .outputs + .iter() + .filter(|&(other_id, _)| other_id != output_id) + .map(|(other_id, other_output)| { + ( + fl!("mirroring", "project", display = other_output.name.as_str()), + Mirroring::Project(other_id), + ) + }) + .collect::>(), + )); + + self.mirror_menu.insert(widget::dropdown::multi::list( + None, + self.list + .outputs + .iter() + .filter(|&(other_id, _)| other_id != output_id) + .map(|(other_id, other_output)| { + ( + fl!("mirroring", "mirror", display = other_output.name.as_str()), + Mirroring::Mirror(other_id), + ) + }) + .collect::>(), + )); + + self.mirror_menu.selected = self + .mirror_map + .get(output_id) + .map(|id| Mirroring::Mirror(*id)); + + self.show_display_options = self.mirror_menu.selected.is_none(); + + if self.mirror_menu.selected.is_none() { + self.mirror_menu.selected = self + .mirror_map + .iter() + .find(|&(_, mirrored_id)| *mirrored_id == output_id) + .map(|(projected_id, _)| Mirroring::Project(projected_id)); + } + + // If mirror menu is not set, set it to don't mirror. + self.mirror_menu.selected = Some(Mirroring::Disable); } /// Change display orientation. @@ -739,6 +824,17 @@ impl Page { let mut command = tokio::process::Command::new("cosmic-randr"); match request { + Randr::Mirror(from_id) => { + let Some(from_output) = self.list.outputs.get(from_id) else { + return Command::none(); + }; + + command + .arg("mirror") + .arg(&output.name) + .arg(&from_output.name); + } + Randr::Position(x, y) => { let Some(current) = output.current.and_then(|id| self.list.modes.get(id)) else { return Command::none(); @@ -856,16 +952,14 @@ pub fn display_arrangement() -> Section { theme.cosmic().space_m(), ])) .spacing(theme.cosmic().space_xs()) - .push(cosmic::widget::text::body( - &descriptions[display_arrangement_desc], - )) + .push(widget::text::body(&descriptions[display_arrangement_desc])) .push({ Arrangement::new(&page.list, &page.display_tabs) .on_select(|id| pages::Message::Displays(Message::Display(id))) .on_placement(|id, x, y| { pages::Message::Displays(Message::Position(id, x, y)) }) - .apply(cosmic::widget::scrollable) + .apply(widget::scrollable) .id(page.display_arrangement_scrollable.clone()) .width(Length::Shrink) .direction(Direction::Horizontal(Properties::new())) @@ -873,7 +967,7 @@ pub fn display_arrangement() -> Section { .center_x() .width(Length::Fill) }) - .apply(cosmic::widget::list::container) + .apply(widget::list::container) .into() }) } @@ -889,6 +983,7 @@ pub fn display_configuration() -> Section { let orientation = descriptions.insert(fl!("orientation")); let enable_label = descriptions.insert(fl!("display", "enable")); let options_label = descriptions.insert(fl!("display", "options")); + let mirroring_label = descriptions.insert(fl!("mirroring")); Section::default() .descriptions(descriptions) @@ -902,9 +997,9 @@ pub fn display_configuration() -> Section { let active_output = &page.list.outputs[active_id]; - let display_options = active_output.enabled.then(|| { + let display_options = (page.show_display_options && active_output.enabled).then(|| { list_column() - .add(cosmic::widget::settings::flex_item( + .add(widget::settings::flex_item( &descriptions[resolution], dropdown( &page.cache.resolutions, @@ -912,7 +1007,7 @@ pub fn display_configuration() -> Section { Message::Resolution, ), )) - .add(cosmic::widget::settings::flex_item( + .add(widget::settings::flex_item( &descriptions[refresh_rate], dropdown( &page.cache.refresh_rates, @@ -920,7 +1015,7 @@ pub fn display_configuration() -> Section { Message::RefreshRate, ), )) - .add(cosmic::widget::settings::flex_item( + .add(widget::settings::flex_item( &descriptions[scale], dropdown( &["50%", "75%", "100%", "125%", "150%", "175%", "200%"], @@ -928,7 +1023,7 @@ pub fn display_configuration() -> Section { Message::Scale, ), )) - .add(cosmic::widget::settings::flex_item( + .add(widget::settings::flex_item( &descriptions[orientation], dropdown( &page.cache.orientations, @@ -962,17 +1057,25 @@ pub fn display_configuration() -> Section { > 1 || !active_output.enabled) .then(|| { - list_column().add(cosmic::widget::settings::flex_item( - &descriptions[enable_label], - toggler(None, active_output.enabled, Message::DisplayToggle), - )) + list_column() + .add(widget::settings::flex_item( + &descriptions[enable_label], + toggler(None, active_output.enabled, Message::DisplayToggle), + )) + .add(widget::settings::flex_item( + &descriptions[mirroring_label], + widget::dropdown::multi::dropdown( + &page.mirror_menu, + Message::Mirroring, + ), + )) }); content = content.push(display_switcher).push_maybe(display_enable); } content - .push(cosmic::widget::text::heading(&descriptions[options_label])) + .push(widget::text::heading(&descriptions[options_label])) .push_maybe(display_options) .apply(Element::from) .map(pages::Message::Displays) @@ -988,9 +1091,8 @@ fn cache_rates(cached_rates: &mut Vec, rates: &[u32]) { pub async fn on_enter() -> crate::pages::Message { let randr_fut = cosmic_randr_shell::list(); - let randr = futures::future::ready(randr_fut).await; crate::pages::Message::Displays(Message::Update { - randr: Arc::new(randr.await), + randr: Arc::new(randr_fut.await), }) } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index f743517..d0ead5e 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.75.0" +channel = "stable" components = ["clippy", "rustfmt"]