LinuxKMS: Add support for libinput event hooks

Hidden behind the unstable-input-09 feature, the BackendSelector now exposes with_input_090_event_hook, which takes a callback that's invoked for every event received from libinput.
This commit is contained in:
Simon Hausmann 2025-08-01 12:37:15 +00:00 committed by Simon Hausmann
parent 8ed0fd327e
commit 0cfe06b810
11 changed files with 102 additions and 20 deletions

View file

@ -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

View file

@ -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

View file

@ -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"]

View file

@ -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 }

View file

@ -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<Option<String>>,
clipboard: RefCell<Option<String>>,
input_event_hook: Option<Box<dyn Fn(&::input::Event) -> bool>>,
}
impl Backend {
pub fn new() -> Result<Self, PlatformError> {
Self::new_with_renderer_by_name(None)
}
pub fn new_with_renderer_by_name(renderer_name: Option<&str>) -> Result<Self, PlatformError> {
pub fn build(builder: BackendBuilder) -> Result<Self, PlatformError> {
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>;

View file

@ -119,6 +119,7 @@ pub struct LibInputHandler<'a> {
last_touch_pos: LogicalPosition,
window: &'a RefCell<Option<Rc<FullscreenWindowAdapter>>>,
keystate: Option<xkb::State>,
input_event_hook: &'a Option<Box<dyn Fn(&::input::Event) -> bool>>,
}
impl<'a> LibInputHandler<'a> {
@ -126,6 +127,7 @@ impl<'a> LibInputHandler<'a> {
window: &'a RefCell<Option<Rc<FullscreenWindowAdapter>>>,
event_loop_handle: &calloop::LoopHandle<'a, T>,
#[cfg(feature = "libseat")] seat: &'a Rc<RefCell<libseat::Seat>>,
input_event_hook: &'a Option<Box<dyn Fn(&::input::Event) -> bool>>,
) -> Result<Pin<Rc<Property<Option<LogicalPosition>>>>, 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 {

View file

@ -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<String>,
#[cfg(target_os = "linux")]
pub(crate) input_event_hook: Option<Box<dyn Fn(&input::Event) -> 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<dyn Fn(&input::Event) -> bool>) -> Self {
self.input_event_hook = Some(event_hook);
self
}
pub fn build(self) -> Result<Backend, PlatformError> {
Backend::build(self)
}
}
#[doc(hidden)]
pub type NativeWidgets = ();

View file

@ -5,10 +5,7 @@ use i_slint_core::platform::PlatformError;
pub struct Backend {}
impl Backend {
pub fn new() -> Result<Self, PlatformError> {
Self::new_with_renderer_by_name(None)
}
pub fn new_with_renderer_by_name(_renderer_name: Option<&str>) -> Result<Self, PlatformError> {
pub fn build(_builder: super::BackendBuilder) -> Result<Self, PlatformError> {
Ok(Backend {})
}
}

View file

@ -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 }

View file

@ -44,6 +44,8 @@ pub struct BackendSelector {
>,
#[cfg(feature = "unstable-winit-030")]
winit_event_loop_builder: Option<i_slint_backend_winit::EventLoopBuilder>,
#[cfg(all(target_os = "linux", feature = "unstable-input-09"))]
input_090_event_hook: Option<Box<dyn Fn(&input::Event) -> 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" => {

View file

@ -32,7 +32,7 @@ fn create_winit_backend() -> Result<Box<dyn Platform + 'static>, PlatformError>
#[cfg(all(feature = "i-slint-backend-linuxkms", target_os = "linux"))]
fn create_linuxkms_backend() -> Result<Box<dyn Platform + 'static>, 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<dyn Platform + 'static>),
#[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<dyn Platform + 'static>),
"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<dyn Platform + 'static>)
},
_ => {},
}