diff --git a/.github/workflows/build_docs.yaml b/.github/workflows/build_docs.yaml index 674eee8ac..623106502 100644 --- a/.github/workflows/build_docs.yaml +++ b/.github/workflows/build_docs.yaml @@ -35,7 +35,7 @@ jobs: - name: Set up crate rustdoc link run: | rgb_version=`grep 'rgb = ' internal/core/Cargo.toml | sed 's/^.*"\(.*\)"/\1/'` - echo "RUSTDOCFLAGS=$RUSTDOCFLAGS --extern-html-root-url rgb=https://docs.rs/rgb/$rgb_version/ --extern-html-root-url android_activity=https://docs.rs/android-activity/0.5/ --extern-html-root-url raw_window_handle=https://docs.rs/raw_window_handle/0.6 --extern-html-root-url winit=https://docs.rs/winit/0.30 --extern-html-root-url wgpu=https://docs.rs/wgpu/26" >> $GITHUB_ENV + echo "RUSTDOCFLAGS=$RUSTDOCFLAGS --extern-html-root-url rgb=https://docs.rs/rgb/$rgb_version/ --extern-html-root-url android_activity=https://docs.rs/android-activity/0.5/ --extern-html-root-url raw_window_handle=https://docs.rs/raw_window_handle/0.6 --extern-html-root-url winit=https://docs.rs/winit/0.30 --extern-html-root-url wgpu=https://docs.rs/wgpu/26 --extern-html-root-url input=https://docs.rs/input/0.9" >> $GITHUB_ENV - uses: actions/setup-node@v4 with: node-version: 20 diff --git a/Cargo.toml b/Cargo.toml index f5895bc51..62c9d22b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -167,6 +167,7 @@ unicode-segmentation = { version = "1.12.0" } glow = { version = "0.16" } tikv-jemallocator = { version = "0.6" } wgpu-26 = { package = "wgpu", version = "26", default-features = false } +input = { version = "0.9.0", default-features = false } [profile.release] lto = true diff --git a/api/rs/slint/Cargo.toml b/api/rs/slint/Cargo.toml index 7d18afd6e..5166911a2 100644 --- a/api/rs/slint/Cargo.toml +++ b/api/rs/slint/Cargo.toml @@ -215,6 +215,20 @@ unstable-wgpu-26 = ["i-slint-core/unstable-wgpu-26", "i-slint-backend-selector/u ## ``` unstable-winit-030 = ["backend-winit", "dep:i-slint-backend-winit", "i-slint-backend-selector/unstable-winit-030"] + +## Enable support for exposing [libinput](https://docs.rs/input/latest/input/index.html) related APIs in the LinuxKMS backend. +## +## APIs guarded with this feature are *NOT* subject to the usual Slint API stability guarantees. This feature as well as the APIs changed or removed +## in future minor releases of Slint, likely to be replaced by a feature with a similar name but the input version suffix being bumped. +## +## To avoid unintended compilation failures, we recommend to use the [tilde requirement](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#tilde-requirements) +## in your `Cargo.toml` when enabling this feature: +## +## ```toml +## slint = { version = "~1.13", features = ["unstable-input-09"] } +## ``` +unstable-input-09 = ["i-slint-backend-selector/unstable-input-09"] + [dependencies] i-slint-core = { workspace = true } slint-macros = { workspace = true } @@ -273,5 +287,6 @@ features = [ "raw-window-handle-06", "unstable-wgpu-26", "unstable-winit-030", + "unstable-input-09", ] rustdoc-args = ["--generate-link-to-definition"] diff --git a/internal/backends/linuxkms/Cargo.toml b/internal/backends/linuxkms/Cargo.toml index 5edcf889d..f22707f54 100644 --- a/internal/backends/linuxkms/Cargo.toml +++ b/internal/backends/linuxkms/Cargo.toml @@ -33,7 +33,7 @@ i-slint-renderer-skia = { workspace = true, features = ["default", "kms"], optio i-slint-renderer-femtovg = { workspace = true, features = ["default"], optional = true } [target.'cfg(target_os = "linux")'.dependencies] -input = { version = "0.9.0" } +input = { workspace = true, default-features = true } xkbcommon = { version = "0.8.0" } calloop = { version = "0.14.1" } libseat = { version = "0.2.1", optional = true, default-features = false } diff --git a/internal/backends/linuxkms/calloop_backend.rs b/internal/backends/linuxkms/calloop_backend.rs index ec9919e66..23f4bde0c 100644 --- a/internal/backends/linuxkms/calloop_backend.rs +++ b/internal/backends/linuxkms/calloop_backend.rs @@ -17,6 +17,7 @@ use calloop::EventLoop; use i_slint_core::platform::PlatformError; use crate::fullscreenwindowadapter::FullscreenWindowAdapter; +use crate::BackendBuilder; #[cfg(not(any(target_family = "windows", target_vendor = "apple", target_arch = "wasm32")))] mod input; @@ -76,16 +77,14 @@ pub struct Backend { >, sel_clipboard: RefCell>, clipboard: RefCell>, + input_event_hook: Option bool>>, } impl Backend { - pub fn new() -> Result { - Self::new_with_renderer_by_name(None) - } - pub fn new_with_renderer_by_name(renderer_name: Option<&str>) -> Result { + pub fn build(builder: BackendBuilder) -> Result { let (user_event_sender, user_event_receiver) = calloop::channel::channel(); - let renderer_factory = match renderer_name { + let renderer_factory = match builder.renderer_name.as_deref() { #[cfg(feature = "renderer-skia-vulkan")] Some("skia-vulkan") => crate::renderer::skia::SkiaRendererAdapter::new_vulkan, #[cfg(feature = "renderer-skia-opengl")] @@ -143,6 +142,7 @@ impl Backend { renderer_factory, sel_clipboard: Default::default(), clipboard: Default::default(), + input_event_hook: builder.input_event_hook, }) } } @@ -216,6 +216,7 @@ impl i_slint_core::platform::Platform for Backend { &event_loop.handle(), #[cfg(feature = "libseat")] &self.seat, + &self.input_event_hook, )?; let Some(user_event_receiver) = self.user_event_receiver.borrow_mut().take() else { @@ -296,5 +297,3 @@ impl i_slint_core::platform::Platform for Backend { #[derive(Default)] pub struct LoopData {} - -pub type EventLoopHandle<'a> = calloop::LoopHandle<'a, LoopData>; diff --git a/internal/backends/linuxkms/calloop_backend/input.rs b/internal/backends/linuxkms/calloop_backend/input.rs index f6732dd5d..d2fc29358 100644 --- a/internal/backends/linuxkms/calloop_backend/input.rs +++ b/internal/backends/linuxkms/calloop_backend/input.rs @@ -119,6 +119,7 @@ pub struct LibInputHandler<'a> { last_touch_pos: LogicalPosition, window: &'a RefCell>>, keystate: Option, + input_event_hook: &'a Option bool>>, } impl<'a> LibInputHandler<'a> { @@ -126,6 +127,7 @@ impl<'a> LibInputHandler<'a> { window: &'a RefCell>>, event_loop_handle: &calloop::LoopHandle<'a, T>, #[cfg(feature = "libseat")] seat: &'a Rc>, + input_event_hook: &'a Option bool>>, ) -> Result>>>, PlatformError> { #[cfg(feature = "libseat")] let libinput = SeatWrap::new(seat); @@ -141,6 +143,7 @@ impl<'a> LibInputHandler<'a> { last_touch_pos: Default::default(), window, keystate: Default::default(), + input_event_hook, }; event_loop_handle @@ -179,6 +182,9 @@ impl<'a> calloop::EventSource for LibInputHandler<'a> { let screen_size = window.size().to_logical(window.scale_factor()); for event in &mut self.libinput { + if self.input_event_hook.as_ref().map_or(false, |hook| hook(&event)) { + continue; + }; match event { input::Event::Pointer(pointer_event) => { match pointer_event { diff --git a/internal/backends/linuxkms/lib.rs b/internal/backends/linuxkms/lib.rs index 6d8640d08..4f8838a57 100644 --- a/internal/backends/linuxkms/lib.rs +++ b/internal/backends/linuxkms/lib.rs @@ -80,12 +80,37 @@ mod renderer { mod calloop_backend; #[cfg(target_os = "linux")] -pub use calloop_backend::*; +use calloop_backend::*; #[cfg(not(target_os = "linux"))] mod noop_backend; +use i_slint_core::api::PlatformError; #[cfg(not(target_os = "linux"))] -pub use noop_backend::*; +use noop_backend::*; + +#[derive(Default)] +pub struct BackendBuilder { + pub(crate) renderer_name: Option, + #[cfg(target_os = "linux")] + pub(crate) input_event_hook: Option bool>>, +} + +impl BackendBuilder { + pub fn with_renderer_name(mut self, name: String) -> Self { + self.renderer_name = Some(name); + self + } + + #[cfg(target_os = "linux")] + pub fn with_input_event_hook(mut self, event_hook: Box bool>) -> Self { + self.input_event_hook = Some(event_hook); + self + } + + pub fn build(self) -> Result { + Backend::build(self) + } +} #[doc(hidden)] pub type NativeWidgets = (); diff --git a/internal/backends/linuxkms/noop_backend.rs b/internal/backends/linuxkms/noop_backend.rs index f6efa6c08..cc051b979 100644 --- a/internal/backends/linuxkms/noop_backend.rs +++ b/internal/backends/linuxkms/noop_backend.rs @@ -5,10 +5,7 @@ use i_slint_core::platform::PlatformError; pub struct Backend {} impl Backend { - pub fn new() -> Result { - Self::new_with_renderer_by_name(None) - } - pub fn new_with_renderer_by_name(_renderer_name: Option<&str>) -> Result { + pub fn build(_builder: super::BackendBuilder) -> Result { Ok(Backend {}) } } diff --git a/internal/backends/selector/Cargo.toml b/internal/backends/selector/Cargo.toml index 01571fe4a..0a55104a1 100644 --- a/internal/backends/selector/Cargo.toml +++ b/internal/backends/selector/Cargo.toml @@ -58,6 +58,8 @@ unstable-wgpu-26 = [ unstable-winit-030 = ["i-slint-backend-winit"] +unstable-input-09 = ["dep:input"] + # note that default enable the i-slint-backend-qt, but not its enable feature default = ["i-slint-backend-qt", "backend-winit"] @@ -76,6 +78,7 @@ i-slint-backend-qt = { workspace = true, optional = true } [target.'cfg(target_os = "linux")'.dependencies] i-slint-backend-linuxkms = { workspace = true, features = ["default"], optional = true } +input = { workspace = true, optional = true } [build-dependencies] i-slint-common = { workspace = true } diff --git a/internal/backends/selector/api.rs b/internal/backends/selector/api.rs index 8b442138b..c873f9fcd 100644 --- a/internal/backends/selector/api.rs +++ b/internal/backends/selector/api.rs @@ -44,6 +44,8 @@ pub struct BackendSelector { >, #[cfg(feature = "unstable-winit-030")] winit_event_loop_builder: Option, + #[cfg(all(target_os = "linux", feature = "unstable-input-09"))] + input_090_event_hook: Option bool>>, } impl BackendSelector { @@ -173,6 +175,25 @@ impl BackendSelector { self } + #[i_slint_core_macros::slint_doc] + /// Configures this builder to use the specified libinput event filter hook when the LinuxKMS backend + /// is selected. + /// + /// The provided hook is invoked for every event received. If the function returns true, the event is + /// not dispatched further. + /// + /// *Note*: This function is behind the [`unstable-input-09` feature flag](slint:rust:slint/docs/cargo_features/#backends) + /// and may be removed or changed in future minor releases, as new major Winit releases become available. + #[must_use] + #[cfg(all(target_os = "linux", feature = "unstable-input-09"))] + pub fn with_input_090_event_hook( + mut self, + event_hook: impl Fn(&input::Event) -> bool + 'static, + ) -> Self { + self.input_090_event_hook = Some(Box::new(event_hook)); + self + } + /// Adds the requirement that the selected renderer must match the given name. This is /// equivalent to setting the `SLINT_BACKEND=name` environment variable and requires /// that the corresponding renderer feature is enabled. For example, to select the Skia renderer, @@ -237,9 +258,18 @@ impl BackendSelector { return Err("The linuxkms backend does not implement renderer selection by graphics API".into()); } - Box::new(i_slint_backend_linuxkms::Backend::new_with_renderer_by_name( - self.renderer.as_deref(), - )?) + let mut builder = i_slint_backend_linuxkms::BackendBuilder::default(); + + if let Some(renderer_name) = self.renderer.as_ref() { + builder = builder.with_renderer_name(renderer_name.into()); + } + + #[cfg(all(target_os = "linux", feature = "unstable-input-09"))] + if let Some(event_hook) = self.input_090_event_hook.take() { + builder = builder.with_input_event_hook(event_hook); + } + + Box::new(builder.build()?) } #[cfg(feature = "i-slint-backend-winit")] "winit" => { diff --git a/internal/backends/selector/lib.rs b/internal/backends/selector/lib.rs index 4297bec6c..1843dec40 100644 --- a/internal/backends/selector/lib.rs +++ b/internal/backends/selector/lib.rs @@ -32,7 +32,7 @@ fn create_winit_backend() -> Result, PlatformError> #[cfg(all(feature = "i-slint-backend-linuxkms", target_os = "linux"))] fn create_linuxkms_backend() -> Result, PlatformError> { - Ok(Box::new(i_slint_backend_linuxkms::Backend::new()?)) + Ok(Box::new(i_slint_backend_linuxkms::BackendBuilder::default().build()?)) } cfg_if::cfg_if! { @@ -101,7 +101,13 @@ cfg_if::cfg_if! { #[cfg(feature = "i-slint-backend-winit")] "winit" => return i_slint_backend_winit::Backend::new_with_renderer_by_name((!_renderer.is_empty()).then_some(_renderer)).map(|b| Box::new(b) as Box), #[cfg(all(feature = "i-slint-backend-linuxkms", target_os = "linux"))] - "linuxkms" => return i_slint_backend_linuxkms::Backend::new_with_renderer_by_name((!_renderer.is_empty()).then(|| _renderer)).map(|b| Box::new(b) as Box), + "linuxkms" => { + let mut builder = i_slint_backend_linuxkms::BackendBuilder::default(); + if !_renderer.is_empty() { + builder = builder.with_renderer_name(_renderer.into()); + } + return builder.build().map(|b| Box::new(b) as Box) + }, _ => {}, }