chore: refresco desde el monorepo (pin tawasuyu.git v0.2.0)
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;