Merge pull request #116 from Smithay/refactor/backend

Refactor drm backend (v3)
This commit is contained in:
Victor Brekenfeld 2018-12-09 16:25:42 +01:00 committed by GitHub
commit 60bb5e8d5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 3408 additions and 2515 deletions

View File

@ -36,11 +36,15 @@ env:
# test individual features
- FEATURES="backend_winit"
- FEATURES="backend_drm"
- FEATURES="backend_drm_legacy"
- FEATURES="backend_drm_gbm"
- FEATURES="backend_drm_egl"
- FEATURES="backend_egl"
- FEATURES="backend_libinput"
- FEATURES="backend_udev"
- FEATURES="backend_session"
- FEATURES="backend_session_udev"
- FEATURES="backend_session_logind"
- FEATURES="renderer_gl"
- FEATURES="renderer_glium"
- FEATURES="xwayland"
# test default features

View File

@ -10,9 +10,9 @@ repository = "https://github.com/Smithay/smithay"
members = [ "anvil" ]
[dependencies]
wayland-server = "0.21.1"
wayland-sys = "0.21.1"
wayland-server = "0.21.6"
wayland-commons = "0.21.1"
wayland-sys = { version = "0.21.6", optional = true }
nix = "0.11"
xkbcommon = "0.2.1"
tempfile = "2.1.5"
@ -21,29 +21,37 @@ slog-stdlog = "3.0.2"
libloading = "0.4.0"
wayland-client = { version = "0.21.1", features = ["egl"], optional = true }
winit = { version = "0.18.0", optional = true }
drm = { version = "^0.3.1", optional = true }
gbm = { version = "^0.4.0", optional = true, default-features = false, features = ["drm-support"] }
drm = { version = "^0.3.4", optional = true }
gbm = { version = "^0.5.0", optional = true, default-features = false, features = ["drm-support"] }
glium = { version = "0.19.0", optional = true, default-features = false }
input = { version = "0.4.0", optional = true }
input = { version = "0.4.1", optional = true }
udev = { version = "0.2.0", optional = true }
dbus = { version = "0.6.1", optional = true }
systemd = { version = "^0.2.0", optional = true }
wayland-protocols = { version = "0.21.1", features = ["unstable_protocols", "native_server"] }
wayland-protocols = { version = "0.21.3", features = ["unstable_protocols", "server"] }
image = "0.17.0"
error-chain = "0.11.0"
lazy_static = "1.0.0"
[dev-dependencies]
slog-term = "2.3"
[build-dependencies]
gl_generator = "0.9"
gl_generator = { version = "0.9", optional = true }
[features]
default = ["backend_winit", "backend_drm", "backend_libinput", "backend_udev", "renderer_glium", "xwayland"]
backend_winit = ["winit", "wayland-server/dlopen", "wayland-client/dlopen"]
backend_drm = ["drm", "gbm"]
default = ["backend_winit", "backend_drm_legacy", "backend_drm_gbm", "backend_drm_egl", "backend_libinput", "backend_udev", "backend_session", "renderer_glium", "xwayland"]
backend_winit = ["winit", "wayland-server/dlopen", "wayland-client/dlopen", "backend_egl", "renderer_gl", "native_lib"]
backend_drm = ["drm"]
backend_drm_legacy = ["backend_drm"]
backend_drm_gbm = ["backend_drm", "gbm"]
backend_drm_egl = ["backend_drm", "backend_egl"]
backend_egl = ["gl_generator"]
backend_libinput = ["input"]
backend_session = []
backend_session_udev = ["udev", "backend_session"]
backend_udev = ["udev"]
backend_session_logind = ["dbus", "systemd", "backend_session"]
backend_udev = ["udev", "backend_drm", "backend_session_udev"]
renderer_glium = ["glium"]
renderer_gl = ["gl_generator"]
renderer_glium = ["renderer_gl", "glium"]
native_lib = ["wayland-sys", "wayland-server/native_lib", "wayland-protocols/native_server"]
xwayland = []

View File

@ -23,8 +23,8 @@ features = [ "renderer_glium" ]
gl_generator = "0.9"
[features]
default = [ "winit", "tty_launch", "udev" ]
default = [ "winit", "egl", "udev" ]
egl = [ "smithay/native_lib" ]
winit = [ "smithay/backend_winit" ]
tty_launch = [ "smithay/backend_libinput", "smithay/backend_drm" ]
udev = [ "tty_launch", "smithay/backend_udev" ]
udev = [ "smithay/backend_libinput", "smithay/backend_drm_legacy", "smithay/backend_drm_gbm", "smithay/backend_drm_egl", "smithay/backend_udev", "smithay/backend_session" ]
logind = [ "smithay/backend_session_logind" ]

View File

@ -11,20 +11,18 @@ use glium::{
};
use slog::Logger;
#[cfg(feature = "egl")]
use smithay::backend::egl::EGLDisplay;
use smithay::{
backend::graphics::{
egl::{
error::Result as EGLResult,
wayland::{BufferAccessError, EGLDisplay, EGLImages, EGLWaylandExtensions, Format},
EGLGraphicsBackend,
},
glium::GliumGraphicsBackend,
backend::{
egl::{BufferAccessError, EGLImages, Format},
graphics::{gl::GLGraphicsBackend, glium::GliumGraphicsBackend},
},
wayland::{
compositor::{roles::Role, SubsurfaceRole, TraversalAction},
shm::with_buffer_contents as shm_buffer_contents,
},
wayland_server::{protocol::wl_buffer, Display, Resource},
wayland_server::{protocol::wl_buffer, Resource},
};
use shaders;
@ -38,22 +36,24 @@ struct Vertex {
implement_vertex!(Vertex, position, tex_coords);
pub struct GliumDrawer<F: EGLGraphicsBackend + 'static> {
pub struct GliumDrawer<F: GLGraphicsBackend + 'static> {
display: GliumGraphicsBackend<F>,
vertex_buffer: glium::VertexBuffer<Vertex>,
index_buffer: glium::IndexBuffer<u16>,
programs: [glium::Program; shaders::FRAGMENT_COUNT],
#[cfg(feature = "egl")]
egl_display: Rc<RefCell<Option<EGLDisplay>>>,
log: Logger,
}
impl<F: EGLGraphicsBackend + 'static> GliumDrawer<F> {
impl<F: GLGraphicsBackend + 'static> GliumDrawer<F> {
pub fn borrow(&self) -> Ref<F> {
self.display.borrow()
}
}
impl<T: Into<GliumGraphicsBackend<T>> + EGLGraphicsBackend + 'static> GliumDrawer<T> {
impl<T: Into<GliumGraphicsBackend<T>> + GLGraphicsBackend + 'static> GliumDrawer<T> {
#[cfg(feature = "egl")]
pub fn init(backend: T, egl_display: Rc<RefCell<Option<EGLDisplay>>>, log: Logger) -> GliumDrawer<T> {
let display = backend.into();
@ -78,7 +78,8 @@ impl<T: Into<GliumGraphicsBackend<T>> + EGLGraphicsBackend + 'static> GliumDrawe
tex_coords: [1.0, 0.0],
},
],
).unwrap();
)
.unwrap();
// building the index buffer
let index_buffer =
@ -95,9 +96,53 @@ impl<T: Into<GliumGraphicsBackend<T>> + EGLGraphicsBackend + 'static> GliumDrawe
log,
}
}
#[cfg(not(feature = "egl"))]
pub fn init(backend: T, log: Logger) -> GliumDrawer<T> {
let display = backend.into();
// building the vertex buffer, which contains all the vertices that we will draw
let vertex_buffer = glium::VertexBuffer::new(
&display,
&[
Vertex {
position: [0.0, 0.0],
tex_coords: [0.0, 0.0],
},
Vertex {
position: [0.0, 1.0],
tex_coords: [0.0, 1.0],
},
Vertex {
position: [1.0, 1.0],
tex_coords: [1.0, 1.0],
},
Vertex {
position: [1.0, 0.0],
tex_coords: [1.0, 0.0],
},
],
)
.unwrap();
// building the index buffer
let index_buffer =
glium::IndexBuffer::new(&display, PrimitiveType::TriangleStrip, &[1 as u16, 2, 0, 3]).unwrap();
let programs = opengl_programs!(&display);
GliumDrawer {
display,
vertex_buffer,
index_buffer,
programs,
log,
}
}
}
impl<F: EGLGraphicsBackend + 'static> GliumDrawer<F> {
impl<F: GLGraphicsBackend + 'static> GliumDrawer<F> {
#[cfg(feature = "egl")]
pub fn texture_from_buffer(&self, buffer: Resource<wl_buffer::WlBuffer>) -> Result<TextureMetadata, ()> {
// try to retrieve the egl contents of this buffer
let images = if let Some(display) = &self.egl_display.borrow().as_ref() {
@ -122,7 +167,8 @@ impl<F: EGLGraphicsBackend + 'static> GliumDrawer<F> {
MipmapsOption::NoMipmap,
images.width,
images.height,
).unwrap();
)
.unwrap();
unsafe {
images
.bind_to_texture(0, opengl_texture.get_id())
@ -138,26 +184,7 @@ impl<F: EGLGraphicsBackend + 'static> GliumDrawer<F> {
}
Err(BufferAccessError::NotManaged(buffer)) => {
// this is not an EGL buffer, try SHM
match shm_buffer_contents(&buffer, |slice, data| {
::shm_load::load_shm_buffer(data, slice)
.map(|(image, kind)| (Texture2d::new(&self.display, image).unwrap(), kind, data))
}) {
Ok(Ok((texture, kind, data))) => Ok(TextureMetadata {
texture,
fragment: kind,
y_inverted: false,
dimensions: (data.width as u32, data.height as u32),
images: None,
}),
Ok(Err(format)) => {
warn!(self.log, "Unsupported SHM buffer format"; "format" => format!("{:?}", format));
Err(())
}
Err(err) => {
warn!(self.log, "Unable to load buffer contents"; "err" => format!("{:?}", err));
Err(())
}
}
self.texture_from_shm_buffer(buffer)
}
Err(err) => {
error!(self.log, "EGL error"; "err" => format!("{:?}", err));
@ -166,6 +193,35 @@ impl<F: EGLGraphicsBackend + 'static> GliumDrawer<F> {
}
}
#[cfg(not(feature = "egl"))]
pub fn texture_from_buffer(&self, buffer: Resource<wl_buffer::WlBuffer>) -> Result<TextureMetadata, ()> {
self.texture_from_shm_buffer(buffer)
}
fn texture_from_shm_buffer(&self, buffer: Resource<wl_buffer::WlBuffer>) -> Result<TextureMetadata, ()> {
match shm_buffer_contents(&buffer, |slice, data| {
::shm_load::load_shm_buffer(data, slice)
.map(|(image, kind)| (Texture2d::new(&self.display, image).unwrap(), kind, data))
}) {
Ok(Ok((texture, kind, data))) => Ok(TextureMetadata {
texture,
fragment: kind,
y_inverted: false,
dimensions: (data.width as u32, data.height as u32),
#[cfg(feature = "egl")]
images: None,
}),
Ok(Err(format)) => {
warn!(self.log, "Unsupported SHM buffer format"; "format" => format!("{:?}", format));
Err(())
}
Err(err) => {
warn!(self.log, "Unable to load buffer contents"; "err" => format!("{:?}", err));
Err(())
}
}
}
pub fn render_texture(
&self,
target: &mut glium::Frame,
@ -208,7 +264,8 @@ impl<F: EGLGraphicsBackend + 'static> GliumDrawer<F> {
blend: blending,
..Default::default()
},
).unwrap();
)
.unwrap();
}
#[inline]
@ -217,21 +274,16 @@ impl<F: EGLGraphicsBackend + 'static> GliumDrawer<F> {
}
}
impl<G: EGLWaylandExtensions + EGLGraphicsBackend + 'static> EGLWaylandExtensions for GliumDrawer<G> {
fn bind_wl_display(&self, display: &Display) -> EGLResult<EGLDisplay> {
self.display.bind_wl_display(display)
}
}
pub struct TextureMetadata {
pub texture: Texture2d,
pub fragment: usize,
pub y_inverted: bool,
pub dimensions: (u32, u32),
#[cfg(feature = "egl")]
images: Option<EGLImages>,
}
impl<F: EGLGraphicsBackend + 'static> GliumDrawer<F> {
impl<F: GLGraphicsBackend + 'static> GliumDrawer<F> {
pub fn draw_windows(&self, window_map: &MyWindowMap, compositor_token: MyCompositorToken, log: &Logger) {
let mut frame = self.draw();
frame.clear(None, Some((0.8, 0.8, 0.9, 1.0)), false, Some(1.0), None);
@ -290,7 +342,8 @@ impl<F: EGLGraphicsBackend + 'static> GliumDrawer<F> {
TraversalAction::SkipChildren
}
},
).unwrap();
)
.unwrap();
}
});
}

View File

