From 9e809f85dde463ba9394c2fa05aa10b37897ed34 Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Sun, 1 Oct 2017 19:12:05 +0200 Subject: [PATCH] wayland: output global --- src/wayland/mod.rs | 1 + src/wayland/output/mod.rs | 301 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 302 insertions(+) create mode 100644 src/wayland/output/mod.rs diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index 5432f40..aa808ce 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -4,6 +4,7 @@ //! and the clients. pub mod compositor; +pub mod output; pub mod seat; pub mod shm; pub mod shell; diff --git a/src/wayland/output/mod.rs b/src/wayland/output/mod.rs new file mode 100644 index 0000000..6dfd1c9 --- /dev/null +++ b/src/wayland/output/mod.rs @@ -0,0 +1,301 @@ +//! Output advertizing capabilities +//! +//! This module provides a type helping you to handle the advertizing +//! of your compositor's output and their capabilities to your client, +//! as well as mapping your clients output request to your physical +//! outputs. +//! +//! # How to use it +//! +//! You need to instanciate an `Output` for each output global you want +//! to advertize to clients. +//! +//! Just insert it in your event loop using the `Output::new(..)` method. +//! It returns a state token that gives you access to the `Output` in order +//! to change it if needed (if the current resolution mode changes for example), +//! it'll automatically forward any changes to the clients. +//! +//! ``` +//! # extern crate wayland_server; +//! # extern crate smithay; +//! use smithay::wayland::output::{Output, PhysicalProperties, Mode}; +//! use wayland_server::protocol::wl_output; +//! +//! # fn main() { +//! # let (display, mut event_loop) = wayland_server::create_display(); +//! // Insert the Output with given name and physical properties +//! let (output_state_token, _output_global) = Output::new( +//! &mut event_loop, // the event loop +//! "output-0".into(), // the name of this output, +//! PhysicalProperties { +//! width: 200, // width in mm +//! height: 150, // height in mm, +//! subpixel: wl_output::Subpixel::HorizontalRgb, // subpixel information +//! maker: "Screens Inc".into(), // manufacturer of the monitor +//! model: "Monitor Ultra".into(), // model of the monitor +//! }, +//! None // insert a logger here +//! ); +//! // Now you can configure it +//! { +//! let output = event_loop.state().get_mut(&output_state_token); +//! // set the current state +//! output.change_current_state( +//! Some(Mode { width: 1902, height: 1080, refresh: 60000 }), // the resolution mode, +//! Some(wl_output::Transform::Normal), // global screen transformation +//! Some(1), // global screen scaling factor +//! ); +//! // set the prefered mode +//! output.set_preferred(Mode { width: 1920, height: 1080, refresh: 60000 }); +//! // add other supported modes +//! output.add_mode(Mode { width: 800, height: 600, refresh: 60000 }); +//! output.add_mode(Mode { width: 1024, height: 768, refresh: 60000 }); +//! } +//! # } +//! ``` + +use wayland_server::{Client, EventLoop, EventLoopHandle, Global, Liveness, Resource, StateToken}; +use wayland_server::protocol::wl_output; + +#[derive(Copy, Clone, PartialEq)] +/// An output mode +/// +/// A possible combination of dimensions and refresh rate for an output. +/// +/// This should only describe the characteristics of the video driver, +/// not taking into account any global scaling. +pub struct Mode { + /// The width in pixels + pub width: i32, + /// The height in pixels + pub height: i32, + /// The refresh rate in mili-Hertz + /// + /// `1000` is one fps (frame per second), `2000` is 2 fps, etc... + pub refresh: i32, +} + +/// The physical properties of an output +pub struct PhysicalProperties { + /// The width in milimeters + pub width: i32, + /// The height in milimeters + pub height: i32, + /// The subpixel geometry + pub subpixel: wl_output::Subpixel, + /// Textual representation of the manufacturer + pub maker: String, + /// Textual representation of the model + pub model: String, +} + +/// An output as seen by the clients +/// +/// This handle is stored in the events loop, and allows you to notify clients +/// about any change in the properties of this output. +pub struct Output { + name: String, + log: ::slog::Logger, + instances: Vec, + physical: PhysicalProperties, + location: (i32, i32), + transform: wl_output::Transform, + scale: i32, + modes: Vec, + current_mode: Option, + preferred_mode: Option, +} + +impl Output { + /// Create a new output global with given name and physical properties + /// + /// The global is directly registered into the eventloop, and this function + /// 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>, + ) + where + L: Into>, + { + let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "output_handler")); + + info!(log, "Creating new wl_output"; "name" => &name); + + let token = evl.state().insert(Output { + name: name, + log: log, + instances: Vec::new(), + physical: physical, + location: (0, 0), + transform: wl_output::Transform::Normal, + scale: 1, + modes: Vec::new(), + current_mode: None, + preferred_mode: None, + }); + + let global = evl.register_global(3, output_bind, token.clone()); + + (token, global) + } + + fn new_global(&mut self, output: wl_output::WlOutput) { + trace!(self.log, "New global instanciated."); + + if self.modes.is_empty() { + warn!(self.log, "Output is used with no modes set"; "name" => &self.name); + } + if self.current_mode.is_none() { + warn!(self.log, "Output is used with no current mod set"; "name" => &self.name); + } + if self.preferred_mode.is_none() { + warn!(self.log, "Output is used with not preferred mode set"; "name" => &self.name); + } + + self.send_geometry(&output); + for &mode in &self.modes { + let mut flags = wl_output::Mode::empty(); + if Some(mode) == self.current_mode { + flags |= wl_output::Current; + } + if Some(mode) == self.preferred_mode { + flags |= wl_output::Preferred; + } + output.mode(flags, mode.width, mode.height, mode.refresh); + } + if output.version() >= 2 { + output.scale(self.scale); + output.done(); + } + + self.instances.push(output); + } + + fn send_geometry(&self, output: &wl_output::WlOutput) { + output.geometry( + self.location.0, + self.location.1, + self.physical.width, + self.physical.height, + self.physical.subpixel, + self.physical.maker.clone(), + self.physical.model.clone(), + self.transform, + ); + } + + /// Sets the preferred mode of this output + /// + /// If the provided mode was not previously known to this output, it is added to its + /// internal list. + pub fn set_preferred(&mut self, mode: Mode) { + self.preferred_mode = Some(mode); + if self.modes.iter().find(|&m| *m == mode).is_none() { + self.modes.push(mode); + } + } + + /// Adds a mode to the list of known modes to this output + pub fn add_mode(&mut self, mode: Mode) { + if self.modes.iter().find(|&m| *m == mode).is_none() { + self.modes.push(mode); + } + } + + /// Removes a mode from the list of known modes + /// + /// It will not de-advertize it from existing clients (the protocol does not + /// allow it), but it won't be advertized to now clients from now on. + pub fn delete_mode(&mut self, mode: Mode) { + self.modes.retain(|&m| m != mode); + if self.current_mode == Some(mode) { + self.current_mode = None; + } + if self.preferred_mode == Some(mode) { + self.preferred_mode = None; + } + } + + /// Change the current state of this output + /// + /// You can changed the current mode, transform status or scale of this output. Providing + /// `None` to any of these field means that the value does not change. + /// + /// If the provided mode was not previously known to this output, it is added to its + /// 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) { + if let Some(mode) = new_mode { + if self.modes.iter().find(|&m| *m == mode).is_none() { + self.modes.push(mode); + } + self.current_mode = new_mode; + } + if let Some(transform) = new_transform { + self.transform = transform; + } + if let Some(scale) = new_scale { + self.scale = scale; + } + let mut flags = wl_output::Current; + if self.preferred_mode == new_mode { + flags |= wl_output::Preferred; + } + for output in &self.instances { + if let Some(mode) = new_mode { + output.mode(flags, mode.width, mode.height, mode.refresh); + } + if new_transform.is_some() { + self.send_geometry(output); + } + if let Some(scale) = new_scale { + if output.version() >= 2 { + output.scale(scale); + } + } + if output.version() >= 2 { + output.done(); + } + } + } + + /// Chech is given wl_output instance is managed by this `Output`. + pub fn owns(&self, output: &wl_output::WlOutput) -> bool { + self.instances.iter().find(|&o| o.equals(output)).is_some() + } + + /// Cleanup internal `wl_output` instances list + /// + /// Clients do not necessarily notify the server on the destruction + /// of their `wl_output` instances. This can lead to accumulation of + /// stale values in the internal instances list. This methods delete + /// them. + /// + /// It can be good to call this regularly (but not necessarily very often). + pub fn cleanup(&mut self) { + self.instances.retain(|o| o.status() == Liveness::Alive); + } +} + +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); +} + +fn output_implementation() -> wl_output::Implementation> { + wl_output::Implementation { + release: |evlh, token, _, output| { + evlh.state() + .get_mut(token) + .instances + .retain(|o| !o.equals(output)); + }, + } +}