Compare commits

...

13 Commits

Author SHA1 Message Date
doloro 1eee28ccc8 bump: 0.2.0 2026-05-25 12:18:08 +01:00
doloro 64e1e5481e migrate: 0.16.1 to 0.18.1 (thanks claude!) 2026-05-25 12:17:42 +01:00
doloro 97dfe31197 update 2026-05-25 11:44:16 +01:00
Shoaib Merchant 6d9e637217 Merge pull request #1 from namana-mecha/main
feat: adds wayland window integration in bevy
2025-09-11 15:30:17 +05:30
Naman Agrawal acf9fd122f feat: Add basic support for foreign top level management.
- Allow users to minimize other applications by sending a
`ForeignToplevelEvent`.
2025-09-01 11:04:34 +05:30
Naman Agrawal 541e0e646f feat: add support to trigger refresh through external events.
- Allow users to trigger a dispatch by using
`Res<ExternalEventDispatcher>`.
2025-08-28 17:30:43 +05:30
Naman Agrawal a91508672e cleanup: add session lock import to prelude
- Add `SessionLockWindow` and `SessionLockEvent` to
`bevy_wayland::prelude::*`
2025-08-28 11:21:16 +05:30
Naman Agrawal 556222135c Merge pull request #1 from akshayr-mecha/cleanup
cleanup: adds prelude module
2025-08-28 11:17:39 +05:30
akshayr-mecha 6564845183 cleanup: adds prelude module 2025-08-28 11:16:08 +05:30
Naman Agrawal 3259ef0d53 feat: add feature to create session lock surfaces.
- Allow users to create session lock surfaces by passing an event
`SessionLockEvent::Lock` and unlock them using
`SessionLockEvent::Unlock`.
- Other surfaces of the application should be destroyed before the
session lock is aquired. The wayland client is only allowed to render to
the lock surfaces while the lock is active.
2025-08-28 11:11:48 +05:30
Naman Agrawal fc96e7a8ac feat: layer shell size now has the option to inherit from the bevy window.
- We had to always manually specify the size of the layer shell window,
now it can be inherited from the bevy window by setting
`layer_shell_settings.size = LayerShellWindowSize::Inherit` which is the
default.
- You can still manully set the layer shell size by setting
`layer_shell_settings.size = LayerShellWindowSize::Fixed(width,
height)`.
2025-08-27 11:11:28 +05:30
Naman Agrawal 494b2cdf52 chore: removed unused imports 2025-08-27 11:08:29 +05:30
Naman Agrawal ce15260e3d chore: simplified layer shell example
- The layer shell example now opens only one window instead of opening
multiple windows.
2025-08-27 11:07:24 +05:30
21 changed files with 2058 additions and 1599 deletions
+1
View File
@@ -0,0 +1 @@
use flake
+1
View File
@@ -20,3 +20,4 @@ logs.txt
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
/.direnv
Generated
+826 -1474
View File
File diff suppressed because it is too large Load Diff
+11 -2
View File
@@ -1,11 +1,20 @@
[package]
name = "bevy_wayland"
version = "0.1.0"
version = "0.2.0"
edition = "2024"
[dependencies]
bevy = "0.16.1"
bevy = { version = "0.18.1", default-features = false, features = [
"bevy_ui",
"bevy_window",
"trace",
"bevy_winit",
"wayland",
"multi_threaded",
] }
lazy_static = "1.5.0"
raw-window-handle = "0.6.2"
smithay-client-toolkit = "0.20.0"
wayland-backend = { version = "0.3.11", features = ["client_system"] }
wayland-protocols-wlr = "0.3.9"
+93
View File
@@ -0,0 +1,93 @@
Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+136
View File
@@ -0,0 +1,136 @@
use std::time::Duration;
use bevy::{color::palettes::basic::*, prelude::*, window::WindowResolution, winit::WinitPlugin};
use bevy_wayland::{prelude::*, ExternalEventDispatcher};
const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15);
const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25);
const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35);
fn main() {
App::new()
.add_plugins((
DefaultPlugins
.build()
.disable::<WinitPlugin>()
.set(WindowPlugin {
primary_window: Some(Window {
resolution: WindowResolution::new(400.0, 400.0),
present_mode: bevy::window::PresentMode::AutoVsync,
..Default::default()
}),
..Default::default()
}),
WaylandPlugin,
))
.add_systems(Startup, (setup, external_tick_sender))
.add_systems(Update, (button_system, exit_on_esc))
.run();
}
fn external_tick_sender(external_event_dispatcher: Res<ExternalEventDispatcher>) {
let displatcher = external_event_dispatcher.clone();
let mut count = 5;
std::thread::spawn(move || loop {
println!("Spawned Thread");
std::thread::sleep(Duration::from_secs(1));
displatcher.dispatch().unwrap();
count -= 1;
if count < 0 {
return;
}
});
}
#[allow(clippy::type_complexity)]
fn button_system(
mut interaction_query: Query<
(
&Interaction,
&mut BackgroundColor,
&mut BorderColor,
&Children,
),
(Changed<Interaction>, With<Button>),
>,
mut text_query: Query<&mut Text>,
) {
//info!("Button system was called!!");
for (interaction, mut color, mut border_color, children) in &mut interaction_query {
let mut text = text_query.get_mut(children[0]).unwrap();
match *interaction {
Interaction::Pressed => {
**text = "Press".to_string();
*color = PRESSED_BUTTON.into();
border_color.0 = RED.into();
}
Interaction::Hovered => {
**text = "Hover".to_string();
*color = HOVERED_BUTTON.into();
border_color.0 = Color::WHITE;
}
Interaction::None => {
**text = "Button".to_string();
*color = NORMAL_BUTTON.into();
border_color.0 = Color::BLACK;
}
}
}
}
fn setup(mut commands: Commands, assets: Res<AssetServer>, windows: Query<Entity, With<Window>>) {
for entity in &windows {
commands.entity(entity).insert((LayerShellSettings {
anchor: Anchor::TOP | Anchor::LEFT,
layer: Layer::Bottom,
..Default::default()
},));
}
// ui camera
commands.spawn(Camera2d);
commands.spawn(button(&assets));
}
fn exit_on_esc(keys: Res<ButtonInput<KeyCode>>) {
if keys.just_pressed(KeyCode::Escape) {
std::process::exit(0);
}
}
fn button(asset_server: &AssetServer) -> impl Bundle + use<> {
(
Node {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
},
children![(
Button,
Node {
width: Val::Px(150.0),
height: Val::Px(65.0),
border: UiRect::all(Val::Px(5.0)),
// horizontally center child text
justify_content: JustifyContent::Center,
// vertically center child text
align_items: AlignItems::Center,
..default()
},
BorderColor(Color::BLACK),
BorderRadius::MAX,
BackgroundColor(NORMAL_BUTTON),
children![(
Text::new("Button"),
TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 33.0,
..default()
},
TextColor(Color::srgb(0.9, 0.9, 0.9)),
TextShadow::default(),
)]
)],
)
}
+122
View File
@@ -0,0 +1,122 @@
use bevy::{color::palettes::basic::*, prelude::*, window::WindowResolution, winit::WinitPlugin};
use bevy_wayland::{foreign_toplevel_manager::ForeignToplevelEvent, prelude::*};
const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15);
const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25);
const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35);
fn main() {
App::new()
.add_plugins((
DefaultPlugins
.build()
.disable::<WinitPlugin>()
.set(WindowPlugin {
primary_window: Some(Window {
resolution: WindowResolution::new(400.0, 400.0),
present_mode: bevy::window::PresentMode::AutoVsync,
..Default::default()
}),
..Default::default()
}),
WaylandPlugin,
))
.add_systems(Startup, setup)
.add_systems(Update, (button_system, exit_on_esc))
.run();
}
#[allow(clippy::type_complexity)]
fn button_system(
mut interaction_query: Query<
(
&Interaction,
&mut BackgroundColor,
&mut BorderColor,
&Children,
),
(Changed<Interaction>, With<Button>),
>,
mut foreign_toplevel_event_writer: EventWriter<ForeignToplevelEvent>,
mut text_query: Query<&mut Text>,
) {
//info!("Button system was called!!");
for (interaction, mut color, mut border_color, children) in &mut interaction_query {
let mut text = text_query.get_mut(children[0]).unwrap();
match *interaction {
Interaction::Pressed => {
**text = "Press".to_string();
*color = PRESSED_BUTTON.into();
border_color.0 = RED.into();
foreign_toplevel_event_writer.write(ForeignToplevelEvent::MinimizeOthers);
}
Interaction::Hovered => {
**text = "Hover".to_string();
*color = HOVERED_BUTTON.into();
border_color.0 = Color::WHITE;
}
Interaction::None => {
**text = "Button".to_string();
*color = NORMAL_BUTTON.into();
border_color.0 = Color::BLACK;
}
}
}
}
fn setup(mut commands: Commands, assets: Res<AssetServer>, windows: Query<Entity, With<Window>>) {
for entity in &windows {
commands.entity(entity).insert((LayerShellSettings {
anchor: Anchor::TOP | Anchor::LEFT,
layer: Layer::Top,
..Default::default()
},));
}
// ui camera
commands.spawn(Camera2d);
commands.spawn(button(&assets));
}
fn exit_on_esc(keys: Res<ButtonInput<KeyCode>>) {
if keys.just_pressed(KeyCode::Escape) {
std::process::exit(0);
}
}
fn button(asset_server: &AssetServer) -> impl Bundle + use<> {
(
Node {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
},
children![(
Button,
Node {
width: Val::Px(150.0),
height: Val::Px(65.0),
border: UiRect::all(Val::Px(5.0)),
// horizontally center child text
justify_content: JustifyContent::Center,
// vertically center child text
align_items: AlignItems::Center,
..default()
},
BorderColor(Color::BLACK),
BorderRadius::MAX,
BackgroundColor(NORMAL_BUTTON),
children![(
Text::new("Button"),
TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 33.0,
..default()
},
TextColor(Color::srgb(0.9, 0.9, 0.9)),
TextShadow::default(),
)]
)],
)
}
+205
View File
@@ -0,0 +1,205 @@
use bevy::{
prelude::*,
window::{exit_on_all_closed, WindowRef},
winit::WinitPlugin,
};
use bevy_wayland::prelude::*;
use smithay_client_toolkit::shell::wlr_layer::{Anchor, Layer};
const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15);
const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25);
fn main() {
App::new()
.add_plugins((
DefaultPlugins
.build()
.disable::<WinitPlugin>()
.set(WindowPlugin {
primary_window: None,
..Default::default()
}),
WaylandPlugin,
))
.add_systems(Startup, setup)
.add_systems(
Update,
(
button_system,
exit_on_esc,
setup_session_lock_windows,
exit_on_all_closed,
),
)
.run();
}
#[allow(clippy::type_complexity)]
fn button_system(
mut interaction_query: Query<
(
&Interaction,
&mut BackgroundColor,
&mut BorderColor,
&Children,
),
(Changed<Interaction>, With<UnlockButton>),
>,
mut text_query: Query<&mut Text>,
mut session_lock_event_writer: EventWriter<SessionLockEvent>,
) {
for (interaction, mut color, mut border_color, children) in &mut interaction_query {
let mut text = text_query.get_mut(children[0]).unwrap();
match *interaction {
Interaction::Pressed => {
session_lock_event_writer.write(SessionLockEvent::Unlock);
}
Interaction::Hovered => {
**text = "Click to unlock".to_string();
*color = HOVERED_BUTTON.into();
border_color.0 = Color::WHITE;
}
Interaction::None => {
**text = "Click to unlock".to_string();
*color = NORMAL_BUTTON.into();
border_color.0 = Color::BLACK;
}
}
}
}
#[derive(Component)]
struct LockButton;
#[derive(Component)]
struct UnlockButton;
fn setup(
mut commands: Commands,
assets: Res<AssetServer>,
windows: Query<Entity, With<Window>>,
mut session_lock_event_writer: EventWriter<SessionLockEvent>,
) {
session_lock_event_writer.write(SessionLockEvent::Lock);
for entity in &windows {
commands.entity(entity).insert((LayerShellSettings {
anchor: Anchor::TOP | Anchor::LEFT,
layer: Layer::Bottom,
..Default::default()
},));
}
// ui camera
commands.spawn(Camera2d);
commands.spawn(lock_button(&assets));
}
#[derive(Component)]
struct ConfiguredWindow;
#[derive(Component)]
struct SessionLockCamera;
fn setup_session_lock_windows(
mut commands: Commands,
asset_server: Res<AssetServer>,
windows: Query<(Entity, &SessionLockWindow), Without<ConfiguredWindow>>,
) {
for (entity, _) in &windows {
let camera = commands
.spawn((
Camera2d,
Camera {
target: bevy::render::camera::RenderTarget::Window(WindowRef::Entity(entity)),
..Default::default()
},
SessionLockCamera,
))
.id();
commands.entity(entity).insert(ConfiguredWindow);
commands.spawn(unlock_button(&asset_server, camera));
}
}
fn exit_on_esc(keys: Res<ButtonInput<KeyCode>>) {
if keys.just_pressed(KeyCode::Escape) {
std::process::exit(0);
}
}
fn lock_button(asset_server: &AssetServer) -> impl Bundle + use<> {
(
Node {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
},
children![(
Button,
LockButton,
Node {
width: Val::Px(250.0),
height: Val::Px(65.0),
border: UiRect::all(Val::Px(5.0)),
// horizontally center child text
justify_content: JustifyContent::Center,
// vertically center child text
align_items: AlignItems::Center,
..default()
},
BorderColor(Color::BLACK),
BorderRadius::MAX,
BackgroundColor(NORMAL_BUTTON),
children![(
Text::new("Button"),
TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 33.0,
..default()
},
TextColor(Color::srgb(0.9, 0.9, 0.9)),
TextShadow::default(),
)]
)],
)
}
fn unlock_button(asset_server: &AssetServer, camera: Entity) -> impl Bundle + use<> {
(
Node {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
},
UiTargetCamera(camera),
children![(
Button,
UnlockButton,
Node {
width: Val::Px(250.0),
height: Val::Px(65.0),
border: UiRect::all(Val::Px(5.0)),
// horizontally center child text
justify_content: JustifyContent::Center,
// vertically center child text
align_items: AlignItems::Center,
..default()
},
BorderColor(Color::BLACK),
BorderRadius::MAX,
BackgroundColor(NORMAL_BUTTON),
children![(
Text::new("Button"),
TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 33.0,
..default()
},
TextColor(Color::srgb(0.9, 0.9, 0.9)),
TextShadow::default(),
)]
)],
)
}
@@ -6,8 +6,7 @@ use bevy::{
window::{WindowCreated, WindowResolution},
winit::WinitPlugin,
};
use bevy_wayland::{input_region::InputRegion, layer_shell::LayerShellSettings, WaylandPlugin};
use smithay_client_toolkit::shell::wlr_layer::{Anchor, Layer};
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);
@@ -28,12 +27,8 @@ fn main() {
}),
WaylandPlugin,
))
.init_resource::<NewWindowInfo>()
.add_systems(Startup, setup)
.add_systems(
Update,
(button_system, spawn_window, setup_new_window, exit_on_esc),
)
.add_systems(Update, (button_system, exit_on_esc))
.run();
}
@@ -72,18 +67,13 @@ fn button_system(
}
}
#[derive(Component, Deref, DerefMut)]
struct WindowTimer(Timer);
fn setup(mut commands: Commands, assets: Res<AssetServer>, windows: Query<Entity, With<Window>>) {
for entity in &windows {
commands.entity(entity).insert((
LayerShellSettings {
commands.entity(entity).insert((LayerShellSettings {
anchor: Anchor::TOP | Anchor::LEFT,
layer: Layer::Bottom,
..Default::default()
},
InputRegion(Rect::new(0., 0., 200., 200.)),
));
},));
}
// ui camera
commands.spawn(Camera2d);
@@ -96,69 +86,6 @@ fn exit_on_esc(keys: Res<ButtonInput<KeyCode>>) {
}
}
fn spawn_window(
mut commands: Commands,
mut windows: Query<(Entity, &mut WindowTimer)>,
mut new_window_info: ResMut<NewWindowInfo>,
keys: Res<ButtonInput<KeyCode>>,
time: Res<Time>,
) {
if keys.pressed(KeyCode::KeyN) {
println!("Pressed");
let new_window_entity = commands
.spawn((
Window {
title: "UI Only Window".to_string(),
resolution: (400., 50.).into(),
..default()
},
LayerShellSettings {
layer: Layer::Top,
anchor: Anchor::TOP | Anchor::LEFT,
..default()
},
WindowTimer(Timer::new(Duration::from_secs(5), TimerMode::Once)),
))
.id();
new_window_info.entity = Some(new_window_entity);
new_window_info.is_setup_pending = true;
}
}
#[derive(Resource, Default)]
struct NewWindowInfo {
entity: Option<Entity>,
is_setup_pending: bool,
}
fn setup_new_window(
mut commands: Commands,
mut window_created_events: EventReader<WindowCreated>,
mut new_window_info: ResMut<NewWindowInfo>,
asset_server: Res<AssetServer>, // For fonts
) {
for event in window_created_events.read() {
if Some(event.window) == new_window_info.entity && new_window_info.is_setup_pending {
info!(
"New UI window created (ID: {:?}), setting up its camera and UI.",
event.window
);
commands.spawn((
Camera {
target: bevy::render::camera::RenderTarget::Window(
bevy::window::WindowRef::Entity(event.window),
),
clear_color: ClearColorConfig::Custom(Color::default()),
..default()
},
Camera2d,
));
new_window_info.is_setup_pending = false; // Mark as setup complete
}
}
}
fn button(asset_server: &AssetServer) -> impl Bundle + use<> {
(
Node {
Generated
+94
View File
@@ -0,0 +1,94 @@
{
"nodes": {
"advisory-db": {
"flake": false,
"locked": {
"lastModified": 1779575509,
"narHash": "sha256-wXKYURZz76ZC5lbuDA1oVQA/MxSB3pSJ1raF1HG0oIc=",
"owner": "rustsec",
"repo": "advisory-db",
"rev": "831c50f4a4304068f125e603add6a8839f08b3eb",
"type": "github"
},
"original": {
"owner": "rustsec",
"repo": "advisory-db",
"type": "github"
}
},
"crane": {
"locked": {
"lastModified": 1779130139,
"narHash": "sha256-BLrtr42azquO7MdGFU5a7KiMl3YpFlTeIXqy1fT5GlQ=",
"owner": "ipetkov",
"repo": "crane",
"rev": "edb38893982a3338972bb4a2ec7ce7c29ba10fd9",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1779536132,
"narHash": "sha256-q+fF42iv/geEbHfgSzy3tS0FF/EyD6XTZ98E6yxiBO8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "3d8f0f3f72a6cd4d93d0ad13203f2ea1cb7e1456",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"advisory-db": "advisory-db",
"crane": "crane",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
+191
View File
@@ -0,0 +1,191 @@
{
description = "Build a cargo project";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
crane.url = "github:ipetkov/crane";
flake-utils.url = "github:numtide/flake-utils";
advisory-db = {
url = "github:rustsec/advisory-db";
flake = false;
};
};
outputs = {
self,
nixpkgs,
crane,
flake-utils,
advisory-db,
...
}:
flake-utils.lib.eachDefaultSystem (
system: let
pkgs = nixpkgs.legacyPackages.${system};
inherit (pkgs) lib;
craneLib = crane.mkLib pkgs;
src = craneLib.cleanCargoSource ./.;
# Common arguments can be set here to avoid repeating them later
commonArgs = {
inherit src;
strictDeps = true;
buildInputs = with pkgs;
[
mold
# for Linux
# Audio (Linux only)
alsa-lib
# Cross Platform 3D Graphics API
vulkan-loader
# For debugging around vulkan
vulkan-tools
# Other dependencies
libudev-zero
libx11
libxcursor
libxi
libxrandr
libxkbcommon
wayland
]
++ lib.optionals pkgs.stdenv.isDarwin [
# Additional darwin specific inputs can be set here
pkgs.libiconv
];
# Additional environment variables can be set directly
# MY_CUSTOM_VAR = "some value";
};
# Build *just* the cargo dependencies, so we can reuse
# all of that work (e.g. via cachix) when running in CI
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
# Build the actual crate itself, reusing the dependency
# artifacts from above.
my-crate = craneLib.buildPackage (
commonArgs
// {
inherit cargoArtifacts;
}
);
in {
checks = {
# Build the crate as part of `nix flake check` for convenience
inherit my-crate;
# Run clippy (and deny all warnings) on the crate source,
# again, reusing the dependency artifacts from above.
#
# Note that this is done as a separate derivation so that
# we can block the CI if there are issues here, but not
# prevent downstream consumers from building our crate by itself.
my-crate-clippy = craneLib.cargoClippy (
commonArgs
// {
inherit cargoArtifacts;
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
}
);
my-crate-doc = craneLib.cargoDoc (
commonArgs
// {
inherit cargoArtifacts;
# This can be commented out or tweaked as necessary, e.g. set to
# `--deny rustdoc::broken-intra-doc-links` to only enforce that lint
env.RUSTDOCFLAGS = "--deny warnings";
}
);
# Check formatting
my-crate-fmt = craneLib.cargoFmt {
inherit src;
};
my-crate-toml-fmt = craneLib.taploFmt {
src = pkgs.lib.sources.sourceFilesBySuffices src [".toml"];
# taplo arguments can be further customized below as needed
# taploExtraArgs = "--config ./taplo.toml";
};
# Audit dependencies
my-crate-audit = craneLib.cargoAudit {
inherit src advisory-db;
};
# Audit licenses
my-crate-deny = craneLib.cargoDeny {
inherit src;
};
# Run tests with cargo-nextest
# Consider setting `doCheck = false` on `my-crate` if you do not want
# the tests to run twice
my-crate-nextest = craneLib.cargoNextest (
commonArgs
// {
inherit cargoArtifacts;
partitions = 1;
partitionType = "count";
cargoNextestPartitionsExtraArgs = "--no-tests=pass";
}
);
};
packages = {
default = my-crate;
};
apps.default = flake-utils.lib.mkApp {
drv = my-crate;
};
devShells.default = craneLib.devShell {
# Inherit inputs from checks.
checks = self.checks.${system};
# Additional dev-shell environment variables can be set directly
# MY_CUSTOM_DEVELOPMENT_VAR = "something else";
# Extra inputs can be added here; cargo and rustc are provided by default.
packages = with pkgs; [
pkgs.clang
mold
# for Linux
# Audio (Linux only)
alsa-lib
# Cross Platform 3D Graphics API
vulkan-loader
# For debugging around vulkan
vulkan-tools
# Other dependencies
libudev-zero
libx11
libxcursor
libxi
libxrandr
libxkbcommon
wayland
pkg-config
];
LD_LIBRARY_PATH = lib.makeLibraryPath [
pkgs.vulkan-loader
pkgs.libx11
pkgs.libxi
pkgs.libxcursor
pkgs.libxkbcommon
pkgs.wayland
];
# PKG_CONFIG_PATH = "${pkgs.wayland}/lib/pkgconfig:${pkgs.alsa-lib}/lib";
};
}
);
}
+96
View File
@@ -0,0 +1,96 @@
use bevy::prelude::*;
use smithay_client_toolkit::{
reexports::client::{Dispatch, QueueHandle, event_created_child},
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, Message)]
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_message::<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: MessageReader<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>,
) {
}
}
+6 -6
View File
@@ -409,9 +409,9 @@ impl KeyboardHandler for WaylandState {
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().write_message(keyboard_event.clone());
self.world_mut()
.send_event(WindowEvent::KeyboardInput(keyboard_event));
.write_message(WindowEvent::KeyboardInput(keyboard_event));
}
fn repeat_key(
@@ -426,9 +426,9 @@ impl KeyboardHandler for WaylandState {
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().write_message(keyboard_event.clone());
self.world_mut()
.send_event(WindowEvent::KeyboardInput(keyboard_event));
.write_message(WindowEvent::KeyboardInput(keyboard_event));
}
fn release_key(
@@ -442,9 +442,9 @@ impl KeyboardHandler for WaylandState {
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().write_message(keyboard_event.clone());
self.world_mut()
.send_event(WindowEvent::KeyboardInput(keyboard_event));
.write_message(WindowEvent::KeyboardInput(keyboard_event));
}
fn update_modifiers(
+7 -7
View File
@@ -101,26 +101,26 @@ impl PointerHandler for WaylandState {
let window_event: WindowEvent = pointer_event;
match window_event.clone() {
WindowEvent::CursorEntered(e) => {
self.world_mut().send_event(e);
self.world_mut().write_message(e);
}
WindowEvent::CursorLeft(e) => {
self.world_mut().send_event(e);
self.world_mut().write_message(e);
}
WindowEvent::CursorMoved(e) => {
self.world_mut().send_event(e);
self.world_mut().write_message(e);
}
WindowEvent::MouseButtonInput(e) => {
self.world_mut().send_event(e);
self.world_mut().write_message(e);
}
WindowEvent::MouseMotion(e) => {
self.world_mut().send_event(e);
self.world_mut().write_message(e);
}
WindowEvent::MouseWheel(e) => {
self.world_mut().send_event(e);
self.world_mut().write_message(e);
}
_ => {}
}
self.world_mut().send_event::<WindowEvent>(window_event);
self.world_mut().write_message::<WindowEvent>(window_event);
}
}
}
+3 -6
View File
@@ -1,10 +1,7 @@
use bevy::{core_pipeline::core_2d::graph::input, prelude::*};
use smithay_client_toolkit::{
compositor::{CompositorState, Region},
reexports::client::{protocol::wl_compositor::WlCompositor, QueueHandle},
};
use bevy::prelude::*;
use smithay_client_toolkit::compositor::{CompositorState, Region};
use crate::{input_region, surface_handler::WaylandSurfaces, WaylandState};
use crate::surface_handler::WaylandSurfaces;
#[derive(Component, Deref)]
pub struct InputRegion(pub Rect);
+37 -11
View File
@@ -1,7 +1,7 @@
use bevy::{platform::collections::HashMap, prelude::*, ui::update};
use bevy::{platform::collections::HashMap, prelude::*};
use smithay_client_toolkit::{
delegate_layer,
reexports::client::{globals::GlobalList, Connection, Proxy, QueueHandle},
reexports::client::{globals::GlobalList, QueueHandle},
shell::{
wlr_layer::{
Anchor, KeyboardInteractivity, Layer, LayerShell, LayerShellHandler, LayerSurface,
@@ -11,7 +11,7 @@ use smithay_client_toolkit::{
};
use crate::{
surface_handler::{create_windows, SurfaceConfigured, WaylandSurfaces},
surface_handler::{SurfaceConfigured, SurfaceHandlerSystems, WaylandSurfaces},
WaylandState,
};
@@ -21,12 +21,18 @@ 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) -> Self {
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
@@ -42,8 +48,12 @@ impl LayerShellWindow {
self.layer_surface
.set_exclusive_zone(self.layer_shell_settings.exclusive_zone);
let (width, height) = self.layer_shell_settings.size;
self.layer_surface.set_size(400, 400);
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);
@@ -59,6 +69,13 @@ impl LayerShellWindow {
}
}
#[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.
@@ -66,7 +83,7 @@ pub struct LayerShellSettings {
/// You can anchor the layer surface to any combination of the top, bottom, left, and right edges of the screen.
pub anchor: Anchor,
/// Defines the size of the layer surface in pixels.
pub size: (u32, u32),
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
@@ -104,7 +121,10 @@ impl Default for LayerShellSettings {
pub struct LayerShellPlugin;
impl Plugin for LayerShellPlugin {
fn build(&self, app: &mut App) {
app.add_systems(PreUpdate, assign_layer_shell_role.after(create_windows))
app.add_systems(
PreUpdate,
assign_layer_shell_role.after(SurfaceHandlerSystems::CreateWindows),
)
.add_systems(Update, update_layer_shell_settings)
.insert_non_send_resource(LayerShellWindows::default());
}
@@ -118,7 +138,7 @@ fn assign_layer_shell_role(
windows: Query<(Entity, &Window, &LayerShellSettings), Without<SurfaceConfigured>>,
mut layer_shell_windows: NonSendMut<LayerShellWindows>,
) {
for (entity, _window, layer_shell_settings) in &windows {
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!")
@@ -136,7 +156,11 @@ fn assign_layer_shell_role(
let _ = layer_shell_windows.insert(
entity,
LayerShellWindow::new(layer, layer_shell_settings.clone()),
LayerShellWindow::new(
layer,
layer_shell_settings.clone(),
(window.width() as u32, window.height() as u32),
),
);
commands.entity(entity).insert(SurfaceConfigured);
@@ -147,8 +171,10 @@ 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 {
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());
}
}
+54 -10
View File
@@ -1,40 +1,77 @@
use std::{
sync::mpsc::SendError,
time::{Duration, Instant},
};
use bevy::{app::PluginsState, prelude::*};
use smithay_client_toolkit::{
delegate_registry,
output::OutputState,
reexports::{
calloop::EventLoop,
calloop::{self, EventLoop, channel::Sender},
calloop_wayland_source::WaylandSource,
client::{globals::registry_queue_init, Connection},
client::{Connection, globals::registry_queue_init},
},
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::WaylandPlugin;
pub use crate::input_region::InputRegion;
pub use crate::layer_shell::{LayerShellSettings, LayerShellWindowSize};
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!");
Connection::connect_to_env().expect("Failed to connect to wayland socket!");
let event_loop =
EventLoop::<WaylandState>::try_new().expect("failed to create 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");
.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");
.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);
@@ -42,10 +79,12 @@ impl Plugin for WaylandPlugin {
app.add_plugins((
output_handler::OutputHandlerPlugin,
input_handler::InputHandlerPlugin,
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));
}
@@ -56,14 +95,19 @@ pub fn runner(mut app: App, mut event_loop: EventLoop<'_, WaylandState>) -> AppE
app.finish();
app.cleanup();
}
let mut state = WaylandState(app);
loop {
// TODO: Error handling
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(None, &mut state);
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);
}
}
+156
View File
@@ -0,0 +1,156 @@
use bevy::{platform::collections::HashMap, prelude::*};
use smithay_client_toolkit::{
delegate_session_lock,
output::OutputState,
reexports::client::{globals::GlobalList, protocol::wl_output::WlOutput, QueueHandle},
session_lock::{SessionLock, SessionLockHandler, SessionLockState, SessionLockSurface},
};
use crate::{
surface_handler::{SurfaceConfigured, SurfaceHandlerSystems, 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, Message)]
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_message::<SessionLockEvent>();
app.add_systems(
PreUpdate,
(
session_lock_event_handler.before(SurfaceHandlerSystems::CreateWindows),
configure_lock_surfaces.after(SurfaceHandlerSystems::CreateWindows),
),
);
}
}
#[derive(Deref, DerefMut, Default)]
struct SessionLockWrapper(Option<SessionLock>);
fn session_lock_event_handler(
mut commands: Commands,
mut session_lock_event_reader: MessageReader<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);
+12 -3
View File
@@ -13,7 +13,7 @@ use smithay_client_toolkit::{
compositor::{CompositorHandler, CompositorState},
delegate_compositor,
reexports::client::{
backend::ObjectId, protocol::wl_surface::WlSurface, Connection, Proxy, QueueHandle,
Connection, Proxy, QueueHandle, backend::ObjectId, protocol::wl_surface::WlSurface,
},
};
@@ -21,6 +21,12 @@ use crate::WaylandState;
#[derive(Component)]
pub struct SurfaceConfigured;
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
pub enum SurfaceHandlerSystems {
CreateWindows,
}
pub struct SurfaceHandlerPlugin;
impl Plugin for SurfaceHandlerPlugin {
fn build(&self, app: &mut App) {
@@ -30,7 +36,10 @@ impl Plugin for SurfaceHandlerPlugin {
CompositorState::bind(globals, queue_handle).expect("failed to bind compositor!"),
);
app.insert_non_send_resource(WaylandSurfaces::default());
app.add_systems(PreUpdate, create_windows);
app.add_systems(
PreUpdate,
create_windows.in_set(SurfaceHandlerSystems::CreateWindows),
);
}
}
@@ -173,7 +182,7 @@ pub fn create_windows(
connection: NonSend<Connection>,
queue_handle: NonSend<QueueHandle<WaylandState>>,
bevy_windows: Query<(Entity, Option<&RawHandleWrapperHolder>), With<Window>>,
mut window_created_event: EventWriter<WindowCreated>,
mut window_created_event: MessageWriter<WindowCreated>,
) {
for (entity, handle_holder) in &bevy_windows {
if wayland_surfaces.get_window_wrapper(entity).is_some() {