@ -11,7 +11,7 @@ use std::{
use slog::Logger;
#[cfg(feature = "udev")]
use smithay::backend::session::auto::AutoSession;
use smithay::backend::session::{auto::AutoSession, Session};
use smithay::{
backend::input::{
self, Event, InputBackend, InputHandler, KeyState, KeyboardKeyEvent, PointerAxisEvent,
@ -131,13 +131,15 @@ impl<B: InputBackend> InputHandler<B> for AnvilInputHandler {
info!(self.log, "Quitting.");
self.running.store(false, Ordering::SeqCst);
}
#[cfg(feature = "tty_lauch")]
KeyAction::VtSwitch(vt) => if let Some(ref mut session) = self.session {
info!(log, "Trying to switch to vt {}", vt);
if let Err(err) = session.change_vt(vt) {
error!(log, "Error switching to vt {}: {}", vt, err);
#[cfg(feature = "udev")]
KeyAction::VtSwitch(vt) => {
if let Some(ref mut session) = self.session {
info!(log, "Trying to switch to vt {}", vt);
if let Err(err) = session.change_vt(vt) {
error!(log, "Error switching to vt {}: {}", vt, err);
}
}
},
}
KeyAction::Run(cmd) => {
info!(self.log, "Starting program"; "cmd" => cmd.clone());
if let Err(e) = Command::new(&cmd).spawn() {

View File

@ -16,8 +16,6 @@ use smithay::wayland_server::{calloop::EventLoop, Display};
mod shaders;
mod glium_drawer;
mod input_handler;
#[cfg(feature = "tty_launch")]
mod raw_drm;
mod shell;
mod shm_load;
#[cfg(feature = "udev")]
@ -29,8 +27,6 @@ mod winit;
static POSSIBLE_BACKENDS: &'static [&'static str] = &[
#[cfg(feature = "winit")]
"--winit : Run anvil as a X11 or Wayland client using winit.",
#[cfg(feature = "tty_launch")]
"--tty-raw : Run anvil as a raw DRM client (requires root).",
#[cfg(feature = "udev")]
"--tty-udev : Run anvil as a tty udev client (requires root if without logind).",
];
@ -54,13 +50,6 @@ fn main() {
crit!(log, "Failed to initialize winit backend.");
}
}
#[cfg(feature = "tty_launch")]
Some("--tty-raw") => {
info!(log, "Starting anvil on a tty using raw DRM");
if let Err(()) = raw_drm::run_raw_drm(display, event_loop, log.clone()) {
crit!(log, "Failed to initialize tty backend.");
}
}
#[cfg(feature = "udev")]
Some("--tty-udev") => {
info!(log, "Starting anvil on a tty using udev");

View File

@ -1,169 +0,0 @@
use std::{
cell::RefCell,
fs::{File, OpenOptions},
os::unix::io::{AsRawFd, RawFd},
rc::Rc,
time::Duration,
};
use smithay::{
backend::{
drm::{drm_device_bind, DrmBackend, DrmDevice, DrmHandler},
graphics::egl::wayland::EGLWaylandExtensions,
},
drm::{
control::{
connector::{Info as ConnectorInfo, State as ConnectorState},
crtc,
encoder::Info as EncoderInfo,
Device as ControlDevice, ResourceInfo,
},
result::Error as DrmError,
Device as BasicDevice,
},
wayland::{compositor::CompositorToken, shm::init_shm_global},
wayland_server::{calloop::EventLoop, Display},
};
use glium::Surface;
use slog::Logger;
use glium_drawer::GliumDrawer;
use shell::{init_shell, MyWindowMap, Roles, SurfaceData};
#[derive(Debug)]
pub struct Card(File);
impl AsRawFd for Card {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
impl BasicDevice for Card {}
impl ControlDevice for Card {}
pub fn run_raw_drm(mut display: Display, mut event_loop: EventLoop<()>, log: Logger) -> Result<(), ()> {
/*
* Initialize the drm backend
*/
// "Find" a suitable drm device
let mut options = OpenOptions::new();
options.read(true);
options.write(true);
let mut device =
DrmDevice::new(Card(options.clone().open("/dev/dri/card0").unwrap()), log.clone()).unwrap();
// Get a set of all modesetting resource handles (excluding planes):
let res_handles = device.resource_handles().unwrap();
// Use first connected connector
let connector_info = res_handles
.connectors()
.iter()
.map(|conn| ConnectorInfo::load_from_device(&device, *conn).unwrap())
.find(|conn| conn.connection_state() == ConnectorState::Connected)
.unwrap();
// Use the first encoder
let encoder_info = EncoderInfo::load_from_device(&device, connector_info.encoders()[0]).unwrap();
// use the connected crtc if any
let crtc = encoder_info
.current_crtc()
// or use the first one that is compatible with the encoder
.unwrap_or_else(|| {
*res_handles
.filter_crtcs(encoder_info.possible_crtcs())
.iter()
.next()
.unwrap()
});
// Assuming we found a good connector and loaded the info into `connector_info`
let mode = connector_info.modes()[0]; // Use first mode (usually highest resoltion, but in reality you should filter and sort and check and match with other connectors, if you use more then one.)
// Initialize the hardware backend
let backend = device
.create_backend(crtc, mode, vec![connector_info.handle()])
.unwrap();
let egl_display = Rc::new(RefCell::new(
if let Ok(egl_display) = backend.bind_wl_display(&display) {
info!(log, "EGL hardware-acceleration enabled");
Some(egl_display)
} else {
None
},
));
let renderer = GliumDrawer::init(backend, egl_display, log.clone());
{
/*
* Initialize Glium
*/
let mut frame = renderer.draw();
frame.clear_color(0.8, 0.8, 0.9, 1.0);
frame.finish().unwrap();
}
/*
* Initialize the globals
*/
init_shm_global(&mut display, vec![], log.clone());
let (compositor_token, _, _, window_map) = init_shell(&mut display, log.clone());
/*
* Add a listening socket:
*/
let name = display.add_socket_auto().unwrap().into_string().unwrap();
println!("Listening on socket: {}", name);
/*
* Register the DrmDevice on the EventLoop
*/
let _source = drm_device_bind(
&event_loop.handle(),
device,
DrmHandlerImpl {
compositor_token,
window_map: window_map.clone(),
drawer: renderer,
logger: log,
},
).map_err(|(err, _)| err)
.unwrap();
loop {
event_loop
.dispatch(Some(::std::time::Duration::from_millis(16)), &mut ())
.unwrap();
display.flush_clients();
window_map.borrow_mut().refresh();
}
}
pub struct DrmHandlerImpl {
compositor_token: CompositorToken<SurfaceData, Roles>,
window_map: Rc<RefCell<MyWindowMap>>,
drawer: GliumDrawer<DrmBackend<Card>>,
logger: ::slog::Logger,
}
impl DrmHandler<Card> for DrmHandlerImpl {
fn ready(
&mut self,
_device: &mut DrmDevice<Card>,
_crtc: crtc::Handle,
_frame: u32,
_duration: Duration,
) {
self.drawer
.draw_windows(&*self.window_map.borrow(), self.compositor_token, &self.logger);
}
fn error(&mut self, _device: &mut DrmDevice<Card>, error: DrmError) {
panic!("{:?}", error);
}
}

View File

@ -2,41 +2,42 @@ use std::{
cell::RefCell,
collections::HashMap,
io::Error as IoError,
os::unix::io::{AsRawFd, RawFd},
path::PathBuf,
rc::Rc,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
time::Duration,
};
use glium::Surface;
use glium::Surface as GliumSurface;
use slog::Logger;
#[cfg(feature = "egl")]
use smithay::backend::egl::{EGLDisplay, EGLGraphicsBackend};
use smithay::{
backend::{
drm::{DevPath, DrmBackend, DrmDevice, DrmHandler},
graphics::{
egl::wayland::{EGLDisplay, EGLWaylandExtensions},
GraphicsBackend,
drm::{
dev_t, device_bind,
egl::{EglDevice, EglSurface},
gbm::{egl::Gbm as EglGbmBackend, GbmDevice},
legacy::LegacyDrmDevice,
DevPath, Device, DeviceHandler, Surface,
},
graphics::CursorBackend,
input::InputBackend,
libinput::{libinput_bind, LibinputInputBackend, LibinputSessionInterface},
session::{
auto::{auto_session_bind, AutoSession},
Session, SessionNotifier,
notify_multiplexer, AsSessionObserver, OFlag, Session, SessionNotifier,
},
udev::{primary_gpu, udev_backend_bind, SessionFdDrmDevice, UdevBackend, UdevHandler},
udev::{primary_gpu, udev_backend_bind, UdevBackend, UdevHandler},
},
drm::{
control::{
connector::{Info as ConnectorInfo, State as ConnectorState},
crtc,
encoder::Info as EncoderInfo,
Device as ControlDevice, ResourceInfo,
},
result::Error as DrmError,
drm::control::{
connector::{Info as ConnectorInfo, State as ConnectorState},
crtc,
encoder::Info as EncoderInfo,
},
image::{ImageBuffer, Rgba},
input::Libinput,
@ -47,18 +48,38 @@ use smithay::{
seat::{Seat, XkbConfig},
shm::init_shm_global,
},
wayland_server::{calloop::EventLoop, protocol::wl_output, Display},
wayland_server::{
calloop::{
generic::{EventedFd, Generic},
EventLoop, LoopHandle, Source,
},
protocol::wl_output,
Display,
},
};
use glium_drawer::GliumDrawer;
use input_handler::AnvilInputHandler;
use shell::{init_shell, MyWindowMap, Roles, SurfaceData};
pub struct SessionFd(RawFd);
impl AsRawFd for SessionFd {
fn as_raw_fd(&self) -> RawFd {
self.0
}
}
type RenderDevice =
EglDevice<EglGbmBackend<LegacyDrmDevice<SessionFd>>, GbmDevice<LegacyDrmDevice<SessionFd>>>;
type RenderSurface =
EglSurface<EglGbmBackend<LegacyDrmDevice<SessionFd>>, GbmDevice<LegacyDrmDevice<SessionFd>>>;
pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger) -> Result<(), ()> {
let name = display.add_socket_auto().unwrap().into_string().unwrap();
info!(log, "Listening on wayland socket"; "name" => name.clone());
::std::env::set_var("WAYLAND_DISPLAY", name);
#[cfg(feature = "egl")]
let active_egl_context = Rc::new(RefCell::new(None));
let display = Rc::new(RefCell::new(display));
@ -74,6 +95,8 @@ pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger
* Initialize session
*/
let (session, mut notifier) = AutoSession::new(log.clone()).ok_or(())?;
let (udev_observer, udev_notifier) = notify_multiplexer();
let udev_session_id = notifier.register(udev_observer);
let running = Arc::new(AtomicBool::new(true));
@ -88,26 +111,31 @@ pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger
let primary_gpu = primary_gpu(&context, &seat).unwrap_or_default();
let bytes = include_bytes!("../resources/cursor2.rgba");
let mut udev_backend = UdevBackend::new(
event_loop.handle(),
let udev_backend = UdevBackend::new(
&context,
session.clone(),
UdevHandlerImpl {
compositor_token,
#[cfg(feature = "egl")]
active_egl_context,
session: session.clone(),
backends: HashMap::new(),
display: display.clone(),
primary_gpu,
window_map: window_map.clone(),
pointer_location: pointer_location.clone(),
pointer_image: ImageBuffer::from_raw(64, 64, bytes.to_vec()).unwrap(),
loop_handle: event_loop.handle(),
notifier: udev_notifier,
logger: log.clone(),
},
seat.clone(),
log.clone(),
).map_err(|_| ())?;
let udev_session_id = notifier.register(&mut udev_backend);
)
.map_err(|_| ())?;
/*
* Initialize wayland clipboard
*/
init_data_device(
&mut display.borrow_mut(),
|_| {},
@ -115,14 +143,21 @@ pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger
log.clone(),
);
/*
* Initialize wayland input object
*/
let (mut w_seat, _) = Seat::new(&mut display.borrow_mut(), session.seat(), log.clone());
let pointer = w_seat.add_pointer();
let keyboard = w_seat
.add_keyboard(XkbConfig::default(), 1000, 500, |seat, focus| {
set_data_device_focus(seat, focus.and_then(|s| s.client()))
}).expect("Failed to initialize the keyboard");
})
.expect("Failed to initialize the keyboard");
/*
* Initialize a fake output (we render one screen to every device in this example)
*/
let (output, _output_global) = Output::new(
&mut display.borrow_mut(),
"Drm".into(),
@ -157,7 +192,7 @@ pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger
*/
let mut libinput_context =
Libinput::new_from_udev::<LibinputSessionInterface<AutoSession>>(session.clone().into(), &context);
let libinput_session_id = notifier.register(&mut libinput_context);
let libinput_session_id = notifier.register(libinput_context.observer());
libinput_context.udev_assign_seat(&seat).unwrap();
let mut libinput_backend = LibinputInputBackend::new(libinput_context, log.clone());
libinput_backend.set_handler(AnvilInputHandler::new_with_session(
@ -170,15 +205,23 @@ pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger
pointer_location,
session,
));
let libinput_event_source = libinput_bind(libinput_backend, event_loop.handle())
.map_err(|(e, _)| e)
.unwrap();
/*
* Bind all our objects that get driven by the event loop
*/
let libinput_event_source = libinput_bind(libinput_backend, event_loop.handle())
.map_err(|e| -> IoError { e.into() })
.unwrap();
let session_event_source = auto_session_bind(notifier, &event_loop.handle())
.map_err(|(e, _)| e)
.unwrap();
let udev_event_source = udev_backend_bind(udev_backend).unwrap();
let udev_event_source = udev_backend_bind(udev_backend, &event_loop.handle())
.map_err(|e| -> IoError { e.into() })
.unwrap();
/*
* And run our loop
*/
while running.load(Ordering::SeqCst) {
if event_loop
.dispatch(Some(::std::time::Duration::from_millis(16)), &mut ())
@ -191,9 +234,12 @@ pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger
}
}
// Cleanup stuff
window_map.borrow_mut().clear();
let mut notifier = session_event_source.unbind();
notifier.unregister(udev_session_id);
notifier.unregister(libinput_session_id);
notifier.unregister(udev_session_id);
libinput_event_source.remove();
udev_event_source.remove();
@ -201,24 +247,36 @@ pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger
Ok(())
}
struct UdevHandlerImpl {
struct UdevHandlerImpl<S: SessionNotifier, Data: 'static> {
compositor_token: CompositorToken<SurfaceData, Roles>,
#[cfg(feature = "egl")]
active_egl_context: Rc<RefCell<Option<EGLDisplay>>>,
backends: HashMap<u64, Rc<RefCell<HashMap<crtc::Handle, GliumDrawer<DrmBackend<SessionFdDrmDevice>>>>>>,
session: AutoSession,
backends: HashMap<
dev_t,
(
S::Id,
Source<Generic<EventedFd<RenderDevice>>>,
Rc<RefCell<HashMap<crtc::Handle, GliumDrawer<RenderSurface>>>>,
),
>,
display: Rc<RefCell<Display>>,
primary_gpu: Option<PathBuf>,
window_map: Rc<RefCell<MyWindowMap>>,
pointer_location: Rc<RefCell<(f64, f64)>>,
pointer_image: ImageBuffer<Rgba<u8>, Vec<u8>>,
loop_handle: LoopHandle<Data>,
notifier: S,
logger: ::slog::Logger,
}
impl UdevHandlerImpl {
impl<S: SessionNotifier, Data: 'static> UdevHandlerImpl<S, Data> {
#[cfg(feature = "egl")]
pub fn scan_connectors(
&self,
device: &mut DrmDevice<SessionFdDrmDevice>,
device: &mut RenderDevice,
egl_display: Rc<RefCell<Option<EGLDisplay>>>,
) -> HashMap<crtc::Handle, GliumDrawer<DrmBackend<SessionFdDrmDevice>>> {
logger: &::slog::Logger,
) -> HashMap<crtc::Handle, GliumDrawer<RenderSurface>> {
// Get a set of all modesetting resource handles (excluding planes):
let res_handles = device.resource_handles().unwrap();
@ -226,9 +284,9 @@ impl UdevHandlerImpl {
let connector_infos: Vec<ConnectorInfo> = res_handles
.connectors()
.iter()
.map(|conn| ConnectorInfo::load_from_device(device, *conn).unwrap())
.map(|conn| device.resource_info::<ConnectorInfo>(*conn).unwrap())
.filter(|conn| conn.connection_state() == ConnectorState::Connected)
.inspect(|conn| info!(self.logger, "Connected: {:?}", conn.connector_type()))
.inspect(|conn| info!(logger, "Connected: {:?}", conn.connector_type()))
.collect();
let mut backends = HashMap::new();
@ -238,33 +296,58 @@ impl UdevHandlerImpl {
let encoder_infos = connector_info
.encoders()
.iter()
.flat_map(|encoder_handle| EncoderInfo::load_from_device(device, *encoder_handle))
.flat_map(|encoder_handle| device.resource_info::<EncoderInfo>(*encoder_handle))
.collect::<Vec<EncoderInfo>>();
for encoder_info in encoder_infos {
for crtc in res_handles.filter_crtcs(encoder_info.possible_crtcs()) {
if !backends.contains_key(&crtc) {
let mode = connector_info.modes()[0]; // Use first mode (usually highest resoltion, but in reality you should filter and sort and check and match with other connectors, if you use more then one.)
// create a backend
let renderer = GliumDrawer::init(
device
.create_backend(crtc, mode, vec![connector_info.handle()])
.unwrap(),
device.create_surface(crtc).unwrap(),
egl_display.clone(),
self.logger.clone(),
logger.clone(),
);
// create cursor
renderer
.borrow()
.set_cursor_representation(&self.pointer_image, (2, 2))
.unwrap();
backends.insert(crtc, renderer);
break;
}
}
}
}
// render first frame
{
let mut frame = renderer.draw();
frame.clear_color(0.8, 0.8, 0.9, 1.0);
frame.finish().unwrap();
}
backends
}
#[cfg(not(feature = "egl"))]
pub fn scan_connectors(
device: &mut RenderDevice,
logger: &::slog::Logger,
) -> HashMap<crtc::Handle, GliumDrawer<RenderSurface>> {
// Get a set of all modesetting resource handles (excluding planes):
let res_handles = device.resource_handles().unwrap();
// Use first connected connector
let connector_infos: Vec<ConnectorInfo> = res_handles
.connectors()
.iter()
.map(|conn| device.resource_info::<ConnectorInfo>(*conn).unwrap())
.filter(|conn| conn.connection_state() == ConnectorState::Connected)
.inspect(|conn| info!(logger, "Connected: {:?}", conn.connector_type()))
.collect();
let mut backends = HashMap::new();
// very naive way of finding good crtc/encoder/connector combinations. This problem is np-complete
for connector_info in connector_infos {
let encoder_infos = connector_info
.encoders()
.iter()
.flat_map(|encoder_handle| device.resource_info::<EncoderInfo>(*encoder_handle))
.collect::<Vec<EncoderInfo>>();
for encoder_info in encoder_infos {
for crtc in res_handles.filter_crtcs(encoder_info.possible_crtcs()) {
if !backends.contains_key(&crtc) {
let renderer =
GliumDrawer::init(device.create_surface(crtc).unwrap(), logger.clone());
backends.insert(crtc, renderer);
break;
@ -277,64 +360,151 @@ impl UdevHandlerImpl {
}
}
impl UdevHandler<DrmHandlerImpl> for UdevHandlerImpl {
fn device_added(&mut self, device: &mut DrmDevice<SessionFdDrmDevice>) -> Option<DrmHandlerImpl> {
// init hardware acceleration on the primary gpu.
if device.dev_path().and_then(|path| path.canonicalize().ok()) == self.primary_gpu {
*self.active_egl_context.borrow_mut() = device.bind_wl_display(&*self.display.borrow()).ok();
impl<S: SessionNotifier, Data: 'static> UdevHandler for UdevHandlerImpl<S, Data> {
fn device_added(&mut self, _device: dev_t, path: PathBuf) {
// Try to open the device
if let Some(mut device) = self
.session
.open(
&path,
OFlag::O_RDWR | OFlag::O_CLOEXEC | OFlag::O_NOCTTY | OFlag::O_NONBLOCK,
)
.ok()
.and_then(|fd| LegacyDrmDevice::new(SessionFd(fd), self.logger.clone()).ok())
.and_then(|drm| GbmDevice::new(drm, self.logger.clone()).ok())
.and_then(|gbm| EglDevice::new(gbm, self.logger.clone()).ok())
{
// init hardware acceleration on the primary gpu.
#[cfg(feature = "egl")]
{
if path.canonicalize().ok() == self.primary_gpu {
*self.active_egl_context.borrow_mut() =
device.bind_wl_display(&*self.display.borrow()).ok();
}
}
#[cfg(feature = "egl")]
let backends = Rc::new(RefCell::new(UdevHandlerImpl::<S, Data>::scan_connectors(
&mut device,
self.active_egl_context.clone(),
&self.logger,
)));
#[cfg(not(feature = "egl"))]
let backends = Rc::new(RefCell::new(UdevHandlerImpl::<S, Data>::scan_connectors(
&mut device,
&self.logger,
)));
// Set the handler.
// Note: if you replicate this (very simple) structure, it is rather easy
// to introduce reference cycles with Rc. Be sure about your drop order
device.set_handler(DrmHandlerImpl {
compositor_token: self.compositor_token,
backends: backends.clone(),
window_map: self.window_map.clone(),
pointer_location: self.pointer_location.clone(),
logger: self.logger.clone(),
});
let device_session_id = self.notifier.register(device.observer());
let dev_id = device.device_id();
let event_source = device_bind(&self.loop_handle, device)
.map_err(|e| -> IoError { e.into() })
.unwrap();
for renderer in backends.borrow_mut().values() {
// create cursor
renderer
.borrow()
.set_cursor_representation(&self.pointer_image, (2, 2))
.unwrap();
// render first frame
{
let mut frame = renderer.draw();
frame.clear_color(0.8, 0.8, 0.9, 1.0);
frame.finish().unwrap();
}
}
self.backends
.insert(dev_id, (device_session_id, event_source, backends));
}
let backends = Rc::new(RefCell::new(
self.scan_connectors(device, self.active_egl_context.clone()),
));
self.backends.insert(device.device_id(), backends.clone());
Some(DrmHandlerImpl {
compositor_token: self.compositor_token,
backends,
window_map: self.window_map.clone(),
pointer_location: self.pointer_location.clone(),
logger: self.logger.clone(),
})
}
fn device_changed(&mut self, device: &mut DrmDevice<SessionFdDrmDevice>) {
//quick and dirt, just re-init all backends
let backends = &self.backends[&device.device_id()];
*backends.borrow_mut() = self.scan_connectors(device, self.active_egl_context.clone());
fn device_changed(&mut self, device: dev_t) {
//quick and dirty, just re-init all backends
if let Some((_, ref mut evt_source, ref backends)) = self.backends.get_mut(&device) {
let source = evt_source.clone_inner();
let mut evented = source.borrow_mut();
let mut backends = backends.borrow_mut();
#[cfg(feature = "egl")]
let new_backends = UdevHandlerImpl::<S, Data>::scan_connectors(
&mut (*evented).0,
self.active_egl_context.clone(),
&self.logger,
);
#[cfg(not(feature = "egl"))]
let new_backends = UdevHandlerImpl::<S, Data>::scan_connectors(&mut (*evented).0, &self.logger);
*backends = new_backends;
for renderer in backends.values() {
// create cursor
renderer
.borrow()
.set_cursor_representation(&self.pointer_image, (2, 2))
.unwrap();
// render first frame
{
let mut frame = renderer.draw();
frame.clear_color(0.8, 0.8, 0.9, 1.0);
frame.finish().unwrap();
}
}
}
}
fn device_removed(&mut self, device: &mut DrmDevice<SessionFdDrmDevice>) {
fn device_removed(&mut self, device: dev_t) {
// drop the backends on this side
self.backends.remove(&device.device_id());
if let Some((id, evt_source, renderers)) = self.backends.remove(&device) {
// drop surfaces
renderers.borrow_mut().clear();
debug!(self.logger, "Surfaces dropped");
// don't use hardware acceleration anymore, if this was the primary gpu
if device.dev_path().and_then(|path| path.canonicalize().ok()) == self.primary_gpu {
*self.active_egl_context.borrow_mut() = None;
let device = Rc::try_unwrap(evt_source.remove().unwrap())
.map_err(|_| "This should not happend")
.unwrap()
.into_inner()
.0;
// don't use hardware acceleration anymore, if this was the primary gpu
#[cfg(feature = "egl")]
{
if device.dev_path().and_then(|path| path.canonicalize().ok()) == self.primary_gpu {
*self.active_egl_context.borrow_mut() = None;
}
}
self.notifier.unregister(id);
debug!(self.logger, "Dropping device");
}
}
fn error(&mut self, error: IoError) {
error!(self.logger, "{:?}", error);
}
}
pub struct DrmHandlerImpl {
compositor_token: CompositorToken<SurfaceData, Roles>,
backends: Rc<RefCell<HashMap<crtc::Handle, GliumDrawer<DrmBackend<SessionFdDrmDevice>>>>>,
backends: Rc<RefCell<HashMap<crtc::Handle, GliumDrawer<RenderSurface>>>>,
window_map: Rc<RefCell<MyWindowMap>>,
pointer_location: Rc<RefCell<(f64, f64)>>,
logger: ::slog::Logger,
}
impl DrmHandler<SessionFdDrmDevice> for DrmHandlerImpl {
fn ready(
&mut self,
_device: &mut DrmDevice<SessionFdDrmDevice>,
crtc: crtc::Handle,
_frame: u32,
_duration: Duration,
) {
impl DeviceHandler for DrmHandlerImpl {
type Device = RenderDevice;
fn vblank(&mut self, crtc: crtc::Handle) {
if let Some(drawer) = self.backends.borrow().get(&crtc) {
{
let (x, y) = *self.pointer_location.borrow();
@ -343,11 +513,12 @@ impl DrmHandler<SessionFdDrmDevice> for DrmHandlerImpl {
.set_cursor_position(x.trunc().abs() as u32, y.trunc().abs() as u32);
}
// and draw in sync with our monitor
drawer.draw_windows(&*self.window_map.borrow(), self.compositor_token, &self.logger);
}
}
fn error(&mut self, _device: &mut DrmDevice<SessionFdDrmDevice>, error: DrmError) {
fn error(&mut self, error: <RenderSurface as Surface>::Error) {
error!(self.logger, "{:?}", error);
}
}

View File

@ -5,11 +5,7 @@ use std::{
};
use smithay::{
backend::{
graphics::egl::{wayland::EGLWaylandExtensions, EGLGraphicsBackend},
input::InputBackend,
winit,
},
backend::{egl::EGLGraphicsBackend, graphics::gl::GLGraphicsBackend, input::InputBackend, winit},
wayland::{
data_device::{default_action_chooser, init_data_device, set_data_device_focus},
output::{Mode, Output, PhysicalProperties},
@ -63,7 +59,8 @@ pub fn run_winit(display: &mut Display, event_loop: &mut EventLoop<()>, log: Log
let keyboard = seat
.add_keyboard(XkbConfig::default(), 1000, 500, |seat, focus| {
set_data_device_focus(seat, focus.and_then(|s| s.client()))
}).expect("Failed to initialize the keyboard");
})
.expect("Failed to initialize the keyboard");
let (output, _) = Output::new(
display,

View File

@ -1,44 +1,56 @@
#[cfg(any(feature = "backend_egl", feature = "renderer_gl"))]
extern crate gl_generator;
#[cfg(any(feature = "backend_egl", feature = "renderer_gl"))]
use gl_generator::{Api, Fallbacks, Profile, Registry};
use std::{env, fs::File, path::PathBuf};
#[cfg(any(feature = "backend_egl", feature = "renderer_gl"))]
fn main() {
let dest = PathBuf::from(&env::var("OUT_DIR").unwrap());
println!("cargo:rerun-if-changed=build.rs");
let mut file = File::create(&dest.join("egl_bindings.rs")).unwrap();
Registry::new(
Api::Egl,
(1, 5),
Profile::Core,
Fallbacks::All,
[
"EGL_KHR_create_context",
"EGL_EXT_create_context_robustness",
"EGL_KHR_create_context_no_error",
"EGL_KHR_platform_x11",
"EGL_KHR_platform_android",
"EGL_KHR_platform_wayland",
"EGL_KHR_platform_gbm",
"EGL_EXT_platform_base",
"EGL_EXT_platform_x11",
"EGL_MESA_platform_gbm",
"EGL_EXT_platform_wayland",
"EGL_EXT_platform_device",
"EGL_KHR_image_base",
],
).write_bindings(gl_generator::GlobalGenerator, &mut file)
.unwrap();
if env::var_os("CARGO_FEATURE_BACKEND_EGL").is_some() {
let mut file = File::create(&dest.join("egl_bindings.rs")).unwrap();
Registry::new(
Api::Egl,
(1, 5),
Profile::Core,
Fallbacks::All,
[
"EGL_KHR_create_context",
"EGL_EXT_create_context_robustness",
"EGL_KHR_create_context_no_error",
"EGL_KHR_platform_x11",
"EGL_KHR_platform_android",
"EGL_KHR_platform_wayland",
"EGL_KHR_platform_gbm",
"EGL_EXT_platform_base",
"EGL_EXT_platform_x11",
"EGL_MESA_platform_gbm",
"EGL_EXT_platform_wayland",
"EGL_EXT_platform_device",
"EGL_KHR_image_base",
],
)
.write_bindings(gl_generator::GlobalGenerator, &mut file)
.unwrap();
}
let mut file = File::create(&dest.join("gl_bindings.rs")).unwrap();
Registry::new(
Api::Gles2,
(3, 2),
Profile::Compatibility,
Fallbacks::None,
["GL_OES_EGL_image"],
).write_bindings(gl_generator::GlobalGenerator, &mut file)
.unwrap();
if env::var_os("CARGO_FEATURE_RENDERER_GL").is_some() {
let mut file = File::create(&dest.join("gl_bindings.rs")).unwrap();
Registry::new(
Api::Gles2,
(3, 2),
Profile::Compatibility,
Fallbacks::None,
["GL_OES_EGL_image"],
)
.write_bindings(gl_generator::StructGenerator, &mut file)
.unwrap();
}
}
#[cfg(not(any(feature = "backend_egl", feature = "renderer_gl")))]
fn main() {}

150
examples/raw_drm.rs Normal file
View File

@ -0,0 +1,150 @@
extern crate drm;
extern crate smithay;
#[macro_use]
extern crate slog;
extern crate slog_term;
use drm::{buffer::PixelFormat, control::dumbbuffer::DumbBuffer};
use slog::Drain;
use smithay::{
backend::drm::{
connector::{self, State as ConnectorState},
crtc, device_bind, encoder, framebuffer,
legacy::{error::Error, LegacyDrmDevice, LegacyDrmSurface},
ControlDevice, Device, DeviceHandler, RawSurface, ResourceInfo, Surface,
},
wayland_server::calloop::EventLoop,
};
use std::{
fs::{File, OpenOptions},
io::Error as IoError,
rc::Rc,
sync::Mutex,
};
fn main() {
let log = slog::Logger::root(Mutex::new(slog_term::term_full().fuse()).fuse(), o!());
/*
* Initialize the drm backend
*/
// "Find" a suitable drm device
let mut options = OpenOptions::new();
options.read(true);
options.write(true);
let mut device = LegacyDrmDevice::new(options.open("/dev/dri/card0").unwrap(), log.clone()).unwrap();
// Get a set of all modesetting resource handles (excluding planes):
let res_handles = Device::resource_handles(&device).unwrap();
// Use first connected connector
let connector_info = res_handles
.connectors()
.iter()
.map(|conn| Device::resource_info::<connector::Info>(&device, *conn).unwrap())
.find(|conn| conn.connection_state() == ConnectorState::Connected)
.unwrap();
// Use the first encoder
let encoder_info = Device::resource_info::<encoder::Info>(&device, connector_info.encoders()[0]).unwrap();
// use the connected crtc if any
let crtc = encoder_info
.current_crtc()
// or use the first one that is compatible with the encoder
.unwrap_or_else(|| {
*res_handles
.filter_crtcs(encoder_info.possible_crtcs())
.iter()
.next()
.unwrap()
});
// Assuming we found a good connector and loaded the info into `connector_info`
let mode = connector_info.modes()[0]; // Use first mode (usually highest resoltion, but in reality you should filter and sort and check and match with other connectors, if you use more then one.)
// Initialize the hardware backend
let surface = Rc::new(device.create_surface(crtc).unwrap());
surface.use_mode(Some(mode)).unwrap();
for conn in surface.current_connectors().into_iter() {
if conn != connector_info.handle() {
surface.remove_connector(conn).unwrap();
}
}
surface.add_connector(connector_info.handle()).unwrap();
/*
* Lets create buffers and framebuffers.
* We use drm-rs DumbBuffers, because they always work and require little to no setup.
* But they are very slow, this is just for demonstration purposes.
*/
let (w, h) = mode.size();
let front_buffer =
DumbBuffer::create_from_device(&device, (w as u32, h as u32), PixelFormat::XRGB8888).unwrap();
let front_framebuffer = device.create_framebuffer(&front_buffer).unwrap();
let back_buffer =
DumbBuffer::create_from_device(&device, (w as u32, h as u32), PixelFormat::XRGB8888).unwrap();
let back_framebuffer = device.create_framebuffer(&back_buffer).unwrap();
device.set_handler(DrmHandlerImpl {
current: front_framebuffer.handle(),
front: (front_buffer, front_framebuffer.clone()),
back: (back_buffer, back_framebuffer),
surface: surface.clone(),
});
/*
* Register the DrmDevice on the EventLoop
*/
let mut event_loop = EventLoop::<()>::new().unwrap();
let _source = device_bind(&event_loop.handle(), device)
.map_err(|err| -> IoError { err.into() })
.unwrap();
// Start rendering
if surface.commit_pending() {
surface.commit(front_framebuffer.handle()).unwrap();
}
surface.page_flip(front_framebuffer.handle()).unwrap();
// Run
event_loop.run(None, &mut (), |_| {}).unwrap();
}
pub struct DrmHandlerImpl {
front: (DumbBuffer, framebuffer::Info),
back: (DumbBuffer, framebuffer::Info),
current: framebuffer::Handle,
surface: Rc<LegacyDrmSurface<File>>,
}
impl DeviceHandler for DrmHandlerImpl {
type Device = LegacyDrmDevice<File>;
fn vblank(&mut self, _crtc: crtc::Handle) {
{
// Swap and map buffer
let mut mapping = if self.current == self.front.1.handle() {
self.current = self.back.1.handle();
self.back.0.map(&*self.surface).unwrap()
} else {
self.current = self.front.1.handle();
self.front.0.map(&*self.surface).unwrap()
};
// now we could render to the mapping via software rendering.
// this example just sets some grey color
for mut x in mapping.as_mut() {
*x = 128;
}
}
self.surface.page_flip(self.current).unwrap();
}
fn error(&mut self, error: Error) {
panic!("{:?}", error);
}
}

View File

@ -1,537 +0,0 @@
use super::{error::*, DevPath};
use backend::graphics::{
egl::{
error::Result as EGLResult,
native::{Gbm, GbmSurfaceArguments},
wayland::{EGLDisplay, EGLWaylandExtensions},
EGLContext, EGLGraphicsBackend, EGLSurface, PixelFormat, SwapBuffersError,
},
GraphicsBackend,
};
use drm::{
control::{connector, crtc, encoder, framebuffer, Device, Mode, ResourceInfo},
Device as BasicDevice,
};
use gbm::{
BufferObject, BufferObjectFlags, Device as GbmDevice, Format as GbmFormat, Surface as GbmSurface,
SurfaceBufferHandle,
};
use image::{ImageBuffer, Rgba};
use nix::libc::c_void;
use std::{
cell::Cell,
os::unix::io::{AsRawFd, RawFd},
rc::{Rc, Weak},
};
use wayland_server::Display;
/// Backend based on a `DrmDevice` and a given crtc
pub struct DrmBackend<A: Device + 'static> {
backend: Rc<DrmBackendInternal<A>>,
surface: EGLSurface<GbmSurface<framebuffer::Info>>,
mode: Mode,
connectors: Vec<connector::Handle>,
}
pub(crate) struct DrmBackendInternal<A: Device + 'static> {
pub(crate) context: Rc<EGLContext<Gbm<framebuffer::Info>, GbmDevice<A>>>,
pub(crate) cursor: Cell<(BufferObject<()>, (u32, u32))>,
current_frame_buffer: Cell<framebuffer::Info>,
front_buffer: Cell<SurfaceBufferHandle<framebuffer::Info>>,
next_buffer: Cell<Option<SurfaceBufferHandle<framebuffer::Info>>>,
crtc: crtc::Handle,
logger: ::slog::Logger,
}
impl<A: Device + 'static> DrmBackend<A> {
pub(crate) fn new(
context: Rc<EGLContext<Gbm<framebuffer::Info>, GbmDevice<A>>>,
crtc: crtc::Handle,
mode: Mode,
connectors: Vec<connector::Handle>,
log: ::slog::Logger,
) -> Result<Self> {
// logger already initialized by the DrmDevice
info!(log, "Initializing DrmBackend");
let (w, h) = mode.size();
debug!(log, "Creating Surface");
let surface = context
.create_surface(GbmSurfaceArguments {
size: (w as u32, h as u32),
format: GbmFormat::XRGB8888,
flags: BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING,
}).chain_err(|| ErrorKind::GbmInitFailed)?;
// make it active for the first `crtc::set`
// (which is needed before the first page_flip)
unsafe { surface.make_current().chain_err(|| ErrorKind::FailedToSwap)? };
surface.swap_buffers().chain_err(|| ErrorKind::FailedToSwap)?;
// init the first screen
// (must be done before calling page_flip for the first time)
let mut front_bo = surface
.lock_front_buffer()
.chain_err(|| ErrorKind::FailedToSwap)?;
debug!(log, "FrontBuffer color format: {:?}", front_bo.format());
// we need a framebuffer for the front buffer
let fb = framebuffer::create(&*context, &*front_bo).chain_err(|| {
ErrorKind::DrmDev(format!("Error creating framebuffer on {:?}", context.dev_path()))
})?;
debug!(log, "Initialize screen");
crtc::set(&*context, crtc, fb.handle(), &connectors, (0, 0), Some(mode)).chain_err(|| {
ErrorKind::DrmDev(format!(
"Error setting crtc {:?} on {:?}",
crtc,
context.dev_path()
))
})?;
front_bo.set_userdata(fb).unwrap();
let cursor = Cell::new((
context
.create_buffer_object(
1,
1,
GbmFormat::ARGB8888,
BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE,
).chain_err(|| ErrorKind::GbmInitFailed)?,
(0, 0),
));
Ok(DrmBackend {
backend: Rc::new(DrmBackendInternal {
context,
cursor,
current_frame_buffer: Cell::new(fb),
front_buffer: Cell::new(front_bo),
next_buffer: Cell::new(None),
crtc,
logger: log,
}),
surface,
mode,
connectors,
})
}
pub(crate) fn weak(&self) -> Weak<DrmBackendInternal<A>> {
Rc::downgrade(&self.backend)
}
/// Add a connector to backend
///
/// # Errors
///
/// Errors if the new connector does not support the currently set `Mode`
pub fn add_connector(&mut self, connector: connector::Handle) -> Result<()> {
let info = connector::Info::load_from_device(&*self.backend.context, connector).chain_err(|| {
ErrorKind::DrmDev(format!(
"Error loading connector info on {:?}",
self.backend.context.dev_path()
))
})?;
// check if the connector can handle the current mode
if info.modes().contains(&self.mode) {
// check if there is a valid encoder
let encoders = info
.encoders()
.iter()
.map(|encoder| {
encoder::Info::load_from_device(&*self.backend.context, *encoder).chain_err(|| {
ErrorKind::DrmDev(format!(
"Error loading encoder info on {:?}",
self.backend.context.dev_path()
))
})
}).collect::<Result<Vec<encoder::Info>>>()?;
// and if any encoder supports the selected crtc
let resource_handles = self.backend.context.resource_handles().chain_err(|| {
ErrorKind::DrmDev(format!(
"Error loading resources on {:?}",
self.backend.context.dev_path()
))
})?;
if !encoders
.iter()
.map(|encoder| encoder.possible_crtcs())
.all(|crtc_list| {
resource_handles
.filter_crtcs(crtc_list)
.contains(&self.backend.crtc)
}) {
bail!(ErrorKind::NoSuitableEncoder(info, self.backend.crtc));
}
info!(
self.backend.logger,
"Adding new connector: {:?}",
info.connector_type()
);
self.connectors.push(connector);
Ok(())
} else {
bail!(ErrorKind::ModeNotSuitable(self.mode))
}
}
/// Returns the currently set connectors
pub fn used_connectors(&self) -> &[connector::Handle] {
&*self.connectors
}
/// Removes a currently set connector
pub fn remove_connector(&mut self, connector: connector::Handle) {
if let Ok(info) = connector::Info::load_from_device(&*self.backend.context, connector) {
info!(
self.backend.logger,
"Removing connector: {:?}",
info.connector_type()
);
} else {
info!(self.backend.logger, "Removing unknown connector");
}
self.connectors.retain(|x| *x != connector);
}
/// Gets the currently used mode
pub fn current_mode(&self) -> Mode {
self.mode
}
/// Changes the currently set mode
///
/// # Errors
///
/// This will fail if not all set connectors support the new `Mode`.
/// Several internal resources will need to be recreated to fit the new `Mode`.
/// Other errors might occur.
pub fn use_mode(&mut self, mode: Mode) -> Result<()> {
// check the connectors
for connector in &self.connectors {
if !connector::Info::load_from_device(&*self.backend.context, *connector)
.chain_err(|| {
ErrorKind::DrmDev(format!(
"Error loading connector info on {:?}",
self.backend.context.dev_path()
))
})?.modes()
.contains(&mode)
{
bail!(ErrorKind::ModeNotSuitable(mode));
}
}
info!(self.backend.logger, "Setting new mode: {:?}", mode.name());
let (w, h) = mode.size();
// Recreate the surface and the related resources to match the new
// resolution.
debug!(
self.backend.logger,
"Reinitializing surface for new mode: {}:{}", w, h
);
let surface = self
.backend
.context
.create_surface(GbmSurfaceArguments {
size: (w as u32, h as u32),
format: GbmFormat::XRGB8888,
flags: BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING,
}).chain_err(|| ErrorKind::GbmInitFailed)?;
// make it active for the first `crtc::set`
// (which is needed before the first page_flip)
unsafe { surface.make_current().chain_err(|| ErrorKind::FailedToSwap)? };
surface.swap_buffers().chain_err(|| ErrorKind::FailedToSwap)?;
// Clean up next_buffer
{
if let Some(mut old_bo) = self.backend.next_buffer.take() {
if let Ok(Some(fb)) = old_bo.take_userdata() {
if let Err(err) = framebuffer::destroy(&*self.backend.context, fb.handle()) {
warn!(
self.backend.logger,
"Error releasing old back_buffer framebuffer: {:?}", err
);
}
}
}
}
// Cleanup front_buffer and init the first screen on the new front_buffer
// (must be done before calling page_flip for the first time)
let mut old_front_bo = self.backend.front_buffer.replace({
let mut front_bo = surface
.lock_front_buffer()
.chain_err(|| ErrorKind::FailedToSwap)?;
debug!(
self.backend.logger,
"FrontBuffer color format: {:?}",
front_bo.format()
);
// we also need a new framebuffer for the front buffer
let dev_path = self.backend.context.dev_path();
let fb = framebuffer::create(&*self.backend.context, &*front_bo)
.chain_err(|| ErrorKind::DrmDev(format!("Error creating framebuffer on {:?}", dev_path)))?;
front_bo.set_userdata(fb).unwrap();
debug!(self.backend.logger, "Setting screen");
crtc::set(
&*self.backend.context,
self.backend.crtc,
fb.handle(),
&self.connectors,
(0, 0),
Some(mode),
).chain_err(|| {
ErrorKind::DrmDev(format!(
"Error setting crtc {:?} on {:?}",
self.backend.crtc,
self.backend.context.dev_path()
))
})?;
front_bo
});
if let Ok(Some(fb)) = old_front_bo.take_userdata() {
if let Err(err) = framebuffer::destroy(&*self.backend.context, fb.handle()) {
warn!(
self.backend.logger,
"Error releasing old front_buffer framebuffer: {:?}", err
);
}
}
// Drop the old surface after cleanup
self.surface = surface;
self.mode = mode;
Ok(())
}
/// Returns the crtc id used by this backend
pub fn crtc(&self) -> crtc::Handle {
self.backend.crtc
}
}
impl<A: Device + 'static> DrmBackendInternal<A> {
pub(crate) fn unlock_buffer(&self) {
// after the page swap is finished we need to release the rendered buffer.
// this is called from the PageFlipHandler
if let Some(next_buffer) = self.next_buffer.replace(None) {
trace!(self.logger, "Releasing old front buffer");
self.front_buffer.set(next_buffer);
// drop and release the old buffer
}
}
pub(crate) fn page_flip(
&self,
fb: Option<&framebuffer::Info>,
) -> ::std::result::Result<(), SwapBuffersError> {
trace!(self.logger, "Queueing Page flip");
let fb = *fb.unwrap_or(&self.current_frame_buffer.get());
// and flip
crtc::page_flip(
&*self.context,
self.crtc,
fb.handle(),
&[crtc::PageFlipFlags::PageFlipEvent],
).map_err(|_| SwapBuffersError::ContextLost)?;
self.current_frame_buffer.set(fb);
Ok(())
}
}
impl<A: Device + 'static> Drop for DrmBackend<A> {
fn drop(&mut self) {
// Drop framebuffers attached to the userdata of the gbm surface buffers.
// (They don't implement drop, as they need the device)
if let Ok(Some(fb)) = {
if let Some(mut next) = self.backend.next_buffer.take() {
next.take_userdata()
} else if let Ok(mut next) = self.surface.lock_front_buffer() {
next.take_userdata()
} else {
Ok(None)
}
} {
// ignore failure at this point
let _ = framebuffer::destroy(&*self.backend.context, fb.handle());
}
}
}
impl<A: Device + 'static> Drop for DrmBackendInternal<A> {
fn drop(&mut self) {
if let Ok(Some(fb)) = self.front_buffer.get_mut().take_userdata() {
// ignore failure at this point
let _ = framebuffer::destroy(&*self.context, fb.handle());
}
// ignore failure at this point
let _ = crtc::clear_cursor(&*self.context, self.crtc);
}
}
impl<A: Device + 'static> GraphicsBackend for DrmBackend<A> {
type CursorFormat = ImageBuffer<Rgba<u8>, Vec<u8>>;
type Error = Error;
fn set_cursor_position(&self, x: u32, y: u32) -> Result<()> {
trace!(self.backend.logger, "Move the cursor to {},{}", x, y);
crtc::move_cursor(&*self.backend.context, self.backend.crtc, (x as i32, y as i32)).chain_err(|| {
ErrorKind::DrmDev(format!(
"Error moving cursor on {:?}",
self.backend.context.dev_path()
))
})
}
fn set_cursor_representation(
&self,
buffer: &ImageBuffer<Rgba<u8>, Vec<u8>>,
hotspot: (u32, u32),
) -> Result<()> {
let (w, h) = buffer.dimensions();
debug!(self.backend.logger, "Importing cursor");
// import the cursor into a buffer we can render
let mut cursor = self
.backend
.context
.create_buffer_object(
w,
h,
GbmFormat::ARGB8888,
BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE,
).chain_err(|| ErrorKind::GbmInitFailed)?;
cursor
.write(&**buffer)
.chain_err(|| ErrorKind::GbmInitFailed)?
.chain_err(|| ErrorKind::GbmInitFailed)?;
trace!(self.backend.logger, "Setting the new imported cursor");
// and set it
if crtc::set_cursor2(
&*self.backend.context,
self.backend.crtc,
&cursor,
(hotspot.0 as i32, hotspot.1 as i32),
).is_err()
{
crtc::set_cursor(&*self.backend.context, self.backend.crtc, &cursor).chain_err(|| {
ErrorKind::DrmDev(format!(
"Failed to set cursor on {:?}",
self.backend.context.dev_path()
))
})?;
}
// and store it
self.backend.cursor.set((cursor, hotspot));
Ok(())
}
}
impl<A: Device + 'static> EGLGraphicsBackend for DrmBackend<A> {
fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> {
let res = {
let nb = self.backend.next_buffer.take();
let res = nb.is_some();
self.backend.next_buffer.set(nb);
res
};
if res {
// We cannot call lock_front_buffer anymore without releasing the previous buffer, which will happen when the page flip is done
warn!(
self.backend.logger,
"Tried to swap a DrmBackend with a queued flip"
);
return Err(SwapBuffersError::AlreadySwapped);
}
// flip normally
self.surface.swap_buffers()?;
// supporting only one buffer would cause a lot of inconvinience and
// would most likely result in a lot of flickering.
// neither weston, wlc or wlroots bother with that as well.
// so we just assume we got at least two buffers to do flipping.
let mut next_bo = self
.surface
.lock_front_buffer()
.expect("Surface only has one front buffer. Not supported by smithay");
// create a framebuffer if the front buffer does not have one already
// (they are reused by gbm)
let maybe_fb = next_bo
.userdata()
.map_err(|_| SwapBuffersError::ContextLost)?
.cloned();
let fb = if let Some(info) = maybe_fb {
info
} else {
let fb = framebuffer::create(&*self.backend.context, &*next_bo)
.map_err(|_| SwapBuffersError::ContextLost)?;
next_bo.set_userdata(fb).unwrap();
fb
};
self.backend.next_buffer.set(Some(next_bo));
self.backend.page_flip(Some(&fb))
}
unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void {
self.backend.context.get_proc_address(symbol)
}
fn get_framebuffer_dimensions(&self) -> (u32, u32) {
let (w, h) = self.mode.size();
(w as u32, h as u32)
}
fn is_current(&self) -> bool {
self.backend.context.is_current() && self.surface.is_current()
}
unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> {
self.surface.make_current()
}
fn get_pixel_format(&self) -> PixelFormat {
self.backend.context.get_pixel_format()
}
}
// for users convenience
impl<A: Device + 'static> AsRawFd for DrmBackend<A> {
fn as_raw_fd(&self) -> RawFd {
self.backend.context.as_raw_fd()
}
}
impl<A: Device + 'static> BasicDevice for DrmBackend<A> {}
impl<A: Device + 'static> Device for DrmBackend<A> {}
impl<A: Device + 'static> EGLWaylandExtensions for DrmBackend<A> {
fn bind_wl_display(&self, display: &Display) -> EGLResult<EGLDisplay> {
self.backend.context.bind_wl_display(display)
}
}

View File

@ -0,0 +1,19 @@
//!
//! Errors thrown by the `DrmDevice` and `DrmBackend`
//!
use backend::egl::error as egl;
error_chain! {
errors {
#[doc = "Underlying backend failed"]
UnderlyingBackendError {
description("The underlying backend reported an error"),
display("The underlying backend reported an error"),
}
}
links {
EGL(egl::Error, egl::ErrorKind) #[doc = "EGL error"];
}
}

204
src/backend/drm/egl/mod.rs Normal file
View File

@ -0,0 +1,204 @@
//!
//! [`Device`](../trait.Device.html) and [`Surface`](../trait.Surface.html)
//! implementations using egl contexts and surfaces for efficient rendering.
//!
//! Usually this implementation's [`EglSurface`](struct.EglSurface.html)s implementation
//! of [`GlGraphicsBackend`](../../graphics/gl/trait.GlGraphicsBackend.html) will be used
//! to let your compositor render.
//! Take a look at `anvil`s source code for an example of this.
//!
use drm::control::{crtc, ResourceHandles, ResourceInfo};
use nix::libc::dev_t;
use std::os::unix::io::{AsRawFd, RawFd};
use std::rc::Rc;
use wayland_server::Display;
use super::{Device, DeviceHandler, Surface};
use backend::egl::context::GlAttributes;
use backend::egl::error::Result as EGLResult;
use backend::egl::native::{Backend, NativeDisplay, NativeSurface};
use backend::egl::EGLContext;
#[cfg(feature = "native_lib")]
use backend::egl::{EGLDisplay, EGLGraphicsBackend};
pub mod error;
use self::error::*;
mod surface;
pub use self::surface::*;
#[cfg(feature = "backend_session")]
pub mod session;
/// Representation of an egl device to create egl rendering surfaces
pub struct EglDevice<B, D>
where
B: Backend<Surface = <D as Device>::Surface> + 'static,
D: Device + NativeDisplay<B, Arguments = crtc::Handle> + 'static,
<D as Device>::Surface: NativeSurface,
{
dev: Rc<EGLContext<B, D>>,
logger: ::slog::Logger,
}
impl<B, D> AsRawFd for EglDevice<B, D>
where
B: Backend<Surface = <D as Device>::Surface> + 'static,
D: Device + NativeDisplay<B, Arguments = crtc::Handle> + 'static,
<D as Device>::Surface: NativeSurface,
{
fn as_raw_fd(&self) -> RawFd {
self.dev.borrow().as_raw_fd()
}
}
impl<B, D> EglDevice<B, D>
where
B: Backend<Surface = <D as Device>::Surface> + 'static,
D: Device + NativeDisplay<B, Arguments = crtc::Handle> + 'static,
<D as Device>::Surface: NativeSurface,
{
/// Try to create a new `EglDevice` from an open device.
///
/// Returns an error if the file is no valid device or context
/// creation was not successful.
pub fn new<L>(dev: D, logger: L) -> Result<Self>
where
L: Into<Option<::slog::Logger>>,
{
EglDevice::new_with_gl_attr(
dev,
GlAttributes {
version: None,
profile: None,
debug: cfg!(debug_assertions),
vsync: true,
},
logger,
)
}
/// Create a new `EglDevice` from an open device and given `GlAttributes`
///
/// Returns an error if the file is no valid device or context
/// creation was not successful.
pub fn new_with_gl_attr<L>(mut dev: D, attributes: GlAttributes, logger: L) -> Result<Self>
where
L: Into<Option<::slog::Logger>>,
{
let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_egl"));
dev.clear_handler();
debug!(log, "Creating egl context from device");
Ok(EglDevice {
// Open the gbm device from the drm device and create a context based on that
dev: Rc::new(
EGLContext::new(dev, attributes, Default::default(), log.clone()).map_err(Error::from)?,
),
logger: log,
})
}
}
struct InternalDeviceHandler<B, D>
where
B: Backend<Surface = <D as Device>::Surface> + 'static,
D: Device + NativeDisplay<B, Arguments = crtc::Handle> + 'static,
<D as Device>::Surface: NativeSurface,
{
handler: Box<DeviceHandler<Device = EglDevice<B, D>> + 'static>,
}
impl<B, D> DeviceHandler for InternalDeviceHandler<B, D>
where
B: Backend<Surface = <D as Device>::Surface> + 'static,
D: Device + NativeDisplay<B, Arguments = crtc::Handle> + 'static,
<D as Device>::Surface: NativeSurface,
{
type Device = D;
fn vblank(&mut self, crtc: crtc::Handle) {
self.handler.vblank(crtc)
}
fn error(&mut self, error: <<D as Device>::Surface as Surface>::Error) {
self.handler
.error(ResultExt::<()>::chain_err(Err(error), || ErrorKind::UnderlyingBackendError).unwrap_err())
}
}
impl<B, D> Device for EglDevice<B, D>
where
B: Backend<Surface = <D as Device>::Surface> + 'static,
D: Device + NativeDisplay<B, Arguments = crtc::Handle> + 'static,
<D as Device>::Surface: NativeSurface,
{
type Surface = EglSurface<B, D>;
fn device_id(&self) -> dev_t {
self.dev.borrow().device_id()
}
fn set_handler(&mut self, handler: impl DeviceHandler<Device = Self> + 'static) {
self.dev.borrow_mut().set_handler(InternalDeviceHandler {
handler: Box::new(handler),
});
}
fn clear_handler(&mut self) {
self.dev.borrow_mut().clear_handler()
}
fn create_surface(&mut self, crtc: crtc::Handle) -> Result<EglSurface<B, D>> {
info!(self.logger, "Initializing EglSurface");
let surface = self.dev.create_surface(crtc)?;
Ok(EglSurface {
dev: self.dev.clone(),
surface,
})
}
fn process_events(&mut self) {
self.dev.borrow_mut().process_events()
}
fn resource_info<T: ResourceInfo>(&self, handle: T::Handle) -> Result<T> {
self.dev
.borrow()
.resource_info(handle)
.chain_err(|| ErrorKind::UnderlyingBackendError)
}
fn resource_handles(&self) -> Result<ResourceHandles> {
self.dev
.borrow()
.resource_handles()
.chain_err(|| ErrorKind::UnderlyingBackendError)
}
}
#[cfg(feature = "native_lib")]
impl<B, D> EGLGraphicsBackend for EglDevice<B, D>
where
B: Backend<Surface = <D as Device>::Surface> + 'static,
D: Device + NativeDisplay<B, Arguments = crtc::Handle> + 'static,
<D as Device>::Surface: NativeSurface,
{
fn bind_wl_display(&self, display: &Display) -> EGLResult<EGLDisplay> {
self.dev.bind_wl_display(display)
}
}
impl<B, D> Drop for EglDevice<B, D>
where
B: Backend<Surface = <D as Device>::Surface> + 'static,
D: Device + NativeDisplay<B, Arguments = crtc::Handle> + 'static,
<D as Device>::Surface: NativeSurface,
{
fn drop(&mut self) {
self.clear_handler();
}
}

View File

@ -0,0 +1,43 @@
//!
//! Support to register an [`EglDevice`](../struct.EglDevice.html)
//! to an open [`Session`](../../session/trait.Session.html).
//!
use drm::control::crtc;
use std::os::unix::io::RawFd;
use super::EglDevice;
use backend::drm::Device;
use backend::egl::native::{Backend, NativeDisplay, NativeSurface};
use backend::session::{AsSessionObserver, SessionObserver};
/// [`SessionObserver`](../../session/trait.SessionObserver.html)
/// linked to the [`EglDevice`](../struct.EglDevice.html) it was
/// created from.
pub struct EglDeviceObserver<S: SessionObserver + 'static> {
observer: S,
}
impl<S, B, D> AsSessionObserver<EglDeviceObserver<S>> for EglDevice<B, D>
where
S: SessionObserver + 'static,
B: Backend<Surface = <D as Device>::Surface> + 'static,
D: Device + NativeDisplay<B, Arguments = crtc::Handle> + AsSessionObserver<S> + 'static,
<D as Device>::Surface: NativeSurface,
{
fn observer(&mut self) -> EglDeviceObserver<S> {
EglDeviceObserver {
observer: self.dev.borrow_mut().observer(),
}
}
}
impl<S: SessionObserver + 'static> SessionObserver for EglDeviceObserver<S> {
fn pause(&mut self, devnum: Option<(u32, u32)>) {
self.observer.pause(devnum);
}
fn activate(&mut self, devnum: Option<(u32, u32, Option<RawFd>)>) {
self.observer.activate(devnum);
}
}

View File

@ -0,0 +1,130 @@
use drm::control::{connector, crtc, Mode};
use nix::libc::c_void;
use std::rc::Rc;
use super::error::*;
use backend::drm::{Device, Surface};
use backend::egl::native::{Backend, NativeDisplay, NativeSurface};
use backend::egl::{EGLContext, EGLSurface};
#[cfg(feature = "renderer_gl")]
use backend::graphics::gl::GLGraphicsBackend;
#[cfg(feature = "renderer_gl")]
use backend::graphics::PixelFormat;
use backend::graphics::{CursorBackend, SwapBuffersError};
/// Egl surface for rendering
pub struct EglSurface<B, D>
where
B: Backend<Surface = <D as Device>::Surface> + 'static,
D: Device + NativeDisplay<B> + 'static,
<D as Device>::Surface: NativeSurface,
{
pub(super) dev: Rc<EGLContext<B, D>>,
pub(super) surface: EGLSurface<B::Surface>,
}
impl<B, D> Surface for EglSurface<B, D>
where
B: Backend<Surface = <D as Device>::Surface> + 'static,
D: Device + NativeDisplay<B> + 'static,
<D as Device>::Surface: NativeSurface,
{
type Error = Error;
type Connectors = <<D as Device>::Surface as Surface>::Connectors;
fn crtc(&self) -> crtc::Handle {
(*self.surface).crtc()
}
fn current_connectors(&self) -> Self::Connectors {
self.surface.current_connectors()
}
fn pending_connectors(&self) -> Self::Connectors {
self.surface.pending_connectors()
}
fn add_connector(&self, connector: connector::Handle) -> Result<()> {
self.surface
.add_connector(connector)
.chain_err(|| ErrorKind::UnderlyingBackendError)
}
fn remove_connector(&self, connector: connector::Handle) -> Result<()> {
self.surface
.remove_connector(connector)
.chain_err(|| ErrorKind::UnderlyingBackendError)
}
fn current_mode(&self) -> Option<Mode> {
self.surface.current_mode()
}
fn pending_mode(&self) -> Option<Mode> {
self.surface.pending_mode()
}
fn use_mode(&self, mode: Option<Mode>) -> Result<()> {
self.surface
.use_mode(mode)
.chain_err(|| ErrorKind::UnderlyingBackendError)
}
}
impl<'a, B, D> CursorBackend<'a> for EglSurface<B, D>
where
B: Backend<Surface = <D as Device>::Surface> + 'static,
D: Device + NativeDisplay<B> + 'static,
<D as Device>::Surface: NativeSurface + CursorBackend<'a>,
{
type CursorFormat = <D::Surface as CursorBackend<'a>>::CursorFormat;
type Error = <D::Surface as CursorBackend<'a>>::Error;
fn set_cursor_position(&self, x: u32, y: u32) -> ::std::result::Result<(), Self::Error> {
self.surface.set_cursor_position(x, y)
}
fn set_cursor_representation<'b>(
&'b self,
buffer: Self::CursorFormat,
hotspot: (u32, u32),
) -> ::std::result::Result<(), Self::Error>
where
'a: 'b,
{
self.surface.set_cursor_representation(buffer, hotspot)
}
}
#[cfg(feature = "renderer_gl")]
impl<B, D> GLGraphicsBackend for EglSurface<B, D>
where
B: Backend<Surface = <D as Device>::Surface> + 'static,
D: Device + NativeDisplay<B> + 'static,
<D as Device>::Surface: NativeSurface,
{
fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> {
self.surface.swap_buffers()
}
unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void {
self.dev.get_proc_address(symbol)
}
fn get_framebuffer_dimensions(&self) -> (u32, u32) {
let (w, h) = self.pending_mode().map(|mode| mode.size()).unwrap_or((1, 1));
(w as u32, h as u32)
}
fn is_current(&self) -> bool {
self.dev.is_current() && self.surface.is_current()
}
unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> {
self.surface.make_current()
}
fn get_pixel_format(&self) -> PixelFormat {
self.dev.get_pixel_format()
}
}

View File

@ -0,0 +1,95 @@
//!
//! Egl [`NativeDisplay`](../../egl/native/trait.NativeDisplay.html) and
//! [`NativeSurface`](../../egl/native/trait.NativeSurface.html) support for
//! [`GbmDevice`](../struct.GbmDevice.html) and [`GbmSurface`](../struct.GbmSurface.html).
//!
use backend::drm::{Device, RawDevice};
use backend::egl::error::Result as EglResult;
use backend::egl::ffi;
use backend::egl::native::{Backend, NativeDisplay, NativeSurface};
use backend::graphics::SwapBuffersError;
use super::error::{Error, Result};
use super::{GbmDevice, GbmSurface};
use drm::control::{crtc, Device as ControlDevice};
use gbm::AsRaw;
use std::marker::PhantomData;
use std::ptr;
/// Egl Gbm backend type
///
/// See [`Backend`](../../egl/native/trait.Backend.html).
pub struct Gbm<D: RawDevice + 'static> {
_userdata: PhantomData<D>,
}
impl<D: RawDevice + 'static> Backend for Gbm<D> {
type Surface = GbmSurface<D>;
unsafe fn get_display<F>(
display: ffi::NativeDisplayType,
has_dp_extension: F,
log: ::slog::Logger,
) -> ffi::egl::types::EGLDisplay
where
F: Fn(&str) -> bool,
{
if has_dp_extension("EGL_KHR_platform_gbm") && ffi::egl::GetPlatformDisplay::is_loaded() {
trace!(log, "EGL Display Initialization via EGL_KHR_platform_gbm");
ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_KHR, display as *mut _, ptr::null())
} else if has_dp_extension("EGL_MESA_platform_gbm") && ffi::egl::GetPlatformDisplayEXT::is_loaded() {
trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm");
ffi::egl::GetPlatformDisplayEXT(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, ptr::null())
} else if has_dp_extension("EGL_MESA_platform_gbm") && ffi::egl::GetPlatformDisplay::is_loaded() {
trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm");
ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, ptr::null())
} else {
trace!(log, "Default EGL Display Initialization via GetDisplay");
ffi::egl::GetDisplay(display as *mut _)
}
}
}
unsafe impl<D: RawDevice + ControlDevice + 'static> NativeDisplay<Gbm<D>> for GbmDevice<D> {
type Arguments = crtc::Handle;
type Error = Error;
fn is_backend(&self) -> bool {
true
}
fn ptr(&self) -> EglResult<ffi::NativeDisplayType> {
Ok(self.dev.borrow().as_raw() as *const _)
}
fn create_surface(&mut self, crtc: crtc::Handle) -> Result<GbmSurface<D>> {
Device::create_surface(self, crtc)
}
}
unsafe impl<D: RawDevice + 'static> NativeSurface for GbmSurface<D> {
fn ptr(&self) -> ffi::NativeWindowType {
self.0.surface.borrow().as_raw() as *const _
}
fn needs_recreation(&self) -> bool {
self.needs_recreation()
}
fn recreate(&self) -> bool {
if let Err(err) = GbmSurface::recreate(self) {
error!(self.0.logger, "Failure recreating internal resources: {:?}", err);
false
} else {
true
}
}
fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> {
// this is safe since `eglSwapBuffers` will have been called exactly once
// if this is used by our egl module, which is why this trait is unsafe.
unsafe { self.page_flip() }
}
}

View File

@ -0,0 +1,53 @@
//!
//! Errors thrown by the `DrmDevice` and `DrmBackend`
//!
error_chain! {
errors {
#[doc = "Creation of gbm device failed"]
InitFailed {
description("Creation of gbm device failed"),
display("Creation of gbm device failed"),
}
#[doc = "Creation of gbm surface failed"]
SurfaceCreationFailed {
description("Creation of gbm surface failed"),
display("Creation of gbm surface failed"),
}
#[doc = "No mode is set, blocking the current operation"]
NoModeSet {
description("No mode is currently set"),
display("No mode is currently set"),
}
#[doc = "Creation of gbm buffer object failed"]
BufferCreationFailed {
description("Creation of gbm buffer object failed"),
display("Creation of gbm buffer object failed"),
}
#[doc = "Writing to gbm buffer failed"]
BufferWriteFailed {
description("Writing to gbm buffer failed"),
display("Writing to gbm buffer failed"),
}
#[doc = "Lock of gbm surface front buffer failed"]
FrontBufferLockFailed {
description("Lock of gbm surface front buffer failed"),
display("Lock of gbm surface front buffer failed"),
}
#[doc = "Underlying backend failed"]
UnderlyingBackendError {
description("The underlying backend reported an error"),
display("The underlying backend reported an error"),
}
}
foreign_links {
FailedToSwap(::backend::graphics::SwapBuffersError) #[doc = "Swapping front buffers failed"];
}
}

213
src/backend/drm/gbm/mod.rs Normal file
View File

@ -0,0 +1,213 @@
//!
//! [`Device`](../trait.Device.html) and [`Surface`](../trait.Surface.html)
//! implementations using gbm buffers for efficient rendering.
//!
//! Usually this implementation will be wrapped into a [`EglDevice`](../egl/struct.EglDevice.html).
//! Take a look at `anvil`s source code for an example of this.
//!
//! To use these types standalone, you will need to consider the special requirements
//! of [`GbmSurface::page_flip`](struct.GbmSurface.html#method.page_flip).
//!
use super::{Device, DeviceHandler, RawDevice, ResourceHandles, ResourceInfo, Surface};
use drm::control::{crtc, Device as ControlDevice};
use gbm::{self, BufferObjectFlags, Format as GbmFormat};
use nix::libc::dev_t;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::os::unix::io::{AsRawFd, RawFd};
use std::rc::{Rc, Weak};
use std::sync::{Once, ONCE_INIT};
pub mod error;
use self::error::*;
mod surface;
pub use self::surface::GbmSurface;
use self::surface::GbmSurfaceInternal;
#[cfg(feature = "backend_egl")]
pub mod egl;
#[cfg(feature = "backend_session")]
pub mod session;
static LOAD: Once = ONCE_INIT;
/// Representation of an open gbm device to create rendering surfaces
pub struct GbmDevice<D: RawDevice + ControlDevice + 'static> {
pub(self) dev: Rc<RefCell<gbm::Device<D>>>,
backends: Rc<RefCell<HashMap<crtc::Handle, Weak<GbmSurfaceInternal<D>>>>>,
logger: ::slog::Logger,
}
impl<D: RawDevice + ControlDevice + 'static> GbmDevice<D> {
/// Create a new `GbmDevice` from an open drm node
///
/// Returns an error if the file is no valid drm node or context creation was not
/// successful.
pub fn new<L>(mut dev: D, logger: L) -> Result<Self>
where
L: Into<Option<::slog::Logger>>,
{
/* GBM will load a dri driver, but even though they need symbols from
* libglapi, in some version of Mesa they are not linked to it. Since
* only the gl-renderer module links to it, these symbols won't be
* globally available, and loading the DRI driver fails.
* Workaround this by dlopen()'ing libglapi with RTLD_GLOBAL.
*/
LOAD.call_once(|| unsafe {
nix::libc::dlopen(
"libglapi.so.0".as_ptr() as *const _,
nix::libc::RTLD_LAZY | nix::libc::RTLD_GLOBAL,
);
});
let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_gbm"));
dev.clear_handler();
debug!(log, "Creating gbm device");
Ok(GbmDevice {
// Open the gbm device from the drm device
dev: Rc::new(RefCell::new(
gbm::Device::new(dev).chain_err(|| ErrorKind::InitFailed)?,
)),
backends: Rc::new(RefCell::new(HashMap::new())),
logger: log,
})
}
}
struct InternalDeviceHandler<D: RawDevice + ControlDevice + 'static> {
handler: Box<DeviceHandler<Device = GbmDevice<D>> + 'static>,
backends: Weak<RefCell<HashMap<crtc::Handle, Weak<GbmSurfaceInternal<D>>>>>,
logger: ::slog::Logger,
}
impl<D: RawDevice + ControlDevice + 'static> DeviceHandler for InternalDeviceHandler<D> {
type Device = D;
fn vblank(&mut self, crtc: crtc::Handle) {
if let Some(backends) = self.backends.upgrade() {
if let Some(surface) = backends.borrow().get(&crtc) {
if let Some(surface) = surface.upgrade() {
surface.unlock_buffer();
self.handler.vblank(crtc);
}
} else {
warn!(
self.logger,
"Surface ({:?}) not managed by gbm, event not handled.", crtc
);
}
}
}
fn error(&mut self, error: <<D as Device>::Surface as Surface>::Error) {
self.handler
.error(ResultExt::<()>::chain_err(Err(error), || ErrorKind::UnderlyingBackendError).unwrap_err())
}
}
impl<D: RawDevice + ControlDevice + 'static> Device for GbmDevice<D> {
type Surface = GbmSurface<D>;
fn device_id(&self) -> dev_t {
self.dev.borrow().device_id()
}
fn set_handler(&mut self, handler: impl DeviceHandler<Device = Self> + 'static) {
self.dev.borrow_mut().set_handler(InternalDeviceHandler {
handler: Box::new(handler),
backends: Rc::downgrade(&self.backends),
logger: self.logger.clone(),
});
}
fn clear_handler(&mut self) {
self.dev.borrow_mut().clear_handler();
}
fn create_surface(&mut self, crtc: crtc::Handle) -> Result<GbmSurface<D>> {
info!(self.logger, "Initializing GbmSurface");
let drm_surface = Device::create_surface(&mut **self.dev.borrow_mut(), crtc)
.chain_err(|| ErrorKind::UnderlyingBackendError)?;
// initialize the surface
let (w, h) = drm_surface
.pending_mode()
.map(|mode| mode.size())
.unwrap_or((1, 1));
let surface = self
.dev
.borrow()
.create_surface(
w as u32,
h as u32,
GbmFormat::XRGB8888,
BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING,
)
.chain_err(|| ErrorKind::SurfaceCreationFailed)?;
// initialize a buffer for the cursor image
let cursor = Cell::new((
self.dev
.borrow()
.create_buffer_object(
1,
1,
GbmFormat::ARGB8888,
BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE,
)
.chain_err(|| ErrorKind::BufferCreationFailed)?,
(0, 0),
));
let backend = Rc::new(GbmSurfaceInternal {
dev: self.dev.clone(),
surface: RefCell::new(surface),
crtc: drm_surface,
cursor,
current_frame_buffer: Cell::new(None),
front_buffer: Cell::new(None),
next_buffer: Cell::new(None),
recreated: Cell::new(true),
logger: self.logger.new(o!("crtc" => format!("{:?}", crtc))),
});
self.backends.borrow_mut().insert(crtc, Rc::downgrade(&backend));
Ok(GbmSurface(backend))
}
fn process_events(&mut self) {
self.dev.borrow_mut().process_events()
}
fn resource_info<T: ResourceInfo>(&self, handle: T::Handle) -> Result<T> {
self.dev
.borrow()
.resource_info(handle)
.chain_err(|| ErrorKind::UnderlyingBackendError)
}
fn resource_handles(&self) -> Result<ResourceHandles> {
self.dev
.borrow()
.resource_handles()
.chain_err(|| ErrorKind::UnderlyingBackendError)
}
}
impl<D: RawDevice + ControlDevice + 'static> AsRawFd for GbmDevice<D> {
fn as_raw_fd(&self) -> RawFd {
self.dev.borrow().as_raw_fd()
}
}
impl<D: RawDevice + ControlDevice + 'static> Drop for GbmDevice<D> {
fn drop(&mut self) {
self.clear_handler();
}
}

