diff --git a/Cargo.lock b/Cargo.lock index 7ab6c99..e2f242b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -253,7 +253,7 @@ checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -270,7 +270,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -445,7 +445,7 @@ checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -799,6 +799,7 @@ dependencies = [ "once_cell", "regex", "rust-embed", + "serde", "slotmap", "tokio", "tracing", @@ -1070,7 +1071,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -1092,7 +1093,7 @@ checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" dependencies = [ "darling_core 0.20.1", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -1134,7 +1135,7 @@ dependencies = [ "darling 0.20.1", "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -1220,7 +1221,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -1293,7 +1294,7 @@ checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -1577,7 +1578,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -1740,7 +1741,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -2084,7 +2085,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.22", + "syn 2.0.28", "unic-langid", ] @@ -3261,7 +3262,7 @@ checksum = "3c02bfa6b3ba8af5434fa0531bf5701f750d983d4260acd6867faca51cdc4484" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -3354,7 +3355,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -3389,7 +3390,7 @@ checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -3738,7 +3739,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.22", + "syn 2.0.28", "walkdir", ] @@ -3861,22 +3862,22 @@ checksum = "1ef965a420fe14fdac7dd018862966a4c14094f900e1650bbc71ddd7d580c8af" [[package]] name = "serde" -version = "1.0.164" +version = "1.0.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +checksum = "0ea67f183f058fe88a4e3ec6e2788e003840893b91bac4559cabedd00863b3ed" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.164" +version = "1.0.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +checksum = "24e744d7782b686ab3b73267ef05697159cc0e5abbed3f47f9933165e5219036" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -3887,7 +3888,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -4206,9 +4207,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.22" +version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efbeae7acf4eabd6bcdcbd11c92f45231ddda7539edc7806bd1a04a03b24616" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", @@ -4298,7 +4299,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -4488,7 +4489,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -4814,7 +4815,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", "wasm-bindgen-shared", ] @@ -4848,7 +4849,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/app/Cargo.toml b/app/Cargo.toml index 56ef6be..d33ed06 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -36,6 +36,7 @@ freedesktop-desktop-entry = "0.5.0" notify = "6.0.0" anyhow = "1.0" image = "0.24.6" +serde = { version = "1.0.180", features = ["derive"] } [dependencies.i18n-embed] version = "0.13.9" diff --git a/app/src/app.rs b/app/src/app.rs index 20027c6..9ee713b 100644 --- a/app/src/app.rs +++ b/app/src/app.rs @@ -39,7 +39,8 @@ use crate::{ applets::{self, APPLET_DND_ICON_ID}, }, }, - input, sound, system, time, + input::{self, keyboard}, + sound, system, time, }, subscription::desktop_files, widget::{page_title, parent_page_button, search_header, sub_page_button}, @@ -273,11 +274,11 @@ impl Application for SettingsApp { crate::pages::Message::Input(message) => { if matches!(message, input::Message::OpenKeyboardShortcuts) { if let Some(id) = self.pages.page_id::() { - self.activate_page(id); + return self.activate_page(id); } } if let Some(page) = self.pages.page_mut::() { - page.update(message); + return page.update(message); } } crate::pages::Message::External { .. } => { @@ -338,6 +339,16 @@ impl Application for SettingsApp { { return page.add_applet_view(); } + if let Some(Some(page)) = + (id == keyboard::ADD_INPUT_SOURCE_DIALOGUE_ID).then(|| self.pages.page::()) + { + return page.add_input_source_view(); + } + if let Some(Some(page)) = (id == keyboard::SPECIAL_CHARACTER_DIALOGUE_ID) + .then(|| self.pages.page::()) + { + return page.special_character_key_view(); + } cosmic::iced::widget::responsive(|size| { let is_condensed = (600.0 * self.scaling_factor) > size.width; diff --git a/app/src/pages/input/keyboard/mod.rs b/app/src/pages/input/keyboard/mod.rs index 9738578..8409ec8 100644 --- a/app/src/pages/input/keyboard/mod.rs +++ b/app/src/pages/input/keyboard/mod.rs @@ -1,17 +1,67 @@ use apply::Apply; -use cosmic::iced::{ - self, - widget::{self, horizontal_space}, - Length, +use cosmic::{ + iced::{ + self, + widget::{self, horizontal_space}, + window, Length, + }, + iced_style, theme, + widget::settings, }; -use cosmic::iced_style; -use cosmic::widget::settings; -use cosmic_settings_page::Section; -use cosmic_settings_page::{self as page, section}; +use cosmic_settings_page::{self as page, section, Section}; use slotmap::SlotMap; use super::Message; +pub const ADD_INPUT_SOURCE_DIALOGUE_ID: window::Id = window::Id(2000); +pub const SPECIAL_CHARACTER_DIALOGUE_ID: window::Id = window::Id(2001); + +static COMPOSE_OPTIONS: &[(&str, &str)] = &[ + // ("Left Alt", "compose:lalt"), XXX? + ("Right Alt", "compose:ralt"), + ("Left Super", "compose:lwin"), + ("Right Super", "compose:rwin"), + ("Menu key", "compose:menu"), + ("Right Ctrl", "compose:rctrl"), + ("Caps Lock", "compose:caps"), + ("Scroll Lock", "compose:sclk"), + ("Print Screen", "compose:prsc"), +]; + +static ALTERNATE_CHARACTER_OPTIONS: &[(&str, &str)] = &[ + ("Left Alt", "lv3:lalt_switch"), + ("Right Alt", "lv3:alt_switch"), + ("Left Super", "lv3:lwin_switch"), + ("Right Super", "lv3:win_switch"), + ("Menu key", "lv3:menu_switch"), + // ("Right Ctrl", "lv3:"), XXX + ("Caps Lock", "lv3:caps_switch"), + // ("Scroll Lock", "lv3:"), XXX + // ("Print Screen", "lv3"), XXX +]; + +#[derive(Copy, Clone, Debug)] +pub enum SpecialKey { + AlternateCharacters, + Compose, +} + +impl SpecialKey { + pub fn title(self) -> String { + match self { + Self::Compose => "Compose".to_string(), + Self::AlternateCharacters => "Alternate Characters".to_string(), + } + } + + pub fn prefix(self) -> &'static str { + match self { + Self::Compose => "compose:", + Self::AlternateCharacters => "lv3:", + } + } +} + fn popover_menu_row(label: String) -> cosmic::Element<'static, Message> { widget::text(label) .apply(widget::container) @@ -92,6 +142,68 @@ pub struct InputSource { label: String, } +impl super::Page { + pub fn add_input_source_view(&self) -> cosmic::Element<'static, crate::app::Message> { + widget::column![].into() + } + + pub fn special_character_key_view(&self) -> cosmic::Element<'_, crate::app::Message> { + let Some(special_key) = self.special_character_dialog else { + return widget::text("").into(); + }; + + let options = match special_key { + SpecialKey::Compose => COMPOSE_OPTIONS, + SpecialKey::AlternateCharacters => ALTERNATE_CHARACTER_OPTIONS, + }; + let prefix = special_key.prefix(); + let current = self + .xkb_options + .iter() + .find(|x| x.starts_with(prefix)) + .map(String::as_str); + + // TODO description, layout default + + let mut list = cosmic::widget::list_column(); + list = list.add(special_char_radio_row("None", None, current)); + for (desc, id) in options { + list = list.add(special_char_radio_row(desc, Some(id), current)); + } + widget::column![ + cosmic::widget::header_bar() + .title(special_key.title()) + .on_close(Message::CloseSpecialCharacterDialog), + cosmic::widget::container( + cosmic::widget::scrollable(cosmic::widget::container(list).padding(24)) + .width(Length::Fill) + .height(Length::Fill) + ) + .style(theme::Container::Background) + .width(Length::Fill) + .height(Length::Fill) + ] + .apply(cosmic::Element::from) + .map(crate::pages::Message::Input) + .map(crate::app::Message::PageMessage) + } +} + +fn special_char_radio_row<'a>( + desc: &'a str, + value: Option<&'static str>, + current_value: Option<&'a str>, +) -> cosmic::Element<'a, Message> { + settings::item_row(vec![iced::widget::radio( + desc, + value, + Some(current_value), + |_| Message::SpecialCharacterSelect(value), + ) + .into()]) + .into() +} + #[derive(Default)] pub struct Page; @@ -161,8 +273,14 @@ fn special_character_entry() -> Section { // TODO dialogs settings::view_section(§ion.title) - .add(settings::item(&descriptions[0], go_next_control())) - .add(settings::item(&descriptions[1], go_next_control())) + .add(go_next_item( + &descriptions[0], + Message::OpenSpecialCharacterDialog(SpecialKey::AlternateCharacters), + )) + .add(go_next_item( + &descriptions[1], + Message::OpenSpecialCharacterDialog(SpecialKey::Compose), + )) .apply(cosmic::Element::from) .map(crate::pages::Message::Input) }) @@ -172,21 +290,14 @@ fn keyboard_shortcuts() -> Section { Section::default() .title(fl!("keyboard-shortcuts")) .descriptions(vec![fl!("keyboard-shortcuts", "desc")]) - .view::(|binder, _page, section| { + .view::(|_binder, _page, section| { let descriptions = §ion.descriptions; settings::view_section(§ion.title) - .add( - settings::item(&descriptions[0], go_next_control()) - .apply(widget::container) - .style(cosmic::theme::Container::custom( - cosmic::widget::list::column::style, - )) - .apply(widget::button) - .style(cosmic::theme::Button::Transparent) - .padding(0) - .on_press(Message::OpenKeyboardShortcuts), - ) + .add(go_next_item( + &descriptions[0], + Message::OpenKeyboardShortcuts, + )) .apply(cosmic::Element::from) .map(crate::pages::Message::Input) }) @@ -199,3 +310,16 @@ fn go_next_control() -> cosmic::Element<'static, Message> { ) .into() } + +fn go_next_item(description: &str, msg: Message) -> cosmic::Element<'_, Message> { + settings::item(description, go_next_control()) + .apply(widget::container) + .style(cosmic::theme::Container::custom( + cosmic::widget::list::column::style, + )) + .apply(widget::button) + .style(cosmic::theme::Button::Transparent) + .padding(0) + .on_press(msg) + .into() +} diff --git a/app/src/pages/input/keyboard/shortcuts.rs b/app/src/pages/input/keyboard/shortcuts.rs index e01fe7c..4b0e036 100644 --- a/app/src/pages/input/keyboard/shortcuts.rs +++ b/app/src/pages/input/keyboard/shortcuts.rs @@ -1,8 +1,5 @@ use apply::Apply; -use cosmic::iced::{ - widget::{self, horizontal_space}, - Length, -}; +use cosmic::iced::widget; use cosmic::widget::settings; use cosmic::Element; use cosmic_settings_page::Section; @@ -35,9 +32,9 @@ fn shortcuts() -> Section { Section::default() .descriptions(vec![]) .view::(|binder, _page, section| { - let descriptions = §ion.descriptions; + let _descriptions = §ion.descriptions; - let input = binder + let _input = binder .page::() .expect("input page not found"); diff --git a/app/src/pages/input/mod.rs b/app/src/pages/input/mod.rs index 744f65a..8c072b3 100644 --- a/app/src/pages/input/mod.rs +++ b/app/src/pages/input/mod.rs @@ -1,8 +1,23 @@ +use crate::app; +use cosmic::{ + iced::{self, wayland::actions::window::SctkWindowSettings, window}, + iced_sctk::commands, + iced_widget::core::layout, +}; use cosmic_settings_page as page; pub mod keyboard; mod mouse; +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +pub struct XkbConfig { + pub rules: String, + pub model: String, + pub layout: String, + pub variant: String, + pub options: Option, +} + #[derive(Clone, Debug)] pub enum Message { SetAcceleration(bool), @@ -14,11 +29,16 @@ pub enum Message { // seperate close message, to make sure another isn't closed? ExpandInputSourcePopover(Option), OpenKeyboardShortcuts, + OpenSpecialCharacterDialog(keyboard::SpecialKey), + CloseSpecialCharacterDialog, + SpecialCharacterSelect(Option<&'static str>), } #[derive(derivative::Derivative)] #[derivative(Default)] pub struct Page { + // cosmic_config::Config + // Mouse #[derivative(Default(value = "mouse::default_primary_button()"))] primary_button: cosmic::widget::segmented_button::SingleSelectModel, @@ -32,11 +52,13 @@ pub struct Page { expanded_source_popover: Option, #[derivative(Default(value = "keyboard::default_input_sources()"))] sources: Vec, + special_character_dialog: Option, + xkb_options: Vec, } impl Page { // TODO - pub fn update(&mut self, message: Message) { + pub fn update(&mut self, message: Message) -> iced::Command { match message { Message::SetAcceleration(value) => { self.acceleration = value; @@ -59,9 +81,45 @@ impl Page { Message::ExpandInputSourcePopover(value) => { self.expanded_source_popover = value; } - // TODO Specially handled in app.rs + Message::OpenSpecialCharacterDialog(special_key) => { + self.special_character_dialog = Some(special_key); + let window_settings = SctkWindowSettings { + window_id: keyboard::SPECIAL_CHARACTER_DIALOGUE_ID, + app_id: Some("com.system76.CosmicSettings".to_string()), + title: Some(special_key.title()), + parent: Some(window::Id(0)), + autosize: false, + size_limits: layout::Limits::NONE + .min_width(300.0) + .max_width(800.0) + .min_height(200.0) + .max_height(1080.0), + size: (512, 420), + resizable: None, + client_decorations: true, + transparent: true, + }; + return commands::window::get_window(window_settings); + } + Message::CloseSpecialCharacterDialog => { + self.special_character_dialog = None; + return commands::window::close_window(keyboard::SPECIAL_CHARACTER_DIALOGUE_ID); + } + Message::SpecialCharacterSelect(id) => { + if let Some(special_key) = self.special_character_dialog { + let prefix = special_key.prefix(); + if let Some(idx) = self.xkb_options.iter().position(|x| x.starts_with(prefix)) { + self.xkb_options.remove(idx); + } + if let Some(id) = id { + self.xkb_options.push(id.to_string()); + } + // TODO set in cosmic-config + } + } Message::OpenKeyboardShortcuts => {} } + iced::Command::none() } } diff --git a/app/src/pages/input/mouse.rs b/app/src/pages/input/mouse.rs index fd23251..7341059 100644 --- a/app/src/pages/input/mouse.rs +++ b/app/src/pages/input/mouse.rs @@ -1,8 +1,5 @@ use apply::Apply; -use cosmic::iced::{ - widget::{self, horizontal_space}, - Length, -}; +use cosmic::iced::widget; use cosmic::widget::settings; use cosmic::Element; use cosmic_settings_page::Section;