mirror of
https://github.com/YaLTeR/niri.git
synced 2024-10-26 20:04:05 +03:00
feat: wlr output management
This commit is contained in:
parent
39632e9c1e
commit
e2e341e5f1
@ -429,7 +429,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,
|
||||
@ -442,7 +442,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,
|
||||
|
@ -62,14 +62,19 @@ use smithay::{
|
||||
delegate_virtual_keyboard_manager, delegate_xdg_activation,
|
||||
};
|
||||
|
||||
use crate::backend::IpcOutputMap;
|
||||
use crate::niri::{ClientState, State};
|
||||
use crate::protocols::foreign_toplevel::{
|
||||
self, ForeignToplevelHandler, ForeignToplevelManagerState,
|
||||
};
|
||||
use crate::protocols::gamma_control::{GammaControlHandler, GammaControlManagerState};
|
||||
use crate::protocols::output_management::OutputManagementHandler;
|
||||
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::utils::{logical_output, output_size, send_scale_transform};
|
||||
use crate::{
|
||||
delegate_foreign_toplevel, delegate_gamma_control, delegate_output_management,
|
||||
delegate_screencopy,
|
||||
};
|
||||
|
||||
impl SeatHandler for State {
|
||||
type KeyboardFocus = WlSurface;
|
||||
@ -544,3 +549,30 @@ delegate_xdg_activation!(State);
|
||||
|
||||
impl FractionalScaleHandler for State {}
|
||||
delegate_fractional_scale!(State);
|
||||
|
||||
impl OutputManagementHandler for State {
|
||||
fn output_management_state(
|
||||
&mut self,
|
||||
) -> &mut crate::protocols::output_management::OutputManagementManagerState {
|
||||
&mut self.niri.output_management_state
|
||||
}
|
||||
|
||||
fn get_outputs(&self) -> IpcOutputMap {
|
||||
self.backend.ipc_outputs().lock().unwrap().clone()
|
||||
}
|
||||
|
||||
fn get_logical(&self, name: &str) -> Option<niri_ipc::LogicalOutput> {
|
||||
self.niri
|
||||
.global_space
|
||||
.outputs()
|
||||
.find(|output| output.name() == *name)
|
||||
.map(logical_output)
|
||||
}
|
||||
|
||||
fn apply_new_conf(&mut self, conf: Vec<niri_config::Output>) {
|
||||
self.niri.config.borrow_mut().outputs = conf;
|
||||
self.backend.on_output_config_changed(&mut self.niri);
|
||||
}
|
||||
}
|
||||
|
||||
delegate_output_management!(State);
|
||||
|
12
src/niri.rs
12
src/niri.rs
@ -112,6 +112,9 @@ 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::{
|
||||
self, OutputManagementHandler, OutputManagementManagerState,
|
||||
};
|
||||
use crate::protocols::screencopy::{Screencopy, ScreencopyManagerState};
|
||||
use crate::pw_utils::{Cast, PipeWire};
|
||||
use crate::render_helpers::debug::draw_opaque_regions;
|
||||
@ -198,6 +201,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,
|
||||
@ -1167,6 +1171,9 @@ impl State {
|
||||
|
||||
#[cfg(feature = "dbus")]
|
||||
self.niri.on_ipc_outputs_changed();
|
||||
|
||||
let ipc_outputs = self.backend.ipc_outputs().lock().unwrap().clone();
|
||||
output_management::notify_changes(self.output_management_state(), ipc_outputs);
|
||||
}
|
||||
|
||||
#[cfg(feature = "xdp-gnome-screencast")]
|
||||
@ -1369,6 +1376,10 @@ impl Niri {
|
||||
ForeignToplevelManagerState::new::<State, _>(&display_handle, |client| {
|
||||
!client.get_data::<ClientState>().unwrap().restricted
|
||||
});
|
||||
let output_management_state =
|
||||
OutputManagementManagerState::new::<State, _>(&display_handle, |client| {
|
||||
!client.get_data::<ClientState>().unwrap().restricted
|
||||
});
|
||||
let screencopy_state = ScreencopyManagerState::new::<State, _>(&display_handle, |client| {
|
||||
!client.get_data::<ClientState>().unwrap().restricted
|
||||
});
|
||||
@ -1513,6 +1524,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;
|
||||
|
807
src/protocols/output_management.rs
Normal file
807
src/protocols/output_management.rs
Normal file
@ -0,0 +1,807 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::backend::IpcOutputMap;
|
||||
use crate::niri::State;
|
||||
use crate::utils::ipc_transform_to_smithay;
|
||||
use niri_ipc::{LogicalOutput, Transform};
|
||||
use smithay::reexports::wayland_server::{
|
||||
protocol::wl_output::Transform as WlTransform, Client, DataInit, Dispatch, DisplayHandle,
|
||||
GlobalDispatch, New, Resource, WEnum,
|
||||
};
|
||||
use smithay::reexports::{
|
||||
wayland_protocols_wlr::output_management::v1::server::{
|
||||
zwlr_output_configuration_head_v1::{self, ZwlrOutputConfigurationHeadV1},
|
||||
zwlr_output_configuration_v1::{self, ZwlrOutputConfigurationV1},
|
||||
zwlr_output_head_v1::{self, AdaptiveSyncState, ZwlrOutputHeadV1},
|
||||
zwlr_output_manager_v1::{self, ZwlrOutputManagerV1},
|
||||
zwlr_output_mode_v1::{self, ZwlrOutputModeV1},
|
||||
},
|
||||
wayland_server::backend::ClientId,
|
||||
};
|
||||
|
||||
const VERSION: u32 = 4;
|
||||
|
||||
/*
|
||||
<clientId<
|
||||
<confID , IpcOutputMap>
|
||||
<headId, String /* output name */>
|
||||
>>
|
||||
*/
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OutputConfigurationDataInner {
|
||||
serial: u32,
|
||||
new_out: IpcOutputMap,
|
||||
}
|
||||
|
||||
pub type OutputConfigurationData = Mutex<OutputConfigurationDataInner>;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ClientData {
|
||||
heads: HashMap<String, (ZwlrOutputHeadV1, Vec<ZwlrOutputModeV1>)>,
|
||||
confs: HashSet<ZwlrOutputConfigurationV1>,
|
||||
manager: ZwlrOutputManagerV1,
|
||||
}
|
||||
|
||||
pub struct OutputManagementManagerState {
|
||||
display: DisplayHandle,
|
||||
serial: u32,
|
||||
clients: HashMap<ClientId, ClientData>,
|
||||
current_out: Option<IpcOutputMap>,
|
||||
}
|
||||
|
||||
pub struct OutputManagementManagerGlobalData {
|
||||
filter: Box<dyn for<'c> Fn(&'c Client) -> bool + Send + Sync>,
|
||||
}
|
||||
|
||||
impl OutputManagementManagerState {
|
||||
pub fn new<D, F>(display: &DisplayHandle, filter: F) -> Self
|
||||
where
|
||||
D: GlobalDispatch<ZwlrOutputManagerV1, OutputManagementManagerGlobalData>,
|
||||
D: Dispatch<ZwlrOutputManagerV1, ()>,
|
||||
D: Dispatch<ZwlrOutputHeadV1, String>,
|
||||
D: Dispatch<ZwlrOutputConfigurationV1, OutputConfigurationData>,
|
||||
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_out: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> GlobalDispatch<ZwlrOutputManagerV1, OutputManagementManagerGlobalData, D>
|
||||
for OutputManagementManagerState
|
||||
where
|
||||
D: GlobalDispatch<ZwlrOutputManagerV1, OutputManagementManagerGlobalData>,
|
||||
D: Dispatch<ZwlrOutputManagerV1, ()>,
|
||||
D: Dispatch<ZwlrOutputHeadV1, String>,
|
||||
D: Dispatch<ZwlrOutputConfigurationV1, OutputConfigurationData>,
|
||||
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 outputs = state.get_outputs();
|
||||
let g_state = state.output_management_state();
|
||||
let mut client_data = ClientData {
|
||||
heads: HashMap::new(),
|
||||
confs: HashSet::new(),
|
||||
manager: manager.clone(),
|
||||
};
|
||||
for (_, head) in outputs {
|
||||
send_new_head::<D>(display, client, &mut client_data, &head);
|
||||
}
|
||||
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, String>,
|
||||
D: Dispatch<ZwlrOutputConfigurationV1, OutputConfigurationData>,
|
||||
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 new_out = match &state.output_management_state().current_out {
|
||||
Some(out) => out.clone(),
|
||||
None => {
|
||||
let current_out = state.get_outputs();
|
||||
state.output_management_state().current_out = Some(current_out.clone());
|
||||
current_out
|
||||
}
|
||||
};
|
||||
let g_state = state.output_management_state();
|
||||
let conf = data_init.init(
|
||||
id,
|
||||
Mutex::new(OutputConfigurationDataInner { serial, new_out }),
|
||||
);
|
||||
if let Some(client_data) = g_state.clients.get_mut(&client.id()) {
|
||||
if serial != g_state.serial {
|
||||
conf.cancelled();
|
||||
}
|
||||
client_data.confs.insert(conf);
|
||||
} else {
|
||||
error!("CreateConfiguration: missing client data");
|
||||
}
|
||||
}
|
||||
zwlr_output_manager_v1::Request::Stop => {
|
||||
let clients = &mut state.output_management_state().clients;
|
||||
clients.get(&client.id()).map(|c| c.manager.finished());
|
||||
clients.remove(&client.id());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> Dispatch<ZwlrOutputConfigurationV1, OutputConfigurationData, D>
|
||||
for OutputManagementManagerState
|
||||
where
|
||||
D: GlobalDispatch<ZwlrOutputManagerV1, OutputManagementManagerGlobalData>,
|
||||
D: Dispatch<ZwlrOutputManagerV1, ()>,
|
||||
D: Dispatch<ZwlrOutputHeadV1, String>,
|
||||
D: Dispatch<ZwlrOutputConfigurationV1, OutputConfigurationData>,
|
||||
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,
|
||||
conf_data: &OutputConfigurationData,
|
||||
_display: &DisplayHandle,
|
||||
data_init: &mut DataInit<'_, D>,
|
||||
) {
|
||||
let g_state = state.output_management_state();
|
||||
let outdated = conf_data.lock().unwrap().serial != g_state.serial;
|
||||
match request {
|
||||
zwlr_output_configuration_v1::Request::EnableHead { id, head } => {
|
||||
let head_name = match head.data::<String>() {
|
||||
Some(name) => name,
|
||||
None => {
|
||||
error!("EnableHead: Missing attached output head name");
|
||||
let _fail = data_init.init(id, OutputConfigurationHeadState::Cancelled);
|
||||
return;
|
||||
}
|
||||
};
|
||||
if outdated {
|
||||
info!("EnableHead: request from an outdated configuration");
|
||||
let _fail = data_init.init(id, OutputConfigurationHeadState::Cancelled);
|
||||
return;
|
||||
}
|
||||
let mut conf_data = conf_data.lock().unwrap();
|
||||
let output = match conf_data.new_out.get_mut(head_name) {
|
||||
Some(out) => out,
|
||||
None => {
|
||||
error!("EnableHead: output configuration missing requested new head",);
|
||||
let _fail = data_init.init(id, OutputConfigurationHeadState::Cancelled);
|
||||
return;
|
||||
}
|
||||
};
|
||||
if output.current_mode.is_none() {
|
||||
output.current_mode = output.modes.iter().position(|a| a.is_preferred);
|
||||
}
|
||||
let current_mode = match output.current_mode {
|
||||
Some(current) => current,
|
||||
None => {
|
||||
error!("EnableHead: missing output prefered mode");
|
||||
let _fail = data_init.init(id, OutputConfigurationHeadState::Cancelled);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let mode = output.modes[current_mode];
|
||||
data_init.init(
|
||||
id,
|
||||
OutputConfigurationHeadState::Ok(head_name.clone(), conf.clone()),
|
||||
);
|
||||
let logical = output
|
||||
.logical
|
||||
.or_else(|| state.get_logical(&head_name))
|
||||
.unwrap_or(LogicalOutput {
|
||||
x: 0,
|
||||
y: 0,
|
||||
scale: 1.,
|
||||
transform: Transform::Normal,
|
||||
width: mode.width as u32,
|
||||
height: mode.height as u32,
|
||||
});
|
||||
output.logical = Some(logical);
|
||||
}
|
||||
zwlr_output_configuration_v1::Request::DisableHead { head } => {
|
||||
if outdated {
|
||||
return;
|
||||
}
|
||||
if let Some(head_name) = head.data::<String>() {
|
||||
if let Some(output) = conf_data.lock().unwrap().new_out.get_mut(head_name) {
|
||||
output.current_mode = None;
|
||||
output.logical = None;
|
||||
}
|
||||
} else {
|
||||
error!("DisableHead: missing attached output head name");
|
||||
return;
|
||||
}
|
||||
}
|
||||
zwlr_output_configuration_v1::Request::Apply => {
|
||||
if outdated {
|
||||
return;
|
||||
}
|
||||
let conf_data = conf_data.lock().unwrap();
|
||||
if conf_data.serial != g_state.serial {
|
||||
return;
|
||||
}
|
||||
let mut new_conf = Vec::with_capacity(conf_data.new_out.len());
|
||||
for (_, out) in conf_data.new_out.iter() {
|
||||
new_conf.push(match (out.current_mode, out.logical) {
|
||||
(Some(current_mode), Some(logical)) => niri_config::Output {
|
||||
off: !(out.current_mode.is_some() && out.logical.is_some()),
|
||||
name: out.name.clone(),
|
||||
scale: Some(niri_config::FloatOrInt(logical.scale)),
|
||||
transform: logical.transform,
|
||||
position: Some(niri_config::Position {
|
||||
x: logical.x,
|
||||
y: logical.y,
|
||||
}),
|
||||
mode: Some(niri_ipc::ConfiguredMode {
|
||||
width: logical.width as u16,
|
||||
height: logical.height as u16,
|
||||
refresh: Some(out.modes[current_mode].refresh_rate as f64 / 1000.),
|
||||
}),
|
||||
variable_refresh_rate: false,
|
||||
},
|
||||
_ => niri_config::Output {
|
||||
off: !(out.current_mode.is_some() && out.logical.is_some()),
|
||||
name: out.name.clone(),
|
||||
scale: None,
|
||||
transform: Transform::Normal,
|
||||
position: None,
|
||||
mode: None,
|
||||
variable_refresh_rate: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
drop(conf_data);
|
||||
state.apply_new_conf(new_conf);
|
||||
conf.succeeded();
|
||||
}
|
||||
zwlr_output_configuration_v1::Request::Test => {
|
||||
if outdated {
|
||||
conf.cancelled()
|
||||
} else {
|
||||
conf.succeeded()
|
||||
}
|
||||
}
|
||||
zwlr_output_configuration_v1::Request::Destroy => {
|
||||
g_state
|
||||
.clients
|
||||
.get_mut(&client.id())
|
||||
.map(|d| d.confs.remove(conf));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum OutputConfigurationHeadState {
|
||||
Cancelled,
|
||||
Ok(String, ZwlrOutputConfigurationV1),
|
||||
}
|
||||
|
||||
impl<D> Dispatch<ZwlrOutputConfigurationHeadV1, OutputConfigurationHeadState, D>
|
||||
for OutputManagementManagerState
|
||||
where
|
||||
D: GlobalDispatch<ZwlrOutputManagerV1, OutputManagementManagerGlobalData>,
|
||||
D: Dispatch<ZwlrOutputManagerV1, ()>,
|
||||
D: Dispatch<ZwlrOutputHeadV1, String>,
|
||||
D: Dispatch<ZwlrOutputConfigurationV1, OutputConfigurationData>,
|
||||
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 client_data = match g_state.clients.get_mut(&client.id()) {
|
||||
Some(data) => data,
|
||||
_ => {
|
||||
error!("ConfigurationHead: missing client data");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let (output_name, conf) = match data {
|
||||
OutputConfigurationHeadState::Cancelled => {
|
||||
warn!("ConfigurationHead: request sent to a cancelled head");
|
||||
return;
|
||||
}
|
||||
OutputConfigurationHeadState::Ok(name, conf) => (name, conf),
|
||||
};
|
||||
let conf_data = match conf.data::<OutputConfigurationData>() {
|
||||
Some(data) => data,
|
||||
None => {
|
||||
error!("ConfigurationHead: missing conf data");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let mut conf_data = conf_data.lock().unwrap();
|
||||
if conf_data.serial != g_state.serial {
|
||||
warn!("ConfigurationHead: request sent to an outdated");
|
||||
return;
|
||||
}
|
||||
let new_out = match conf_data.new_out.get_mut(output_name) {
|
||||
Some(out) => out,
|
||||
None => {
|
||||
error!("ConfigurationHead: missing matching head conf data");
|
||||
return;
|
||||
}
|
||||
};
|
||||
match request {
|
||||
zwlr_output_configuration_head_v1::Request::SetMode { mode } => {
|
||||
let index = match client_data
|
||||
.heads
|
||||
.get(output_name)
|
||||
.map(|(_, mods)| mods.iter().position(|m| m.id() == mode.id()))
|
||||
{
|
||||
Some(Some(index)) => index,
|
||||
_ => {
|
||||
error!("SetMode: failed to find requested mode");
|
||||
return;
|
||||
}
|
||||
};
|
||||
if new_out.modes.len() <= index {
|
||||
error!("SetMode: requested mode is out of range");
|
||||
return;
|
||||
}
|
||||
new_out.current_mode = Some(index);
|
||||
}
|
||||
zwlr_output_configuration_head_v1::Request::SetCustomMode {
|
||||
width,
|
||||
height,
|
||||
refresh,
|
||||
} => {
|
||||
// TODO: 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;
|
||||
}
|
||||
};
|
||||
if let Some(index) = new_out.modes.iter().position(|m| {
|
||||
m.width == width && m.height == height && m.refresh_rate == refresh
|
||||
}) {
|
||||
new_out.current_mode = Some(index)
|
||||
} else {
|
||||
error!("SetCustomMode: no matching mode");
|
||||
return;
|
||||
}
|
||||
}
|
||||
zwlr_output_configuration_head_v1::Request::SetPosition { x, y } => {
|
||||
match &mut new_out.logical {
|
||||
Some(mut logical) => {
|
||||
logical.x = x;
|
||||
logical.y = y;
|
||||
}
|
||||
None => {
|
||||
error!("SetPosition: head is disabled");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
zwlr_output_configuration_head_v1::Request::SetTransform { transform } => {
|
||||
let logical = match &mut new_out.logical {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
error!("SetTransform: head is disabled");
|
||||
return;
|
||||
}
|
||||
};
|
||||
logical.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,
|
||||
_ => {
|
||||
error!("SetTransform: unknown requested transform");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
zwlr_output_configuration_head_v1::Request::SetScale { scale } => {
|
||||
let logical = match &mut new_out.logical {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
error!("SetScale: head is disabled");
|
||||
return;
|
||||
}
|
||||
};
|
||||
logical.scale = scale;
|
||||
}
|
||||
zwlr_output_configuration_head_v1::Request::SetAdaptiveSync { state } => match state {
|
||||
WEnum::Value(AdaptiveSyncState::Enabled) => new_out.vrr_enabled = true,
|
||||
WEnum::Value(AdaptiveSyncState::Disabled) => new_out.vrr_enabled = false,
|
||||
_ => {
|
||||
error!("SetAdaptativeSync: Unknown requested adaptative sync");
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> Dispatch<ZwlrOutputHeadV1, String, D> for OutputManagementManagerState
|
||||
where
|
||||
D: GlobalDispatch<ZwlrOutputManagerV1, OutputManagementManagerGlobalData>,
|
||||
D: Dispatch<ZwlrOutputManagerV1, ()>,
|
||||
D: Dispatch<ZwlrOutputHeadV1, String>,
|
||||
D: Dispatch<ZwlrOutputConfigurationV1, OutputConfigurationData>,
|
||||
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: &String,
|
||||
_display: &DisplayHandle,
|
||||
_data_init: &mut DataInit<'_, D>,
|
||||
) {
|
||||
match request {
|
||||
zwlr_output_head_v1::Request::Release => {
|
||||
let g_state = state.output_management_state();
|
||||
let client_data = match g_state.clients.get_mut(&client.id()) {
|
||||
Some(data) => data,
|
||||
_ => {
|
||||
error!("Release: missing client data");
|
||||
return;
|
||||
}
|
||||
};
|
||||
client_data.heads.remove(data).map(|(h, _)| h.finished());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> Dispatch<ZwlrOutputModeV1, (), D> for OutputManagementManagerState
|
||||
where
|
||||
D: GlobalDispatch<ZwlrOutputManagerV1, OutputManagementManagerGlobalData>,
|
||||
D: Dispatch<ZwlrOutputManagerV1, ()>,
|
||||
D: Dispatch<ZwlrOutputHeadV1, String>,
|
||||
D: Dispatch<ZwlrOutputConfigurationV1, OutputConfigurationData>,
|
||||
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 => mode.finished(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub trait OutputManagementHandler {
|
||||
fn get_outputs(&self) -> IpcOutputMap;
|
||||
fn output_management_state(&mut self) -> &mut OutputManagementManagerState;
|
||||
fn get_logical(&self, name: &str) -> Option<LogicalOutput>;
|
||||
fn apply_new_conf(&mut self, conf: Vec<niri_config::Output>);
|
||||
}
|
||||
|
||||
#[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: $crate::protocols::output_management::OutputConfigurationData
|
||||
] => $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: String
|
||||
] => $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>, name: &str) {
|
||||
for (_, data) in clients.iter_mut() {
|
||||
data.heads.remove(name).map(|(head, mods)| {
|
||||
mods.iter().for_each(|m| m.finished());
|
||||
head.finished();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn notify_new_head(state: &mut OutputManagementManagerState, output: &niri_ipc::Output) {
|
||||
let display = &state.display;
|
||||
let clients = &mut state.clients;
|
||||
for (_, data) in clients.iter_mut() {
|
||||
if let Some(client) = data.manager.client() {
|
||||
send_new_head::<State>(&display, &client, data, output);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_new_head<D>(
|
||||
display: &DisplayHandle,
|
||||
client: &Client,
|
||||
client_data: &mut ClientData,
|
||||
output: &niri_ipc::Output,
|
||||
) where
|
||||
D: GlobalDispatch<ZwlrOutputManagerV1, OutputManagementManagerGlobalData>,
|
||||
D: Dispatch<ZwlrOutputManagerV1, ()>,
|
||||
D: Dispatch<ZwlrOutputHeadV1, String>,
|
||||
D: Dispatch<ZwlrOutputConfigurationV1, OutputConfigurationData>,
|
||||
D: Dispatch<ZwlrOutputConfigurationHeadV1, OutputConfigurationHeadState>,
|
||||
D: Dispatch<ZwlrOutputModeV1, ()>,
|
||||
D: OutputManagementHandler,
|
||||
D: 'static,
|
||||
D: Dispatch<ZwlrOutputHeadV1, String>,
|
||||
D: Dispatch<ZwlrOutputModeV1, ()>,
|
||||
D: 'static,
|
||||
{
|
||||
let new_head = client
|
||||
.create_resource::<ZwlrOutputHeadV1, _, D>(
|
||||
display,
|
||||
client_data.manager.version(),
|
||||
output.name.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
client_data.manager.head(&new_head);
|
||||
new_head.name(output.name.clone());
|
||||
new_head.description(output.name.clone());
|
||||
if let Some((width, height)) = output.physical_size {
|
||||
if let (Ok(a), Ok(b)) = (width.try_into(), height.try_into()) {
|
||||
new_head.physical_size(a, b);
|
||||
}
|
||||
}
|
||||
let mut index = 0;
|
||||
let mut new_modes = Vec::with_capacity(output.modes.len());
|
||||
for mode in &output.modes {
|
||||
let new_mode = client
|
||||
.create_resource::<ZwlrOutputModeV1, _, D>(display, new_head.version(), ())
|
||||
.unwrap();
|
||||
new_head.mode(&new_mode);
|
||||
if let (Ok(width), Ok(height)) = (mode.width.try_into(), mode.height.try_into()) {
|
||||
new_mode.size(width, 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) == output.current_mode {
|
||||
new_head.current_mode(&new_mode);
|
||||
}
|
||||
new_modes.push(new_mode);
|
||||
index += 1;
|
||||
}
|
||||
if let Some(logical) = output.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(output.current_mode.is_some() as i32);
|
||||
new_head.make(output.make.clone());
|
||||
new_head.model(output.model.clone());
|
||||
|
||||
new_head.adaptive_sync(match output.vrr_enabled {
|
||||
true => AdaptiveSyncState::Enabled,
|
||||
false => AdaptiveSyncState::Disabled,
|
||||
});
|
||||
// new_head.serial_number(output.serial);
|
||||
client_data
|
||||
.heads
|
||||
.insert(output.name.clone(), (new_head, new_modes));
|
||||
}
|
||||
|
||||
pub fn notify_changes(state: &mut OutputManagementManagerState, new_out: IpcOutputMap) {
|
||||
let mut changed = false; /* most likely to endup true */
|
||||
for (name, new) in new_out.iter() {
|
||||
if let Some(old) = state.current_out.as_ref().map(|c| c.get(name)).flatten() {
|
||||
if old.vrr_enabled != new.vrr_enabled {
|
||||
changed = true;
|
||||
for (_, client) in &state.clients {
|
||||
if let Some((head, _)) = client.heads.get(name) {
|
||||
head.adaptive_sync(match new.vrr_enabled {
|
||||
true => AdaptiveSyncState::Enabled,
|
||||
false => AdaptiveSyncState::Disabled,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
match (old.current_mode, new.current_mode) {
|
||||
(Some(old_index), Some(new_index)) => {
|
||||
if old.modes.len() <= old_index
|
||||
|| old.modes.len() <= new_index
|
||||
|| new.modes.len() <= new_index
|
||||
{
|
||||
error!("notify_changes: out of bound mode");
|
||||
} else if old.modes[old_index] != new.modes[new_index] {
|
||||
changed = true;
|
||||
if old.modes[new_index] == new.modes[new_index] {
|
||||
for (_, client) in &state.clients {
|
||||
if let Some((head, modes)) = client.heads.get(name) {
|
||||
head.current_mode(&modes[new_index]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The mod has not been registerd with the head :/
|
||||
}
|
||||
}
|
||||
}
|
||||
(Some(_), None) => {
|
||||
changed = true;
|
||||
for (_, client) in &state.clients {
|
||||
if let Some((head, _)) = client.heads.get(name) {
|
||||
head.enabled(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
(None, Some(new_index)) => {
|
||||
changed = true;
|
||||
for (_, client) in &state.clients {
|
||||
if let Some((head, _)) = client.heads.get(name) {
|
||||
head.enabled(1);
|
||||
if old.modes.len() <= new_index || new.modes.len() <= new_index {
|
||||
error!("notify_changes: out of bound mode");
|
||||
} else if old.modes[new_index] == new.modes[new_index] {
|
||||
for (_, client) in &state.clients {
|
||||
if let Some((head, modes)) = client.heads.get(name) {
|
||||
head.current_mode(&modes[new_index]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The mod has not been registerd with the head :/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(None, None) => {}
|
||||
}
|
||||
match (old.logical, new.logical) {
|
||||
(Some(old_logical), Some(new_logical)) => {
|
||||
if old_logical != new_logical {
|
||||
changed = true;
|
||||
for (_, client) in &state.clients {
|
||||
if let Some((head, _)) = client.heads.get(name) {
|
||||
if old_logical.x != new_logical.x || old_logical.y != old_logical.x
|
||||
{
|
||||
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 &state.clients {
|
||||
if let Some((head, _)) = client.heads.get(name) {
|
||||
head.enabled(0);
|
||||
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) => {
|
||||
changed = true;
|
||||
for (_, client) in &state.clients {
|
||||
if let Some((head, _)) = client.heads.get(name) {
|
||||
head.enabled(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
(None, None) => {}
|
||||
}
|
||||
} else {
|
||||
changed = true;
|
||||
notify_new_head(state, new);
|
||||
}
|
||||
}
|
||||
if let Some(old_out) = &state.current_out {
|
||||
for (old, _) in old_out.iter() {
|
||||
if new_out.get(old).is_none() {
|
||||
changed = true;
|
||||
notify_removed_head(&mut state.clients, old);
|
||||
}
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
state.current_out = Some(new_out);
|
||||
state.serial += 1;
|
||||
for (_, data) in state.clients.iter() {
|
||||
data.manager.done(state.serial);
|
||||
for conf in data.confs.iter() {
|
||||
conf.cancelled();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user