diff --git a/.gitignore b/.gitignore index e63a49c..0bf1ddb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ target Cargo.lock *.bk .vscode +.vagga diff --git a/.travis.yml b/.travis.yml index d8e9b98..9ceec5a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,46 +1,41 @@ language: rust -cache: cargo - -sudo: required - +# We need this for the matrix, install is quick although unused rust: - stable - beta - nightly -os: - - linux +sudo: required dist: trusty +# We cannot cache .vagga, because we actually do not have read permissions +# without sudo and travis cache script runs not as sudo... +cache: + directories: + - .vagga/stable-home + - .vagga/beta-home + - .vagga/nightly-home + - .vagga/.cache + addons: apt: packages: - - libssl-dev - - libudev-dev - - libgbm-dev - - libxkbcommon-dev - - libegl1-mesa-dev - -before_install: - - sudo add-apt-repository -y ppa:wayland.admin/daily-builds - - sudo apt-get update -qq || echo "Ignoring failed apt-get update..." - - sudo apt-get install -y libwayland-dev libinput-dev - -branches: - only: - - master + - libcurl4-openssl-dev + - libelf-dev + - libdw-dev env: - global: - - RUST_BACKTRACE=1 matrix: - FEATURES="" # test individual features - FEATURES="backend_winit" - FEATURES="backend_drm" - FEATURES="backend_libinput" + - FEATURES="backend_udev" + - FEATURES="backend_session" + - FEATURES="backend_session_udev" - FEATURES="renderer_glium" # test default features - FEATURES="default" @@ -58,38 +53,36 @@ matrix: - rust: nightly before_script: - - export PATH=$HOME/.local/bin:$HOME/.cargo/bin:$PATH + - 'echo ubuntu-mirror: http://mirrors.us.kernel.org/ubuntu/ > ~/.vagga.yaml' + - 'echo alpine-mirror: http://mirrors.gigenet.com/alpinelinux/ >> ~/.vagga.yaml' - | - if [ "$FEATURES" = "cargo-fmt" ]; then - cargo install rustfmt-nightly --force; - fi - - | - if [ "$FEATURES" = "cargo-clippy" ]; then - cargo install clippy --force; - fi - - mkdir $(pwd)/socket - - export XDG_RUNTIME_DIR="$(pwd)/socket" + echo "$(id -un):100000:65536" | sudo tee /etc/subuid | sudo tee /etc/subgid + sudo apt-get install uidmap -y + curl http://files.zerogw.com/vagga/vagga-install.sh | sh + - vagga update-$TRAVIS_RUST_VERSION script: - | case $FEATURES in "all") - cargo test --all-features && - cargo doc --no-deps --all-features + vagga cargo-$TRAVIS_RUST_VERSION test --all-features && + vagga cargo-$TRAVIS_RUST_VERSION doc --no-deps --all-features ;; "default") - cargo test && - cargo doc --no-deps + vagga cargo-$TRAVIS_RUST_VERSION test && + vagga cargo-$TRAVIS_RUST_VERSION doc --no-deps ;; "cargo-fmt") - cargo fmt -- --write-mode=diff + vagga cargo-$TRAVIS_RUST_VERSION install -f rustfmt-nightly && + vagga cargo-$TRAVIS_RUST_VERSION fmt -- --write-mode=diff ;; "cargo-clippy") - cargo clippy --all-features -- -D warnings + vagga cargo-$TRAVIS_RUST_VERSION install -f clippy && + vagga cargo-$TRAVIS_RUST_VERSION clippy --all-features -- -D warnings ;; *) - cargo test --lib --doc --tests --no-default-features --features "$FEATURES" && - cargo doc --no-deps --no-default-features --features "$FEATURES" + vagga cargo-$TRAVIS_RUST_VERSION test --lib --doc --tests --no-default-features --features "$FEATURES" && + vagga cargo-$TRAVIS_RUST_VERSION doc --no-deps --no-default-features --features "$FEATURES" esac after_success: @@ -106,9 +99,9 @@ deploy: condition: "$FEATURES = all" notifications: - webhooks: - urls: - - "https://scalar.vector.im/api/neb/services/hooks/dHJhdmlzLWNpLyU0MGxldmFucyUzQXNhZmFyYWRlZy5uZXQvJTIxRkt4aGprSUNwakJWelZlQ2RGJTNBc2FmYXJhZGVnLm5ldA" - on_success: change - on_failure: always - on_start: never + webhooks: + urls: + - "https://scalar.vector.im/api/neb/services/hooks/dHJhdmlzLWNpLyU0MGxldmFucyUzQXNhZmFyYWRlZy5uZXQvJTIxRkt4aGprSUNwakJWelZlQ2RGJTNBc2FmYXJhZGVnLm5ldA" + on_success: change + on_failure: always + on_start: never diff --git a/Cargo.toml b/Cargo.toml index 51027dd..b50c13d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,13 +16,14 @@ slog-stdlog = "2.0.0-0.2" libloading = "0.4.0" wayland-client = { version = "0.9.9", optional = true } winit = { version = "0.8.2", optional = true } -drm = { version = "^0.3.0", optional = true } -gbm = { version = "^0.2.2", optional = true } +drm = { version = "^0.3.1", optional = true } +gbm = { version = "^0.3.0", optional = true, default-features = false, features = ["drm-support"] } glium = { version = "0.17.1", optional = true, default-features = false } -input = { version = "0.2.0", optional = true } +input = { version = "0.4.0", optional = true } +udev = { version = "0.2.0", optional = true } rental = "0.4.11" wayland-protocols = { version = "0.12.0", features = ["unstable_protocols", "server"] } -image = "0.16.0" +image = "0.17.0" error-chain = "0.11.0" [build-dependencies] @@ -32,10 +33,14 @@ gl_generator = "0.5" slog-term = "2.0" slog-async = "2.0" rand = "0.3" +ctrlc = { version = "3.0", features = ["termination"] } [features] -default = ["backend_winit", "backend_drm", "backend_libinput", "renderer_glium"] +default = ["backend_winit", "backend_drm", "backend_libinput", "backend_udev", "renderer_glium"] backend_winit = ["winit", "wayland-server/dlopen", "wayland-client/dlopen"] backend_drm = ["drm", "gbm"] backend_libinput = ["input"] +backend_session = [] +backend_session_udev = ["udev", "backend_session"] +backend_udev = ["udev", "backend_drm", "backend_session_udev"] renderer_glium = ["glium"] diff --git a/examples/drm.rs b/examples/drm.rs index 5f88726..197dcc7 100644 --- a/examples/drm.rs +++ b/examples/drm.rs @@ -15,6 +15,7 @@ mod helpers; use drm::control::{Device as ControlDevice, ResourceInfo}; use drm::control::connector::{Info as ConnectorInfo, State as ConnectorState}; +use drm::control::crtc; use drm::control::encoder::Info as EncoderInfo; use drm::result::Error as DrmError; use glium::Surface; @@ -30,7 +31,7 @@ use std::cell::RefCell; use std::fs::OpenOptions; use std::rc::Rc; use std::time::Duration; -use wayland_server::{EventLoopHandle, StateToken}; +use wayland_server::{StateToken, StateProxy}; fn main() { // A logger facility, here we use the terminal for this example @@ -80,10 +81,19 @@ fn main() { // 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_token = device - .create_backend(&mut event_loop, crtc, mode, vec![connector_info.handle()]) - .unwrap(); + { + // Initialize the hardware backend + let renderer = device + .create_backend(event_loop.state(), crtc, mode, vec![connector_info.handle()]) + .unwrap(); + + /* + * Initialize glium + */ + let mut frame = event_loop.state().get(renderer).draw(); + frame.clear_color(0.8, 0.8, 0.9, 1.0); + frame.finish().unwrap(); + } /* * Initialize the globals @@ -93,16 +103,6 @@ fn main() { let (compositor_token, shell_state_token, window_map) = init_shell(&mut event_loop, log.clone()); - /* - * Initialize glium - */ - { - let drawer = event_loop.state().get(&renderer_token); - let mut frame = drawer.draw(); - frame.clear_color(0.8, 0.8, 0.9, 1.0); - frame.finish().unwrap(); - } - /* * Add a listening socket: */ @@ -112,9 +112,10 @@ fn main() { /* * Register the DrmDevice on the EventLoop */ + let device_token = event_loop.state().insert(device); let _source = drm_device_bind( &mut event_loop, - device, + device_token, DrmHandlerImpl { shell_state_token, compositor_token, @@ -139,9 +140,9 @@ pub struct DrmHandlerImpl { } impl DrmHandler> for DrmHandlerImpl { - fn ready(&mut self, evlh: &mut EventLoopHandle, _device: &mut DrmDevice>, - backend: &StateToken>, _frame: u32, _duration: Duration) { - let state = evlh.state(); + fn ready<'a, S: Into>>(&mut self, state: S, _device: &mut DrmDevice>, + backend: &StateToken>, _crtc: crtc::Handle, _frame: u32, _duration: Duration) { + let state = state.into(); let drawer = state.get(backend); let mut frame = drawer.draw(); frame.clear_color(0.8, 0.8, 0.9, 1.0); @@ -185,7 +186,7 @@ impl DrmHandler> for DrmHandlerImpl { frame.finish().unwrap(); } - fn error(&mut self, _evlh: &mut EventLoopHandle, _device: &mut DrmDevice>, + fn error<'a, S: Into>>(&mut self, _state: S, _device: &mut DrmDevice>, error: DrmError) { panic!("{:?}", error); } diff --git a/examples/resources/cursor.rgba b/examples/resources/cursor.rgba new file mode 100644 index 0000000..1bd3509 Binary files /dev/null and b/examples/resources/cursor.rgba differ diff --git a/examples/resources/cursor2.rgba b/examples/resources/cursor2.rgba new file mode 100644 index 0000000..729c1cc Binary files /dev/null and b/examples/resources/cursor2.rgba differ diff --git a/examples/udev.rs b/examples/udev.rs new file mode 100644 index 0000000..830d178 --- /dev/null +++ b/examples/udev.rs @@ -0,0 +1,477 @@ +extern crate drm; +#[macro_use] +extern crate glium; +extern crate rand; +extern crate input as libinput; +extern crate image; +extern crate udev; +#[macro_use(define_roles)] +extern crate smithay; +extern crate xkbcommon; +extern crate wayland_server; + +#[macro_use] +extern crate slog; +extern crate slog_async; +extern crate slog_term; + +extern crate ctrlc; + +mod helpers; + +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 drm::control::crtc; +use drm::result::Error as DrmError; +use glium::Surface; +use image::{ImageBuffer, Rgba}; +use libinput::{Libinput, Device as LibinputDevice, event}; +use libinput::event::keyboard::KeyboardEventTrait; +use helpers::{init_shell, GliumDrawer, MyWindowMap, Roles, SurfaceData}; +use slog::{Drain, Logger}; +use smithay::backend::drm::{DrmBackend, DrmDevice, DrmHandler}; +use smithay::backend::graphics::GraphicsBackend; +use smithay::backend::graphics::egl::EGLGraphicsBackend; +use smithay::backend::input::{self, Event, InputBackend, InputHandler, KeyboardKeyEvent, PointerButtonEvent, + PointerAxisEvent, KeyState}; +use smithay::backend::libinput::{LibinputInputBackend, libinput_bind, PointerAxisEvent as LibinputPointerAxisEvent, LibinputSessionInterface}; +use smithay::backend::udev::{UdevBackend, UdevHandler, udev_backend_bind}; +use smithay::backend::session::{Session, SessionNotifier}; +use smithay::backend::session::direct::{direct_session_bind, DirectSession}; +use smithay::wayland::compositor::{CompositorToken, SubsurfaceRole, TraversalAction}; +use smithay::wayland::compositor::roles::Role; +use smithay::wayland::output::{Mode, Output, PhysicalProperties}; +use smithay::wayland::seat::{KeyboardHandle, PointerHandle, Seat}; +use smithay::wayland::shell::ShellState; +use smithay::wayland::shm::init_shm_global; +use std::cell::RefCell; +use std::collections::HashSet; +use std::io::Error as IoError; +use std::rc::Rc; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::time::Duration; +use std::process::Command; +use xkbcommon::xkb::keysyms as xkb; +use wayland_server::{StateToken, StateProxy}; +use wayland_server::protocol::{wl_output, wl_pointer}; + +struct LibinputInputHandler { + log: Logger, + pointer: PointerHandle, + keyboard: KeyboardHandle, + window_map: Rc>, + pointer_location: Rc>, + screen_size: (u32, u32), + serial: u32, + running: Arc, +} + +impl LibinputInputHandler { + fn next_serial(&mut self) -> u32 { + self.serial += 1; + self.serial + } +} + +impl InputHandler for LibinputInputHandler { + fn on_seat_created(&mut self, _: &input::Seat) { + /* we just create a single static one */ + } + fn on_seat_destroyed(&mut self, _: &input::Seat) { + /* we just create a single static one */ + } + fn on_seat_changed(&mut self, _: &input::Seat) { + /* we just create a single static one */ + } + fn on_keyboard_key(&mut self, _: &input::Seat, evt: event::keyboard::KeyboardKeyEvent) { + let keycode = evt.key(); + let state = evt.state(); + debug!(self.log, "key"; "keycode" => keycode, "state" => format!("{:?}", state)); + + let serial = self.next_serial(); + self.keyboard.input(keycode, state, serial, |modifiers, keysym| { + if modifiers.ctrl && modifiers.alt && keysym == xkb::KEY_BackSpace { + self.running.store(false, Ordering::SeqCst); + false + } else if modifiers.logo && keysym == xkb::KEY_Return && state == KeyState::Pressed { + let _ = Command::new("weston-terminal").spawn(); + false + } else { + true + } + }); + } + fn on_pointer_move(&mut self, _: &input::Seat, evt: event::pointer::PointerMotionEvent) { + let (x, y) = (evt.dx(), evt.dy()); + let serial = self.next_serial(); + let mut location = self.pointer_location.borrow_mut(); + location.0 += x; + location.1 += y; + let under = self.window_map.borrow().get_surface_under((location.0, location.1)); + self.pointer.motion( + under.as_ref().map(|&(ref s, (x, y))| (s, x, y)), + serial, + evt.time(), + ); + } + fn on_pointer_move_absolute(&mut self, _: &input::Seat, evt: event::pointer::PointerMotionAbsoluteEvent) { + let (x, y) = (evt.absolute_x_transformed(self.screen_size.0), evt.absolute_y_transformed(self.screen_size.1)); + *self.pointer_location.borrow_mut() = (x, y); + let serial = self.next_serial(); + let under = self.window_map.borrow().get_surface_under((x, y)); + self.pointer.motion( + under.as_ref().map(|&(ref s, (x, y))| (s, x, y)), + serial, + evt.time(), + ); + } + fn on_pointer_button(&mut self, _: &input::Seat, evt: event::pointer::PointerButtonEvent) { + let serial = self.next_serial(); + let button = evt.button(); + let state = match evt.state() { + input::MouseButtonState::Pressed => { + // change the keyboard focus + let under = self.window_map + .borrow_mut() + .get_surface_and_bring_to_top(*self.pointer_location.borrow()); + self.keyboard + .set_focus(under.as_ref().map(|&(ref s, _)| s), serial); + wl_pointer::ButtonState::Pressed + } + input::MouseButtonState::Released => wl_pointer::ButtonState::Released, + }; + self.pointer.button(button, state, serial, evt.time()); + } + fn on_pointer_axis(&mut self, _: &input::Seat, evt: LibinputPointerAxisEvent) { + let axis = match evt.axis() { + input::Axis::Vertical => wayland_server::protocol::wl_pointer::Axis::VerticalScroll, + input::Axis::Horizontal => wayland_server::protocol::wl_pointer::Axis::HorizontalScroll, + }; + self.pointer.axis(axis, evt.amount(), evt.time()); + } + fn on_touch_down(&mut self, _: &input::Seat, _: event::touch::TouchDownEvent) { + /* not done in this example */ + } + fn on_touch_motion(&mut self, _: &input::Seat, _: event::touch::TouchMotionEvent) { + /* not done in this example */ + } + fn on_touch_up(&mut self, _: &input::Seat, _: event::touch::TouchUpEvent) { + /* not done in this example */ + } + fn on_touch_cancel(&mut self, _: &input::Seat, _: event::touch::TouchCancelEvent) { + /* not done in this example */ + } + fn on_touch_frame(&mut self, _: &input::Seat, _: event::touch::TouchFrameEvent) { + /* not done in this example */ + } + fn on_input_config_changed(&mut self, _: &mut [LibinputDevice]) { + /* not done in this example */ + } +} + +fn main() { + // A logger facility, here we use the terminal for this example + let log = Logger::root( + slog_term::FullFormat::new(slog_term::PlainSyncDecorator::new(std::io::stdout())).build().fuse(), + o!(), + ); + + // Initialize the wayland server + let (mut display, mut event_loop) = wayland_server::create_display(); + + /* + * Initialize the compositor + */ + init_shm_global(&mut event_loop, vec![], log.clone()); + + let (compositor_token, shell_state_token, window_map) = init_shell(&mut event_loop, log.clone()); + + /* + * Initialize session on the current tty + */ + let (session, mut notifier) = DirectSession::new(None, log.clone()).unwrap(); + let session = Rc::new(RefCell::new(session)); + + let running = Arc::new(AtomicBool::new(true)); + let r = running.clone(); + ctrlc::set_handler(move || { + r.store(false, Ordering::SeqCst); + }).expect("Error setting Ctrl-C handler"); + + let pointer_location = Rc::new(RefCell::new((0.0, 0.0))); + + /* + * Initialize the udev backend + */ + let context = udev::Context::new().unwrap(); + let bytes = include_bytes!("resources/cursor2.rgba"); + let udev_token + = UdevBackend::new(&mut event_loop, &context, session.clone(), UdevHandlerImpl { + shell_state_token, + compositor_token, + window_map: window_map.clone(), + pointer_location: pointer_location.clone(), + pointer_image: ImageBuffer::from_raw(64, 64, bytes.to_vec()).unwrap(), + logger: log.clone(), + }, log.clone()).unwrap(); + + let udev_session_id = notifier.register(udev_token.clone()); + + let (seat_token, _) = Seat::new(&mut event_loop, session.seat().into(), log.clone()); + + let pointer = event_loop.state().get_mut(&seat_token).add_pointer(); + let keyboard = event_loop + .state() + .get_mut(&seat_token) + .add_keyboard("", "", "", None, 1000, 500) + .expect("Failed to initialize the keyboard"); + + let (output_token, _output_global) = Output::new( + &mut event_loop, + "Drm".into(), + PhysicalProperties { + width: 0, + height: 0, + subpixel: wl_output::Subpixel::Unknown, + maker: "Smithay".into(), + model: "Generic DRM".into(), + }, + log.clone(), + ); + + let (w, h) = (1920, 1080); // Hardcode full-hd res + event_loop + .state() + .get_mut(&output_token) + .change_current_state( + Some(Mode { + width: w as i32, + height: h as i32, + refresh: 60_000, + }), + None, + None, + ); + event_loop + .state() + .get_mut(&output_token) + .set_preferred(Mode { + width: w as i32, + height: h as i32, + refresh: 60_000, + }); + + /* + * Initialize libinput backend + */ + let seat = session.seat(); + let mut libinput_context = Libinput::new_from_udev::>>>(session.into(), &context); + let libinput_session_id = notifier.register(libinput_context.clone()); + libinput_context.udev_assign_seat(&seat).unwrap(); + let mut libinput_backend = LibinputInputBackend::new(libinput_context, log.clone()); + libinput_backend.set_handler(LibinputInputHandler { + log: log.clone(), + pointer, + keyboard, + window_map: window_map.clone(), + pointer_location, + screen_size: (w, h), + serial: 0, + running: running.clone(), + }); + let libinput_event_source = libinput_bind(libinput_backend, &mut event_loop).unwrap(); + + let session_event_source = direct_session_bind(notifier, &mut event_loop, log.clone()).unwrap(); + let udev_event_source = udev_backend_bind(&mut event_loop, udev_token).unwrap(); + + /* + * Add a listening socket + */ + let name = display.add_socket_auto().unwrap().into_string().unwrap(); + println!("Listening on socket: {}", name); + + while running.load(Ordering::SeqCst) { + event_loop.dispatch(Some(16)); + display.flush_clients(); + window_map.borrow_mut().refresh(); + } + + println!("Bye Bye"); + + let mut notifier = session_event_source.remove(); + notifier.unregister(udev_session_id); + notifier.unregister(libinput_session_id); + + libinput_event_source.remove(); + + let udev_token = udev_event_source.remove(); + let udev = event_loop.state().remove(udev_token); + udev.close(event_loop.state()); +} + +struct UdevHandlerImpl { + shell_state_token: StateToken>, + compositor_token: CompositorToken, + window_map: Rc>, + pointer_location: Rc>, + pointer_image: ImageBuffer, Vec>, + logger: ::slog::Logger, +} + +impl UdevHandlerImpl { + pub fn scan_connectors<'a, S: Into>>(&self, state: S, device: &mut DrmDevice>) { + // 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 = res_handles + .connectors() + .iter() + .map(|conn| { + ConnectorInfo::load_from_device(device, *conn).unwrap() + }) + .filter(|conn| conn.connection_state() == ConnectorState::Connected) + .inspect(|conn| info!(self.logger, "Connected: {:?}", conn.connector_type())) + .collect(); + + let mut used_crtcs: HashSet = HashSet::new(); + + let mut state = state.into(); + + // 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| EncoderInfo::load_from_device(device, *encoder_handle)).collect::>(); + for encoder_info in encoder_infos { + for crtc in res_handles.filter_crtcs(encoder_info.possible_crtcs()) { + if !used_crtcs.contains(&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_token = device.create_backend(&mut state, crtc, mode, vec![connector_info.handle()]).unwrap(); + + // create cursor + { + let renderer = state.get_mut(renderer_token); + renderer.set_cursor_representation(&self.pointer_image, (2, 2)).unwrap(); + } + + // render first frame + { + let renderer = state.get_mut(renderer_token); + let mut frame = renderer.draw(); + frame.clear_color(0.8, 0.8, 0.9, 1.0); + frame.finish().unwrap(); + } + + used_crtcs.insert(crtc); + break; + } + } + } + } + } +} + +impl UdevHandler, DrmHandlerImpl> for UdevHandlerImpl { + fn device_added<'a, S: Into>>(&mut self, state: S, device: &mut DrmDevice>) -> Option + { + self.scan_connectors(state, device); + + Some(DrmHandlerImpl { + shell_state_token: self.shell_state_token.clone(), + compositor_token: self.compositor_token.clone(), + window_map: self.window_map.clone(), + pointer_location: self.pointer_location.clone(), + logger: self.logger.clone(), + }) + } + + fn device_changed<'a, S: Into>>(&mut self, state: S, device: &StateToken>>) { + //quick and dirt, just re-init the device + let mut state = state.into(); + self.device_removed(&mut state, device); + state.with_value(device, |state, device| self.scan_connectors(state, device)); + } + + fn device_removed<'a, S: Into>>(&mut self, state: S, device: &StateToken>>) { + state.into().with_value(device, |state, device| { + let crtcs = device.current_backends().into_iter().map(|backend| state.get(backend).crtc()).collect::>(); + let mut state: StateProxy = state.into(); + for crtc in crtcs { + device.destroy_backend(&mut state, &crtc); + } + }); + } + + fn error<'a, S: Into>>(&mut self, _state: S, error: IoError) { + error!(self.logger, "{:?}", error); + } +} + +pub struct DrmHandlerImpl { + shell_state_token: StateToken>, + compositor_token: CompositorToken, + window_map: Rc>, + pointer_location: Rc>, + logger: ::slog::Logger, +} + +impl DrmHandler> for DrmHandlerImpl { + fn ready<'a, S: Into>>(&mut self, state: S, _device: &mut DrmDevice>, + backend: &StateToken>, _crtc: crtc::Handle, _frame: u32, _duration: Duration) { + let state = state.into(); + let drawer = state.get(backend); + { + let (x, y) = *self.pointer_location.borrow(); + let _ = (**drawer).set_cursor_position(x.trunc().abs() as u32, y.trunc().abs() as u32); + } + let mut frame = 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 = drawer.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)| { + if let Some((ref contents, (w, h))) = attributes.user_data.buffer { + // there is actually something to draw ! + if let Ok(subdata) = Role::::data(role) { + x += subdata.x; + y += subdata.y; + } + drawer.render( + &mut frame, + contents, + (w, h), + (x, y), + screen_dimensions, + ); + TraversalAction::DoChildren((x, y)) + } else { + // we are not display, so our children are neither + TraversalAction::SkipChildren + } + }, + ) + .unwrap(); + } + }); + } + if let Err(err) = frame.finish() { + error!(self.logger, "Error during rendering: {:?}", err); + } + } + + fn error<'a, S: Into>>(&mut self, _state: S, _device: &mut DrmDevice>, + error: DrmError) { + error!(self.logger, "{:?}", error); + } +} diff --git a/src/backend/drm/backend.rs b/src/backend/drm/backend.rs index d2ccd95..768d888 100644 --- a/src/backend/drm/backend.rs +++ b/src/backend/drm/backend.rs @@ -60,7 +60,6 @@ rental! { } use self::graphics::{Graphics, Surface}; - /// Backend based on a `DrmDevice` and a given crtc pub struct DrmBackend { graphics: Graphics, @@ -71,9 +70,10 @@ pub struct DrmBackend { } impl DrmBackend { - pub(crate) fn new(context: Rc, crtc: crtc::Handle, mode: Mode, - connectors: Vec, logger: ::slog::Logger) - -> Result { + pub(crate) fn new( + context: Rc, crtc: crtc::Handle, mode: Mode, connectors: Vec, + logger: ::slog::Logger, + ) -> Result { // logger already initialized by the DrmDevice let log = ::slog_or_stdlog(logger); info!(log, "Initializing DrmBackend"); @@ -92,7 +92,7 @@ impl DrmBackend { 1, 1, GbmFormat::ARGB8888, - &[BufferObjectFlags::Cursor, BufferObjectFlags::Write], + BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE, ) .chain_err(|| ErrorKind::GbmInitFailed)?) }, @@ -107,7 +107,7 @@ impl DrmBackend { w as u32, h as u32, GbmFormat::XRGB8888, - &[BufferObjectFlags::Scanout, BufferObjectFlags::Rendering], + BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING, ) .chain_err(|| ErrorKind::GbmInitFailed)?) }, @@ -145,9 +145,7 @@ impl DrmBackend { &connectors, (0, 0), Some(mode), - ).chain_err( - || ErrorKind::DrmDev(format!("{:?}", context.devices.drm)), - )?; + ).chain_err(|| ErrorKind::DrmDev(format!("{:?}", context.devices.drm)))?; front_bo.set_userdata(fb); Ok(EGL { @@ -176,11 +174,9 @@ impl DrmBackend { let next_bo = egl.buffers.next_buffer.replace(None); if let Some(next_buffer) = next_bo { - trace!(self.logger, "Releasing all front buffer"); + trace!(self.logger, "Releasing old front buffer"); egl.buffers.front_buffer.set(next_buffer); - // drop and release the old buffer - } else { - unreachable!(); + // drop and release the old buffer } }) }); @@ -193,9 +189,7 @@ impl DrmBackend { /// 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.graphics.head().head().head(), connector) - .chain_err(|| { - ErrorKind::DrmDev(format!("{:?}", self.graphics.head().head().head())) - })?; + .chain_err(|| ErrorKind::DrmDev(format!("{:?}", self.graphics.head().head().head())))?; // check if the connector can handle the current mode if info.modes().contains(&self.mode) { @@ -203,9 +197,8 @@ impl DrmBackend { let encoders = info.encoders() .iter() .map(|encoder| { - encoder::Info::load_from_device(self.graphics.head().head().head(), *encoder).chain_err( - || ErrorKind::DrmDev(format!("{:?}", self.graphics.head().head().head())), - ) + encoder::Info::load_from_device(self.graphics.head().head().head(), *encoder) + .chain_err(|| ErrorKind::DrmDev(format!("{:?}", self.graphics.head().head().head()))) }) .collect::>>()?; @@ -215,9 +208,7 @@ impl DrmBackend { .head() .head() .resource_handles() - .chain_err(|| { - ErrorKind::DrmDev(format!("{:?}", self.graphics.head().head().head())) - })?; + .chain_err(|| ErrorKind::DrmDev(format!("{:?}", self.graphics.head().head().head())))?; if !encoders .iter() .map(|encoder| encoder.possible_crtcs()) @@ -272,9 +263,7 @@ impl DrmBackend { // check the connectors for connector in &self.connectors { if !connector::Info::load_from_device(self.graphics.head().head().head(), *connector) - .chain_err(|| { - ErrorKind::DrmDev(format!("{:?}", self.graphics.head().head().head())) - })? + .chain_err(|| ErrorKind::DrmDev(format!("{:?}", self.graphics.head().head().head())))? .modes() .contains(&mode) { @@ -295,9 +284,7 @@ impl DrmBackend { // resolution. debug!( logger_ref, - "Reinitializing surface for new mode: {}:{}", - w, - h + "Reinitializing surface for new mode: {}:{}", w, h ); graphics.gbm.surface = Surface::try_new( { @@ -311,7 +298,7 @@ impl DrmBackend { w as u32, h as u32, GbmFormat::XRGB8888, - &[BufferObjectFlags::Scanout, BufferObjectFlags::Rendering], + BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING, ) .chain_err(|| ErrorKind::GbmInitFailed)?) }, @@ -340,9 +327,8 @@ impl DrmBackend { front_bo.format() ); // we need a framebuffer per front_buffer - let fb = framebuffer::create(graphics.context.devices.drm, &*front_bo).chain_err(|| { - ErrorKind::DrmDev(format!("{:?}", graphics.context.devices.drm)) - })?; + let fb = framebuffer::create(graphics.context.devices.drm, &*front_bo) + .chain_err(|| ErrorKind::DrmDev(format!("{:?}", graphics.context.devices.drm)))?; debug!(logger_ref, "Initialize screen"); crtc::set( @@ -352,9 +338,7 @@ impl DrmBackend { connectors_ref, (0, 0), Some(mode), - ).chain_err(|| { - ErrorKind::DrmDev(format!("{:?}", graphics.context.devices.drm)) - })?; + ).chain_err(|| ErrorKind::DrmDev(format!("{:?}", graphics.context.devices.drm)))?; front_bo.set_userdata(fb); Ok(EGL { @@ -374,12 +358,18 @@ impl DrmBackend { self.mode = mode; Ok(()) } + + /// Returns the crtc id used by this backend + pub fn crtc(&self) -> crtc::Handle { + self.crtc + } } impl Drop for DrmBackend { 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) + let crtc = self.crtc; self.graphics.rent_all_mut(|graphics| { if let Some(fb) = graphics.gbm.surface.rent(|egl| { if let Some(mut next) = egl.buffers.next_buffer.take() { @@ -401,6 +391,9 @@ impl Drop for DrmBackend { // ignore failure at this point let _ = framebuffer::destroy(&*graphics.context.devices.drm, fb.handle()); } + + // ignore failure at this point + let _ = crtc::clear_cursor(&*graphics.context.devices.drm, crtc); }) } } @@ -415,13 +408,12 @@ impl GraphicsBackend for DrmBackend { self.graphics.head().head().head(), self.crtc, (x as i32, y as i32), - ).chain_err(|| { - ErrorKind::DrmDev(format!("{:?}", self.graphics.head().head().head())) - }) + ).chain_err(|| ErrorKind::DrmDev(format!("{:?}", self.graphics.head().head().head()))) } - fn set_cursor_representation(&self, buffer: ImageBuffer, Vec>, hotspot: (u32, u32)) - -> Result<()> { + fn set_cursor_representation( + &self, buffer: &ImageBuffer, Vec>, hotspot: (u32, u32) + ) -> Result<()> { let (w, h) = buffer.dimensions(); debug!(self.logger, "Importing cursor"); @@ -437,11 +429,11 @@ impl GraphicsBackend for DrmBackend { w, h, GbmFormat::ARGB8888, - &[BufferObjectFlags::Cursor, BufferObjectFlags::Write], + BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE, ) .chain_err(|| ErrorKind::GbmInitFailed)?; cursor - .write(&*buffer.into_raw()) + .write(&**buffer) .chain_err(|| ErrorKind::GbmInitFailed)?; trace!(self.logger, "Set the new imported cursor"); @@ -454,9 +446,9 @@ impl GraphicsBackend for DrmBackend { (hotspot.0 as i32, hotspot.1 as i32), ).is_err() { - crtc::set_cursor(self.graphics.head().head().head(), self.crtc, &cursor).chain_err(|| { - ErrorKind::DrmDev(format!("{:?}", self.graphics.head().head().head())) - })?; + crtc::set_cursor(self.graphics.head().head().head(), self.crtc, &cursor).chain_err( + || ErrorKind::DrmDev(format!("{:?}", self.graphics.head().head().head())), + )?; } // and store it @@ -489,7 +481,10 @@ impl EGLGraphicsBackend for DrmBackend { // 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 = surface.gbm.lock_front_buffer().expect("Surface only has one front buffer. Not supported by smithay"); + let mut next_bo = surface + .gbm + .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) @@ -497,7 +492,8 @@ impl EGLGraphicsBackend for DrmBackend { let fb = if let Some(info) = maybe_fb { info } else { - let fb = framebuffer::create(graphics.context.devices.drm, &*next_bo).map_err(|_| SwapBuffersError::ContextLost)?; + let fb = framebuffer::create(graphics.context.devices.drm, &*next_bo) + .map_err(|_| SwapBuffersError::ContextLost)?; next_bo.set_userdata(fb); fb }; @@ -506,7 +502,12 @@ impl EGLGraphicsBackend for DrmBackend { trace!(self.logger, "Queueing Page flip"); // and flip - crtc::page_flip(graphics.context.devices.drm, self.crtc, fb.handle(), &[crtc::PageFlipFlags::PageFlipEvent]).map_err(|_| SwapBuffersError::ContextLost) + crtc::page_flip( + graphics.context.devices.drm, + self.crtc, + fb.handle(), + &[crtc::PageFlipFlags::PageFlipEvent], + ).map_err(|_| SwapBuffersError::ContextLost) }) }) } @@ -523,7 +524,9 @@ impl EGLGraphicsBackend for DrmBackend { } fn is_current(&self) -> bool { - self.graphics.head().rent(|context| context.is_current()) + self.graphics.rent_all(|graphics| { + graphics.context.egl.is_current() && graphics.gbm.surface.rent(|egl| egl.surface.is_current()) + }) } unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> { diff --git a/src/backend/drm/error.rs b/src/backend/drm/error.rs index 00cd73c..35517ee 100644 --- a/src/backend/drm/error.rs +++ b/src/backend/drm/error.rs @@ -8,6 +8,11 @@ use rental::TryNewError; error_chain! { errors { + #[doc = "Unable to acquire drm master"] + DrmMasterFailed { + description("Failed to acquire drm master") + } + #[doc = "The `DrmDevice` encountered an access error"] DrmDev(dev: String) { description("The drm device encountered an access error"), @@ -26,7 +31,13 @@ error_chain! { display("Swapping front buffers failed"), } - #[doc = "mode is not compatible with all given connectors"] + #[doc = "Device is currently paused"] + DeviceInactive { + description("Device is currently paused, operation rejected"), + display("Device is currently paused, operation rejected"), + } + + #[doc = "Mode is not compatible with all given connectors"] ModeNotSuitable(mode: Mode) { description("Mode is not compatible with all given connectors"), display("Mode ({:?}) is not compatible with all given connectors", mode), diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index 9278b7f..bc321b6 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -90,11 +90,11 @@ //! //! // Create the backend //! let backend: StateToken = device.create_backend( -//! &mut event_loop, +//! event_loop.state(), //! crtc, //! mode, //! vec![connector_info.handle()] -//! ).unwrap(); +//! ).unwrap().clone(); //! # } //! ``` //! @@ -118,12 +118,13 @@ //! # //! # 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::OpenOptions; //! # use std::time::Duration; //! use smithay::backend::drm::{DrmDevice, DrmBackend, DrmHandler, drm_device_bind}; //! use smithay::backend::graphics::egl::EGLGraphicsBackend; -//! use wayland_server::{EventLoopHandle, StateToken}; +//! use wayland_server::{StateToken, StateProxy}; //! # //! # fn main() { //! # @@ -145,29 +146,32 @@ //! # let crtc = res_handles.crtcs()[0]; //! # let mode = connector_info.modes()[0]; //! # let backend: StateToken = device.create_backend( -//! # &mut event_loop, +//! # event_loop.state(), //! # crtc, //! # mode, //! # vec![connector_info.handle()] -//! # ).unwrap(); +//! # ).unwrap().clone(); //! //! struct MyDrmHandler; //! //! impl DrmHandler for MyDrmHandler { -//! fn ready(&mut self, -//! evlh: &mut EventLoopHandle, -//! _device: &mut DrmDevice, -//! backend: &StateToken, -//! _frame: u32, -//! _duration: Duration) +//! fn ready<'a, S: Into>>( +//! &mut self, +//! state: S, +//! _device: &mut DrmDevice, +//! backend: &StateToken, +//! _crtc: CrtcHandle, +//! _frame: u32, +//! _duration: Duration) //! { //! // render surfaces and swap again -//! evlh.state().get(backend).swap_buffers().unwrap(); +//! state.into().get(backend).swap_buffers().unwrap(); //! } -//! fn error(&mut self, -//! _: &mut EventLoopHandle, -//! device: &mut DrmDevice, -//! error: DrmError) +//! fn error<'a, S: Into>>( +//! &mut self, +//! _state: S, +//! device: &mut DrmDevice, +//! error: DrmError) //! { //! panic!("DrmDevice errored: {}", error); //! } @@ -176,27 +180,35 @@ //! // render something (like clear_color) //! event_loop.state().get(&backend).swap_buffers().unwrap(); //! -//! let _source = drm_device_bind(&mut event_loop, device, MyDrmHandler).unwrap(); +//! let device_token = event_loop.state().insert(device); +//! let _source = drm_device_bind(&mut event_loop, device_token, MyDrmHandler).unwrap(); //! //! event_loop.run().unwrap(); //! # } //! ``` use backend::graphics::egl::{EGLContext, GlAttributes, PixelFormatRequirements}; +use backend::graphics::egl::EGLGraphicsBackend; +#[cfg(feature = "backend_session")] +use backend::session::SessionObserver; use drm::Device as BasicDevice; use drm::control::{connector, crtc, encoder, Mode, ResourceInfo}; use drm::control::Device as ControlDevice; use drm::result::Error as DrmError; use gbm::Device as GbmDevice; use nix; +use nix::Result as NixResult; +use nix::unistd::close; use std::borrow::Borrow; use std::collections::HashMap; use std::fs::File; +use std::hash::{Hash, Hasher}; use std::io::Result as IoResult; -use std::os::unix::io::{AsRawFd, RawFd}; +use std::mem; +use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; use std::rc::Rc; use std::time::Duration; -use wayland_server::{EventLoopHandle, StateToken}; +use wayland_server::{EventLoopHandle, StateProxy, StateToken}; use wayland_server::sources::{FdEventSource, FdEventSourceImpl, FdInterest}; mod backend; @@ -207,11 +219,11 @@ use self::error::*; /// Internal struct as required by the drm crate #[derive(Debug)] -pub(crate) struct DrmDev(File); +pub(crate) struct DrmDev(RawFd); impl AsRawFd for DrmDev { fn as_raw_fd(&self) -> RawFd { - self.0.as_raw_fd() + self.0 } } impl BasicDevice for DrmDev {} @@ -219,12 +231,11 @@ impl ControlDevice for DrmDev {} impl DrmDev { unsafe fn new_from_fd(fd: RawFd) -> Self { - use std::os::unix::io::FromRawFd; - DrmDev(File::from_raw_fd(fd)) + DrmDev(fd) } fn new_from_file(file: File) -> Self { - DrmDev(file) + DrmDev(file.into_raw_fd()) } } @@ -256,6 +267,8 @@ use self::devices::{Context, Devices}; pub struct DrmDevice + 'static> { context: Rc, backends: HashMap>, + old_state: HashMap)>, + active: bool, logger: ::slog::Logger, } @@ -351,6 +364,30 @@ impl + Borrow + 'static> DrmDevice { info!(log, "DrmDevice initializing"); + // we want to mode-set, so we better be the master + drm.set_master().chain_err(|| ErrorKind::DrmMasterFailed)?; + + let mut old_state = HashMap::new(); + let res_handles = drm.resource_handles() + .chain_err(|| ErrorKind::DrmDev(format!("Loading drm resources on {:?}", drm)))?; + for &con in res_handles.connectors() { + let con_info = connector::Info::load_from_device(&drm, con) + .chain_err(|| ErrorKind::DrmDev(format!("Loading connector info on {:?}", drm)))?; + if let Some(enc) = con_info.current_encoder() { + let enc_info = encoder::Info::load_from_device(&drm, enc) + .chain_err(|| ErrorKind::DrmDev(format!("Loading encoder info on {:?}", drm)))?; + if let Some(crtc) = enc_info.current_crtc() { + let info = crtc::Info::load_from_device(&drm, crtc) + .chain_err(|| ErrorKind::DrmDev(format!("Loading crtc info on {:?}", drm)))?; + old_state + .entry(crtc) + .or_insert((info, Vec::new())) + .1 + .push(con); + } + } + } + // Open the gbm device from the drm device and create a context based on that Ok(DrmDevice { context: Rc::new(Context::try_new( @@ -374,6 +411,8 @@ impl + Borrow + 'static> DrmDevice { }, )?), backends: HashMap::new(), + old_state, + active: true, logger: log, }) } @@ -383,16 +422,21 @@ impl + Borrow + 'static> DrmDevice { /// /// Errors if initialization fails or the mode is not available on all given /// connectors. - pub fn create_backend(&mut self, evlh: &mut EventLoopHandle, crtc: crtc::Handle, mode: Mode, - connectors: I) - -> Result> + pub fn create_backend<'a, I, S>( + &mut self, state: S, crtc: crtc::Handle, mode: Mode, connectors: I + ) -> Result<&StateToken> where I: Into>, + S: Into>, { if self.backends.contains_key(&crtc) { bail!(ErrorKind::CrtcAlreadyInUse(crtc)); } + if !self.active { + bail!(ErrorKind::DeviceInactive); + } + // check if the given connectors and crtc match let connectors = connectors.into(); @@ -400,7 +444,10 @@ impl + Borrow + 'static> DrmDevice { for connector in &connectors { let con_info = connector::Info::load_from_device(self.context.head().head(), *connector) .chain_err(|| { - ErrorKind::DrmDev(format!("{:?}", self.context.head().head())) + ErrorKind::DrmDev(format!( + "Loading connector info on {:?}", + self.context.head().head() + )) })?; // check the mode @@ -414,21 +461,26 @@ impl + Borrow + 'static> DrmDevice { .iter() .map(|encoder| { encoder::Info::load_from_device(self.context.head().head(), *encoder).chain_err(|| { - ErrorKind::DrmDev(format!("{:?}", self.context.head().head())) + ErrorKind::DrmDev(format!( + "Loading encoder info on {:?}", + self.context.head().head() + )) }) }) .collect::>>()?; // and if any encoder supports the selected crtc let resource_handles = self.resource_handles().chain_err(|| { - ErrorKind::DrmDev(format!("{:?}", self.context.head().head())) + ErrorKind::DrmDev(format!( + "Loading drm resources on {:?}", + self.context.head().head() + )) })?; if !encoders .iter() .map(|encoder| encoder.possible_crtcs()) - .all(|crtc_list| { - resource_handles.filter_crtcs(crtc_list).contains(&crtc) - }) { + .any(|crtc_list| resource_handles.filter_crtcs(crtc_list).contains(&crtc)) + { bail!(ErrorKind::NoSuitableEncoder(con_info, crtc)) } } @@ -437,10 +489,44 @@ impl + Borrow + 'static> DrmDevice { let logger = self.logger.new(o!("crtc" => format!("{:?}", crtc))); let backend = DrmBackend::new(self.context.clone(), crtc, mode, connectors, logger)?; - let token = evlh.state().insert(backend.into()); - self.backends.insert(crtc, token.clone()); + self.backends + .insert(crtc, state.into().insert(backend.into())); - Ok(token) + Ok(self.backends.get(&crtc).unwrap()) + } + + /// Get the current backend for a given crtc if any + pub fn backend_for_crtc(&self, crtc: &crtc::Handle) -> Option<&StateToken> { + self.backends.get(crtc) + } + + /// Get all belonging backends + pub fn current_backends(&self) -> Vec<&StateToken> { + self.backends.values().collect() + } + + /// Destroy the backend using a given crtc if any + /// + /// ## Panics + /// Panics if the backend is already borrowed from the state + pub fn destroy_backend<'a, S>(&mut self, state: S, crtc: &crtc::Handle) + where + S: Into>, + { + if let Some(token) = self.backends.remove(crtc) { + state.into().remove(token); + } + } + + /// Close the device + /// + /// ## Warning + /// Never call this function if the device is managed by another backend e.g. the `UdevBackend`. + /// Only use this function for manually initialized devices. + pub fn close(self) -> NixResult<()> { + let fd = self.as_raw_fd(); + mem::drop(self); + close(fd) } } @@ -450,9 +536,45 @@ impl + 'static> AsRawFd for DrmDevice { self.context.head().head().as_raw_fd() } } + impl + 'static> BasicDevice for DrmDevice {} impl + 'static> ControlDevice for DrmDevice {} +impl + 'static> Drop for DrmDevice { + fn drop(&mut self) { + if Rc::strong_count(&self.context) > 1 { + panic!("Pending DrmBackends. Please 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.head().head(), + handle, + info.fb(), + &connectors, + info.position(), + info.mode(), + ) { + error!( + self.logger, + "Failed to reset crtc ({:?}). Error: {}", handle, err + ); + } + } + if let Err(err) = self.drop_master() { + error!( + self.logger, + "Failed to drop drm master state. Error: {}", err + ); + } + } +} + +impl + 'static> Hash for DrmDevice { + fn hash(&self, state: &mut H) { + self.as_raw_fd().hash(state) + } +} + /// Handler for drm node events /// /// See module-level documentation for its use @@ -462,61 +584,130 @@ pub trait DrmHandler + 'static> { /// /// The `id` argument is the `Id` of the `DrmBackend` that finished rendering, /// check using `DrmBackend::is`. - fn ready(&mut self, evlh: &mut EventLoopHandle, device: &mut DrmDevice, backend: &StateToken, - frame: u32, duration: Duration); + /// + /// ## Panics + /// The device is already borrowed from the given `state`. Borrowing it again will panic + /// and is not necessary as it is already provided via the `device` parameter. + fn ready<'a, S: Into>>( + &mut self, state: S, device: &mut DrmDevice, backend: &StateToken, 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, evlh: &mut EventLoopHandle, device: &mut DrmDevice, error: DrmError); + /// the whole stack has to be recreated.. + /// + /// ## Panics + /// The device is already borrowed from the given `state`. Borrowing it again will panic + /// and is not necessary as it is already provided via the `device` parameter. + fn error<'a, S: Into>>(&mut self, state: S, device: &mut DrmDevice, 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(evlh: &mut EventLoopHandle, device: DrmDevice, handler: H) - -> IoResult, H)>> +pub fn drm_device_bind( + evlh: &mut EventLoopHandle, device: StateToken>, handler: H +) -> IoResult>, H)>> where - B: Borrow + 'static, + B: From + Borrow + 'static, H: DrmHandler + 'static, { + let fd = evlh.state().get(&device).as_raw_fd(); evlh.add_fd_event_source( - device.as_raw_fd(), + fd, fd_event_source_implementation(), (device, handler), FdInterest::READ, ) } -fn fd_event_source_implementation() -> FdEventSourceImpl<(DrmDevice, H)> +fn fd_event_source_implementation() -> FdEventSourceImpl<(StateToken>, H)> where - B: Borrow + 'static, + B: From + Borrow + 'static, H: DrmHandler + 'static, { FdEventSourceImpl { - ready: |evlh, id, _, _| { - let &mut (ref mut dev, ref mut handler) = id; + ready: |evlh, &mut (ref mut dev_token, ref mut handler), _, _| { + let (events, logger) = { + let dev = evlh.state().get(dev_token); + let events = crtc::receive_events(dev); + let logger = dev.logger.clone(); + (events, logger) + }; - let events = crtc::receive_events(dev); match events { Ok(events) => for event in events { if let crtc::Event::PageFlip(event) = event { - let token = dev.backends.get(&event.crtc).cloned(); - if let Some(token) = token { - // we can now unlock the buffer - evlh.state().get(&token).borrow().unlock_buffer(); - trace!(dev.logger, "Handling event for backend {:?}", event.crtc); - // and then call the user to render the next frame - handler.ready(evlh, dev, &token, event.frame, event.duration); - } + evlh.state().with_value(dev_token, |state, mut dev| { + if dev.active { + if let Some(backend_token) = dev.backend_for_crtc(&event.crtc).cloned() { + // we can now unlock the buffer + state.get(&backend_token).borrow().unlock_buffer(); + trace!(logger, "Handling event for backend {:?}", event.crtc); + // and then call the user to render the next frame + handler.ready( + state, + &mut dev, + &backend_token, + event.crtc, + event.frame, + event.duration, + ); + } + } + }); } }, - Err(err) => handler.error(evlh, dev, err), + Err(err) => evlh.state().with_value(dev_token, |state, mut dev| { + handler.error(state, &mut dev, err) + }), }; }, - error: |evlh, id, _, error| { - warn!(id.0.logger, "DrmDevice errored: {}", error); - id.1.error(evlh, &mut id.0, error.into()); + error: |evlh, &mut (ref mut dev_token, ref mut handler), _, error| { + evlh.state().with_value(dev_token, |state, mut dev| { + warn!(dev.logger, "DrmDevice errored: {}", error); + handler.error(state, &mut dev, error.into()); + }) }, } } + +#[cfg(feature = "backend_session")] +impl + 'static> SessionObserver for StateToken> { + fn pause<'a>(&mut self, state: &mut StateProxy<'a>) { + let device: &mut DrmDevice = state.get_mut(self); + device.active = false; + if let Err(err) = device.drop_master() { + error!( + device.logger, + "Failed to drop drm master state. Error: {}", err + ); + } + } + + fn activate<'a>(&mut self, state: &mut StateProxy<'a>) { + state.with_value(self, |state, device| { + device.active = true; + if let Err(err) = device.set_master() { + crit!( + device.logger, + "Failed to acquire drm master again. Error: {}", + err + ); + } + for token in device.backends.values() { + let backend = state.get(token); + if let Err(err) = backend.borrow().swap_buffers() { + // TODO handle this better? + error!( + device.logger, + "Failed to activate crtc ({:?}) again. Error: {}", + backend.borrow().crtc(), + err + ); + } + } + }) + } +} diff --git a/src/backend/graphics/egl.rs b/src/backend/graphics/egl.rs index ad809d8..296422b 100644 --- a/src/backend/graphics/egl.rs +++ b/src/backend/graphics/egl.rs @@ -238,9 +238,9 @@ pub struct EGLContext<'a, T: NativeSurface> { impl<'a> EGLContext<'a, ()> { /// Create a new context from a given `winit`-`Window` #[cfg(feature = "backend_winit")] - pub fn new_from_winit(window: &'a WinitWindow, attributes: GlAttributes, - reqs: PixelFormatRequirements, logger: L) - -> Result> + pub fn new_from_winit( + window: &'a WinitWindow, attributes: GlAttributes, reqs: PixelFormatRequirements, logger: L + ) -> Result> where L: Into>, { @@ -268,9 +268,9 @@ impl<'a> EGLContext<'a, ()> { /// Create a new context from a given `gbm::Device` #[cfg(feature = "backend_drm")] - pub fn new_from_gbm(gbm: &'a GbmDevice<'a>, attributes: GlAttributes, - reqs: PixelFormatRequirements, logger: L) - -> Result>> + pub fn new_from_gbm( + gbm: &'a GbmDevice<'a>, attributes: GlAttributes, reqs: PixelFormatRequirements, logger: L + ) -> Result>> where L: Into>, { @@ -288,9 +288,10 @@ impl<'a> EGLContext<'a, ()> { } impl<'a, T: NativeSurface> EGLContext<'a, T> { - unsafe fn new(native: NativeDisplayPtr, mut attributes: GlAttributes, reqs: PixelFormatRequirements, - log: ::slog::Logger) - -> Result> + unsafe fn new( + native: NativeDisplayPtr, mut attributes: GlAttributes, reqs: PixelFormatRequirements, + log: ::slog::Logger, + ) -> Result> where T: NativeSurface, { @@ -322,8 +323,7 @@ impl<'a, T: NativeSurface> EGLContext<'a, T> { Some(version) => { error!( log, - "OpenGLES {:?} is unknown and not supported by the EGL renderer backend", - version + "OpenGLES {:?} is unknown and not supported by the EGL renderer backend", version ); bail!(ErrorKind::OpenGlVersionNotSupported(version)); } @@ -423,9 +423,9 @@ impl<'a, T: NativeSurface> EGLContext<'a, T> { ) } - NativeDisplayPtr::X11(display) | - NativeDisplayPtr::Gbm(display) | - NativeDisplayPtr::Wayland(display) => { + NativeDisplayPtr::X11(display) + | NativeDisplayPtr::Gbm(display) + | NativeDisplayPtr::Wayland(display) => { trace!(log, "Default EGL Display Initialization via GetDisplay"); egl.GetDisplay(display as *mut _) } @@ -727,9 +727,9 @@ impl<'a, T: NativeSurface> EGLContext<'a, T> { self.display, self.config_id, match surface { - NativeSurfacePtr::X11(ptr) | - NativeSurfacePtr::Wayland(ptr) | - NativeSurfacePtr::Gbm(ptr) => ptr, + NativeSurfacePtr::X11(ptr) + | NativeSurfacePtr::Wayland(ptr) + | NativeSurfacePtr::Gbm(ptr) => ptr, }, self.surface_attributes.as_ptr(), ) @@ -817,7 +817,7 @@ impl<'context, 'surface, T: NativeSurface> EGLSurface<'context, 'surface, T> { if ret == 0 { match unsafe { self.context.egl.GetError() } as u32 { ffi::egl::CONTEXT_LOST => Err(SwapBuffersError::ContextLost), - err => panic!("eglSwapBuffers failed (eglGetError returned 0x{:x})", err), + err => Err(SwapBuffersError::Unknown(err)), } } else { Ok(()) @@ -847,6 +847,14 @@ impl<'context, 'surface, T: NativeSurface> EGLSurface<'context, 'surface, T> { Ok(()) } } + + /// Returns true if the OpenGL surface is the current one in the thread. + pub fn is_current(&self) -> bool { + unsafe { + self.context.egl.GetCurrentSurface(ffi::egl::DRAW as _) == self.surface as *const _ + && self.context.egl.GetCurrentSurface(ffi::egl::READ as _) == self.surface as *const _ + } + } } unsafe impl<'a, 'b, T: NativeSurface> Send for EGLSurface<'a, 'b, T> {} @@ -882,6 +890,8 @@ pub enum SwapBuffersError { /// 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 { @@ -898,6 +908,7 @@ impl error::Error for SwapBuffersError { SwapBuffersError::AlreadySwapped => { "Buffers are already swapped, swap_buffers was called too many times" } + SwapBuffersError::Unknown(_) => "Unknown Open GL error occurred", } } diff --git a/src/backend/graphics/glium.rs b/src/backend/graphics/glium.rs index 42ffedf..484b798 100644 --- a/src/backend/graphics/glium.rs +++ b/src/backend/graphics/glium.rs @@ -14,6 +14,7 @@ impl From for GliumSwapBuffersError { match error { SwapBuffersError::ContextLost => GliumSwapBuffersError::ContextLost, SwapBuffersError::AlreadySwapped => GliumSwapBuffersError::AlreadySwapped, + SwapBuffersError::Unknown(_) => GliumSwapBuffersError::ContextLost, // TODO } } } diff --git a/src/backend/graphics/mod.rs b/src/backend/graphics/mod.rs index 0a082d7..05d276e 100644 --- a/src/backend/graphics/mod.rs +++ b/src/backend/graphics/mod.rs @@ -29,8 +29,9 @@ pub trait 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>; + fn set_cursor_representation( + &self, cursor: &Self::CursorFormat, hotspot: (u32, u32) + ) -> Result<(), Self::Error>; } pub mod software; diff --git a/src/backend/graphics/software.rs b/src/backend/graphics/software.rs index e053c77..2a96606 100644 --- a/src/backend/graphics/software.rs +++ b/src/backend/graphics/software.rs @@ -1,6 +1,5 @@ //! 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; diff --git a/src/backend/input.rs b/src/backend/input.rs index 6baf26b..e08b439 100644 --- a/src/backend/input.rs +++ b/src/backend/input.rs @@ -1,6 +1,5 @@ //! Common traits for input backends to receive input from. -use backend::{SeatInternal, TouchSlotInternal}; use std::error::Error; /// A seat describes a group of input devices and at least one @@ -20,20 +19,18 @@ pub struct Seat { capabilities: SeatCapabilities, } -impl SeatInternal for Seat { - fn new(id: u64, capabilities: SeatCapabilities) -> Seat { +impl Seat { + pub(crate) fn new(id: u64, capabilities: SeatCapabilities) -> Seat { Seat { id: id, capabilities: capabilities, } } - fn capabilities_mut(&mut self) -> &mut SeatCapabilities { + pub(crate) fn capabilities_mut(&mut self) -> &mut SeatCapabilities { &mut self.capabilities } -} -impl Seat { /// Get the currently capabilities of this `Seat` pub fn capabilities(&self) -> &SeatCapabilities { &self.capabilities @@ -318,8 +315,8 @@ pub struct TouchSlot { id: u64, } -impl TouchSlotInternal for TouchSlot { - fn new(id: u64) -> Self { +impl TouchSlot { + pub(crate) fn new(id: u64) -> Self { TouchSlot { id: id } } } diff --git a/src/backend/libinput.rs b/src/backend/libinput.rs index c55ddf8..7c5475b 100644 --- a/src/backend/libinput.rs +++ b/src/backend/libinput.rs @@ -1,13 +1,18 @@ //! Implementation of input backend trait for types provided by `libinput` -use backend::{SeatInternal, TouchSlotInternal}; use backend::input as backend; +#[cfg(feature = "backend_session")] +use backend::session::{AsErrno, Session, SessionObserver}; use input as libinput; use input::event; use std::collections::hash_map::{DefaultHasher, Entry, HashMap}; use std::hash::{Hash, Hasher}; -use std::io::Error as IoError; +use std::io::{Error as IoError, Result as IoResult}; +use std::os::unix::io::RawFd; +use std::path::Path; use std::rc::Rc; +use wayland_server::{EventLoopHandle, StateProxy}; +use wayland_server::sources::{FdEventSource, FdEventSourceImpl, FdInterest}; /// Libinput based `InputBackend`. /// @@ -261,7 +266,7 @@ impl backend::InputBackend for LibinputInputBackend { if self.handler.is_some() { self.clear_handler(); } - info!(self.logger, "New input handler set."); + info!(self.logger, "New input handler set"); for seat in self.seats.values() { trace!(self.logger, "Calling on_seat_created with {:?}", seat); handler.on_seat_created(seat); @@ -549,3 +554,71 @@ impl From for backend::MouseButtonState { } } } + +#[cfg(feature = "backend_session")] +impl SessionObserver for libinput::Libinput { + fn pause<'a>(&mut self, _state: &mut StateProxy<'a>) { + self.suspend() + } + + fn activate<'a>(&mut self, _state: &mut StateProxy<'a>) { + // TODO Is this the best way to handle this failure? + self.resume().expect("Unable to resume libinput context"); + } +} + +/// Wrapper for types implementing the `Session` trait to provide +/// a `LibinputInterface` implementation. +#[cfg(feature = "backend_session")] +pub struct LibinputSessionInterface(S); + +#[cfg(feature = "backend_session")] +impl From for LibinputSessionInterface { + fn from(session: S) -> LibinputSessionInterface { + LibinputSessionInterface(session) + } +} + +#[cfg(feature = "backend_session")] +impl libinput::LibinputInterface for LibinputSessionInterface { + fn open_restricted(&mut self, path: &Path, flags: i32) -> Result { + use nix::fcntl::OFlag; + self.0 + .open(path, OFlag::from_bits_truncate(flags)) + .map_err(|err| err.as_errno().unwrap_or(1 /*Use EPERM by default*/)) + } + + fn close_restricted(&mut self, fd: RawFd) { + let _ = self.0.close(fd); + } +} + +/// Binds a `LibinputInputBackend` to a given `EventLoop`. +/// +/// Automatically feeds the backend with incoming events without any manual calls to +/// `dispatch_new_events`. Should be used to achieve the smallest possible latency. +pub fn libinput_bind( + backend: LibinputInputBackend, evlh: &mut EventLoopHandle +) -> IoResult> { + let fd = unsafe { backend.context.fd() }; + evlh.add_fd_event_source( + fd, + fd_event_source_implementation(), + backend, + FdInterest::READ, + ) +} + +fn fd_event_source_implementation() -> FdEventSourceImpl { + FdEventSourceImpl { + ready: |_evlh, ref mut backend, _, _| { + use backend::input::InputBackend; + if let Err(error) = backend.dispatch_new_events() { + warn!(backend.logger, "Libinput errored: {}", error); + } + }, + error: |_evlh, ref backend, _, error| { + warn!(backend.logger, "Libinput fd errored: {}", error); + }, + } +} diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 09bb18f..f73914a 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -23,14 +23,7 @@ pub mod winit; pub mod drm; #[cfg(feature = "backend_libinput")] pub mod libinput; - -// Internal functions that need to be accessible by the different backend implementations - -trait SeatInternal { - fn new(id: u64, capabilities: input::SeatCapabilities) -> Self; - fn capabilities_mut(&mut self) -> &mut input::SeatCapabilities; -} - -trait TouchSlotInternal { - fn new(id: u64) -> Self; -} +#[cfg(feature = "backend_session")] +pub mod session; +#[cfg(feature = "backend_udev")] +pub mod udev; diff --git a/src/backend/session/direct.rs b/src/backend/session/direct.rs new file mode 100644 index 0000000..2b23d04 --- /dev/null +++ b/src/backend/session/direct.rs @@ -0,0 +1,465 @@ +//! +//! Implementation of the `Session` trait through the legacy vt kernel interface. +//! +//! This requires write permissions for the given tty device and any devices opened through this +//! interface. This means it will almost certainly require root permissions and not allow to run +//! the compositor as an unpriviledged user. Use this session type *only* as a fallback or for testing, +//! if anything better is available. +//! +//! ## How to use it +//! +//! ### Initialization +//! +//! To initialize the session you may pass the path to any tty device, that shall be used. +//! If no path is given the tty used to start this compositor (if any) will be used. +//! A new session and its notifier will be returned. +//! +//! ```rust,no_run +//! extern crate smithay; +//! +//! use smithay::backend::session::direct::DirectSession; +//! +//! # fn main() { +//! let (session, mut notifier) = DirectSession::new(None, None).unwrap(); +//! # } +//! ``` +//! +//! ### Usage of the session +//! +//! The session may be used to open devices manually through the `Session` interface +//! or be passed to other object that need to open devices themselves. +//! +//! Examples for those are e.g. the `LibinputInputBackend` (its context might be initialized through a +//! `Session` via the `LibinputSessionInterface`) or the `UdevBackend`. +//! +//! In case you want to pass the same `Session` to multiple objects, `Session` is implement for +//! every `Rc>` or `Arc>`. +//! +//! ### Usage of the session notifier +//! +//! The notifier might be used to pause device access, when the session gets paused (e.g. by +//! switching the tty via `DirectSession::change_vt`) and to automatically enable it again, +//! when the session becomes active again. +//! +//! It is crutial to avoid errors during that state. Examples for object that might be registered +//! for notifications are the `Libinput` context, the `UdevBackend` or a `DrmDevice` (handled +//! automatically by the `UdevBackend`, if not done manually). +//! ``` + +use super::{AsErrno, Session, SessionNotifier, SessionObserver}; +use nix::{Error as NixError, Result as NixResult}; +use nix::fcntl::{self, open, OFlag}; +use nix::libc::c_int; +use nix::sys::signal::{self, Signal}; +use nix::sys::stat::{dev_t, fstat, major, minor, Mode}; +use nix::unistd::{close, dup}; +use std::io::Result as IoResult; +use std::os::unix::io::RawFd; +use std::path::Path; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; +#[cfg(feature = "backend_session_udev")] +use udev::Context; +use wayland_server::EventLoopHandle; +use wayland_server::sources::SignalEventSource; + +#[allow(dead_code)] +mod tty { + ioctl!(bad read kd_get_mode with 0x4B3B; i16); + ioctl!(bad write_int kd_set_mode with 0x4B3A); + pub const KD_TEXT: i16 = 0x00; + pub const KD_GRAPHICS: i16 = 0x00; + + ioctl!(bad read kd_get_kb_mode with 0x4B44; i32); + ioctl!(bad write_int kd_set_kb_mode with 0x4B45); + pub const K_RAW: i32 = 0x00; + pub const K_XLATE: i32 = 0x01; + pub const K_MEDIUMRAW: i32 = 0x02; + pub const K_UNICODE: i32 = 0x03; + pub const K_OFF: i32 = 0x04; + + ioctl!(bad write_int vt_activate with 0x5606); + ioctl!(bad write_int vt_wait_active with 0x5607); + ioctl!(bad write_ptr vt_set_mode with 0x5602; VtMode); + ioctl!(bad write_int vt_rel_disp with 0x5605); + #[repr(C)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] + pub struct VtMode { + /// vt mode + pub mode: i8, + /// if set, hang on writes if not active + pub waitv: i8, + /// signal to raise on release req + pub relsig: i16, + /// signal to raise on acquisition + pub acqsig: i16, + /// unused (set to 0) + pub frsig: i16, + } + pub const VT_AUTO: i8 = 0x00; + pub const VT_PROCESS: i8 = 0x01; + pub const VT_ACKACQ: i32 = 0x02; + + extern "C" { + pub fn __libc_current_sigrtmin() -> i8; + pub fn __libc_current_sigrtmax() -> i8; + } +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +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"))] +fn is_tty_device(dev: dev_t, _path: Option<&Path>) -> bool { + major(dev) == TTY_MAJOR +} + +#[cfg(feature = "backend_session_udev")] +fn is_tty_device(dev: dev_t, path: Option<&Path>) -> bool { + match path { + Some(path) => { + let udev = match Context::new() { + Ok(context) => context, + Err(_) => return major(dev) == TTY_MAJOR || minor(dev) != 0, + }; + + let device = match udev.device_from_syspath(path) { + Ok(device) => device, + Err(_) => return major(dev) == TTY_MAJOR || minor(dev) != 0, + }; + + let res = if let Some(subsystem) = device.subsystem() { + subsystem == "tty" + } else { + major(dev) == TTY_MAJOR + }; + res || minor(dev) != 0 + } + None => major(dev) == TTY_MAJOR || minor(dev) != 0, + } +} + +/// `Session` via the virtual terminal direct kernel interface +pub struct DirectSession { + tty: RawFd, + active: Arc, + vt: i32, + old_keyboard_mode: i32, + logger: ::slog::Logger, +} + +/// `SessionNotifier` via the virtual terminal direct kernel interface +pub struct DirectSessionNotifier { + tty: RawFd, + active: Arc, + signals: Vec>>, + signal: Signal, + logger: ::slog::Logger, +} + +impl DirectSession { + /// Tries to creates a new session via the legacy virtual terminal interface. + /// + /// If you do not provide a tty device path, it will try to open the currently active tty if any. + pub fn new(tty: Option<&Path>, logger: L) -> Result<(DirectSession, DirectSessionNotifier)> + where + L: Into>, + { + let logger = ::slog_or_stdlog(logger) + .new(o!("smithay_module" => "backend_session", "session_type" => "direct/vt")); + + let fd = tty.map(|path| { + open(path, fcntl::O_RDWR | fcntl::O_CLOEXEC, Mode::empty()) + .chain_err(|| ErrorKind::FailedToOpenTTY(String::from(path.to_string_lossy()))) + }).unwrap_or(dup(0 /*stdin*/).chain_err(|| ErrorKind::FailedToOpenTTY(String::from(""))))?; + + let active = Arc::new(AtomicBool::new(true)); + + match DirectSession::setup_tty(tty, fd, logger.clone()) { + Ok((vt, old_keyboard_mode, signal)) => Ok(( + DirectSession { + tty: fd, + active: active.clone(), + vt, + old_keyboard_mode, + logger: logger.new(o!("vt" => format!("{}", vt), "component" => "session")), + }, + DirectSessionNotifier { + tty: fd, + active, + signals: Vec::new(), + signal, + logger: logger.new(o!("vt" => format!("{}", vt), "component" => "session_notifier")), + }, + )), + Err(err) => { + let _ = close(fd); + Err(err) + } + } + } + + fn setup_tty(path: Option<&Path>, tty: RawFd, logger: ::slog::Logger) -> Result<(i32, i32, Signal)> { + let stat = fstat(tty).chain_err(|| ErrorKind::NotRunningFromTTY)?; + if !is_tty_device(stat.st_dev, path) { + bail!(ErrorKind::NotRunningFromTTY); + } + + let vt_num = minor(stat.st_rdev) as i32; + info!(logger, "Running from tty: {}", vt_num); + + let mut mode = 0; + unsafe { + tty::kd_get_mode(tty, &mut mode).chain_err(|| ErrorKind::NotRunningFromTTY)?; + } + if mode != tty::KD_TEXT { + bail!(ErrorKind::TTYAlreadyInGraphicsMode); + } + + unsafe { + tty::vt_activate(tty, vt_num as c_int).chain_err(|| ErrorKind::FailedToActivateTTY(vt_num))?; + tty::vt_wait_active(tty, vt_num as c_int).chain_err(|| ErrorKind::FailedToWaitForTTY(vt_num))?; + } + + let mut old_keyboard_mode = 0; + unsafe { + tty::kd_get_kb_mode(tty, &mut old_keyboard_mode) + .chain_err(|| ErrorKind::FailedToSaveTTYState(vt_num))?; + tty::kd_set_kb_mode(tty, tty::K_OFF).chain_err(|| ErrorKind::FailedToSetTTYKbMode(vt_num))?; + tty::kd_set_mode(tty, tty::KD_GRAPHICS as i32) + .chain_err(|| ErrorKind::FailedToSetTTYMode(vt_num))?; + } + + // TODO: Support realtime signals + // https://github.com/nix-rust/nix/issues/495 + /* + let signal = if tty::__libc_current_sigrtmin() > tty::__libc_current_sigrtmax() { + warn!(logger, "Not enough real-time signals available, falling back to USR1"); + nix::sys::signal::SIGUSR2 as i32 + } else { + tty::__libc_current_sigrtmin() + };*/ + let signal = signal::SIGUSR2; + + let mode = tty::VtMode { + mode: tty::VT_PROCESS, + relsig: signal as i16, + acqsig: signal as i16, + ..Default::default() + }; + + unsafe { + tty::vt_set_mode(tty, &mode).chain_err(|| ErrorKind::FailedToTakeControlOfTTY(vt_num))?; + } + + Ok((vt_num, old_keyboard_mode, signal)) + } + + /// Get the number of the virtual terminal used by this session + pub fn vt(&self) -> i32 { + self.vt + } +} + +impl Session for DirectSession { + type Error = NixError; + + fn open(&mut self, path: &Path, flags: OFlag) -> NixResult { + debug!(self.logger, "Opening device: {:?}", path); + let fd = open(path, flags, Mode::empty())?; + trace!(self.logger, "Fd num: {:?}", fd); + Ok(fd) + } + + fn close(&mut self, fd: RawFd) -> NixResult<()> { + debug!(self.logger, "Closing device: {:?}", fd); + close(fd) + } + + fn is_active(&self) -> bool { + self.active.load(Ordering::SeqCst) + } + + fn seat(&self) -> String { + // The VT api can only be used on seat0 + String::from("seat0") + } + + fn change_vt(&mut self, vt_num: i32) -> NixResult<()> { + unsafe { tty::vt_activate(self.tty, vt_num).map(|_| ()) } + } +} + +impl AsErrno for NixError { + fn as_errno(&self) -> Option { + match *self { + NixError::Sys(errno) => Some(errno as i32), + _ => None, + } + } +} + +impl Drop for DirectSession { + fn drop(&mut self) { + info!(self.logger, "Deallocating tty {}", self.tty); + + if let Err(err) = unsafe { tty::kd_set_kb_mode(self.tty, self.old_keyboard_mode) } { + warn!( + self.logger, + "Unable to restore vt keyboard mode. Error: {}", err + ); + } + if let Err(err) = unsafe { tty::kd_set_mode(self.tty, tty::KD_TEXT as i32) } { + warn!( + self.logger, + "Unable to restore vt text mode. Error: {}", err + ); + } + if let Err(err) = unsafe { + tty::vt_set_mode( + self.tty, + &tty::VtMode { + mode: tty::VT_AUTO, + ..Default::default() + }, + ) + } { + error!(self.logger, "Failed to reset vt handling. Error: {}", err); + } + if let Err(err) = close(self.tty) { + error!( + self.logger, + "Failed to close tty file descriptor. Error: {}", err + ); + } + } +} + +/// Ids of registered `SessionObserver`s of the `DirectSessionNotifier` +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Id(usize); + +impl SessionNotifier for DirectSessionNotifier { + type Id = Id; + + fn register(&mut self, signal: S) -> 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" + } +} + +/// Bind a `DirectSessionNotifier` to an `EventLoop`. +/// +/// Allows the `DirectSessionNotifier` to listen for the incoming signals signalling the session state. +/// If you don't use this function `DirectSessionNotifier` will not correctly tell you the current +/// session state. +pub fn direct_session_bind( + notifier: DirectSessionNotifier, evlh: &mut EventLoopHandle, _logger: L +) -> IoResult> +where + L: Into>, +{ + let signal = notifier.signal; + + evlh.add_signal_event_source( + |evlh, notifier, _| { + if notifier.is_active() { + info!(notifier.logger, "Session shall become inactive"); + for signal in &mut notifier.signals { + if let &mut Some(ref mut signal) = signal { + signal.pause(&mut evlh.state().as_proxy()); + } + } + notifier.active.store(false, Ordering::SeqCst); + unsafe { + tty::vt_rel_disp(notifier.tty, 1).expect("Unable to release tty lock"); + } + debug!(notifier.logger, "Session is now inactive"); + } else { + debug!(notifier.logger, "Session will become active again"); + unsafe { + tty::vt_rel_disp(notifier.tty, tty::VT_ACKACQ).expect("Unable to acquire tty lock"); + } + for signal in &mut notifier.signals { + if let &mut Some(ref mut signal) = signal { + signal.activate(&mut evlh.state().as_proxy()); + } + } + notifier.active.store(true, Ordering::SeqCst); + info!(notifier.logger, "Session is now active again"); + } + }, + notifier, + signal, + ) +} + +error_chain! { + errors { + #[doc = "Failed to open tty"] + FailedToOpenTTY(path: String) { + description("Failed to open tty"), + display("Failed to open tty ({:?})", path), + } + + #[doc = "Not running from a tty"] + NotRunningFromTTY { + description("Not running from a tty"), + } + + #[doc = "tty is already in KB_GRAPHICS mode"] + TTYAlreadyInGraphicsMode { + description("The tty is already in KB_GRAPHICS mode"), + display("The tty is already in graphics mode, is already a compositor running?"), + } + + #[doc = "Failed to activate open tty"] + FailedToActivateTTY(num: i32) { + description("Failed to activate open tty"), + display("Failed to activate open tty ({:?})", num), + } + + #[doc = "Failed to wait for tty to become active"] + FailedToWaitForTTY(num: i32) { + description("Failed to wait for tty to become active"), + display("Failed to wait for tty ({:?}) to become active", num), + } + + #[doc = "Failed to save old tty state"] + FailedToSaveTTYState(num: i32) { + description("Failed to save old tty state"), + display("Failed to save old tty ({:?}) state", num), + } + + #[doc = "Failed to set tty kb mode"] + FailedToSetTTYKbMode(num: i32) { + description("Failed to set tty kb mode to K_OFF"), + display("Failed to set tty ({:?}) kb mode to K_OFF", num), + } + + #[doc = "Failed to set tty mode"] + FailedToSetTTYMode(num: i32) { + description("Failed to set tty mode to KD_GRAPHICS"), + display("Failed to set tty ({:?}) mode into graphics mode", num), + } + + #[doc = "Failed to set tty in process mode"] + FailedToTakeControlOfTTY(num: i32) { + description("Failed to set tty mode to VT_PROCESS"), + display("Failed to take control of tty ({:?})", num), + } + } +} diff --git a/src/backend/session/mod.rs b/src/backend/session/mod.rs new file mode 100644 index 0000000..0d16259 --- /dev/null +++ b/src/backend/session/mod.rs @@ -0,0 +1,165 @@ +//! +//! Abstraction of different session apis. +//! +//! Sessions provide a way for multiple graphical systems to run in parallel by providing +//! mechanisms to switch between and handle device access and permissions for every running +//! instance. +//! +//! They are crutial to allow unpriviledged processes to use graphical or input devices. +//! +//! The following mechanisms are currently provided: +//! - direct - legacy tty / virtual terminal kernel api +//! +use nix::fcntl::OFlag; +use std::cell::RefCell; +use std::os::unix::io::RawFd; +use std::path::Path; +use std::rc::Rc; +use std::sync::{Arc, Mutex}; +use wayland_server::StateProxy; + +/// General session interface. +/// +/// Provides a way to open and close devices and change the active vt. +pub trait Session { + /// Error type of the implementation + type Error: AsErrno; + + /// Opens a device at the given `path` with the given flags. + /// + /// Returns a raw file descriptor + fn open(&mut self, path: &Path, flags: OFlag) -> Result; + /// Close a previously opened file descriptor + fn close(&mut self, fd: RawFd) -> Result<(), Self::Error>; + + /// Change the currently active virtual terminal + fn change_vt(&mut self, vt: i32) -> Result<(), Self::Error>; + + /// Check if this session is currently active + fn is_active(&self) -> bool; + /// Which seat this session is on + fn seat(&self) -> String; +} + +/// Interface for registering for notifications for a given session. +/// +/// Part of the session api which allows to get notified, when the given session +/// gets paused or becomes active again. Any object implementing the `SessionObserver` trait +/// may be registered. +pub trait SessionNotifier { + /// Id type of registered observers + type Id: PartialEq + Eq; + + /// Registers a given `SessionObserver`. + /// + /// Returns an id of the inserted observer, can be used to remove it again. + fn register(&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 be notified when the session pauses or becomes active again. +/// +/// It might be impossible to interact with devices while the session is disabled. +/// This interface provides callbacks for when that happens. +pub trait SessionObserver { + /// Session is about to be paused. + /// + /// In case the implementor is a `StateToken` the state of the `EventLoop` + /// is provided via a `StateProxy`. + fn pause<'a>(&mut self, state: &mut StateProxy<'a>); + /// Session got active again + /// + /// In case the implementor is a `StateToken` the state of the `EventLoop` + /// is provided via a `StateProxy`. + fn activate<'a>(&mut self, state: &mut StateProxy<'a>); +} + +impl Session for () { + type Error = (); + + fn open(&mut self, _path: &Path, _flags: OFlag) -> Result { + Err(()) + } + fn close(&mut self, _fd: RawFd) -> Result<(), Self::Error> { + Err(()) + } + + fn change_vt(&mut self, _vt: i32) -> Result<(), Self::Error> { + Err(()) + } + + fn is_active(&self) -> bool { + false + } + fn seat(&self) -> String { + String::from("seat0") + } +} + +impl Session for Rc> { + type Error = S::Error; + + fn open(&mut self, path: &Path, flags: OFlag) -> Result { + self.borrow_mut().open(path, flags) + } + + fn close(&mut self, fd: RawFd) -> Result<(), Self::Error> { + self.borrow_mut().close(fd) + } + + fn change_vt(&mut self, vt: i32) -> Result<(), Self::Error> { + self.borrow_mut().change_vt(vt) + } + + fn is_active(&self) -> bool { + self.borrow().is_active() + } + + fn seat(&self) -> String { + self.borrow().seat() + } +} + +impl Session for Arc> { + type Error = S::Error; + + fn open(&mut self, path: &Path, flags: OFlag) -> Result { + self.lock().unwrap().open(path, flags) + } + + fn close(&mut self, fd: RawFd) -> Result<(), Self::Error> { + self.lock().unwrap().close(fd) + } + + fn change_vt(&mut self, vt: i32) -> Result<(), Self::Error> { + self.lock().unwrap().change_vt(vt) + } + + fn is_active(&self) -> bool { + self.lock().unwrap().is_active() + } + + fn seat(&self) -> String { + self.lock().unwrap().seat() + } +} + +/// Allows errors to be described by an error number +pub trait AsErrno: ::std::fmt::Debug { + /// Returns the error number representing this error if any + fn as_errno(&self) -> Option; +} + +impl AsErrno for () { + fn as_errno(&self) -> Option { + None + } +} + +pub mod direct; diff --git a/src/backend/udev.rs b/src/backend/udev.rs new file mode 100644 index 0000000..a04b670 --- /dev/null +++ b/src/backend/udev.rs @@ -0,0 +1,466 @@ +//! +//! Provides `udev` related functionality for automated device scanning. +//! +//! This module mainly provides the `UdevBackend`, which constantly monitors available drm devices +//! and notifies a user supplied `UdevHandler` of any changes. +//! +//! Additionally this contains some utility functions related to scanning. +//! +//! See also `examples/udev.rs` for pure hardware backed example of a compositor utilizing this +//! backend. + +use backend::drm::{drm_device_bind, DrmBackend, DrmDevice, DrmHandler}; +use backend::session::{Session, SessionObserver}; +use nix::fcntl; +use nix::sys::stat::{dev_t, fstat}; +use std::borrow::Borrow; +use std::collections::HashMap; +use std::ffi::OsString; +use std::io::{Error as IoError, Result as IoResult}; +use std::mem::drop; +use std::os::unix::io::AsRawFd; +use std::path::{Path, PathBuf}; +use udev::{Context, Enumerator, Event, EventType, MonitorBuilder, MonitorSocket, Result as UdevResult}; +use wayland_server::{EventLoopHandle, StateProxy, StateToken}; +use wayland_server::sources::{FdEventSource, FdEventSourceImpl, FdInterest}; + +/// Graphical backend that monitors 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 +/// attached monitors. +pub struct UdevBackend< + B: Borrow + 'static, + H: DrmHandler + 'static, + S: Session + 'static, + T: UdevHandler + 'static, +> { + devices: HashMap< + dev_t, + ( + StateToken>, + FdEventSource<(StateToken>, H)>, + ), + >, + monitor: MonitorSocket, + session: S, + handler: T, + logger: ::slog::Logger, +} + +impl< + B: From + Borrow + 'static, + H: DrmHandler + 'static, + S: Session + 'static, + T: UdevHandler + 'static, +> UdevBackend { + /// 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 + /// `logger` - slog Logger to be used by the backend and its `DrmDevices`. + pub fn new<'a, L>( + mut evlh: &mut EventLoopHandle, context: &Context, mut session: S, mut handler: T, logger: L + ) -> Result>> + where + L: Into>, + { + 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)? + .into_iter() + // Create devices + .flat_map(|path| { + match unsafe { DrmDevice::new_from_fd( + { + match session.open(&path, fcntl::O_RDWR | fcntl::O_CLOEXEC | fcntl::O_NOCTTY | fcntl::O_NONBLOCK) { + Ok(fd) => 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) => match handler.device_added(&mut evlh.state().as_proxy(), &mut device) { + // fstat them + Some(drm_handler) => match fstat(device.as_raw_fd()) { + Ok(stat) => { + let token = evlh.state().insert(device); + if let Ok(event_source) = drm_device_bind(&mut evlh, token.clone(), drm_handler) { + Some((stat.st_rdev, (token, event_source))) + } else { + handler.device_removed(evlh.state(), &token); + let device = evlh.state().remove(token); + let fd = device.as_raw_fd(); + drop(device); + if let Err(err) = session.close(fd) { + warn!(logger, "Failed to close dropped device. Error: {:?}. Ignoring", err); + }; + None + } + }, + Err(err) => { + // almost impossible to hit, but lets do it as good as possible + error!(logger, "Failed to get devnum of newly initialized device, dropping. Error: {:?}", err); + let token = evlh.state().insert(device); + handler.device_removed(evlh.state(), &token); + let device = evlh.state().remove(token); + let fd = device.as_raw_fd(); + drop(device); + if let Err(err) = session.close(fd) { + warn!(logger, "Failed to close dropped device. Error: {:?}. Ignoring", err); + }; + None + } + }, + None => { + let fd = device.as_raw_fd(); + 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); + return None; + } + } + }) + .collect::>, FdEventSource<(StateToken>, H)>)>>(); + + 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)?; + + Ok(evlh.state().insert(UdevBackend { + devices, + monitor, + session, + handler, + logger, + })) + } + + /// Closes the udev backend and frees all remaining open devices. + /// + /// Needs to be called after the `FdEventSource` was removed and the backend was removed from + /// the `EventLoop`'s `State`. + /// + /// ## Panics + /// The given state might be passed to the registered `UdevHandler::device_removed` callback. + /// Make sure not to borrow any tokens twice. + pub fn close<'a, ST: Into>>(mut self, state: ST) { + let mut state = state.into(); + for (_, (device, event_source)) in self.devices.drain() { + event_source.remove(); + self.handler.device_removed(&mut state, &device); + let device = state.remove(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< + B: Borrow + 'static, + H: DrmHandler + 'static, + S: Session + 'static, + T: UdevHandler + 'static, +> SessionObserver for StateToken> { + fn pause<'a>(&mut self, state: &mut StateProxy<'a>) { + state.with_value(self, |state, udev| { + for &mut (ref mut device, _) in udev.devices.values_mut() { + device.pause(state); + } + }); + } + + fn activate<'a>(&mut self, state: &mut StateProxy<'a>) { + state.with_value(self, |state, udev| { + for &mut (ref mut device, _) in udev.devices.values_mut() { + device.activate(state); + } + }); + } +} + +/// Binds a `UdevBackend` to a given `EventLoop`. +/// +/// Allows the backend to recieve kernel events and thus to drive the `UdevHandler`. +/// No runtime functionality can be provided without using this function. +pub fn udev_backend_bind( + evlh: &mut EventLoopHandle, udev: StateToken> +) -> IoResult>>> +where + B: From + Borrow + 'static, + H: DrmHandler + 'static, + T: UdevHandler + 'static, + S: Session + 'static, +{ + let fd = evlh.state().get(&udev).monitor.as_raw_fd(); + evlh.add_fd_event_source(fd, fd_event_source_implementation(), udev, FdInterest::READ) +} + +fn fd_event_source_implementation() -> FdEventSourceImpl>> +where + B: From + Borrow + 'static, + H: DrmHandler + 'static, + T: UdevHandler + 'static, + S: Session + 'static, +{ + FdEventSourceImpl { + ready: |mut evlh, token, _, _| { + let events = evlh.state() + .get(token) + .monitor + .clone() + .collect::>(); + for event in events { + match event.event_type() { + // New device + EventType::Add => { + info!(evlh.state().get(token).logger, "Device Added"); + if let (Some(path), Some(devnum)) = (event.devnode(), event.devnum()) { + let mut device = { + match unsafe { + DrmDevice::new_from_fd( + { + let logger = evlh.state().get(token).logger.clone(); + match evlh.state().get_mut(token).session.open( + path, + fcntl::O_RDWR | fcntl::O_CLOEXEC | fcntl::O_NOCTTY + | fcntl::O_NONBLOCK, + ) { + Ok(fd) => fd, + Err(err) => { + warn!(logger, "Unable to open drm device {:?}, Error: {:?}. Skipping", path, err); + continue; + } + } + }, + evlh.state().get(token).logger.clone(), + ) + } { + Ok(dev) => dev, + Err(err) => { + warn!( + evlh.state().get(token).logger, + "Failed to initialize device {:?}. Error: {}. Skipping", + path, + err + ); + continue; + } + } + }; + match evlh.state().with_value(token, |state, udev| { + udev.handler.device_added(state, &mut device) + }) { + Some(drm_handler) => { + let dev_token = evlh.state().insert(device); + if let Ok(fd_event_source) = + drm_device_bind(&mut evlh, dev_token.clone(), drm_handler) + { + evlh.state() + .get_mut(token) + .devices + .insert(devnum, (dev_token, fd_event_source)); + } else { + evlh.state().with_value(token, |state, udev| { + let mut state: StateProxy = state.into(); + udev.handler.device_removed(&mut state, &dev_token); + let device = state.remove(dev_token); + let fd = device.as_raw_fd(); + drop(device); + if let Err(err) = udev.session.close(fd) { + warn!( + udev.logger, + "Failed to close dropped device. Error: {:?}. Ignoring", + err + ); + }; + }) + } + } + None => { + let fd = device.as_raw_fd(); + drop(device); + evlh.state().with_value(token, |_state, udev| { + if let Err(err) = udev.session.close(fd) { + warn!( + udev.logger, + "Failed to close unused device. Error: {:?}", err + ); + } + }) + } + }; + } + } + // Device removed + EventType::Remove => evlh.state().with_value(token, |state, udev| { + info!(udev.logger, "Device Remove"); + if let Some(devnum) = event.devnum() { + if let Some((device, fd_event_source)) = udev.devices.remove(&devnum) { + fd_event_source.remove(); + let mut state: StateProxy = state.into(); + udev.handler.device_removed(&mut state, &device); + let device = state.remove(device); + let fd = device.as_raw_fd(); + drop(device); + if let Err(err) = udev.session.close(fd) { + warn!( + udev.logger, + "Failed to close device {:?}. Error: {:?}. Ignoring", + event.sysname(), + err + ); + }; + } + } + }), + // New connector + EventType::Change => evlh.state().with_value(token, |state, udev| { + info!(udev.logger, "Device Changed"); + if let Some(devnum) = event.devnum() { + info!(udev.logger, "Devnum: {:b}", devnum); + if let Some(&(ref device, _)) = udev.devices.get(&devnum) { + info!(udev.logger, "changed successful"); + udev.handler.device_changed(state, device); + } else { + info!(udev.logger, "changed, but device not tracked by backend"); + } + } else { + info!(udev.logger, "changed, but no devnum"); + } + }), + _ => {} + } + } + }, + error: |evlh, token, _, err| { + evlh.state() + .with_value(token, |state, udev| udev.handler.error(state, err)) + }, + } +} + +/// Handler for the `UdevBackend`, allows to open, close and update drm devices as they change during runtime. +pub trait UdevHandler + 'static, H: DrmHandler + '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<'a, S: Into>>(&mut self, state: S, device: &mut DrmDevice) + -> Option; + /// 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<'a, S: Into>>(&mut self, state: S, device: &StateToken>); + /// 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<'a, S: Into>>(&mut self, state: S, device: &StateToken>); + /// 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<'a, S: Into>>(&mut self, state: S, error: IoError); +} + +/// Returns the path of the primary gpu device if any +/// +/// Might be used for filtering in `UdevHandler::device_added` or for manual `DrmDevice` initialization +pub fn primary_gpu>(context: &Context, seat: S) -> UdevResult> { + let mut enumerator = Enumerator::new(context)?; + enumerator.match_subsystem("drm")?; + enumerator.match_sysname("card[0-9]*")?; + + let mut result = None; + for device in enumerator.scan_devices()? { + if device + .property_value("ID_SEAT") + .map(|x| x.to_os_string()) + .unwrap_or(OsString::from("seat0")) == *seat.as_ref() + { + if let Some(pci) = device.parent_with_subsystem(Path::new("pci"))? { + if let Some(id) = pci.attribute_value("boot_vga") { + if id == "1" { + result = Some(device); + } + } + } else if result.is_none() { + result = Some(device); + } + } + } + Ok(result.and_then(|device| device.devnode().map(PathBuf::from))) +} + +/// Returns the paths of all available gpu devices +/// +/// Might be used for manual `DrmDevice` initialization +pub fn all_gpus>(context: &Context, seat: S) -> UdevResult> { + let mut enumerator = Enumerator::new(context)?; + enumerator.match_subsystem("drm")?; + enumerator.match_sysname("card[0-9]*")?; + Ok(enumerator + .scan_devices()? + .filter(|device| { + device + .property_value("ID_SEAT") + .map(|x| x.to_os_string()) + .unwrap_or(OsString::from("seat0")) == *seat.as_ref() + }) + .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"), + } + } +} diff --git a/src/backend/winit.rs b/src/backend/winit.rs index f81b1d4..428a8d4 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -1,6 +1,5 @@ //! Implementation of backend traits for types provided by `winit` -use backend::{SeatInternal, TouchSlotInternal}; use backend::graphics::GraphicsBackend; use backend::graphics::egl::{self, EGLContext, EGLGraphicsBackend, GlAttributes, PixelFormat, PixelFormatRequirements, SwapBuffersError}; @@ -100,8 +99,9 @@ where /// Create a new `WinitGraphicsBackend`, which implements the `EGLGraphicsBackend` /// graphics backend trait, from a given `WindowBuilder` struct and a corresponding /// `WinitInputBackend`, which implements the `InputBackend` trait -pub fn init_from_builder(builder: WindowBuilder, logger: L) - -> Result<(WinitGraphicsBackend, WinitInputBackend)> +pub fn init_from_builder( + builder: WindowBuilder, logger: L +) -> Result<(WinitGraphicsBackend, WinitInputBackend)> where L: Into>, { @@ -121,8 +121,9 @@ where /// graphics backend trait, from a given `WindowBuilder` struct, as well as given /// `GlAttributes` for further customization of the rendering pipeline and a /// corresponding `WinitInputBackend`, which implements the `InputBackend` trait. -pub fn init_from_builder_with_gl_attr(builder: WindowBuilder, attributes: GlAttributes, logger: L) - -> Result<(WinitGraphicsBackend, WinitInputBackend)> +pub fn init_from_builder_with_gl_attr( + builder: WindowBuilder, attributes: GlAttributes, logger: L +) -> Result<(WinitGraphicsBackend, WinitInputBackend)> where L: Into>, { @@ -191,11 +192,12 @@ impl GraphicsBackend for WinitGraphicsBackend { self.window.head().set_cursor_position(x as i32, y as i32) } - fn set_cursor_representation(&self, cursor: Self::CursorFormat, _hotspot: (u32, u32)) - -> ::std::result::Result<(), ()> { + fn set_cursor_representation( + &self, cursor: &Self::CursorFormat, _hotspot: (u32, u32) + ) -> ::std::result::Result<(), ()> { // Cannot log this one, as `CursorFormat` is not `Debug` and should not be debug!(self.logger, "Changing cursor representation"); - self.window.head().set_cursor(cursor); + self.window.head().set_cursor(*cursor); Ok(()) } } @@ -229,7 +231,8 @@ impl EGLGraphicsBackend for WinitGraphicsBackend { } fn is_current(&self) -> bool { - self.window.rent(|egl| egl.head().is_current()) + self.window + .rent(|egl| egl.rent_all(|egl| egl.context.is_current() && egl.surface.is_current())) } unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> { @@ -372,10 +375,10 @@ impl PointerAxisEvent for WinitMouseWheelEvent { fn amount(&self) -> f64 { match (self.axis, self.delta) { - (Axis::Horizontal, MouseScrollDelta::LineDelta(x, _)) | - (Axis::Horizontal, MouseScrollDelta::PixelDelta(x, _)) => x as f64, - (Axis::Vertical, MouseScrollDelta::LineDelta(_, y)) | - (Axis::Vertical, MouseScrollDelta::PixelDelta(_, y)) => y as f64, + (Axis::Horizontal, MouseScrollDelta::LineDelta(x, _)) + | (Axis::Horizontal, MouseScrollDelta::PixelDelta(x, _)) => x as f64, + (Axis::Vertical, MouseScrollDelta::LineDelta(_, y)) + | (Axis::Vertical, MouseScrollDelta::PixelDelta(_, y)) => y as f64, } } } @@ -625,8 +628,10 @@ impl InputBackend for WinitInputBackend { trace!(logger, "Resizing window to {:?}", (x, y)); window.head().set_inner_size(x, y); window.rent(|egl| { - egl.rent(|surface| if let Some(wegl_surface) = (**surface).as_ref() { - wegl_surface.resize(x as i32, y as i32, 0, 0) + egl.rent(|surface| { + if let Some(wegl_surface) = (**surface).as_ref() { + wegl_surface.resize(x as i32, y as i32, 0, 0) + } }) }); } diff --git a/src/lib.rs b/src/lib.rs index 28c4cbe..a69d09f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ #![recursion_limit = "1024"] extern crate image; +#[macro_use] extern crate nix; #[macro_use] extern crate rental; @@ -22,6 +23,14 @@ extern crate drm; extern crate gbm; #[cfg(feature = "backend_libinput")] extern crate input; +#[cfg(feature = "udev")] +extern crate udev; +/* +#[cfg(feature = "backend_session_logind")] +extern crate dbus; +#[cfg(feature = "backend_session_logind")] +extern crate systemd; +*/ #[cfg(feature = "backend_winit")] extern crate wayland_client; #[cfg(feature = "backend_winit")] diff --git a/src/wayland/compositor/handlers.rs b/src/wayland/compositor/handlers.rs index 7cf4df6..da2e760 100644 --- a/src/wayland/compositor/handlers.rs +++ b/src/wayland/compositor/handlers.rs @@ -11,9 +11,10 @@ use wayland_server::protocol::{wl_compositor, wl_region, wl_subcompositor, wl_su * wl_compositor */ -pub(crate) fn compositor_bind(evlh: &mut EventLoopHandle, idata: &mut SurfaceIData, - _: &Client, compositor: wl_compositor::WlCompositor) -where +pub(crate) fn compositor_bind( + evlh: &mut EventLoopHandle, idata: &mut SurfaceIData, _: &Client, + compositor: wl_compositor::WlCompositor, +) where U: Default + 'static, R: Default + 'static, ID: 'static, @@ -65,8 +66,9 @@ pub struct SurfaceIData { } impl SurfaceIData { - pub(crate) fn make(log: ::slog::Logger, implem: SurfaceUserImplementation, idata: ID) - -> SurfaceIData { + pub(crate) fn make( + log: ::slog::Logger, implem: SurfaceUserImplementation, idata: ID + ) -> SurfaceIData { SurfaceIData { log: log, implem: implem, @@ -86,9 +88,7 @@ impl Clone for SurfaceIData { } pub(crate) fn surface_implementation( - ) - -> wl_surface::Implementation> -{ +) -> wl_surface::Implementation> { wl_surface::Implementation { attach: |_, _, _, surface, buffer, x, y| unsafe { SurfaceData::::with_data(surface, |d| { @@ -199,9 +199,9 @@ fn destroy_region(region: &wl_region::WlRegion) { * wl_subcompositor */ -pub(crate) fn subcompositor_bind(evlh: &mut EventLoopHandle, _: &mut (), _: &Client, - subcompositor: wl_subcompositor::WlSubcompositor) -where +pub(crate) fn subcompositor_bind( + evlh: &mut EventLoopHandle, _: &mut (), _: &Client, subcompositor: wl_subcompositor::WlSubcompositor +) where R: RoleType + Role + 'static, U: 'static, { diff --git a/src/wayland/compositor/mod.rs b/src/wayland/compositor/mod.rs index 6cdc9b1..2265d6c 100644 --- a/src/wayland/compositor/mod.rs +++ b/src/wayland/compositor/mod.rs @@ -314,11 +314,11 @@ where /// /// If the surface not managed by the CompositorGlobal that provided this token, this /// will panic (having more than one compositor is not supported). - pub fn with_surface_tree_upward(&self, surface: &wl_surface::WlSurface, initial: T, f: F) - -> Result<(), ()> + pub fn with_surface_tree_upward( + &self, surface: &wl_surface::WlSurface, initial: T, f: F + ) -> Result<(), ()> where - F: FnMut(&wl_surface::WlSurface, &mut SurfaceAttributes, &mut R, &T) - -> TraversalAction, + F: FnMut(&wl_surface::WlSurface, &mut SurfaceAttributes, &mut R, &T) -> TraversalAction, { assert!( resource_is_registered( @@ -340,11 +340,11 @@ where /// supposed to be drawn: top-most first. /// /// Behavior is the same as `with_surface_tree_upward`. - pub fn with_surface_tree_downward(&self, surface: &wl_surface::WlSurface, initial: T, f: F) - -> Result<(), ()> + pub fn with_surface_tree_downward( + &self, surface: &wl_surface::WlSurface, initial: T, f: F + ) -> Result<(), ()> where - F: FnMut(&wl_surface::WlSurface, &mut SurfaceAttributes, &mut R, &T) - -> TraversalAction, + F: FnMut(&wl_surface::WlSurface, &mut SurfaceAttributes, &mut R, &T) -> TraversalAction, { assert!( resource_is_registered( @@ -426,7 +426,6 @@ impl CompositorToken { unsafe { SurfaceData::::has_role::(surface) } } - /// Register that this surface has given role with default data /// /// Fails if the surface already has a role. @@ -454,8 +453,9 @@ impl CompositorToken { /// /// If the surface is not managed by the CompositorGlobal that provided this token, this /// will panic (having more than one compositor is not supported). - pub fn give_role_with(&self, surface: &wl_surface::WlSurface, data: RoleData) - -> Result<(), RoleData> + pub fn give_role_with( + &self, surface: &wl_surface::WlSurface, data: RoleData + ) -> Result<(), RoleData> where R: Role, { @@ -475,8 +475,9 @@ impl CompositorToken { /// /// If the surface is not managed by the CompositorGlobal that provided this token, this /// will panic (having more than one compositor is not supported). - pub fn with_role_data(&self, surface: &wl_surface::WlSurface, f: F) - -> Result + pub fn with_role_data( + &self, surface: &wl_surface::WlSurface, f: F + ) -> Result where R: Role, F: FnOnce(&mut RoleData) -> T, @@ -533,12 +534,12 @@ impl CompositorToken { /// It also returns the two global handles, in case you whish to remove these /// globals from the event loop in the future. pub fn compositor_init( - evl: &mut EventLoop, implem: SurfaceUserImplementation, idata: ID, logger: L) - -> ( - CompositorToken, - Global>, - Global, - ) + evl: &mut EventLoop, implem: SurfaceUserImplementation, idata: ID, logger: L +) -> ( + CompositorToken, + Global>, + Global, +) where L: Into>, U: Default + 'static, @@ -580,10 +581,10 @@ pub struct SurfaceUserImplementation { /// See [`wayland_server::protocol::wl_surface::Implementation::commit`](https://docs.rs/wayland-server/0.10.1/wayland_server/protocol/wl_surface/struct.Implementation.html#structfield.commit) /// for more details pub commit: fn( - evlh: &mut EventLoopHandle, - idata: &mut ID, - surface: &wl_surface::WlSurface, - token: CompositorToken, + evlh: &mut EventLoopHandle, + idata: &mut ID, + surface: &wl_surface::WlSurface, + token: CompositorToken, ), /// The client asks to be notified when would be a good time to update the contents of this surface /// @@ -593,11 +594,11 @@ pub struct SurfaceUserImplementation { /// See [`wayland_server::protocol::wl_surface::Implementation::frame`](https://docs.rs/wayland-server/0.10.1/wayland_server/protocol/wl_surface/struct.Implementation.html#structfield.frame) /// for more details pub frame: fn( - evlh: &mut EventLoopHandle, - idata: &mut ID, - surface: &wl_surface::WlSurface, - callback: wl_callback::WlCallback, - token: CompositorToken, + evlh: &mut EventLoopHandle, + idata: &mut ID, + surface: &wl_surface::WlSurface, + callback: wl_callback::WlCallback, + token: CompositorToken, ), } diff --git a/src/wayland/compositor/tree.rs b/src/wayland/compositor/tree.rs index 7cbecc9..10fd2a1 100644 --- a/src/wayland/compositor/tree.rs +++ b/src/wayland/compositor/tree.rs @@ -132,8 +132,9 @@ impl SurfaceData { /// Register that this surface has a role with given data /// /// Fails if it already has one and returns the data - pub unsafe fn give_role_with(surface: &wl_surface::WlSurface, data: RoleData) - -> Result<(), RoleData> + pub unsafe fn give_role_with( + surface: &wl_surface::WlSurface, data: RoleData + ) -> Result<(), RoleData> where R: Role, { @@ -158,8 +159,9 @@ impl SurfaceData { } /// Access to the role data - pub unsafe fn with_role_data(surface: &wl_surface::WlSurface, f: F) - -> Result + pub unsafe fn with_role_data( + surface: &wl_surface::WlSurface, f: F + ) -> Result where R: Role, F: FnOnce(&mut RoleData) -> T, @@ -177,8 +179,9 @@ impl + 'static> SurfaceData /// /// if this surface already has a role, does nothing and fails, otherwise /// its role is now to be a subsurface - pub unsafe fn set_parent(child: &wl_surface::WlSurface, parent: &wl_surface::WlSurface) - -> Result<(), ()> { + pub unsafe fn set_parent( + child: &wl_surface::WlSurface, parent: &wl_surface::WlSurface + ) -> Result<(), ()> { debug_assert!(child.status() == Liveness::Alive); debug_assert!(parent.status() == Liveness::Alive); @@ -246,9 +249,9 @@ impl + 'static> SurfaceData /// Reorders a surface relative to one of its sibling /// /// Fails if `relative_to` is not a sibling or parent of `surface`. - pub unsafe fn reorder(surface: &wl_surface::WlSurface, to: Location, - relative_to: &wl_surface::WlSurface) - -> Result<(), ()> { + pub unsafe fn reorder( + surface: &wl_surface::WlSurface, to: Location, relative_to: &wl_surface::WlSurface + ) -> Result<(), ()> { let parent = { let data_mutex = Self::get_data(surface); let data_guard = data_mutex.lock().unwrap(); @@ -316,17 +319,15 @@ impl SurfaceData { /// false will cause an early-stopping. pub unsafe fn map_tree(root: &wl_surface::WlSurface, initial: T, mut f: F, reverse: bool) where - F: FnMut(&wl_surface::WlSurface, &mut SurfaceAttributes, &mut R, &T) - -> TraversalAction, + F: FnMut(&wl_surface::WlSurface, &mut SurfaceAttributes, &mut R, &T) -> TraversalAction, { // helper function for recursion - unsafe fn map(surface: &wl_surface::WlSurface, - root: &wl_surface::WlSurface, initial: &T, f: &mut F, - reverse: bool) - -> bool + unsafe fn map( + surface: &wl_surface::WlSurface, root: &wl_surface::WlSurface, initial: &T, f: &mut F, + reverse: bool, + ) -> bool where - F: FnMut(&wl_surface::WlSurface, &mut SurfaceAttributes, &mut R, &T) - -> TraversalAction, + F: FnMut(&wl_surface::WlSurface, &mut SurfaceAttributes, &mut R, &T) -> TraversalAction, { // stop if we met the root, so to not deadlock/inifinte loop if surface.equals(root) { diff --git a/src/wayland/output/mod.rs b/src/wayland/output/mod.rs index 3caf46a..9404d64 100644 --- a/src/wayland/output/mod.rs +++ b/src/wayland/output/mod.rs @@ -113,11 +113,11 @@ impl Output { /// returns the state token allowing you to access it, as well as the global handle, /// in case you whish to remove this global in the future. pub fn new( - evl: &mut EventLoop, name: String, physical: PhysicalProperties, logger: L) - -> ( - StateToken, - Global>, - ) + evl: &mut EventLoop, name: String, physical: PhysicalProperties, logger: L + ) -> ( + StateToken, + Global>, + ) where L: Into>, { @@ -229,8 +229,10 @@ impl Output { /// internal list. /// /// By default, transform status is `Normal`, and scale is `1`. - pub fn change_current_state(&mut self, new_mode: Option, - new_transform: Option, new_scale: Option) { + pub fn change_current_state( + &mut self, new_mode: Option, new_transform: Option, + new_scale: Option, + ) { if let Some(mode) = new_mode { if self.modes.iter().find(|&m| *m == mode).is_none() { self.modes.push(mode); @@ -283,8 +285,9 @@ impl Output { } } -fn output_bind(evlh: &mut EventLoopHandle, token: &mut StateToken, _: &Client, - global: wl_output::WlOutput) { +fn output_bind( + evlh: &mut EventLoopHandle, token: &mut StateToken, _: &Client, global: wl_output::WlOutput +) { evlh.register(&global, output_implementation(), token.clone(), None); evlh.state().get_mut(token).new_global(global); } diff --git a/src/wayland/seat/keyboard.rs b/src/wayland/seat/keyboard.rs index 4d23023..0a9c309 100644 --- a/src/wayland/seat/keyboard.rs +++ b/src/wayland/seat/keyboard.rs @@ -66,9 +66,10 @@ struct KbdInternal { } impl KbdInternal { - fn new(rules: &str, model: &str, layout: &str, variant: &str, options: Option, - repeat_rate: i32, repeat_delay: i32) - -> Result { + fn new( + rules: &str, model: &str, layout: &str, variant: &str, options: Option, repeat_rate: i32, + repeat_delay: i32, + ) -> Result { // we create a new contex for each keyboard because libxkbcommon is actually NOT threadsafe // so confining it inside the KbdInternal allows us to use Rusts mutability rules to make // sure nothing goes wrong. @@ -168,10 +169,10 @@ pub enum Error { } /// Create a keyboard handler from a set of RMLVO rules -pub(crate) fn create_keyboard_handler(rules: &str, model: &str, layout: &str, variant: &str, - options: Option, repeat_delay: i32, repeat_rate: i32, - logger: &::slog::Logger) - -> Result { +pub(crate) fn create_keyboard_handler( + rules: &str, model: &str, layout: &str, variant: &str, options: Option, repeat_delay: i32, + repeat_rate: i32, logger: &::slog::Logger, +) -> Result { let log = logger.new(o!("smithay_module" => "xkbcommon_handler")); info!(log, "Initializing a xkbcommon handler with keymap query"; "rules" => rules, "model" => model, "layout" => layout, "variant" => variant, @@ -192,7 +193,6 @@ pub(crate) fn create_keyboard_handler(rules: &str, model: &str, layout: &str, va info!(log, "Loaded Keymap"; "name" => internal.keymap.layouts().next()); - // prepare a tempfile with the keymap, to send it to clients let mut keymap_file = tempfile().map_err(Error::IoError)?; let keymap_data = internal.keymap.get_as_string(xkb::KEYMAP_FORMAT_TEXT_V1); diff --git a/src/wayland/seat/mod.rs b/src/wayland/seat/mod.rs index 45612d2..afa5e0f 100644 --- a/src/wayland/seat/mod.rs +++ b/src/wayland/seat/mod.rs @@ -87,8 +87,9 @@ impl Seat { /// You are provided with the state token to retrieve it (allowing /// you to add or remove capabilities from it), and the global handle, /// in case you want to remove it. - pub fn new(evl: &mut EventLoop, name: String, logger: L) - -> (StateToken, Global>) + pub fn new( + evl: &mut EventLoop, name: String, logger: L + ) -> (StateToken, Global>) where L: Into>, { @@ -158,9 +159,10 @@ impl Seat { /// Calling this method on a seat that already has a keyboard capability /// will overwrite it, and will be seen by the clients as if the /// keyboard was unplugged and a new one was plugged. - pub fn add_keyboard(&mut self, model: &str, layout: &str, variant: &str, options: Option, - repeat_delay: i32, repeat_rate: i32) - -> Result { + pub fn add_keyboard( + &mut self, model: &str, layout: &str, variant: &str, options: Option, repeat_delay: i32, + repeat_rate: i32, + ) -> Result { let keyboard = self::keyboard::create_keyboard_handler( "evdev", // we need this one model, @@ -229,8 +231,9 @@ impl Seat { } } -fn seat_global_bind(evlh: &mut EventLoopHandle, token: &mut StateToken, _: &Client, - seat: wl_seat::WlSeat) { +fn seat_global_bind( + evlh: &mut EventLoopHandle, token: &mut StateToken, _: &Client, seat: wl_seat::WlSeat +) { evlh.register(&seat, seat_implementation(), token.clone(), None); let seat_mgr = evlh.state().get_mut(token); seat.name(seat_mgr.name.clone()); diff --git a/src/wayland/shell/mod.rs b/src/wayland/shell/mod.rs index 835e310..6a4ca1a 100644 --- a/src/wayland/shell/mod.rs +++ b/src/wayland/shell/mod.rs @@ -302,12 +302,12 @@ impl Clone for ShellSurfaceIData { /// globals from the event loop in the future. pub fn shell_init( evl: &mut EventLoop, token: CompositorToken, - implementation: ShellSurfaceUserImplementation, idata: SID, logger: L) - -> ( - StateToken>, - Global>, - Global>, - ) + implementation: ShellSurfaceUserImplementation, idata: SID, logger: L, +) -> ( + StateToken>, + Global>, + Global>, +) where U: 'static, R: Role + 'static, @@ -449,7 +449,8 @@ impl ShellClient { } match self.kind { ShellClientKind::Wl(ref shell) => { - let mutex = unsafe { &*(shell.get_user_data() as *mut self::wl_handlers::ShellUserData) }; + let mutex = + unsafe { &*(shell.get_user_data() as *mut self::wl_handlers::ShellUserData) }; let mut guard = mutex.lock().unwrap(); if guard.0.pending_ping == 0 { return Err(()); @@ -486,7 +487,8 @@ impl ShellClient { } match self.kind { ShellClientKind::Wl(ref shell) => { - let mutex = unsafe { &*(shell.get_user_data() as *mut self::wl_handlers::ShellUserData) }; + let mutex = + unsafe { &*(shell.get_user_data() as *mut self::wl_handlers::ShellUserData) }; let mut guard = mutex.lock().unwrap(); Ok(f(&mut guard.0.data)) } @@ -844,46 +846,44 @@ pub struct ShellSurfaceUserImplementation { /// /// You need to return a `ToplevelConfigure` from this function, which will be sent /// to the client to configure this surface - pub new_toplevel: fn( - evlh: &mut EventLoopHandle, - idata: &mut SID, - surface: ToplevelSurface, - ) -> ToplevelConfigure, + pub new_toplevel: + fn(evlh: &mut EventLoopHandle, idata: &mut SID, surface: ToplevelSurface) + -> ToplevelConfigure, /// A new popup surface was created /// /// You need to return a `PopupConfigure` from this function, which will be sent /// to the client to configure this surface pub new_popup: fn(evlh: &mut EventLoopHandle, idata: &mut SID, surface: PopupSurface) - -> PopupConfigure, + -> PopupConfigure, /// The client requested the start of an interactive move for this surface pub move_: fn( - evlh: &mut EventLoopHandle, - idata: &mut SID, - surface: ToplevelSurface, - seat: &wl_seat::WlSeat, - serial: u32, + evlh: &mut EventLoopHandle, + idata: &mut SID, + surface: ToplevelSurface, + seat: &wl_seat::WlSeat, + serial: u32, ), /// The client requested the start of an interactive resize for this surface /// /// The `edges` argument specifies which part of the window's border is being dragged. pub resize: fn( - evlh: &mut EventLoopHandle, - idata: &mut SID, - surface: ToplevelSurface, - seat: &wl_seat::WlSeat, - serial: u32, - edges: zxdg_toplevel_v6::ResizeEdge, + evlh: &mut EventLoopHandle, + idata: &mut SID, + surface: ToplevelSurface, + seat: &wl_seat::WlSeat, + serial: u32, + edges: zxdg_toplevel_v6::ResizeEdge, ), /// This popup requests a grab of the pointer /// /// This means it requests to be sent a `popup_done` event when the pointer leaves /// the grab area. pub grab: fn( - evlh: &mut EventLoopHandle, - idata: &mut SID, - surface: PopupSurface, - seat: &wl_seat::WlSeat, - serial: u32, + evlh: &mut EventLoopHandle, + idata: &mut SID, + surface: PopupSurface, + seat: &wl_seat::WlSeat, + serial: u32, ), /// A toplevel surface requested its display state to be changed /// @@ -899,26 +899,26 @@ pub struct ShellSurfaceUserImplementation { /// You are to answer with a `ToplevelConfigure` that will be sent to the client in /// response. pub change_display_state: fn( - evlh: &mut EventLoopHandle, - idata: &mut SID, - surface: ToplevelSurface, - maximized: Option, - minimized: Option, - fullscreen: Option, - output: Option<&wl_output::WlOutput>, + evlh: &mut EventLoopHandle, + idata: &mut SID, + surface: ToplevelSurface, + maximized: Option, + minimized: Option, + fullscreen: Option, + output: Option<&wl_output::WlOutput>, ) -> ToplevelConfigure, /// The client requests the window menu to be displayed on this surface at this location /// /// This menu belongs to the compositor. It is typically expected to contain options for /// control of the window (maximize/minimize/close/move/etc...). pub show_window_menu: fn( - evlh: &mut EventLoopHandle, - idata: &mut SID, - surface: ToplevelSurface, - seat: &wl_seat::WlSeat, - serial: u32, - x: i32, - y: i32, + evlh: &mut EventLoopHandle, + idata: &mut SID, + surface: ToplevelSurface, + seat: &wl_seat::WlSeat, + serial: u32, + x: i32, + y: i32, ), } diff --git a/src/wayland/shell/wl_handlers.rs b/src/wayland/shell/wl_handlers.rs index 7c5ab41..0255f07 100644 --- a/src/wayland/shell/wl_handlers.rs +++ b/src/wayland/shell/wl_handlers.rs @@ -10,10 +10,10 @@ use wayland_protocols::unstable::xdg_shell::v6::server::{zxdg_positioner_v6 as x use wayland_server::{Client, EventLoopHandle, Resource}; use wayland_server::protocol::{wl_output, wl_shell, wl_shell_surface, wl_surface}; -pub(crate) fn wl_shell_bind(evlh: &mut EventLoopHandle, - idata: &mut ShellSurfaceIData, - _: &Client, shell: wl_shell::WlShell) -where +pub(crate) fn wl_shell_bind( + evlh: &mut EventLoopHandle, idata: &mut ShellSurfaceIData, _: &Client, + shell: wl_shell::WlShell, +) where U: 'static, R: Role + 'static, CID: 'static, @@ -56,8 +56,7 @@ pub fn make_shell_client(resource: &wl_shell::WlShell) -> ShellClient { } fn shell_implementation( - ) - -> wl_shell::Implementation> +) -> wl_shell::Implementation> where U: 'static, R: Role + 'static, @@ -122,9 +121,9 @@ fn destroy_shell_surface(shell_surface: &wl_shell_surface::WlShellSurface) { ::std::mem::drop(surface); } -fn make_toplevel_handle(token: CompositorToken, - resource: &wl_shell_surface::WlShellSurface) - -> super::ToplevelSurface { +fn make_toplevel_handle( + token: CompositorToken, resource: &wl_shell_surface::WlShellSurface +) -> super::ToplevelSurface { let ptr = resource.get_user_data(); let &(ref wl_surface, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; super::ToplevelSurface { @@ -135,9 +134,9 @@ fn make_toplevel_handle(token: CompositorToken, } } -fn make_popup_handle(token: CompositorToken, - resource: &wl_shell_surface::WlShellSurface) - -> super::PopupSurface { +fn make_popup_handle( + token: CompositorToken, resource: &wl_shell_surface::WlShellSurface +) -> super::PopupSurface { let ptr = resource.get_user_data(); let &(ref wl_surface, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; super::PopupSurface { @@ -158,12 +157,11 @@ pub fn send_popup_configure(resource: &wl_shell_surface::WlShellSurface, configu resource.configure(wl_shell_surface::Resize::empty(), w, h); } -fn wl_handle_display_state_change(evlh: &mut EventLoopHandle, - idata: &ShellSurfaceIData, - shell_surface: &wl_shell_surface::WlShellSurface, - maximized: Option, minimized: Option, - fullscreen: Option, - output: Option<&wl_output::WlOutput>) { +fn wl_handle_display_state_change( + evlh: &mut EventLoopHandle, idata: &ShellSurfaceIData, + shell_surface: &wl_shell_surface::WlShellSurface, maximized: Option, minimized: Option, + fullscreen: Option, output: Option<&wl_output::WlOutput>, +) { let handle = make_toplevel_handle(idata.compositor_token, shell_surface); // handler callback let mut user_idata = idata.idata.borrow_mut(); @@ -181,10 +179,10 @@ fn wl_handle_display_state_change(evlh: &mut EventLoopHandle shell_surface.configure(wl_shell_surface::Resize::None, w, h); } -fn wl_set_parent(idata: &ShellSurfaceIData, - shell_surface: &wl_shell_surface::WlShellSurface, - parent: Option) -where +fn wl_set_parent( + idata: &ShellSurfaceIData, shell_surface: &wl_shell_surface::WlShellSurface, + parent: Option, +) where U: 'static, R: Role + 'static, CID: 'static, @@ -204,10 +202,10 @@ where .unwrap(); } -fn wl_ensure_toplevel(evlh: &mut EventLoopHandle, - idata: &ShellSurfaceIData, - shell_surface: &wl_shell_surface::WlShellSurface) -where +fn wl_ensure_toplevel( + evlh: &mut EventLoopHandle, idata: &ShellSurfaceIData, + shell_surface: &wl_shell_surface::WlShellSurface, +) where U: 'static, R: Role + 'static, CID: 'static, @@ -263,8 +261,7 @@ where } fn shell_surface_implementation( - ) - -> wl_shell_surface::Implementation> +) -> wl_shell_surface::Implementation> where U: 'static, R: Role + 'static, @@ -274,7 +271,8 @@ where { wl_shell_surface::Implementation { pong: |evlh, idata, _, shell_surface, serial| { - let &(_, ref shell) = unsafe { &*(shell_surface.get_user_data() as *mut ShellSurfaceUserData) }; + let &(_, ref shell) = + unsafe { &*(shell_surface.get_user_data() as *mut ShellSurfaceUserData) }; let valid = { let mutex = unsafe { &*(shell.get_user_data() as *mut ShellUserData) }; let mut guard = mutex.lock().unwrap(); diff --git a/src/wayland/shell/xdg_handlers.rs b/src/wayland/shell/xdg_handlers.rs index 66b5380..203d665 100644 --- a/src/wayland/shell/xdg_handlers.rs +++ b/src/wayland/shell/xdg_handlers.rs @@ -10,10 +10,10 @@ use wayland_protocols::unstable::xdg_shell::v6::server::{zxdg_popup_v6, zxdg_pos use wayland_server::{Client, EventLoopHandle, Resource}; use wayland_server::protocol::{wl_output, wl_surface}; -pub(crate) fn xdg_shell_bind(evlh: &mut EventLoopHandle, - idata: &mut ShellSurfaceIData, - _: &Client, shell: zxdg_shell_v6::ZxdgShellV6) -where +pub(crate) fn xdg_shell_bind( + evlh: &mut EventLoopHandle, idata: &mut ShellSurfaceIData, _: &Client, + shell: zxdg_shell_v6::ZxdgShellV6, +) where U: 'static, R: Role + 'static, CID: 'static, @@ -53,8 +53,7 @@ pub(crate) fn make_shell_client(resource: &zxdg_shell_v6::ZxdgShellV6) -> Sh } fn shell_implementation( - ) - -> zxdg_shell_v6::Implementation> +) -> zxdg_shell_v6::Implementation> where U: 'static, R: Role + 'static, @@ -104,11 +103,10 @@ where ); return; } - xdg_surface.set_user_data( - Box::into_raw(Box::new((unsafe { wl_surface.clone_unchecked() }, unsafe { - shell.clone_unchecked() - }))) as *mut _, - ); + xdg_surface.set_user_data(Box::into_raw(Box::new(( + unsafe { wl_surface.clone_unchecked() }, + unsafe { shell.clone_unchecked() }, + ))) as *mut _); evlh.register( &xdg_surface, surface_implementation(), @@ -151,30 +149,34 @@ fn destroy_positioner(positioner: &zxdg_positioner_v6::ZxdgPositionerV6) { fn positioner_implementation() -> zxdg_positioner_v6::Implementation<()> { zxdg_positioner_v6::Implementation { destroy: |_, _, _, _| {}, - set_size: |_, _, _, positioner, width, height| if width < 1 || height < 1 { - positioner.post_error( - zxdg_positioner_v6::Error::InvalidInput as u32, - "Invalid size for positioner.".into(), - ); - } else { - let ptr = positioner.get_user_data(); - let state = unsafe { &mut *(ptr as *mut PositionerState) }; - state.rect_size = (width, height); + set_size: |_, _, _, positioner, width, height| { + if width < 1 || height < 1 { + positioner.post_error( + zxdg_positioner_v6::Error::InvalidInput as u32, + "Invalid size for positioner.".into(), + ); + } else { + let ptr = positioner.get_user_data(); + let state = unsafe { &mut *(ptr as *mut PositionerState) }; + state.rect_size = (width, height); + } }, - set_anchor_rect: |_, _, _, positioner, x, y, width, height| if width < 1 || height < 1 { - positioner.post_error( - zxdg_positioner_v6::Error::InvalidInput as u32, - "Invalid size for positioner's anchor rectangle.".into(), - ); - } else { - let ptr = positioner.get_user_data(); - let state = unsafe { &mut *(ptr as *mut PositionerState) }; - state.anchor_rect = Rectangle { - x, - y, - width, - height, - }; + set_anchor_rect: |_, _, _, positioner, x, y, width, height| { + if width < 1 || height < 1 { + positioner.post_error( + zxdg_positioner_v6::Error::InvalidInput as u32, + "Invalid size for positioner's anchor rectangle.".into(), + ); + } else { + let ptr = positioner.get_user_data(); + let state = unsafe { &mut *(ptr as *mut PositionerState) }; + state.anchor_rect = Rectangle { + x, + y, + width, + height, + }; + } }, set_anchor: |_, _, _, positioner, anchor| { use self::zxdg_positioner_v6::Anchor; @@ -228,15 +230,15 @@ fn destroy_surface(surface: &zxdg_surface_v6::ZxdgSurfaceV6) { let ptr = surface.get_user_data(); surface.set_user_data(::std::ptr::null_mut()); // drop the state - let data = - unsafe { Box::from_raw(ptr as *mut (zxdg_surface_v6::ZxdgSurfaceV6, zxdg_shell_v6::ZxdgShellV6)) }; + let data = unsafe { + Box::from_raw(ptr as *mut (zxdg_surface_v6::ZxdgSurfaceV6, zxdg_shell_v6::ZxdgShellV6)) + }; // explicit call to drop to not forget what we're doing here ::std::mem::drop(data); } fn surface_implementation( - ) - -> zxdg_surface_v6::Implementation> +) -> zxdg_surface_v6::Implementation> where U: 'static, R: Role + 'static, @@ -417,9 +419,9 @@ fn destroy_toplevel(surface: &zxdg_toplevel_v6::ZxdgToplevelV6) { } // Utility functions allowing to factor out a lot of the upcoming logic -fn with_surface_toplevel_data(idata: &ShellSurfaceIData, - toplevel: &zxdg_toplevel_v6::ZxdgToplevelV6, f: F) -where +fn with_surface_toplevel_data( + idata: &ShellSurfaceIData, toplevel: &zxdg_toplevel_v6::ZxdgToplevelV6, f: F +) where U: 'static, R: Role + 'static, CID: 'static, @@ -438,13 +440,11 @@ where .expect("xdg_toplevel exists but surface has not shell_surface role?!"); } -fn xdg_handle_display_state_change(evlh: &mut EventLoopHandle, - idata: &ShellSurfaceIData, - toplevel: &zxdg_toplevel_v6::ZxdgToplevelV6, - maximized: Option, minimized: Option, - fullscreen: Option, - output: Option<&wl_output::WlOutput>) -where +fn xdg_handle_display_state_change( + evlh: &mut EventLoopHandle, idata: &ShellSurfaceIData, + toplevel: &zxdg_toplevel_v6::ZxdgToplevelV6, maximized: Option, minimized: Option, + fullscreen: Option, output: Option<&wl_output::WlOutput>, +) where U: 'static, R: Role + 'static, CID: 'static, @@ -467,11 +467,10 @@ where send_toplevel_configure(idata.compositor_token, toplevel, configure); } - -pub fn send_toplevel_configure(token: CompositorToken, - resource: &zxdg_toplevel_v6::ZxdgToplevelV6, - configure: ToplevelConfigure) -where +pub fn send_toplevel_configure( + token: CompositorToken, resource: &zxdg_toplevel_v6::ZxdgToplevelV6, + configure: ToplevelConfigure, +) where U: 'static, R: Role + 'static, ID: 'static, @@ -497,9 +496,9 @@ where .expect("xdg_toplevel exists but surface has not shell_surface role?!"); } -fn make_toplevel_handle(token: CompositorToken, - resource: &zxdg_toplevel_v6::ZxdgToplevelV6) - -> super::ToplevelSurface { +fn make_toplevel_handle( + token: CompositorToken, resource: &zxdg_toplevel_v6::ZxdgToplevelV6 +) -> super::ToplevelSurface { let ptr = resource.get_user_data(); let &(ref wl_surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; super::ToplevelSurface { @@ -511,8 +510,7 @@ fn make_toplevel_handle(token: CompositorToken, } fn toplevel_implementation( - ) - -> zxdg_toplevel_v6::Implementation> +) -> zxdg_toplevel_v6::Implementation> where U: 'static, R: Role + 'static, @@ -546,8 +544,9 @@ where with_surface_toplevel_data(idata, toplevel, |toplevel_data| { toplevel_data.parent = parent.map(|toplevel_surface_parent| { let parent_ptr = toplevel_surface_parent.get_user_data(); - let &(ref parent_surface, _) = - unsafe { &*(parent_ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) }; + let &(ref parent_surface, _) = unsafe { + &*(parent_ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) + }; unsafe { parent_surface.clone_unchecked() } }) }); @@ -620,10 +619,9 @@ fn destroy_popup(surface: &zxdg_popup_v6::ZxdgPopupV6) { ::std::mem::drop(data); } -pub(crate) fn send_popup_configure(token: CompositorToken, - resource: &zxdg_popup_v6::ZxdgPopupV6, - configure: PopupConfigure) -where +pub(crate) fn send_popup_configure( + token: CompositorToken, resource: &zxdg_popup_v6::ZxdgPopupV6, configure: PopupConfigure +) where U: 'static, R: Role + 'static, ID: 'static, @@ -641,8 +639,9 @@ where .expect("xdg_toplevel exists but surface has not shell_surface role?!"); } -fn make_popup_handle(token: CompositorToken, resource: &zxdg_popup_v6::ZxdgPopupV6) - -> super::PopupSurface { +fn make_popup_handle( + token: CompositorToken, resource: &zxdg_popup_v6::ZxdgPopupV6 +) -> super::PopupSurface { let ptr = resource.get_user_data(); let &(ref wl_surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; super::PopupSurface { @@ -654,8 +653,7 @@ fn make_popup_handle(token: CompositorToken, resource: &zx } fn popup_implementation( - ) - -> zxdg_popup_v6::Implementation> +) -> zxdg_popup_v6::Implementation> where U: 'static, R: Role + 'static, diff --git a/src/wayland/shm/mod.rs b/src/wayland/shm/mod.rs index e60a02b..0346079 100644 --- a/src/wayland/shm/mod.rs +++ b/src/wayland/shm/mod.rs @@ -61,7 +61,6 @@ //! //! If you are already using an handler for this signal, you probably don't want to use this handler. - use self::pool::{Pool, ResizeError}; use std::rc::Rc; use std::sync::Arc; @@ -89,8 +88,9 @@ pub struct ShmGlobalData { /// The global is directly registered into the eventloop, and this function /// returns the global handle, in case you whish to remove this global in /// the future. -pub fn init_shm_global(evl: &mut EventLoop, mut formats: Vec, logger: L) - -> Global +pub fn init_shm_global( + evl: &mut EventLoop, mut formats: Vec, logger: L +) -> Global where L: Into>, { diff --git a/src/wayland/shm/pool.rs b/src/wayland/shm/pool.rs index 20faa62..b70442a 100644 --- a/src/wayland/shm/pool.rs +++ b/src/wayland/shm/pool.rs @@ -1,5 +1,3 @@ - - use nix::{libc, unistd}; use nix::sys::mman; use nix::sys::signal::{self, SigAction, SigHandler, Signal}; diff --git a/vagga.yaml b/vagga.yaml new file mode 100644 index 0000000..48e4845 --- /dev/null +++ b/vagga.yaml @@ -0,0 +1,77 @@ +minimum-vagga: v0.5.0 + +containers: + base: + auto-clean: true + setup: + - !UbuntuRelease { codename: artful } + - !UbuntuUniverse + - !Install [build-essential, wget, curl, pkg-config, file, openssl, sudo, ca-certificates, libssl-dev, cmake, libudev-dev, libgbm-dev, libxkbcommon-dev, libegl1-mesa-dev, libwayland-dev, libinput-dev] + + stable: + auto-clean: true + environ: + HOME: /work/.vagga/stable-home + PATH: /bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/work/.vagga/stable-home/.cargo/bin:/work/.vagga/stable-home/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/ + RUST_BACKTRACE: 1 + XDG_RUNTIME_DIR: /work/.vagga/stable-home + setup: + - !Container base + - !Env HOME: /work/.vagga/stable-home + - !Sh curl https://sh.rustup.rs -sSf | sh -s -- -y --default-host x86_64-unknown-linux-gnu --default-toolchain stable --no-modify-path + + beta: + auto-clean: true + environ: + HOME: /work/.vagga/beta-home + PATH: /bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/work/.vagga/beta-home/.cargo/bin:/work/.vagga/beta-home/.rustup/toolchains/beta-x86_64-unknown-linux-gnu/bin/ + RUST_BACKTRACE: 1 + XDG_RUNTIME_DIR: /work/.vagga/beta-home + setup: + - !Container base + - !Env HOME: /work/.vagga/beta-home + - !Sh curl https://sh.rustup.rs -sSf | sh -s -- -y --default-host x86_64-unknown-linux-gnu --default-toolchain beta --no-modify-path + + nightly: + auto-clean: true + environ: + HOME: /work/.vagga/nightly-home + PATH: /bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/work/.vagga/nightly-home/.cargo/bin:/work/.vagga/nightly-home/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/ + RUST_BACKTRACE: 1 + XDG_RUNTIME_DIR: /work/.vagga/nightly-home + setup: + - !Container base + - !Env HOME: /work/.vagga/nightly-home + - !Sh curl https://sh.rustup.rs -sSf | sh -s -- -y --default-host x86_64-unknown-linux-gnu --default-toolchain nightly --no-modify-path + +commands: + update-stable: !Command + description: Update container + container: stable + run: rustup update + + update-beta: !Command + description: Update container + container: beta + run: rustup update + + update-nightly: !Command + description: Update container + container: nightly + run: | + rustup update + + cargo-stable: !Command + description: Run cargo + container: stable + run: ["/work/.vagga/stable-home/.cargo/bin/cargo"] + + cargo-beta: !Command + description: Run cargo + container: beta + run: ["/work/.vagga/beta-home/.cargo/bin/cargo"] + + cargo-nightly: !Command + description: Run cargo + container: nightly + run: ["/work/.vagga/nightly-home/.cargo/bin/cargo"]