aboutsummaryrefslogtreecommitdiff
path: root/src/protocols
diff options
context:
space:
mode:
authortet <gmorer@pm.me>2024-05-21 22:43:42 +0000
committerIvan Molodetskikh <yalterz@gmail.com>2024-07-05 07:35:01 +0300
commit43df7fad46033692abc6a2b223e6d98f8b7a5aed (patch)
treeaa7bf3fee968b97650a84b626615f8edb6fa44d4 /src/protocols
parentd2087a2cd9f30e40778861666370df56b532af63 (diff)
downloadniri-43df7fad46033692abc6a2b223e6d98f8b7a5aed.tar.gz
niri-43df7fad46033692abc6a2b223e6d98f8b7a5aed.tar.bz2
niri-43df7fad46033692abc6a2b223e6d98f8b7a5aed.zip
Implement wlr-output-management protocol
fix: wlr_output_management use WeakOutput
Diffstat (limited to 'src/protocols')
-rw-r--r--src/protocols/mod.rs1
-rw-r--r--src/protocols/output_management.rs891
2 files changed, 892 insertions, 0 deletions
diff --git a/src/protocols/mod.rs b/src/protocols/mod.rs
index d1779b7e..a58b48bf 100644
--- a/src/protocols/mod.rs
+++ b/src/protocols/mod.rs
@@ -1,3 +1,4 @@
pub mod foreign_toplevel;
pub mod gamma_control;
+pub mod output_management;
pub mod screencopy;
diff --git a/src/protocols/output_management.rs b/src/protocols/output_management.rs
new file mode 100644
index 00000000..2842fced
--- /dev/null
+++ b/src/protocols/output_management.rs
@@ -0,0 +1,891 @@
+use std::collections::hash_map::Entry;
+use std::collections::HashMap;
+use std::iter::zip;
+use std::mem;
+
+use niri_config::FloatOrInt;
+use niri_ipc::Transform;
+use smithay::reexports::wayland_protocols_wlr::output_management::v1::server::{
+ zwlr_output_configuration_head_v1, zwlr_output_configuration_v1, zwlr_output_head_v1,
+ zwlr_output_manager_v1, zwlr_output_mode_v1,
+};
+use smithay::reexports::wayland_server::backend::ClientId;
+use smithay::reexports::wayland_server::protocol::wl_output::Transform as WlTransform;
+use smithay::reexports::wayland_server::{
+ Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, WEnum,
+};
+use zwlr_output_configuration_head_v1::ZwlrOutputConfigurationHeadV1;
+use zwlr_output_configuration_v1::ZwlrOutputConfigurationV1;
+use zwlr_output_head_v1::{AdaptiveSyncState, ZwlrOutputHeadV1};
+use zwlr_output_manager_v1::ZwlrOutputManagerV1;
+use zwlr_output_mode_v1::ZwlrOutputModeV1;
+
+use crate::backend::OutputId;
+use crate::niri::State;
+use crate::utils::ipc_transform_to_smithay;
+
+const VERSION: u32 = 4;
+
+#[derive(Debug)]
+struct ClientData {
+ heads: HashMap<OutputId, (ZwlrOutputHeadV1, Vec<ZwlrOutputModeV1>)>,
+ confs: HashMap<ZwlrOutputConfigurationV1, OutputConfigurationState>,
+ manager: ZwlrOutputManagerV1,
+}
+
+pub struct OutputManagementManagerState {
+ display: DisplayHandle,
+ serial: u32,
+ clients: HashMap<ClientId, ClientData>,
+ current_state: HashMap<OutputId, niri_ipc::Output>,
+ current_config: Vec<niri_config::Output>,
+}
+
+pub struct OutputManagementManagerGlobalData {
+ filter: Box<dyn for<'c> Fn(&'c Client) -> bool + Send + Sync>,
+}
+
+pub trait OutputManagementHandler {
+ fn output_management_state(&mut self) -> &mut OutputManagementManagerState;
+ fn apply_output_config(&mut self, config: Vec<niri_config::Output>);
+}
+
+#[derive(Debug)]
+enum OutputConfigurationState {
+ Ongoing(HashMap<OutputId, niri_config::Output>),
+ Finished,
+}
+
+pub enum OutputConfigurationHeadState {
+ Cancelled,
+ Ok(OutputId, ZwlrOutputConfigurationV1),
+}
+
+impl OutputManagementManagerState {
+ pub fn new<D, F>(display: &DisplayHandle, filter: F) -> Self
+ where
+ D: GlobalDispatch<ZwlrOutputManagerV1, OutputManagementManagerGlobalData>,
+ D: Dispatch<ZwlrOutputManagerV1, ()>,
+ D: Dispatch<ZwlrOutputHeadV1, OutputId>,
+ D: Dispatch<ZwlrOutputConfigurationV1, u32>,
+ D: Dispatch<ZwlrOutputConfigurationHeadV1, OutputConfigurationHeadState>,
+ D: Dispatch<ZwlrOutputModeV1, ()>,
+ D: OutputManagementHandler,
+ D: 'static,
+ F: for<'c> Fn(&'c Client) -> bool + Send + Sync + 'static,
+ {
+ let global_data = OutputManagementManagerGlobalData {
+ filter: Box::new(filter),
+ };
+ display.create_global::<D, ZwlrOutputManagerV1, _>(VERSION, global_data);
+
+ Self {
+ display: display.clone(),
+ clients: HashMap::new(),
+ serial: 0,
+ current_state: HashMap::new(),
+ current_config: Vec::new(),
+ }
+ }
+
+ pub fn on_config_changed(&mut self, new_config: Vec<niri_config::Output>) {
+ self.current_config = new_config;
+ }
+
+ pub fn notify_changes(&mut self, new_state: HashMap<OutputId, niri_ipc::Output>) {
+ let mut changed = false; /* most likely to end up true */
+ for (output, conf) in new_state.iter() {
+ if let Some(old) = self.current_state.get(output) {
+ if old.vrr_enabled != conf.vrr_enabled {
+ changed = true;
+ for client in self.clients.values() {
+ if let Some((head, _)) = client.heads.get(output) {
+ if head.version() >= zwlr_output_head_v1::EVT_ADAPTIVE_SYNC_SINCE {
+ head.adaptive_sync(match conf.vrr_enabled {
+ true => AdaptiveSyncState::Enabled,
+ false => AdaptiveSyncState::Disabled,
+ });
+ }
+ }
+ }
+ }
+
+ // TTY outputs can't change modes I think, however, winit and virtual outputs can.
+ let modes_changed = old.modes != conf.modes;
+ if modes_changed {
+ changed = true;
+ if old.modes.len() != conf.modes.len() {
+ error!("output's old mode count doesn't match new modes");
+ } else {
+ for client in self.clients.values() {
+ if let Some((_, modes)) = client.heads.get(output) {
+ for (wl_mode, mode) in zip(modes, &conf.modes) {
+ wl_mode.size(i32::from(mode.width), i32::from(mode.height));
+ if let Ok(refresh_rate) = mode.refresh_rate.try_into() {
+ wl_mode.refresh(refresh_rate);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ match (old.current_mode, conf.current_mode) {
+ (Some(old_index), Some(new_index)) => {
+ if old.modes.len() == conf.modes.len()
+ && (modes_changed || old_index != new_index)
+ {
+ changed = true;
+ for client in self.clients.values() {
+ if let Some((head, modes)) = client.heads.get(output) {
+ if let Some(new_mode) = modes.get(new_index) {
+ head.current_mode(new_mode);
+ } else {
+ error!(
+ "output new mode doesnt exist for the client's output"
+ );
+ }
+ }
+ }
+ }
+ }
+ (Some(_), None) => {
+ changed = true;
+ for client in self.clients.values() {
+ if let Some((head, _)) = client.heads.get(output) {
+ head.enabled(0);
+ }
+ }
+ }
+ (None, Some(new_index)) => {
+ if old.modes.len() == conf.modes.len() {
+ changed = true;
+ for client in self.clients.values() {
+ if let Some((head, modes)) = client.heads.get(output) {
+ head.enabled(1);
+ if let Some(mode) = modes.get(new_index) {
+ head.current_mode(mode);
+ } else {
+ error!(
+ "output new mode doesnt exist for the client's output"
+ );
+ }
+ }
+ }
+ }
+ }
+ (None, None) => {}
+ }
+ match (old.logical, conf.logical) {
+ (Some(old_logical), Some(new_logical)) => {
+ if old_logical != new_logical {
+ changed = true;
+ for client in self.clients.values() {
+ if let Some((head, _)) = client.heads.get(output) {
+ if old_logical.x != new_logical.x
+ || old_logical.y != new_logical.y
+ {
+ head.position(new_logical.x, new_logical.y);
+ }
+ if old_logical.scale != new_logical.scale {
+ head.scale(new_logical.scale);
+ }
+ if old_logical.transform != new_logical.transform {
+ head.transform(
+ ipc_transform_to_smithay(new_logical.transform).into(),
+ );
+ }
+ }
+ }
+ }
+ }
+ (None, Some(new_logical)) => {
+ changed = true;
+ for client in self.clients.values() {
+ if let Some((head, _)) = client.heads.get(output) {
+ // head enable in the mode diff check
+ head.position(new_logical.x, new_logical.y);
+ head.transform(
+ ipc_transform_to_smithay(new_logical.transform).into(),
+ );
+ head.scale(new_logical.scale);
+ }
+ }
+ }
+ (Some(_), None) => {
+ // heads disabled in the mode diff check
+ }
+ (None, None) => {}
+ }
+ } else {
+ changed = true;
+ notify_new_head(self, output, conf);
+ }
+ }
+ for (old, _) in self.current_state.iter() {
+ if !new_state.contains_key(old) {
+ changed = true;
+ notify_removed_head(&mut self.clients, old);
+ }
+ }
+ if changed {
+ self.current_state = new_state;
+ self.serial += 1;
+ for data in self.clients.values() {
+ data.manager.done(self.serial);
+ for conf in data.confs.keys() {
+ conf.cancelled();
+ }
+ }
+ }
+ }
+}
+
+impl<D> GlobalDispatch<ZwlrOutputManagerV1, OutputManagementManagerGlobalData, D>
+ for OutputManagementManagerState
+where
+ D: GlobalDispatch<ZwlrOutputManagerV1, OutputManagementManagerGlobalData>,
+ D: Dispatch<ZwlrOutputManagerV1, ()>,
+ D: Dispatch<ZwlrOutputHeadV1, OutputId>,
+ D: Dispatch<ZwlrOutputConfigurationV1, u32>,
+ D: Dispatch<ZwlrOutputConfigurationHeadV1, OutputConfigurationHeadState>,
+ D: Dispatch<ZwlrOutputModeV1, ()>,
+ D: OutputManagementHandler,
+ D: 'static,
+{
+ fn bind(
+ state: &mut D,
+ display: &DisplayHandle,
+ client: &Client,
+ manager: New<ZwlrOutputManagerV1>,
+ _manager_state: &OutputManagementManagerGlobalData,
+ data_init: &mut DataInit<'_, D>,
+ ) {
+ let manager = data_init.init(manager, ());
+ let g_state = state.output_management_state();
+ let mut client_data = ClientData {
+ heads: HashMap::new(),
+ confs: HashMap::new(),
+ manager: manager.clone(),
+ };
+ for (output, conf) in &g_state.current_state {
+ send_new_head::<D>(display, client, &mut client_data, *output, conf);
+ }
+ g_state.clients.insert(client.id(), client_data);
+ manager.done(g_state.serial);
+ }
+
+ fn can_view(client: Client, global_data: &OutputManagementManagerGlobalData) -> bool {
+ (global_data.filter)(&client)
+ }
+}
+
+impl<D> Dispatch<ZwlrOutputManagerV1, (), D> for OutputManagementManagerState
+where
+ D: GlobalDispatch<ZwlrOutputManagerV1, OutputManagementManagerGlobalData>,
+ D: Dispatch<ZwlrOutputManagerV1, ()>,
+ D: Dispatch<ZwlrOutputHeadV1, OutputId>,
+ D: Dispatch<ZwlrOutputConfigurationV1, u32>,
+ D: Dispatch<ZwlrOutputConfigurationHeadV1, OutputConfigurationHeadState>,
+ D: Dispatch<ZwlrOutputModeV1, ()>,
+ D: OutputManagementHandler,
+ D: 'static,
+{
+ fn request(
+ state: &mut D,
+ client: &Client,
+ _manager: &ZwlrOutputManagerV1,
+ request: zwlr_output_manager_v1::Request,
+ _data: &(),
+ _display: &DisplayHandle,
+ data_init: &mut DataInit<'_, D>,
+ ) {
+ match request {
+ zwlr_output_manager_v1::Request::CreateConfiguration { id, serial } => {
+ let g_state = state.output_management_state();
+ let conf = data_init.init(id, serial);
+ if let Some(client_data) = g_state.clients.get_mut(&client.id()) {
+ if serial != g_state.serial {
+ conf.cancelled();
+ }
+ let state = OutputConfigurationState::Ongoing(HashMap::new());
+ client_data.confs.insert(conf, state);
+ } else {
+ error!("CreateConfiguration: missing client data");
+ }
+ }
+ zwlr_output_manager_v1::Request::Stop => {
+ if let Some(c) = state.output_management_state().clients.remove(&client.id()) {
+ c.manager.finished()
+ }
+ }
+ _ => unreachable!(),
+ }
+ }
+ fn destroyed(state: &mut D, client: ClientId, _resource: &ZwlrOutputManagerV1, _data: &()) {
+ state.output_management_state().clients.remove(&client);
+ }
+}
+
+impl<D> Dispatch<ZwlrOutputConfigurationV1, u32, D> for OutputManagementManagerState
+where
+ D: GlobalDispatch<ZwlrOutputManagerV1, OutputManagementManagerGlobalData>,
+ D: Dispatch<ZwlrOutputManagerV1, ()>,
+ D: Dispatch<ZwlrOutputHeadV1, OutputId>,
+ D: Dispatch<ZwlrOutputConfigurationV1, u32>,
+ D: Dispatch<ZwlrOutputConfigurationHeadV1, OutputConfigurationHeadState>,
+ D: Dispatch<ZwlrOutputModeV1, ()>,
+ D: OutputManagementHandler,
+ D: 'static,
+{
+ fn request(
+ state: &mut D,
+ client: &Client,
+ conf: &ZwlrOutputConfigurationV1,
+ request: zwlr_output_configuration_v1::Request,
+ serial: &u32,
+ _display: &DisplayHandle,
+ data_init: &mut DataInit<'_, D>,
+ ) {
+ let g_state = state.output_management_state();
+ let outdated = *serial != g_state.serial;
+ if outdated {
+ debug!("OutputConfiguration: request from an outdated configuration");
+ }
+
+ let new_config = g_state
+ .clients
+ .get_mut(&client.id())
+ .and_then(|data| data.confs.get_mut(conf));
+ if new_config.is_none() {
+ error!("OutputConfiguration: request from unknown configuration object");
+ }
+
+ match request {
+ zwlr_output_configuration_v1::Request::EnableHead { id, head } => {
+ let Some(output) = head.data::<OutputId>() else {
+ error!("EnableHead: Missing attached output");
+ let _fail = data_init.init(id, OutputConfigurationHeadState::Cancelled);
+ return;
+ };
+ if outdated {
+ let _fail = data_init.init(id, OutputConfigurationHeadState::Cancelled);
+ return;
+ }
+
+ let Some(new_config) = new_config else {
+ let _fail = data_init.init(id, OutputConfigurationHeadState::Cancelled);
+ return;
+ };
+
+ let OutputConfigurationState::Ongoing(new_config) = new_config else {
+ let _fail = data_init.init(id, OutputConfigurationHeadState::Cancelled);
+ conf.post_error(
+ zwlr_output_configuration_v1::Error::AlreadyUsed,
+ "configuration had already been used",
+ );
+ return;
+ };
+
+ let Some(current_config) = g_state.current_state.get(output) else {
+ error!("EnableHead: output missing from current config");
+ let _fail = data_init.init(id, OutputConfigurationHeadState::Cancelled);
+ return;
+ };
+
+ match new_config.entry(*output) {
+ Entry::Occupied(_) => {
+ let _fail = data_init.init(id, OutputConfigurationHeadState::Cancelled);
+ conf.post_error(
+ zwlr_output_configuration_v1::Error::AlreadyConfiguredHead,
+ "head has been already configured",
+ );
+ return;
+ }
+ Entry::Vacant(entry) => {
+ let mut config = g_state
+ .current_config
+ .iter()
+ .find(|o| o.name.eq_ignore_ascii_case(&current_config.name))
+ .cloned()
+ .unwrap_or_else(|| niri_config::Output {
+ name: current_config.name.clone(),
+ ..Default::default()
+ });
+ config.off = false;
+ entry.insert(config);
+ }
+ };
+
+ data_init.init(id, OutputConfigurationHeadState::Ok(*output, conf.clone()));
+ }
+ zwlr_output_configuration_v1::Request::DisableHead { head } => {
+ if outdated {
+ return;
+ }
+ let Some(output) = head.data::<OutputId>() else {
+ error!("DisableHead: missing attached output head name");
+ return;
+ };
+
+ let Some(new_config) = new_config else {
+ return;
+ };
+
+ let OutputConfigurationState::Ongoing(new_config) = new_config else {
+ conf.post_error(
+ zwlr_output_configuration_v1::Error::AlreadyUsed,
+ "configuration had already been used",
+ );
+ return;
+ };
+
+ let Some(current_config) = g_state.current_state.get(output) else {
+ error!("EnableHead: output missing from current config");
+ return;
+ };
+
+ match new_config.entry(*output) {
+ Entry::Occupied(_) => {
+ conf.post_error(
+ zwlr_output_configuration_v1::Error::AlreadyConfiguredHead,
+ "head has been already configured",
+ );
+ }
+ Entry::Vacant(entry) => {
+ let mut config = g_state
+ .current_config
+ .iter()
+ .find(|o| o.name.eq_ignore_ascii_case(&current_config.name))
+ .cloned()
+ .unwrap_or_else(|| niri_config::Output {
+ name: current_config.name.clone(),
+ ..Default::default()
+ });
+ config.off = true;
+ entry.insert(config);
+ }
+ };
+ }
+ zwlr_output_configuration_v1::Request::Apply => {
+ if outdated {
+ conf.cancelled();
+ return;
+ }
+
+ let Some(new_config) = new_config else {
+ return;
+ };
+
+ let OutputConfigurationState::Ongoing(new_config) =
+ mem::replace(new_config, OutputConfigurationState::Finished)
+ else {
+ conf.post_error(
+ zwlr_output_configuration_v1::Error::AlreadyUsed,
+ "configuration had already been used",
+ );
+ return;
+ };
+
+ let any_enabled = new_config.values().any(|c| !c.off);
+ if !any_enabled {
+ conf.failed();
+ return;
+ }
+
+ state.apply_output_config(new_config.into_values().collect());
+ // FIXME: verify that it had been applied successfully (which may be difficult).
+ conf.succeeded();
+ }
+ zwlr_output_configuration_v1::Request::Test => {
+ if outdated {
+ conf.cancelled();
+ return;
+ }
+
+ let Some(new_config) = new_config else {
+ return;
+ };
+
+ let OutputConfigurationState::Ongoing(new_config) =
+ mem::replace(new_config, OutputConfigurationState::Finished)
+ else {
+ conf.post_error(
+ zwlr_output_configuration_v1::Error::AlreadyUsed,
+ "configuration had already been used",
+ );
+ return;
+ };
+
+ let any_enabled = new_config.values().any(|c| !c.off);
+ if !any_enabled {
+ conf.failed();
+ return;
+ }
+
+ // FIXME: actually test the configuration with TTY.
+ conf.succeeded()
+ }
+ zwlr_output_configuration_v1::Request::Destroy => {
+ g_state
+ .clients
+ .get_mut(&client.id())
+ .map(|d| d.confs.remove(conf));
+ }
+ _ => unreachable!(),
+ }
+ }
+}
+
+impl<D> Dispatch<ZwlrOutputConfigurationHeadV1, OutputConfigurationHeadState, D>
+ for OutputManagementManagerState
+where
+ D: GlobalDispatch<ZwlrOutputManagerV1, OutputManagementManagerGlobalData>,
+ D: Dispatch<ZwlrOutputManagerV1, ()>,
+ D: Dispatch<ZwlrOutputHeadV1, OutputId>,
+ D: Dispatch<ZwlrOutputConfigurationV1, u32>,
+ D: Dispatch<ZwlrOutputConfigurationHeadV1, OutputConfigurationHeadState>,
+ D: Dispatch<ZwlrOutputModeV1, ()>,
+ D: OutputManagementHandler,
+ D: 'static,
+{
+ fn request(
+ state: &mut D,
+ client: &Client,
+ conf_head: &ZwlrOutputConfigurationHeadV1,
+ request: zwlr_output_configuration_head_v1::Request,
+ data: &OutputConfigurationHeadState,
+ _display: &DisplayHandle,
+ _data_init: &mut DataInit<'_, D>,
+ ) {
+ let g_state = state.output_management_state();
+ let Some(client_data) = g_state.clients.get_mut(&client.id()) else {
+ error!("ConfigurationHead: missing client data");
+ return;
+ };
+ let OutputConfigurationHeadState::Ok(output_id, conf) = data else {
+ warn!("ConfigurationHead: request sent to a cancelled head");
+ return;
+ };
+ let Some(serial) = conf.data::<u32>() else {
+ error!("ConfigurationHead: missing serial");
+ return;
+ };
+ if *serial != g_state.serial {
+ warn!("ConfigurationHead: request sent to an outdated");
+ return;
+ }
+ let Some(new_config) = client_data.confs.get_mut(conf) else {
+ error!("ConfigurationHead: unknown configuration");
+ return;
+ };
+ let OutputConfigurationState::Ongoing(new_config) = new_config else {
+ conf.post_error(
+ zwlr_output_configuration_v1::Error::AlreadyUsed,
+ "configuration had already been used",
+ );
+ return;
+ };
+ let Some(new_config) = new_config.get_mut(output_id) else {
+ error!("ConfigurationHead: config missing from enabled heads");
+ return;
+ };
+
+ match request {
+ zwlr_output_configuration_head_v1::Request::SetMode { mode } => {
+ let index = match client_data
+ .heads
+ .get(output_id)
+ .map(|(_, mods)| mods.iter().position(|m| m.id() == mode.id()))
+ {
+ Some(Some(index)) => index,
+ _ => {
+ warn!("SetMode: failed to find requested mode");
+ conf_head.post_error(
+ zwlr_output_configuration_head_v1::Error::InvalidMode,
+ "failed to find requested mode",
+ );
+ return;
+ }
+ };
+
+ let Some(current_config) = g_state.current_state.get(output_id) else {
+ warn!("SetMode: output missing from the current config");
+ return;
+ };
+
+ let Some(mode) = current_config.modes.get(index) else {
+ error!("SetMode: requested mode is out of range");
+ return;
+ };
+
+ new_config.mode = Some(niri_ipc::ConfiguredMode {
+ width: mode.width,
+ height: mode.height,
+ refresh: Some(mode.refresh_rate as f64 / 1000.),
+ });
+ }
+ zwlr_output_configuration_head_v1::Request::SetCustomMode {
+ width,
+ height,
+ refresh,
+ } => {
+ // FIXME: Support custom mode
+ let (width, height, refresh): (u16, u16, u32) =
+ match (width.try_into(), height.try_into(), refresh.try_into()) {
+ (Ok(width), Ok(height), Ok(refresh)) => (width, height, refresh),
+ _ => {
+ warn!("SetCustomMode: invalid input data");
+ return;
+ }
+ };
+
+ let Some(current_config) = g_state.current_state.get(output_id) else {
+ warn!("SetMode: output missing from the current config");
+ return;
+ };
+
+ let Some(mode) = current_config.modes.iter().find(|m| {
+ m.width == width
+ && m.height == height
+ && (refresh == 0 || m.refresh_rate == refresh)
+ }) else {
+ warn!("SetCustomMode: no matching mode");
+ return;
+ };
+
+ new_config.mode = Some(niri_ipc::ConfiguredMode {
+ width: mode.width,
+ height: mode.height,
+ refresh: Some(mode.refresh_rate as f64 / 1000.),
+ });
+ }
+ zwlr_output_configuration_head_v1::Request::SetPosition { x, y } => {
+ new_config.position = Some(niri_config::Position { x, y });
+ }
+ zwlr_output_configuration_head_v1::Request::SetTransform { transform } => {
+ let transform = match transform {
+ WEnum::Value(WlTransform::Normal) => Transform::Normal,
+ WEnum::Value(WlTransform::_90) => Transform::_90,
+ WEnum::Value(WlTransform::_180) => Transform::_180,
+ WEnum::Value(WlTransform::_270) => Transform::_270,
+ WEnum::Value(WlTransform::Flipped) => Transform::Flipped,
+ WEnum::Value(WlTransform::Flipped90) => Transform::Flipped90,
+ WEnum::Value(WlTransform::Flipped180) => Transform::Flipped180,
+ WEnum::Value(WlTransform::Flipped270) => Transform::Flipped270,
+ _ => {
+ warn!("SetTransform: unknown requested transform");
+ conf_head.post_error(
+ zwlr_output_configuration_head_v1::Error::InvalidTransform,
+ "unknown transform value",
+ );
+ return;
+ }
+ };
+ new_config.transform = transform;
+ }
+ zwlr_output_configuration_head_v1::Request::SetScale { scale } => {
+ if scale <= 0. {
+ conf_head.post_error(
+ zwlr_output_configuration_head_v1::Error::InvalidScale,
+ "scale is negative or zero",
+ );
+ return;
+ }
+ new_config.scale = Some(FloatOrInt(scale));
+ }
+ zwlr_output_configuration_head_v1::Request::SetAdaptiveSync { state } => {
+ let enabled = match state {
+ WEnum::Value(AdaptiveSyncState::Enabled) => true,
+ WEnum::Value(AdaptiveSyncState::Disabled) => false,
+ _ => {
+ warn!("SetAdaptativeSync: unknown requested adaptative sync");
+ conf_head.post_error(
+ zwlr_output_configuration_head_v1::Error::InvalidAdaptiveSyncState,
+ "unknown adaptive sync value",
+ );
+ return;
+ }
+ };
+ new_config.variable_refresh_rate = enabled;
+ }
+ _ => unreachable!(),
+ }
+ }
+}
+
+impl<D> Dispatch<ZwlrOutputHeadV1, OutputId, D> for OutputManagementManagerState
+where
+ D: GlobalDispatch<ZwlrOutputManagerV1, OutputManagementManagerGlobalData>,
+ D: Dispatch<ZwlrOutputManagerV1, ()>,
+ D: Dispatch<ZwlrOutputHeadV1, OutputId>,
+ D: Dispatch<ZwlrOutputConfigurationV1, u32>,
+ D: Dispatch<ZwlrOutputConfigurationHeadV1, OutputConfigurationHeadState>,
+ D: Dispatch<ZwlrOutputModeV1, ()>,
+ D: OutputManagementHandler,
+ D: 'static,
+{
+ fn request(
+ _state: &mut D,
+ _client: &Client,
+ _output_head: &ZwlrOutputHeadV1,
+ request: zwlr_output_head_v1::Request,
+ _data: &OutputId,
+ _display: &DisplayHandle,
+ _data_init: &mut DataInit<'_, D>,
+ ) {
+ match request {
+ zwlr_output_head_v1::Request::Release => {}
+ _ => unreachable!(),
+ }
+ }
+ fn destroyed(state: &mut D, client: ClientId, _resource: &ZwlrOutputHeadV1, data: &OutputId) {
+ if let Some(c) = state.output_management_state().clients.get_mut(&client) {
+ c.heads.remove(data);
+ }
+ }
+}
+
+impl<D> Dispatch<ZwlrOutputModeV1, (), D> for OutputManagementManagerState
+where
+ D: GlobalDispatch<ZwlrOutputManagerV1, OutputManagementManagerGlobalData>,
+ D: Dispatch<ZwlrOutputManagerV1, ()>,
+ D: Dispatch<ZwlrOutputHeadV1, OutputId>,
+ D: Dispatch<ZwlrOutputConfigurationV1, u32>,
+ D: Dispatch<ZwlrOutputConfigurationHeadV1, OutputConfigurationHeadState>,
+ D: Dispatch<ZwlrOutputModeV1, ()>,
+ D: OutputManagementHandler,
+ D: 'static,
+{
+ fn request(
+ _state: &mut D,
+ _client: &Client,
+ _mode: &ZwlrOutputModeV1,
+ request: zwlr_output_mode_v1::Request,
+ _data: &(),
+ _display: &DisplayHandle,
+ _data_init: &mut DataInit<'_, D>,
+ ) {
+ match request {
+ zwlr_output_mode_v1::Request::Release => {}
+ _ => unreachable!(),
+ }
+ }
+}
+
+#[macro_export]
+macro_rules! delegate_output_management{
+ ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
+ smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
+ smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_manager_v1::ZwlrOutputManagerV1: $crate::protocols::output_management::OutputManagementManagerGlobalData
+ ] => $crate::protocols::output_management::OutputManagementManagerState);
+ smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
+ smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_manager_v1::ZwlrOutputManagerV1: ()
+ ] => $crate::protocols::output_management::OutputManagementManagerState);
+ smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
+ smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_configuration_v1::ZwlrOutputConfigurationV1: u32
+ ] => $crate::protocols::output_management::OutputManagementManagerState);
+ smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
+ smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_head_v1::ZwlrOutputHeadV1: $crate::backend::OutputId
+ ] => $crate::protocols::output_management::OutputManagementManagerState);
+ smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
+ smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_mode_v1::ZwlrOutputModeV1: ()
+ ] => $crate::protocols::output_management::OutputManagementManagerState);
+ smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
+ smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_configuration_head_v1::ZwlrOutputConfigurationHeadV1: $crate::protocols::output_management::OutputConfigurationHeadState
+ ] => $crate::protocols::output_management::OutputManagementManagerState);
+ };
+}
+
+fn notify_removed_head(clients: &mut HashMap<ClientId, ClientData>, head: &OutputId) {
+ for data in clients.values_mut() {
+ if let Some((head, mods)) = data.heads.remove(head) {
+ mods.iter().for_each(|m| m.finished());
+ head.finished();
+ }
+ }
+}
+
+fn notify_new_head(
+ state: &mut OutputManagementManagerState,
+ output: &OutputId,
+ conf: &niri_ipc::Output,
+) {
+ let display = &state.display;
+ let clients = &mut state.clients;
+ for data in clients.values_mut() {
+ if let Some(client) = data.manager.client() {
+ send_new_head::<State>(display, &client, data, *output, conf);
+ }
+ }
+}
+
+fn send_new_head<D>(
+ display: &DisplayHandle,
+ client: &Client,
+ client_data: &mut ClientData,
+ output: OutputId,
+ conf: &niri_ipc::Output,
+) where
+ D: GlobalDispatch<ZwlrOutputManagerV1, OutputManagementManagerGlobalData>,
+ D: Dispatch<ZwlrOutputManagerV1, ()>,
+ D: Dispatch<ZwlrOutputConfigurationV1, u32>,
+ D: Dispatch<ZwlrOutputConfigurationHeadV1, OutputConfigurationHeadState>,
+ D: Dispatch<ZwlrOutputModeV1, ()>,
+ D: OutputManagementHandler,
+ D: 'static,
+ D: Dispatch<ZwlrOutputHeadV1, OutputId>,
+ D: Dispatch<ZwlrOutputModeV1, ()>,
+ D: 'static,
+{
+ let new_head = client
+ .create_resource::<ZwlrOutputHeadV1, _, D>(display, client_data.manager.version(), output)
+ .unwrap();
+ client_data.manager.head(&new_head);
+ new_head.name(conf.name.clone());
+ new_head.description(format!("{} - {} - {}", conf.make, conf.model, conf.name));
+ if let Some((width, height)) = conf.physical_size {
+ if let (Ok(a), Ok(b)) = (width.try_into(), height.try_into()) {
+ new_head.physical_size(a, b);
+ }
+ }
+ let mut new_modes = Vec::with_capacity(conf.modes.len());
+ for (index, mode) in conf.modes.iter().enumerate() {
+ let new_mode = client
+ .create_resource::<ZwlrOutputModeV1, _, D>(display, new_head.version(), ())
+ .unwrap();
+ new_head.mode(&new_mode);
+ new_mode.size(i32::from(mode.width), i32::from(mode.height));
+ if mode.is_preferred {
+ new_mode.preferred();
+ }
+ if let Ok(refresh_rate) = mode.refresh_rate.try_into() {
+ new_mode.refresh(refresh_rate);
+ }
+ if Some(index) == conf.current_mode {
+ new_head.current_mode(&new_mode);
+