feat: keyboard input and input region

This commit is contained in:
Naman Agrawal
2025-08-22 15:52:48 +05:30
parent 93b879c937
commit b368d43531
10 changed files with 1020 additions and 89 deletions
+1
View File
@@ -2,6 +2,7 @@
# will have compiled files and executables # will have compiled files and executables
debug debug
target target
logs.txt
# These are backup files generated by rustfmt # These are backup files generated by rustfmt
**/*.rs.bk **/*.rs.bk
+180 -6
View File
@@ -1,18 +1,192 @@
use bevy::{prelude::*, winit::WinitPlugin}; use std::time::Duration;
use bevy_wayland::{layer_shell::LayerShellWindow, WaylandPlugin};
use bevy::{
color::palettes::basic::*,
prelude::*,
window::{WindowCreated, WindowResolution},
winit::WinitPlugin,
};
use bevy_wayland::{layer_shell::LayerShellSettings, WaylandPlugin};
use smithay_client_toolkit::shell::wlr_layer::{Anchor, Layer};
const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15);
const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25);
const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35);
fn main() { fn main() {
App::new() App::new()
.add_plugins(( .add_plugins((
DefaultPlugins.build().disable::<WinitPlugin>(), DefaultPlugins
.build()
.disable::<WinitPlugin>()
.set(WindowPlugin {
primary_window: Some(Window {
resolution: WindowResolution::new(400.0, 400.0),
..Default::default()
}),
..Default::default()
}),
WaylandPlugin, WaylandPlugin,
)) ))
.init_resource::<NewWindowInfo>()
.add_systems(Startup, setup) .add_systems(Startup, setup)
.add_systems(Update, (button_system, setup_new_window, spawn_window))
.run(); .run();
} }
fn setup(mut commands: Commands, windows: Query<Entity, With<Window>>) { #[allow(clippy::type_complexity)]
for entity in &windows { fn button_system(
commands.entity(entity).insert(LayerShellWindow::default()); mut interaction_query: Query<
(
&Interaction,
&mut BackgroundColor,
&mut BorderColor,
&Children,
),
(Changed<Interaction>, With<Button>),
>,
mut text_query: Query<&mut Text>,
) {
for (interaction, mut color, mut border_color, children) in &mut interaction_query {
let mut text = text_query.get_mut(children[0]).unwrap();
match *interaction {
Interaction::Pressed => {
**text = "Press".to_string();
*color = PRESSED_BUTTON.into();
border_color.0 = RED.into();
}
Interaction::Hovered => {
**text = "Hover".to_string();
*color = HOVERED_BUTTON.into();
border_color.0 = Color::WHITE;
}
Interaction::None => {
**text = "Button".to_string();
*color = NORMAL_BUTTON.into();
border_color.0 = Color::BLACK;
}
}
} }
} }
#[derive(Component, Deref, DerefMut)]
struct WindowTimer(Timer);
fn setup(mut commands: Commands, assets: Res<AssetServer>, windows: Query<Entity, With<Window>>) {
for entity in &windows {
commands.entity(entity).insert((
LayerShellSettings {
anchor: Anchor::all(),
layer: Layer::Bottom,
..Default::default()
},
WindowTimer(Timer::new(Duration::from_secs(5), TimerMode::Once)),
));
}
// ui camera
commands.spawn(Camera2d);
commands.spawn(button(&assets));
}
fn spawn_window(
mut commands: Commands,
mut windows: Query<(Entity, &mut WindowTimer)>,
mut new_window_info: ResMut<NewWindowInfo>,
time: Res<Time>,
) {
for (entity, mut timer) in &mut windows {
timer.tick(time.delta());
if timer.finished() {
commands.entity(entity).remove::<WindowTimer>();
let new_window_entity = commands
.spawn((
Window {
title: "UI Only Window".to_string(),
resolution: (400., 50.).into(),
..default()
},
LayerShellSettings {
layer: Layer::Top,
anchor: Anchor::TOP | Anchor::LEFT,
..default()
},
WindowTimer(Timer::new(Duration::from_secs(5), TimerMode::Once)),
))
.id();
new_window_info.entity = Some(new_window_entity);
new_window_info.is_setup_pending = true;
}
}
}
#[derive(Resource, Default)]
struct NewWindowInfo {
entity: Option<Entity>,
is_setup_pending: bool,
}
fn setup_new_window(
mut commands: Commands,
mut window_created_events: EventReader<WindowCreated>,
mut new_window_info: ResMut<NewWindowInfo>,
asset_server: Res<AssetServer>, // For fonts
) {
for event in window_created_events.read() {
if Some(event.window) == new_window_info.entity && new_window_info.is_setup_pending {
info!(
"New UI window created (ID: {:?}), setting up its camera and UI.",
event.window
);
commands.spawn((
Camera {
target: bevy::render::camera::RenderTarget::Window(
bevy::window::WindowRef::Entity(event.window),
),
clear_color: ClearColorConfig::Custom(Color::default()),
..default()
},
Camera2d,
));
new_window_info.is_setup_pending = false; // Mark as setup complete
}
}
}
fn button(asset_server: &AssetServer) -> impl Bundle + use<> {
(
Node {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
},
children![(
Button,
Node {
width: Val::Px(150.0),
height: Val::Px(65.0),
border: UiRect::all(Val::Px(5.0)),
// horizontally center child text
justify_content: JustifyContent::Center,
// vertically center child text
align_items: AlignItems::Center,
..default()
},
BorderColor(Color::BLACK),
BorderRadius::MAX,
BackgroundColor(NORMAL_BUTTON),
children![(
Text::new("Button"),
TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 33.0,
..default()
},
TextColor(Color::srgb(0.9, 0.9, 0.9)),
TextShadow::default(),
)]
)],
)
}
-62
View File
@@ -1,62 +0,0 @@
use bevy::prelude::*;
use smithay_client_toolkit::{
delegate_seat,
reexports::client::QueueHandle,
seat::{SeatHandler, SeatState},
};
use crate::WaylandState;
pub struct InputHandlerPlugin;
impl Plugin for InputHandlerPlugin {
fn build(&self, app: &mut App) {
let globals = app.world().non_send_resource();
let queue_handle: &QueueHandle<WaylandState> = app.world().non_send_resource();
let seat_state = SeatState::new(globals, queue_handle);
app.insert_non_send_resource(seat_state);
}
}
impl SeatHandler for WaylandState {
fn seat_state(&mut self) -> &mut SeatState {
self.world_mut()
.non_send_resource_mut::<SeatState>()
.into_inner()
}
fn new_seat(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_seat: smithay_client_toolkit::reexports::client::protocol::wl_seat::WlSeat,
) {
}
fn new_capability(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_seat: smithay_client_toolkit::reexports::client::protocol::wl_seat::WlSeat,
_capability: smithay_client_toolkit::seat::Capability,
) {
}
fn remove_capability(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_seat: smithay_client_toolkit::reexports::client::protocol::wl_seat::WlSeat,
_capability: smithay_client_toolkit::seat::Capability,
) {
}
fn remove_seat(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_seat: smithay_client_toolkit::reexports::client::protocol::wl_seat::WlSeat,
) {
}
}
delegate_seat!(WaylandState);
+460
View File
@@ -0,0 +1,460 @@
use bevy::input::keyboard::Key;
use bevy::input::{keyboard::KeyboardInput, ButtonState};
use bevy::log::warn;
use bevy::prelude::*;
use bevy::window::WindowEvent;
use smithay_client_toolkit::reexports::client::Proxy;
use smithay_client_toolkit::{
delegate_keyboard,
seat::keyboard::{KeyEvent, KeyboardHandler, Keysym},
};
use crate::surface_handler::WaylandSurfaces;
use crate::WaylandState;
/// Converts a Smithay keyboard event to a Bevy keyboard input event.
fn convert_keyboard_event(
event: KeyEvent,
entity: Entity,
state: ButtonState,
) -> bevy::input::keyboard::KeyboardInput {
KeyboardInput {
state,
text: None,
window: entity,
key_code: convert_to_key_code(event.keysym),
logical_key: convert_to_logical_key(event.keysym),
repeat: false,
}
}
/// Converts a Smithay Keysym to a Bevy Key.
fn convert_to_logical_key(keysym: Keysym) -> bevy::input::keyboard::Key {
// First, attempt to get the character representation based on keyboard layout.
if let Some(c) = keysym.key_char() {
// Use Key::Character for printable chars. Convert to SmolStr.
return Key::Character((c.to_string()).into());
}
// If key_char() returned None, it's a non-printable key (Control, Function, Arrow etc.)
// Map the Keysym variant to the corresponding Bevy Key variant.
match keysym {
// Function Keys
Keysym::F1 => Key::F1,
Keysym::F2 => Key::F2,
Keysym::F3 => Key::F3,
Keysym::F4 => Key::F4,
Keysym::F5 => Key::F5,
Keysym::F6 => Key::F6,
Keysym::F7 => Key::F7,
Keysym::F8 => Key::F8,
Keysym::F9 => Key::F9,
Keysym::F10 => Key::F10,
Keysym::F11 => Key::F11,
Keysym::F12 => Key::F12,
Keysym::F13 => Key::F13,
Keysym::F14 => Key::F14,
Keysym::F15 => Key::F15,
Keysym::F16 => Key::F16,
Keysym::F17 => Key::F17,
Keysym::F18 => Key::F18,
Keysym::F19 => Key::F19,
Keysym::F20 => Key::F20,
Keysym::F21 => Key::F21,
Keysym::F22 => Key::F22,
Keysym::F23 => Key::F23,
Keysym::F24 => Key::F24,
// Control & Navigation
Keysym::Escape => Key::Escape,
Keysym::Pause => Key::Pause,
Keysym::Scroll_Lock => Key::ScrollLock,
Keysym::Print => Key::PrintScreen,
Keysym::Insert => Key::Insert,
Keysym::Delete => Key::Delete,
Keysym::Home => Key::Home,
Keysym::End => Key::End,
Keysym::Page_Up => Key::PageUp,
Keysym::Page_Down => Key::PageDown,
Keysym::BackSpace => Key::Backspace,
Keysym::Return | Keysym::KP_Enter => Key::Enter, // Map both main and numpad Enter
Keysym::Tab => Key::Tab,
Keysym::Menu => Key::ContextMenu,
// Arrow Keys
Keysym::Left => Key::ArrowLeft,
Keysym::Right => Key::ArrowRight,
Keysym::Up => Key::ArrowUp,
Keysym::Down => Key::ArrowDown,
// Modifiers
Keysym::Shift_L => Key::Shift,
Keysym::Shift_R => Key::Shift,
Keysym::Control_L => Key::Control,
Keysym::Control_R => Key::Control,
Keysym::Alt_L => Key::Alt,
Keysym::Alt_R => Key::Alt,
Keysym::Super_L | Keysym::Meta_L => Key::Super, // Combine Super/Meta
Keysym::Super_R | Keysym::Meta_R => Key::Super,
Keysym::Caps_Lock => Key::CapsLock,
Keysym::Num_Lock => Key::NumLock,
// Keys that *might* have produced a character but key_char() returned None.
// This could happen for dead keys, IME input, or if key_char() implementation is limited.
// We need a fallback. Panicking with todo!() highlights these cases during development.
// A production system might need more robust handling (e.g., logging, ignoring, specific Key variant if available).
Keysym::A
| Keysym::B
| Keysym::C
| Keysym::D
| Keysym::E
| Keysym::F
| Keysym::G
| Keysym::H
| Keysym::I
| Keysym::J
| Keysym::K
| Keysym::L
| Keysym::M
| Keysym::N
| Keysym::O
| Keysym::P
| Keysym::Q
| Keysym::R
| Keysym::S
| Keysym::T
| Keysym::U
| Keysym::V
| Keysym::W
| Keysym::X
| Keysym::Y
| Keysym::Z
| Keysym::_0
| Keysym::_1
| Keysym::_2
| Keysym::_3
| Keysym::_4
| Keysym::_5
| Keysym::_6
| Keysym::_7
| Keysym::_8
| Keysym::_9
| Keysym::grave
| Keysym::minus
| Keysym::equal
| Keysym::bracketleft
| Keysym::bracketright
| Keysym::backslash
| Keysym::semicolon
| Keysym::apostrophe
| Keysym::comma
| Keysym::period
| Keysym::slash
| Keysym::space => {
warn!(
"Keysym {:?} did not produce a character via key_char(). This might be unexpected.",
keysym
);
// Bevy's Key enum doesn't have a generic 'Unidentified' or 'Unknown' variant.
// Panicking via todo!() helps catch these during development.
// You might need custom logic or decide to ignore these cases in production.
todo!(
"Unhandled case: Keysym {:?} did not yield a char via key_char(). How to map to bevy::input::keyboard::Key?",
keysym
)
}
// Catch-all for any other Keysym variants not handled above.
_ => {
todo!("Unhandled keysym variant: {:?}", keysym)
}
}
}
/// Converts a Smithay Keysym to a Bevy KeyCode.
fn convert_to_key_code(keysym: Keysym) -> bevy::prelude::KeyCode {
match keysym {
// Letters
Keysym::A => bevy::prelude::KeyCode::KeyA,
Keysym::B => bevy::prelude::KeyCode::KeyB,
Keysym::C => bevy::prelude::KeyCode::KeyC,
Keysym::D => bevy::prelude::KeyCode::KeyD,
Keysym::E => bevy::prelude::KeyCode::KeyE,
Keysym::F => bevy::prelude::KeyCode::KeyF,
Keysym::G => bevy::prelude::KeyCode::KeyG,
Keysym::H => bevy::prelude::KeyCode::KeyH,
Keysym::I => bevy::prelude::KeyCode::KeyI,
Keysym::J => bevy::prelude::KeyCode::KeyJ,
Keysym::K => bevy::prelude::KeyCode::KeyK,
Keysym::L => bevy::prelude::KeyCode::KeyL,
Keysym::M => bevy::prelude::KeyCode::KeyM,
Keysym::N => bevy::prelude::KeyCode::KeyN,
Keysym::O => bevy::prelude::KeyCode::KeyO,
Keysym::P => bevy::prelude::KeyCode::KeyP,
Keysym::Q => bevy::prelude::KeyCode::KeyQ,
Keysym::R => bevy::prelude::KeyCode::KeyR,
Keysym::S => bevy::prelude::KeyCode::KeyS,
Keysym::T => bevy::prelude::KeyCode::KeyT,
Keysym::U => bevy::prelude::KeyCode::KeyU,
Keysym::V => bevy::prelude::KeyCode::KeyV,
Keysym::W => bevy::prelude::KeyCode::KeyW,
Keysym::X => bevy::prelude::KeyCode::KeyX,
Keysym::Y => bevy::prelude::KeyCode::KeyY,
Keysym::Z => bevy::prelude::KeyCode::KeyZ,
Keysym::a => bevy::prelude::KeyCode::KeyA,
Keysym::b => bevy::prelude::KeyCode::KeyB,
Keysym::c => bevy::prelude::KeyCode::KeyC,
Keysym::d => bevy::prelude::KeyCode::KeyD,
Keysym::e => bevy::prelude::KeyCode::KeyE,
Keysym::f => bevy::prelude::KeyCode::KeyF,
Keysym::g => bevy::prelude::KeyCode::KeyG,
Keysym::h => bevy::prelude::KeyCode::KeyH,
Keysym::i => bevy::prelude::KeyCode::KeyI,
Keysym::j => bevy::prelude::KeyCode::KeyJ,
Keysym::k => bevy::prelude::KeyCode::KeyK,
Keysym::l => bevy::prelude::KeyCode::KeyL,
Keysym::m => bevy::prelude::KeyCode::KeyM,
Keysym::n => bevy::prelude::KeyCode::KeyN,
Keysym::o => bevy::prelude::KeyCode::KeyO,
Keysym::p => bevy::prelude::KeyCode::KeyP,
Keysym::q => bevy::prelude::KeyCode::KeyQ,
Keysym::r => bevy::prelude::KeyCode::KeyR,
Keysym::s => bevy::prelude::KeyCode::KeyS,
Keysym::t => bevy::prelude::KeyCode::KeyT,
Keysym::u => bevy::prelude::KeyCode::KeyU,
Keysym::v => bevy::prelude::KeyCode::KeyV,
Keysym::w => bevy::prelude::KeyCode::KeyW,
Keysym::x => bevy::prelude::KeyCode::KeyX,
Keysym::y => bevy::prelude::KeyCode::KeyY,
Keysym::z => bevy::prelude::KeyCode::KeyZ,
// Numbers Row (might be Keysym::0, Keysym::1 etc. in some libs)
Keysym::_0 => bevy::prelude::KeyCode::Digit0,
Keysym::_1 => bevy::prelude::KeyCode::Digit1,
Keysym::_2 => bevy::prelude::KeyCode::Digit2,
Keysym::_3 => bevy::prelude::KeyCode::Digit3,
Keysym::_4 => bevy::prelude::KeyCode::Digit4,
Keysym::_5 => bevy::prelude::KeyCode::Digit5,
Keysym::_6 => bevy::prelude::KeyCode::Digit6,
Keysym::_7 => bevy::prelude::KeyCode::Digit7,
Keysym::_8 => bevy::prelude::KeyCode::Digit8,
Keysym::_9 => bevy::prelude::KeyCode::Digit9,
// Function Keys
Keysym::F1 => bevy::prelude::KeyCode::F1,
Keysym::F2 => bevy::prelude::KeyCode::F2,
Keysym::F3 => bevy::prelude::KeyCode::F3,
Keysym::F4 => bevy::prelude::KeyCode::F4,
Keysym::F5 => bevy::prelude::KeyCode::F5,
Keysym::F6 => bevy::prelude::KeyCode::F6,
Keysym::F7 => bevy::prelude::KeyCode::F7,
Keysym::F8 => bevy::prelude::KeyCode::F8,
Keysym::F9 => bevy::prelude::KeyCode::F9,
Keysym::F10 => bevy::prelude::KeyCode::F10,
Keysym::F11 => bevy::prelude::KeyCode::F11,
Keysym::F12 => bevy::prelude::KeyCode::F12,
Keysym::F13 => bevy::prelude::KeyCode::F13,
Keysym::F14 => bevy::prelude::KeyCode::F14,
Keysym::F15 => bevy::prelude::KeyCode::F15,
Keysym::F16 => bevy::prelude::KeyCode::F16,
Keysym::F17 => bevy::prelude::KeyCode::F17,
Keysym::F18 => bevy::prelude::KeyCode::F18,
Keysym::F19 => bevy::prelude::KeyCode::F19,
Keysym::F20 => bevy::prelude::KeyCode::F20,
Keysym::F21 => bevy::prelude::KeyCode::F21,
Keysym::F22 => bevy::prelude::KeyCode::F22,
Keysym::F23 => bevy::prelude::KeyCode::F23,
Keysym::F24 => bevy::prelude::KeyCode::F24,
// Arrow Keys
Keysym::Left => bevy::prelude::KeyCode::ArrowLeft,
Keysym::Right => bevy::prelude::KeyCode::ArrowRight,
Keysym::Up => bevy::prelude::KeyCode::ArrowUp,
Keysym::Down => bevy::prelude::KeyCode::ArrowDown,
// Modifiers
Keysym::Shift_L => bevy::prelude::KeyCode::ShiftLeft,
Keysym::Shift_R => bevy::prelude::KeyCode::ShiftRight,
Keysym::Control_L => bevy::prelude::KeyCode::ControlLeft,
Keysym::Control_R => bevy::prelude::KeyCode::ControlRight,
Keysym::Alt_L => bevy::prelude::KeyCode::AltLeft,
Keysym::Alt_R => bevy::prelude::KeyCode::AltRight,
Keysym::Super_L | Keysym::Meta_L => bevy::prelude::KeyCode::SuperLeft, // Handle Super/Meta variations
Keysym::Super_R | Keysym::Meta_R => bevy::prelude::KeyCode::SuperRight,
Keysym::Caps_Lock => bevy::prelude::KeyCode::CapsLock,
Keysym::Num_Lock => bevy::prelude::KeyCode::NumLock,
Keysym::Scroll_Lock => bevy::prelude::KeyCode::ScrollLock,
// Navigation & Editing
Keysym::Home => bevy::prelude::KeyCode::Home,
Keysym::End => bevy::prelude::KeyCode::End,
Keysym::Page_Up => bevy::prelude::KeyCode::PageUp,
Keysym::Page_Down => bevy::prelude::KeyCode::PageDown,
Keysym::Insert => bevy::prelude::KeyCode::Insert,
Keysym::Delete => bevy::prelude::KeyCode::Delete,
Keysym::BackSpace => bevy::prelude::KeyCode::Backspace, // Note: Bevy uses Backspace
// Whitespace & Control (Keysym::Return for main Enter)
Keysym::space => bevy::prelude::KeyCode::Space,
Keysym::Tab => bevy::prelude::KeyCode::Tab,
Keysym::Return | Keysym::KP_Enter => bevy::prelude::KeyCode::Enter, // Map both Enter and Numpad Enter
Keysym::Escape => bevy::prelude::KeyCode::Escape,
// Numpad Keys (might start with KP_ in some libs)
Keysym::KP_0 => bevy::prelude::KeyCode::Numpad0,
Keysym::KP_1 => bevy::prelude::KeyCode::Numpad1,
Keysym::KP_2 => bevy::prelude::KeyCode::Numpad2,
Keysym::KP_3 => bevy::prelude::KeyCode::Numpad3,
Keysym::KP_4 => bevy::prelude::KeyCode::Numpad4,
Keysym::KP_5 => bevy::prelude::KeyCode::Numpad5,
Keysym::KP_6 => bevy::prelude::KeyCode::Numpad6,
Keysym::KP_7 => bevy::prelude::KeyCode::Numpad7,
Keysym::KP_8 => bevy::prelude::KeyCode::Numpad8,
Keysym::KP_9 => bevy::prelude::KeyCode::Numpad9,
Keysym::KP_Add => bevy::prelude::KeyCode::NumpadAdd,
Keysym::KP_Subtract => bevy::prelude::KeyCode::NumpadSubtract,
Keysym::KP_Multiply => bevy::prelude::KeyCode::NumpadMultiply,
Keysym::KP_Divide => bevy::prelude::KeyCode::NumpadDivide,
Keysym::KP_Decimal => bevy::prelude::KeyCode::NumpadDecimal,
Keysym::KP_Separator => bevy::prelude::KeyCode::NumpadComma, // Bevy uses NumpadComma for the separator
Keysym::KP_Equal => bevy::prelude::KeyCode::NumpadEqual,
// KP_Enter handled above with Return
// Punctuation & Symbols (Names can vary significantly)
Keysym::grave => bevy::prelude::KeyCode::Backquote, // `
Keysym::minus => bevy::prelude::KeyCode::Minus, // -
Keysym::equal => bevy::prelude::KeyCode::Equal, // =
Keysym::bracketleft => bevy::prelude::KeyCode::BracketLeft, // [
Keysym::bracketright => bevy::prelude::KeyCode::BracketRight, // ]
Keysym::backslash => bevy::prelude::KeyCode::Backslash, // \
Keysym::semicolon => bevy::prelude::KeyCode::Semicolon, // ;
Keysym::apostrophe => bevy::prelude::KeyCode::Quote, // ' (Bevy uses Quote)
Keysym::comma => bevy::prelude::KeyCode::Comma, // ,
Keysym::period => bevy::prelude::KeyCode::Period, // .
Keysym::slash => bevy::prelude::KeyCode::Slash, // /
// Other Keys
Keysym::Print => bevy::prelude::KeyCode::PrintScreen,
Keysym::Pause => bevy::prelude::KeyCode::Pause,
Keysym::Menu => bevy::prelude::KeyCode::ContextMenu, // Bevy uses ContextMenu
// --- Fallback ---
// This arm catches any Keysym not explicitly handled above.
// Using todo!() will cause a panic, alerting you during development
// that a key mapping is missing. For production, consider returning Option<KeyCode>
// or logging an error instead of panicking.
_ => {
// e.g., eprintln!("Warning: Unhandled keysym: {:?}", keysym);
// If the function returned Option<KeyCode>, you'd return None here.
// Since it must return KeyCode, panic is the explicit way to fail.
todo!("Unhandled keysym: {:?}", keysym)
// Or potentially return a "default" like Escape, but this hides errors:
// KeyCode::Escape
}
}
}
#[derive(Resource, Deref)]
struct ActiveWindow(Entity);
impl KeyboardHandler for WaylandState {
fn enter(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &smithay_client_toolkit::reexports::client::QueueHandle<Self>,
_keyboard: &smithay_client_toolkit::reexports::client::protocol::wl_keyboard::WlKeyboard,
surface: &smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface,
_serial: u32,
_raw: &[u32],
_keysyms: &[Keysym],
) {
let wayland_surfaces = self.world().non_send_resource::<WaylandSurfaces>();
let active_window_entity = *wayland_surfaces
.get_window_entity(&surface.id())
.expect("keyboard event was passed before creating a window!");
self.world_mut()
.insert_resource(ActiveWindow(active_window_entity));
}
fn leave(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &smithay_client_toolkit::reexports::client::QueueHandle<Self>,
_keyboard: &smithay_client_toolkit::reexports::client::protocol::wl_keyboard::WlKeyboard,
surface: &smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface,
_serial: u32,
) {
let wayland_surfaces = self.world().non_send_resource::<WaylandSurfaces>();
let left_window_entity = *wayland_surfaces
.get_window_entity(&surface.id())
.expect("keyboard event was passed before creating a window!");
let active_window_entity = **self.world().resource::<ActiveWindow>();
if left_window_entity != active_window_entity {
// Currently we are assuming that there can only be one window which has access to the
// keyboard so this check is just redundancy.
return;
}
self.world_mut().remove_resource::<ActiveWindow>();
}
fn press_key(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &smithay_client_toolkit::reexports::client::QueueHandle<Self>,
_keyboard: &smithay_client_toolkit::reexports::client::protocol::wl_keyboard::WlKeyboard,
_serial: u32,
event: KeyEvent,
) {
info!("Wayland got a keypress event {:?}", event);
let active_window_entity = **self.world().resource::<ActiveWindow>();
let keyboard_event =
convert_keyboard_event(event, active_window_entity, ButtonState::Pressed);
self.world_mut()
.send_event(WindowEvent::KeyboardInput(keyboard_event));
}
fn repeat_key(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &smithay_client_toolkit::reexports::client::QueueHandle<Self>,
_keyboard: &smithay_client_toolkit::reexports::client::protocol::wl_keyboard::WlKeyboard,
_serial: u32,
event: KeyEvent,
) {
let active_window_entity = **self.world().resource::<ActiveWindow>();
let mut keyboard_event =
convert_keyboard_event(event, active_window_entity, ButtonState::Pressed);
keyboard_event.repeat = true;
self.world_mut()
.send_event(WindowEvent::KeyboardInput(keyboard_event));
}
fn release_key(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &smithay_client_toolkit::reexports::client::QueueHandle<Self>,
_keyboard: &smithay_client_toolkit::reexports::client::protocol::wl_keyboard::WlKeyboard,
_serial: u32,
event: KeyEvent,
) {
let active_window_entity = **self.world().resource::<ActiveWindow>();
let keyboard_event =
convert_keyboard_event(event, active_window_entity, ButtonState::Released);
self.world_mut()
.send_event(WindowEvent::KeyboardInput(keyboard_event));
}
fn update_modifiers(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &smithay_client_toolkit::reexports::client::QueueHandle<Self>,
_keyboard: &smithay_client_toolkit::reexports::client::protocol::wl_keyboard::WlKeyboard,
_serial: u32,
_modifiers: smithay_client_toolkit::seat::keyboard::Modifiers,
_raw_modifiers: smithay_client_toolkit::seat::keyboard::RawModifiers,
_layout: u32,
) {
todo!()
}
}
delegate_keyboard!(WaylandState);
+98
View File
@@ -0,0 +1,98 @@
use bevy::prelude::*;
use smithay_client_toolkit::{
delegate_seat,
reexports::client::{
protocol::{wl_keyboard::WlKeyboard, wl_pointer::WlPointer},
QueueHandle,
},
seat::{Capability, SeatHandler, SeatState},
};
use crate::WaylandState;
mod keyboard;
mod pointer;
pub struct InputHandlerPlugin;
impl Plugin for InputHandlerPlugin {
fn build(&self, app: &mut App) {
let globals = app.world().non_send_resource();
let queue_handle: &QueueHandle<WaylandState> = app.world().non_send_resource();
let seat_state = SeatState::new(globals, queue_handle);
app.insert_non_send_resource(seat_state);
}
}
impl SeatHandler for WaylandState {
fn seat_state(&mut self) -> &mut SeatState {
self.world_mut()
.non_send_resource_mut::<SeatState>()
.into_inner()
}
fn new_seat(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_seat: smithay_client_toolkit::reexports::client::protocol::wl_seat::WlSeat,
) {
}
fn new_capability(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
qh: &QueueHandle<Self>,
seat: smithay_client_toolkit::reexports::client::protocol::wl_seat::WlSeat,
capability: smithay_client_toolkit::seat::Capability,
) {
if capability == Capability::Keyboard {
//let mut seat_state = self.world_mut().non_send_resource_mut::<SeatState>();
//let wl_keyboard = seat_state
// .get_keyboard(qh, &seat, None)
// .expect("error while attaching keyboard!");
//self.world_mut().insert_non_send_resource(wl_keyboard);
info!("Keyboard Attached");
}
if capability == Capability::Pointer {
let mut seat_state = self.world_mut().non_send_resource_mut::<SeatState>();
let wl_pointer = seat_state
.get_pointer(qh, &seat)
.expect("error while attaching pointer!");
self.world_mut().insert_non_send_resource(wl_pointer);
info!("Pointer Attached");
}
if capability == Capability::Touch {
info!("Touchscreen Attached");
}
}
fn remove_capability(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_seat: smithay_client_toolkit::reexports::client::protocol::wl_seat::WlSeat,
capability: smithay_client_toolkit::seat::Capability,
) {
if capability == Capability::Keyboard {
self.world_mut().remove_non_send_resource::<WlKeyboard>();
info!("Keyboard detatched");
}
if capability == Capability::Pointer {
self.world_mut().remove_non_send_resource::<WlPointer>();
info!("Pointer detatched");
}
if capability == Capability::Touch {
info!("Touchscreen Attached");
}
}
fn remove_seat(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_seat: smithay_client_toolkit::reexports::client::protocol::wl_seat::WlSeat,
) {
}
}
delegate_seat!(WaylandState);
+108
View File
@@ -0,0 +1,108 @@
use bevy::{
input::{
mouse::{MouseButtonInput, MouseScrollUnit, MouseWheel},
ButtonState,
},
prelude::MouseButton,
window::{CursorEntered, CursorLeft, CursorMoved, Window, WindowEvent},
};
use smithay_client_toolkit::{
delegate_pointer,
reexports::{client::Proxy, csd_frame::WindowState},
seat::pointer::PointerHandler,
};
use crate::{surface_handler::WaylandSurfaces, WaylandState};
/// Converts a u32 button code to a Bevy MouseButton.
fn convert_to_mouse_button(button: u32) -> MouseButton {
match button {
272 => MouseButton::Left,
273 => MouseButton::Right,
274 => MouseButton::Middle,
277 => MouseButton::Forward,
278 => MouseButton::Back,
other => MouseButton::Other(other as u16),
}
}
impl PointerHandler for WaylandState {
fn pointer_frame(
&mut self,
_: &smithay_client_toolkit::reexports::client::Connection,
_: &smithay_client_toolkit::reexports::client::QueueHandle<Self>,
_: &smithay_client_toolkit::reexports::client::protocol::wl_pointer::WlPointer,
events: &[smithay_client_toolkit::seat::pointer::PointerEvent],
) {
for event in events {
let smithay_windows = self.world().non_send_resource::<WaylandSurfaces>();
let entity = smithay_windows.get_window_entity(&event.surface.id());
if entity.is_none() {
continue;
}
let entity = *entity.unwrap();
let window = self.world().get::<Window>(entity).unwrap().clone();
let mut position = bevy::math::Vec2 {
x: event.position.0 as f32,
y: event.position.1 as f32,
};
let delta = window
.physical_cursor_position()
.map(|old_position| (position - old_position) / window.scale_factor());
let pointer_event: WindowEvent = match event.kind {
smithay_client_toolkit::seat::pointer::PointerEventKind::Enter { .. } => {
CursorEntered { window: entity }.into()
}
smithay_client_toolkit::seat::pointer::PointerEventKind::Leave { .. } => {
CursorLeft { window: entity }.into()
}
smithay_client_toolkit::seat::pointer::PointerEventKind::Motion { .. } => {
self.world_mut()
.get_mut::<Window>(entity)
.unwrap()
.set_physical_cursor_position(Some(position.as_dvec2()));
position /= window.scale_factor();
CursorMoved {
window: entity,
position,
delta,
}
.into()
}
smithay_client_toolkit::seat::pointer::PointerEventKind::Press {
button, ..
} => MouseButtonInput {
button: convert_to_mouse_button(button),
state: ButtonState::Pressed,
window: entity,
}
.into(),
smithay_client_toolkit::seat::pointer::PointerEventKind::Release {
button, ..
} => MouseButtonInput {
button: convert_to_mouse_button(button),
state: ButtonState::Released,
window: entity,
}
.into(),
smithay_client_toolkit::seat::pointer::PointerEventKind::Axis {
horizontal,
vertical,
..
} => MouseWheel {
unit: MouseScrollUnit::Pixel,
x: horizontal.absolute as f32,
y: vertical.absolute as f32,
window: entity,
}
.into(),
};
let window_event: WindowEvent = pointer_event;
self.world_mut().send_event::<WindowEvent>(window_event);
}
}
}
delegate_pointer!(WaylandState);
+44
View File
@@ -0,0 +1,44 @@
use bevy::{core_pipeline::core_2d::graph::input, prelude::*};
use smithay_client_toolkit::{
compositor::{CompositorState, Region},
reexports::client::{protocol::wl_compositor::WlCompositor, QueueHandle},
};
use crate::{input_region, surface_handler::WaylandSurfaces, WaylandState};
#[derive(Component, Deref)]
pub struct InputRegion(Rect);
pub struct InputRegionPlugin;
impl Plugin for InputRegionPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, update_input_region);
}
}
fn update_input_region(
windows: Query<(Entity, Option<&InputRegion>), With<Window>>,
compositor: NonSendMut<CompositorState>,
wayland_surfaces: NonSendMut<WaylandSurfaces>,
) {
for (entity, input_region) in &windows {
let window_wrapper = wayland_surfaces.get_window_wrapper(entity).unwrap();
let region = input_region.map(|input_region| {
let region = Region::new(compositor.as_ref()).unwrap();
region.add(
input_region.min.x as i32,
input_region.max.y as i32,
input_region.width() as i32,
input_region.height() as i32,
);
region
});
if let Some(region) = region {
window_wrapper
.wl_surface()
.set_input_region(Some(region.wl_region()));
} else {
window_wrapper.wl_surface().set_input_region(None);
}
}
}
+118 -18
View File
@@ -1,24 +1,112 @@
use bevy::prelude::*; use bevy::{platform::collections::HashMap, prelude::*, ui::update};
use smithay_client_toolkit::{ use smithay_client_toolkit::{
delegate_layer, delegate_layer,
reexports::client::{globals::GlobalList, QueueHandle}, reexports::client::{globals::GlobalList, Proxy, QueueHandle},
shell::{ shell::{
wlr_layer::{Layer, LayerShell, LayerShellHandler}, wlr_layer::{
Anchor, KeyboardInteractivity, Layer, LayerShell, LayerShellHandler, LayerSurface,
},
WaylandSurface, WaylandSurface,
}, },
}; };
use crate::{surface_handler::WaylandSurfaces, WaylandState}; use crate::{
surface_handler::{create_windows, SurfaceConfigured, WaylandSurfaces},
WaylandState,
};
#[derive(Component, Default)] #[derive(Default, Deref, DerefMut)]
pub struct LayerShellWindow {} struct LayerShellWindows(HashMap<Entity, LayerShellWindow>);
#[derive(Component)]
struct LayerShellRoleAssigned; struct LayerShellWindow {
layer_surface: LayerSurface,
layer_shell_settings: LayerShellSettings,
}
impl LayerShellWindow {
fn new(layer_surface: LayerSurface, layer_shell_settings: LayerShellSettings) -> Self {
let mut layer_shell_window = Self {
layer_surface,
layer_shell_settings,
};
layer_shell_window.sync();
layer_shell_window
}
fn sync(&mut self) {
self.layer_surface
.set_layer(self.layer_shell_settings.layer);
self.layer_surface
.set_anchor(self.layer_shell_settings.anchor);
self.layer_surface
.set_keyboard_interactivity(self.layer_shell_settings.keyboard_interactivity);
self.layer_surface
.set_exclusive_zone(self.layer_shell_settings.exclusive_zone);
let (width, height) = self.layer_shell_settings.size;
self.layer_surface.set_size(width, height);
let (top, right, bottom, left) = self.layer_shell_settings.margin;
self.layer_surface.set_margin(top, right, bottom, left);
self.layer_surface.commit();
}
pub fn set_settings(&mut self, layer_shell_settings: LayerShellSettings) {
if self.layer_shell_settings == layer_shell_settings {
return;
}
self.layer_shell_settings = layer_shell_settings;
self.sync();
}
}
#[derive(Component, Debug, Clone, PartialEq, Eq)]
pub struct LayerShellSettings {
/// Defines where the layer surface should be anchored to the screen.
///
/// You can anchor the layer surface to any combination of the top, bottom, left, and right edges of the screen.
pub anchor: Anchor,
/// Defines the size of the layer surface in pixels.
pub size: (u32, u32),
/// Defines the amount of exclusive space the layer surface should reserve.
///
/// Other surfaces will not be placed in this area. A negative value means that the layer surface
/// will not reserve any exclusive space.
pub exclusive_zone: i32,
/// Defines the margins for the layer surface.
///
/// Margins are specified in the order: top, right, bottom, left.
pub margin: (i32, i32, i32, i32),
/// Defines how the layer surface should handle keyboard interactivity.
///
/// If set to `Exclusive`, the layer surface will receive all keyboard input.
/// If set to `OnDemand`, the layer surface will only receive keyboard input when it is focused.
/// If set to `None`, the layer surface will never receive keyboard input.
pub keyboard_interactivity: KeyboardInteractivity,
/// Defines the layer that the surface should be placed on.
///
/// The layer determines the stacking order of the surface. Surfaces on higher layers are
/// always drawn on top of surfaces on lower layers.
pub layer: Layer,
}
impl Default for LayerShellSettings {
fn default() -> Self {
Self {
anchor: Anchor::empty(),
size: Default::default(),
exclusive_zone: Default::default(),
margin: Default::default(),
keyboard_interactivity: Default::default(),
layer: Layer::Top,
}
}
}
pub struct LayerShellPlugin; pub struct LayerShellPlugin;
impl Plugin for LayerShellPlugin { impl Plugin for LayerShellPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Update, assign_layer_shell_role); app.add_systems(PreUpdate, assign_layer_shell_role.after(create_windows))
.add_systems(Update, update_layer_shell_settings)
.insert_non_send_resource(LayerShellWindows::default());
} }
} }
@@ -27,12 +115,10 @@ fn assign_layer_shell_role(
wayland_surfaces: NonSend<WaylandSurfaces>, wayland_surfaces: NonSend<WaylandSurfaces>,
queue_handle: NonSend<QueueHandle<WaylandState>>, queue_handle: NonSend<QueueHandle<WaylandState>>,
globals: NonSend<GlobalList>, globals: NonSend<GlobalList>,
layer_shell_windows: Query< windows: Query<(Entity, &Window, &LayerShellSettings), Without<SurfaceConfigured>>,
(Entity, &Window, &LayerShellWindow), mut layer_shell_windows: NonSendMut<LayerShellWindows>,
Without<LayerShellRoleAssigned>,
>,
) { ) {
for (entity, _window, _layer_shell_settings) in &layer_shell_windows { for (entity, _window, layer_shell_settings) in &windows {
let window_wrapper = wayland_surfaces.get_window_wrapper(entity); let window_wrapper = wayland_surfaces.get_window_wrapper(entity);
let surface = window_wrapper let surface = window_wrapper
.expect("tried to assign role before creating surface!") .expect("tried to assign role before creating surface!")
@@ -43,13 +129,27 @@ fn assign_layer_shell_role(
let layer = layer_shell.create_layer_surface( let layer = layer_shell.create_layer_surface(
&queue_handle, &queue_handle,
surface.clone(), surface.clone(),
Layer::Top, layer_shell_settings.layer,
Some("simple_layer"), Some("simple_layer"),
None, None,
); );
layer.commit();
Box::leak(Box::new(layer)); let _ = layer_shell_windows.insert(
commands.entity(entity).insert(LayerShellRoleAssigned); entity,
LayerShellWindow::new(layer, layer_shell_settings.clone()),
);
commands.entity(entity).insert(SurfaceConfigured);
}
}
fn update_layer_shell_settings(
mut layer_shell_windows: NonSendMut<LayerShellWindows>,
windows: Query<(Entity, &Window, &LayerShellSettings), Without<SurfaceConfigured>>,
) {
for (entity, _window, layer_shell_settings) in &windows {
let layer_shell_window = layer_shell_windows.get_mut(&entity).unwrap();
layer_shell_window.set_settings(layer_shell_settings.clone());
} }
} }
+5 -1
View File
@@ -1,11 +1,12 @@
use bevy::{app::PluginsState, prelude::*}; use bevy::{app::PluginsState, prelude::*};
use smithay_client_toolkit::{ use smithay_client_toolkit::{
delegate_registry, delegate_registry,
globals::ProvidesBoundGlobal,
output::OutputState, output::OutputState,
reexports::{ reexports::{
calloop::EventLoop, calloop::EventLoop,
calloop_wayland_source::WaylandSource, calloop_wayland_source::WaylandSource,
client::{globals::registry_queue_init, Connection}, client::{globals::registry_queue_init, protocol::wl_compositor::WlCompositor, Connection},
}, },
registry::{ProvidesRegistryState, RegistryState}, registry::{ProvidesRegistryState, RegistryState},
registry_handlers, registry_handlers,
@@ -13,6 +14,7 @@ use smithay_client_toolkit::{
}; };
mod input_handler; mod input_handler;
pub mod input_region;
pub mod layer_shell; pub mod layer_shell;
mod output_handler; mod output_handler;
mod surface_handler; mod surface_handler;
@@ -44,6 +46,7 @@ impl Plugin for WaylandPlugin {
input_handler::InputHandlerPlugin, input_handler::InputHandlerPlugin,
surface_handler::SurfaceHandlerPlugin, surface_handler::SurfaceHandlerPlugin,
layer_shell::LayerShellPlugin, layer_shell::LayerShellPlugin,
input_region::InputRegionPlugin,
)); ));
app.set_runner(|app| runner(app, event_loop)); app.set_runner(|app| runner(app, event_loop));
} }
@@ -76,4 +79,5 @@ impl ProvidesRegistryState for WaylandState {
} }
registry_handlers!(OutputState, SeatState); registry_handlers!(OutputState, SeatState);
} }
delegate_registry!(WaylandState); delegate_registry!(WaylandState);
+6 -2
View File
@@ -19,6 +19,8 @@ use smithay_client_toolkit::{
use crate::WaylandState; use crate::WaylandState;
#[derive(Component)]
pub struct SurfaceConfigured;
pub struct SurfaceHandlerPlugin; pub struct SurfaceHandlerPlugin;
impl Plugin for SurfaceHandlerPlugin { impl Plugin for SurfaceHandlerPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
@@ -113,6 +115,10 @@ impl WaylandSurfaces {
.get(&entity) .get(&entity)
.map(|surface_id| self.windows.get(surface_id))? .map(|surface_id| self.windows.get(surface_id))?
} }
pub fn get_window_entity(&self, surface_id: &ObjectId) -> Option<&Entity> {
self.surface_to_entity.get(surface_id)
}
} }
pub struct WaylandSurface { pub struct WaylandSurface {
@@ -173,8 +179,6 @@ pub fn create_windows(
if wayland_surfaces.get_window_wrapper(entity).is_some() { if wayland_surfaces.get_window_wrapper(entity).is_some() {
continue; continue;
} }
println!("Creating Window");
let surface = wayland_surfaces.create_surface( let surface = wayland_surfaces.create_surface(
entity, entity,
&queue_handle, &queue_handle,