View File

@ -0,0 +1,88 @@
//!
//! Support to register a [`GbmDevice`](../struct.GbmDevice.html)
//! to an open [`Session`](../../session/trait.Session.html).
//!
use drm::control::{crtc, Device as ControlDevice, ResourceInfo};
use gbm::BufferObject;
use std::cell::RefCell;
use std::collections::HashMap;
use std::os::unix::io::RawFd;
use std::rc::{Rc, Weak};
use super::{GbmDevice, GbmSurfaceInternal};
use backend::drm::{RawDevice, RawSurface};
use backend::session::{AsSessionObserver, SessionObserver};
/// [`SessionObserver`](../../session/trait.SessionObserver.html)
/// linked to the [`GbmDevice`](../struct.GbmDevice.html) it was
/// created from.
pub struct GbmDeviceObserver<
S: SessionObserver + 'static,
D: RawDevice + ControlDevice + AsSessionObserver<S> + 'static,
> {
observer: S,
backends: Weak<RefCell<HashMap<crtc::Handle, Weak<GbmSurfaceInternal<D>>>>>,
logger: ::slog::Logger,
}
impl<S: SessionObserver + 'static, D: RawDevice + ControlDevice + AsSessionObserver<S> + 'static>
AsSessionObserver<GbmDeviceObserver<S, D>> for GbmDevice<D>
{
fn observer(&mut self) -> GbmDeviceObserver<S, D> {
GbmDeviceObserver {
observer: (**self.dev.borrow_mut()).observer(),
backends: Rc::downgrade(&self.backends),
logger: self.logger.clone(),
}
}
}
impl<S: SessionObserver + 'static, D: RawDevice + ControlDevice + AsSessionObserver<S> + 'static>
SessionObserver for GbmDeviceObserver<S, D>
{
fn pause(&mut self, devnum: Option<(u32, u32)>) {
self.observer.pause(devnum);
}
fn activate(&mut self, devnum: Option<(u32, u32, Option<RawFd>)>) {
self.observer.activate(devnum);
let mut crtcs = Vec::new();
if let Some(backends) = self.backends.upgrade() {
for (crtc, backend) in backends.borrow().iter() {
if let Some(backend) = backend.upgrade() {
// restart rendering loop, if it was previously running
if let Some(Err(err)) = backend
.current_frame_buffer
.get()
.map(|fb| backend.crtc.page_flip(fb.handle()))
{
warn!(self.logger, "Failed to restart rendering loop. Error: {}", err);
}
// reset cursor
{
let &(ref cursor, ref hotspot): &(BufferObject<()>, (u32, u32)) =
unsafe { &*backend.cursor.as_ptr() };
if crtc::set_cursor2(
&*backend.dev.borrow(),
*crtc,
cursor,
((*hotspot).0 as i32, (*hotspot).1 as i32),
)
.is_err()
{
if let Err(err) = crtc::set_cursor(&*backend.dev.borrow(), *crtc, cursor) {
error!(self.logger, "Failed to reset cursor. Error: {}", err);
}
}
}
} else {
crtcs.push(*crtc);
}
}
for crtc in crtcs {
backends.borrow_mut().remove(&crtc);
}
}
}
}

View File

