feat: shortcut key input

This commit is contained in:
Ashley Wulber 2025-09-15 18:55:38 -04:00 committed by Michael Murphy
parent 52cd2f281c
commit 95180b19e4
9 changed files with 590 additions and 129 deletions

153
Cargo.lock generated
View file

@ -502,20 +502,20 @@ dependencies = [
[[package]]
name = "async-io"
version = "2.5.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca"
checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
dependencies = [
"async-lock 3.4.1",
"autocfg",
"cfg-if",
"concurrent-queue",
"futures-io",
"futures-lite 2.6.1",
"parking",
"polling 3.10.0",
"polling 3.11.0",
"rustix 1.1.2",
"slab",
"windows-sys 0.60.2",
"windows-sys 0.61.0",
]
[[package]]
@ -557,12 +557,12 @@ dependencies = [
[[package]]
name = "async-process"
version = "2.4.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65daa13722ad51e6ab1a1b9c01299142bc75135b337923cfa10e79bbbd669f00"
checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"
dependencies = [
"async-channel",
"async-io 2.5.0",
"async-io 2.6.0",
"async-lock 3.4.1",
"async-signal",
"async-task",
@ -586,11 +586,11 @@ dependencies = [
[[package]]
name = "async-signal"
version = "0.2.12"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f567af260ef69e1d52c2b560ce0ea230763e6fbb9214a85d768760a920e3e3c1"
checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c"
dependencies = [
"async-io 2.5.0",
"async-io 2.6.0",
"async-lock 3.4.1",
"atomic-waker",
"cfg-if",
@ -599,7 +599,7 @@ dependencies = [
"rustix 1.1.2",
"signal-hook-registry",
"slab",
"windows-sys 0.60.2",
"windows-sys 0.61.0",
]
[[package]]
@ -1046,7 +1046,7 @@ checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec"
dependencies = [
"bitflags 2.9.4",
"log",
"polling 3.10.0",
"polling 3.11.0",
"rustix 0.38.44",
"slab",
"thiserror 1.0.69",
@ -1059,7 +1059,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb9f6e1368bd4621d2c86baa7e37de77a938adf5221e5dd3d6133340101b309e"
dependencies = [
"bitflags 2.9.4",
"polling 3.10.0",
"polling 3.11.0",
"rustix 1.1.2",
"slab",
"tracing",
@ -1517,7 +1517,7 @@ dependencies = [
[[package]]
name = "cosmic-comp-config"
version = "0.1.0"
source = "git+https://github.com/pop-os/cosmic-comp#cd1117080c026da61f1c7e6be55f893b3a2f87ef"
source = "git+https://github.com/pop-os/cosmic-comp#b83e9f1d32f7d7b933c3fc8d45ed574d7440212a"
dependencies = [
"cosmic-config",
"input",
@ -1529,7 +1529,7 @@ dependencies = [
[[package]]
name = "cosmic-config"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic#b9a00c6e799b80154190f11943bb65c1fc4dc58b"
source = "git+https://github.com/pop-os/libcosmic#0e797b244043ee86610113d547950204258dea83"
dependencies = [
"atomicwrites",
"cosmic-config-derive",
@ -1550,7 +1550,7 @@ dependencies = [
[[package]]
name = "cosmic-config-derive"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic#b9a00c6e799b80154190f11943bb65c1fc4dc58b"
source = "git+https://github.com/pop-os/libcosmic#0e797b244043ee86610113d547950204258dea83"
dependencies = [
"quote",
"syn 2.0.106",
@ -1614,7 +1614,7 @@ dependencies = [
[[package]]
name = "cosmic-panel-config"
version = "0.1.0"
source = "git+https://github.com/pop-os/cosmic-panel#2bd1a6f8e42b3857853a23b097daf2ab45eb0e18"
source = "git+https://github.com/pop-os/cosmic-panel#9c315e4acb60876aa2faacfe5777855830e88d79"
dependencies = [
"anyhow",
"cosmic-config",
@ -1646,7 +1646,7 @@ source = "git+https://github.com/pop-os/cosmic-randr#bce9cdf2d447508d4e2d54a2be4
dependencies = [
"cosmic-protocols",
"futures-lite 2.6.1",
"indexmap 2.11.1",
"indexmap 2.11.3",
"tachyonix",
"thiserror 2.0.16",
"tokio",
@ -1707,7 +1707,7 @@ dependencies = [
"i18n-embed-fl",
"icu",
"image",
"indexmap 2.11.1",
"indexmap 2.11.3",
"itertools 0.14.0",
"itoa",
"libcosmic",
@ -1737,6 +1737,7 @@ dependencies = [
"upower_dbus",
"url",
"xkb-data",
"xkeysym",
"zbus 5.11.0",
"zbus_polkit",
]
@ -1796,7 +1797,7 @@ dependencies = [
"cosmic-protocols",
"futures",
"iced_futures",
"indexmap 2.11.1",
"indexmap 2.11.3",
"itertools 0.14.0",
"libcosmic",
"libpulse-binding",
@ -1870,7 +1871,7 @@ dependencies = [
[[package]]
name = "cosmic-theme"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic#b9a00c6e799b80154190f11943bb65c1fc4dc58b"
source = "git+https://github.com/pop-os/libcosmic#0e797b244043ee86610113d547950204258dea83"
dependencies = [
"almost",
"cosmic-config",
@ -2743,9 +2744,9 @@ dependencies = [
[[package]]
name = "fs-err"
version = "3.1.1"
version = "3.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d7be93788013f265201256d58f04936a8079ad5dc898743aa20525f503b683"
checksum = "44f150ffc8782f35521cec2b23727707cb4045706ba3c854e86bef66b3a8cdbd"
dependencies = [
"autocfg",
"tokio",
@ -2937,7 +2938,7 @@ dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi 0.14.5+wasi-0.2.4",
"wasi 0.14.7+wasi-0.2.4",
]
[[package]]
@ -3100,9 +3101,9 @@ dependencies = [
[[package]]
name = "harfrust"
version = "0.2.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "406a98b615ed380f2195fa8fb2ed3083e64b2a6329d710e06f95a42466f0f0c4"
checksum = "75a4c970f1a00edc1626f1e3cc039492b15b73df28b9fff70f95404a571b4fae"
dependencies = [
"bitflags 2.9.4",
"bytemuck",
@ -3298,7 +3299,7 @@ dependencies = [
[[package]]
name = "iced"
version = "0.14.0-dev"
source = "git+https://github.com/pop-os/libcosmic#b9a00c6e799b80154190f11943bb65c1fc4dc58b"
source = "git+https://github.com/pop-os/libcosmic#0e797b244043ee86610113d547950204258dea83"
dependencies = [
"dnd",
"iced_accessibility",
@ -3316,7 +3317,7 @@ dependencies = [
[[package]]
name = "iced_accessibility"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic#b9a00c6e799b80154190f11943bb65c1fc4dc58b"
source = "git+https://github.com/pop-os/libcosmic#0e797b244043ee86610113d547950204258dea83"
dependencies = [
"accesskit",
"accesskit_winit",
@ -3325,7 +3326,7 @@ dependencies = [
[[package]]
name = "iced_core"
version = "0.14.0-dev"
source = "git+https://github.com/pop-os/libcosmic#b9a00c6e799b80154190f11943bb65c1fc4dc58b"
source = "git+https://github.com/pop-os/libcosmic#0e797b244043ee86610113d547950204258dea83"
dependencies = [
"bitflags 2.9.4",
"bytes",
@ -3350,7 +3351,7 @@ dependencies = [
[[package]]
name = "iced_futures"
version = "0.14.0-dev"
source = "git+https://github.com/pop-os/libcosmic#b9a00c6e799b80154190f11943bb65c1fc4dc58b"
source = "git+https://github.com/pop-os/libcosmic#0e797b244043ee86610113d547950204258dea83"
dependencies = [
"futures",
"iced_core",
@ -3376,7 +3377,7 @@ dependencies = [
[[package]]
name = "iced_graphics"
version = "0.14.0-dev"
source = "git+https://github.com/pop-os/libcosmic#b9a00c6e799b80154190f11943bb65c1fc4dc58b"
source = "git+https://github.com/pop-os/libcosmic#0e797b244043ee86610113d547950204258dea83"
dependencies = [
"bitflags 2.9.4",
"bytemuck",
@ -3398,7 +3399,7 @@ dependencies = [
[[package]]
name = "iced_renderer"
version = "0.14.0-dev"
source = "git+https://github.com/pop-os/libcosmic#b9a00c6e799b80154190f11943bb65c1fc4dc58b"
source = "git+https://github.com/pop-os/libcosmic#0e797b244043ee86610113d547950204258dea83"
dependencies = [
"iced_graphics",
"iced_tiny_skia",
@ -3410,7 +3411,7 @@ dependencies = [
[[package]]
name = "iced_runtime"
version = "0.14.0-dev"
source = "git+https://github.com/pop-os/libcosmic#b9a00c6e799b80154190f11943bb65c1fc4dc58b"
source = "git+https://github.com/pop-os/libcosmic#0e797b244043ee86610113d547950204258dea83"
dependencies = [
"bytes",
"cosmic-client-toolkit",
@ -3426,7 +3427,7 @@ dependencies = [
[[package]]
name = "iced_tiny_skia"
version = "0.14.0-dev"
source = "git+https://github.com/pop-os/libcosmic#b9a00c6e799b80154190f11943bb65c1fc4dc58b"
source = "git+https://github.com/pop-os/libcosmic#0e797b244043ee86610113d547950204258dea83"
dependencies = [
"bytemuck",
"cosmic-text",
@ -3442,7 +3443,7 @@ dependencies = [
[[package]]
name = "iced_wgpu"
version = "0.14.0-dev"
source = "git+https://github.com/pop-os/libcosmic#b9a00c6e799b80154190f11943bb65c1fc4dc58b"
source = "git+https://github.com/pop-os/libcosmic#0e797b244043ee86610113d547950204258dea83"
dependencies = [
"as-raw-xcb-connection",
"bitflags 2.9.4",
@ -3473,7 +3474,7 @@ dependencies = [
[[package]]
name = "iced_widget"
version = "0.14.0-dev"
source = "git+https://github.com/pop-os/libcosmic#b9a00c6e799b80154190f11943bb65c1fc4dc58b"
source = "git+https://github.com/pop-os/libcosmic#0e797b244043ee86610113d547950204258dea83"
dependencies = [
"cosmic-client-toolkit",
"dnd",
@ -3493,7 +3494,7 @@ dependencies = [
[[package]]
name = "iced_winit"
version = "0.14.0-dev"
source = "git+https://github.com/pop-os/libcosmic#b9a00c6e799b80154190f11943bb65c1fc4dc58b"
source = "git+https://github.com/pop-os/libcosmic#0e797b244043ee86610113d547950204258dea83"
dependencies = [
"cosmic-client-toolkit",
"dnd",
@ -4029,13 +4030,14 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.11.1"
version = "2.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921"
checksum = "92119844f513ffa41556430369ab02c295a3578af21cf945caa3e9e0c2481ac3"
dependencies = [
"equivalent",
"hashbrown 0.15.5",
"serde",
"serde_core",
]
[[package]]
@ -4548,7 +4550,7 @@ checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[package]]
name = "libcosmic"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic#b9a00c6e799b80154190f11943bb65c1fc4dc58b"
source = "git+https://github.com/pop-os/libcosmic#0e797b244043ee86610113d547950204258dea83"
dependencies = [
"apply",
"ashpd 0.12.0",
@ -4646,9 +4648,9 @@ dependencies = [
[[package]]
name = "libredox"
version = "0.1.9"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
dependencies = [
"bitflags 2.9.4",
"libc",
@ -5045,7 +5047,7 @@ dependencies = [
"cfg_aliases 0.1.1",
"codespan-reporting",
"hexf-parse",
"indexmap 2.11.1",
"indexmap 2.11.3",
"log",
"rustc-hash 1.1.0",
"spirv",
@ -5971,16 +5973,16 @@ dependencies = [
[[package]]
name = "polling"
version = "3.10.0"
version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829"
checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
dependencies = [
"cfg-if",
"concurrent-queue",
"hermit-abi 0.5.2",
"pin-project-lite",
"rustix 1.1.2",
"windows-sys 0.60.2",
"windows-sys 0.61.0",
]
[[package]]
@ -6631,9 +6633,9 @@ dependencies = [
[[package]]
name = "rust_decimal"
version = "1.37.2"
version = "1.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b203a6425500a03e0919c42d3c47caca51e79f1132046626d2c8871c5092035d"
checksum = "c8975fc98059f365204d635119cf9c5a60ae67b841ed49b5422a9a7e56cdfac0"
dependencies = [
"arrayvec",
"borsh",
@ -6813,10 +6815,11 @@ checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749"
[[package]]
name = "serde"
version = "1.0.219"
version = "1.0.224"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
checksum = "6aaeb1e94f53b16384af593c71e20b095e958dab1d26939c1b70645c5cfbcc0b"
dependencies = [
"serde_core",
"serde_derive",
]
@ -6833,10 +6836,19 @@ dependencies = [
]
[[package]]
name = "serde_derive"
version = "1.0.219"
name = "serde_core"
version = "1.0.224"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
checksum = "32f39390fa6346e24defbcdd3d9544ba8a19985d0af74df8501fbfe9a64341ab"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.224"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87ff78ab5e8561c9a675bfc1785cb07ae721f0ee53329a595cefd8c04c2ac4e0"
dependencies = [
"proc-macro2",
"quote",
@ -6845,15 +6857,16 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.143"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
dependencies = [
"indexmap 2.11.1",
"indexmap 2.11.3",
"itoa",
"memchr",
"ryu",
"serde",
"serde_core",
]
[[package]]
@ -6886,7 +6899,7 @@ dependencies = [
"chrono",
"hex",
"indexmap 1.9.3",
"indexmap 2.11.1",
"indexmap 2.11.3",
"schemars 0.9.0",
"schemars 1.0.4",
"serde",
@ -7664,7 +7677,7 @@ version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [
"indexmap 2.11.1",
"indexmap 2.11.3",
"toml_datetime",
"winnow 0.5.40",
]
@ -7675,7 +7688,7 @@ version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap 2.11.1",
"indexmap 2.11.3",
"serde",
"serde_spanned",
"toml_datetime",
@ -8040,18 +8053,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasi"
version = "0.14.5+wasi-0.2.4"
version = "0.14.7+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4494f6290a82f5fe584817a676a34b9d6763e8d9d18204009fb31dceca98fd4"
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
dependencies = [
"wasip2",
]
[[package]]
name = "wasip2"
version = "1.0.0+wasi-0.2.4"
version = "1.0.1+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03fa2761397e5bd52002cd7e73110c71af2109aca4e521a9f40473fe685b0a24"
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
dependencies = [
"wit-bindgen",
]
@ -8355,7 +8368,7 @@ dependencies = [
"bitflags 2.9.4",
"cfg_aliases 0.1.1",
"document-features",
"indexmap 2.11.1",
"indexmap 2.11.3",
"log",
"naga",
"once_cell",
@ -9083,9 +9096,9 @@ dependencies = [
[[package]]
name = "wit-bindgen"
version = "0.45.1"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "write16"
@ -9175,7 +9188,7 @@ dependencies = [
[[package]]
name = "xdg-shell-wrapper-config"
version = "0.1.0"
source = "git+https://github.com/pop-os/cosmic-panel#2bd1a6f8e42b3857853a23b097daf2ab45eb0e18"
source = "git+https://github.com/pop-os/cosmic-panel#9c315e4acb60876aa2faacfe5777855830e88d79"
dependencies = [
"serde",
"wayland-protocols-wlr",
@ -9347,9 +9360,9 @@ checksum = "2d07e46d035fb8e375b2ce63ba4e4ff90a7f73cf2ffb0138b29e1158d2eaadf7"
dependencies = [
"async-broadcast 0.7.2",
"async-executor",
"async-io 2.5.0",
"async-io 2.6.0",
"async-lock 3.4.1",
"async-process 2.4.0",
"async-process 2.5.0",
"async-recursion",
"async-task",
"async-trait",

View file

@ -71,6 +71,7 @@ upower_dbus = { git = "https://github.com/pop-os/dbus-settings-bindings", option
bluez-zbus = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
url = "2.5.7"
xkb-data = "0.2.1"
xkeysym = { version = "0.2.0", optional = true }
zbus = { version = "5.11.0", default-features = false, features = [
"tokio",
], optional = true }
@ -147,6 +148,8 @@ page-input = [
"dep:cosmic-comp-config",
"dep:cosmic-settings-config",
"dep:udev",
"dep:xkeysym",
"wayland",
]
page-legacy-applications = ["dep:cosmic-comp-config"]
page-networking = [

View file

@ -2,14 +2,17 @@
// SPDX-License-Identifier: GPL-3.0-only
use cosmic::app::ContextDrawer;
use cosmic::iced::{Alignment, Length};
use cosmic::iced::event::listen_with;
use cosmic::iced::keyboard::key::Named;
use cosmic::iced::keyboard::{Key, Location, Modifiers};
use cosmic::iced::{self, Alignment, Length};
use cosmic::widget::{self, button, icon, settings, text};
use cosmic::{Apply, Element, Task, theme};
use cosmic::{Apply, Element, Task, iced_winit, theme};
use cosmic_config::{ConfigGet, ConfigSet};
use cosmic_settings_config::shortcuts::{self, Action, Binding, Shortcuts};
use cosmic_settings_page as page;
use slab::Slab;
use slotmap::Key;
use slotmap::Key as SlotmapKey;
use std::borrow::Cow;
use std::str::FromStr;
use std::{io, mem};
@ -26,12 +29,18 @@ pub enum ShortcutMessage {
ResetBindings,
ShowShortcut(usize, String),
SubmitBinding(usize),
Inhibited(bool),
ProtocolUnavailable,
ModifiersChanged(Modifiers),
KeyReleased(u32, Key, Location),
KeyPressed(u32, Key, Location, Modifiers),
}
#[derive(Debug)]
pub struct ShortcutBinding {
pub id: widget::Id,
pub binding: Binding,
pub pending: Binding,
pub input: String,
pub is_default: bool,
pub is_saved: bool,
@ -67,6 +76,7 @@ impl ShortcutModel {
slab.insert(ShortcutBinding {
id: widget::Id::unique(),
binding: binding.clone(),
pending: binding.clone(),
input: String::new(),
is_default,
is_saved: true,
@ -150,6 +160,7 @@ impl Model {
pub(super) fn config_add(&self, action: Action, binding: Binding) {
let mut shortcuts = self.shortcuts_config();
shortcuts.0.insert(binding, action);
self.shortcuts_config_set(shortcuts);
}
@ -226,7 +237,7 @@ impl Model {
None
}
pub(super) fn on_enter(&mut self) {
pub(super) fn on_enter(&mut self) -> cosmic::Task<ShortcutMessage> {
let mut shortcuts = self.config.get::<Shortcuts>("defaults").unwrap_or_default();
self.defaults = shortcuts.clone();
@ -240,6 +251,8 @@ impl Model {
self.shortcut_models = (self.actions)(&self.defaults, &shortcuts);
self.shortcut_context = None;
self.editing = None;
return Task::none();
}
pub(super) fn on_context_drawer_close(&mut self) {
@ -313,7 +326,10 @@ impl Model {
self.editing = Some(binding_id);
shortcut.input.clear();
return widget::text_input::focus(shortcut.id.clone());
return Task::batch(vec![
iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(true).discard(),
widget::text_input::focus(shortcut.id.clone())
]);
}
// Create a new input and focus it.
@ -321,16 +337,19 @@ impl Model {
self.editing = Some(model.bindings.insert(ShortcutBinding {
id: id.clone(),
binding: Binding::default(),
pending: Binding::default(),
input: String::new(),
is_default: false,
is_saved: false,
}));
return widget::text_input::focus(id);
return Task::batch(vec![
iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(true).discard(),
widget::text_input::focus(id)
]);
}
}
}
ShortcutMessage::ApplyReplace => {
if let Some((id, new_binding, ..)) = self.replace_dialog.take() {
if let Some(short_id) = self.shortcut_context {
@ -368,11 +387,10 @@ impl Model {
}
}
self.on_enter();
_ = self.on_enter();
}
}
}
ShortcutMessage::CancelReplace => {
if let Some(((id, _, _, _), short_id)) =
self.replace_dialog.take().zip(self.shortcut_context)
@ -385,7 +403,6 @@ impl Model {
}
}
}
ShortcutMessage::DeleteBinding(id) => {
if let Some(short_id) = self.shortcut_context {
if let Some(model) = self.shortcut_models.get_mut(short_id) {
@ -398,41 +415,47 @@ impl Model {
}
}
}
ShortcutMessage::DeleteShortcut(id) => {
let model = self.shortcut_models.remove(id);
for (_, shortcut) in model.bindings {
self.config_remove(&shortcut.binding);
}
}
ShortcutMessage::EditBinding(id, enable) => {
if !enable && self.editing == Some(id) {
self.editing = None;
if let Some(short_id) = self.shortcut_context {
if let Some(model) = self.shortcut_models.get_mut(short_id) {
if let Some(shortcut) = model.bindings.get_mut(id) {
shortcut.pending = shortcut.binding.clone();
}
}
}
return Task::batch(vec![
cosmic::widget::text_input::focus(self.add_keybindings_button_id.clone()),
iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(false).discard()
]);
}
if let Some(short_id) = self.shortcut_context {
if let Some(model) = self.shortcut_models.get_mut(short_id) {
if let Some(shortcut) = model.bindings.get_mut(id) {
if enable {
self.editing = Some(id);
shortcut.input = shortcut.binding.to_string();
return widget::text_input::select_all(shortcut.id.clone());
return iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(true).discard();
} else if self.editing == Some(id) {
self.editing = None;
return iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(false).discard();
}
}
}
}
}
ShortcutMessage::InputBinding(id, text) => {
if let Some(short_id) = self.shortcut_context {
if let Some(model) = self.shortcut_models.get_mut(short_id) {
if let Some(shortcut) = model.bindings.get_mut(id) {
shortcut.input = text;
}
}
ShortcutMessage::InputBinding(bind_id, ..) => {
if self.editing.is_none() {
return self.update(ShortcutMessage::EditBinding(bind_id, true));
}
}
// Removes all bindings from the active shortcut context, and reloads the shortcuts model.
ShortcutMessage::ResetBindings => {
if let Some(short_id) = self.shortcut_context {
if let Some(model) = self.shortcut_models.get(short_id) {
@ -449,10 +472,9 @@ impl Model {
}
}
self.on_enter();
_ = self.on_enter();
}
}
ShortcutMessage::ShowShortcut(id, description) => {
self.shortcut_context = Some(id);
self.shortcut_title = description;
@ -472,13 +494,162 @@ impl Model {
return Task::batch(tasks);
}
ShortcutMessage::SubmitBinding(_) => {}
ShortcutMessage::ProtocolUnavailable => {
tracing::error!("shortcut inhibit protocol is unavailable");
}
ShortcutMessage::Inhibited(v) => {
panic!("{}", v);
}
ShortcutMessage::ModifiersChanged(modifiers) => {
if let Some((short_id, id)) = self.shortcut_context.zip(self.editing) {
if let Some(model) = self.shortcut_models.get_mut(short_id) {
if let Some(shortcut) = model.bindings.get_mut(id) {
let mut cfg_modifiers =
cosmic_settings_config::shortcuts::Modifiers::new();
if modifiers.alt() {
cfg_modifiers = cfg_modifiers.alt()
}
if modifiers.control() {
cfg_modifiers = cfg_modifiers.ctrl()
}
if modifiers.shift() {
cfg_modifiers = cfg_modifiers.shift()
}
if modifiers.logo() {
cfg_modifiers = cfg_modifiers.logo()
}
shortcut.pending.modifiers = cfg_modifiers;
ShortcutMessage::SubmitBinding(id) => return self.submit_binding(id),
if shortcut.pending.keycode.is_none() && modifiers.is_empty() {
self.editing = None;
shortcut.input = String::new();
return Task::batch(vec![
cosmic::widget::text_input::focus(self.add_keybindings_button_id.clone()),
iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(false).discard()
]);
}
shortcut.input = shortcut.pending.to_string();
}
}
}
}
ShortcutMessage::KeyReleased(keycode, _, _) => {
if let Some((short_id, id)) = self.shortcut_context.zip(self.editing) {
if let Some(model) = self.shortcut_models.get_mut(short_id) {
if let Some(shortcut) = model.bindings.get_mut(id) {
// if the currently selected shortcut matches, finish selecting shortcut
if shortcut.pending.key.is_some()
&& shortcut.pending.keycode.is_some_and(|k| k == keycode)
{
if shortcut.pending.modifiers
!= cosmic_settings_config::shortcuts::Modifiers::new()
{
shortcut.input = shortcut.pending.to_string();
// XX for now avoid applying the keycode
shortcut.binding.keycode = None;
return Task::batch(vec![
iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(false).discard(),
self.submit_binding(id)
]);
}
return Task::batch(vec![
cosmic::widget::text_input::focus(self.add_keybindings_button_id.clone()),
iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(false).discard()
]);
}
}
}
}
}
ShortcutMessage::KeyPressed(keycode, unmodified_keysym, location, modifiers) => {
if unmodified_keysym == Key::Named(Named::Escape) && modifiers.is_empty() {
if let Some((short_id, id)) = self.shortcut_context.zip(self.editing) {
if let Some(model) = self.shortcut_models.get_mut(short_id) {
if let Some(binding) = model.bindings.get_mut(id) {
binding.reset();
self.editing = None;
return Task::batch(vec![
cosmic::widget::text_input::focus(self.add_keybindings_button_id.clone()),
iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(false).discard()
]);
}
}
}
return Task::none();
}
if let Some((short_id, id)) = self.shortcut_context.zip(self.editing) {
if let Some(model) = self.shortcut_models.get_mut(short_id) {
if let Some(shortcut) = model.bindings.get_mut(id) {
shortcut.pending.keycode = Some(keycode);
shortcut.pending.key =
iced_winit::platform_specific::wayland::keymap::key_to_keysym(
unmodified_keysym,
location,
);
}
}
}
}
}
Task::none()
}
#[cfg(feature = "wayland")]
pub(crate) fn subscription(
&self,
_core: &cosmic::Core,
) -> cosmic::iced::Subscription<ShortcutMessage> {
if self.editing.is_some() {
listen_with(|event, _, _| match event {
iced::event::Event::Keyboard(iced::keyboard::Event::KeyPressed {
key,
physical_key,
location,
modifiers,
..
}) => {
use cosmic::iced::keyboard::{Key, key::Named};
if matches!(
key,
Key::Named(Named::Super | Named::Alt | Named::Control | Named::Shift)
) {
return None;
}
cosmic::iced_winit::conversion::physical_to_scancode(physical_key)
.map(|code| ShortcutMessage::KeyPressed(code, key, location, modifiers))
}
iced::event::Event::Keyboard(iced::keyboard::Event::KeyReleased {
key,
physical_key,
location,
..
}) => {
use cosmic::iced::keyboard::{Key, key::Named};
if matches!(
key,
Key::Named(Named::Super | Named::Alt | Named::Control | Named::Shift)
) {
return None;
}
cosmic::iced_winit::conversion::physical_to_scancode(physical_key)
.map(|code| ShortcutMessage::KeyReleased(code, key, location))
}
iced::event::Event::Keyboard(iced::keyboard::Event::ModifiersChanged(e)) => {
Some(ShortcutMessage::ModifiersChanged(e))
}
_ => None,
})
} else {
cosmic::iced::Subscription::none()
}
}
pub(super) fn view(&self) -> Element<ShortcutMessage> {
self.shortcut_models
.iter()
@ -535,7 +706,6 @@ impl Model {
if let Some(model) = self.shortcut_models.get_mut(short_id) {
if let Some(shortcut) = model.bindings.get_mut(id) {
let prev_binding = mem::replace(&mut shortcut.binding, new_binding.clone());
shortcut.is_saved = true;
shortcut.input.clear();
@ -544,7 +714,6 @@ impl Model {
}
let action = model.action.clone();
if shortcut.is_default {
self.config_add(Action::Disable, prev_binding);
} else {
@ -604,6 +773,7 @@ fn context_drawer<'a>(
ShortcutMessage::EditBinding(bind_id, enable)
})
.select_on_focus(true)
.on_focus(ShortcutMessage::EditBinding(bind_id, true))
.on_input(move |text| ShortcutMessage::InputBinding(bind_id, text))
.on_unfocus(ShortcutMessage::SubmitBinding(bind_id))
.on_submit(move |_| ShortcutMessage::SubmitBinding(bind_id))

View file

@ -6,14 +6,17 @@ use std::str::FromStr;
use super::{ShortcutBinding, ShortcutMessage, ShortcutModel};
use cosmic::app::ContextDrawer;
use cosmic::iced::keyboard::key::Named;
use cosmic::iced::keyboard::{Key, Location, Modifiers};
use cosmic::iced::{Alignment, Length};
use cosmic::iced_winit;
use cosmic::widget::{self, button, icon};
use cosmic::{Apply, Element, Task};
use cosmic_settings_config::Binding;
use cosmic_settings_config::shortcuts::{Action, Shortcuts};
use cosmic_settings_page::{self as page, Section, section};
use slab::Slab;
use slotmap::{Key, SlotMap};
use slotmap::{Key as SlotKey, SlotMap};
pub struct Page {
entity: page::Entity,
@ -63,6 +66,9 @@ pub enum Message {
Shortcut(ShortcutMessage),
/// Open the add shortcut context drawer
ShortcutContext,
ModifiersChanged(Modifiers),
KeyReleased(u32, Key, Location),
KeyPressed(u32, Key, Location, Modifiers),
}
#[derive(Default)]
@ -72,6 +78,7 @@ struct AddShortcut {
pub name: String,
pub task: String,
pub keys: Slab<(String, widget::Id)>,
pub binding: Binding,
}
impl AddShortcut {
@ -99,17 +106,19 @@ impl Page {
self.add_shortcut.task = text;
}
Message::KeyInput(id, text) => {
self.add_shortcut.keys[id].0 = text;
}
Message::KeyInput(..) => {}
Message::KeyEditing(id, enable) => {
if enable {
self.add_shortcut.editing = Some(id)
self.add_shortcut.editing = Some(id);
return iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(true).discard();
} else if self.add_shortcut.editing == Some(id) {
let task = self.add_keybinding();
self.add_shortcut.editing = None;
return task;
return Task::batch(vec![
widget::text_input::focus(widget::Id::unique()),
iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(false).discard(),
]);
}
}
@ -151,11 +160,13 @@ impl Page {
addable_bindings.push(binding);
}
for binding in addable_bindings {
for mut binding in addable_bindings {
binding.keycode = None;
self.add_shortcut(binding);
}
self.model.on_enter();
self.add_shortcut.binding = Default::default();
_ = self.model.on_enter();
}
Message::EditCombination => {
@ -164,6 +175,10 @@ impl Page {
return Task::batch(vec![
widget::text_input::focus(id.clone()),
widget::text_input::select_all(id.clone()),
iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(
true,
)
.discard()
]);
}
}
@ -175,12 +190,14 @@ impl Page {
}
Message::ReplaceApply => {
if let Some((binding, ..)) = self.replace_dialog.pop() {
if let Some((mut binding, ..)) = self.replace_dialog.pop() {
self.model.config_remove(&binding);
binding.keycode = None;
self.add_shortcut(binding);
if self.replace_dialog.is_empty() {
self.model.on_enter();
self.add_shortcut = Default::default();
_ = self.model.on_enter();
}
}
}
@ -188,7 +205,8 @@ impl Page {
Message::ReplaceCancel => {
_ = self.replace_dialog.pop();
if self.replace_dialog.is_empty() {
self.model.on_enter();
self.add_shortcut = Default::default();
_ = self.model.on_enter();
}
}
@ -207,6 +225,112 @@ impl Page {
widget::text_input::focus(self.name_id.clone()),
]);
}
Message::ModifiersChanged(modifiers) => {
if self.add_shortcut.active {
let mut cfg_modifiers = cosmic_settings_config::shortcuts::Modifiers::new();
if modifiers.alt() {
cfg_modifiers = cfg_modifiers.alt()
}
if modifiers.control() {
cfg_modifiers = cfg_modifiers.ctrl()
}
if modifiers.shift() {
cfg_modifiers = cfg_modifiers.shift()
}
if modifiers.logo() {
cfg_modifiers = cfg_modifiers.logo()
}
self.add_shortcut.binding.modifiers = cfg_modifiers;
if self.add_shortcut.binding.keycode.is_none() && modifiers.is_empty() {
self.add_shortcut = Default::default();
self.add_shortcut = Default::default();
_ = self.model.on_enter();
return Task::batch(vec![
iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(false).discard()
]);
}
if let Some(k) = self
.add_shortcut
.keys
.get_mut(self.add_shortcut.editing.unwrap())
{
k.0 = self.add_shortcut.binding.to_string();
}
}
}
Message::KeyReleased(keycode, _, _) => {
// if the currently selected shortcut matches, finish selecting shortcut
if self.add_shortcut.editing.is_some()
&& self.add_shortcut.active
&& self.add_shortcut.binding.key.is_some()
&& self
.add_shortcut
.binding
.keycode
.is_some_and(|k| k == keycode)
&& self.add_shortcut.binding.modifiers
!= cosmic_settings_config::shortcuts::Modifiers::new()
{
// XX for now avoid applying the keycode
let binding = Binding {
modifiers: self.add_shortcut.binding.modifiers.clone(),
key: self.add_shortcut.binding.key,
keycode: None,
description: None,
};
let Some(k) = self
.add_shortcut
.keys
.get_mut(self.add_shortcut.editing.unwrap())
else {
return iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(false).discard();
};
k.0 = binding.to_string();
if self.add_shortcut.name.trim().is_empty()
|| self.add_shortcut.task.trim().is_empty()
{
return Task::batch(vec![
widget::text_input::focus(widget::Id::unique()),
iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(false).discard(),
]);
}
self.add_shortcut(binding);
self.add_shortcut = Default::default();
_ = self.model.on_enter();
return Task::batch(vec![
iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(false).discard(),
]);
}
}
Message::KeyPressed(keycode, unmodified_keysym, location, modifiers) => {
if unmodified_keysym == Key::Named(Named::Escape) && modifiers.is_empty() {
self.add_shortcut.editing = None;
return Task::batch(vec![
widget::text_input::focus(widget::Id::unique()),
iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(false).discard(),
]);
}
if self.add_shortcut.active {
self.add_shortcut.binding.keycode = Some(keycode);
self.add_shortcut.binding.key =
iced_winit::platform_specific::wayland::keymap::key_to_keysym(
unmodified_keysym,
location,
);
if let Some(k) = self
.add_shortcut
.keys
.get_mut(self.add_shortcut.editing.unwrap())
{
k.0 = self.add_shortcut.binding.to_string();
}
}
}
}
Task::none()
@ -221,7 +345,13 @@ impl Page {
binding.clear();
return widget::text_input::focus(id.clone());
return Task::batch(vec![
widget::text_input::focus(id.clone()),
iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(
true,
)
.discard(),
]);
}
let new_id = widget::Id::unique();
@ -234,6 +364,10 @@ impl Page {
Task::batch(vec![
widget::text_input::focus(new_id.clone()),
widget::text_input::select_all(new_id),
iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(
true,
)
.discard(),
])
}
@ -275,6 +409,7 @@ impl Page {
self.add_shortcut.editing == Some(id),
move |enable| Message::KeyEditing(id, enable),
)
.on_focus(Message::KeyEditing(id, true))
.select_on_focus(true)
.padding([0, 12])
.on_input(move |input| Message::KeyInput(id, input))
@ -381,13 +516,87 @@ impl page::Page<crate::pages::Message> for Page {
}
fn on_enter(&mut self) -> Task<crate::pages::Message> {
self.model.on_enter();
self.add_shortcut = Default::default();
_ = self.model.on_enter();
Task::none()
}
fn on_leave(&mut self) -> Task<crate::pages::Message> {
self.model.on_clear();
Task::none()
_ = self.model.on_clear();
iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(
false,
)
.discard()
}
#[cfg(feature = "wayland")]
fn subscription(
&self,
core: &cosmic::Core,
) -> cosmic::iced::Subscription<crate::pages::Message> {
use cosmic::iced::{self, event::listen_with};
cosmic::iced::Subscription::batch(vec![
if self.add_shortcut.active && self.add_shortcut.editing.is_some() {
listen_with(|event, _, _| match event {
iced::event::Event::Keyboard(iced::keyboard::Event::KeyPressed {
key,
physical_key,
location,
modifiers,
..
}) => {
use cosmic::iced::keyboard::{Key, key::Named};
if matches!(
key,
Key::Named(Named::Super | Named::Alt | Named::Control | Named::Shift)
) {
return None;
}
cosmic::iced_winit::conversion::physical_to_scancode(physical_key).map(
|code| {
crate::pages::Message::CustomShortcuts(Message::KeyPressed(
code, key, location, modifiers,
))
},
)
}
iced::event::Event::Keyboard(iced::keyboard::Event::KeyReleased {
key,
physical_key,
location,
..
}) => {
use cosmic::iced::keyboard::{Key, key::Named};
if matches!(
key,
Key::Named(Named::Super | Named::Alt | Named::Control | Named::Shift)
) {
return None;
}
cosmic::iced_winit::conversion::physical_to_scancode(physical_key).map(
|code| {
crate::pages::Message::CustomShortcuts(Message::KeyReleased(
code, key, location,
))
},
)
}
iced::event::Event::Keyboard(iced::keyboard::Event::ModifiersChanged(e)) => {
Some(crate::pages::Message::CustomShortcuts(
Message::ModifiersChanged(e),
))
}
_ => None,
})
} else {
cosmic::iced::Subscription::none()
},
self.model
.subscription(core)
.map(|m| crate::pages::Message::CustomShortcuts(Message::Shortcut(m))),
])
}
}
@ -406,6 +615,7 @@ fn bindings(_defaults: &Shortcuts, keybindings: &Shortcuts) -> Slab<ShortcutMode
let new_binding = ShortcutBinding {
id: widget::Id::unique(),
binding: binding.clone(),
pending: binding.clone(),
input: String::new(),
is_default: false,
is_saved: true,

View file

@ -64,13 +64,26 @@ impl page::Page<crate::pages::Message> for Page {
}
fn on_enter(&mut self) -> Task<crate::pages::Message> {
self.model.on_enter();
_ = self.model.on_enter();
Task::none()
}
fn on_leave(&mut self) -> Task<crate::pages::Message> {
self.model.on_clear();
Task::none()
_ = self.model.on_clear();
cosmic::iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(
false,
)
.discard()
}
#[cfg(feature = "wayland")]
fn subscription(
&self,
core: &cosmic::Core,
) -> cosmic::iced::Subscription<crate::pages::Message> {
self.model
.subscription(core)
.map(crate::pages::Message::ManageWindowShortcuts)
}
}

View file

@ -64,13 +64,26 @@ impl page::Page<crate::pages::Message> for Page {
}
fn on_enter(&mut self) -> Task<crate::pages::Message> {
self.model.on_enter();
_ = self.model.on_enter();
Task::none()
}
fn on_leave(&mut self) -> Task<crate::pages::Message> {
self.model.on_clear();
Task::none()
_ = self.model.on_clear();
cosmic::iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(
false,
)
.discard()
}
#[cfg(feature = "wayland")]
fn subscription(
&self,
core: &cosmic::Core,
) -> cosmic::iced::Subscription<crate::pages::Message> {
self.model
.subscription(core)
.map(crate::pages::Message::MoveWindowShortcuts)
}
}

View file

@ -65,13 +65,26 @@ impl page::Page<crate::pages::Message> for Page {
}
fn on_enter(&mut self) -> Task<crate::pages::Message> {
self.model.on_enter();
_ = self.model.on_enter();
Task::none()
}
fn on_leave(&mut self) -> Task<crate::pages::Message> {
self.model.on_clear();
Task::none()
_ = self.model.on_clear();
cosmic::iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(
false,
)
.discard()
}
#[cfg(feature = "wayland")]
fn subscription(
&self,
core: &cosmic::Core,
) -> cosmic::iced::Subscription<crate::pages::Message> {
self.model
.subscription(core)
.map(crate::pages::Message::NavShortcuts)
}
}

View file

@ -64,13 +64,26 @@ impl page::Page<crate::pages::Message> for Page {
}
fn on_enter(&mut self) -> Task<crate::pages::Message> {
self.model.on_enter();
_ = self.model.on_enter();
Task::none()
}
fn on_leave(&mut self) -> Task<crate::pages::Message> {
self.model.on_clear();
Task::none()
_ = self.model.on_clear();
cosmic::iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(
false,
)
.discard()
}
#[cfg(feature = "wayland")]
fn subscription(
&self,
core: &cosmic::Core,
) -> cosmic::iced::Subscription<crate::pages::Message> {
self.model
.subscription(core)
.map(crate::pages::Message::SystemShortcuts)
}
}

View file

@ -64,13 +64,26 @@ impl page::Page<crate::pages::Message> for Page {
}
fn on_enter(&mut self) -> Task<crate::pages::Message> {
self.model.on_enter();
_ = self.model.on_enter();
Task::none()
}
fn on_leave(&mut self) -> Task<crate::pages::Message> {
self.model.on_clear();
Task::none()
_ = self.model.on_clear();
cosmic::iced_winit::platform_specific::commands::keyboard_shortcuts_inhibit::inhibit_shortcuts(
false,
)
.discard()
}
#[cfg(feature = "wayland")]
fn subscription(
&self,
core: &cosmic::Core,
) -> cosmic::iced::Subscription<crate::pages::Message> {
self.model
.subscription(core)
.map(crate::pages::Message::TilingShortcuts)
}
}