This commit is contained in:
Wanjohi
2023-11-25 18:16:42 +03:00
parent 65fc63b325
commit 9140dbf081
90 changed files with 15051 additions and 265 deletions

View 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"
]

View 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.

View 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
```

Binary file not shown.

View 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>

View 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),
}
}
}

View 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;
}
}
}
}

View 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.");
}
}

View 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))
}
}

View 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");
};
}
}

View File

@@ -0,0 +1,3 @@
mod target;
pub use self::target::*;

View 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(),
}
}
}

View 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);

View 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);

View 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);

View 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;

View File

@@ -0,0 +1,5 @@
use smithay::delegate_output;
use crate::comp::State;
delegate_output!(State);

View File

@@ -0,0 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::comp::State;
use smithay::delegate_presentation;
delegate_presentation!(State);

View File

@@ -0,0 +1,5 @@
use smithay::delegate_relative_pointer;
use crate::comp::State;
delegate_relative_pointer!(State);

View 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);

View 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);

View File

@@ -0,0 +1,5 @@
use smithay::delegate_viewporter;
use crate::comp::State;
delegate_viewporter!(State);

View File

@@ -0,0 +1,3 @@
use crate::{comp::State, wayland::protocols::wl_drm::delegate_wl_drm};
delegate_wl_drm!(State);

View 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);

View File

@@ -0,0 +1,2 @@
pub mod handlers;
pub mod protocols;

View File

@@ -0,0 +1 @@
pub mod wl_drm;

View 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;