@ -0,0 +1,373 @@
use super::super::{Device, RawDevice, RawSurface, Surface};
use super::error::*;
use drm::control::{connector, crtc, framebuffer, Mode, ResourceInfo};
use gbm::{self, BufferObject, BufferObjectFlags, Format as GbmFormat, SurfaceBufferHandle};
use image::{ImageBuffer, Rgba};
use std::cell::{Cell, RefCell};
use std::os::unix::io::AsRawFd;
use std::rc::Rc;
#[cfg(feature = "backend_drm_legacy")]
use backend::drm::legacy::LegacyDrmDevice;
use backend::graphics::CursorBackend;
use backend::graphics::SwapBuffersError;
pub(super) struct GbmSurfaceInternal<D: RawDevice + 'static> {
pub(super) dev: Rc<RefCell<gbm::Device<D>>>,
pub(super) surface: RefCell<gbm::Surface<framebuffer::Info>>,
pub(super) crtc: <D as Device>::Surface,
pub(super) cursor: Cell<(BufferObject<()>, (u32, u32))>,
pub(super) current_frame_buffer: Cell<Option<framebuffer::Info>>,
pub(super) front_buffer: Cell<Option<SurfaceBufferHandle<framebuffer::Info>>>,
pub(super) next_buffer: Cell<Option<SurfaceBufferHandle<framebuffer::Info>>>,
pub(super) recreated: Cell<bool>,
pub(super) logger: ::slog::Logger,
}
impl<D: RawDevice + 'static> GbmSurfaceInternal<D> {
pub(super) fn unlock_buffer(&self) {
// after the page swap is finished we need to release the rendered buffer.
// this is called from the PageFlipHandler
trace!(self.logger, "Releasing old front buffer");
self.front_buffer.set(self.next_buffer.replace(None));
// drop and release the old buffer
}
pub unsafe fn page_flip(&self) -> ::std::result::Result<(), SwapBuffersError> {
let res = {
let nb = self.next_buffer.take();
let res = nb.is_some();
self.next_buffer.set(nb);
res
};
if res {
// We cannot call lock_front_buffer anymore without releasing the previous buffer, which will happen when the page flip is done
warn!(self.logger, "Tried to swap with an already queued flip");
return Err(SwapBuffersError::AlreadySwapped);
}
// supporting only one buffer would cause a lot of inconvinience and
// would most likely result in a lot of flickering.
// neither weston, wlc or wlroots bother with that as well.
// so we just assume we got at least two buffers to do flipping.
let mut next_bo = self
.surface
.borrow()
.lock_front_buffer()
.expect("Surface only has one front buffer. Not supported by smithay");
// create a framebuffer if the front buffer does not have one already
// (they are reused by gbm)
let maybe_fb = next_bo
.userdata()
.map_err(|_| SwapBuffersError::ContextLost)?
.cloned();
let fb = if let Some(info) = maybe_fb {
info
} else {
let fb = framebuffer::create(&self.crtc, &*next_bo).map_err(|_| SwapBuffersError::ContextLost)?;
next_bo.set_userdata(fb).unwrap();
fb
};
self.next_buffer.set(Some(next_bo));
if self.recreated.get() {
debug!(self.logger, "Commiting new state");
self.crtc
.commit(fb.handle())
.map_err(|_| SwapBuffersError::ContextLost)?;
self.recreated.set(false);
}
trace!(self.logger, "Queueing Page flip");
self.crtc.page_flip(fb.handle())?;
self.current_frame_buffer.set(Some(fb));
Ok(())
}
pub fn recreate(&self) -> Result<()> {
let (w, h) = self.pending_mode().chain_err(|| ErrorKind::NoModeSet)?.size();
// Recreate the surface and the related resources to match the new
// resolution.
debug!(self.logger, "(Re-)Initializing surface for mode: {}:{}", w, h);
let surface = self
.dev
.borrow_mut()
.create_surface(
w as u32,
h as u32,
GbmFormat::XRGB8888,
BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING,
)
.chain_err(|| ErrorKind::SurfaceCreationFailed)?;
// Clean up buffers
if let Some(Ok(Some(fb))) = self.next_buffer.take().map(|mut bo| bo.take_userdata()) {
if let Err(err) = framebuffer::destroy(&self.crtc, fb.handle()) {
warn!(
self.logger,
"Error releasing old back_buffer framebuffer: {:?}", err
);
}
}
if let Some(Ok(Some(fb))) = self.front_buffer.take().map(|mut bo| bo.take_userdata()) {
if let Err(err) = framebuffer::destroy(&self.crtc, fb.handle()) {
warn!(
self.logger,
"Error releasing old front_buffer framebuffer: {:?}", err
);
}
}
// Drop the old surface after cleanup
*self.surface.borrow_mut() = surface;
self.recreated.set(true);
Ok(())
}
}
impl<D: RawDevice + 'static> Surface for GbmSurfaceInternal<D> {
type Connectors = <<D as Device>::Surface as Surface>::Connectors;
type Error = Error;
fn crtc(&self) -> crtc::Handle {
self.crtc.crtc()
}
fn current_connectors(&self) -> Self::Connectors {
self.crtc.current_connectors()
}
fn pending_connectors(&self) -> Self::Connectors {
self.crtc.pending_connectors()
}
fn add_connector(&self, connector: connector::Handle) -> Result<()> {
self.crtc
.add_connector(connector)
.chain_err(|| ErrorKind::UnderlyingBackendError)
}
fn remove_connector(&self, connector: connector::Handle) -> Result<()> {
self.crtc
.remove_connector(connector)
.chain_err(|| ErrorKind::UnderlyingBackendError)
}
fn current_mode(&self) -> Option<Mode> {
self.crtc.current_mode()
}
fn pending_mode(&self) -> Option<Mode> {
self.crtc.pending_mode()
}
fn use_mode(&self, mode: Option<Mode>) -> Result<()> {
self.crtc
.use_mode(mode)
.chain_err(|| ErrorKind::UnderlyingBackendError)
}
}
// FIXME:
//
// Option 1: When there is GAT support, impl `GraphicsBackend` for `LegacyDrmBackend`
// using a new generic `B: Buffer` and use this:
/*
impl<'a, D: RawDevice + 'static> CursorBackend<'a> for GbmSurfaceInternal<D>
where
<D as RawDevice>::Surface: CursorBackend<'a>,
<<D as RawDevice>::Surface as CursorBackend<'a>>::CursorFormat: Buffer,
<<D as RawDevice>::Surface as CursorBackend<'a>>::Error: ::std::error::Error + Send
{
*/
//
// Option 2: When equality checks in where clauses are supported, we could at least do this:
/*
impl<'a, D: RawDevice + 'static> GraphicsBackend<'a> for GbmSurfaceInternal<D>
where
<D as RawDevice>::Surface: CursorBackend<'a>,
<<D as RawDevice>::Surface as CursorBackend<'a>>::CursorFormat=&'a Buffer,
<<D as RawDevice>::Surface as CursorBackend<'a>>::Error: ::std::error::Error + Send
{
*/
// But for now got to do this:
#[cfg(feature = "backend_drm_legacy")]
impl<'a, A: AsRawFd + 'static> CursorBackend<'a> for GbmSurfaceInternal<LegacyDrmDevice<A>> {
type CursorFormat = &'a ImageBuffer<Rgba<u8>, Vec<u8>>;
type Error = Error;
fn set_cursor_position(&self, x: u32, y: u32) -> Result<()> {
ResultExt::chain_err(self.crtc.set_cursor_position(x, y), || {
ErrorKind::UnderlyingBackendError
})
}
fn set_cursor_representation<'b>(
&'b self,
buffer: &ImageBuffer<Rgba<u8>, Vec<u8>>,
hotspot: (u32, u32),
) -> Result<()>
where
'a: 'b,
{
let (w, h) = buffer.dimensions();
debug!(self.logger, "Importing cursor");
// import the cursor into a buffer we can render
let mut cursor = self
.dev
.borrow_mut()
.create_buffer_object(
w,
h,
GbmFormat::ARGB8888,
BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE,
)
.chain_err(|| ErrorKind::BufferCreationFailed)?;
cursor
.write(&**buffer)
.chain_err(|| ErrorKind::BufferWriteFailed)?
.chain_err(|| ErrorKind::BufferWriteFailed)?;
trace!(self.logger, "Setting the new imported cursor");
ResultExt::chain_err(self.crtc.set_cursor_representation(&cursor, hotspot), || {
ErrorKind::UnderlyingBackendError
})?;
// and store it
self.cursor.set((cursor, hotspot));
Ok(())
}
}
impl<D: RawDevice + 'static> Drop for GbmSurfaceInternal<D> {
fn drop(&mut self) {
// Drop framebuffers attached to the userdata of the gbm surface buffers.
// (They don't implement drop, as they need the device)
if let Ok(Some(fb)) = {
if let Some(mut next) = self.next_buffer.take() {
next.take_userdata()
} else {
Ok(None)
}
} {
// ignore failure at this point
let _ = framebuffer::destroy(&self.crtc, fb.handle());
}
if let Ok(Some(fb)) = {
if let Some(mut next) = self.front_buffer.take() {
next.take_userdata()
} else {
Ok(None)
}
} {
// ignore failure at this point
let _ = framebuffer::destroy(&self.crtc, fb.handle());
}
}
}
/// Gbm surface for rendering
pub struct GbmSurface<D: RawDevice + 'static>(pub(super) Rc<GbmSurfaceInternal<D>>);
impl<D: RawDevice + 'static> GbmSurface<D> {
/// Flips the underlying buffers.
///
/// The surface will report being already flipped until the matching event
/// was processed either by calling `GbmDevice::process_events` manually after the flip
/// (bad idea performance-wise) or by binding the device to an event-loop by using
/// `device_bind`.
///
/// *Note*: This might trigger a full modeset on the underlying device,
/// potentially causing some flickering. In that case this operation is
/// blocking until the crtc is in the desired state.
pub unsafe fn page_flip(&self) -> ::std::result::Result<(), SwapBuffersError> {
self.0.page_flip()
}
/// Recreate underlying gbm resources.
///
/// This recreates the gbm surfaces resources, which might be needed after e.g.
/// calling [`Surface::use_mode`](../trait.Surface.html#method.use_mode).
/// You may check if your `GbmSurface` needs recreation through
/// [`needs_recreation`](#method.needs_recreation).
pub fn recreate(&self) -> Result<()> {
self.0.recreate()
}
/// Check if underlying gbm resources need to be recreated.
pub fn needs_recreation(&self) -> bool {
self.0.crtc.commit_pending()
}
}
impl<D: RawDevice + 'static> Surface for GbmSurface<D> {
type Connectors = <<D as Device>::Surface as Surface>::Connectors;
type Error = Error;
fn crtc(&self) -> crtc::Handle {
self.0.crtc()
}
fn current_connectors(&self) -> Self::Connectors {
self.0.current_connectors()
}
fn pending_connectors(&self) -> Self::Connectors {
self.0.pending_connectors()
}
fn add_connector(&self, connector: connector::Handle) -> Result<()> {
self.0.add_connector(connector)
}
fn remove_connector(&self, connector: connector::Handle) -> Result<()> {
self.0.remove_connector(connector)
}
fn current_mode(&self) -> Option<Mode> {
self.0.current_mode()
}
fn pending_mode(&self) -> Option<Mode> {
self.0.pending_mode()
}
fn use_mode(&self, mode: Option<Mode>) -> Result<()> {
self.0.use_mode(mode)
}
}
#[cfg(feature = "backend_drm_legacy")]
impl<'a, A: AsRawFd + 'static> CursorBackend<'a> for GbmSurface<LegacyDrmDevice<A>> {
type CursorFormat = &'a ImageBuffer<Rgba<u8>, Vec<u8>>;
type Error = Error;
fn set_cursor_position(&self, x: u32, y: u32) -> Result<()> {
self.0.set_cursor_position(x, y)
}
fn set_cursor_representation<'b>(
&'b self,
buffer: &ImageBuffer<Rgba<u8>, Vec<u8>>,
hotspot: (u32, u32),
) -> Result<()>
where
'a: 'b,
{
self.0.set_cursor_representation(buffer, hotspot)
}
}

View File

@ -1,8 +1,7 @@
//!
//! Errors thrown by the `DrmDevice` and `DrmBackend`
//! Errors thrown by the `LegacyDrmDevice` and `LegacyDrmSurface`
//!
use backend::graphics::egl::error as egl;
use drm::control::{connector, crtc, Mode};
error_chain! {
@ -23,18 +22,6 @@ error_chain! {
description("Unable to determine device id of drm device"),
}
#[doc = "Creation of gbm resource failed"]
GbmInitFailed {
description("Creation of gbm resource failed"),
display("Creation of gbm resource failed"),
}
#[doc = "Swapping front buffers failed"]
FailedToSwap {
description("Swapping front buffers failed"),
display("Swapping front buffers failed"),
}
#[doc = "Device is currently paused"]
DeviceInactive {
description("Device is currently paused, operation rejected"),
@ -60,7 +47,7 @@ error_chain! {
}
}
links {
EGL(egl::Error, egl::ErrorKind) #[doc = "EGL error"];
foreign_links {
FailedToSwap(::backend::graphics::SwapBuffersError) #[doc = "Swapping front buffers failed"];
}
}

View File

@ -0,0 +1,286 @@
//!
//! [`RawDevice`](../trait.RawDevice.html) and [`RawSurface`](../trait.RawSurface.html)
//! implementations using the legacy mode-setting infrastructure.
//!
//! Usually this implementation will be wrapped into a [`GbmDevice`](../gbm/struct.GbmDevice.html).
//! Take a look at `anvil`s source code for an example of this.
//!
//! For an example how to use this standalone, take a look at the `raw_drm` example.
//!
use super::{DevPath, Device, DeviceHandler, RawDevice};
use drm::control::{connector, crtc, encoder, Device as ControlDevice, ResourceHandles, ResourceInfo};
use drm::Device as BasicDevice;
use nix::libc::dev_t;
use nix::sys::stat::fstat;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::os::unix::io::{AsRawFd, RawFd};
use std::rc::{Rc, Weak};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock};
mod surface;
pub use self::surface::LegacyDrmSurface;
use self::surface::{LegacyDrmSurfaceInternal, State};
pub mod error;
use self::error::*;
#[cfg(feature = "backend_session")]
pub mod session;
/// Open raw drm device utilizing legacy mode-setting
pub struct LegacyDrmDevice<A: AsRawFd + 'static> {
dev: Rc<Dev<A>>,
dev_id: dev_t,
active: Arc<AtomicBool>,
backends: Rc<RefCell<HashMap<crtc::Handle, Weak<LegacyDrmSurfaceInternal<A>>>>>,
handler: Option<RefCell<Box<DeviceHandler<Device = LegacyDrmDevice<A>>>>>,
logger: ::slog::Logger,
}
pub(in crate::backend::drm) struct Dev<A: AsRawFd + 'static> {
fd: A,
priviledged: bool,
active: Arc<AtomicBool>,
old_state: HashMap<crtc::Handle, (crtc::Info, Vec<connector::Handle>)>,
logger: ::slog::Logger,
}
impl<A: AsRawFd + 'static> AsRawFd for Dev<A> {
fn as_raw_fd(&self) -> RawFd {
self.fd.as_raw_fd()
}
}
impl<A: AsRawFd + 'static> BasicDevice for Dev<A> {}
impl<A: AsRawFd + 'static> ControlDevice for Dev<A> {}
impl<A: AsRawFd + 'static> Drop for Dev<A> {
fn drop(&mut self) {
info!(self.logger, "Dropping device: {:?}", self.dev_path());
if self.active.load(Ordering::SeqCst) {
// Here we restore the tty to it's previous state.
// In case e.g. getty was running on the tty sets the correct framebuffer again,
// so that getty will be visible.
let old_state = self.old_state.clone();
for (handle, (info, connectors)) in old_state {
if let Err(err) = crtc::set(
&*self,
handle,
info.fb(),
&connectors,
info.position(),
info.mode(),
) {
error!(self.logger, "Failed to reset crtc ({:?}). Error: {}", handle, err);
}
}
}
if self.priviledged {
if let Err(err) = self.drop_master() {
error!(self.logger, "Failed to drop drm master state. Error: {}", err);
}
}
}
}
impl<A: AsRawFd + 'static> LegacyDrmDevice<A> {
/// Create a new `LegacyDrmDevice` from an open drm node
///
/// Returns an error if the file is no valid drm node or context creation was not
/// successful.
pub fn new<L>(dev: A, logger: L) -> Result<Self>
where
L: Into<Option<::slog::Logger>>,
{
let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm"));
info!(log, "DrmDevice initializing");
let dev_id = fstat(dev.as_raw_fd())
.chain_err(|| ErrorKind::UnableToGetDeviceId)?
.st_rdev;
let active = Arc::new(AtomicBool::new(true));
let mut dev = Dev {
fd: dev,
priviledged: true,
old_state: HashMap::new(),
active: active.clone(),
logger: log.clone(),
};
// we want to modeset, so we better be the master, if we run via a tty session
if dev.set_master().is_err() {
warn!(log, "Unable to become drm master, assuming unpriviledged mode");
dev.priviledged = false;
};
// enumerate (and save) the current device state
let res_handles = ControlDevice::resource_handles(&dev).chain_err(|| {
ErrorKind::DrmDev(format!("Error loading drm resources on {:?}", dev.dev_path()))
})?;
for &con in res_handles.connectors() {
let con_info = connector::Info::load_from_device(&dev, con).chain_err(|| {
ErrorKind::DrmDev(format!("Error loading connector info on {:?}", dev.dev_path()))
})?;
if let Some(enc) = con_info.current_encoder() {
let enc_info = encoder::Info::load_from_device(&dev, enc).chain_err(|| {
ErrorKind::DrmDev(format!("Error loading encoder info on {:?}", dev.dev_path()))
})?;
if let Some(crtc) = enc_info.current_crtc() {
let info = crtc::Info::load_from_device(&dev, crtc).chain_err(|| {
ErrorKind::DrmDev(format!("Error loading crtc info on {:?}", dev.dev_path()))
})?;
dev.old_state
.entry(crtc)
.or_insert((info, Vec::new()))
.1
.push(con);
}
}
}
Ok(LegacyDrmDevice {
dev: Rc::new(dev),
dev_id,
active,
backends: Rc::new(RefCell::new(HashMap::new())),
handler: None,
logger: log.clone(),
})
}
}
impl<A: AsRawFd + 'static> AsRawFd for LegacyDrmDevice<A> {
fn as_raw_fd(&self) -> RawFd {
self.dev.as_raw_fd()
}
}
impl<A: AsRawFd + 'static> BasicDevice for LegacyDrmDevice<A> {}
impl<A: AsRawFd + 'static> ControlDevice for LegacyDrmDevice<A> {}
impl<A: AsRawFd + 'static> Device for LegacyDrmDevice<A> {
type Surface = LegacyDrmSurface<A>;
fn device_id(&self) -> dev_t {
self.dev_id
}
fn set_handler(&mut self, handler: impl DeviceHandler<Device = Self> + 'static) {
self.handler = Some(RefCell::new(Box::new(handler)));
}
fn clear_handler(&mut self) {
let _ = self.handler.take();
}
fn create_surface(&mut self, crtc: crtc::Handle) -> Result<LegacyDrmSurface<A>> {
if self.backends.borrow().contains_key(&crtc) {
bail!(ErrorKind::CrtcAlreadyInUse(crtc));
}
if !self.active.load(Ordering::SeqCst) {
bail!(ErrorKind::DeviceInactive);
}
// Try to enumarate the current state to set the initial state variable correctly
let crtc_info = crtc::Info::load_from_device(self, crtc)
.chain_err(|| ErrorKind::DrmDev(format!("Error loading crtc info on {:?}", self.dev_path())))?;
let mode = crtc_info.mode();
let mut connectors = HashSet::new();
let res_handles = ControlDevice::resource_handles(self).chain_err(|| {
ErrorKind::DrmDev(format!("Error loading drm resources on {:?}", self.dev_path()))
})?;
for &con in res_handles.connectors() {
let con_info = connector::Info::load_from_device(self, con).chain_err(|| {
ErrorKind::DrmDev(format!("Error loading connector info on {:?}", self.dev_path()))
})?;
if let Some(enc) = con_info.current_encoder() {
let enc_info = encoder::Info::load_from_device(self, enc).chain_err(|| {
ErrorKind::DrmDev(format!("Error loading encoder info on {:?}", self.dev_path()))
})?;
if let Some(current_crtc) = enc_info.current_crtc() {
if crtc == current_crtc {
connectors.insert(con);
}
}
}
}
let state = State { mode, connectors };
let backend = Rc::new(LegacyDrmSurfaceInternal {
dev: self.dev.clone(),
crtc,
state: RwLock::new(state.clone()),
pending: RwLock::new(state),
logger: self.logger.new(o!("crtc" => format!("{:?}", crtc))),
});
self.backends.borrow_mut().insert(crtc, Rc::downgrade(&backend));
Ok(LegacyDrmSurface(backend))
}
fn process_events(&mut self) {
match crtc::receive_events(self) {
Ok(events) => {
for event in events {
if let crtc::Event::PageFlip(event) = event {
if self.active.load(Ordering::SeqCst) {
if self
.backends
.borrow()
.get(&event.crtc)
.iter()
.flat_map(|x| x.upgrade())
.next()
.is_some()
{
trace!(self.logger, "Handling event for backend {:?}", event.crtc);
if let Some(handler) = self.handler.as_ref() {
handler.borrow_mut().vblank(event.crtc);
}
} else {
self.backends.borrow_mut().remove(&event.crtc);
}
}
}
}
}
Err(err) => {
if let Some(handler) = self.handler.as_ref() {
handler.borrow_mut().error(
ResultExt::<()>::chain_err(Err(err), || {
ErrorKind::DrmDev(format!("Error processing drm events on {:?}", self.dev_path()))
})
.unwrap_err(),
);
}
}
}
}
fn resource_info<T: ResourceInfo>(&self, handle: T::Handle) -> Result<T> {
T::load_from_device(self, handle)
.chain_err(|| ErrorKind::DrmDev(format!("Error loading resource info on {:?}", self.dev_path())))
}
fn resource_handles(&self) -> Result<ResourceHandles> {
ControlDevice::resource_handles(self)
.chain_err(|| ErrorKind::DrmDev(format!("Error loading resource info on {:?}", self.dev_path())))
}
}
impl<A: AsRawFd + 'static> RawDevice for LegacyDrmDevice<A> {
type Surface = LegacyDrmSurface<A>;
}
impl<A: AsRawFd + 'static> Drop for LegacyDrmDevice<A> {
fn drop(&mut self) {
self.clear_handler();
}
}

View File

@ -0,0 +1,92 @@
//!
//! Support to register an open [`LegacyDrmDevice`](../struct.LegacyDrmDevice.html)
//! to an open [`Session`](../../session/trait.Session.html).
//!
use drm::control::crtc;
use drm::Device as BasicDevice;
use nix::libc::dev_t;
use nix::sys::stat;
use std::cell::RefCell;
use std::collections::HashMap;
use std::os::unix::io::{AsRawFd, RawFd};
use std::rc::{Rc, Weak};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use super::{Dev, LegacyDrmDevice, LegacyDrmSurfaceInternal};
use backend::session::{AsSessionObserver, SessionObserver};
/// [`SessionObserver`](../../session/trait.SessionObserver.html)
/// linked to the [`LegacyDrmDevice`](../struct.LegacyDrmDevice.html)
/// it was created from.
pub struct LegacyDrmDeviceObserver<A: AsRawFd + 'static> {
dev: Weak<Dev<A>>,
dev_id: dev_t,
priviledged: bool,
active: Arc<AtomicBool>,
backends: Weak<RefCell<HashMap<crtc::Handle, Weak<LegacyDrmSurfaceInternal<A>>>>>,
logger: ::slog::Logger,
}
impl<A: AsRawFd + 'static> AsSessionObserver<LegacyDrmDeviceObserver<A>> for LegacyDrmDevice<A> {
fn observer(&mut self) -> LegacyDrmDeviceObserver<A> {
LegacyDrmDeviceObserver {
dev: Rc::downgrade(&self.dev),
dev_id: self.dev_id,
active: self.active.clone(),
priviledged: self.dev.priviledged,
backends: Rc::downgrade(&self.backends),
logger: self.logger.clone(),
}
}
}
impl<A: AsRawFd + 'static> SessionObserver for LegacyDrmDeviceObserver<A> {
fn pause(&mut self, devnum: Option<(u32, u32)>) {
if let Some((major, minor)) = devnum {
if major as u64 != stat::major(self.dev_id) || minor as u64 != stat::minor(self.dev_id) {
return;
}
}
if let Some(device) = self.dev.upgrade() {
if let Some(backends) = self.backends.upgrade() {
for surface in backends.borrow().values().filter_map(Weak::upgrade) {
// other ttys that use no cursor, might not clear it themselves.
// This makes sure our cursor won't stay visible.
let _ = crtc::clear_cursor(&*device, surface.crtc);
}
}
}
self.active.store(false, Ordering::SeqCst);
if self.priviledged {
if let Some(device) = self.dev.upgrade() {
if let Err(err) = device.drop_master() {
error!(self.logger, "Failed to drop drm master state. Error: {}", err);
}
}
}
}
fn activate(&mut self, devnum: Option<(u32, u32, Option<RawFd>)>) {
if let Some((major, minor, fd)) = devnum {
if major as u64 != stat::major(self.dev_id) || minor as u64 != stat::minor(self.dev_id) {
return;
} else if let Some(fd) = fd {
info!(self.logger, "Replacing fd");
if let Some(device) = self.dev.upgrade() {
::nix::unistd::dup2(device.as_raw_fd(), fd)
.expect("Failed to replace file descriptor of drm device");
}
}
}
self.active.store(true, Ordering::SeqCst);
if self.priviledged {
if let Some(device) = self.dev.upgrade() {
if let Err(err) = device.set_master() {
crit!(self.logger, "Failed to acquire drm master again. Error: {}", err);
}
}
}
}
}

View File

@ -0,0 +1,317 @@
pub use drm::buffer::Buffer;
use drm::control::{connector, crtc, encoder, framebuffer, Device as ControlDevice, Mode, ResourceInfo};
use drm::Device as BasicDevice;
use std::collections::HashSet;
use std::os::unix::io::{AsRawFd, RawFd};
use std::rc::Rc;
use std::sync::RwLock;
use backend::drm::{DevPath, RawSurface, Surface};
use backend::graphics::CursorBackend;
use backend::graphics::SwapBuffersError;
use super::{error::*, Dev};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct State {
pub mode: Option<Mode>,
pub connectors: HashSet<connector::Handle>,
}
pub(super) struct LegacyDrmSurfaceInternal<A: AsRawFd + 'static> {
pub(super) dev: Rc<Dev<A>>,
pub(super) crtc: crtc::Handle,
pub(super) state: RwLock<State>,
pub(super) pending: RwLock<State>,
pub(super) logger: ::slog::Logger,
}
impl<A: AsRawFd + 'static> AsRawFd for LegacyDrmSurfaceInternal<A> {
fn as_raw_fd(&self) -> RawFd {
self.dev.as_raw_fd()
}
}
impl<A: AsRawFd + 'static> BasicDevice for LegacyDrmSurfaceInternal<A> {}
impl<A: AsRawFd + 'static> ControlDevice for LegacyDrmSurfaceInternal<A> {}
impl<'a, A: AsRawFd + 'static> CursorBackend<'a> for LegacyDrmSurfaceInternal<A> {
type CursorFormat = &'a Buffer;
type Error = Error;
fn set_cursor_position(&self, x: u32, y: u32) -> Result<()> {
trace!(self.logger, "Move the cursor to {},{}", x, y);
crtc::move_cursor(self, self.crtc, (x as i32, y as i32))
.chain_err(|| ErrorKind::DrmDev(format!("Error moving cursor on {:?}", self.dev_path())))
}
fn set_cursor_representation<'b>(&'b self, buffer: Self::CursorFormat, hotspot: (u32, u32)) -> Result<()>
where
'a: 'b,
{
trace!(self.logger, "Setting the new imported cursor");
if crtc::set_cursor2(self, self.crtc, buffer, (hotspot.0 as i32, hotspot.1 as i32)).is_err() {
crtc::set_cursor(self, self.crtc, buffer)
.chain_err(|| ErrorKind::DrmDev(format!("Failed to set cursor on {:?}", self.dev_path())))?;
}
Ok(())
}
}
impl<A: AsRawFd + 'static> Surface for LegacyDrmSurfaceInternal<A> {
type Error = Error;
type Connectors = HashSet<connector::Handle>;
fn crtc(&self) -> crtc::Handle {
self.crtc
}
fn current_connectors(&self) -> Self::Connectors {
self.state.read().unwrap().connectors.clone()
}
fn pending_connectors(&self) -> Self::Connectors {
self.pending.read().unwrap().connectors.clone()
}
fn current_mode(&self) -> Option<Mode> {
self.state.read().unwrap().mode.clone()
}
fn pending_mode(&self) -> Option<Mode> {
self.pending.read().unwrap().mode.clone()
}
fn add_connector(&self, connector: connector::Handle) -> Result<()> {
let info = connector::Info::load_from_device(self, connector).chain_err(|| {
ErrorKind::DrmDev(format!("Error loading connector info on {:?}", self.dev_path()))
})?;
let mut pending = self.pending.write().unwrap();
// check if the connector can handle the current mode
if info.modes().contains(pending.mode.as_ref().unwrap()) {
// check if there is a valid encoder
let encoders = info
.encoders()
.iter()
.map(|encoder| {
encoder::Info::load_from_device(self, *encoder).chain_err(|| {
ErrorKind::DrmDev(format!("Error loading encoder info on {:?}", self.dev_path()))
})
})
.collect::<Result<Vec<encoder::Info>>>()?;
// and if any encoder supports the selected crtc
let resource_handles = self.resource_handles().chain_err(|| {
ErrorKind::DrmDev(format!("Error loading resources on {:?}", self.dev_path()))
})?;
if !encoders
.iter()
.map(|encoder| encoder.possible_crtcs())
.all(|crtc_list| resource_handles.filter_crtcs(crtc_list).contains(&self.crtc))
{
bail!(ErrorKind::NoSuitableEncoder(info, self.crtc));
}
pending.connectors.insert(connector);
Ok(())
} else {
bail!(ErrorKind::ModeNotSuitable(pending.mode.unwrap()));
}
}
fn remove_connector(&self, connector: connector::Handle) -> Result<()> {
self.pending.write().unwrap().connectors.remove(&connector);
Ok(())
}
fn use_mode(&self, mode: Option<Mode>) -> Result<()> {
let mut pending = self.pending.write().unwrap();
// check the connectors to see if this mode is supported
if let Some(mode) = mode {
for connector in &pending.connectors {
if !connector::Info::load_from_device(self, *connector)
.chain_err(|| {
ErrorKind::DrmDev(format!("Error loading connector info on {:?}", self.dev_path()))
})?
.modes()
.contains(&mode)
{
bail!(ErrorKind::ModeNotSuitable(mode));
}
}
}
pending.mode = mode;
Ok(())
}
}
impl<A: AsRawFd + 'static> RawSurface for LegacyDrmSurfaceInternal<A> {
fn commit_pending(&self) -> bool {
*self.pending.read().unwrap() != *self.state.read().unwrap()
}
fn commit(&self, framebuffer: framebuffer::Handle) -> Result<()> {
let mut current = self.state.write().unwrap();
let pending = self.pending.read().unwrap();
{
let removed = current.connectors.difference(&pending.connectors);
let added = pending.connectors.difference(&current.connectors);
for conn in removed {
if let Ok(info) = connector::Info::load_from_device(self, *conn) {
info!(self.logger, "Removing connector: {:?}", info.connector_type());
} else {
info!(self.logger, "Removing unknown connector");
}
}
for conn in added {
if let Ok(info) = connector::Info::load_from_device(self, *conn) {
info!(self.logger, "Adding connector: {:?}", info.connector_type());
} else {
info!(self.logger, "Adding unknown connector");
}
}
if current.mode != pending.mode {
info!(
self.logger,
"Setting new mode: {:?}",
pending.mode.as_ref().unwrap().name()
);
}
}
debug!(self.logger, "Setting screen");
crtc::set(
self,
self.crtc,
framebuffer,
&pending
.connectors
.iter()
.map(|x| *x)
.collect::<Vec<connector::Handle>>(),
(0, 0),
pending.mode,
)
.chain_err(|| {
ErrorKind::DrmDev(format!(
"Error setting crtc {:?} on {:?}",
self.crtc,
self.dev_path()
))
})?;
*current = pending.clone();
Ok(())
}
fn page_flip(&self, framebuffer: framebuffer::Handle) -> ::std::result::Result<(), SwapBuffersError> {
trace!(self.logger, "Queueing Page flip");
crtc::page_flip(
self,
self.crtc,
framebuffer,
&[crtc::PageFlipFlags::PageFlipEvent],
)
.map_err(|_| SwapBuffersError::ContextLost)
}
}
impl<A: AsRawFd + 'static> Drop for LegacyDrmSurfaceInternal<A> {
fn drop(&mut self) {
// ignore failure at this point
let _ = crtc::clear_cursor(self, self.crtc);
}
}
/// Open raw crtc utilizing legacy mode-setting
pub struct LegacyDrmSurface<A: AsRawFd + 'static>(pub(super) Rc<LegacyDrmSurfaceInternal<A>>);
impl<A: AsRawFd + 'static> AsRawFd for LegacyDrmSurface<A> {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
impl<A: AsRawFd + 'static> BasicDevice for LegacyDrmSurface<A> {}
impl<A: AsRawFd + 'static> ControlDevice for LegacyDrmSurface<A> {}
impl<'a, A: AsRawFd + 'static> CursorBackend<'a> for LegacyDrmSurface<A> {
type CursorFormat = &'a Buffer;
type Error = Error;
fn set_cursor_position(&self, x: u32, y: u32) -> Result<()> {
self.0.set_cursor_position(x, y)
}
fn set_cursor_representation<'b>(&'b self, buffer: Self::CursorFormat, hotspot: (u32, u32)) -> Result<()>
where
'a: 'b,
{
self.0.set_cursor_representation(buffer, hotspot)
}
}
impl<A: AsRawFd + 'static> Surface for LegacyDrmSurface<A> {
type Error = Error;
type Connectors = HashSet<connector::Handle>;
fn crtc(&self) -> crtc::Handle {
self.0.crtc()
}
fn current_connectors(&self) -> Self::Connectors {
self.0.current_connectors()
}
fn pending_connectors(&self) -> Self::Connectors {
self.0.pending_connectors()
}
fn current_mode(&self) -> Option<Mode> {
self.0.current_mode()
}
fn pending_mode(&self) -> Option<Mode> {
self.0.pending_mode()
}
fn add_connector(&self, connector: connector::Handle) -> Result<()> {
self.0.add_connector(connector)
}
fn remove_connector(&self, connector: connector::Handle) -> Result<()> {
self.0.remove_connector(connector)
}
fn use_mode(&self, mode: Option<Mode>) -> Result<()> {
self.0.use_mode(mode)
}
}
impl<A: AsRawFd + 'static> RawSurface for LegacyDrmSurface<A> {
fn commit_pending(&self) -> bool {
self.0.commit_pending()
}
fn commit(&self, framebuffer: framebuffer::Handle) -> Result<()> {
self.0.commit(framebuffer)
}
fn page_flip(&self, framebuffer: framebuffer::Handle) -> ::std::result::Result<(), SwapBuffersError> {
self.0.page_flip(framebuffer)
}
}

