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
|
||||
debug
|
||||
target
|
||||
logs.txt
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.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