mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-23 02:05:33 +07:00
Implement wlr-output-management protocol
fix: wlr_output_management use WeakOutput
This commit is contained in:
+2
-2
@@ -433,7 +433,7 @@ pub struct Output {
|
||||
}
|
||||
|
||||
/// Output mode.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
|
||||
pub struct Mode {
|
||||
/// Width in physical pixels.
|
||||
pub width: u16,
|
||||
@@ -446,7 +446,7 @@ pub struct Mode {
|
||||
}
|
||||
|
||||
/// Logical output in the compositor's coordinate space.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
|
||||
pub struct LogicalOutput {
|
||||
/// Logical X position.
|
||||
pub x: i32,
|
||||
|
||||
+17
-1
@@ -68,9 +68,13 @@ use crate::protocols::foreign_toplevel::{
|
||||
self, ForeignToplevelHandler, ForeignToplevelManagerState,
|
||||
};
|
||||
use crate::protocols::gamma_control::{GammaControlHandler, GammaControlManagerState};
|
||||
use crate::protocols::output_management::{OutputManagementHandler, OutputManagementManagerState};
|
||||
use crate::protocols::screencopy::{Screencopy, ScreencopyHandler};
|
||||
use crate::utils::{output_size, send_scale_transform};
|
||||
use crate::{delegate_foreign_toplevel, delegate_gamma_control, delegate_screencopy};
|
||||
use crate::{
|
||||
delegate_foreign_toplevel, delegate_gamma_control, delegate_output_management,
|
||||
delegate_screencopy,
|
||||
};
|
||||
|
||||
impl SeatHandler for State {
|
||||
type KeyboardFocus = WlSurface;
|
||||
@@ -545,3 +549,15 @@ delegate_xdg_activation!(State);
|
||||
|
||||
impl FractionalScaleHandler for State {}
|
||||
delegate_fractional_scale!(State);
|
||||
|
||||
impl OutputManagementHandler for State {
|
||||
fn output_management_state(&mut self) -> &mut OutputManagementManagerState {
|
||||
&mut self.niri.output_management_state
|
||||
}
|
||||
|
||||
fn apply_output_config(&mut self, config: Vec<niri_config::Output>) {
|
||||
self.niri.config.borrow_mut().outputs = config;
|
||||
self.reload_output_config();
|
||||
}
|
||||
}
|
||||
delegate_output_management!(State);
|
||||
|
||||
+15
-1
@@ -114,6 +114,7 @@ use crate::ipc::server::IpcServer;
|
||||
use crate::layout::{Layout, LayoutElement as _, MonitorRenderElement};
|
||||
use crate::protocols::foreign_toplevel::{self, ForeignToplevelManagerState};
|
||||
use crate::protocols::gamma_control::GammaControlManagerState;
|
||||
use crate::protocols::output_management::OutputManagementManagerState;
|
||||
use crate::protocols::screencopy::{Screencopy, ScreencopyManagerState};
|
||||
use crate::pw_utils::{Cast, PipeWire};
|
||||
#[cfg(feature = "xdp-gnome-screencast")]
|
||||
@@ -202,6 +203,7 @@ pub struct Niri {
|
||||
pub session_lock_state: SessionLockManagerState,
|
||||
pub foreign_toplevel_state: ForeignToplevelManagerState,
|
||||
pub screencopy_state: ScreencopyManagerState,
|
||||
pub output_management_state: OutputManagementManagerState,
|
||||
pub viewporter_state: ViewporterState,
|
||||
pub xdg_foreign_state: XdgForeignState,
|
||||
pub shm_state: ShmState,
|
||||
@@ -1051,7 +1053,7 @@ impl State {
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
|
||||
fn reload_output_config(&mut self) {
|
||||
pub fn reload_output_config(&mut self) {
|
||||
let mut resized_outputs = vec![];
|
||||
for output in self.niri.global_space.outputs() {
|
||||
let name = output.name();
|
||||
@@ -1103,6 +1105,9 @@ impl State {
|
||||
if let Some(touch) = self.niri.seat.get_touch() {
|
||||
touch.cancel(self);
|
||||
}
|
||||
|
||||
let config = self.niri.config.borrow().outputs.clone();
|
||||
self.niri.output_management_state.on_config_changed(config);
|
||||
}
|
||||
|
||||
pub fn apply_transient_output_config(&mut self, name: &str, action: niri_ipc::OutputAction) {
|
||||
@@ -1178,6 +1183,9 @@ impl State {
|
||||
|
||||
#[cfg(feature = "dbus")]
|
||||
self.niri.on_ipc_outputs_changed();
|
||||
|
||||
let new_config = self.backend.ipc_outputs().lock().unwrap().clone();
|
||||
self.niri.output_management_state.notify_changes(new_config);
|
||||
}
|
||||
|
||||
#[cfg(feature = "xdp-gnome-screencast")]
|
||||
@@ -1482,6 +1490,11 @@ impl Niri {
|
||||
ForeignToplevelManagerState::new::<State, _>(&display_handle, |client| {
|
||||
!client.get_data::<ClientState>().unwrap().restricted
|
||||
});
|
||||
let mut output_management_state =
|
||||
OutputManagementManagerState::new::<State, _>(&display_handle, |client| {
|
||||
!client.get_data::<ClientState>().unwrap().restricted
|
||||
});
|
||||
output_management_state.on_config_changed(config_.outputs.clone());
|
||||
let screencopy_state = ScreencopyManagerState::new::<State, _>(&display_handle, |client| {
|
||||
!client.get_data::<ClientState>().unwrap().restricted
|
||||
});
|
||||
@@ -1626,6 +1639,7 @@ impl Niri {
|
||||
layer_shell_state,
|
||||
session_lock_state,
|
||||
foreign_toplevel_state,
|
||||
output_management_state,
|
||||
screencopy_state,
|
||||
viewporter_state,
|
||||
xdg_foreign_state,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod foreign_toplevel;
|
||||
pub mod gamma_control;
|
||||
pub mod output_management;
|
||||
pub mod screencopy;
|
||||
|
||||
@@ -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(¤t_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(¤t_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);
|
||||
}
|
||||
new_modes.push(new_mode);
|
||||
}
|
||||
if let Some(logical) = conf.logical {
|
||||
new_head.position(logical.x, logical.y);
|
||||
new_head.transform(ipc_transform_to_smithay(logical.transform).into());
|
||||
new_head.scale(logical.scale);
|
||||
}
|
||||
new_head.enabled(conf.current_mode.is_some() as i32);
|
||||
if new_head.version() >= zwlr_output_head_v1::EVT_MAKE_SINCE {
|
||||
new_head.make(conf.make.clone());
|
||||
}
|
||||
if new_head.version() >= zwlr_output_head_v1::EVT_MODEL_SINCE {
|
||||
new_head.model(conf.model.clone());
|
||||
}
|
||||
|
||||
if new_head.version() >= zwlr_output_head_v1::EVT_ADAPTIVE_SYNC_SINCE {
|
||||
new_head.adaptive_sync(match conf.vrr_enabled {
|
||||
true => AdaptiveSyncState::Enabled,
|
||||
false => AdaptiveSyncState::Disabled,
|
||||
});
|
||||
}
|
||||
// new_head.serial_number(output.serial);
|
||||
client_data.heads.insert(output, (new_head, new_modes));
|
||||
}
|
||||
Reference in New Issue
Block a user