diff --git a/snowcap/api/lua/snowcap/grpc/defs.lua b/snowcap/api/lua/snowcap/grpc/defs.lua index 6cd8187bf..1d47e41d9 100644 --- a/snowcap/api/lua/snowcap/grpc/defs.lua +++ b/snowcap/api/lua/snowcap/grpc/defs.lua @@ -921,6 +921,8 @@ local snowcap_layer_v1_Layer = { ---@field key integer? ---@field modifiers snowcap.input.v1.Modifiers? ---@field pressed boolean? +---@field captured boolean? +---@field text string? ---@class snowcap.input.v1.PointerButtonRequest ---@field id integer? diff --git a/snowcap/api/lua/snowcap/input.lua b/snowcap/api/lua/snowcap/input.lua index 5498841fc..d592e0b39 100644 --- a/snowcap/api/lua/snowcap/input.lua +++ b/snowcap/api/lua/snowcap/input.lua @@ -12,4 +12,12 @@ local input = { ---@field alt boolean ---@field super boolean +---A Key event. +---@class snowcap.input.KeyEvent +---@field key snowcap.Key Key Symbol. +---@field mods snowcap.input.Modifiers Currently active modifiers. +---@field pressed boolean True if the key is currently pressed, false on release. +---@field captured boolean True if the event was flagged as Captured by a widget. +---@field text? string Text produced by the event, if any. + return input diff --git a/snowcap/api/lua/snowcap/layer.lua b/snowcap/api/lua/snowcap/layer.lua index e2d76a0e2..32ee01a6c 100644 --- a/snowcap/api/lua/snowcap/layer.lua +++ b/snowcap/api/lua/snowcap/layer.lua @@ -169,6 +169,40 @@ function layer.new_widget(args) end) end +---Do something when a key event is received. +---@param on_event fun(handle: snowcap.layer.LayerHandle, event: snowcap.input.KeyEvent) +function LayerHandle:on_key_event(on_event) + local err = client:snowcap_input_v1_InputService_KeyboardKey( + { id = self.id }, + function(response) + ---@cast response snowcap.input.v1.KeyboardKeyResponse + + local mods = response.modifiers or {} + mods.shift = mods.shift or false + mods.ctrl = mods.ctrl or false + mods.alt = mods.alt or false + mods.super = mods.super or false + + ---@cast mods snowcap.input.Modifiers + + ---@type snowcap.input.KeyEvent + local event = { + key = response.key or 0, + mods = mods, + pressed = response.pressed, + captured = response.captured, + text = response.text, + } + + on_event(self, event) + end + ) + + if err then + log.error(err) + end +end + ---@param on_press fun(mods: snowcap.input.Modifiers, key: snowcap.Key) function LayerHandle:on_key_press(on_press) local err = client:snowcap_input_v1_InputService_KeyboardKey( @@ -176,7 +210,7 @@ function LayerHandle:on_key_press(on_press) function(response) ---@cast response snowcap.input.v1.KeyboardKeyResponse - if not response.pressed then + if not response.pressed or response.captured then return end diff --git a/snowcap/api/protobuf/snowcap/input/v1/input.proto b/snowcap/api/protobuf/snowcap/input/v1/input.proto index d1623c091..06f1fd4aa 100644 --- a/snowcap/api/protobuf/snowcap/input/v1/input.proto +++ b/snowcap/api/protobuf/snowcap/input/v1/input.proto @@ -17,6 +17,8 @@ message KeyboardKeyResponse { uint32 key = 1; Modifiers modifiers = 2; bool pressed = 3; + bool captured = 4; + optional string text = 5; } message PointerButtonRequest { diff --git a/snowcap/api/rust/src/input.rs b/snowcap/api/rust/src/input.rs index d5d6d4d2c..3e8690ffc 100644 --- a/snowcap/api/rust/src/input.rs +++ b/snowcap/api/rust/src/input.rs @@ -1,10 +1,11 @@ //! Input types. use snowcap_api_defs::snowcap::input; +use xkbcommon::xkb::Keysym; /// Keyboard modifiers. #[allow(missing_docs)] -#[derive(Default)] +#[derive(Default, Debug, Clone, Copy)] pub struct Modifiers { pub shift: bool, pub ctrl: bool, @@ -22,3 +23,30 @@ impl From for Modifiers { } } } + +/// A Key event. +#[derive(Debug, Clone)] +pub struct KeyEvent { + /// Key Symbol. + pub key: Keysym, + /// Currently active modifiers. + pub mods: Modifiers, + /// True if the key is currently pressed, false on release. + pub pressed: bool, + /// True if the event was flagged as Captured by a widget. + pub captured: bool, + /// Text produced by the event, if any. + pub text: Option, +} + +impl From for KeyEvent { + fn from(value: input::v1::KeyboardKeyResponse) -> Self { + Self { + key: Keysym::new(value.key), + mods: Modifiers::from(value.modifiers.unwrap_or_default()), + pressed: value.pressed, + captured: value.captured, + text: value.text, + } + } +} diff --git a/snowcap/api/rust/src/layer.rs b/snowcap/api/rust/src/layer.rs index ac27e39ad..f7e7aafae 100644 --- a/snowcap/api/rust/src/layer.rs +++ b/snowcap/api/rust/src/layer.rs @@ -18,7 +18,7 @@ use xkbcommon::xkb::Keysym; use crate::{ BlockOnTokio, client::Client, - input::Modifiers, + input::{KeyEvent, Modifiers}, widget::{Program, Widget, WidgetId}, }; @@ -264,6 +264,35 @@ where let _ = self.msg_sender.send(message); } + /// Do something when a key event is received + pub fn on_key_event( + &self, + mut on_event: impl FnMut(LayerHandle, KeyEvent) + Send + 'static, + ) { + let mut stream = match Client::input() + .keyboard_key(KeyboardKeyRequest { + id: self.id.to_inner(), + }) + .block_on_tokio() + { + Ok(stream) => stream.into_inner(), + Err(status) => { + error!("Failed to set `on_key_event` handler: {status}"); + return; + } + }; + + let handle = self.clone(); + + tokio::spawn(async move { + while let Some(Ok(response)) = stream.next().await { + let event = KeyEvent::from(response); + + on_event(handle.clone(), event); + } + }); + } + /// Do something on key press. pub fn on_key_press( &self, @@ -286,7 +315,7 @@ where tokio::spawn(async move { while let Some(Ok(response)) = stream.next().await { - if !response.pressed { + if !response.pressed || response.captured { continue; } diff --git a/snowcap/src/api/input/v1.rs b/snowcap/src/api/input/v1.rs index dedb469eb..034a21e10 100644 --- a/snowcap/src/api/input/v1.rs +++ b/snowcap/src/api/input/v1.rs @@ -43,6 +43,8 @@ impl input_service_server::InputService for super::InputService { key: item.key.raw(), modifiers: Some(api_modifiers), pressed: item.pressed, + captured: item.captured, + text: item.text, }) }, ) diff --git a/snowcap/src/handlers.rs b/snowcap/src/handlers.rs index b65e4ca6a..301a724fc 100644 --- a/snowcap/src/handlers.rs +++ b/snowcap/src/handlers.rs @@ -63,7 +63,17 @@ impl SeatHandler for State { capability: Capability, ) { if capability == Capability::Keyboard && self.keyboard.is_none() { - let keyboard = self.seat_state.get_keyboard(qh, &seat, None).unwrap(); + let keyboard = self + .seat_state + .get_keyboard_with_repeat( + qh, + &seat, + None, + self.loop_handle.clone(), + Box::new(State::on_key_press), + ) + .unwrap(); + self.keyboard = Some(keyboard); } diff --git a/snowcap/src/handlers/keyboard.rs b/snowcap/src/handlers/keyboard.rs index 76458e726..4880c6bbb 100644 --- a/snowcap/src/handlers/keyboard.rs +++ b/snowcap/src/handlers/keyboard.rs @@ -11,11 +11,53 @@ use smithay_client_toolkit::{ use crate::{input::keyboard::keysym_to_iced_key_and_loc, state::State}; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug)] pub struct KeyboardKey { pub key: Keysym, pub modifiers: Modifiers, pub pressed: bool, + pub captured: bool, + pub text: Option, +} + +impl State { + pub(crate) fn on_key_press(&mut self, _keyboard: &WlKeyboard, event: KeyEvent) { + let Some(KeyboardFocus::Layer(layer)) = self.keyboard_focus.as_ref() else { + return; + }; + + let Some(snowcap_layer) = self.layers.iter_mut().find(|sn_l| &sn_l.layer == layer) else { + return; + }; + + let (key, location) = keysym_to_iced_key_and_loc(event.keysym); + + let mut modifiers = iced::keyboard::Modifiers::empty(); + if self.keyboard_modifiers.ctrl { + modifiers |= iced::keyboard::Modifiers::CTRL; + } + if self.keyboard_modifiers.alt { + modifiers |= iced::keyboard::Modifiers::ALT; + } + if self.keyboard_modifiers.shift { + modifiers |= iced::keyboard::Modifiers::SHIFT; + } + if self.keyboard_modifiers.logo { + modifiers |= iced::keyboard::Modifiers::LOGO; + } + + snowcap_layer + .surface + .widgets + .queue_event(iced::Event::Keyboard(iced::keyboard::Event::KeyPressed { + key: key.clone(), + location, + modifiers, + text: event.utf8.map(Into::into), + modified_key: key, // TODO: + physical_key: Physical::Unidentified(NativeCode::Xkb(event.keysym.raw())), + })); + } } impl KeyboardHandler for State { @@ -60,6 +102,17 @@ impl KeyboardHandler for State { _keyboard: &WlKeyboard, _serial: u32, event: KeyEvent, + ) { + self.on_key_press(_keyboard, event) + } + + fn release_key( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _keyboard: &WlKeyboard, + _serial: u32, + event: KeyEvent, ) { let Some(KeyboardFocus::Layer(layer)) = self.keyboard_focus.as_ref() else { return; @@ -88,24 +141,28 @@ impl KeyboardHandler for State { snowcap_layer .surface .widgets - .queue_event(iced::Event::Keyboard(iced::keyboard::Event::KeyPressed { + .queue_event(iced::Event::Keyboard(iced::keyboard::Event::KeyReleased { key: key.clone(), location, modifiers, - text: None, - modified_key: key, // TODO: + // TODO: + modified_key: key, physical_key: Physical::Unidentified(NativeCode::Xkb(event.keysym.raw())), })); } - fn release_key( + fn update_modifiers( &mut self, _conn: &Connection, _qh: &QueueHandle, _keyboard: &WlKeyboard, _serial: u32, - event: KeyEvent, + modifiers: Modifiers, + _layout: u32, ) { + self.keyboard_modifiers = modifiers; + + // TODO: Should we check if the modifiers actually changed ? let Some(KeyboardFocus::Layer(layer)) = self.keyboard_focus.as_ref() else { return; }; @@ -114,8 +171,6 @@ impl KeyboardHandler for State { return; }; - let (key, location) = keysym_to_iced_key_and_loc(event.keysym); - let mut modifiers = iced::keyboard::Modifiers::empty(); if self.keyboard_modifiers.ctrl { modifiers |= iced::keyboard::Modifiers::CTRL; @@ -133,27 +188,9 @@ impl KeyboardHandler for State { snowcap_layer .surface .widgets - .queue_event(iced::Event::Keyboard(iced::keyboard::Event::KeyReleased { - key: key.clone(), - location, - modifiers, - // TODO: - modified_key: key, - physical_key: Physical::Unidentified(NativeCode::Xkb(event.keysym.raw())), - })); - } - - fn update_modifiers( - &mut self, - _conn: &Connection, - _qh: &QueueHandle, - _keyboard: &WlKeyboard, - _serial: u32, - modifiers: Modifiers, - _layout: u32, - ) { - // TODO: per wl_keyboard - self.keyboard_modifiers = modifiers; + .queue_event(iced::Event::Keyboard( + iced::keyboard::Event::ModifiersChanged(modifiers), + )); } } delegate_keyboard!(State); diff --git a/snowcap/src/state.rs b/snowcap/src/state.rs index e8689d7c5..b6375dec9 100644 --- a/snowcap/src/state.rs +++ b/snowcap/src/state.rs @@ -149,14 +149,12 @@ impl State { runtime.track(iced_futures::subscription::into_recipes( iced::event::listen_with(|event, status, id| { - if status == iced::event::Status::Captured { - return None; - } - + let captured = status == iced::event::Status::Captured; match event { iced::Event::Keyboard(iced::keyboard::Event::KeyPressed { modifiers, physical_key: Physical::Unidentified(NativeCode::Xkb(raw)), + text, .. }) => Some(( id, @@ -171,6 +169,8 @@ impl State { num_lock: false, }, pressed: true, + captured, + text: text.map(|s| s.into()), }), )), iced::Event::Keyboard(iced::keyboard::Event::KeyReleased { @@ -190,6 +190,8 @@ impl State { num_lock: false, }, pressed: false, + captured, + text: None, }), )), _ => None,