🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
//! Output
//!
//! This module provides a type helping you to abstract over various
//! properties, that make up an [`Output`] of your compositor.
//!
//! # How to use it
//!
//! You need to instantiate an [`Output`].
//!
//! To advertise a new output global to wayland clients take a look at [`crate::wayland::output`],
//! you need to have the `wayland_frontend` feature enabled to use it.
//!
//! Additionally outputs are used to by the desktop abstractions (see [`crate::desktop`], needs the
//! `desktop` feature) to represent views into your window grid.
//!
//! You can use the returned [`Output`] to change
//! the properties of your output (if the current resolution mode changes for example).
//! These may influence how contents will be rendered to your output, when used in conjunction
//! with the desktop abstractions.
//!
//! You can attach additional properties to your `Output`s by using [`Output::user_data`].
//!
//! ```
//! # extern crate smithay;
//! use smithay::output::{Output, PhysicalProperties, Scale, Mode, Subpixel};
//! use smithay::utils::Transform;
//!
//! // Create the Output with given name and physical properties.
//! let output = Output::new(
//! "output-0".into(), // the name of this output,
//! PhysicalProperties {
//! size: (200, 150).into(), // dimensions (width, height) in mm
//! subpixel: Subpixel::HorizontalRgb, // subpixel information
//! make: "Screens Inc".into(), // make of the monitor
//! model: "Monitor Ultra".into(), // model of the monitor
//! },
//! );
//! // Now you can configure it
//! output.change_current_state(
//! Some(Mode { size: (1920, 1080).into(), refresh: 60000 }), // the resolution mode,
//! Some(Transform::Normal), // global screen transformation
//! Some(Scale::Integer(1)), // global screen scaling factor
//! Some((0,0).into()) // output position
//! );
//! // set the preferred mode
//! output.set_preferred(Mode { size: (1920, 1080).into(), refresh: 60000 });
//! // add other supported modes
//! output.add_mode(Mode { size: (800, 600).into(), refresh: 60000 });
//! output.add_mode(Mode { size: (1024, 768).into(), refresh: 60000 });
//! ```
use std::{
hash::{Hash, Hasher},
sync::{Arc, Mutex, Weak},
};
use tracing::{info, instrument};
#[cfg(feature = "wayland_frontend")]
use crate::wayland::output::xdg::XdgOutput;
#[cfg(feature = "backend_drm")]
use drm::control::{connector::SubPixel as DrmSubPixel, Mode as DrmMode, ModeFlags};
#[cfg(feature = "wayland_frontend")]
use std::collections::HashSet;
#[cfg(feature = "wayland_frontend")]
use wayland_server::{
backend::WeakHandle, protocol::wl_output::WlOutput, protocol::wl_surface::WlSurface, Weak as WlWeak,
};
use crate::utils::{self, user_data::UserDataMap, Logical, Physical, Point, Raw, Size, Transform};
/// 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.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Mode {
/// The size of the mode, in pixels
pub size: Size<i32, Physical>,
/// The refresh rate in millihertz
///
/// `1000` is one fps (frame per second), `2000` is 2 fps, etc...
pub refresh: i32,
}
#[cfg(feature = "backend_drm")]
impl From<DrmMode> for Mode {
#[inline]
fn from(mode: DrmMode) -> Self {
let clock = mode.clock() as u64;
let htotal = mode.hsync().2 as u64;
let vtotal = mode.vsync().2 as u64;
let mut refresh = (clock * 1_000_000 / htotal + vtotal / 2) / vtotal;
if mode.flags().contains(ModeFlags::INTERLACE) {
refresh *= 2;
}
if mode.flags().contains(ModeFlags::DBLSCAN) {
refresh /= 2;
}
if mode.vscan() > 1 {
refresh /= mode.vscan() as u64;
}
let (w, h) = mode.size();
Self {
size: (w as i32, h as i32).into(),
refresh: refresh as i32,
}
}
}
/// Subpixel geometry information
///
/// This enumeration describes how the physical pixels on an output are laid out.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Subpixel {
/// Unknown subpixel geometry
Unknown,
/// No subpixel geometry
None,
/// Subpixels are arranged horizontally starting with
/// red, then green, last blue
HorizontalRgb,
/// Subpixels are arranged horizontally starting with
/// blue, then green, last red
HorizontalBgr,
/// Subpixels are arranged vertically starting with
/// red, then green, last blue
VerticalRgb,
/// Subpixels are arranged vertically starting with
/// blue, then green, last red
VerticalBgr,
}
#[cfg(feature = "backend_drm")]
impl From<DrmSubPixel> for Subpixel {
#[inline]
fn from(mode: DrmSubPixel) -> Self {
match mode {
DrmSubPixel::Unknown => Self::Unknown,
DrmSubPixel::HorizontalRgb => Self::HorizontalRgb,
DrmSubPixel::HorizontalBgr => Self::HorizontalBgr,
DrmSubPixel::VerticalRgb => Self::VerticalRgb,
DrmSubPixel::VerticalBgr => Self::VerticalBgr,
DrmSubPixel::None => Self::None,
DrmSubPixel::NotImplemented => Self::Unknown,
_ => Self::Unknown,
}
}
}
/// The physical properties of an output
#[derive(Debug, Clone)]
pub struct PhysicalProperties {
/// The size of the monitor, in millimeters
pub size: Size<i32, Raw>,
/// The subpixel geometry
pub subpixel: Subpixel,
/// Textual representation of the make
pub make: String,
/// Textual representation of the model
pub model: String,
}
/// Describes the scale advertised to clients.
#[derive(Debug, Clone, Copy)]
pub enum Scale {
/// Integer based scaling
Integer(i32),
/// Fractional scaling
///
/// *Note*: Protocols only supporting integer-scales (e.g. wl_output),
/// will be rounded **up** to the next integer value. To control the value
/// send to those clients use `Scale::Custom` instead.
Fractional(f64),
/// Split scale values advertised to clients
Custom {
/// Integer value for protocols not supporting fractional scaling
advertised_integer: i32,
/// Fractional scaling value used elsewhere
fractional: f64,
},
}
impl Scale {
/// Returns the integer scale as advertised for this `Scale` variant
pub fn integer_scale(&self) -> i32 {
match self {
Scale::Integer(scale) => *scale,
Scale::Fractional(scale) => scale.ceil() as i32,
Scale::Custom {
advertised_integer, ..
} => *advertised_integer,
}
}
/// Returns the fractional scale (calculated if necessary)
pub fn fractional_scale(&self) -> f64 {
match self {
Scale::Integer(scale) => *scale as f64,
Scale::Fractional(scale) => *scale,
Scale::Custom { fractional, .. } => *fractional,
}
}
}
#[derive(Debug)]
pub(crate) struct Inner {
pub(crate) name: String,
pub(crate) description: String,
pub(crate) physical: PhysicalProperties,
pub(crate) location: Point<i32, Logical>,
pub(crate) transform: Transform,
pub(crate) scale: Scale,
pub(crate) modes: Vec<Mode>,
pub(crate) current_mode: Option<Mode>,
pub(crate) preferred_mode: Option<Mode>,
// used by the wayland::output module.
#[cfg(feature = "wayland_frontend")]
pub(crate) instances: Vec<wayland_server::Weak<WlOutput>>,
#[cfg(feature = "wayland_frontend")]
pub(crate) handle: Option<WeakHandle>,
#[cfg(feature = "wayland_frontend")]
pub(crate) xdg_output: Option<XdgOutput>,
#[cfg(feature = "wayland_frontend")]
pub(crate) surfaces: HashSet<WlWeak<WlSurface>>,
}
/// An abstract output.
///
/// This handle is stored in the event loop, and allows you to notify clients
/// about any change in the properties of this output.
#[derive(Debug, Clone)]
pub struct Output {
pub(crate) inner: Arc<(Mutex<Inner>, UserDataMap)>,
}
/// Weak variant of an [`Output`]
///
/// Does not keep associated user data alive,
/// and can be used to refer to a potentially already destroyed output.
#[derive(Debug, Default, Clone)]
pub struct WeakOutput {
pub(crate) inner: Weak<(Mutex<Inner>, UserDataMap)>,
}
impl Output {
/// Create a new output with given name and physical properties.
#[instrument]
pub fn new(name: String, physical: PhysicalProperties) -> Output {
info!(name, "Creating new Output");
let data = Arc::new((
Mutex::new(Inner {
name: name.clone(),
description: format!("{} - {} - {}", physical.make, physical.model, name),
#[cfg(feature = "wayland_frontend")]
instances: Vec::new(),
#[cfg(feature = "wayland_frontend")]
handle: None,
physical,
location: (0, 0).into(),
transform: Transform::Normal,
scale: Scale::Integer(1),
modes: Vec::new(),
current_mode: None,
preferred_mode: None,
#[cfg(feature = "wayland_frontend")]
xdg_output: None,
#[cfg(feature = "wayland_frontend")]
surfaces: HashSet::new(),
}),
UserDataMap::default(),
));
Output { inner: data }
}
/// 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(&self, mode: Mode) {
let mut inner = self.inner.0.lock().unwrap();
inner.preferred_mode = Some(mode);
if inner.modes.iter().all(|&m| m != mode) {
inner.modes.push(mode);
}
}
/// Adds a mode to the list of known modes to this output
pub fn add_mode(&self, mode: Mode) {
let mut inner = self.inner.0.lock().unwrap();
if inner.modes.iter().all(|&m| m != mode) {
inner.modes.push(mode);
}
}
/// Returns the currently advertised mode of the output
pub fn current_mode(&self) -> Option<Mode> {
self.inner.0.lock().unwrap().current_mode
}
/// Returns the preferred mode of the output
pub fn preferred_mode(&self) -> Option<Mode> {
self.inner.0.lock().unwrap().preferred_mode
}
/// Returns the currently advertised transformation of the output
pub fn current_transform(&self) -> Transform {
self.inner.0.lock().unwrap().transform
}
/// Returns the currenly set scale of the output
pub fn current_scale(&self) -> Scale {
self.inner.0.lock().unwrap().scale
}
/// Returns the currenly advertised location of the output
pub fn current_location(&self) -> Point<i32, Logical> {
self.inner.0.lock().unwrap().location
}
/// Returns the name of the output
pub fn name(&self) -> String {
self.inner.0.lock().unwrap().name.clone()
}
/// Returns the description of the output, if xdg-output is initialized
pub fn description(&self) -> String {
self.inner.0.lock().unwrap().description.clone()
}
/// Returns the physical properties of the output
pub fn physical_properties(&self) -> PhysicalProperties {
self.inner.0.lock().unwrap().physical.clone()
}
/// Returns the currently advertised modes of the output
pub fn modes(&self) -> Vec<Mode> {
self.inner.0.lock().unwrap().modes.clone()
}
/// Removes a mode from the list of known modes
///
/// It will not de-advertise it from existing clients (the protocol does not
/// allow it), but it won't be advertised to now clients from now on.
pub fn delete_mode(&self, mode: Mode) {
let mut inner = self.inner.0.lock().unwrap();
inner.modes.retain(|&m| m != mode);
if inner.current_mode == Some(mode) {
inner.current_mode = None;
}
if inner.preferred_mode == Some(mode) {
inner.preferred_mode = None;
}
}
/// Change the current state of this output
///
/// You can changed the current mode, transform status, location 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`.
#[instrument(skip(self), fields(output = self.name()))]
pub fn change_current_state(
&self,
new_mode: Option<Mode>,
new_transform: Option<Transform>,
new_scale: Option<Scale>,
new_location: Option<Point<i32, Logical>>,
) {
{
let mut inner = self.inner.0.lock().unwrap();
if let Some(mode) = new_mode {
if inner.modes.iter().all(|&m| m != mode) {
inner.modes.push(mode);
}
inner.current_mode = new_mode;
}
if let Some(transform) = new_transform {
inner.transform = transform;
}
if let Some(scale) = new_scale {
inner.scale = scale;
}
if let Some(new_location) = new_location {
inner.location = new_location;
}
}
#[cfg(feature = "wayland_frontend")]
self.wl_change_current_state(new_mode, new_transform.map(Into::into), new_scale, new_location)
}
/// Returns the user data of this output
pub fn user_data(&self) -> &UserDataMap {
&self.inner.1
}
/// Create a weak reference to this output
pub fn downgrade(&self) -> WeakOutput {
WeakOutput {
inner: Arc::downgrade(&self.inner),
}
}
/// Cleanup some internal resource.
///
/// Needs to be called periodically, at best before every
/// wayland socket flush.
///
/// Note: [`Space::refresh`](crate::desktop::space::Space) already calls this
/// function internally. So in case you already use the desktop module you can
/// skip calling this function explicitly.
#[profiling::function]
pub fn cleanup(&self) {
#[cfg(feature = "wayland_frontend")]
self.cleanup_surfaces();
}
}
impl PartialEq for Output {
#[inline]
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.inner, &other.inner)
}
}
impl Eq for Output {}
impl Hash for Output {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.inner).hash(state);
}
}
impl WeakOutput {
/// Try to retrieve the original `Output`, if it still exists
#[inline]
pub fn upgrade(&self) -> Option<Output> {
self.inner.upgrade().map(|inner| Output { inner })
}
/// Check if the output is still alive
#[inline]
pub fn is_alive(&self) -> bool {
self.inner.strong_count() != 0
}
}
impl PartialEq for WeakOutput {
#[inline]
fn eq(&self, other: &Self) -> bool {
Weak::ptr_eq(&self.inner, &other.inner)
}
}
impl Eq for WeakOutput {}
impl Hash for WeakOutput {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
Weak::as_ptr(&self.inner).hash(state);
}
}
impl PartialEq<WeakOutput> for Output {
#[inline]
fn eq(&self, other: &WeakOutput) -> bool {
other.upgrade().map(|o| &o == self).unwrap_or(false)
}
}
impl PartialEq<Output> for WeakOutput {
#[inline]
fn eq(&self, other: &Output) -> bool {
self.upgrade().map(|o| &o == other).unwrap_or(false)
}
}
/// Source for determining output mode information.
#[derive(PartialEq, Clone, Debug)]
pub enum OutputModeSource {
/// Automatic mode based on an [`Output`].
Auto(Output),
/// Static output mode.
Static {
/// Size of the static output
size: Size<i32, Physical>,
/// Scale of the static output
scale: utils::Scale<f64>,
/// Transform of the static output
transform: Transform,
},
}
impl From<&Output> for OutputModeSource {
#[inline]
fn from(output: &Output) -> Self {
Self::Auto(output.clone())
}
}
impl TryFrom<&OutputModeSource> for (Size<i32, Physical>, utils::Scale<f64>, Transform) {
type Error = OutputNoMode;
#[inline]
fn try_from(mode: &OutputModeSource) -> Result<Self, Self::Error> {
match mode {
OutputModeSource::Auto(output) => {
let guard = output.inner.0.lock().unwrap();
Ok((
guard.current_mode.as_ref().ok_or(OutputNoMode)?.size,
guard.scale.fractional_scale().into(),
guard.transform,
))
}
OutputModeSource::Static {
size,
scale,
transform,
} => Ok((*size, *scale, *transform)),
}
}
}
impl TryFrom<OutputModeSource> for (Size<i32, Physical>, utils::Scale<f64>, Transform) {
type Error = OutputNoMode;
#[inline]
fn try_from(mode: OutputModeSource) -> Result<Self, Self::Error> {
Self::try_from(&mode)
}
}
/// Output has no active mode
#[derive(Debug, thiserror::Error)]
#[error("Output has no active mode")]
pub struct OutputNoMode;