Merge pull request #1 from namana-mecha/main

feat: adds wayland window integration in bevy
This commit is contained in:
Shoaib Merchant
2025-09-11 15:30:17 +05:30
committed by GitHub
20 changed files with 6871 additions and 0 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
Generated
+4614
View File
File diff suppressed because it is too large Load Diff
+20
View File
@@ -0,0 +1,20 @@
[package]
name = "bevy_wayland"
version = "0.1.0"
edition = "2024"
[dependencies]
bevy = { version = "0.16.1", default-features = false, features = [
"bevy_ui",
"bevy_window",
"trace",
"bevy_winit",
"wayland",
"multi_threaded",
] }
lazy_static = "1.5.0"
raw-window-handle = "0.6.2"
smithay-client-toolkit = "0.20.0"
wayland-backend = { version = "0.3.11", features = ["client_system"] }
wayland-protocols-wlr = "0.3.9"
+93
View File
@@ -0,0 +1,93 @@
Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+136
View File
@@ -0,0 +1,136 @@
use std::time::Duration;
use bevy::{color::palettes::basic::*, prelude::*, window::WindowResolution, winit::WinitPlugin};
use bevy_wayland::{prelude::*, ExternalEventDispatcher};
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() {
App::new()
.add_plugins((
DefaultPlugins
.build()
.disable::<WinitPlugin>()
.set(WindowPlugin {
primary_window: Some(Window {
resolution: WindowResolution::new(400.0, 400.0),
present_mode: bevy::window::PresentMode::AutoVsync,
..Default::default()
}),
..Default::default()
}),
WaylandPlugin,
))
.add_systems(Startup, (setup, external_tick_sender))
.add_systems(Update, (button_system, exit_on_esc))
.run();
}
fn external_tick_sender(external_event_dispatcher: Res<ExternalEventDispatcher>) {
let displatcher = external_event_dispatcher.clone();
let mut count = 5;
std::thread::spawn(move || loop {
println!("Spawned Thread");
std::thread::sleep(Duration::from_secs(1));
displatcher.dispatch().unwrap();
count -= 1;
if count < 0 {
return;
}
});
}
#[allow(clippy::type_complexity)]
fn button_system(
mut interaction_query: Query<
(
&Interaction,
&mut BackgroundColor,
&mut BorderColor,
&Children,
),
(Changed<Interaction>, With<Button>),
>,
mut text_query: Query<&mut Text>,
) {
//info!("Button system was called!!");
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;
}
}
}
}
fn setup(mut commands: Commands, assets: Res<AssetServer>, windows: Query<Entity, With<Window>>) {
for entity in &windows {
commands.entity(entity).insert((LayerShellSettings {
anchor: Anchor::TOP | Anchor::LEFT,
layer: Layer::Bottom,
..Default::default()
},));
}
// ui camera
commands.spawn(Camera2d);
commands.spawn(button(&assets));
}
fn exit_on_esc(keys: Res<ButtonInput<KeyCode>>) {
if keys.just_pressed(KeyCode::Escape) {
std::process::exit(0);
}
}
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(),
)]
)],
)
}
+122
View File
@@ -0,0 +1,122 @@
use bevy::{color::palettes::basic::*, prelude::*, window::WindowResolution, winit::WinitPlugin};
use bevy_wayland::{foreign_toplevel_manager::ForeignToplevelEvent, prelude::*};
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() {
App::new()
.add_plugins((
DefaultPlugins
.build()
.disable::<WinitPlugin>()
.set(WindowPlugin {
primary_window: Some(Window {
resolution: WindowResolution::new(400.0, 400.0),
present_mode: bevy::window::PresentMode::AutoVsync,
..Default::default()
}),
..Default::default()
}),
WaylandPlugin,
))
.add_systems(Startup, setup)
.add_systems(Update, (button_system, exit_on_esc))
.run();
}
#[allow(clippy::type_complexity)]
fn button_system(
mut interaction_query: Query<
(
&Interaction,
&mut BackgroundColor,
&mut BorderColor,
&Children,
),
(Changed<Interaction>, With<Button>),
>,
mut foreign_toplevel_event_writer: EventWriter<ForeignToplevelEvent>,
mut text_query: Query<&mut Text>,
) {
//info!("Button system was called!!");
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();
foreign_toplevel_event_writer.write(ForeignToplevelEvent::MinimizeOthers);
}
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;
}
}
}
}
fn setup(mut commands: Commands, assets: Res<AssetServer>, windows: Query<Entity, With<Window>>) {
for entity in &windows {
commands.entity(entity).insert((LayerShellSettings {
anchor: Anchor::TOP | Anchor::LEFT,
layer: Layer::Top,
..Default::default()
},));
}
// ui camera
commands.spawn(Camera2d);
commands.spawn(button(&assets));
}
fn exit_on_esc(keys: Res<ButtonInput<KeyCode>>) {
if keys.just_pressed(KeyCode::Escape) {
std::process::exit(0);
}
}
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(),
)]
)],
)
}
+205
View File
@@ -0,0 +1,205 @@
use bevy::{
prelude::*,
window::{exit_on_all_closed, WindowRef},
winit::WinitPlugin,
};
use bevy_wayland::prelude::*;
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);
fn main() {
App::new()
.add_plugins((
DefaultPlugins
.build()
.disable::<WinitPlugin>()
.set(WindowPlugin {
primary_window: None,
..Default::default()
}),
WaylandPlugin,
))
.add_systems(Startup, setup)
.add_systems(
Update,
(
button_system,
exit_on_esc,
setup_session_lock_windows,
exit_on_all_closed,
),
)
.run();
}
#[allow(clippy::type_complexity)]
fn button_system(
mut interaction_query: Query<
(
&Interaction,
&mut BackgroundColor,
&mut BorderColor,
&Children,
),
(Changed<Interaction>, With<UnlockButton>),
>,
mut text_query: Query<&mut Text>,
mut session_lock_event_writer: EventWriter<SessionLockEvent>,
) {
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 => {
session_lock_event_writer.write(SessionLockEvent::Unlock);
}
Interaction::Hovered => {
**text = "Click to unlock".to_string();
*color = HOVERED_BUTTON.into();
border_color.0 = Color::WHITE;
}
Interaction::None => {
**text = "Click to unlock".to_string();
*color = NORMAL_BUTTON.into();
border_color.0 = Color::BLACK;
}
}
}
}
#[derive(Component)]
struct LockButton;
#[derive(Component)]
struct UnlockButton;
fn setup(
mut commands: Commands,
assets: Res<AssetServer>,
windows: Query<Entity, With<Window>>,
mut session_lock_event_writer: EventWriter<SessionLockEvent>,
) {
session_lock_event_writer.write(SessionLockEvent::Lock);
for entity in &windows {
commands.entity(entity).insert((LayerShellSettings {
anchor: Anchor::TOP | Anchor::LEFT,
layer: Layer::Bottom,
..Default::default()
},));
}
// ui camera
commands.spawn(Camera2d);
commands.spawn(lock_button(&assets));
}
#[derive(Component)]
struct ConfiguredWindow;
#[derive(Component)]
struct SessionLockCamera;
fn setup_session_lock_windows(
mut commands: Commands,
asset_server: Res<AssetServer>,
windows: Query<(Entity, &SessionLockWindow), Without<ConfiguredWindow>>,
) {
for (entity, _) in &windows {
let camera = commands
.spawn((
Camera2d,
Camera {
target: bevy::render::camera::RenderTarget::Window(WindowRef::Entity(entity)),
..Default::default()
},
SessionLockCamera,
))
.id();
commands.entity(entity).insert(ConfiguredWindow);
commands.spawn(unlock_button(&asset_server, camera));
}
}
fn exit_on_esc(keys: Res<ButtonInput<KeyCode>>) {
if keys.just_pressed(KeyCode::Escape) {
std::process::exit(0);
}
}
fn lock_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,
LockButton,
Node {
width: Val::Px(250.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(),
)]
)],
)
}
fn unlock_button(asset_server: &AssetServer, camera: Entity) -> impl Bundle + use<> {
(
Node {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
},
UiTargetCamera(camera),
children![(
Button,
UnlockButton,
Node {
width: Val::Px(250.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(),
)]
)],
)
}
+125
View File
@@ -0,0 +1,125 @@
use std::time::Duration;
use bevy::{
color::palettes::basic::*,
prelude::*,
window::{WindowCreated, WindowResolution},
winit::WinitPlugin,
};
use bevy_wayland::prelude::*;
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() {
App::new()
.add_plugins((
DefaultPlugins
.build()
.disable::<WinitPlugin>()
.set(WindowPlugin {
primary_window: Some(Window {
resolution: WindowResolution::new(400.0, 400.0),
..Default::default()
}),
..Default::default()
}),
WaylandPlugin,
))
.add_systems(Startup, setup)
.add_systems(Update, (button_system, exit_on_esc))
.run();
}
#[allow(clippy::type_complexity)]
fn button_system(
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;
}
}
}
}
fn setup(mut commands: Commands, assets: Res<AssetServer>, windows: Query<Entity, With<Window>>) {
for entity in &windows {
commands.entity(entity).insert((LayerShellSettings {
anchor: Anchor::TOP | Anchor::LEFT,
layer: Layer::Bottom,
..Default::default()
},));
}
// ui camera
commands.spawn(Camera2d);
commands.spawn(button(&assets));
}
fn exit_on_esc(keys: Res<ButtonInput<KeyCode>>) {
if keys.just_pressed(KeyCode::Escape) {
std::process::exit(0);
}
}
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(),
)]
)],
)
}
+96
View File
@@ -0,0 +1,96 @@
use bevy::prelude::*;
use smithay_client_toolkit::{
reexports::client::{event_created_child, Dispatch, QueueHandle},
registry::RegistryState,
};
use wayland_protocols_wlr::foreign_toplevel::v1::client::{
zwlr_foreign_toplevel_handle_v1::{self, ZwlrForeignToplevelHandleV1},
zwlr_foreign_toplevel_manager_v1::{self, ZwlrForeignToplevelManagerV1},
};
use crate::WaylandState;
#[derive(Debug, Copy, Clone, Event)]
pub enum ForeignToplevelEvent {
MinimizeOthers,
}
#[derive(Default, Deref, DerefMut)]
struct ForeignToplevels(Vec<ZwlrForeignToplevelHandleV1>);
pub struct ForeignToplevelManagerPlugin;
impl Plugin for ForeignToplevelManagerPlugin {
fn build(&self, app: &mut App) {
let registry_state = app.world().non_send_resource::<RegistryState>();
let queue_handle = app.world().non_send_resource::<QueueHandle<WaylandState>>();
let foreign_top_level_manager =
registry_state.bind_one::<ZwlrForeignToplevelManagerV1, _, _>(queue_handle, 2..=3, ());
if let Ok(foreign_top_level_manager) = foreign_top_level_manager {
info!("Foreign toplevel manager was bound!");
app.insert_non_send_resource(foreign_top_level_manager);
app.insert_non_send_resource(ForeignToplevels::default());
app.add_event::<ForeignToplevelEvent>();
app.add_systems(Update, foreign_top_level_event_handler);
} else {
let bind_error = foreign_top_level_manager.err().unwrap();
error!("Couldn't bind foreign toplevel manager! {:?}", bind_error);
}
}
}
fn foreign_top_level_event_handler(
foreign_top_levels: NonSendMut<ForeignToplevels>,
mut events: EventReader<ForeignToplevelEvent>,
) {
for event in events.read() {
match event {
ForeignToplevelEvent::MinimizeOthers => {
info!("Minimizing other windows");
for toplevel in foreign_top_levels.iter() {
toplevel.set_minimized();
}
}
}
}
}
impl Dispatch<ZwlrForeignToplevelManagerV1, ()> for WaylandState {
fn event(
state: &mut Self,
_proxy: &ZwlrForeignToplevelManagerV1,
event: <ZwlrForeignToplevelManagerV1 as smithay_client_toolkit::reexports::client::Proxy>::Event,
_data: &(),
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qhandle: &QueueHandle<Self>,
) {
let mut foreign_toplevels = state
.world_mut()
.non_send_resource_mut::<ForeignToplevels>();
match event {
wayland_protocols_wlr::foreign_toplevel::v1::client::zwlr_foreign_toplevel_manager_v1::Event::Toplevel { toplevel } => {
foreign_toplevels.push(toplevel);
},
wayland_protocols_wlr::foreign_toplevel::v1::client::zwlr_foreign_toplevel_manager_v1::Event::Finished => {},
_ => {},
}
}
event_created_child!(WaylandState, ZwlrForeignToplevelManagerV1, [
// Opcode 0 is the `toplevel` event. It creates a new `zwlr_foreign_toplevel_handle_v1`.
zwlr_foreign_toplevel_manager_v1::EVT_TOPLEVEL_OPCODE => (
zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1,
()
)
]);
}
impl Dispatch<ZwlrForeignToplevelHandleV1, ()> for WaylandState {
fn event(
_state: &mut Self,
_proxy: &ZwlrForeignToplevelHandleV1,
_event: <ZwlrForeignToplevelHandleV1 as smithay_client_toolkit::reexports::client::Proxy>::Event,
_data: &(),
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qhandle: &QueueHandle<Self>,
) {
}
}
+462
View File
@@ -0,0 +1,462 @@
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(keyboard_event.clone());
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(keyboard_event.clone());
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(keyboard_event.clone());
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,
) {
}
}
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);
+127
View File
@@ -0,0 +1,127 @@
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, 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;
match window_event.clone() {
WindowEvent::CursorEntered(e) => {
self.world_mut().send_event(e);
}
WindowEvent::CursorLeft(e) => {
self.world_mut().send_event(e);
}
WindowEvent::CursorMoved(e) => {
self.world_mut().send_event(e);
}
WindowEvent::MouseButtonInput(e) => {
self.world_mut().send_event(e);
}
WindowEvent::MouseMotion(e) => {
self.world_mut().send_event(e);
}
WindowEvent::MouseWheel(e) => {
self.world_mut().send_event(e);
}
_ => {}
}
self.world_mut().send_event::<WindowEvent>(window_event);
}
}
}
delegate_pointer!(WaylandState);
+41
View File
@@ -0,0 +1,41 @@
use bevy::prelude::*;
use smithay_client_toolkit::compositor::{CompositorState, Region};
use crate::surface_handler::WaylandSurfaces;
#[derive(Component, Deref)]
pub struct InputRegion(pub 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.min.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);
}
}
}
+198
View File
@@ -0,0 +1,198 @@
use bevy::{platform::collections::HashMap, prelude::*};
use smithay_client_toolkit::{
delegate_layer,
reexports::client::{globals::GlobalList, QueueHandle},
shell::{
wlr_layer::{
Anchor, KeyboardInteractivity, Layer, LayerShell, LayerShellHandler, LayerSurface,
},
WaylandSurface,
},
};
use crate::{
surface_handler::{create_windows, SurfaceConfigured, WaylandSurfaces},
WaylandState,
};
#[derive(Default, Deref, DerefMut)]
struct LayerShellWindows(HashMap<Entity, LayerShellWindow>);
struct LayerShellWindow {
layer_surface: LayerSurface,
layer_shell_settings: LayerShellSettings,
window_size: (u32, u32),
}
impl LayerShellWindow {
fn new(
layer_surface: LayerSurface,
layer_shell_settings: LayerShellSettings,
window_size: (u32, u32),
) -> Self {
let mut layer_shell_window = Self {
layer_surface,
layer_shell_settings,
window_size,
};
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);
if let LayerShellWindowSize::Fixed(width, height) = self.layer_shell_settings.size {
self.layer_surface.set_size(width, height);
} else {
self.layer_surface
.set_size(self.window_size.0, self.window_size.1);
}
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(Default, Eq, PartialEq, Clone, Debug)]
pub enum LayerShellWindowSize {
#[default]
Inherit,
Fixed(u32, u32),
}
#[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: LayerShellWindowSize,
/// 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: KeyboardInteractivity::OnDemand,
layer: Layer::Top,
}
}
}
pub struct LayerShellPlugin;
impl Plugin for LayerShellPlugin {
fn build(&self, app: &mut App) {
app.add_systems(PreUpdate, assign_layer_shell_role.after(create_windows))
.add_systems(Update, update_layer_shell_settings)
.insert_non_send_resource(LayerShellWindows::default());
}
}
fn assign_layer_shell_role(
mut commands: Commands,
wayland_surfaces: NonSend<WaylandSurfaces>,
queue_handle: NonSend<QueueHandle<WaylandState>>,
globals: NonSend<GlobalList>,
windows: Query<(Entity, &Window, &LayerShellSettings), Without<SurfaceConfigured>>,
mut layer_shell_windows: NonSendMut<LayerShellWindows>,
) {
for (entity, window, layer_shell_settings) in &windows {
let window_wrapper = wayland_surfaces.get_window_wrapper(entity);
let surface = window_wrapper
.expect("tried to assign role before creating surface!")
.wl_surface();
let layer_shell =
LayerShell::bind(&globals, &queue_handle).expect("layer shell not available!");
let layer = layer_shell.create_layer_surface(
&queue_handle,
surface.clone(),
layer_shell_settings.layer,
Some("simple_layer"),
None,
);
let _ = layer_shell_windows.insert(
entity,
LayerShellWindow::new(
layer,
layer_shell_settings.clone(),
(window.width() as u32, window.height() as u32),
),
);
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();
let window_size = (window.width() as u32, window.height() as u32);
layer_shell_window.window_size = window_size;
layer_shell_window.set_settings(layer_shell_settings.clone());
}
}
impl LayerShellHandler for WaylandState {
fn closed(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_layer: &smithay_client_toolkit::shell::wlr_layer::LayerSurface,
) {
}
fn configure(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_layer: &smithay_client_toolkit::shell::wlr_layer::LayerSurface,
_configure: smithay_client_toolkit::shell::wlr_layer::LayerSurfaceConfigure,
_serial: u32,
) {
}
}
delegate_layer!(WaylandState);
+126
View File
@@ -0,0 +1,126 @@
use std::{
sync::mpsc::SendError,
time::{Duration, Instant},
};
use bevy::{app::PluginsState, prelude::*};
use smithay_client_toolkit::{
delegate_registry,
output::OutputState,
reexports::{
calloop::{self, channel::Sender, EventLoop},
calloop_wayland_source::WaylandSource,
client::{globals::registry_queue_init, Connection},
},
registry::{ProvidesRegistryState, RegistryState},
registry_handlers,
seat::SeatState,
};
pub mod foreign_toplevel_manager;
mod input_handler;
pub mod input_region;
pub mod layer_shell;
mod output_handler;
pub mod session_lock;
mod surface_handler;
pub mod prelude {
pub use crate::input_region::InputRegion;
pub use crate::layer_shell::{LayerShellSettings, LayerShellWindowSize};
pub use crate::session_lock::{SessionLockEvent, SessionLockWindow};
pub use crate::WaylandPlugin;
pub use smithay_client_toolkit::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer};
}
pub struct Tick;
#[derive(Resource, Clone)]
pub struct ExternalEventDispatcher(Sender<Tick>);
impl ExternalEventDispatcher {
fn new(tx: Sender<Tick>) -> Self {
Self(tx)
}
pub fn dispatch(&self) -> Result<(), SendError<Tick>> {
self.0.send(Tick)
}
}
#[derive(Default)]
pub struct WaylandPlugin;
impl Plugin for WaylandPlugin {
fn build(&self, app: &mut App) {
let connection =
Connection::connect_to_env().expect("Failed to connect to wayland socket!");
let event_loop =
EventLoop::<WaylandState>::try_new().expect("Failed to create event_loop!");
let (globals, event_queue) = registry_queue_init::<WaylandState>(&connection)
.expect("Failed to init registry queue");
let qh = event_queue.handle();
let loop_handle = event_loop.handle();
WaylandSource::new(connection.clone(), event_queue)
.insert(loop_handle.clone())
.expect("Failed to insert wayland source to event loop");
let (tx, rx) = calloop::channel::channel::<Tick>();
loop_handle
.insert_source(rx, |_, _, state| {
info!("External event was received!");
if state.plugins_state() == PluginsState::Cleaned {
state.update();
}
})
.expect("Failed to insert external tick channel!");
app.insert_resource(ExternalEventDispatcher::new(tx));
app.insert_non_send_resource(RegistryState::new(&globals));
app.insert_non_send_resource(connection.clone());
app.insert_non_send_resource(globals);
app.insert_non_send_resource(qh);
app.add_plugins((
output_handler::OutputHandlerPlugin,
surface_handler::SurfaceHandlerPlugin,
input_handler::InputHandlerPlugin,
layer_shell::LayerShellPlugin,
session_lock::SessionLockPlugin,
input_region::InputRegionPlugin,
foreign_toplevel_manager::ForeignToplevelManagerPlugin,
));
app.set_runner(|app| runner(app, event_loop));
}
}
pub fn runner(mut app: App, mut event_loop: EventLoop<'_, WaylandState>) -> AppExit {
if app.plugins_state() == PluginsState::Ready {
app.finish();
app.cleanup();
}
let mut state = WaylandState(app);
loop {
let frame_start = Instant::now();
let _ = event_loop.dispatch(Duration::from_millis(5000), &mut state);
if state.plugins_state() == PluginsState::Cleaned {
state.update();
}
let _ = event_loop.dispatch(Duration::from_millis(0), &mut state);
// TODO: Poll until delta time is greater than target frame time.
if Instant::now() - frame_start < Duration::from_millis(16) {
std::thread::sleep(Duration::from_millis(16) - (frame_start - Instant::now()));
}
let _ = event_loop.dispatch(Duration::from_millis(0), &mut state);
}
}
#[derive(Deref, DerefMut)]
pub struct WaylandState(App);
impl ProvidesRegistryState for WaylandState {
fn registry(&mut self) -> &mut smithay_client_toolkit::registry::RegistryState {
self.world_mut()
.non_send_resource_mut::<RegistryState>()
.into_inner()
}
registry_handlers!(OutputState, SeatState);
}
delegate_registry!(WaylandState);
+53
View File
@@ -0,0 +1,53 @@
use bevy::prelude::*;
use smithay_client_toolkit::{
delegate_output,
output::{OutputHandler, OutputState},
reexports::client::QueueHandle,
};
use crate::WaylandState;
pub struct OutputHandlerPlugin;
impl Plugin for OutputHandlerPlugin {
fn build(&self, app: &mut App) {
let globals = app.world().non_send_resource();
let queue_handle: &QueueHandle<WaylandState> = app.world().non_send_resource();
let output_state = OutputState::new(globals, queue_handle);
app.insert_non_send_resource(output_state);
}
}
impl OutputHandler for WaylandState {
fn output_state(&mut self) -> &mut OutputState {
self.world_mut()
.non_send_resource_mut::<OutputState>()
.into_inner()
}
fn new_output(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_output: smithay_client_toolkit::reexports::client::protocol::wl_output::WlOutput,
) {
info!("new output was added");
}
fn update_output(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_output: smithay_client_toolkit::reexports::client::protocol::wl_output::WlOutput,
) {
}
fn output_destroyed(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_output: smithay_client_toolkit::reexports::client::protocol::wl_output::WlOutput,
) {
}
}
delegate_output!(WaylandState);
+156
View File
@@ -0,0 +1,156 @@
use bevy::{platform::collections::HashMap, prelude::*};
use smithay_client_toolkit::{
delegate_session_lock,
output::OutputState,
reexports::client::{globals::GlobalList, protocol::wl_output::WlOutput, QueueHandle},
session_lock::{SessionLock, SessionLockHandler, SessionLockState, SessionLockSurface},
};
use crate::{
surface_handler::{create_windows, SurfaceConfigured, WaylandSurfaces},
WaylandState,
};
#[derive(Default, Deref, DerefMut)]
struct SessionLockWindows(HashMap<Entity, SessionLockWindowInternal>);
struct SessionLockWindowInternal {
_session_lock_surface: SessionLockSurface,
}
#[derive(Component)]
pub struct SessionLockWindow;
#[derive(Component)]
struct SessionLockUnconfiguredWindow {
output: WlOutput,
}
impl SessionLockUnconfiguredWindow {
pub fn new(output: WlOutput) -> Self {
Self { output }
}
}
#[derive(Clone, Copy, Event)]
pub enum SessionLockEvent {
Lock,
Unlock,
}
pub struct SessionLockPlugin;
impl Plugin for SessionLockPlugin {
fn build(&self, app: &mut App) {
let globals = app.world().non_send_resource::<GlobalList>();
let queue_handle = app.world().non_send_resource::<QueueHandle<WaylandState>>();
let session_lock_state = SessionLockState::new(globals, queue_handle);
app.insert_non_send_resource(session_lock_state);
app.insert_non_send_resource(SessionLockWindows::default());
app.insert_non_send_resource(SessionLockWrapper::default());
app.add_event::<SessionLockEvent>();
app.add_systems(
PreUpdate,
(
session_lock_event_handler.before(create_windows),
configure_lock_surfaces.after(create_windows),
),
);
}
}
#[derive(Deref, DerefMut, Default)]
struct SessionLockWrapper(Option<SessionLock>);
fn session_lock_event_handler(
mut commands: Commands,
mut session_lock_event_reader: EventReader<SessionLockEvent>,
session_lock_state: NonSend<SessionLockState>,
mut session_lock_wrapper: NonSendMut<SessionLockWrapper>,
queue_handle: NonSend<QueueHandle<WaylandState>>,
output_state: NonSend<OutputState>,
) {
for session_lock_event in session_lock_event_reader.read() {
match session_lock_event {
SessionLockEvent::Lock => {
if session_lock_wrapper.is_some() {
error!("Lock was called even if it was already aquired");
return;
}
let session_lock = session_lock_state
.lock(&queue_handle)
.expect("Unable to aquire session lock");
let _ = session_lock_wrapper.insert(session_lock);
for output in output_state.outputs() {
commands.spawn((
Window::default(),
SessionLockUnconfiguredWindow::new(output),
));
}
}
SessionLockEvent::Unlock => {
if let Some(session_lock) = &**session_lock_wrapper {
session_lock.unlock();
}
}
}
}
}
fn configure_lock_surfaces(
mut commands: Commands,
mut session_lock_windows: NonSendMut<SessionLockWindows>,
session_lock_wrapper: NonSend<SessionLockWrapper>,
wayland_surfaces: NonSend<WaylandSurfaces>,
qh: NonSend<QueueHandle<WaylandState>>,
unconfigured_windows: Query<(Entity, &SessionLockUnconfiguredWindow)>,
) {
if let Some(session_lock) = &**session_lock_wrapper {
for (entity, unconfigured_window) in &unconfigured_windows {
let window_wrapper = wayland_surfaces.get_window_wrapper(entity);
let surface = window_wrapper
.expect("tried to assign role before creating surface!")
.wl_surface();
let _session_lock_surface =
session_lock.create_lock_surface(surface.clone(), &unconfigured_window.output, &qh);
let session_lock_window = SessionLockWindowInternal {
_session_lock_surface,
};
session_lock_windows.insert(entity, session_lock_window);
commands
.entity(entity)
.insert(SurfaceConfigured)
.insert(SessionLockWindow)
.remove::<SessionLockUnconfiguredWindow>();
}
}
}
impl SessionLockHandler for WaylandState {
fn locked(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &smithay_client_toolkit::reexports::client::QueueHandle<Self>,
_session_lock: smithay_client_toolkit::session_lock::SessionLock,
) {
}
fn finished(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &smithay_client_toolkit::reexports::client::QueueHandle<Self>,
_session_lock: smithay_client_toolkit::session_lock::SessionLock,
) {
}
fn configure(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &smithay_client_toolkit::reexports::client::QueueHandle<Self>,
_surface: smithay_client_toolkit::session_lock::SessionLockSurface,
_configure: smithay_client_toolkit::session_lock::SessionLockSurfaceConfigure,
_serial: u32,
) {
}
}
delegate_session_lock!(WaylandState);
+198
View File
@@ -0,0 +1,198 @@
use std::collections::HashMap;
use bevy::{
ecs::entity::EntityHashMap,
prelude::*,
window::{RawHandleWrapper, RawHandleWrapperHolder, WindowCreated, WindowWrapper},
};
use raw_window_handle::{
DisplayHandle, HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle,
WaylandDisplayHandle, WaylandWindowHandle, WindowHandle,
};
use smithay_client_toolkit::{
compositor::{CompositorHandler, CompositorState},
delegate_compositor,
reexports::client::{
backend::ObjectId, protocol::wl_surface::WlSurface, Connection, Proxy, QueueHandle,
},
};
use crate::WaylandState;
#[derive(Component)]
pub struct SurfaceConfigured;
pub struct SurfaceHandlerPlugin;
impl Plugin for SurfaceHandlerPlugin {
fn build(&self, app: &mut App) {
let queue_handle: &QueueHandle<WaylandState> = app.world().non_send_resource();
let globals = app.world().non_send_resource();
app.insert_non_send_resource(
CompositorState::bind(globals, queue_handle).expect("failed to bind compositor!"),
);
app.insert_non_send_resource(WaylandSurfaces::default());
app.add_systems(PreUpdate, create_windows);
}
}
impl CompositorHandler for WaylandState {
fn scale_factor_changed(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_surface: &smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface,
_new_factor: i32,
) {
}
fn transform_changed(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_surface: &smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface,
_new_transform: smithay_client_toolkit::reexports::client::protocol::wl_output::Transform,
) {
}
fn frame(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_surface: &smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface,
_time: u32,
) {
}
fn surface_enter(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_surface: &smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface,
_output: &smithay_client_toolkit::reexports::client::protocol::wl_output::WlOutput,
) {
}
fn surface_leave(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_surface: &smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface,
_output: &smithay_client_toolkit::reexports::client::protocol::wl_output::WlOutput,
) {
}
}
delegate_compositor!(WaylandState);
#[derive(Default)]
pub struct WaylandSurfaces {
windows: HashMap<ObjectId, WindowWrapper<WaylandSurface>>,
entity_to_surface: EntityHashMap<ObjectId>,
surface_to_entity: HashMap<ObjectId, Entity>,
_not_send_sync: core::marker::PhantomData<*const ()>,
}
impl WaylandSurfaces {
pub fn create_surface(
&mut self,
entity: Entity,
queue_handle: &QueueHandle<WaylandState>,
connection: Connection,
compositor_state: &CompositorState,
) -> &WindowWrapper<WaylandSurface> {
let wl_surface = compositor_state.create_surface(queue_handle);
let wayland_surface = WaylandSurface::new(wl_surface, connection);
let surface_id = wayland_surface.id();
self.windows
.insert(wayland_surface.id(), WindowWrapper::new(wayland_surface));
self.entity_to_surface.insert(entity, surface_id.clone());
self.surface_to_entity.insert(surface_id.clone(), entity);
self.windows.get(&surface_id).unwrap()
}
pub fn get_window_wrapper(&self, entity: Entity) -> Option<&WindowWrapper<WaylandSurface>> {
self.entity_to_surface
.get(&entity)
.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 {
surface: WlSurface,
connection: Connection,
}
impl WaylandSurface {
pub fn new(surface: WlSurface, connection: Connection) -> Self {
Self {
surface,
connection,
}
}
pub fn wl_surface(&self) -> &WlSurface {
&self.surface
}
pub fn id(&self) -> ObjectId {
self.surface.id()
}
}
impl HasWindowHandle for WaylandSurface {
fn window_handle(
&self,
) -> std::result::Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError>
{
let raw_window_handle = RawWindowHandle::Wayland(WaylandWindowHandle::new(
core::ptr::NonNull::new(self.wl_surface().id().as_ptr() as *mut _).unwrap(),
));
unsafe { Ok(WindowHandle::borrow_raw(raw_window_handle)) }
}
}
impl HasDisplayHandle for WaylandSurface {
fn display_handle(
&self,
) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
let raw_display_handle = RawDisplayHandle::Wayland(WaylandDisplayHandle::new(
core::ptr::NonNull::new(self.connection.backend().display_ptr() as *mut _).unwrap(),
));
unsafe { Ok(DisplayHandle::borrow_raw(raw_display_handle)) }
}
}
pub fn create_windows(
mut commands: Commands,
mut wayland_surfaces: NonSendMut<WaylandSurfaces>,
compositor_state: NonSend<CompositorState>,
connection: NonSend<Connection>,
queue_handle: NonSend<QueueHandle<WaylandState>>,
bevy_windows: Query<(Entity, Option<&RawHandleWrapperHolder>), With<Window>>,
mut window_created_event: EventWriter<WindowCreated>,
) {
for (entity, handle_holder) in &bevy_windows {
if wayland_surfaces.get_window_wrapper(entity).is_some() {
continue;
}
let surface = wayland_surfaces.create_surface(
entity,
&queue_handle,
connection.clone(),
&compositor_state,
);
let mut wrapper: Option<_> = None;
if let Ok(handle_wrapper) = RawHandleWrapper::new(surface) {
wrapper = Some(handle_wrapper.clone());
if let Some(handle_holder) = handle_holder {
*handle_holder.0.lock().unwrap() = Some(handle_wrapper);
}
}
commands.entity(entity).insert(wrapper.unwrap());
window_created_event.write(WindowCreated { window: entity });
}
}