View File

@ -1,455 +1,217 @@
//! Drm/Kms types and backend implementations
//!
//! This module provide a `DrmDevice` which acts as a representation for any DRM
//! device and can be used to create the second provided structure a `DrmBackend`.
//!
//! Initialization happens through the types provided by [`drm-rs`](https://docs.rs/drm/).
//!
//! Three entities are relevant for the initialization procedure.
//!
//! "Crtc"s represent scanout engines of the device pointer to one framebuffer. There responsibility
//! is to read the data of the framebuffer and export it into an "Encoder". The number of crtc's
//! represent the number of independent output devices the hardware may handle.
//!
//! An "Encoder" encodes the data of connected crtcs into a video signal for a fixed set
//! of connectors. E.g. you might have an analog encoder based on a DAG for VGA ports, but another
//! one for digital ones. Also not every encoder might be connected to every crtc.
//!
//! The last entity the "Connector" represents a port on your computer, possibly with a connected
//! monitor, TV, capture card, etc.
//!
//! The `DrmBackend` created from a `DrmDevice` represents a crtc of the device you can render to
//! and that feeds a given set of connectors, that can be manipulated at runtime.
//!
//! From these circumstances it becomes clear, that one crtc might only send it's data to a connector,
//! that is attached to any encoder that is attached to the crtc itself. It is the responsibility of the
//! user to ensure that a given set of a crtc with it's connectors is valid or an error will be thrown.
//!
//! For more details refer to the [`drm-rs` documentation](https://docs.rs/drm).
//!
//!
//! ## How to use it
//! This module provides Traits reprensentating open devices
//! and their surfaces to render contents.
//!
//! ### Initialization
//! ---
//!
//! To initialize the `DrmDevice` you need either a `RawFd` or a `File` of
//! your DRM node. The `File` is recommended as it represents the save API.
//! Initialization of devices happens through an open file descriptor
//! of a drm device.
//!
//! Once you got your `DrmDevice` you can then use it to create `DrmBackend`s.
//! You will need to use the `drm` crate to provide the required types to create
//! a backend.
//! ---
//!
//! ```rust,no_run
//! extern crate drm;
//! extern crate smithay;
//! extern crate wayland_server;
//! Initialization of surfaces happens through the types provided by
//! [`drm-rs`](https://docs.rs/drm/0.3.4/drm/).
//!
//! use drm::Device as BasicDevice;
//! use drm::control::{Device as ControlDevice, ResourceInfo};
//! use drm::control::connector::{Info as ConnectorInfo, State as ConnectorState};
//! use drm::control::encoder::{Info as EncoderInfo};
//! use std::fs::{File, OpenOptions};
//! use std::os::unix::io::RawFd;
//! use std::os::unix::io::AsRawFd;
//! use smithay::backend::drm::{DrmDevice, DrmBackend};
//! Four entities are relevant for the initialization procedure.
//!
//! #[derive(Debug)]
//! pub struct Card(File);
//! [`crtc`](https://docs.rs/drm/0.3.4/drm/control/crtc/index.html)s represent scanout engines
//! of the device pointer to one framebuffer.
//! Their responsibility is to read the data of the framebuffer and export it into an "Encoder".
//! The number of crtc's represent the number of independant output devices the hardware may handle.
//!
//! impl AsRawFd for Card {
//! fn as_raw_fd(&self) -> RawFd {
//! self.0.as_raw_fd()
//! }
//! }
//! An [`encoder`](https://docs.rs/drm/0.3.4/drm/control/encoder/index.html) encodes the data of
//! connected crtcs into a video signal for a fixed set of connectors.
//! E.g. you might have an analog encoder based on a DAG for VGA ports, but another one for digital ones.
//! Also not every encoder might be connected to every crtc.
//!
//! impl BasicDevice for Card {}
//! impl ControlDevice for Card {}
//! A [`connector`](https://docs.rs/drm/0.3.4/drm/control/connector/index.html) represents a port
//! on your computer, possibly with a connected monitor, TV, capture card, etc.
//!
//! # fn main() {
//! // Open the drm device
//! let mut options = OpenOptions::new();
//! options.read(true);
//! options.write(true);
//! let mut device = DrmDevice::new(
//! Card(options.open("/dev/dri/card0").unwrap()), // try to detect it properly
//! None /*put a logger here*/
//! ).unwrap();
//! On surface creation a matching encoder for your `encoder`-`connector` is automatically selected,
//! if it exists, which means you still need to check your configuration.
//!
//! // Get a set of all modesetting resource handles
//! let res_handles = device.resource_handles().unwrap();
//! At last a [`Mode`](https://docs.rs/drm/0.3.4/drm/control/struct.Mode.html) needs to be selected,
//! supported by the `crtc` in question.
//!
//! // Use first connected connector for this example
//! let connector_info = res_handles.connectors().iter()
//! .map(|conn| ConnectorInfo::load_from_device(&device, *conn).unwrap())
//! .find(|conn| conn.connection_state() == ConnectorState::Connected)
//! .unwrap();
//!
//! // Use the first encoder
//! let encoder_info = EncoderInfo::load_from_device(&device, connector_info.encoders()[0]).unwrap();
//!
//! // use the connected crtc if any
//! let crtc = encoder_info.current_crtc()
//! // or use the first one that is compatible with the encoder
//! .unwrap_or_else(||
//! *res_handles.filter_crtcs(encoder_info.possible_crtcs())
//! .iter()
//! .next()
//! .unwrap());
//!
//! // Use first mode (usually the highest resolution)
//! let mode = connector_info.modes()[0];
//!
//! // Create the backend
//! let backend = device.create_backend(
//! crtc,
//! mode,
//! vec![connector_info.handle()]
//! ).unwrap();
//! # }
//! ```
//!
//! ### Page Flips / Tear-free video
//! Calling the usual `EglGraphicsBackend::swap_buffers` function on a
//! `DrmBackend` works the same to finish the rendering, but will return
//! `SwapBuffersError::AlreadySwapped` for any new calls until the page flip of the
//! crtc has happened.
//!
//! You can monitor the page flips by registering the `DrmDevice` as and
//! `FdEventSourceHandler` and setting a `DrmHandler` on it. You will be notified
//! whenever a page flip has happened, so you can render the next frame immediately
//! and get a tear-free representation on the display.
//!
//! You need to render at least once to successfully trigger the first event.
//!
//! ```rust,no_run
//! # extern crate drm;
//! # extern crate smithay;
//! # extern crate wayland_server;
//! #
//! # use drm::Device as BasicDevice;
//! # use drm::control::{Device as ControlDevice, ResourceInfo};
//! # use drm::control::connector::{Info as ConnectorInfo, State as ConnectorState};
//! use drm::control::crtc::{Handle as CrtcHandle};
//! use drm::result::Error as DrmError;
//! # use std::fs::{File, OpenOptions};
//! # use std::os::unix::io::RawFd;
//! # use std::os::unix::io::AsRawFd;
//! # use std::time::Duration;
//! use smithay::backend::drm::{DrmDevice, DrmBackend, DrmHandler, drm_device_bind};
//! use smithay::backend::graphics::egl::EGLGraphicsBackend;
//! #
//! # #[derive(Debug)]
//! # pub struct Card(File);
//! # impl AsRawFd for Card {
//! # fn as_raw_fd(&self) -> RawFd {
//! # self.0.as_raw_fd()
//! # }
//! # }
//! # impl BasicDevice for Card {}
//! # impl ControlDevice for Card {}
//! #
//! # fn main() {
//! #
//! # let mut event_loop = wayland_server::calloop::EventLoop::<()>::new().unwrap();
//! # let mut display = wayland_server::Display::new(event_loop.handle());
//! #
//! # let mut options = OpenOptions::new();
//! # options.read(true);
//! # options.write(true);
//! # let mut device = DrmDevice::new(
//! # Card(options.open("/dev/dri/card0").unwrap()), // try to detect it properly
//! # None /*put a logger here*/
//! # ).unwrap();
//! #
//! # let res_handles = device.resource_handles().unwrap();
//! # let connector_info = res_handles.connectors().iter()
//! # .map(|conn| ConnectorInfo::load_from_device(&device, *conn).unwrap())
//! # .find(|conn| conn.connection_state() == ConnectorState::Connected)
//! # .unwrap();
//! # let crtc = res_handles.crtcs()[0];
//! # let mode = connector_info.modes()[0];
//! # let backend = device.create_backend(
//! # crtc,
//! # mode,
//! # vec![connector_info.handle()]
//! # ).unwrap();
//!
//! struct MyDrmHandler(DrmBackend<Card>);
//!
//! impl DrmHandler<Card> for MyDrmHandler {
//! fn ready(
//! &mut self,
//! _device: &mut DrmDevice<Card>,
//! _crtc: CrtcHandle,
//! _frame: u32,
//! _duration: Duration)
//! {
//! // render surfaces and swap again
//! self.0.swap_buffers().unwrap();
//! }
//! fn error(
//! &mut self,
//! device: &mut DrmDevice<Card>,
//! error: DrmError)
//! {
//! panic!("DrmDevice errored: {}", error);
//! }
//! }
//!
//! // render something (like clear_color)
//! backend.swap_buffers().unwrap();
//!
//! let (_source, _device_rc) = drm_device_bind(
//! &event_loop.handle(),
//! device,
//! MyDrmHandler(backend)
//! ).map_err(|(err, _)| err).unwrap();
//!
//! /* And then run the event loop once all your setup is done */
//! # }
//! ```
use backend::graphics::egl::{
context::{EGLContext, GlAttributes},
error::Result as EGLResult,
native::Gbm,
wayland::{EGLDisplay, EGLWaylandExtensions},
};
#[cfg(feature = "backend_session")]
use backend::session::{AsSessionObserver, SessionObserver};
use drm::{
control::{connector, crtc, encoder, framebuffer, Device as ControlDevice, Mode, ResourceInfo},
result::Error as DrmError,
pub use drm::{
buffer::Buffer,
control::{
connector, crtc, encoder, framebuffer, Device as ControlDevice, Mode, ResourceHandles, ResourceInfo,
},
Device as BasicDevice,
};
use gbm::{BufferObject, Device as GbmDevice};
use nix::{
self,
sys::stat::{self, dev_t, fstat},
};
use std::{
cell::RefCell,
collections::HashMap,
hash::{Hash, Hasher},
io::Error as IoError,
os::unix::io::{AsRawFd, RawFd},
path::PathBuf,
rc::{Rc, Weak},
sync::{
atomic::{AtomicBool, Ordering},
Arc, Once, ONCE_INIT,
},
time::Duration,
};
pub use nix::libc::dev_t;
use wayland_server::{
calloop::{
generic::{EventedRawFd, Generic},
mio::Ready,
LoopHandle, Source,
},
Display,
};
use std::error::Error;
use std::iter::IntoIterator;
use std::os::unix::io::AsRawFd;
use std::path::PathBuf;
mod backend;
pub mod error;
use wayland_server::calloop::generic::{EventedFd, Generic};
use wayland_server::calloop::mio::Ready;
pub use wayland_server::calloop::InsertError;
use wayland_server::calloop::{LoopHandle, Source};
pub use self::backend::DrmBackend;
use self::{backend::DrmBackendInternal, error::*};
use super::graphics::SwapBuffersError;
static LOAD: Once = ONCE_INIT;
#[cfg(feature = "backend_drm_egl")]
pub mod egl;
#[cfg(feature = "backend_drm_gbm")]
pub mod gbm;
#[cfg(feature = "backend_drm_legacy")]
pub mod legacy;
/// Representation of an open DRM device node to create rendering backends
pub struct DrmDevice<A: ControlDevice + 'static> {
context: Rc<EGLContext<Gbm<framebuffer::Info>, GbmDevice<A>>>,
old_state: HashMap<crtc::Handle, (crtc::Info, Vec<connector::Handle>)>,
device_id: dev_t,
backends: Rc<RefCell<HashMap<crtc::Handle, Weak<DrmBackendInternal<A>>>>>,
active: Arc<AtomicBool>,
privileged: bool,
logger: ::slog::Logger,
/// Trait to receive events of a bound [`Device`](trait.Device.html)
///
/// See [`device_bind`](fn.device_bind.html)
pub trait DeviceHandler {
/// The [`Device`](trait.Device.html) type this handler can handle
type Device: Device + ?Sized;
/// A vblank blank event on the provided crtc has happend
fn vblank(&mut self, crtc: crtc::Handle);
/// An error happend while processing events
fn error(&mut self, error: <<<Self as DeviceHandler>::Device as Device>::Surface as Surface>::Error);
}
impl<A: ControlDevice + 'static> DrmDevice<A> {
/// Create a new `DrmDevice` from an open DRM node
/// An open drm device
pub trait Device: AsRawFd + DevPath {
/// Associated [`Surface`](trait.Surface.html) of this `Device` type
type Surface: Surface;
/// Returns the `id` of this device node.
fn device_id(&self) -> dev_t;
/// Assigns a `DeviceHandler` called during event processing.
///
/// Returns an error if the file is no valid DRM node or context creation was not
/// successful.
pub fn new<L>(dev: A, logger: L) -> Result<Self>
where
L: Into<Option<::slog::Logger>>,
{
DrmDevice::new_with_gl_attr(
dev,
GlAttributes {
version: None,
profile: None,
debug: cfg!(debug_assertions),
vsync: true,
},
logger,
)
}
/// See [`device_bind`](fn.device_bind.html) and [`DeviceHandler`](trait.DeviceHandler.html)
fn set_handler(&mut self, handler: impl DeviceHandler<Device = Self> + 'static);
/// Clear a set [`DeviceHandler`](trait.DeviceHandler.html), if any
fn clear_handler(&mut self);
/// Create a new `DrmDevice` from an open DRM node and given `GlAttributes`
/// Creates a new rendering surface.
///
/// Returns an error if the file is no valid DRM node or context creation was not
/// successful.
pub fn new_with_gl_attr<L>(dev: A, attributes: GlAttributes, logger: L) -> Result<Self>
where
L: Into<Option<::slog::Logger>>,
{
let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm"));
/* GBM will load a dri driver, but even though they need symbols from
* libglapi, in some version of Mesa they are not linked to it. Since
* only the gl-renderer module links to it, these symbols won't be globally available,
* and loading the DRI driver fails.
* Workaround this by dlopen()'ing libglapi with RTLD_GLOBAL.
*/
LOAD.call_once(|| unsafe {
nix::libc::dlopen(
"libglapi.so.0".as_ptr() as *const _,
nix::libc::RTLD_LAZY | nix::libc::RTLD_GLOBAL,
);
});
let device_id = fstat(dev.as_raw_fd())
.chain_err(|| ErrorKind::UnableToGetDeviceId)?
.st_rdev;
let mut drm = DrmDevice {
// Open the gbm device from the DRM device and create a context based on that
context: Rc::new(
EGLContext::new(
{
debug!(log, "Creating gbm device");
let gbm = GbmDevice::new(dev).chain_err(|| ErrorKind::GbmInitFailed)?;
debug!(log, "Creating egl context from gbm device");
gbm
},
attributes,
Default::default(),
log.clone(),
).map_err(Error::from)?,
),
backends: Rc::new(RefCell::new(HashMap::new())),
device_id,
old_state: HashMap::new(),
active: Arc::new(AtomicBool::new(true)),
privileged: true,
logger: log.clone(),
};
info!(log, "DrmDevice initializing");
// we want to mode-set, so we better be the master, if we run via a tty session
if drm.set_master().is_err() {
warn!(log, "Unable to become drm master, assuming unprivileged mode");
drm.privileged = false;
};
let res_handles = drm.resource_handles().chain_err(|| {
ErrorKind::DrmDev(format!("Error loading drm resources on {:?}", drm.dev_path()))
})?;
for &con in res_handles.connectors() {
let con_info = connector::Info::load_from_device(&drm, con).chain_err(|| {
ErrorKind::DrmDev(format!("Error loading connector info on {:?}", drm.dev_path()))
})?;
if let Some(enc) = con_info.current_encoder() {
let enc_info = encoder::Info::load_from_device(&drm, enc).chain_err(|| {
ErrorKind::DrmDev(format!("Error loading encoder info on {:?}", drm.dev_path()))
})?;
if let Some(crtc) = enc_info.current_crtc() {
let info = crtc::Info::load_from_device(&drm, crtc).chain_err(|| {
ErrorKind::DrmDev(format!("Error loading crtc info on {:?}", drm.dev_path()))
})?;
drm.old_state
.entry(crtc)
.or_insert((info, Vec::new()))
.1
.push(con);
}
}
}
Ok(drm)
}
/// Create a new backend on a given crtc with a given `Mode` for a given amount
/// of `connectors` (mirroring).
/// Initialization of surfaces happens through the types provided by
/// [`drm-rs`](https://docs.rs/drm/0.3.4/drm/).
///
/// Errors if initialization fails or the mode is not available on all given
/// connectors.
pub fn create_backend<I>(
/// [`crtc`](https://docs.rs/drm/0.3.4/drm/control/crtc/index.html)s represent scanout engines
/// of the device pointer to one framebuffer.
/// Their responsibility is to read the data of the framebuffer and export it into an "Encoder".
/// The number of crtc's represent the number of independant output devices the hardware may handle.
fn create_surface(
&mut self,
crtc: crtc::Handle,
mode: Mode,
connectors: I,
) -> Result<DrmBackend<A>>
where
I: Into<Vec<connector::Handle>>,
{
if self.backends.borrow().contains_key(&crtc) {
bail!(ErrorKind::CrtcAlreadyInUse(crtc));
}
ctrc: crtc::Handle,
) -> Result<Self::Surface, <Self::Surface as Surface>::Error>;
if !self.active.load(Ordering::SeqCst) {
bail!(ErrorKind::DeviceInactive);
}
/// Processes any open events of the underlying file descriptor.
///
/// You should not call this function manually, but rather use
/// [`device_bind`](fn.device_bind.html) to register the device
/// to an [`EventLoop`](https://docs.rs/calloop/0.4.2/calloop/struct.EventLoop.html)
/// to synchronize your rendering to the vblank events of the open crtc's
fn process_events(&mut self);
// check if the given connectors and crtc match
let connectors = connectors.into();
/// Load the resource from a `Device` given its
/// [`ResourceHandle`](https://docs.rs/drm/0.3.4/drm/control/trait.ResourceHandle.html)
fn resource_info<T: ResourceInfo>(
&self,
handle: T::Handle,
) -> Result<T, <Self::Surface as Surface>::Error>;
// check if we have an encoder for every connector and the mode mode
for connector in &connectors {
let con_info = connector::Info::load_from_device(self, *connector).chain_err(|| {
ErrorKind::DrmDev(format!("Error loading connector info on {:?}", self.dev_path()))
})?;
// check the mode
if !con_info.modes().contains(&mode) {
bail!(ErrorKind::ModeNotSuitable(mode));
}
// check for every connector which encoders it does support
let encoders = con_info
.encoders()
.iter()
.map(|encoder| {
encoder::Info::load_from_device(self, *encoder).chain_err(|| {
ErrorKind::DrmDev(format!("Error loading encoder info on {:?}", self.dev_path()))
})
}).collect::<Result<Vec<encoder::Info>>>()?;
// and if any encoder supports the selected crtc
let resource_handles = self.resource_handles().chain_err(|| {
ErrorKind::DrmDev(format!("Error loading drm resources on {:?}", self.dev_path()))
})?;
if !encoders
.iter()
.map(|encoder| encoder.possible_crtcs())
.any(|crtc_list| resource_handles.filter_crtcs(crtc_list).contains(&crtc))
{
bail!(ErrorKind::NoSuitableEncoder(con_info, crtc))
}
}
// configuration is valid, the kernel will figure out the rest
let logger = self.logger.new(o!("crtc" => format!("{:?}", crtc)));
let backend = DrmBackend::new(self.context.clone(), crtc, mode, connectors, logger)?;
self.backends.borrow_mut().insert(crtc, backend.weak());
Ok(backend)
}
/// Returns an internal device id, that is unique per boot per system
pub fn device_id(&self) -> u64 {
self.device_id
}
/// Attempts to acquire a copy of the `Device`'s
/// [`ResourceHandles`](https://docs.rs/drm/0.3.4/drm/control/struct.ResourceHandles.html)
fn resource_handles(&self) -> Result<ResourceHandles, <Self::Surface as Surface>::Error>;
}
/// Trait for types representing open devices
/// Marker trait for `Device`s able to provide [`RawSurface`](trait.RawSurface.html)s
pub trait RawDevice: Device<Surface = <Self as RawDevice>::Surface> {
/// Associated [`RawSurface`](trait.RawSurface.html) of this `RawDevice` type
type Surface: RawSurface;
}
/// An open crtc that can be used for rendering
pub trait Surface {
/// Type repesenting a collection of
/// [`connector`](https://docs.rs/drm/0.3.4/drm/control/connector/index.html)s
/// returned by [`current_connectors`](#method.current_connectors) and
/// [`pending_connectors`](#method.pending_connectors)
type Connectors: IntoIterator<Item = connector::Handle>;
/// Error type returned by methods of this trait
type Error: Error + Send;
/// Returns the underlying [`crtc`](https://docs.rs/drm/0.3.4/drm/control/crtc/index.html) of this surface
fn crtc(&self) -> crtc::Handle;
/// Currently used [`connector`](https://docs.rs/drm/0.3.4/drm/control/connector/index.html)s of this `Surface`
fn current_connectors(&self) -> Self::Connectors;
/// Returns the pending [`connector`](https://docs.rs/drm/0.3.4/drm/control/connector/index.html)s
/// used after the next `commit` of this `Surface`
///
/// *Note*: Only on a [`RawSurface`](trait.RawSurface.html) you may directly trigger
/// a [`commit`](trait.RawSurface.html#method.commit). Other `Surface`s provide their
/// own methods that *may* trigger a commit, you will need to read their docs.
fn pending_connectors(&self) -> Self::Connectors;
/// Tries to add a new [`connector`](https://docs.rs/drm/0.3.4/drm/control/connector/index.html)
/// to be used after the next commit.
///
/// Fails if the `connector` is not compatible with the underlying [`crtc`](https://docs.rs/drm/0.3.4/drm/control/crtc/index.html)
/// (e.g. no suitable [`encoder`](https://docs.rs/drm/0.3.4/drm/control/encoder/index.html) may be found)
/// or is not compatible with the currently pending
/// [`Mode`](https://docs.rs/drm/0.3.4/drm/control/struct.Mode.html).
fn add_connector(&self, connector: connector::Handle) -> Result<(), Self::Error>;
/// Tries to mark a [`connector`](https://docs.rs/drm/0.3.4/drm/control/connector/index.html)
/// for removal on the next commit.
fn remove_connector(&self, connector: connector::Handle) -> Result<(), Self::Error>;
/// Returns the currently active [`Mode`](https://docs.rs/drm/0.3.4/drm/control/struct.Mode.html)
/// of the underlying [`crtc`](https://docs.rs/drm/0.3.4/drm/control/crtc/index.html)
/// if any.
fn current_mode(&self) -> Option<Mode>;
/// Returns the currently pending [`Mode`](https://docs.rs/drm/0.3.4/drm/control/struct.Mode.html)
/// to be used after the next commit, if any.
fn pending_mode(&self) -> Option<Mode>;
/// Tries to set a new [`Mode`](https://docs.rs/drm/0.3.4/drm/control/struct.Mode.html)
/// to be used after the next commit.
///
/// Fails if the mode is not compatible with the underlying
/// [`crtc`](https://docs.rs/drm/0.3.4/drm/control/crtc/index.html) or any of the
/// pending [`connector`](https://docs.rs/drm/0.3.4/drm/control/connector/index.html)s.
///
/// *Note*: Only on a [`RawSurface`](trait.RawSurface.html) you may directly trigger
/// a [`commit`](trait.RawSurface.html#method.commit). Other `Surface`s provide their
/// own methods that *may* trigger a commit, you will need to read their docs.
fn use_mode(&self, mode: Option<Mode>) -> Result<(), Self::Error>;
}
/// An open bare crtc without any rendering abstractions
pub trait RawSurface: Surface + ControlDevice + BasicDevice {
/// Returns true whenever any state changes are pending to be commited
///
/// The following functions may trigger a pending commit:
/// - [`add_connector`](trait.Surface.html#method.add_connector)
/// - [`remove_connector`](trait.Surface.html#method.remove_connector)
/// - [`use_mode`](trait.Surface.html#method.use_mode)
fn commit_pending(&self) -> bool;
/// Commit the pending state rendering a given framebuffer.
///
/// *Note*: This will trigger a full modeset on the underlying device,
/// potentially causing some flickering. Check before performing this
/// operation if a commit really is necessary using [`commit_pending`](#method.commit_pending).
///
/// This operation is blocking until the crtc is in the desired state.
fn commit(&self, framebuffer: framebuffer::Handle) -> Result<(), <Self as Surface>::Error>;
/// Page-flip the underlying [`crtc`](https://docs.rs/drm/0.3.4/drm/control/crtc/index.html)
/// to a new given [`framebuffer`].
///
/// This will not cause the crtc to modeset.
///
/// This operation is not blocking and will produce a `vblank` event once swapping is done.
/// Make sure to [set a `DeviceHandler`](trait.Device.html#method.set_handler) and
/// [register the belonging `Device`](fn.device_bind.html) before to receive the event in time.
fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), SwapBuffersError>;
}
/// Trait representing open devices that *may* return a `Path`
pub trait DevPath {
/// Returns the path of the open device if possible
fn dev_path(&self) -> Option<PathBuf>;
@ -463,243 +225,22 @@ impl<A: AsRawFd> DevPath for A {
}
}
impl<A: ControlDevice + 'static> PartialEq for DrmDevice<A> {
fn eq(&self, other: &DrmDevice<A>) -> bool {
self.device_id == other.device_id
}
}
impl<A: ControlDevice + 'static> Eq for DrmDevice<A> {}
impl<A: ControlDevice + 'static> Hash for DrmDevice<A> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.device_id.hash(state);
}
}
// for users convenience and FdEventSource registering
impl<A: ControlDevice + 'static> AsRawFd for DrmDevice<A> {
fn as_raw_fd(&self) -> RawFd {
self.context.as_raw_fd()
}
}
impl<A: ControlDevice + 'static> BasicDevice for DrmDevice<A> {}
impl<A: ControlDevice + 'static> ControlDevice for DrmDevice<A> {}
impl<A: ControlDevice + 'static> EGLWaylandExtensions for DrmDevice<A> {
fn bind_wl_display(&self, display: &Display) -> EGLResult<EGLDisplay> {
self.context.bind_wl_display(display)
}
}
impl<A: ControlDevice + 'static> Drop for DrmDevice<A> {
fn drop(&mut self) {
if Rc::strong_count(&self.context) > 1 {
panic!("Pending DrmBackends. You need to free all backends before the DrmDevice gets destroyed");
}
for (handle, (info, connectors)) in self.old_state.drain() {
if let Err(err) = crtc::set(
&*self.context,
handle,
info.fb(),
&connectors,
info.position(),
info.mode(),
) {
error!(self.logger, "Failed to reset crtc ({:?}). Error: {}", handle, err);
}
}
if self.privileged {
if let Err(err) = self.drop_master() {
error!(self.logger, "Failed to drop drm master state. Error: {}", err);
}
}
}
}
/// Handler for DRM node events
/// Bind a `Device` to an `EventLoop`,
///
/// See module-level documentation for its use
pub trait DrmHandler<A: ControlDevice + 'static> {
/// The `DrmBackend` of crtc has finished swapping buffers and new frame can now
/// (and should be immediately) be rendered.
fn ready(&mut self, device: &mut DrmDevice<A>, crtc: crtc::Handle, frame: u32, duration: Duration);
/// The `DrmDevice` has thrown an error.
///
/// The related backends are most likely *not* usable anymore and
/// the whole stack has to be recreated..
fn error(&mut self, device: &mut DrmDevice<A>, error: DrmError);
}
/// Bind a `DrmDevice` to an `EventLoop`,
///
/// This will cause it to recieve events and feed them into an `DrmHandler`
pub fn drm_device_bind<A, H, Data: 'static>(
/// This will cause it to recieve events and feed them into a previously
/// set [`DeviceHandler`](trait.DeviceHandler.html).
pub fn device_bind<D: Device + 'static, Data>(
handle: &LoopHandle<Data>,
device: DrmDevice<A>,
mut handler: H,
) -> ::std::result::Result<(Source<Generic<EventedRawFd>>, Rc<RefCell<DrmDevice<A>>>), (IoError, DrmDevice<A>)>
device: D,
) -> ::std::result::Result<Source<Generic<EventedFd<D>>>, InsertError<Generic<EventedFd<D>>>>
where
A: ControlDevice + 'static,
H: DrmHandler<A> + 'static,
D: Device,
Data: 'static,
{
let fd = device.as_raw_fd();
let device = Rc::new(RefCell::new(device));
let mut source = Generic::from_raw_fd(fd);
let mut source = Generic::from_fd_source(device);
source.set_interest(Ready::readable());
match handle.insert_source(source, {
let device = device.clone();
move |_evt, _| {
let mut device = device.borrow_mut();
process_events(&mut *device, &mut handler);
}
}) {
Ok(source) => Ok((source, device)),
Err(e) => {
let device = Rc::try_unwrap(device).unwrap_or_else(|_| unreachable!());
Err((e.into(), device.into_inner()))
}
}
}
fn process_events<A, H>(device: &mut DrmDevice<A>, handler: &mut H)
where
A: ControlDevice + 'static,
H: DrmHandler<A> + 'static,
{
match crtc::receive_events(&*device) {
Ok(events) => for event in events {
if let crtc::Event::PageFlip(event) = event {
if device.active.load(Ordering::SeqCst) {
let backends = device.backends.borrow().clone();
if let Some(backend) = backends.get(&event.crtc).iter().flat_map(|x| x.upgrade()).next() {
// we can now unlock the buffer
backend.unlock_buffer();
trace!(device.logger, "Handling event for backend {:?}", event.crtc);
// and then call the user to render the next frame
handler.ready(device, event.crtc, event.frame, event.duration);
} else {
device.backends.borrow_mut().remove(&event.crtc);
}
}
}
},
Err(err) => handler.error(device, err),
}
}
/// `SessionObserver` linked to the `DrmDevice` it was created from.
pub struct DrmDeviceObserver<A: ControlDevice + 'static> {
context: Weak<EGLContext<Gbm<framebuffer::Info>, GbmDevice<A>>>,
device_id: dev_t,
backends: Rc<RefCell<HashMap<crtc::Handle, Weak<DrmBackendInternal<A>>>>>,
old_state: HashMap<crtc::Handle, (crtc::Info, Vec<connector::Handle>)>,
active: Arc<AtomicBool>,
privileged: bool,
logger: ::slog::Logger,
}
#[cfg(feature = "backend_session")]
impl<A: ControlDevice + 'static> AsSessionObserver<DrmDeviceObserver<A>> for DrmDevice<A> {
fn observer(&mut self) -> DrmDeviceObserver<A> {
DrmDeviceObserver {
context: Rc::downgrade(&self.context),
device_id: self.device_id,
backends: self.backends.clone(),
old_state: self.old_state.clone(),
active: self.active.clone(),
privileged: self.privileged,
logger: self.logger.clone(),
}
}
}
#[cfg(feature = "backend_session")]
impl<A: ControlDevice + 'static> SessionObserver for DrmDeviceObserver<A> {
fn pause(&mut self, devnum: Option<(u32, u32)>) {
if let Some((major, minor)) = devnum {
if major as u64 != stat::major(self.device_id) || minor as u64 != stat::minor(self.device_id) {
return;
}
}
if let Some(device) = self.context.upgrade() {
for (handle, &(ref info, ref connectors)) in &self.old_state {
if let Err(err) = crtc::set(
&*device,
*handle,
info.fb(),
connectors,
info.position(),
info.mode(),
) {
error!(self.logger, "Failed to reset crtc ({:?}). Error: {}", handle, err);
}
}
}
self.active.store(false, Ordering::SeqCst);
if self.privileged {
if let Some(device) = self.context.upgrade() {
if let Err(err) = device.drop_master() {
error!(self.logger, "Failed to drop drm master state. Error: {}", err);
}
}
}
}
fn activate(&mut self, devnum: Option<(u32, u32, Option<RawFd>)>) {
if let Some((major, minor, fd)) = devnum {
if major as u64 != stat::major(self.device_id) || minor as u64 != stat::minor(self.device_id) {
return;
} else if let Some(fd) = fd {
info!(self.logger, "Replacing fd");
if let Some(device) = self.context.upgrade() {
nix::unistd::dup2(device.as_raw_fd(), fd)
.expect("Failed to replace file descriptor of drm device");
}
}
}
self.active.store(true, Ordering::SeqCst);
if self.privileged {
if let Some(device) = self.context.upgrade() {
if let Err(err) = device.set_master() {
crit!(self.logger, "Failed to acquire drm master again. Error: {}", err);
}
}
}
let mut crtcs = Vec::new();
for (crtc, backend) in self.backends.borrow().iter() {
if let Some(backend) = backend.upgrade() {
backend.unlock_buffer();
if let Err(err) = backend.page_flip(None) {
error!(
self.logger,
"Failed to activate crtc ({:?}) again. Error: {}", crtc, err
);
}
// reset cursor
{
let &(ref cursor, ref hotspot): &(BufferObject<()>, (u32, u32)) =
unsafe { &*backend.cursor.as_ptr() };
if crtc::set_cursor2(
&*backend.context,
*crtc,
cursor,
((*hotspot).0 as i32, (*hotspot).1 as i32),
).is_err()
{
if let Err(err) = crtc::set_cursor(&*backend.context, *crtc, cursor) {
error!(self.logger, "Failed to reset cursor. Error: {}", err);
}
}
}
} else {
crtcs.push(*crtc);
}
}
for crtc in crtcs {
self.backends.borrow_mut().remove(&crtc);
}
}
handle.insert_source(source, |evt, _| {
evt.source.borrow_mut().0.process_events();
})
}

