wayland: output global
This commit is contained in:
parent
51886fbf00
commit
9e809f85dd
|
@ -4,6 +4,7 @@
|
|||
//! and the clients.
|
||||
|
||||
pub mod compositor;
|
||||
pub mod output;
|
||||
pub mod seat;
|
||||
pub mod shm;
|
||||
pub mod shell;
|
||||
|
|
|
@ -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<wl_output::WlOutput>,
|
||||
physical: PhysicalProperties,
|
||||
location: (i32, i32),
|
||||
transform: wl_output::Transform,
|
||||
scale: i32,
|
||||
modes: Vec<Mode>,
|
||||
current_mode: Option<Mode>,
|
||||
preferred_mode: Option<Mode>,
|
||||
}
|
||||
|
||||
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<L>(
|
||||
evl: &mut EventLoop, name: String, physical: PhysicalProperties, logger: L)
|
||||
-> (
|
||||
StateToken<Output>,
|
||||
Global<wl_output::WlOutput, StateToken<Output>>,
|
||||
)
|
||||
where
|
||||
L: Into<Option<::slog::Logger>>,
|
||||
{
|
||||
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<Mode>,
|
||||
new_transform: Option<wl_output::Transform>, new_scale: Option<i32>) {
|
||||
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<Output>, _: &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<StateToken<Output>> {
|
||||
wl_output::Implementation {
|
||||
release: |evlh, token, _, output| {
|
||||
evlh.state()
|
||||
.get_mut(token)
|
||||
.instances
|
||||
.retain(|o| !o.equals(output));
|
||||
},
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue