diff --git a/src/backend/drm/backend.rs b/src/backend/drm/backend.rs index e784321..9343003 100644 --- a/src/backend/drm/backend.rs +++ b/src/backend/drm/backend.rs @@ -375,6 +375,7 @@ impl DrmBackend { Ok(()) } + /// Returns the crtc id used by this backend pub fn crtc(&self) -> crtc::Handle { self.crtc } diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index 1b8bacd..c603463 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -457,14 +457,20 @@ impl + Borrow + 'static> DrmDevice { 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> @@ -474,6 +480,11 @@ impl + Borrow + 'static> DrmDevice { } } + /// 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); @@ -517,12 +528,20 @@ pub trait DrmHandler + 'static> { /// /// The `id` argument is the `Id` of the `DrmBackend` that finished rendering, /// check using `DrmBackend::is`. + /// + /// ## 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. + /// 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); } @@ -610,6 +629,5 @@ impl + 'static> SessionObserver for StateToken(S); impl From for LibinputSessionInterface { @@ -585,6 +587,10 @@ impl libinput::LibinputInterface for LibinputSessionInterface { } } +/// 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> { diff --git a/src/backend/session/direct.rs b/src/backend/session/direct.rs index 3212a38..f72acf7 100644 --- a/src/backend/session/direct.rs +++ b/src/backend/session/direct.rs @@ -1,3 +1,51 @@ +//! +//! 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 std::io::Result as IoResult; use std::path::Path; use std::os::unix::io::RawFd; @@ -17,6 +65,7 @@ use libudev::Context; use super::{AsErrno, Session, SessionNotifier, SessionObserver}; +#[allow(dead_code)] mod tty { ioctl!(bad read kd_get_mode with 0x4B3B; i16); ioctl!(bad write_int kd_set_mode with 0x4B3A); @@ -59,60 +108,17 @@ mod tty { } } - -// on freebsd and dragonfly -#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] -const DRM_MAJOR: u64 = 145; - -// on netbsd -#[cfg(target_os = "netbsd")] -const DRM_MAJOR: u64 = 34; - -// on openbsd (32 & 64 bit) -#[cfg(all(target_os = "openbsd", target_pointer_width = "32"))] -const DRM_MAJOR: u64 = 88; -#[cfg(all(target_os = "openbsd", target_pointer_width = "64"))] -const DRM_MAJOR: u64 = 87; - -// on linux/android -#[cfg(any(target_os = "linux", target_os = "android"))] -const DRM_MAJOR: u64 = 226; - #[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_drm_device(dev: dev_t, _path: &Path) -> bool { - major(dev) == DRM_MAJOR -} - #[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_drm_device(dev: dev_t, path: &Path) -> bool { - let udev = match Context::new() { - Ok(context) => context, - Err(_) => return major(dev) == DRM_MAJOR, - }; - - let device = match udev.device_from_syspath(path) { - Ok(device) => device, - Err(_) => return major(dev) == DRM_MAJOR, - }; - - if let Some(subsystem) = device.subsystem() { - subsystem == "drm" - } else { - major(dev) == DRM_MAJOR - } -} - #[cfg(feature = "backend_session_udev")] fn is_tty_device(dev: dev_t, path: Option<&Path>) -> bool { match path { @@ -138,6 +144,7 @@ fn is_tty_device(dev: dev_t, path: Option<&Path>) -> bool { } } +/// `Session` via the virtual terminal direct kernel interface pub struct DirectSession { tty: RawFd, active: Arc, @@ -146,6 +153,7 @@ pub struct DirectSession { logger: ::slog::Logger, } +/// `SessionNotifier` via the virtual terminal direct kernel interface pub struct DirectSessionNotifier { tty: RawFd, active: Arc, @@ -155,6 +163,9 @@ pub struct DirectSessionNotifier { } 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> @@ -244,6 +255,11 @@ impl DirectSession { 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 { @@ -323,6 +339,11 @@ impl SessionNotifier for DirectSessionNotifier { } } +/// 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 @@ -332,6 +353,7 @@ where 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()); } } @@ -339,7 +361,9 @@ where 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"); } @@ -347,6 +371,7 @@ where 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) } diff --git a/src/backend/session/logind.rs b/src/backend/session/logind.rs index e69de29..2df3563 100644 --- a/src/backend/session/logind.rs +++ b/src/backend/session/logind.rs @@ -0,0 +1,27 @@ +use dbus::{BusType, Connection as DbusConnection}; +use systemd::login as logind; + +pub struct LogindSession { + dbus: DbusConnection, +} + +impl Session for LogindSession { + +} + +impl LogindSession { + pub fn new() -> Result { + let session = logind::get_session(None)?; + let vt = logind::get_vt(&session)?; + let seat = logind::get_seat(&session)?; + + let dbus = DbusConnection::get_private(BusType::System)?; + + } +} + +error_chain! { + errors { + + } +} diff --git a/src/backend/session/mod.rs b/src/backend/session/mod.rs index 5a297ca..67aab3d 100644 --- a/src/backend/session/mod.rs +++ b/src/backend/session/mod.rs @@ -1,3 +1,15 @@ +//! +//! 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 std::path::Path; use std::sync::{Arc, Mutex}; use std::rc::Rc; @@ -6,28 +18,62 @@ use std::os::unix::io::RawFd; use nix::fcntl::OFlag; 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 { + /// Registers a given `SessionObserver`. + /// + /// Returns an id of the inserted observer, can be used to remove it again. fn register(&mut self, signal: S) -> usize; + /// Removes an observer by its given id from `SessionNotifier::register`. fn unregister(&mut self, signal: usize); + /// 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>); } @@ -91,7 +137,9 @@ impl Session for Arc> { } } +/// 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; } diff --git a/src/backend/udev.rs b/src/backend/udev.rs index e7aac4a..daf8754 100644 --- a/src/backend/udev.rs +++ b/src/backend/udev.rs @@ -1,3 +1,14 @@ +//! +//! 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 libudev::{Context, MonitorBuilder, MonitorSocket, Event, EventType, Enumerator, Result as UdevResult}; use nix::fcntl; use nix::sys::stat::{dev_t, fstat}; @@ -14,6 +25,11 @@ use wayland_server::sources::{FdEventSource, FdEventSourceImpl, FdInterest}; use ::backend::drm::{DrmDevice, DrmBackend, DrmHandler, drm_device_bind}; use ::backend::session::{Session, SessionObserver}; +/// 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 + 'static, H: DrmHandler + 'static, S: Session + 'static, T: UdevHandler + 'static> { devices: HashMap>, FdEventSource<(StateToken>, H)>)>, monitor: MonitorSocket, @@ -23,6 +39,14 @@ pub struct UdevBackend + 'static, H: DrmHandler + 'stat } impl + 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, @@ -113,6 +137,14 @@ impl + Borrow + 'static, H: DrmHandler + 'sta })) } + /// 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 (_, (mut device, event_source)) in self.devices.drain() { @@ -147,6 +179,10 @@ impl + 'static, H: DrmHandler + 'static, S: Session + ' } } +/// 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 @@ -275,13 +311,41 @@ where } } +/// 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")?; @@ -304,6 +368,9 @@ pub fn primary_gpu>(context: &Context, seat: S) -> UdevResult>(context: &Context, seat: S) -> UdevResult> { let mut enumerator = Enumerator::new(context)?; enumerator.match_subsystem("drm")?;