gioser

tawasuyu/mirada

Sovereign tiling Wayland compositor + window manager in Rust, on Llimphi.

Rust 50Markdown 30TOML 211282 Definitions

⎇ main vendor/smithay/src/output.rs Rust · 552 lines · 17.2 KB History Who touched what raw

chore: refresco desde el monorepo (pin tawasuyu.git v0.2.0) a91da3fea · Sergio ·

1//! Output
2//!
3//! This module provides a type helping you to abstract over various
4//! properties, that make up an [`Output`] of your compositor.
5//!
6//! # How to use it
7//!
8//! You need to instantiate an [`Output`].
9//!
10//! To advertise a new output global to wayland clients take a look at [`crate::wayland::output`],
11//! you need to have the `wayland_frontend` feature enabled to use it.
12//!
13//! Additionally outputs are used to by the desktop abstractions (see [`crate::desktop`], needs the
14//! `desktop` feature) to represent views into your window grid.
15//!
16//! You can use the returned [`Output`] to change
17//! the properties of your output (if the current resolution mode changes for example).
18//! These may influence how contents will be rendered to your output, when used in conjunction
19//! with the desktop abstractions.
20//!
21//! You can attach additional properties to your `Output`s by using [`Output::user_data`].
22//!
23//! ```
24//! # extern crate smithay;
25//! use smithay::output::{Output, PhysicalProperties, Scale, Mode, Subpixel};
26//! use smithay::utils::Transform;
27//!
28//! // Create the Output with given name and physical properties.
29//! let output = Output::new(
30//!     "output-0".into(), // the name of this output,
31//!     PhysicalProperties {
32//!         size: (200, 150).into(),        // dimensions (width, height) in mm
33//!         subpixel: Subpixel::HorizontalRgb,  // subpixel information
34//!         make: "Screens Inc".into(),     // make of the monitor
35//!         model: "Monitor Ultra".into(),  // model of the monitor
36//!     },
37//! );
38//! // Now you can configure it
39//! output.change_current_state(
40//!     Some(Mode { size: (1920, 1080).into(), refresh: 60000 }), // the resolution mode,
41//!     Some(Transform::Normal), // global screen transformation
42//!     Some(Scale::Integer(1)), // global screen scaling factor
43//!     Some((0,0).into()) // output position
44//! );
45//! // set the preferred mode
46//! output.set_preferred(Mode { size: (1920, 1080).into(), refresh: 60000 });
47//! // add other supported modes
48//! output.add_mode(Mode { size: (800, 600).into(), refresh: 60000 });
49//! output.add_mode(Mode { size: (1024, 768).into(), refresh: 60000 });
50//! ```
51
52use std::{
53    hash::{Hash, Hasher},
54    sync::{Arc, Mutex, Weak},
55};
56
57use tracing::{info, instrument};
58
59#[cfg(feature = "wayland_frontend")]
60use crate::wayland::output::xdg::XdgOutput;
61#[cfg(feature = "backend_drm")]
62use drm::control::{connector::SubPixel as DrmSubPixel, Mode as DrmMode, ModeFlags};
63#[cfg(feature = "wayland_frontend")]
64use std::collections::HashSet;
65#[cfg(feature = "wayland_frontend")]
66use wayland_server::{
67    backend::WeakHandle, protocol::wl_output::WlOutput, protocol::wl_surface::WlSurface, Weak as WlWeak,
68};
69
70use crate::utils::{self, user_data::UserDataMap, Logical, Physical, Point, Raw, Size, Transform};
71
72/// An output mode
73///
74/// A possible combination of dimensions and refresh rate for an output.
75///
76/// This should only describe the characteristics of the video driver,
77/// not taking into account any global scaling.
78#[derive(Debug, Copy, Clone, PartialEq, Eq)]
79pub struct Mode {
80    /// The size of the mode, in pixels
81    pub size: Size<i32, Physical>,
82    /// The refresh rate in millihertz
83    ///
84    /// `1000` is one fps (frame per second), `2000` is 2 fps, etc...
85    pub refresh: i32,
86}
87
88#[cfg(feature = "backend_drm")]
89impl From<DrmMode> for Mode {
90    #[inline]
91    fn from(mode: DrmMode) -> Self {
92        let clock = mode.clock() as u64;
93        let htotal = mode.hsync().2 as u64;
94        let vtotal = mode.vsync().2 as u64;
95
96        let mut refresh = (clock * 1_000_000 / htotal + vtotal / 2) / vtotal;
97
98        if mode.flags().contains(ModeFlags::INTERLACE) {
99            refresh *= 2;
100        }
101
102        if mode.flags().contains(ModeFlags::DBLSCAN) {
103            refresh /= 2;
104        }
105
106        if mode.vscan() > 1 {
107            refresh /= mode.vscan() as u64;
108        }
109
110        let (w, h) = mode.size();
111
112        Self {
113            size: (w as i32, h as i32).into(),
114            refresh: refresh as i32,
115        }
116    }
117}
118
119/// Subpixel geometry information
120///
121/// This enumeration describes how the physical pixels on an output are laid out.
122#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
123pub enum Subpixel {
124    /// Unknown subpixel geometry
125    Unknown,
126    /// No subpixel geometry
127    None,
128    /// Subpixels are arranged horizontally starting with
129    /// red, then green, last blue
130    HorizontalRgb,
131    /// Subpixels are arranged horizontally starting with
132    /// blue, then green, last red
133    HorizontalBgr,
134    /// Subpixels are arranged vertically starting with
135    /// red, then green, last blue
136    VerticalRgb,
137    /// Subpixels are arranged vertically starting with
138    /// blue, then green, last red
139    VerticalBgr,
140}
141
142#[cfg(feature = "backend_drm")]
143impl From<DrmSubPixel> for Subpixel {
144    #[inline]
145    fn from(mode: DrmSubPixel) -> Self {
146        match mode {
147            DrmSubPixel::Unknown => Self::Unknown,
148            DrmSubPixel::HorizontalRgb => Self::HorizontalRgb,
149            DrmSubPixel::HorizontalBgr => Self::HorizontalBgr,
150            DrmSubPixel::VerticalRgb => Self::VerticalRgb,
151            DrmSubPixel::VerticalBgr => Self::VerticalBgr,
152            DrmSubPixel::None => Self::None,
153            DrmSubPixel::NotImplemented => Self::Unknown,
154            _ => Self::Unknown,
155        }
156    }
157}
158
159/// The physical properties of an output
160#[derive(Debug, Clone)]
161pub struct PhysicalProperties {
162    /// The size of the monitor, in millimeters
163    pub size: Size<i32, Raw>,
164    /// The subpixel geometry
165    pub subpixel: Subpixel,
166    /// Textual representation of the make
167    pub make: String,
168    /// Textual representation of the model
169    pub model: String,
170}
171
172/// Describes the scale advertised to clients.
173#[derive(Debug, Clone, Copy)]
174pub enum Scale {
175    /// Integer based scaling
176    Integer(i32),
177    /// Fractional scaling
178    ///
179    /// *Note*: Protocols only supporting integer-scales (e.g. wl_output),
180    /// will be rounded **up** to the next integer value. To control the value
181    /// send to those clients use `Scale::Custom` instead.
182    Fractional(f64),
183    /// Split scale values advertised to clients
184    Custom {
185        /// Integer value for protocols not supporting fractional scaling
186        advertised_integer: i32,
187        /// Fractional scaling value used elsewhere
188        fractional: f64,
189    },
190}
191
192impl Scale {
193    /// Returns the integer scale as advertised for this `Scale` variant
194    pub fn integer_scale(&self) -> i32 {
195        match self {
196            Scale::Integer(scale) => *scale,
197            Scale::Fractional(scale) => scale.ceil() as i32,
198            Scale::Custom {
199                advertised_integer, ..
200            } => *advertised_integer,
201        }
202    }
203
204    /// Returns the fractional scale (calculated if necessary)
205    pub fn fractional_scale(&self) -> f64 {
206        match self {
207            Scale::Integer(scale) => *scale as f64,
208            Scale::Fractional(scale) => *scale,
209            Scale::Custom { fractional, .. } => *fractional,
210        }
211    }
212}
213
214#[derive(Debug)]
215pub(crate) struct Inner {
216    pub(crate) name: String,
217    pub(crate) description: String,
218    pub(crate) physical: PhysicalProperties,
219    pub(crate) location: Point<i32, Logical>,
220    pub(crate) transform: Transform,
221    pub(crate) scale: Scale,
222    pub(crate) modes: Vec<Mode>,
223    pub(crate) current_mode: Option<Mode>,
224    pub(crate) preferred_mode: Option<Mode>,
225
226    // used by the wayland::output module.
227    #[cfg(feature = "wayland_frontend")]
228    pub(crate) instances: Vec<wayland_server::Weak<WlOutput>>,
229    #[cfg(feature = "wayland_frontend")]
230    pub(crate) handle: Option<WeakHandle>,
231    #[cfg(feature = "wayland_frontend")]
232    pub(crate) xdg_output: Option<XdgOutput>,
233    #[cfg(feature = "wayland_frontend")]
234    pub(crate) surfaces: HashSet<WlWeak<WlSurface>>,
235}
236
237/// An abstract output.
238///
239/// This handle is stored in the event loop, and allows you to notify clients
240/// about any change in the properties of this output.
241#[derive(Debug, Clone)]
242pub struct Output {
243    pub(crate) inner: Arc<(Mutex<Inner>, UserDataMap)>,
244}
245
246/// Weak variant of an [`Output`]
247///
248/// Does not keep associated user data alive,
249/// and can be used to refer to a potentially already destroyed output.
250#[derive(Debug, Default, Clone)]
251pub struct WeakOutput {
252    pub(crate) inner: Weak<(Mutex<Inner>, UserDataMap)>,
253}
254
255impl Output {
256    /// Create a new output with given name and physical properties.
257    #[instrument]
258    pub fn new(name: String, physical: PhysicalProperties) -> Output {
259        info!(name, "Creating new Output");
260
261        let data = Arc::new((
262            Mutex::new(Inner {
263                name: name.clone(),
264                description: format!("{} - {} - {}", physical.make, physical.model, name),
265                #[cfg(feature = "wayland_frontend")]
266                instances: Vec::new(),
267                #[cfg(feature = "wayland_frontend")]
268                handle: None,
269                physical,
270                location: (0, 0).into(),
271                transform: Transform::Normal,
272                scale: Scale::Integer(1),
273                modes: Vec::new(),
274                current_mode: None,
275                preferred_mode: None,
276                #[cfg(feature = "wayland_frontend")]
277                xdg_output: None,
278                #[cfg(feature = "wayland_frontend")]
279                surfaces: HashSet::new(),
280            }),
281            UserDataMap::default(),
282        ));
283
284        Output { inner: data }
285    }
286
287    /// Sets the preferred mode of this output
288    ///
289    /// If the provided mode was not previously known to this output, it is added to its
290    /// internal list.
291    pub fn set_preferred(&self, mode: Mode) {
292        let mut inner = self.inner.0.lock().unwrap();
293        inner.preferred_mode = Some(mode);
294        if inner.modes.iter().all(|&m| m != mode) {
295            inner.modes.push(mode);
296        }
297    }
298
299    /// Adds a mode to the list of known modes to this output
300    pub fn add_mode(&self, mode: Mode) {
301        let mut inner = self.inner.0.lock().unwrap();
302        if inner.modes.iter().all(|&m| m != mode) {
303            inner.modes.push(mode);
304        }
305    }
306
307    /// Returns the currently advertised mode of the output
308    pub fn current_mode(&self) -> Option<Mode> {
309        self.inner.0.lock().unwrap().current_mode
310    }
311
312    /// Returns the preferred mode of the output
313    pub fn preferred_mode(&self) -> Option<Mode> {
314        self.inner.0.lock().unwrap().preferred_mode
315    }
316
317    /// Returns the currently advertised transformation of the output
318    pub fn current_transform(&self) -> Transform {
319        self.inner.0.lock().unwrap().transform
320    }
321
322    /// Returns the currenly set scale of the output
323    pub fn current_scale(&self) -> Scale {
324        self.inner.0.lock().unwrap().scale
325    }
326
327    /// Returns the currenly advertised location of the output
328    pub fn current_location(&self) -> Point<i32, Logical> {
329        self.inner.0.lock().unwrap().location
330    }
331
332    /// Returns the name of the output
333    pub fn name(&self) -> String {
334        self.inner.0.lock().unwrap().name.clone()
335    }
336
337    /// Returns the description of the output, if xdg-output is initialized
338    pub fn description(&self) -> String {
339        self.inner.0.lock().unwrap().description.clone()
340    }
341
342    /// Returns the physical properties of the output
343    pub fn physical_properties(&self) -> PhysicalProperties {
344        self.inner.0.lock().unwrap().physical.clone()
345    }
346
347    /// Returns the currently advertised modes of the output
348    pub fn modes(&self) -> Vec<Mode> {
349        self.inner.0.lock().unwrap().modes.clone()
350    }
351
352    /// Removes a mode from the list of known modes
353    ///
354    /// It will not de-advertise it from existing clients (the protocol does not
355    /// allow it), but it won't be advertised to now clients from now on.
356    pub fn delete_mode(&self, mode: Mode) {
357        let mut inner = self.inner.0.lock().unwrap();
358        inner.modes.retain(|&m| m != mode);
359        if inner.current_mode == Some(mode) {
360            inner.current_mode = None;
361        }
362        if inner.preferred_mode == Some(mode) {
363            inner.preferred_mode = None;
364        }
365    }
366
367    /// Change the current state of this output
368    ///
369    /// You can changed the current mode, transform status, location or scale of this output. Providing
370    /// `None` to any of these field means that the value does not change.
371    ///
372    /// If the provided mode was not previously known to this output, it is added to its
373    /// internal list.
374    ///
375    /// By default, transform status is `Normal`, and scale is `1`.
376    #[instrument(skip(self), fields(output = self.name()))]
377    pub fn change_current_state(
378        &self,
379        new_mode: Option<Mode>,
380        new_transform: Option<Transform>,
381        new_scale: Option<Scale>,
382        new_location: Option<Point<i32, Logical>>,
383    ) {
384        {
385            let mut inner = self.inner.0.lock().unwrap();
386            if let Some(mode) = new_mode {
387                if inner.modes.iter().all(|&m| m != mode) {
388                    inner.modes.push(mode);
389                }
390                inner.current_mode = new_mode;
391            }
392            if let Some(transform) = new_transform {
393                inner.transform = transform;
394            }
395            if let Some(scale) = new_scale {
396                inner.scale = scale;
397            }
398            if let Some(new_location) = new_location {
399                inner.location = new_location;
400            }
401        }
402
403        #[cfg(feature = "wayland_frontend")]
404        self.wl_change_current_state(new_mode, new_transform.map(Into::into), new_scale, new_location)
405    }
406
407    /// Returns the user data of this output
408    pub fn user_data(&self) -> &UserDataMap {
409        &self.inner.1
410    }
411
412    /// Create a weak reference to this output
413    pub fn downgrade(&self) -> WeakOutput {
414        WeakOutput {
415            inner: Arc::downgrade(&self.inner),
416        }
417    }
418
419    /// Cleanup some internal resource.
420    ///
421    /// Needs to be called periodically, at best before every
422    /// wayland socket flush.
423    ///
424    /// Note: [`Space::refresh`](crate::desktop::space::Space) already calls this
425    /// function internally. So in case you already use the desktop module you can
426    /// skip calling this function explicitly.
427    #[profiling::function]
428    pub fn cleanup(&self) {
429        #[cfg(feature = "wayland_frontend")]
430        self.cleanup_surfaces();
431    }
432}
433
434impl PartialEq for Output {
435    #[inline]
436    fn eq(&self, other: &Self) -> bool {
437        Arc::ptr_eq(&self.inner, &other.inner)
438    }
439}
440
441impl Eq for Output {}
442
443impl Hash for Output {
444    #[inline]
445    fn hash<H: Hasher>(&self, state: &mut H) {
446        Arc::as_ptr(&self.inner).hash(state);
447    }
448}
449
450impl WeakOutput {
451    /// Try to retrieve the original `Output`, if it still exists
452    #[inline]
453    pub fn upgrade(&self) -> Option<Output> {
454        self.inner.upgrade().map(|inner| Output { inner })
455    }
456
457    /// Check if the output is still alive
458    #[inline]
459    pub fn is_alive(&self) -> bool {
460        self.inner.strong_count() != 0
461    }
462}
463
464impl PartialEq for WeakOutput {
465    #[inline]
466    fn eq(&self, other: &Self) -> bool {
467        Weak::ptr_eq(&self.inner, &other.inner)
468    }
469}
470
471impl Eq for WeakOutput {}
472
473impl Hash for WeakOutput {
474    #[inline]
475    fn hash<H: Hasher>(&self, state: &mut H) {
476        Weak::as_ptr(&self.inner).hash(state);
477    }
478}
479
480impl PartialEq<WeakOutput> for Output {
481    #[inline]
482    fn eq(&self, other: &WeakOutput) -> bool {
483        other.upgrade().map(|o| &o == self).unwrap_or(false)
484    }
485}
486
487impl PartialEq<Output> for WeakOutput {
488    #[inline]
489    fn eq(&self, other: &Output) -> bool {
490        self.upgrade().map(|o| &o == other).unwrap_or(false)
491    }
492}
493
494/// Source for determining output mode information.
495#[derive(PartialEq, Clone, Debug)]
496pub enum OutputModeSource {
497    /// Automatic mode based on an [`Output`].
498    Auto(Output),
499    /// Static output mode.
500    Static {
501        /// Size of the static output
502        size: Size<i32, Physical>,
503        /// Scale of the static output
504        scale: utils::Scale<f64>,
505        /// Transform of the static output
506        transform: Transform,
507    },
508}
509
510impl From<&Output> for OutputModeSource {
511    #[inline]
512    fn from(output: &Output) -> Self {
513        Self::Auto(output.clone())
514    }
515}
516
517impl TryFrom<&OutputModeSource> for (Size<i32, Physical>, utils::Scale<f64>, Transform) {
518    type Error = OutputNoMode;
519
520    #[inline]
521    fn try_from(mode: &OutputModeSource) -> Result<Self, Self::Error> {
522        match mode {
523            OutputModeSource::Auto(output) => {
524                let guard = output.inner.0.lock().unwrap();
525                Ok((
526                    guard.current_mode.as_ref().ok_or(OutputNoMode)?.size,
527                    guard.scale.fractional_scale().into(),
528                    guard.transform,
529                ))
530            }
531            OutputModeSource::Static {
532                size,
533                scale,
534                transform,
535            } => Ok((*size, *scale, *transform)),
536        }
537    }
538}
539
540impl TryFrom<OutputModeSource> for (Size<i32, Physical>, utils::Scale<f64>, Transform) {
541    type Error = OutputNoMode;
542
543    #[inline]
544    fn try_from(mode: OutputModeSource) -> Result<Self, Self::Error> {
545        Self::try_from(&mode)
546    }
547}
548
549/// Output has no active mode
550#[derive(Debug, thiserror::Error)]
551#[error("Output has no active mode")]
552pub struct OutputNoMode;