View File

@ -1,52 +1,30 @@
//! EGL context related structs
use super::{error::*, ffi, native, EGLSurface, PixelFormat};
#[cfg(feature = "backend_drm")]
use drm::control::Device as ControlDevice;
#[cfg(feature = "backend_drm")]
use drm::Device as BasicDevice;
#[cfg(feature = "backend_drm")]
use gbm::Device as GbmDevice;
use super::{error::*, ffi, native, EGLSurface};
use backend::graphics::PixelFormat;
use nix::libc::{c_int, c_void};
use slog;
#[cfg(feature = "backend_drm")]
use std::os::unix::io::{AsRawFd, RawFd};
use std::{
cell::{Ref, RefCell, RefMut},
ffi::{CStr, CString},
marker::PhantomData,
mem,
ops::{Deref, DerefMut},
ptr,
mem, ptr,
rc::Rc,
};
/// EGL context for rendering
pub struct EGLContext<B: native::Backend, N: native::NativeDisplay<B>> {
native: N,
native: RefCell<N>,
pub(crate) context: Rc<ffi::egl::types::EGLContext>,
pub(crate) display: Rc<ffi::egl::types::EGLDisplay>,
pub(crate) config_id: ffi::egl::types::EGLConfig,
pub(crate) surface_attributes: Vec<c_int>,
pixel_format: PixelFormat,
pub(crate) wl_drm_support: bool,
pub(crate) egl_to_texture_support: bool,
logger: slog::Logger,
_backend: PhantomData<B>,
}
impl<B: native::Backend, N: native::NativeDisplay<B>> Deref for EGLContext<B, N> {
type Target = N;
fn deref(&self) -> &N {
&self.native
}
}
impl<B: native::Backend, N: native::NativeDisplay<B>> DerefMut for EGLContext<B, N> {
fn deref_mut(&mut self) -> &mut N {
&mut self.native
}
}
impl<B: native::Backend, N: native::NativeDisplay<B>> EGLContext<B, N> {
/// Create a new `EGLContext` from a given `NativeDisplay`
pub fn new<L>(
@ -60,25 +38,17 @@ impl<B: native::Backend, N: native::NativeDisplay<B>> EGLContext<B, N> {
{
let log = ::slog_or_stdlog(logger.into()).new(o!("smithay_module" => "renderer_egl"));
let ptr = native.ptr()?;
let (
context,
display,
config_id,
surface_attributes,
pixel_format,
wl_drm_support,
egl_to_texture_support,
) = unsafe { EGLContext::<B, N>::new_internal(ptr, attributes, reqs, log.clone()) }?;
let (context, display, config_id, surface_attributes, pixel_format, wl_drm_support) =
unsafe { EGLContext::<B, N>::new_internal(ptr, attributes, reqs, log.clone()) }?;
Ok(EGLContext {
native,
native: RefCell::new(native),
context,
display,
config_id,
surface_attributes,
pixel_format,
wl_drm_support,
egl_to_texture_support,
logger: log,
_backend: PhantomData,
})
@ -96,7 +66,6 @@ impl<B: native::Backend, N: native::NativeDisplay<B>> EGLContext<B, N> {
Vec<c_int>,
PixelFormat,
bool,
bool,
)> {
// If no version is given, try OpenGLES 3.0, if available,
// fallback to 2.0 otherwise
@ -136,6 +105,7 @@ impl<B: native::Backend, N: native::NativeDisplay<B>> EGLContext<B, N> {
{
f
};
ffi::egl::load_with(|sym| {
let name = CString::new(sym).unwrap();
let symbol = ffi::egl::LIB.get::<*mut c_void>(name.as_bytes());
@ -153,7 +123,6 @@ impl<B: native::Backend, N: native::NativeDisplay<B>> EGLContext<B, N> {
ffi::egl::BindWaylandDisplayWL::load_with(&proc_address);
ffi::egl::UnbindWaylandDisplayWL::load_with(&proc_address);
ffi::egl::QueryWaylandBufferWL::load_with(&proc_address);
ffi::gl::load_with(&proc_address);
});
// the first step is to query the list of extensions without any display, if supported
@ -435,17 +404,6 @@ impl<B: native::Backend, N: native::NativeDisplay<B>> EGLContext<B, N> {
// make current and get list of gl extensions
ffi::egl::MakeCurrent(display as *const _, ptr::null(), ptr::null(), context as *const _);
// the list of gl extensions supported by the context
let gl_extensions = {
let data = CStr::from_ptr(ffi::gl::GetString(ffi::gl::EXTENSIONS) as *const _)
.to_bytes()
.to_vec();
let list = String::from_utf8(data).unwrap();
list.split(' ').map(|e| e.to_string()).collect::<Vec<_>>()
};
info!(log, "GL Extensions: {:?}", gl_extensions);
Ok((
Rc::new(context as *const _),
Rc::new(display as *const _),
@ -453,25 +411,21 @@ impl<B: native::Backend, N: native::NativeDisplay<B>> EGLContext<B, N> {
surface_attributes,
desc,
extensions.iter().any(|s| *s == "EGL_WL_bind_wayland_display"),
gl_extensions
.iter()
.any(|s| *s == "GL_OES_EGL_image" || *s == "GL_OES_EGL_image_base"),
))
}
/// Creates a surface for rendering
pub fn create_surface(&self, args: N::Arguments) -> Result<EGLSurface<B::Surface>> {
trace!(self.logger, "Creating EGL window surface.");
let res = EGLSurface::new(
self,
self.native
.create_surface(args)
.chain_err(|| ErrorKind::SurfaceCreationFailed)?,
);
if res.is_ok() {
let surface = self
.native
.borrow_mut()
.create_surface(args)
.chain_err(|| ErrorKind::SurfaceCreationFailed)?;
EGLSurface::new(self, surface).map(|x| {
debug!(self.logger, "EGL surface successfully created");
}
res
x
})
}
/// Returns the address of an OpenGL function.
@ -492,6 +446,25 @@ impl<B: native::Backend, N: native::NativeDisplay<B>> EGLContext<B, N> {
pub fn get_pixel_format(&self) -> PixelFormat {
self.pixel_format
}
/// Borrow the underlying native display.
///
/// This follows the same semantics as `std::cell:RefCell`.
/// Multiple read-only borrows are possible. Borrowing the
/// backend while there is a mutable reference will panic.
pub fn borrow(&self) -> Ref<N> {
self.native.borrow()
}
/// Borrow the underlying native display mutably.
///
/// This follows the same semantics as `std::cell:RefCell`.
/// Holding any other borrow while trying to borrow the backend
/// mutably will panic. Note that EGL will borrow the display
/// mutably during surface creation.
pub fn borrow_mut(&self) -> RefMut<N> {
self.native.borrow_mut()
}
}
unsafe impl<B: native::Backend, N: native::NativeDisplay<B> + Send> Send for EGLContext<B, N> {}
@ -508,18 +481,6 @@ impl<B: native::Backend, N: native::NativeDisplay<B>> Drop for EGLContext<B, N>
}
}
#[cfg(feature = "backend_drm")]
impl<T: 'static, A: AsRawFd + 'static> AsRawFd for EGLContext<native::Gbm<T>, GbmDevice<A>> {
fn as_raw_fd(&self) -> RawFd {
self.native.as_raw_fd()
}
}
#[cfg(feature = "backend_drm")]
impl<T: 'static, A: BasicDevice + 'static> BasicDevice for EGLContext<native::Gbm<T>, GbmDevice<A>> {}
#[cfg(feature = "backend_drm")]
impl<T: 'static, A: ControlDevice + 'static> ControlDevice for EGLContext<native::Gbm<T>, GbmDevice<A>> {}
/// Attributes to use when creating an OpenGL context.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GlAttributes {

View File

@ -13,11 +13,6 @@ pub type NativeDisplayType = *const c_void;
pub type NativePixmapType = *const c_void;
pub type NativeWindowType = *const c_void;
#[cfg_attr(feature = "cargo-clippy", allow(clippy))]
pub mod gl {
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
}
#[cfg_attr(feature = "cargo-clippy", allow(clippy))]
pub mod egl {
use super::*;

View File

@ -1,3 +1,10 @@
//! Common traits and types for egl rendering
//!
//! Large parts of this module are taken from
//! https://github.com/tomaka/glutin/tree/044e651edf67a2029eecc650dd42546af1501414/src/api/egl/
//!
//! It therefore falls under glutin's Apache 2.0 license
//! (see https://github.com/tomaka/glutin/tree/044e651edf67a2029eecc650dd42546af1501414/LICENSE)
//! Wayland specific EGL functionality - EGL based `WlBuffer`s.
//!
//! The types of this module can be used to initialize hardware acceleration rendering
@ -10,13 +17,11 @@
//! You may then use the resulting `EGLDisplay` to receive `EGLImages` of an EGL-based `WlBuffer`
//! for rendering.
use backend::graphics::egl::{
error::*,
ffi::{self, egl::types::EGLImage},
native, EGLContext, EglExtensionNotSupportedError,
};
#[cfg(feature = "renderer_gl")]
use backend::graphics::gl::ffi as gl_ffi;
use nix::libc::c_uint;
use std::{
ffi::CStr,
fmt,
rc::{Rc, Weak},
};
@ -24,8 +29,47 @@ use wayland_server::{
protocol::wl_buffer::{self, WlBuffer},
Display, Resource,
};
#[cfg(feature = "native_lib")]
use wayland_sys::server::wl_display;
pub mod context;
pub use self::context::EGLContext;
pub mod error;
use self::error::*;
#[allow(non_camel_case_types, dead_code, unused_mut, non_upper_case_globals)]
pub mod ffi;
use self::ffi::egl::types::EGLImage;
pub mod native;
pub mod surface;
pub use self::surface::EGLSurface;
/// Error that can happen on optional EGL features
#[derive(Debug, Clone, PartialEq)]
pub struct EglExtensionNotSupportedError(&'static [&'static str]);
impl fmt::Display for EglExtensionNotSupportedError {
fn fmt(&self, formatter: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> {
write!(
formatter,
"None of the following EGL extensions is supported by the underlying EGL implementation,
at least one is required: {:?}",
self.0
)
}
}
impl ::std::error::Error for EglExtensionNotSupportedError {
fn description(&self) -> &str {
"The required EGL extension is not supported by the underlying EGL implementation"
}
fn cause(&self) -> Option<&::std::error::Error> {
None
}
}
/// Error that can occur when accessing an EGL buffer
pub enum BufferAccessError {
/// The corresponding Context is not alive anymore
@ -104,6 +148,8 @@ pub enum TextureCreationError {
/// application on sleep and wakes it up later. However any OpenGL implementation
/// can theoretically lose the context at any time.
ContextLost,
/// Required OpenGL Extension for texture creation is missing
GLExtensionNotSupported(&'static str),
/// Failed to bind the `EGLImage` to the given texture
///
/// The given argument is the GL error code
@ -116,6 +162,9 @@ impl fmt::Display for TextureCreationError {
match *self {
TextureCreationError::ContextLost => write!(formatter, "{}", self.description()),
TextureCreationError::PlaneIndexOutOfBounds => write!(formatter, "{}", self.description()),
TextureCreationError::GLExtensionNotSupported(ext) => {
write!(formatter, "{}: {:}", self.description(), ext)
}
TextureCreationError::TextureBindingFailed(code) => {
write!(formatter, "{}. Gl error code: {:?}", self.description(), code)
}
@ -128,6 +177,9 @@ impl ::std::error::Error for TextureCreationError {
match *self {
TextureCreationError::ContextLost => "The context has been lost, it needs to be recreated",
TextureCreationError::PlaneIndexOutOfBounds => "This buffer is not managed by EGL",
TextureCreationError::GLExtensionNotSupported(_) => {
"Required OpenGL Extension for texture creation is missing"
}
TextureCreationError::TextureBindingFailed(_) => "Failed to create EGLImages from the buffer",
}
}
@ -180,6 +232,10 @@ pub struct EGLImages {
pub format: Format,
images: Vec<EGLImage>,
buffer: Resource<WlBuffer>,
#[cfg(feature = "renderer_gl")]
gl: gl_ffi::Gles2,
#[cfg(feature = "renderer_gl")]
egl_to_texture_support: bool,
}
impl EGLImages {
@ -195,17 +251,22 @@ impl EGLImages {
/// # Unsafety
///
/// The given `tex_id` needs to be a valid GL texture otherwise undefined behavior might occur.
#[cfg(feature = "renderer_gl")]
pub unsafe fn bind_to_texture(
&self,
plane: usize,
tex_id: c_uint,
) -> ::std::result::Result<(), TextureCreationError> {
if self.display.upgrade().is_some() {
if !self.egl_to_texture_support {
return Err(TextureCreationError::GLExtensionNotSupported("GL_OES_EGL_image"));
}
let mut old_tex_id: i32 = 0;
ffi::gl::GetIntegerv(ffi::gl::TEXTURE_BINDING_2D, &mut old_tex_id);
ffi::gl::BindTexture(ffi::gl::TEXTURE_2D, tex_id);
ffi::gl::EGLImageTargetTexture2DOES(
ffi::gl::TEXTURE_2D,
self.gl.GetIntegerv(gl_ffi::TEXTURE_BINDING_2D, &mut old_tex_id);
self.gl.BindTexture(gl_ffi::TEXTURE_2D, tex_id);
self.gl.EGLImageTargetTexture2DOES(
gl_ffi::TEXTURE_2D,
*self
.images
.get(plane)
@ -215,7 +276,7 @@ impl EGLImages {
ffi::egl::SUCCESS => Ok(()),
err => Err(TextureCreationError::TextureBindingFailed(err)),
};
ffi::gl::BindTexture(ffi::gl::TEXTURE_2D, old_tex_id as u32);
self.gl.BindTexture(gl_ffi::TEXTURE_2D, old_tex_id as u32);
res
} else {
Err(TextureCreationError::ContextLost)
@ -238,7 +299,8 @@ impl Drop for EGLImages {
/// Trait any backend type may implement that allows binding a `wayland_server::Display`
/// to create an `EGLDisplay` for EGL-based `WlBuffer`s.
pub trait EGLWaylandExtensions {
#[cfg(feature = "native_lib")]
pub trait EGLGraphicsBackend {
/// Binds this EGL context to the given Wayland display.
///
/// This will allow clients to utilize EGL to create hardware-accelerated
@ -257,15 +319,42 @@ pub trait EGLWaylandExtensions {
/// Type to receive `EGLImages` for EGL-based `WlBuffer`s.
///
/// Can be created by using `EGLWaylandExtensions::bind_wl_display`.
pub struct EGLDisplay(Weak<ffi::egl::types::EGLDisplay>, *mut wl_display);
/// Can be created by using `EGLGraphicsBackend::bind_wl_display`.
#[cfg(feature = "native_lib")]
pub struct EGLDisplay {
egl: Weak<ffi::egl::types::EGLDisplay>,
wayland: *mut wl_display,
#[cfg(feature = "renderer_gl")]
gl: gl_ffi::Gles2,
#[cfg(feature = "renderer_gl")]
egl_to_texture_support: bool,
}
#[cfg(feature = "native_lib")]
impl EGLDisplay {
fn new<B: native::Backend, N: native::NativeDisplay<B>>(
context: &EGLContext<B, N>,
display: *mut wl_display,
) -> EGLDisplay {
EGLDisplay(Rc::downgrade(&context.display), display)
#[cfg(feature = "renderer_gl")]
let gl = gl_ffi::Gles2::load_with(|s| unsafe { context.get_proc_address(s) as *const _ });
EGLDisplay {
egl: Rc::downgrade(&context.display),
wayland: display,
#[cfg(feature = "renderer_gl")]
egl_to_texture_support: {
// the list of gl extensions supported by the context
let data = unsafe { CStr::from_ptr(gl.GetString(gl_ffi::EXTENSIONS) as *const _) }
.to_bytes()
.to_vec();
let list = String::from_utf8(data).unwrap();
list.split(' ')
.any(|s| s == "GL_OES_EGL_image" || s == "GL_OES_EGL_image_base")
},
#[cfg(feature = "renderer_gl")]
gl,
}
}
/// Try to receive `EGLImages` from a given `WlBuffer`.
@ -277,7 +366,7 @@ impl EGLDisplay {
&self,
buffer: Resource<WlBuffer>,
) -> ::std::result::Result<EGLImages, BufferAccessError> {
if let Some(display) = self.0.upgrade() {
if let Some(display) = self.egl.upgrade() {
let mut format: i32 = 0;
if unsafe {
ffi::egl::QueryWaylandBufferWL(
@ -368,6 +457,10 @@ impl EGLDisplay {
format,
images,
buffer,
#[cfg(feature = "renderer_gl")]
gl: self.gl.clone(),
#[cfg(feature = "renderer_gl")]
egl_to_texture_support: self.egl_to_texture_support,
})
} else {
Err(BufferAccessError::ContextLost)
@ -375,34 +468,34 @@ impl EGLDisplay {
}
}
#[cfg(feature = "native_lib")]
impl Drop for EGLDisplay {
fn drop(&mut self) {
if let Some(display) = self.0.upgrade() {
if !self.1.is_null() {
if let Some(display) = self.egl.upgrade() {
if !self.wayland.is_null() {
unsafe {
ffi::egl::UnbindWaylandDisplayWL(*display, self.1 as *mut _);
ffi::egl::UnbindWaylandDisplayWL(*display, self.wayland as *mut _);
}
}
}
}
}
impl<E: EGLWaylandExtensions> EGLWaylandExtensions for Rc<E> {
#[cfg(feature = "native_lib")]
impl<E: EGLGraphicsBackend> EGLGraphicsBackend for Rc<E> {
fn bind_wl_display(&self, display: &Display) -> Result<EGLDisplay> {
(**self).bind_wl_display(display)
}
}
impl<B: native::Backend, N: native::NativeDisplay<B>> EGLWaylandExtensions for EGLContext<B, N> {
#[cfg(feature = "native_lib")]
impl<B: native::Backend, N: native::NativeDisplay<B>> EGLGraphicsBackend for EGLContext<B, N> {
fn bind_wl_display(&self, display: &Display) -> Result<EGLDisplay> {
if !self.wl_drm_support {
bail!(ErrorKind::EglExtensionNotSupported(&[
"EGL_WL_bind_wayland_display"
]));
}
if !self.egl_to_texture_support {
bail!(ErrorKind::EglExtensionNotSupported(&["GL_OES_EGL_image"]));
}
let res = unsafe { ffi::egl::BindWaylandDisplayWL(*self.display, display.c_ptr() as *mut _) };
if res == 0 {
bail!(ErrorKind::OtherEGLDisplayAlreadyBound);

View File

@ -1,16 +1,11 @@
//! Type safe native types for safe context/surface creation
use super::{error::*, ffi};
#[cfg(feature = "backend_drm")]
use backend::drm::error::{Error as DrmError, ErrorKind as DrmErrorKind, Result as DrmResult};
#[cfg(feature = "backend_drm")]
use gbm::{AsRaw, BufferObjectFlags, Device as GbmDevice, Format as GbmFormat, Surface as GbmSurface};
#[cfg(feature = "backend_drm")]
use std::marker::PhantomData;
#[cfg(feature = "backend_drm")]
use std::os::unix::io::AsRawFd;
#[cfg(any(feature = "backend_drm", feature = "backend_winit"))]
use backend::graphics::SwapBuffersError;
#[cfg(feature = "backend_winit")]
use std::ptr;
#[cfg(feature = "backend_winit")]
use wayland_client::egl as wegl;
#[cfg(feature = "backend_winit")]
@ -95,38 +90,6 @@ impl Backend for X11 {
}
}
}
#[cfg(feature = "backend_drm")]
/// Gbm backend type
pub struct Gbm<T: 'static> {
_userdata: PhantomData<T>,
}
#[cfg(feature = "backend_drm")]
impl<T: 'static> Backend for Gbm<T> {
type Surface = GbmSurface<T>;
unsafe fn get_display<F>(
display: ffi::NativeDisplayType,
has_dp_extension: F,
log: ::slog::Logger,
) -> ffi::egl::types::EGLDisplay
where
F: Fn(&str) -> bool,
{
if has_dp_extension("EGL_KHR_platform_gbm") && ffi::egl::GetPlatformDisplay::is_loaded() {
trace!(log, "EGL Display Initialization via EGL_KHR_platform_gbm");
ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_KHR, display as *mut _, ptr::null())
} else if has_dp_extension("EGL_MESA_platform_gbm") && ffi::egl::GetPlatformDisplayEXT::is_loaded() {
trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm");
ffi::egl::GetPlatformDisplayEXT(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, ptr::null())
} else if has_dp_extension("EGL_MESA_platform_gbm") && ffi::egl::GetPlatformDisplay::is_loaded() {
trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm");
ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, ptr::null())
} else {
trace!(log, "Default EGL Display Initialization via GetDisplay");
ffi::egl::GetDisplay(display as *mut _)
}
}
}
/// Trait for types returning Surfaces which can be used to initialize `EGLSurface`s
///
@ -144,7 +107,7 @@ pub unsafe trait NativeDisplay<B: Backend> {
/// Return a raw pointer EGL will accept for context creation.
fn ptr(&self) -> Result<ffi::NativeDisplayType>;
/// Create a surface
fn create_surface(&self, args: Self::Arguments) -> ::std::result::Result<B::Surface, Self::Error>;
fn create_surface(&mut self, args: Self::Arguments) -> ::std::result::Result<B::Surface, Self::Error>;
}
#[cfg(feature = "backend_winit")]
@ -162,7 +125,7 @@ unsafe impl NativeDisplay<X11> for WinitWindow {
.ok_or(ErrorKind::NonMatchingBackend("X11").into())
}
fn create_surface(&self, _args: ()) -> Result<XlibWindow> {
fn create_surface(&mut self, _args: ()) -> Result<XlibWindow> {
self.get_xlib_window()
.map(XlibWindow)
.ok_or(ErrorKind::NonMatchingBackend("X11").into())
@ -184,7 +147,7 @@ unsafe impl NativeDisplay<Wayland> for WinitWindow {
.ok_or(ErrorKind::NonMatchingBackend("Wayland").into())
}
fn create_surface(&self, _args: ()) -> Result<wegl::WlEglSurface> {
fn create_surface(&mut self, _args: ()) -> Result<wegl::WlEglSurface> {
if let Some(surface) = self.get_wayland_surface() {
let size = self.get_inner_size().unwrap();
Ok(unsafe {
@ -196,41 +159,7 @@ unsafe impl NativeDisplay<Wayland> for WinitWindow {
}
}
#[cfg(feature = "backend_drm")]
/// Arguments necessary to construct a `GbmSurface`
pub struct GbmSurfaceArguments {
/// Size of the surface
pub size: (u32, u32),
/// Pixel format of the surface
pub format: GbmFormat,
/// Flags for surface creation
pub flags: BufferObjectFlags,
}
#[cfg(feature = "backend_drm")]
unsafe impl<A: AsRawFd + 'static, T: 'static> NativeDisplay<Gbm<T>> for GbmDevice<A> {
type Arguments = GbmSurfaceArguments;
type Error = DrmError;
fn is_backend(&self) -> bool {
true
}
fn ptr(&self) -> Result<ffi::NativeDisplayType> {
Ok(self.as_raw() as *const _)
}
fn create_surface(&self, args: GbmSurfaceArguments) -> DrmResult<GbmSurface<T>> {
use backend::drm::error::ResultExt as DrmResultExt;
DrmResultExt::chain_err(
GbmDevice::create_surface(self, args.size.0, args.size.1, args.format, args.flags),
|| DrmErrorKind::GbmInitFailed,
)
}
}
/// Trait for types returning valid surface pointers for initializing EGL
/// Trait for types returning valid surface pointers for initializing egl
///
/// ## Unsafety
///
@ -238,6 +167,32 @@ unsafe impl<A: AsRawFd + 'static, T: 'static> NativeDisplay<Gbm<T>> for GbmDevic
pub unsafe trait NativeSurface {
/// Return a raw pointer egl will accept for surface creation.
fn ptr(&self) -> ffi::NativeWindowType;
/// Will be called to check if any internal resources will need
/// to be recreated. Old resources must be used until `recreate`
/// was called.
///
/// Only needs to be recreated, if this shall sometimes return true.
/// The default implementation always returns false.
fn needs_recreation(&self) -> bool {
false
}
/// Instructs the surface to recreate internal resources
///
/// Must only be implemented if `needs_recreation` can return `true`.
/// Returns true on success.
/// If this call was successful `ptr()` *should* return something different.
fn recreate(&self) -> bool {
true
}
/// Adds additional semantics when calling EGLSurface::swap_buffers
///
/// Only implement if required by the backend.
fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> {
Ok(())
}
}
#[cfg(feature = "backend_winit")]
@ -253,10 +208,3 @@ unsafe impl NativeSurface for wegl::WlEglSurface {
self.ptr() as *const _
}
}
#[cfg(feature = "backend_drm")]
unsafe impl<T: 'static> NativeSurface for GbmSurface<T> {
fn ptr(&self) -> ffi::NativeWindowType {
self.as_raw() as *const _
}
}

View File

@ -1,7 +1,10 @@
//! EGL surface related structs
use super::{error::*, ffi, native, EGLContext, SwapBuffersError};
use super::{error::*, ffi, native, EGLContext};
use backend::graphics::SwapBuffersError;
use nix::libc::c_int;
use std::{
cell::Cell,
ops::{Deref, DerefMut},
rc::{Rc, Weak},
};
@ -11,7 +14,9 @@ pub struct EGLSurface<N: native::NativeSurface> {
context: Weak<ffi::egl::types::EGLContext>,
display: Weak<ffi::egl::types::EGLDisplay>,
native: N,
surface: ffi::egl::types::EGLSurface,
surface: Cell<ffi::egl::types::EGLSurface>,
config_id: ffi::egl::types::EGLConfig,
surface_attributes: Vec<c_int>,
}
impl<N: native::NativeSurface> Deref for EGLSurface<N> {
@ -49,26 +54,48 @@ impl<N: native::NativeSurface> EGLSurface<N> {
context: Rc::downgrade(&context.context),
display: Rc::downgrade(&context.display),
native,
surface,
surface: Cell::new(surface),
config_id: context.config_id,
surface_attributes: context.surface_attributes.clone(),
})
}
/// Swaps buffers at the end of a frame.
pub fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> {
if let Some(display) = self.display.upgrade() {
let ret = unsafe { ffi::egl::SwapBuffers((*display) as *const _, self.surface as *const _) };
let surface = self.surface.get();
if ret == 0 {
match unsafe { ffi::egl::GetError() } as u32 {
ffi::egl::CONTEXT_LOST => Err(SwapBuffersError::ContextLost),
err => Err(SwapBuffersError::Unknown(err)),
if !surface.is_null() {
if let Some(display) = self.display.upgrade() {
let ret = unsafe { ffi::egl::SwapBuffers((*display) as *const _, surface as *const _) };
if ret == 0 {
match unsafe { ffi::egl::GetError() } as u32 {
ffi::egl::CONTEXT_LOST => return Err(SwapBuffersError::ContextLost),
err => return Err(SwapBuffersError::Unknown(err)),
};
} else {
self.native.swap_buffers()?;
}
} else {
Ok(())
return Err(SwapBuffersError::ContextLost);
}
};
if self.native.needs_recreation() || surface.is_null() {
if let Some(display) = self.display.upgrade() {
self.native.recreate();
self.surface.set(unsafe {
ffi::egl::CreateWindowSurface(
*display,
self.config_id,
self.native.ptr(),
self.surface_attributes.as_ptr(),
)
});
}
} else {
Err(SwapBuffersError::ContextLost)
}
Ok(())
}
/// Makes the OpenGL context the current context in the current thread.
@ -81,8 +108,8 @@ impl<N: native::NativeSurface> EGLSurface<N> {
if let (Some(display), Some(context)) = (self.display.upgrade(), self.context.upgrade()) {
let ret = ffi::egl::MakeCurrent(
(*display) as *const _,
self.surface as *const _,
self.surface as *const _,
self.surface.get() as *const _,
self.surface.get() as *const _,
(*context) as *const _,
);
@ -103,8 +130,8 @@ impl<N: native::NativeSurface> EGLSurface<N> {
pub fn is_current(&self) -> bool {
if self.context.upgrade().is_some() {
unsafe {
ffi::egl::GetCurrentSurface(ffi::egl::DRAW as _) == self.surface as *const _
&& ffi::egl::GetCurrentSurface(ffi::egl::READ as _) == self.surface as *const _
ffi::egl::GetCurrentSurface(ffi::egl::DRAW as _) == self.surface.get() as *const _
&& ffi::egl::GetCurrentSurface(ffi::egl::READ as _) == self.surface.get() as *const _
}
} else {
false
@ -116,7 +143,7 @@ impl<N: native::NativeSurface> Drop for EGLSurface<N> {
fn drop(&mut self) {
if let Some(display) = self.display.upgrade() {
unsafe {
ffi::egl::DestroySurface((*display) as *const _, self.surface as *const _);
ffi::egl::DestroySurface((*display) as *const _, self.surface.get() as *const _);
}
}
}

View File

@ -0,0 +1,34 @@
/// Functions to render cursors on any graphics backend independently from it's rendering techique.
pub trait CursorBackend<'a> {
/// Format representing the image drawn for the cursor.
type CursorFormat: 'a;
/// Error the underlying backend throws if operations fail
type Error;
/// Sets the cursor position and therefore updates the drawn cursors position.
/// Useful as well for e.g. pointer wrapping.
///
/// Not guaranteed to be supported on every backend. The result usually
/// depends on the backend, the cursor might be "owned" by another more priviledged
/// compositor (running nested).
///
/// In these cases setting the position is actually not required, as movement is done
/// by the higher compositor and not by the backend. It is still good practice to update
/// the position after every recieved event, but don't rely on pointer wrapping working.
///
fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Self::Error>;
/// Set the cursor drawn on the `CursorBackend`.
///
/// The format is entirely dictated by the concrete implementation and might range
/// from raw image buffers over a fixed list of possible cursor types to simply the
/// void type () to represent no possible customization of the cursor itself.
fn set_cursor_representation<'b>(
&'b self,
cursor: Self::CursorFormat,
hotspot: (u32, u32),
) -> Result<(), Self::Error>
where
'a: 'b;
}

View File

@ -1,155 +0,0 @@
//! Common traits and types for EGL rendering
// Large parts of this module are taken from
// https://github.com/tomaka/glutin/tree/044e651edf67a2029eecc650dd42546af1501414/src/api/egl/
//
// It therefore falls under glutin's Apache 2.0 license
// (see https://github.com/tomaka/glutin/tree/044e651edf67a2029eecc650dd42546af1501414/LICENSE)
use super::GraphicsBackend;
use nix::libc::c_void;
use std::fmt;
pub mod context;
pub use self::context::EGLContext;
pub mod error;
#[allow(
non_camel_case_types,
dead_code,
unused_mut,
non_upper_case_globals
)]
pub mod ffi;
pub mod native;
pub mod surface;
pub use self::surface::EGLSurface;
pub mod wayland;
pub use self::wayland::{BufferAccessError, EGLImages, EGLWaylandExtensions};
/// Error that can happen when swapping buffers.
#[derive(Debug, Clone, PartialEq)]
pub enum SwapBuffersError {
/// The OpenGL context has been lost and needs to be recreated.
///
/// All the objects associated to it (textures, buffers, programs, etc.)
/// need to be recreated from scratch.
///
/// Operations will have no effect. Functions that read textures, buffers, etc.
/// from OpenGL will return uninitialized data instead.
///
/// A context loss usually happens on mobile devices when the user puts the
/// application to sleep and wakes it up later. However any OpenGL implementation
/// can theoretically lose the context at any time.
ContextLost,
/// The buffers have already been swapped.
///
/// This error can be returned when `swap_buffers` has been called multiple times
/// without any modification in between.
AlreadySwapped,
/// Unknown GL error
Unknown(u32),
}
impl fmt::Display for SwapBuffersError {
fn fmt(&self, formatter: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> {
use std::error::Error;
write!(formatter, "{}", self.description())
}
}
impl ::std::error::Error for SwapBuffersError {
fn description(&self) -> &str {
match *self {
SwapBuffersError::ContextLost => "The context has been lost, it needs to be recreated",
SwapBuffersError::AlreadySwapped => {
"Buffers are already swapped, swap_buffers was called too many times"
}
SwapBuffersError::Unknown(_) => "Unknown Open GL error occurred",
}
}
fn cause(&self) -> Option<&::std::error::Error> {
None
}
}
/// Error that can happen on optional EGL features
#[derive(Debug, Clone, PartialEq)]
pub struct EglExtensionNotSupportedError(&'static [&'static str]);
impl fmt::Display for EglExtensionNotSupportedError {
fn fmt(&self, formatter: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> {
write!(
formatter,
"None of the following EGL extensions is supported by the underlying EGL implementation,
at least one is required: {:?}",
self.0
)
}
}
impl ::std::error::Error for EglExtensionNotSupportedError {
fn description(&self) -> &str {
"The required EGL extension is not supported by the underlying EGL implementation"
}
fn cause(&self) -> Option<&::std::error::Error> {
None
}
}
/// Describes the pixel format of the main framebuffer
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PixelFormat {
/// is the format hardware accelerated
pub hardware_accelerated: bool,
/// number of bits used for colors
pub color_bits: u8,
/// number of bits used for alpha channel
pub alpha_bits: u8,
/// number of bits used for depth channel
pub depth_bits: u8,
/// number of bits used for stencil buffer
pub stencil_bits: u8,
/// is stereoscopy enabled
pub stereoscopy: bool,
/// is double buffering enabled
pub double_buffer: bool,
/// number of samples used for multisampling if enabled
pub multisampling: Option<u16>,
/// is srgb enabled
pub srgb: bool,
}
/// Trait that describes objects that have an OpenGL context
/// and can be used to render upon
pub trait EGLGraphicsBackend: GraphicsBackend {
/// Swaps buffers at the end of a frame.
fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError>;
/// Returns the address of an OpenGL function.
///
/// Supposes that the context has been made current before this function is called.
unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void;
/// Returns the dimensions of the window, or screen, etc in points.
///
/// That are the scaled pixels of the underlying graphics backend.
/// For nested compositors this will respect the scaling of the root compositor.
/// For drawing directly onto hardware this unit will be equal to actual pixels.
fn get_framebuffer_dimensions(&self) -> (u32, u32);
/// Returns `true` if the OpenGL context is the current one in the thread.
fn is_current(&self) -> bool;
/// Makes the OpenGL context the current context in the current thread.
///
/// # Unsafety
///
/// This function is marked unsafe, because the context cannot be made current
/// on multiple threads.
unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError>;
/// Returns the pixel format of the main framebuffer of the context.
fn get_pixel_format(&self) -> PixelFormat;
}

View File

@ -0,0 +1,45 @@
use std::error::Error;
use std::fmt;
/// Error that can happen when swapping buffers.
#[derive(Debug, Clone, PartialEq)]
pub enum SwapBuffersError {
/// The corresponding context has been lost and needs to be recreated.
///
/// All the objects associated to it (textures, buffers, programs, etc.)
/// need to be recreated from scratch.
///
/// Operations will have no effect. Functions that read textures, buffers, etc.
/// will return uninitialized data instead.
ContextLost,
/// The buffers have already been swapped.
///
/// This error can be returned when `swap_buffers` has been called multiple times
/// without any modification in between.
AlreadySwapped,
/// Unknown error
Unknown(u32),
}
impl fmt::Display for SwapBuffersError {
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use std::error::Error;
write!(formatter, "{}", self.description())
}
}
impl Error for SwapBuffersError {
fn description(&self) -> &str {
match *self {
SwapBuffersError::ContextLost => "The context has been lost, it needs to be recreated",
SwapBuffersError::AlreadySwapped => {
"Buffers are already swapped, swap_buffers was called too many times"
}
SwapBuffersError::Unknown(_) => "Unknown error occurred",
}
}
fn cause(&self) -> Option<&Error> {
None
}
}

View File

@ -0,0 +1,22 @@
/// Describes the pixel format of a framebuffer
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PixelFormat {
/// is the format hardware accelerated
pub hardware_accelerated: bool,
/// number of bits used for colors
pub color_bits: u8,
/// number of bits used for alpha channel
pub alpha_bits: u8,
/// number of bits used for depth channel
pub depth_bits: u8,
/// number of bits used for stencil buffer
pub stencil_bits: u8,
/// is stereoscopy enabled
pub stereoscopy: bool,
/// is double buffering enabled
pub double_buffer: bool,
/// number of samples used for multisampling if enabled
pub multisampling: Option<u16>,
/// is srgb enabled
pub srgb: bool,
}

View File

@ -0,0 +1,55 @@
//! Gl rendering types
use nix::libc::c_void;
use super::{PixelFormat, SwapBuffersError};
#[cfg_attr(feature = "cargo-clippy", allow(clippy))]
#[allow(missing_docs)]
pub(crate) mod ffi {
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
}
pub use self::ffi::Gles2;
/// Trait that describes objects that have an OpenGL context
/// and can be used to render upon
pub trait GLGraphicsBackend {
/// Swaps buffers at the end of a frame.
fn swap_buffers(&self) -> Result<(), SwapBuffersError>;
/// Returns the address of an OpenGL function.
///
/// Supposes that the context has been made current before this function is called.
unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void;
/// Returns the dimensions of the window, or screen, etc in points.
///
/// These are the actual pixels of the underlying graphics backend.
/// For nested compositors you will need to handle the scaling
/// of the root compositor yourself, if you want to.
fn get_framebuffer_dimensions(&self) -> (u32, u32);
/// Returns true if the OpenGL context is the current one in the thread.
fn is_current(&self) -> bool;
/// Makes the OpenGL context the current context in the current thread.
///
/// # Unsafety
///
/// This function is marked unsafe, because the context cannot be made current
/// on multiple threads.
unsafe fn make_current(&self) -> Result<(), SwapBuffersError>;
/// Returns the pixel format of the main framebuffer of the context.
fn get_pixel_format(&self) -> PixelFormat;
}
/// Loads a Raw GLES Interface for a given `GLGraphicsBackend`
///
/// This remains valid as long as the underlying `GLGraphicsBackend` is alive
/// and may only be used in combination with the backend. Using this with any
/// other gl context *may* cause undefined behavior.
pub fn load_raw_gl<B: GLGraphicsBackend>(backend: &B) -> Gles2 {
Gles2::load_with(|s| unsafe { backend.get_proc_address(s) as *const _ })
}

View File

@ -1,10 +1,6 @@
//! Glium compatibility module
use backend::graphics::egl::{
error::Result as EGLResult,
wayland::{EGLDisplay, EGLWaylandExtensions},
EGLGraphicsBackend, SwapBuffersError,
};
use backend::graphics::{gl::GLGraphicsBackend, SwapBuffersError};
use glium::{
backend::{Backend, Context, Facade},
debug::DebugCallbackBehavior,
@ -15,7 +11,6 @@ use std::{
os::raw::c_void,
rc::Rc,
};
use wayland_server::Display;
impl From<SwapBuffersError> for GliumSwapBuffersError {
fn from(error: SwapBuffersError) -> Self {
@ -28,14 +23,14 @@ impl From<SwapBuffersError> for GliumSwapBuffersError {
}
/// Wrapper to expose `Glium` compatibility
pub struct GliumGraphicsBackend<T: EGLGraphicsBackend> {
pub struct GliumGraphicsBackend<T: GLGraphicsBackend> {
context: Rc<Context>,
backend: Rc<InternalBackend<T>>,
}
struct InternalBackend<T: EGLGraphicsBackend>(RefCell<T>);
struct InternalBackend<T: GLGraphicsBackend>(RefCell<T>);
impl<T: EGLGraphicsBackend + 'static> GliumGraphicsBackend<T> {
impl<T: GLGraphicsBackend + 'static> GliumGraphicsBackend<T> {
fn new(backend: T) -> GliumGraphicsBackend<T> {
let internal = Rc::new(InternalBackend(RefCell::new(backend)));
@ -79,27 +74,19 @@ impl<T: EGLGraphicsBackend + 'static> GliumGraphicsBackend<T> {
}
}
impl<T: EGLGraphicsBackend> Facade for GliumGraphicsBackend<T> {
impl<T: GLGraphicsBackend> Facade for GliumGraphicsBackend<T> {
fn get_context(&self) -> &Rc<Context> {
&self.context
}
}
impl<T: EGLGraphicsBackend + 'static> From<T> for GliumGraphicsBackend<T> {
impl<T: GLGraphicsBackend + 'static> From<T> for GliumGraphicsBackend<T> {
fn from(backend: T) -> Self {
GliumGraphicsBackend::new(backend)
}
}
impl<T: EGLGraphicsBackend + EGLWaylandExtensions + 'static> EGLWaylandExtensions
for GliumGraphicsBackend<T>
{
fn bind_wl_display(&self, display: &Display) -> EGLResult<EGLDisplay> {
(*self.backend).0.borrow().bind_wl_display(display)
}
}
unsafe impl<T: EGLGraphicsBackend> Backend for InternalBackend<T> {
unsafe impl<T: GLGraphicsBackend> Backend for InternalBackend<T> {
fn swap_buffers(&self) -> Result<(), GliumSwapBuffersError> {
self.0.borrow().swap_buffers().map_err(Into::into)
}

View File

@ -2,40 +2,18 @@
//!
//! Note: Not every API may be supported by every backend
/// General functions any graphics backend should support independently from it's rendering
/// technique.
pub trait GraphicsBackend {
/// Format representing the image drawn for the cursor.
type CursorFormat;
mod errors;
pub use self::errors::*;
/// Error the underlying backend throws if operations fail
type Error;
mod cursor;
pub use self::cursor::*;
/// Sets the cursor position and therefor updates the drawn cursors position.
/// Useful as well for e.g. pointer wrapping.
///
/// Not guaranteed to be supported on every backend. The result usually
/// depends on the backend, the cursor might be "owned" by another more privileged
/// compositor (running nested).
///
/// In these cases setting the position is actually not required, as movement is done
/// by the higher compositor and not by the backend. It is still good practice to update
/// the position after every received event, but don't rely on pointer wrapping working.
fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Self::Error>;
mod format;
pub use self::format::*;
/// Set the cursor drawn on the `GraphicsBackend`.
///
/// The format is entirely dictated by the concrete implementation and might range
/// from raw image buffers over a fixed list of possible cursor types to simply the
/// void type `()` to represent no possible customization of the cursor itself.
fn set_cursor_representation(
&self,
cursor: &Self::CursorFormat,
hotspot: (u32, u32),
) -> Result<(), Self::Error>;
}
pub mod egl;
#[cfg(feature = "renderer_gl")]
pub mod gl;
#[cfg(feature = "renderer_glium")]
pub mod glium;
#[cfg(feature = "renderer_software")]
pub mod software;

View File

@ -1,11 +1,10 @@
//! Common traits and types used for software rendering on graphics backends
use super::GraphicsBackend;
use std::error::Error;
use wayland_server::protocol::wl_shm::Format;
/// Trait that describes objects providing a software rendering implementation
pub trait CpuGraphicsBackend<E: Error>: GraphicsBackend {
pub trait CpuGraphicsBackend<E: Error> {
/// Render a given buffer of a given format at a specified place in the framebuffer
///
/// # Error

View File

@ -6,26 +6,25 @@ use backend::{input as backend, input::Axis};
use input as libinput;
use input::event;
#[cfg(feature = "backend_session")]
use std::path::Path;
use std::{
cell::RefCell,
collections::hash_map::{DefaultHasher, Entry, HashMap},
hash::{Hash, Hasher},
io::Error as IoError,
os::unix::io::RawFd,
path::Path,
rc::Rc,
os::unix::io::{AsRawFd, RawFd},
};
use wayland_server::calloop::{
generic::{EventedRawFd, Generic},
generic::{EventedFd, Generic},
mio::Ready,
LoopHandle, Source,
InsertError, LoopHandle, Source,
};
// No idea if this is the same across unix platforms
// Lets make this linux exclusive for now, once someone tries to build it for
// any BSD-like system, they can verify if this is right and make a PR to change this.
#[cfg(any(target_os = "linux", target_os = "android"))]
#[cfg(all(any(target_os = "linux", target_os = "android"), feature = "backend_session"))]
const INPUT_MAJOR: u32 = 13;
/// Libinput based `InputBackend`.
@ -446,16 +445,18 @@ impl backend::InputBackend for LibinputInputBackend {
libinput::Event::Keyboard(keyboard_event) => {
use input::event::keyboard::*;
match keyboard_event {
KeyboardEvent::Key(key_event) => if let Some(ref mut handler) = self.handler {
let device_seat = key_event.device().seat();
if let Some(ref seat) = self.seats.get(&device_seat) {
trace!(self.logger, "Calling on_keyboard_key with {:?}", key_event);
handler.on_keyboard_key(seat, key_event);
} else {
warn!(self.logger, "Received key event of non existing Seat");
continue;
KeyboardEvent::Key(key_event) => {
if let Some(ref mut handler) = self.handler {
let device_seat = key_event.device().seat();
if let Some(ref seat) = self.seats.get(&device_seat) {
trace!(self.logger, "Calling on_keyboard_key with {:?}", key_event);
handler.on_keyboard_key(seat, key_event);
} else {
warn!(self.logger, "Received key event of non existing Seat");
continue;
}
}
},
}
}
}
libinput::Event::Pointer(pointer_event) => {
@ -590,6 +591,12 @@ impl<S: Session> libinput::LibinputInterface for LibinputSessionInterface<S> {
}
}
impl AsRawFd for LibinputInputBackend {
fn as_raw_fd(&self) -> RawFd {
self.context.as_raw_fd()
}
}
/// Binds a `LibinputInputBackend` to a given `EventLoop`.
///
/// Automatically feeds the backend with incoming events without any manual calls to
@ -597,22 +604,19 @@ impl<S: Session> libinput::LibinputInterface for LibinputSessionInterface<S> {
pub fn libinput_bind<Data: 'static>(
backend: LibinputInputBackend,
handle: LoopHandle<Data>,
) -> ::std::result::Result<Source<Generic<EventedRawFd>>, (IoError, LibinputInputBackend)> {
let mut source = Generic::from_raw_fd(unsafe { backend.context.fd() });
) -> ::std::result::Result<
Source<Generic<EventedFd<LibinputInputBackend>>>,
InsertError<Generic<EventedFd<LibinputInputBackend>>>,
> {
let mut source = Generic::from_fd_source(backend);
source.set_interest(Ready::readable());
let backend = Rc::new(RefCell::new(backend));
let fail_backend = backend.clone();
handle
.insert_source(source, move |_, _| {
use backend::input::InputBackend;
if let Err(error) = backend.borrow_mut().dispatch_new_events() {
warn!(backend.borrow().logger, "Libinput errored: {}", error);
}
}).map_err(move |e| {
// the backend in the closure should already have been dropped
let backend = Rc::try_unwrap(fail_backend)
.unwrap_or_else(|_| unreachable!())
.into_inner();
(e.into(), backend)
})
handle.insert_source(source, move |evt, _| {
use backend::input::InputBackend;
let mut backend = evt.source.borrow_mut();
if let Err(error) = backend.0.dispatch_new_events() {
warn!(backend.0.logger, "Libinput errored: {}", error);
}
})
}

View File

@ -19,6 +19,8 @@ pub mod input;
#[cfg(feature = "backend_drm")]
pub mod drm;
#[cfg(feature = "backend_egl")]
pub mod egl;
#[cfg(feature = "backend_libinput")]
pub mod libinput;
#[cfg(feature = "backend_session")]

View File

@ -31,7 +31,7 @@
use super::logind::{self, logind_session_bind, BoundLogindSession, LogindSession, LogindSessionNotifier};
use super::{
direct::{self, direct_session_bind, BoundDirectSession, DirectSession, DirectSessionNotifier},
AsErrno, AsSessionObserver, Session, SessionNotifier, SessionObserver,
AsErrno, Session, SessionNotifier, SessionObserver,
};
use nix::fcntl::OFlag;
use std::{cell::RefCell, io::Error as IoError, os::unix::io::RawFd, path::Path, rc::Rc};
@ -114,6 +114,7 @@ impl AutoSession {
}
}
/// Tries to create a new session via the best available interface.
#[cfg(not(feature = "backend_session_logind"))]
pub fn new<L>(logger: L) -> Option<(AutoSession, AutoSessionNotifier)>
where
@ -202,10 +203,7 @@ impl Session for AutoSession {
impl SessionNotifier for AutoSessionNotifier {
type Id = AutoId;
fn register<S: SessionObserver + 'static, A: AsSessionObserver<S>>(
&mut self,
signal: &mut A,
) -> Self::Id {
fn register<S: SessionObserver + 'static>(&mut self, signal: S) -> Self::Id {
match *self {
#[cfg(feature = "backend_session_logind")]
AutoSessionNotifier::Logind(ref mut logind) => {
@ -231,21 +229,6 @@ impl SessionNotifier for AutoSessionNotifier {
_ => unreachable!(),
}
}
fn is_active(&self) -> bool {
match *self {
#[cfg(feature = "backend_session_logind")]
AutoSessionNotifier::Logind(ref logind) => logind.is_active(),
AutoSessionNotifier::Direct(ref direct) => direct.is_active(),
}
}
fn seat(&self) -> &str {
match *self {
#[cfg(feature = "backend_session_logind")]
AutoSessionNotifier::Logind(ref logind) => logind.seat(),
AutoSessionNotifier::Direct(ref direct) => direct.seat(),
}
}
}
impl BoundAutoSession {

View File

@ -30,7 +30,7 @@
//! automatically by the `UdevBackend`, if not done manually).
//! ```
use backend::session::{AsErrno, AsSessionObserver, Session, SessionNotifier, SessionObserver};
use backend::session::{AsErrno, Session, SessionNotifier, SessionObserver};
use dbus::{
BusName, BusType, Connection, ConnectionItem, ConnectionItems, Interface, Member, Message, MessageItem,
OwnedFd, Path as DbusPath, Watch, WatchEvent,
@ -101,7 +101,8 @@ impl LogindSession {
"org.freedesktop.login1.Manager",
"GetSession",
Some(vec![session_id.clone().into()]),
)?.get1::<DbusPath<'static>>()
)?
.get1::<DbusPath<'static>>()
.chain_err(|| ErrorKind::UnexpectedMethodReturn)?;
// Match all signals that we want to receive and handle
@ -342,7 +343,8 @@ impl Session for LogindSession {
(major(stat.st_rdev) as u32).into(),
(minor(stat.st_rdev) as u32).into(),
]),
)?.get2::<OwnedFd, bool>();
)?
.get2::<OwnedFd, bool>();
let fd = fd.chain_err(|| ErrorKind::UnexpectedMethodReturn)?.into_fd();
Ok(fd)
} else {
@ -363,7 +365,8 @@ impl Session for LogindSession {
(major(stat.st_rdev) as u32).into(),
(minor(stat.st_rdev) as u32).into(),
]),
).map(|_| ())
)
.map(|_| ())
} else {
bail!(ErrorKind::SessionLost)
}
@ -390,7 +393,8 @@ impl Session for LogindSession {
"org.freedesktop.login1.Seat",
"SwitchTo",
Some(vec![(vt_num as u32).into()]),
).map(|_| ())
)
.map(|_| ())
} else {
bail!(ErrorKind::SessionLost)
}
@ -404,27 +408,13 @@ pub struct Id(usize);
impl SessionNotifier for LogindSessionNotifier {
type Id = Id;
fn register<S: SessionObserver + 'static, A: AsSessionObserver<S>>(
&mut self,
signal: &mut A,
) -> Self::Id {
self.internal
.signals
.borrow_mut()
.push(Some(Box::new(signal.observer())));
fn register<S: SessionObserver + 'static>(&mut self, signal: S) -> Self::Id {
self.internal.signals.borrow_mut().push(Some(Box::new(signal)));
Id(self.internal.signals.borrow().len() - 1)
}
fn unregister(&mut self, signal: Id) {
self.internal.signals.borrow_mut()[signal.0] = None;
}
fn is_active(&self) -> bool {
self.internal.active.load(Ordering::SeqCst)
}
fn seat(&self) -> &str {
&self.internal.seat
}
}
/// Bound logind session that is driven by the `wayland_server::EventLoop`.
@ -455,7 +445,10 @@ pub fn logind_session_bind<Data: 'static>(
.into_iter()
.map(|watch| {
let mut source = Generic::from_raw_fd(watch.fd());
source.set_interest(Ready::readable() | Ready::writable());
source.set_interest(
if watch.readable() { Ready::readable() } else { Ready::empty() }
| if watch.writable() { Ready::writable() } else { Ready::empty() }
);
handle.insert_source(source, {
let mut notifier = notifier.clone();
move |evt, _| notifier.event(evt)

View File

@ -45,7 +45,7 @@
//! for notifications are the `Libinput` context, the `UdevBackend` or a `DrmDevice` (handled
//! automatically by the `UdevBackend`, if not done manually).
use super::{AsErrno, AsSessionObserver, Session, SessionNotifier, SessionObserver};
use super::{AsErrno, Session, SessionNotifier, SessionObserver};
use nix::{
fcntl::{self, open, OFlag},
libc::c_int,
@ -67,7 +67,7 @@ use std::{
Arc,
},
};
#[cfg(feature = "backend_session_udev")]
#[cfg(feature = "backend_udev")]
use udev::Context;
use wayland_server::calloop::{signals::Signals, LoopHandle, Source};
@ -120,12 +120,12 @@ const TTY_MAJOR: u64 = 4;
#[cfg(not(any(target_os = "linux", target_os = "android")))]
const TTY_MAJOR: u64 = 0;
#[cfg(not(feature = "backend_session_udev"))]
#[cfg(not(feature = "backend_udev"))]
fn is_tty_device(dev: dev_t, _path: Option<&Path>) -> bool {
major(dev) == TTY_MAJOR
}
#[cfg(feature = "backend_session_udev")]
#[cfg(feature = "backend_udev")]
fn is_tty_device(dev: dev_t, path: Option<&Path>) -> bool {
match path {
Some(path) => {
@ -185,8 +185,10 @@ impl DirectSession {
path,
fcntl::OFlag::O_RDWR | fcntl::OFlag::O_CLOEXEC,
Mode::empty(),
).chain_err(|| ErrorKind::FailedToOpenTTY(String::from(path.to_string_lossy())))
}).unwrap_or_else(|| {
)
.chain_err(|| ErrorKind::FailedToOpenTTY(String::from(path.to_string_lossy())))
})
.unwrap_or_else(|| {
dup(0 /*stdin*/).chain_err(|| ErrorKind::FailedToOpenTTY(String::from("<stdin>")))
})?;
@ -350,28 +352,18 @@ pub struct Id(usize);
impl SessionNotifier for DirectSessionNotifier {
type Id = Id;
fn register<S: SessionObserver + 'static, A: AsSessionObserver<S>>(
&mut self,
signal: &mut A,
) -> Self::Id {
self.signals.push(Some(Box::new(signal.observer())));
fn register<S: SessionObserver + 'static>(&mut self, signal: S) -> Self::Id {
self.signals.push(Some(Box::new(signal)));
Id(self.signals.len() - 1)
}
fn unregister(&mut self, signal: Id) {
self.signals[signal.0] = None;
}
fn is_active(&self) -> bool {
self.active.load(Ordering::SeqCst)
}
fn seat(&self) -> &str {
"seat0"
}
}
impl DirectSessionNotifier {
fn signal_received(&mut self) {
if self.is_active() {
if self.active.load(Ordering::SeqCst) {
info!(self.logger, "Session shall become inactive.");
for signal in &mut self.signals {
if let Some(ref mut signal) = *signal {
@ -439,7 +431,8 @@ pub fn direct_session_bind<Data: 'static>(
.insert_source(source, {
let notifier = notifier.clone();
move |_, _| notifier.borrow_mut().signal_received()
}).map_err(move |e| {
})
.map_err(move |e| {
// the backend in the closure should already have been dropped
let notifier = Rc::try_unwrap(fail_notifier)
.unwrap_or_else(|_| unreachable!())

View File

@ -10,7 +10,7 @@
//! The following mechanisms are currently provided:
//! - direct - legacy tty / virtual terminal kernel API
//!
use nix::fcntl::OFlag;
pub use nix::fcntl::OFlag;
use std::{
cell::RefCell,
os::unix::io::RawFd,
@ -54,15 +54,9 @@ pub trait SessionNotifier {
/// Registers a given `SessionObserver`.
///
/// Returns an id of the inserted observer, can be used to remove it again.
fn register<S: SessionObserver + 'static, A: AsSessionObserver<S>>(&mut self, signal: &mut A)
-> Self::Id;
fn register<S: SessionObserver + 'static>(&mut self, signal: S) -> Self::Id;
/// Removes an observer by its given id from `SessionNotifier::register`.
fn unregister(&mut self, signal: Self::Id);
/// Check if this session is currently active
fn is_active(&self) -> bool;
/// Which seat this session is on
fn seat(&self) -> &str;
}
/// Trait describing the ability to return a `SessionObserver` related to Self.
@ -186,3 +180,5 @@ pub mod auto;
mod dbus;
pub mod direct;
pub use self::dbus::*;
mod multi;
pub use self::multi::*;

View File

@ -0,0 +1,69 @@
use std::{
collections::HashMap,
os::unix::io::RawFd,
sync::{
atomic::{AtomicUsize, Ordering},
Arc, Mutex,
},
};
use super::{SessionNotifier, SessionObserver};
static ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
/// Ids of registered `SessionObserver`s of the `DirectSessionNotifier`
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub struct Id(usize);
struct MultiObserver {
observer: Arc<Mutex<HashMap<Id, Box<SessionObserver>>>>,
}
impl SessionObserver for MultiObserver {
fn pause(&mut self, device: Option<(u32, u32)>) {
let mut lock = self.observer.lock().unwrap();
for mut observer in lock.values_mut() {
observer.pause(device)
}
}
fn activate(&mut self, device: Option<(u32, u32, Option<RawFd>)>) {
let mut lock = self.observer.lock().unwrap();
for mut observer in lock.values_mut() {
observer.activate(device)
}
}
}
struct MultiNotifier {
observer: Arc<Mutex<HashMap<Id, Box<SessionObserver>>>>,
}
impl SessionNotifier for MultiNotifier {
type Id = Id;
fn register<S: SessionObserver + 'static>(&mut self, signal: S) -> Self::Id {
let id = Id(ID_COUNTER.fetch_add(1, Ordering::SeqCst));
self.observer.lock().unwrap().insert(id, Box::new(signal));
id
}
fn unregister(&mut self, signal: Self::Id) {
self.observer.lock().unwrap().remove(&signal);
}
}
/// Create a pair of a linked [`SessionObserver`](../trait.SessionObserver.html) and a
/// [`SessionNotifier`](../trait.SessionNotifier.html).
///
/// Observers added to the returned notifier are notified,
/// when the returned observer is notified.
pub fn notify_multiplexer() -> (impl SessionObserver, impl SessionNotifier<Id = Id>) {
let observer = Arc::new(Mutex::new(HashMap::new()));
(
MultiObserver {
observer: observer.clone(),
},
MultiNotifier { observer },
)
}

View File

@ -9,258 +9,90 @@
//! See also `examples/udev.rs` for pure hardware backed example of a compositor utilizing this
//! backend.
use backend::{
drm::{drm_device_bind, DrmDevice, DrmHandler},
session::{AsSessionObserver, Session, SessionObserver},
};
use drm::{control::Device as ControlDevice, Device as BasicDevice};
use nix::{fcntl, sys::stat::dev_t};
use nix::sys::stat::{dev_t, stat};
use std::{
cell::RefCell,
collections::HashMap,
collections::HashSet,
ffi::OsString,
io::Error as IoError,
mem::drop,
os::unix::io::{AsRawFd, RawFd},
path::{Path, PathBuf},
rc::{Rc, Weak},
};
use udev::{Context, Enumerator, Event, EventType, MonitorBuilder, MonitorSocket, Result as UdevResult};
use udev::{Context, Enumerator, EventType, MonitorBuilder, MonitorSocket, Result as UdevResult};
use wayland_server::calloop::{
generic::{EventedRawFd, Generic},
generic::{EventedFd, Generic},
mio::Ready,
LoopHandle, Source,
InsertError, LoopHandle, Source,
};
/// Udev's `DrmDevice` type based on the underlying session
pub struct SessionFdDrmDevice(RawFd);
impl AsRawFd for SessionFdDrmDevice {
fn as_raw_fd(&self) -> RawFd {
self.0
}
}
impl BasicDevice for SessionFdDrmDevice {}
impl ControlDevice for SessionFdDrmDevice {}
/// Graphical backend that monitors available DRM devices.
/// Backend to monitor available drm devices.
///
/// Provides a way to automatically initialize a `DrmDevice` for available GPUs and notifies the
/// given handler of any changes. Can be used to provide hot-plug functionality for GPUs and
/// Provides a way to automatically scan for available gpus and notifies the
/// given handler of any changes. Can be used to provide hot-plug functionality for gpus and
/// attached monitors.
pub struct UdevBackend<
H: DrmHandler<SessionFdDrmDevice> + 'static,
S: Session + 'static,
T: UdevHandler<H> + 'static,
Data: 'static,
> {
_handler: ::std::marker::PhantomData<H>,
devices: Rc<
RefCell<
HashMap<
dev_t,
(
Source<Generic<EventedRawFd>>,
Rc<RefCell<DrmDevice<SessionFdDrmDevice>>>,
),
>,
>,
>,
pub struct UdevBackend<T: UdevHandler + 'static> {
devices: HashSet<dev_t>,
monitor: MonitorSocket,
session: S,
handler: T,
logger: ::slog::Logger,
handle: LoopHandle<Data>,
}
impl<
H: DrmHandler<SessionFdDrmDevice> + 'static,
S: Session + 'static,
T: UdevHandler<H> + 'static,
Data: 'static,
> UdevBackend<H, S, T, Data>
{
impl<T: UdevHandler + 'static> AsRawFd for UdevBackend<T> {
fn as_raw_fd(&self) -> RawFd {
self.monitor.as_raw_fd()
}
}
impl<T: UdevHandler + 'static> UdevBackend<T> {
/// Creates a new `UdevBackend` and adds it to the given `EventLoop`'s state.
///
/// ## Arguments
/// `evlh` - An event loop to use for binding `DrmDevices`
/// `context` - An initialized udev context
/// `session` - A session used to open and close devices as they become available
/// `handler` - User-provided handler to respond to any detected changes
/// `seat` -
/// `logger` - slog Logger to be used by the backend and its `DrmDevices`.
pub fn new<L>(
handle: LoopHandle<Data>,
pub fn new<L, S: AsRef<str>>(
context: &Context,
mut session: S,
mut handler: T,
seat: S,
logger: L,
) -> Result<UdevBackend<H, S, T, Data>>
) -> UdevResult<UdevBackend<T>>
where
L: Into<Option<::slog::Logger>>,
{
let logger = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_udev"));
let seat = session.seat();
let devices = all_gpus(context, seat)
.chain_err(|| ErrorKind::FailedToScan)?
let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_udev"));
let devices = all_gpus(context, seat)?
.into_iter()
// Create devices
.flat_map(|path| {
match DrmDevice::new(
{
match session.open(
&path,
fcntl::OFlag::O_RDWR
| fcntl::OFlag::O_CLOEXEC
| fcntl::OFlag::O_NOCTTY
| fcntl::OFlag::O_NONBLOCK,
) {
Ok(fd) => SessionFdDrmDevice(fd),
Err(err) => {
warn!(
logger,
"Unable to open drm device {:?}, Error: {:?}. Skipping", path, err
);
return None;
}
}
},
logger.clone(),
) {
// Call the handler, which might add it to the runloop
Ok(mut device) => {
let devnum = device.device_id();
let fd = device.as_raw_fd();
match handler.device_added(&mut device) {
Some(drm_handler) => match drm_device_bind(&handle, device, drm_handler) {
Ok((event_source, device)) => Some((devnum, (event_source, device))),
Err((err, mut device)) => {
warn!(logger, "Failed to bind device. Error: {:?}.", err);
handler.device_removed(&mut device);
drop(device);
if let Err(err) = session.close(fd) {
warn!(
logger,
"Failed to close dropped device. Error: {:?}. Ignoring", err
);
};
None
}
},
None => {
drop(device); //drops master
if let Err(err) = session.close(fd) {
warn!(logger, "Failed to close device. Error: {:?}. Ignoring", err);
}
None
}
}
}
Err(err) => {
warn!(
logger,
"Failed to initialize device {:?}. Error: {:?}. Skipping", path, err
);
None
}
.flat_map(|path| match stat(&path) {
Ok(stat) => {
handler.device_added(stat.st_rdev, path);
Some(stat.st_rdev)
}
}).collect::<HashMap<dev_t, _>>();
Err(err) => {
warn!(log, "Unable to get id of {:?}, Error: {:?}. Skipping", path, err);
None
}
})
.collect();
let mut builder = MonitorBuilder::new(context).chain_err(|| ErrorKind::FailedToInitMonitor)?;
builder
.match_subsystem("drm")
.chain_err(|| ErrorKind::FailedToInitMonitor)?;
let monitor = builder.listen().chain_err(|| ErrorKind::FailedToInitMonitor)?;
let mut builder = MonitorBuilder::new(context)?;
builder.match_subsystem("drm")?;
let monitor = builder.listen()?;
Ok(UdevBackend {
_handler: ::std::marker::PhantomData,
devices: Rc::new(RefCell::new(devices)),
devices,
monitor,
session,
handler,
logger,
handle,
logger: log,
})
}
/// Closes the udev backend and frees all remaining open devices.
pub fn close(&mut self) {
let mut devices = self.devices.borrow_mut();
for (_, (event_source, device)) in devices.drain() {
event_source.remove();
let mut device = Rc::try_unwrap(device)
.unwrap_or_else(|_| unreachable!())
.into_inner();
self.handler.device_removed(&mut device);
let fd = device.as_raw_fd();
drop(device);
if let Err(err) = self.session.close(fd) {
warn!(self.logger, "Failed to close device. Error: {:?}. Ignoring", err);
};
}
info!(self.logger, "All devices closed");
}
}
impl<
H: DrmHandler<SessionFdDrmDevice> + 'static,
S: Session + 'static,
T: UdevHandler<H> + 'static,
Data: 'static,
> Drop for UdevBackend<H, S, T, Data>
{
impl<T: UdevHandler + 'static> Drop for UdevBackend<T> {
fn drop(&mut self) {
self.close();
}
}
/// `SessionObserver` linked to the `UdevBackend` it was created from.
pub struct UdevBackendObserver {
devices: Weak<
RefCell<
HashMap<
dev_t,
(
Source<Generic<EventedRawFd>>,
Rc<RefCell<DrmDevice<SessionFdDrmDevice>>>,
),
>,
>,
>,
logger: ::slog::Logger,
}
impl<
H: DrmHandler<SessionFdDrmDevice> + 'static,
S: Session + 'static,
T: UdevHandler<H> + 'static,
Data: 'static,
> AsSessionObserver<UdevBackendObserver> for UdevBackend<H, S, T, Data>
{
fn observer(&mut self) -> UdevBackendObserver {
UdevBackendObserver {
devices: Rc::downgrade(&self.devices),
logger: self.logger.clone(),
}
}
}
impl SessionObserver for UdevBackendObserver {
fn pause(&mut self, devnum: Option<(u32, u32)>) {
if let Some(devices) = self.devices.upgrade() {
for &mut (_, ref device) in devices.borrow_mut().values_mut() {
info!(self.logger, "changed successful");
device.borrow_mut().observer().pause(devnum);
}
}
}
fn activate(&mut self, devnum: Option<(u32, u32, Option<RawFd>)>) {
if let Some(devices) = self.devices.upgrade() {
for &mut (_, ref device) in devices.borrow_mut().values_mut() {
info!(self.logger, "changed successful");
device.borrow_mut().observer().activate(devnum);
}
for device in &self.devices {
self.handler.device_removed(*device);
}
}
}
@ -269,122 +101,38 @@ impl SessionObserver for UdevBackendObserver {
///
/// Allows the backend to receive kernel events and thus to drive the `UdevHandler`.
/// No runtime functionality can be provided without using this function.
pub fn udev_backend_bind<H, S, T, Data>(
mut udev: UdevBackend<H, S, T, Data>,
) -> ::std::result::Result<Source<Generic<EventedRawFd>>, IoError>
where
H: DrmHandler<SessionFdDrmDevice> + 'static,
T: UdevHandler<H> + 'static,
S: Session + 'static,
{
let fd = udev.monitor.as_raw_fd();
let handle = udev.handle.clone();
let mut source = Generic::from_raw_fd(fd);
pub fn udev_backend_bind<T: UdevHandler + 'static, Data: 'static>(
udev: UdevBackend<T>,
handle: &LoopHandle<Data>,
) -> Result<Source<Generic<EventedFd<UdevBackend<T>>>>, InsertError<Generic<EventedFd<UdevBackend<T>>>>> {
let mut source = Generic::from_fd_source(udev);
source.set_interest(Ready::readable());
handle
.insert_source(source, move |_, _| {
udev.process_events();
}).map_err(Into::into)
handle.insert_source(source, |evt, _| {
evt.source.borrow_mut().0.process_events();
})
}
impl<H, S, T, Data> UdevBackend<H, S, T, Data>
where
H: DrmHandler<SessionFdDrmDevice> + 'static,
T: UdevHandler<H> + 'static,
S: Session + 'static,
Data: 'static,
{
impl<T: UdevHandler + 'static> UdevBackend<T> {
fn process_events(&mut self) {
let events = self.monitor.clone().collect::<Vec<Event>>();
for event in events {
let monitor = self.monitor.clone();
for event in monitor {
match event.event_type() {
// New device
EventType::Add => {
info!(self.logger, "Device Added");
if let (Some(path), Some(devnum)) = (event.devnode(), event.devnum()) {
let mut device = {
match DrmDevice::new(
{
let logger = self.logger.clone();
match self.session.open(
path,
fcntl::OFlag::O_RDWR
| fcntl::OFlag::O_CLOEXEC
| fcntl::OFlag::O_NOCTTY
| fcntl::OFlag::O_NONBLOCK,
) {
Ok(fd) => SessionFdDrmDevice(fd),
Err(err) => {
warn!(
logger,
"Unable to open drm device {:?}, Error: {:?}. Skipping",
path,
err
);
continue;
}
}
},
self.logger.clone(),
) {
Ok(dev) => dev,
Err(err) => {
warn!(
self.logger,
"Failed to initialize device {:?}. Error: {}. Skipping", path, err
);
continue;
}
}
};
let fd = device.as_raw_fd();
match self.handler.device_added(&mut device) {
Some(drm_handler) => match drm_device_bind(&self.handle, device, drm_handler) {
Ok(fd_event_source) => {
self.devices.borrow_mut().insert(devnum, fd_event_source);
}
Err((err, mut device)) => {
warn!(self.logger, "Failed to bind device. Error: {:?}.", err);
self.handler.device_removed(&mut device);
drop(device);
if let Err(err) = self.session.close(fd) {
warn!(
self.logger,
"Failed to close dropped device. Error: {:?}. Ignoring", err
);
};
}
},
None => {
self.handler.device_removed(&mut device);
drop(device);
if let Err(err) = self.session.close(fd) {
warn!(self.logger, "Failed to close unused device. Error: {:?}", err);
}
}
};
if self.devices.insert(devnum) {
self.handler.device_added(devnum, path.to_path_buf());
}
}
}
// Device removed
EventType::Remove => {
info!(self.logger, "Device Remove");
if let Some(devnum) = event.devnum() {
if let Some((fd_event_source, device)) = self.devices.borrow_mut().remove(&devnum) {
fd_event_source.remove();
let mut device = Rc::try_unwrap(device)
.unwrap_or_else(|_| unreachable!())
.into_inner();
self.handler.device_removed(&mut device);
let fd = device.as_raw_fd();
drop(device);
if let Err(err) = self.session.close(fd) {
warn!(
self.logger,
"Failed to close device {:?}. Error: {:?}. Ignoring",
event.sysname(),
err
);
};
if self.devices.remove(&devnum) {
self.handler.device_removed(devnum);
}
}
}
@ -393,9 +141,8 @@ where
info!(self.logger, "Device Changed");
if let Some(devnum) = event.devnum() {
info!(self.logger, "Devnum: {:b}", devnum);
if let Some(&(_, ref device)) = self.devices.borrow_mut().get(&devnum) {
let handler = &mut self.handler;
handler.device_changed(&mut device.borrow_mut());
if self.devices.contains(&devnum) {
self.handler.device_changed(devnum);
} else {
info!(self.logger, "changed, but device not tracked by backend");
};
@ -409,36 +156,21 @@ where
}
}
/// Handler for the `UdevBackend`, allows to open, close and update DRM devices as they change during runtime.
pub trait UdevHandler<H: DrmHandler<SessionFdDrmDevice> + 'static> {
/// Called on initialization for every known device and when a new device is detected.
///
/// Returning a `DrmHandler` will initialize the device, returning `None` will ignore the device.
///
/// ## Panics
/// Panics if you try to borrow the token of the belonging `UdevBackend` using this `StateProxy`.
fn device_added(&mut self, device: &mut DrmDevice<SessionFdDrmDevice>) -> Option<H>;
/// Handler for the `UdevBackend`, allows to open, close and update drm devices as they change during runtime.
pub trait UdevHandler {
/// Called when a new device is detected.
fn device_added(&mut self, device: dev_t, path: PathBuf);
/// Called when an open device is changed.
///
/// This usually indicates that some connectors did become available or were unplugged. The handler
/// should scan again for connected monitors and mode switch accordingly.
///
/// ## Panics
/// Panics if you try to borrow the token of the belonging `UdevBackend` using this `StateProxy`.
fn device_changed(&mut self, device: &mut DrmDevice<SessionFdDrmDevice>);
fn device_changed(&mut self, device: dev_t);
/// Called when a device was removed.
///
/// The device will not accept any operations anymore and its file descriptor will be closed once
/// this function returns, any open references/tokens to this device need to be released.
///
/// ## Panics
/// Panics if you try to borrow the token of the belonging `UdevBackend` using this `StateProxy`.
fn device_removed(&mut self, device: &mut DrmDevice<SessionFdDrmDevice>);
/// Called when the udev context has encountered and error.
///
/// ## Panics
/// Panics if you try to borrow the token of the belonging `UdevBackend` using this `StateProxy`.
fn error(&mut self, error: IoError);
/// The corresponding `UdevRawFd` will never return a valid `RawFd` anymore
/// and its file descriptor will be closed once this function returns,
/// any open references/tokens to this device need to be released.
fn device_removed(&mut self, device: dev_t);
}
/// Returns the path of the primary GPU device if any
@ -486,25 +218,7 @@ pub fn all_gpus<S: AsRef<str>>(context: &Context, seat: S) -> UdevResult<Vec<Pat
.map(|x| x.to_os_string())
.unwrap_or(OsString::from("seat0"))
== *seat.as_ref()
}).flat_map(|device| device.devnode().map(PathBuf::from))
})
.flat_map(|device| device.devnode().map(PathBuf::from))
.collect())
}
error_chain! {
errors {
#[doc = "Failed to scan for devices"]
FailedToScan {
description("Failed to scan for devices"),
}
#[doc = "Failed to initialize udev monitor"]
FailedToInitMonitor {
description("Failed to initialize udev monitor"),
}
#[doc = "Failed to identify devices"]
FailedToIdentifyDevices {
description("Failed to identify devices"),
}
}
}

View File

@ -1,17 +1,11 @@
//! Implementation of backend traits for types provided by `winit`
use backend::{
graphics::{
egl::{
context::GlAttributes,
error as egl_error,
error::Result as EGLResult,
native,
wayland::{EGLDisplay, EGLWaylandExtensions},
EGLContext, EGLGraphicsBackend, EGLSurface, PixelFormat, SwapBuffersError,
},
GraphicsBackend,
egl::{
context::GlAttributes, error as egl_error, error::Result as EGLResult, native, EGLContext,
EGLDisplay, EGLGraphicsBackend, EGLSurface,
},
graphics::{gl::GLGraphicsBackend, CursorBackend, PixelFormat, SwapBuffersError},
input::{
Axis, AxisSource, Event as BackendEvent, InputBackend, InputHandler, KeyState, KeyboardKeyEvent,
MouseButton, MouseButtonState, PointerAxisEvent, PointerButtonEvent, PointerMotionAbsoluteEvent,
@ -20,7 +14,12 @@ use backend::{
},
};
use nix::libc::c_void;
use std::{cell::RefCell, cmp, error, fmt, rc::Rc, time::Instant};
use std::{
cell::{Ref, RefCell},
cmp, error, fmt,
rc::Rc,
time::Instant,
};
use wayland_client::egl as wegl;
use wayland_server::Display;
use winit::{
@ -59,10 +58,10 @@ enum Window {
}
impl Window {
fn window(&self) -> &WinitWindow {
fn window(&self) -> Ref<WinitWindow> {
match *self {
Window::Wayland { ref context, .. } => &**context,
Window::X11 { ref context, .. } => &**context,
Window::Wayland { ref context, .. } => context.borrow(),
Window::X11 { ref context, .. } => context.borrow(),
}
}
}
@ -156,12 +155,12 @@ where
let reqs = Default::default();
let window = Rc::new(
if native::NativeDisplay::<native::Wayland>::is_backend(&winit_window) {
let context =
let mut context =
EGLContext::<native::Wayland, WinitWindow>::new(winit_window, attributes, reqs, log.clone())?;
let surface = context.create_surface(())?;
Window::Wayland { context, surface }
} else if native::NativeDisplay::<native::X11>::is_backend(&winit_window) {
let context =
let mut context =
EGLContext::<native::X11, WinitWindow>::new(winit_window, attributes, reqs, log.clone())?;
let surface = context.create_surface(())?;
Window::X11 { context, surface }
@ -221,13 +220,13 @@ pub trait WinitEventsHandler {
impl WinitGraphicsBackend {
/// Get a reference to the internally used `winit::Window`
pub fn winit_window(&self) -> &WinitWindow {
pub fn winit_window(&self) -> Ref<WinitWindow> {
self.window.window()
}
}
impl GraphicsBackend for WinitGraphicsBackend {
type CursorFormat = MouseCursor;
impl<'a> CursorBackend<'a> for WinitGraphicsBackend {
type CursorFormat = &'a MouseCursor;
type Error = ();
fn set_cursor_position(&self, x: u32, y: u32) -> ::std::result::Result<(), ()> {
@ -240,11 +239,14 @@ impl GraphicsBackend for WinitGraphicsBackend {
})
}
fn set_cursor_representation(
&self,
cursor: &Self::CursorFormat,
fn set_cursor_representation<'b>(
&'b self,
cursor: Self::CursorFormat,
_hotspot: (u32, u32),
) -> ::std::result::Result<(), ()> {
) -> ::std::result::Result<(), ()>
where
'a: 'b,
{
// Cannot log this one, as `CursorFormat` is not `Debug` and should not be
debug!(self.logger, "Changing cursor representation");
self.window.window().set_cursor(*cursor);
@ -252,7 +254,7 @@ impl GraphicsBackend for WinitGraphicsBackend {
}
}
impl EGLGraphicsBackend for WinitGraphicsBackend {
impl GLGraphicsBackend for WinitGraphicsBackend {
fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> {
trace!(self.logger, "Swapping buffers");
match *self.window {
@ -303,7 +305,7 @@ impl EGLGraphicsBackend for WinitGraphicsBackend {
}
}
impl EGLWaylandExtensions for WinitGraphicsBackend {
impl EGLGraphicsBackend for WinitGraphicsBackend {
fn bind_wl_display(&self, display: &Display) -> EGLResult<EGLDisplay> {
match *self.window {
Window::Wayland { ref context, .. } => context.bind_wl_display(display),

View File

@ -14,6 +14,7 @@ extern crate tempfile;
pub extern crate wayland_commons;
pub extern crate wayland_protocols;
pub extern crate wayland_server;
#[cfg(feature = "native_lib")]
extern crate wayland_sys;
extern crate xkbcommon;
@ -21,13 +22,13 @@ extern crate xkbcommon;
pub extern crate dbus;
#[cfg(feature = "backend_drm")]
pub extern crate drm;
#[cfg(feature = "backend_drm")]
#[cfg(feature = "backend_drm_gbm")]
pub extern crate gbm;
#[cfg(feature = "backend_libinput")]
pub extern crate input;
#[cfg(feature = "backend_session_logind")]
pub extern crate systemd;
#[cfg(feature = "udev")]
#[cfg(feature = "backend_udev")]
pub extern crate udev;
#[cfg(feature = "backend_winit")]
extern crate wayland_client;

View File

@ -108,7 +108,8 @@ impl PointerGrab for DnDGrab {
offer_data.clone(),
action_choice,
)
}).unwrap();
})
.unwrap();
// advertize the offer to the client
device.send(wl_data_device::Event::DataOffer { id: offer.clone() });
with_source_metadata(source, |meta| {
@ -118,7 +119,8 @@ impl PointerGrab for DnDGrab {
offer.send(wl_data_offer::Event::SourceActions {
source_actions: meta.dnd_action.to_raw(),
});
}).unwrap();
})
.unwrap();
device.send(wl_data_device::Event::Enter {
serial,
x: x - sx,

View File

@ -150,7 +150,8 @@ impl SeatData {
// check if the source and associated mime type is still valid
let valid = with_source_metadata(&source, |meta| {
meta.mime_types.contains(&mime_type)
}).unwrap_or(false)
})
.unwrap_or(false)
&& source.is_alive();
if !valid {
// deny the receive
@ -171,7 +172,8 @@ impl SeatData {
for mime_type in meta.mime_types.iter().cloned() {
offer.send(wl_data_offer::Event::Offer { mime_type })
}
}).unwrap();
})
.unwrap();
dd.send(wl_data_device::Event::Selection { id: Some(offer) });
}
}

View File

@ -131,7 +131,8 @@ where
self.callback.clone(),
action_choice,
)
}).unwrap();
})
.unwrap();
// advertize the offer to the client
device.send(wl_data_device::Event::DataOffer { id: offer.clone() });
for mime_type in self.metadata.mime_types.iter().cloned() {

View File

@ -141,7 +141,8 @@ impl KbdInternal {
&xkb_config.variant,
xkb_config.options,
xkb::KEYMAP_COMPILE_NO_FLAGS,
).ok_or(())?;
)
.ok_or(())?;
let state = xkb::State::new(&keymap);
Ok(KbdInternal {
known_kbds: Vec::new(),

View File

@ -119,7 +119,8 @@ where
} else {
false
}
}).expect("wl_shell_surface exists but surface has not the right role?");
})
.expect("wl_shell_surface exists but surface has not the right role?");
if valid {
(&mut *user_impl)(ShellRequest::Pong {
surface: make_handle(&shell_surface, ctoken),

View File

@ -620,7 +620,8 @@ where
.with_role_data::<XdgSurfaceRole, _, _>(&self.wl_surface, |data| match data.pending_state {
XdgSurfacePendingState::Toplevel(ref state) => Some(state.clone()),
_ => None,
}).ok()
})
.ok()
.and_then(|x| x)
}
}
@ -782,7 +783,8 @@ where
.with_role_data::<XdgSurfaceRole, _, _>(&self.wl_surface, |data| match data.pending_state {
XdgSurfacePendingState::Popup(ref state) => Some(state.clone()),
_ => None,
}).ok()
})
.ok()
.and_then(|x| x)
}
}

View File

@ -217,7 +217,8 @@ where
"xdg_surface was destroyed before its role object".into(),
);
}
}).expect("xdg_surface exists but surface has not shell_surface role?!");
})
.expect("xdg_surface exists but surface has not shell_surface role?!");
}
fn xdg_surface_implementation<U, R, SD>(
@ -244,7 +245,8 @@ fn xdg_surface_implementation<U, R, SD>(
min_size: (0, 0),
max_size: (0, 0),
});
}).expect("xdg_surface exists but surface has not shell_surface role?!");
})
.expect("xdg_surface exists but surface has not shell_surface role?!");
let toplevel = id.implement_nonsend(
toplevel_implementation::<U, R, SD>,
Some(destroy_toplevel::<U, R, SD>),
@ -286,7 +288,8 @@ fn xdg_surface_implementation<U, R, SD>(
parent: parent_surface,
positioner: positioner_data.borrow().clone(),
});
}).expect("xdg_surface exists but surface has not shell_surface role?!");
})
.expect("xdg_surface exists but surface has not shell_surface role?!");
let popup = id.implement_nonsend(
xg_popup_implementation::<U, R, SD>,
Some(destroy_popup::<U, R, SD>),
@ -315,7 +318,8 @@ fn xdg_surface_implementation<U, R, SD>(
.compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| {
data.window_geometry = Some(Rectangle { x, y, width, height });
}).expect("xdg_surface exists but surface has not shell_surface role?!");
})
.expect("xdg_surface exists but surface has not shell_surface role?!");
}
xdg_surface::Request::AckConfigure { serial } => {
data.shell_data
@ -336,7 +340,8 @@ fn xdg_surface_implementation<U, R, SD>(
);
}
role_data.configured = true;
}).expect("xdg_surface exists but surface has not shell_surface role?!");
})
.expect("xdg_surface exists but surface has not shell_surface role?!");
}
}
}
@ -369,7 +374,8 @@ fn with_surface_toplevel_data<U, R, SD, F>(
.with_role_data::<XdgSurfaceRole, _, _>(&toplevel_data.wl_surface, |data| match data.pending_state {
XdgSurfacePendingState::Toplevel(ref mut toplevel_data) => f(toplevel_data),
_ => unreachable!(),
}).expect("xdg_toplevel exists but surface has not shell_surface role?!");
})
.expect("xdg_toplevel exists but surface has not shell_surface role?!");
}
pub fn send_toplevel_configure<U, R, SD>(
@ -539,7 +545,8 @@ where
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| {
data.pending_state = XdgSurfacePendingState::None;
data.configured = false;
}).expect("xdg_toplevel exists but surface has not shell_surface role?!");
})
.expect("xdg_toplevel exists but surface has not shell_surface role?!");
}
// remove this surface from the known ones (as well as any leftover dead surface)
data.shell_data
@ -627,7 +634,8 @@ where
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| {
data.pending_state = XdgSurfacePendingState::None;
data.configured = false;
}).expect("xdg_popup exists but surface has not shell_surface role?!");
})
.expect("xdg_popup exists but surface has not shell_surface role?!");
}
// remove this surface from the known ones (as well as any leftover dead surface)
data.shell_data

View File

@ -236,7 +236,8 @@ where
"xdg_surface was destroyed before its role object".into(),
);
}
}).expect("xdg_surface exists but surface has not shell_surface role?!");
})
.expect("xdg_surface exists but surface has not shell_surface role?!");
}
fn xdg_surface_implementation<U, R, SD>(
@ -263,7 +264,8 @@ fn xdg_surface_implementation<U, R, SD>(
min_size: (0, 0),
max_size: (0, 0),
});
}).expect("xdg_surface exists but surface has not shell_surface role?!");
})
.expect("xdg_surface exists but surface has not shell_surface role?!");
let toplevel = id.implement_nonsend(
toplevel_implementation::<U, R, SD>,
Some(destroy_toplevel::<U, R, SD>),
@ -302,7 +304,8 @@ fn xdg_surface_implementation<U, R, SD>(
parent: Some(parent_data.wl_surface.clone()),
positioner: positioner_data.borrow().clone(),
});
}).expect("xdg_surface exists but surface has not shell_surface role?!");
})
.expect("xdg_surface exists but surface has not shell_surface role?!");
let popup = id.implement_nonsend(
popup_implementation::<U, R, SD>,
Some(destroy_popup::<U, R, SD>),
@ -331,7 +334,8 @@ fn xdg_surface_implementation<U, R, SD>(
.compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| {
data.window_geometry = Some(Rectangle { x, y, width, height });
}).expect("xdg_surface exists but surface has not shell_surface role?!");
})
.expect("xdg_surface exists but surface has not shell_surface role?!");
}
zxdg_surface_v6::Request::AckConfigure { serial } => {
data.shell_data
@ -352,7 +356,8 @@ fn xdg_surface_implementation<U, R, SD>(
);
}
role_data.configured = true;
}).expect("xdg_surface exists but surface has not shell_surface role?!");
})
.expect("xdg_surface exists but surface has not shell_surface role?!");
}
}
}
@ -382,7 +387,8 @@ where
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| match data.pending_state {
XdgSurfacePendingState::Toplevel(ref mut toplevel_data) => f(toplevel_data),
_ => unreachable!(),
}).expect("xdg_toplevel exists but surface has not shell_surface role?!");
})
.expect("xdg_toplevel exists but surface has not shell_surface role?!");
}
pub fn send_toplevel_configure<U, R, SD>(
@ -553,7 +559,8 @@ where
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| {
data.pending_state = XdgSurfacePendingState::None;
data.configured = false;
}).expect("xdg_toplevel exists but surface has not shell_surface role?!");
})
.expect("xdg_toplevel exists but surface has not shell_surface role?!");
}
// remove this surface from the known ones (as well as any leftover dead surface)
data.shell_data
@ -644,7 +651,8 @@ where
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| {
data.pending_state = XdgSurfacePendingState::None;
data.configured = false;
}).expect("xdg_popup exists but surface has not shell_surface role?!");
})
.expect("xdg_popup exists but surface has not shell_surface role?!");
}
// remove this surface from the known ones (as well as any leftover dead surface)
data.shell_data

View File

@ -97,7 +97,8 @@ impl<WM: XWindowManager + 'static> XWayland<WM> {
debug_assert!(evt.signal() == Signal::SIGUSR1);
xwayland_ready(&inner);
},
).map_err(|_| ())
)
.map_err(|_| ())
}),
wayland_display: display,
instance: None,