From 3eb6c02008a5b181512ba88610e2e599f4f6fd61 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 24 Apr 2024 17:25:33 +0200 Subject: [PATCH] kms: Output mirroring --- src/backend/kms/mod.rs | 372 ++++++++++++++----- src/backend/render/element.rs | 40 +- src/config/mod.rs | 55 ++- src/wayland/handlers/output_configuration.rs | 6 +- 4 files changed, 365 insertions(+), 108 deletions(-) diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 7d259c74..acce9bd0 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -5,7 +5,7 @@ use crate::{ element::{CosmicElement, DamageElement}, workspace_elements, CLEAR_COLOR, }, - config::OutputConfig, + config::{OutputConfig, OutputState}, shell::Shell, state::{BackendData, Common, Fps, SurfaceDmabufFeedback}, utils::prelude::*, @@ -36,14 +36,18 @@ use smithay::{ libinput::{LibinputInputBackend, LibinputSessionInterface}, renderer::{ buffer_dimensions, - damage::Error as RenderError, - element::{Element, RenderElementStates}, - gles::GlesRenderbuffer, + damage::{Error as RenderError, OutputDamageTracker}, + element::{ + texture::{TextureRenderBuffer, TextureRenderElement}, + utils::{constrain_render_elements, ConstrainAlign, ConstrainScaleBehavior}, + Element, Kind, RenderElementStates, + }, + gles::{GlesRenderbuffer, GlesTexture}, glow::GlowRenderer, multigpu::{gbm::GbmGlesBackend, Error as MultiError, GpuManager}, sync::SyncPoint, utils::with_renderer_surface_state, - Bind, ImportDma, Offscreen, + Bind, ImportDma, Offscreen, Renderer, Texture, }, session::{libseat::LibSeatSession, Event as SessionEvent, Session}, udev::{all_gpus, primary_gpu, UdevBackend, UdevEvent}, @@ -155,7 +159,11 @@ impl fmt::Debug for Device { pub struct Surface { surface: Option, connector: connector::Handle, + output: Output, + mirroring: Option, + mirroring_textures: HashMap, + refresh_rate: u32, vrr: bool, scheduled: bool, @@ -165,6 +173,45 @@ pub struct Surface { feedback: HashMap, } +#[derive(Debug)] +struct MirroringState { + texture: TextureRenderBuffer, + damage_tracker: OutputDamageTracker, +} + +impl MirroringState { + fn new_with_renderer( + renderer: &mut GlMultiRenderer, + format: Fourcc, + output: &Output, + ) -> Result { + let size = output + .current_mode() + .map(|mode| mode.size) + .unwrap_or_default() + .to_logical(1) + .to_buffer(1, Transform::Normal); + let opaque_regions = vec![Rectangle::from_loc_and_size((0, 0), size)]; + + let texture = Offscreen::::create_buffer(renderer, format, size)?; + let transform = output.current_transform(); + let texture_buffer = TextureRenderBuffer::from_texture( + renderer, + texture, + 1, + transform, + Some(opaque_regions), + ); + + let damage_tracker = OutputDamageTracker::from_output(output); + + Ok(MirroringState { + texture: texture_buffer, + damage_tracker, + }) + } +} + pub type GbmDrmCompositor = DrmCompositor< GbmAllocator, GbmDevice, @@ -1036,6 +1083,8 @@ impl Device { let data = Surface { output: output.clone(), + mirroring: None, + mirroring_textures: HashMap::new(), surface: None, connector: conn, vrr, @@ -1183,7 +1232,7 @@ impl Surface { api.renderer(&render_node, &target_node, compositor.format()) .unwrap(), ), - None => (target_node, api.single_renderer(&target_node).unwrap()), + _ => (target_node, api.single_renderer(&target_node).unwrap()), }; self.fps.start(); @@ -1197,8 +1246,10 @@ impl Surface { let mut elements = { let shell = state.shell.read().unwrap(); - let (previous_workspace, workspace) = shell.workspaces.active(&self.output); - let (previous_idx, idx) = shell.workspaces.active_num(&self.output); + let output = self.mirroring.as_ref().unwrap_or(&self.output); + + let (previous_workspace, workspace) = shell.workspaces.active(output); + let (previous_idx, idx) = shell.workspaces.active_num(output); let previous_workspace = previous_workspace .zip(previous_idx) .map(|((w, start), idx)| (w.handle, idx, start)); @@ -1211,7 +1262,7 @@ impl Surface { &state.config, &state.theme, state.clock.now(), - &self.output, + output, previous_workspace, workspace, CursorMode::All, @@ -1229,73 +1280,166 @@ impl Surface { ScreencopyFrame, Result<(Option>>, RenderElementStates), OutputNoMode>, )> = self - .output - .take_pending_frames() - .into_iter() - .map(|(session, frame)| { - let additional_damage = frame.damage(); - let session_data = session.user_data().get::().unwrap(); - let mut damage_tracking = session_data.borrow_mut(); + .mirroring + .is_none() + .then(|| { + self.output + .take_pending_frames() + .into_iter() + .map(|(session, frame)| { + let additional_damage = frame.damage(); + let session_data = session.user_data().get::().unwrap(); + let mut damage_tracking = session_data.borrow_mut(); - let old_len = if !additional_damage.is_empty() { - let area = self - .output - .current_mode() - .unwrap() - /* TODO: Mode is Buffer..., why is this Physical in the first place */ - .size - .to_logical(1) - .to_buffer(1, Transform::Normal) - .to_f64(); + let old_len = if !additional_damage.is_empty() { + let area = self + .output + .current_mode() + .unwrap() + /* TODO: Mode is Buffer..., why is this Physical in the first place */ + .size + .to_logical(1) + .to_buffer(1, Transform::Normal) + .to_f64(); - let old_len = elements.len(); - elements.extend( - additional_damage - .into_iter() - .map(|rect| { - rect.to_f64() - .to_logical( - self.output.current_scale().fractional_scale(), - self.output.current_transform(), - &area, - ) - .to_i32_round() - }) - .map(DamageElement::new) - .map(Into::into), - ); + let old_len = elements.len(); + elements.extend( + additional_damage + .into_iter() + .map(|rect| { + rect.to_f64() + .to_logical( + self.output.current_scale().fractional_scale(), + self.output.current_transform(), + &area, + ) + .to_i32_round() + }) + .map(DamageElement::new) + .map(Into::into), + ); - Some(old_len) - } else { - None - }; + Some(old_len) + } else { + None + }; - let buffer = frame.buffer(); - let age = damage_tracking.age_for_buffer(&buffer); - let res = damage_tracking.dt.damage_output(age, &elements); + let buffer = frame.buffer(); + let age = damage_tracking.age_for_buffer(&buffer); + let res = damage_tracking.dt.damage_output(age, &elements); - if let Some(old_len) = old_len { - elements.truncate(old_len); - } + if let Some(old_len) = old_len { + elements.truncate(old_len); + } - let res = res.map(|(a, b)| (a.cloned(), b)); - std::mem::drop(damage_tracking); - (session, frame, res) + let res = res.map(|(a, b)| (a.cloned(), b)); + std::mem::drop(damage_tracking); + (session, frame, res) + }) + .collect() }) - .collect(); + .unwrap_or_default(); - let res = compositor.render_frame( - &mut renderer, - &elements, - CLEAR_COLOR, // TODO use a theme neutral color - ); + let res = if let Some(mirrored_output) = self.mirroring.as_ref().filter(|mirrored_output| { + mirrored_output.current_mode().is_some_and(|mirror_mode| { + self.output + .current_mode() + .is_some_and(|mode| mode != mirror_mode) + }) + }) { + let mirroring_state = { + let entry = self.mirroring_textures.entry(*target_node); + let mut new_state = None; + if matches!(entry, std::collections::hash_map::Entry::Vacant(_)) { + new_state = Some(MirroringState::new_with_renderer( + &mut renderer, + compositor.format(), + mirrored_output, + )?); + } + // I really want a failable initializer... + entry.or_insert_with(|| new_state.unwrap()) + }; + + mirroring_state + .texture + .render() + .draw::<_, ::Error>(|tex| { + let res = match mirroring_state.damage_tracker.render_output_with( + &mut renderer, + tex.clone(), + 1, + &elements, + CLEAR_COLOR, + ) { + Ok(res) => res, + Err(RenderError::Rendering(err)) => return Err(err), + Err(RenderError::OutputNoMode(_)) => unreachable!(), + }; + + renderer.wait(&res.sync)?; + + let transform = mirrored_output.current_transform(); + let area = tex.size().to_logical(1, transform); + + Ok(res + .damage + .cloned() + .map(|v| { + v.into_iter() + .map(|r| r.to_logical(1).to_buffer(1, transform, &area)) + .collect::>() + }) + .unwrap_or_default()) + }) + .context("Failed to draw to offscreen render target")?; + + let texture_elem = TextureRenderElement::from_texture_render_buffer( + (0., 0.), + &mirroring_state.texture, + Some(1.0), + None, + None, + Kind::Unspecified, + ); + let texture_geometry = texture_elem.geometry(1.0.into()); + elements = constrain_render_elements( + std::iter::once(texture_elem), + (0, 0), + Rectangle::from_loc_and_size( + (0, 0), + self.output + .geometry() + .size + .as_logical() + .to_f64() + .to_physical(self.output.current_scale().fractional_scale()) + .to_i32_round(), + ), + texture_geometry, + ConstrainScaleBehavior::Fit, + ConstrainAlign::CENTER, + 1.0, + ) + .map(CosmicElement::Mirror) + .collect::>(); + + renderer = api.single_renderer(&target_node).unwrap(); + compositor.render_frame(&mut renderer, &elements, [0.0, 0.0, 0.0, 1.0]) + } else { + compositor.render_frame( + &mut renderer, + &elements, + CLEAR_COLOR, // TODO use a theme neutral color + ) + }; self.fps.render(); match res { Ok(frame_result) => { let (tx, rx) = std::sync::mpsc::channel(); - let feedback = if !frame_result.is_empty { + let feedback = if !frame_result.is_empty && self.mirroring.is_none() { Some(( state.take_presentation_feedback(&self.output, &frame_result.states), rx, @@ -1462,31 +1606,33 @@ impl Surface { } }; - state.send_frames(&self.output, &frame_result.states, |source_node| { - Some( - self.feedback - .entry(source_node) - .or_insert_with(|| { - let render_formats = api - .single_renderer(&source_node) - .unwrap() - .dmabuf_formats() - .collect::>(); - let target_formats = api - .single_renderer(target_node) - .unwrap() - .dmabuf_formats() - .collect::>(); - get_surface_dmabuf_feedback( - source_node, - render_formats, - target_formats, - compositor, - ) - }) - .clone(), - ) - }); + if self.mirroring.is_none() { + state.send_frames(&self.output, &frame_result.states, |source_node| { + Some( + self.feedback + .entry(source_node) + .or_insert_with(|| { + let render_formats = api + .single_renderer(&source_node) + .unwrap() + .dmabuf_formats() + .collect::>(); + let target_formats = api + .single_renderer(target_node) + .unwrap() + .dmabuf_formats() + .collect::>(); + get_surface_dmabuf_feedback( + source_node, + render_formats, + target_formats, + compositor, + ) + }) + .clone(), + ) + }); + } } Err(err) => { compositor.reset_buffers(); @@ -1513,6 +1659,17 @@ impl KmsState { workspace_state: &mut WorkspaceUpdateGuard<'_, State>, xdg_activation_state: &XdgActivationState, ) -> Result<(), anyhow::Error> { + let outputs = self + .devices + .values() + .flat_map(|device| { + device + .surfaces + .values() + .map(|surface| surface.output.clone()) + }) + .collect::>(); + let recreated = if let Some(device) = self .devices .values_mut() @@ -1528,8 +1685,19 @@ impl KmsState { .get::>() .unwrap() .borrow(); + let mirrored_output = if let OutputState::Mirroring(conn) = &output_config.enabled { + Some( + outputs + .iter() + .find(|output| &output.name() == conn) + .cloned() + .ok_or(anyhow::anyhow!("Unable to find mirroring output"))?, + ) + } else { + None + }; - if !output_config.enabled { + if output_config.enabled == OutputState::Disabled { if !test_only { shell.workspaces.remove_output( output, @@ -1541,6 +1709,7 @@ impl KmsState { // just drop it surface.pending = false; } + surface.mirroring_textures.clear(); } false } else { @@ -1635,9 +1804,22 @@ impl KmsState { surface.surface = Some(target); true }; - shell - .workspaces - .add_output(output, workspace_state, xdg_activation_state); + + if mirrored_output != surface.mirroring { + shell.workspaces.remove_output( + output, + shell.seats.iter(), + workspace_state, + xdg_activation_state, + ); + surface.mirroring = mirrored_output.clone(); + surface.mirroring_textures.clear(); + } + if mirrored_output.is_none() { + shell + .workspaces + .add_output(output, workspace_state, xdg_activation_state); + } res } else { false @@ -1737,11 +1919,13 @@ impl KmsState { output: &Output, estimated_rendertime: Option, ) -> Result<(), InsertError> { - if let Some((device, crtc, surface)) = self + for (device, crtc, surface) in self .devices .iter_mut() .flat_map(|(node, d)| d.surfaces.iter_mut().map(move |(c, s)| (node, c, s))) - .find(|(_, _, s)| s.output == *output) + .filter(|(_, _, s)| { + s.output == *output || s.mirroring.as_ref().is_some_and(|o| o == output) + }) { if surface.surface.is_none() { return Ok(()); @@ -1774,7 +1958,7 @@ impl KmsState { let common = &mut state.common; let target_node = target_device.render_node; let render_node = render_node_for_output( - &surface.output, + surface.mirroring.as_ref().unwrap_or(&surface.output), backend.primary_node, target_node, &*common.shell.read().unwrap(), diff --git a/src/backend/render/element.rs b/src/backend/render/element.rs index 66220daf..7aeb0728 100644 --- a/src/backend/render/element.rs +++ b/src/backend/render/element.rs @@ -4,9 +4,11 @@ use smithay::{ backend::renderer::{ element::{ surface::WaylandSurfaceRenderElement, - utils::{Relocate, RelocateRenderElement}, + texture::TextureRenderElement, + utils::{CropRenderElement, Relocate, RelocateRenderElement, RescaleRenderElement}, Element, Id, Kind, RenderElement, UnderlyingStorage, }, + gles::GlesTexture, glow::{GlowFrame, GlowRenderer}, utils::{CommitCounter, DamageSet}, Frame, ImportAll, ImportMem, Renderer, @@ -30,6 +32,11 @@ where Dnd(WaylandSurfaceRenderElement), MoveGrab(CosmicMappedRenderElement), AdditionalDamage(DamageElement), + Mirror( + CropRenderElement< + RelocateRenderElement>>, + >, + ), #[cfg(feature = "debug")] Egui(TextureRenderElement), } @@ -47,6 +54,7 @@ where CosmicElement::Dnd(elem) => elem.id(), CosmicElement::MoveGrab(elem) => elem.id(), CosmicElement::AdditionalDamage(elem) => elem.id(), + CosmicElement::Mirror(elem) => elem.id(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.id(), } @@ -59,6 +67,7 @@ where CosmicElement::Dnd(elem) => elem.current_commit(), CosmicElement::MoveGrab(elem) => elem.current_commit(), CosmicElement::AdditionalDamage(elem) => elem.current_commit(), + CosmicElement::Mirror(elem) => elem.current_commit(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.current_commit(), } @@ -71,6 +80,7 @@ where CosmicElement::Dnd(elem) => elem.src(), CosmicElement::MoveGrab(elem) => elem.src(), CosmicElement::AdditionalDamage(elem) => elem.src(), + CosmicElement::Mirror(elem) => elem.src(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.src(), } @@ -83,6 +93,7 @@ where CosmicElement::Dnd(elem) => elem.geometry(scale), CosmicElement::MoveGrab(elem) => elem.geometry(scale), CosmicElement::AdditionalDamage(elem) => elem.geometry(scale), + CosmicElement::Mirror(elem) => elem.geometry(scale), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.geometry(scale), } @@ -95,6 +106,7 @@ where CosmicElement::Dnd(elem) => elem.location(scale), CosmicElement::MoveGrab(elem) => elem.location(scale), CosmicElement::AdditionalDamage(elem) => elem.location(scale), + CosmicElement::Mirror(elem) => elem.location(scale), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.location(scale), } @@ -107,6 +119,7 @@ where CosmicElement::Dnd(elem) => elem.transform(), CosmicElement::MoveGrab(elem) => elem.transform(), CosmicElement::AdditionalDamage(elem) => elem.transform(), + CosmicElement::Mirror(elem) => elem.transform(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.transform(), } @@ -123,6 +136,7 @@ where CosmicElement::Dnd(elem) => elem.damage_since(scale, commit), CosmicElement::MoveGrab(elem) => elem.damage_since(scale, commit), CosmicElement::AdditionalDamage(elem) => elem.damage_since(scale, commit), + CosmicElement::Mirror(elem) => elem.damage_since(scale, commit), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.damage_since(scale, commit), } @@ -135,6 +149,7 @@ where CosmicElement::Dnd(elem) => elem.opaque_regions(scale), CosmicElement::MoveGrab(elem) => elem.opaque_regions(scale), CosmicElement::AdditionalDamage(elem) => elem.opaque_regions(scale), + CosmicElement::Mirror(elem) => elem.opaque_regions(scale), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.opaque_regions(scale), } @@ -147,6 +162,7 @@ where CosmicElement::Dnd(elem) => elem.alpha(), CosmicElement::MoveGrab(elem) => elem.alpha(), CosmicElement::AdditionalDamage(elem) => elem.alpha(), + CosmicElement::Mirror(elem) => elem.alpha(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.alpha(), } @@ -159,6 +175,7 @@ where CosmicElement::Dnd(elem) => elem.kind(), CosmicElement::MoveGrab(elem) => elem.kind(), CosmicElement::AdditionalDamage(elem) => elem.kind(), + CosmicElement::Mirror(elem) => elem.kind(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.kind(), } @@ -181,6 +198,9 @@ impl RenderElement for CosmicElement { CosmicElement::AdditionalDamage(elem) => { RenderElement::::draw(elem, frame, src, dst, damage) } + CosmicElement::Mirror(elem) => { + RenderElement::::draw(elem, frame, src, dst, damage) + } #[cfg(feature = "debug")] CosmicElement::Egui(elem) => { RenderElement::::draw(elem, frame, src, dst, damage) @@ -195,6 +215,7 @@ impl RenderElement for CosmicElement { CosmicElement::Dnd(elem) => elem.underlying_storage(renderer), CosmicElement::MoveGrab(elem) => elem.underlying_storage(renderer), CosmicElement::AdditionalDamage(elem) => elem.underlying_storage(renderer), + CosmicElement::Mirror(elem) => elem.underlying_storage(renderer), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.underlying_storage(renderer), } @@ -217,6 +238,14 @@ impl<'a> RenderElement> for CosmicElement { RenderElement::>::draw(elem, frame, src, dst, damage) } + CosmicElement::Mirror(elem) => { + let elem = { + let glow_frame = frame.glow_frame_mut(); + RenderElement::::draw(elem, glow_frame, src, dst, damage) + .map_err(|err| GlMultiError::Render(err)) + }; + elem + } #[cfg(feature = "debug")] CosmicElement::Egui(elem) => { let elem = { @@ -236,6 +265,15 @@ impl<'a> RenderElement> for CosmicElement elem.underlying_storage(renderer), CosmicElement::MoveGrab(elem) => elem.underlying_storage(renderer), CosmicElement::AdditionalDamage(elem) => elem.underlying_storage(renderer), + CosmicElement::Mirror(elem) => { + let glow_renderer = renderer.glow_renderer_mut(); + match elem.underlying_storage(glow_renderer) { + Some(UnderlyingStorage::Wayland(buffer)) => { + Some(UnderlyingStorage::Wayland(buffer)) + } + _ => None, + } + } #[cfg(feature = "debug")] CosmicElement::Egui(elem) => { let glow_renderer = renderer.glow_renderer_mut(); diff --git a/src/config/mod.rs b/src/config/mod.rs index 3637f5c4..35d743e2 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -109,8 +109,18 @@ impl From for OutputInfo { } } -fn default_enabled() -> bool { - true +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum OutputState { + #[serde(rename = "true")] + Enabled, + #[serde(rename = "false")] + Disabled, + Mirroring(String), +} + +fn default_enabled() -> OutputState { + OutputState::Enabled } #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] @@ -122,7 +132,7 @@ pub struct OutputConfig { pub transform: Transform, pub position: (i32, i32), #[serde(default = "default_enabled")] - pub enabled: bool, + pub enabled: OutputState, #[serde(default, skip_serializing_if = "Option::is_none")] pub max_bpc: Option, } @@ -135,7 +145,7 @@ impl Default for OutputConfig { scale: 1.0, transform: Transform::Normal, position: (0, 0), - enabled: true, + enabled: OutputState::Enabled, max_bpc: None, } } @@ -257,8 +267,32 @@ impl Config { fn load_outputs(path: &Option) -> OutputsConfig { if let Some(path) = path.as_ref() { if path.exists() { - match ron::de::from_reader(OpenOptions::new().read(true).open(path).unwrap()) { - Ok(config) => return config, + match ron::de::from_reader::<_, OutputsConfig>( + OpenOptions::new().read(true).open(path).unwrap(), + ) { + Ok(mut config) => { + for (info, config) in config.config.iter_mut() { + let config_clone = config.clone(); + for conf in config.iter_mut() { + if let OutputState::Mirroring(conn) = &conf.enabled { + if let Some((j, _)) = info + .iter() + .enumerate() + .find(|(_, info)| &info.connector == conn) + { + if config_clone[j].enabled != OutputState::Enabled { + warn!("Invalid Mirroring tag, overriding with `Enabled` instead"); + conf.enabled = OutputState::Enabled; + } + } else { + warn!("Invalid Mirroring tag, overriding with `Enabled` instead"); + conf.enabled = OutputState::Enabled; + } + } + } + } + return config; + } Err(err) => { warn!(?err, "Failed to read output_config, resetting.."); if let Err(err) = std::fs::remove_file(path) { @@ -307,7 +341,7 @@ impl Config { for (name, output_config) in infos.iter().map(|o| &o.connector).zip(configs.into_iter()) { let output = outputs.iter().find(|o| &o.name() == name).unwrap().clone(); - let enabled = output_config.enabled; + let enabled = output_config.enabled.clone(); *output .user_data() .get::>() @@ -329,7 +363,7 @@ impl Config { reset = true; break; } else { - if enabled { + if enabled == OutputState::Enabled { output_state.enable_head(&output); } else { output_state.disable_head(&output); @@ -343,7 +377,7 @@ impl Config { .into_iter() .zip(known_good_configs.into_iter()) { - let enabled = output_config.enabled; + let enabled = output_config.enabled.clone(); *output .user_data() .get::>() @@ -359,7 +393,7 @@ impl Config { ) { error!(?err, "Failed to reset config for output {}.", output.name()); } else { - if enabled { + if enabled == OutputState::Enabled { output_state.enable_head(&output); } else { output_state.disable_head(&output); @@ -392,6 +426,7 @@ impl Config { .unwrap() .borrow() .enabled + == OutputState::Enabled { output_state.enable_head(&output); } else { diff --git a/src/wayland/handlers/output_configuration.rs b/src/wayland/handlers/output_configuration.rs index 433b49a7..5e3fc2cf 100644 --- a/src/wayland/handlers/output_configuration.rs +++ b/src/wayland/handlers/output_configuration.rs @@ -4,7 +4,7 @@ use smithay::output::Output; use tracing::{error, warn}; use crate::{ - config::OutputConfig, + config::{OutputConfig, OutputState}, state::State, wayland::protocols::output_configuration::{ delegate_output_configuration, ModeConfiguration, OutputConfiguration, @@ -76,9 +76,9 @@ impl State { if let Some(position) = position { current_config.position = (*position).into(); } - current_config.enabled = true; + current_config.enabled = OutputState::Enabled; } else { - current_config.enabled = false; + current_config.enabled = OutputState::Disabled; } }