cloud storage client
git clone https://git.lucas.co/cce-cloud.git
feat: resolve launcher list nav through input.kdl's cce-cloud domain
select_next/select_prev (ctrl+n/ctrl+p defaults). Keys are handled at
the raw keysym layer here, so only single-character chords (optionally
with ctrl) are supported.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PXSsCppeDFmSRRM5NAug5a
src/main.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 45 insertions(+), 2 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index 36f741f..d54ef41 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1940,6 +1940,7 @@ impl AppState {
return;
}
+ let (select_next, select_prev) = *nav_keys();
let logical_key = match event.keysym {
xkeysym::Keysym::Escape => Key::Named(NamedKey::Escape),
xkeysym::Keysym::Return => Key::Named(NamedKey::Enter),
@@ -1951,8 +1952,8 @@ impl AppState {
xkeysym::Keysym::Tab => Key::Named(NamedKey::Tab),
xkeysym::Keysym::Delete => Key::Named(NamedKey::Delete),
xkeysym::Keysym::space => Key::Named(NamedKey::Space),
- xkeysym::Keysym::n if self.ctrl_pressed => Key::Named(NamedKey::ArrowDown),
- xkeysym::Keysym::p if self.ctrl_pressed => Key::Named(NamedKey::ArrowUp),
+ sym if nav_matches(select_next, self.ctrl_pressed, sym) => Key::Named(NamedKey::ArrowDown),
+ sym if nav_matches(select_prev, self.ctrl_pressed, sym) => Key::Named(NamedKey::ArrowUp),
_ => {
if let Some(ref text) = event.utf8 {
Key::Character(text.clone())
@@ -2868,6 +2869,48 @@ fn run_daemon(socket_path: &str) {
}
}
+/// A launcher list-nav chord parsed to (needs_ctrl, key char). This app
+/// handles keys at the raw keysym layer, so only single-character keys
+/// (optionally with ctrl) are supported here.
+fn chord_ctrl_char(chord: &str) -> Option<(bool, char)> {
+ let mut ctrl = false;
+ let mut segs = chord.split('+').map(str::trim);
+ let key = segs.next_back()?;
+ for seg in segs {
+ match seg.to_lowercase().as_str() {
+ "ctrl" | "control" => ctrl = true,
+ _ => return None,
+ }
+ }
+ let mut chars = key.chars();
+ let c = chars.next()?;
+ if chars.next().is_some() {
+ return None;
+ }
+ Some((ctrl, c.to_ascii_lowercase()))
+}
+
+/// input.kdl `cce-cloud` domain: select_next / select_prev (emacs-style
+/// ctrl+n / ctrl+p defaults), resolved once per process.
+fn nav_keys() -> &'static (Option<(bool, char)>, Option<(bool, char)>) {
+ static KEYS: std::sync::OnceLock<(Option<(bool, char)>, Option<(bool, char)>)> = std::sync::OnceLock::new();
+ KEYS.get_or_init(|| {
+ (
+ chord_ctrl_char(&cce_ui::input::app_chord("select_next", "ctrl+n")),
+ chord_ctrl_char(&cce_ui::input::app_chord("select_prev", "ctrl+p")),
+ )
+ })
+}
+
+fn nav_matches(spec: Option<(bool, char)>, ctrl_pressed: bool, sym: xkeysym::Keysym) -> bool {
+ match spec {
+ Some((need_ctrl, c)) => {
+ ctrl_pressed == need_ctrl && sym.key_char().map(|k| k.to_ascii_lowercase()) == Some(c)
+ }
+ None => false,
+ }
+}
+
fn main() {
env_logger::Builder::from_default_env()
.filter_level(log::LevelFilter::Info)