mirror of
https://github.com/nestriness/warp.git
synced 2025-12-15 19:35:37 +02:00
init
This commit is contained in:
36
wayland-display-core/Cargo.toml
Normal file
36
wayland-display-core/Cargo.toml
Normal file
@@ -0,0 +1,36 @@
|
||||
[package]
|
||||
name = "wayland-display-core"
|
||||
authors = ["Victoria Brekenfeld <git@drakulix.de>", "Alessandro Beltramo <github.com/ABeltramo>"]
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
description = "Wayland Compositor producing GStreamer buffers"
|
||||
rust-version = "1.70"
|
||||
|
||||
[lib]
|
||||
name = "waylanddisplaycore"
|
||||
crate-type = ["cdylib", "staticlib", "rlib"]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
gst.workspace = true
|
||||
gst-video.workspace = true
|
||||
tracing.workspace = true
|
||||
once_cell.workspace = true
|
||||
wayland-backend = "0.1.0"
|
||||
wayland-scanner = "0.30.0"
|
||||
|
||||
[dependencies.smithay]
|
||||
git = "https://github.com/smithay/Smithay"
|
||||
rev = "b1c682742a"
|
||||
default-features = false
|
||||
features = [
|
||||
"backend_drm",
|
||||
"backend_egl",
|
||||
"backend_libinput",
|
||||
"backend_udev",
|
||||
"renderer_gl",
|
||||
"use_system_lib",
|
||||
"desktop",
|
||||
"wayland_frontend"
|
||||
]
|
||||
21
wayland-display-core/LICENSE
Normal file
21
wayland-display-core/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Games on Whales https://github.com/games-on-whales/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
70
wayland-display-core/README.md
Normal file
70
wayland-display-core/README.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# gst-wayland-display
|
||||
|
||||
A micro Wayland compositor that can be used as a Gstreamer plugin
|
||||
|
||||
## Install
|
||||
|
||||
see [cargo-c](https://github.com/lu-zero/cargo-c)
|
||||
|
||||
```bash
|
||||
git clone ...
|
||||
cd gst-wayland-display
|
||||
cargo cinstall --prefix=/usr/local
|
||||
```
|
||||
|
||||
## GStreamer plugin
|
||||
|
||||
TODO
|
||||
|
||||
## C Bindings
|
||||
|
||||
CmakeLists.txt
|
||||
|
||||
```cmake
|
||||
pkg_check_modules(libgstwaylanddisplay REQUIRED IMPORTED_TARGET libgstwaylanddisplay)
|
||||
target_link_libraries(<YOUR_PROJECT_HERE> PUBLIC PkgConfig::libgstwaylanddisplay)
|
||||
```
|
||||
|
||||
Include in your code:
|
||||
|
||||
```c
|
||||
#include <libgstwaylanddisplay/libgstwaylanddisplay.h>
|
||||
```
|
||||
|
||||
Example usage:
|
||||
|
||||
```c++
|
||||
auto w_state = display_init("/dev/dri/renderD128"); // Pass a render node
|
||||
|
||||
display_add_input_device(w_state, "/dev/input/event20"); // Mouse
|
||||
display_add_input_device(w_state, "/dev/input/event21"); // Keyboard
|
||||
|
||||
// Setting video as 1920x1080@60
|
||||
auto video_info = gst_caps_new_simple("video/x-raw",
|
||||
"width", G_TYPE_INT, 1920,
|
||||
"height", G_TYPE_INT, 1080,
|
||||
"framerate", GST_TYPE_FRACTION, 60, 1,
|
||||
"format", G_TYPE_STRING, "RGBx",
|
||||
NULL);
|
||||
display_set_video_info(w_state, video_info);
|
||||
|
||||
// Get a list of the devices needed, ex: ["/dev/dri/renderD128", "/dev/dri/card0"]
|
||||
auto n_devices = display_get_devices_len(w_state);
|
||||
const char *devs[n_devices];
|
||||
display_get_devices(w_state, devs, n_devices);
|
||||
|
||||
// Get a list of the env vars needed, notably the wayland socket
|
||||
// ex: ["WAYLAND_DISPLAY=wayland-1"]
|
||||
auto n_envs = display_get_envvars_len(w_state);
|
||||
const char *envs[n_envs];
|
||||
display_get_envvars(w_state, envs, n_envs);
|
||||
|
||||
// Example of polling for new video data
|
||||
GstBuffer * v_buffer;
|
||||
while(true){
|
||||
v_buffer = display_get_frame(w_state);
|
||||
// TODO: do something with the video data
|
||||
}
|
||||
|
||||
display_finish(w_state); // Cleanup
|
||||
```
|
||||
BIN
wayland-display-core/resources/cursor.rgba
Normal file
BIN
wayland-display-core/resources/cursor.rgba
Normal file
Binary file not shown.
189
wayland-display-core/resources/protocols/wayland-drm.xml
Normal file
189
wayland-display-core/resources/protocols/wayland-drm.xml
Normal file
@@ -0,0 +1,189 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="drm">
|
||||
|
||||
<copyright>
|
||||
Copyright © 2008-2011 Kristian Høgsberg
|
||||
Copyright © 2010-2011 Intel Corporation
|
||||
|
||||
Permission to use, copy, modify, distribute, and sell this
|
||||
software and its documentation for any purpose is hereby granted
|
||||
without fee, provided that\n the above copyright notice appear in
|
||||
all copies and that both that copyright notice and this permission
|
||||
notice appear in supporting documentation, and that the name of
|
||||
the copyright holders not be used in advertising or publicity
|
||||
pertaining to distribution of the software without specific,
|
||||
written prior permission. The copyright holders make no
|
||||
representations about the suitability of this software for any
|
||||
purpose. It is provided "as is" without express or implied
|
||||
warranty.
|
||||
|
||||
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<!-- drm support. This object is created by the server and published
|
||||
using the display's global event. -->
|
||||
<interface name="wl_drm" version="2">
|
||||
<enum name="error">
|
||||
<entry name="authenticate_fail" value="0"/>
|
||||
<entry name="invalid_format" value="1"/>
|
||||
<entry name="invalid_name" value="2"/>
|
||||
</enum>
|
||||
|
||||
<enum name="format">
|
||||
<!-- The drm format codes match the #defines in drm_fourcc.h.
|
||||
The formats actually supported by the compositor will be
|
||||
reported by the format event. New codes must not be added,
|
||||
unless directly taken from drm_fourcc.h. -->
|
||||
<entry name="c8" value="0x20203843"/>
|
||||
<entry name="rgb332" value="0x38424752"/>
|
||||
<entry name="bgr233" value="0x38524742"/>
|
||||
<entry name="xrgb4444" value="0x32315258"/>
|
||||
<entry name="xbgr4444" value="0x32314258"/>
|
||||
<entry name="rgbx4444" value="0x32315852"/>
|
||||
<entry name="bgrx4444" value="0x32315842"/>
|
||||
<entry name="argb4444" value="0x32315241"/>
|
||||
<entry name="abgr4444" value="0x32314241"/>
|
||||
<entry name="rgba4444" value="0x32314152"/>
|
||||
<entry name="bgra4444" value="0x32314142"/>
|
||||
<entry name="xrgb1555" value="0x35315258"/>
|
||||
<entry name="xbgr1555" value="0x35314258"/>
|
||||
<entry name="rgbx5551" value="0x35315852"/>
|
||||
<entry name="bgrx5551" value="0x35315842"/>
|
||||
<entry name="argb1555" value="0x35315241"/>
|
||||
<entry name="abgr1555" value="0x35314241"/>
|
||||
<entry name="rgba5551" value="0x35314152"/>
|
||||
<entry name="bgra5551" value="0x35314142"/>
|
||||
<entry name="rgb565" value="0x36314752"/>
|
||||
<entry name="bgr565" value="0x36314742"/>
|
||||
<entry name="rgb888" value="0x34324752"/>
|
||||
<entry name="bgr888" value="0x34324742"/>
|
||||
<entry name="xrgb8888" value="0x34325258"/>
|
||||
<entry name="xbgr8888" value="0x34324258"/>
|
||||
<entry name="rgbx8888" value="0x34325852"/>
|
||||
<entry name="bgrx8888" value="0x34325842"/>
|
||||
<entry name="argb8888" value="0x34325241"/>
|
||||
<entry name="abgr8888" value="0x34324241"/>
|
||||
<entry name="rgba8888" value="0x34324152"/>
|
||||
<entry name="bgra8888" value="0x34324142"/>
|
||||
<entry name="xrgb2101010" value="0x30335258"/>
|
||||
<entry name="xbgr2101010" value="0x30334258"/>
|
||||
<entry name="rgbx1010102" value="0x30335852"/>
|
||||
<entry name="bgrx1010102" value="0x30335842"/>
|
||||
<entry name="argb2101010" value="0x30335241"/>
|
||||
<entry name="abgr2101010" value="0x30334241"/>
|
||||
<entry name="rgba1010102" value="0x30334152"/>
|
||||
<entry name="bgra1010102" value="0x30334142"/>
|
||||
<entry name="yuyv" value="0x56595559"/>
|
||||
<entry name="yvyu" value="0x55595659"/>
|
||||
<entry name="uyvy" value="0x59565955"/>
|
||||
<entry name="vyuy" value="0x59555956"/>
|
||||
<entry name="ayuv" value="0x56555941"/>
|
||||
<entry name="xyuv8888" value="0x56555958"/>
|
||||
<entry name="nv12" value="0x3231564e"/>
|
||||
<entry name="nv21" value="0x3132564e"/>
|
||||
<entry name="nv16" value="0x3631564e"/>
|
||||
<entry name="nv61" value="0x3136564e"/>
|
||||
<entry name="yuv410" value="0x39565559"/>
|
||||
<entry name="yvu410" value="0x39555659"/>
|
||||
<entry name="yuv411" value="0x31315559"/>
|
||||
<entry name="yvu411" value="0x31315659"/>
|
||||
<entry name="yuv420" value="0x32315559"/>
|
||||
<entry name="yvu420" value="0x32315659"/>
|
||||
<entry name="yuv422" value="0x36315559"/>
|
||||
<entry name="yvu422" value="0x36315659"/>
|
||||
<entry name="yuv444" value="0x34325559"/>
|
||||
<entry name="yvu444" value="0x34325659"/>
|
||||
<entry name="abgr16f" value="0x48344241"/>
|
||||
<entry name="xbgr16f" value="0x48344258"/>
|
||||
</enum>
|
||||
|
||||
<!-- Call this request with the magic received from drmGetMagic().
|
||||
It will be passed on to the drmAuthMagic() or
|
||||
DRIAuthConnection() call. This authentication must be
|
||||
completed before create_buffer could be used. -->
|
||||
<request name="authenticate">
|
||||
<arg name="id" type="uint"/>
|
||||
</request>
|
||||
|
||||
<!-- Create a wayland buffer for the named DRM buffer. The DRM
|
||||
surface must have a name using the flink ioctl -->
|
||||
<request name="create_buffer">
|
||||
<arg name="id" type="new_id" interface="wl_buffer"/>
|
||||
<arg name="name" type="uint"/>
|
||||
<arg name="width" type="int"/>
|
||||
<arg name="height" type="int"/>
|
||||
<arg name="stride" type="uint"/>
|
||||
<arg name="format" type="uint"/>
|
||||
</request>
|
||||
|
||||
<!-- Create a wayland buffer for the named DRM buffer. The DRM
|
||||
surface must have a name using the flink ioctl -->
|
||||
<request name="create_planar_buffer">
|
||||
<arg name="id" type="new_id" interface="wl_buffer"/>
|
||||
<arg name="name" type="uint"/>
|
||||
<arg name="width" type="int"/>
|
||||
<arg name="height" type="int"/>
|
||||
<arg name="format" type="uint"/>
|
||||
<arg name="offset0" type="int"/>
|
||||
<arg name="stride0" type="int"/>
|
||||
<arg name="offset1" type="int"/>
|
||||
<arg name="stride1" type="int"/>
|
||||
<arg name="offset2" type="int"/>
|
||||
<arg name="stride2" type="int"/>
|
||||
</request>
|
||||
|
||||
<!-- Notification of the path of the drm device which is used by
|
||||
the server. The client should use this device for creating
|
||||
local buffers. Only buffers created from this device should
|
||||
be be passed to the server using this drm object's
|
||||
create_buffer request. -->
|
||||
<event name="device">
|
||||
<arg name="name" type="string"/>
|
||||
</event>
|
||||
|
||||
<event name="format">
|
||||
<arg name="format" type="uint"/>
|
||||
</event>
|
||||
|
||||
<!-- Raised if the authenticate request succeeded -->
|
||||
<event name="authenticated"/>
|
||||
|
||||
<enum name="capability" since="2">
|
||||
<description summary="wl_drm capability bitmask">
|
||||
Bitmask of capabilities.
|
||||
</description>
|
||||
<entry name="prime" value="1" summary="wl_drm prime available"/>
|
||||
</enum>
|
||||
|
||||
<event name="capabilities">
|
||||
<arg name="value" type="uint"/>
|
||||
</event>
|
||||
|
||||
<!-- Version 2 additions -->
|
||||
|
||||
<!-- Create a wayland buffer for the prime fd. Use for regular and planar
|
||||
buffers. Pass 0 for offset and stride for unused planes. -->
|
||||
<request name="create_prime_buffer" since="2">
|
||||
<arg name="id" type="new_id" interface="wl_buffer"/>
|
||||
<arg name="name" type="fd"/>
|
||||
<arg name="width" type="int"/>
|
||||
<arg name="height" type="int"/>
|
||||
<arg name="format" type="uint"/>
|
||||
<arg name="offset0" type="int"/>
|
||||
<arg name="stride0" type="int"/>
|
||||
<arg name="offset1" type="int"/>
|
||||
<arg name="stride1" type="int"/>
|
||||
<arg name="offset2" type="int"/>
|
||||
<arg name="stride2" type="int"/>
|
||||
</request>
|
||||
|
||||
</interface>
|
||||
|
||||
</protocol>
|
||||
156
wayland-display-core/src/comp/focus.rs
Normal file
156
wayland-display-core/src/comp/focus.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
use smithay::{
|
||||
backend::input::KeyState,
|
||||
desktop::{PopupKind, Window},
|
||||
input::{
|
||||
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
|
||||
pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerTarget, RelativeMotionEvent},
|
||||
Seat,
|
||||
},
|
||||
reexports::wayland_server::{backend::ObjectId, protocol::wl_surface::WlSurface},
|
||||
utils::{IsAlive, Serial},
|
||||
wayland::seat::WaylandFocus,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum FocusTarget {
|
||||
Wayland(Window),
|
||||
Popup(PopupKind),
|
||||
}
|
||||
|
||||
impl IsAlive for FocusTarget {
|
||||
fn alive(&self) -> bool {
|
||||
match self {
|
||||
FocusTarget::Wayland(w) => w.alive(),
|
||||
FocusTarget::Popup(p) => p.alive(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Window> for FocusTarget {
|
||||
fn from(w: Window) -> Self {
|
||||
FocusTarget::Wayland(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PopupKind> for FocusTarget {
|
||||
fn from(p: PopupKind) -> Self {
|
||||
FocusTarget::Popup(p)
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyboardTarget<super::State> for FocusTarget {
|
||||
fn enter(
|
||||
&self,
|
||||
seat: &Seat<super::State>,
|
||||
data: &mut super::State,
|
||||
keys: Vec<KeysymHandle<'_>>,
|
||||
serial: Serial,
|
||||
) {
|
||||
match self {
|
||||
FocusTarget::Wayland(w) => KeyboardTarget::enter(w, seat, data, keys, serial),
|
||||
FocusTarget::Popup(p) => {
|
||||
KeyboardTarget::enter(p.wl_surface(), seat, data, keys, serial)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn leave(&self, seat: &Seat<super::State>, data: &mut super::State, serial: Serial) {
|
||||
match self {
|
||||
FocusTarget::Wayland(w) => KeyboardTarget::leave(w, seat, data, serial),
|
||||
FocusTarget::Popup(p) => KeyboardTarget::leave(p.wl_surface(), seat, data, serial),
|
||||
}
|
||||
}
|
||||
|
||||
fn key(
|
||||
&self,
|
||||
seat: &Seat<super::State>,
|
||||
data: &mut super::State,
|
||||
key: KeysymHandle<'_>,
|
||||
state: KeyState,
|
||||
serial: Serial,
|
||||
time: u32,
|
||||
) {
|
||||
match self {
|
||||
FocusTarget::Wayland(w) => w.key(seat, data, key, state, serial, time),
|
||||
FocusTarget::Popup(p) => p.wl_surface().key(seat, data, key, state, serial, time),
|
||||
}
|
||||
}
|
||||
|
||||
fn modifiers(
|
||||
&self,
|
||||
seat: &Seat<super::State>,
|
||||
data: &mut super::State,
|
||||
modifiers: ModifiersState,
|
||||
serial: Serial,
|
||||
) {
|
||||
match self {
|
||||
FocusTarget::Wayland(w) => w.modifiers(seat, data, modifiers, serial),
|
||||
FocusTarget::Popup(p) => p.wl_surface().modifiers(seat, data, modifiers, serial),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PointerTarget<super::State> for FocusTarget {
|
||||
fn enter(&self, seat: &Seat<super::State>, data: &mut super::State, event: &MotionEvent) {
|
||||
match self {
|
||||
FocusTarget::Wayland(w) => PointerTarget::enter(w, seat, data, event),
|
||||
FocusTarget::Popup(p) => PointerTarget::enter(p.wl_surface(), seat, data, event),
|
||||
}
|
||||
}
|
||||
|
||||
fn motion(&self, seat: &Seat<super::State>, data: &mut super::State, event: &MotionEvent) {
|
||||
match self {
|
||||
FocusTarget::Wayland(w) => w.motion(seat, data, event),
|
||||
FocusTarget::Popup(p) => p.wl_surface().motion(seat, data, event),
|
||||
}
|
||||
}
|
||||
|
||||
fn relative_motion(
|
||||
&self,
|
||||
seat: &Seat<super::State>,
|
||||
data: &mut super::State,
|
||||
event: &RelativeMotionEvent,
|
||||
) {
|
||||
match self {
|
||||
FocusTarget::Wayland(w) => w.relative_motion(seat, data, event),
|
||||
FocusTarget::Popup(p) => p.wl_surface().relative_motion(seat, data, event),
|
||||
}
|
||||
}
|
||||
|
||||
fn button(&self, seat: &Seat<super::State>, data: &mut super::State, event: &ButtonEvent) {
|
||||
match self {
|
||||
FocusTarget::Wayland(w) => w.button(seat, data, event),
|
||||
FocusTarget::Popup(p) => p.wl_surface().button(seat, data, event),
|
||||
}
|
||||
}
|
||||
|
||||
fn axis(&self, seat: &Seat<super::State>, data: &mut super::State, frame: AxisFrame) {
|
||||
match self {
|
||||
FocusTarget::Wayland(w) => w.axis(seat, data, frame),
|
||||
FocusTarget::Popup(p) => p.wl_surface().axis(seat, data, frame),
|
||||
}
|
||||
}
|
||||
|
||||
fn leave(&self, seat: &Seat<super::State>, data: &mut super::State, serial: Serial, time: u32) {
|
||||
match self {
|
||||
FocusTarget::Wayland(w) => PointerTarget::leave(w, seat, data, serial, time),
|
||||
FocusTarget::Popup(p) => PointerTarget::leave(p.wl_surface(), seat, data, serial, time),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WaylandFocus for FocusTarget {
|
||||
fn wl_surface(&self) -> Option<WlSurface> {
|
||||
match self {
|
||||
FocusTarget::Wayland(w) => w.wl_surface(),
|
||||
FocusTarget::Popup(p) => Some(p.wl_surface().clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn same_client_as(&self, object_id: &ObjectId) -> bool {
|
||||
match self {
|
||||
FocusTarget::Wayland(w) => w.same_client_as(object_id),
|
||||
FocusTarget::Popup(p) => p.wl_surface().same_client_as(object_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
264
wayland-display-core/src/comp/input.rs
Normal file
264
wayland-display-core/src/comp/input.rs
Normal file
@@ -0,0 +1,264 @@
|
||||
use super::{focus::FocusTarget, State};
|
||||
use smithay::{
|
||||
backend::{
|
||||
input::{
|
||||
Axis, AxisSource, Event, InputEvent, KeyState, KeyboardKeyEvent, PointerAxisEvent,
|
||||
PointerButtonEvent, PointerMotionEvent,
|
||||
},
|
||||
libinput::LibinputInputBackend,
|
||||
},
|
||||
input::{
|
||||
keyboard::{keysyms, FilterResult},
|
||||
pointer::{AxisFrame, ButtonEvent, MotionEvent, RelativeMotionEvent},
|
||||
},
|
||||
reexports::{
|
||||
input::LibinputInterface,
|
||||
nix::{fcntl, fcntl::OFlag, sys::stat},
|
||||
wayland_server::protocol::wl_pointer,
|
||||
},
|
||||
utils::{Logical, Point, Serial, SERIAL_COUNTER},
|
||||
};
|
||||
use std::{
|
||||
os::{fd::FromRawFd, unix::io::OwnedFd},
|
||||
path::Path,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
pub struct NixInterface;
|
||||
|
||||
impl LibinputInterface for NixInterface {
|
||||
fn open_restricted(&mut self, path: &Path, flags: i32) -> Result<OwnedFd, i32> {
|
||||
fcntl::open(path, OFlag::from_bits_truncate(flags), stat::Mode::empty())
|
||||
.map(|fd| unsafe { OwnedFd::from_raw_fd(fd) })
|
||||
.map_err(|err| err as i32)
|
||||
}
|
||||
fn close_restricted(&mut self, fd: OwnedFd) {
|
||||
let _ = fd;
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn process_input_event(&mut self, event: InputEvent<LibinputInputBackend>) {
|
||||
match event {
|
||||
InputEvent::Keyboard { event, .. } => {
|
||||
let keycode = event.key_code();
|
||||
let state = event.state();
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
let time = event.time_msec();
|
||||
let keyboard = self.seat.get_keyboard().unwrap();
|
||||
|
||||
keyboard.input::<(), _>(
|
||||
self,
|
||||
keycode,
|
||||
state,
|
||||
serial,
|
||||
time,
|
||||
|data, modifiers, handle| {
|
||||
if state == KeyState::Pressed {
|
||||
if modifiers.ctrl
|
||||
&& modifiers.shift
|
||||
&& !modifiers.alt
|
||||
&& !modifiers.logo
|
||||
{
|
||||
match handle.modified_sym() {
|
||||
keysyms::KEY_Tab => {
|
||||
if let Some(element) = data.space.elements().last().cloned()
|
||||
{
|
||||
data.surpressed_keys.insert(keysyms::KEY_Tab);
|
||||
let location =
|
||||
data.space.element_location(&element).unwrap();
|
||||
data.space.map_element(element.clone(), location, true);
|
||||
data.seat.get_keyboard().unwrap().set_focus(
|
||||
data,
|
||||
Some(FocusTarget::from(element)),
|
||||
serial,
|
||||
);
|
||||
return FilterResult::Intercept(());
|
||||
}
|
||||
}
|
||||
keysyms::KEY_Q => {
|
||||
if let Some(target) =
|
||||
data.seat.get_keyboard().unwrap().current_focus()
|
||||
{
|
||||
match target {
|
||||
FocusTarget::Wayland(window) => {
|
||||
window.toplevel().send_close();
|
||||
}
|
||||
_ => return FilterResult::Forward,
|
||||
};
|
||||
data.surpressed_keys.insert(keysyms::KEY_Q);
|
||||
return FilterResult::Intercept(());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if data.surpressed_keys.remove(&handle.modified_sym()) {
|
||||
return FilterResult::Intercept(());
|
||||
}
|
||||
}
|
||||
|
||||
FilterResult::Forward
|
||||
},
|
||||
);
|
||||
}
|
||||
InputEvent::PointerMotion { event, .. } => {
|
||||
self.last_pointer_movement = Instant::now();
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
let delta = event.delta();
|
||||
self.pointer_location += delta;
|
||||
self.pointer_location = self.clamp_coords(self.pointer_location);
|
||||
|
||||
let pointer = self.seat.get_pointer().unwrap();
|
||||
let under = self
|
||||
.space
|
||||
.element_under(self.pointer_location)
|
||||
.map(|(w, pos)| (w.clone().into(), pos));
|
||||
pointer.motion(
|
||||
self,
|
||||
under.clone(),
|
||||
&MotionEvent {
|
||||
location: self.pointer_location,
|
||||
serial,
|
||||
time: event.time_msec(),
|
||||
},
|
||||
);
|
||||
pointer.relative_motion(
|
||||
self,
|
||||
under,
|
||||
&RelativeMotionEvent {
|
||||
delta,
|
||||
delta_unaccel: event.delta_unaccel(),
|
||||
utime: event.time(),
|
||||
},
|
||||
)
|
||||
}
|
||||
InputEvent::PointerMotionAbsolute { event } => {
|
||||
self.last_pointer_movement = Instant::now();
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
if let Some(output) = self.output.as_ref() {
|
||||
let output_size = output
|
||||
.current_mode()
|
||||
.unwrap()
|
||||
.size
|
||||
.to_f64()
|
||||
.to_logical(output.current_scale().fractional_scale())
|
||||
.to_i32_round();
|
||||
self.pointer_location = (
|
||||
event.absolute_x_transformed(output_size.w),
|
||||
event.absolute_y_transformed(output_size.h),
|
||||
)
|
||||
.into();
|
||||
|
||||
let pointer = self.seat.get_pointer().unwrap();
|
||||
let under = self
|
||||
.space
|
||||
.element_under(self.pointer_location)
|
||||
.map(|(w, pos)| (w.clone().into(), pos));
|
||||
pointer.motion(
|
||||
self,
|
||||
under.clone(),
|
||||
&MotionEvent {
|
||||
location: self.pointer_location,
|
||||
serial,
|
||||
time: event.time_msec(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
InputEvent::PointerButton { event, .. } => {
|
||||
self.last_pointer_movement = Instant::now();
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
let button = event.button_code();
|
||||
|
||||
let state = wl_pointer::ButtonState::from(event.state());
|
||||
if wl_pointer::ButtonState::Pressed == state {
|
||||
self.update_keyboard_focus(serial);
|
||||
};
|
||||
self.seat.get_pointer().unwrap().button(
|
||||
self,
|
||||
&ButtonEvent {
|
||||
button,
|
||||
state: state.try_into().unwrap(),
|
||||
serial,
|
||||
time: event.time_msec(),
|
||||
},
|
||||
);
|
||||
}
|
||||
InputEvent::PointerAxis { event, .. } => {
|
||||
self.last_pointer_movement = Instant::now();
|
||||
let horizontal_amount = event
|
||||
.amount(Axis::Horizontal)
|
||||
.or_else(|| event.amount_discrete(Axis::Horizontal).map(|x| x * 2.0))
|
||||
.unwrap_or(0.0);
|
||||
let vertical_amount = event
|
||||
.amount(Axis::Vertical)
|
||||
.or_else(|| event.amount_discrete(Axis::Vertical).map(|y| y * 2.0))
|
||||
.unwrap_or(0.0);
|
||||
let horizontal_amount_discrete = event.amount_discrete(Axis::Horizontal);
|
||||
let vertical_amount_discrete = event.amount_discrete(Axis::Vertical);
|
||||
|
||||
{
|
||||
let mut frame = AxisFrame::new(event.time_msec()).source(event.source());
|
||||
if horizontal_amount != 0.0 {
|
||||
frame = frame.value(Axis::Horizontal, horizontal_amount);
|
||||
if let Some(discrete) = horizontal_amount_discrete {
|
||||
frame = frame.discrete(Axis::Horizontal, discrete as i32);
|
||||
}
|
||||
} else if event.source() == AxisSource::Finger {
|
||||
frame = frame.stop(Axis::Horizontal);
|
||||
}
|
||||
if vertical_amount != 0.0 {
|
||||
frame = frame.value(Axis::Vertical, vertical_amount);
|
||||
if let Some(discrete) = vertical_amount_discrete {
|
||||
frame = frame.discrete(Axis::Vertical, discrete as i32);
|
||||
}
|
||||
} else if event.source() == AxisSource::Finger {
|
||||
frame = frame.stop(Axis::Vertical);
|
||||
}
|
||||
self.seat.get_pointer().unwrap().axis(self, frame);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn clamp_coords(&self, pos: Point<f64, Logical>) -> Point<f64, Logical> {
|
||||
if let Some(output) = self.output.as_ref() {
|
||||
if let Some(mode) = output.current_mode() {
|
||||
return (
|
||||
pos.x.max(0.0).min(mode.size.w as f64),
|
||||
pos.y.max(0.0).min(mode.size.h as f64),
|
||||
)
|
||||
.into();
|
||||
}
|
||||
}
|
||||
pos
|
||||
}
|
||||
|
||||
fn update_keyboard_focus(&mut self, serial: Serial) {
|
||||
let pointer = self.seat.get_pointer().unwrap();
|
||||
let keyboard = self.seat.get_keyboard().unwrap();
|
||||
// change the keyboard focus unless the pointer or keyboard is grabbed
|
||||
// We test for any matching surface type here but always use the root
|
||||
// (in case of a window the toplevel) surface for the focus.
|
||||
// So for example if a user clicks on a subsurface or popup the toplevel
|
||||
// will receive the keyboard focus. Directly assigning the focus to the
|
||||
// matching surface leads to issues with clients dismissing popups and
|
||||
// subsurface menus (for example firefox-wayland).
|
||||
// see here for a discussion about that issue:
|
||||
// https://gitlab.freedesktop.org/wayland/wayland/-/issues/294
|
||||
if !pointer.is_grabbed() && !keyboard.is_grabbed() {
|
||||
if let Some((window, _)) = self
|
||||
.space
|
||||
.element_under(self.pointer_location)
|
||||
.map(|(w, p)| (w.clone(), p))
|
||||
{
|
||||
self.space.raise_element(&window, true);
|
||||
keyboard.set_focus(self, Some(FocusTarget::from(window)), serial);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
521
wayland-display-core/src/comp/mod.rs
Normal file
521
wayland-display-core/src/comp/mod.rs
Normal file
@@ -0,0 +1,521 @@
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
ffi::CString,
|
||||
os::unix::prelude::AsRawFd,
|
||||
sync::{mpsc::Sender, Arc, Mutex, Weak},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use super::Command;
|
||||
use gst_video::VideoInfo;
|
||||
use once_cell::sync::Lazy;
|
||||
use smithay::{
|
||||
backend::{
|
||||
allocator::dmabuf::Dmabuf,
|
||||
drm::{DrmNode, NodeType},
|
||||
egl::{EGLContext, EGLDevice, EGLDisplay},
|
||||
libinput::LibinputInputBackend,
|
||||
renderer::{
|
||||
damage::{DamageTrackedRenderer, DamageTrackedRendererError as DTRError},
|
||||
element::memory::MemoryRenderBuffer,
|
||||
gles2::{Gles2Renderbuffer, Gles2Renderer},
|
||||
Bind, Offscreen,
|
||||
},
|
||||
},
|
||||
desktop::{
|
||||
utils::{
|
||||
send_frames_surface_tree, surface_presentation_feedback_flags_from_states,
|
||||
surface_primary_scanout_output, update_surface_primary_scanout_output,
|
||||
OutputPresentationFeedback,
|
||||
},
|
||||
PopupManager, Space, Window,
|
||||
},
|
||||
input::{keyboard::XkbConfig, pointer::CursorImageStatus, Seat, SeatState},
|
||||
output::{Mode as OutputMode, Output, PhysicalProperties, Subpixel},
|
||||
reexports::{
|
||||
calloop::{
|
||||
channel::{Channel, Event},
|
||||
generic::Generic,
|
||||
timer::{TimeoutAction, Timer},
|
||||
EventLoop, Interest, LoopHandle, Mode, PostAction,
|
||||
},
|
||||
input::Libinput,
|
||||
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback,
|
||||
wayland_server::{
|
||||
backend::{ClientData, ClientId, DisconnectReason},
|
||||
Display, DisplayHandle,
|
||||
},
|
||||
},
|
||||
utils::{Clock, Logical, Monotonic, Physical, Point, Rectangle, Size, Transform},
|
||||
wayland::{
|
||||
compositor::{with_states, CompositorState},
|
||||
data_device::DataDeviceState,
|
||||
dmabuf::{DmabufGlobal, DmabufState},
|
||||
output::OutputManagerState,
|
||||
presentation::PresentationState,
|
||||
shell::xdg::{XdgShellState, XdgToplevelSurfaceData},
|
||||
shm::ShmState,
|
||||
socket::ListeningSocketSource,
|
||||
viewporter::ViewporterState,
|
||||
relative_pointer::RelativePointerManagerState,
|
||||
},
|
||||
};
|
||||
use wayland_backend::server::GlobalId;
|
||||
|
||||
mod focus;
|
||||
mod input;
|
||||
mod rendering;
|
||||
|
||||
pub use self::focus::*;
|
||||
pub use self::input::*;
|
||||
pub use self::rendering::*;
|
||||
use crate::{utils::RenderTarget, wayland::protocols::wl_drm::create_drm_global};
|
||||
|
||||
static EGL_DISPLAYS: Lazy<Mutex<HashMap<Option<DrmNode>, Weak<EGLDisplay>>>> =
|
||||
Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
|
||||
struct ClientState;
|
||||
impl ClientData for ClientState {
|
||||
fn initialized(&self, _client_id: ClientId) {}
|
||||
fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {}
|
||||
}
|
||||
|
||||
pub(crate) struct Data {
|
||||
pub(crate) display: Display<State>,
|
||||
pub(crate) state: State,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) struct State {
|
||||
handle: LoopHandle<'static, Data>,
|
||||
should_quit: bool,
|
||||
clock: Clock<Monotonic>,
|
||||
|
||||
// render
|
||||
dtr: Option<DamageTrackedRenderer>,
|
||||
renderbuffer: Option<Gles2Renderbuffer>,
|
||||
pub renderer: Gles2Renderer,
|
||||
egl_display_ref: Arc<EGLDisplay>,
|
||||
dmabuf_global: Option<(DmabufGlobal, GlobalId)>,
|
||||
last_render: Option<Instant>,
|
||||
|
||||
// management
|
||||
pub output: Option<Output>,
|
||||
pub video_info: Option<VideoInfo>,
|
||||
pub seat: Seat<Self>,
|
||||
pub space: Space<Window>,
|
||||
pub popups: PopupManager,
|
||||
pointer_location: Point<f64, Logical>,
|
||||
last_pointer_movement: Instant,
|
||||
cursor_element: MemoryRenderBuffer,
|
||||
pub cursor_state: CursorImageStatus,
|
||||
surpressed_keys: HashSet<u32>,
|
||||
pub pending_windows: Vec<Window>,
|
||||
input_context: Libinput,
|
||||
|
||||
// wayland state
|
||||
pub dh: DisplayHandle,
|
||||
pub compositor_state: CompositorState,
|
||||
pub data_device_state: DataDeviceState,
|
||||
pub dmabuf_state: DmabufState,
|
||||
output_state: OutputManagerState,
|
||||
presentation_state: PresentationState,
|
||||
relative_ptr_state: RelativePointerManagerState,
|
||||
pub seat_state: SeatState<Self>,
|
||||
pub shell_state: XdgShellState,
|
||||
pub shm_state: ShmState,
|
||||
viewporter_state: ViewporterState,
|
||||
}
|
||||
|
||||
pub fn get_egl_device_for_node(drm_node: &DrmNode) -> EGLDevice {
|
||||
let drm_node = drm_node
|
||||
.node_with_type(NodeType::Render)
|
||||
.and_then(Result::ok)
|
||||
.unwrap_or(drm_node.clone());
|
||||
EGLDevice::enumerate()
|
||||
.expect("Failed to enumerate EGLDevices")
|
||||
.find(|d| d.try_get_render_node().unwrap_or_default() == Some(drm_node))
|
||||
.expect("Unable to find EGLDevice for drm-node")
|
||||
}
|
||||
|
||||
pub(crate) fn init(
|
||||
command_src: Channel<Command>,
|
||||
render: impl Into<RenderTarget>,
|
||||
devices_tx: Sender<Vec<CString>>,
|
||||
envs_tx: Sender<Vec<CString>>,
|
||||
) {
|
||||
let clock = Clock::new().expect("Failed to initialize clock");
|
||||
let mut display = Display::<State>::new().unwrap();
|
||||
let dh = display.handle();
|
||||
|
||||
// init state
|
||||
let compositor_state = CompositorState::new::<State>(&dh);
|
||||
let data_device_state = DataDeviceState::new::<State>(&dh);
|
||||
let mut dmabuf_state = DmabufState::new();
|
||||
let output_state = OutputManagerState::new_with_xdg_output::<State>(&dh);
|
||||
let presentation_state = PresentationState::new::<State>(&dh, clock.id() as _);
|
||||
let relative_ptr_state = RelativePointerManagerState::new::<State>(&dh);
|
||||
let mut seat_state = SeatState::new();
|
||||
let shell_state = XdgShellState::new::<State>(&dh);
|
||||
let viewporter_state = ViewporterState::new::<State>(&dh);
|
||||
|
||||
let render_target = render.into();
|
||||
let render_node: Option<DrmNode> = render_target.clone().into();
|
||||
|
||||
// init render backend
|
||||
let (egl_display_ref, context) = {
|
||||
let mut displays = EGL_DISPLAYS.lock().unwrap();
|
||||
let maybe_display = displays
|
||||
.get(&render_node)
|
||||
.and_then(|weak_display| weak_display.upgrade());
|
||||
|
||||
let egl = match maybe_display {
|
||||
Some(display) => display,
|
||||
None => {
|
||||
let device = match render_node.as_ref() {
|
||||
Some(render_node) => get_egl_device_for_node(render_node),
|
||||
None => EGLDevice::enumerate()
|
||||
.expect("Failed to enumerate EGLDevices")
|
||||
.find(|device| {
|
||||
device
|
||||
.extensions()
|
||||
.iter()
|
||||
.any(|e| e == "EGL_MESA_device_software")
|
||||
})
|
||||
.expect("Failed to find software device"),
|
||||
};
|
||||
let display =
|
||||
Arc::new(EGLDisplay::new(device).expect("Failed to initialize EGL display"));
|
||||
displays.insert(render_node, Arc::downgrade(&display));
|
||||
display
|
||||
}
|
||||
};
|
||||
let context = EGLContext::new(&egl).expect("Failed to initialize EGL context");
|
||||
(egl, context)
|
||||
};
|
||||
let renderer = unsafe { Gles2Renderer::new(context) }.expect("Failed to initialize renderer");
|
||||
let _ = devices_tx.send(render_target.as_devices());
|
||||
|
||||
let shm_state = ShmState::new::<State>(&dh, vec![]);
|
||||
let dmabuf_global = if let RenderTarget::Hardware(node) = render_target {
|
||||
let formats = Bind::<Dmabuf>::supported_formats(&renderer)
|
||||
.expect("Failed to query formats")
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// dma buffer
|
||||
let dmabuf_global = dmabuf_state.create_global::<State>(&dh, formats.clone());
|
||||
// wl_drm (mesa protocol, so we don't need EGL_WL_bind_display)
|
||||
let wl_drm_global = create_drm_global::<State>(
|
||||
&dh,
|
||||
node.dev_path().expect("Failed to determine DrmNode path?"),
|
||||
formats.clone(),
|
||||
&dmabuf_global,
|
||||
);
|
||||
|
||||
Some((dmabuf_global, wl_drm_global))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let cursor_element =
|
||||
MemoryRenderBuffer::from_memory(CURSOR_DATA_BYTES, (64, 64), 1, Transform::Normal, None);
|
||||
|
||||
// init input backend
|
||||
let libinput_context = Libinput::new_from_path(NixInterface);
|
||||
let input_context = libinput_context.clone();
|
||||
let libinput_backend = LibinputInputBackend::new(libinput_context);
|
||||
|
||||
let space = Space::default();
|
||||
|
||||
let mut seat = seat_state.new_wl_seat(&dh, "seat-0");
|
||||
seat.add_keyboard(XkbConfig::default(), 200, 25)
|
||||
.expect("Failed to add keyboard to seat");
|
||||
seat.add_pointer();
|
||||
|
||||
let mut event_loop =
|
||||
EventLoop::<Data>::try_new_high_precision().expect("Unable to create event_loop");
|
||||
let state = State {
|
||||
handle: event_loop.handle(),
|
||||
should_quit: false,
|
||||
clock,
|
||||
|
||||
renderer,
|
||||
egl_display_ref,
|
||||
dtr: None,
|
||||
renderbuffer: None,
|
||||
dmabuf_global,
|
||||
video_info: None,
|
||||
last_render: None,
|
||||
|
||||
space,
|
||||
popups: PopupManager::default(),
|
||||
seat,
|
||||
output: None,
|
||||
pointer_location: (0., 0.).into(),
|
||||
last_pointer_movement: Instant::now(),
|
||||
cursor_element,
|
||||
cursor_state: CursorImageStatus::Default,
|
||||
surpressed_keys: HashSet::new(),
|
||||
pending_windows: Vec::new(),
|
||||
input_context,
|
||||
|
||||
dh: display.handle(),
|
||||
compositor_state,
|
||||
data_device_state,
|
||||
dmabuf_state,
|
||||
output_state,
|
||||
presentation_state,
|
||||
relative_ptr_state,
|
||||
seat_state,
|
||||
shell_state,
|
||||
shm_state,
|
||||
viewporter_state,
|
||||
};
|
||||
|
||||
// init event loop
|
||||
event_loop
|
||||
.handle()
|
||||
.insert_source(libinput_backend, move |event, _, data| {
|
||||
data.state.process_input_event(event)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
event_loop
|
||||
.handle()
|
||||
.insert_source(command_src, move |event, _, data| {
|
||||
match event {
|
||||
Event::Msg(Command::VideoInfo(info)) => {
|
||||
let size: Size<i32, Physical> =
|
||||
(info.width() as i32, info.height() as i32).into();
|
||||
let framerate = info.fps();
|
||||
let duration = Duration::from_secs_f64(
|
||||
framerate.numer() as f64 / framerate.denom() as f64,
|
||||
);
|
||||
|
||||
// init wayland objects
|
||||
let output = data.state.output.get_or_insert_with(|| {
|
||||
let output = Output::new(
|
||||
"HEADLESS-1".into(),
|
||||
PhysicalProperties {
|
||||
make: "Virtual".into(),
|
||||
model: "Wolf".into(),
|
||||
size: (0, 0).into(),
|
||||
subpixel: Subpixel::Unknown,
|
||||
},
|
||||
);
|
||||
output.create_global::<State>(&data.display.handle());
|
||||
output
|
||||
});
|
||||
let mode = OutputMode {
|
||||
size: size.into(),
|
||||
refresh: (duration.as_secs_f64() * 1000.0).round() as i32,
|
||||
};
|
||||
output.change_current_state(Some(mode), None, None, None);
|
||||
output.set_preferred(mode);
|
||||
let dtr = DamageTrackedRenderer::from_output(&output);
|
||||
|
||||
data.state.space.map_output(&output, (0, 0));
|
||||
data.state.dtr = Some(dtr);
|
||||
data.state.pointer_location = (size.w as f64 / 2.0, size.h as f64 / 2.0).into();
|
||||
data.state.renderbuffer = Some(
|
||||
data.state
|
||||
.renderer
|
||||
.create_buffer((info.width() as i32, info.height() as i32).into())
|
||||
.expect("Failed to create renderbuffer"),
|
||||
);
|
||||
data.state.video_info = Some(info);
|
||||
|
||||
let new_size = size
|
||||
.to_f64()
|
||||
.to_logical(output.current_scale().fractional_scale())
|
||||
.to_i32_round();
|
||||
for window in data.state.space.elements() {
|
||||
let toplevel = window.toplevel();
|
||||
let max_size = Rectangle::from_loc_and_size(
|
||||
(0, 0),
|
||||
with_states(toplevel.wl_surface(), |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.map(|attrs| attrs.lock().unwrap().max_size)
|
||||
})
|
||||
.unwrap_or(new_size),
|
||||
);
|
||||
|
||||
let new_size = max_size
|
||||
.intersection(Rectangle::from_loc_and_size((0, 0), new_size))
|
||||
.map(|rect| rect.size);
|
||||
toplevel.with_pending_state(|state| state.size = new_size);
|
||||
toplevel.send_configure();
|
||||
}
|
||||
}
|
||||
Event::Msg(Command::InputDevice(path)) => {
|
||||
tracing::info!(path, "Adding input device.");
|
||||
data.state.input_context.path_add_device(&path);
|
||||
}
|
||||
Event::Msg(Command::Buffer(buffer_sender)) => {
|
||||
let wait = if let Some(last_render) = data.state.last_render {
|
||||
let framerate = data.state.video_info.as_ref().unwrap().fps();
|
||||
let duration = Duration::from_secs_f64(
|
||||
framerate.denom() as f64 / framerate.numer() as f64,
|
||||
);
|
||||
let time_passed = Instant::now().duration_since(last_render);
|
||||
if time_passed < duration {
|
||||
Some(duration - time_passed)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let render = move |data: &mut Data, now: Instant| {
|
||||
if let Err(_) = match data.state.create_frame() {
|
||||
Ok((buf, damage, render_element_states)) => {
|
||||
data.state.last_render = Some(now);
|
||||
let res = buffer_sender.send(Ok(buf));
|
||||
|
||||
if let Some(output) = data.state.output.as_ref() {
|
||||
let mut output_presentation_feedback =
|
||||
OutputPresentationFeedback::new(output);
|
||||
for window in data.state.space.elements() {
|
||||
window.with_surfaces(|surface, states| {
|
||||
update_surface_primary_scanout_output(
|
||||
surface,
|
||||
output,
|
||||
states,
|
||||
&render_element_states,
|
||||
|next_output, _, _, _| next_output,
|
||||
);
|
||||
});
|
||||
window.send_frame(
|
||||
output,
|
||||
data.state.clock.now(),
|
||||
Some(Duration::ZERO),
|
||||
|_, _| Some(output.clone()),
|
||||
);
|
||||
window.take_presentation_feedback(
|
||||
&mut output_presentation_feedback,
|
||||
surface_primary_scanout_output,
|
||||
|surface, _| {
|
||||
surface_presentation_feedback_flags_from_states(
|
||||
surface,
|
||||
&render_element_states,
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
if damage.is_some() {
|
||||
output_presentation_feedback.presented(
|
||||
data.state.clock.now(),
|
||||
output
|
||||
.current_mode()
|
||||
.map(|mode| mode.refresh as u32)
|
||||
.unwrap_or_default(),
|
||||
0,
|
||||
wp_presentation_feedback::Kind::Vsync,
|
||||
);
|
||||
}
|
||||
if let CursorImageStatus::Surface(wl_surface) =
|
||||
&data.state.cursor_state
|
||||
{
|
||||
send_frames_surface_tree(
|
||||
wl_surface,
|
||||
output,
|
||||
data.state.clock.now(),
|
||||
None,
|
||||
|_, _| Some(output.clone()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!(?err, "Rendering failed.");
|
||||
buffer_sender.send(Err(match err {
|
||||
DTRError::OutputNoMode(_) => unreachable!(),
|
||||
DTRError::Rendering(err) => err.into(),
|
||||
}))
|
||||
}
|
||||
} {
|
||||
data.state.should_quit = true;
|
||||
}
|
||||
};
|
||||
|
||||
match wait {
|
||||
Some(duration) => {
|
||||
if let Err(err) = data.state.handle.insert_source(
|
||||
Timer::from_duration(duration),
|
||||
move |now, _, data| {
|
||||
render(data, now);
|
||||
TimeoutAction::Drop
|
||||
},
|
||||
) {
|
||||
tracing::error!(?err, "Event loop error.");
|
||||
data.state.should_quit = true;
|
||||
};
|
||||
}
|
||||
None => render(data, Instant::now()),
|
||||
};
|
||||
}
|
||||
Event::Msg(Command::Quit) | Event::Closed => {
|
||||
data.state.should_quit = true;
|
||||
}
|
||||
};
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let source = ListeningSocketSource::new_auto().unwrap();
|
||||
let socket_name = source.socket_name().to_string_lossy().into_owned();
|
||||
tracing::info!(?socket_name, "Listening on wayland socket.");
|
||||
event_loop
|
||||
.handle()
|
||||
.insert_source(source, |client_stream, _, data| {
|
||||
if let Err(err) = data
|
||||
.display
|
||||
.handle()
|
||||
.insert_client(client_stream, std::sync::Arc::new(ClientState))
|
||||
{
|
||||
tracing::error!(?err, "Error adding wayland client.");
|
||||
};
|
||||
})
|
||||
.expect("Failed to init wayland socket source");
|
||||
|
||||
event_loop
|
||||
.handle()
|
||||
.insert_source(
|
||||
Generic::new(
|
||||
display.backend().poll_fd().as_raw_fd(),
|
||||
Interest::READ,
|
||||
Mode::Level,
|
||||
),
|
||||
|_, _, data| {
|
||||
data.display.dispatch_clients(&mut data.state).unwrap();
|
||||
Ok(PostAction::Continue)
|
||||
},
|
||||
)
|
||||
.expect("Failed to init wayland server source");
|
||||
|
||||
let env_vars = vec![CString::new(format!("WAYLAND_DISPLAY={}", socket_name)).unwrap()];
|
||||
if let Err(err) = envs_tx.send(env_vars) {
|
||||
tracing::warn!(?err, "Failed to post environment to application.");
|
||||
}
|
||||
|
||||
let mut data = Data { display, state };
|
||||
let signal = event_loop.get_signal();
|
||||
if let Err(err) = event_loop.run(None, &mut data, |data| {
|
||||
data.display
|
||||
.flush_clients()
|
||||
.expect("Failed to flush clients");
|
||||
data.state.space.refresh();
|
||||
data.state.popups.cleanup();
|
||||
|
||||
if data.state.should_quit {
|
||||
signal.stop();
|
||||
}
|
||||
}) {
|
||||
tracing::error!(?err, "Event loop broke.");
|
||||
}
|
||||
}
|
||||
121
wayland-display-core/src/comp/rendering.rs
Normal file
121
wayland-display-core/src/comp/rendering.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use smithay::{
|
||||
backend::renderer::{
|
||||
damage::DamageTrackedRendererError as DTRError,
|
||||
element::{
|
||||
memory::MemoryRenderBufferRenderElement, surface::WaylandSurfaceRenderElement,
|
||||
RenderElementStates,
|
||||
},
|
||||
gles2::Gles2Renderer,
|
||||
Bind, ExportMem, ImportAll, ImportMem, Renderer, Unbind,
|
||||
},
|
||||
desktop::space::render_output,
|
||||
input::pointer::CursorImageStatus,
|
||||
render_elements,
|
||||
utils::{Physical, Rectangle},
|
||||
};
|
||||
|
||||
use super::State;
|
||||
|
||||
pub const CURSOR_DATA_BYTES: &[u8] = include_bytes!("../../resources/cursor.rgba");
|
||||
|
||||
render_elements! {
|
||||
CursorElement<R> where R: Renderer + ImportAll + ImportMem;
|
||||
Surface=WaylandSurfaceRenderElement<R>,
|
||||
Memory=MemoryRenderBufferRenderElement<R>
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn create_frame(
|
||||
&mut self,
|
||||
) -> Result<
|
||||
(
|
||||
gst::Buffer,
|
||||
Option<Vec<Rectangle<i32, Physical>>>,
|
||||
RenderElementStates,
|
||||
),
|
||||
DTRError<Gles2Renderer>,
|
||||
> {
|
||||
assert!(self.output.is_some());
|
||||
assert!(self.dtr.is_some());
|
||||
assert!(self.video_info.is_some());
|
||||
assert!(self.renderbuffer.is_some());
|
||||
|
||||
let elements =
|
||||
if Instant::now().duration_since(self.last_pointer_movement) < Duration::from_secs(5) {
|
||||
match &self.cursor_state {
|
||||
CursorImageStatus::Default => vec![CursorElement::Memory(
|
||||
MemoryRenderBufferRenderElement::from_buffer(
|
||||
&mut self.renderer,
|
||||
self.pointer_location.to_physical_precise_round(1),
|
||||
&self.cursor_element,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.map_err(DTRError::Rendering)?,
|
||||
)],
|
||||
CursorImageStatus::Surface(wl_surface) => {
|
||||
smithay::backend::renderer::element::surface::render_elements_from_surface_tree(
|
||||
&mut self.renderer,
|
||||
wl_surface,
|
||||
self.pointer_location.to_physical_precise_round(1),
|
||||
1.,
|
||||
)
|
||||
}
|
||||
CursorImageStatus::Hidden => vec![],
|
||||
}
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
self.renderer
|
||||
.bind(self.renderbuffer.clone().unwrap())
|
||||
.map_err(DTRError::Rendering)?;
|
||||
let (damage, render_element_states) = render_output(
|
||||
self.output.as_ref().unwrap(),
|
||||
&mut self.renderer,
|
||||
1,
|
||||
[&self.space],
|
||||
&*elements,
|
||||
self.dtr.as_mut().unwrap(),
|
||||
[0.0, 0.0, 0.0, 1.0],
|
||||
)?;
|
||||
|
||||
let mapping = self
|
||||
.renderer
|
||||
.copy_framebuffer(Rectangle::from_loc_and_size(
|
||||
(0, 0),
|
||||
(
|
||||
self.video_info.as_ref().unwrap().width() as i32,
|
||||
self.video_info.as_ref().unwrap().height() as i32,
|
||||
),
|
||||
))
|
||||
.expect("Failed to export framebuffer");
|
||||
let map = self
|
||||
.renderer
|
||||
.map_texture(&mapping)
|
||||
.expect("Failed to download framebuffer");
|
||||
|
||||
let buffer = {
|
||||
let mut buffer = gst::Buffer::with_size(map.len()).expect("failed to create buffer");
|
||||
{
|
||||
let buffer = buffer.get_mut().unwrap();
|
||||
|
||||
let mut vframe = gst_video::VideoFrameRef::from_buffer_ref_writable(
|
||||
buffer,
|
||||
self.video_info.as_ref().unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let plane_data = vframe.plane_data_mut(0).unwrap();
|
||||
plane_data.clone_from_slice(map);
|
||||
}
|
||||
|
||||
buffer
|
||||
};
|
||||
self.renderer.unbind().map_err(DTRError::Rendering)?;
|
||||
|
||||
Ok((buffer, damage, render_element_states))
|
||||
}
|
||||
}
|
||||
134
wayland-display-core/src/lib.rs
Normal file
134
wayland-display-core/src/lib.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
use gst_video::VideoInfo;
|
||||
|
||||
use smithay::backend::drm::CreateDrmNodeError;
|
||||
use smithay::backend::SwapBuffersError;
|
||||
use smithay::reexports::calloop::channel::Sender;
|
||||
|
||||
use std::ffi::{CString};
|
||||
use std::str::FromStr;
|
||||
use std::sync::mpsc::{self, Receiver, SyncSender};
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
use utils::RenderTarget;
|
||||
|
||||
pub(crate) mod comp;
|
||||
pub(crate) mod utils;
|
||||
pub(crate) mod wayland;
|
||||
|
||||
pub(crate) enum Command {
|
||||
InputDevice(String),
|
||||
VideoInfo(VideoInfo),
|
||||
Buffer(SyncSender<Result<gst::Buffer, SwapBuffersError>>),
|
||||
Quit,
|
||||
}
|
||||
|
||||
pub struct WaylandDisplay {
|
||||
thread_handle: Option<JoinHandle<()>>,
|
||||
command_tx: Sender<Command>,
|
||||
|
||||
pub devices: MaybeRecv<Vec<CString>>,
|
||||
pub envs: MaybeRecv<Vec<CString>>,
|
||||
}
|
||||
|
||||
pub enum MaybeRecv<T: Clone> {
|
||||
Rx(Receiver<T>),
|
||||
Value(T),
|
||||
}
|
||||
|
||||
impl<T: Clone> MaybeRecv<T> {
|
||||
pub fn get(&mut self) -> &T {
|
||||
match self {
|
||||
MaybeRecv::Rx(recv) => {
|
||||
let value = recv.recv().unwrap();
|
||||
*self = MaybeRecv::Value(value.clone());
|
||||
self.get()
|
||||
}
|
||||
MaybeRecv::Value(val) => val,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WaylandDisplay {
|
||||
pub fn new(render_node: Option<String>) -> Result<WaylandDisplay, CreateDrmNodeError> {
|
||||
let (channel_tx, channel_rx) = std::sync::mpsc::sync_channel(0);
|
||||
let (devices_tx, devices_rx) = std::sync::mpsc::channel();
|
||||
let (envs_tx, envs_rx) = std::sync::mpsc::channel();
|
||||
let render_target = RenderTarget::from_str(
|
||||
&render_node.unwrap_or_else(|| String::from("/dev/dri/renderD128")),
|
||||
)?;
|
||||
|
||||
let thread_handle = std::thread::spawn(move || {
|
||||
if let Err(err) = std::panic::catch_unwind(|| {
|
||||
// calloops channel is not "UnwindSafe", but the std channel is... *sigh* lets workaround it creatively
|
||||
let (command_tx, command_src) = smithay::reexports::calloop::channel::channel();
|
||||
channel_tx.send(command_tx).unwrap();
|
||||
comp::init(command_src, render_target, devices_tx, envs_tx);
|
||||
}) {
|
||||
tracing::error!(?err, "Compositor thread panic'ed!");
|
||||
}
|
||||
});
|
||||
let command_tx = channel_rx.recv().unwrap();
|
||||
|
||||
Ok(WaylandDisplay {
|
||||
thread_handle: Some(thread_handle),
|
||||
command_tx,
|
||||
devices: MaybeRecv::Rx(devices_rx),
|
||||
envs: MaybeRecv::Rx(envs_rx),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn devices(&mut self) -> impl Iterator<Item = &str> {
|
||||
self.devices
|
||||
.get()
|
||||
.iter()
|
||||
.map(|string| string.to_str().unwrap())
|
||||
}
|
||||
|
||||
pub fn env_vars(&mut self) -> impl Iterator<Item = &str> {
|
||||
self.envs
|
||||
.get()
|
||||
.iter()
|
||||
.map(|string| string.to_str().unwrap())
|
||||
}
|
||||
|
||||
pub fn add_input_device(&self, path: impl Into<String>) {
|
||||
let _ = self.command_tx.send(Command::InputDevice(path.into()));
|
||||
}
|
||||
|
||||
pub fn set_video_info(&self, info: VideoInfo) {
|
||||
let _ = self.command_tx.send(Command::VideoInfo(info));
|
||||
}
|
||||
|
||||
pub fn frame(&self) -> Result<gst::Buffer, gst::FlowError> {
|
||||
let (buffer_tx, buffer_rx) = mpsc::sync_channel(0);
|
||||
if let Err(err) = self.command_tx.send(Command::Buffer(buffer_tx)) {
|
||||
tracing::warn!(?err, "Failed to send buffer command.");
|
||||
return Err(gst::FlowError::Eos);
|
||||
}
|
||||
|
||||
match buffer_rx.recv() {
|
||||
Ok(Ok(buffer)) => Ok(buffer),
|
||||
Ok(Err(err)) => match err {
|
||||
SwapBuffersError::AlreadySwapped => unreachable!(),
|
||||
SwapBuffersError::ContextLost(_) => Err(gst::FlowError::Eos),
|
||||
SwapBuffersError::TemporaryFailure(_) => Err(gst::FlowError::Error),
|
||||
},
|
||||
Err(err) => {
|
||||
tracing::warn!(?err, "Failed to recv buffer ack.");
|
||||
Err(gst::FlowError::Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WaylandDisplay {
|
||||
fn drop(&mut self) {
|
||||
if let Err(err) = self.command_tx.send(Command::Quit) {
|
||||
tracing::warn!("Failed to send stop command: {}", err);
|
||||
return;
|
||||
};
|
||||
if self.thread_handle.take().unwrap().join().is_err() {
|
||||
tracing::warn!("Failed to join compositor thread");
|
||||
};
|
||||
}
|
||||
}
|
||||
3
wayland-display-core/src/utils/mod.rs
Normal file
3
wayland-display-core/src/utils/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
mod target;
|
||||
|
||||
pub use self::target::*;
|
||||
88
wayland-display-core/src/utils/target.rs
Normal file
88
wayland-display-core/src/utils/target.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use std::{ffi::CString, os::unix::fs::MetadataExt, str::FromStr};
|
||||
|
||||
use smithay::{
|
||||
backend::{
|
||||
drm::{CreateDrmNodeError, DrmNode, NodeType},
|
||||
udev,
|
||||
},
|
||||
reexports::nix::sys::stat::major,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum RenderTarget {
|
||||
Hardware(DrmNode),
|
||||
Software,
|
||||
}
|
||||
|
||||
impl FromStr for RenderTarget {
|
||||
type Err = CreateDrmNodeError;
|
||||
fn from_str(s: &str) -> Result<Self, CreateDrmNodeError> {
|
||||
Ok(match s {
|
||||
"software" => RenderTarget::Software,
|
||||
path => RenderTarget::Hardware(DrmNode::from_path(path)?),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Option<DrmNode>> for RenderTarget {
|
||||
fn into(self) -> Option<DrmNode> {
|
||||
match self {
|
||||
RenderTarget::Hardware(node) => Some(node),
|
||||
RenderTarget::Software => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<RenderTarget> for DrmNode {
|
||||
fn into(self) -> RenderTarget {
|
||||
RenderTarget::Hardware(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
const NVIDIA_MAJOR: u64 = 195;
|
||||
|
||||
// no clue how this number is on BSDs, feel free to contribute
|
||||
|
||||
impl RenderTarget {
|
||||
pub fn as_devices(&self) -> Vec<CString> {
|
||||
match self {
|
||||
RenderTarget::Hardware(node) => {
|
||||
let mut devices = Vec::new();
|
||||
if let Some(primary) = node.dev_path_with_type(NodeType::Primary) {
|
||||
devices.push(primary);
|
||||
}
|
||||
if let Some(render) = node.dev_path_with_type(NodeType::Render) {
|
||||
devices.push(render);
|
||||
}
|
||||
if udev::driver(node.dev_id())
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|s| s.to_str() == Some("nvidia"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
// no idea how match nvidia device nodes to kms/dri-nodes, so lets map all nvidia-nodes to be sure
|
||||
for entry in std::fs::read_dir("/dev").expect("Unable to access /dev") {
|
||||
if let Ok(entry) = entry {
|
||||
if let Ok(metadata) = entry.metadata() {
|
||||
if metadata.is_file() && major(metadata.dev()) == NVIDIA_MAJOR {
|
||||
devices.push(entry.path());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
devices
|
||||
.into_iter()
|
||||
.flat_map(|path| {
|
||||
path.to_str()
|
||||
.map(String::from)
|
||||
.and_then(|string| CString::new(string).ok())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
RenderTarget::Software => Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
147
wayland-display-core/src/wayland/handlers/compositor.rs
Normal file
147
wayland-display-core/src/wayland/handlers/compositor.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
use smithay::{
|
||||
backend::renderer::utils::{on_commit_buffer_handler},
|
||||
delegate_compositor,
|
||||
desktop::PopupKind,
|
||||
reexports::{
|
||||
wayland_protocols::xdg::shell::server::xdg_toplevel::State as XdgState,
|
||||
wayland_server::protocol::{wl_buffer::WlBuffer, wl_surface::WlSurface},
|
||||
},
|
||||
utils::{Size, SERIAL_COUNTER},
|
||||
wayland::{
|
||||
buffer::BufferHandler,
|
||||
compositor::{with_states, CompositorHandler, CompositorState},
|
||||
seat::WaylandFocus,
|
||||
shell::xdg::{XdgPopupSurfaceData, XdgToplevelSurfaceData},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::comp::{FocusTarget, State};
|
||||
|
||||
impl BufferHandler for State {
|
||||
fn buffer_destroyed(&mut self, _buffer: &WlBuffer) {}
|
||||
}
|
||||
|
||||
impl CompositorHandler for State {
|
||||
fn compositor_state(&mut self) -> &mut CompositorState {
|
||||
&mut self.compositor_state
|
||||
}
|
||||
|
||||
fn commit(&mut self, surface: &WlSurface) {
|
||||
on_commit_buffer_handler(surface);
|
||||
|
||||
if let Some(window) = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|w| w.wl_surface().as_ref() == Some(surface))
|
||||
{
|
||||
window.on_commit();
|
||||
}
|
||||
self.popups.commit(surface);
|
||||
|
||||
// send the initial configure if relevant
|
||||
if let Some(idx) = self
|
||||
.pending_windows
|
||||
.iter_mut()
|
||||
.position(|w| w.wl_surface().as_ref() == Some(surface))
|
||||
{
|
||||
let window = self.pending_windows.swap_remove(idx);
|
||||
|
||||
let toplevel = window.toplevel();
|
||||
let (initial_configure_sent, max_size) = with_states(surface, |states| {
|
||||
let attributes = states.data_map.get::<XdgToplevelSurfaceData>().unwrap();
|
||||
let attributes_guard = attributes.lock().unwrap();
|
||||
|
||||
(
|
||||
attributes_guard.initial_configure_sent,
|
||||
attributes_guard.max_size,
|
||||
)
|
||||
});
|
||||
|
||||
if self.output.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
if !initial_configure_sent {
|
||||
if max_size.w == 0 && max_size.h == 0 {
|
||||
toplevel.with_pending_state(|state| {
|
||||
state.size = Some(
|
||||
self.output
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.current_mode()
|
||||
.unwrap()
|
||||
.size
|
||||
.to_f64()
|
||||
.to_logical(
|
||||
self.output
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.current_scale()
|
||||
.fractional_scale(),
|
||||
)
|
||||
.to_i32_round(),
|
||||
);
|
||||
state.states.set(XdgState::Fullscreen);
|
||||
});
|
||||
}
|
||||
toplevel.with_pending_state(|state| {
|
||||
state.states.set(XdgState::Activated);
|
||||
});
|
||||
toplevel.send_configure();
|
||||
self.pending_windows.push(window);
|
||||
} else {
|
||||
let window_size = toplevel.current_state().size.unwrap_or((0, 0).into());
|
||||
let output_size: Size<i32, _> = self
|
||||
.output
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.current_mode()
|
||||
.unwrap()
|
||||
.size
|
||||
.to_f64()
|
||||
.to_logical(
|
||||
self.output
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.current_scale()
|
||||
.fractional_scale(),
|
||||
)
|
||||
.to_i32_round();
|
||||
let loc = (
|
||||
(output_size.w / 2) - (window_size.w / 2),
|
||||
(output_size.h / 2) - (window_size.h / 2),
|
||||
);
|
||||
self.space.map_element(window.clone(), loc, true);
|
||||
self.seat.get_keyboard().unwrap().set_focus(
|
||||
self,
|
||||
Some(FocusTarget::from(window)),
|
||||
SERIAL_COUNTER.next_serial(),
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(popup) = self.popups.find_popup(surface) {
|
||||
let PopupKind::Xdg(ref popup) = popup;
|
||||
let initial_configure_sent = with_states(surface, |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<XdgPopupSurfaceData>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.initial_configure_sent
|
||||
});
|
||||
if !initial_configure_sent {
|
||||
// NOTE: This should never fail as the initial configure is always
|
||||
// allowed.
|
||||
popup.send_configure().expect("initial configure failed");
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
delegate_compositor!(State);
|
||||
18
wayland-display-core/src/wayland/handlers/data_device.rs
Normal file
18
wayland-display-core/src/wayland/handlers/data_device.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use smithay::{
|
||||
delegate_data_device,
|
||||
wayland::data_device::{
|
||||
ClientDndGrabHandler, DataDeviceHandler, DataDeviceState, ServerDndGrabHandler,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::comp::State;
|
||||
|
||||
impl ServerDndGrabHandler for State {}
|
||||
impl ClientDndGrabHandler for State {}
|
||||
impl DataDeviceHandler for State {
|
||||
fn data_device_state(&self) -> &DataDeviceState {
|
||||
&self.data_device_state
|
||||
}
|
||||
}
|
||||
|
||||
delegate_data_device!(State);
|
||||
26
wayland-display-core/src/wayland/handlers/dmabuf.rs
Normal file
26
wayland-display-core/src/wayland/handlers/dmabuf.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use smithay::{
|
||||
backend::{allocator::dmabuf::Dmabuf, renderer::ImportDma},
|
||||
delegate_dmabuf,
|
||||
wayland::dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportError},
|
||||
};
|
||||
|
||||
use crate::comp::State;
|
||||
|
||||
impl DmabufHandler for State {
|
||||
fn dmabuf_state(&mut self) -> &mut DmabufState {
|
||||
&mut self.dmabuf_state
|
||||
}
|
||||
|
||||
fn dmabuf_imported(
|
||||
&mut self,
|
||||
_global: &DmabufGlobal,
|
||||
dmabuf: Dmabuf,
|
||||
) -> Result<(), ImportError> {
|
||||
self.renderer
|
||||
.import_dmabuf(&dmabuf, None)
|
||||
.map(|_| ())
|
||||
.map_err(|_| ImportError::Failed)
|
||||
}
|
||||
}
|
||||
|
||||
delegate_dmabuf!(State);
|
||||
11
wayland-display-core/src/wayland/handlers/mod.rs
Normal file
11
wayland-display-core/src/wayland/handlers/mod.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
pub mod compositor;
|
||||
pub mod data_device;
|
||||
pub mod dmabuf;
|
||||
pub mod output;
|
||||
pub mod presentation;
|
||||
pub mod relative_pointer;
|
||||
pub mod seat;
|
||||
pub mod shm;
|
||||
pub mod viewporter;
|
||||
pub mod wl_drm;
|
||||
pub mod xdg;
|
||||
5
wayland-display-core/src/wayland/handlers/output.rs
Normal file
5
wayland-display-core/src/wayland/handlers/output.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
use smithay::delegate_output;
|
||||
|
||||
use crate::comp::State;
|
||||
|
||||
delegate_output!(State);
|
||||
@@ -0,0 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::comp::State;
|
||||
use smithay::delegate_presentation;
|
||||
|
||||
delegate_presentation!(State);
|
||||
@@ -0,0 +1,5 @@
|
||||
use smithay::delegate_relative_pointer;
|
||||
|
||||
use crate::comp::State;
|
||||
|
||||
delegate_relative_pointer!(State);
|
||||
35
wayland-display-core/src/wayland/handlers/seat.rs
Normal file
35
wayland-display-core/src/wayland/handlers/seat.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use smithay::{
|
||||
delegate_seat,
|
||||
input::{pointer::CursorImageStatus, Seat, SeatHandler, SeatState},
|
||||
reexports::wayland_server::Resource,
|
||||
wayland::data_device::set_data_device_focus,
|
||||
};
|
||||
|
||||
use crate::comp::{FocusTarget, State};
|
||||
|
||||
impl SeatHandler for State {
|
||||
type KeyboardFocus = FocusTarget;
|
||||
type PointerFocus = FocusTarget;
|
||||
|
||||
fn seat_state(&mut self) -> &mut SeatState<Self> {
|
||||
&mut self.seat_state
|
||||
}
|
||||
|
||||
fn focus_changed(&mut self, seat: &Seat<Self>, focus: Option<&Self::KeyboardFocus>) {
|
||||
if let Some(surface) = focus {
|
||||
let client = match surface {
|
||||
FocusTarget::Wayland(w) => w.toplevel().wl_surface().client(),
|
||||
FocusTarget::Popup(p) => p.wl_surface().client(),
|
||||
};
|
||||
set_data_device_focus(&self.dh, seat, client);
|
||||
} else {
|
||||
set_data_device_focus(&self.dh, seat, None);
|
||||
}
|
||||
}
|
||||
|
||||
fn cursor_image(&mut self, _seat: &Seat<Self>, image: CursorImageStatus) {
|
||||
self.cursor_state = image;
|
||||
}
|
||||
}
|
||||
|
||||
delegate_seat!(State);
|
||||
14
wayland-display-core/src/wayland/handlers/shm.rs
Normal file
14
wayland-display-core/src/wayland/handlers/shm.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use smithay::{
|
||||
delegate_shm,
|
||||
wayland::shm::{ShmHandler, ShmState},
|
||||
};
|
||||
|
||||
use crate::comp::State;
|
||||
|
||||
impl ShmHandler for State {
|
||||
fn shm_state(&self) -> &ShmState {
|
||||
&self.shm_state
|
||||
}
|
||||
}
|
||||
|
||||
delegate_shm!(State);
|
||||
5
wayland-display-core/src/wayland/handlers/viewporter.rs
Normal file
5
wayland-display-core/src/wayland/handlers/viewporter.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
use smithay::delegate_viewporter;
|
||||
|
||||
use crate::comp::State;
|
||||
|
||||
delegate_viewporter!(State);
|
||||
3
wayland-display-core/src/wayland/handlers/wl_drm.rs
Normal file
3
wayland-display-core/src/wayland/handlers/wl_drm.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
use crate::{comp::State, wayland::protocols::wl_drm::delegate_wl_drm};
|
||||
|
||||
delegate_wl_drm!(State);
|
||||
83
wayland-display-core/src/wayland/handlers/xdg.rs
Normal file
83
wayland-display-core/src/wayland/handlers/xdg.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use smithay::{
|
||||
delegate_xdg_shell,
|
||||
desktop::{
|
||||
find_popup_root_surface, PopupKeyboardGrab, PopupKind, PopupPointerGrab,
|
||||
PopupUngrabStrategy, Window,
|
||||
},
|
||||
input::{pointer::Focus, Seat},
|
||||
reexports::wayland_server::protocol::wl_seat::WlSeat,
|
||||
utils::Serial,
|
||||
wayland::{
|
||||
seat::WaylandFocus,
|
||||
shell::xdg::{
|
||||
PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::comp::{FocusTarget, State};
|
||||
|
||||
impl XdgShellHandler for State {
|
||||
fn xdg_shell_state(&mut self) -> &mut XdgShellState {
|
||||
&mut self.shell_state
|
||||
}
|
||||
|
||||
fn new_toplevel(&mut self, surface: ToplevelSurface) {
|
||||
let window = Window::new(surface);
|
||||
self.pending_windows.push(window);
|
||||
}
|
||||
|
||||
fn new_popup(&mut self, surface: PopupSurface, positioner: PositionerState) {
|
||||
// TODO: properly recompute the geometry with the whole of positioner state
|
||||
surface.with_pending_state(|state| {
|
||||
// NOTE: This is not really necessary as the default geometry
|
||||
// is already set the same way, but for demonstrating how
|
||||
// to set the initial popup geometry this code is left as
|
||||
// an example
|
||||
state.geometry = positioner.get_geometry();
|
||||
});
|
||||
if let Err(err) = self.popups.track_popup(PopupKind::from(surface)) {
|
||||
tracing::warn!(?err, "Failed to track popup.");
|
||||
}
|
||||
}
|
||||
|
||||
fn grab(&mut self, surface: PopupSurface, seat: WlSeat, serial: Serial) {
|
||||
let seat: Seat<State> = Seat::from_resource(&seat).unwrap();
|
||||
let kind = PopupKind::Xdg(surface.clone());
|
||||
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(FocusTarget::from)
|
||||
}) {
|
||||
let ret = self.popups.grab_popup(root, surface.into(), &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(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::Clear);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate_xdg_shell!(State);
|
||||
2
wayland-display-core/src/wayland/mod.rs
Normal file
2
wayland-display-core/src/wayland/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod handlers;
|
||||
pub mod protocols;
|
||||
1
wayland-display-core/src/wayland/protocols/mod.rs
Normal file
1
wayland-display-core/src/wayland/protocols/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod wl_drm;
|
||||
255
wayland-display-core/src/wayland/protocols/wl_drm.rs
Normal file
255
wayland-display-core/src/wayland/protocols/wl_drm.rs
Normal file
@@ -0,0 +1,255 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
// Re-export only the actual code, and then only use this re-export
|
||||
// The `generated` module below is just some boilerplate to properly isolate stuff
|
||||
// and avoid exposing internal details.
|
||||
//
|
||||
// You can use all the types from my_protocol as if they went from `wayland_client::protocol`.
|
||||
pub use generated::wl_drm;
|
||||
|
||||
mod generated {
|
||||
use smithay::reexports::wayland_server::{self, protocol::*};
|
||||
|
||||
pub mod __interfaces {
|
||||
use smithay::reexports::wayland_server::protocol::__interfaces::*;
|
||||
use wayland_backend;
|
||||
wayland_scanner::generate_interfaces!("resources/protocols/wayland-drm.xml");
|
||||
}
|
||||
use self::__interfaces::*;
|
||||
|
||||
wayland_scanner::generate_server_code!("resources/protocols/wayland-drm.xml");
|
||||
}
|
||||
|
||||
use smithay::{
|
||||
backend::allocator::{
|
||||
dmabuf::{Dmabuf, DmabufFlags},
|
||||
Format, Fourcc, Modifier,
|
||||
},
|
||||
reexports::wayland_server::{
|
||||
backend::GlobalId, protocol::wl_buffer::WlBuffer, Client, DataInit, Dispatch,
|
||||
DisplayHandle, GlobalDispatch, New, Resource,
|
||||
},
|
||||
wayland::{
|
||||
buffer::BufferHandler,
|
||||
dmabuf::{DmabufGlobal, DmabufHandler, ImportError},
|
||||
},
|
||||
};
|
||||
|
||||
use std::{convert::TryFrom, path::PathBuf, sync::Arc};
|
||||
|
||||
pub struct WlDrmState;
|
||||
|
||||
/// Data associated with a drm global.
|
||||
pub struct DrmGlobalData {
|
||||
filter: Box<dyn for<'a> Fn(&'a Client) -> bool + Send + Sync>,
|
||||
formats: Arc<Vec<Fourcc>>,
|
||||
device_path: PathBuf,
|
||||
dmabuf_global: DmabufGlobal,
|
||||
}
|
||||
|
||||
pub struct DrmInstanceData {
|
||||
formats: Arc<Vec<Fourcc>>,
|
||||
dmabuf_global: DmabufGlobal,
|
||||
}
|
||||
|
||||
impl<D> GlobalDispatch<wl_drm::WlDrm, DrmGlobalData, D> for WlDrmState
|
||||
where
|
||||
D: GlobalDispatch<wl_drm::WlDrm, DrmGlobalData>
|
||||
+ Dispatch<wl_drm::WlDrm, DrmInstanceData>
|
||||
+ BufferHandler
|
||||
+ DmabufHandler
|
||||
+ 'static,
|
||||
{
|
||||
fn bind(
|
||||
_state: &mut D,
|
||||
_dh: &DisplayHandle,
|
||||
_client: &Client,
|
||||
resource: New<wl_drm::WlDrm>,
|
||||
global_data: &DrmGlobalData,
|
||||
data_init: &mut DataInit<'_, D>,
|
||||
) {
|
||||
let data = DrmInstanceData {
|
||||
formats: global_data.formats.clone(),
|
||||
dmabuf_global: global_data.dmabuf_global.clone(),
|
||||
};
|
||||
let drm_instance = data_init.init(resource, data);
|
||||
|
||||
drm_instance.device(global_data.device_path.to_string_lossy().into_owned());
|
||||
if drm_instance.version() >= 2 {
|
||||
drm_instance.capabilities(wl_drm::Capability::Prime as u32);
|
||||
}
|
||||
for format in global_data.formats.iter() {
|
||||
if let Ok(converted) = wl_drm::Format::try_from(*format as u32) {
|
||||
drm_instance.format(converted as u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn can_view(client: Client, global_data: &DrmGlobalData) -> bool {
|
||||
(global_data.filter)(&client)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> Dispatch<wl_drm::WlDrm, DrmInstanceData, D> for WlDrmState
|
||||
where
|
||||
D: GlobalDispatch<wl_drm::WlDrm, DrmGlobalData>
|
||||
+ Dispatch<wl_drm::WlDrm, DrmInstanceData>
|
||||
+ Dispatch<WlBuffer, Dmabuf>
|
||||
+ BufferHandler
|
||||
+ DmabufHandler
|
||||
+ 'static,
|
||||
{
|
||||
fn request(
|
||||
state: &mut D,
|
||||
_client: &Client,
|
||||
drm: &wl_drm::WlDrm,
|
||||
request: wl_drm::Request,
|
||||
data: &DrmInstanceData,
|
||||
_dh: &DisplayHandle,
|
||||
data_init: &mut DataInit<'_, D>,
|
||||
) {
|
||||
match request {
|
||||
wl_drm::Request::Authenticate { .. } => drm.authenticated(),
|
||||
wl_drm::Request::CreateBuffer { .. } => drm.post_error(
|
||||
wl_drm::Error::InvalidName,
|
||||
String::from("Flink handles are unsupported, use PRIME"),
|
||||
),
|
||||
wl_drm::Request::CreatePlanarBuffer { .. } => drm.post_error(
|
||||
wl_drm::Error::InvalidName,
|
||||
String::from("Flink handles are unsupported, use PRIME"),
|
||||
),
|
||||
wl_drm::Request::CreatePrimeBuffer {
|
||||
id,
|
||||
name,
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
offset0,
|
||||
stride0,
|
||||
..
|
||||
} => {
|
||||
let format = match Fourcc::try_from(format) {
|
||||
Ok(format) => {
|
||||
if !data.formats.contains(&format) {
|
||||
drm.post_error(
|
||||
wl_drm::Error::InvalidFormat,
|
||||
String::from("Format not advertised by wl_drm"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
format
|
||||
}
|
||||
Err(_) => {
|
||||
drm.post_error(
|
||||
wl_drm::Error::InvalidFormat,
|
||||
String::from("Format unknown / not advertised by wl_drm"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if width < 1 || height < 1 {
|
||||
drm.post_error(
|
||||
wl_drm::Error::InvalidFormat,
|
||||
String::from("width or height not positive"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut dma = Dmabuf::builder((width, height), format, DmabufFlags::empty());
|
||||
dma.add_plane(name, 0, offset0 as u32, stride0 as u32, Modifier::Invalid);
|
||||
match dma.build() {
|
||||
Some(dmabuf) => {
|
||||
match state.dmabuf_imported(&data.dmabuf_global, dmabuf.clone()) {
|
||||
Ok(_) => {
|
||||
// import was successful
|
||||
data_init.init(id, dmabuf);
|
||||
}
|
||||
|
||||
Err(ImportError::InvalidFormat) => {
|
||||
drm.post_error(
|
||||
wl_drm::Error::InvalidFormat,
|
||||
"format and plane combination are not valid",
|
||||
);
|
||||
}
|
||||
|
||||
Err(ImportError::Failed) => {
|
||||
// Buffer import failed. The protocol documentation heavily implies killing the
|
||||
// client is the right thing to do here.
|
||||
drm.post_error(wl_drm::Error::InvalidName, "buffer import failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// Buffer import failed. The protocol documentation heavily implies killing the
|
||||
// client is the right thing to do here.
|
||||
drm.post_error(
|
||||
wl_drm::Error::InvalidName,
|
||||
"dmabuf global was destroyed on server",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_drm_global<D>(
|
||||
display: &DisplayHandle,
|
||||
device_path: PathBuf,
|
||||
formats: Vec<Format>,
|
||||
dmabuf_global: &DmabufGlobal,
|
||||
) -> GlobalId
|
||||
where
|
||||
D: GlobalDispatch<wl_drm::WlDrm, DrmGlobalData>
|
||||
+ Dispatch<wl_drm::WlDrm, DrmInstanceData>
|
||||
+ BufferHandler
|
||||
+ DmabufHandler
|
||||
+ 'static,
|
||||
{
|
||||
create_drm_global_with_filter::<D, _>(display, device_path, formats, dmabuf_global, |_| true)
|
||||
}
|
||||
|
||||
pub fn create_drm_global_with_filter<D, F>(
|
||||
display: &DisplayHandle,
|
||||
device_path: PathBuf,
|
||||
formats: Vec<Format>,
|
||||
dmabuf_global: &DmabufGlobal,
|
||||
client_filter: F,
|
||||
) -> GlobalId
|
||||
where
|
||||
D: GlobalDispatch<wl_drm::WlDrm, DrmGlobalData>
|
||||
+ Dispatch<wl_drm::WlDrm, DrmInstanceData>
|
||||
+ BufferHandler
|
||||
+ DmabufHandler
|
||||
+ 'static,
|
||||
F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static,
|
||||
{
|
||||
let formats = Arc::new(
|
||||
formats
|
||||
.into_iter()
|
||||
.filter(|f| f.modifier == Modifier::Invalid)
|
||||
.map(|f| f.code)
|
||||
.collect(),
|
||||
);
|
||||
let data = DrmGlobalData {
|
||||
filter: Box::new(client_filter),
|
||||
formats,
|
||||
device_path,
|
||||
dmabuf_global: dmabuf_global.clone(),
|
||||
};
|
||||
|
||||
display.create_global::<D, wl_drm::WlDrm, _>(2, data)
|
||||
}
|
||||
|
||||
macro_rules! delegate_wl_drm {
|
||||
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
|
||||
smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
|
||||
$crate::wayland::protocols::wl_drm::wl_drm::WlDrm: $crate::wayland::protocols::wl_drm::DrmGlobalData
|
||||
] => $crate::wayland::protocols::wl_drm::WlDrmState);
|
||||
smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
|
||||
$crate::wayland::protocols::wl_drm::wl_drm::WlDrm: $crate::wayland::protocols::wl_drm::DrmInstanceData
|
||||
] => $crate::wayland::protocols::wl_drm::WlDrmState);
|
||||
};
|
||||
}
|
||||
pub(crate) use delegate_wl_drm;
|
||||
Reference in New Issue
Block a user