Integrate native Rime and stabilize input, output, and rendering
- Add optional modifier-only Super binding whose tap is cancelled by other input, focus changes, grabs, locks, and session teardown. - Ship the native ferese-rime user service, Rime defaults, installer inventory, package dependencies, session environment, and docs. - Vendor Smithay 8a27023 with the SHM texture-cache format fix and add GPU regressions for format changes and resize snapshots. - Release reused CRTC owners before staging output modes, then restore changed outputs before former owners when a transaction fails. - Wait for resize-capture GPU completion, expand filtered surface damage, and preserve IME popup origin under fractional scaling. - Mark weekends like holidays in both calendar layouts.
This commit is contained in:
1 parent
a2be421974
commit
993bc0f88f
391 files changed
+130908
-76
No files matched your search
Vendored
+65
@@ -0,0 +1,65 @@
|
||||
[package]
|
||||
authors = ["Victor Berger <[email protected]>", "Drakulix (Victoria Brekenfeld)"]
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
name = "anvil"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2.2.1"
|
||||
fps_ticker = {version = "1.0.0", optional = true}
|
||||
image = {version = "0.25.1", default-features = false, optional = true, features = ["png"]}
|
||||
rand = "0.8"
|
||||
tracing = { version = "0.1.37", features = ["max_level_trace", "release_max_level_debug"] }
|
||||
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
|
||||
thiserror = "1"
|
||||
xcursor = {version = "0.3.3", optional = true}
|
||||
xkbcommon = "0.8.0"
|
||||
renderdoc = {version = "0.11.0", optional = true}
|
||||
smithay-drm-extras = {path = "../smithay-drm-extras", optional = true}
|
||||
puffin_http = { version = "0.13", optional = true }
|
||||
profiling = { version = "1.0" }
|
||||
|
||||
[dependencies.smithay]
|
||||
default-features = false
|
||||
features = ["desktop", "wayland_frontend"]
|
||||
path = ".."
|
||||
|
||||
[dependencies.x11rb]
|
||||
default-features = false
|
||||
features = ["composite"]
|
||||
optional = true
|
||||
version = "0.13.0"
|
||||
|
||||
[build-dependencies]
|
||||
gl_generator = "0.14"
|
||||
|
||||
[features]
|
||||
debug = ["fps_ticker", "image/png", "renderdoc"]
|
||||
default = ["egl", "winit", "x11", "udev", "xwayland", "libei"]
|
||||
egl = ["smithay/use_system_lib", "smithay/backend_egl"]
|
||||
test_all_features = ["default", "debug"]
|
||||
udev = [
|
||||
"smithay-drm-extras",
|
||||
"smithay/backend_libinput",
|
||||
"smithay/backend_udev",
|
||||
"smithay/backend_drm",
|
||||
"smithay/backend_gbm",
|
||||
"smithay/backend_vulkan",
|
||||
"smithay/backend_egl",
|
||||
"smithay/backend_session_libseat",
|
||||
"image",
|
||||
"smithay/renderer_gl",
|
||||
"smithay/renderer_pixman",
|
||||
"smithay/renderer_multi",
|
||||
"xcursor",
|
||||
]
|
||||
winit = ["smithay/backend_winit", "smithay/backend_drm"]
|
||||
x11 = ["smithay/backend_x11", "x11rb", "smithay/renderer_gl", "smithay/backend_vulkan"]
|
||||
xwayland = ["smithay/xwayland", "x11rb", "smithay/x11rb_event_source", "xcursor"]
|
||||
libei = ["smithay/backend_libei", "udev"]
|
||||
profile-with-puffin = ["profiling/profile-with-puffin", "puffin_http"]
|
||||
profile-with-tracy = ["profiling/profile-with-tracy"]
|
||||
profile-with-tracy-mem = ["profile-with-tracy"]
|
||||
renderer_sync = []
|
||||
Vendored
+54
@@ -0,0 +1,54 @@
|
||||
# Anvil
|
||||
|
||||
A compositor used as a testing ground for new smithay features.
|
||||
For a simple example compositor consider reading [smallvil](https://github.com/Smithay/smithay/tree/master/smallvil)
|
||||
|
||||
## Dependencies
|
||||
|
||||
You'll need to install the following dependencies (note, that those package
|
||||
names may vary depending on your OS and linux distribution):
|
||||
|
||||
- `libwayland`
|
||||
- `libxkbcommon`
|
||||
|
||||
#### These are needed for the "Udev/DRM backend"
|
||||
|
||||
- `libudev`
|
||||
- `libinput`
|
||||
- `libgbm`
|
||||
- [`libseat`](https://git.sr.ht/~kennylevinsen/seatd)
|
||||
|
||||
If you want to enable X11 support (to run X11 applications within anvil),
|
||||
then you'll need to install the following packages as well:
|
||||
- `xwayland`
|
||||
|
||||
## Build and run
|
||||
|
||||
You can run it with cargo after having cloned this repository:
|
||||
|
||||
```
|
||||
cd anvil;
|
||||
|
||||
cargo run -- --{backend}
|
||||
```
|
||||
|
||||
The currently available backends are:
|
||||
|
||||
- `--x11`: start anvil as an X11 client. This allows you to run the compositor inside an X11 session or any compositor supporting XWayland. Should be preferred over the winit backend where possible.
|
||||
- `--winit`: start anvil as a [Winit](https://github.com/tomaka/winit) application. This allows you to run it
|
||||
inside of an other X11 or Wayland session.
|
||||
- `--tty-udev`: start anvil in a tty with udev support. This is the "traditional" launch of a Wayland
|
||||
compositor. Note that this requires you to start anvil as root if your system does not have logind
|
||||
available.
|
||||
|
||||
### Supported Environment Variables
|
||||
|
||||
| Variable | Example | Backends |
|
||||
|-------------------------------|-----------------|-----------|
|
||||
| ANVIL_DRM_DEVICE | /dev/dri/card0 | tty-udev |
|
||||
| ANVIL_DISABLE_10BIT | any | tty-udev |
|
||||
| ANVIL_DISABLE_DIRECT_SCANOUT | any | tty-udev |
|
||||
| ANVIL_GLES_DISABLE_INSTANCING | any | tty-udev |
|
||||
| ANVIL_NO_VULKAN | 1,true,yes,y | x11 |
|
||||
| SMITHAY_USE_LEGACY | 1,true,yes,y | tty-udev |
|
||||
| SMITHAY_VK_VERSION | 1.3 | |
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
Vendored
+96
@@ -0,0 +1,96 @@
|
||||
use std::{io::Read, time::Duration};
|
||||
|
||||
use tracing::warn;
|
||||
use xcursor::{
|
||||
CursorTheme,
|
||||
parser::{Image, parse_xcursor},
|
||||
};
|
||||
|
||||
static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../resources/cursor.rgba");
|
||||
|
||||
pub struct Cursor {
|
||||
icons: Vec<Image>,
|
||||
size: u32,
|
||||
}
|
||||
|
||||
impl Cursor {
|
||||
pub fn load() -> Cursor {
|
||||
let name = std::env::var("XCURSOR_THEME")
|
||||
.ok()
|
||||
.unwrap_or_else(|| "default".into());
|
||||
let size = std::env::var("XCURSOR_SIZE")
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(24);
|
||||
|
||||
let theme = CursorTheme::load(&name);
|
||||
let icons = load_icon(&theme)
|
||||
.map_err(|err| warn!("Unable to load xcursor: {}, using fallback cursor", err))
|
||||
.unwrap_or_else(|_| {
|
||||
vec![Image {
|
||||
size: 32,
|
||||
width: 64,
|
||||
height: 64,
|
||||
xhot: 1,
|
||||
yhot: 1,
|
||||
delay: 1,
|
||||
pixels_rgba: Vec::from(FALLBACK_CURSOR_DATA),
|
||||
pixels_argb: vec![], //unused
|
||||
}]
|
||||
});
|
||||
|
||||
Cursor { icons, size }
|
||||
}
|
||||
|
||||
pub fn get_image(&self, scale: u32, time: Duration) -> Image {
|
||||
let size = self.size * scale;
|
||||
frame(time.as_millis() as u32, size, &self.icons)
|
||||
}
|
||||
}
|
||||
|
||||
fn nearest_images(size: u32, images: &[Image]) -> impl Iterator<Item = &Image> {
|
||||
// Follow the nominal size of the cursor to choose the nearest
|
||||
let nearest_image = images
|
||||
.iter()
|
||||
.min_by_key(|image| (size as i32 - image.size as i32).abs())
|
||||
.unwrap();
|
||||
|
||||
images
|
||||
.iter()
|
||||
.filter(move |image| image.width == nearest_image.width && image.height == nearest_image.height)
|
||||
}
|
||||
|
||||
fn frame(mut millis: u32, size: u32, images: &[Image]) -> Image {
|
||||
let total = nearest_images(size, images).fold(0, |acc, image| acc + image.delay);
|
||||
if total == 0 {
|
||||
return nearest_images(size, images).next().unwrap().clone();
|
||||
}
|
||||
millis %= total;
|
||||
|
||||
for img in nearest_images(size, images) {
|
||||
if millis < img.delay {
|
||||
return img.clone();
|
||||
}
|
||||
millis -= img.delay;
|
||||
}
|
||||
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
enum Error {
|
||||
#[error("Theme has no default cursor")]
|
||||
NoDefaultCursor,
|
||||
#[error("Error opening xcursor file: {0}")]
|
||||
File(#[from] std::io::Error),
|
||||
#[error("Failed to parse XCursor file")]
|
||||
Parse,
|
||||
}
|
||||
|
||||
fn load_icon(theme: &CursorTheme) -> Result<Vec<Image>, Error> {
|
||||
let icon_path = theme.load_icon("default").ok_or(Error::NoDefaultCursor)?;
|
||||
let mut cursor_file = std::fs::File::open(icon_path)?;
|
||||
let mut cursor_data = Vec::new();
|
||||
cursor_file.read_to_end(&mut cursor_data)?;
|
||||
parse_xcursor(&cursor_data).ok_or(Error::Parse)
|
||||
}
|
||||
Vendored
+259
@@ -0,0 +1,259 @@
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use smithay::{
|
||||
backend::renderer::{
|
||||
Color32F, ImportAll, ImportMem, Renderer, Texture,
|
||||
element::{
|
||||
AsRenderElements, Kind,
|
||||
memory::{MemoryRenderBuffer, MemoryRenderBufferRenderElement},
|
||||
surface::WaylandSurfaceRenderElement,
|
||||
},
|
||||
},
|
||||
input::pointer::CursorImageStatus,
|
||||
render_elements,
|
||||
utils::{Physical, Point, Scale},
|
||||
};
|
||||
#[cfg(feature = "debug")]
|
||||
use smithay::{
|
||||
backend::renderer::{
|
||||
Frame,
|
||||
element::{Element, Id, RenderElement},
|
||||
utils::CommitCounter,
|
||||
},
|
||||
utils::{Buffer, Logical, Rectangle, Size, Transform, user_data::UserDataMap},
|
||||
};
|
||||
|
||||
pub static CLEAR_COLOR: Color32F = Color32F::new(0.8, 0.8, 0.9, 1.0);
|
||||
pub static CLEAR_COLOR_FULLSCREEN: Color32F = Color32F::new(0.0, 0.0, 0.0, 0.0);
|
||||
|
||||
pub struct PointerElement {
|
||||
buffer: Option<MemoryRenderBuffer>,
|
||||
status: CursorImageStatus,
|
||||
}
|
||||
|
||||
impl Default for PointerElement {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
buffer: Default::default(),
|
||||
status: CursorImageStatus::default_named(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PointerElement {
|
||||
pub fn set_status(&mut self, status: CursorImageStatus) {
|
||||
self.status = status;
|
||||
}
|
||||
|
||||
pub fn set_buffer(&mut self, buffer: MemoryRenderBuffer) {
|
||||
self.buffer = Some(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
render_elements! {
|
||||
pub PointerRenderElement<R> where R: ImportAll + ImportMem;
|
||||
Surface=WaylandSurfaceRenderElement<R>,
|
||||
Memory=MemoryRenderBufferRenderElement<R>,
|
||||
}
|
||||
|
||||
impl<R: Renderer> std::fmt::Debug for PointerRenderElement<R> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Surface(arg0) => f.debug_tuple("Surface").field(arg0).finish(),
|
||||
Self::Memory(arg0) => f.debug_tuple("Memory").field(arg0).finish(),
|
||||
Self::_GenericCatcher(arg0) => f.debug_tuple("_GenericCatcher").field(arg0).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Texture + Clone + Send + 'static, R> AsRenderElements<R> for PointerElement
|
||||
where
|
||||
R: Renderer<TextureId = T> + ImportAll + ImportMem,
|
||||
{
|
||||
type RenderElement = PointerRenderElement<R>;
|
||||
fn render_elements<E>(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
location: Point<i32, Physical>,
|
||||
scale: Scale<f64>,
|
||||
alpha: f32,
|
||||
) -> Vec<E>
|
||||
where
|
||||
E: From<PointerRenderElement<R>>,
|
||||
{
|
||||
match &self.status {
|
||||
CursorImageStatus::Hidden => vec![],
|
||||
// Always render `Default` for a named shape.
|
||||
CursorImageStatus::Named(_) => {
|
||||
if let Some(buffer) = self.buffer.as_ref() {
|
||||
vec![
|
||||
PointerRenderElement::<R>::from(
|
||||
MemoryRenderBufferRenderElement::from_buffer(
|
||||
renderer,
|
||||
location.to_f64(),
|
||||
buffer,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Kind::Cursor,
|
||||
)
|
||||
.expect("Lost system pointer buffer"),
|
||||
)
|
||||
.into(),
|
||||
]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
CursorImageStatus::Surface(surface) => {
|
||||
let elements: Vec<PointerRenderElement<R>> =
|
||||
smithay::backend::renderer::element::surface::render_elements_from_surface_tree(
|
||||
renderer,
|
||||
surface,
|
||||
location,
|
||||
scale,
|
||||
alpha,
|
||||
Kind::Cursor,
|
||||
);
|
||||
elements.into_iter().map(E::from).collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
pub static FPS_NUMBERS_PNG: &[u8] = include_bytes!("../resources/numbers.png");
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FpsElement<T: Texture> {
|
||||
id: Id,
|
||||
value: u32,
|
||||
texture: T,
|
||||
commit_counter: CommitCounter,
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
impl<T: Texture> FpsElement<T> {
|
||||
pub fn new(texture: T) -> Self {
|
||||
FpsElement {
|
||||
id: Id::new(),
|
||||
texture,
|
||||
value: 0,
|
||||
commit_counter: CommitCounter::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_fps(&mut self, fps: u32) {
|
||||
if self.value != fps {
|
||||
self.value = fps;
|
||||
self.commit_counter.increment();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
impl<T> Element for FpsElement<T>
|
||||
where
|
||||
T: Texture + 'static,
|
||||
{
|
||||
fn id(&self) -> &Id {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn location(&self, _scale: Scale<f64>) -> Point<i32, Physical> {
|
||||
(0, 0).into()
|
||||
}
|
||||
|
||||
fn src(&self) -> Rectangle<f64, Buffer> {
|
||||
let digits = if self.value < 10 {
|
||||
1
|
||||
} else if self.value < 100 {
|
||||
2
|
||||
} else {
|
||||
3
|
||||
};
|
||||
Rectangle::from_size((24 * digits, 35).into()).to_f64()
|
||||
}
|
||||
|
||||
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
|
||||
let digits = if self.value < 10 {
|
||||
1
|
||||
} else if self.value < 100 {
|
||||
2
|
||||
} else {
|
||||
3
|
||||
};
|
||||
Rectangle::from_size((24 * digits, 35).into()).to_physical_precise_round(scale)
|
||||
}
|
||||
|
||||
fn current_commit(&self) -> CommitCounter {
|
||||
self.commit_counter
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
impl<R> RenderElement<R> for FpsElement<R::TextureId>
|
||||
where
|
||||
R: Renderer + ImportAll,
|
||||
R::TextureId: 'static,
|
||||
{
|
||||
fn draw(
|
||||
&self,
|
||||
frame: &mut R::Frame<'_, '_>,
|
||||
_src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
_opaque_regions: &[Rectangle<i32, Physical>],
|
||||
_cache: Option<&UserDataMap>,
|
||||
) -> Result<(), R::Error> {
|
||||
// FIXME: respect the src for cropping
|
||||
let scale = dst.size.to_f64() / self.src().size;
|
||||
let value_str = std::cmp::min(self.value, 999).to_string();
|
||||
let mut offset: Point<f64, Physical> = Point::from((0.0, 0.0));
|
||||
for digit in value_str.chars().map(|d| d.to_digit(10).unwrap()) {
|
||||
let digit_location = dst.loc.to_f64() + offset;
|
||||
let digit_size = Size::<i32, Logical>::from((22, 35)).to_f64().to_physical(scale);
|
||||
let dst = Rectangle::new(
|
||||
digit_location.to_i32_round(),
|
||||
((digit_size.to_point() + digit_location).to_i32_round() - digit_location.to_i32_round())
|
||||
.to_size(),
|
||||
);
|
||||
let damage = damage
|
||||
.iter()
|
||||
.cloned()
|
||||
.flat_map(|x| x.intersection(dst))
|
||||
.map(|mut x| {
|
||||
x.loc -= dst.loc;
|
||||
x
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let texture_src: Rectangle<i32, Buffer> = match digit {
|
||||
9 => Rectangle::from_size((22, 35).into()),
|
||||
6 => Rectangle::new((22, 0).into(), (22, 35).into()),
|
||||
3 => Rectangle::new((44, 0).into(), (22, 35).into()),
|
||||
1 => Rectangle::new((66, 0).into(), (22, 35).into()),
|
||||
8 => Rectangle::new((0, 35).into(), (22, 35).into()),
|
||||
0 => Rectangle::new((22, 35).into(), (22, 35).into()),
|
||||
2 => Rectangle::new((44, 35).into(), (22, 35).into()),
|
||||
7 => Rectangle::new((0, 70).into(), (22, 35).into()),
|
||||
4 => Rectangle::new((22, 70).into(), (22, 35).into()),
|
||||
5 => Rectangle::new((44, 70).into(), (22, 35).into()),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
frame.render_texture_from_to(
|
||||
&self.texture,
|
||||
texture_src.to_f64(),
|
||||
dst,
|
||||
&damage,
|
||||
&[],
|
||||
Transform::Normal,
|
||||
1.0,
|
||||
)?;
|
||||
offset += Point::from((24.0, 0.0)).to_physical(scale);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Vendored
+712
@@ -0,0 +1,712 @@
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
|
||||
#[cfg(feature = "xwayland")]
|
||||
use smithay::xwayland::X11Surface;
|
||||
#[cfg(feature = "xwayland")]
|
||||
use smithay::xwayland::xwm::XwmOfferData;
|
||||
pub use smithay::{
|
||||
backend::input::{InputTime, KeyState},
|
||||
desktop::{LayerSurface, PopupKind},
|
||||
input::{
|
||||
Seat,
|
||||
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
|
||||
pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerTarget, RelativeMotionEvent},
|
||||
},
|
||||
reexports::wayland_server::{Resource, backend::ObjectId, protocol::wl_surface::WlSurface},
|
||||
utils::{IsAlive, Serial},
|
||||
wayland::seat::WaylandFocus,
|
||||
};
|
||||
use smithay::{
|
||||
desktop::{Window, WindowSurface},
|
||||
input::{
|
||||
dnd::{DndFocus, OfferData, Source},
|
||||
pointer::{
|
||||
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent, GesturePinchEndEvent,
|
||||
GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
|
||||
},
|
||||
tablet::tool::TabletToolTarget,
|
||||
touch::{FrameMarker, TouchTarget},
|
||||
},
|
||||
reexports::wayland_server::DisplayHandle,
|
||||
utils::{Logical, Point},
|
||||
wayland::selection::data_device::WlOfferData,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
shell::{SSD, WindowElement},
|
||||
state::{AnvilState, Backend},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum KeyboardFocusTarget {
|
||||
Window(Window),
|
||||
LayerSurface(LayerSurface),
|
||||
Popup(PopupKind),
|
||||
}
|
||||
|
||||
impl IsAlive for KeyboardFocusTarget {
|
||||
#[inline]
|
||||
fn alive(&self) -> bool {
|
||||
match self {
|
||||
KeyboardFocusTarget::Window(w) => w.alive(),
|
||||
KeyboardFocusTarget::LayerSurface(l) => l.alive(),
|
||||
KeyboardFocusTarget::Popup(p) => p.alive(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum PointerFocusTarget {
|
||||
WlSurface(WlSurface),
|
||||
#[cfg(feature = "xwayland")]
|
||||
X11Surface(X11Surface),
|
||||
SSD(SSD),
|
||||
}
|
||||
|
||||
impl IsAlive for PointerFocusTarget {
|
||||
#[inline]
|
||||
fn alive(&self) -> bool {
|
||||
match self {
|
||||
PointerFocusTarget::WlSurface(w) => w.alive(),
|
||||
#[cfg(feature = "xwayland")]
|
||||
PointerFocusTarget::X11Surface(w) => w.alive(),
|
||||
PointerFocusTarget::SSD(x) => x.alive(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PointerFocusTarget> for WlSurface {
|
||||
#[inline]
|
||||
fn from(target: PointerFocusTarget) -> Self {
|
||||
target.wl_surface().unwrap().into_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyboardFocusTarget {
|
||||
fn inner_keyboard_target<BackendData: Backend>(&self) -> &dyn KeyboardTarget<AnvilState<BackendData>> {
|
||||
match self {
|
||||
Self::Window(w) => match w.underlying_surface() {
|
||||
WindowSurface::Wayland(w) => w.wl_surface(),
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(s) => s,
|
||||
},
|
||||
Self::LayerSurface(l) => l.wl_surface(),
|
||||
Self::Popup(p) => p.wl_surface(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PointerFocusTarget {
|
||||
fn inner_pointer_target<BackendData: Backend>(&self) -> &dyn PointerTarget<AnvilState<BackendData>> {
|
||||
match self {
|
||||
Self::WlSurface(w) => w,
|
||||
#[cfg(feature = "xwayland")]
|
||||
Self::X11Surface(w) => w,
|
||||
Self::SSD(w) => w,
|
||||
}
|
||||
}
|
||||
|
||||
fn inner_touch_target<BackendData: Backend>(&self) -> &dyn TouchTarget<AnvilState<BackendData>> {
|
||||
match self {
|
||||
Self::WlSurface(w) => w,
|
||||
#[cfg(feature = "xwayland")]
|
||||
Self::X11Surface(w) => w,
|
||||
Self::SSD(w) => w,
|
||||
}
|
||||
}
|
||||
|
||||
fn inner_tablet_tool_target<BackendData: Backend>(
|
||||
&self,
|
||||
) -> &dyn TabletToolTarget<AnvilState<BackendData>> {
|
||||
match self {
|
||||
Self::WlSurface(w) => w,
|
||||
#[cfg(feature = "xwayland")]
|
||||
Self::X11Surface(w) => w,
|
||||
Self::SSD(w) => w,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> PointerTarget<AnvilState<BackendData>> for PointerFocusTarget {
|
||||
fn enter(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &MotionEvent,
|
||||
) {
|
||||
self.inner_pointer_target().enter(seat, data, event)
|
||||
}
|
||||
fn motion(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &MotionEvent,
|
||||
) {
|
||||
self.inner_pointer_target().motion(seat, data, event)
|
||||
}
|
||||
fn relative_motion(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &RelativeMotionEvent,
|
||||
) {
|
||||
self.inner_pointer_target().relative_motion(seat, data, event)
|
||||
}
|
||||
fn button(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &ButtonEvent,
|
||||
) {
|
||||
self.inner_pointer_target().button(seat, data, event)
|
||||
}
|
||||
fn axis(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
frame: AxisFrame,
|
||||
) {
|
||||
self.inner_pointer_target().axis(seat, data, frame)
|
||||
}
|
||||
fn frame(&self, seat: &Seat<AnvilState<BackendData>>, data: &mut AnvilState<BackendData>) {
|
||||
self.inner_pointer_target().frame(seat, data)
|
||||
}
|
||||
fn leave(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
serial: Serial,
|
||||
time: InputTime,
|
||||
) {
|
||||
self.inner_pointer_target().leave(seat, data, serial, time)
|
||||
}
|
||||
fn gesture_swipe_begin(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &GestureSwipeBeginEvent,
|
||||
) {
|
||||
self.inner_pointer_target().gesture_swipe_begin(seat, data, event)
|
||||
}
|
||||
fn gesture_swipe_update(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &GestureSwipeUpdateEvent,
|
||||
) {
|
||||
self.inner_pointer_target()
|
||||
.gesture_swipe_update(seat, data, event)
|
||||
}
|
||||
fn gesture_swipe_end(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &GestureSwipeEndEvent,
|
||||
) {
|
||||
self.inner_pointer_target().gesture_swipe_end(seat, data, event)
|
||||
}
|
||||
fn gesture_pinch_begin(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &GesturePinchBeginEvent,
|
||||
) {
|
||||
self.inner_pointer_target().gesture_pinch_begin(seat, data, event)
|
||||
}
|
||||
fn gesture_pinch_update(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &GesturePinchUpdateEvent,
|
||||
) {
|
||||
self.inner_pointer_target()
|
||||
.gesture_pinch_update(seat, data, event)
|
||||
}
|
||||
fn gesture_pinch_end(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &GesturePinchEndEvent,
|
||||
) {
|
||||
self.inner_pointer_target().gesture_pinch_end(seat, data, event)
|
||||
}
|
||||
fn gesture_hold_begin(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &GestureHoldBeginEvent,
|
||||
) {
|
||||
self.inner_pointer_target().gesture_hold_begin(seat, data, event)
|
||||
}
|
||||
fn gesture_hold_end(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &GestureHoldEndEvent,
|
||||
) {
|
||||
self.inner_pointer_target().gesture_hold_end(seat, data, event)
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> KeyboardTarget<AnvilState<BackendData>> for KeyboardFocusTarget {
|
||||
fn enter(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
keys: Vec<KeysymHandle<'_>>,
|
||||
serial: Serial,
|
||||
) {
|
||||
self.inner_keyboard_target().enter(seat, data, keys, serial)
|
||||
}
|
||||
fn leave(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
serial: Serial,
|
||||
) {
|
||||
self.inner_keyboard_target().leave(seat, data, serial)
|
||||
}
|
||||
fn key(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
key: KeysymHandle<'_>,
|
||||
state: KeyState,
|
||||
serial: Serial,
|
||||
time: InputTime,
|
||||
) {
|
||||
self.inner_keyboard_target()
|
||||
.key(seat, data, key, state, serial, time)
|
||||
}
|
||||
fn modifiers(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
modifiers: ModifiersState,
|
||||
serial: Serial,
|
||||
) {
|
||||
self.inner_keyboard_target()
|
||||
.modifiers(seat, data, modifiers, serial)
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> TouchTarget<AnvilState<BackendData>> for PointerFocusTarget {
|
||||
fn down(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &smithay::input::touch::DownEvent,
|
||||
) {
|
||||
self.inner_touch_target().down(seat, data, event)
|
||||
}
|
||||
|
||||
fn up(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &smithay::input::touch::UpEvent,
|
||||
) {
|
||||
self.inner_touch_target().up(seat, data, event)
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &smithay::input::touch::MotionEvent,
|
||||
) {
|
||||
self.inner_touch_target().motion(seat, data, event)
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
marker: FrameMarker,
|
||||
) {
|
||||
self.inner_touch_target().frame(seat, data, marker)
|
||||
}
|
||||
|
||||
fn cancel(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
marker: FrameMarker,
|
||||
) {
|
||||
self.inner_touch_target().cancel(seat, data, marker)
|
||||
}
|
||||
|
||||
fn shape(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &smithay::input::touch::ShapeEvent,
|
||||
) {
|
||||
self.inner_touch_target().shape(seat, data, event)
|
||||
}
|
||||
|
||||
fn orientation(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &smithay::input::touch::OrientationEvent,
|
||||
) {
|
||||
self.inner_touch_target().orientation(seat, data, event)
|
||||
}
|
||||
|
||||
fn last_frame(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
) -> Option<FrameMarker> {
|
||||
self.inner_touch_target().last_frame(seat, data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> TabletToolTarget<AnvilState<BackendData>> for PointerFocusTarget {
|
||||
fn proximity_in(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
tablet: &smithay::input::tablet::Tablet,
|
||||
serial: Serial,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.proximity_in(seat, data, tool_descriptor, tablet, serial);
|
||||
}
|
||||
|
||||
fn proximity_out(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.proximity_out(seat, data, tool_descriptor);
|
||||
}
|
||||
|
||||
fn down(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
event: &smithay::input::tablet::tool::DownEvent,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.down(seat, data, tool_descriptor, event);
|
||||
}
|
||||
|
||||
fn up(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
event: &smithay::input::tablet::tool::UpEvent,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.up(seat, data, tool_descriptor, event);
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
event: &smithay::input::tablet::tool::MotionEvent,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.motion(seat, data, tool_descriptor, event);
|
||||
}
|
||||
|
||||
fn button(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
event: &smithay::input::tablet::tool::ButtonEvent,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.button(seat, data, tool_descriptor, event);
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
frame: smithay::input::tablet::tool::AxisFrame,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.axis(seat, data, tool_descriptor, frame);
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
time: InputTime,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.frame(seat, data, tool_descriptor, time);
|
||||
}
|
||||
}
|
||||
|
||||
impl WaylandFocus for PointerFocusTarget {
|
||||
#[inline]
|
||||
fn wl_surface(&self) -> Option<Cow<'_, WlSurface>> {
|
||||
match self {
|
||||
PointerFocusTarget::WlSurface(w) => w.wl_surface(),
|
||||
#[cfg(feature = "xwayland")]
|
||||
PointerFocusTarget::X11Surface(w) => w.wl_surface().map(Cow::Owned),
|
||||
PointerFocusTarget::SSD(_) => None,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
fn same_client_as(&self, object_id: &ObjectId) -> bool {
|
||||
match self {
|
||||
PointerFocusTarget::WlSurface(w) => w.same_client_as(object_id),
|
||||
#[cfg(feature = "xwayland")]
|
||||
PointerFocusTarget::X11Surface(w) => w.same_client_as(object_id),
|
||||
PointerFocusTarget::SSD(w) => w
|
||||
.wl_surface()
|
||||
.map(|surface| surface.same_client_as(object_id))
|
||||
.unwrap_or(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WaylandFocus for KeyboardFocusTarget {
|
||||
#[inline]
|
||||
fn wl_surface(&self) -> Option<Cow<'_, WlSurface>> {
|
||||
match self {
|
||||
KeyboardFocusTarget::Window(w) => w.wl_surface(),
|
||||
KeyboardFocusTarget::LayerSurface(l) => Some(Cow::Borrowed(l.wl_surface())),
|
||||
KeyboardFocusTarget::Popup(p) => Some(Cow::Borrowed(p.wl_surface())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum AnvilOfferData<S: Source> {
|
||||
Wayland(WlOfferData<S>),
|
||||
#[cfg(feature = "xwayland")]
|
||||
X11(XwmOfferData<S>),
|
||||
}
|
||||
|
||||
impl<S: Source> OfferData for AnvilOfferData<S> {
|
||||
fn disable(&self) {
|
||||
match self {
|
||||
AnvilOfferData::Wayland(data) => data.disable(),
|
||||
#[cfg(feature = "xwayland")]
|
||||
AnvilOfferData::X11(data) => data.disable(),
|
||||
}
|
||||
}
|
||||
|
||||
fn drop(&self) {
|
||||
match self {
|
||||
AnvilOfferData::Wayland(data) => data.drop(),
|
||||
#[cfg(feature = "xwayland")]
|
||||
AnvilOfferData::X11(data) => data.drop(),
|
||||
}
|
||||
}
|
||||
|
||||
fn validated(&self) -> bool {
|
||||
match self {
|
||||
AnvilOfferData::Wayland(data) => data.validated(),
|
||||
#[cfg(feature = "xwayland")]
|
||||
AnvilOfferData::X11(data) => data.validated(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unreachable_patterns)]
|
||||
impl<BackendData: Backend> DndFocus<AnvilState<BackendData>> for PointerFocusTarget {
|
||||
type OfferData<S>
|
||||
= AnvilOfferData<S>
|
||||
where
|
||||
S: Source;
|
||||
|
||||
fn enter<S: Source>(
|
||||
&self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
dh: &DisplayHandle,
|
||||
source: Arc<S>,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
location: Point<f64, Logical>,
|
||||
serial: &Serial,
|
||||
) -> Option<AnvilOfferData<S>> {
|
||||
match self {
|
||||
PointerFocusTarget::WlSurface(surface) => {
|
||||
DndFocus::enter(surface, data, dh, source, seat, location, serial)
|
||||
.map(AnvilOfferData::Wayland)
|
||||
}
|
||||
#[cfg(feature = "xwayland")]
|
||||
PointerFocusTarget::X11Surface(surface) => {
|
||||
DndFocus::enter(surface, data, dh, source, seat, location, serial).map(AnvilOfferData::X11)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn motion<S: Source>(
|
||||
&self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
offer: Option<&mut AnvilOfferData<S>>,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
location: Point<f64, Logical>,
|
||||
time: InputTime,
|
||||
) {
|
||||
match self {
|
||||
PointerFocusTarget::WlSurface(surface) => {
|
||||
let offer = match offer {
|
||||
Some(AnvilOfferData::Wayland(offer)) => Some(offer),
|
||||
None => None,
|
||||
_ => return,
|
||||
};
|
||||
DndFocus::motion(surface, data, offer, seat, location, time)
|
||||
}
|
||||
#[cfg(feature = "xwayland")]
|
||||
PointerFocusTarget::X11Surface(surface) => {
|
||||
let offer = match offer {
|
||||
Some(AnvilOfferData::X11(offer)) => Some(offer),
|
||||
None => None,
|
||||
_ => return,
|
||||
};
|
||||
DndFocus::motion(surface, data, offer, seat, location, time)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn leave<S: Source>(
|
||||
&self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
offer: Option<&mut AnvilOfferData<S>>,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
) {
|
||||
match self {
|
||||
PointerFocusTarget::WlSurface(surface) => {
|
||||
let offer = match offer {
|
||||
Some(AnvilOfferData::Wayland(offer)) => Some(offer),
|
||||
None => None,
|
||||
_ => return,
|
||||
};
|
||||
DndFocus::leave(surface, data, offer, seat)
|
||||
}
|
||||
#[cfg(feature = "xwayland")]
|
||||
PointerFocusTarget::X11Surface(surface) => {
|
||||
let offer = match offer {
|
||||
Some(AnvilOfferData::X11(offer)) => Some(offer),
|
||||
None => None,
|
||||
_ => return,
|
||||
};
|
||||
DndFocus::leave(surface, data, offer, seat)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn drop<S: Source>(
|
||||
&self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
offer: Option<&mut AnvilOfferData<S>>,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
) {
|
||||
match self {
|
||||
PointerFocusTarget::WlSurface(surface) => {
|
||||
let offer = match offer {
|
||||
Some(AnvilOfferData::Wayland(offer)) => Some(offer),
|
||||
None => None,
|
||||
_ => return,
|
||||
};
|
||||
DndFocus::drop(surface, data, offer, seat)
|
||||
}
|
||||
#[cfg(feature = "xwayland")]
|
||||
PointerFocusTarget::X11Surface(surface) => {
|
||||
let offer = match offer {
|
||||
Some(AnvilOfferData::X11(offer)) => Some(offer),
|
||||
None => None,
|
||||
_ => return,
|
||||
};
|
||||
DndFocus::drop(surface, data, offer, seat)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WlSurface> for PointerFocusTarget {
|
||||
#[inline]
|
||||
fn from(value: WlSurface) -> Self {
|
||||
PointerFocusTarget::WlSurface(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&WlSurface> for PointerFocusTarget {
|
||||
#[inline]
|
||||
fn from(value: &WlSurface) -> Self {
|
||||
PointerFocusTarget::from(value.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PopupKind> for PointerFocusTarget {
|
||||
#[inline]
|
||||
fn from(value: PopupKind) -> Self {
|
||||
PointerFocusTarget::from(value.wl_surface())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "xwayland")]
|
||||
impl From<X11Surface> for PointerFocusTarget {
|
||||
#[inline]
|
||||
fn from(value: X11Surface) -> Self {
|
||||
PointerFocusTarget::X11Surface(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "xwayland")]
|
||||
impl From<&X11Surface> for PointerFocusTarget {
|
||||
#[inline]
|
||||
fn from(value: &X11Surface) -> Self {
|
||||
PointerFocusTarget::from(value.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WindowElement> for KeyboardFocusTarget {
|
||||
#[inline]
|
||||
fn from(w: WindowElement) -> Self {
|
||||
KeyboardFocusTarget::Window(w.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LayerSurface> for KeyboardFocusTarget {
|
||||
#[inline]
|
||||
fn from(l: LayerSurface) -> Self {
|
||||
KeyboardFocusTarget::LayerSurface(l)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PopupKind> for KeyboardFocusTarget {
|
||||
#[inline]
|
||||
fn from(p: PopupKind) -> Self {
|
||||
KeyboardFocusTarget::Popup(p)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeyboardFocusTarget> for PointerFocusTarget {
|
||||
#[inline]
|
||||
fn from(value: KeyboardFocusTarget) -> Self {
|
||||
match value {
|
||||
KeyboardFocusTarget::Window(w) => match w.underlying_surface() {
|
||||
WindowSurface::Wayland(w) => PointerFocusTarget::from(w.wl_surface()),
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(s) => PointerFocusTarget::from(s),
|
||||
},
|
||||
KeyboardFocusTarget::LayerSurface(surface) => PointerFocusTarget::from(surface.wl_surface()),
|
||||
KeyboardFocusTarget::Popup(popup) => PointerFocusTarget::from(popup.wl_surface()),
|
||||
}
|
||||
}
|
||||
}
|
||||
+1457
File diff suppressed because it is too large.
Load diff
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![allow(clippy::collapsible_match)]
|
||||
// If no backend is enabled, a large portion of the codebase is unused.
|
||||
// So silence this useless warning for the CI.
|
||||
#![cfg_attr(
|
||||
not(any(feature = "winit", feature = "x11", feature = "udev")),
|
||||
allow(dead_code, unused_imports)
|
||||
)]
|
||||
|
||||
#[cfg(any(feature = "udev", feature = "xwayland"))]
|
||||
pub mod cursor;
|
||||
pub mod drawing;
|
||||
pub mod focus;
|
||||
pub mod input_handler;
|
||||
#[cfg(feature = "libei")]
|
||||
pub mod libei;
|
||||
pub mod render;
|
||||
pub mod shell;
|
||||
pub mod state;
|
||||
#[cfg(feature = "udev")]
|
||||
pub mod udev;
|
||||
#[cfg(feature = "winit")]
|
||||
pub mod winit;
|
||||
#[cfg(feature = "x11")]
|
||||
pub mod x11;
|
||||
|
||||
pub use state::{AnvilState, ClientState};
|
||||
Vendored
+50
@@ -0,0 +1,50 @@
|
||||
use smithay::{
|
||||
backend::libei::{EiInput, EiInputEvent},
|
||||
input::keyboard::XkbConfig,
|
||||
reexports::{
|
||||
calloop,
|
||||
reis::{calloop::EisListenerSource, eis},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{state::AnvilState, udev::UdevData};
|
||||
|
||||
pub fn listen_eis(handle: &calloop::LoopHandle<'static, AnvilState<UdevData>>) {
|
||||
let listener = match eis::Listener::bind_auto() {
|
||||
Ok(listener) => listener,
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to bind EI listener socket: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
unsafe { std::env::set_var("LIBEI_SOCKET", listener.path()) };
|
||||
|
||||
let listener_source = EisListenerSource::new(listener);
|
||||
let handle_clone = handle.clone();
|
||||
handle
|
||||
.insert_source(listener_source, move |context, _, _| {
|
||||
let source = EiInput::new(context);
|
||||
handle_clone
|
||||
.insert_source(source, |event, connection, data| match event {
|
||||
EiInputEvent::Connected => {
|
||||
let seat = connection.add_seat("default");
|
||||
let _ = seat.add_keyboard("virtual keyboard", XkbConfig::default());
|
||||
seat.add_pointer("virtual pointer");
|
||||
seat.add_pointer_absolute("virtual absolute pointer", &[]);
|
||||
seat.add_touch("virtual touch", &[]);
|
||||
}
|
||||
EiInputEvent::Disconnected => {}
|
||||
EiInputEvent::Event(event) => {
|
||||
let dh = data.display_handle.clone();
|
||||
data.process_input_event(&dh, event);
|
||||
}
|
||||
EiInputEvent::TextKeysym { .. } | EiInputEvent::TextUtf8 { .. } => {
|
||||
// Anvil doesn't add a text device to the libei seat
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
Ok(calloop::PostAction::Continue)
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
Vendored
+69
@@ -0,0 +1,69 @@
|
||||
static POSSIBLE_BACKENDS: &[&str] = &[
|
||||
#[cfg(feature = "winit")]
|
||||
"--winit : Run anvil as a X11 or Wayland client using winit.",
|
||||
#[cfg(feature = "udev")]
|
||||
"--tty-udev : Run anvil as a tty udev client (requires root if without logind).",
|
||||
#[cfg(feature = "x11")]
|
||||
"--x11 : Run anvil as an X11 client.",
|
||||
];
|
||||
|
||||
#[cfg(feature = "profile-with-tracy-mem")]
|
||||
#[global_allocator]
|
||||
static GLOBAL: profiling::tracy_client::ProfiledAllocator<std::alloc::System> =
|
||||
profiling::tracy_client::ProfiledAllocator::new(std::alloc::System, 10);
|
||||
|
||||
// Allow in this function because of existing usage
|
||||
#[allow(clippy::uninlined_format_args)]
|
||||
fn main() {
|
||||
if let Ok(env_filter) = tracing_subscriber::EnvFilter::try_from_default_env() {
|
||||
tracing_subscriber::fmt()
|
||||
.compact()
|
||||
.with_env_filter(env_filter)
|
||||
.init();
|
||||
} else {
|
||||
tracing_subscriber::fmt().compact().init();
|
||||
}
|
||||
|
||||
#[cfg(feature = "profile-with-tracy")]
|
||||
profiling::tracy_client::Client::start();
|
||||
|
||||
profiling::register_thread!("Main Thread");
|
||||
|
||||
#[cfg(feature = "profile-with-puffin")]
|
||||
let _server = puffin_http::Server::new(&format!("0.0.0.0:{}", puffin_http::DEFAULT_PORT)).unwrap();
|
||||
#[cfg(feature = "profile-with-puffin")]
|
||||
profiling::puffin::set_scopes_on(true);
|
||||
|
||||
let arg = ::std::env::args().nth(1);
|
||||
match arg.as_ref().map(|s| &s[..]) {
|
||||
#[cfg(feature = "winit")]
|
||||
Some("--winit") => {
|
||||
tracing::info!("Starting anvil with winit backend");
|
||||
anvil::winit::run_winit();
|
||||
}
|
||||
#[cfg(feature = "udev")]
|
||||
Some("--tty-udev") => {
|
||||
tracing::info!("Starting anvil on a tty using udev");
|
||||
anvil::udev::run_udev();
|
||||
}
|
||||
#[cfg(feature = "x11")]
|
||||
Some("--x11") => {
|
||||
tracing::info!("Starting anvil with x11 backend");
|
||||
anvil::x11::run_x11();
|
||||
}
|
||||
Some(other) => {
|
||||
tracing::error!("Unknown backend: {}", other);
|
||||
}
|
||||
None => {
|
||||
#[allow(clippy::disallowed_macros)]
|
||||
{
|
||||
println!("USAGE: anvil --backend");
|
||||
println!();
|
||||
println!("Possible backends are:");
|
||||
for b in POSSIBLE_BACKENDS {
|
||||
println!("\t{b}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+210
@@ -0,0 +1,210 @@
|
||||
use smithay::{
|
||||
backend::renderer::{
|
||||
Color32F, ImportAll, ImportMem, Renderer,
|
||||
damage::{Error as OutputDamageTrackerError, OutputDamageTracker, RenderOutputResult},
|
||||
element::{
|
||||
AsRenderElements, RenderElement, Wrap,
|
||||
surface::WaylandSurfaceRenderElement,
|
||||
utils::{
|
||||
ConstrainAlign, ConstrainScaleBehavior, CropRenderElement, RelocateRenderElement,
|
||||
RescaleRenderElement,
|
||||
},
|
||||
},
|
||||
},
|
||||
desktop::space::{
|
||||
ConstrainBehavior, ConstrainReference, Space, SpaceRenderElements, constrain_space_element,
|
||||
},
|
||||
output::Output,
|
||||
utils::{Point, Rectangle, Size},
|
||||
};
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
use crate::drawing::FpsElement;
|
||||
use crate::{
|
||||
drawing::{CLEAR_COLOR, CLEAR_COLOR_FULLSCREEN, PointerRenderElement},
|
||||
shell::{FullscreenSurface, WindowElement, WindowRenderElement},
|
||||
};
|
||||
|
||||
smithay::backend::renderer::element::render_elements! {
|
||||
pub CustomRenderElements<R> where
|
||||
R: ImportAll + ImportMem;
|
||||
Pointer=PointerRenderElement<R>,
|
||||
Surface=WaylandSurfaceRenderElement<R>,
|
||||
#[cfg(feature = "debug")]
|
||||
// Note: We would like to borrow this element instead, but that would introduce
|
||||
// a feature-dependent lifetime, which introduces a lot more feature bounds
|
||||
// as the whole type changes and we can't have an unused lifetime (for when "debug" is disabled)
|
||||
// in the declaration.
|
||||
Fps=FpsElement<R::TextureId>,
|
||||
}
|
||||
|
||||
impl<R: Renderer> std::fmt::Debug for CustomRenderElements<R> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Pointer(arg0) => f.debug_tuple("Pointer").field(arg0).finish(),
|
||||
Self::Surface(arg0) => f.debug_tuple("Surface").field(arg0).finish(),
|
||||
#[cfg(feature = "debug")]
|
||||
Self::Fps(arg0) => f.debug_tuple("Fps").field(arg0).finish(),
|
||||
Self::_GenericCatcher(arg0) => f.debug_tuple("_GenericCatcher").field(arg0).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
smithay::backend::renderer::element::render_elements! {
|
||||
pub OutputRenderElements<R, E> where R: ImportAll + ImportMem;
|
||||
Space=SpaceRenderElements<R, E>,
|
||||
Window=Wrap<E>,
|
||||
Custom=CustomRenderElements<R>,
|
||||
Preview=CropRenderElement<RelocateRenderElement<RescaleRenderElement<WindowRenderElement<R>>>>,
|
||||
}
|
||||
|
||||
impl<R: Renderer + ImportAll + ImportMem, E: RenderElement<R> + std::fmt::Debug> std::fmt::Debug
|
||||
for OutputRenderElements<R, E>
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Space(arg0) => f.debug_tuple("Space").field(arg0).finish(),
|
||||
Self::Window(arg0) => f.debug_tuple("Window").field(arg0).finish(),
|
||||
Self::Custom(arg0) => f.debug_tuple("Custom").field(arg0).finish(),
|
||||
Self::Preview(arg0) => f.debug_tuple("Preview").field(arg0).finish(),
|
||||
Self::_GenericCatcher(arg0) => f.debug_tuple("_GenericCatcher").field(arg0).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn space_preview_elements<'a, R, C>(
|
||||
renderer: &'a mut R,
|
||||
space: &'a Space<WindowElement>,
|
||||
output: &'a Output,
|
||||
) -> impl Iterator<Item = C> + 'a
|
||||
where
|
||||
R: Renderer + ImportAll + ImportMem,
|
||||
R::TextureId: Clone + 'static,
|
||||
C: From<CropRenderElement<RelocateRenderElement<RescaleRenderElement<WindowRenderElement<R>>>>> + 'a,
|
||||
{
|
||||
let constrain_behavior = ConstrainBehavior {
|
||||
reference: ConstrainReference::BoundingBox,
|
||||
behavior: ConstrainScaleBehavior::Fit,
|
||||
align: ConstrainAlign::CENTER,
|
||||
};
|
||||
|
||||
let preview_padding = 10;
|
||||
|
||||
let elements_on_space = space.elements_for_output(output).count();
|
||||
let output_scale = output.current_scale().fractional_scale();
|
||||
let output_transform = output.current_transform();
|
||||
let output_size = output
|
||||
.current_mode()
|
||||
.map(|mode| {
|
||||
output_transform
|
||||
.transform_size(mode.size)
|
||||
.to_f64()
|
||||
.to_logical(output_scale)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let max_elements_per_row = 4;
|
||||
let elements_per_row = usize::min(elements_on_space, max_elements_per_row);
|
||||
let rows = f64::ceil(elements_on_space as f64 / elements_per_row as f64);
|
||||
|
||||
let preview_size = Size::from((
|
||||
f64::round(output_size.w / elements_per_row as f64) as i32 - preview_padding * 2,
|
||||
f64::round(output_size.h / rows) as i32 - preview_padding * 2,
|
||||
));
|
||||
|
||||
space
|
||||
.elements_for_output(output)
|
||||
.enumerate()
|
||||
.flat_map(move |(element_index, window)| {
|
||||
let column = element_index % elements_per_row;
|
||||
let row = element_index / elements_per_row;
|
||||
let preview_location = Point::from((
|
||||
preview_padding + (preview_padding + preview_size.w) * column as i32,
|
||||
preview_padding + (preview_padding + preview_size.h) * row as i32,
|
||||
));
|
||||
let constrain = Rectangle::new(preview_location, preview_size);
|
||||
constrain_space_element(
|
||||
renderer,
|
||||
window,
|
||||
preview_location,
|
||||
1.0,
|
||||
output_scale,
|
||||
constrain,
|
||||
constrain_behavior,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[profiling::function]
|
||||
pub fn output_elements<R>(
|
||||
output: &Output,
|
||||
space: &Space<WindowElement>,
|
||||
custom_elements: impl IntoIterator<Item = CustomRenderElements<R>>,
|
||||
renderer: &mut R,
|
||||
show_window_preview: bool,
|
||||
) -> (Vec<OutputRenderElements<R, WindowRenderElement<R>>>, Color32F)
|
||||
where
|
||||
R: Renderer + ImportAll + ImportMem,
|
||||
R::TextureId: Clone + 'static,
|
||||
{
|
||||
if let Some(window) = output
|
||||
.user_data()
|
||||
.get::<FullscreenSurface>()
|
||||
.and_then(|f| f.get())
|
||||
{
|
||||
let scale = output.current_scale().fractional_scale().into();
|
||||
let window_render_elements: Vec<WindowRenderElement<R>> =
|
||||
AsRenderElements::<R>::render_elements(&window, renderer, (0, 0).into(), scale, 1.0);
|
||||
|
||||
let elements = custom_elements
|
||||
.into_iter()
|
||||
.map(OutputRenderElements::from)
|
||||
.chain(
|
||||
window_render_elements
|
||||
.into_iter()
|
||||
.map(|e| OutputRenderElements::Window(Wrap::from(e))),
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
(elements, CLEAR_COLOR_FULLSCREEN)
|
||||
} else {
|
||||
let mut output_render_elements = custom_elements
|
||||
.into_iter()
|
||||
.map(OutputRenderElements::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if show_window_preview && space.elements_for_output(output).count() > 0 {
|
||||
output_render_elements.extend(space_preview_elements(renderer, space, output));
|
||||
}
|
||||
|
||||
let space_elements = smithay::desktop::space::space_render_elements::<_, WindowElement, _>(
|
||||
renderer,
|
||||
[space],
|
||||
output,
|
||||
1.0,
|
||||
)
|
||||
.expect("output without mode?");
|
||||
output_render_elements.extend(space_elements.into_iter().map(OutputRenderElements::Space));
|
||||
|
||||
(output_render_elements, CLEAR_COLOR)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn render_output<'a, 'd, R>(
|
||||
output: &'a Output,
|
||||
space: &'a Space<WindowElement>,
|
||||
custom_elements: impl IntoIterator<Item = CustomRenderElements<R>>,
|
||||
renderer: &'a mut R,
|
||||
framebuffer: &'a mut R::Framebuffer<'_>,
|
||||
damage_tracker: &'d mut OutputDamageTracker,
|
||||
age: usize,
|
||||
show_window_preview: bool,
|
||||
) -> Result<RenderOutputResult<'d>, OutputDamageTrackerError<R::Error>>
|
||||
where
|
||||
R: Renderer + ImportAll + ImportMem,
|
||||
R::TextureId: Clone + 'static,
|
||||
{
|
||||
let (elements, clear_color) =
|
||||
output_elements(output, space, custom_elements, renderer, show_window_preview);
|
||||
damage_tracker.render_output(renderer, framebuffer, age, &elements, clear_color)
|
||||
}
|
||||
+564
@@ -0,0 +1,564 @@
|
||||
use std::{borrow::Cow, time::Duration};
|
||||
|
||||
use smithay::{
|
||||
backend::{
|
||||
input::InputTime,
|
||||
renderer::{
|
||||
ImportAll, ImportMem, Renderer, Texture,
|
||||
element::{
|
||||
AsRenderElements, solid::SolidColorRenderElement, surface::WaylandSurfaceRenderElement,
|
||||
},
|
||||
},
|
||||
},
|
||||
desktop::{
|
||||
Window, WindowSurface, WindowSurfaceType, space::SpaceElement, utils::OutputPresentationFeedback,
|
||||
},
|
||||
input::{
|
||||
Seat,
|
||||
pointer::{
|
||||
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent,
|
||||
GestureSwipeUpdateEvent, MotionEvent, PointerTarget, RelativeMotionEvent,
|
||||
},
|
||||
tablet::tool::TabletToolTarget,
|
||||
touch::{FrameMarker, TouchTarget},
|
||||
},
|
||||
output::Output,
|
||||
reexports::{
|
||||
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback,
|
||||
wayland_server::protocol::wl_surface::WlSurface,
|
||||
},
|
||||
render_elements,
|
||||
utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, user_data::UserDataMap},
|
||||
wayland::{compositor::SurfaceData as WlSurfaceData, dmabuf::DmabufFeedback, seat::WaylandFocus},
|
||||
};
|
||||
|
||||
use super::ssd::HEADER_BAR_HEIGHT;
|
||||
use crate::{AnvilState, focus::PointerFocusTarget, state::Backend};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct WindowElement(pub Window);
|
||||
|
||||
impl WindowElement {
|
||||
pub fn surface_under(
|
||||
&self,
|
||||
location: Point<f64, Logical>,
|
||||
window_type: WindowSurfaceType,
|
||||
) -> Option<(PointerFocusTarget, Point<i32, Logical>)> {
|
||||
let state = self.decoration_state();
|
||||
if state.is_ssd && location.y < HEADER_BAR_HEIGHT as f64 {
|
||||
return Some((PointerFocusTarget::SSD(SSD(self.clone())), Point::default()));
|
||||
}
|
||||
let offset = if state.is_ssd {
|
||||
Point::from((0, HEADER_BAR_HEIGHT))
|
||||
} else {
|
||||
Point::default()
|
||||
};
|
||||
|
||||
let surface_under = self.0.surface_under(location - offset.to_f64(), window_type);
|
||||
let (under, loc) = match self.0.underlying_surface() {
|
||||
WindowSurface::Wayland(_) => {
|
||||
surface_under.map(|(surface, loc)| (PointerFocusTarget::WlSurface(surface), loc))
|
||||
}
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(s) => {
|
||||
surface_under.map(|(_, loc)| (PointerFocusTarget::X11Surface(s.clone()), loc))
|
||||
}
|
||||
}?;
|
||||
Some((under, loc + offset))
|
||||
}
|
||||
|
||||
pub fn with_surfaces<F>(&self, processor: F)
|
||||
where
|
||||
F: FnMut(&WlSurface, &WlSurfaceData),
|
||||
{
|
||||
self.0.with_surfaces(processor);
|
||||
}
|
||||
|
||||
pub fn send_frame<T, F>(
|
||||
&self,
|
||||
output: &Output,
|
||||
time: T,
|
||||
throttle: Option<Duration>,
|
||||
primary_scan_out_output: F,
|
||||
) where
|
||||
T: Into<Duration>,
|
||||
F: FnMut(&WlSurface, &WlSurfaceData) -> Option<Output> + Copy,
|
||||
{
|
||||
self.0.send_frame(output, time, throttle, primary_scan_out_output)
|
||||
}
|
||||
|
||||
pub fn send_dmabuf_feedback<'a, P, F>(
|
||||
&self,
|
||||
output: &Output,
|
||||
primary_scan_out_output: P,
|
||||
select_dmabuf_feedback: F,
|
||||
) where
|
||||
P: FnMut(&WlSurface, &WlSurfaceData) -> Option<Output> + Copy,
|
||||
F: Fn(&WlSurface, &WlSurfaceData) -> &'a DmabufFeedback + Copy,
|
||||
{
|
||||
self.0
|
||||
.send_dmabuf_feedback(output, primary_scan_out_output, select_dmabuf_feedback)
|
||||
}
|
||||
|
||||
pub fn take_presentation_feedback<F1, F2>(
|
||||
&self,
|
||||
output_feedback: &mut OutputPresentationFeedback,
|
||||
primary_scan_out_output: F1,
|
||||
presentation_feedback_flags: F2,
|
||||
) where
|
||||
F1: FnMut(&WlSurface, &WlSurfaceData) -> Option<Output> + Copy,
|
||||
F2: FnMut(&WlSurface, &WlSurfaceData) -> wp_presentation_feedback::Kind + Copy,
|
||||
{
|
||||
self.0.take_presentation_feedback(
|
||||
output_feedback,
|
||||
primary_scan_out_output,
|
||||
presentation_feedback_flags,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "xwayland")]
|
||||
#[inline]
|
||||
pub fn is_x11(&self) -> bool {
|
||||
self.0.is_x11()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_wayland(&self) -> bool {
|
||||
self.0.is_wayland()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn wl_surface(&self) -> Option<Cow<'_, WlSurface>> {
|
||||
self.0.wl_surface()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn user_data(&self) -> &UserDataMap {
|
||||
self.0.user_data()
|
||||
}
|
||||
}
|
||||
|
||||
impl IsAlive for WindowElement {
|
||||
#[inline]
|
||||
fn alive(&self) -> bool {
|
||||
self.0.alive()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct SSD(WindowElement);
|
||||
|
||||
impl IsAlive for SSD {
|
||||
#[inline]
|
||||
fn alive(&self) -> bool {
|
||||
self.0.alive()
|
||||
}
|
||||
}
|
||||
|
||||
impl WaylandFocus for SSD {
|
||||
#[inline]
|
||||
fn wl_surface(&self) -> Option<Cow<'_, WlSurface>> {
|
||||
self.0.wl_surface()
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> PointerTarget<AnvilState<BackendData>> for SSD {
|
||||
fn enter(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
event: &MotionEvent,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.pointer_enter(event.location);
|
||||
}
|
||||
}
|
||||
fn motion(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
event: &MotionEvent,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.pointer_enter(event.location);
|
||||
}
|
||||
}
|
||||
fn relative_motion(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_event: &RelativeMotionEvent,
|
||||
) {
|
||||
}
|
||||
fn button(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &ButtonEvent,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.clicked(seat, data, &self.0, event.serial);
|
||||
}
|
||||
}
|
||||
fn axis(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_frame: AxisFrame,
|
||||
) {
|
||||
}
|
||||
fn frame(&self, _seat: &Seat<AnvilState<BackendData>>, _data: &mut AnvilState<BackendData>) {}
|
||||
fn leave(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_serial: Serial,
|
||||
_time: InputTime,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.pointer_leave();
|
||||
}
|
||||
}
|
||||
fn gesture_swipe_begin(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_event: &GestureSwipeBeginEvent,
|
||||
) {
|
||||
}
|
||||
fn gesture_swipe_update(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_event: &GestureSwipeUpdateEvent,
|
||||
) {
|
||||
}
|
||||
fn gesture_swipe_end(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_event: &GestureSwipeEndEvent,
|
||||
) {
|
||||
}
|
||||
fn gesture_pinch_begin(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_event: &GesturePinchBeginEvent,
|
||||
) {
|
||||
}
|
||||
fn gesture_pinch_update(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_event: &GesturePinchUpdateEvent,
|
||||
) {
|
||||
}
|
||||
fn gesture_pinch_end(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_event: &GesturePinchEndEvent,
|
||||
) {
|
||||
}
|
||||
fn gesture_hold_begin(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_event: &GestureHoldBeginEvent,
|
||||
) {
|
||||
}
|
||||
fn gesture_hold_end(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_event: &GestureHoldEndEvent,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> TouchTarget<AnvilState<BackendData>> for SSD {
|
||||
fn down(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &smithay::input::touch::DownEvent,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.pointer_enter(event.location);
|
||||
state.header_bar.touch_down(seat, data, &self.0, event.serial);
|
||||
}
|
||||
}
|
||||
|
||||
fn up(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
_event: &smithay::input::touch::UpEvent,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.touch_up(seat, data, &self.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
event: &smithay::input::touch::MotionEvent,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.pointer_enter(event.location);
|
||||
}
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_marker: FrameMarker,
|
||||
) {
|
||||
}
|
||||
|
||||
fn cancel(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_marker: FrameMarker,
|
||||
) {
|
||||
}
|
||||
|
||||
fn shape(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_event: &smithay::input::touch::ShapeEvent,
|
||||
) {
|
||||
}
|
||||
|
||||
fn orientation(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_event: &smithay::input::touch::OrientationEvent,
|
||||
) {
|
||||
}
|
||||
|
||||
fn last_frame(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
) -> Option<FrameMarker> {
|
||||
// It would be more correct to store the marker on frame and cancel,
|
||||
// but since we're ignoring those anyway, no need for the added complexity.
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> TabletToolTarget<AnvilState<BackendData>> for SSD {
|
||||
fn proximity_in(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
_tablet: &smithay::input::tablet::Tablet,
|
||||
_serial: Serial,
|
||||
) {
|
||||
}
|
||||
|
||||
fn proximity_out(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.pointer_leave();
|
||||
}
|
||||
}
|
||||
|
||||
fn down(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
_tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
event: &smithay::input::tablet::tool::DownEvent,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.touch_down(seat, data, &self.0, event.serial);
|
||||
}
|
||||
}
|
||||
|
||||
fn up(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
_tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
_event: &smithay::input::tablet::tool::UpEvent,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.touch_up(seat, data, &self.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
event: &smithay::input::tablet::tool::MotionEvent,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.pointer_enter(event.location);
|
||||
}
|
||||
}
|
||||
|
||||
fn button(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
_tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
event: &smithay::input::tablet::tool::ButtonEvent,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.clicked(seat, data, &self.0, event.serial);
|
||||
}
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
_frame: smithay::input::tablet::tool::AxisFrame,
|
||||
) {
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
_time: InputTime,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl SpaceElement for WindowElement {
|
||||
fn geometry(&self) -> Rectangle<i32, Logical> {
|
||||
let mut geo = SpaceElement::geometry(&self.0);
|
||||
if self.decoration_state().is_ssd {
|
||||
geo.size.h += HEADER_BAR_HEIGHT;
|
||||
}
|
||||
geo
|
||||
}
|
||||
fn bbox(&self) -> Rectangle<i32, Logical> {
|
||||
let mut bbox = SpaceElement::bbox(&self.0);
|
||||
if self.decoration_state().is_ssd {
|
||||
bbox.size.h += HEADER_BAR_HEIGHT;
|
||||
}
|
||||
bbox
|
||||
}
|
||||
fn is_in_input_region(&self, point: &Point<f64, Logical>) -> bool {
|
||||
if self.decoration_state().is_ssd {
|
||||
point.y < HEADER_BAR_HEIGHT as f64
|
||||
|| SpaceElement::is_in_input_region(
|
||||
&self.0,
|
||||
&(*point - Point::from((0.0, HEADER_BAR_HEIGHT as f64))),
|
||||
)
|
||||
} else {
|
||||
SpaceElement::is_in_input_region(&self.0, point)
|
||||
}
|
||||
}
|
||||
fn z_index(&self) -> u8 {
|
||||
SpaceElement::z_index(&self.0)
|
||||
}
|
||||
|
||||
fn set_activate(&self, activated: bool) {
|
||||
SpaceElement::set_activate(&self.0, activated);
|
||||
}
|
||||
fn output_enter(&self, output: &Output, overlap: Rectangle<i32, Logical>) {
|
||||
SpaceElement::output_enter(&self.0, output, overlap);
|
||||
}
|
||||
fn output_leave(&self, output: &Output) {
|
||||
SpaceElement::output_leave(&self.0, output);
|
||||
}
|
||||
#[profiling::function]
|
||||
fn refresh(&self) {
|
||||
SpaceElement::refresh(&self.0);
|
||||
}
|
||||
}
|
||||
|
||||
render_elements!(
|
||||
pub WindowRenderElement<R> where R: ImportAll + ImportMem;
|
||||
Window=WaylandSurfaceRenderElement<R>,
|
||||
Decoration=SolidColorRenderElement,
|
||||
);
|
||||
|
||||
impl<R: Renderer> std::fmt::Debug for WindowRenderElement<R> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Window(arg0) => f.debug_tuple("Window").field(arg0).finish(),
|
||||
Self::Decoration(arg0) => f.debug_tuple("Decoration").field(arg0).finish(),
|
||||
Self::_GenericCatcher(arg0) => f.debug_tuple("_GenericCatcher").field(arg0).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> AsRenderElements<R> for WindowElement
|
||||
where
|
||||
R: Renderer + ImportAll + ImportMem,
|
||||
R::TextureId: Clone + Texture + 'static,
|
||||
{
|
||||
type RenderElement = WindowRenderElement<R>;
|
||||
|
||||
fn render_elements<C: From<Self::RenderElement>>(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
mut location: Point<i32, Physical>,
|
||||
scale: Scale<f64>,
|
||||
alpha: f32,
|
||||
) -> Vec<C> {
|
||||
let window_bbox = SpaceElement::bbox(&self.0);
|
||||
|
||||
if self.decoration_state().is_ssd && !window_bbox.is_empty() {
|
||||
let window_geo = SpaceElement::geometry(&self.0);
|
||||
|
||||
let mut state = self.decoration_state();
|
||||
let width = window_geo.size.w;
|
||||
state.header_bar.redraw(width as u32);
|
||||
let mut vec = AsRenderElements::<R>::render_elements::<WindowRenderElement<R>>(
|
||||
&state.header_bar,
|
||||
renderer,
|
||||
location,
|
||||
scale,
|
||||
alpha,
|
||||
);
|
||||
|
||||
location.y += (scale.y * HEADER_BAR_HEIGHT as f64) as i32;
|
||||
|
||||
let window_elements =
|
||||
AsRenderElements::render_elements(&self.0, renderer, location, scale, alpha);
|
||||
vec.extend(window_elements);
|
||||
vec.into_iter().map(C::from).collect()
|
||||
} else {
|
||||
AsRenderElements::render_elements(&self.0, renderer, location, scale, alpha)
|
||||
.into_iter()
|
||||
.map(C::from)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
+855
@@ -0,0 +1,855 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use smithay::{
|
||||
desktop::{WindowSurface, space::SpaceElement},
|
||||
input::{
|
||||
pointer::{
|
||||
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent,
|
||||
GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab,
|
||||
PointerInnerHandle, RelativeMotionEvent,
|
||||
},
|
||||
touch::{GrabStartData as TouchGrabStartData, TouchGrab},
|
||||
},
|
||||
reexports::wayland_protocols::xdg::shell::server::xdg_toplevel,
|
||||
utils::{IsAlive, Logical, Point, Serial, Size},
|
||||
wayland::{compositor::with_states, shell::xdg::SurfaceCachedState},
|
||||
};
|
||||
#[cfg(feature = "xwayland")]
|
||||
use smithay::{utils::Rectangle, xwayland::xwm::ResizeEdge as X11ResizeEdge};
|
||||
|
||||
use super::{SurfaceData, WindowElement};
|
||||
use crate::{
|
||||
focus::PointerFocusTarget,
|
||||
state::{AnvilState, Backend},
|
||||
};
|
||||
|
||||
pub struct PointerMoveSurfaceGrab<BackendData: Backend + 'static> {
|
||||
pub start_data: PointerGrabStartData<AnvilState<BackendData>>,
|
||||
pub window: WindowElement,
|
||||
pub initial_window_location: Point<i32, Logical>,
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> PointerGrab<AnvilState<BackendData>> for PointerMoveSurfaceGrab<BackendData> {
|
||||
fn motion(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||
event: &MotionEvent,
|
||||
) {
|
||||
// While the grab is active, no client has pointer focus
|
||||
handle.motion(data, None, event);
|
||||
|
||||
let delta = event.location - self.start_data.location;
|
||||
let new_location = self.initial_window_location.to_f64() + delta;
|
||||
|
||||
data.space
|
||||
.map_element(self.window.clone(), new_location.to_i32_round(), true);
|
||||
}
|
||||
|
||||
fn relative_motion(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||
event: &RelativeMotionEvent,
|
||||
) {
|
||||
handle.relative_motion(data, focus, event);
|
||||
}
|
||||
|
||||
fn button(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &ButtonEvent,
|
||||
) {
|
||||
handle.button(data, event);
|
||||
if handle.current_pressed().is_empty() {
|
||||
// No more buttons are pressed, release the grab.
|
||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||
}
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
details: AxisFrame,
|
||||
) {
|
||||
handle.axis(data, details)
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
) {
|
||||
handle.frame(data);
|
||||
}
|
||||
|
||||
fn gesture_swipe_begin(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GestureSwipeBeginEvent,
|
||||
) {
|
||||
handle.gesture_swipe_begin(data, event);
|
||||
}
|
||||
|
||||
fn gesture_swipe_update(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GestureSwipeUpdateEvent,
|
||||
) {
|
||||
handle.gesture_swipe_update(data, event);
|
||||
}
|
||||
|
||||
fn gesture_swipe_end(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GestureSwipeEndEvent,
|
||||
) {
|
||||
handle.gesture_swipe_end(data, event);
|
||||
}
|
||||
|
||||
fn gesture_pinch_begin(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GesturePinchBeginEvent,
|
||||
) {
|
||||
handle.gesture_pinch_begin(data, event);
|
||||
}
|
||||
|
||||
fn gesture_pinch_update(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GesturePinchUpdateEvent,
|
||||
) {
|
||||
handle.gesture_pinch_update(data, event);
|
||||
}
|
||||
|
||||
fn gesture_pinch_end(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GesturePinchEndEvent,
|
||||
) {
|
||||
handle.gesture_pinch_end(data, event);
|
||||
}
|
||||
|
||||
fn gesture_hold_begin(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GestureHoldBeginEvent,
|
||||
) {
|
||||
handle.gesture_hold_begin(data, event);
|
||||
}
|
||||
|
||||
fn gesture_hold_end(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GestureHoldEndEvent,
|
||||
) {
|
||||
handle.gesture_hold_end(data, event);
|
||||
}
|
||||
|
||||
fn start_data(&self) -> &PointerGrabStartData<AnvilState<BackendData>> {
|
||||
&self.start_data
|
||||
}
|
||||
|
||||
fn unset(&mut self, _data: &mut AnvilState<BackendData>) {}
|
||||
}
|
||||
|
||||
pub struct TouchMoveSurfaceGrab<BackendData: Backend + 'static> {
|
||||
pub start_data: TouchGrabStartData<AnvilState<BackendData>>,
|
||||
pub window: WindowElement,
|
||||
pub initial_window_location: Point<i32, Logical>,
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> TouchGrab<AnvilState<BackendData>> for TouchMoveSurfaceGrab<BackendData> {
|
||||
fn down(
|
||||
&mut self,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
_focus: Option<(
|
||||
<AnvilState<BackendData> as smithay::input::SeatHandler>::TouchFocus,
|
||||
Point<f64, Logical>,
|
||||
)>,
|
||||
_event: &smithay::input::touch::DownEvent,
|
||||
) {
|
||||
}
|
||||
|
||||
fn up(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &smithay::input::touch::UpEvent,
|
||||
) {
|
||||
if event.slot != self.start_data.slot {
|
||||
return;
|
||||
}
|
||||
|
||||
handle.up(data, event);
|
||||
handle.unset_grab(self, data);
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
_handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
_focus: Option<(
|
||||
<AnvilState<BackendData> as smithay::input::SeatHandler>::TouchFocus,
|
||||
Point<f64, Logical>,
|
||||
)>,
|
||||
event: &smithay::input::touch::MotionEvent,
|
||||
) {
|
||||
if event.slot != self.start_data.slot {
|
||||
return;
|
||||
}
|
||||
|
||||
let delta = event.location - self.start_data.location;
|
||||
let new_location = self.initial_window_location.to_f64() + delta;
|
||||
data.space
|
||||
.map_element(self.window.clone(), new_location.to_i32_round(), true);
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&mut self,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn cancel(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
) {
|
||||
handle.cancel(data);
|
||||
handle.unset_grab(self, data);
|
||||
}
|
||||
|
||||
fn shape(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &smithay::input::touch::ShapeEvent,
|
||||
) {
|
||||
handle.shape(data, event);
|
||||
}
|
||||
|
||||
fn orientation(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &smithay::input::touch::OrientationEvent,
|
||||
) {
|
||||
handle.orientation(data, event);
|
||||
}
|
||||
|
||||
fn start_data(&self) -> &smithay::input::touch::GrabStartData<AnvilState<BackendData>> {
|
||||
&self.start_data
|
||||
}
|
||||
|
||||
fn unset(&mut self, _data: &mut AnvilState<BackendData>) {}
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ResizeEdge: u32 {
|
||||
const NONE = 0;
|
||||
const TOP = 1;
|
||||
const BOTTOM = 2;
|
||||
const LEFT = 4;
|
||||
const TOP_LEFT = 5;
|
||||
const BOTTOM_LEFT = 6;
|
||||
const RIGHT = 8;
|
||||
const TOP_RIGHT = 9;
|
||||
const BOTTOM_RIGHT = 10;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<xdg_toplevel::ResizeEdge> for ResizeEdge {
|
||||
#[inline]
|
||||
fn from(x: xdg_toplevel::ResizeEdge) -> Self {
|
||||
Self::from_bits(x as u32).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ResizeEdge> for xdg_toplevel::ResizeEdge {
|
||||
#[inline]
|
||||
fn from(x: ResizeEdge) -> Self {
|
||||
Self::try_from(x.bits()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "xwayland")]
|
||||
impl From<X11ResizeEdge> for ResizeEdge {
|
||||
#[inline]
|
||||
fn from(edge: X11ResizeEdge) -> Self {
|
||||
match edge {
|
||||
X11ResizeEdge::Bottom => ResizeEdge::BOTTOM,
|
||||
X11ResizeEdge::BottomLeft => ResizeEdge::BOTTOM_LEFT,
|
||||
X11ResizeEdge::BottomRight => ResizeEdge::BOTTOM_RIGHT,
|
||||
X11ResizeEdge::Left => ResizeEdge::LEFT,
|
||||
X11ResizeEdge::Right => ResizeEdge::RIGHT,
|
||||
X11ResizeEdge::Top => ResizeEdge::TOP,
|
||||
X11ResizeEdge::TopLeft => ResizeEdge::TOP_LEFT,
|
||||
X11ResizeEdge::TopRight => ResizeEdge::TOP_RIGHT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about the resize operation.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub struct ResizeData {
|
||||
/// The edges the surface is being resized with.
|
||||
pub edges: ResizeEdge,
|
||||
/// The initial window location.
|
||||
pub initial_window_location: Point<i32, Logical>,
|
||||
/// The initial window size (geometry width and height).
|
||||
pub initial_window_size: Size<i32, Logical>,
|
||||
}
|
||||
|
||||
/// State of the resize operation.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
|
||||
pub enum ResizeState {
|
||||
/// The surface is not being resized.
|
||||
#[default]
|
||||
NotResizing,
|
||||
/// The surface is currently being resized.
|
||||
Resizing(ResizeData),
|
||||
/// The resize has finished, and the surface needs to ack the final configure.
|
||||
WaitingForFinalAck(ResizeData, Serial),
|
||||
/// The resize has finished, and the surface needs to commit its final state.
|
||||
WaitingForCommit(ResizeData),
|
||||
}
|
||||
|
||||
pub struct PointerResizeSurfaceGrab<BackendData: Backend + 'static> {
|
||||
pub start_data: PointerGrabStartData<AnvilState<BackendData>>,
|
||||
pub window: WindowElement,
|
||||
pub edges: ResizeEdge,
|
||||
pub initial_window_location: Point<i32, Logical>,
|
||||
pub initial_window_size: Size<i32, Logical>,
|
||||
pub last_window_size: Size<i32, Logical>,
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> PointerGrab<AnvilState<BackendData>> for PointerResizeSurfaceGrab<BackendData> {
|
||||
fn motion(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||
event: &MotionEvent,
|
||||
) {
|
||||
// While the grab is active, no client has pointer focus
|
||||
handle.motion(data, None, event);
|
||||
|
||||
// It is impossible to get `min_size` and `max_size` of dead toplevel, so we return early.
|
||||
if !self.window.alive() {
|
||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||
return;
|
||||
}
|
||||
|
||||
let (mut dx, mut dy) = (event.location - self.start_data.location).into();
|
||||
|
||||
let mut new_window_width = self.initial_window_size.w;
|
||||
let mut new_window_height = self.initial_window_size.h;
|
||||
|
||||
let left_right = ResizeEdge::LEFT | ResizeEdge::RIGHT;
|
||||
let top_bottom = ResizeEdge::TOP | ResizeEdge::BOTTOM;
|
||||
|
||||
if self.edges.intersects(left_right) {
|
||||
if self.edges.intersects(ResizeEdge::LEFT) {
|
||||
dx = -dx;
|
||||
}
|
||||
|
||||
new_window_width = (self.initial_window_size.w as f64 + dx) as i32;
|
||||
}
|
||||
|
||||
if self.edges.intersects(top_bottom) {
|
||||
if self.edges.intersects(ResizeEdge::TOP) {
|
||||
dy = -dy;
|
||||
}
|
||||
|
||||
new_window_height = (self.initial_window_size.h as f64 + dy) as i32;
|
||||
}
|
||||
|
||||
let (min_size, max_size) = if let Some(surface) = self.window.wl_surface() {
|
||||
with_states(&surface, |states| {
|
||||
let mut guard = states.cached_state.get::<SurfaceCachedState>();
|
||||
let data = guard.current();
|
||||
(data.min_size, data.max_size)
|
||||
})
|
||||
} else {
|
||||
((0, 0).into(), (0, 0).into())
|
||||
};
|
||||
|
||||
let min_width = min_size.w.max(1);
|
||||
let min_height = min_size.h.max(1);
|
||||
let max_width = if max_size.w == 0 { i32::MAX } else { max_size.w };
|
||||
let max_height = if max_size.h == 0 { i32::MAX } else { max_size.h };
|
||||
|
||||
new_window_width = new_window_width.max(min_width).min(max_width);
|
||||
new_window_height = new_window_height.max(min_height).min(max_height);
|
||||
|
||||
self.last_window_size = (new_window_width, new_window_height).into();
|
||||
|
||||
match &self.window.0.underlying_surface() {
|
||||
WindowSurface::Wayland(xdg) => {
|
||||
xdg.with_pending_state(|state| {
|
||||
state.states.set(xdg_toplevel::State::Resizing);
|
||||
state.size = Some(self.last_window_size);
|
||||
});
|
||||
xdg.send_pending_configure();
|
||||
}
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(x11) => {
|
||||
let location = data.space.element_location(&self.window).unwrap();
|
||||
x11.configure_with_sync(Rectangle::new(location, self.last_window_size), None)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn relative_motion(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||
event: &RelativeMotionEvent,
|
||||
) {
|
||||
handle.relative_motion(data, focus, event);
|
||||
}
|
||||
|
||||
fn button(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &ButtonEvent,
|
||||
) {
|
||||
handle.button(data, event);
|
||||
if handle.current_pressed().is_empty() {
|
||||
// No more buttons are pressed, release the grab.
|
||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||
|
||||
// If toplevel is dead, we can't resize it, so we return early.
|
||||
if !self.window.alive() {
|
||||
return;
|
||||
}
|
||||
|
||||
match &self.window.0.underlying_surface() {
|
||||
WindowSurface::Wayland(xdg) => {
|
||||
xdg.with_pending_state(|state| {
|
||||
state.states.unset(xdg_toplevel::State::Resizing);
|
||||
state.size = Some(self.last_window_size);
|
||||
});
|
||||
xdg.send_pending_configure();
|
||||
if self.edges.intersects(ResizeEdge::TOP_LEFT) {
|
||||
let geometry = self.window.geometry();
|
||||
let mut location = data.space.element_location(&self.window).unwrap();
|
||||
|
||||
if self.edges.intersects(ResizeEdge::LEFT) {
|
||||
location.x = self.initial_window_location.x
|
||||
+ (self.initial_window_size.w - geometry.size.w);
|
||||
}
|
||||
if self.edges.intersects(ResizeEdge::TOP) {
|
||||
location.y = self.initial_window_location.y
|
||||
+ (self.initial_window_size.h - geometry.size.h);
|
||||
}
|
||||
|
||||
data.space.map_element(self.window.clone(), location, true);
|
||||
}
|
||||
|
||||
with_states(&self.window.wl_surface().unwrap(), |states| {
|
||||
let mut data = states
|
||||
.data_map
|
||||
.get::<RefCell<SurfaceData>>()
|
||||
.unwrap()
|
||||
.borrow_mut();
|
||||
if let ResizeState::Resizing(resize_data) = data.resize_state {
|
||||
data.resize_state = ResizeState::WaitingForFinalAck(resize_data, event.serial);
|
||||
} else {
|
||||
panic!("invalid resize state: {:?}", data.resize_state);
|
||||
}
|
||||
});
|
||||
}
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(x11) => {
|
||||
let mut location = data.space.element_location(&self.window).unwrap();
|
||||
if self.edges.intersects(ResizeEdge::TOP_LEFT) {
|
||||
let geometry = self.window.geometry();
|
||||
|
||||
if self.edges.intersects(ResizeEdge::LEFT) {
|
||||
location.x = self.initial_window_location.x
|
||||
+ (self.initial_window_size.w - geometry.size.w);
|
||||
}
|
||||
if self.edges.intersects(ResizeEdge::TOP) {
|
||||
location.y = self.initial_window_location.y
|
||||
+ (self.initial_window_size.h - geometry.size.h);
|
||||
}
|
||||
|
||||
data.space.map_element(self.window.clone(), location, true);
|
||||
}
|
||||
x11.configure_with_sync(Rectangle::new(location, self.last_window_size), None)
|
||||
.unwrap();
|
||||
|
||||
let Some(surface) = self.window.wl_surface() else {
|
||||
// X11 Window got unmapped, abort
|
||||
return;
|
||||
};
|
||||
with_states(&surface, |states| {
|
||||
let mut data = states
|
||||
.data_map
|
||||
.get::<RefCell<SurfaceData>>()
|
||||
.unwrap()
|
||||
.borrow_mut();
|
||||
if let ResizeState::Resizing(resize_data) = data.resize_state {
|
||||
data.resize_state = ResizeState::WaitingForCommit(resize_data);
|
||||
} else {
|
||||
panic!("invalid resize state: {:?}", data.resize_state);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
details: AxisFrame,
|
||||
) {
|
||||
handle.axis(data, details)
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
) {
|
||||
handle.frame(data);
|
||||
}
|
||||
|
||||
fn gesture_swipe_begin(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GestureSwipeBeginEvent,
|
||||
) {
|
||||
handle.gesture_swipe_begin(data, event);
|
||||
}
|
||||
|
||||
fn gesture_swipe_update(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GestureSwipeUpdateEvent,
|
||||
) {
|
||||
handle.gesture_swipe_update(data, event);
|
||||
}
|
||||
|
||||
fn gesture_swipe_end(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GestureSwipeEndEvent,
|
||||
) {
|
||||
handle.gesture_swipe_end(data, event);
|
||||
}
|
||||
|
||||
fn gesture_pinch_begin(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GesturePinchBeginEvent,
|
||||
) {
|
||||
handle.gesture_pinch_begin(data, event);
|
||||
}
|
||||
|
||||
fn gesture_pinch_update(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GesturePinchUpdateEvent,
|
||||
) {
|
||||
handle.gesture_pinch_update(data, event);
|
||||
}
|
||||
|
||||
fn gesture_pinch_end(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GesturePinchEndEvent,
|
||||
) {
|
||||
handle.gesture_pinch_end(data, event);
|
||||
}
|
||||
|
||||
fn gesture_hold_begin(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GestureHoldBeginEvent,
|
||||
) {
|
||||
handle.gesture_hold_begin(data, event);
|
||||
}
|
||||
|
||||
fn gesture_hold_end(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GestureHoldEndEvent,
|
||||
) {
|
||||
handle.gesture_hold_end(data, event);
|
||||
}
|
||||
|
||||
fn start_data(&self) -> &PointerGrabStartData<AnvilState<BackendData>> {
|
||||
&self.start_data
|
||||
}
|
||||
|
||||
fn unset(&mut self, _data: &mut AnvilState<BackendData>) {}
|
||||
}
|
||||
|
||||
pub struct TouchResizeSurfaceGrab<BackendData: Backend + 'static> {
|
||||
pub start_data: TouchGrabStartData<AnvilState<BackendData>>,
|
||||
pub window: WindowElement,
|
||||
pub edges: ResizeEdge,
|
||||
pub initial_window_location: Point<i32, Logical>,
|
||||
pub initial_window_size: Size<i32, Logical>,
|
||||
pub last_window_size: Size<i32, Logical>,
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> TouchGrab<AnvilState<BackendData>> for TouchResizeSurfaceGrab<BackendData> {
|
||||
fn down(
|
||||
&mut self,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
_focus: Option<(
|
||||
<AnvilState<BackendData> as smithay::input::SeatHandler>::TouchFocus,
|
||||
Point<f64, Logical>,
|
||||
)>,
|
||||
_event: &smithay::input::touch::DownEvent,
|
||||
) {
|
||||
}
|
||||
|
||||
fn up(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &smithay::input::touch::UpEvent,
|
||||
) {
|
||||
if event.slot != self.start_data.slot {
|
||||
return;
|
||||
}
|
||||
handle.unset_grab(self, data);
|
||||
|
||||
// If toplevel is dead, we can't resize it, so we return early.
|
||||
if !self.window.alive() {
|
||||
return;
|
||||
}
|
||||
|
||||
match self.window.0.underlying_surface() {
|
||||
WindowSurface::Wayland(xdg) => {
|
||||
xdg.with_pending_state(|state| {
|
||||
state.states.unset(xdg_toplevel::State::Resizing);
|
||||
state.size = Some(self.last_window_size);
|
||||
});
|
||||
xdg.send_pending_configure();
|
||||
if self.edges.intersects(ResizeEdge::TOP_LEFT) {
|
||||
let geometry = self.window.geometry();
|
||||
let mut location = data.space.element_location(&self.window).unwrap();
|
||||
|
||||
if self.edges.intersects(ResizeEdge::LEFT) {
|
||||
location.x =
|
||||
self.initial_window_location.x + (self.initial_window_size.w - geometry.size.w);
|
||||
}
|
||||
if self.edges.intersects(ResizeEdge::TOP) {
|
||||
location.y =
|
||||
self.initial_window_location.y + (self.initial_window_size.h - geometry.size.h);
|
||||
}
|
||||
|
||||
data.space.map_element(self.window.clone(), location, true);
|
||||
}
|
||||
|
||||
with_states(&self.window.wl_surface().unwrap(), |states| {
|
||||
let mut data = states
|
||||
.data_map
|
||||
.get::<RefCell<SurfaceData>>()
|
||||
.unwrap()
|
||||
.borrow_mut();
|
||||
if let ResizeState::Resizing(resize_data) = data.resize_state {
|
||||
data.resize_state = ResizeState::WaitingForFinalAck(resize_data, event.serial);
|
||||
} else {
|
||||
panic!("invalid resize state: {:?}", data.resize_state);
|
||||
}
|
||||
});
|
||||
}
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(x11) => {
|
||||
let mut location = data.space.element_location(&self.window).unwrap();
|
||||
if self.edges.intersects(ResizeEdge::TOP_LEFT) {
|
||||
let geometry = self.window.geometry();
|
||||
|
||||
if self.edges.intersects(ResizeEdge::LEFT) {
|
||||
location.x =
|
||||
self.initial_window_location.x + (self.initial_window_size.w - geometry.size.w);
|
||||
}
|
||||
if self.edges.intersects(ResizeEdge::TOP) {
|
||||
location.y =
|
||||
self.initial_window_location.y + (self.initial_window_size.h - geometry.size.h);
|
||||
}
|
||||
|
||||
data.space.map_element(self.window.clone(), location, true);
|
||||
}
|
||||
x11.configure_with_sync(Rectangle::new(location, self.last_window_size), None)
|
||||
.unwrap();
|
||||
|
||||
let Some(surface) = self.window.wl_surface() else {
|
||||
// X11 Window got unmapped, abort
|
||||
return;
|
||||
};
|
||||
with_states(&surface, |states| {
|
||||
let mut data = states
|
||||
.data_map
|
||||
.get::<RefCell<SurfaceData>>()
|
||||
.unwrap()
|
||||
.borrow_mut();
|
||||
if let ResizeState::Resizing(resize_data) = data.resize_state {
|
||||
data.resize_state = ResizeState::WaitingForCommit(resize_data);
|
||||
} else {
|
||||
panic!("invalid resize state: {:?}", data.resize_state);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
_focus: Option<(
|
||||
<AnvilState<BackendData> as smithay::input::SeatHandler>::TouchFocus,
|
||||
Point<f64, Logical>,
|
||||
)>,
|
||||
event: &smithay::input::touch::MotionEvent,
|
||||
) {
|
||||
if event.slot != self.start_data.slot {
|
||||
return;
|
||||
}
|
||||
|
||||
// It is impossible to get `min_size` and `max_size` of dead toplevel, so we return early.
|
||||
if !self.window.alive() {
|
||||
handle.unset_grab(self, data);
|
||||
return;
|
||||
}
|
||||
|
||||
let (mut dx, mut dy) = (event.location - self.start_data.location).into();
|
||||
|
||||
let mut new_window_width = self.initial_window_size.w;
|
||||
let mut new_window_height = self.initial_window_size.h;
|
||||
|
||||
let left_right = ResizeEdge::LEFT | ResizeEdge::RIGHT;
|
||||
let top_bottom = ResizeEdge::TOP | ResizeEdge::BOTTOM;
|
||||
|
||||
if self.edges.intersects(left_right) {
|
||||
if self.edges.intersects(ResizeEdge::LEFT) {
|
||||
dx = -dx;
|
||||
}
|
||||
|
||||
new_window_width = (self.initial_window_size.w as f64 + dx) as i32;
|
||||
}
|
||||
|
||||
if self.edges.intersects(top_bottom) {
|
||||
if self.edges.intersects(ResizeEdge::TOP) {
|
||||
dy = -dy;
|
||||
}
|
||||
|
||||
new_window_height = (self.initial_window_size.h as f64 + dy) as i32;
|
||||
}
|
||||
|
||||
let (min_size, max_size) = if let Some(surface) = self.window.wl_surface() {
|
||||
with_states(&surface, |states| {
|
||||
let mut guard = states.cached_state.get::<SurfaceCachedState>();
|
||||
let data = guard.current();
|
||||
(data.min_size, data.max_size)
|
||||
})
|
||||
} else {
|
||||
((0, 0).into(), (0, 0).into())
|
||||
};
|
||||
|
||||
let min_width = min_size.w.max(1);
|
||||
let min_height = min_size.h.max(1);
|
||||
let max_width = if max_size.w == 0 { i32::MAX } else { max_size.w };
|
||||
let max_height = if max_size.h == 0 { i32::MAX } else { max_size.h };
|
||||
|
||||
new_window_width = new_window_width.max(min_width).min(max_width);
|
||||
new_window_height = new_window_height.max(min_height).min(max_height);
|
||||
|
||||
self.last_window_size = (new_window_width, new_window_height).into();
|
||||
|
||||
match self.window.0.underlying_surface() {
|
||||
WindowSurface::Wayland(xdg) => {
|
||||
xdg.with_pending_state(|state| {
|
||||
state.states.set(xdg_toplevel::State::Resizing);
|
||||
state.size = Some(self.last_window_size);
|
||||
});
|
||||
xdg.send_pending_configure();
|
||||
}
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(x11) => {
|
||||
let location = data.space.element_location(&self.window).unwrap();
|
||||
x11.configure_with_sync(Rectangle::new(location, self.last_window_size), None)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&mut self,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn cancel(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
) {
|
||||
handle.cancel(data);
|
||||
handle.unset_grab(self, data);
|
||||
}
|
||||
|
||||
fn shape(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &smithay::input::touch::ShapeEvent,
|
||||
) {
|
||||
handle.shape(data, event);
|
||||
}
|
||||
|
||||
fn orientation(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &smithay::input::touch::OrientationEvent,
|
||||
) {
|
||||
handle.orientation(data, event);
|
||||
}
|
||||
|
||||
fn start_data(&self) -> &smithay::input::touch::GrabStartData<AnvilState<BackendData>> {
|
||||
&self.start_data
|
||||
}
|
||||
|
||||
fn unset(&mut self, _data: &mut AnvilState<BackendData>) {}
|
||||
}
|
||||
+475
@@ -0,0 +1,475 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
#[cfg(feature = "xwayland")]
|
||||
use smithay::xwayland::XWaylandClientData;
|
||||
|
||||
#[cfg(feature = "udev")]
|
||||
use smithay::wayland::drm_syncobj::DrmSyncobjCachedState;
|
||||
|
||||
use smithay::{
|
||||
backend::renderer::utils::on_commit_buffer_handler,
|
||||
desktop::{
|
||||
LayerSurface, PopupKind, PopupManager, Space, WindowSurfaceType, layer_map_for_output,
|
||||
space::SpaceElement,
|
||||
},
|
||||
input::pointer::{CursorImageStatus, CursorImageSurfaceData},
|
||||
output::Output,
|
||||
reexports::{
|
||||
calloop::Interest,
|
||||
wayland_server::{
|
||||
Client, Resource,
|
||||
protocol::{wl_buffer::WlBuffer, wl_output, wl_surface::WlSurface},
|
||||
},
|
||||
},
|
||||
utils::{IsAlive, Logical, Point, Rectangle, Size},
|
||||
wayland::{
|
||||
buffer::BufferHandler,
|
||||
compositor::{
|
||||
BufferAssignment, CompositorClientState, CompositorHandler, CompositorState, SurfaceAttributes,
|
||||
TraversalAction, add_blocker, add_pre_commit_hook, get_parent, is_sync_subsurface, with_states,
|
||||
with_surface_tree_upward,
|
||||
},
|
||||
dmabuf::get_dmabuf,
|
||||
shell::{
|
||||
wlr_layer::{
|
||||
Layer, LayerSurface as WlrLayerSurface, LayerSurfaceData, WlrLayerShellHandler,
|
||||
WlrLayerShellState,
|
||||
},
|
||||
xdg::XdgToplevelSurfaceData,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ClientState,
|
||||
state::{AnvilState, Backend},
|
||||
};
|
||||
|
||||
mod element;
|
||||
mod grabs;
|
||||
pub(crate) mod ssd;
|
||||
#[cfg(feature = "xwayland")]
|
||||
mod x11;
|
||||
mod xdg;
|
||||
|
||||
pub use self::element::*;
|
||||
pub use self::grabs::*;
|
||||
|
||||
fn fullscreen_output_geometry(
|
||||
wl_surface: &WlSurface,
|
||||
wl_output: Option<&wl_output::WlOutput>,
|
||||
space: &mut Space<WindowElement>,
|
||||
) -> Option<Rectangle<i32, Logical>> {
|
||||
// First test if a specific output has been requested
|
||||
// if the requested output is not found ignore the request
|
||||
wl_output
|
||||
.and_then(Output::from_resource)
|
||||
.or_else(|| {
|
||||
let w = space
|
||||
.elements()
|
||||
.find(|window| window.wl_surface().map(|s| &*s == wl_surface).unwrap_or(false));
|
||||
w.and_then(|w| space.outputs_for_element(w).first().cloned())
|
||||
})
|
||||
.as_ref()
|
||||
.and_then(|o| space.output_geometry(o))
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FullscreenSurface(RefCell<Option<WindowElement>>);
|
||||
|
||||
impl FullscreenSurface {
|
||||
pub fn set(&self, window: WindowElement) {
|
||||
*self.0.borrow_mut() = Some(window);
|
||||
}
|
||||
|
||||
pub fn get(&self) -> Option<WindowElement> {
|
||||
let mut window = self.0.borrow_mut();
|
||||
if window.as_ref().map(|w| !w.alive()).unwrap_or(false) {
|
||||
*window = None;
|
||||
}
|
||||
window.clone()
|
||||
}
|
||||
|
||||
pub fn clear(&self) -> Option<WindowElement> {
|
||||
self.0.borrow_mut().take()
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> BufferHandler for AnvilState<BackendData> {
|
||||
fn buffer_destroyed(&mut self, _buffer: &WlBuffer) {}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> CompositorHandler for AnvilState<BackendData> {
|
||||
fn compositor_state(&mut self) -> &mut CompositorState {
|
||||
&mut self.compositor_state
|
||||
}
|
||||
fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
|
||||
#[cfg(feature = "xwayland")]
|
||||
if let Some(state) = client.get_data::<XWaylandClientData>() {
|
||||
return &state.compositor_state;
|
||||
}
|
||||
if let Some(state) = client.get_data::<ClientState>() {
|
||||
return &state.compositor_state;
|
||||
}
|
||||
panic!("Unknown client data type")
|
||||
}
|
||||
|
||||
fn new_surface(&mut self, surface: &WlSurface) {
|
||||
add_pre_commit_hook::<Self, _>(surface, move |state, _dh, surface| {
|
||||
#[cfg(feature = "udev")]
|
||||
let mut acquire_point = None;
|
||||
let maybe_dmabuf = with_states(surface, |surface_data| {
|
||||
#[cfg(feature = "udev")]
|
||||
acquire_point.clone_from(
|
||||
&surface_data
|
||||
.cached_state
|
||||
.get::<DrmSyncobjCachedState>()
|
||||
.pending()
|
||||
.acquire_point,
|
||||
);
|
||||
surface_data
|
||||
.cached_state
|
||||
.get::<SurfaceAttributes>()
|
||||
.pending()
|
||||
.buffer
|
||||
.as_ref()
|
||||
.and_then(|assignment| match assignment {
|
||||
BufferAssignment::NewBuffer(buffer) => get_dmabuf(buffer).cloned().ok(),
|
||||
_ => None,
|
||||
})
|
||||
});
|
||||
if let Some(dmabuf) = maybe_dmabuf {
|
||||
#[cfg(feature = "udev")]
|
||||
if let Some(acquire_point) = acquire_point {
|
||||
if let Ok((blocker, source)) = acquire_point.generate_blocker() {
|
||||
let client = surface.client().unwrap();
|
||||
let res = state.handle.insert_source(source, move |_, _, data| {
|
||||
let dh = data.display_handle.clone();
|
||||
data.client_compositor_state(&client).blocker_cleared(data, &dh);
|
||||
Ok(())
|
||||
});
|
||||
if res.is_ok() {
|
||||
add_blocker(surface, blocker);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Ok((blocker, source)) = dmabuf.generate_blocker(Interest::READ) {
|
||||
if let Some(client) = surface.client() {
|
||||
let res = state.handle.insert_source(source, move |_, _, data| {
|
||||
let dh = data.display_handle.clone();
|
||||
data.client_compositor_state(&client).blocker_cleared(data, &dh);
|
||||
Ok(())
|
||||
});
|
||||
if res.is_ok() {
|
||||
add_blocker(surface, blocker);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn commit(&mut self, surface: &WlSurface) {
|
||||
on_commit_buffer_handler::<Self>(surface);
|
||||
self.backend_data.early_import(surface);
|
||||
|
||||
if !is_sync_subsurface(surface) {
|
||||
let mut root = surface.clone();
|
||||
while let Some(parent) = get_parent(&root) {
|
||||
root = parent;
|
||||
}
|
||||
if let Some(window) = self.window_for_surface(&root) {
|
||||
window.0.on_commit();
|
||||
|
||||
if &root == surface {
|
||||
let buffer_offset = with_states(surface, |states| {
|
||||
states
|
||||
.cached_state
|
||||
.get::<SurfaceAttributes>()
|
||||
.current()
|
||||
.buffer_delta
|
||||
.take()
|
||||
});
|
||||
|
||||
if let Some(buffer_offset) = buffer_offset {
|
||||
let current_loc = self.space.element_location(&window).unwrap();
|
||||
self.space.map_element(window, current_loc + buffer_offset, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.popups.commit(surface);
|
||||
|
||||
if matches!(&self.cursor_status, CursorImageStatus::Surface(cursor_surface) if cursor_surface == surface)
|
||||
{
|
||||
with_states(surface, |states| {
|
||||
let cursor_image_attributes = states.data_map.get::<CursorImageSurfaceData>();
|
||||
|
||||
if let Some(mut cursor_image_attributes) =
|
||||
cursor_image_attributes.map(|attrs| attrs.lock().unwrap())
|
||||
{
|
||||
let buffer_delta = states
|
||||
.cached_state
|
||||
.get::<SurfaceAttributes>()
|
||||
.current()
|
||||
.buffer_delta
|
||||
.take();
|
||||
if let Some(buffer_delta) = buffer_delta {
|
||||
tracing::trace!(hotspot = ?cursor_image_attributes.hotspot, ?buffer_delta, "decrementing cursor hotspot");
|
||||
cursor_image_attributes.hotspot -= buffer_delta;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if matches!(&self.dnd_icon, Some(icon) if &icon.surface == surface) {
|
||||
let dnd_icon = self.dnd_icon.as_mut().unwrap();
|
||||
with_states(&dnd_icon.surface, |states| {
|
||||
let buffer_delta = states
|
||||
.cached_state
|
||||
.get::<SurfaceAttributes>()
|
||||
.current()
|
||||
.buffer_delta
|
||||
.take()
|
||||
.unwrap_or_default();
|
||||
tracing::trace!(offset = ?dnd_icon.offset, ?buffer_delta, "moving dnd offset");
|
||||
dnd_icon.offset += buffer_delta;
|
||||
});
|
||||
}
|
||||
|
||||
ensure_initial_configure(surface, &self.space, &mut self.popups)
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> WlrLayerShellHandler for AnvilState<BackendData> {
|
||||
fn shell_state(&mut self) -> &mut WlrLayerShellState {
|
||||
&mut self.layer_shell_state
|
||||
}
|
||||
|
||||
fn new_layer_surface(
|
||||
&mut self,
|
||||
surface: WlrLayerSurface,
|
||||
wl_output: Option<wl_output::WlOutput>,
|
||||
_layer: Layer,
|
||||
namespace: String,
|
||||
) {
|
||||
let output = wl_output
|
||||
.as_ref()
|
||||
.and_then(Output::from_resource)
|
||||
.unwrap_or_else(|| self.space.outputs().next().unwrap().clone());
|
||||
let mut map = layer_map_for_output(&output);
|
||||
map.map_layer(&LayerSurface::new(surface, namespace)).unwrap();
|
||||
}
|
||||
|
||||
fn layer_destroyed(&mut self, surface: WlrLayerSurface) {
|
||||
if let Some((mut map, layer)) = self.space.outputs().find_map(|o| {
|
||||
let map = layer_map_for_output(o);
|
||||
let layer = map
|
||||
.layers()
|
||||
.find(|&layer| layer.layer_surface() == &surface)
|
||||
.cloned();
|
||||
layer.map(|layer| (map, layer))
|
||||
}) {
|
||||
map.unmap_layer(&layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> AnvilState<BackendData> {
|
||||
pub fn window_for_surface(&self, surface: &WlSurface) -> Option<WindowElement> {
|
||||
self.space
|
||||
.elements()
|
||||
.find(|window| window.wl_surface().map(|s| &*s == surface).unwrap_or(false))
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SurfaceData {
|
||||
pub geometry: Option<Rectangle<i32, Logical>>,
|
||||
pub resize_state: ResizeState,
|
||||
}
|
||||
|
||||
fn ensure_initial_configure(surface: &WlSurface, space: &Space<WindowElement>, popups: &mut PopupManager) {
|
||||
with_surface_tree_upward(
|
||||
surface,
|
||||
(),
|
||||
|_, _, _| TraversalAction::DoChildren(()),
|
||||
|_, states, _| {
|
||||
states
|
||||
.data_map
|
||||
.insert_if_missing(|| RefCell::new(SurfaceData::default()));
|
||||
},
|
||||
|_, _, _| true,
|
||||
);
|
||||
|
||||
if let Some(window) = space
|
||||
.elements()
|
||||
.find(|window| window.wl_surface().map(|s| &*s == surface).unwrap_or(false))
|
||||
.cloned()
|
||||
{
|
||||
// send the initial configure if relevant
|
||||
#[cfg_attr(not(feature = "xwayland"), allow(irrefutable_let_patterns))]
|
||||
if let Some(toplevel) = window.0.toplevel() {
|
||||
let initial_configure_sent = with_states(surface, |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.initial_configure_sent
|
||||
});
|
||||
if !initial_configure_sent {
|
||||
toplevel.send_configure();
|
||||
}
|
||||
}
|
||||
|
||||
with_states(surface, |states| {
|
||||
let mut data = states
|
||||
.data_map
|
||||
.get::<RefCell<SurfaceData>>()
|
||||
.unwrap()
|
||||
.borrow_mut();
|
||||
|
||||
// Finish resizing.
|
||||
if let ResizeState::WaitingForCommit(_) = data.resize_state {
|
||||
data.resize_state = ResizeState::NotResizing;
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(popup) = popups.find_popup(surface) {
|
||||
let popup = match popup {
|
||||
PopupKind::Xdg(ref popup) => popup,
|
||||
// Doesn't require configure
|
||||
PopupKind::InputMethod(ref _input_popup) => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if !popup.is_initial_configure_sent() {
|
||||
// NOTE: This should never fail as the initial configure is always
|
||||
// allowed.
|
||||
popup.send_configure().expect("initial configure failed");
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(output) = space.outputs().find(|o| {
|
||||
let map = layer_map_for_output(o);
|
||||
map.layer_for_surface(surface, WindowSurfaceType::TOPLEVEL)
|
||||
.is_some()
|
||||
}) {
|
||||
let initial_configure_sent = with_states(surface, |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<LayerSurfaceData>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.initial_configure_sent
|
||||
});
|
||||
|
||||
let mut map = layer_map_for_output(output);
|
||||
|
||||
// arrange the layers before sending the initial configure
|
||||
// to respect any size the client may have sent
|
||||
map.arrange();
|
||||
// send the initial configure if relevant
|
||||
if !initial_configure_sent {
|
||||
let layer = map
|
||||
.layer_for_surface(surface, WindowSurfaceType::TOPLEVEL)
|
||||
.unwrap();
|
||||
|
||||
layer.layer_surface().send_configure();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn place_new_window(
|
||||
space: &mut Space<WindowElement>,
|
||||
pointer_location: Point<f64, Logical>,
|
||||
window: &WindowElement,
|
||||
activate: bool,
|
||||
) {
|
||||
// place the window at a random location on same output as pointer
|
||||
// or if there is not output in a [0;800]x[0;800] square
|
||||
use rand::distributions::{Distribution, Uniform};
|
||||
|
||||
let output = space
|
||||
.output_under(pointer_location)
|
||||
.next()
|
||||
.or_else(|| space.outputs().next())
|
||||
.cloned();
|
||||
let output_geometry = output
|
||||
.and_then(|o| {
|
||||
let geo = space.output_geometry(&o)?;
|
||||
let map = layer_map_for_output(&o);
|
||||
let zone = map.non_exclusive_zone();
|
||||
Some(Rectangle::new(geo.loc + zone.loc, zone.size))
|
||||
})
|
||||
.unwrap_or_else(|| Rectangle::from_size((800, 800).into()));
|
||||
|
||||
// set the initial toplevel bounds
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
if let Some(toplevel) = window.0.toplevel() {
|
||||
toplevel.with_pending_state(|state| {
|
||||
state.bounds = Some(output_geometry.size);
|
||||
});
|
||||
}
|
||||
|
||||
let max_x = output_geometry.loc.x + (((output_geometry.size.w as f32) / 3.0) * 2.0) as i32;
|
||||
let max_y = output_geometry.loc.y + (((output_geometry.size.h as f32) / 3.0) * 2.0) as i32;
|
||||
let x_range = Uniform::new(output_geometry.loc.x, max_x);
|
||||
let y_range = Uniform::new(output_geometry.loc.y, max_y);
|
||||
let mut rng = rand::thread_rng();
|
||||
let x = x_range.sample(&mut rng);
|
||||
let y = y_range.sample(&mut rng);
|
||||
|
||||
space.map_element(window.clone(), (x, y), activate);
|
||||
}
|
||||
|
||||
pub fn fixup_positions(space: &mut Space<WindowElement>, pointer_location: Point<f64, Logical>) {
|
||||
// fixup outputs
|
||||
let mut offset = Point::<i32, Logical>::from((0, 0));
|
||||
for output in space.outputs().cloned().collect::<Vec<_>>().into_iter() {
|
||||
let size = space
|
||||
.output_geometry(&output)
|
||||
.map(|geo| geo.size)
|
||||
.unwrap_or_else(|| Size::from((0, 0)));
|
||||
space.map_output(&output, offset);
|
||||
layer_map_for_output(&output).arrange();
|
||||
offset.x += size.w;
|
||||
}
|
||||
|
||||
// fixup windows
|
||||
let mut orphaned_windows = Vec::new();
|
||||
let outputs = space
|
||||
.outputs()
|
||||
.flat_map(|o| {
|
||||
let geo = space.output_geometry(o)?;
|
||||
let map = layer_map_for_output(o);
|
||||
let zone = map.non_exclusive_zone();
|
||||
Some(Rectangle::new(geo.loc + zone.loc, zone.size))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
for window in space.elements() {
|
||||
let window_location = match space.element_location(window) {
|
||||
Some(loc) => loc,
|
||||
None => continue,
|
||||
};
|
||||
let geo_loc = window.bbox().loc + window_location;
|
||||
|
||||
if !outputs.iter().any(|o_geo| o_geo.contains(geo_loc)) {
|
||||
orphaned_windows.push(window.clone());
|
||||
}
|
||||
}
|
||||
for window in orphaned_windows.into_iter() {
|
||||
place_new_window(space, pointer_location, &window, false);
|
||||
}
|
||||
}
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
use smithay::{
|
||||
backend::renderer::{
|
||||
Renderer,
|
||||
element::{
|
||||
AsRenderElements, Kind,
|
||||
solid::{SolidColorBuffer, SolidColorRenderElement},
|
||||
},
|
||||
},
|
||||
desktop::WindowSurface,
|
||||
input::Seat,
|
||||
utils::{Logical, Point, Serial},
|
||||
wayland::shell::xdg::XdgShellHandler,
|
||||
};
|
||||
|
||||
use std::cell::{RefCell, RefMut};
|
||||
|
||||
use crate::{AnvilState, state::Backend};
|
||||
|
||||
use super::WindowElement;
|
||||
|
||||
pub struct WindowState {
|
||||
pub is_ssd: bool,
|
||||
pub header_bar: HeaderBar,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HeaderBar {
|
||||
pub pointer_loc: Option<Point<f64, Logical>>,
|
||||
pub width: u32,
|
||||
pub close_button_hover: bool,
|
||||
pub maximize_button_hover: bool,
|
||||
pub background: SolidColorBuffer,
|
||||
pub close_button: SolidColorBuffer,
|
||||
pub maximize_button: SolidColorBuffer,
|
||||
}
|
||||
|
||||
const BG_COLOR: [f32; 4] = [0.75f32, 0.9f32, 0.78f32, 1f32];
|
||||
const MAX_COLOR: [f32; 4] = [1f32, 0.965f32, 0.71f32, 1f32];
|
||||
const CLOSE_COLOR: [f32; 4] = [1f32, 0.66f32, 0.612f32, 1f32];
|
||||
const MAX_COLOR_HOVER: [f32; 4] = [0.71f32, 0.624f32, 0f32, 1f32];
|
||||
const CLOSE_COLOR_HOVER: [f32; 4] = [0.75f32, 0.11f32, 0.016f32, 1f32];
|
||||
|
||||
pub const HEADER_BAR_HEIGHT: i32 = 32;
|
||||
const BUTTON_HEIGHT: u32 = HEADER_BAR_HEIGHT as u32;
|
||||
const BUTTON_WIDTH: u32 = 32;
|
||||
|
||||
impl HeaderBar {
|
||||
pub fn pointer_enter(&mut self, loc: Point<f64, Logical>) {
|
||||
self.pointer_loc = Some(loc);
|
||||
}
|
||||
|
||||
pub fn pointer_leave(&mut self) {
|
||||
self.pointer_loc = None;
|
||||
}
|
||||
|
||||
pub fn clicked<BackendData: Backend>(
|
||||
&mut self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
state: &mut AnvilState<BackendData>,
|
||||
window: &WindowElement,
|
||||
serial: Serial,
|
||||
) {
|
||||
match self.pointer_loc.as_ref() {
|
||||
Some(loc) if loc.x >= (self.width - BUTTON_WIDTH) as f64 => {
|
||||
match window.0.underlying_surface() {
|
||||
WindowSurface::Wayland(w) => w.send_close(),
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(w) => {
|
||||
let _ = w.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
Some(loc) if loc.x >= (self.width - (BUTTON_WIDTH * 2)) as f64 => {
|
||||
match window.0.underlying_surface() {
|
||||
WindowSurface::Wayland(w) => state.maximize_request(w.clone()),
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(w) => {
|
||||
let surface = w.clone();
|
||||
state
|
||||
.handle
|
||||
.insert_idle(move |data| data.maximize_request_x11(&surface));
|
||||
}
|
||||
};
|
||||
}
|
||||
Some(_) => {
|
||||
match window.0.underlying_surface() {
|
||||
WindowSurface::Wayland(w) => {
|
||||
let seat = seat.clone();
|
||||
let toplevel = w.clone();
|
||||
state
|
||||
.handle
|
||||
.insert_idle(move |data| data.move_request_xdg(&toplevel, &seat, serial));
|
||||
}
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(w) => {
|
||||
let window = w.clone();
|
||||
state
|
||||
.handle
|
||||
.insert_idle(move |data| data.move_request_x11(&window));
|
||||
}
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn touch_down<BackendData: Backend>(
|
||||
&mut self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
state: &mut AnvilState<BackendData>,
|
||||
window: &WindowElement,
|
||||
serial: Serial,
|
||||
) {
|
||||
match self.pointer_loc.as_ref() {
|
||||
Some(loc) if loc.x >= (self.width - BUTTON_WIDTH) as f64 => {}
|
||||
Some(loc) if loc.x >= (self.width - (BUTTON_WIDTH * 2)) as f64 => {}
|
||||
Some(_) => {
|
||||
match window.0.underlying_surface() {
|
||||
WindowSurface::Wayland(w) => {
|
||||
let seat = seat.clone();
|
||||
let toplevel = w.clone();
|
||||
state
|
||||
.handle
|
||||
.insert_idle(move |data| data.move_request_xdg(&toplevel, &seat, serial));
|
||||
}
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(w) => {
|
||||
let window = w.clone();
|
||||
state
|
||||
.handle
|
||||
.insert_idle(move |data| data.move_request_x11(&window));
|
||||
}
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn touch_up<BackendData: Backend>(
|
||||
&mut self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
state: &mut AnvilState<BackendData>,
|
||||
window: &WindowElement,
|
||||
) {
|
||||
match self.pointer_loc.as_ref() {
|
||||
Some(loc) if loc.x >= (self.width - BUTTON_WIDTH) as f64 => {
|
||||
match window.0.underlying_surface() {
|
||||
WindowSurface::Wayland(w) => w.send_close(),
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(w) => {
|
||||
let _ = w.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
Some(loc) if loc.x >= (self.width - (BUTTON_WIDTH * 2)) as f64 => {
|
||||
match window.0.underlying_surface() {
|
||||
WindowSurface::Wayland(w) => state.maximize_request(w.clone()),
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(w) => {
|
||||
let surface = w.clone();
|
||||
state
|
||||
.handle
|
||||
.insert_idle(move |data| data.maximize_request_x11(&surface));
|
||||
}
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn redraw(&mut self, width: u32) {
|
||||
if width == 0 {
|
||||
self.width = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
self.background
|
||||
.update((width as i32, HEADER_BAR_HEIGHT), BG_COLOR);
|
||||
|
||||
let mut needs_redraw_buttons = false;
|
||||
if width != self.width {
|
||||
needs_redraw_buttons = true;
|
||||
self.width = width;
|
||||
}
|
||||
|
||||
if self
|
||||
.pointer_loc
|
||||
.as_ref()
|
||||
.map(|l| l.x >= (width - BUTTON_WIDTH) as f64)
|
||||
.unwrap_or(false)
|
||||
&& (needs_redraw_buttons || !self.close_button_hover)
|
||||
{
|
||||
self.close_button
|
||||
.update((BUTTON_WIDTH as i32, BUTTON_HEIGHT as i32), CLOSE_COLOR_HOVER);
|
||||
self.close_button_hover = true;
|
||||
} else if !self
|
||||
.pointer_loc
|
||||
.as_ref()
|
||||
.map(|l| l.x >= (width - BUTTON_WIDTH) as f64)
|
||||
.unwrap_or(false)
|
||||
&& (needs_redraw_buttons || self.close_button_hover)
|
||||
{
|
||||
self.close_button
|
||||
.update((BUTTON_WIDTH as i32, BUTTON_HEIGHT as i32), CLOSE_COLOR);
|
||||
self.close_button_hover = false;
|
||||
}
|
||||
|
||||
if self
|
||||
.pointer_loc
|
||||
.as_ref()
|
||||
.map(|l| l.x >= (width - BUTTON_WIDTH * 2) as f64 && l.x <= (width - BUTTON_WIDTH) as f64)
|
||||
.unwrap_or(false)
|
||||
&& (needs_redraw_buttons || !self.maximize_button_hover)
|
||||
{
|
||||
self.maximize_button
|
||||
.update((BUTTON_WIDTH as i32, BUTTON_HEIGHT as i32), MAX_COLOR_HOVER);
|
||||
self.maximize_button_hover = true;
|
||||
} else if !self
|
||||
.pointer_loc
|
||||
.as_ref()
|
||||
.map(|l| l.x >= (width - BUTTON_WIDTH * 2) as f64 && l.x <= (width - BUTTON_WIDTH) as f64)
|
||||
.unwrap_or(false)
|
||||
&& (needs_redraw_buttons || self.maximize_button_hover)
|
||||
{
|
||||
self.maximize_button
|
||||
.update((BUTTON_WIDTH as i32, BUTTON_HEIGHT as i32), MAX_COLOR);
|
||||
self.maximize_button_hover = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Renderer> AsRenderElements<R> for HeaderBar {
|
||||
type RenderElement = SolidColorRenderElement;
|
||||
|
||||
fn render_elements<C: From<Self::RenderElement>>(
|
||||
&self,
|
||||
_renderer: &mut R,
|
||||
location: Point<i32, smithay::utils::Physical>,
|
||||
scale: smithay::utils::Scale<f64>,
|
||||
alpha: f32,
|
||||
) -> Vec<C> {
|
||||
let header_end_offset: Point<i32, Logical> = Point::from((self.width as i32, 0));
|
||||
let button_offset: Point<i32, Logical> = Point::from((BUTTON_WIDTH as i32, 0));
|
||||
|
||||
vec![
|
||||
SolidColorRenderElement::from_buffer(
|
||||
&self.close_button,
|
||||
location + (header_end_offset - button_offset).to_physical_precise_round(scale),
|
||||
scale,
|
||||
alpha,
|
||||
Kind::Unspecified,
|
||||
)
|
||||
.into(),
|
||||
SolidColorRenderElement::from_buffer(
|
||||
&self.maximize_button,
|
||||
location + (header_end_offset - button_offset.upscale(2)).to_physical_precise_round(scale),
|
||||
scale,
|
||||
alpha,
|
||||
Kind::Unspecified,
|
||||
)
|
||||
.into(),
|
||||
SolidColorRenderElement::from_buffer(&self.background, location, scale, alpha, Kind::Unspecified)
|
||||
.into(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowElement {
|
||||
pub fn decoration_state(&self) -> RefMut<'_, WindowState> {
|
||||
self.user_data().insert_if_missing(|| {
|
||||
RefCell::new(WindowState {
|
||||
is_ssd: false,
|
||||
header_bar: HeaderBar {
|
||||
pointer_loc: None,
|
||||
width: 0,
|
||||
close_button_hover: false,
|
||||
maximize_button_hover: false,
|
||||
background: SolidColorBuffer::default(),
|
||||
close_button: SolidColorBuffer::default(),
|
||||
maximize_button: SolidColorBuffer::default(),
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
self.user_data()
|
||||
.get::<RefCell<WindowState>>()
|
||||
.unwrap()
|
||||
.borrow_mut()
|
||||
}
|
||||
|
||||
pub fn set_ssd(&self, ssd: bool) {
|
||||
self.decoration_state().is_ssd = ssd;
|
||||
}
|
||||
}
|
||||
+431
@@ -0,0 +1,431 @@
|
||||
use std::{cell::RefCell, os::unix::io::OwnedFd};
|
||||
|
||||
use smithay::{
|
||||
desktop::{Window, space::SpaceElement},
|
||||
input::pointer::Focus,
|
||||
utils::{Logical, Rectangle, SERIAL_COUNTER},
|
||||
wayland::{
|
||||
compositor::with_states,
|
||||
selection::{
|
||||
SelectionTarget,
|
||||
data_device::{
|
||||
clear_data_device_selection, current_data_device_selection_userdata,
|
||||
request_data_device_client_selection, set_data_device_selection,
|
||||
},
|
||||
primary_selection::{
|
||||
clear_primary_selection, current_primary_selection_userdata,
|
||||
request_primary_client_selection, set_primary_selection,
|
||||
},
|
||||
},
|
||||
xwayland_shell::{XWaylandShellHandler, XWaylandShellState},
|
||||
},
|
||||
xwayland::{
|
||||
X11Surface, X11Wm, XwmHandler,
|
||||
xwm::{Reorder, ResizeEdge as X11ResizeEdge, XwmId},
|
||||
},
|
||||
};
|
||||
use tracing::{error, trace};
|
||||
|
||||
use crate::{AnvilState, focus::KeyboardFocusTarget, state::Backend};
|
||||
|
||||
use super::{
|
||||
FullscreenSurface, PointerMoveSurfaceGrab, PointerResizeSurfaceGrab, ResizeData, ResizeState,
|
||||
SurfaceData, TouchMoveSurfaceGrab, WindowElement, place_new_window,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct OldGeometry(RefCell<Option<Rectangle<i32, Logical>>>);
|
||||
impl OldGeometry {
|
||||
pub fn save(&self, geo: Rectangle<i32, Logical>) {
|
||||
*self.0.borrow_mut() = Some(geo);
|
||||
}
|
||||
|
||||
pub fn restore(&self) -> Option<Rectangle<i32, Logical>> {
|
||||
self.0.borrow_mut().take()
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> XWaylandShellHandler for AnvilState<BackendData> {
|
||||
fn xwayland_shell_state(&mut self) -> &mut XWaylandShellState {
|
||||
&mut self.xwayland_shell_state
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> XwmHandler for AnvilState<BackendData> {
|
||||
fn xwm_state(&mut self, _xwm: XwmId) -> &mut X11Wm {
|
||||
self.xwm.as_mut().unwrap()
|
||||
}
|
||||
|
||||
fn new_window(&mut self, _xwm: XwmId, _window: X11Surface) {}
|
||||
fn new_override_redirect_window(&mut self, _xwm: XwmId, _window: X11Surface) {}
|
||||
|
||||
fn map_window_request(&mut self, _xwm: XwmId, window: X11Surface) {
|
||||
window.set_mapped(true).unwrap();
|
||||
let window = WindowElement(Window::new_x11_window(window));
|
||||
place_new_window(&mut self.space, self.pointer.current_location(), &window, true);
|
||||
let bbox = self.space.element_bbox(&window).unwrap();
|
||||
let Some(xsurface) = window.0.x11_surface() else {
|
||||
unreachable!()
|
||||
};
|
||||
xsurface.configure(Some(bbox)).unwrap();
|
||||
window.set_ssd(!xsurface.is_decorated());
|
||||
}
|
||||
|
||||
fn mapped_override_redirect_window(&mut self, _xwm: XwmId, window: X11Surface) {
|
||||
let location = window.last_configure().loc;
|
||||
let window = WindowElement(Window::new_x11_window(window));
|
||||
self.space.map_element(window, location, true);
|
||||
}
|
||||
|
||||
fn unmapped_window(&mut self, _xwm: XwmId, window: X11Surface) {
|
||||
let maybe = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == &window))
|
||||
.cloned();
|
||||
if let Some(elem) = maybe {
|
||||
self.space.unmap_elem(&elem)
|
||||
}
|
||||
if !window.is_override_redirect() {
|
||||
window.set_mapped(false).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn destroyed_window(&mut self, _xwm: XwmId, _window: X11Surface) {}
|
||||
|
||||
fn configure_request(
|
||||
&mut self,
|
||||
_xwm: XwmId,
|
||||
window: X11Surface,
|
||||
_x: Option<i32>,
|
||||
_y: Option<i32>,
|
||||
w: Option<u32>,
|
||||
h: Option<u32>,
|
||||
_reorder: Option<Reorder>,
|
||||
) {
|
||||
// we just set the new size, but don't let windows move themselves around freely
|
||||
let mut geo = window.last_configure();
|
||||
if let Some(w) = w {
|
||||
geo.size.w = w as i32;
|
||||
}
|
||||
if let Some(h) = h {
|
||||
geo.size.h = h as i32;
|
||||
}
|
||||
let _ = window.configure(geo);
|
||||
}
|
||||
|
||||
fn configure_notify(
|
||||
&mut self,
|
||||
_xwm: XwmId,
|
||||
window: X11Surface,
|
||||
geometry: Rectangle<i32, Logical>,
|
||||
_above: Option<u32>,
|
||||
) {
|
||||
let Some(elem) = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == &window))
|
||||
.cloned()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
self.space.map_element(elem, geometry.loc, false);
|
||||
// TODO: We don't properly handle the order of override-redirect windows here,
|
||||
// they are always mapped top and then never reordered.
|
||||
}
|
||||
|
||||
fn maximize_request(&mut self, _xwm: XwmId, window: X11Surface) {
|
||||
self.maximize_request_x11(&window);
|
||||
}
|
||||
|
||||
fn unmaximize_request(&mut self, _xwm: XwmId, window: X11Surface) {
|
||||
let Some(elem) = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == &window))
|
||||
.cloned()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
window.set_maximized(false).unwrap();
|
||||
if let Some(old_geo) = window
|
||||
.user_data()
|
||||
.get::<OldGeometry>()
|
||||
.and_then(|data| data.restore())
|
||||
{
|
||||
window.configure(old_geo).unwrap();
|
||||
self.space.map_element(elem, old_geo.loc, false);
|
||||
}
|
||||
}
|
||||
|
||||
fn fullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) {
|
||||
if let Some(elem) = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == &window))
|
||||
{
|
||||
let outputs_for_window = self.space.outputs_for_element(elem);
|
||||
let output = outputs_for_window
|
||||
.first()
|
||||
// The window hasn't been mapped yet, use the primary output instead
|
||||
.or_else(|| self.space.outputs().next())
|
||||
// Assumes that at least one output exists
|
||||
.expect("No outputs found");
|
||||
let geometry = self.space.output_geometry(output).unwrap();
|
||||
|
||||
window.set_fullscreen(true).unwrap();
|
||||
elem.set_ssd(false);
|
||||
window.configure(geometry).unwrap();
|
||||
output.user_data().insert_if_missing(FullscreenSurface::default);
|
||||
output
|
||||
.user_data()
|
||||
.get::<FullscreenSurface>()
|
||||
.unwrap()
|
||||
.set(elem.clone());
|
||||
trace!("Fullscreening: {:?}", elem);
|
||||
}
|
||||
}
|
||||
|
||||
fn unfullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) {
|
||||
if let Some(elem) = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == &window))
|
||||
{
|
||||
window.set_fullscreen(false).unwrap();
|
||||
elem.set_ssd(!window.is_decorated());
|
||||
if let Some(output) = self.space.outputs().find(|o| {
|
||||
o.user_data()
|
||||
.get::<FullscreenSurface>()
|
||||
.and_then(|f| f.get())
|
||||
.map(|w| &w == elem)
|
||||
.unwrap_or(false)
|
||||
}) {
|
||||
trace!("Unfullscreening: {:?}", elem);
|
||||
output.user_data().get::<FullscreenSurface>().unwrap().clear();
|
||||
window.configure(self.space.element_bbox(elem)).unwrap();
|
||||
self.backend_data.reset_buffers(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resize_request(&mut self, _xwm: XwmId, window: X11Surface, _button: u32, edges: X11ResizeEdge) {
|
||||
// luckily anvil only supports one seat anyway...
|
||||
let start_data = self.pointer.grab_start_data().unwrap();
|
||||
|
||||
let Some(element) = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == &window))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let geometry = element.geometry();
|
||||
let loc = self.space.element_location(element).unwrap();
|
||||
let (initial_window_location, initial_window_size) = (loc, geometry.size);
|
||||
|
||||
with_states(&element.wl_surface().unwrap(), move |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<RefCell<SurfaceData>>()
|
||||
.unwrap()
|
||||
.borrow_mut()
|
||||
.resize_state = ResizeState::Resizing(ResizeData {
|
||||
edges: edges.into(),
|
||||
initial_window_location,
|
||||
initial_window_size,
|
||||
});
|
||||
});
|
||||
|
||||
let grab = PointerResizeSurfaceGrab {
|
||||
start_data,
|
||||
window: element.clone(),
|
||||
edges: edges.into(),
|
||||
initial_window_location,
|
||||
initial_window_size,
|
||||
last_window_size: initial_window_size,
|
||||
};
|
||||
|
||||
let pointer = self.pointer.clone();
|
||||
pointer.set_grab(self, grab, SERIAL_COUNTER.next_serial(), Focus::Clear);
|
||||
}
|
||||
|
||||
fn move_request(&mut self, _xwm: XwmId, window: X11Surface, _button: u32) {
|
||||
self.move_request_x11(&window)
|
||||
}
|
||||
|
||||
fn allow_selection_access(&mut self, xwm: XwmId, _selection: SelectionTarget) -> bool {
|
||||
if let Some(keyboard) = self.seat.get_keyboard() {
|
||||
// check that an X11 window is focused
|
||||
if let Some(KeyboardFocusTarget::Window(w)) = keyboard.current_focus() {
|
||||
if let Some(surface) = w.x11_surface() {
|
||||
if surface.xwm_id().unwrap() == xwm {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn send_selection(&mut self, _xwm: XwmId, selection: SelectionTarget, mime_type: String, fd: OwnedFd) {
|
||||
match selection {
|
||||
SelectionTarget::Clipboard => {
|
||||
if let Err(err) = request_data_device_client_selection(&self.seat, mime_type, fd) {
|
||||
error!(?err, "Failed to request current wayland clipboard for Xwayland",);
|
||||
}
|
||||
}
|
||||
SelectionTarget::Primary => {
|
||||
if let Err(err) = request_primary_client_selection(&self.seat, mime_type, fd) {
|
||||
error!(
|
||||
?err,
|
||||
"Failed to request current wayland primary selection for Xwayland",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn new_selection(&mut self, _xwm: XwmId, selection: SelectionTarget, mime_types: Vec<String>) {
|
||||
trace!(?selection, ?mime_types, "Got Selection from X11",);
|
||||
// TODO check, that focused windows is X11 window before doing this
|
||||
match selection {
|
||||
SelectionTarget::Clipboard => {
|
||||
set_data_device_selection(&self.display_handle, &self.seat, mime_types, ())
|
||||
}
|
||||
SelectionTarget::Primary => {
|
||||
set_primary_selection(&self.display_handle, &self.seat, mime_types, ())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cleared_selection(&mut self, _xwm: XwmId, selection: SelectionTarget) {
|
||||
match selection {
|
||||
SelectionTarget::Clipboard => {
|
||||
if current_data_device_selection_userdata(&self.seat).is_some() {
|
||||
clear_data_device_selection(&self.display_handle, &self.seat)
|
||||
}
|
||||
}
|
||||
SelectionTarget::Primary => {
|
||||
if current_primary_selection_userdata(&self.seat).is_some() {
|
||||
clear_primary_selection(&self.display_handle, &self.seat)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn disconnected(&mut self, _xwm: XwmId) {
|
||||
self.xwm = None;
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> AnvilState<BackendData> {
|
||||
pub fn maximize_request_x11(&mut self, window: &X11Surface) {
|
||||
let Some(elem) = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == window))
|
||||
.cloned()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let old_geo = self.space.element_bbox(&elem).unwrap();
|
||||
let outputs_for_window = self.space.outputs_for_element(&elem);
|
||||
let output = outputs_for_window
|
||||
.first()
|
||||
// The window hasn't been mapped yet, use the primary output instead
|
||||
.or_else(|| self.space.outputs().next())
|
||||
// Assumes that at least one output exists
|
||||
.expect("No outputs found");
|
||||
let geometry = self.space.output_geometry(output).unwrap();
|
||||
|
||||
window.set_maximized(true).unwrap();
|
||||
window.configure(geometry).unwrap();
|
||||
window.user_data().insert_if_missing(OldGeometry::default);
|
||||
window.user_data().get::<OldGeometry>().unwrap().save(old_geo);
|
||||
self.space.map_element(elem, geometry.loc, false);
|
||||
}
|
||||
|
||||
pub fn move_request_x11(&mut self, window: &X11Surface) {
|
||||
if let Some(touch) = self.seat.get_touch() {
|
||||
if let Some(start_data) = touch.grab_start_data() {
|
||||
let element = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == window));
|
||||
|
||||
if let Some(element) = element {
|
||||
let mut initial_window_location = self.space.element_location(element).unwrap();
|
||||
|
||||
// If surface is maximized then unmaximize it
|
||||
if window.is_maximized() {
|
||||
window.set_maximized(false).unwrap();
|
||||
let pos = start_data.location;
|
||||
initial_window_location = (pos.x as i32, pos.y as i32).into();
|
||||
if let Some(old_geo) = window
|
||||
.user_data()
|
||||
.get::<OldGeometry>()
|
||||
.and_then(|data| data.restore())
|
||||
{
|
||||
window
|
||||
.configure(Rectangle::new(initial_window_location, old_geo.size))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let grab = TouchMoveSurfaceGrab {
|
||||
start_data,
|
||||
window: element.clone(),
|
||||
initial_window_location,
|
||||
};
|
||||
|
||||
touch.set_grab(self, grab, SERIAL_COUNTER.next_serial());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// luckily anvil only supports one seat anyway...
|
||||
let Some(start_data) = self.pointer.grab_start_data() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(element) = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == window))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut initial_window_location = self.space.element_location(element).unwrap();
|
||||
|
||||
// If surface is maximized then unmaximize it
|
||||
if window.is_maximized() {
|
||||
window.set_maximized(false).unwrap();
|
||||
let pos = self.pointer.current_location();
|
||||
initial_window_location = (pos.x as i32, pos.y as i32).into();
|
||||
if let Some(old_geo) = window
|
||||
.user_data()
|
||||
.get::<OldGeometry>()
|
||||
.and_then(|data| data.restore())
|
||||
{
|
||||
window
|
||||
.configure(Rectangle::new(initial_window_location, old_geo.size))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let grab = PointerMoveSurfaceGrab {
|
||||
start_data,
|
||||
window: element.clone(),
|
||||
initial_window_location,
|
||||
};
|
||||
|
||||
let pointer = self.pointer.clone();
|
||||
pointer.set_grab(self, grab, SERIAL_COUNTER.next_serial(), Focus::Clear);
|
||||
}
|
||||
}
|
||||
+641
@@ -0,0 +1,641 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use smithay::{
|
||||
desktop::{
|
||||
PopupKeyboardGrab, PopupKind, PopupPointerGrab, PopupUngrabStrategy, Space, Window,
|
||||
WindowSurfaceType, find_popup_root_surface, get_popup_toplevel_coords, layer_map_for_output,
|
||||
space::SpaceElement,
|
||||
},
|
||||
input::{Seat, pointer::Focus},
|
||||
output::Output,
|
||||
reexports::{
|
||||
wayland_protocols::xdg::{decoration as xdg_decoration, shell::server::xdg_toplevel},
|
||||
wayland_server::{
|
||||
Resource,
|
||||
protocol::{wl_output, wl_seat, wl_surface::WlSurface},
|
||||
},
|
||||
},
|
||||
utils::{Logical, Point, Serial},
|
||||
wayland::{
|
||||
compositor::{self, with_states},
|
||||
seat::WaylandFocus,
|
||||
shell::xdg::{
|
||||
Configure, PopupSurface, PositionerState, ToplevelCachedState, ToplevelSurface, XdgShellHandler,
|
||||
XdgShellState,
|
||||
},
|
||||
},
|
||||
};
|
||||
use tracing::{trace, warn};
|
||||
|
||||
use crate::{
|
||||
focus::KeyboardFocusTarget,
|
||||
shell::{TouchMoveSurfaceGrab, TouchResizeSurfaceGrab},
|
||||
state::{AnvilState, Backend},
|
||||
};
|
||||
|
||||
use super::{
|
||||
FullscreenSurface, PointerMoveSurfaceGrab, PointerResizeSurfaceGrab, ResizeData, ResizeEdge, ResizeState,
|
||||
SurfaceData, WindowElement, fullscreen_output_geometry, place_new_window,
|
||||
};
|
||||
|
||||
impl<BackendData: Backend> XdgShellHandler for AnvilState<BackendData> {
|
||||
fn xdg_shell_state(&mut self) -> &mut XdgShellState {
|
||||
&mut self.xdg_shell_state
|
||||
}
|
||||
|
||||
fn new_toplevel(&mut self, surface: ToplevelSurface) {
|
||||
// Do not send a configure here, the initial configure
|
||||
// of a xdg_surface has to be sent during the commit if
|
||||
// the surface is not already configured
|
||||
let window = WindowElement(Window::new_wayland_window(surface.clone()));
|
||||
place_new_window(&mut self.space, self.pointer.current_location(), &window, true);
|
||||
|
||||
compositor::add_post_commit_hook(surface.wl_surface(), |state: &mut Self, _, surface| {
|
||||
handle_toplevel_commit(&mut state.space, surface);
|
||||
});
|
||||
}
|
||||
|
||||
fn new_popup(&mut self, surface: PopupSurface, _positioner: PositionerState) {
|
||||
// Do not send a configure here, the initial configure
|
||||
// of a xdg_surface has to be sent during the commit if
|
||||
// the surface is not already configured
|
||||
|
||||
self.unconstrain_popup(&surface);
|
||||
|
||||
if let Err(err) = self.popups.track_popup(PopupKind::from(surface)) {
|
||||
warn!("Failed to track popup: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
fn reposition_request(&mut self, surface: PopupSurface, positioner: PositionerState, token: u32) {
|
||||
surface.with_pending_state(|state| {
|
||||
let geometry = positioner.get_geometry();
|
||||
state.geometry = geometry;
|
||||
state.positioner = positioner;
|
||||
});
|
||||
self.unconstrain_popup(&surface);
|
||||
surface.send_repositioned(token);
|
||||
}
|
||||
|
||||
fn move_request(&mut self, surface: ToplevelSurface, seat: wl_seat::WlSeat, serial: Serial) {
|
||||
let seat: Seat<AnvilState<BackendData>> = Seat::from_resource(&seat).unwrap();
|
||||
self.move_request_xdg(&surface, &seat, serial)
|
||||
}
|
||||
|
||||
fn resize_request(
|
||||
&mut self,
|
||||
surface: ToplevelSurface,
|
||||
seat: wl_seat::WlSeat,
|
||||
serial: Serial,
|
||||
edges: xdg_toplevel::ResizeEdge,
|
||||
) {
|
||||
let seat: Seat<AnvilState<BackendData>> = Seat::from_resource(&seat).unwrap();
|
||||
|
||||
if let Some(touch) = seat.get_touch() {
|
||||
if touch.has_grab(serial) {
|
||||
let start_data = touch.grab_start_data().unwrap();
|
||||
tracing::info!(?start_data);
|
||||
|
||||
// If the client disconnects after requesting a move
|
||||
// we can just ignore the request
|
||||
let Some(window) = self.window_for_surface(surface.wl_surface()) else {
|
||||
tracing::info!("no window");
|
||||
return;
|
||||
};
|
||||
|
||||
// If the focus was for a different surface, ignore the request.
|
||||
if start_data.focus.is_none()
|
||||
|| !start_data
|
||||
.focus
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.0
|
||||
.same_client_as(&surface.wl_surface().id())
|
||||
{
|
||||
tracing::info!("different surface");
|
||||
return;
|
||||
}
|
||||
let geometry = window.geometry();
|
||||
let loc = self.space.element_location(&window).unwrap();
|
||||
let (initial_window_location, initial_window_size) = (loc, geometry.size);
|
||||
|
||||
with_states(surface.wl_surface(), move |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<RefCell<SurfaceData>>()
|
||||
.unwrap()
|
||||
.borrow_mut()
|
||||
.resize_state = ResizeState::Resizing(ResizeData {
|
||||
edges: edges.into(),
|
||||
initial_window_location,
|
||||
initial_window_size,
|
||||
});
|
||||
});
|
||||
|
||||
let grab = TouchResizeSurfaceGrab {
|
||||
start_data,
|
||||
window,
|
||||
edges: edges.into(),
|
||||
initial_window_location,
|
||||
initial_window_size,
|
||||
last_window_size: initial_window_size,
|
||||
};
|
||||
|
||||
touch.set_grab(self, grab, serial);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let pointer = seat.get_pointer().unwrap();
|
||||
|
||||
// Check that this surface has a click grab.
|
||||
if !pointer.has_grab(serial) {
|
||||
return;
|
||||
}
|
||||
|
||||
let start_data = pointer.grab_start_data().unwrap();
|
||||
|
||||
let window = self.window_for_surface(surface.wl_surface()).unwrap();
|
||||
|
||||
// If the focus was for a different surface, ignore the request.
|
||||
if start_data.focus.is_none()
|
||||
|| !start_data
|
||||
.focus
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.0
|
||||
.same_client_as(&surface.wl_surface().id())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let geometry = window.geometry();
|
||||
let loc = self.space.element_location(&window).unwrap();
|
||||
let (initial_window_location, initial_window_size) = (loc, geometry.size);
|
||||
|
||||
with_states(surface.wl_surface(), move |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<RefCell<SurfaceData>>()
|
||||
.unwrap()
|
||||
.borrow_mut()
|
||||
.resize_state = ResizeState::Resizing(ResizeData {
|
||||
edges: edges.into(),
|
||||
initial_window_location,
|
||||
initial_window_size,
|
||||
});
|
||||
});
|
||||
|
||||
let grab = PointerResizeSurfaceGrab {
|
||||
start_data,
|
||||
window,
|
||||
edges: edges.into(),
|
||||
initial_window_location,
|
||||
initial_window_size,
|
||||
last_window_size: initial_window_size,
|
||||
};
|
||||
|
||||
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||
}
|
||||
|
||||
fn ack_configure(&mut self, surface: WlSurface, configure: Configure) {
|
||||
if let Configure::Toplevel(configure) = configure {
|
||||
if let Some(serial) = with_states(&surface, |states| {
|
||||
if let Some(data) = states.data_map.get::<RefCell<SurfaceData>>() {
|
||||
if let ResizeState::WaitingForFinalAck(_, serial) = data.borrow().resize_state {
|
||||
return Some(serial);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}) {
|
||||
// When the resize grab is released the surface
|
||||
// resize state will be set to WaitingForFinalAck
|
||||
// and the client will receive a configure request
|
||||
// without the resize state to inform the client
|
||||
// resizing has finished. Here we will wait for
|
||||
// the client to acknowledge the end of the
|
||||
// resizing. To check if the surface was resizing
|
||||
// before sending the configure we need to use
|
||||
// the current state as the received acknowledge
|
||||
// will no longer have the resize state set
|
||||
let is_resizing = with_states(&surface, |states| {
|
||||
states
|
||||
.cached_state
|
||||
.get::<ToplevelCachedState>()
|
||||
.current()
|
||||
.last_acked
|
||||
.as_ref()
|
||||
.is_some_and(|c| c.state.states.contains(xdg_toplevel::State::Resizing))
|
||||
});
|
||||
|
||||
if configure.serial >= serial && is_resizing {
|
||||
with_states(&surface, |states| {
|
||||
let mut data = states
|
||||
.data_map
|
||||
.get::<RefCell<SurfaceData>>()
|
||||
.unwrap()
|
||||
.borrow_mut();
|
||||
if let ResizeState::WaitingForFinalAck(resize_data, _) = data.resize_state {
|
||||
data.resize_state = ResizeState::WaitingForCommit(resize_data);
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let window = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|element| element.wl_surface().as_deref() == Some(&surface));
|
||||
if let Some(window) = window {
|
||||
use xdg_decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode;
|
||||
let is_ssd = configure
|
||||
.state
|
||||
.decoration_mode
|
||||
.map(|mode| mode == Mode::ServerSide)
|
||||
.unwrap_or(false);
|
||||
window.set_ssd(is_ssd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fullscreen_request(&mut self, surface: ToplevelSurface, mut wl_output: Option<wl_output::WlOutput>) {
|
||||
// NOTE: This is only one part of the solution. We can set the
|
||||
// location and configure size here, but the surface should be rendered fullscreen
|
||||
// independently from its buffer size
|
||||
let wl_surface = surface.wl_surface();
|
||||
|
||||
let output_geometry = fullscreen_output_geometry(wl_surface, wl_output.as_ref(), &mut self.space);
|
||||
|
||||
if let Some(geometry) = output_geometry {
|
||||
let output = wl_output
|
||||
.as_ref()
|
||||
.and_then(Output::from_resource)
|
||||
.unwrap_or_else(|| self.space.outputs().next().unwrap().clone());
|
||||
let client = match self.display_handle.get_client(wl_surface.id()) {
|
||||
Ok(client) => client,
|
||||
Err(_) => return,
|
||||
};
|
||||
for output in output.client_outputs(&client) {
|
||||
wl_output = Some(output);
|
||||
}
|
||||
let window = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|window| window.wl_surface().map(|s| &*s == wl_surface).unwrap_or(false))
|
||||
.unwrap();
|
||||
|
||||
surface.with_pending_state(|state| {
|
||||
state.states.set(xdg_toplevel::State::Fullscreen);
|
||||
state.size = Some(geometry.size);
|
||||
state.fullscreen_output = wl_output;
|
||||
});
|
||||
output.user_data().insert_if_missing(FullscreenSurface::default);
|
||||
output
|
||||
.user_data()
|
||||
.get::<FullscreenSurface>()
|
||||
.unwrap()
|
||||
.set(window.clone());
|
||||
trace!("Fullscreening: {:?}", window);
|
||||
}
|
||||
|
||||
// The protocol demands us to always reply with a configure,
|
||||
// regardless of we fulfilled the request or not
|
||||
if surface.is_initial_configure_sent() {
|
||||
surface.send_configure();
|
||||
} else {
|
||||
// Will be sent during initial configure
|
||||
}
|
||||
}
|
||||
|
||||
fn unfullscreen_request(&mut self, surface: ToplevelSurface) {
|
||||
let ret = surface.with_pending_state(|state| {
|
||||
state.states.unset(xdg_toplevel::State::Fullscreen);
|
||||
state.size = None;
|
||||
state.fullscreen_output.take()
|
||||
});
|
||||
if let Some(output) = ret {
|
||||
let output = Output::from_resource(&output).unwrap();
|
||||
if let Some(fullscreen) = output.user_data().get::<FullscreenSurface>() {
|
||||
trace!("Unfullscreening: {:?}", fullscreen.get());
|
||||
fullscreen.clear();
|
||||
self.backend_data.reset_buffers(&output);
|
||||
}
|
||||
}
|
||||
|
||||
// The protocol demands us to always reply with a configure,
|
||||
// regardless of we fulfilled the request or not
|
||||
if surface.is_initial_configure_sent() {
|
||||
surface.send_configure();
|
||||
} else {
|
||||
// Will be sent during initial configure
|
||||
}
|
||||
}
|
||||
|
||||
fn maximize_request(&mut self, surface: ToplevelSurface) {
|
||||
// NOTE: This should use layer-shell when it is implemented to
|
||||
// get the correct maximum size
|
||||
let window = self.window_for_surface(surface.wl_surface()).unwrap();
|
||||
let outputs_for_window = self.space.outputs_for_element(&window);
|
||||
let output = outputs_for_window
|
||||
.first()
|
||||
// The window hasn't been mapped yet, use the primary output instead
|
||||
.or_else(|| self.space.outputs().next())
|
||||
// Assumes that at least one output exists
|
||||
.expect("No outputs found");
|
||||
let geometry = self.space.output_geometry(output).unwrap();
|
||||
|
||||
surface.with_pending_state(|state| {
|
||||
state.states.set(xdg_toplevel::State::Maximized);
|
||||
state.size = Some(geometry.size);
|
||||
});
|
||||
self.space.map_element(window, geometry.loc, true);
|
||||
|
||||
// The protocol demands us to always reply with a configure,
|
||||
// regardless of we fulfilled the request or not
|
||||
if surface.is_initial_configure_sent() {
|
||||
surface.send_configure();
|
||||
} else {
|
||||
// Will be sent during initial configure
|
||||
}
|
||||
}
|
||||
|
||||
fn unmaximize_request(&mut self, surface: ToplevelSurface) {
|
||||
surface.with_pending_state(|state| {
|
||||
state.states.unset(xdg_toplevel::State::Maximized);
|
||||
state.size = None;
|
||||
});
|
||||
|
||||
// The protocol demands us to always reply with a configure,
|
||||
// regardless of we fulfilled the request or not
|
||||
if surface.is_initial_configure_sent() {
|
||||
surface.send_configure();
|
||||
} else {
|
||||
// Will be sent during initial configure
|
||||
}
|
||||
}
|
||||
|
||||
fn grab(&mut self, surface: PopupSurface, seat: wl_seat::WlSeat, serial: Serial) {
|
||||
let seat: Seat<AnvilState<BackendData>> = Seat::from_resource(&seat).unwrap();
|
||||
let kind = PopupKind::Xdg(surface);
|
||||
if let Some(root) = find_popup_root_surface(&kind).ok().and_then(|root| {
|
||||
self.space
|
||||
.elements()
|
||||
.find(|w| w.wl_surface().map(|s| *s == root).unwrap_or(false))
|
||||
.cloned()
|
||||
.map(KeyboardFocusTarget::from)
|
||||
.or_else(|| {
|
||||
self.space
|
||||
.outputs()
|
||||
.find_map(|o| {
|
||||
let map = layer_map_for_output(o);
|
||||
map.layer_for_surface(&root, WindowSurfaceType::TOPLEVEL).cloned()
|
||||
})
|
||||
.map(KeyboardFocusTarget::LayerSurface)
|
||||
})
|
||||
}) {
|
||||
let ret = self.popups.grab_popup(root, kind, &seat, serial);
|
||||
|
||||
if let Ok(mut grab) = ret {
|
||||
if let Some(keyboard) = seat.get_keyboard() {
|
||||
if keyboard.is_grabbed()
|
||||
&& !(keyboard.has_grab(serial)
|
||||
|| keyboard.has_grab(grab.previous_serial().unwrap_or(serial)))
|
||||
{
|
||||
grab.ungrab(PopupUngrabStrategy::All);
|
||||
return;
|
||||
}
|
||||
keyboard.set_focus(self, grab.current_grab(), serial);
|
||||
keyboard.set_grab(self, PopupKeyboardGrab::new(&grab), serial);
|
||||
}
|
||||
if let Some(pointer) = seat.get_pointer() {
|
||||
if pointer.is_grabbed()
|
||||
&& !(pointer.has_grab(serial)
|
||||
|| pointer.has_grab(grab.previous_serial().unwrap_or_else(|| grab.serial())))
|
||||
{
|
||||
grab.ungrab(PopupUngrabStrategy::All);
|
||||
return;
|
||||
}
|
||||
pointer.set_grab(self, PopupPointerGrab::new(&grab), serial, Focus::Keep);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> AnvilState<BackendData> {
|
||||
pub fn move_request_xdg(&mut self, surface: &ToplevelSurface, seat: &Seat<Self>, serial: Serial) {
|
||||
if let Some(touch) = seat.get_touch() {
|
||||
if touch.has_grab(serial) {
|
||||
let start_data = touch.grab_start_data().unwrap();
|
||||
|
||||
// If the client disconnects after requesting a move
|
||||
// we can just ignore the request
|
||||
let Some(window) = self.window_for_surface(surface.wl_surface()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// If the focus was for a different surface, ignore the request.
|
||||
if start_data.focus.is_none()
|
||||
|| !start_data
|
||||
.focus
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.0
|
||||
.same_client_as(&surface.wl_surface().id())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let mut initial_window_location = self.space.element_location(&window).unwrap();
|
||||
|
||||
// If surface is maximized then unmaximize it
|
||||
let changed = surface.with_pending_state(|state| {
|
||||
if state.states.unset(xdg_toplevel::State::Maximized) {
|
||||
state.size = None;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
if changed {
|
||||
surface.send_configure();
|
||||
|
||||
// NOTE: In real compositor mouse location should be mapped to a new window size
|
||||
// For example, you could:
|
||||
// 1) transform mouse pointer position from compositor space to window space (location relative)
|
||||
// 2) divide the x coordinate by width of the window to get the percentage
|
||||
// - 0.0 would be on the far left of the window
|
||||
// - 0.5 would be in middle of the window
|
||||
// - 1.0 would be on the far right of the window
|
||||
// 3) multiply the percentage by new window width
|
||||
// 4) by doing that, drag will look a lot more natural
|
||||
//
|
||||
// but for anvil needs setting location to pointer location is fine
|
||||
initial_window_location = start_data.location.to_i32_round();
|
||||
}
|
||||
|
||||
let grab = TouchMoveSurfaceGrab {
|
||||
start_data,
|
||||
window,
|
||||
initial_window_location,
|
||||
};
|
||||
|
||||
touch.set_grab(self, grab, serial);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let pointer = seat.get_pointer().unwrap();
|
||||
|
||||
// Check that this surface has a click grab.
|
||||
if !pointer.has_grab(serial) {
|
||||
return;
|
||||
}
|
||||
|
||||
let start_data = pointer.grab_start_data().unwrap();
|
||||
|
||||
// If the client disconnects after requesting a move
|
||||
// we can just ignore the request
|
||||
let Some(window) = self.window_for_surface(surface.wl_surface()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// If the focus was for a different surface, ignore the request.
|
||||
if start_data.focus.is_none()
|
||||
|| !start_data
|
||||
.focus
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.0
|
||||
.same_client_as(&surface.wl_surface().id())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let mut initial_window_location = self.space.element_location(&window).unwrap();
|
||||
|
||||
// If surface is maximized then unmaximize it
|
||||
let changed = surface.with_pending_state(|state| {
|
||||
if state.states.unset(xdg_toplevel::State::Maximized) {
|
||||
state.size = None;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
if changed {
|
||||
surface.send_configure();
|
||||
|
||||
// NOTE: In real compositor mouse location should be mapped to a new window size
|
||||
// For example, you could:
|
||||
// 1) transform mouse pointer position from compositor space to window space (location relative)
|
||||
// 2) divide the x coordinate by width of the window to get the percentage
|
||||
// - 0.0 would be on the far left of the window
|
||||
// - 0.5 would be in middle of the window
|
||||
// - 1.0 would be on the far right of the window
|
||||
// 3) multiply the percentage by new window width
|
||||
// 4) by doing that, drag will look a lot more natural
|
||||
//
|
||||
// but for anvil needs setting location to pointer location is fine
|
||||
let pos = pointer.current_location();
|
||||
initial_window_location = (pos.x as i32, pos.y as i32).into();
|
||||
}
|
||||
|
||||
let grab = PointerMoveSurfaceGrab {
|
||||
start_data,
|
||||
window,
|
||||
initial_window_location,
|
||||
};
|
||||
|
||||
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||
}
|
||||
|
||||
fn unconstrain_popup(&self, popup: &PopupSurface) {
|
||||
let Ok(root) = find_popup_root_surface(&PopupKind::Xdg(popup.clone())) else {
|
||||
return;
|
||||
};
|
||||
let Some(window) = self.window_for_surface(&root) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut outputs_for_window = self.space.outputs_for_element(&window);
|
||||
if outputs_for_window.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get a union of all outputs' geometries.
|
||||
let mut outputs_geo = self
|
||||
.space
|
||||
.output_geometry(&outputs_for_window.pop().unwrap())
|
||||
.unwrap();
|
||||
for output in outputs_for_window {
|
||||
outputs_geo = outputs_geo.merge(self.space.output_geometry(&output).unwrap());
|
||||
}
|
||||
|
||||
let window_geo = self.space.element_geometry(&window).unwrap();
|
||||
|
||||
// The target geometry for the positioner should be relative to its parent's geometry, so
|
||||
// we will compute that here.
|
||||
let mut target = outputs_geo;
|
||||
target.loc -= get_popup_toplevel_coords(&PopupKind::Xdg(popup.clone()));
|
||||
target.loc -= window_geo.loc;
|
||||
|
||||
popup.with_pending_state(|state| {
|
||||
state.geometry = state.positioner.get_unconstrained_geometry(target);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Should be called on `WlSurface::commit` of xdg toplevel
|
||||
fn handle_toplevel_commit(space: &mut Space<WindowElement>, surface: &WlSurface) -> Option<()> {
|
||||
let window = space
|
||||
.elements()
|
||||
.find(|w| w.wl_surface().as_deref() == Some(surface))
|
||||
.cloned()?;
|
||||
|
||||
let mut window_loc = space.element_location(&window)?;
|
||||
let geometry = window.geometry();
|
||||
|
||||
let new_loc: Point<Option<i32>, Logical> = with_states(window.wl_surface().as_deref()?, |states| {
|
||||
let data = states.data_map.get::<RefCell<SurfaceData>>()?.borrow_mut();
|
||||
|
||||
if let ResizeState::Resizing(resize_data) = data.resize_state {
|
||||
let edges = resize_data.edges;
|
||||
let loc = resize_data.initial_window_location;
|
||||
let size = resize_data.initial_window_size;
|
||||
|
||||
// If the window is being resized by top or left, its location must be adjusted
|
||||
// accordingly.
|
||||
edges.intersects(ResizeEdge::TOP_LEFT).then(|| {
|
||||
let new_x = edges
|
||||
.intersects(ResizeEdge::LEFT)
|
||||
.then_some(loc.x + (size.w - geometry.size.w));
|
||||
|
||||
let new_y = edges
|
||||
.intersects(ResizeEdge::TOP)
|
||||
.then_some(loc.y + (size.h - geometry.size.h));
|
||||
|
||||
(new_x, new_y).into()
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})?;
|
||||
|
||||
if let Some(new_x) = new_loc.x {
|
||||
window_loc.x = new_x;
|
||||
}
|
||||
if let Some(new_y) = new_loc.y {
|
||||
window_loc.y = new_y;
|
||||
}
|
||||
|
||||
if new_loc.x.is_some() || new_loc.y.is_some() {
|
||||
// If TOP or LEFT side of the window got resized, we have to move it
|
||||
space.map_element(window, window_loc, false);
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
Vendored
+1215
File diff suppressed because it is too large.
Load diff
Vendored
+1688
File diff suppressed because it is too large.
Load diff
Vendored
+463
@@ -0,0 +1,463 @@
|
||||
use std::{
|
||||
sync::{Mutex, atomic::Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[cfg(feature = "egl")]
|
||||
use smithay::backend::renderer::ImportEgl;
|
||||
#[cfg(feature = "debug")]
|
||||
use smithay::{
|
||||
backend::{allocator::Fourcc, renderer::ImportMem},
|
||||
reexports::winit::raw_window_handle::{HasWindowHandle, RawWindowHandle},
|
||||
};
|
||||
|
||||
use smithay::{
|
||||
backend::{
|
||||
SwapBuffersError,
|
||||
allocator::dmabuf::Dmabuf,
|
||||
egl::EGLDevice,
|
||||
renderer::{
|
||||
ImportDma, ImportMemWl,
|
||||
damage::{Error as OutputDamageTrackerError, OutputDamageTracker},
|
||||
element::AsRenderElements,
|
||||
gles::GlesRenderer,
|
||||
},
|
||||
winit::{self, WinitEvent, WinitGraphicsBackend},
|
||||
},
|
||||
input::{
|
||||
keyboard::LedState,
|
||||
pointer::{CursorImageAttributes, CursorImageStatus},
|
||||
},
|
||||
output::{Mode, Output, PhysicalProperties, Subpixel},
|
||||
reexports::{
|
||||
calloop::EventLoop,
|
||||
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback,
|
||||
wayland_server::{Display, protocol::wl_surface},
|
||||
winit::event_loop::pump_events::PumpStatus,
|
||||
},
|
||||
utils::{IsAlive, Scale, Transform},
|
||||
wayland::{
|
||||
compositor,
|
||||
dmabuf::{
|
||||
DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier,
|
||||
},
|
||||
presentation::Refresh,
|
||||
},
|
||||
};
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use crate::state::{AnvilState, Backend, take_presentation_feedback, update_primary_scanout_output};
|
||||
use crate::{drawing::*, render::*};
|
||||
|
||||
pub const OUTPUT_NAME: &str = "winit";
|
||||
|
||||
pub struct WinitData {
|
||||
backend: WinitGraphicsBackend<GlesRenderer>,
|
||||
damage_tracker: OutputDamageTracker,
|
||||
dmabuf_state: (DmabufState, DmabufGlobal, Option<DmabufFeedback>),
|
||||
full_redraw: u8,
|
||||
#[cfg(feature = "debug")]
|
||||
pub fps: fps_ticker::Fps,
|
||||
}
|
||||
|
||||
impl DmabufHandler for AnvilState<WinitData> {
|
||||
fn dmabuf_state(&mut self) -> &mut DmabufState {
|
||||
&mut self.backend_data.dmabuf_state.0
|
||||
}
|
||||
|
||||
fn dmabuf_imported(&mut self, _global: &DmabufGlobal, dmabuf: Dmabuf, notifier: ImportNotifier) {
|
||||
if self
|
||||
.backend_data
|
||||
.backend
|
||||
.renderer()
|
||||
.import_dmabuf(&dmabuf, None)
|
||||
.is_ok()
|
||||
{
|
||||
let _ = notifier.successful::<AnvilState<WinitData>>();
|
||||
} else {
|
||||
notifier.failed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Backend for WinitData {
|
||||
const HAS_GESTURES: bool = true;
|
||||
|
||||
fn seat_name(&self) -> String {
|
||||
String::from("winit")
|
||||
}
|
||||
fn reset_buffers(&mut self, _output: &Output) {
|
||||
self.full_redraw = 4;
|
||||
}
|
||||
fn early_import(&mut self, _surface: &wl_surface::WlSurface) {}
|
||||
fn update_led_state(&mut self, _led_state: LedState) {}
|
||||
}
|
||||
|
||||
pub fn run_winit() {
|
||||
let mut event_loop = EventLoop::try_new().unwrap();
|
||||
let display = Display::new().unwrap();
|
||||
let mut display_handle = display.handle();
|
||||
|
||||
#[cfg_attr(not(feature = "egl"), allow(unused_mut))]
|
||||
let (mut backend, mut winit) = match winit::init::<GlesRenderer>() {
|
||||
Ok(ret) => ret,
|
||||
Err(err) => {
|
||||
error!("Failed to initialize Winit backend: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let size = backend.window_size();
|
||||
|
||||
let mode = Mode {
|
||||
size,
|
||||
refresh: 60_000,
|
||||
};
|
||||
let output = Output::new(
|
||||
OUTPUT_NAME.to_string(),
|
||||
PhysicalProperties {
|
||||
size: (0, 0).into(),
|
||||
subpixel: Subpixel::Unknown,
|
||||
make: "Smithay".into(),
|
||||
model: "Winit".into(),
|
||||
serial_number: "Unknown".into(),
|
||||
},
|
||||
);
|
||||
let _global = output.create_global::<AnvilState<WinitData>>(&display.handle());
|
||||
output.change_current_state(Some(mode), Some(Transform::Flipped180), None, Some((0, 0).into()));
|
||||
output.set_preferred(mode);
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
#[allow(deprecated)]
|
||||
let fps_image =
|
||||
image::io::Reader::with_format(std::io::Cursor::new(FPS_NUMBERS_PNG), image::ImageFormat::Png)
|
||||
.decode()
|
||||
.unwrap();
|
||||
#[cfg(feature = "debug")]
|
||||
let fps_texture = backend
|
||||
.renderer()
|
||||
.import_memory(
|
||||
&fps_image.to_rgba8(),
|
||||
Fourcc::Abgr8888,
|
||||
(fps_image.width() as i32, fps_image.height() as i32).into(),
|
||||
false,
|
||||
)
|
||||
.expect("Unable to upload FPS texture");
|
||||
#[cfg(feature = "debug")]
|
||||
let mut fps_element = FpsElement::new(fps_texture);
|
||||
|
||||
let render_node = EGLDevice::device_for_display(backend.renderer().egl_context().display())
|
||||
.and_then(|device| device.try_get_render_node());
|
||||
|
||||
let dmabuf_default_feedback = match render_node {
|
||||
Ok(Some(node)) => {
|
||||
let dmabuf_formats = backend.renderer().dmabuf_formats();
|
||||
let dmabuf_default_feedback = DmabufFeedbackBuilder::new(node.dev_id(), dmabuf_formats)
|
||||
.build()
|
||||
.unwrap();
|
||||
Some(dmabuf_default_feedback)
|
||||
}
|
||||
Ok(None) => {
|
||||
warn!("failed to query render node, dmabuf will use v3");
|
||||
None
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(?err, "failed to egl device for display, dmabuf will use v3");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
// if we failed to build dmabuf feedback we fall back to dmabuf v3
|
||||
// Note: egl on Mesa requires either v4 or wl_drm (initialized with bind_wl_display)
|
||||
let dmabuf_state = if let Some(default_feedback) = dmabuf_default_feedback {
|
||||
let mut dmabuf_state = DmabufState::new();
|
||||
let dmabuf_global = dmabuf_state.create_global_with_default_feedback::<AnvilState<WinitData>>(
|
||||
&display.handle(),
|
||||
&default_feedback,
|
||||
);
|
||||
(dmabuf_state, dmabuf_global, Some(default_feedback))
|
||||
} else {
|
||||
let dmabuf_formats = backend.renderer().dmabuf_formats();
|
||||
let mut dmabuf_state = DmabufState::new();
|
||||
let dmabuf_global =
|
||||
dmabuf_state.create_global::<AnvilState<WinitData>>(&display.handle(), dmabuf_formats);
|
||||
(dmabuf_state, dmabuf_global, None)
|
||||
};
|
||||
|
||||
#[cfg(feature = "egl")]
|
||||
if backend.renderer().bind_wl_display(&display.handle()).is_ok() {
|
||||
info!("EGL hardware-acceleration enabled");
|
||||
};
|
||||
|
||||
let data = {
|
||||
let damage_tracker = OutputDamageTracker::from_output(&output);
|
||||
|
||||
WinitData {
|
||||
backend,
|
||||
damage_tracker,
|
||||
dmabuf_state,
|
||||
full_redraw: 0,
|
||||
#[cfg(feature = "debug")]
|
||||
fps: fps_ticker::Fps::default(),
|
||||
}
|
||||
};
|
||||
let mut state = AnvilState::init(display, event_loop.handle(), data, true);
|
||||
state
|
||||
.shm_state
|
||||
.update_formats(state.backend_data.backend.renderer().shm_formats());
|
||||
state.space.map_output(&output, (0, 0));
|
||||
|
||||
#[cfg(feature = "xwayland")]
|
||||
state.start_xwayland();
|
||||
|
||||
info!("Initialization completed, starting the main loop.");
|
||||
|
||||
let mut pointer_element = PointerElement::default();
|
||||
|
||||
while state.running.load(Ordering::SeqCst) {
|
||||
let status = winit.dispatch_new_events(|event| match event {
|
||||
WinitEvent::Resized { size, .. } => {
|
||||
// We only have one output
|
||||
let output = state.space.outputs().next().unwrap().clone();
|
||||
state.space.map_output(&output, (0, 0));
|
||||
let mode = Mode {
|
||||
size,
|
||||
refresh: 60_000,
|
||||
};
|
||||
output.change_current_state(Some(mode), None, None, None);
|
||||
output.set_preferred(mode);
|
||||
crate::shell::fixup_positions(&mut state.space, state.pointer.current_location());
|
||||
}
|
||||
WinitEvent::Input(event) => state.process_input_event_windowed(event, OUTPUT_NAME),
|
||||
_ => (),
|
||||
});
|
||||
|
||||
if let PumpStatus::Exit(_) = status {
|
||||
state.running.store(false, Ordering::SeqCst);
|
||||
break;
|
||||
}
|
||||
|
||||
// drawing logic
|
||||
{
|
||||
let now = state.clock.now();
|
||||
let frame_target = now
|
||||
+ output
|
||||
.current_mode()
|
||||
.map(|mode| Duration::from_secs_f64(1_000f64 / mode.refresh as f64))
|
||||
.unwrap_or_default();
|
||||
state.pre_repaint(&output, frame_target);
|
||||
|
||||
let backend = &mut state.backend_data.backend;
|
||||
|
||||
// draw the cursor as relevant
|
||||
// reset the cursor if the surface is no longer alive
|
||||
let mut reset = false;
|
||||
if let CursorImageStatus::Surface(ref surface) = state.cursor_status {
|
||||
reset = !surface.alive();
|
||||
}
|
||||
if reset {
|
||||
state.cursor_status = CursorImageStatus::default_named();
|
||||
}
|
||||
let cursor_visible = !matches!(state.cursor_status, CursorImageStatus::Surface(_));
|
||||
|
||||
pointer_element.set_status(state.cursor_status.clone());
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
let fps = state.backend_data.fps.avg().round() as u32;
|
||||
#[cfg(feature = "debug")]
|
||||
fps_element.update_fps(fps);
|
||||
|
||||
let full_redraw = &mut state.backend_data.full_redraw;
|
||||
*full_redraw = full_redraw.saturating_sub(1);
|
||||
let space = &mut state.space;
|
||||
let damage_tracker = &mut state.backend_data.damage_tracker;
|
||||
let show_window_preview = state.show_window_preview;
|
||||
|
||||
let dnd_icon = state.dnd_icon.as_ref();
|
||||
|
||||
let scale = Scale::from(output.current_scale().fractional_scale());
|
||||
let cursor_hotspot = if let CursorImageStatus::Surface(ref surface) = state.cursor_status {
|
||||
compositor::with_states(surface, |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<Mutex<CursorImageAttributes>>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.hotspot
|
||||
})
|
||||
} else {
|
||||
(0, 0).into()
|
||||
};
|
||||
let cursor_pos = state.pointer.current_location();
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
let mut renderdoc = state.renderdoc.as_mut();
|
||||
|
||||
let age = if *full_redraw > 0 {
|
||||
0
|
||||
} else {
|
||||
backend.buffer_age().unwrap_or(0)
|
||||
};
|
||||
#[cfg(feature = "debug")]
|
||||
let window_handle = backend
|
||||
.window()
|
||||
.window_handle()
|
||||
.map(|handle| {
|
||||
if let RawWindowHandle::Wayland(handle) = handle.as_raw() {
|
||||
handle.surface.as_ptr()
|
||||
} else {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|_| std::ptr::null_mut());
|
||||
let render_res = backend.bind().and_then(|(renderer, mut fb)| {
|
||||
#[cfg(feature = "debug")]
|
||||
if let Some(renderdoc) = renderdoc.as_mut() {
|
||||
renderdoc.start_frame_capture(renderer.egl_context().get_context_handle(), window_handle);
|
||||
}
|
||||
|
||||
let mut elements = Vec::<CustomRenderElements<GlesRenderer>>::new();
|
||||
|
||||
elements.extend(
|
||||
pointer_element.render_elements(
|
||||
renderer,
|
||||
(cursor_pos - cursor_hotspot.to_f64())
|
||||
.to_physical(scale)
|
||||
.to_i32_round(),
|
||||
scale,
|
||||
1.0,
|
||||
),
|
||||
);
|
||||
|
||||
// draw the dnd icon if any
|
||||
if let Some(icon) = dnd_icon {
|
||||
let dnd_icon_pos = (cursor_pos + icon.offset.to_f64())
|
||||
.to_physical(scale)
|
||||
.to_i32_round();
|
||||
if icon.surface.alive() {
|
||||
elements.extend(AsRenderElements::<GlesRenderer>::render_elements(
|
||||
&smithay::desktop::space::SurfaceTree::from_surface(&icon.surface),
|
||||
renderer,
|
||||
dnd_icon_pos,
|
||||
scale,
|
||||
1.0,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
elements.push(CustomRenderElements::Fps(fps_element.clone()));
|
||||
|
||||
render_output(
|
||||
&output,
|
||||
space,
|
||||
elements,
|
||||
renderer,
|
||||
&mut fb,
|
||||
damage_tracker,
|
||||
age,
|
||||
show_window_preview,
|
||||
)
|
||||
.map_err(|err| match err {
|
||||
OutputDamageTrackerError::Rendering(err) => err.into(),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
});
|
||||
|
||||
match render_res {
|
||||
Ok(render_output_result) => {
|
||||
let has_rendered = render_output_result.damage.is_some();
|
||||
if let Some(damage) = render_output_result.damage {
|
||||
if let Err(err) = backend.submit(Some(damage)) {
|
||||
warn!("Failed to submit buffer: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
if let Some(renderdoc) = renderdoc.as_mut() {
|
||||
renderdoc.end_frame_capture(
|
||||
backend.renderer().egl_context().get_context_handle(),
|
||||
backend
|
||||
.window()
|
||||
.window_handle()
|
||||
.map(|handle| {
|
||||
if let RawWindowHandle::Wayland(handle) = handle.as_raw() {
|
||||
handle.surface.as_ptr()
|
||||
} else {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|_| std::ptr::null_mut()),
|
||||
);
|
||||
}
|
||||
|
||||
backend.window().set_cursor_visible(cursor_visible);
|
||||
|
||||
let states = render_output_result.states;
|
||||
|
||||
update_primary_scanout_output(
|
||||
&state.space,
|
||||
&output,
|
||||
&state.dnd_icon,
|
||||
&state.cursor_status,
|
||||
&states,
|
||||
);
|
||||
|
||||
if has_rendered {
|
||||
let mut output_presentation_feedback =
|
||||
take_presentation_feedback(&output, &state.space, &states);
|
||||
output_presentation_feedback.presented(
|
||||
frame_target,
|
||||
output
|
||||
.current_mode()
|
||||
.map(|mode| {
|
||||
Refresh::fixed(Duration::from_secs_f64(1_000f64 / mode.refresh as f64))
|
||||
})
|
||||
.unwrap_or(Refresh::Unknown),
|
||||
0,
|
||||
wp_presentation_feedback::Kind::Vsync,
|
||||
)
|
||||
}
|
||||
|
||||
// Send frame events so that client start drawing their next frame
|
||||
state.post_repaint(&output, frame_target, None, &states);
|
||||
}
|
||||
Err(SwapBuffersError::ContextLost(err)) => {
|
||||
#[cfg(feature = "debug")]
|
||||
if let Some(renderdoc) = renderdoc.as_mut() {
|
||||
renderdoc.discard_frame_capture(
|
||||
backend.renderer().egl_context().get_context_handle(),
|
||||
backend
|
||||
.window()
|
||||
.window_handle()
|
||||
.map(|handle| {
|
||||
if let RawWindowHandle::Wayland(handle) = handle.as_raw() {
|
||||
handle.surface.as_ptr()
|
||||
} else {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|_| std::ptr::null_mut()),
|
||||
);
|
||||
}
|
||||
|
||||
error!("Critical Rendering Error: {}", err);
|
||||
state.running.store(false, Ordering::SeqCst);
|
||||
}
|
||||
Err(err) => warn!("Rendering error: {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
let result = event_loop.dispatch(Some(Duration::from_millis(1)), &mut state);
|
||||
if result.is_err() {
|
||||
state.running.store(false, Ordering::SeqCst);
|
||||
} else {
|
||||
state.space.refresh();
|
||||
state.popups.cleanup();
|
||||
display_handle.flush_clients().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
state.backend_data.fps.tick();
|
||||
}
|
||||
}
|
||||
Vendored
+494
@@ -0,0 +1,494 @@
|
||||
use std::{
|
||||
sync::{Mutex, atomic::Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
drawing::*,
|
||||
render::*,
|
||||
state::{AnvilState, Backend, take_presentation_feedback, update_primary_scanout_output},
|
||||
};
|
||||
#[cfg(feature = "egl")]
|
||||
use smithay::backend::renderer::ImportEgl;
|
||||
#[cfg(feature = "debug")]
|
||||
use smithay::backend::{allocator::Fourcc, renderer::ImportMem};
|
||||
|
||||
use smithay::{
|
||||
backend::{
|
||||
allocator::{
|
||||
dmabuf::{Dmabuf, DmabufAllocator},
|
||||
gbm::{GbmAllocator, GbmBufferFlags},
|
||||
vulkan::{ImageUsageFlags, VulkanAllocator},
|
||||
},
|
||||
egl::{EGLContext, EGLDisplay},
|
||||
renderer::{
|
||||
Bind, ImportDma, ImportMemWl, damage::OutputDamageTracker, element::AsRenderElements,
|
||||
gles::GlesRenderer,
|
||||
},
|
||||
vulkan::{Instance, PhysicalDevice, version::Version},
|
||||
x11::{WindowBuilder, X11Backend, X11Event, X11Surface},
|
||||
},
|
||||
input::{
|
||||
keyboard::LedState,
|
||||
pointer::{CursorImageAttributes, CursorImageStatus},
|
||||
},
|
||||
output::{Mode, Output, PhysicalProperties, Subpixel},
|
||||
reexports::{
|
||||
ash::ext,
|
||||
calloop::EventLoop,
|
||||
gbm,
|
||||
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback,
|
||||
wayland_server::{Display, protocol::wl_surface},
|
||||
},
|
||||
utils::{DeviceFd, IsAlive, Scale},
|
||||
wayland::{
|
||||
compositor,
|
||||
dmabuf::{
|
||||
DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier,
|
||||
},
|
||||
presentation::Refresh,
|
||||
},
|
||||
};
|
||||
use tracing::{error, info, trace, warn};
|
||||
|
||||
pub const OUTPUT_NAME: &str = "x11";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct X11Data {
|
||||
render: bool,
|
||||
mode: Mode,
|
||||
// FIXME: If GlesRenderer is dropped before X11Surface, then the MakeCurrent call inside Gles2Renderer will
|
||||
// fail because the X11Surface is keeping gbm alive.
|
||||
renderer: GlesRenderer,
|
||||
damage_tracker: OutputDamageTracker,
|
||||
surface: X11Surface,
|
||||
dmabuf_state: DmabufState,
|
||||
_dmabuf_global: DmabufGlobal,
|
||||
_dmabuf_default_feedback: DmabufFeedback,
|
||||
#[cfg(feature = "debug")]
|
||||
fps: fps_ticker::Fps,
|
||||
}
|
||||
|
||||
impl DmabufHandler for AnvilState<X11Data> {
|
||||
fn dmabuf_state(&mut self) -> &mut DmabufState {
|
||||
&mut self.backend_data.dmabuf_state
|
||||
}
|
||||
|
||||
fn dmabuf_imported(&mut self, _global: &DmabufGlobal, dmabuf: Dmabuf, notifier: ImportNotifier) {
|
||||
if self.backend_data.renderer.import_dmabuf(&dmabuf, None).is_ok() {
|
||||
let _ = notifier.successful::<AnvilState<X11Data>>();
|
||||
} else {
|
||||
notifier.failed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Backend for X11Data {
|
||||
fn seat_name(&self) -> String {
|
||||
"x11".to_owned()
|
||||
}
|
||||
fn reset_buffers(&mut self, _output: &Output) {
|
||||
self.surface.reset_buffers();
|
||||
}
|
||||
fn early_import(&mut self, _surface: &wl_surface::WlSurface) {}
|
||||
fn update_led_state(&mut self, _led_state: LedState) {}
|
||||
}
|
||||
|
||||
pub fn run_x11() {
|
||||
let mut event_loop = EventLoop::try_new().unwrap();
|
||||
let display = Display::new().unwrap();
|
||||
let mut display_handle = display.handle();
|
||||
|
||||
let backend = X11Backend::new().expect("Failed to initialize X11 backend");
|
||||
let handle = backend.handle();
|
||||
|
||||
// Obtain the DRM node the X server uses for direct rendering.
|
||||
let (node, fd) = handle
|
||||
.drm_node()
|
||||
.expect("Could not get DRM node used by X server");
|
||||
|
||||
// Create the gbm device for buffer allocation.
|
||||
let device = gbm::Device::new(DeviceFd::from(fd)).expect("Failed to create gbm device");
|
||||
// Initialize EGL using the GBM device.
|
||||
let egl = unsafe { EGLDisplay::new(device.clone()).expect("Failed to create EGLDisplay") };
|
||||
// Create the OpenGL context
|
||||
let context = EGLContext::new(&egl).expect("Failed to create EGLContext");
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.title("Anvil")
|
||||
.build(&handle)
|
||||
.expect("Failed to create first window");
|
||||
|
||||
let skip_vulkan = std::env::var("ANVIL_NO_VULKAN")
|
||||
.map(|x| {
|
||||
x == "1" || x.to_lowercase() == "true" || x.to_lowercase() == "yes" || x.to_lowercase() == "y"
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
let vulkan_allocator = if !skip_vulkan {
|
||||
Instance::new(Version::VERSION_1_2, None)
|
||||
.ok()
|
||||
.and_then(|instance| {
|
||||
PhysicalDevice::enumerate(&instance).ok().and_then(|devices| {
|
||||
devices
|
||||
.filter(|phd| phd.has_device_extension(ext::physical_device_drm::NAME))
|
||||
.find(|phd| {
|
||||
phd.primary_node().unwrap() == Some(node)
|
||||
|| phd.render_node().unwrap() == Some(node)
|
||||
})
|
||||
})
|
||||
})
|
||||
.and_then(|physical_device| {
|
||||
VulkanAllocator::new(
|
||||
&physical_device,
|
||||
ImageUsageFlags::COLOR_ATTACHMENT | ImageUsageFlags::SAMPLED,
|
||||
)
|
||||
.ok()
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let surface = match vulkan_allocator {
|
||||
// Create the surface for the window.
|
||||
Some(vulkan_allocator) => handle
|
||||
.create_surface(
|
||||
&window,
|
||||
DmabufAllocator(vulkan_allocator),
|
||||
context
|
||||
.dmabuf_render_formats()
|
||||
.iter()
|
||||
.map(|format| format.modifier),
|
||||
)
|
||||
.expect("Failed to create X11 surface"),
|
||||
None => handle
|
||||
.create_surface(
|
||||
&window,
|
||||
DmabufAllocator(GbmAllocator::new(device, GbmBufferFlags::RENDERING)),
|
||||
context
|
||||
.dmabuf_render_formats()
|
||||
.iter()
|
||||
.map(|format| format.modifier),
|
||||
)
|
||||
.expect("Failed to create X11 surface"),
|
||||
};
|
||||
|
||||
#[cfg_attr(not(feature = "egl"), allow(unused_mut))]
|
||||
let mut renderer = unsafe { GlesRenderer::new(context) }.expect("Failed to initialize renderer");
|
||||
|
||||
#[cfg(feature = "egl")]
|
||||
if renderer.bind_wl_display(&display.handle()).is_ok() {
|
||||
info!("EGL hardware-acceleration enabled");
|
||||
}
|
||||
|
||||
let dmabuf_formats = renderer.dmabuf_formats();
|
||||
let dmabuf_default_feedback = DmabufFeedbackBuilder::new(node.dev_id(), dmabuf_formats)
|
||||
.build()
|
||||
.unwrap();
|
||||
let mut dmabuf_state = DmabufState::new();
|
||||
let dmabuf_global = dmabuf_state.create_global_with_default_feedback::<AnvilState<X11Data>>(
|
||||
&display.handle(),
|
||||
&dmabuf_default_feedback,
|
||||
);
|
||||
|
||||
let size = {
|
||||
let s = window.size();
|
||||
|
||||
(s.w as i32, s.h as i32).into()
|
||||
};
|
||||
|
||||
let mode = Mode {
|
||||
size,
|
||||
refresh: 60_000,
|
||||
};
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
#[allow(deprecated)]
|
||||
let fps_image =
|
||||
image::io::Reader::with_format(std::io::Cursor::new(FPS_NUMBERS_PNG), image::ImageFormat::Png)
|
||||
.decode()
|
||||
.unwrap();
|
||||
#[cfg(feature = "debug")]
|
||||
let fps_texture = renderer
|
||||
.import_memory(
|
||||
&fps_image.to_rgba8(),
|
||||
Fourcc::Abgr8888,
|
||||
(fps_image.width() as i32, fps_image.height() as i32).into(),
|
||||
false,
|
||||
)
|
||||
.expect("Unable to upload FPS texture");
|
||||
#[cfg(feature = "debug")]
|
||||
let mut fps_element = FpsElement::new(fps_texture);
|
||||
let output = Output::new(
|
||||
OUTPUT_NAME.to_string(),
|
||||
PhysicalProperties {
|
||||
size: (0, 0).into(),
|
||||
subpixel: Subpixel::Unknown,
|
||||
make: "Smithay".into(),
|
||||
model: "X11".into(),
|
||||
serial_number: "Unknown".into(),
|
||||
},
|
||||
);
|
||||
let _global = output.create_global::<AnvilState<X11Data>>(&display.handle());
|
||||
output.change_current_state(Some(mode), None, None, Some((0, 0).into()));
|
||||
output.set_preferred(mode);
|
||||
|
||||
let damage_tracker = OutputDamageTracker::from_output(&output);
|
||||
|
||||
let data = X11Data {
|
||||
render: true,
|
||||
mode,
|
||||
surface,
|
||||
renderer,
|
||||
damage_tracker,
|
||||
dmabuf_state,
|
||||
_dmabuf_global: dmabuf_global,
|
||||
_dmabuf_default_feedback: dmabuf_default_feedback,
|
||||
#[cfg(feature = "debug")]
|
||||
fps: fps_ticker::Fps::default(),
|
||||
};
|
||||
|
||||
let mut state = AnvilState::init(display, event_loop.handle(), data, true);
|
||||
state
|
||||
.shm_state
|
||||
.update_formats(state.backend_data.renderer.shm_formats());
|
||||
state.space.map_output(&output, (0, 0));
|
||||
|
||||
let output_clone = output.clone();
|
||||
event_loop
|
||||
.handle()
|
||||
.insert_source(backend, move |event, _, data| match event {
|
||||
X11Event::CloseRequested { .. } => {
|
||||
data.running.store(false, Ordering::SeqCst);
|
||||
}
|
||||
X11Event::Resized { new_size, .. } => {
|
||||
let output = &output_clone;
|
||||
let size = { (new_size.w as i32, new_size.h as i32).into() };
|
||||
|
||||
data.backend_data.mode = Mode {
|
||||
size,
|
||||
refresh: 60_000,
|
||||
};
|
||||
output.delete_mode(output.current_mode().unwrap());
|
||||
output.change_current_state(Some(data.backend_data.mode), None, None, None);
|
||||
output.set_preferred(data.backend_data.mode);
|
||||
crate::shell::fixup_positions(&mut data.space, data.pointer.current_location());
|
||||
|
||||
data.backend_data.render = true;
|
||||
}
|
||||
X11Event::PresentCompleted { .. } | X11Event::Refresh { .. } => {
|
||||
data.backend_data.render = true;
|
||||
}
|
||||
X11Event::Input { event, .. } => data.process_input_event_windowed(event, OUTPUT_NAME),
|
||||
X11Event::Focus { focused: false, .. } => {
|
||||
data.release_all_keys();
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.expect("Failed to insert X11 Backend into event loop");
|
||||
|
||||
#[cfg(feature = "xwayland")]
|
||||
state.start_xwayland();
|
||||
|
||||
info!("Initialization completed, starting the main loop.");
|
||||
|
||||
let mut pointer_element = PointerElement::default();
|
||||
|
||||
while state.running.load(Ordering::SeqCst) {
|
||||
if state.backend_data.render {
|
||||
profiling::scope!("render_frame");
|
||||
|
||||
let now = state.clock.now();
|
||||
let frame_target = now
|
||||
+ output
|
||||
.current_mode()
|
||||
.map(|mode| Duration::from_secs_f64(1_000f64 / mode.refresh as f64))
|
||||
.unwrap_or_default();
|
||||
state.pre_repaint(&output, frame_target);
|
||||
|
||||
let backend_data = &mut state.backend_data;
|
||||
// We need to borrow everything we want to refer to inside the renderer callback otherwise rustc is unhappy.
|
||||
#[cfg(feature = "debug")]
|
||||
let fps = backend_data.fps.avg().round() as u32;
|
||||
#[cfg(feature = "debug")]
|
||||
fps_element.update_fps(fps);
|
||||
|
||||
let (mut buffer, age) = backend_data.surface.buffer().expect("gbm device was destroyed");
|
||||
let mut fb = match backend_data.renderer.bind(&mut buffer) {
|
||||
Ok(fb) => fb,
|
||||
Err(err) => {
|
||||
error!("Error while binding buffer: {}", err);
|
||||
profiling::finish_frame!();
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
if let Some(renderdoc) = state.renderdoc.as_mut() {
|
||||
renderdoc.start_frame_capture(
|
||||
backend_data.renderer.egl_context().get_context_handle(),
|
||||
std::ptr::null(),
|
||||
);
|
||||
}
|
||||
|
||||
let mut elements: Vec<CustomRenderElements<GlesRenderer>> = Vec::new();
|
||||
|
||||
// draw the cursor as relevant
|
||||
// reset the cursor if the surface is no longer alive
|
||||
let mut reset = false;
|
||||
if let CursorImageStatus::Surface(ref surface) = state.cursor_status {
|
||||
reset = !surface.alive();
|
||||
}
|
||||
if reset {
|
||||
state.cursor_status = CursorImageStatus::default_named();
|
||||
}
|
||||
let cursor_visible = !matches!(state.cursor_status, CursorImageStatus::Surface(_));
|
||||
|
||||
let scale = Scale::from(output.current_scale().fractional_scale());
|
||||
let cursor_hotspot = if let CursorImageStatus::Surface(ref surface) = state.cursor_status {
|
||||
compositor::with_states(surface, |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<Mutex<CursorImageAttributes>>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.hotspot
|
||||
})
|
||||
} else {
|
||||
(0, 0).into()
|
||||
};
|
||||
let cursor_pos = state.pointer.current_location();
|
||||
|
||||
pointer_element.set_status(state.cursor_status.clone());
|
||||
elements.extend(
|
||||
pointer_element.render_elements(
|
||||
&mut backend_data.renderer,
|
||||
(cursor_pos - cursor_hotspot.to_f64())
|
||||
.to_physical(scale)
|
||||
.to_i32_round(),
|
||||
scale,
|
||||
1.0,
|
||||
),
|
||||
);
|
||||
|
||||
// draw the dnd icon if any
|
||||
if let Some(icon) = state.dnd_icon.as_ref() {
|
||||
let dnd_icon_pos = (cursor_pos + icon.offset.to_f64())
|
||||
.to_physical(scale)
|
||||
.to_i32_round();
|
||||
if icon.surface.alive() {
|
||||
elements.extend(AsRenderElements::<GlesRenderer>::render_elements(
|
||||
&smithay::desktop::space::SurfaceTree::from_surface(&icon.surface),
|
||||
&mut backend_data.renderer,
|
||||
dnd_icon_pos,
|
||||
scale,
|
||||
1.0,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
elements.push(CustomRenderElements::Fps(fps_element.clone()));
|
||||
|
||||
let render_res = render_output(
|
||||
&output,
|
||||
&state.space,
|
||||
elements,
|
||||
&mut backend_data.renderer,
|
||||
&mut fb,
|
||||
&mut backend_data.damage_tracker,
|
||||
age.into(),
|
||||
state.show_window_preview,
|
||||
);
|
||||
|
||||
match render_res {
|
||||
Ok(render_output_result) => {
|
||||
trace!("Finished rendering");
|
||||
let submitted = if let Err(err) = backend_data.surface.submit() {
|
||||
backend_data.surface.reset_buffers();
|
||||
warn!("Failed to submit buffer: {}. Retrying", err);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
let states = render_output_result.states;
|
||||
|
||||
update_primary_scanout_output(
|
||||
&state.space,
|
||||
&output,
|
||||
&state.dnd_icon,
|
||||
&state.cursor_status,
|
||||
&states,
|
||||
);
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
let rendered = render_output_result.damage.is_some();
|
||||
if render_output_result.damage.is_some() {
|
||||
let mut output_presentation_feedback =
|
||||
take_presentation_feedback(&output, &state.space, &states);
|
||||
output_presentation_feedback.presented(
|
||||
frame_target,
|
||||
output
|
||||
.current_mode()
|
||||
.map(|mode| {
|
||||
Refresh::fixed(Duration::from_secs_f64(1_000f64 / mode.refresh as f64))
|
||||
})
|
||||
.unwrap_or(Refresh::Unknown),
|
||||
0,
|
||||
wp_presentation_feedback::Kind::Vsync,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
if rendered {
|
||||
if let Some(renderdoc) = state.renderdoc.as_mut() {
|
||||
renderdoc.end_frame_capture(
|
||||
state.backend_data.renderer.egl_context().get_context_handle(),
|
||||
std::ptr::null(),
|
||||
);
|
||||
}
|
||||
} else if let Some(renderdoc) = state.renderdoc.as_mut() {
|
||||
renderdoc.discard_frame_capture(
|
||||
state.backend_data.renderer.egl_context().get_context_handle(),
|
||||
std::ptr::null(),
|
||||
);
|
||||
}
|
||||
|
||||
state.backend_data.render = !submitted;
|
||||
|
||||
// Send frame events so that client start drawing their next frame
|
||||
state.post_repaint(&output, frame_target, None, &states);
|
||||
}
|
||||
Err(err) => {
|
||||
#[cfg(feature = "debug")]
|
||||
if let Some(renderdoc) = state.renderdoc.as_mut() {
|
||||
renderdoc.discard_frame_capture(
|
||||
backend_data.renderer.egl_context().get_context_handle(),
|
||||
std::ptr::null(),
|
||||
);
|
||||
}
|
||||
|
||||
backend_data.surface.reset_buffers();
|
||||
error!("Rendering error: {}", err);
|
||||
// TODO: convert RenderError into SwapBuffersError and skip temporary (will retry) and panic on ContextLost or recreate
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
state.backend_data.fps.tick();
|
||||
window.set_cursor_visible(cursor_visible);
|
||||
profiling::finish_frame!();
|
||||
}
|
||||
|
||||
let result = event_loop.dispatch(Some(Duration::from_millis(16)), &mut state);
|
||||
if result.is_err() {
|
||||
state.running.store(false, Ordering::SeqCst);
|
||||
} else {
|
||||
state.space.refresh();
|
||||
state.popups.cleanup();
|
||||
display_handle.flush_clients().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user