diff --git a/anvil/Cargo.toml b/anvil/Cargo.toml index 72dbc4b..c49841b 100644 --- a/anvil/Cargo.toml +++ b/anvil/Cargo.toml @@ -23,8 +23,9 @@ features = [ "renderer_glium" ] gl_generator = "0.9" [features] -default = [ "winit", "tty_launch" ] +default = [ "winit", "tty_launch", "udev" ] winit = [ "smithay/backend_winit" ] -tty_launch = [ "smithay/backend_udev", "smithay/backend_libinput", "smithay/backend_drm" ] +tty_launch = [ "smithay/backend_libinput", "smithay/backend_drm" ] +udev = [ "tty_launch", "smithay/backend_udev" ] logind = [ "smithay/backend_session_logind" ] diff --git a/anvil/src/input_handler.rs b/anvil/src/input_handler.rs index e52cafe..28edc94 100644 --- a/anvil/src/input_handler.rs +++ b/anvil/src/input_handler.rs @@ -6,7 +6,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use slog::Logger; -#[cfg(feature = "tty_launch")] +#[cfg(feature = "udev")] use smithay::backend::session::auto::AutoSession; use smithay::backend::input::{self, Event, InputBackend, InputHandler, KeyState, KeyboardKeyEvent, PointerAxisEvent, PointerButtonEvent, PointerMotionAbsoluteEvent, @@ -24,7 +24,7 @@ pub struct AnvilInputHandler { pointer_location: Rc>, screen_size: (u32, u32), serial: u32, - #[cfg(feature = "tty_launch")] + #[cfg(feature = "udev")] session: Option, running: Arc, } @@ -48,12 +48,12 @@ impl AnvilInputHandler { running, pointer_location, serial: 1, - #[cfg(feature = "tty_launch")] + #[cfg(feature = "udev")] session: None, } } - #[cfg(feature = "tty_launch")] + #[cfg(feature = "udev")] pub fn new_with_session( log: Logger, pointer: PointerHandle, @@ -173,7 +173,7 @@ impl InputHandler for AnvilInputHandler { fn on_pointer_move_absolute(&mut self, _: &input::Seat, evt: B::PointerMotionAbsoluteEvent) { // different cases depending on the context: let (x, y) = { - #[cfg(feature = "tty_launch")] + #[cfg(feature = "udev")] { if self.session.is_some() { // we are started on a tty @@ -184,7 +184,7 @@ impl InputHandler for AnvilInputHandler { evt.position() } } - #[cfg(not(feature = "tty_launch"))] + #[cfg(not(feature = "udev"))] { evt.position() } diff --git a/anvil/src/main.rs b/anvil/src/main.rs index 1cf5f91..e108950 100644 --- a/anvil/src/main.rs +++ b/anvil/src/main.rs @@ -14,18 +14,22 @@ use smithay::wayland_server::Display; mod glium_drawer; mod shell; -#[cfg(feature = "tty_launch")] +#[cfg(feature = "udev")] mod udev; mod window_map; #[cfg(feature = "winit")] mod winit; mod input_handler; +#[cfg(feature = "tty_launch")] +mod raw_drm; static POSSIBLE_BACKENDS: &'static [&'static str] = &[ #[cfg(feature = "winit")] - "--winit", + "--winit : Run anvil as a X11 or Wayland client using winit.", #[cfg(feature = "tty_launch")] - "--tty", + "--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).", ]; fn main() { @@ -47,8 +51,15 @@ fn main() { } } #[cfg(feature = "tty_launch")] - Some("--tty") => { - info!(log, "Starting anvil on a tty"); + 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"); if let Err(()) = udev::run_udev(display, event_loop, log.clone()) { crit!(log, "Failed to initialize tty backend."); } diff --git a/anvil/src/raw_drm.rs b/anvil/src/raw_drm.rs new file mode 100644 index 0000000..2b96312 --- /dev/null +++ b/anvil/src/raw_drm.rs @@ -0,0 +1,237 @@ +use std::cell::RefCell; +use std::fs::{File, OpenOptions}; +use std::os::unix::io::AsRawFd; +use std::os::unix::io::RawFd; +use std::rc::Rc; +use std::time::Duration; + +use smithay::drm::Device as BasicDevice; +use smithay::drm::control::{Device as ControlDevice, ResourceInfo}; +use smithay::drm::control::connector::{Info as ConnectorInfo, State as ConnectorState}; +use smithay::drm::control::crtc; +use smithay::drm::control::encoder::Info as EncoderInfo; +use smithay::drm::result::Error as DrmError; +use smithay::backend::drm::{drm_device_bind, DrmBackend, DrmDevice, DrmHandler}; +use smithay::backend::graphics::egl::EGLGraphicsBackend; +use smithay::backend::graphics::egl::wayland::{EGLWaylandExtensions, Format}; +use smithay::wayland::compositor::{CompositorToken, SubsurfaceRole, TraversalAction}; +use smithay::wayland::compositor::roles::Role; +use smithay::wayland::shm::init_shm_global; +use smithay::wayland_server::{Display, EventLoop}; + +use glium::{Blend, Surface}; +use slog::Logger; + +use glium_drawer::GliumDrawer; +use shell::{init_shell, Buffer, 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 renderer = GliumDrawer::from( + device + .create_backend(crtc, mode, vec![connector_info.handle()]) + .unwrap(), + ); + { + /* + * Initialize glium + */ + let mut frame = renderer.draw(); + frame.clear_color(0.8, 0.8, 0.9, 1.0); + frame.finish().unwrap(); + } + + let egl_display = Rc::new(RefCell::new( + if let Ok(egl_display) = renderer.bind_wl_display(&display) { + info!(log, "EGL hardware-acceleration enabled"); + Some(egl_display) + } else { + None + }, + )); + + /* + * Initialize the globals + */ + + init_shm_global(&mut display, event_loop.token(), vec![], log.clone()); + + let (compositor_token, _, _, window_map) = + init_shell(&mut display, event_loop.token(), log.clone(), egl_display); + + /* + * 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.token(), + device, + DrmHandlerImpl { + compositor_token, + window_map: window_map.clone(), + drawer: renderer, + logger: log, + }, + ).map_err(|(err, _)| err) + .unwrap(); + + loop { + event_loop.dispatch(Some(16)).unwrap(); + display.flush_clients(); + + window_map.borrow_mut().refresh(); + } +} + +pub struct DrmHandlerImpl { + compositor_token: CompositorToken, + window_map: Rc>, + drawer: GliumDrawer>, + logger: ::slog::Logger, +} + +impl DrmHandler for DrmHandlerImpl { + fn ready( + &mut self, + _device: &mut DrmDevice, + _crtc: crtc::Handle, + _frame: u32, + _duration: Duration, + ) { + let mut frame = self.drawer.draw(); + frame.clear_color(0.8, 0.8, 0.9, 1.0); + // redraw the frame, in a simple but inneficient way + { + let screen_dimensions = self.drawer.borrow().get_framebuffer_dimensions(); + self.window_map + .borrow() + .with_windows_from_bottom_to_top(|toplevel_surface, initial_place| { + if let Some(wl_surface) = toplevel_surface.get_surface() { + // this surface is a root of a subsurface tree that needs to be drawn + self.compositor_token + .with_surface_tree_upward( + wl_surface, + initial_place, + |_surface, attributes, role, &(mut x, mut y)| { + // there is actually something to draw ! + if attributes.user_data.texture.is_none() { + let mut remove = false; + match attributes.user_data.buffer { + Some(Buffer::Egl { ref images }) => { + match images.format { + Format::RGB | Format::RGBA => { + attributes.user_data.texture = + self.drawer.texture_from_egl(&images); + } + _ => { + // we don't handle the more complex formats here. + attributes.user_data.texture = None; + remove = true; + } + }; + } + Some(Buffer::Shm { ref data, ref size }) => { + attributes.user_data.texture = + Some(self.drawer.texture_from_mem(data, *size)); + } + _ => {} + } + if remove { + attributes.user_data.buffer = None; + } + } + + if let Some(ref texture) = attributes.user_data.texture { + if let Ok(subdata) = Role::::data(role) { + x += subdata.location.0; + y += subdata.location.1; + } + info!(self.logger, "Render window"); + self.drawer.render_texture( + &mut frame, + texture, + match *attributes.user_data.buffer.as_ref().unwrap() { + Buffer::Egl { ref images } => images.y_inverted, + Buffer::Shm { .. } => false, + }, + match *attributes.user_data.buffer.as_ref().unwrap() { + Buffer::Egl { ref images } => (images.width, images.height), + Buffer::Shm { ref size, .. } => *size, + }, + (x, y), + screen_dimensions, + Blend::alpha_blending(), + ); + TraversalAction::DoChildren((x, y)) + } else { + // we are not display, so our children are neither + TraversalAction::SkipChildren + } + }, + ) + .unwrap(); + } + }); + } + frame.finish().unwrap(); + } + + fn error(&mut self, _device: &mut DrmDevice, error: DrmError) { + panic!("{:?}", error); + } +}