Merge pull request #1 from namana-mecha/main
feat: adds wayland window integration in bevy
This commit is contained in:
@@ -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
File diff suppressed because it is too large
Load Diff
+20
@@ -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"
|
||||||
@@ -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.
|
||||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -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(),
|
||||||
|
)]
|
||||||
|
)],
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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(),
|
||||||
|
)]
|
||||||
|
)],
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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(),
|
||||||
|
)]
|
||||||
|
)],
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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(),
|
||||||
|
)]
|
||||||
|
)],
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
@@ -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);
|
||||||
@@ -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);
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -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);
|
||||||
@@ -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);
|
||||||
@@ -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);
|
||||||
@@ -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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user