From 13248411a8580faa5329ac76b300bcb32cecf2d7 Mon Sep 17 00:00:00 2001 From: Diamond White Date: Wed, 25 Feb 2026 02:44:35 +0000 Subject: [PATCH 01/35] start to integrate swipetype with keyboardstate --- Cargo.lock | 283 +++++++++++++++++++++- wayvr/Cargo.toml | 4 + wayvr/src/overlays/keyboard/builder.rs | 16 +- wayvr/src/overlays/keyboard/layout.rs | 3 +- wayvr/src/overlays/keyboard/mod.rs | 20 +- wayvr/src/overlays/keyboard/swipe_type.rs | 36 +++ wayvr/src/subsystem/hid/mod.rs | 1 + 7 files changed, 357 insertions(+), 6 deletions(-) create mode 100644 wayvr/src/overlays/keyboard/swipe_type.rs diff --git a/Cargo.lock b/Cargo.lock index a2c4ab85..c4d49f43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -681,6 +681,26 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f09c37bc0e9f0924b7dae9988265ef3c76c88538f41a3b06caf4bed07cee5226" +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] + [[package]] name = "bindgen" version = "0.69.5" @@ -763,6 +783,16 @@ dependencies = [ "piper", ] +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "built" version = "0.8.0" @@ -936,6 +966,17 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "chrono-tz" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf 0.11.3", +] + [[package]] name = "chrono-tz" version = "0.10.4" @@ -943,7 +984,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" dependencies = [ "chrono", - "phf", + "phf 0.12.1", +] + +[[package]] +name = "chrono-tz-build" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" +dependencies = [ + "parse-zoneinfo", + "phf 0.11.3", + "phf_codegen", ] [[package]] @@ -1006,6 +1058,40 @@ dependencies = [ "cc", ] +[[package]] +name = "codes-agency" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809a7790247f949cc35a1f9b497fd13a79ee330e34f049c6e837685363d20faf" +dependencies = [ + "codes-common", + "serde", +] + +[[package]] +name = "codes-common" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b16b1037e7111ea4a4bd4a2b48733b990a76ecc321539858a5ab534792b287" +dependencies = [ + "csv", + "tera", + "tracing", +] + +[[package]] +name = "codes-iso-639" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30dbd0357410908cd59147abbb5d40a9b9a5d9261285c272844cf53dbaa6f7a8" +dependencies = [ + "codes-agency", + "codes-common", + "csv", + "serde", + "tera", +] + [[package]] name = "codespan-reporting" version = "0.13.1" @@ -1302,6 +1388,27 @@ dependencies = [ "typenum", ] +[[package]] +name = "csv" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde_core", +] + +[[package]] +name = "csv-core" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +dependencies = [ + "memchr", +] + [[package]] name = "cursor-icon" version = "1.2.0" @@ -1498,6 +1605,12 @@ dependencies = [ "syn 2.0.113", ] +[[package]] +name = "deunicode" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" + [[package]] name = "digest" version = "0.10.7" @@ -2150,6 +2263,30 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "globset" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "globwalk" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" +dependencies = [ + "bitflags 2.10.0", + "ignore", + "walkdir", +] + [[package]] name = "grid" version = "1.0.0" @@ -2319,6 +2456,15 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + [[package]] name = "humantime" version = "2.3.0" @@ -2512,6 +2658,22 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "ignore" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "image" version = "0.25.9" @@ -3865,6 +4027,15 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "parse-zoneinfo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +dependencies = [ + "regex", +] + [[package]] name = "paste" version = "1.0.15" @@ -3932,13 +4103,51 @@ dependencies = [ "sha2", ] +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared 0.11.3", +] + [[package]] name = "phf" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" dependencies = [ - "phf_shared", + "phf_shared 0.12.1", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", ] [[package]] @@ -5072,6 +5281,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "slug" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" +dependencies = [ + "deunicode", + "wasm-bindgen", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -5266,6 +5485,17 @@ dependencies = [ "syn 2.0.113", ] +[[package]] +name = "super-swipe-engine" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328d2395160cd56a41ac6a1c77b4587b4604f408d79c67d34ae9f27db67f354c" +dependencies = [ + "bincode", + "codes-iso-639", + "swipe-types", +] + [[package]] name = "svg_fmt" version = "0.4.5" @@ -5293,6 +5523,16 @@ dependencies = [ "zeno", ] +[[package]] +name = "swipe-types" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebfd0c6e0e4aaf949ad93dedb75da61a531ce94a46fdd87c11830cfb7c32c8f" +dependencies = [ + "bincode", + "serde", +] + [[package]] name = "symphonia" version = "0.5.5" @@ -5442,6 +5682,28 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "tera" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8004bca281f2d32df3bacd59bc67b312cb4c70cea46cbd79dbe8ac5ed206722" +dependencies = [ + "chrono", + "chrono-tz 0.9.0", + "globwalk", + "humansize", + "lazy_static", + "percent-encoding", + "pest", + "pest_derive", + "rand 0.8.5", + "regex", + "serde", + "serde_json", + "slug", + "unicode-segmentation", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -5878,6 +6140,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + [[package]] name = "url" version = "2.5.7" @@ -5972,6 +6240,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + [[package]] name = "vk-parse" version = "0.15.0" @@ -6293,8 +6567,9 @@ dependencies = [ "ash", "bytes", "chrono", - "chrono-tz", + "chrono-tz 0.10.4", "clap", + "codes-iso-639", "config", "dash-frontend", "dbus", @@ -6326,6 +6601,8 @@ dependencies = [ "smallvec", "smithay", "strum", + "super-swipe-engine", + "swipe-types", "sysinfo", "thiserror 2.0.17", "tracing", diff --git a/wayvr/Cargo.toml b/wayvr/Cargo.toml index 5762f7cb..885da3af 100644 --- a/wayvr/Cargo.toml +++ b/wayvr/Cargo.toml @@ -87,6 +87,10 @@ xcb = { version = "1.6.0", optional = true, features = [ "as-raw-xcb-connection", ] } xkbcommon = { version = "0.8.0" } # 0.9.0 breaks keymap import on some distros +super-swipe-engine = "0.1.9" +codes-iso-639 = "0.1.5" +swipe-types = "0.1.6" + [build-dependencies] regex.workspace = true diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index 874d8516..6bf78696 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -12,6 +12,7 @@ use crate::{ windowing::backend::OverlayEventData, }; use anyhow::Context; +use dbus::arg::Append; use glam::{FloatExt, Mat4, Vec2, vec2, vec3}; use wgui::{ animation::{Animation, AnimationEasing}, @@ -108,6 +109,7 @@ pub(super) fn create_keyboard_panel( KeyButtonData::Modifier { modifier, .. } => Some(modifier), _ => None, }; + let key_label: Rc> = Rc::from(key.label.clone()); // todo: make this easier to maintain somehow let mut params: HashMap, Rc> = HashMap::new(); @@ -115,7 +117,7 @@ pub(super) fn create_keyboard_panel( params.insert(Rc::from("width"), Rc::from(key_width.to_string())); params.insert(Rc::from("height"), Rc::from(key_height.to_string())); - let mut label = key.label.into_iter(); + let mut label = key.label.clone().into_iter(); label .next() .and_then(|s| params.insert("text".into(), s.into())); @@ -216,11 +218,21 @@ pub(super) fn create_keyboard_panel( EventListenerKind::MousePress, Box::new({ let k = key_state.clone(); + let k_label = key_label.clone(); move |common, data, app, state| { let CallbackMetadata::MouseButton(button) = data.metadata else { panic!("CallbackMetadata should contain MouseButton!"); }; + println!("pressed {:?}", &k_label); + // match key.cap_type { + // KeyCapType::Letter => { + // let cloned_label = &key.label.clone(); + // state.current_swipe_input.push_str(cloned_label.iter().next().unwrap()) + // } + // _ => {} + // } + handle_press(app, &k, state, button); on_press_anim(k.clone(), common, data); Ok(EventResult::Pass) @@ -232,10 +244,12 @@ pub(super) fn create_keyboard_panel( EventListenerKind::MouseRelease, Box::new({ let k = key_state.clone(); + let k_label = key_label.clone(); move |common, data, app, state| { if handle_release(app, &k, state) { on_release_anim(k.clone(), common, data); } + println!("released {:?}", &k_label); Ok(EventResult::Pass) } }), diff --git a/wayvr/src/overlays/keyboard/layout.rs b/wayvr/src/overlays/keyboard/layout.rs index 2af0590f..df6fb077 100644 --- a/wayvr/src/overlays/keyboard/layout.rs +++ b/wayvr/src/overlays/keyboard/layout.rs @@ -2,7 +2,8 @@ use std::{collections::HashMap, str::FromStr, sync::LazyLock}; use regex::Regex; use serde::{Deserialize, Serialize}; - +use smithay::reexports::wayland_protocols::wp::pointer_gestures::zv1::server::zwp_pointer_gesture_swipe_v1::ZwpPointerGestureSwipeV1; +use swipe_types::types::Point; use crate::{ config::{ConfigType, load_known_yaml}, subsystem::hid::{ diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index 61f63791..18d79dc8 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -30,6 +30,7 @@ use anyhow::Context; use glam::{Affine3A, Quat, Vec3, vec3}; use regex::Regex; use slotmap::{SlotMap, new_key_type}; +use super_swipe_engine::SwipeEngine; use wgui::{ drawing, event::{InternalStateChangeEvent, MouseButtonEvent, MouseButtonIndex}, @@ -39,9 +40,12 @@ use wlx_common::{ config::AltModifier, overlays::{BackendAttrib, BackendAttribValue}, }; +use codes_iso_639::part_1::LanguageCode; +use crate::overlays::keyboard::swipe_type::build_key_to_char_point_map; pub mod builder; mod layout; +mod swipe_type; pub const KEYBOARD_NAME: &str = "kbd"; const AUTO_RELEASE_MODS: [KeyModifier; 5] = [SHIFT, CTRL, ALT, SUPER, META]; @@ -56,6 +60,8 @@ pub fn create_keyboard(app: &mut AppState, wayland: bool) -> anyhow::Result, app: &mut AppState, ) -> anyhow::Result { + let mut state = self.default_state.take(); + let layout_name = keymap.and_then(|k| k.get_name()).unwrap_or("us"); + + if layout_name == "us" { + let point_map = build_key_to_char_point_map(keymap, &self.wlx_layout); + state.swipe_engine = SwipeEngine::new(LanguageCode::En, Some(point_map)).and_then(|s| Ok(Some(s))).unwrap_or(None); + } + let panel = - create_keyboard_panel(app, keymap, self.default_state.take(), &self.wlx_layout)?; + create_keyboard_panel(app, keymap, state, &self.wlx_layout)?; let id = self.layout_panels.insert(panel); if let Some(layout_name) = keymap.and_then(|k| k.get_name()) { @@ -327,6 +341,8 @@ struct KeyboardState { overlay_list: OverlayList, set_list: SetList, clock_12h: bool, + swipe_engine: Option, + current_swipe_input: String, } macro_rules! take_and_leave_default { @@ -346,6 +362,8 @@ impl KeyboardState { overlay_list: OverlayList::default(), set_list: SetList::default(), clock_12h: self.clock_12h, + swipe_engine: None, + current_swipe_input: String::new(), } } } diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs new file mode 100644 index 00000000..50b6c96c --- /dev/null +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -0,0 +1,36 @@ +use std::collections::HashMap; +use strum::IntoEnumIterator; +use swipe_types::types::Point; +use crate::overlays::keyboard::layout; +use crate::overlays::keyboard::layout::KeyCapType; +use crate::subsystem::hid::{get_key_type, KeyType, VirtualKey, XkbKeymap}; + + +pub fn build_key_to_char_point_map(keymap: Option<&XkbKeymap>, layout: &layout::Layout) -> HashMap { + let mut map = HashMap::new(); + + let has_altgr = keymap.as_ref().is_some_and(|m| XkbKeymap::has_altgr(m)); + let mut pos_x: f32 = 0.0; + let mut pos_y: f32 = 0.0; + + for (row_idx, row) in layout.main_layout.iter().enumerate() { + for (col_idx, col) in row.iter().enumerate() { + + let label = layout.get_key_data(keymap, has_altgr, col_idx, row_idx); + if let Some(label) = label { + match label.cap_type { + KeyCapType::Letter => { + if let Some(char) = label.label.iter().next() { + map.insert(char.to_ascii_lowercase().chars().next().unwrap(), Point {x: pos_x as f64, y: pos_y as f64}); + } + } + _ => {} + } + } + pos_x += layout.key_sizes[row_idx][col_idx]; + } + pos_y += layout.row_size; + pos_x = 0.0; + } + map +} diff --git a/wayvr/src/subsystem/hid/mod.rs b/wayvr/src/subsystem/hid/mod.rs index ed446d12..7642137e 100644 --- a/wayvr/src/subsystem/hid/mod.rs +++ b/wayvr/src/subsystem/hid/mod.rs @@ -337,6 +337,7 @@ pub const META: KeyModifier = 0x80; #[allow(non_camel_case_types)] #[repr(u16)] #[derive(Debug, Deserialize, PartialEq, Eq, Clone, Copy, IntegerId, EnumString, EnumIter)] +#[derive(Hash)] pub enum VirtualKey { Escape = 9, N1, // number row From 5db05a7802726da80c2b52b5e15b0e74ead24bad Mon Sep 17 00:00:00 2001 From: Diamond White Date: Wed, 25 Feb 2026 03:47:53 +0000 Subject: [PATCH 02/35] successful debug implementation --- wayvr/src/overlays/keyboard/builder.rs | 43 +++++++++++++++++------ wayvr/src/overlays/keyboard/mod.rs | 7 ++-- wayvr/src/overlays/keyboard/swipe_type.rs | 6 ++-- 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index 6bf78696..a997686b 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -26,7 +26,7 @@ use wgui::{ taffy::{self, prelude::length}, widget::{EventResult, div::WidgetDiv, rectangle::WidgetRectangle}, }; - +use crate::overlays::keyboard::layout::KeyData; use super::{ KeyButtonData, KeyState, KeyboardState, handle_press, handle_release, layout::{self, KeyCapType}, @@ -109,7 +109,6 @@ pub(super) fn create_keyboard_panel( KeyButtonData::Modifier { modifier, .. } => Some(modifier), _ => None, }; - let key_label: Rc> = Rc::from(key.label.clone()); // todo: make this easier to maintain somehow let mut params: HashMap, Rc> = HashMap::new(); @@ -173,6 +172,9 @@ pub(super) fn create_keyboard_panel( }) }; + let key_cap_type: Rc = Rc::from(key.cap_type); + let key_label: Rc> = Rc::from(key.label); + let width_mul = 1. / my_size_f32; panel.add_event_listener( @@ -180,7 +182,9 @@ pub(super) fn create_keyboard_panel( EventListenerKind::MouseEnter, Box::new({ let k = key_state.clone(); - move |common, data, _app, _state| { + let k_label = key_label.clone(); + let k_cap_type = key_cap_type.clone(); + move |common, data, _app, state| { common.alterables.trigger_haptics(); on_enter_anim( k.clone(), @@ -190,6 +194,16 @@ pub(super) fn create_keyboard_panel( anim_mult, width_mul, ); + + if !&state.current_swipe_input.is_empty() { + match k_cap_type.as_ref() { + KeyCapType::Letter => { + state.current_swipe_input.push_str(&*k_label.iter().next().unwrap().to_ascii_lowercase()) + } + _ => {} + } + + } Ok(EventResult::Pass) } }), @@ -209,6 +223,7 @@ pub(super) fn create_keyboard_panel( anim_mult, width_mul, ); + Ok(EventResult::Pass) } }), @@ -219,19 +234,20 @@ pub(super) fn create_keyboard_panel( Box::new({ let k = key_state.clone(); let k_label = key_label.clone(); + let k_cap_type = key_cap_type.clone(); move |common, data, app, state| { let CallbackMetadata::MouseButton(button) = data.metadata else { panic!("CallbackMetadata should contain MouseButton!"); }; println!("pressed {:?}", &k_label); - // match key.cap_type { - // KeyCapType::Letter => { - // let cloned_label = &key.label.clone(); - // state.current_swipe_input.push_str(cloned_label.iter().next().unwrap()) - // } - // _ => {} - // } + match k_cap_type.as_ref() { + KeyCapType::Letter => { + state.current_swipe_input.clear(); + state.current_swipe_input.push_str(&*k_label.iter().next().unwrap().to_ascii_lowercase()) + } + _ => {} + } handle_press(app, &k, state, button); on_press_anim(k.clone(), common, data); @@ -245,10 +261,17 @@ pub(super) fn create_keyboard_panel( Box::new({ let k = key_state.clone(); let k_label = key_label.clone(); + let k_cap_type = key_cap_type.clone(); move |common, data, app, state| { if handle_release(app, &k, state) { on_release_anim(k.clone(), common, data); } + + if let Some(engine) = &state.swipe_engine { + let prediction = engine.predict(&state.current_swipe_input, None, 5); + println!("swipe path: {}", &state.current_swipe_input); + println!("{:?}", prediction); + } println!("released {:?}", &k_label); Ok(EventResult::Pass) } diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index 18d79dc8..ffdb1183 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -159,11 +159,10 @@ impl KeyboardBackend { let mut state = self.default_state.take(); let layout_name = keymap.and_then(|k| k.get_name()).unwrap_or("us"); - if layout_name == "us" { - let point_map = build_key_to_char_point_map(keymap, &self.wlx_layout); - state.swipe_engine = SwipeEngine::new(LanguageCode::En, Some(point_map)).and_then(|s| Ok(Some(s))).unwrap_or(None); - } + let point_map = build_key_to_char_point_map(keymap, &self.wlx_layout); + state.swipe_engine = SwipeEngine::new(LanguageCode::En, Some(point_map)).and_then(|s| Ok(Some(s))).unwrap_or(None); + log::info!("swipe engine created"); let panel = create_keyboard_panel(app, keymap, state, &self.wlx_layout)?; diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs index 50b6c96c..83259116 100644 --- a/wayvr/src/overlays/keyboard/swipe_type.rs +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -21,7 +21,9 @@ pub fn build_key_to_char_point_map(keymap: Option<&XkbKeymap>, layout: &layout:: match label.cap_type { KeyCapType::Letter => { if let Some(char) = label.label.iter().next() { - map.insert(char.to_ascii_lowercase().chars().next().unwrap(), Point {x: pos_x as f64, y: pos_y as f64}); + let point = Point {x: pos_x as f64, y: pos_y as f64}; + println!("char: {} \n point: {:?}\n",char, point); + map.insert(char.to_ascii_lowercase().chars().next().unwrap(), point); } } _ => {} @@ -29,7 +31,7 @@ pub fn build_key_to_char_point_map(keymap: Option<&XkbKeymap>, layout: &layout:: } pos_x += layout.key_sizes[row_idx][col_idx]; } - pos_y += layout.row_size; + pos_y += 1.0; pos_x = 0.0; } map From b7ed16348433653681f00e139b6be0e9d9597328 Mon Sep 17 00:00:00 2001 From: Diamond White Date: Wed, 25 Feb 2026 19:22:36 +0000 Subject: [PATCH 03/35] change to git dependency --- Cargo.lock | 5 ++--- wayvr/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c4d49f43..7ea5f283 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5487,9 +5487,8 @@ dependencies = [ [[package]] name = "super-swipe-engine" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "328d2395160cd56a41ac6a1c77b4587b4604f408d79c67d34ae9f27db67f354c" +version = "0.1.10" +source = "git+https://github.com/oneshinyboi/swipeType?tag=v0.1.10#eeb5e7f841d6ac38480986e6a970ca0ae2a78560" dependencies = [ "bincode", "codes-iso-639", diff --git a/wayvr/Cargo.toml b/wayvr/Cargo.toml index 885da3af..049b3fa6 100644 --- a/wayvr/Cargo.toml +++ b/wayvr/Cargo.toml @@ -87,7 +87,7 @@ xcb = { version = "1.6.0", optional = true, features = [ "as-raw-xcb-connection", ] } xkbcommon = { version = "0.8.0" } # 0.9.0 breaks keymap import on some distros -super-swipe-engine = "0.1.9" +super-swipe-engine = {git="https://github.com/oneshinyboi/swipeType", tag = "v0.1.10"} codes-iso-639 = "0.1.5" swipe-types = "0.1.6" From 66f06abcc5af3d387900fa84ad3e819a88ef39ca Mon Sep 17 00:00:00 2001 From: Diamond White Date: Wed, 25 Feb 2026 19:56:00 +0000 Subject: [PATCH 04/35] add manual trigger to app image build to test --- .github/workflows/build-appimage.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-appimage.yml b/.github/workflows/build-appimage.yml index 9eb37b2c..d5bcc0e6 100644 --- a/.github/workflows/build-appimage.yml +++ b/.github/workflows/build-appimage.yml @@ -1,6 +1,7 @@ name: Build AppImage on: + workflow_dispatch: push: branches: - 'main' From 452ccf5eff7b0bc3f4d7930a6b0ef0ee0fdd35ae Mon Sep 17 00:00:00 2001 From: Diamond White Date: Wed, 25 Feb 2026 20:18:58 +0000 Subject: [PATCH 05/35] use latest swipeType lib to test including binaries --- wayvr/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wayvr/Cargo.toml b/wayvr/Cargo.toml index 049b3fa6..78e74430 100644 --- a/wayvr/Cargo.toml +++ b/wayvr/Cargo.toml @@ -87,7 +87,7 @@ xcb = { version = "1.6.0", optional = true, features = [ "as-raw-xcb-connection", ] } xkbcommon = { version = "0.8.0" } # 0.9.0 breaks keymap import on some distros -super-swipe-engine = {git="https://github.com/oneshinyboi/swipeType", tag = "v0.1.10"} +super-swipe-engine = {git="https://github.com/oneshinyboi/swipeType"} codes-iso-639 = "0.1.5" swipe-types = "0.1.6" From e45756d7879b3eafdf382952c4e67f42c118417f Mon Sep 17 00:00:00 2001 From: Diamond White Date: Sat, 28 Feb 2026 04:11:13 +0000 Subject: [PATCH 06/35] use clipboard to paste swipe output --- Cargo.lock | 617 +++++++++++++++++++++- wayvr/Cargo.toml | 3 +- wayvr/src/overlays/keyboard/builder.rs | 34 +- wayvr/src/overlays/keyboard/layout.rs | 2 +- wayvr/src/overlays/keyboard/mod.rs | 145 ++++- wayvr/src/overlays/keyboard/swipe_type.rs | 17 +- wayvr/src/subsystem/hid/mod.rs | 1 + 7 files changed, 750 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ea5f283..c134c9c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,6 +33,17 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.8.12" @@ -243,6 +254,27 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +[[package]] +name = "arboard" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2 0.6.3", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "parking_lot", + "percent-encoding", + "windows-sys 0.60.2", + "wl-clipboard-rs", + "x11rb", +] + [[package]] name = "arg_enum_proc_macro" version = "0.3.4" @@ -675,6 +707,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "bcdec_rs" version = "0.2.0" @@ -843,6 +881,48 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "cached-path" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56aaf5c3daef0c2cc113b07a624b17317754fc2144821df7cb755c0a5494be3e" +dependencies = [ + "flate2", + "fs2", + "glob", + "infer", + "log", + "rand 0.8.5", + "reqwest", + "serde", + "serde_json", + "sha2", + "tar", + "tempfile", + "thiserror 1.0.69", + "zip", +] + [[package]] name = "calloop" version = "0.13.0" @@ -921,6 +1001,17 @@ dependencies = [ "nom 7.1.3", ] +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + [[package]] name = "cfg-expr" version = "0.15.8" @@ -998,6 +1089,16 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -1049,6 +1150,15 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + [[package]] name = "cmake" version = "0.1.57" @@ -1174,6 +1284,12 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "convert_case" version = "0.4.0" @@ -1619,6 +1735,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -1870,6 +1987,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + [[package]] name = "etagere" version = "0.2.15" @@ -1931,6 +2054,26 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.113", +] + [[package]] name = "fdeflate" version = "0.3.7" @@ -1940,6 +2083,17 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "filetime" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +dependencies = [ + "cfg-if", + "libc", + "libredox", +] + [[package]] name = "find-msvc-tools" version = "0.1.6" @@ -2075,6 +2229,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "futures" version = "0.3.31" @@ -2411,6 +2575,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "hound" version = "3.5.1" @@ -2493,6 +2666,45 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.64" @@ -2691,8 +2903,9 @@ dependencies = [ "png 0.18.0", "ravif", "rayon", - "zune-core", - "zune-jpeg", + "tiff", + "zune-core 0.5.0", + "zune-jpeg 0.5.8", ] [[package]] @@ -2757,6 +2970,24 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "input-linux" version = "0.7.1" @@ -2803,6 +3034,22 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -3557,6 +3804,18 @@ dependencies = [ "objc2-quartz-core 0.2.2", ] +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.10.0", + "objc2 0.6.3", + "objc2-core-graphics", + "objc2-foundation 0.3.2", +] + [[package]] name = "objc2-audio-toolbox" version = "0.3.2" @@ -3641,6 +3900,19 @@ dependencies = [ "objc2 0.6.3", ] +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.10.0", + "dispatch2", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-io-surface", +] + [[package]] name = "objc2-core-image" version = "0.2.2" @@ -3705,6 +3977,17 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.10.0", + "objc2 0.6.3", + "objc2-core-foundation", +] + [[package]] name = "objc2-link-presentation" version = "0.2.2" @@ -3713,7 +3996,7 @@ checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ "block2", "objc2 0.5.2", - "objc2-app-kit", + "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", ] @@ -3938,6 +4221,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "os_pipe" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "ouroboros" version = "0.18.5" @@ -4036,6 +4329,17 @@ dependencies = [ "regex", ] +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "paste" version = "1.0.15" @@ -4054,6 +4358,18 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -4103,6 +4419,17 @@ dependencies = [ "sha2", ] +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap 2.12.1", +] + [[package]] name = "phf" version = "0.11.3" @@ -4737,6 +5064,44 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "resvg" version = "0.45.1" @@ -4919,6 +5284,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -5119,6 +5493,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" @@ -5132,6 +5518,17 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.9" @@ -5485,12 +5882,19 @@ dependencies = [ "syn 2.0.113", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "super-swipe-engine" -version = "0.1.10" -source = "git+https://github.com/oneshinyboi/swipeType?tag=v0.1.10#eeb5e7f841d6ac38480986e6a970ca0ae2a78560" +version = "0.1.11" +source = "git+https://github.com/oneshinyboi/swipeType?tag=v0.1.11#5f9ac506f97ffa60186cc74ab723340fd0362dd9" dependencies = [ "bincode", + "cached-path", "codes-iso-639", "swipe-types", ] @@ -5603,6 +6007,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -5662,6 +6075,17 @@ dependencies = [ "slotmap", ] +[[package]] +name = "tar" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "target-lexicon" version = "0.12.16" @@ -5761,6 +6185,20 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "tiff" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg 0.4.21", +] + [[package]] name = "time" version = "0.3.47" @@ -5878,6 +6316,16 @@ dependencies = [ "syn 2.0.113", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -5968,6 +6416,51 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.44" @@ -6030,6 +6523,17 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "tree_magic_mini" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6" +dependencies = [ + "memchr", + "nom 8.0.0", + "petgraph", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -6563,6 +7067,7 @@ name = "wayvr" version = "26.2.1" dependencies = [ "anyhow", + "arboard", "ash", "bytes", "chrono", @@ -7172,7 +7677,7 @@ dependencies = [ "memmap2", "ndk", "objc2 0.5.2", - "objc2-app-kit", + "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", "objc2-ui-kit", "orbclient", @@ -7215,6 +7720,24 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "wl-clipboard-rs" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9651471a32e87d96ef3a127715382b2d11cc7c8bb9822ded8a7cc94072eb0a3" +dependencies = [ + "libc", + "log", + "os_pipe", + "rustix 1.1.3", + "thiserror 2.0.17", + "tree_magic_mini", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-wlr", +] + [[package]] name = "wlx-capture" version = "0.6.0" @@ -7293,6 +7816,16 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix 1.1.3", +] + [[package]] name = "xcb" version = "1.7.0" @@ -7544,6 +8077,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + [[package]] name = "zerotrie" version = "0.2.3" @@ -7577,12 +8116,67 @@ dependencies = [ "syn 2.0.113", ] +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "aes", + "byteorder", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2", + "sha1", + "time", + "zstd", +] + [[package]] name = "zmij" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30e0d8dffbae3d840f64bda38e28391faef673a7b5a6017840f2a106c8145868" +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + [[package]] name = "zune-core" version = "0.5.0" @@ -7598,13 +8192,22 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core 0.4.12", +] + [[package]] name = "zune-jpeg" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e35aee689668bf9bd6f6f3a6c60bb29ba1244b3b43adfd50edd554a371da37d5" dependencies = [ - "zune-core", + "zune-core 0.5.0", ] [[package]] diff --git a/wayvr/Cargo.toml b/wayvr/Cargo.toml index 78e74430..1f4bbdf8 100644 --- a/wayvr/Cargo.toml +++ b/wayvr/Cargo.toml @@ -87,9 +87,10 @@ xcb = { version = "1.6.0", optional = true, features = [ "as-raw-xcb-connection", ] } xkbcommon = { version = "0.8.0" } # 0.9.0 breaks keymap import on some distros -super-swipe-engine = {git="https://github.com/oneshinyboi/swipeType"} +super-swipe-engine = {git="https://github.com/oneshinyboi/swipeType", tag="v0.1.11"} codes-iso-639 = "0.1.5" swipe-types = "0.1.6" +arboard = { version="3.6.1", features = ["wayland-data-control", "wl-clipboard-rs"] } [build-dependencies] diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index a997686b..18783ca8 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -27,10 +27,7 @@ use wgui::{ widget::{EventResult, div::WidgetDiv, rectangle::WidgetRectangle}, }; use crate::overlays::keyboard::layout::KeyData; -use super::{ - KeyButtonData, KeyState, KeyboardState, handle_press, handle_release, - layout::{self, KeyCapType}, -}; +use super::{KeyButtonData, KeyState, KeyboardState, handle_press, handle_release, layout::{self, KeyCapType}, handle_enter}; const PIXELS_PER_UNIT: f32 = 60.; @@ -195,15 +192,7 @@ pub(super) fn create_keyboard_panel( width_mul, ); - if !&state.current_swipe_input.is_empty() { - match k_cap_type.as_ref() { - KeyCapType::Letter => { - state.current_swipe_input.push_str(&*k_label.iter().next().unwrap().to_ascii_lowercase()) - } - _ => {} - } - - } + handle_enter(&k, &k_label, &k_cap_type, state); Ok(EventResult::Pass) } }), @@ -240,16 +229,7 @@ pub(super) fn create_keyboard_panel( panic!("CallbackMetadata should contain MouseButton!"); }; - println!("pressed {:?}", &k_label); - match k_cap_type.as_ref() { - KeyCapType::Letter => { - state.current_swipe_input.clear(); - state.current_swipe_input.push_str(&*k_label.iter().next().unwrap().to_ascii_lowercase()) - } - _ => {} - } - - handle_press(app, &k, state, button); + handle_press(app, &k, &k_cap_type, &k_label, state, button); on_press_anim(k.clone(), common, data); Ok(EventResult::Pass) } @@ -263,16 +243,10 @@ pub(super) fn create_keyboard_panel( let k_label = key_label.clone(); let k_cap_type = key_cap_type.clone(); move |common, data, app, state| { - if handle_release(app, &k, state) { + if handle_release(app, &k, &k_cap_type, state) { on_release_anim(k.clone(), common, data); } - if let Some(engine) = &state.swipe_engine { - let prediction = engine.predict(&state.current_swipe_input, None, 5); - println!("swipe path: {}", &state.current_swipe_input); - println!("{:?}", prediction); - } - println!("released {:?}", &k_label); Ok(EventResult::Pass) } }), diff --git a/wayvr/src/overlays/keyboard/layout.rs b/wayvr/src/overlays/keyboard/layout.rs index df6fb077..b53d7b65 100644 --- a/wayvr/src/overlays/keyboard/layout.rs +++ b/wayvr/src/overlays/keyboard/layout.rs @@ -231,7 +231,7 @@ pub(super) struct KeyData { pub(super) cap_type: KeyCapType, } -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub enum KeyCapType { /// Label an SVG Special, diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index ffdb1183..ff359c97 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -27,6 +27,7 @@ use crate::{ }, }; use anyhow::Context; +use arboard::Clipboard; use glam::{Affine3A, Quat, Vec3, vec3}; use regex::Regex; use slotmap::{SlotMap, new_key_type}; @@ -41,7 +42,8 @@ use wlx_common::{ overlays::{BackendAttrib, BackendAttribValue}, }; use codes_iso_639::part_1::LanguageCode; -use crate::overlays::keyboard::swipe_type::build_key_to_char_point_map; +use crate::overlays::keyboard::layout::KeyCapType; +use crate::overlays::keyboard::swipe_type::{copy_text_to_primary_clipboard, create_new_swipe_engine}; pub mod builder; mod layout; @@ -62,6 +64,10 @@ pub fn create_keyboard(app: &mut AppState, wayland: bool) -> anyhow::Result anyhow::Result { let mut state = self.default_state.take(); - let layout_name = keymap.and_then(|k| k.get_name()).unwrap_or("us"); - let point_map = build_key_to_char_point_map(keymap, &self.wlx_layout); - state.swipe_engine = SwipeEngine::new(LanguageCode::En, Some(point_map)).and_then(|s| Ok(Some(s))).unwrap_or(None); + state.swipe_engine = match create_new_swipe_engine(&keymap, &self.wlx_layout) { + Ok(engine) => Some(engine), + Err(e) => { + log::error!("Error occured while trying to load swipe engine: {:?}", e); + None + } + }; log::info!("swipe engine created"); let panel = @@ -189,24 +199,31 @@ impl KeyboardBackend { if self.active_layout.eq(new_key) { return Ok(false); } - self.internal_switch_keymap(*new_key); + self.internal_switch_keymap(*new_key, keymap); } else { let new_key = self.add_new_keymap(Some(keymap), app)?; - self.internal_switch_keymap(new_key); + self.internal_switch_keymap(new_key, keymap); } app.tasks .enqueue(TaskType::Overlay(OverlayTask::KeyboardChanged)); Ok(true) } - fn internal_switch_keymap(&mut self, new_key: KeyboardPanelKey) { - let state_from = self + fn internal_switch_keymap(&mut self, new_key: KeyboardPanelKey, keymap: &XkbKeymap) { + let mut state_from = self .layout_panels .get_mut(self.active_layout) .unwrap() .state .take(); + state_from.swipe_engine = match create_new_swipe_engine(&Some(keymap), &self.wlx_layout) { + Ok(engine) => Some(engine), + Err(e) => { + log::error!("Error occured while trying to load swipe engine: {:?}", e); + None + } + }; self.active_layout = new_key; self.layout_panels @@ -340,8 +357,15 @@ struct KeyboardState { overlay_list: OverlayList, set_list: SetList, clock_12h: bool, + + // todo move all this swipe stuff into its own class swipe_engine: Option, current_swipe_input: String, + last_pressed_key_label: String, + is_swiping: bool, + clipboard: Clipboard, + last_swiped_word: Option + } macro_rules! take_and_leave_default { @@ -363,6 +387,10 @@ impl KeyboardState { clock_12h: self.clock_12h, swipe_engine: None, current_swipe_input: String::new(), + is_swiping: false, + last_pressed_key_label: String::new(), + clipboard: Clipboard::new().unwrap(), + last_swiped_word: None } } } @@ -403,26 +431,51 @@ enum KeyButtonData { }, } +fn handle_enter(key: &KeyState, key_label: &Vec, key_cap_type: &KeyCapType, keyboard: &mut KeyboardState) { + if let Some(_) = keyboard.swipe_engine.as_ref() && *key_cap_type == KeyCapType::Letter { + if *key_label.iter().next().unwrap() != keyboard.last_pressed_key_label { + keyboard.is_swiping = true; + } + if keyboard.is_swiping { + match &key.button_state { + KeyButtonData::Key { vk, pressed } => { + keyboard.current_swipe_input.push_str(&*key_label.iter().next().unwrap().to_ascii_lowercase()) + } + _ => {} + } + } + } +} fn handle_press( app: &mut AppState, key: &KeyState, + key_cap_type: &KeyCapType, + key_label: &Vec, keyboard: &mut KeyboardState, button: MouseButtonEvent, ) { + keyboard.is_swiping = false; match &key.button_state { KeyButtonData::Key { vk, pressed } => { - keyboard.modifiers |= match button.index { - MouseButtonIndex::Right => SHIFT, - MouseButtonIndex::Middle => keyboard.alt_modifier, - _ => 0, - }; - - app.hid_provider - .set_modifiers_routed(app.wvr_server.as_mut(), keyboard.modifiers); - app.hid_provider - .send_key_routed(app.wvr_server.as_mut(), *vk, true); - pressed.set(true); - play_key_click(app); + if let Some(_) = keyboard.swipe_engine.as_ref() && *key_cap_type == KeyCapType::Letter { + let actual_label = key_label.iter().next().unwrap(); + keyboard.last_pressed_key_label = actual_label.clone(); + keyboard.current_swipe_input.clear(); + keyboard.current_swipe_input.push_str(&*actual_label.to_ascii_lowercase()) + } + else { + keyboard.modifiers |= match button.index { + MouseButtonIndex::Right => SHIFT, + MouseButtonIndex::Middle => keyboard.alt_modifier, + _ => 0, + }; + app.hid_provider + .set_modifiers_routed(app.wvr_server.as_mut(), keyboard.modifiers); + app.hid_provider + .send_key_routed(app.wvr_server.as_mut(), *vk, true); + pressed.set(true); + play_key_click(app); + } } KeyButtonData::Modifier { modifier, sticky } => { sticky.set(keyboard.modifiers & *modifier == 0); @@ -452,20 +505,54 @@ fn handle_press( } } -fn handle_release(app: &mut AppState, key: &KeyState, keyboard: &mut KeyboardState) -> bool { +fn handle_release(app: &mut AppState, key: &KeyState, k_cap_type: &KeyCapType, keyboard: &mut KeyboardState) -> bool { match &key.button_state { KeyButtonData::Key { vk, pressed } => { - pressed.set(false); + if let Some(engine) = keyboard.swipe_engine.as_ref() && *k_cap_type == KeyCapType::Letter { + if keyboard.is_swiping { + if !keyboard.current_swipe_input.is_empty() { + let prediction = engine.predict(&*keyboard.current_swipe_input, keyboard.last_swiped_word.as_ref().map(|x| x.as_str()), 5); + keyboard.current_swipe_input.clear(); + println!("swipe path: {}", keyboard.current_swipe_input); + println!("{:?}", prediction); - for m in &AUTO_RELEASE_MODS { - if keyboard.modifiers & *m != 0 { - keyboard.modifiers &= !*m; + let best_prediction = prediction.first().unwrap().word.as_ref(); + + copy_text_to_primary_clipboard(best_prediction, &mut keyboard.clipboard); + app.hid_provider + .set_modifiers_routed(app.wvr_server.as_mut(), SHIFT); + app.hid_provider + .send_key_routed(app.wvr_server.as_mut(), VirtualKey::Insert, true); + app.hid_provider + .send_key_routed(app.wvr_server.as_mut(), VirtualKey::Insert, false); + app.hid_provider + .set_modifiers_routed(app.wvr_server.as_mut(), keyboard.modifiers); + keyboard.last_swiped_word = Some(best_prediction.parse().unwrap()) + } } + else { // pointer must have been released on the same key it was pressed on + app.hid_provider + .send_key_routed(app.wvr_server.as_mut(), *vk, true); + pressed.set(true); + app.hid_provider + .send_key_routed(app.wvr_server.as_mut(), *vk, false); + play_key_click(app); + } + + } + else { + pressed.set(false); + + for m in &AUTO_RELEASE_MODS { + if keyboard.modifiers & *m != 0 { + keyboard.modifiers &= !*m; + } + } + app.hid_provider + .send_key_routed(app.wvr_server.as_mut(), *vk, false); + app.hid_provider + .set_modifiers_routed(app.wvr_server.as_mut(), keyboard.modifiers); } - app.hid_provider - .send_key_routed(app.wvr_server.as_mut(), *vk, false); - app.hid_provider - .set_modifiers_routed(app.wvr_server.as_mut(), keyboard.modifiers); true } KeyButtonData::Modifier { modifier, sticky } => { diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs index 83259116..0ab7816b 100644 --- a/wayvr/src/overlays/keyboard/swipe_type.rs +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -1,12 +1,27 @@ use std::collections::HashMap; +use arboard::{Clipboard, LinuxClipboardKind, SetExtLinux}; +use codes_iso_639::part_1::LanguageCode; +use futures::TryFutureExt; use strum::IntoEnumIterator; +use super_swipe_engine::{EngineLoadError, SwipeEngine}; use swipe_types::types::Point; use crate::overlays::keyboard::layout; use crate::overlays::keyboard::layout::KeyCapType; use crate::subsystem::hid::{get_key_type, KeyType, VirtualKey, XkbKeymap}; +pub fn copy_text_to_primary_clipboard(text: &str, clip: &mut Clipboard) { -pub fn build_key_to_char_point_map(keymap: Option<&XkbKeymap>, layout: &layout::Layout) -> HashMap { + println!("{}", std::env::var("WAYLAND_DISPLAY").unwrap()); + clip.set_text(format!("{text} ")).unwrap(); +} +pub fn create_new_swipe_engine(keymap: &Option<&XkbKeymap>, layout: &layout::Layout) -> Result { + let layout_name = keymap.and_then(|k| k.get_name()).unwrap_or("us"); + let point_map = build_key_to_char_point_map(*keymap, layout); + + // todo: use the layout_name to choose a sensible language for the swipe engine + SwipeEngine::new(LanguageCode::En, Some(point_map)) +} +fn build_key_to_char_point_map(keymap: Option<&XkbKeymap>, layout: &layout::Layout) -> HashMap { let mut map = HashMap::new(); let has_altgr = keymap.as_ref().is_some_and(|m| XkbKeymap::has_altgr(m)); diff --git a/wayvr/src/subsystem/hid/mod.rs b/wayvr/src/subsystem/hid/mod.rs index 7642137e..cba95ac6 100644 --- a/wayvr/src/subsystem/hid/mod.rs +++ b/wayvr/src/subsystem/hid/mod.rs @@ -253,6 +253,7 @@ impl HidProvider for UInputProvider { log::error!("send_key: {res}"); } } + fn set_desktop_extent(&mut self, extent: Vec2) { self.desktop_extent = extent; } From 84b43f30dc5251a45e716f2a2856cd8086ceab63 Mon Sep 17 00:00:00 2001 From: Diamond White Date: Thu, 12 Mar 2026 03:06:27 +0000 Subject: [PATCH 07/35] use new prediction engine, move swipe type related logic to its own module --- Cargo.lock | 641 ++++++++++++++++++---- wayvr/Cargo.toml | 3 +- wayvr/src/overlays/keyboard/builder.rs | 25 +- wayvr/src/overlays/keyboard/layout.rs | 1 - wayvr/src/overlays/keyboard/mod.rs | 103 ++-- wayvr/src/overlays/keyboard/swipe_type.rs | 160 ++++-- wgui/src/event.rs | 4 + 7 files changed, 709 insertions(+), 228 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c134c9c4..ad97adda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,7 +41,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -216,9 +216,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "appendlist" @@ -719,26 +719,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f09c37bc0e9f0924b7dae9988265ef3c76c88538f41a3b06caf4bed07cee5226" -[[package]] -name = "bincode" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" -dependencies = [ - "bincode_derive", - "serde", - "unty", -] - -[[package]] -name = "bincode_derive" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" -dependencies = [ - "virtue", -] - [[package]] name = "bindgen" version = "0.69.5" @@ -883,43 +863,32 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "bzip2" -version = "0.4.4" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" dependencies = [ - "bzip2-sys", - "libc", -] - -[[package]] -name = "bzip2-sys" -version = "0.1.13+1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" -dependencies = [ - "cc", - "pkg-config", + "libbz2-rs-sys", ] [[package]] name = "cached-path" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56aaf5c3daef0c2cc113b07a624b17317754fc2144821df7cb755c0a5494be3e" +checksum = "a9e79c94134331f0107c9f5b7caaf15f118ec91de67dcd3fa1145921e9fc27fa" dependencies = [ "flate2", "fs2", "glob", "infer", "log", - "rand 0.8.5", + "rand 0.10.0", "reqwest", "serde", "serde_json", "sha2", "tar", "tempfile", - "thiserror 1.0.69", + "thiserror 2.0.17", "zip", ] @@ -1044,6 +1013,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.0", +] + [[package]] name = "chrono" version = "0.4.42" @@ -1286,9 +1266,9 @@ dependencies = [ [[package]] name = "constant_time_eq" -version = "0.1.5" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] name = "convert_case" @@ -1445,6 +1425,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -1699,6 +1688,22 @@ dependencies = [ "num-traits", ] +[[package]] +name = "deflate64" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.5.5" @@ -2119,6 +2124,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", + "libz-rs-sys", "miniz_oxide", ] @@ -2239,6 +2245,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "fst" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab85b9b05e3978cc9a9cf8fea7f01b494e1a09ed3037e16ba39edc7a29eb61a" + [[package]] name = "futures" version = "0.3.31" @@ -2391,10 +2403,26 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.0", + "wasip2", + "wasip3", + "wasm-bindgen", +] + [[package]] name = "gif" version = "0.14.1" @@ -2436,8 +2464,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.13", + "regex-syntax 0.8.8", ] [[package]] @@ -2584,6 +2612,12 @@ dependencies = [ "digest", ] +[[package]] +name = "hmac-sha256" +version = "1.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec9d92d097f4749b64e8cc33d924d9f40a2d4eb91402b458014b781f5733d60f" + [[package]] name = "hound" version = "3.5.1" @@ -2810,6 +2844,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -2880,7 +2920,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata", + "regex-automata 0.4.13", "same-file", "walkdir", "winapi-util", @@ -2962,6 +3002,8 @@ checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -3036,9 +3078,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" @@ -3231,12 +3273,24 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "lebe" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" +[[package]] +name = "libbz2-rs-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" + [[package]] name = "libc" version = "0.2.179" @@ -3341,6 +3395,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "libz-rs-sys" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c10501e7805cee23da17c7790e59df2870c0d4043ec6d03f67d31e2b53e77415" +dependencies = [ + "zlib-rs", +] + [[package]] name = "linebender_resource_handle" version = "0.1.1" @@ -3417,6 +3480,21 @@ dependencies = [ "hashbrown 0.16.1", ] +[[package]] +name = "lzma-rust2" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1670343e58806300d87950e3401e820b519b9384281bbabfb15e3636689ffd69" + +[[package]] +name = "lzma-rust2" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47bb1e988e6fb779cf720ad431242d3f03167c1b3f2b1aae7f1a94b2495b36ae" +dependencies = [ + "sha2", +] + [[package]] name = "mach2" version = "0.4.3" @@ -3432,7 +3510,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "regex-automata", + "regex-automata 0.4.13", +] + +[[package]] +name = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "rawpointer", ] [[package]] @@ -3451,6 +3539,16 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "memmap" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "memmap2" version = "0.9.9" @@ -3561,6 +3659,21 @@ dependencies = [ "tempfile", ] +[[package]] +name = "ndarray" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", +] + [[package]] name = "ndk" version = "0.9.0" @@ -3695,6 +3808,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.2.0" @@ -4221,6 +4343,30 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "ort" +version = "2.0.0-rc.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7de3af33d24a745ffb8fab904b13478438d1cd52868e6f17735ef6e1f8bf133" +dependencies = [ + "ndarray", + "ort-sys", + "smallvec", + "tracing", + "ureq", +] + +[[package]] +name = "ort-sys" +version = "2.0.0-rc.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7b497d21a8b6fbb4b5a544f8fadb77e801a09ae0add9e411d31c6f89e3c1e90" +dependencies = [ + "hmac-sha256", + "lzma-rust2 0.15.7", + "ureq", +] + [[package]] name = "os_pipe" version = "1.2.3" @@ -4329,17 +4475,6 @@ dependencies = [ "regex", ] -[[package]] -name = "password-hash" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" -dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "paste" version = "1.0.15" @@ -4360,14 +4495,21 @@ checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "pbkdf2" -version = "0.11.0" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest", "hmac", - "password-hash", - "sha2", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", ] [[package]] @@ -4637,6 +4779,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppmd-rust" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -4778,6 +4926,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.7.3" @@ -4813,6 +4967,17 @@ dependencies = [ "rand_core 0.9.3", ] +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.0", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -4870,6 +5035,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + [[package]] name = "rand_hc" version = "0.2.0" @@ -4971,6 +5142,12 @@ dependencies = [ "objc2-quartz-core 0.3.2", ] +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon" version = "1.11.0" @@ -5037,14 +5214,24 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.13", + "regex-syntax 0.8.8", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "fst", + "regex-syntax 0.6.29", ] [[package]] @@ -5055,9 +5242,15 @@ checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.8", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.8" @@ -5525,7 +5718,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -5536,7 +5729,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -5834,6 +6027,17 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "socks" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b" +dependencies = [ + "byteorder", + "libc", + "winapi", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -5889,14 +6093,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] -name = "super-swipe-engine" -version = "0.1.11" -source = "git+https://github.com/oneshinyboi/swipeType?tag=v0.1.11#5f9ac506f97ffa60186cc74ab723340fd0362dd9" +name = "super-swipe-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32fd40ad969f38b55f6d1541d7c1a16226821b51a38362de7aa800f976632336" dependencies = [ - "bincode", + "anyhow", "cached-path", - "codes-iso-639", - "swipe-types", + "fst", + "memmap", + "ort", + "regex", + "regex-automata 0.1.10", + "vector2", ] [[package]] @@ -5926,16 +6135,6 @@ dependencies = [ "zeno", ] -[[package]] -name = "swipe-types" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebfd0c6e0e4aaf949ad93dedb75da61a531ce94a46fdd87c11830cfb7c32c8f" -dependencies = [ - "bincode", - "serde", -] - [[package]] name = "symphonia" version = "0.5.5" @@ -6207,6 +6406,7 @@ checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", + "js-sys", "num-conv", "powerfmt", "serde_core", @@ -6514,7 +6714,7 @@ dependencies = [ "matchers", "nu-ansi-term", "once_cell", - "regex-automata", + "regex-automata 0.4.13", "sharded-slab", "smallvec", "thread_local", @@ -6549,6 +6749,12 @@ dependencies = [ "core_maths", ] +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + [[package]] name = "typeid" version = "1.0.3" @@ -6637,6 +6843,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -6644,10 +6856,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] -name = "unty" -version = "0.0.4" +name = "ureq" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" +checksum = "fdc97a28575b85cfedf2a7e7d3cc64b3e11bd8ac766666318003abbacc7a21fc" +dependencies = [ + "base64", + "der", + "log", + "native-tls", + "percent-encoding", + "rustls-pki-types", + "socks", + "ureq-proto", + "utf-8", + "webpki-root-certs", +] + +[[package]] +name = "ureq-proto" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" +dependencies = [ + "base64", + "http", + "httparse", + "log", +] [[package]] name = "url" @@ -6683,6 +6919,12 @@ dependencies = [ "xmlwriter", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -6731,6 +6973,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "vector2" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f562f8d4f41867568374e1a63bd12914795f558319f9bf9b0582c4d96a8de04" + [[package]] name = "version-compare" version = "0.2.1" @@ -6743,12 +6991,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "virtue" -version = "0.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" - [[package]] name = "vk-parse" version = "0.15.0" @@ -6850,7 +7092,16 @@ version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.46.0", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", ] [[package]] @@ -6911,6 +7162,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.12.1", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.10.0", + "hashbrown 0.15.5", + "indexmap 2.12.1", + "semver", +] + [[package]] name = "wayland-backend" version = "0.3.12" @@ -7105,8 +7390,7 @@ dependencies = [ "smallvec", "smithay", "strum", - "super-swipe-engine", - "swipe-types", + "super-swipe-type", "sysinfo", "thiserror 2.0.17", "tracing", @@ -7175,6 +7459,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "weezl" version = "0.1.12" @@ -7720,6 +8013,94 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap 2.12.1", + "prettyplease", + "syn 2.0.113", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.113", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.10.0", + "indexmap 2.12.1", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.12.1", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + [[package]] name = "wl-clipboard-rs" version = "0.9.3" @@ -8082,6 +8463,20 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.113", +] [[package]] name = "zerotrie" @@ -8118,24 +8513,37 @@ dependencies = [ [[package]] name = "zip" -version = "0.6.6" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +checksum = "b680f2a0cd479b4cff6e1233c483fdead418106eae419dc60200ae9850f6d004" dependencies = [ "aes", - "byteorder", "bzip2", "constant_time_eq", "crc32fast", - "crossbeam-utils", + "deflate64", "flate2", + "getrandom 0.4.2", "hmac", + "indexmap 2.12.1", + "lzma-rust2 0.16.2", + "memchr", "pbkdf2", + "ppmd-rust", "sha1", "time", + "typed-path", + "zeroize", + "zopfli", "zstd", ] +[[package]] +name = "zlib-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" + [[package]] name = "zmij" version = "1.0.10" @@ -8143,21 +8551,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30e0d8dffbae3d840f64bda38e28391faef673a7b5a6017840f2a106c8145868" [[package]] -name = "zstd" -version = "0.11.2+zstd.1.5.2" +name = "zopfli" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ - "libc", "zstd-sys", ] diff --git a/wayvr/Cargo.toml b/wayvr/Cargo.toml index 1f4bbdf8..b225972d 100644 --- a/wayvr/Cargo.toml +++ b/wayvr/Cargo.toml @@ -87,10 +87,9 @@ xcb = { version = "1.6.0", optional = true, features = [ "as-raw-xcb-connection", ] } xkbcommon = { version = "0.8.0" } # 0.9.0 breaks keymap import on some distros -super-swipe-engine = {git="https://github.com/oneshinyboi/swipeType", tag="v0.1.11"} codes-iso-639 = "0.1.5" -swipe-types = "0.1.6" arboard = { version="3.6.1", features = ["wayland-data-control", "wl-clipboard-rs"] } +super-swipe-type = "0.1.2" [build-dependencies] diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index 18783ca8..2094836c 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -27,7 +27,7 @@ use wgui::{ widget::{EventResult, div::WidgetDiv, rectangle::WidgetRectangle}, }; use crate::overlays::keyboard::layout::KeyData; -use super::{KeyButtonData, KeyState, KeyboardState, handle_press, handle_release, layout::{self, KeyCapType}, handle_enter}; +use super::{KeyButtonData, KeyState, KeyboardState, handle_press, handle_release, layout::{self, KeyCapType}, handle_mouse_motion}; const PIXELS_PER_UNIT: f32 = 60.; @@ -179,8 +179,6 @@ pub(super) fn create_keyboard_panel( EventListenerKind::MouseEnter, Box::new({ let k = key_state.clone(); - let k_label = key_label.clone(); - let k_cap_type = key_cap_type.clone(); move |common, data, _app, state| { common.alterables.trigger_haptics(); on_enter_anim( @@ -192,7 +190,7 @@ pub(super) fn create_keyboard_panel( width_mul, ); - handle_enter(&k, &k_label, &k_cap_type, state); + Ok(EventResult::Pass) } }), @@ -228,19 +226,34 @@ pub(super) fn create_keyboard_panel( let CallbackMetadata::MouseButton(button) = data.metadata else { panic!("CallbackMetadata should contain MouseButton!"); }; + let within_key_pos = data.metadata.get_mouse_pos_normalized(&common.alterables.transform_stack); - handle_press(app, &k, &k_cap_type, &k_label, state, button); + handle_press(app, &k, &k_label, &k_cap_type, &within_key_pos, state, button); on_press_anim(k.clone(), common, data); Ok(EventResult::Pass) } }), ); + panel.add_event_listener( + widget_id, + EventListenerKind::MouseMotion, + Box::new({ + let k = key_state.clone(); + let k_label = key_label.clone(); + let k_cap_type = key_cap_type.clone(); + move |common, data, app, state| { + let within_key_pos = data.metadata.get_mouse_pos_normalized(&common.alterables.transform_stack); + + handle_mouse_motion(&k, &k_label, &k_cap_type, state, &within_key_pos); + Ok(EventResult::Pass) + } + }), + ); panel.add_event_listener( widget_id, EventListenerKind::MouseRelease, Box::new({ let k = key_state.clone(); - let k_label = key_label.clone(); let k_cap_type = key_cap_type.clone(); move |common, data, app, state| { if handle_release(app, &k, &k_cap_type, state) { diff --git a/wayvr/src/overlays/keyboard/layout.rs b/wayvr/src/overlays/keyboard/layout.rs index b53d7b65..f65e3bd0 100644 --- a/wayvr/src/overlays/keyboard/layout.rs +++ b/wayvr/src/overlays/keyboard/layout.rs @@ -3,7 +3,6 @@ use std::{collections::HashMap, str::FromStr, sync::LazyLock}; use regex::Regex; use serde::{Deserialize, Serialize}; use smithay::reexports::wayland_protocols::wp::pointer_gestures::zv1::server::zwp_pointer_gesture_swipe_v1::ZwpPointerGestureSwipeV1; -use swipe_types::types::Point; use crate::{ config::{ConfigType, load_known_yaml}, subsystem::hid::{ diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index ff359c97..9ab63a11 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -28,10 +28,9 @@ use crate::{ }; use anyhow::Context; use arboard::Clipboard; -use glam::{Affine3A, Quat, Vec3, vec3}; +use glam::{Affine3A, Quat, Vec3, vec3, Vec2}; use regex::Regex; use slotmap::{SlotMap, new_key_type}; -use super_swipe_engine::SwipeEngine; use wgui::{ drawing, event::{InternalStateChangeEvent, MouseButtonEvent, MouseButtonIndex}, @@ -43,7 +42,7 @@ use wlx_common::{ }; use codes_iso_639::part_1::LanguageCode; use crate::overlays::keyboard::layout::KeyCapType; -use crate::overlays::keyboard::swipe_type::{copy_text_to_primary_clipboard, create_new_swipe_engine}; +use crate::overlays::keyboard::swipe_type::SwipeTypingManager; pub mod builder; mod layout; @@ -62,12 +61,7 @@ pub fn create_keyboard(app: &mut AppState, wayland: bool) -> anyhow::Result anyhow::Result { let mut state = self.default_state.take(); - state.swipe_engine = match create_new_swipe_engine(&keymap, &self.wlx_layout) { + state.swipe_typing_manager = match SwipeTypingManager::new() { Ok(engine) => Some(engine), Err(e) => { log::error!("Error occured while trying to load swipe engine: {:?}", e); @@ -217,7 +211,7 @@ impl KeyboardBackend { .state .take(); - state_from.swipe_engine = match create_new_swipe_engine(&Some(keymap), &self.wlx_layout) { + state_from.swipe_typing_manager = match SwipeTypingManager::new() { Ok(engine) => Some(engine), Err(e) => { log::error!("Error occured while trying to load swipe engine: {:?}", e); @@ -357,15 +351,7 @@ struct KeyboardState { overlay_list: OverlayList, set_list: SetList, clock_12h: bool, - - // todo move all this swipe stuff into its own class - swipe_engine: Option, - current_swipe_input: String, - last_pressed_key_label: String, - is_swiping: bool, - clipboard: Clipboard, - last_swiped_word: Option - + swipe_typing_manager: Option, } macro_rules! take_and_leave_default { @@ -385,12 +371,7 @@ impl KeyboardState { overlay_list: OverlayList::default(), set_list: SetList::default(), clock_12h: self.clock_12h, - swipe_engine: None, - current_swipe_input: String::new(), - is_swiping: false, - last_pressed_key_label: String::new(), - clipboard: Clipboard::new().unwrap(), - last_swiped_word: None + swipe_typing_manager: None, } } } @@ -431,37 +412,44 @@ enum KeyButtonData { }, } -fn handle_enter(key: &KeyState, key_label: &Vec, key_cap_type: &KeyCapType, keyboard: &mut KeyboardState) { - if let Some(_) = keyboard.swipe_engine.as_ref() && *key_cap_type == KeyCapType::Letter { - if *key_label.iter().next().unwrap() != keyboard.last_pressed_key_label { - keyboard.is_swiping = true; - } - if keyboard.is_swiping { +fn handle_mouse_motion(key: &KeyState, key_label: &Vec, key_cap_type: &KeyCapType, keyboard: &mut KeyboardState, within_key_pos: &Option) { + if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() && *key_cap_type == KeyCapType::Letter { + if !swipe_manager.is_current_swipe_empty() { match &key.button_state { KeyButtonData::Key { vk, pressed } => { - keyboard.current_swipe_input.push_str(&*key_label.iter().next().unwrap().to_ascii_lowercase()) + if let Some(pos) = within_key_pos { + // check because mouse motion can trigger despite hover being false + if pos.x >= 0.0 && pos.x <= 1.0 && pos.y >= 0.0 && pos.y <= 1.0 { + + if let Some(label) = key_label.first() { + swipe_manager.add_swipe(pos, label.chars().next().unwrap_or_default()); + } + } + } } _ => {} } } } + } fn handle_press( app: &mut AppState, key: &KeyState, - key_cap_type: &KeyCapType, key_label: &Vec, + key_cap_type: &KeyCapType, + within_key_pos: &Option, keyboard: &mut KeyboardState, button: MouseButtonEvent, ) { - keyboard.is_swiping = false; match &key.button_state { KeyButtonData::Key { vk, pressed } => { - if let Some(_) = keyboard.swipe_engine.as_ref() && *key_cap_type == KeyCapType::Letter { - let actual_label = key_label.iter().next().unwrap(); - keyboard.last_pressed_key_label = actual_label.clone(); - keyboard.current_swipe_input.clear(); - keyboard.current_swipe_input.push_str(&*actual_label.to_ascii_lowercase()) + if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() && *key_cap_type == KeyCapType::Letter { + if let Some(pos) = within_key_pos { + if let Some(label) = key_label.first() { + swipe_manager.add_swipe(pos, label.chars().next().unwrap_or_default()); + } + } } else { keyboard.modifiers |= match button.index { @@ -508,29 +496,23 @@ fn handle_press( fn handle_release(app: &mut AppState, key: &KeyState, k_cap_type: &KeyCapType, keyboard: &mut KeyboardState) -> bool { match &key.button_state { KeyButtonData::Key { vk, pressed } => { - if let Some(engine) = keyboard.swipe_engine.as_ref() && *k_cap_type == KeyCapType::Letter { - if keyboard.is_swiping { - if !keyboard.current_swipe_input.is_empty() { - let prediction = engine.predict(&*keyboard.current_swipe_input, keyboard.last_swiped_word.as_ref().map(|x| x.as_str()), 5); - keyboard.current_swipe_input.clear(); - println!("swipe path: {}", keyboard.current_swipe_input); - println!("{:?}", prediction); - - let best_prediction = prediction.first().unwrap().word.as_ref(); - - copy_text_to_primary_clipboard(best_prediction, &mut keyboard.clipboard); - app.hid_provider - .set_modifiers_routed(app.wvr_server.as_mut(), SHIFT); - app.hid_provider - .send_key_routed(app.wvr_server.as_mut(), VirtualKey::Insert, true); - app.hid_provider - .send_key_routed(app.wvr_server.as_mut(), VirtualKey::Insert, false); - app.hid_provider - .set_modifiers_routed(app.wvr_server.as_mut(), keyboard.modifiers); - keyboard.last_swiped_word = Some(best_prediction.parse().unwrap()) + if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() && *k_cap_type == KeyCapType::Letter { + if swipe_manager.swipe_left_first_key() { + match swipe_manager.predict() { + Ok(predictions) => { + let best_prediction = predictions.first().unwrap(); + println!("best prediction for swipe: {best_prediction}"); + + swipe_manager.select_word(&best_prediction, app, keyboard.modifiers); + }, + Err(e) => { + log::error!("{}", e) + } } } else { // pointer must have been released on the same key it was pressed on + swipe_manager.reset(); // drop swipe tracking that was started on press + app.hid_provider .send_key_routed(app.wvr_server.as_mut(), *vk, true); pressed.set(true); @@ -538,7 +520,6 @@ fn handle_release(app: &mut AppState, key: &KeyState, k_cap_type: &KeyCapType, k .send_key_routed(app.wvr_server.as_mut(), *vk, false); play_key_click(app); } - } else { pressed.set(false); diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs index 0ab7816b..ca25c945 100644 --- a/wayvr/src/overlays/keyboard/swipe_type.rs +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -1,53 +1,119 @@ -use std::collections::HashMap; -use arboard::{Clipboard, LinuxClipboardKind, SetExtLinux}; -use codes_iso_639::part_1::LanguageCode; -use futures::TryFutureExt; -use strum::IntoEnumIterator; -use super_swipe_engine::{EngineLoadError, SwipeEngine}; -use swipe_types::types::Point; -use crate::overlays::keyboard::layout; -use crate::overlays::keyboard::layout::KeyCapType; -use crate::subsystem::hid::{get_key_type, KeyType, VirtualKey, XkbKeymap}; +use crate::state::AppState; +use crate::subsystem::hid::{KeyModifier, VirtualKey, CTRL}; +use anyhow::bail; +use arboard::Clipboard; +use glam::Vec2; +use std::mem; +use std::time::Instant; +use super_swipe_type::keyboard_manager::QwertyKeyboardGrid; +use super_swipe_type::swipe_orchestrator::SwipeOrchestrator; +use super_swipe_type::{SwipeCandidate, SwipePoint}; -pub fn copy_text_to_primary_clipboard(text: &str, clip: &mut Clipboard) { - - println!("{}", std::env::var("WAYLAND_DISPLAY").unwrap()); - clip.set_text(format!("{text} ")).unwrap(); +pub struct SwipeTypingManager { + swipe_engine: SwipeOrchestrator, + keyboard_gird: QwertyKeyboardGrid, + current_swipe: Vec, + swipe_start_time: Option, + clipboard: Clipboard, + swipe_left_first_key: bool, + first_swipe_char: char, + last_swiped_word: Option } -pub fn create_new_swipe_engine(keymap: &Option<&XkbKeymap>, layout: &layout::Layout) -> Result { - let layout_name = keymap.and_then(|k| k.get_name()).unwrap_or("us"); - let point_map = build_key_to_char_point_map(*keymap, layout); +impl SwipeTypingManager { - // todo: use the layout_name to choose a sensible language for the swipe engine - SwipeEngine::new(LanguageCode::En, Some(point_map)) -} -fn build_key_to_char_point_map(keymap: Option<&XkbKeymap>, layout: &layout::Layout) -> HashMap { - let mut map = HashMap::new(); - - let has_altgr = keymap.as_ref().is_some_and(|m| XkbKeymap::has_altgr(m)); - let mut pos_x: f32 = 0.0; - let mut pos_y: f32 = 0.0; - - for (row_idx, row) in layout.main_layout.iter().enumerate() { - for (col_idx, col) in row.iter().enumerate() { - - let label = layout.get_key_data(keymap, has_altgr, col_idx, row_idx); - if let Some(label) = label { - match label.cap_type { - KeyCapType::Letter => { - if let Some(char) = label.label.iter().next() { - let point = Point {x: pos_x as f64, y: pos_y as f64}; - println!("char: {} \n point: {:?}\n",char, point); - map.insert(char.to_ascii_lowercase().chars().next().unwrap(), point); - } - } - _ => {} - } - } - pos_x += layout.key_sizes[row_idx][col_idx]; + /// Attempts to "type" the word by copying to clipboard and pasting + pub fn select_word(&mut self, word: &String, app: &mut AppState, original_keyboard_mods: KeyModifier) { + self.last_swiped_word = Some(word.clone()); + if let Ok(_) =self.copy_text_to_primary_clipboard(word.as_ref()) { + Self::paste(app, original_keyboard_mods); } - pos_y += 1.0; - pos_x = 0.0; } - map + fn paste(app: &mut AppState, original_keyboard_mods: KeyModifier) { + app.hid_provider + .set_modifiers_routed(app.wvr_server.as_mut(), CTRL); + app.hid_provider + .send_key_routed(app.wvr_server.as_mut(), VirtualKey::V, true); + app.hid_provider + .send_key_routed(app.wvr_server.as_mut(), VirtualKey::V, false); + app.hid_provider + .set_modifiers_routed(app.wvr_server.as_mut(), original_keyboard_mods); + } + fn copy_text_to_primary_clipboard(&mut self, text: &str) -> Result<(), arboard::Error> { + println!("{}", std::env::var("WAYLAND_DISPLAY").unwrap()); + self.clipboard.set_text(format!("{text} ")) + } + pub fn new() -> anyhow::Result { + // todo: use the layout_name to choose a sensible language for the swipe engine + + Ok(Self { + swipe_engine: SwipeOrchestrator::new()?, + keyboard_gird: QwertyKeyboardGrid::new(), // contains a hashmap where the vector2 is the center pos of each key in querty + current_swipe: Vec::new(), + swipe_start_time: None, + clipboard: Clipboard::new()?, + swipe_left_first_key: false, + first_swipe_char: char::default(), + last_swiped_word: None + }) + } + pub fn predict(&mut self) -> anyhow::Result>{ + if self.is_current_swipe_empty() { + bail!("nothing to predict"); + } else { + let current_swipe = mem::take(&mut self.current_swipe); + self.reset(); + + let candidates: Vec = self.swipe_engine.predict(current_swipe, &self.last_swiped_word)?; + Ok(candidates.iter().map(|c| c.word.clone()).collect()) + } + } + pub fn reset(&mut self) { + self.last_swiped_word = None; + self.swipe_start_time = None; + self.current_swipe = Vec::new(); + self.first_swipe_char = char::default(); + self.swipe_left_first_key = false; + } + pub fn swipe_left_first_key(&self) -> bool { + self.swipe_left_first_key + } + pub fn is_current_swipe_empty(&self) -> bool { + self.current_swipe.is_empty() + } + pub fn add_swipe(&mut self, within_key_pos_normalized: &Vec2, key_label: char) { + if let Some(pos) = self.keyboard_gird.key_positions.get(&key_label.to_ascii_lowercase()) { + if self.first_swipe_char != char::default() && self.first_swipe_char != key_label.to_ascii_lowercase() { + self.swipe_left_first_key = true; + } + let key_pos = Vec2{x: pos.x as f32, y: pos.y as f32 }; + println!("char : {key_label} at pos: {key_pos}"); + let start_time = match self.swipe_start_time { + Some(time) => time, + None => { + // this is the first swipe entry + let now = Instant::now(); + self.swipe_start_time = Some(now); + self.first_swipe_char = key_label.to_ascii_lowercase(); + now + } + }; + + let within_key_pos_from_center = Vec2 { + x: within_key_pos_normalized.x - 0.5, + y: 0.5 - within_key_pos_normalized.y, + }; + let key_dimensions = Vec2 { + x: QwertyKeyboardGrid::get_key_width() as f32, + y: QwertyKeyboardGrid::get_key_width() as f32, + }; + + let point = within_key_pos_from_center*key_dimensions + key_pos; + let duration = Instant::now().duration_since(start_time); + self.current_swipe.push(SwipePoint::new(point.x.into(), point.y.into(), duration)) + } + + } + + + } diff --git a/wgui/src/event.rs b/wgui/src/event.rs index 57417808..244471c8 100644 --- a/wgui/src/event.rs +++ b/wgui/src/event.rs @@ -219,6 +219,10 @@ impl CallbackMetadata { let mouse_pos_abs = self.get_mouse_pos_absolute()?; Some(mouse_pos_abs - transform_stack.get().abs_pos) } + pub fn get_mouse_pos_normalized(&self, transform_stack: &TransformStack) -> Option { + let pos_relative = self.get_mouse_pos_relative(transform_stack)?; + Some(pos_relative/transform_stack.parent().raw_dim) + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] From ff03f8a75a48692e407de7e426505109c563e3fb Mon Sep 17 00:00:00 2001 From: Diamond White Date: Thu, 12 Mar 2026 05:01:07 +0000 Subject: [PATCH 08/35] update appimage bulid to support building ort runtime --- .github/workflows/build-appimage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-appimage.yml b/.github/workflows/build-appimage.yml index d5bcc0e6..826fc778 100644 --- a/.github/workflows/build-appimage.yml +++ b/.github/workflows/build-appimage.yml @@ -16,7 +16,7 @@ env: jobs: build_appimage: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 defaults: run: working-directory: ./wayvr From 74e8c4b58890f5b87b5d87f4922061f4c5806e7b Mon Sep 17 00:00:00 2001 From: Diamond White Date: Sun, 15 Mar 2026 10:43:28 +0000 Subject: [PATCH 09/35] implement swipe selection ui in keyboard overlay --- wayvr/src/assets/gui/keyboard.xml | 15 ++ wayvr/src/overlays/keyboard/builder.rs | 179 +++++++++++++++++++++- wayvr/src/overlays/keyboard/mod.rs | 34 ++-- wayvr/src/overlays/keyboard/swipe_type.rs | 55 +++++-- wlx-common/src/config.rs | 3 + 5 files changed, 259 insertions(+), 27 deletions(-) diff --git a/wayvr/src/assets/gui/keyboard.xml b/wayvr/src/assets/gui/keyboard.xml index 4d6fc321..ceea4027 100644 --- a/wayvr/src/assets/gui/keyboard.xml +++ b/wayvr/src/assets/gui/keyboard.xml @@ -13,6 +13,11 @@ width="${width}" height="${height}" min_width="${width}" min_height="${height}" max_width="${width}" max_height="${height}" /> + + @@ -90,6 +95,13 @@ + + + @@ -232,6 +244,9 @@
+ + +
diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index fad16dca..efd1e44c 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -1,5 +1,4 @@ use std::{collections::HashMap, rc::Rc, time::Duration}; - use crate::{ app_misc, gui::{ @@ -12,8 +11,8 @@ use crate::{ windowing::backend::OverlayEventData, }; use anyhow::Context; -use dbus::arg::Append; use glam::{FloatExt, Mat4, Vec2, vec2, vec3}; +use slotmap::Key; use wgui::{ animation::{Animation, AnimationEasing}, assets::AssetPath, @@ -26,7 +25,10 @@ use wgui::{ taffy::{self, prelude::length}, widget::{EventResult, div::WidgetDiv, rectangle::WidgetRectangle}, }; -use crate::overlays::keyboard::layout::KeyData; +use wgui::event::StyleSetRequest; +use wgui::layout::LayoutTask; +use wgui::taffy::Display; +use crate::overlays::keyboard::swipe_type::SwipeTypingManager; use super::{KeyButtonData, KeyState, KeyboardState, handle_press, handle_release, layout::{self, KeyCapType}, handle_mouse_motion}; const PIXELS_PER_UNIT: f32 = 60.; @@ -39,6 +41,141 @@ fn new_doc_params(panel: &mut GuiPanel) -> ParseDocumentParams<'s } } +pub(super) fn update_swipe_prediction_bar( + panel: &mut GuiPanel, + app: &mut AppState +) -> anyhow::Result { + let mut elements_changed = false; + + let (accent_color, anim_mult) = { + let theme = &app.wgui_theme; + (theme.accent_color, theme.animation_mult) + }; + + let new_swipe_candidates; + if let Some(manager) = panel.state.swipe_typing_manager.as_mut() + && manager.swipe_candidates != panel.state.swipe_bar_candidates { + panel.state.swipe_bar_candidates = manager.swipe_candidates.clone(); + new_swipe_candidates = manager.swipe_candidates.clone(); + } else { + return Ok(elements_changed) + } + + let predictions_root = panel.parser_state + .get_widget_id("swipe_predictions_root") + .unwrap_or_default(); + + if predictions_root.is_null() { + return Ok(elements_changed) + } + let doc_params = new_doc_params(panel); + + panel.layout.remove_children(predictions_root); + + for (i, prediction) in new_swipe_candidates.iter().enumerate() { + let mut params = HashMap::new(); + let id: Rc = Rc::from(format!("Prediction-{i}")); + params.insert("id".into(), id.clone()); + params.insert("text".into(), prediction.clone().into()); + + panel.parser_state.instantiate_template( + &doc_params, + "KeyPrediction", + &mut panel.layout, + predictions_root, + params + )?; + + if let Ok(widget_id) = panel.parser_state.get_widget_id(&id) { + let key_state = { + let rect = panel + .layout + .state + .widgets + .get_as::(widget_id) + .unwrap(); // want panic + + Rc::new(KeyState { + // fake button state just so we get key state for anims + button_state: KeyButtonData::Modifier { + modifier: 0, + sticky: core::cell::Cell::new(false), + }, + color: rect.params.color, + color2: rect.params.color2, + base_border_color: rect.params.border_color, + cur_border_color: rect.params.border_color.into(), + border: rect.params.border, + drawn_state: false.into(), + }) + }; + panel.add_event_listener( + widget_id, + EventListenerKind::MousePress, + Box::new({ + let k = key_state.clone(); + let pred = prediction.clone(); + move |common, data, app, state| { + if let Some(manager) = state.swipe_typing_manager.as_mut() { + manager.select_alternate_prediction(&pred, app, state.modifiers); + on_press_anim(k.clone(), common, data) + } + Ok(EventResult::Pass) + } + }) + ); + panel.add_event_listener( + widget_id, + EventListenerKind::MouseEnter, + Box::new({ + let k = key_state.clone(); + move |common, data, _app, _state| { + on_enter_anim( + k.clone(), + common, + data, + accent_color, + anim_mult, + 0.0, + ); + Ok(EventResult::Pass) + } + }) + ); + panel.add_event_listener( + widget_id, + EventListenerKind::MouseLeave, + Box::new({ + let k = key_state.clone(); + move |common, data, _app, _state | { + on_leave_anim( + k.clone(), + common, + data, + accent_color, + anim_mult, + 0.0, + ); + Ok(EventResult::Pass) + } + }) + ); + panel.add_event_listener( + widget_id, + EventListenerKind::MouseRelease, + Box::new({ + let k = key_state.clone(); + move |common, data, _app, _state| { + on_release_anim(k.clone(), common, data); + Ok(EventResult::Pass) + } + }) + ); + } + } + elements_changed = true; + Ok(elements_changed) +} #[allow(clippy::too_many_lines, clippy::significant_drop_tightening)] pub(super) fn create_keyboard_panel( app: &mut AppState, @@ -331,6 +468,42 @@ pub(super) fn create_keyboard_panel( elems_changed = true; } } + if !app.session.config.swipe_to_type_enabled { + panel.state.swipe_typing_manager = None; + let predictions_root = panel.parser_state + .get_widget_id("swipe_predictions_root") + .unwrap_or_default(); + + if !predictions_root.is_null() { + panel.layout.remove_children(predictions_root); + + panel.layout.tasks.push(LayoutTask::SetWidgetStyle( + predictions_root, + StyleSetRequest::Display(Display::None), + )); + + } + } + if app.session.config.swipe_to_type_enabled && panel.state.swipe_typing_manager.is_none() { + panel.state.swipe_typing_manager = match SwipeTypingManager::new() { + Ok(engine) => Some(engine), + Err(e) => { + log::error!("Error occurred while trying to load swipe engine: {:?}", e); + None + } + }; + let predictions_root = panel.parser_state + .get_widget_id("swipe_predictions_root") + .unwrap_or_default(); + + if !predictions_root.is_null() { + panel.layout.tasks.push(LayoutTask::SetWidgetStyle( + predictions_root, + StyleSetRequest::Display(Display::Flex), + )); + + } + } } OverlayEventData::CustomCommand { element, command } => { diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index 9ab63a11..6c1177a1 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -27,7 +27,6 @@ use crate::{ }, }; use anyhow::Context; -use arboard::Clipboard; use glam::{Affine3A, Quat, Vec3, vec3, Vec2}; use regex::Regex; use slotmap::{SlotMap, new_key_type}; @@ -40,7 +39,7 @@ use wlx_common::{ config::AltModifier, overlays::{BackendAttrib, BackendAttribValue}, }; -use codes_iso_639::part_1::LanguageCode; +use crate::overlays::keyboard::builder::update_swipe_prediction_bar; use crate::overlays::keyboard::layout::KeyCapType; use crate::overlays::keyboard::swipe_type::SwipeTypingManager; @@ -62,6 +61,7 @@ pub fn create_keyboard(app: &mut AppState, wayland: bool) -> anyhow::Result anyhow::Result anyhow::Result<()> { + if update_swipe_prediction_bar(self.panel(), app)? { + self.panel().process_custom_elems(app); + } + Ok(()) + } + fn auto_switch_keymap(&mut self, app: &mut AppState) -> anyhow::Result { let keymap = self.get_effective_keymap()?; app.hid_provider @@ -290,6 +297,7 @@ impl OverlayBackend for KeyboardBackend { }); } } + self.update_swipe_prediction_bar(app)?; self.panel().should_render(app) } fn render(&mut self, app: &mut AppState, rdr: &mut RenderResources) -> anyhow::Result<()> { @@ -352,6 +360,7 @@ struct KeyboardState { set_list: SetList, clock_12h: bool, swipe_typing_manager: Option, + swipe_bar_candidates: Vec, } macro_rules! take_and_leave_default { @@ -372,6 +381,7 @@ impl KeyboardState { set_list: SetList::default(), clock_12h: self.clock_12h, swipe_typing_manager: None, + swipe_bar_candidates: Vec::new(), } } } @@ -497,13 +507,13 @@ fn handle_release(app: &mut AppState, key: &KeyState, k_cap_type: &KeyCapType, k match &key.button_state { KeyButtonData::Key { vk, pressed } => { if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() && *k_cap_type == KeyCapType::Letter { - if swipe_manager.swipe_left_first_key() { + println!("some"); + if swipe_manager.did_swipe_leave_first_key() { + println!("left first"); match swipe_manager.predict() { - Ok(predictions) => { - let best_prediction = predictions.first().unwrap(); - println!("best prediction for swipe: {best_prediction}"); - - swipe_manager.select_word(&best_prediction, app, keyboard.modifiers); + Ok(top_prediction) => { + println!("best prediction for swipe: {top_prediction}"); + swipe_manager.select_word(&top_prediction, app, keyboard.modifiers); }, Err(e) => { log::error!("{}", e) @@ -511,8 +521,9 @@ fn handle_release(app: &mut AppState, key: &KeyState, k_cap_type: &KeyCapType, k } } else { // pointer must have been released on the same key it was pressed on + println!("swipe reset"); swipe_manager.reset(); // drop swipe tracking that was started on press - + app.hid_provider .send_key_routed(app.wvr_server.as_mut(), *vk, true); pressed.set(true); @@ -522,6 +533,9 @@ fn handle_release(app: &mut AppState, key: &KeyState, k_cap_type: &KeyCapType, k } } else { + if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() { + swipe_manager.reset(); + } pressed.set(false); for m in &AUTO_RELEASE_MODS { diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs index ca25c945..871f3d84 100644 --- a/wayvr/src/overlays/keyboard/swipe_type.rs +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -1,6 +1,6 @@ use crate::state::AppState; use crate::subsystem::hid::{KeyModifier, VirtualKey, CTRL}; -use anyhow::bail; +use anyhow::{anyhow, bail}; use arboard::Clipboard; use glam::Vec2; use std::mem; @@ -9,10 +9,12 @@ use super_swipe_type::keyboard_manager::QwertyKeyboardGrid; use super_swipe_type::swipe_orchestrator::SwipeOrchestrator; use super_swipe_type::{SwipeCandidate, SwipePoint}; +const PREDICTION_SUGGESTION_COUNT: usize = 4; pub struct SwipeTypingManager { swipe_engine: SwipeOrchestrator, keyboard_gird: QwertyKeyboardGrid, current_swipe: Vec, + pub swipe_candidates: Vec, swipe_start_time: Option, clipboard: Clipboard, swipe_left_first_key: bool, @@ -21,6 +23,10 @@ pub struct SwipeTypingManager { } impl SwipeTypingManager { + pub fn select_alternate_prediction(&mut self, word: &String, app: &mut AppState, original_keyboard_mods: KeyModifier) { + Self::undo_paste(app, original_keyboard_mods); + self.select_word(word, app, original_keyboard_mods); + } /// Attempts to "type" the word by copying to clipboard and pasting pub fn select_word(&mut self, word: &String, app: &mut AppState, original_keyboard_mods: KeyModifier) { self.last_swiped_word = Some(word.clone()); @@ -28,6 +34,16 @@ impl SwipeTypingManager { Self::paste(app, original_keyboard_mods); } } + fn undo_paste(app: &mut AppState, original_keyboard_mods: KeyModifier) { + app.hid_provider + .set_modifiers_routed(app.wvr_server.as_mut(), CTRL); + app.hid_provider + .send_key_routed(app.wvr_server.as_mut(), VirtualKey::Z, true); + app.hid_provider + .send_key_routed(app.wvr_server.as_mut(), VirtualKey::Z, false); + app.hid_provider + .set_modifiers_routed(app.wvr_server.as_mut(), original_keyboard_mods); + } fn paste(app: &mut AppState, original_keyboard_mods: KeyModifier) { app.hid_provider .set_modifiers_routed(app.wvr_server.as_mut(), CTRL); @@ -39,7 +55,6 @@ impl SwipeTypingManager { .set_modifiers_routed(app.wvr_server.as_mut(), original_keyboard_mods); } fn copy_text_to_primary_clipboard(&mut self, text: &str) -> Result<(), arboard::Error> { - println!("{}", std::env::var("WAYLAND_DISPLAY").unwrap()); self.clipboard.set_text(format!("{text} ")) } pub fn new() -> anyhow::Result { @@ -49,6 +64,7 @@ impl SwipeTypingManager { swipe_engine: SwipeOrchestrator::new()?, keyboard_gird: QwertyKeyboardGrid::new(), // contains a hashmap where the vector2 is the center pos of each key in querty current_swipe: Vec::new(), + swipe_candidates: Vec::new(), swipe_start_time: None, clipboard: Clipboard::new()?, swipe_left_first_key: false, @@ -56,25 +72,41 @@ impl SwipeTypingManager { last_swiped_word: None }) } - pub fn predict(&mut self) -> anyhow::Result>{ + pub fn predict(&mut self) -> anyhow::Result{ if self.is_current_swipe_empty() { bail!("nothing to predict"); } else { let current_swipe = mem::take(&mut self.current_swipe); - self.reset(); + self.reset_swipe(); let candidates: Vec = self.swipe_engine.predict(current_swipe, &self.last_swiped_word)?; - Ok(candidates.iter().map(|c| c.word.clone()).collect()) + + let mut iter = candidates.iter(); + let top_choice = iter.next().ok_or(anyhow!("No swipe candidates generated"))?.word.clone(); + self.swipe_candidates = iter.take(PREDICTION_SUGGESTION_COUNT).map(|c| c.word.clone()).collect(); + + Ok(top_choice) } } pub fn reset(&mut self) { + self.reset_swipe(); self.last_swiped_word = None; + self.swipe_candidates = Vec::new(); + } + fn reset_swipe(&mut self) { self.swipe_start_time = None; self.current_swipe = Vec::new(); self.first_swipe_char = char::default(); self.swipe_left_first_key = false; } - pub fn swipe_left_first_key(&self) -> bool { + fn start_swipe(&mut self, key_label: char) -> Instant{ + let now = Instant::now(); + self.swipe_start_time = Some(now); + self.first_swipe_char = key_label.to_ascii_lowercase(); + self.swipe_candidates = Vec::new(); + now + } + pub fn did_swipe_leave_first_key(&self) -> bool { self.swipe_left_first_key } pub fn is_current_swipe_empty(&self) -> bool { @@ -85,17 +117,12 @@ impl SwipeTypingManager { if self.first_swipe_char != char::default() && self.first_swipe_char != key_label.to_ascii_lowercase() { self.swipe_left_first_key = true; } + let key_pos = Vec2{x: pos.x as f32, y: pos.y as f32 }; - println!("char : {key_label} at pos: {key_pos}"); + //println!("char : {key_label} at pos: {key_pos}"); let start_time = match self.swipe_start_time { Some(time) => time, - None => { - // this is the first swipe entry - let now = Instant::now(); - self.swipe_start_time = Some(now); - self.first_swipe_char = key_label.to_ascii_lowercase(); - now - } + None => self.start_swipe(key_label) }; let within_key_pos_from_center = Vec2 { diff --git a/wlx-common/src/config.rs b/wlx-common/src/config.rs index 8ab23297..c578ef18 100644 --- a/wlx-common/src/config.rs +++ b/wlx-common/src/config.rs @@ -327,4 +327,7 @@ pub struct GeneralConfig { #[serde(default = "def_one")] pub grid_opacity: f32, + + #[serde(default = "def_true")] + pub swipe_to_type_enabled: bool, } From f8f38f6adf2a14b98f5ccba92cb2877913ad6da5 Mon Sep 17 00:00:00 2001 From: Diamond White Date: Sun, 15 Mar 2026 11:05:11 +0000 Subject: [PATCH 10/35] only add motion data for the device that started the swipe --- wayvr/src/overlays/keyboard/builder.rs | 7 +++++-- wayvr/src/overlays/keyboard/mod.rs | 14 +++++++++++--- wayvr/src/overlays/keyboard/swipe_type.rs | 15 ++++++++++++--- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index efd1e44c..a3749356 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -363,7 +363,7 @@ pub(super) fn create_keyboard_panel( }; let within_key_pos = data.metadata.get_mouse_pos_normalized(&common.alterables.transform_stack); - handle_press(app, &k, &k_label, &k_cap_type, &within_key_pos, state, button); + handle_press(app, &k, &k_label, &k_cap_type, &within_key_pos, state, button, button.device); on_press_anim(k.clone(), common, data); Ok(EventResult::Pass) } @@ -378,8 +378,11 @@ pub(super) fn create_keyboard_panel( let k_cap_type = key_cap_type.clone(); move |common, data, app, state| { let within_key_pos = data.metadata.get_mouse_pos_normalized(&common.alterables.transform_stack); + let CallbackMetadata::MousePosition(position) = data.metadata else { + panic!("CallbackMetadata should contain MousePosition!"); + }; - handle_mouse_motion(&k, &k_label, &k_cap_type, state, &within_key_pos); + handle_mouse_motion(&k, &k_label, &k_cap_type, state, &within_key_pos, position.device); Ok(EventResult::Pass) } }), diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index 6c1177a1..7475acf8 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -422,7 +422,14 @@ enum KeyButtonData { }, } -fn handle_mouse_motion(key: &KeyState, key_label: &Vec, key_cap_type: &KeyCapType, keyboard: &mut KeyboardState, within_key_pos: &Option) { +fn handle_mouse_motion( + key: &KeyState, + key_label: &Vec, + key_cap_type: &KeyCapType, + keyboard: &mut KeyboardState, + within_key_pos: &Option, + device: usize +) { if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() && *key_cap_type == KeyCapType::Letter { if !swipe_manager.is_current_swipe_empty() { match &key.button_state { @@ -432,7 +439,7 @@ fn handle_mouse_motion(key: &KeyState, key_label: &Vec, key_cap_type: &K if pos.x >= 0.0 && pos.x <= 1.0 && pos.y >= 0.0 && pos.y <= 1.0 { if let Some(label) = key_label.first() { - swipe_manager.add_swipe(pos, label.chars().next().unwrap_or_default()); + swipe_manager.add_swipe(pos, label.chars().next().unwrap_or_default(), device); } } } @@ -451,13 +458,14 @@ fn handle_press( within_key_pos: &Option, keyboard: &mut KeyboardState, button: MouseButtonEvent, + device: usize ) { match &key.button_state { KeyButtonData::Key { vk, pressed } => { if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() && *key_cap_type == KeyCapType::Letter { if let Some(pos) = within_key_pos { if let Some(label) = key_label.first() { - swipe_manager.add_swipe(pos, label.chars().next().unwrap_or_default()); + swipe_manager.add_swipe(pos, label.chars().next().unwrap_or_default(), device); } } } diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs index 871f3d84..1929dc8c 100644 --- a/wayvr/src/overlays/keyboard/swipe_type.rs +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -19,6 +19,7 @@ pub struct SwipeTypingManager { clipboard: Clipboard, swipe_left_first_key: bool, first_swipe_char: char, + current_swipe_device: Option, // the pointer that started this swipe last_swiped_word: Option } impl SwipeTypingManager { @@ -69,6 +70,7 @@ impl SwipeTypingManager { clipboard: Clipboard::new()?, swipe_left_first_key: false, first_swipe_char: char::default(), + current_swipe_device: None, last_swiped_word: None }) } @@ -98,11 +100,13 @@ impl SwipeTypingManager { self.current_swipe = Vec::new(); self.first_swipe_char = char::default(); self.swipe_left_first_key = false; + self.current_swipe_device = None; } - fn start_swipe(&mut self, key_label: char) -> Instant{ + fn start_swipe(&mut self, key_label: char, device: usize) -> Instant{ let now = Instant::now(); self.swipe_start_time = Some(now); self.first_swipe_char = key_label.to_ascii_lowercase(); + self.current_swipe_device = Some(device); self.swipe_candidates = Vec::new(); now } @@ -112,7 +116,7 @@ impl SwipeTypingManager { pub fn is_current_swipe_empty(&self) -> bool { self.current_swipe.is_empty() } - pub fn add_swipe(&mut self, within_key_pos_normalized: &Vec2, key_label: char) { + pub fn add_swipe(&mut self, within_key_pos_normalized: &Vec2, key_label: char, device: usize) { if let Some(pos) = self.keyboard_gird.key_positions.get(&key_label.to_ascii_lowercase()) { if self.first_swipe_char != char::default() && self.first_swipe_char != key_label.to_ascii_lowercase() { self.swipe_left_first_key = true; @@ -122,9 +126,14 @@ impl SwipeTypingManager { //println!("char : {key_label} at pos: {key_pos}"); let start_time = match self.swipe_start_time { Some(time) => time, - None => self.start_swipe(key_label) + None => self.start_swipe(key_label, device) }; + // only allow the pointer that started the swipe to contribute to it + if let Some(current_device) = self.current_swipe_device && current_device != device{ + return; + } + let within_key_pos_from_center = Vec2 { x: within_key_pos_normalized.x - 0.5, y: 0.5 - within_key_pos_normalized.y, From 0ee76718324f79c9ebe17905a53619c8cd31af14 Mon Sep 17 00:00:00 2001 From: Diamond White Date: Sun, 15 Mar 2026 12:32:16 +0000 Subject: [PATCH 11/35] process swipes on separate thread to not block ui --- wayvr/src/overlays/keyboard/builder.rs | 257 +++++++++++----------- wayvr/src/overlays/keyboard/mod.rs | 45 ++-- wayvr/src/overlays/keyboard/swipe_type.rs | 178 ++++++++++----- 3 files changed, 276 insertions(+), 204 deletions(-) diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index a3749356..87e41a4f 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -10,7 +10,7 @@ use crate::{ subsystem::hid::XkbKeymap, windowing::backend::OverlayEventData, }; -use anyhow::Context; +use anyhow::{bail, Context}; use glam::{FloatExt, Mat4, Vec2, vec2, vec3}; use slotmap::Key; use wgui::{ @@ -29,7 +29,7 @@ use wgui::event::StyleSetRequest; use wgui::layout::LayoutTask; use wgui::taffy::Display; use crate::overlays::keyboard::swipe_type::SwipeTypingManager; -use super::{KeyButtonData, KeyState, KeyboardState, handle_press, handle_release, layout::{self, KeyCapType}, handle_mouse_motion}; +use super::{KeyButtonData, KeyState, KeyboardState, handle_press, handle_release, layout::{self, KeyCapType}, handle_mouse_motion, init_swipe_type_manager}; const PIXELS_PER_UNIT: f32 = 60.; @@ -52,128 +52,134 @@ pub(super) fn update_swipe_prediction_bar( (theme.accent_color, theme.animation_mult) }; - let new_swipe_candidates; - if let Some(manager) = panel.state.swipe_typing_manager.as_mut() - && manager.swipe_candidates != panel.state.swipe_bar_candidates { - panel.state.swipe_bar_candidates = manager.swipe_candidates.clone(); - new_swipe_candidates = manager.swipe_candidates.clone(); - } else { - return Ok(elements_changed) - } + if let Some(recv) = panel.state.swipe_candidate_receiver.as_mut() + && let Ok(candidates) = recv.try_recv() { - let predictions_root = panel.parser_state - .get_widget_id("swipe_predictions_root") - .unwrap_or_default(); + let predictions_root = panel.parser_state + .get_widget_id("swipe_predictions_root") + .unwrap_or_default(); - if predictions_root.is_null() { - return Ok(elements_changed) - } - let doc_params = new_doc_params(panel); - - panel.layout.remove_children(predictions_root); - - for (i, prediction) in new_swipe_candidates.iter().enumerate() { - let mut params = HashMap::new(); - let id: Rc = Rc::from(format!("Prediction-{i}")); - params.insert("id".into(), id.clone()); - params.insert("text".into(), prediction.clone().into()); - - panel.parser_state.instantiate_template( - &doc_params, - "KeyPrediction", - &mut panel.layout, - predictions_root, - params - )?; - - if let Ok(widget_id) = panel.parser_state.get_widget_id(&id) { - let key_state = { - let rect = panel - .layout - .state - .widgets - .get_as::(widget_id) - .unwrap(); // want panic - - Rc::new(KeyState { - // fake button state just so we get key state for anims - button_state: KeyButtonData::Modifier { - modifier: 0, - sticky: core::cell::Cell::new(false), - }, - color: rect.params.color, - color2: rect.params.color2, - base_border_color: rect.params.border_color, - cur_border_color: rect.params.border_color.into(), - border: rect.params.border, - drawn_state: false.into(), - }) - }; - panel.add_event_listener( - widget_id, - EventListenerKind::MousePress, - Box::new({ - let k = key_state.clone(); - let pred = prediction.clone(); - move |common, data, app, state| { - if let Some(manager) = state.swipe_typing_manager.as_mut() { - manager.select_alternate_prediction(&pred, app, state.modifiers); - on_press_anim(k.clone(), common, data) - } - Ok(EventResult::Pass) - } - }) - ); - panel.add_event_listener( - widget_id, - EventListenerKind::MouseEnter, - Box::new({ - let k = key_state.clone(); - move |common, data, _app, _state| { - on_enter_anim( - k.clone(), - common, - data, - accent_color, - anim_mult, - 0.0, - ); - Ok(EventResult::Pass) - } - }) - ); - panel.add_event_listener( - widget_id, - EventListenerKind::MouseLeave, - Box::new({ - let k = key_state.clone(); - move |common, data, _app, _state | { - on_leave_anim( - k.clone(), - common, - data, - accent_color, - anim_mult, - 0.0, - ); - Ok(EventResult::Pass) - } - }) - ); - panel.add_event_listener( - widget_id, - EventListenerKind::MouseRelease, - Box::new({ - let k = key_state.clone(); - move |common, data, _app, _state| { - on_release_anim(k.clone(), common, data); - Ok(EventResult::Pass) - } - }) - ); + if predictions_root.is_null() { + return Ok(elements_changed) } + let doc_params = new_doc_params(panel); + + panel.layout.remove_children(predictions_root); + + let Some(new_suggestions) = candidates else { + return Ok(elements_changed) + }; + + let mut iter = new_suggestions.iter(); + let Some(best_prediction) = iter.next() else { + bail!("not enough swipe predictions"); + }; + if let Some(manager) = panel.state.swipe_typing_manager.as_mut() { + manager.select_word(best_prediction, app, panel.state.modifiers); + } + for (i, prediction) in iter.enumerate() { + let mut params = HashMap::new(); + let id: Rc = Rc::from(format!("Prediction-{i}")); + params.insert("id".into(), id.clone()); + params.insert("text".into(), prediction.clone().into()); + + panel.parser_state.instantiate_template( + &doc_params, + "KeyPrediction", + &mut panel.layout, + predictions_root, + params + )?; + + if let Ok(widget_id) = panel.parser_state.get_widget_id(&id) { + let key_state = { + let rect = panel + .layout + .state + .widgets + .get_as::(widget_id) + .unwrap(); // want panic + + Rc::new(KeyState { + // fake button state just so we get key state for anims + button_state: KeyButtonData::Modifier { + modifier: 0, + sticky: core::cell::Cell::new(false), + }, + color: rect.params.color, + color2: rect.params.color2, + base_border_color: rect.params.border_color, + cur_border_color: rect.params.border_color.into(), + border: rect.params.border, + drawn_state: false.into(), + }) + }; + panel.add_event_listener( + widget_id, + EventListenerKind::MousePress, + Box::new({ + let k = key_state.clone(); + let pred = prediction.clone(); + move |common, data, app, state| { + if let Some(manager) = state.swipe_typing_manager.as_mut() { + manager.select_alternate_prediction(&pred, app, state.modifiers); + on_press_anim(k.clone(), common, data) + } + Ok(EventResult::Pass) + } + }) + ); + panel.add_event_listener( + widget_id, + EventListenerKind::MouseEnter, + Box::new({ + let k = key_state.clone(); + move |common, data, _app, _state| { + on_enter_anim( + k.clone(), + common, + data, + accent_color, + anim_mult, + 0.0, + ); + Ok(EventResult::Pass) + } + }) + ); + panel.add_event_listener( + widget_id, + EventListenerKind::MouseLeave, + Box::new({ + let k = key_state.clone(); + move |common, data, _app, _state | { + on_leave_anim( + k.clone(), + common, + data, + accent_color, + anim_mult, + 0.0, + ); + Ok(EventResult::Pass) + } + }) + ); + panel.add_event_listener( + widget_id, + EventListenerKind::MouseRelease, + Box::new({ + let k = key_state.clone(); + move |common, data, _app, _state| { + on_release_anim(k.clone(), common, data); + Ok(EventResult::Pass) + } + }) + ); + } + } + elements_changed = true; } - elements_changed = true; Ok(elements_changed) } #[allow(clippy::too_many_lines, clippy::significant_drop_tightening)] @@ -473,6 +479,8 @@ pub(super) fn create_keyboard_panel( } if !app.session.config.swipe_to_type_enabled { panel.state.swipe_typing_manager = None; + panel.state.swipe_candidate_receiver = None; + let predictions_root = panel.parser_state .get_widget_id("swipe_predictions_root") .unwrap_or_default(); @@ -488,13 +496,8 @@ pub(super) fn create_keyboard_panel( } } if app.session.config.swipe_to_type_enabled && panel.state.swipe_typing_manager.is_none() { - panel.state.swipe_typing_manager = match SwipeTypingManager::new() { - Ok(engine) => Some(engine), - Err(e) => { - log::error!("Error occurred while trying to load swipe engine: {:?}", e); - None - } - }; + init_swipe_type_manager(&mut panel.state); + let predictions_root = panel.parser_state .get_widget_id("swipe_predictions_root") .unwrap_or_default(); diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index 7475acf8..e98d4333 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -4,7 +4,7 @@ use std::{ process::{Child, Command}, sync::atomic::Ordering, }; - +use std::sync::mpsc::Receiver; use crate::{ KEYMAP_CHANGE, backend::{ @@ -61,7 +61,7 @@ pub fn create_keyboard(app: &mut AppState, wayland: bool) -> anyhow::Result anyhow::Result { + state.swipe_typing_manager = Some(engine); + state.swipe_candidate_receiver = Some(receiver); + }, + Err(e) => { + log::error!("Error occured while trying to load swipe engine: {:?}", e); + } + }; +} fn alt_modifier_to_key(m: AltModifier) -> KeyModifier { match m { AltModifier::Shift => SHIFT, @@ -158,13 +168,7 @@ impl KeyboardBackend { ) -> anyhow::Result { let mut state = self.default_state.take(); - state.swipe_typing_manager = match SwipeTypingManager::new() { - Ok(engine) => Some(engine), - Err(e) => { - log::error!("Error occured while trying to load swipe engine: {:?}", e); - None - } - }; + init_swipe_type_manager(&mut state); log::info!("swipe engine created"); let panel = @@ -179,6 +183,7 @@ impl KeyboardBackend { Ok(id) } + fn switch_keymap(&mut self, keymap: &XkbKeymap, app: &mut AppState) -> anyhow::Result { if !self.wlx_layout.auto_labels.unwrap_or(true) { return Ok(false); @@ -211,13 +216,8 @@ impl KeyboardBackend { .state .take(); - state_from.swipe_typing_manager = match SwipeTypingManager::new() { - Ok(engine) => Some(engine), - Err(e) => { - log::error!("Error occured while trying to load swipe engine: {:?}", e); - None - } - }; + init_swipe_type_manager(&mut state_from); + self.active_layout = new_key; self.layout_panels @@ -360,7 +360,7 @@ struct KeyboardState { set_list: SetList, clock_12h: bool, swipe_typing_manager: Option, - swipe_bar_candidates: Vec, + swipe_candidate_receiver: Option>>> } macro_rules! take_and_leave_default { @@ -381,7 +381,7 @@ impl KeyboardState { set_list: SetList::default(), clock_12h: self.clock_12h, swipe_typing_manager: None, - swipe_bar_candidates: Vec::new(), + swipe_candidate_receiver: None, } } } @@ -515,14 +515,9 @@ fn handle_release(app: &mut AppState, key: &KeyState, k_cap_type: &KeyCapType, k match &key.button_state { KeyButtonData::Key { vk, pressed } => { if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() && *k_cap_type == KeyCapType::Letter { - println!("some"); if swipe_manager.did_swipe_leave_first_key() { - println!("left first"); match swipe_manager.predict() { - Ok(top_prediction) => { - println!("best prediction for swipe: {top_prediction}"); - swipe_manager.select_word(&top_prediction, app, keyboard.modifiers); - }, + Ok(()) => {}, Err(e) => { log::error!("{}", e) } diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs index 1929dc8c..7132ff2c 100644 --- a/wayvr/src/overlays/keyboard/swipe_type.rs +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -4,37 +4,50 @@ use anyhow::{anyhow, bail}; use arboard::Clipboard; use glam::Vec2; use std::mem; +use std::sync::mpsc::{sync_channel, Receiver, SyncSender, channel, Sender}; +use std::thread::{self, JoinHandle}; use std::time::Instant; use super_swipe_type::keyboard_manager::QwertyKeyboardGrid; use super_swipe_type::swipe_orchestrator::SwipeOrchestrator; use super_swipe_type::{SwipeCandidate, SwipePoint}; -const PREDICTION_SUGGESTION_COUNT: usize = 4; +const PREDICTION_SUGGESTION_COUNT: usize = 5; + +enum PredictionTask { + Predict { + swipe: Vec, + last_word: Option, + }, + Shutdown, +} + pub struct SwipeTypingManager { - swipe_engine: SwipeOrchestrator, keyboard_gird: QwertyKeyboardGrid, current_swipe: Vec, - pub swipe_candidates: Vec, + swipe_candidate_sender: SyncSender>>, + prediction_task_sender: Sender, + worker_thread: Option>, swipe_start_time: Option, clipboard: Clipboard, swipe_left_first_key: bool, first_swipe_char: char, - current_swipe_device: Option, // the pointer that started this swipe - last_swiped_word: Option + current_swipe_device: Option, + last_swiped_word: Option, } -impl SwipeTypingManager { +impl SwipeTypingManager { pub fn select_alternate_prediction(&mut self, word: &String, app: &mut AppState, original_keyboard_mods: KeyModifier) { Self::undo_paste(app, original_keyboard_mods); self.select_word(word, app, original_keyboard_mods); } - /// Attempts to "type" the word by copying to clipboard and pasting + pub fn select_word(&mut self, word: &String, app: &mut AppState, original_keyboard_mods: KeyModifier) { self.last_swiped_word = Some(word.clone()); - if let Ok(_) =self.copy_text_to_primary_clipboard(word.as_ref()) { + if let Ok(_) = self.copy_text_to_primary_clipboard(word.as_ref()) { Self::paste(app, original_keyboard_mods); } } + fn undo_paste(app: &mut AppState, original_keyboard_mods: KeyModifier) { app.hid_provider .set_modifiers_routed(app.wvr_server.as_mut(), CTRL); @@ -45,6 +58,7 @@ impl SwipeTypingManager { app.hid_provider .set_modifiers_routed(app.wvr_server.as_mut(), original_keyboard_mods); } + fn paste(app: &mut AppState, original_keyboard_mods: KeyModifier) { app.hid_provider .set_modifiers_routed(app.wvr_server.as_mut(), CTRL); @@ -55,46 +69,91 @@ impl SwipeTypingManager { app.hid_provider .set_modifiers_routed(app.wvr_server.as_mut(), original_keyboard_mods); } + fn copy_text_to_primary_clipboard(&mut self, text: &str) -> Result<(), arboard::Error> { self.clipboard.set_text(format!("{text} ")) } - pub fn new() -> anyhow::Result { - // todo: use the layout_name to choose a sensible language for the swipe engine - Ok(Self { - swipe_engine: SwipeOrchestrator::new()?, - keyboard_gird: QwertyKeyboardGrid::new(), // contains a hashmap where the vector2 is the center pos of each key in querty - current_swipe: Vec::new(), - swipe_candidates: Vec::new(), - swipe_start_time: None, - clipboard: Clipboard::new()?, - swipe_left_first_key: false, - first_swipe_char: char::default(), - current_swipe_device: None, - last_swiped_word: None - }) + pub fn new() -> anyhow::Result<(SwipeTypingManager, Receiver>>)> { + let (candidate_sender, candidate_receiver) = sync_channel(1); + let (task_sender, task_receiver) = channel::(); + + // Spawn persistent worker thread + let worker_candidate_sender = candidate_sender.clone(); + let worker_thread = thread::spawn(move || { + let mut swipe_engine = match SwipeOrchestrator::new() { + Ok(engine) => engine, + Err(e) => { + log::error!("Failed to initialize SwipeOrchestrator: {}", e); + return; + } + }; + + while let Ok(task) = task_receiver.recv() { + match task { + PredictionTask::Predict { swipe, last_word } => { + match swipe_engine.predict(swipe, &last_word) { + Ok(candidates) => { + let words: Vec = candidates + .iter() + .take(PREDICTION_SUGGESTION_COUNT) + .map(|c| c.word.clone()) + .collect(); + + let _ = worker_candidate_sender.send(Some(words)); + } + Err(e) => { + log::error!("Prediction failed: {}", e); + } + } + } + PredictionTask::Shutdown => break, + } + } + }); + + Ok(( + Self { + keyboard_gird: QwertyKeyboardGrid::new(), + current_swipe: Vec::new(), + swipe_candidate_sender: candidate_sender, + prediction_task_sender: task_sender, + worker_thread: Some(worker_thread), + swipe_start_time: None, + clipboard: Clipboard::new()?, + swipe_left_first_key: false, + first_swipe_char: char::default(), + current_swipe_device: None, + last_swiped_word: None, + }, + candidate_receiver, + )) } - pub fn predict(&mut self) -> anyhow::Result{ + + pub fn predict(&mut self) -> anyhow::Result<()> { if self.is_current_swipe_empty() { bail!("nothing to predict"); - } else { - let current_swipe = mem::take(&mut self.current_swipe); - self.reset_swipe(); - - let candidates: Vec = self.swipe_engine.predict(current_swipe, &self.last_swiped_word)?; - - let mut iter = candidates.iter(); - let top_choice = iter.next().ok_or(anyhow!("No swipe candidates generated"))?.word.clone(); - self.swipe_candidates = iter.take(PREDICTION_SUGGESTION_COUNT).map(|c| c.word.clone()).collect(); - - Ok(top_choice) } + + let current_swipe = mem::take(&mut self.current_swipe); + let last_word = self.last_swiped_word.clone(); + self.reset_swipe(); + + self.prediction_task_sender + .send(PredictionTask::Predict { + swipe: current_swipe, + last_word, + })?; + + Ok(()) } + pub fn reset(&mut self) { self.reset_swipe(); + let _ = self.swipe_candidate_sender.send(None); self.last_swiped_word = None; - self.swipe_candidates = Vec::new(); } + fn reset_swipe(&mut self) { self.swipe_start_time = None; self.current_swipe = Vec::new(); @@ -102,36 +161,45 @@ impl SwipeTypingManager { self.swipe_left_first_key = false; self.current_swipe_device = None; } - fn start_swipe(&mut self, key_label: char, device: usize) -> Instant{ + + fn start_swipe(&mut self, key_label: char, device: usize) -> Instant { let now = Instant::now(); self.swipe_start_time = Some(now); self.first_swipe_char = key_label.to_ascii_lowercase(); self.current_swipe_device = Some(device); - self.swipe_candidates = Vec::new(); now } + pub fn did_swipe_leave_first_key(&self) -> bool { self.swipe_left_first_key } + pub fn is_current_swipe_empty(&self) -> bool { self.current_swipe.is_empty() } + pub fn add_swipe(&mut self, within_key_pos_normalized: &Vec2, key_label: char, device: usize) { if let Some(pos) = self.keyboard_gird.key_positions.get(&key_label.to_ascii_lowercase()) { - if self.first_swipe_char != char::default() && self.first_swipe_char != key_label.to_ascii_lowercase() { + if self.first_swipe_char != char::default() + && self.first_swipe_char != key_label.to_ascii_lowercase() + { self.swipe_left_first_key = true; } - let key_pos = Vec2{x: pos.x as f32, y: pos.y as f32 }; - //println!("char : {key_label} at pos: {key_pos}"); - let start_time = match self.swipe_start_time { - Some(time) => time, - None => self.start_swipe(key_label, device) + let key_pos = Vec2 { + x: pos.x as f32, + y: pos.y as f32, }; - // only allow the pointer that started the swipe to contribute to it - if let Some(current_device) = self.current_swipe_device && current_device != device{ - return; + let start_time = match self.swipe_start_time { + Some(time) => time, + None => self.start_swipe(key_label, device), + }; + + if let Some(current_device) = self.current_swipe_device { + if current_device != device { + return; + } } let within_key_pos_from_center = Vec2 { @@ -143,13 +211,19 @@ impl SwipeTypingManager { y: QwertyKeyboardGrid::get_key_width() as f32, }; - let point = within_key_pos_from_center*key_dimensions + key_pos; + let point = within_key_pos_from_center * key_dimensions + key_pos; let duration = Instant::now().duration_since(start_time); - self.current_swipe.push(SwipePoint::new(point.x.into(), point.y.into(), duration)) + self.current_swipe + .push(SwipePoint::new(point.x.into(), point.y.into(), duration)) + } + } +} + +impl Drop for SwipeTypingManager { + fn drop(&mut self) { + let _ = self.prediction_task_sender.send(PredictionTask::Shutdown); + if let Some(handle) = self.worker_thread.take() { + let _ = handle.join(); } - } - - - } From 55756e6e51f70ee6e8a191c7da60ee11702e7dea Mon Sep 17 00:00:00 2001 From: Diamond White Date: Sun, 15 Mar 2026 13:01:40 +0000 Subject: [PATCH 12/35] add toggle to settings.rs --- dash-frontend/assets/lang/en.json | 2 ++ dash-frontend/src/tab/settings.rs | 5 +++++ wayvr/src/assets/gui/keyboard.xml | 4 ++-- wayvr/src/overlays/keyboard/builder.rs | 4 ++-- wlx-common/src/config.rs | 2 +- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index c7b4f8ea..2fd78c5e 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -63,6 +63,8 @@ "KEYBOARD_MIDDLE_CLICK": "Keyboard middle click", "KEYBOARD_MIDDLE_CLICK_HELP": "Modifier to use when typing\nwith purple laser", "KEYBOARD_SOUND_ENABLED": "Keyboard sounds", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED": "Keyboard swipe to type", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED_HELP": "Only works for English! Results will be drastically worse on non qwerty keyboards.", "LANGUAGE": "Language", "LEFT_HANDED_MOUSE": "Left-handed mouse", "LEFT_HANDED_MOUSE_HELP": "Use this if mouse buttons are swapped", diff --git a/dash-frontend/src/tab/settings.rs b/dash-frontend/src/tab/settings.rs index 32a1a2c4..89db8222 100644 --- a/dash-frontend/src/tab/settings.rs +++ b/dash-frontend/src/tab/settings.rs @@ -228,6 +228,7 @@ enum SettingType { InvertScrollDirectionY, KeyboardMiddleClick, KeyboardSoundEnabled, + KeyboardSwipeToTypeEnabled, Language, LeftHandedMouse, LongPressDuration, @@ -280,6 +281,7 @@ impl SettingType { Self::HideUsername => &mut config.hide_username, Self::OpaqueBackground => &mut config.opaque_background, Self::XwaylandByDefault => &mut config.xwayland_by_default, + Self::KeyboardSwipeToTypeEnabled => &mut config.keyboard_swipe_to_type_enabled, _ => panic!("Requested bool for non-bool SettingType"), } } @@ -378,6 +380,7 @@ impl SettingType { Self::InvertScrollDirectionY => Ok("APP_SETTINGS.INVERT_SCROLL_DIRECTION_Y"), Self::KeyboardMiddleClick => Ok("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK"), Self::KeyboardSoundEnabled => Ok("APP_SETTINGS.KEYBOARD_SOUND_ENABLED"), + Self::KeyboardSwipeToTypeEnabled => Ok("APP_SETTINGS.KEYBOARD_SWIPE_TO_TYPE_ENABLED"), Self::Language => Ok("APP_SETTINGS.LANGUAGE"), Self::LeftHandedMouse => Ok("APP_SETTINGS.LEFT_HANDED_MOUSE"), Self::LongPressDuration => Ok("APP_SETTINGS.LONG_PRESS_DURATION"), @@ -414,6 +417,7 @@ impl SettingType { Self::GridOpacity => Some("APP_SETTINGS.GRID_OPACITY_HELP"), Self::HandsfreePointer => Some("APP_SETTINGS.HANDSFREE_POINTER_HELP"), Self::KeyboardMiddleClick => Some("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK_HELP"), + Self::KeyboardSwipeToTypeEnabled => Some("APP_SETTINGS.KEYBOARD_SWIPE_TO_TYPE_ENABLED_HELP"), Self::LeftHandedMouse => Some("APP_SETTINGS.LEFT_HANDED_MOUSE_HELP"), Self::ScreenRenderDown => Some("APP_SETTINGS.SCREEN_RENDER_DOWN_HELP"), Self::UprightScreenFix => Some("APP_SETTINGS.UPRIGHT_SCREEN_FIX_HELP"), @@ -809,6 +813,7 @@ impl TabSettings { checkbox!(mp, c, SettingType::NotificationsEnabled); checkbox!(mp, c, SettingType::NotificationsSoundEnabled); checkbox!(mp, c, SettingType::KeyboardSoundEnabled); + checkbox!(mp, c, SettingType::KeyboardSwipeToTypeEnabled); checkbox!(mp, c, SettingType::SpaceDragUnlocked); checkbox!(mp, c, SettingType::SpaceRotateUnlocked); slider_f32!(mp, c, SettingType::SpaceDragMultiplier, -10.0, 10.0, 0.5); diff --git a/wayvr/src/assets/gui/keyboard.xml b/wayvr/src/assets/gui/keyboard.xml index ceea4027..ca94b8aa 100644 --- a/wayvr/src/assets/gui/keyboard.xml +++ b/wayvr/src/assets/gui/keyboard.xml @@ -243,10 +243,10 @@
-
+
-
+
diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index 87e41a4f..d924cd07 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -477,7 +477,7 @@ pub(super) fn create_keyboard_panel( elems_changed = true; } } - if !app.session.config.swipe_to_type_enabled { + if !app.session.config.keyboard_swipe_to_type_enabled { panel.state.swipe_typing_manager = None; panel.state.swipe_candidate_receiver = None; @@ -495,7 +495,7 @@ pub(super) fn create_keyboard_panel( } } - if app.session.config.swipe_to_type_enabled && panel.state.swipe_typing_manager.is_none() { + if app.session.config.keyboard_swipe_to_type_enabled && panel.state.swipe_typing_manager.is_none() { init_swipe_type_manager(&mut panel.state); let predictions_root = panel.parser_state diff --git a/wlx-common/src/config.rs b/wlx-common/src/config.rs index c578ef18..c375b310 100644 --- a/wlx-common/src/config.rs +++ b/wlx-common/src/config.rs @@ -329,5 +329,5 @@ pub struct GeneralConfig { pub grid_opacity: f32, #[serde(default = "def_true")] - pub swipe_to_type_enabled: bool, + pub keyboard_swipe_to_type_enabled: bool, } From 5891c4b5b6ab6252f0245cf04866ff012c452c66 Mon Sep 17 00:00:00 2001 From: Diamond White Date: Sun, 15 Mar 2026 14:19:39 +0000 Subject: [PATCH 13/35] default swipe to type config to false --- wayvr/src/overlays/keyboard/builder.rs | 1 - wayvr/src/overlays/keyboard/layout.rs | 1 - wayvr/src/overlays/keyboard/mod.rs | 47 +++++++++++++++++++++----- wlx-common/src/config.rs | 2 +- 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index d924cd07..e95e8c80 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -507,7 +507,6 @@ pub(super) fn create_keyboard_panel( predictions_root, StyleSetRequest::Display(Display::Flex), )); - } } } diff --git a/wayvr/src/overlays/keyboard/layout.rs b/wayvr/src/overlays/keyboard/layout.rs index f65e3bd0..84c489a8 100644 --- a/wayvr/src/overlays/keyboard/layout.rs +++ b/wayvr/src/overlays/keyboard/layout.rs @@ -2,7 +2,6 @@ use std::{collections::HashMap, str::FromStr, sync::LazyLock}; use regex::Regex; use serde::{Deserialize, Serialize}; -use smithay::reexports::wayland_protocols::wp::pointer_gestures::zv1::server::zwp_pointer_gesture_swipe_v1::ZwpPointerGestureSwipeV1; use crate::{ config::{ConfigType, load_known_yaml}, subsystem::hid::{ diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index e98d4333..13c56cd3 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -29,11 +29,15 @@ use crate::{ use anyhow::Context; use glam::{Affine3A, Quat, Vec3, vec3, Vec2}; use regex::Regex; -use slotmap::{SlotMap, new_key_type}; +use slotmap::{SlotMap, new_key_type, Key}; use wgui::{ drawing, event::{InternalStateChangeEvent, MouseButtonEvent, MouseButtonIndex}, }; +use wgui::event::StyleSetRequest; +use wgui::layout::{Layout, LayoutTask}; +use wgui::parser::Fetchable; +use wgui::taffy::Display; use wlx_common::windowing::{OverlayWindowState, Positioning}; use wlx_common::{ config::AltModifier, @@ -124,7 +128,7 @@ pub fn create_keyboard(app: &mut AppState, wayland: bool) -> anyhow::Result { state.swipe_typing_manager = Some(engine); @@ -135,6 +139,18 @@ pub fn init_swipe_type_manager(state: &mut KeyboardState) { } }; } +pub(self) fn hide_swipe_type_manager(panel: &mut GuiPanel) { + let predictions_root = panel.parser_state + .get_widget_id("swipe_predictions_root") + .unwrap_or_default(); + + if !predictions_root.is_null() { + panel.layout.tasks.push(LayoutTask::SetWidgetStyle( + predictions_root, + StyleSetRequest::Display(Display::None), + )); + } +} fn alt_modifier_to_key(m: AltModifier) -> KeyModifier { match m { AltModifier::Shift => SHIFT, @@ -168,12 +184,18 @@ impl KeyboardBackend { ) -> anyhow::Result { let mut state = self.default_state.take(); - init_swipe_type_manager(&mut state); + if app.session.config.keyboard_swipe_to_type_enabled { + init_swipe_type_manager(&mut state); + } log::info!("swipe engine created"); - let panel = + let mut panel = create_keyboard_panel(app, keymap, state, &self.wlx_layout)?; + if !app.session.config.keyboard_swipe_to_type_enabled { + hide_swipe_type_manager(&mut panel); + } + let id = self.layout_panels.insert(panel); if let Some(layout_name) = keymap.and_then(|k| k.get_name()) { self.layout_ids.insert(layout_name.into(), id); @@ -198,17 +220,17 @@ impl KeyboardBackend { if self.active_layout.eq(new_key) { return Ok(false); } - self.internal_switch_keymap(*new_key, keymap); + self.internal_switch_keymap(*new_key, app); } else { let new_key = self.add_new_keymap(Some(keymap), app)?; - self.internal_switch_keymap(new_key, keymap); + self.internal_switch_keymap(new_key, app); } app.tasks .enqueue(TaskType::Overlay(OverlayTask::KeyboardChanged)); Ok(true) } - fn internal_switch_keymap(&mut self, new_key: KeyboardPanelKey, keymap: &XkbKeymap) { + fn internal_switch_keymap(&mut self, new_key: KeyboardPanelKey, app: &AppState) { let mut state_from = self .layout_panels .get_mut(self.active_layout) @@ -216,7 +238,9 @@ impl KeyboardBackend { .state .take(); - init_swipe_type_manager(&mut state_from); + if app.session.config.keyboard_swipe_to_type_enabled { + init_swipe_type_manager(&mut state_from); + } self.active_layout = new_key; @@ -224,6 +248,13 @@ impl KeyboardBackend { .get_mut(self.active_layout) .unwrap() .state = state_from; + + if !app.session.config.keyboard_swipe_to_type_enabled { + hide_swipe_type_manager(self.layout_panels + .get_mut(self.active_layout) + .unwrap() + ) + } } fn get_effective_keymap(&mut self) -> anyhow::Result { diff --git a/wlx-common/src/config.rs b/wlx-common/src/config.rs index c375b310..1878df81 100644 --- a/wlx-common/src/config.rs +++ b/wlx-common/src/config.rs @@ -328,6 +328,6 @@ pub struct GeneralConfig { #[serde(default = "def_one")] pub grid_opacity: f32, - #[serde(default = "def_true")] + #[serde(default = "def_false")] pub keyboard_swipe_to_type_enabled: bool, } From 9382af67e2ab0cde12a77e14c8cb82bec3d445da Mon Sep 17 00:00:00 2001 From: Diamond White Date: Mon, 16 Mar 2026 00:53:25 +0000 Subject: [PATCH 14/35] remove println, minor fix --- wayvr/src/assets/gui/keyboard.xml | 2 +- wayvr/src/overlays/keyboard/mod.rs | 1 - wayvr/src/overlays/keyboard/swipe_type.rs | 16 ++++++++-------- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/wayvr/src/assets/gui/keyboard.xml b/wayvr/src/assets/gui/keyboard.xml index ca94b8aa..405823d8 100644 --- a/wayvr/src/assets/gui/keyboard.xml +++ b/wayvr/src/assets/gui/keyboard.xml @@ -98,7 +98,7 @@ diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index 13c56cd3..70dd9471 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -555,7 +555,6 @@ fn handle_release(app: &mut AppState, key: &KeyState, k_cap_type: &KeyCapType, k } } else { // pointer must have been released on the same key it was pressed on - println!("swipe reset"); swipe_manager.reset(); // drop swipe tracking that was started on press app.hid_provider diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs index 7132ff2c..62ee3f62 100644 --- a/wayvr/src/overlays/keyboard/swipe_type.rs +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -43,7 +43,7 @@ impl SwipeTypingManager { pub fn select_word(&mut self, word: &String, app: &mut AppState, original_keyboard_mods: KeyModifier) { self.last_swiped_word = Some(word.clone()); - if let Ok(_) = self.copy_text_to_primary_clipboard(word.as_ref()) { + if let Ok(_) = self.copy_text_to_clipboard(word.as_ref()) { Self::paste(app, original_keyboard_mods); } } @@ -70,7 +70,7 @@ impl SwipeTypingManager { .set_modifiers_routed(app.wvr_server.as_mut(), original_keyboard_mods); } - fn copy_text_to_primary_clipboard(&mut self, text: &str) -> Result<(), arboard::Error> { + fn copy_text_to_clipboard(&mut self, text: &str) -> Result<(), arboard::Error> { self.clipboard.set_text(format!("{text} ")) } @@ -180,6 +180,12 @@ impl SwipeTypingManager { pub fn add_swipe(&mut self, within_key_pos_normalized: &Vec2, key_label: char, device: usize) { if let Some(pos) = self.keyboard_gird.key_positions.get(&key_label.to_ascii_lowercase()) { + if let Some(current_device) = self.current_swipe_device { + if current_device != device { + return; + } + } + if self.first_swipe_char != char::default() && self.first_swipe_char != key_label.to_ascii_lowercase() { @@ -196,12 +202,6 @@ impl SwipeTypingManager { None => self.start_swipe(key_label, device), }; - if let Some(current_device) = self.current_swipe_device { - if current_device != device { - return; - } - } - let within_key_pos_from_center = Vec2 { x: within_key_pos_normalized.x - 0.5, y: 0.5 - within_key_pos_normalized.y, From 5cf1a0c34c493f964505f9be3430b43f857c681a Mon Sep 17 00:00:00 2001 From: Diamond White Date: Mon, 16 Mar 2026 02:04:22 +0000 Subject: [PATCH 15/35] use correct key_dimensions --- wayvr/src/overlays/keyboard/swipe_type.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs index 62ee3f62..61352783 100644 --- a/wayvr/src/overlays/keyboard/swipe_type.rs +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -208,7 +208,7 @@ impl SwipeTypingManager { }; let key_dimensions = Vec2 { x: QwertyKeyboardGrid::get_key_width() as f32, - y: QwertyKeyboardGrid::get_key_width() as f32, + y: QwertyKeyboardGrid::get_key_height() as f32, }; let point = within_key_pos_from_center * key_dimensions + key_pos; From 47a3c65ca09ecdfb3947edeb705dbdcebc990f3e Mon Sep 17 00:00:00 2001 From: oneshinyboi Date: Mon, 16 Mar 2026 04:35:48 +0000 Subject: [PATCH 16/35] use latest swipe_type library version --- Cargo.lock | 4 ++-- wayvr/Cargo.toml | 2 +- wayvr/src/overlays/keyboard/swipe_type.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a3a6c1f..df023aec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6101,9 +6101,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "super-swipe-type" -version = "0.1.2" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32fd40ad969f38b55f6d1541d7c1a16226821b51a38362de7aa800f976632336" +checksum = "22126517bdf94f41b3d0af625fdf69b5ff131b7dd19a59866d3c00a56072af88" dependencies = [ "anyhow", "cached-path", diff --git a/wayvr/Cargo.toml b/wayvr/Cargo.toml index 807528c9..2b544422 100644 --- a/wayvr/Cargo.toml +++ b/wayvr/Cargo.toml @@ -90,7 +90,7 @@ xcb = { version = "1.6.0", optional = true, features = [ xkbcommon = { version = "0.8.0" } # 0.9.0 breaks keymap import on some distros codes-iso-639 = "0.1.5" arboard = { version="3.6.1", features = ["wayland-data-control", "wl-clipboard-rs"] } -super-swipe-type = "0.1.2" +super-swipe-type = "0.2.2" [build-dependencies] diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs index 61352783..771adf5f 100644 --- a/wayvr/src/overlays/keyboard/swipe_type.rs +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -212,7 +212,7 @@ impl SwipeTypingManager { }; let point = within_key_pos_from_center * key_dimensions + key_pos; - let duration = Instant::now().duration_since(start_time); + let duration = Instant::now().duration_since(start_time).mul_f32(0.8); // multiply by .8 because library is trained on mobile swipes which happen on a smaller keyboard and are faster self.current_swipe .push(SwipePoint::new(point.x.into(), point.y.into(), duration)) } From 1c272fc6557f8d3757d7b2009ea62a4ca298084c Mon Sep 17 00:00:00 2001 From: oneshinyboi Date: Mon, 16 Mar 2026 08:33:06 +0000 Subject: [PATCH 17/35] optionally use wayvr compositor clipboard when pasting --- wayvr/src/backend/wayvr/client.rs | 15 ++++++++++- wayvr/src/backend/wayvr/mod.rs | 5 +++- wayvr/src/overlays/keyboard/swipe_type.rs | 31 +++++++++++++++-------- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/wayvr/src/backend/wayvr/client.rs b/wayvr/src/backend/wayvr/client.rs index 862fcc5d..662eee26 100644 --- a/wayvr/src/backend/wayvr/client.rs +++ b/wayvr/src/backend/wayvr/client.rs @@ -7,10 +7,12 @@ use smithay::{ reexports::wayland_server, utils::SerialCounter, }; +use smithay::input::Seat; +use smithay::wayland::selection::data_device::set_data_device_selection; use xkbcommon::xkb; use crate::backend::wayvr::{ExternalProcessRequest, WayVRTask}; - +use crate::backend::wayvr::comp::Application; use super::{ ProcessWayVREnv, comp::{self, ClientState}, @@ -26,6 +28,7 @@ pub struct WayVRCompositor { pub state: comp::Application, pub seat_keyboard: KeyboardHandle, pub seat_pointer: PointerHandle, + pub seat: Seat, pub serial_counter: SerialCounter, pub wayland_env: super::WaylandEnv, @@ -68,6 +71,7 @@ impl WayVRCompositor { display: wayland_server::Display, seat_keyboard: KeyboardHandle, seat_pointer: PointerHandle, + seat: Seat, ) -> anyhow::Result { let (wayland_env, listener) = create_wayland_listener()?; @@ -81,6 +85,7 @@ impl WayVRCompositor { serial_counter: SerialCounter::new(), clients: Vec::new(), toplevel_surf_count: 0, + seat, }) } @@ -200,6 +205,14 @@ impl WayVRCompositor { ); } + pub fn set_clipboard_text(&mut self, text: String) { + set_data_device_selection::( + &self.state.display_handle, + &self.seat, + vec!["text/plain;charset=utf-8".to_string(), "text/plain".to_string()], + text.as_bytes().into(), + ); + } pub fn set_keymap(&mut self, keymap: &xkb::Keymap) -> anyhow::Result<()> { // Smithay only accepts keymaps in a string form due to thread safety concerns self.seat_keyboard diff --git a/wayvr/src/backend/wayvr/mod.rs b/wayvr/src/backend/wayvr/mod.rs index 1b04636b..20b6460b 100644 --- a/wayvr/src/backend/wayvr/mod.rs +++ b/wayvr/src/backend/wayvr/mod.rs @@ -267,7 +267,7 @@ impl WvrServerState { Ok(Self { time_start, - manager: client::WayVRCompositor::new(state, display, seat_keyboard, seat_pointer)?, + manager: client::WayVRCompositor::new(state, display, seat_keyboard, seat_pointer, seat)?, processes: ProcessVec::new(), wm: window::WindowManager::new(), ticks: 0, @@ -635,6 +635,9 @@ impl WvrServerState { pub fn send_key(&mut self, virtual_key: u32, down: bool) { self.manager.send_key(virtual_key, down); } + pub fn set_clipboard_text(&mut self, text: String) { + self.manager.set_clipboard_text(text); + } pub fn set_keymap(&mut self, keymap: &xkb::Keymap) -> anyhow::Result<()> { self.manager.set_keymap(keymap) diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs index 771adf5f..28861d27 100644 --- a/wayvr/src/overlays/keyboard/swipe_type.rs +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -1,6 +1,6 @@ use crate::state::AppState; use crate::subsystem::hid::{KeyModifier, VirtualKey, CTRL}; -use anyhow::{anyhow, bail}; +use anyhow::{bail}; use arboard::Clipboard; use glam::Vec2; use std::mem; @@ -9,7 +9,8 @@ use std::thread::{self, JoinHandle}; use std::time::Instant; use super_swipe_type::keyboard_manager::QwertyKeyboardGrid; use super_swipe_type::swipe_orchestrator::SwipeOrchestrator; -use super_swipe_type::{SwipeCandidate, SwipePoint}; +use super_swipe_type::{SwipePoint}; +use crate::subsystem::input::KeyboardFocus; const PREDICTION_SUGGESTION_COUNT: usize = 5; @@ -37,18 +38,31 @@ pub struct SwipeTypingManager { impl SwipeTypingManager { pub fn select_alternate_prediction(&mut self, word: &String, app: &mut AppState, original_keyboard_mods: KeyModifier) { - Self::undo_paste(app, original_keyboard_mods); + Self::undo(app, original_keyboard_mods); self.select_word(word, app, original_keyboard_mods); } pub fn select_word(&mut self, word: &String, app: &mut AppState, original_keyboard_mods: KeyModifier) { self.last_swiped_word = Some(word.clone()); - if let Ok(_) = self.copy_text_to_clipboard(word.as_ref()) { - Self::paste(app, original_keyboard_mods); + let text_to_paste = format!("{word} "); + + match app.hid_provider.keyboard_focus { + KeyboardFocus::PhysicalScreen => { + if let Ok(_) = self.clipboard.set_text(text_to_paste) { + Self::paste(app, original_keyboard_mods); + } + }, + KeyboardFocus::WayVR => { + if let Some(wvr_server) = app.wvr_server.as_mut() { + wvr_server.set_clipboard_text(text_to_paste); + Self::paste(app, original_keyboard_mods); + } + }, } + } - fn undo_paste(app: &mut AppState, original_keyboard_mods: KeyModifier) { + fn undo(app: &mut AppState, original_keyboard_mods: KeyModifier) { app.hid_provider .set_modifiers_routed(app.wvr_server.as_mut(), CTRL); app.hid_provider @@ -69,11 +83,6 @@ impl SwipeTypingManager { app.hid_provider .set_modifiers_routed(app.wvr_server.as_mut(), original_keyboard_mods); } - - fn copy_text_to_clipboard(&mut self, text: &str) -> Result<(), arboard::Error> { - self.clipboard.set_text(format!("{text} ")) - } - pub fn new() -> anyhow::Result<(SwipeTypingManager, Receiver>>)> { let (candidate_sender, candidate_receiver) = sync_channel(1); let (task_sender, task_receiver) = channel::(); From f7be9ca5ef8028948547c9cbe0e7f0042816de19 Mon Sep 17 00:00:00 2001 From: oneshinyboi Date: Mon, 16 Mar 2026 08:41:41 +0000 Subject: [PATCH 18/35] remove unused imports --- wayvr/src/overlays/keyboard/builder.rs | 5 ++--- wayvr/src/overlays/keyboard/mod.rs | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index e95e8c80..c1b957f5 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -28,7 +28,6 @@ use wgui::{ use wgui::event::StyleSetRequest; use wgui::layout::LayoutTask; use wgui::taffy::Display; -use crate::overlays::keyboard::swipe_type::SwipeTypingManager; use super::{KeyButtonData, KeyState, KeyboardState, handle_press, handle_release, layout::{self, KeyCapType}, handle_mouse_motion, init_swipe_type_manager}; const PIXELS_PER_UNIT: f32 = 60.; @@ -320,7 +319,7 @@ pub(super) fn create_keyboard_panel( EventListenerKind::MouseEnter, Box::new({ let k = key_state.clone(); - move |common, data, _app, state| { + move |common, data, _app, _state| { common.alterables.trigger_haptics(); on_enter_anim( k.clone(), @@ -382,7 +381,7 @@ pub(super) fn create_keyboard_panel( let k = key_state.clone(); let k_label = key_label.clone(); let k_cap_type = key_cap_type.clone(); - move |common, data, app, state| { + move |common, data, _app, state| { let within_key_pos = data.metadata.get_mouse_pos_normalized(&common.alterables.transform_stack); let CallbackMetadata::MousePosition(position) = data.metadata else { panic!("CallbackMetadata should contain MousePosition!"); diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index 70dd9471..9334f0a4 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -35,7 +35,7 @@ use wgui::{ event::{InternalStateChangeEvent, MouseButtonEvent, MouseButtonIndex}, }; use wgui::event::StyleSetRequest; -use wgui::layout::{Layout, LayoutTask}; +use wgui::layout::{LayoutTask}; use wgui::parser::Fetchable; use wgui::taffy::Display; use wlx_common::windowing::{OverlayWindowState, Positioning}; From 9651152b8b2106b76a8486757a9da4ffb80999d9 Mon Sep 17 00:00:00 2001 From: oneshinyboi Date: Fri, 20 Mar 2026 18:15:13 +0000 Subject: [PATCH 19/35] fix typo in log::error --- wayvr/src/overlays/keyboard/mod.rs | 2 +- wayvr/src/overlays/keyboard/swipe_type.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index 9334f0a4..96480d4e 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -135,7 +135,7 @@ pub(self) fn init_swipe_type_manager(state: &mut KeyboardState) { state.swipe_candidate_receiver = Some(receiver); }, Err(e) => { - log::error!("Error occured while trying to load swipe engine: {:?}", e); + log::error!("Error occurred while trying to load swipe engine: {}", e); } }; } diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs index 28861d27..471dca1a 100644 --- a/wayvr/src/overlays/keyboard/swipe_type.rs +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -104,9 +104,9 @@ impl SwipeTypingManager { match swipe_engine.predict(swipe, &last_word) { Ok(candidates) => { let words: Vec = candidates - .iter() + .into_iter() .take(PREDICTION_SUGGESTION_COUNT) - .map(|c| c.word.clone()) + .map(|c| c.word) .collect(); let _ = worker_candidate_sender.send(Some(words)); From e91b5875f692010fe8a1abf1f3efc507e6609207 Mon Sep 17 00:00:00 2001 From: oneshinyboi Date: Mon, 23 Mar 2026 20:58:11 +0000 Subject: [PATCH 20/35] fix within key positioning --- wayvr/src/overlays/keyboard/swipe_type.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs index 471dca1a..428303dc 100644 --- a/wayvr/src/overlays/keyboard/swipe_type.rs +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -213,7 +213,7 @@ impl SwipeTypingManager { let within_key_pos_from_center = Vec2 { x: within_key_pos_normalized.x - 0.5, - y: 0.5 - within_key_pos_normalized.y, + y: within_key_pos_normalized.y - 0.5, }; let key_dimensions = Vec2 { x: QwertyKeyboardGrid::get_key_width() as f32, From 7d35789f62b544a79d34d275cb2fd1a25e81a0a6 Mon Sep 17 00:00:00 2001 From: Diamond White Date: Sat, 4 Apr 2026 16:31:49 -0500 Subject: [PATCH 21/35] run swipes on AltGr keycaps --- wayvr/src/main.rs | 1 + wayvr/src/overlays/keyboard/mod.rs | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/wayvr/src/main.rs b/wayvr/src/main.rs index 6d73a9ec..45085b09 100644 --- a/wayvr/src/main.rs +++ b/wayvr/src/main.rs @@ -280,6 +280,7 @@ fn logging_init(args: &mut Args) { .from_env_lossy() .add_directive("symphonia_core::probe=warn".parse().unwrap()) .add_directive("symphonia_bundle_mp3=warn".parse().unwrap()) + .add_directive("ort=warn".parse().unwrap()) .add_directive("zbus=warn".parse().unwrap()) .add_directive("usvg=error".parse().unwrap()) .add_directive("resvg=error".parse().unwrap()) diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index 96480d4e..7e486381 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -461,7 +461,9 @@ fn handle_mouse_motion( within_key_pos: &Option, device: usize ) { - if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() && *key_cap_type == KeyCapType::Letter { + if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() + && matches!(*key_cap_type, KeyCapType::Letter | KeyCapType::LetterAltGr) + { if !swipe_manager.is_current_swipe_empty() { match &key.button_state { KeyButtonData::Key { vk, pressed } => { @@ -493,7 +495,9 @@ fn handle_press( ) { match &key.button_state { KeyButtonData::Key { vk, pressed } => { - if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() && *key_cap_type == KeyCapType::Letter { + if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() + && matches!(*key_cap_type, KeyCapType::Letter | KeyCapType::LetterAltGr) + { if let Some(pos) = within_key_pos { if let Some(label) = key_label.first() { swipe_manager.add_swipe(pos, label.chars().next().unwrap_or_default(), device); @@ -545,7 +549,9 @@ fn handle_press( fn handle_release(app: &mut AppState, key: &KeyState, k_cap_type: &KeyCapType, keyboard: &mut KeyboardState) -> bool { match &key.button_state { KeyButtonData::Key { vk, pressed } => { - if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() && *k_cap_type == KeyCapType::Letter { + if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() + && matches!(*k_cap_type, KeyCapType::Letter | KeyCapType::LetterAltGr) + { if swipe_manager.did_swipe_leave_first_key() { match swipe_manager.predict() { Ok(()) => {}, From db0e6ab08094a9663dca5816db26cdbcbe7bf51d Mon Sep 17 00:00:00 2001 From: oneshinyboi Date: Sun, 26 Jul 2026 23:29:13 -0500 Subject: [PATCH 22/35] get it to build --- Cargo.lock | 845 +++++++++++++++++++++- wayvr/Cargo.toml | 2 + wayvr/src/backend/wayvr/client.rs | 6 +- wayvr/src/main.rs | 1 - wayvr/src/overlays/keyboard/builder.rs | 25 +- wayvr/src/overlays/keyboard/layout.rs | 1 + wayvr/src/overlays/keyboard/mod.rs | 10 +- wayvr/src/overlays/keyboard/swipe_type.rs | 15 +- wlx-common/src/config.rs | 2 +- 9 files changed, 861 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee28ae54..115c527b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,6 +33,17 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aes" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" +dependencies = [ + "cipher", + "cpubits", + "cpufeatures 0.3.0", +] + [[package]] name = "ahash" version = "0.8.12" @@ -241,6 +252,26 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +[[package]] +name = "arboard" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "parking_lot", + "percent-encoding", + "windows-sys 0.59.0", + "x11rb", +] + [[package]] name = "arg_enum_proc_macro" version = "0.3.4" @@ -710,6 +741,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", + "zeroize", +] + [[package]] name = "block2" version = "0.5.1" @@ -791,6 +832,37 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + +[[package]] +name = "cached-path" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e79c94134331f0107c9f5b7caaf15f118ec91de67dcd3fa1145921e9fc27fa" +dependencies = [ + "flate2", + "fs2", + "glob", + "infer", + "log", + "rand 0.10.2", + "reqwest", + "serde", + "serde_json", + "sha2 0.10.9", + "tar", + "tempfile", + "thiserror 2.0.18", + "zip", +] + [[package]] name = "calloop" version = "0.13.0" @@ -869,6 +941,17 @@ dependencies = [ "nom 7.1.3", ] +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + [[package]] name = "cfg-expr" version = "0.20.8" @@ -936,6 +1019,16 @@ dependencies = [ "phf", ] +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "crypto-common 0.2.2", + "inout", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -987,6 +1080,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + [[package]] name = "cmake" version = "0.1.58" @@ -996,6 +1098,12 @@ dependencies = [ "cc", ] +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "codespan-reporting" version = "0.13.1" @@ -1058,6 +1166,12 @@ dependencies = [ "yaml-rust2", ] +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "const-random" version = "0.1.18" @@ -1078,6 +1192,12 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "convert_case" version = "0.4.0" @@ -1226,6 +1346,12 @@ dependencies = [ "windows", ] +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1303,6 +1429,24 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + [[package]] name = "cursor-icon" version = "1.2.0" @@ -1491,6 +1635,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "deflate64" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" + [[package]] name = "defmt" version = "1.1.1" @@ -1547,8 +1697,21 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid", + "crypto-common 0.2.2", + "ctutils", + "zeroize", ] [[package]] @@ -1773,6 +1936,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + [[package]] name = "etagere" version = "0.3.0" @@ -1845,6 +2014,12 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + [[package]] name = "fdeflate" version = "0.3.7" @@ -1854,6 +2029,16 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -1872,6 +2057,16 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe" +[[package]] +name = "flatbuffers" +version = "24.12.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1baf0dbf96932ec9a3038d57900329c015b0bfb7b63d904f3bc27e2b02a096" +dependencies = [ + "bitflags 1.3.2", + "rustc_version", +] + [[package]] name = "flate2" version = "1.1.9" @@ -1880,6 +2075,7 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -1989,12 +2185,28 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "fs_extra" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "fst" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab85b9b05e3978cc9a9cf8fea7f01b494e1a09ed3037e16ba39edc7a29eb61a" + [[package]] name = "funty" version = "2.0.0" @@ -2163,9 +2375,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", "rand_core 0.10.1", + "wasm-bindgen", ] [[package]] @@ -2320,6 +2534,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", +] + [[package]] name = "hound" version = "3.5.1" @@ -2371,16 +2594,72 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" +[[package]] +name = "hybrid-array" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ + "atomic-waker", "bytes", + "futures-channel", + "futures-core", "http", "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", "tokio", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", ] [[package]] @@ -2566,6 +2845,7 @@ dependencies = [ "png", "ravif", "rayon", + "tiff", "zune-core", "zune-jpeg", ] @@ -2632,6 +2912,24 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "hybrid-array", +] + [[package]] name = "input" version = "0.9.1" @@ -2705,6 +3003,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -2932,6 +3236,12 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" +[[package]] +name = "libbz2-rs-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" + [[package]] name = "libc" version = "0.2.186" @@ -3132,6 +3442,15 @@ dependencies = [ "twox-hash", ] +[[package]] +name = "lzma-rust2" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca93e534d1142d1d0dcca6d25fe302508a5dfb40b302802904577725ea0b695b" +dependencies = [ + "sha2 0.11.0", +] + [[package]] name = "mach2" version = "0.5.0" @@ -3147,7 +3466,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "regex-automata", + "regex-automata 0.4.14", ] [[package]] @@ -3166,6 +3485,16 @@ version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +[[package]] +name = "memmap" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "memmap2" version = "0.9.11" @@ -3220,6 +3549,17 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.61.2", +] + [[package]] name = "moveit" version = "0.6.0" @@ -3427,6 +3767,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi 0.5.2", + "libc", +] + [[package]] name = "num_enum" version = "0.7.6" @@ -3490,6 +3840,18 @@ dependencies = [ "objc2-quartz-core 0.2.2", ] +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.13.0", + "objc2 0.6.4", + "objc2-core-graphics", + "objc2-foundation 0.3.2", +] + [[package]] name = "objc2-audio-toolbox" version = "0.3.2" @@ -3587,6 +3949,19 @@ dependencies = [ "objc2 0.6.4", ] +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.13.0", + "dispatch2", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-io-surface", +] + [[package]] name = "objc2-core-image" version = "0.2.2" @@ -3653,6 +4028,17 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.13.0", + "objc2 0.6.4", + "objc2-core-foundation", +] + [[package]] name = "objc2-link-presentation" version = "0.2.2" @@ -3661,7 +4047,7 @@ checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ "block2 0.5.1", "objc2 0.5.2", - "objc2-app-kit", + "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", ] @@ -3993,6 +4379,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "pbkdf2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" +dependencies = [ + "digest 0.11.3", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -4216,6 +4612,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppmd-rust" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -4722,8 +5124,18 @@ checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.14", + "regex-syntax 0.8.11", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "fst", + "regex-syntax 0.6.29", ] [[package]] @@ -4734,15 +5146,59 @@ checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.11", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "resvg" version = "0.47.0" @@ -4844,6 +5300,104 @@ dependencies = [ "memchr", ] +[[package]] +name = "rten" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c230fa4ade87c913f61dbd911b7eb0d49460ceff3f1e4fabc837fac191137c" +dependencies = [ + "flatbuffers", + "num_cpus", + "rayon", + "rten-base", + "rten-gemm", + "rten-model-file", + "rten-onnx", + "rten-shape-inference", + "rten-simd", + "rten-tensor", + "rten-vecmath", + "rustc-hash", + "smallvec", + "typeid", + "wasm-bindgen", +] + +[[package]] +name = "rten-base" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2738cf8bb4c27f828ac788d01ccf4e367e8e773cfec6851f81851b5211de6a79" +dependencies = [ + "rayon", +] + +[[package]] +name = "rten-gemm" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a81a0ca209fb5ce21bd17efa0bd287d5881c6cebfbff0b21c4294a1a14a9e" +dependencies = [ + "rayon", + "rten-base", + "rten-simd", + "rten-tensor", +] + +[[package]] +name = "rten-model-file" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2f8d270f07ab1bbfff47250c6039f6caa5da59d6da7d74f66aa48559aa6fea" +dependencies = [ + "flatbuffers", + "rten-base", +] + +[[package]] +name = "rten-onnx" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23086eef75bfb55278cb0b45cf9f5a877d466d914914aafebee4ffca9b24d20c" + +[[package]] +name = "rten-shape-inference" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8a913c7ca40e2bfbb2a0cd447cce56b33ab19435f56693271a2ef37cf58984" +dependencies = [ + "rten-tensor", + "smallvec", +] + +[[package]] +name = "rten-simd" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b19a0032dfcb70dd20960c1c51a37674b237586cbc1ce586f45b46605d108e82" + +[[package]] +name = "rten-tensor" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05dc744a270aa32d154f1a3df8e48740ccc1be9dfbcf23295ada66d83aa98de6" +dependencies = [ + "rayon", + "rten-base", + "smallvec", + "typeid", +] + +[[package]] +name = "rten-vecmath" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9574ddebf5671bc08ceb76e2e1638fadc57fdeff318634eab2c29e9a803cff64" +dependencies = [ + "rten-base", + "rten-simd", +] + [[package]] name = "rtrb" version = "0.3.4" @@ -4880,7 +5434,7 @@ version = "8.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1" dependencies = [ - "sha2", + "sha2 0.10.9", "walkdir", ] @@ -5156,6 +5710,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" @@ -5169,6 +5735,17 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + [[package]] name = "sha2" version = "0.10.9" @@ -5177,7 +5754,18 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -5379,7 +5967,7 @@ dependencies = [ "rand 0.9.4", "rustix 1.1.4", "scopeguard", - "sha2", + "sha2 0.10.9", "smallvec", "tempfile", "thiserror 2.0.18", @@ -5503,6 +6091,16 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -5557,6 +6155,22 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "super-swipe-type" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2514d173cdebfac064577dd38387f71244daa7aa4d769076a5806fd4f7cf73e" +dependencies = [ + "anyhow", + "cached-path", + "fst", + "memmap", + "regex", + "regex-automata 0.1.10", + "rten", + "vector2", +] + [[package]] name = "svgtypes" version = "0.16.1" @@ -5649,6 +6263,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -5715,6 +6338,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "target-lexicon" version = "0.13.5" @@ -5792,6 +6426,20 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + [[package]] name = "time" version = "0.3.53" @@ -5799,6 +6447,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" dependencies = [ "deranged", + "js-sys", "num-conv", "powerfmt", "serde_core", @@ -5913,7 +6562,22 @@ version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ + "bytes", + "libc", + "mio", "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", ] [[package]] @@ -5967,6 +6631,51 @@ version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.13.0", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.44" @@ -6020,7 +6729,7 @@ dependencies = [ "matchers", "nu-ansi-term", "once_cell", - "regex-automata", + "regex-automata 0.4.14", "sharded-slab", "smallvec", "thread_local", @@ -6029,6 +6738,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "ttf-parser" version = "0.25.1" @@ -6044,6 +6759,12 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + [[package]] name = "typeid" version = "1.0.3" @@ -6263,6 +6984,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "vector2" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f562f8d4f41867568374e1a63bd12914795f558319f9bf9b0582c4d96a8de04" + [[package]] name = "version-compare" version = "0.2.1" @@ -6349,6 +7076,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -6581,6 +7317,7 @@ name = "wayvr" version = "26.7.1" dependencies = [ "anyhow", + "arboard", "ash", "bytes", "calloop 0.14.4", @@ -6626,6 +7363,7 @@ dependencies = [ "smithay-clipboard", "smol", "strum", + "super-swipe-type", "sysinfo", "thiserror 2.0.18", "tracing", @@ -7161,7 +7899,7 @@ dependencies = [ "memmap2", "ndk", "objc2 0.5.2", - "objc2-app-kit", + "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", "objc2-ui-kit", "orbclient", @@ -7294,6 +8032,16 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix 1.1.4", +] + [[package]] name = "xcb" version = "1.7.0" @@ -7514,12 +8262,85 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "zip" +version = "8.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" +dependencies = [ + "aes", + "bzip2", + "constant_time_eq", + "crc32fast", + "deflate64", + "flate2", + "getrandom 0.4.3", + "hmac", + "indexmap 2.14.0", + "lzma-rust2", + "memchr", + "pbkdf2", + "ppmd-rust", + "sha1", + "time", + "typed-path", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zlib-rs" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5" + [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "zune-core" version = "0.5.1" diff --git a/wayvr/Cargo.toml b/wayvr/Cargo.toml index 8f608ce4..75d1305d 100644 --- a/wayvr/Cargo.toml +++ b/wayvr/Cargo.toml @@ -127,6 +127,8 @@ xkbcommon = { calloop = { version = "0.14.4", optional = true } calloop-wayland-source = { version = "0.4.1", optional = true } evdev = "0.13.2" +super-swipe-type = "0.3.2" +arboard = "3.6.1" [build-dependencies] regex.workspace = true diff --git a/wayvr/src/backend/wayvr/client.rs b/wayvr/src/backend/wayvr/client.rs index d8118bb0..1225f0c5 100644 --- a/wayvr/src/backend/wayvr/client.rs +++ b/wayvr/src/backend/wayvr/client.rs @@ -18,7 +18,6 @@ use smithay::{ reexports::wayland_server::{self, protocol::wl_surface::WlSurface}, utils::{Logical, Point, SerialCounter}, }; -use smithay::input::Seat; use smithay::wayland::selection::data_device::set_data_device_selection; use wgui::log::LogErr; use xkbcommon::xkb; @@ -40,7 +39,6 @@ pub struct WayVRCompositor { pub state: comp::Application, pub seat_keyboard: KeyboardHandle, pub seat_pointer: PointerHandle, - pub seat: Seat, pub serial_counter: SerialCounter, pub wayland_env: super::WaylandEnv, @@ -98,7 +96,6 @@ impl WayVRCompositor { display: wayland_server::Display, seat_keyboard: KeyboardHandle, seat_pointer: PointerHandle, - seat: Seat, ) -> anyhow::Result { let (wayland_env, listener) = create_wayland_listener()?; @@ -122,7 +119,6 @@ impl WayVRCompositor { serial_counter: SerialCounter::new(), clients: Vec::new(), toplevel_surf_count: 0, - seat, scroll_accumulator: [0.0, 0.0], }) } @@ -267,7 +263,7 @@ impl WayVRCompositor { pub fn set_clipboard_text(&mut self, text: String) { set_data_device_selection::( &self.state.display_handle, - &self.seat, + &self.state.seat, vec!["text/plain;charset=utf-8".to_string(), "text/plain".to_string()], text.as_bytes().into(), ); diff --git a/wayvr/src/main.rs b/wayvr/src/main.rs index 7bf577d1..67187fc1 100644 --- a/wayvr/src/main.rs +++ b/wayvr/src/main.rs @@ -314,7 +314,6 @@ fn logging_init(args: &mut Args) { .from_env_lossy() .add_directive("symphonia_core::probe=warn".parse().unwrap()) .add_directive("symphonia_bundle_mp3=warn".parse().unwrap()) - .add_directive("ort=warn".parse().unwrap()) .add_directive("zbus=warn".parse().unwrap()) .add_directive("usvg=error".parse().unwrap()) .add_directive("resvg=error".parse().unwrap()) diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index 407a3a17..50cb792b 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -1,5 +1,5 @@ use std::{rc::Rc, time::Duration}; - +use std::collections::HashMap; use crate::{ app_misc, gui::{ @@ -13,6 +13,7 @@ use crate::{ }; use anyhow::{bail, Context}; use glam::{FloatExt, Mat4, Vec2, vec2, vec3}; +use slotmap::Key; use smallvec::{SmallVec, smallvec}; use wgui::{ animation::{Animation, AnimationEasing}, @@ -30,11 +31,10 @@ use wgui::{ sprite::WidgetSprite, }, }; - -use super::{ - KeyButtonData, KeyState, KeyboardState, handle_press, handle_release, - layout::{self, KeyCapType}, -}; +use wgui::event::StyleSetRequest; +use wgui::layout::LayoutTask; +use wgui::taffy::Display; +use super::{KeyButtonData, KeyState, KeyboardState, handle_press, handle_release, layout::{self, KeyCapType}, handle_mouse_motion, init_swipe_type_manager}; const PIXELS_PER_UNIT: f32 = 60.; @@ -52,10 +52,7 @@ pub(super) fn update_swipe_prediction_bar( ) -> anyhow::Result { let mut elements_changed = false; - let (accent_color, anim_mult) = { - let theme = &app.wgui_theme; - (theme.accent_color, theme.animation_mult) - }; + let anim_mult = app.wgui_theme.animation_mult; if let Some(recv) = panel.state.swipe_candidate_receiver.as_mut() && let Ok(candidates) = recv.try_recv() { @@ -93,7 +90,7 @@ pub(super) fn update_swipe_prediction_bar( "KeyPrediction", &mut panel.layout, predictions_root, - params + TemplateParams::from_hashmap(params) )?; if let Ok(widget_id) = panel.parser_state.get_widget_id(&id) { @@ -117,6 +114,8 @@ pub(super) fn update_swipe_prediction_bar( cur_border_color: rect.params.border_color.into(), border: rect.params.border, drawn_state: false.into(), + labels: Default::default(), + sprites: Default::default(), }) }; panel.add_event_listener( @@ -144,7 +143,6 @@ pub(super) fn update_swipe_prediction_bar( k.clone(), common, data, - accent_color, anim_mult, 0.0, ); @@ -162,7 +160,6 @@ pub(super) fn update_swipe_prediction_bar( k.clone(), common, data, - accent_color, anim_mult, 0.0, ); @@ -256,7 +253,7 @@ pub(super) fn create_keyboard_panel( params.insert_rc("width", key_width.to_string().into()); params.insert_rc("height", key_height.to_string().into()); - let mut label = key.label.into_iter(); + let mut label = key.label.clone().into_iter(); label .next() .and_then(|s| params.insert_rc("text", s.into())); diff --git a/wayvr/src/overlays/keyboard/layout.rs b/wayvr/src/overlays/keyboard/layout.rs index 958aadb1..93620acf 100644 --- a/wayvr/src/overlays/keyboard/layout.rs +++ b/wayvr/src/overlays/keyboard/layout.rs @@ -2,6 +2,7 @@ use std::{collections::HashMap, str::FromStr, sync::LazyLock}; use regex::Regex; use serde::{Deserialize, Serialize}; + use crate::{ config::{ConfigType, load_known_yaml}, subsystem::hid::{ diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index 1be073ea..88aabb09 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -30,14 +30,13 @@ use anyhow::Context; use glam::{Affine3A, Quat, Vec3, vec3, Vec2}; use regex::Regex; use slotmap::{SlotMap, new_key_type, Key}; -use slotmap::{SlotMap, new_key_type}; use smallvec::SmallVec; use wgui::{ color::WguiColor, event::{InternalStateChangeEvent, MouseButtonEvent, MouseButtonIndex}, layout::WidgetID, }; -use wgui::event::StyleSetRequest; +use wgui::event::{DeviceBitmask, StyleSetRequest}; use wgui::layout::{LayoutTask}; use wgui::parser::Fetchable; use wgui::taffy::Display; @@ -124,7 +123,7 @@ pub fn create_keyboard(app: &mut AppState, wayland: bool) -> anyhow::Result anyhow::Result { if !self.wlx_layout.auto_labels.unwrap_or(true) { return Ok(false); @@ -470,7 +468,7 @@ fn handle_mouse_motion( key_cap_type: &KeyCapType, keyboard: &mut KeyboardState, within_key_pos: &Option, - device: usize + device: DeviceBitmask ) { if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() && matches!(*key_cap_type, KeyCapType::Letter | KeyCapType::LetterAltGr) @@ -502,7 +500,7 @@ fn handle_press( within_key_pos: &Option, keyboard: &mut KeyboardState, button: MouseButtonEvent, - device: usize + device: DeviceBitmask ) { match &key.button_state { KeyButtonData::Key { vk, pressed } => { diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs index 428303dc..b4b24757 100644 --- a/wayvr/src/overlays/keyboard/swipe_type.rs +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -10,7 +10,8 @@ use std::time::Instant; use super_swipe_type::keyboard_manager::QwertyKeyboardGrid; use super_swipe_type::swipe_orchestrator::SwipeOrchestrator; use super_swipe_type::{SwipePoint}; -use crate::subsystem::input::KeyboardFocus; +use wgui::event::DeviceBitmask; +use crate::subsystem::input::InputFocus; const PREDICTION_SUGGESTION_COUNT: usize = 5; @@ -32,7 +33,7 @@ pub struct SwipeTypingManager { clipboard: Clipboard, swipe_left_first_key: bool, first_swipe_char: char, - current_swipe_device: Option, + current_swipe_device: Option, last_swiped_word: Option, } @@ -46,13 +47,13 @@ impl SwipeTypingManager { self.last_swiped_word = Some(word.clone()); let text_to_paste = format!("{word} "); - match app.hid_provider.keyboard_focus { - KeyboardFocus::PhysicalScreen => { + match app.hid_provider.get_input_focus() { + InputFocus::PhysicalScreen => { if let Ok(_) = self.clipboard.set_text(text_to_paste) { Self::paste(app, original_keyboard_mods); } }, - KeyboardFocus::WayVR => { + InputFocus::WayVR => { if let Some(wvr_server) = app.wvr_server.as_mut() { wvr_server.set_clipboard_text(text_to_paste); Self::paste(app, original_keyboard_mods); @@ -171,7 +172,7 @@ impl SwipeTypingManager { self.current_swipe_device = None; } - fn start_swipe(&mut self, key_label: char, device: usize) -> Instant { + fn start_swipe(&mut self, key_label: char, device: DeviceBitmask) -> Instant { let now = Instant::now(); self.swipe_start_time = Some(now); self.first_swipe_char = key_label.to_ascii_lowercase(); @@ -187,7 +188,7 @@ impl SwipeTypingManager { self.current_swipe.is_empty() } - pub fn add_swipe(&mut self, within_key_pos_normalized: &Vec2, key_label: char, device: usize) { + pub fn add_swipe(&mut self, within_key_pos_normalized: &Vec2, key_label: char, device: DeviceBitmask) { if let Some(pos) = self.keyboard_gird.key_positions.get(&key_label.to_ascii_lowercase()) { if let Some(current_device) = self.current_swipe_device { if current_device != device { diff --git a/wlx-common/src/config.rs b/wlx-common/src/config.rs index 8951fe1c..0bb6726b 100644 --- a/wlx-common/src/config.rs +++ b/wlx-common/src/config.rs @@ -490,7 +490,7 @@ pub struct GeneralConfig { #[serde(default = "def_one")] pub grid_opacity: f32, - #[serde(default = "def_false")] + #[serde(default)] pub keyboard_swipe_to_type_enabled: bool, #[serde(default)] From b62a4fde2887ece011a0f5d993f57beaf2d7cd47 Mon Sep 17 00:00:00 2001 From: oneshinyboi Date: Mon, 27 Jul 2026 00:04:27 -0500 Subject: [PATCH 23/35] preserve pointer mods while swiping --- wayvr/src/overlays/keyboard/mod.rs | 9 +++++++-- wayvr/src/overlays/keyboard/swipe_type.rs | 16 ++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index 88aabb09..eb3e7b9a 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -481,7 +481,7 @@ fn handle_mouse_motion( if pos.x >= 0.0 && pos.x <= 1.0 && pos.y >= 0.0 && pos.y <= 1.0 { if let Some(label) = key_label.first() { - swipe_manager.add_swipe(pos, label.chars().next().unwrap_or_default(), device); + swipe_manager.add_swipe(pos, label.chars().next().unwrap_or_default(), device, None); } } } @@ -509,7 +509,7 @@ fn handle_press( { if let Some(pos) = within_key_pos { if let Some(label) = key_label.first() { - swipe_manager.add_swipe(pos, label.chars().next().unwrap_or_default(), device); + swipe_manager.add_swipe(pos, label.chars().next().unwrap_or_default(), device, Some(button.index)); } } } @@ -570,6 +570,11 @@ fn handle_release(app: &mut AppState, key: &KeyState, k_cap_type: &KeyCapType, k } } else { // pointer must have been released on the same key it was pressed on + keyboard.modifiers |= match swipe_manager.current_swipe_mouse_button_index() { + Some(MouseButtonIndex::Right) => SHIFT, + Some(MouseButtonIndex::Middle) => keyboard.alt_modifier, + _ => 0, + }; swipe_manager.reset(); // drop swipe tracking that was started on press app.hid_provider diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs index b4b24757..aab67af3 100644 --- a/wayvr/src/overlays/keyboard/swipe_type.rs +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -10,7 +10,7 @@ use std::time::Instant; use super_swipe_type::keyboard_manager::QwertyKeyboardGrid; use super_swipe_type::swipe_orchestrator::SwipeOrchestrator; use super_swipe_type::{SwipePoint}; -use wgui::event::DeviceBitmask; +use wgui::event::{DeviceBitmask, MouseButtonIndex}; use crate::subsystem::input::InputFocus; const PREDICTION_SUGGESTION_COUNT: usize = 5; @@ -34,6 +34,7 @@ pub struct SwipeTypingManager { swipe_left_first_key: bool, first_swipe_char: char, current_swipe_device: Option, + current_swipe_mouse_button_index: Option, last_swiped_word: Option, } @@ -134,6 +135,7 @@ impl SwipeTypingManager { swipe_left_first_key: false, first_swipe_char: char::default(), current_swipe_device: None, + current_swipe_mouse_button_index: None, last_swiped_word: None, }, candidate_receiver, @@ -170,13 +172,15 @@ impl SwipeTypingManager { self.first_swipe_char = char::default(); self.swipe_left_first_key = false; self.current_swipe_device = None; + self.current_swipe_mouse_button_index = None; } - fn start_swipe(&mut self, key_label: char, device: DeviceBitmask) -> Instant { + fn start_swipe(&mut self, key_label: char, device: DeviceBitmask, index: Option) -> Instant { let now = Instant::now(); self.swipe_start_time = Some(now); self.first_swipe_char = key_label.to_ascii_lowercase(); self.current_swipe_device = Some(device); + self.current_swipe_mouse_button_index = index; now } @@ -187,8 +191,12 @@ impl SwipeTypingManager { pub fn is_current_swipe_empty(&self) -> bool { self.current_swipe.is_empty() } + + pub fn current_swipe_mouse_button_index(&self) -> Option { + self.current_swipe_mouse_button_index + } - pub fn add_swipe(&mut self, within_key_pos_normalized: &Vec2, key_label: char, device: DeviceBitmask) { + pub fn add_swipe(&mut self, within_key_pos_normalized: &Vec2, key_label: char, device: DeviceBitmask, index: Option) { if let Some(pos) = self.keyboard_gird.key_positions.get(&key_label.to_ascii_lowercase()) { if let Some(current_device) = self.current_swipe_device { if current_device != device { @@ -209,7 +217,7 @@ impl SwipeTypingManager { let start_time = match self.swipe_start_time { Some(time) => time, - None => self.start_swipe(key_label, device), + None => self.start_swipe(key_label, device, index), }; let within_key_pos_from_center = Vec2 { From 18d3dc5e9363b153a4f38014247334c765e0b216 Mon Sep 17 00:00:00 2001 From: oneshinyboi Date: Mon, 27 Jul 2026 23:54:26 -0500 Subject: [PATCH 24/35] use built in clipboard, add to settings menu, start gating swipe to type behind feature --- Cargo.lock | 97 +------------------ .../src/tab/settings/tab_features.rs | 2 + wayvr/Cargo.toml | 4 +- wayvr/src/overlays/keyboard/mod.rs | 14 ++- wayvr/src/overlays/keyboard/swipe_type.rs | 32 +++++- wayvr/src/subsystem/clipboard/mod.rs | 1 + wayvr/src/subsystem/mod.rs | 2 +- 7 files changed, 47 insertions(+), 105 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 115c527b..840c5c44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -252,26 +252,6 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" -[[package]] -name = "arboard" -version = "3.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" -dependencies = [ - "clipboard-win", - "image", - "log", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation 0.3.2", - "parking_lot", - "percent-encoding", - "windows-sys 0.59.0", - "x11rb", -] - [[package]] name = "arg_enum_proc_macro" version = "0.3.4" @@ -1080,15 +1060,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" -[[package]] -name = "clipboard-win" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" -dependencies = [ - "error-code", -] - [[package]] name = "cmake" version = "0.1.58" @@ -1936,12 +1907,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "error-code" -version = "3.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" - [[package]] name = "etagere" version = "0.3.0" @@ -2014,12 +1979,6 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" -[[package]] -name = "fax" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" - [[package]] name = "fdeflate" version = "0.3.7" @@ -2845,7 +2804,6 @@ dependencies = [ "png", "ravif", "rayon", - "tiff", "zune-core", "zune-jpeg", ] @@ -3840,18 +3798,6 @@ dependencies = [ "objc2-quartz-core 0.2.2", ] -[[package]] -name = "objc2-app-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" -dependencies = [ - "bitflags 2.13.0", - "objc2 0.6.4", - "objc2-core-graphics", - "objc2-foundation 0.3.2", -] - [[package]] name = "objc2-audio-toolbox" version = "0.3.2" @@ -3949,19 +3895,6 @@ dependencies = [ "objc2 0.6.4", ] -[[package]] -name = "objc2-core-graphics" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" -dependencies = [ - "bitflags 2.13.0", - "dispatch2", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-io-surface", -] - [[package]] name = "objc2-core-image" version = "0.2.2" @@ -4028,17 +3961,6 @@ dependencies = [ "objc2-core-foundation", ] -[[package]] -name = "objc2-io-surface" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" -dependencies = [ - "bitflags 2.13.0", - "objc2 0.6.4", - "objc2-core-foundation", -] - [[package]] name = "objc2-link-presentation" version = "0.2.2" @@ -4047,7 +3969,7 @@ checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ "block2 0.5.1", "objc2 0.5.2", - "objc2-app-kit 0.2.2", + "objc2-app-kit", "objc2-foundation 0.2.2", ] @@ -6426,20 +6348,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "tiff" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" -dependencies = [ - "fax", - "flate2", - "half", - "quick-error", - "weezl", - "zune-jpeg", -] - [[package]] name = "time" version = "0.3.53" @@ -7317,7 +7225,6 @@ name = "wayvr" version = "26.7.1" dependencies = [ "anyhow", - "arboard", "ash", "bytes", "calloop 0.14.4", @@ -7899,7 +7806,7 @@ dependencies = [ "memmap2", "ndk", "objc2 0.5.2", - "objc2-app-kit 0.2.2", + "objc2-app-kit", "objc2-foundation 0.2.2", "objc2-ui-kit", "orbclient", diff --git a/dash-frontend/src/tab/settings/tab_features.rs b/dash-frontend/src/tab/settings/tab_features.rs index 75c8db7f..a73dafbb 100644 --- a/dash-frontend/src/tab/settings/tab_features.rs +++ b/dash-frontend/src/tab/settings/tab_features.rs @@ -152,6 +152,8 @@ impl State { options_checkbox(par.mp, c, SettingType::NotificationsEnabled)?; options_checkbox(par.mp, c, SettingType::NotificationsSoundEnabled)?; options_checkbox(par.mp, c, SettingType::KeyboardSoundEnabled)?; + options_checkbox(par.mp, c, SettingType::KeyboardSwipeToTypeEnabled)?; + if !par.feats.openxr || par.feats.monado { // monado or openvr options_checkbox(par.mp, c, SettingType::BlockGameInput)?; diff --git a/wayvr/Cargo.toml b/wayvr/Cargo.toml index 75d1305d..0d5e93b1 100644 --- a/wayvr/Cargo.toml +++ b/wayvr/Cargo.toml @@ -127,8 +127,7 @@ xkbcommon = { calloop = { version = "0.14.4", optional = true } calloop-wayland-source = { version = "0.4.1", optional = true } evdev = "0.13.2" -super-swipe-type = "0.3.2" -arboard = "3.6.1" +super-swipe-type = { version = "0.3.2", optional = true } [build-dependencies] regex.workspace = true @@ -149,5 +148,6 @@ whisper = [ "dep:pipewire", "dep:whisper-rs" ] +swipe-to-type = ["dep:super-swipe-type"] x11 = ["dep:xcb", "wlx-capture/xshm", "xkbcommon/x11"] xcb = ["dep:xcb"] diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index eb3e7b9a..1ca64d6e 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -51,6 +51,8 @@ use crate::overlays::keyboard::swipe_type::SwipeTypingManager; pub mod builder; mod layout; + +#[cfg(feature = "swipe-to-type ")] mod swipe_type; pub const KEYBOARD_NAME: &str = "kbd"; @@ -123,7 +125,7 @@ pub fn create_keyboard(app: &mut AppState, wayland: bool) -> anyhow::Result, @@ -30,7 +31,7 @@ pub struct SwipeTypingManager { prediction_task_sender: Sender, worker_thread: Option>, swipe_start_time: Option, - clipboard: Clipboard, + clipboard: Option>, swipe_left_first_key: bool, first_swipe_char: char, current_swipe_device: Option, @@ -50,7 +51,8 @@ impl SwipeTypingManager { match app.hid_provider.get_input_focus() { InputFocus::PhysicalScreen => { - if let Ok(_) = self.clipboard.set_text(text_to_paste) { + if let Some(clipboard) = self.clipboard.as_mut() { + clipboard.set_clipboard_utf8(&*text_to_paste); Self::paste(app, original_keyboard_mods); } }, @@ -122,7 +124,27 @@ impl SwipeTypingManager { } } }); + let clipboard_provider: Option> = { + #[cfg(feature = "wayland")] + { + clipboard::wl::Provider::new() + .log_err("Could not create Wayland clipboard provider") + .ok() + .map(|p| Box::new(p) as Box); + } + #[cfg(feature = "x11")] + { + clipboard::x11::Provider::new() + .log_err("Could not create X11 clipboard provider") + .ok() + .map(|p| Box::new(p) as Box) + } + #[cfg(not(any(feature = "wayland", feature = "x11")))] + { + None + } + }; Ok(( Self { keyboard_gird: QwertyKeyboardGrid::new(), @@ -131,7 +153,7 @@ impl SwipeTypingManager { prediction_task_sender: task_sender, worker_thread: Some(worker_thread), swipe_start_time: None, - clipboard: Clipboard::new()?, + clipboard: clipboard_provider, swipe_left_first_key: false, first_swipe_char: char::default(), current_swipe_device: None, diff --git a/wayvr/src/subsystem/clipboard/mod.rs b/wayvr/src/subsystem/clipboard/mod.rs index a19b9e22..05d31ed1 100644 --- a/wayvr/src/subsystem/clipboard/mod.rs +++ b/wayvr/src/subsystem/clipboard/mod.rs @@ -6,3 +6,4 @@ pub mod x11; pub trait ClipboardProvider { fn set_clipboard_utf8(&mut self, content: &str); } + diff --git a/wayvr/src/subsystem/mod.rs b/wayvr/src/subsystem/mod.rs index 5909c060..8c214093 100644 --- a/wayvr/src/subsystem/mod.rs +++ b/wayvr/src/subsystem/mod.rs @@ -3,7 +3,7 @@ pub mod hid; pub mod input; pub mod notifications; -#[cfg(feature = "whisper")] +#[cfg(any(feature = "whisper", feature = "swipe-to-type"))] pub mod clipboard; #[cfg(feature = "whisper")] From 404b5ea6591df1fc8501001ad68ae73e66c329e3 Mon Sep 17 00:00:00 2001 From: Diamond White Date: Fri, 14 Aug 2026 15:56:04 -0500 Subject: [PATCH 25/35] start dashboard swipe type download button --- dash-frontend/assets/lang/en.json | 9 + dash-frontend/src/tab/settings/mod.rs | 32 +++- .../src/tab/settings/tab_features.rs | 158 +++++++++++++++++- dash-frontend/src/util/mod.rs | 1 + dash-frontend/src/util/swipe_type.rs | 70 ++++++++ wayvr/src/overlays/dashboard.rs | 4 + wlx-common/src/dash_interface.rs | 1 + wlx-common/src/dash_interface_emulated.rs | 1 + 8 files changed, 264 insertions(+), 12 deletions(-) create mode 100644 dash-frontend/src/util/swipe_type.rs diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index 0629f776..d193e624 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -226,6 +226,15 @@ }, "WHISPER_MODEL": "Speech-to-text model", "WHISPER_MODEL_HELP": "Whisper model to use.\nLarger models produce more accurate\nresults, but consume more VRAM.", + "SWIPE_TYPE": { + "NEED_TO_DOWNLOAD_MODELS": "The swipe-to-type model needs to be downloaded.\nDownload them now?", + "MODELS_DOWNLOADED": "Model downloaded", + "MODELS_NOT_DOWNLOADED": "Model not downloaded", + "REMOVE_MODELS": "Remove swipe to type model", + "DOWNLOAD_MODELS": "Download swipe to type model ({} total)" + }, + "SWIPE_TYPE_MODELS": "Swipe-to-type models", + "SWIPE_TYPE_MODELS_HELP": "Model files used by swipe-to-type.\nAll of them are required.", "GRID_OPACITY": "Floor grid opacity", "GRID_OPACITY_HELP": "Opacity of the floor grid when the skybox is enabled", "XWAYLAND_BY_DEFAULT": "Run apps in Compatibility mode by default", diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs index bff41c0c..04e8f39c 100644 --- a/dash-frontend/src/tab/settings/mod.rs +++ b/dash-frontend/src/tab/settings/mod.rs @@ -83,6 +83,7 @@ pub(crate) enum Task { SavePlayspaceCenter, SetTab(TabNameEnum), SettingUpdated(SettingType), + SwipeTypeAction(Rc), UpdateBool(SettingType, bool), UpdateFloat(SettingType, f32), UpdateInt(SettingType, i32), @@ -110,6 +111,17 @@ trait SettingsTab { Ok(()) } + fn push_task_string( + &mut self, + _action: &str, + _config: &mut GeneralConfig, + _change_kind: &mut Option, + _layout: &mut Layout, + _state: &mut ParserState, + ) -> anyhow::Result<()> { + Ok(()) + } + fn context_menu_custom( &mut self, _action: Rc, @@ -254,6 +266,22 @@ impl Tab for TabSettings { _ => { /* do nothing */ } } } + Task::SwipeTypeAction(action) => { + if let Some(tab) = self.current_tab.as_mut() { + let config = frontend.interface.general_config(data); + let mut change_kind: Option = None; + tab.push_task_string( + &action, + config, + &mut change_kind, + &mut frontend.layout, + &mut self.state, + )?; + if let Some(change_kind) = change_kind { + frontend.interface.config_changed(data, change_kind); + } + } + } } } @@ -325,7 +353,6 @@ pub(crate) enum SettingType { InvertScrollDirectionY, KeyboardMiddleClick, KeyboardSoundEnabled, - KeyboardSwipeToTypeEnabled, Language, LeftHandedMouse, LongPressDuration, @@ -400,7 +427,6 @@ impl SettingType { Self::UsePassthrough => &mut config.use_passthrough, Self::UseSkybox => &mut config.use_skybox, Self::XwaylandByDefault => &mut config.xwayland_by_default, - Self::KeyboardSwipeToTypeEnabled => &mut config.keyboard_swipe_to_type_enabled, _ => panic!("Requested bool for non-bool SettingType"), } } @@ -524,7 +550,6 @@ impl SettingType { Self::InvertScrollDirectionY => Ok("APP_SETTINGS.INVERT_SCROLL_DIRECTION_Y"), Self::KeyboardMiddleClick => Ok("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK"), Self::KeyboardSoundEnabled => Ok("APP_SETTINGS.KEYBOARD_SOUND_ENABLED"), - Self::KeyboardSwipeToTypeEnabled => Ok("APP_SETTINGS.KEYBOARD_SWIPE_TO_TYPE_ENABLED"), Self::Language => Ok("APP_SETTINGS.LANGUAGE"), Self::LeftHandedMouse => Ok("APP_SETTINGS.LEFT_HANDED_MOUSE"), Self::LongPressDuration => Ok("APP_SETTINGS.LONG_PRESS_DURATION"), @@ -576,7 +601,6 @@ impl SettingType { Self::InputCaptureMethod => Some("APP_SETTINGS.INPUT_CAPTURE_METHOD_HELP"), Self::InputEmulationMethod => Some("APP_SETTINGS.INPUT_EMULATION_METHOD_HELP"), Self::KeyboardMiddleClick => Some("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK_HELP"), - Self::KeyboardSwipeToTypeEnabled => Some("APP_SETTINGS.KEYBOARD_SWIPE_TO_TYPE_ENABLED_HELP"), Self::LeftHandedMouse => Some("APP_SETTINGS.LEFT_HANDED_MOUSE_HELP"), Self::MouseAcceleration => Some("APP_SETTINGS.POINTER_ACCELERATION_HELP"), Self::ScreenRenderDown => Some("APP_SETTINGS.SCREEN_RENDER_DOWN_HELP"), diff --git a/dash-frontend/src/tab/settings/tab_features.rs b/dash-frontend/src/tab/settings/tab_features.rs index a73dafbb..e6996678 100644 --- a/dash-frontend/src/tab/settings/tab_features.rs +++ b/dash-frontend/src/tab/settings/tab_features.rs @@ -21,6 +21,10 @@ use crate::{ }, util::{ popup_manager::PopupHolder, + swipe_type::{ + SWIPE_TYPE_MODELS, SwipeTypeModel, swipe_type_all_models_downloaded, + swipe_type_delete_all_models, swipe_type_model_path, + }, whisper::{ WHISPER_MODELS, WhisperModel, whisper_any_models_downloaded, whisper_delete_all_models, whisper_model_from_name, whisper_model_path, @@ -35,6 +39,10 @@ enum Task { WhisperRemoveUnused, WhisperDownload(&'static WhisperModel), WhisperDownloadDone, + SwipeTypeDownloadClosed, + SwipeTypeRemoveAll, + SwipeTypeDownloadAll, + SwipeTypeDownloadDone, CloseDialog, ReloadTab, } @@ -46,7 +54,8 @@ pub struct State { globals: WguiGlobals, tasks: Tasks, parent_tasks: Tasks, - pending_download: Option<&'static WhisperModel>, + pending_whisper_download: Option<&'static WhisperModel>, + pending_swipe_download: Option<&'static [SwipeTypeModel]>, } impl SettingsTab for State { @@ -57,7 +66,7 @@ impl SettingsTab for State { for task in self.tasks.drain() { match task { Task::WhisperDownloadClosed => { - if let Some(model) = self.pending_download.take() { + if let Some(model) = self.pending_whisper_download.take() { if !whisper_model_path(model.file_name).exists() { // download failed, set to selection to none par.general_config.whisper_model = "".into(); @@ -71,11 +80,11 @@ impl SettingsTab for State { let _ = whisper_delete_all_models().log_err("could not remove whisper models"); } Task::WhisperDownload(model) => { - self.pending_download = Some(model); + self.pending_whisper_download = Some(model); self.show_whisper_download_dialog(model, par.executor.clone()); } Task::WhisperDownloadDone => { - if let Some(model) = self.pending_download.take() { + if let Some(model) = self.pending_whisper_download.take() { par.general_config.whisper_model = model.file_name.into(); par.config_change_kind.replace(ConfigChangeKind::Other); @@ -83,6 +92,28 @@ impl SettingsTab for State { self.parent_tasks.push(ParentTask::SetTab(TabNameEnum::Features)); } } + Task::SwipeTypeDownloadClosed => { + self.pending_swipe_download = None; + if !swipe_type_all_models_downloaded().unwrap_or_default() { + par.general_config.swipe_type_models_downloaded = false; + par.config_change_kind.replace(ConfigChangeKind::Other); + self.parent_tasks.push(ParentTask::SetTab(TabNameEnum::Features)); + } + } + Task::SwipeTypeRemoveAll => { + let _ = swipe_type_delete_all_models().log_err("could not remove swipe type models"); + } + Task::SwipeTypeDownloadAll => { + self.pending_swipe_download = Some(SWIPE_TYPE_MODELS); + self.show_swipe_type_download_dialog(SWIPE_TYPE_MODELS, par.executor.clone()); + } + Task::SwipeTypeDownloadDone => { + if let Some(_) = self.pending_swipe_download.take() { + par.general_config.swipe_type_models_downloaded = true; + par.config_change_kind.replace(ConfigChangeKind::Other); + self.parent_tasks.push(ParentTask::SetTab(TabNameEnum::Features)); + } + } Task::CloseDialog => { let close_dialog = self.popup_dialog.get_close_callback(par.layout); close_dialog(); @@ -135,6 +166,27 @@ impl SettingsTab for State { Ok(()) } + + fn push_task_string( + &mut self, + action: &str, + _config: &mut GeneralConfig, + _change_kind: &mut Option, + _layout: &mut wgui::layout::Layout, + _state: &mut wgui::parser::ParserState, + ) -> anyhow::Result<()> { + match action { + "swipe_type_download" => { + self.show_swipe_type_model_dialog_box_download()?; + } + "swipe_type_remove" => { + self.tasks.push(Task::SwipeTypeRemoveAll); + self.tasks.push(Task::ReloadTab); + } + _ => {} + } + Ok(()) + } } impl State { @@ -149,11 +201,14 @@ impl State { whisper_models_dropdown(par.mp, c)?; } + if par.feats.swipe_to_type { + swipe_type_models_button(par.mp, c)?; + } + options_checkbox(par.mp, c, SettingType::NotificationsEnabled)?; options_checkbox(par.mp, c, SettingType::NotificationsSoundEnabled)?; options_checkbox(par.mp, c, SettingType::KeyboardSoundEnabled)?; - options_checkbox(par.mp, c, SettingType::KeyboardSwipeToTypeEnabled)?; - + if !par.feats.openxr || par.feats.monado { // monado or openvr options_checkbox(par.mp, c, SettingType::BlockGameInput)?; @@ -180,7 +235,8 @@ impl State { popup_dialog, frontend_tasks: par.frontend_tasks.clone(), globals: par.mp.doc_params.globals.clone(), - pending_download: None, + pending_whisper_download: None, + pending_swipe_download: None, }) } @@ -199,11 +255,16 @@ impl State { ); } + fn show_swipe_type_download_dialog(&mut self, models: &[SwipeTypeModel], executor: AsyncExecutor) { + //TODO: create views::download_files and use that + unimplemented!() + } + fn show_whisper_model_dialog_box_download(&mut self, model: &'static WhisperModel) -> anyhow::Result<()> { const ACTION_DOWNLOAD: &str = "download"; const ACTION_CANCEL: &str = "cancel"; - self.pending_download = Some(model); + self.pending_whisper_download = Some(model); let tasks = self.tasks.clone(); views::dialog_box::mount_popup( @@ -241,6 +302,45 @@ impl State { Ok(()) } + fn show_swipe_type_model_dialog_box_download(&mut self) -> anyhow::Result<()> { + const ACTION_DOWNLOAD: &str = "download"; + const ACTION_CANCEL: &str = "cancel"; + + let tasks = self.tasks.clone(); + views::dialog_box::mount_popup( + self.popup_dialog.clone(), + self.frontend_tasks.clone(), + views::dialog_box::Params { + globals: self.globals.clone(), + message: Translation::from_translation_key("APP_SETTINGS.SWIPE_TYPE.NEED_TO_DOWNLOAD_MODELS"), + entries: vec![ + views::dialog_box::ButtonEntry { + content: Translation::from_translation_key("APP_SETTINGS.CANCEL"), + icon: "dashboard/close.svg", + action: ACTION_CANCEL, + }, + views::dialog_box::ButtonEntry { + content: Translation::from_translation_key("DOWNLOAD"), + icon: "dashboard/download.svg", + action: ACTION_DOWNLOAD, + }, + ], + on_action_click: Box::new(move |action| match action { + ACTION_DOWNLOAD => { + tasks.push(Task::SwipeTypeDownloadAll); + } + ACTION_CANCEL => { + tasks.push(Task::CloseDialog); + tasks.push(Task::SwipeTypeDownloadClosed); + } + _ => unreachable!(), + }), + }, + ); + + Ok(()) + } + fn show_whisper_model_dialog_box_cleanup(&mut self) -> anyhow::Result<()> { const ACTION_REMOVE: &str = "remove"; const ACTION_CANCEL: &str = "cancel"; @@ -357,3 +457,45 @@ fn whisper_models_dropdown(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Re Ok(()) } + +fn swipe_type_models_button(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Result<()> { + let id = mp.idx.to_string(); + mp.idx += 1; + + let id_cell = horiz_cell(mp.layout, parent)?; + + let all_downloaded = swipe_type_all_models_downloaded().unwrap_or_default(); + let (translation, icon, action) = if all_downloaded { + ( + "APP_SETTINGS.SWIPE_TYPE.REMOVE_MODELS", + "dashboard/trash.svg", + "swipe_type_remove", + ) + } else { + ( + "APP_SETTINGS.SWIPE_TYPE.DOWNLOAD_MODELS;5.0 MiB", + "dashboard/download.svg", + "swipe_type_download", + ) + }; + + let mut params = TemplateParams::new(); + params.insert("id", &id); + params.insert("translation", translation); + params.insert("icon", icon); + + mp.parser_state + .instantiate_template(mp.doc_params, "ButtonText", mp.layout, id_cell, params)?; + + let btn = mp.parser_state.fetch_component_as::(&id)?; + btn.on_click(Rc::new({ + let parent_tasks = mp.tasks.clone(); + let action = Rc::::from(action); + move |_common, _e: ButtonClickEvent| { + parent_tasks.push(ParentTask::SwipeTypeAction(action.clone())); + Ok(()) + } + })); + + Ok(()) +} \ No newline at end of file diff --git a/dash-frontend/src/util/mod.rs b/dash-frontend/src/util/mod.rs index 7eda5e07..f898e589 100644 --- a/dash-frontend/src/util/mod.rs +++ b/dash-frontend/src/util/mod.rs @@ -7,3 +7,4 @@ pub mod steam_utils; pub mod toast_manager; pub mod wgui_simple; pub mod whisper; +pub mod swipe_type; diff --git a/dash-frontend/src/util/swipe_type.rs b/dash-frontend/src/util/swipe_type.rs new file mode 100644 index 00000000..e0cfdeb4 --- /dev/null +++ b/dash-frontend/src/util/swipe_type.rs @@ -0,0 +1,70 @@ +use std::{fs, io, path::PathBuf}; + +use wlx_common::data_dir; + +/// The set of model files required by the swipe-to-type engine. +/// These correspond to the assets that `super-swipe-type` currently +/// downloads via `cached_path` in `SwipeOrchestrator::new()`. +pub struct SwipeTypeModel { + pub file_name: &'static str, + pub url: &'static str, +} + +pub const SWIPE_TYPE_MODELS: &[SwipeTypeModel] = &[ + SwipeTypeModel { + file_name: "swipe_encoder_android.onnx", + url: "https://wayvr.org/files/swipe_type/swipe_encoder_android.onnx", + }, + SwipeTypeModel { + file_name: "swipe_decoder_android.onnx", + url: "https://wayvr.org/files/swipe_type/swipe_decoder_android.onnx", + }, + SwipeTypeModel { + file_name: "en_wordlist.fst", + url: "https://wayvr.org/files/swipe_type/en_wordlist.fst", + }, + SwipeTypeModel { + file_name: "en_bigrams.fst", + url: "https://wayvr.org/files/swipe_type/en_bigrams.fst", + }, +]; + +pub fn swipe_type_model_folder() -> PathBuf { + data_dir::get_path("swipe_type") +} + +pub fn swipe_type_model_path(file_name: &str) -> PathBuf { + swipe_type_model_folder().join(file_name) +} + +/// Returns true when every required model file is present on disk. +pub fn swipe_type_all_models_downloaded() -> io::Result { + let path = swipe_type_model_folder(); + if !path.is_dir() { + return Ok(false); + } + for model in SWIPE_TYPE_MODELS { + if !path.join(model.file_name).exists() { + return Ok(false); + } + } + Ok(true) +} + +pub fn swipe_type_delete_all_models() -> io::Result<()> { + let path = swipe_type_model_folder(); + if !path.is_dir() { + return Ok(()); + } + + for entry in fs::read_dir(path)? { + let entry = entry?; + let file_type = entry.file_type()?; + + if file_type.is_file() || file_type.is_symlink() { + fs::remove_file(entry.path())?; + } + } + + Ok(()) +} \ No newline at end of file diff --git a/wayvr/src/overlays/dashboard.rs b/wayvr/src/overlays/dashboard.rs index fe972925..63c84a92 100644 --- a/wayvr/src/overlays/dashboard.rs +++ b/wayvr/src/overlays/dashboard.rs @@ -588,6 +588,10 @@ impl DashInterface for DashInterfaceLive { whisper: true, #[cfg(not(feature = "whisper"))] whisper: false, + #[cfg(feature = "swipe-to-type")] + swipe_to_type: true, + #[cfg(not(feature = "swipe-to-type"))] + swipe_to_type: false, } } diff --git a/wlx-common/src/dash_interface.rs b/wlx-common/src/dash_interface.rs index 420931a4..dd7526b6 100644 --- a/wlx-common/src/dash_interface.rs +++ b/wlx-common/src/dash_interface.rs @@ -50,6 +50,7 @@ pub struct InterfaceFeats { pub openxr: bool, pub monado: bool, pub whisper: bool, + pub swipe_to_type: bool, } pub trait DashInterface { diff --git a/wlx-common/src/dash_interface_emulated.rs b/wlx-common/src/dash_interface_emulated.rs index 9a4ff1a0..6d5f2aa7 100644 --- a/wlx-common/src/dash_interface_emulated.rs +++ b/wlx-common/src/dash_interface_emulated.rs @@ -238,6 +238,7 @@ impl DashInterface<()> for DashInterfaceEmulated { openxr: true, monado: true, whisper: true, + swipe_to_type: true, } } From ad89b44db2405a272238cd2cec95242d6a2d77d4 Mon Sep 17 00:00:00 2001 From: oneshinyboi Date: Sat, 15 Aug 2026 19:01:05 -0500 Subject: [PATCH 26/35] gate swipe type behind feature, edit dashboard --- .idea/.gitignore | 10 ++++ dash-frontend/assets/lang/en.json | 5 +- dash-frontend/src/tab/settings/mod.rs | 3 ++ .../src/tab/settings/tab_features.rs | 47 +++++++++---------- dash-frontend/src/util/downloadable_file.rs | 5 ++ dash-frontend/src/util/mod.rs | 1 + dash-frontend/src/util/swipe_type.rs | 41 ++++------------ dash-frontend/src/util/whisper.rs | 21 ++++----- wayvr/src/overlays/keyboard/builder.rs | 9 +++- wayvr/src/overlays/keyboard/mod.rs | 41 ++++++++++++++-- wayvr/src/overlays/keyboard/swipe_type.rs | 5 +- 11 files changed, 107 insertions(+), 81 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 dash-frontend/src/util/downloadable_file.rs diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..ab1f4164 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index d193e624..7f022b21 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -227,11 +227,12 @@ "WHISPER_MODEL": "Speech-to-text model", "WHISPER_MODEL_HELP": "Whisper model to use.\nLarger models produce more accurate\nresults, but consume more VRAM.", "SWIPE_TYPE": { - "NEED_TO_DOWNLOAD_MODELS": "The swipe-to-type model needs to be downloaded.\nDownload them now?", + "NEED_TO_DOWNLOAD_MODELS": "The swipe-to-type model needs to be downloaded.\nDownload it now?", "MODELS_DOWNLOADED": "Model downloaded", "MODELS_NOT_DOWNLOADED": "Model not downloaded", "REMOVE_MODELS": "Remove swipe to type model", - "DOWNLOAD_MODELS": "Download swipe to type model ({} total)" + "DOWNLOAD_MODELS": "Download swipe to type model", + "DOWNLOAD_MODELS_HELP": "Swipe to type only works on qwerty layout keyboards" }, "SWIPE_TYPE_MODELS": "Swipe-to-type models", "SWIPE_TYPE_MODELS_HELP": "Model files used by swipe-to-type.\nAll of them are required.", diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs index 04e8f39c..25d425dd 100644 --- a/dash-frontend/src/tab/settings/mod.rs +++ b/dash-frontend/src/tab/settings/mod.rs @@ -353,6 +353,7 @@ pub(crate) enum SettingType { InvertScrollDirectionY, KeyboardMiddleClick, KeyboardSoundEnabled, + KeyboardSwipeToTypeEnabled, Language, LeftHandedMouse, LongPressDuration, @@ -412,6 +413,7 @@ impl SettingType { Self::InvertScrollDirectionX => &mut config.invert_scroll_direction_x, Self::InvertScrollDirectionY => &mut config.invert_scroll_direction_y, Self::KeyboardSoundEnabled => &mut config.keyboard_sound_enabled, + Self::KeyboardSwipeToTypeEnabled => &mut config.keyboard_swipe_to_type_enabled, Self::LeftHandedMouse => &mut config.left_handed_mouse, Self::NotificationsEnabled => &mut config.notifications_enabled, Self::NotificationsSoundEnabled => &mut config.notifications_sound_enabled, @@ -550,6 +552,7 @@ impl SettingType { Self::InvertScrollDirectionY => Ok("APP_SETTINGS.INVERT_SCROLL_DIRECTION_Y"), Self::KeyboardMiddleClick => Ok("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK"), Self::KeyboardSoundEnabled => Ok("APP_SETTINGS.KEYBOARD_SOUND_ENABLED"), + Self::KeyboardSwipeToTypeEnabled => Ok("APP_SETTINGS.KEYBOARD_SWIPE_TYPE_ENABLED"), Self::Language => Ok("APP_SETTINGS.LANGUAGE"), Self::LeftHandedMouse => Ok("APP_SETTINGS.LEFT_HANDED_MOUSE"), Self::LongPressDuration => Ok("APP_SETTINGS.LONG_PRESS_DURATION"), diff --git a/dash-frontend/src/tab/settings/tab_features.rs b/dash-frontend/src/tab/settings/tab_features.rs index e6996678..89a3edc1 100644 --- a/dash-frontend/src/tab/settings/tab_features.rs +++ b/dash-frontend/src/tab/settings/tab_features.rs @@ -1,3 +1,4 @@ +use std::path::PathBuf; use std::rc::Rc; use wgui::{ @@ -22,22 +23,23 @@ use crate::{ util::{ popup_manager::PopupHolder, swipe_type::{ - SWIPE_TYPE_MODELS, SwipeTypeModel, swipe_type_all_models_downloaded, + SWIPE_TYPE_MODEL, swwipe_type_model_downloaded, swipe_type_delete_all_models, swipe_type_model_path, }, whisper::{ - WHISPER_MODELS, WhisperModel, whisper_any_models_downloaded, whisper_delete_all_models, whisper_model_from_name, + WHISPER_MODELS, whisper_any_models_downloaded, whisper_delete_all_models, whisper_model_from_name, whisper_model_path, }, }, views::{self, ViewUpdateParams}, }; +use crate::util::downloadable_file::DownloadableFile; #[derive(Clone)] enum Task { WhisperDownloadClosed, WhisperRemoveUnused, - WhisperDownload(&'static WhisperModel), + WhisperDownload(&'static DownloadableFile), WhisperDownloadDone, SwipeTypeDownloadClosed, SwipeTypeRemoveAll, @@ -54,8 +56,8 @@ pub struct State { globals: WguiGlobals, tasks: Tasks, parent_tasks: Tasks, - pending_whisper_download: Option<&'static WhisperModel>, - pending_swipe_download: Option<&'static [SwipeTypeModel]>, + pending_whisper_download: Option<&'static DownloadableFile>, + pending_swipe_download: Option<&'static DownloadableFile>, } impl SettingsTab for State { @@ -81,7 +83,7 @@ impl SettingsTab for State { } Task::WhisperDownload(model) => { self.pending_whisper_download = Some(model); - self.show_whisper_download_dialog(model, par.executor.clone()); + self.show_download_dialogue(model, par.executor.clone(), whisper_model_path(model.file_name), Task::WhisperDownloadClosed, Task::WhisperDownloadDone); } Task::WhisperDownloadDone => { if let Some(model) = self.pending_whisper_download.take() { @@ -94,22 +96,22 @@ impl SettingsTab for State { } Task::SwipeTypeDownloadClosed => { self.pending_swipe_download = None; - if !swipe_type_all_models_downloaded().unwrap_or_default() { - par.general_config.swipe_type_models_downloaded = false; + if !swwipe_type_model_downloaded().unwrap_or_default() { + par.general_config.keyboard_swipe_to_type_enabled = false; par.config_change_kind.replace(ConfigChangeKind::Other); self.parent_tasks.push(ParentTask::SetTab(TabNameEnum::Features)); } } Task::SwipeTypeRemoveAll => { let _ = swipe_type_delete_all_models().log_err("could not remove swipe type models"); + par.general_config.keyboard_swipe_to_type_enabled = false; } Task::SwipeTypeDownloadAll => { - self.pending_swipe_download = Some(SWIPE_TYPE_MODELS); - self.show_swipe_type_download_dialog(SWIPE_TYPE_MODELS, par.executor.clone()); + self.pending_swipe_download = Some(&SWIPE_TYPE_MODEL); + self.show_download_dialogue(&SWIPE_TYPE_MODEL, par.executor.clone(), swipe_type_model_path(SWIPE_TYPE_MODEL.file_name), Task::SwipeTypeDownloadClosed, Task::SwipeTypeDownloadDone); } Task::SwipeTypeDownloadDone => { if let Some(_) = self.pending_swipe_download.take() { - par.general_config.swipe_type_models_downloaded = true; par.config_change_kind.replace(ConfigChangeKind::Other); self.parent_tasks.push(ParentTask::SetTab(TabNameEnum::Features)); } @@ -204,7 +206,7 @@ impl State { if par.feats.swipe_to_type { swipe_type_models_button(par.mp, c)?; } - + options_checkbox(par.mp, c, SettingType::KeyboardSwipeToTypeEnabled)?; options_checkbox(par.mp, c, SettingType::NotificationsEnabled)?; options_checkbox(par.mp, c, SettingType::NotificationsSoundEnabled)?; options_checkbox(par.mp, c, SettingType::KeyboardSoundEnabled)?; @@ -240,27 +242,22 @@ impl State { }) } - fn show_whisper_download_dialog(&mut self, model: &WhisperModel, executor: AsyncExecutor) { + fn show_download_dialogue(&mut self, file: &DownloadableFile, executor: AsyncExecutor, target_path: PathBuf, on_closed: Task, on_downloaded: Task) { views::download_file::mount_popup( self.popup_download.clone(), self.frontend_tasks.clone(), - self.tasks.make_callback_box(Task::WhisperDownloadClosed), + self.tasks.make_callback_box(on_closed), views::download_file::Params { globals: self.globals.clone(), executor, - target_path: whisper_model_path(model.file_name), - url: model.url.into(), - on_downloaded: self.tasks.make_callback_box(Task::WhisperDownloadDone), + target_path, + url: file.url.into(), + on_downloaded: self.tasks.make_callback_box(on_downloaded), }, ); } - fn show_swipe_type_download_dialog(&mut self, models: &[SwipeTypeModel], executor: AsyncExecutor) { - //TODO: create views::download_files and use that - unimplemented!() - } - - fn show_whisper_model_dialog_box_download(&mut self, model: &'static WhisperModel) -> anyhow::Result<()> { + fn show_whisper_model_dialog_box_download(&mut self, model: &'static DownloadableFile) -> anyhow::Result<()> { const ACTION_DOWNLOAD: &str = "download"; const ACTION_CANCEL: &str = "cancel"; @@ -464,7 +461,7 @@ fn swipe_type_models_button(mp: &mut MacroParams, parent: WidgetID) -> anyhow::R let id_cell = horiz_cell(mp.layout, parent)?; - let all_downloaded = swipe_type_all_models_downloaded().unwrap_or_default(); + let all_downloaded = swwipe_type_model_downloaded().unwrap_or_default(); let (translation, icon, action) = if all_downloaded { ( "APP_SETTINGS.SWIPE_TYPE.REMOVE_MODELS", @@ -473,7 +470,7 @@ fn swipe_type_models_button(mp: &mut MacroParams, parent: WidgetID) -> anyhow::R ) } else { ( - "APP_SETTINGS.SWIPE_TYPE.DOWNLOAD_MODELS;5.0 MiB", + "APP_SETTINGS.SWIPE_TYPE.DOWNLOAD_MODELS", "dashboard/download.svg", "swipe_type_download", ) diff --git a/dash-frontend/src/util/downloadable_file.rs b/dash-frontend/src/util/downloadable_file.rs new file mode 100644 index 00000000..a23a4dd9 --- /dev/null +++ b/dash-frontend/src/util/downloadable_file.rs @@ -0,0 +1,5 @@ +pub struct DownloadableFile { + pub file_name: &'static str, + pub display_name: &'static str, + pub url: &'static str, +} \ No newline at end of file diff --git a/dash-frontend/src/util/mod.rs b/dash-frontend/src/util/mod.rs index f898e589..a3e6f408 100644 --- a/dash-frontend/src/util/mod.rs +++ b/dash-frontend/src/util/mod.rs @@ -8,3 +8,4 @@ pub mod toast_manager; pub mod wgui_simple; pub mod whisper; pub mod swipe_type; +pub mod downloadable_file; diff --git a/dash-frontend/src/util/swipe_type.rs b/dash-frontend/src/util/swipe_type.rs index e0cfdeb4..faef0db7 100644 --- a/dash-frontend/src/util/swipe_type.rs +++ b/dash-frontend/src/util/swipe_type.rs @@ -1,33 +1,13 @@ use std::{fs, io, path::PathBuf}; use wlx_common::data_dir; +use crate::util::downloadable_file::DownloadableFile; -/// The set of model files required by the swipe-to-type engine. -/// These correspond to the assets that `super-swipe-type` currently -/// downloads via `cached_path` in `SwipeOrchestrator::new()`. -pub struct SwipeTypeModel { - pub file_name: &'static str, - pub url: &'static str, -} - -pub const SWIPE_TYPE_MODELS: &[SwipeTypeModel] = &[ - SwipeTypeModel { - file_name: "swipe_encoder_android.onnx", - url: "https://wayvr.org/files/swipe_type/swipe_encoder_android.onnx", - }, - SwipeTypeModel { - file_name: "swipe_decoder_android.onnx", - url: "https://wayvr.org/files/swipe_type/swipe_decoder_android.onnx", - }, - SwipeTypeModel { - file_name: "en_wordlist.fst", - url: "https://wayvr.org/files/swipe_type/en_wordlist.fst", - }, - SwipeTypeModel { - file_name: "en_bigrams.fst", - url: "https://wayvr.org/files/swipe_type/en_bigrams.fst", - }, -]; +pub const SWIPE_TYPE_MODEL: DownloadableFile = DownloadableFile { + file_name: "en.tar", + display_name: "English Qwerty (15 MiB)", + url: "https://github.com/oneshinyboi/super-swipe-type/raw/refs/tags/v0.4.2/crates/super-swipe-type/assets/en.tar", +}; pub fn swipe_type_model_folder() -> PathBuf { data_dir::get_path("swipe_type") @@ -37,16 +17,13 @@ pub fn swipe_type_model_path(file_name: &str) -> PathBuf { swipe_type_model_folder().join(file_name) } -/// Returns true when every required model file is present on disk. -pub fn swipe_type_all_models_downloaded() -> io::Result { +pub fn swwipe_type_model_downloaded() -> io::Result { let path = swipe_type_model_folder(); if !path.is_dir() { return Ok(false); } - for model in SWIPE_TYPE_MODELS { - if !path.join(model.file_name).exists() { - return Ok(false); - } + if !path.join(SWIPE_TYPE_MODEL.file_name).exists() { + return Ok(false); } Ok(true) } diff --git a/dash-frontend/src/util/whisper.rs b/dash-frontend/src/util/whisper.rs index 9ae5a1f3..ef29fce9 100644 --- a/dash-frontend/src/util/whisper.rs +++ b/dash-frontend/src/util/whisper.rs @@ -1,42 +1,37 @@ use std::{fs, io, path::PathBuf}; use wlx_common::data_dir; +use crate::util::downloadable_file::DownloadableFile; -pub struct WhisperModel { - pub file_name: &'static str, - pub display_name: &'static str, - pub url: &'static str, -} - -pub const WHISPER_MODELS: &[WhisperModel] = &[ - WhisperModel { +pub const WHISPER_MODELS: &[DownloadableFile] = &[ + DownloadableFile { file_name: "ggml-base-q8_0.bin", display_name: "Base Q8 (78MiB)", url: "https://wayvr.org/files/whisper/ggml-base-q8_0.bin", }, - WhisperModel { + DownloadableFile { file_name: "ggml-small-q8_0.bin", display_name: "Small Q8 (252MiB)", url: "https://wayvr.org/files/whisper/ggml-small-q8_0.bin", }, - WhisperModel { + DownloadableFile { file_name: "ggml-large-v3-turbo-q5_0.bin", display_name: "Turbo Q5 (574MiB)", url: "https://wayvr.org/files/whisper/ggml-large-v3-turbo-q5_0.bin", }, - WhisperModel { + DownloadableFile { file_name: "ggml-large-v3-turbo-q8_0.bin", display_name: "Turbo Q8 (874MiB)", url: "https://wayvr.org/files/whisper/ggml-large-v3-turbo-q8_0.bin", }, - WhisperModel { + DownloadableFile { file_name: "ggml-large-v3-turbo.bin", display_name: "Turbo (1.5GiB)", url: "https://wayvr.org/files/whisper/ggml-large-v3-turbo.bin", }, ]; -pub fn whisper_model_from_name(file_name: &str) -> Option<&'static WhisperModel> { +pub fn whisper_model_from_name(file_name: &str) -> Option<&'static DownloadableFile> { WHISPER_MODELS.iter().find(|x| x.file_name == file_name) } diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index 50cb792b..3059e130 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -34,7 +34,11 @@ use wgui::{ use wgui::event::StyleSetRequest; use wgui::layout::LayoutTask; use wgui::taffy::Display; -use super::{KeyButtonData, KeyState, KeyboardState, handle_press, handle_release, layout::{self, KeyCapType}, handle_mouse_motion, init_swipe_type_manager}; +#[cfg(feature = "swipe-to-type")] +use wlx_common::data_dir; +use super::{KeyButtonData, KeyState, KeyboardState, handle_press, handle_release, layout::{self, KeyCapType}, handle_mouse_motion}; +#[cfg(feature = "swipe-to-type")] +use super::init_swipe_type_manager; const PIXELS_PER_UNIT: f32 = 60.; @@ -478,7 +482,8 @@ pub(super) fn create_keyboard_panel( } } if app.session.config.keyboard_swipe_to_type_enabled && panel.state.swipe_typing_manager.is_none() { - init_swipe_type_manager(&mut panel.state); + #[cfg(feature = "swipe-to-type")] + init_swipe_type_manager(&mut panel.state, data_dir::get_path("swipe_type")); let predictions_root = panel.parser_state .get_widget_id("swipe_predictions_root") diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index 1ca64d6e..e1c7aecd 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -45,6 +45,8 @@ use wlx_common::{ config::AltModifier, overlays::{BackendAttrib, BackendAttribValue}, }; +#[cfg(feature = "swipe-to-type")] +use wlx_common::data_dir; use crate::overlays::keyboard::builder::update_swipe_prediction_bar; use crate::overlays::keyboard::layout::KeyCapType; use crate::overlays::keyboard::swipe_type::SwipeTypingManager; @@ -52,9 +54,32 @@ use crate::overlays::keyboard::swipe_type::SwipeTypingManager; pub mod builder; mod layout; -#[cfg(feature = "swipe-to-type ")] +#[cfg(feature = "swipe-to-type")] mod swipe_type; +#[cfg(not(feature = "swipe-to-type"))] +mod swipe_type { + use std::sync::mpsc::Receiver; + use wgui::event::{DeviceBitmask, MouseButtonIndex}; + use glam::Vec2; + + pub struct SwipeTypingManager; + + impl SwipeTypingManager { + pub fn new(_model_folder: std::path::PathBuf) -> anyhow::Result<(Self, Receiver>>)> { + Ok((Self, std::sync::mpsc::sync_channel(1).1)) + } + pub fn add_swipe(&mut self, _within_key_pos_normalized: &Vec2, _key_label: char, _device: DeviceBitmask, _index: Option) {} + pub fn predict(&mut self) -> anyhow::Result<()> { Ok(()) } + pub fn reset(&mut self) {} + pub fn did_swipe_leave_first_key(&self) -> bool { false } + pub fn is_current_swipe_empty(&self) -> bool { true } + pub fn current_swipe_mouse_button_index(&self) -> Option { None } + pub fn select_word(&mut self, _word: &String, _app: &mut crate::state::AppState, _original_keyboard_mods: crate::subsystem::hid::KeyModifier) {} + pub fn select_alternate_prediction(&mut self, _word: &String, _app: &mut crate::state::AppState, _original_keyboard_mods: crate::subsystem::hid::KeyModifier) {} + } +} + pub const KEYBOARD_NAME: &str = "kbd"; const AUTO_RELEASE_MODS: [KeyModifier; 5] = [SHIFT, CTRL, ALT, SUPER, ALTGR]; const SYSTEM_LAYOUT_ALIASES: [&str; 5] = ["mozc", "pinyin", "hangul", "sayura", "unikey"]; @@ -132,8 +157,9 @@ pub fn create_keyboard(app: &mut AppState, wayland: bool) -> anyhow::Result { state.swipe_typing_manager = Some(engine); state.swipe_candidate_receiver = Some(receiver); @@ -143,6 +169,7 @@ pub(self) fn init_swipe_type_manager(state: &mut KeyboardState) { } }; } +#[cfg(feature = "swipe-to-type")] pub(self) fn hide_swipe_type_manager(panel: &mut GuiPanel) { let predictions_root = panel.parser_state .get_widget_id("swipe_predictions_root") @@ -188,7 +215,8 @@ impl KeyboardBackend { let mut state = self.default_state.take(); if app.session.config.keyboard_swipe_to_type_enabled { - init_swipe_type_manager(&mut state); + #[cfg(feature = "swipe-to-type")] + init_swipe_type_manager(&mut state, data_dir::get_path("swipe_type")); log::info!("swipe engine created"); } @@ -196,6 +224,7 @@ impl KeyboardBackend { create_keyboard_panel(app, keymap, state, &self.wlx_layout)?; if !app.session.config.keyboard_swipe_to_type_enabled { + #[cfg(feature = "swipe-to-type")] hide_swipe_type_manager(&mut panel); } @@ -243,7 +272,8 @@ impl KeyboardBackend { .take(); if app.session.config.keyboard_swipe_to_type_enabled { - init_swipe_type_manager(&mut state_from); + #[cfg(feature = "swipe-to-type")] + init_swipe_type_manager(&mut state_from, data_dir::get_path("swipe_type")); } self.active_layout = new_key; @@ -254,6 +284,7 @@ impl KeyboardBackend { .state = state_from; if !app.session.config.keyboard_swipe_to_type_enabled { + #[cfg(feature = "swipe-to-type")] hide_swipe_type_manager(self.layout_panels .get_mut(self.active_layout) .unwrap() diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs index f2f67970..a943d358 100644 --- a/wayvr/src/overlays/keyboard/swipe_type.rs +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -3,6 +3,7 @@ use crate::subsystem::hid::{KeyModifier, VirtualKey, CTRL}; use anyhow::{bail}; use glam::Vec2; use std::mem; +use std::path::PathBuf; use std::sync::mpsc::{sync_channel, Receiver, SyncSender, channel, Sender}; use std::thread::{self, JoinHandle}; use std::time::Instant; @@ -87,14 +88,14 @@ impl SwipeTypingManager { app.hid_provider .set_modifiers_routed(app.wvr_server.as_mut(), original_keyboard_mods); } - pub fn new() -> anyhow::Result<(SwipeTypingManager, Receiver>>)> { + pub fn new(model_folder: PathBuf) -> anyhow::Result<(SwipeTypingManager, Receiver>>)> { let (candidate_sender, candidate_receiver) = sync_channel(1); let (task_sender, task_receiver) = channel::(); // Spawn persistent worker thread let worker_candidate_sender = candidate_sender.clone(); let worker_thread = thread::spawn(move || { - let mut swipe_engine = match SwipeOrchestrator::new() { + let mut swipe_engine = match SwipeOrchestrator::new_with_paths(&model_folder) { Ok(engine) => engine, Err(e) => { log::error!("Failed to initialize SwipeOrchestrator: {}", e); From 13d63b00fd3db6b56e524b6829537e18158bab58 Mon Sep 17 00:00:00 2001 From: oneshinyboi Date: Sat, 15 Aug 2026 20:06:22 -0500 Subject: [PATCH 27/35] add disabled state for checkboxes and apply to enable swipe to type --- dash-frontend/assets/lang/en.json | 1 + dash-frontend/src/tab/settings/mod.rs | 2 +- .../src/tab/settings/tab_features.rs | 68 ++++++- wgui/src/components/checkbox.rs | 186 ++++++++++++------ 4 files changed, 188 insertions(+), 69 deletions(-) diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index 7f022b21..b4a356c9 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -230,6 +230,7 @@ "NEED_TO_DOWNLOAD_MODELS": "The swipe-to-type model needs to be downloaded.\nDownload it now?", "MODELS_DOWNLOADED": "Model downloaded", "MODELS_NOT_DOWNLOADED": "Model not downloaded", + "MODEL_REQUIRED_TO_ENABLE": "Download the swipe-to-type model to enable this option.", "REMOVE_MODELS": "Remove swipe to type model", "DOWNLOAD_MODELS": "Download swipe to type model", "DOWNLOAD_MODELS_HELP": "Swipe to type only works on qwerty layout keyboards" diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs index 25d425dd..6e61c475 100644 --- a/dash-frontend/src/tab/settings/mod.rs +++ b/dash-frontend/src/tab/settings/mod.rs @@ -552,7 +552,7 @@ impl SettingType { Self::InvertScrollDirectionY => Ok("APP_SETTINGS.INVERT_SCROLL_DIRECTION_Y"), Self::KeyboardMiddleClick => Ok("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK"), Self::KeyboardSoundEnabled => Ok("APP_SETTINGS.KEYBOARD_SOUND_ENABLED"), - Self::KeyboardSwipeToTypeEnabled => Ok("APP_SETTINGS.KEYBOARD_SWIPE_TYPE_ENABLED"), + Self::KeyboardSwipeToTypeEnabled => Ok("APP_SETTINGS.KEYBOARD_SWIPE_TO_TYPE_ENABLED"), Self::Language => Ok("APP_SETTINGS.LANGUAGE"), Self::LeftHandedMouse => Ok("APP_SETTINGS.LEFT_HANDED_MOUSE"), Self::LongPressDuration => Ok("APP_SETTINGS.LONG_PRESS_DURATION"), diff --git a/dash-frontend/src/tab/settings/tab_features.rs b/dash-frontend/src/tab/settings/tab_features.rs index 89a3edc1..d58a1aff 100644 --- a/dash-frontend/src/tab/settings/tab_features.rs +++ b/dash-frontend/src/tab/settings/tab_features.rs @@ -2,7 +2,10 @@ use std::path::PathBuf; use std::rc::Rc; use wgui::{ - components::button::{ButtonClickEvent, ComponentButton}, + components::{ + button::{ButtonClickEvent, ComponentButton}, + checkbox::ComponentCheckbox, + }, globals::WguiGlobals, i18n::Translation, layout::WidgetID, @@ -18,7 +21,10 @@ use crate::{ frontend::FrontendTasks, tab::settings::{ SettingType, SettingsMountParams, SettingsTab, TabNameEnum, Task as ParentTask, horiz_cell, - macros::{MacroParams, options_category, options_checkbox, options_range_f32}, + mount_requires_restart, + macros::{ + MacroParams, options_category, options_checkbox, options_range_f32, + }, }, util::{ popup_manager::PopupHolder, @@ -206,7 +212,7 @@ impl State { if par.feats.swipe_to_type { swipe_type_models_button(par.mp, c)?; } - options_checkbox(par.mp, c, SettingType::KeyboardSwipeToTypeEnabled)?; + swipe_type_enabled_checkbox(par.mp, c)?; options_checkbox(par.mp, c, SettingType::NotificationsEnabled)?; options_checkbox(par.mp, c, SettingType::NotificationsSoundEnabled)?; options_checkbox(par.mp, c, SettingType::KeyboardSoundEnabled)?; @@ -494,5 +500,61 @@ fn swipe_type_models_button(mp: &mut MacroParams, parent: WidgetID) -> anyhow::R } })); + Ok(()) +} + +fn swipe_type_enabled_checkbox(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Result<()> { + let id = mp.idx.to_string(); + mp.idx += 1; + + let setting = SettingType::KeyboardSwipeToTypeEnabled; + + // cannot enable swipe-to-type until the model is downloaded + let model_downloaded = swwipe_type_model_downloaded().unwrap_or_default(); + + let mut params = TemplateParams::new(); + params.insert("id", &id); + + match setting.get_translation() { + Ok(translation) => params.insert("translation", translation), + Err(raw_text) => params.insert("text", raw_text), + }; + + let tooltip = if model_downloaded { + setting.get_tooltip() + } else { + Some("APP_SETTINGS.SWIPE_TYPE.MODEL_REQUIRED_TO_ENABLE") + }; + if let Some(tooltip) = tooltip { + params.insert("tooltip", tooltip); + } + + let checked = if *setting.mut_bool(mp.config) { "1" } else { "0" }; + params.insert("checked", checked); + + let id_cell = horiz_cell(mp.layout, parent)?; + + mp.parser_state + .instantiate_template(mp.doc_params, "CheckBoxSetting", mp.layout, id_cell, params)?; + + if setting.requires_restart() { + mount_requires_restart(mp.layout, id_cell)?; + } + + let checkbox = mp.parser_state.fetch_component_as::(&id)?; + + if !model_downloaded { + let mut common = mp.layout.common(); + checkbox.set_disabled(&mut common, true); + } + + checkbox.on_toggle(Box::new({ + let tasks = mp.tasks.clone(); + move |_common, e| { + tasks.push(ParentTask::UpdateBool(setting, e.checked)); + Ok(()) + } + })); + Ok(()) } \ No newline at end of file diff --git a/wgui/src/components/checkbox.rs b/wgui/src/components/checkbox.rs index 9f6f9700..4bd78414 100644 --- a/wgui/src/components/checkbox.rs +++ b/wgui/src/components/checkbox.rs @@ -66,6 +66,7 @@ struct State { checked: bool, hovered: bool, down: bool, + disabled: bool, on_toggle: Option, self_ref: Weak, active_tooltip: Option>, @@ -99,6 +100,32 @@ pub struct ComponentCheckbox { const COLOR_UNCHECKED: WguiColor = WguiColor::Raw(drawing::Color::new(0., 0., 0., 0.)); const COLOR_HOVERED: WguiColor = WguiColorName::Tertiary.to_wgui_color(); +fn disabled_color() -> WguiColor { + WguiColor::from(WguiColorName::OnBackground).with_alpha(0.35) +} + +/// Color for the inner checkbox box, for any checked/hover/disabled combination. +fn box_fill_color(checked: bool, hovered: bool, disabled: bool, checked_color: WguiColor) -> WguiColor { + if disabled { + if checked { disabled_color() } else { COLOR_UNCHECKED } + } else if checked { + if hovered { COLOR_HOVERED } else { checked_color } + } else { + COLOR_UNCHECKED + } +} + +/// Color for the checkbox border, for any hover/press/disabled combination. +fn box_border_color(hovered: bool, down: bool, disabled: bool) -> WguiColor { + if disabled { + disabled_color() + } else if hovered || down { + COLOR_HOVERED + } else { + WguiColorName::OnBackground.into() + } +} + impl ComponentTrait for ComponentCheckbox { fn base(&self) -> &ComponentBase { &self.base @@ -120,17 +147,9 @@ impl ComponentTrait for ComponentCheckbox { } } -fn set_box_checked(widgets: &layout::WidgetMap, data: &Data, checked: bool, hovered: bool) { +fn set_box_checked(widgets: &layout::WidgetMap, data: &Data, checked: bool, hovered: bool, disabled: bool) { widgets.call(data.id_inner_box, |rect: &mut WidgetRectangle| { - rect.params.color = if checked { - if hovered { - COLOR_HOVERED.into() - } else { - data.color_checked - } - } else { - COLOR_UNCHECKED.into() - } + rect.params.color = box_fill_color(checked, hovered, disabled, data.color_checked); }); } @@ -145,6 +164,7 @@ impl ComponentCheckbox { pub fn set_checked(&self, common: &mut CallbackDataCommon, checked: bool) { let hovered; + let disabled; { let mut state = self.state.borrow_mut(); if state.checked == checked { @@ -152,8 +172,9 @@ impl ComponentCheckbox { } state.checked = checked; hovered = state.hovered; + disabled = state.disabled; } - set_box_checked(&common.state.widgets, &self.data, checked, hovered); + set_box_checked(&common.state.widgets, &self.data, checked, hovered, disabled); common.alterables.mark_redraw(); } @@ -174,16 +195,46 @@ impl ComponentCheckbox { pub fn on_toggle(&self, func: CheckboxToggleCallback) { self.state.borrow_mut().on_toggle = Some(func); } + + /// Enable or disable user interaction with the checkbox and dim its appearance. + pub fn set_disabled(&self, common: &mut CallbackDataCommon, disabled: bool) { + let checked; + let hovered; + let down; + { + let mut state = self.state.borrow_mut(); + if state.disabled == disabled { + return; + } + state.disabled = disabled; + checked = state.checked; + hovered = state.hovered; + down = state.down; + } + + set_box_checked(&common.state.widgets, &self.data, checked, hovered, disabled); + + common.state.widgets.call(self.data.id_outer_box, |rect: &mut WidgetRectangle| { + rect.params.border_color = box_border_color(hovered, down, disabled); + }); + + if let Some(mut label) = common.state.widgets.get_as::(self.data.id_label) { + let color = if disabled { disabled_color() } else { WguiColorName::OnBackground.into() }; + label.set_color(common, color, true); + } + + common.alterables.mark_redraw(); + } + + pub fn get_disabled(&self) -> bool { + self.state.borrow().disabled + } } -fn anim_hover(anim_data: &mut crate::animation::CallbackData<'_>, pos: f32, _pressed: bool) { +fn anim_hover(anim_data: &mut crate::animation::CallbackData<'_>, pos: f32, pressed: bool, disabled: bool) { let rect = anim_data.obj.as_any_mut().downcast_mut::().unwrap(); rect.params.border = 2.0; - rect.params.border_color = if pos > 0.0 { - COLOR_HOVERED.into() - } else { - WguiColorName::OnBackground.into() - }; + rect.params.border_color = box_border_color(pos > 0.0, pressed, disabled); } fn anim_hover_in(state: Rc>, data: Rc, anim_mult: f32) -> Animation { @@ -193,7 +244,8 @@ fn anim_hover_in(state: Rc>, data: Rc, anim_mult: f32) -> A (5. * anim_mult) as _, AnimationEasing::OutQuad, Box::new(move |common, anim_data| { - anim_hover(anim_data, anim_data.pos, down); + let disabled = state.borrow().disabled; + anim_hover(anim_data, anim_data.pos, down, disabled); common.alterables.mark_redraw(); }), ) @@ -206,7 +258,8 @@ fn anim_hover_out(state: Rc>, data: Rc, anim_mult: f32) -> (8. * anim_mult) as _, AnimationEasing::OutQuad, Box::new(move |common, anim_data| { - anim_hover(anim_data, 1.0 - anim_data.pos, down); + let disabled = state.borrow().disabled; + anim_hover(anim_data, 1.0 - anim_data.pos, down, disabled); common.alterables.mark_redraw(); }), ) @@ -222,28 +275,33 @@ fn register_event_mouse_enter( listeners.register( EventListenerKind::MouseEnter, Box::new(move |common, _event_data, (), ()| { - common.alterables.trigger_haptics(); - common - .alterables - .animate(anim_hover_in(state.clone(), data.clone(), anim_mult)); + let checked; + let disabled; + { + let mut state = state.borrow_mut(); + checked = state.checked; + disabled = state.disabled; + state.hovered = true; + } + + if !disabled { + common.alterables.trigger_haptics(); + common + .alterables + .animate(anim_hover_in(state.clone(), data.clone(), anim_mult)); + + if checked { + common + .state + .widgets + .call(data.id_inner_box, |rect: &mut WidgetRectangle| { + rect.params.color = box_fill_color(checked, true, false, data.color_checked); + }); + } + } ComponentTooltip::register_hover_in(common, &tooltip_info, data.id_container, state.clone()); - let checked = { - let mut state = state.borrow_mut(); - state.hovered = true; - state.checked - }; - - if checked { - common - .state - .widgets - .call(data.id_inner_box, |rect: &mut WidgetRectangle| { - rect.params.color = COLOR_HOVERED.into(); - }); - } - Ok(EventResult::Pass) }), ) @@ -258,25 +316,30 @@ fn register_event_mouse_leave( listeners.register( EventListenerKind::MouseLeave, Box::new(move |common, _event_data, (), ()| { - common.alterables.trigger_haptics(); - common - .alterables - .animate(anim_hover_out(state.clone(), data.clone(), anim_mult)); - - let checked = { + let checked; + let disabled; + { let mut state = state.borrow_mut(); + checked = state.checked; + disabled = state.disabled; state.hovered = false; state.active_tooltip = None; - state.checked - }; + } - if checked { + if !disabled { + common.alterables.trigger_haptics(); common - .state - .widgets - .call(data.id_inner_box, |rect: &mut WidgetRectangle| { - rect.params.color = data.color_checked; - }); + .alterables + .animate(anim_hover_out(state.clone(), data.clone(), anim_mult)); + + if checked { + common + .state + .widgets + .call(data.id_inner_box, |rect: &mut WidgetRectangle| { + rect.params.color = box_fill_color(checked, false, false, data.color_checked); + }); + } } Ok(EventResult::Pass) @@ -311,18 +374,14 @@ fn register_event_mouse_press( .widgets .call(data.id_outer_box, |rect: &mut WidgetRectangle| { rect.params.border = 2.0; - rect.params.border_color = if pressed_hovered { - COLOR_HOVERED.into() - } else { - WguiColorName::OnBackground.into() - }; + rect.params.border_color = box_border_color(pressed_hovered, false, state.disabled); }); common.alterables.trigger_haptics(); common.alterables.mark_redraw(); common.alterables.unfocus(); - if state.hovered { + if state.hovered && !state.disabled { state.down = true; Ok(EventResult::Consumed) } else { @@ -349,17 +408,13 @@ fn register_event_mouse_release( .widgets .call(data.id_outer_box, |rect: &mut WidgetRectangle| { rect.params.border = 2.0; - rect.params.border_color = if released_hovered { - COLOR_HOVERED.into() - } else { - WguiColorName::OnBackground.into() - }; + rect.params.border_color = box_border_color(released_hovered, was_down, state.disabled); }); common.alterables.trigger_haptics(); common.alterables.mark_redraw(); - if was_down { + if !state.disabled && was_down { state.down = false; if let Some(self_ref) = state.self_ref.upgrade() @@ -377,7 +432,7 @@ fn register_event_mouse_release( }); } - set_box_checked(&common.state.widgets, &data, state.checked, state.hovered); + set_box_checked(&common.state.widgets, &data, state.checked, state.hovered, state.disabled); if state.hovered && let Some(on_toggle) = &state.on_toggle { @@ -512,6 +567,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul checked: params.checked, down: false, hovered: false, + disabled: false, on_toggle: None, self_ref: Weak::new(), active_tooltip: None, From 55c46ccfd21c6740ce58dad132c8d4bb1feecf5c Mon Sep 17 00:00:00 2001 From: oneshinyboi Date: Sat, 15 Aug 2026 21:14:53 -0500 Subject: [PATCH 28/35] fix swipe_type wayvr --- Cargo.lock | 5 ++-- .../src/tab/settings/tab_features.rs | 2 +- wayvr/Cargo.toml | 4 +-- wayvr/src/overlays/keyboard/mod.rs | 8 +++--- wayvr/src/overlays/keyboard/swipe_type.rs | 27 +++++++++---------- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 840c5c44..9d4f557b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6079,9 +6079,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "super-swipe-type" -version = "0.3.2" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2514d173cdebfac064577dd38387f71244daa7aa4d769076a5806fd4f7cf73e" +checksum = "d39d2eca9f0ac1a82ec03e8de1b7672a9a145f5bcdeae941efd943d9b9575b5c" dependencies = [ "anyhow", "cached-path", @@ -6090,6 +6090,7 @@ dependencies = [ "regex", "regex-automata 0.1.10", "rten", + "tar", "vector2", ] diff --git a/dash-frontend/src/tab/settings/tab_features.rs b/dash-frontend/src/tab/settings/tab_features.rs index d58a1aff..f6a7d5f7 100644 --- a/dash-frontend/src/tab/settings/tab_features.rs +++ b/dash-frontend/src/tab/settings/tab_features.rs @@ -211,8 +211,8 @@ impl State { if par.feats.swipe_to_type { swipe_type_models_button(par.mp, c)?; + swipe_type_enabled_checkbox(par.mp, c)?; } - swipe_type_enabled_checkbox(par.mp, c)?; options_checkbox(par.mp, c, SettingType::NotificationsEnabled)?; options_checkbox(par.mp, c, SettingType::NotificationsSoundEnabled)?; options_checkbox(par.mp, c, SettingType::KeyboardSoundEnabled)?; diff --git a/wayvr/Cargo.toml b/wayvr/Cargo.toml index 0d5e93b1..2cff870f 100644 --- a/wayvr/Cargo.toml +++ b/wayvr/Cargo.toml @@ -127,13 +127,13 @@ xkbcommon = { calloop = { version = "0.14.4", optional = true } calloop-wayland-source = { version = "0.4.1", optional = true } evdev = "0.13.2" -super-swipe-type = { version = "0.3.2", optional = true } +super-swipe-type = { version = "0.4.2", optional = true } [build-dependencies] regex.workspace = true [features] -default = ["openvr", "openxr", "osc", "wayland", "whisper", "x11"] +default = ["openvr", "openxr", "osc", "wayland", "whisper", "x11", "swipe-to-type"] as-raw-xcb-connection = [] feat-monado-metrics = [] openvr = ["dep:json", "dep:ovr_overlay"] diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index e1c7aecd..b87fb7f4 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -158,8 +158,8 @@ pub fn create_keyboard(app: &mut AppState, wayland: bool) -> anyhow::Result { state.swipe_typing_manager = Some(engine); state.swipe_candidate_receiver = Some(receiver); @@ -216,7 +216,7 @@ impl KeyboardBackend { if app.session.config.keyboard_swipe_to_type_enabled { #[cfg(feature = "swipe-to-type")] - init_swipe_type_manager(&mut state, data_dir::get_path("swipe_type")); + init_swipe_type_manager(&mut state, data_dir::get_path("swipe_type").join("en.tar")); log::info!("swipe engine created"); } @@ -273,7 +273,7 @@ impl KeyboardBackend { if app.session.config.keyboard_swipe_to_type_enabled { #[cfg(feature = "swipe-to-type")] - init_swipe_type_manager(&mut state_from, data_dir::get_path("swipe_type")); + init_swipe_type_manager(&mut state_from, data_dir::get_path("swipe_type").join("en.tar")); } self.active_layout = new_key; diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs index a943d358..0500887f 100644 --- a/wayvr/src/overlays/keyboard/swipe_type.rs +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -53,7 +53,7 @@ impl SwipeTypingManager { match app.hid_provider.get_input_focus() { InputFocus::PhysicalScreen => { if let Some(clipboard) = self.clipboard.as_mut() { - clipboard.set_clipboard_utf8(&*text_to_paste); + clipboard.set_clipboard_utf8(&text_to_paste); Self::paste(app, original_keyboard_mods); } }, @@ -88,17 +88,17 @@ impl SwipeTypingManager { app.hid_provider .set_modifiers_routed(app.wvr_server.as_mut(), original_keyboard_mods); } - pub fn new(model_folder: PathBuf) -> anyhow::Result<(SwipeTypingManager, Receiver>>)> { + pub fn new(model_path: PathBuf) -> anyhow::Result<(SwipeTypingManager, Receiver>>)> { let (candidate_sender, candidate_receiver) = sync_channel(1); let (task_sender, task_receiver) = channel::(); // Spawn persistent worker thread let worker_candidate_sender = candidate_sender.clone(); let worker_thread = thread::spawn(move || { - let mut swipe_engine = match SwipeOrchestrator::new_with_paths(&model_folder) { + let mut swipe_engine = match SwipeOrchestrator::new_from_path(&model_path) { Ok(engine) => engine, Err(e) => { - log::error!("Failed to initialize SwipeOrchestrator: {}", e); + log::error!("Failed to initialize SwipeOrchestrator: {e}"); return; } }; @@ -117,7 +117,7 @@ impl SwipeTypingManager { let _ = worker_candidate_sender.send(Some(words)); } Err(e) => { - log::error!("Prediction failed: {}", e); + log::error!("Prediction failed: {e}"); } } } @@ -131,7 +131,7 @@ impl SwipeTypingManager { clipboard::wl::Provider::new() .log_err("Could not create Wayland clipboard provider") .ok() - .map(|p| Box::new(p) as Box); + .map(|p| Box::new(p) as Box) } #[cfg(feature = "x11")] { @@ -207,26 +207,25 @@ impl SwipeTypingManager { now } - pub fn did_swipe_leave_first_key(&self) -> bool { + pub const fn did_swipe_leave_first_key(&self) -> bool { self.swipe_left_first_key } - pub fn is_current_swipe_empty(&self) -> bool { + pub const fn is_current_swipe_empty(&self) -> bool { self.current_swipe.is_empty() } - pub fn current_swipe_mouse_button_index(&self) -> Option { + pub const fn current_swipe_mouse_button_index(&self) -> Option { self.current_swipe_mouse_button_index } pub fn add_swipe(&mut self, within_key_pos_normalized: &Vec2, key_label: char, device: DeviceBitmask, index: Option) { if let Some(pos) = self.keyboard_gird.key_positions.get(&key_label.to_ascii_lowercase()) { - if let Some(current_device) = self.current_swipe_device { - if current_device != device { - return; - } + if let Some(current_device) = self.current_swipe_device && current_device != device { + return; } + if self.first_swipe_char != char::default() && self.first_swipe_char != key_label.to_ascii_lowercase() { @@ -255,7 +254,7 @@ impl SwipeTypingManager { let point = within_key_pos_from_center * key_dimensions + key_pos; let duration = Instant::now().duration_since(start_time).mul_f32(0.8); // multiply by .8 because library is trained on mobile swipes which happen on a smaller keyboard and are faster self.current_swipe - .push(SwipePoint::new(point.x.into(), point.y.into(), duration)) + .push(SwipePoint::new(point.x.into(), point.y.into(), duration)); } } } From 5d69d496b04665de1dc5ebf0b5bc031067926b4e Mon Sep 17 00:00:00 2001 From: oneshinyboi Date: Sat, 15 Aug 2026 21:58:16 -0500 Subject: [PATCH 29/35] fix merge --- Cargo.lock | 383 +++++++++++++++++++++- wayvr/src/overlays/keyboard/builder.rs | 4 +- wayvr/src/overlays/keyboard/swipe_type.rs | 32 +- wayvr/src/state.rs | 3 + wgui/src/components/checkbox.rs | 22 +- wlx-common/src/dash_interface.rs | 1 + 6 files changed, 408 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d68a827c..b36a9314 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1188,6 +1188,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1201,9 +1211,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "core-graphics-types", - "foreign-types", + "foreign-types 0.5.0", "libc", ] @@ -1214,7 +1224,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "libc", ] @@ -1677,6 +1687,17 @@ dependencies = [ "objc2 0.6.4", ] +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "dlib" version = "0.5.3" @@ -2058,6 +2079,15 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -2065,7 +2095,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared", + "foreign-types-shared 0.3.1", ] [[package]] @@ -2079,6 +2109,12 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -2468,6 +2504,29 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + [[package]] name = "httparse" version = "1.10.1" @@ -2572,6 +2631,89 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92a7ed671a6aad807a8651a2e1782a6598fda9ce5185dd8158549e95a91c6428" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -2611,6 +2753,27 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "image" version = "0.25.10" @@ -3167,6 +3330,12 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + [[package]] name = "lock_api" version = "0.4.14" @@ -3362,6 +3531,23 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndk" version = "0.9.0" @@ -3911,6 +4097,49 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "openssl" +version = "0.10.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" +dependencies = [ + "bitflags 2.13.0", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "openxr" version = "0.21.1" @@ -4278,6 +4507,15 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -5232,6 +5470,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -5263,6 +5510,29 @@ dependencies = [ "tiny-skia 0.11.4", ] +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "self_cell" version = "1.2.2" @@ -5891,6 +6161,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -6145,6 +6426,16 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.11.0" @@ -6500,6 +6791,18 @@ dependencies = [ "log", ] +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + [[package]] name = "usvg" version = "0.47.0" @@ -6528,6 +6831,12 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -7475,7 +7784,7 @@ dependencies = [ "calloop 0.13.0", "cfg_aliases", "concurrent-queue", - "core-foundation", + "core-foundation 0.9.4", "core-graphics", "cursor-icon", "dpi", @@ -7570,6 +7879,12 @@ dependencies = [ "xdg 3.0.0", ] +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + [[package]] name = "wyz" version = "0.5.1" @@ -7732,6 +8047,29 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", + "synstructure", +] + [[package]] name = "zeno" version = "0.3.3" @@ -7758,6 +8096,27 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", + "synstructure", +] + [[package]] name = "zeroize" version = "1.9.0" @@ -7766,9 +8125,9 @@ checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" [[package]] name = "zerotrie" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" dependencies = [ "displaydoc", "yoke", @@ -7777,9 +8136,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +checksum = "94b5c6b5976d66c1d703c4fd17d3f5e43c8cedaacf604961b171adc7130896d8" dependencies = [ "yoke", "zerofrom", @@ -7788,13 +8147,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +checksum = "47402523226a02bfe5230160dc3ccc089aa6f6f19e7fcbb4e6f824bbb1b4aa62" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 3.0.3", ] [[package]] diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index 1a144753..58de9332 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -1,4 +1,4 @@ -use std::{rc::Rc, time::Duration}; +use std::{collections::HashMap, rc::Rc, time::Duration}; use crate::{ app_misc, @@ -505,7 +505,7 @@ pub(super) fn create_keyboard_panel( } if app.session.config.keyboard_swipe_to_type_enabled && panel.state.swipe_typing_manager.is_none() { #[cfg(feature = "swipe-to-type")] - init_swipe_type_manager(&mut panel.state, data_dir::get_path("swipe_type")); + init_swipe_type_manager(&mut panel.state, data_dir::get_path("swipe_type").join("en.tar")); let predictions_root = panel.parser_state .get_widget_id("swipe_predictions_root") diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs index 0500887f..c7be4769 100644 --- a/wayvr/src/overlays/keyboard/swipe_type.rs +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -127,24 +127,22 @@ impl SwipeTypingManager { }); let clipboard_provider: Option> = { #[cfg(feature = "wayland")] - { - clipboard::wl::Provider::new() - .log_err("Could not create Wayland clipboard provider") - .ok() - .map(|p| Box::new(p) as Box) - } - #[cfg(feature = "x11")] - { - clipboard::x11::Provider::new() - .log_err("Could not create X11 clipboard provider") - .ok() - .map(|p| Box::new(p) as Box) - } - #[cfg(not(any(feature = "wayland", feature = "x11")))] - { - None - } + let wl = clipboard::wl::Provider::new() + .log_err("Could not create Wayland clipboard provider") + .ok() + .map(|p| Box::new(p) as Box); + #[cfg(not(feature = "wayland"))] + let wl = None; + #[cfg(feature = "x11")] + let x11 = clipboard::x11::Provider::new() + .log_err("Could not create X11 clipboard provider") + .ok() + .map(|p| Box::new(p) as Box); + #[cfg(not(feature = "x11"))] + let x11 = None; + + wl.or(x11) }; Ok(( Self { diff --git a/wayvr/src/state.rs b/wayvr/src/state.rs index fb2363fc..cf9d8373 100644 --- a/wayvr/src/state.rs +++ b/wayvr/src/state.rs @@ -192,6 +192,9 @@ impl AppState { { feats.whisper = true; } + #[cfg(feature = "swipe-to-type")] { + feats.swipe_to_type = true; + } let mut app_state = Self { tasks, diff --git a/wgui/src/components/checkbox.rs b/wgui/src/components/checkbox.rs index 1afe3345..20ee4fed 100644 --- a/wgui/src/components/checkbox.rs +++ b/wgui/src/components/checkbox.rs @@ -238,13 +238,18 @@ fn anim_hover(anim_data: &mut crate::animation::CallbackData<'_>, pos: f32, pres } fn anim_hover_in(state: &Rc>, data: &Rc, anim_mult: f32) -> Animation { - let down = state.borrow().down; + let down; + let disabled; + { + let state = state.borrow(); + down = state.down; + disabled = state.disabled; + } Animation::new( data.id_outer_box, (5. * anim_mult) as _, AnimationEasing::OutQuad, Box::new(move |common, anim_data| { - let disabled = state.borrow().disabled; anim_hover(anim_data, anim_data.pos, down, disabled); common.alterables.mark_redraw(); }), @@ -252,13 +257,18 @@ fn anim_hover_in(state: &Rc>, data: &Rc, anim_mult: f32) -> } fn anim_hover_out(state: &Rc>, data: &Rc, anim_mult: f32) -> Animation { - let down = state.borrow().down; + let down; + let disabled; + { + let state = state.borrow(); + down = state.down; + disabled = state.disabled; + } Animation::new( data.id_outer_box, (8. * anim_mult) as _, AnimationEasing::OutQuad, Box::new(move |common, anim_data| { - let disabled = state.borrow().disabled; anim_hover(anim_data, 1.0 - anim_data.pos, down, disabled); common.alterables.mark_redraw(); }), @@ -288,7 +298,7 @@ fn register_event_mouse_enter( common.alterables.trigger_haptics(); common .alterables - .animate(anim_hover_in(state.clone(), data.clone(), anim_mult)); + .animate(anim_hover_in(&state, &data, anim_mult)); if checked { common @@ -330,7 +340,7 @@ fn register_event_mouse_leave( common.alterables.trigger_haptics(); common .alterables - .animate(anim_hover_out(state.clone(), data.clone(), anim_mult)); + .animate(anim_hover_out(&state, &data, anim_mult)); if checked { common diff --git a/wlx-common/src/dash_interface.rs b/wlx-common/src/dash_interface.rs index 611f5e89..d9f99bef 100644 --- a/wlx-common/src/dash_interface.rs +++ b/wlx-common/src/dash_interface.rs @@ -63,6 +63,7 @@ impl InterfaceFeats { monado: false, passthru: false, whisper: false, + swipe_to_type: false, } } } From 3eefb28848549e77aebc813068ccb47ab3d29a03 Mon Sep 17 00:00:00 2001 From: oneshinyboi Date: Sat, 15 Aug 2026 22:57:18 -0500 Subject: [PATCH 30/35] separate swipe code from main keyboard code --- wayvr/src/assets/gui/keyboard.xml | 2 +- wayvr/src/overlays/keyboard/builder.rs | 182 +----------------- wayvr/src/overlays/keyboard/mod.rs | 148 ++++++-------- wayvr/src/overlays/keyboard/prediction_bar.rs | 174 +++++++++++++++++ wayvr/src/overlays/keyboard/swipe_type.rs | 76 +++++++- 5 files changed, 323 insertions(+), 259 deletions(-) create mode 100644 wayvr/src/overlays/keyboard/prediction_bar.rs diff --git a/wayvr/src/assets/gui/keyboard.xml b/wayvr/src/assets/gui/keyboard.xml index fb79613e..ec2305c0 100644 --- a/wayvr/src/assets/gui/keyboard.xml +++ b/wayvr/src/assets/gui/keyboard.xml @@ -15,7 +15,7 @@ diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index 58de9332..19a07674 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, rc::Rc, time::Duration}; +use std::{rc::Rc, time::Duration}; use crate::{ app_misc, @@ -11,9 +11,8 @@ use crate::{ subsystem::hid::XkbKeymap, windowing::backend::OverlayEventData, }; -use anyhow::{bail, Context}; +use anyhow::Context; use glam::{FloatExt, Mat4, Vec2, vec2, vec3}; -use slotmap::Key; use smallvec::{SmallVec, smallvec}; use wgui::{ animation::{Animation, AnimationEasing}, @@ -31,9 +30,6 @@ use wgui::{ sprite::WidgetSprite, }, }; -use wgui::event::StyleSetRequest; -use wgui::layout::LayoutTask; -use wgui::taffy::Display; #[cfg(feature = "swipe-to-type")] use wlx_common::data_dir; use super::{KeyButtonData, KeyState, KeyboardState, handle_press, handle_release, layout::{self, KeyCapType}, handle_mouse_motion}; @@ -42,152 +38,13 @@ use super::init_swipe_type_manager; const PIXELS_PER_UNIT: f32 = 60.; -fn new_doc_params(panel: &mut GuiPanel) -> ParseDocumentParams<'static> { +pub(super) fn new_doc_params(panel: &mut GuiPanel) -> ParseDocumentParams<'static> { ParseDocumentParams { globals: panel.layout.state.globals.clone(), path: AssetPath::FileOrBuiltIn("gui/keyboard.xml"), extra: panel.doc_extra.take().unwrap_or_default(), } } - -pub(super) fn update_swipe_prediction_bar( - panel: &mut GuiPanel, - app: &mut AppState -) -> anyhow::Result { - let mut elements_changed = false; - - let anim_mult = app.wgui_theme.animation_mult; - - if let Some(recv) = panel.state.swipe_candidate_receiver.as_mut() - && let Ok(candidates) = recv.try_recv() { - - let predictions_root = panel.parser_state - .get_widget_id("swipe_predictions_root") - .unwrap_or_default(); - - if predictions_root.is_null() { - return Ok(elements_changed) - } - let doc_params = new_doc_params(panel); - - panel.layout.remove_children(predictions_root); - - let Some(new_suggestions) = candidates else { - return Ok(elements_changed) - }; - - let mut iter = new_suggestions.iter(); - let Some(best_prediction) = iter.next() else { - bail!("not enough swipe predictions"); - }; - if let Some(manager) = panel.state.swipe_typing_manager.as_mut() { - manager.select_word(best_prediction, app, panel.state.modifiers); - } - for (i, prediction) in iter.enumerate() { - let mut params = HashMap::new(); - let id: Rc = Rc::from(format!("Prediction-{i}")); - params.insert("id".into(), id.clone()); - params.insert("text".into(), prediction.clone().into()); - - panel.parser_state.instantiate_template( - &doc_params, - "KeyPrediction", - &mut panel.layout, - predictions_root, - TemplateParams::from_hashmap(params) - )?; - - if let Ok(widget_id) = panel.parser_state.get_widget_id(&id) { - let key_state = { - let rect = panel - .layout - .state - .widgets - .get_as::(widget_id) - .unwrap(); // want panic - - Rc::new(KeyState { - // fake button state just so we get key state for anims - button_state: KeyButtonData::Modifier { - modifier: 0, - sticky: core::cell::Cell::new(false), - }, - color: rect.params.color, - color2: rect.params.color2, - base_border_color: rect.params.border_color, - cur_border_color: rect.params.border_color.into(), - border: rect.params.border, - drawn_state: false.into(), - labels: Default::default(), - sprites: Default::default(), - }) - }; - panel.add_event_listener( - widget_id, - EventListenerKind::MousePress, - Box::new({ - let k = key_state.clone(); - let pred = prediction.clone(); - move |common, data, app, state| { - if let Some(manager) = state.swipe_typing_manager.as_mut() { - manager.select_alternate_prediction(&pred, app, state.modifiers); - on_press_anim(k.clone(), common, data) - } - Ok(EventResult::Pass) - } - }) - ); - panel.add_event_listener( - widget_id, - EventListenerKind::MouseEnter, - Box::new({ - let k = key_state.clone(); - move |common, data, _app, _state| { - on_enter_anim( - k.clone(), - common, - data, - anim_mult, - 0.0, - ); - Ok(EventResult::Pass) - } - }) - ); - panel.add_event_listener( - widget_id, - EventListenerKind::MouseLeave, - Box::new({ - let k = key_state.clone(); - move |common, data, _app, _state | { - on_leave_anim( - k.clone(), - common, - data, - anim_mult, - 0.0, - ); - Ok(EventResult::Pass) - } - }) - ); - panel.add_event_listener( - widget_id, - EventListenerKind::MouseRelease, - Box::new({ - let k = key_state.clone(); - move |common, data, _app, _state| { - on_release_anim(k.clone(), common, data); - Ok(EventResult::Pass) - } - }) - ); - } - } - elements_changed = true; - } - Ok(elements_changed) -} fn bool_to_rc_str(val: bool) -> Rc { if val { "1" } else { "0" }.into() } @@ -489,34 +346,13 @@ pub(super) fn create_keyboard_panel( panel.state.swipe_typing_manager = None; panel.state.swipe_candidate_receiver = None; - let predictions_root = panel.parser_state - .get_widget_id("swipe_predictions_root") - .unwrap_or_default(); - - if !predictions_root.is_null() { - panel.layout.remove_children(predictions_root); - - panel.layout.tasks.push(LayoutTask::SetWidgetStyle( - predictions_root, - StyleSetRequest::Display(Display::None), - )); - - } + super::prediction_bar::set_visible(panel, false); } if app.session.config.keyboard_swipe_to_type_enabled && panel.state.swipe_typing_manager.is_none() { #[cfg(feature = "swipe-to-type")] init_swipe_type_manager(&mut panel.state, data_dir::get_path("swipe_type").join("en.tar")); - let predictions_root = panel.parser_state - .get_widget_id("swipe_predictions_root") - .unwrap_or_default(); - - if !predictions_root.is_null() { - panel.layout.tasks.push(LayoutTask::SetWidgetStyle( - predictions_root, - StyleSetRequest::Display(Display::Flex), - )); - } + super::prediction_bar::set_visible(panel, true); } } @@ -612,7 +448,7 @@ fn set_anim_color( rect.params.border = key_state.border.lerp(key_state.border * 1.5, pos); } -fn on_enter_anim( +pub(super) fn on_enter_anim( key_state: Rc, common: &mut event::CallbackDataCommon, data: &event::CallbackData, @@ -660,7 +496,7 @@ fn on_enter_anim( )); } -fn on_leave_anim( +pub(super) fn on_leave_anim( key_state: Rc, common: &mut event::CallbackDataCommon, data: &event::CallbackData, @@ -710,7 +546,7 @@ fn on_leave_anim( )); } -fn on_press_anim( +pub(super) fn on_press_anim( key_state: Rc, common: &mut event::CallbackDataCommon, data: &mut event::CallbackData, @@ -725,7 +561,7 @@ fn on_press_anim( key_state.drawn_state.set(true); } -fn on_release_anim( +pub(super) fn on_release_anim( key_state: Rc, common: &mut event::CallbackDataCommon, data: &mut event::CallbackData, diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index c549006b..0885f45d 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -32,7 +32,7 @@ use crate::{ use anyhow::Context; use glam::{Affine3A, Quat, Vec3, vec3, Vec2}; use regex::Regex; -use slotmap::{SlotMap, new_key_type, Key}; +use slotmap::{SlotMap, new_key_type}; use smallvec::SmallVec; use wgui::{ color::WguiColor, @@ -40,10 +40,7 @@ use wgui::{ i18n::Translation, layout::WidgetID, }; -use wgui::event::{DeviceBitmask, StyleSetRequest}; -use wgui::layout::{LayoutTask}; -use wgui::parser::Fetchable; -use wgui::taffy::Display; +use wgui::event::DeviceBitmask; use wlx_common::windowing::{OverlayWindowState, Positioning}; use wlx_common::{ config::AltModifier, @@ -51,12 +48,12 @@ use wlx_common::{ }; #[cfg(feature = "swipe-to-type")] use wlx_common::data_dir; -use crate::overlays::keyboard::builder::update_swipe_prediction_bar; use crate::overlays::keyboard::layout::KeyCapType; use crate::overlays::keyboard::swipe_type::SwipeTypingManager; pub mod builder; mod layout; +mod prediction_bar; #[cfg(feature = "swipe-to-type")] mod swipe_type; @@ -69,6 +66,7 @@ mod swipe_type { pub struct SwipeTypingManager; + #[allow(dead_code)] // stub used only to satisfy compilation when the feature is disabled impl SwipeTypingManager { pub fn new(_model_folder: std::path::PathBuf) -> anyhow::Result<(Self, Receiver>>)> { Ok((Self, std::sync::mpsc::sync_channel(1).1)) @@ -79,6 +77,9 @@ mod swipe_type { pub fn did_swipe_leave_first_key(&self) -> bool { false } pub fn is_current_swipe_empty(&self) -> bool { true } pub fn current_swipe_mouse_button_index(&self) -> Option { None } + pub fn handle_key_press(&mut self, _key_cap_type: &super::layout::KeyCapType, _within_key_pos: &Option, _key_label: &[String], _device: DeviceBitmask, _index: MouseButtonIndex) -> super::KeyPressOutcome { super::KeyPressOutcome::Dispatch } + pub fn handle_key_motion(&mut self, _key_cap_type: &super::layout::KeyCapType, _within_key_pos: &Option, _key_label: &[String], _device: DeviceBitmask) {} + pub fn handle_key_release(&mut self, _key_cap_type: &super::layout::KeyCapType, _alt_modifier: crate::subsystem::hid::KeyModifier) -> super::KeyReleaseOutcome { super::KeyReleaseOutcome::Normal } pub fn select_word(&mut self, _word: &String, _app: &mut crate::state::AppState, _original_keyboard_mods: crate::subsystem::hid::KeyModifier) {} pub fn select_alternate_prediction(&mut self, _word: &String, _app: &mut crate::state::AppState, _original_keyboard_mods: crate::subsystem::hid::KeyModifier) {} } @@ -86,6 +87,26 @@ mod swipe_type { pub const KEYBOARD_NAME: &str = "kbd"; const AUTO_RELEASE_MODS: [KeyModifier; 5] = [SHIFT, CTRL, ALT, SUPER, ALTGR]; + +/// Outcome of handling a key press (or motion) against the swipe-to-type engine. +#[allow(dead_code)] // `Consumed` only constructed with `swipe-to-type` feature enabled +pub enum KeyPressOutcome { + /// The event was fed into swipe tracking. Do not dispatch a key. + Consumed, + /// Not a swipe target. Dispatch as a normal key event. + Dispatch, +} + +/// Outcome of handling a key release against the swipe-to-type engine. +#[allow(dead_code)] // `Predict`/`TapKey` only constructed with `swipe-to-type` feature enabled +pub enum KeyReleaseOutcome { + /// A swipe left the first key; prediction was already triggered. Nothing to dispatch. + Predict, + /// Pressed and released on the same key. Dispatch a tap with the given modifier. + TapKey { modifier: KeyModifier }, + /// Not a swipe target. Dispatch a normal key-up. + Normal, +} const SYSTEM_LAYOUT_ALIASES: [&str; 5] = ["mozc", "pinyin", "hangul", "sayura", "unikey"]; pub fn create_keyboard(app: &mut AppState) -> anyhow::Result { @@ -164,18 +185,6 @@ pub(self) fn init_swipe_type_manager(state: &mut KeyboardState, model_path: std: } }; } -#[cfg(feature = "swipe-to-type")] -pub(self) fn hide_swipe_type_manager(panel: &mut GuiPanel) { - let predictions_root = panel.parser_state - .get_widget_id("swipe_predictions_root") - .unwrap_or_default(); - if !predictions_root.is_null() { - panel.layout.tasks.push(LayoutTask::SetWidgetStyle( - predictions_root, - StyleSetRequest::Display(Display::None), - )); - } -} const fn alt_modifier_to_key(m: AltModifier) -> KeyModifier { match m { AltModifier::Shift => SHIFT, @@ -220,8 +229,7 @@ impl KeyboardBackend { create_keyboard_panel(app, keymap, state, &self.wlx_layout)?; if !app.session.config.keyboard_swipe_to_type_enabled { - #[cfg(feature = "swipe-to-type")] - hide_swipe_type_manager(&mut panel); + prediction_bar::set_visible(&mut panel, false); } let id = self.layout_panels.insert(panel); @@ -280,11 +288,12 @@ impl KeyboardBackend { .state = state_from; if !app.session.config.keyboard_swipe_to_type_enabled { - #[cfg(feature = "swipe-to-type")] - hide_swipe_type_manager(self.layout_panels - .get_mut(self.active_layout) - .unwrap() - ) + prediction_bar::set_visible( + self.layout_panels + .get_mut(self.active_layout) + .unwrap(), + false, + ); } } @@ -320,7 +329,7 @@ impl KeyboardBackend { } fn update_swipe_prediction_bar(&mut self, app: &mut AppState) -> anyhow::Result<()> { - if update_swipe_prediction_bar(self.panel(), app)? { + if prediction_bar::update(self.panel(), app)? { self.panel().process_custom_elems(app); } Ok(()) @@ -578,27 +587,11 @@ fn handle_mouse_motion( within_key_pos: &Option, device: DeviceBitmask ) { - if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() - && matches!(*key_cap_type, KeyCapType::Letter | KeyCapType::LetterAltGr) + if let KeyButtonData::Key { .. } = &key.button_state + && let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() { - if !swipe_manager.is_current_swipe_empty() { - match &key.button_state { - KeyButtonData::Key { vk, pressed } => { - if let Some(pos) = within_key_pos { - // check because mouse motion can trigger despite hover being false - if pos.x >= 0.0 && pos.x <= 1.0 && pos.y >= 0.0 && pos.y <= 1.0 { - - if let Some(label) = key_label.first() { - swipe_manager.add_swipe(pos, label.chars().next().unwrap_or_default(), device, None); - } - } - } - } - _ => {} - } - } + swipe_manager.handle_key_motion(key_cap_type, within_key_pos, key_label, device); } - } fn handle_press( app: &mut AppState, @@ -612,16 +605,14 @@ fn handle_press( ) { match &key.button_state { KeyButtonData::Key { vk, pressed } => { - if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() - && matches!(*key_cap_type, KeyCapType::Letter | KeyCapType::LetterAltGr) - { - if let Some(pos) = within_key_pos { - if let Some(label) = key_label.first() { - swipe_manager.add_swipe(pos, label.chars().next().unwrap_or_default(), device, Some(button.index)); - } + let outcome = match keyboard.swipe_typing_manager.as_mut() { + Some(swipe_manager) => { + swipe_manager.handle_key_press(key_cap_type, within_key_pos, key_label, device, button.index) } - } - else { + None => KeyPressOutcome::Dispatch, + }; + + if matches!(outcome, KeyPressOutcome::Dispatch) { keyboard.modifiers |= match button.index { MouseButtonIndex::Right => SHIFT, MouseButtonIndex::Middle => keyboard.alt_modifier, @@ -683,25 +674,17 @@ fn handle_press( fn handle_release(app: &mut AppState, key: &KeyState, k_cap_type: &KeyCapType, keyboard: &mut KeyboardState) -> bool { match &key.button_state { KeyButtonData::Key { vk, pressed } => { - if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() - && matches!(*k_cap_type, KeyCapType::Letter | KeyCapType::LetterAltGr) - { - if swipe_manager.did_swipe_leave_first_key() { - match swipe_manager.predict() { - Ok(()) => {}, - Err(e) => { - log::error!("{}", e) - } - } + let outcome = match keyboard.swipe_typing_manager.as_mut() { + Some(swipe_manager) => { + swipe_manager.handle_key_release(k_cap_type, keyboard.alt_modifier) } - else { // pointer must have been released on the same key it was pressed on - keyboard.modifiers |= match swipe_manager.current_swipe_mouse_button_index() { - Some(MouseButtonIndex::Right) => SHIFT, - Some(MouseButtonIndex::Middle) => keyboard.alt_modifier, - _ => 0, - }; - swipe_manager.reset(); // drop swipe tracking that was started on press + None => KeyReleaseOutcome::Normal, + }; + match outcome { + KeyReleaseOutcome::Predict => {} + KeyReleaseOutcome::TapKey { modifier } => { + keyboard.modifiers |= modifier; app.hid_provider .set_modifiers_routed(app.wvr_server.as_mut(), keyboard.modifiers); app.hid_provider @@ -717,22 +700,19 @@ fn handle_release(app: &mut AppState, key: &KeyState, k_cap_type: &KeyCapType, k } play_key_click(app); } - } - else { - if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() { - swipe_manager.reset(); - } - pressed.set(false); + KeyReleaseOutcome::Normal => { + pressed.set(false); - for m in &AUTO_RELEASE_MODS { - if keyboard.modifiers & *m != 0 { - keyboard.modifiers &= !*m; + for m in &AUTO_RELEASE_MODS { + if keyboard.modifiers & *m != 0 { + keyboard.modifiers &= !*m; + } } + app.hid_provider + .send_key_routed(app.wvr_server.as_mut(), *vk, false); + app.hid_provider + .set_modifiers_routed(app.wvr_server.as_mut(), keyboard.modifiers); } - app.hid_provider - .send_key_routed(app.wvr_server.as_mut(), *vk, false); - app.hid_provider - .set_modifiers_routed(app.wvr_server.as_mut(), keyboard.modifiers); } true } diff --git a/wayvr/src/overlays/keyboard/prediction_bar.rs b/wayvr/src/overlays/keyboard/prediction_bar.rs new file mode 100644 index 00000000..29f0d21a --- /dev/null +++ b/wayvr/src/overlays/keyboard/prediction_bar.rs @@ -0,0 +1,174 @@ +use std::{collections::HashMap, rc::Rc}; + +use crate::{ + gui::panel::GuiPanel, + state::AppState, +}; +use anyhow::bail; +use slotmap::Key; +use super::{ + KeyButtonData, KeyState, KeyboardState, + builder::{ + new_doc_params, on_enter_anim, on_leave_anim, on_press_anim, on_release_anim, + }, +}; +use wgui::{ + event::EventListenerKind, + layout::LayoutTask, + parser::{Fetchable, TemplateParams}, + taffy::Display, + widget::{EventResult, rectangle::WidgetRectangle}, +}; +use wgui::event::StyleSetRequest; + +/// Root widget that hosts the swipe-to-type prediction candidates. +pub(super) const ROOT: &str = "swipe_predictions_root"; + +/// Render the prediction candidate bar from the latest received predictions. +pub(super) fn update( + panel: &mut GuiPanel, + app: &mut AppState, +) -> anyhow::Result { + let mut elements_changed = false; + + let anim_mult = app.wgui_theme.animation_mult; + + if let Some(recv) = panel.state.swipe_candidate_receiver.as_mut() + && let Ok(candidates) = recv.try_recv() + { + let predictions_root = panel + .parser_state + .get_widget_id(ROOT) + .unwrap_or_default(); + + if predictions_root.is_null() { + return Ok(elements_changed); + } + let doc_params = new_doc_params(panel); + + panel.layout.remove_children(predictions_root); + + let Some(new_suggestions) = candidates else { + return Ok(elements_changed); + }; + + let mut iter = new_suggestions.iter(); + let Some(best_prediction) = iter.next() else { + bail!("not enough swipe predictions"); + }; + if let Some(manager) = panel.state.swipe_typing_manager.as_mut() { + manager.select_word(best_prediction, app, panel.state.modifiers); + } + for (i, prediction) in iter.enumerate() { + let mut params = HashMap::new(); + let id: Rc = Rc::from(format!("Prediction-{i}")); + params.insert("id".into(), id.clone()); + params.insert("text".into(), prediction.clone().into()); + + panel.parser_state.instantiate_template( + &doc_params, + "KeyPrediction", + &mut panel.layout, + predictions_root, + TemplateParams::from_hashmap(params), + )?; + + if let Ok(widget_id) = panel.parser_state.get_widget_id(&id) { + let key_state = { + let rect = panel + .layout + .state + .widgets + .get_as::(widget_id) + .unwrap(); // want panic + + Rc::new(KeyState { + // fake button state just so we get key state for anims + button_state: KeyButtonData::Modifier { + modifier: 0, + sticky: core::cell::Cell::new(false), + }, + color: rect.params.color, + color2: rect.params.color2, + base_border_color: rect.params.border_color, + cur_border_color: rect.params.border_color.into(), + border: rect.params.border, + drawn_state: false.into(), + labels: Default::default(), + sprites: Default::default(), + }) + }; + panel.add_event_listener( + widget_id, + EventListenerKind::MousePress, + Box::new({ + let k = key_state.clone(); + let pred = prediction.clone(); + move |common, data, app, state| { + if let Some(manager) = state.swipe_typing_manager.as_mut() { + manager.select_alternate_prediction(&pred, app, state.modifiers); + on_press_anim(k.clone(), common, data); + } + Ok(EventResult::Pass) + } + }), + ); + panel.add_event_listener( + widget_id, + EventListenerKind::MouseEnter, + Box::new({ + let k = key_state.clone(); + move |common, data, _app, _state| { + on_enter_anim(k.clone(), common, data, anim_mult, 0.0); + Ok(EventResult::Pass) + } + }), + ); + panel.add_event_listener( + widget_id, + EventListenerKind::MouseLeave, + Box::new({ + let k = key_state.clone(); + move |common, data, _app, _state| { + on_leave_anim(k.clone(), common, data, anim_mult, 0.0); + Ok(EventResult::Pass) + } + }), + ); + panel.add_event_listener( + widget_id, + EventListenerKind::MouseRelease, + Box::new({ + let k = key_state.clone(); + move |common, data, _app, _state| { + on_release_anim(k.clone(), common, data); + Ok(EventResult::Pass) + } + }), + ); + } + } + elements_changed = true; + } + Ok(elements_changed) +} + +/// Show or hide the prediction candidate bar. +pub(super) fn set_visible(panel: &mut GuiPanel, visible: bool) { + let predictions_root = panel + .parser_state + .get_widget_id(ROOT) + .unwrap_or_default(); + if predictions_root.is_null() { + return; + } + + if !visible { + panel.layout.remove_children(predictions_root); + } + + panel.layout.tasks.push(LayoutTask::SetWidgetStyle( + predictions_root, + StyleSetRequest::Display(if visible { Display::Flex } else { Display::None }), + )); +} \ No newline at end of file diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs index c7be4769..54991f43 100644 --- a/wayvr/src/overlays/keyboard/swipe_type.rs +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -1,5 +1,5 @@ use crate::state::AppState; -use crate::subsystem::hid::{KeyModifier, VirtualKey, CTRL}; +use crate::subsystem::hid::{KeyModifier, VirtualKey, CTRL, SHIFT}; use anyhow::{bail}; use glam::Vec2; use std::mem; @@ -15,6 +15,7 @@ use wgui::log::LogErr; use crate::subsystem::clipboard; use crate::subsystem::clipboard::ClipboardProvider; use crate::subsystem::input::InputFocus; +use super::layout::KeyCapType; const PREDICTION_SUGGESTION_COUNT: usize = 5; @@ -217,6 +218,79 @@ impl SwipeTypingManager { self.current_swipe_mouse_button_index } + /// Handle a key press. Letter keys feed the swipe engine and are consumed; + /// anything else is dispatched as a normal key. + pub fn handle_key_press( + &mut self, + key_cap_type: &KeyCapType, + within_key_pos: &Option, + key_label: &[String], + device: DeviceBitmask, + index: MouseButtonIndex, + ) -> super::KeyPressOutcome { + if !matches!(key_cap_type, KeyCapType::Letter | KeyCapType::LetterAltGr) { + return super::KeyPressOutcome::Dispatch; + } + + if let Some(pos) = within_key_pos + && let Some(label) = key_label.first() + { + self.add_swipe(pos, label.chars().next().unwrap_or_default(), device, Some(index)); + } + + super::KeyPressOutcome::Consumed + } + + /// Handle pointer motion over a key while swiping. + pub fn handle_key_motion( + &mut self, + key_cap_type: &KeyCapType, + within_key_pos: &Option, + key_label: &[String], + device: DeviceBitmask, + ) { + if !self.is_current_swipe_empty() + && matches!(key_cap_type, KeyCapType::Letter | KeyCapType::LetterAltGr) + && let Some(pos) = within_key_pos + && pos.x >= 0.0 + && pos.x <= 1.0 + && pos.y >= 0.0 + && pos.y <= 1.0 + && let Some(label) = key_label.first() + { + self.add_swipe(pos, label.chars().next().unwrap_or_default(), device, None); + } + } + + /// Handle a key release. A swipe that left the first key triggers a + /// prediction; a tap on the same key dispatches the key; anything else is + /// a normal key-up. + pub fn handle_key_release( + &mut self, + key_cap_type: &KeyCapType, + alt_modifier: KeyModifier, + ) -> super::KeyReleaseOutcome { + if !matches!(key_cap_type, KeyCapType::Letter | KeyCapType::LetterAltGr) { + self.reset(); + return super::KeyReleaseOutcome::Normal; + } + + if self.did_swipe_leave_first_key() { + if let Err(e) = self.predict() { + log::error!("{}", e); + } + return super::KeyReleaseOutcome::Predict; + } + + let modifier = match self.current_swipe_mouse_button_index() { + Some(MouseButtonIndex::Right) => SHIFT, + Some(MouseButtonIndex::Middle) => alt_modifier, + _ => 0, + }; + self.reset(); + super::KeyReleaseOutcome::TapKey { modifier } + } + pub fn add_swipe(&mut self, within_key_pos_normalized: &Vec2, key_label: char, device: DeviceBitmask, index: Option) { if let Some(pos) = self.keyboard_gird.key_positions.get(&key_label.to_ascii_lowercase()) { if let Some(current_device) = self.current_swipe_device && current_device != device { From 47bd796c4833bf9545c61980c3924a3875af335c Mon Sep 17 00:00:00 2001 From: oneshinyboi Date: Sat, 15 Aug 2026 22:59:44 -0500 Subject: [PATCH 31/35] tweaks --- wayvr/src/overlays/keyboard/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index 0885f45d..c21e7dd3 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -89,7 +89,7 @@ pub const KEYBOARD_NAME: &str = "kbd"; const AUTO_RELEASE_MODS: [KeyModifier; 5] = [SHIFT, CTRL, ALT, SUPER, ALTGR]; /// Outcome of handling a key press (or motion) against the swipe-to-type engine. -#[allow(dead_code)] // `Consumed` only constructed with `swipe-to-type` feature enabled +#[allow(dead_code)] pub enum KeyPressOutcome { /// The event was fed into swipe tracking. Do not dispatch a key. Consumed, @@ -98,7 +98,7 @@ pub enum KeyPressOutcome { } /// Outcome of handling a key release against the swipe-to-type engine. -#[allow(dead_code)] // `Predict`/`TapKey` only constructed with `swipe-to-type` feature enabled +#[allow(dead_code)] pub enum KeyReleaseOutcome { /// A swipe left the first key; prediction was already triggered. Nothing to dispatch. Predict, From 2305461f26c5a93b10842c13d5b2f469c735aada Mon Sep 17 00:00:00 2001 From: oneshinyboi Date: Sun, 16 Aug 2026 00:10:34 -0500 Subject: [PATCH 32/35] fix lang --- dash-frontend/assets/lang/en.json | 13 +++++-------- dash-frontend/src/tab/settings/mod.rs | 1 + dash-frontend/src/tab/settings/tab_features.rs | 6 +++--- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index 84f5289f..f271f236 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -242,16 +242,13 @@ "WHISPER_MODEL": "Speech-to-text model", "WHISPER_MODEL_HELP": "Whisper model to use.\nLarger models produce more accurate\nresults, but consume more VRAM.", "SWIPE_TYPE": { - "NEED_TO_DOWNLOAD_MODELS": "The swipe-to-type model needs to be downloaded.\nDownload it now?", - "MODELS_DOWNLOADED": "Model downloaded", - "MODELS_NOT_DOWNLOADED": "Model not downloaded", + "NEED_TO_DOWNLOAD_MODEL": "The swipe-to-type model needs to be downloaded.\nDownload it now?", "MODEL_REQUIRED_TO_ENABLE": "Download the swipe-to-type model to enable this option.", - "REMOVE_MODELS": "Remove swipe to type model", - "DOWNLOAD_MODELS": "Download swipe to type model", - "DOWNLOAD_MODELS_HELP": "Swipe to type only works on qwerty layout keyboards" + "REMOVE_MODEL": "Remove swipe to type model", + "REMOVE_MODEL_HELP": "Deletes the swipe to type model", + "DOWNLOAD_MODEL": "Download swipe to type model", + "DOWNLOAD_MODEL_HELP": "Swipe to type only works on qwerty layout keyboards" }, - "SWIPE_TYPE_MODELS": "Swipe-to-type models", - "SWIPE_TYPE_MODELS_HELP": "Model files used by swipe-to-type.\nAll of them are required.", "GRID_OPACITY": "Floor grid opacity", "GRID_OPACITY_HELP": "Opacity of the floor grid when the skymap is enabled", "XWAYLAND_BY_DEFAULT": "Run apps in Compatibility mode by default", diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs index be96c488..772a5795 100644 --- a/dash-frontend/src/tab/settings/mod.rs +++ b/dash-frontend/src/tab/settings/mod.rs @@ -620,6 +620,7 @@ impl SettingType { Self::InputCaptureMethod => Some("APP_SETTINGS.INPUT_CAPTURE_METHOD_HELP"), Self::InputEmulationMethod => Some("APP_SETTINGS.INPUT_EMULATION_METHOD_HELP"), Self::KeyboardMiddleClick => Some("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK_HELP"), + Self::KeyboardSwipeToTypeEnabled => Some("APP_SETTINGS.KEYBOARD_SWIPE_TO_TYPE_ENABLED_HELP"), Self::LeftHandedMouse => Some("APP_SETTINGS.LEFT_HANDED_MOUSE_HELP"), Self::MouseAcceleration => Some("APP_SETTINGS.POINTER_ACCELERATION_HELP"), Self::ScreenRenderDown => Some("APP_SETTINGS.SCREEN_RENDER_DOWN_HELP"), diff --git a/dash-frontend/src/tab/settings/tab_features.rs b/dash-frontend/src/tab/settings/tab_features.rs index 4bb1dde0..03d0f3f6 100644 --- a/dash-frontend/src/tab/settings/tab_features.rs +++ b/dash-frontend/src/tab/settings/tab_features.rs @@ -314,7 +314,7 @@ impl State { self.frontend_tasks.clone(), views::dialog_box::Params { globals: self.globals.clone(), - message: Translation::from_translation_key("APP_SETTINGS.SWIPE_TYPE.NEED_TO_DOWNLOAD_MODELS"), + message: Translation::from_translation_key("APP_SETTINGS.SWIPE_TYPE.NEED_TO_DOWNLOAD_MODEL"), entries: vec![ views::dialog_box::ButtonEntry { content: Translation::from_translation_key("APP_SETTINGS.CANCEL"), @@ -469,13 +469,13 @@ fn swipe_type_models_button(mp: &mut MacroParams, parent: WidgetID) -> anyhow::R let all_downloaded = swwipe_type_model_downloaded().unwrap_or_default(); let (translation, icon, action) = if all_downloaded { ( - "APP_SETTINGS.SWIPE_TYPE.REMOVE_MODELS", + "APP_SETTINGS.SWIPE_TYPE.REMOVE_MODEL", "dashboard/trash.svg", "swipe_type_remove", ) } else { ( - "APP_SETTINGS.SWIPE_TYPE.DOWNLOAD_MODELS", + "APP_SETTINGS.SWIPE_TYPE.DOWNLOAD_MODEL", "dashboard/download.svg", "swipe_type_download", ) From 7d9f1fedeb232110d9518cfbb76b8f57e949c131 Mon Sep 17 00:00:00 2001 From: oneshinyboi Date: Sun, 16 Aug 2026 00:15:47 -0500 Subject: [PATCH 33/35] update translations --- dash-frontend/assets/lang/de.json | 10 ++++++++++ dash-frontend/assets/lang/es.json | 10 ++++++++++ dash-frontend/assets/lang/it.json | 10 ++++++++++ dash-frontend/assets/lang/ja.json | 10 ++++++++++ dash-frontend/assets/lang/pl.json | 10 ++++++++++ dash-frontend/assets/lang/zh_CN.json | 10 ++++++++++ 6 files changed, 60 insertions(+) diff --git a/dash-frontend/assets/lang/de.json b/dash-frontend/assets/lang/de.json index a8f282a2..d5e48df3 100644 --- a/dash-frontend/assets/lang/de.json +++ b/dash-frontend/assets/lang/de.json @@ -149,6 +149,8 @@ "KEYBOARD_MIDDLE_CLICK": "Tastatur-Mittelklick", "KEYBOARD_MIDDLE_CLICK_HELP": "Modifier-Taste bei der Eingabe\nmit dem lila Laser", "KEYBOARD_SOUND_ENABLED": "Tastatursounds", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED": "Wischen zum Tippen", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED_HELP": "Funktioniert nur für Englisch! Die Ergebnisse sind auf anderen Tastaturen als QWERTY deutlich schlechter.", "LANGUAGE": "Sprache", "LEFT_HANDED_MOUSE": "Linkshänder-Maus", "LEFT_HANDED_MOUSE_HELP": "Nutze dies, wenn die Maustasten vertauscht sind", @@ -239,6 +241,14 @@ }, "WHISPER_MODEL": "Sprache-zu-Text-Modell", "WHISPER_MODEL_HELP": "Zu verwendendes Whisper-Modell.\nGrößere Modelle liefern präzisere\nErgebnisse, verbrauchen aber mehr VRAM.", + "SWIPE_TYPE": { + "NEED_TO_DOWNLOAD_MODEL": "Das Wisch-Tipp-Modell muss heruntergeladen werden.\nJetzt herunterladen?", + "MODEL_REQUIRED_TO_ENABLE": "Lade das Wisch-Tipp-Modell herunter, um diese Option zu aktivieren.", + "REMOVE_MODEL": "Wisch-Tipp-Modell entfernen", + "REMOVE_MODEL_HELP": "Löscht das Wisch-Tipp-Modell", + "DOWNLOAD_MODEL": "Wisch-Tipp-Modell herunterladen", + "DOWNLOAD_MODEL_HELP": "Wischen zum Tippen funktioniert nur auf Tastaturen mit QWERTY-Layout" + }, "GRID_OPACITY": "Bodenraster-Deckkraft", "GRID_OPACITY_HELP": "Deckkraft des Bodengitters, wenn die Skymap aktiviert ist", "XWAYLAND_BY_DEFAULT": "Apps standardmäßig im Kompatibilitätsmodus starten", diff --git a/dash-frontend/assets/lang/es.json b/dash-frontend/assets/lang/es.json index 6fea379a..402aaccf 100644 --- a/dash-frontend/assets/lang/es.json +++ b/dash-frontend/assets/lang/es.json @@ -149,6 +149,8 @@ "KEYBOARD_MIDDLE_CLICK": "Clic central del teclado", "KEYBOARD_MIDDLE_CLICK_HELP": "Modificador al escribir\ncon el láser púrpura", "KEYBOARD_SOUND_ENABLED": "Sonidos del teclado", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED": "Deslizar para escribir", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED_HELP": "¡Solo funciona en inglés! Los resultados serán mucho peores en teclados que no sean QWERTY.", "LANGUAGE": "Idioma", "LEFT_HANDED_MOUSE": "Mouse para zurdos", "LEFT_HANDED_MOUSE_HELP": "Úsalo si los botones del ratón están invertidos", @@ -239,6 +241,14 @@ }, "WHISPER_MODEL": "Modelo de dictado", "WHISPER_MODEL_HELP": "Modelo de Whisper a utilizar.\nLos modelos más grandes dan resultados\nmás precisos, pero consumen más VRAM.", + "SWIPE_TYPE": { + "NEED_TO_DOWNLOAD_MODEL": "El modelo de deslizar para escribir debe descargarse.\n¿Descargarlo ahora?", + "MODEL_REQUIRED_TO_ENABLE": "Descarga el modelo de deslizar para escribir para habilitar esta opción.", + "REMOVE_MODEL": "Eliminar el modelo de deslizar para escribir", + "REMOVE_MODEL_HELP": "Borra el modelo de deslizar para escribir", + "DOWNLOAD_MODEL": "Descargar el modelo de deslizar para escribir", + "DOWNLOAD_MODEL_HELP": "Deslizar para escribir solo funciona en teclados con distribución QWERTY" + }, "GRID_OPACITY": "Opacidad de la rejilla del suelo", "GRID_OPACITY_HELP": "Opacidad de la rejilla del suelo cuando el skymap está activado", "XWAYLAND_BY_DEFAULT": "Ejecutar apps en modo de compatibilidad por defecto", diff --git a/dash-frontend/assets/lang/it.json b/dash-frontend/assets/lang/it.json index 4fffe6f5..491fe1a0 100644 --- a/dash-frontend/assets/lang/it.json +++ b/dash-frontend/assets/lang/it.json @@ -149,6 +149,8 @@ "KEYBOARD_MIDDLE_CLICK": "Tasto centrale tastiera", "KEYBOARD_MIDDLE_CLICK_HELP": "Tasto modificatore da usare quando si scrive\ncon il laser viola", "KEYBOARD_SOUND_ENABLED": "Suoni tastiera", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED": "Scorri per digitare", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED_HELP": "Funziona solo con l'inglese! I risultati saranno drasticamente peggiori su tastiere non QWERTY.", "LANGUAGE": "Lingua", "LEFT_HANDED_MOUSE": "Mouse per mancini", "LEFT_HANDED_MOUSE_HELP": "Usalo se i tasti del mouse sono invertiti", @@ -239,6 +241,14 @@ }, "WHISPER_MODEL": "Modello speech-to-text", "WHISPER_MODEL_HELP": "Modello Whisper da utilizzare.\nModelli più grandi offrono risultati\npiù precisi, ma consumano più VRAM.", + "SWIPE_TYPE": { + "NEED_TO_DOWNLOAD_MODEL": "Il modello per scorri per digitare deve essere scaricato.\nScaricarlo ora?", + "MODEL_REQUIRED_TO_ENABLE": "Scarica il modello per scorri per digitare per abilitare questa opzione.", + "REMOVE_MODEL": "Rimuovi il modello per scorri per digitare", + "REMOVE_MODEL_HELP": "Elimina il modello per scorri per digitare", + "DOWNLOAD_MODEL": "Scarica il modello per scorri per digitare", + "DOWNLOAD_MODEL_HELP": "Scorri per digitare funziona solo su tastiere con layout QWERTY" + }, "GRID_OPACITY": "Opacità griglia pavimento", "GRID_OPACITY_HELP": "Opacità della griglia del pavimento quando lo skymap è attivo", "XWAYLAND_BY_DEFAULT": "Avvia le app in modalità compatibilità per impostazione predefinita", diff --git a/dash-frontend/assets/lang/ja.json b/dash-frontend/assets/lang/ja.json index 4aa9e508..72c937de 100644 --- a/dash-frontend/assets/lang/ja.json +++ b/dash-frontend/assets/lang/ja.json @@ -149,6 +149,8 @@ "KEYBOARD_MIDDLE_CLICK": "キーボード中クリック", "KEYBOARD_MIDDLE_CLICK_HELP": "紫色のレーザーで\n入力する際の修飾キー", "KEYBOARD_SOUND_ENABLED": "キーボード入力音", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED": "スワイプ入力", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED_HELP": "英語のみで動作します!QWERTY以外のキーボードでは結果が大幅に悪くなります。", "LANGUAGE": "言語", "LEFT_HANDED_MOUSE": "左利きマウス", "LEFT_HANDED_MOUSE_HELP": "マウスボタンを入れ替えて使用する場合にチェック", @@ -239,6 +241,14 @@ }, "WHISPER_MODEL": "音声認識モデル", "WHISPER_MODEL_HELP": "使用するWhisperモデルを選択。\nモデルが大きいほど精度は向上するが、\nVRAMの消費量が増える。", + "SWIPE_TYPE": { + "NEED_TO_DOWNLOAD_MODEL": "スワイプ入力モデルをダウンロードする必要があります。\n今すぐダウンロードしますか?", + "MODEL_REQUIRED_TO_ENABLE": "このオプションを有効にするには、スワイプ入力モデルをダウンロードしてください。", + "REMOVE_MODEL": "スワイプ入力モデルを削除", + "REMOVE_MODEL_HELP": "スワイプ入力モデルを削除します", + "DOWNLOAD_MODEL": "スワイプ入力モデルをダウンロード", + "DOWNLOAD_MODEL_HELP": "スワイプ入力はQWERTYレイアウトのキーボードでのみ動作します" + }, "GRID_OPACITY": "フロアグリッドの不透明度", "GRID_OPACITY_HELP": "スカイマップ有効時のフロアグリッドの不透明度", "XWAYLAND_BY_DEFAULT": "デフォルトで互換モードでアプリを起動", diff --git a/dash-frontend/assets/lang/pl.json b/dash-frontend/assets/lang/pl.json index a0c382b6..d4aeef96 100644 --- a/dash-frontend/assets/lang/pl.json +++ b/dash-frontend/assets/lang/pl.json @@ -149,6 +149,8 @@ "KEYBOARD_MIDDLE_CLICK": "Środkowy przycisk myszy (klawiatura)", "KEYBOARD_MIDDLE_CLICK_HELP": "Modyfikator używany podczas\npisania fioletowym laserem", "KEYBOARD_SOUND_ENABLED": "Dźwięki klawiatury", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED": "Przesuwaj, aby pisać", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED_HELP": "Działa tylko dla języka angielskiego! Wyniki będą znacznie gorsze na klawiaturach innych niż QWERTY.", "LANGUAGE": "Język", "LEFT_HANDED_MOUSE": "Mysz dla leworęcznych", "LEFT_HANDED_MOUSE_HELP": "Użyj, jeśli przyciski myszy są zamienione", @@ -239,6 +241,14 @@ }, "WHISPER_MODEL": "Model mowy na tekst", "WHISPER_MODEL_HELP": "Model Whisper do użycia.\nWiększe modele dają dokładniejsze\nwyniki, ale zużywają więcej pamięci VRAM.", + "SWIPE_TYPE": { + "NEED_TO_DOWNLOAD_MODEL": "Model do pisania przesuwaniem musi zostać pobrany.\nPobrać go teraz?", + "MODEL_REQUIRED_TO_ENABLE": "Pobierz model do pisania przesuwaniem, aby włączyć tę opcję.", + "REMOVE_MODEL": "Usuń model do pisania przesuwaniem", + "REMOVE_MODEL_HELP": "Usuwa model do pisania przesuwaniem", + "DOWNLOAD_MODEL": "Pobierz model do pisania przesuwaniem", + "DOWNLOAD_MODEL_HELP": "Pisanie przesuwaniem działa tylko na klawiaturach QWERTY" + }, "GRID_OPACITY": "Przezroczystość siatki podłogi", "GRID_OPACITY_HELP": "Przezroczystość siatki podłogi przy włączonym Skymapie", "XWAYLAND_BY_DEFAULT": "Uruchamiaj aplikacje domyślnie w trybie kompatybilności", diff --git a/dash-frontend/assets/lang/zh_CN.json b/dash-frontend/assets/lang/zh_CN.json index 94862c4d..d947f5cd 100644 --- a/dash-frontend/assets/lang/zh_CN.json +++ b/dash-frontend/assets/lang/zh_CN.json @@ -149,6 +149,8 @@ "KEYBOARD_MIDDLE_CLICK": "键盘鼠标中键", "KEYBOARD_MIDDLE_CLICK_HELP": "使用紫色激光输入时的\n修饰键", "KEYBOARD_SOUND_ENABLED": "键盘声音", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED": "滑动输入", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED_HELP": "仅支持英语!在非 QWERTY 键盘上效果会明显变差。", "LANGUAGE": "语言", "LEFT_HANDED_MOUSE": "左手鼠标模式", "LEFT_HANDED_MOUSE_HELP": "若鼠标按键已互换,请启用此项", @@ -239,6 +241,14 @@ }, "WHISPER_MODEL": "语音转文本模型", "WHISPER_MODEL_HELP": "所使用的 Whisper 模型。\n模型越大,结果越准确,\n但占用的显存 (VRAM) 越多。", + "SWIPE_TYPE": { + "NEED_TO_DOWNLOAD_MODEL": "需要下载滑动输入模型。\n现在下载吗?", + "MODEL_REQUIRED_TO_ENABLE": "请下载滑动输入模型以启用此选项。", + "REMOVE_MODEL": "移除滑动输入模型", + "REMOVE_MODEL_HELP": "删除滑动输入模型", + "DOWNLOAD_MODEL": "下载滑动输入模型", + "DOWNLOAD_MODEL_HELP": "滑动输入仅适用于 QWERTY 布局的键盘" + }, "GRID_OPACITY": "地面网格不透明度", "GRID_OPACITY_HELP": "启用天空盒时地面网格的不透明度", "XWAYLAND_BY_DEFAULT": "默认以兼容模式运行应用程序", From d17624251add45746b4b8163fa6d880260a33a66 Mon Sep 17 00:00:00 2001 From: oneshinyboi Date: Sun, 16 Aug 2026 00:46:57 -0500 Subject: [PATCH 34/35] prevent main thread deadlock and fix xml --- wayvr/src/assets/gui/keyboard.xml | 2 +- wayvr/src/overlays/keyboard/builder.rs | 2 +- wayvr/src/overlays/keyboard/mod.rs | 32 ++++++---- wayvr/src/overlays/keyboard/prediction_bar.rs | 4 +- wayvr/src/overlays/keyboard/swipe_type.rs | 59 ++++++++++++++++--- 5 files changed, 76 insertions(+), 23 deletions(-) diff --git a/wayvr/src/assets/gui/keyboard.xml b/wayvr/src/assets/gui/keyboard.xml index ec2305c0..22f6075b 100644 --- a/wayvr/src/assets/gui/keyboard.xml +++ b/wayvr/src/assets/gui/keyboard.xml @@ -15,7 +15,7 @@ diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index 19a07674..29b968b1 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -344,7 +344,7 @@ pub(super) fn create_keyboard_panel( } if !app.session.config.keyboard_swipe_to_type_enabled { panel.state.swipe_typing_manager = None; - panel.state.swipe_candidate_receiver = None; + panel.state.swipe_candidate_slot = None; super::prediction_bar::set_visible(panel, false); } diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index c21e7dd3..b791065d 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -4,7 +4,6 @@ use std::{ process::{Child, Command}, sync::{Arc, atomic::Ordering}, }; -use std::sync::mpsc::Receiver; use crate::overlays::toast::Toast; use crate::{ @@ -49,7 +48,7 @@ use wlx_common::{ #[cfg(feature = "swipe-to-type")] use wlx_common::data_dir; use crate::overlays::keyboard::layout::KeyCapType; -use crate::overlays::keyboard::swipe_type::SwipeTypingManager; +use crate::overlays::keyboard::swipe_type::{PredictionSlot, SwipeTypingManager}; pub mod builder; mod layout; @@ -60,16 +59,29 @@ mod swipe_type; #[cfg(not(feature = "swipe-to-type"))] mod swipe_type { - use std::sync::mpsc::Receiver; use wgui::event::{DeviceBitmask, MouseButtonIndex}; use glam::Vec2; pub struct SwipeTypingManager; + #[derive(Clone, Default)] + pub struct PredictionSlot; + + #[allow(dead_code)] // stub used only to satisfy compilation when the feature is disabled + impl PredictionSlot { + pub fn new() -> Self { + Self + } + pub fn set(&self, _value: Option>) {} + pub fn take(&self) -> Option>> { + None + } + } + #[allow(dead_code)] // stub used only to satisfy compilation when the feature is disabled impl SwipeTypingManager { - pub fn new(_model_folder: std::path::PathBuf) -> anyhow::Result<(Self, Receiver>>)> { - Ok((Self, std::sync::mpsc::sync_channel(1).1)) + pub fn new(_model_folder: std::path::PathBuf) -> anyhow::Result<(Self, PredictionSlot)> { + Ok((Self, PredictionSlot)) } pub fn add_swipe(&mut self, _within_key_pos_normalized: &Vec2, _key_label: char, _device: DeviceBitmask, _index: Option) {} pub fn predict(&mut self) -> anyhow::Result<()> { Ok(()) } @@ -138,7 +150,7 @@ pub fn create_keyboard(app: &mut AppState) -> anyhow::Result anyhow::Result { + Ok((engine, slot)) => { state.swipe_typing_manager = Some(engine); - state.swipe_candidate_receiver = Some(receiver); + state.swipe_candidate_slot = Some(slot); }, Err(e) => { log::error!("Error occurred while trying to load swipe engine: {}", e); @@ -504,7 +516,7 @@ struct KeyboardState { keymap_switch_index: usize, keymap_switch_pending: bool, swipe_typing_manager: Option, - swipe_candidate_receiver: Option>>> + swipe_candidate_slot: Option } macro_rules! take_and_leave_default { @@ -528,7 +540,7 @@ impl KeyboardState { keymap_switch_index: self.keymap_switch_index, keymap_switch_pending: false, swipe_typing_manager: None, - swipe_candidate_receiver: None, + swipe_candidate_slot: None, } } } diff --git a/wayvr/src/overlays/keyboard/prediction_bar.rs b/wayvr/src/overlays/keyboard/prediction_bar.rs index 29f0d21a..4f853ed6 100644 --- a/wayvr/src/overlays/keyboard/prediction_bar.rs +++ b/wayvr/src/overlays/keyboard/prediction_bar.rs @@ -33,8 +33,8 @@ pub(super) fn update( let anim_mult = app.wgui_theme.animation_mult; - if let Some(recv) = panel.state.swipe_candidate_receiver.as_mut() - && let Ok(candidates) = recv.try_recv() + if let Some(slot) = panel.state.swipe_candidate_slot.as_mut() + && let Some(candidates) = slot.take() { let predictions_root = panel .parser_state diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs index 54991f43..b4190f30 100644 --- a/wayvr/src/overlays/keyboard/swipe_type.rs +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -4,7 +4,9 @@ use anyhow::{bail}; use glam::Vec2; use std::mem; use std::path::PathBuf; -use std::sync::mpsc::{sync_channel, Receiver, SyncSender, channel, Sender}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; +use std::sync::mpsc::{channel, Sender}; use std::thread::{self, JoinHandle}; use std::time::Instant; use super_swipe_type::keyboard_manager::QwertyKeyboardGrid; @@ -26,10 +28,49 @@ enum PredictionTask { }, Shutdown, } +#[derive(Clone)] +pub struct PredictionSlot(Arc); + +struct PredictionSlotInner { + present: AtomicBool, + value: Mutex>>, +} + +impl PredictionSlot { + pub fn new() -> Self { + Self(Arc::new(PredictionSlotInner { + present: AtomicBool::new(false), + value: Mutex::new(None), + })) + } + + pub fn set(&self, value: Option>) { + *self.0.value.lock().unwrap() = value; + self.0.present.store(true, Ordering::Release); + } + + /// Returns the latest pending value, if any, and clears the slot. + /// `Some(Some(words))` is a fresh prediction, `Some(None)` is an explicit + /// clear (mirrors the previous `send(None)`), `None` means nothing pending. + pub fn take(&self) -> Option>> { + if !self.0.present.swap(false, Ordering::Acquire) { + return None; + } + let value = self.0.value.lock().unwrap().take(); + Some(value) + } +} + +impl Default for PredictionSlot { + fn default() -> Self { + Self::new() + } +} + pub struct SwipeTypingManager { keyboard_gird: QwertyKeyboardGrid, current_swipe: Vec, - swipe_candidate_sender: SyncSender>>, + prediction_slot: PredictionSlot, prediction_task_sender: Sender, worker_thread: Option>, swipe_start_time: Option, @@ -89,12 +130,12 @@ impl SwipeTypingManager { app.hid_provider .set_modifiers_routed(app.wvr_server.as_mut(), original_keyboard_mods); } - pub fn new(model_path: PathBuf) -> anyhow::Result<(SwipeTypingManager, Receiver>>)> { - let (candidate_sender, candidate_receiver) = sync_channel(1); + pub fn new(model_path: PathBuf) -> anyhow::Result<(SwipeTypingManager, PredictionSlot)> { + let prediction_slot = PredictionSlot::new(); + let worker_slot = prediction_slot.clone(); let (task_sender, task_receiver) = channel::(); // Spawn persistent worker thread - let worker_candidate_sender = candidate_sender.clone(); let worker_thread = thread::spawn(move || { let mut swipe_engine = match SwipeOrchestrator::new_from_path(&model_path) { Ok(engine) => engine, @@ -115,7 +156,7 @@ impl SwipeTypingManager { .map(|c| c.word) .collect(); - let _ = worker_candidate_sender.send(Some(words)); + worker_slot.set(Some(words)); } Err(e) => { log::error!("Prediction failed: {e}"); @@ -149,7 +190,7 @@ impl SwipeTypingManager { Self { keyboard_gird: QwertyKeyboardGrid::new(), current_swipe: Vec::new(), - swipe_candidate_sender: candidate_sender, + prediction_slot: prediction_slot.clone(), prediction_task_sender: task_sender, worker_thread: Some(worker_thread), swipe_start_time: None, @@ -160,7 +201,7 @@ impl SwipeTypingManager { current_swipe_mouse_button_index: None, last_swiped_word: None, }, - candidate_receiver, + prediction_slot, )) } @@ -184,7 +225,7 @@ impl SwipeTypingManager { pub fn reset(&mut self) { self.reset_swipe(); - let _ = self.swipe_candidate_sender.send(None); + self.prediction_slot.set(None); self.last_swiped_word = None; } From d0f134e83d4655b18e504efad15cb2ce1efea49d Mon Sep 17 00:00:00 2001 From: oneshinyboi Date: Sun, 16 Aug 2026 00:56:11 -0500 Subject: [PATCH 35/35] cargo fmt --- dash-frontend/src/tab/settings/mod.rs | 8 +- .../src/tab/settings/tab_features.rs | 38 ++++-- dash-frontend/src/util/downloadable_file.rs | 8 +- dash-frontend/src/util/mod.rs | 4 +- dash-frontend/src/util/swipe_type.rs | 4 +- dash-frontend/src/util/whisper.rs | 2 +- wayvr/src/backend/wayvr/client.rs | 11 +- wayvr/src/overlays/keyboard/builder.rs | 46 +++++-- wayvr/src/overlays/keyboard/mod.rs | 120 +++++++++++++----- wayvr/src/overlays/keyboard/prediction_bar.rs | 33 ++--- wayvr/src/overlays/keyboard/swipe_type.rs | 81 ++++++++---- wayvr/src/state.rs | 3 +- wayvr/src/subsystem/clipboard/mod.rs | 1 - wgui/src/components/checkbox.rs | 31 +++-- wgui/src/event.rs | 2 +- 15 files changed, 260 insertions(+), 132 deletions(-) diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs index 772a5795..aeeaad4e 100644 --- a/dash-frontend/src/tab/settings/mod.rs +++ b/dash-frontend/src/tab/settings/mod.rs @@ -270,13 +270,7 @@ impl Tab for TabSettings { if let Some(tab) = self.current_tab.as_mut() { let config = frontend.interface.general_config(data); let mut change_kind: Option = None; - tab.push_task_string( - &action, - config, - &mut change_kind, - &mut frontend.layout, - &mut self.state, - )?; + tab.push_task_string(&action, config, &mut change_kind, &mut frontend.layout, &mut self.state)?; if let Some(change_kind) = change_kind { frontend.interface.config_changed(data, change_kind); } diff --git a/dash-frontend/src/tab/settings/tab_features.rs b/dash-frontend/src/tab/settings/tab_features.rs index 03d0f3f6..e6848e5d 100644 --- a/dash-frontend/src/tab/settings/tab_features.rs +++ b/dash-frontend/src/tab/settings/tab_features.rs @@ -17,21 +17,17 @@ use wgui::{ }; use wlx_common::{async_executor::AsyncExecutor, config::GeneralConfig, dash_interface::ConfigChangeKind}; +use crate::util::downloadable_file::DownloadableFile; use crate::{ frontend::FrontendTasks, tab::settings::{ SettingType, SettingsMountParams, SettingsTab, TabNameEnum, Task as ParentTask, horiz_cell, + macros::{MacroParams, options_category, options_checkbox, options_range_f32}, mount_requires_restart, - macros::{ - MacroParams, options_category, options_checkbox, options_range_f32, - }, }, util::{ popup_manager::PopupHolder, - swipe_type::{ - SWIPE_TYPE_MODEL, swwipe_type_model_downloaded, - swipe_type_delete_all_models, swipe_type_model_path, - }, + swipe_type::{SWIPE_TYPE_MODEL, swipe_type_delete_all_models, swipe_type_model_path, swwipe_type_model_downloaded}, whisper::{ WHISPER_MODELS, whisper_any_models_downloaded, whisper_delete_all_models, whisper_model_from_name, whisper_model_path, @@ -39,7 +35,6 @@ use crate::{ }, views::{self, ViewUpdateParams}, }; -use crate::util::downloadable_file::DownloadableFile; #[derive(Clone)] enum Task { @@ -89,7 +84,13 @@ impl SettingsTab for State { } Task::WhisperDownload(model) => { self.pending_whisper_download = Some(model); - self.show_download_dialogue(model, par.executor.clone(), whisper_model_path(model.file_name), Task::WhisperDownloadClosed, Task::WhisperDownloadDone); + self.show_download_dialogue( + model, + par.executor.clone(), + whisper_model_path(model.file_name), + Task::WhisperDownloadClosed, + Task::WhisperDownloadDone, + ); } Task::WhisperDownloadDone => { if let Some(model) = self.pending_whisper_download.take() { @@ -114,7 +115,13 @@ impl SettingsTab for State { } Task::SwipeTypeDownloadAll => { self.pending_swipe_download = Some(&SWIPE_TYPE_MODEL); - self.show_download_dialogue(&SWIPE_TYPE_MODEL, par.executor.clone(), swipe_type_model_path(SWIPE_TYPE_MODEL.file_name), Task::SwipeTypeDownloadClosed, Task::SwipeTypeDownloadDone); + self.show_download_dialogue( + &SWIPE_TYPE_MODEL, + par.executor.clone(), + swipe_type_model_path(SWIPE_TYPE_MODEL.file_name), + Task::SwipeTypeDownloadClosed, + Task::SwipeTypeDownloadDone, + ); } Task::SwipeTypeDownloadDone => { if let Some(_) = self.pending_swipe_download.take() { @@ -247,7 +254,14 @@ impl State { }) } - fn show_download_dialogue(&mut self, file: &DownloadableFile, executor: AsyncExecutor, target_path: PathBuf, on_closed: Task, on_downloaded: Task) { + fn show_download_dialogue( + &mut self, + file: &DownloadableFile, + executor: AsyncExecutor, + target_path: PathBuf, + on_closed: Task, + on_downloaded: Task, + ) { views::download_file::mount_popup( self.popup_download.clone(), self.frontend_tasks.clone(), @@ -556,4 +570,4 @@ fn swipe_type_enabled_checkbox(mp: &mut MacroParams, parent: WidgetID) -> anyhow })); Ok(()) -} \ No newline at end of file +} diff --git a/dash-frontend/src/util/downloadable_file.rs b/dash-frontend/src/util/downloadable_file.rs index a23a4dd9..818cb59b 100644 --- a/dash-frontend/src/util/downloadable_file.rs +++ b/dash-frontend/src/util/downloadable_file.rs @@ -1,5 +1,5 @@ pub struct DownloadableFile { - pub file_name: &'static str, - pub display_name: &'static str, - pub url: &'static str, -} \ No newline at end of file + pub file_name: &'static str, + pub display_name: &'static str, + pub url: &'static str, +} diff --git a/dash-frontend/src/util/mod.rs b/dash-frontend/src/util/mod.rs index a3e6f408..06f7e3af 100644 --- a/dash-frontend/src/util/mod.rs +++ b/dash-frontend/src/util/mod.rs @@ -1,11 +1,11 @@ pub mod cached_fetcher; +pub mod downloadable_file; pub mod networking; pub mod openxr_bindings; pub mod pactl_wrapper; pub mod popup_manager; pub mod steam_utils; +pub mod swipe_type; pub mod toast_manager; pub mod wgui_simple; pub mod whisper; -pub mod swipe_type; -pub mod downloadable_file; diff --git a/dash-frontend/src/util/swipe_type.rs b/dash-frontend/src/util/swipe_type.rs index faef0db7..70928e8e 100644 --- a/dash-frontend/src/util/swipe_type.rs +++ b/dash-frontend/src/util/swipe_type.rs @@ -1,7 +1,7 @@ use std::{fs, io, path::PathBuf}; -use wlx_common::data_dir; use crate::util::downloadable_file::DownloadableFile; +use wlx_common::data_dir; pub const SWIPE_TYPE_MODEL: DownloadableFile = DownloadableFile { file_name: "en.tar", @@ -44,4 +44,4 @@ pub fn swipe_type_delete_all_models() -> io::Result<()> { } Ok(()) -} \ No newline at end of file +} diff --git a/dash-frontend/src/util/whisper.rs b/dash-frontend/src/util/whisper.rs index ef29fce9..8464ac9e 100644 --- a/dash-frontend/src/util/whisper.rs +++ b/dash-frontend/src/util/whisper.rs @@ -1,7 +1,7 @@ use std::{fs, io, path::PathBuf}; -use wlx_common::data_dir; use crate::util::downloadable_file::DownloadableFile; +use wlx_common::data_dir; pub const WHISPER_MODELS: &[DownloadableFile] = &[ DownloadableFile { diff --git a/wayvr/src/backend/wayvr/client.rs b/wayvr/src/backend/wayvr/client.rs index 5bf70a97..ddde3502 100644 --- a/wayvr/src/backend/wayvr/client.rs +++ b/wayvr/src/backend/wayvr/client.rs @@ -9,6 +9,7 @@ use std::{ use anyhow::Context; use glam::{DVec2, Vec2}; use slotmap::DenseSlotMap; +use smithay::wayland::selection::data_device::set_data_device_selection; use smithay::{ backend::input::{Axis, AxisSource, ButtonState, Keycode}, input::{ @@ -18,17 +19,16 @@ use smithay::{ reexports::wayland_server::{self, protocol::wl_surface::WlSurface}, utils::{Logical, Point, SerialCounter}, }; -use smithay::wayland::selection::data_device::set_data_device_selection; use wgui::log::LogErr; use xkbcommon::xkb; -use crate::backend::wayvr::{ExternalProcessRequest, WayVRTask}; -use crate::backend::wayvr::comp::Application; use super::{ ProcessWayVREnv, comp::{self, ClientState}, process, }; +use crate::backend::wayvr::comp::Application; +use crate::backend::wayvr::{ExternalProcessRequest, WayVRTask}; pub struct WayVRClient { pub client: wayland_server::Client, @@ -264,7 +264,10 @@ impl WayVRCompositor { set_data_device_selection::( &self.state.display_handle, &self.state.seat, - vec!["text/plain;charset=utf-8".to_string(), "text/plain".to_string()], + vec![ + "text/plain;charset=utf-8".to_string(), + "text/plain".to_string(), + ], text.as_bytes().into(), ); } diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index 29b968b1..715c64ab 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -1,5 +1,11 @@ use std::{rc::Rc, time::Duration}; +#[cfg(feature = "swipe-to-type")] +use super::init_swipe_type_manager; +use super::{ + KeyButtonData, KeyState, KeyboardState, handle_mouse_motion, handle_press, handle_release, + layout::{self, KeyCapType}, +}; use crate::{ app_misc, gui::{ @@ -32,9 +38,6 @@ use wgui::{ }; #[cfg(feature = "swipe-to-type")] use wlx_common::data_dir; -use super::{KeyButtonData, KeyState, KeyboardState, handle_press, handle_release, layout::{self, KeyCapType}, handle_mouse_motion}; -#[cfg(feature = "swipe-to-type")] -use super::init_swipe_type_manager; const PIXELS_PER_UNIT: f32 = 60.; @@ -236,9 +239,20 @@ pub(super) fn create_keyboard_panel( let CallbackMetadata::MouseButton(button) = data.metadata else { panic!("CallbackMetadata should contain MouseButton!"); }; - let within_key_pos = data.metadata.get_mouse_pos_normalized(&common.alterables.transform_stack); + let within_key_pos = data + .metadata + .get_mouse_pos_normalized(&common.alterables.transform_stack); - handle_press(app, &k, &k_label, &k_cap_type, &within_key_pos, state, button, button.device); + handle_press( + app, + &k, + &k_label, + &k_cap_type, + &within_key_pos, + state, + button, + button.device, + ); on_press_anim(k.clone(), common, data); Ok(EventResult::Pass) } @@ -252,12 +266,21 @@ pub(super) fn create_keyboard_panel( let k_label = key_label.clone(); let k_cap_type = key_cap_type.clone(); move |common, data, _app, state| { - let within_key_pos = data.metadata.get_mouse_pos_normalized(&common.alterables.transform_stack); + let within_key_pos = data + .metadata + .get_mouse_pos_normalized(&common.alterables.transform_stack); let CallbackMetadata::MousePosition(position) = data.metadata else { panic!("CallbackMetadata should contain MousePosition!"); }; - handle_mouse_motion(&k, &k_label, &k_cap_type, state, &within_key_pos, position.device); + handle_mouse_motion( + &k, + &k_label, + &k_cap_type, + state, + &within_key_pos, + position.device, + ); Ok(EventResult::Pass) } }), @@ -348,9 +371,14 @@ pub(super) fn create_keyboard_panel( super::prediction_bar::set_visible(panel, false); } - if app.session.config.keyboard_swipe_to_type_enabled && panel.state.swipe_typing_manager.is_none() { + if app.session.config.keyboard_swipe_to_type_enabled + && panel.state.swipe_typing_manager.is_none() + { #[cfg(feature = "swipe-to-type")] - init_swipe_type_manager(&mut panel.state, data_dir::get_path("swipe_type").join("en.tar")); + init_swipe_type_manager( + &mut panel.state, + data_dir::get_path("swipe_type").join("en.tar"), + ); super::prediction_bar::set_visible(panel, true); } diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index b791065d..3de46499 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -5,6 +5,8 @@ use std::{ sync::{Arc, atomic::Ordering}, }; +use crate::overlays::keyboard::layout::KeyCapType; +use crate::overlays::keyboard::swipe_type::{PredictionSlot, SwipeTypingManager}; use crate::overlays::toast::Toast; use crate::{ KEYMAP_CHANGE, @@ -29,26 +31,24 @@ use crate::{ }, }; use anyhow::Context; -use glam::{Affine3A, Quat, Vec3, vec3, Vec2}; +use glam::{Affine3A, Quat, Vec2, Vec3, vec3}; use regex::Regex; use slotmap::{SlotMap, new_key_type}; use smallvec::SmallVec; +use wgui::event::DeviceBitmask; use wgui::{ color::WguiColor, event::{InternalStateChangeEvent, MouseButtonEvent, MouseButtonIndex}, i18n::Translation, layout::WidgetID, }; -use wgui::event::DeviceBitmask; +#[cfg(feature = "swipe-to-type")] +use wlx_common::data_dir; use wlx_common::windowing::{OverlayWindowState, Positioning}; use wlx_common::{ config::AltModifier, overlays::{BackendAttrib, BackendAttribValue, ToastTopic}, }; -#[cfg(feature = "swipe-to-type")] -use wlx_common::data_dir; -use crate::overlays::keyboard::layout::KeyCapType; -use crate::overlays::keyboard::swipe_type::{PredictionSlot, SwipeTypingManager}; pub mod builder; mod layout; @@ -59,8 +59,8 @@ mod swipe_type; #[cfg(not(feature = "swipe-to-type"))] mod swipe_type { - use wgui::event::{DeviceBitmask, MouseButtonIndex}; use glam::Vec2; + use wgui::event::{DeviceBitmask, MouseButtonIndex}; pub struct SwipeTypingManager; @@ -83,17 +83,66 @@ mod swipe_type { pub fn new(_model_folder: std::path::PathBuf) -> anyhow::Result<(Self, PredictionSlot)> { Ok((Self, PredictionSlot)) } - pub fn add_swipe(&mut self, _within_key_pos_normalized: &Vec2, _key_label: char, _device: DeviceBitmask, _index: Option) {} - pub fn predict(&mut self) -> anyhow::Result<()> { Ok(()) } + pub fn add_swipe( + &mut self, + _within_key_pos_normalized: &Vec2, + _key_label: char, + _device: DeviceBitmask, + _index: Option, + ) { + } + pub fn predict(&mut self) -> anyhow::Result<()> { + Ok(()) + } pub fn reset(&mut self) {} - pub fn did_swipe_leave_first_key(&self) -> bool { false } - pub fn is_current_swipe_empty(&self) -> bool { true } - pub fn current_swipe_mouse_button_index(&self) -> Option { None } - pub fn handle_key_press(&mut self, _key_cap_type: &super::layout::KeyCapType, _within_key_pos: &Option, _key_label: &[String], _device: DeviceBitmask, _index: MouseButtonIndex) -> super::KeyPressOutcome { super::KeyPressOutcome::Dispatch } - pub fn handle_key_motion(&mut self, _key_cap_type: &super::layout::KeyCapType, _within_key_pos: &Option, _key_label: &[String], _device: DeviceBitmask) {} - pub fn handle_key_release(&mut self, _key_cap_type: &super::layout::KeyCapType, _alt_modifier: crate::subsystem::hid::KeyModifier) -> super::KeyReleaseOutcome { super::KeyReleaseOutcome::Normal } - pub fn select_word(&mut self, _word: &String, _app: &mut crate::state::AppState, _original_keyboard_mods: crate::subsystem::hid::KeyModifier) {} - pub fn select_alternate_prediction(&mut self, _word: &String, _app: &mut crate::state::AppState, _original_keyboard_mods: crate::subsystem::hid::KeyModifier) {} + pub fn did_swipe_leave_first_key(&self) -> bool { + false + } + pub fn is_current_swipe_empty(&self) -> bool { + true + } + pub fn current_swipe_mouse_button_index(&self) -> Option { + None + } + pub fn handle_key_press( + &mut self, + _key_cap_type: &super::layout::KeyCapType, + _within_key_pos: &Option, + _key_label: &[String], + _device: DeviceBitmask, + _index: MouseButtonIndex, + ) -> super::KeyPressOutcome { + super::KeyPressOutcome::Dispatch + } + pub fn handle_key_motion( + &mut self, + _key_cap_type: &super::layout::KeyCapType, + _within_key_pos: &Option, + _key_label: &[String], + _device: DeviceBitmask, + ) { + } + pub fn handle_key_release( + &mut self, + _key_cap_type: &super::layout::KeyCapType, + _alt_modifier: crate::subsystem::hid::KeyModifier, + ) -> super::KeyReleaseOutcome { + super::KeyReleaseOutcome::Normal + } + pub fn select_word( + &mut self, + _word: &String, + _app: &mut crate::state::AppState, + _original_keyboard_mods: crate::subsystem::hid::KeyModifier, + ) { + } + pub fn select_alternate_prediction( + &mut self, + _word: &String, + _app: &mut crate::state::AppState, + _original_keyboard_mods: crate::subsystem::hid::KeyModifier, + ) { + } } } @@ -191,7 +240,7 @@ pub(self) fn init_swipe_type_manager(state: &mut KeyboardState, model_path: std: Ok((engine, slot)) => { state.swipe_typing_manager = Some(engine); state.swipe_candidate_slot = Some(slot); - }, + } Err(e) => { log::error!("Error occurred while trying to load swipe engine: {}", e); } @@ -237,8 +286,7 @@ impl KeyboardBackend { log::info!("swipe engine created"); } - let mut panel = - create_keyboard_panel(app, keymap, state, &self.wlx_layout)?; + let mut panel = create_keyboard_panel(app, keymap, state, &self.wlx_layout)?; if !app.session.config.keyboard_swipe_to_type_enabled { prediction_bar::set_visible(&mut panel, false); @@ -289,7 +337,10 @@ impl KeyboardBackend { if app.session.config.keyboard_swipe_to_type_enabled { #[cfg(feature = "swipe-to-type")] - init_swipe_type_manager(&mut state_from, data_dir::get_path("swipe_type").join("en.tar")); + init_swipe_type_manager( + &mut state_from, + data_dir::get_path("swipe_type").join("en.tar"), + ); } self.active_layout = new_key; @@ -301,9 +352,7 @@ impl KeyboardBackend { if !app.session.config.keyboard_swipe_to_type_enabled { prediction_bar::set_visible( - self.layout_panels - .get_mut(self.active_layout) - .unwrap(), + self.layout_panels.get_mut(self.active_layout).unwrap(), false, ); } @@ -516,7 +565,7 @@ struct KeyboardState { keymap_switch_index: usize, keymap_switch_pending: bool, swipe_typing_manager: Option, - swipe_candidate_slot: Option + swipe_candidate_slot: Option, } macro_rules! take_and_leave_default { @@ -597,7 +646,7 @@ fn handle_mouse_motion( key_cap_type: &KeyCapType, keyboard: &mut KeyboardState, within_key_pos: &Option, - device: DeviceBitmask + device: DeviceBitmask, ) { if let KeyButtonData::Key { .. } = &key.button_state && let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() @@ -613,14 +662,18 @@ fn handle_press( within_key_pos: &Option, keyboard: &mut KeyboardState, button: MouseButtonEvent, - device: DeviceBitmask + device: DeviceBitmask, ) { match &key.button_state { KeyButtonData::Key { vk, pressed } => { let outcome = match keyboard.swipe_typing_manager.as_mut() { - Some(swipe_manager) => { - swipe_manager.handle_key_press(key_cap_type, within_key_pos, key_label, device, button.index) - } + Some(swipe_manager) => swipe_manager.handle_key_press( + key_cap_type, + within_key_pos, + key_label, + device, + button.index, + ), None => KeyPressOutcome::Dispatch, }; @@ -683,7 +736,12 @@ fn handle_press( } } -fn handle_release(app: &mut AppState, key: &KeyState, k_cap_type: &KeyCapType, keyboard: &mut KeyboardState) -> bool { +fn handle_release( + app: &mut AppState, + key: &KeyState, + k_cap_type: &KeyCapType, + keyboard: &mut KeyboardState, +) -> bool { match &key.button_state { KeyButtonData::Key { vk, pressed } => { let outcome = match keyboard.swipe_typing_manager.as_mut() { diff --git a/wayvr/src/overlays/keyboard/prediction_bar.rs b/wayvr/src/overlays/keyboard/prediction_bar.rs index 4f853ed6..0cf024e8 100644 --- a/wayvr/src/overlays/keyboard/prediction_bar.rs +++ b/wayvr/src/overlays/keyboard/prediction_bar.rs @@ -1,17 +1,13 @@ use std::{collections::HashMap, rc::Rc}; -use crate::{ - gui::panel::GuiPanel, - state::AppState, -}; -use anyhow::bail; -use slotmap::Key; use super::{ KeyButtonData, KeyState, KeyboardState, - builder::{ - new_doc_params, on_enter_anim, on_leave_anim, on_press_anim, on_release_anim, - }, + builder::{new_doc_params, on_enter_anim, on_leave_anim, on_press_anim, on_release_anim}, }; +use crate::{gui::panel::GuiPanel, state::AppState}; +use anyhow::bail; +use slotmap::Key; +use wgui::event::StyleSetRequest; use wgui::{ event::EventListenerKind, layout::LayoutTask, @@ -19,7 +15,6 @@ use wgui::{ taffy::Display, widget::{EventResult, rectangle::WidgetRectangle}, }; -use wgui::event::StyleSetRequest; /// Root widget that hosts the swipe-to-type prediction candidates. pub(super) const ROOT: &str = "swipe_predictions_root"; @@ -36,10 +31,7 @@ pub(super) fn update( if let Some(slot) = panel.state.swipe_candidate_slot.as_mut() && let Some(candidates) = slot.take() { - let predictions_root = panel - .parser_state - .get_widget_id(ROOT) - .unwrap_or_default(); + let predictions_root = panel.parser_state.get_widget_id(ROOT).unwrap_or_default(); if predictions_root.is_null() { return Ok(elements_changed); @@ -155,10 +147,7 @@ pub(super) fn update( /// Show or hide the prediction candidate bar. pub(super) fn set_visible(panel: &mut GuiPanel, visible: bool) { - let predictions_root = panel - .parser_state - .get_widget_id(ROOT) - .unwrap_or_default(); + let predictions_root = panel.parser_state.get_widget_id(ROOT).unwrap_or_default(); if predictions_root.is_null() { return; } @@ -169,6 +158,10 @@ pub(super) fn set_visible(panel: &mut GuiPanel, visible: bool) { panel.layout.tasks.push(LayoutTask::SetWidgetStyle( predictions_root, - StyleSetRequest::Display(if visible { Display::Flex } else { Display::None }), + StyleSetRequest::Display(if visible { + Display::Flex + } else { + Display::None + }), )); -} \ No newline at end of file +} diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs index b4190f30..dc986f85 100644 --- a/wayvr/src/overlays/keyboard/swipe_type.rs +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -1,23 +1,23 @@ +use super::layout::KeyCapType; use crate::state::AppState; -use crate::subsystem::hid::{KeyModifier, VirtualKey, CTRL, SHIFT}; -use anyhow::{bail}; +use crate::subsystem::clipboard; +use crate::subsystem::clipboard::ClipboardProvider; +use crate::subsystem::hid::{CTRL, KeyModifier, SHIFT, VirtualKey}; +use crate::subsystem::input::InputFocus; +use anyhow::bail; use glam::Vec2; use std::mem; use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::mpsc::{Sender, channel}; use std::sync::{Arc, Mutex}; -use std::sync::mpsc::{channel, Sender}; use std::thread::{self, JoinHandle}; use std::time::Instant; +use super_swipe_type::SwipePoint; use super_swipe_type::keyboard_manager::QwertyKeyboardGrid; use super_swipe_type::swipe_orchestrator::SwipeOrchestrator; -use super_swipe_type::{SwipePoint}; use wgui::event::{DeviceBitmask, MouseButtonIndex}; use wgui::log::LogErr; -use crate::subsystem::clipboard; -use crate::subsystem::clipboard::ClipboardProvider; -use crate::subsystem::input::InputFocus; -use super::layout::KeyCapType; const PREDICTION_SUGGESTION_COUNT: usize = 5; @@ -83,30 +83,39 @@ pub struct SwipeTypingManager { } impl SwipeTypingManager { - pub fn select_alternate_prediction(&mut self, word: &String, app: &mut AppState, original_keyboard_mods: KeyModifier) { + pub fn select_alternate_prediction( + &mut self, + word: &String, + app: &mut AppState, + original_keyboard_mods: KeyModifier, + ) { Self::undo(app, original_keyboard_mods); self.select_word(word, app, original_keyboard_mods); } - pub fn select_word(&mut self, word: &String, app: &mut AppState, original_keyboard_mods: KeyModifier) { + pub fn select_word( + &mut self, + word: &String, + app: &mut AppState, + original_keyboard_mods: KeyModifier, + ) { self.last_swiped_word = Some(word.clone()); let text_to_paste = format!("{word} "); match app.hid_provider.get_input_focus() { - InputFocus::PhysicalScreen => { + InputFocus::PhysicalScreen => { if let Some(clipboard) = self.clipboard.as_mut() { clipboard.set_clipboard_utf8(&text_to_paste); Self::paste(app, original_keyboard_mods); } - }, + } InputFocus::WayVR => { if let Some(wvr_server) = app.wvr_server.as_mut() { wvr_server.set_clipboard_text(text_to_paste); Self::paste(app, original_keyboard_mods); } - }, + } } - } fn undo(app: &mut AppState, original_keyboard_mods: KeyModifier) { @@ -214,11 +223,10 @@ impl SwipeTypingManager { let last_word = self.last_swiped_word.clone(); self.reset_swipe(); - self.prediction_task_sender - .send(PredictionTask::Predict { - swipe: current_swipe, - last_word, - })?; + self.prediction_task_sender.send(PredictionTask::Predict { + swipe: current_swipe, + last_word, + })?; Ok(()) } @@ -238,7 +246,12 @@ impl SwipeTypingManager { self.current_swipe_mouse_button_index = None; } - fn start_swipe(&mut self, key_label: char, device: DeviceBitmask, index: Option) -> Instant { + fn start_swipe( + &mut self, + key_label: char, + device: DeviceBitmask, + index: Option, + ) -> Instant { let now = Instant::now(); self.swipe_start_time = Some(now); self.first_swipe_char = key_label.to_ascii_lowercase(); @@ -254,7 +267,7 @@ impl SwipeTypingManager { pub const fn is_current_swipe_empty(&self) -> bool { self.current_swipe.is_empty() } - + pub const fn current_swipe_mouse_button_index(&self) -> Option { self.current_swipe_mouse_button_index } @@ -276,7 +289,12 @@ impl SwipeTypingManager { if let Some(pos) = within_key_pos && let Some(label) = key_label.first() { - self.add_swipe(pos, label.chars().next().unwrap_or_default(), device, Some(index)); + self.add_swipe( + pos, + label.chars().next().unwrap_or_default(), + device, + Some(index), + ); } super::KeyPressOutcome::Consumed @@ -332,13 +350,24 @@ impl SwipeTypingManager { super::KeyReleaseOutcome::TapKey { modifier } } - pub fn add_swipe(&mut self, within_key_pos_normalized: &Vec2, key_label: char, device: DeviceBitmask, index: Option) { - if let Some(pos) = self.keyboard_gird.key_positions.get(&key_label.to_ascii_lowercase()) { - if let Some(current_device) = self.current_swipe_device && current_device != device { + pub fn add_swipe( + &mut self, + within_key_pos_normalized: &Vec2, + key_label: char, + device: DeviceBitmask, + index: Option, + ) { + if let Some(pos) = self + .keyboard_gird + .key_positions + .get(&key_label.to_ascii_lowercase()) + { + if let Some(current_device) = self.current_swipe_device + && current_device != device + { return; } - if self.first_swipe_char != char::default() && self.first_swipe_char != key_label.to_ascii_lowercase() { diff --git a/wayvr/src/state.rs b/wayvr/src/state.rs index cf9d8373..f2ac99ba 100644 --- a/wayvr/src/state.rs +++ b/wayvr/src/state.rs @@ -192,7 +192,8 @@ impl AppState { { feats.whisper = true; } - #[cfg(feature = "swipe-to-type")] { + #[cfg(feature = "swipe-to-type")] + { feats.swipe_to_type = true; } diff --git a/wayvr/src/subsystem/clipboard/mod.rs b/wayvr/src/subsystem/clipboard/mod.rs index 05d31ed1..a19b9e22 100644 --- a/wayvr/src/subsystem/clipboard/mod.rs +++ b/wayvr/src/subsystem/clipboard/mod.rs @@ -6,4 +6,3 @@ pub mod x11; pub trait ClipboardProvider { fn set_clipboard_utf8(&mut self, content: &str); } - diff --git a/wgui/src/components/checkbox.rs b/wgui/src/components/checkbox.rs index 20ee4fed..465d12d0 100644 --- a/wgui/src/components/checkbox.rs +++ b/wgui/src/components/checkbox.rs @@ -214,12 +214,19 @@ impl ComponentCheckbox { set_box_checked(&common.state.widgets, &self.data, checked, hovered, disabled); - common.state.widgets.call(self.data.id_outer_box, |rect: &mut WidgetRectangle| { - rect.params.border_color = box_border_color(hovered, down, disabled); - }); + common + .state + .widgets + .call(self.data.id_outer_box, |rect: &mut WidgetRectangle| { + rect.params.border_color = box_border_color(hovered, down, disabled); + }); if let Some(mut label) = common.state.widgets.get_as::(self.data.id_label) { - let color = if disabled { disabled_color() } else { WguiColorName::OnBackground.into() }; + let color = if disabled { + disabled_color() + } else { + WguiColorName::OnBackground.into() + }; label.set_color(common, color, true); } @@ -296,9 +303,7 @@ fn register_event_mouse_enter( if !disabled { common.alterables.trigger_haptics(); - common - .alterables - .animate(anim_hover_in(&state, &data, anim_mult)); + common.alterables.animate(anim_hover_in(&state, &data, anim_mult)); if checked { common @@ -338,9 +343,7 @@ fn register_event_mouse_leave( if !disabled { common.alterables.trigger_haptics(); - common - .alterables - .animate(anim_hover_out(&state, &data, anim_mult)); + common.alterables.animate(anim_hover_out(&state, &data, anim_mult)); if checked { common @@ -442,7 +445,13 @@ fn register_event_mouse_release( }); } - set_box_checked(&common.state.widgets, &data, state.checked, state.hovered, state.disabled); + set_box_checked( + &common.state.widgets, + &data, + state.checked, + state.hovered, + state.disabled, + ); if state.hovered && let Some(on_toggle) = &state.on_toggle { diff --git a/wgui/src/event.rs b/wgui/src/event.rs index 6dd5daa2..3add97aa 100644 --- a/wgui/src/event.rs +++ b/wgui/src/event.rs @@ -255,7 +255,7 @@ impl CallbackMetadata { } pub fn get_mouse_pos_normalized(&self, transform_stack: &TransformStack) -> Option { let pos_relative = self.get_mouse_pos_relative(transform_stack)?; - Some(pos_relative/transform_stack.parent().raw_dim) + Some(pos_relative / transform_stack.parent().raw_dim) } }