mirror of
https://github.com/YaLTeR/niri.git
synced 2024-10-26 20:04:05 +03:00
Merge branch 'YaLTeR:main' into main
This commit is contained in:
commit
d128c7c837
@ -23,7 +23,7 @@ pub struct Config {
|
||||
#[knuffel(child, default)]
|
||||
pub input: Input,
|
||||
#[knuffel(children(name = "output"))]
|
||||
pub outputs: Vec<Output>,
|
||||
pub outputs: Outputs,
|
||||
#[knuffel(children(name = "spawn-at-startup"))]
|
||||
pub spawn_at_startup: Vec<SpawnAtStartup>,
|
||||
#[knuffel(child, default)]
|
||||
@ -289,6 +289,9 @@ pub struct Touch {
|
||||
pub map_to_output: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct Outputs(pub Vec<Output>);
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
||||
pub struct Output {
|
||||
#[knuffel(child)]
|
||||
@ -894,6 +897,7 @@ pub struct Binds(pub Vec<Bind>);
|
||||
pub struct Bind {
|
||||
pub key: Key,
|
||||
pub action: Action,
|
||||
pub repeat: bool,
|
||||
pub cooldown: Option<Duration>,
|
||||
pub allow_when_locked: bool,
|
||||
}
|
||||
@ -957,6 +961,8 @@ pub enum Action {
|
||||
FocusColumnLast,
|
||||
FocusColumnRightOrFirst,
|
||||
FocusColumnLeftOrLast,
|
||||
FocusWindowOrMonitorUp,
|
||||
FocusWindowOrMonitorDown,
|
||||
FocusColumnOrMonitorLeft,
|
||||
FocusColumnOrMonitorRight,
|
||||
FocusWindowDown,
|
||||
@ -1035,6 +1041,8 @@ impl From<niri_ipc::Action> for Action {
|
||||
niri_ipc::Action::FocusColumnLast => Self::FocusColumnLast,
|
||||
niri_ipc::Action::FocusColumnRightOrFirst => Self::FocusColumnRightOrFirst,
|
||||
niri_ipc::Action::FocusColumnLeftOrLast => Self::FocusColumnLeftOrLast,
|
||||
niri_ipc::Action::FocusWindowOrMonitorUp => Self::FocusWindowOrMonitorUp,
|
||||
niri_ipc::Action::FocusWindowOrMonitorDown => Self::FocusWindowOrMonitorDown,
|
||||
niri_ipc::Action::FocusColumnOrMonitorLeft => Self::FocusColumnOrMonitorLeft,
|
||||
niri_ipc::Action::FocusColumnOrMonitorRight => Self::FocusColumnOrMonitorRight,
|
||||
niri_ipc::Action::FocusWindowDown => Self::FocusWindowDown,
|
||||
@ -1525,6 +1533,24 @@ fn expect_only_children<S>(
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<Output> for Outputs {
|
||||
fn from_iter<T: IntoIterator<Item = Output>>(iter: T) -> Self {
|
||||
Self(Vec::from_iter(iter))
|
||||
}
|
||||
}
|
||||
|
||||
impl Outputs {
|
||||
pub fn find(&self, name: &str) -> Option<&Output> {
|
||||
self.0.iter().find(|o| o.name.eq_ignore_ascii_case(name))
|
||||
}
|
||||
|
||||
pub fn find_mut(&mut self, name: &str) -> Option<&mut Output> {
|
||||
self.0
|
||||
.iter_mut()
|
||||
.find(|o| o.name.eq_ignore_ascii_case(name))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for DefaultColumnWidth
|
||||
where
|
||||
S: knuffel::traits::ErrorSpan,
|
||||
@ -2203,11 +2229,15 @@ where
|
||||
.parse::<Key>()
|
||||
.map_err(|e| DecodeError::conversion(&node.node_name, e.wrap_err("invalid keybind")))?;
|
||||
|
||||
let mut repeat = true;
|
||||
let mut cooldown = None;
|
||||
let mut allow_when_locked = false;
|
||||
let mut allow_when_locked_node = None;
|
||||
for (name, val) in &node.properties {
|
||||
match &***name {
|
||||
"repeat" => {
|
||||
repeat = knuffel::traits::DecodeScalar::decode(val, ctx)?;
|
||||
}
|
||||
"cooldown-ms" => {
|
||||
cooldown = Some(Duration::from_millis(
|
||||
knuffel::traits::DecodeScalar::decode(val, ctx)?,
|
||||
@ -2235,6 +2265,7 @@ where
|
||||
let dummy = Self {
|
||||
key,
|
||||
action: Action::Spawn(vec![]),
|
||||
repeat: true,
|
||||
cooldown: None,
|
||||
allow_when_locked: false,
|
||||
};
|
||||
@ -2262,6 +2293,7 @@ where
|
||||
Ok(Self {
|
||||
key,
|
||||
action,
|
||||
repeat,
|
||||
cooldown,
|
||||
allow_when_locked,
|
||||
})
|
||||
@ -2646,7 +2678,7 @@ mod tests {
|
||||
focus_follows_mouse: true,
|
||||
workspace_auto_back_and_forth: true,
|
||||
},
|
||||
outputs: vec![Output {
|
||||
outputs: Outputs(vec![Output {
|
||||
off: false,
|
||||
name: "eDP-1".to_owned(),
|
||||
scale: Some(FloatOrInt(2.)),
|
||||
@ -2658,7 +2690,7 @@ mod tests {
|
||||
refresh: Some(144.),
|
||||
}),
|
||||
variable_refresh_rate: true,
|
||||
}],
|
||||
}]),
|
||||
layout: Layout {
|
||||
focus_ring: FocusRing {
|
||||
off: false,
|
||||
@ -2830,6 +2862,7 @@ mod tests {
|
||||
modifiers: Modifiers::COMPOSITOR,
|
||||
},
|
||||
action: Action::Spawn(vec!["alacritty".to_owned()]),
|
||||
repeat: true,
|
||||
cooldown: None,
|
||||
allow_when_locked: true,
|
||||
},
|
||||
@ -2839,6 +2872,7 @@ mod tests {
|
||||
modifiers: Modifiers::COMPOSITOR,
|
||||
},
|
||||
action: Action::CloseWindow,
|
||||
repeat: true,
|
||||
cooldown: None,
|
||||
allow_when_locked: false,
|
||||
},
|
||||
@ -2848,6 +2882,7 @@ mod tests {
|
||||
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT,
|
||||
},
|
||||
action: Action::FocusMonitorLeft,
|
||||
repeat: true,
|
||||
cooldown: None,
|
||||
allow_when_locked: false,
|
||||
},
|
||||
@ -2857,6 +2892,7 @@ mod tests {
|
||||
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT | Modifiers::CTRL,
|
||||
},
|
||||
action: Action::MoveWindowToMonitorRight,
|
||||
repeat: true,
|
||||
cooldown: None,
|
||||
allow_when_locked: false,
|
||||
},
|
||||
@ -2866,6 +2902,7 @@ mod tests {
|
||||
modifiers: Modifiers::COMPOSITOR,
|
||||
},
|
||||
action: Action::ConsumeWindowIntoColumn,
|
||||
repeat: true,
|
||||
cooldown: None,
|
||||
allow_when_locked: false,
|
||||
},
|
||||
@ -2875,6 +2912,7 @@ mod tests {
|
||||
modifiers: Modifiers::COMPOSITOR,
|
||||
},
|
||||
action: Action::FocusWorkspace(WorkspaceReference::Index(1)),
|
||||
repeat: true,
|
||||
cooldown: None,
|
||||
allow_when_locked: false,
|
||||
},
|
||||
@ -2886,6 +2924,7 @@ mod tests {
|
||||
action: Action::FocusWorkspace(WorkspaceReference::Name(
|
||||
"workspace-1".to_string(),
|
||||
)),
|
||||
repeat: true,
|
||||
cooldown: None,
|
||||
allow_when_locked: false,
|
||||
},
|
||||
@ -2895,6 +2934,7 @@ mod tests {
|
||||
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT,
|
||||
},
|
||||
action: Action::Quit(true),
|
||||
repeat: true,
|
||||
cooldown: None,
|
||||
allow_when_locked: false,
|
||||
},
|
||||
@ -2904,6 +2944,7 @@ mod tests {
|
||||
modifiers: Modifiers::COMPOSITOR,
|
||||
},
|
||||
action: Action::FocusWorkspaceDown,
|
||||
repeat: true,
|
||||
cooldown: Some(Duration::from_millis(150)),
|
||||
allow_when_locked: false,
|
||||
},
|
||||
|
@ -120,6 +120,10 @@ pub enum Action {
|
||||
FocusColumnRightOrFirst,
|
||||
/// Focus the next column to the left, looping if at start.
|
||||
FocusColumnLeftOrLast,
|
||||
/// Focus the window or the monitor above.
|
||||
FocusWindowOrMonitorUp,
|
||||
/// Focus the window or the monitor below.
|
||||
FocusWindowOrMonitorDown,
|
||||
/// Focus the column or the monitor to the left.
|
||||
FocusColumnOrMonitorLeft,
|
||||
/// Focus the column or the monitor to the right.
|
||||
@ -433,7 +437,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 +450,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,
|
||||
|
@ -9,6 +9,7 @@ use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
|
||||
use crate::input::CompositorMod;
|
||||
use crate::niri::Niri;
|
||||
use crate::utils::id::IdCounter;
|
||||
|
||||
pub mod tty;
|
||||
pub use tty::Tty;
|
||||
@ -31,7 +32,22 @@ pub enum RenderResult {
|
||||
Skipped,
|
||||
}
|
||||
|
||||
pub type IpcOutputMap = HashMap<String, niri_ipc::Output>;
|
||||
pub type IpcOutputMap = HashMap<OutputId, niri_ipc::Output>;
|
||||
|
||||
static OUTPUT_ID_COUNTER: IdCounter = IdCounter::new();
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct OutputId(u32);
|
||||
|
||||
impl OutputId {
|
||||
fn next() -> OutputId {
|
||||
OutputId(OUTPUT_ID_COUNTER.next())
|
||||
}
|
||||
|
||||
pub fn get(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
pub fn init(&mut self, niri: &mut Niri) {
|
||||
|
@ -57,6 +57,7 @@ use wayland_protocols::wp::linux_dmabuf::zv1::server::zwp_linux_dmabuf_feedback_
|
||||
use wayland_protocols::wp::presentation_time::server::wp_presentation_feedback;
|
||||
|
||||
use super::{IpcOutputMap, RenderResult};
|
||||
use crate::backend::OutputId;
|
||||
use crate::frame_clock::FrameClock;
|
||||
use crate::niri::{Niri, RedrawState, State};
|
||||
use crate::render_helpers::debug::draw_damage;
|
||||
@ -118,6 +119,7 @@ pub struct OutputDevice {
|
||||
render_node: DrmNode,
|
||||
drm_scanner: DrmScanner,
|
||||
surfaces: HashMap<crtc::Handle, Surface>,
|
||||
output_ids: HashMap<crtc::Handle, OutputId>,
|
||||
// SAFETY: drop after all the objects used with them are dropped.
|
||||
// See https://github.com/Smithay/smithay/issues/1102.
|
||||
drm: DrmDevice,
|
||||
@ -575,6 +577,7 @@ impl Tty {
|
||||
gbm,
|
||||
drm_scanner: DrmScanner::new(),
|
||||
surfaces: HashMap::new(),
|
||||
output_ids: HashMap::new(),
|
||||
drm_lease_state,
|
||||
active_leases: Vec::new(),
|
||||
non_desktop_connectors: HashSet::new(),
|
||||
@ -599,6 +602,7 @@ impl Tty {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut removed = Vec::new();
|
||||
for event in device.drm_scanner.scan_connectors(&device.drm) {
|
||||
match event {
|
||||
DrmScanEvent::Connected {
|
||||
@ -611,11 +615,27 @@ impl Tty {
|
||||
}
|
||||
DrmScanEvent::Disconnected {
|
||||
crtc: Some(crtc), ..
|
||||
} => self.connector_disconnected(niri, node, crtc),
|
||||
} => {
|
||||
self.connector_disconnected(niri, node, crtc);
|
||||
removed.push(crtc);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: this is better done in connector_disconnected(), but currently we call that to
|
||||
// turn off outputs temporarily, too. So we can't do this there.
|
||||
let Some(device) = self.devices.get_mut(&node) else {
|
||||
error!("device disappeared");
|
||||
return;
|
||||
};
|
||||
|
||||
for crtc in removed {
|
||||
if device.output_ids.remove(&crtc).is_none() {
|
||||
error!("output ID missing for disconnected crtc: {crtc:?}");
|
||||
}
|
||||
}
|
||||
|
||||
self.refresh_ipc_outputs(niri);
|
||||
}
|
||||
|
||||
@ -724,12 +744,15 @@ impl Tty {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// This should be unique per CRTC connection, however currently we can call
|
||||
// connector_connected() multiple times for turning the output off and on.
|
||||
device.output_ids.entry(crtc).or_insert_with(OutputId::next);
|
||||
|
||||
let config = self
|
||||
.config
|
||||
.borrow()
|
||||
.outputs
|
||||
.iter()
|
||||
.find(|o| o.name.eq_ignore_ascii_case(&output_name))
|
||||
.find(&output_name)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
@ -1464,7 +1487,7 @@ impl Tty {
|
||||
|
||||
for (node, device) in &self.devices {
|
||||
for (connector, crtc) in device.drm_scanner.crtcs() {
|
||||
let connector_name = format!(
|
||||
let name = format!(
|
||||
"{}-{}",
|
||||
connector.interface().as_str(),
|
||||
connector.interface_id(),
|
||||
@ -1527,7 +1550,7 @@ impl Tty {
|
||||
.map(logical_output);
|
||||
|
||||
let ipc_output = niri_ipc::Output {
|
||||
name: connector_name.clone(),
|
||||
name,
|
||||
make,
|
||||
model,
|
||||
physical_size,
|
||||
@ -1538,7 +1561,11 @@ impl Tty {
|
||||
logical,
|
||||
};
|
||||
|
||||
ipc_outputs.insert(connector_name, ipc_output);
|
||||
let id = device.output_ids.get(&crtc).copied().unwrap_or_else(|| {
|
||||
error!("output ID missing for crtc: {crtc:?}");
|
||||
OutputId::next()
|
||||
});
|
||||
ipc_outputs.insert(id, ipc_output);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1605,8 +1632,7 @@ impl Tty {
|
||||
.config
|
||||
.borrow()
|
||||
.outputs
|
||||
.iter()
|
||||
.find(|o| o.name.eq_ignore_ascii_case(&surface.name))
|
||||
.find(&surface.name)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
if config.off {
|
||||
@ -1735,8 +1761,7 @@ impl Tty {
|
||||
.config
|
||||
.borrow()
|
||||
.outputs
|
||||
.iter()
|
||||
.find(|o| o.name.eq_ignore_ascii_case(&output_name))
|
||||
.find(&output_name)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
|
@ -17,7 +17,7 @@ use smithay::reexports::wayland_protocols::wp::presentation_time::server::wp_pre
|
||||
use smithay::reexports::winit::dpi::LogicalSize;
|
||||
use smithay::reexports::winit::window::Window;
|
||||
|
||||
use super::{IpcOutputMap, RenderResult};
|
||||
use super::{IpcOutputMap, OutputId, RenderResult};
|
||||
use crate::niri::{Niri, RedrawState, State};
|
||||
use crate::render_helpers::debug::draw_damage;
|
||||
use crate::render_helpers::{resources, shaders, RenderTarget};
|
||||
@ -61,7 +61,7 @@ impl Winit {
|
||||
|
||||
let physical_properties = output.physical_properties();
|
||||
let ipc_outputs = Arc::new(Mutex::new(HashMap::from([(
|
||||
"winit".to_owned(),
|
||||
OutputId::next(),
|
||||
niri_ipc::Output {
|
||||
name: output.name(),
|
||||
make: physical_properties.make,
|
||||
@ -98,7 +98,7 @@ impl Winit {
|
||||
|
||||
{
|
||||
let mut ipc_outputs = winit.ipc_outputs.lock().unwrap();
|
||||
let output = ipc_outputs.get_mut("winit").unwrap();
|
||||
let output = ipc_outputs.values_mut().next().unwrap();
|
||||
let mode = &mut output.modes[0];
|
||||
mode.width = size.w.clamp(0, u16::MAX as i32) as u16;
|
||||
mode.height = size.h.clamp(0, u16::MAX as i32) as u16;
|
||||
|
@ -57,11 +57,12 @@ impl DisplayConfig {
|
||||
.ipc_outputs
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.values()
|
||||
// Take only enabled outputs.
|
||||
.filter(|(_, output)| output.current_mode.is_some() && output.logical.is_some())
|
||||
.map(|(c, output)| {
|
||||
.filter(|output| output.current_mode.is_some() && output.logical.is_some())
|
||||
.map(|output| {
|
||||
// Loosely matches the check in Mutter.
|
||||
let c = &output.name;
|
||||
let is_laptop_panel = matches!(c.get(..4), Some("eDP-" | "LVDS" | "DSI-"));
|
||||
|
||||
// FIXME: use proper serial when we have libdisplay-info.
|
||||
|
@ -191,7 +191,11 @@ impl Session {
|
||||
) -> fdo::Result<OwnedObjectPath> {
|
||||
debug!(connector, ?properties, "record_monitor");
|
||||
|
||||
let Some(output) = self.ipc_outputs.lock().unwrap().get(connector).cloned() else {
|
||||
let output = {
|
||||
let ipc_outputs = self.ipc_outputs.lock().unwrap();
|
||||
ipc_outputs.values().find(|o| o.name == connector).cloned()
|
||||
};
|
||||
let Some(output) = output else {
|
||||
return Err(fdo::Error::Failed("no such monitor".to_owned()));
|
||||
};
|
||||
|
||||
|
@ -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: niri_config::Outputs) {
|
||||
self.niri.config.borrow_mut().outputs = config;
|
||||
self.reload_output_config();
|
||||
}
|
||||
}
|
||||
delegate_output_management!(State);
|
||||
|
@ -294,6 +294,19 @@ impl State {
|
||||
let time = Event::time_msec(&event);
|
||||
let pressed = event.state() == KeyState::Pressed;
|
||||
|
||||
// Stop bind key repeat on any release. This won't work 100% correctly in cases like:
|
||||
// 1. Press Mod
|
||||
// 2. Press Left (repeat starts)
|
||||
// 3. Press PgDown (new repeat starts)
|
||||
// 4. Release Left (PgDown repeat stops)
|
||||
// But it's good enough for now.
|
||||
// FIXME: handle this properly.
|
||||
if !pressed {
|
||||
if let Some(token) = self.niri.bind_repeat_timer.take() {
|
||||
self.niri.event_loop.remove(token);
|
||||
}
|
||||
}
|
||||
|
||||
let Some(Some(bind)) = self.niri.seat.get_keyboard().unwrap().input(
|
||||
self,
|
||||
event.key_code(),
|
||||
@ -330,12 +343,44 @@ impl State {
|
||||
return;
|
||||
};
|
||||
|
||||
// Filter actions when the key is released or the session is locked.
|
||||
if !pressed {
|
||||
return;
|
||||
}
|
||||
|
||||
self.handle_bind(bind);
|
||||
self.handle_bind(bind.clone());
|
||||
|
||||
// Start the key repeat timer if necessary.
|
||||
if !bind.repeat {
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop the previous key repeat if any.
|
||||
if let Some(token) = self.niri.bind_repeat_timer.take() {
|
||||
self.niri.event_loop.remove(token);
|
||||
}
|
||||
|
||||
let config = self.niri.config.borrow();
|
||||
let config = &config.input.keyboard;
|
||||
|
||||
let repeat_rate = config.repeat_rate;
|
||||
if repeat_rate == 0 {
|
||||
return;
|
||||
}
|
||||
let repeat_duration = Duration::from_secs_f64(1. / f64::from(repeat_rate));
|
||||
|
||||
let repeat_timer =
|
||||
Timer::from_duration(Duration::from_millis(u64::from(config.repeat_delay)));
|
||||
|
||||
let token = self
|
||||
.niri
|
||||
.event_loop
|
||||
.insert_source(repeat_timer, move |_, _, state| {
|
||||
state.handle_bind(bind.clone());
|
||||
TimeoutAction::ToDuration(repeat_duration)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
self.niri.bind_repeat_timer = Some(token);
|
||||
}
|
||||
|
||||
pub fn handle_bind(&mut self, bind: Bind) {
|
||||
@ -596,6 +641,40 @@ impl State {
|
||||
// FIXME: granular
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
Action::FocusWindowOrMonitorUp => {
|
||||
if let Some(output) = self.niri.output_up() {
|
||||
if self.niri.layout.focus_window_up_or_output(&output)
|
||||
&& !self.maybe_warp_cursor_to_focus_centered()
|
||||
{
|
||||
self.move_cursor_to_output(&output);
|
||||
} else {
|
||||
self.maybe_warp_cursor_to_focus();
|
||||
}
|
||||
} else {
|
||||
self.niri.layout.focus_up();
|
||||
self.maybe_warp_cursor_to_focus();
|
||||
}
|
||||
|
||||
// FIXME: granular
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
Action::FocusWindowOrMonitorDown => {
|
||||
if let Some(output) = self.niri.output_down() {
|
||||
if self.niri.layout.focus_window_down_or_output(&output)
|
||||
&& !self.maybe_warp_cursor_to_focus_centered()
|
||||
{
|
||||
self.move_cursor_to_output(&output);
|
||||
} else {
|
||||
self.maybe_warp_cursor_to_focus();
|
||||
}
|
||||
} else {
|
||||
self.niri.layout.focus_down();
|
||||
self.maybe_warp_cursor_to_focus();
|
||||
}
|
||||
|
||||
// FIXME: granular
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
Action::FocusColumnOrMonitorLeft => {
|
||||
if let Some(output) = self.niri.output_left() {
|
||||
if self.niri.layout.focus_column_left_or_output(&output)
|
||||
@ -2098,6 +2177,7 @@ fn should_intercept_key(
|
||||
modifiers: Modifiers::empty(),
|
||||
},
|
||||
action,
|
||||
repeat: true,
|
||||
cooldown: None,
|
||||
allow_when_locked: false,
|
||||
});
|
||||
@ -2147,6 +2227,7 @@ fn find_bind(
|
||||
modifiers: Modifiers::empty(),
|
||||
},
|
||||
action,
|
||||
repeat: true,
|
||||
cooldown: None,
|
||||
allow_when_locked: false,
|
||||
});
|
||||
@ -2478,6 +2559,7 @@ mod tests {
|
||||
modifiers: Modifiers::COMPOSITOR | Modifiers::CTRL,
|
||||
},
|
||||
action: Action::CloseWindow,
|
||||
repeat: true,
|
||||
cooldown: None,
|
||||
allow_when_locked: false,
|
||||
}]);
|
||||
@ -2611,6 +2693,7 @@ mod tests {
|
||||
modifiers: Modifiers::COMPOSITOR,
|
||||
},
|
||||
action: Action::CloseWindow,
|
||||
repeat: true,
|
||||
cooldown: None,
|
||||
allow_when_locked: false,
|
||||
},
|
||||
@ -2620,6 +2703,7 @@ mod tests {
|
||||
modifiers: Modifiers::SUPER,
|
||||
},
|
||||
action: Action::FocusColumnLeft,
|
||||
repeat: true,
|
||||
cooldown: None,
|
||||
allow_when_locked: false,
|
||||
},
|
||||
@ -2629,6 +2713,7 @@ mod tests {
|
||||
modifiers: Modifiers::empty(),
|
||||
},
|
||||
action: Action::FocusWindowDown,
|
||||
repeat: true,
|
||||
cooldown: None,
|
||||
allow_when_locked: false,
|
||||
},
|
||||
@ -2638,6 +2723,7 @@ mod tests {
|
||||
modifiers: Modifiers::COMPOSITOR | Modifiers::SUPER,
|
||||
},
|
||||
action: Action::FocusWindowUp,
|
||||
repeat: true,
|
||||
cooldown: None,
|
||||
allow_when_locked: false,
|
||||
},
|
||||
@ -2647,6 +2733,7 @@ mod tests {
|
||||
modifiers: Modifiers::SUPER | Modifiers::ALT,
|
||||
},
|
||||
action: Action::FocusColumnRight,
|
||||
repeat: true,
|
||||
cooldown: None,
|
||||
allow_when_locked: false,
|
||||
},
|
||||
|
@ -143,7 +143,8 @@ async fn process(ctx: &ClientCtx, request: Request) -> Reply {
|
||||
Request::Version => Response::Version(version()),
|
||||
Request::Outputs => {
|
||||
let ipc_outputs = ctx.ipc_outputs.lock().unwrap().clone();
|
||||
Response::Outputs(ipc_outputs)
|
||||
let outputs = ipc_outputs.values().cloned().map(|o| (o.name.clone(), o));
|
||||
Response::Outputs(outputs.collect())
|
||||
}
|
||||
Request::FocusedWindow => {
|
||||
let window = ctx.ipc_focused_window.lock().unwrap().clone();
|
||||
@ -183,8 +184,8 @@ async fn process(ctx: &ClientCtx, request: Request) -> Reply {
|
||||
Request::Output { output, action } => {
|
||||
let ipc_outputs = ctx.ipc_outputs.lock().unwrap();
|
||||
let found = ipc_outputs
|
||||
.keys()
|
||||
.any(|name| name.eq_ignore_ascii_case(&output));
|
||||
.values()
|
||||
.any(|o| o.name.eq_ignore_ascii_case(&output));
|
||||
let response = if found {
|
||||
OutputConfigChanged::Applied
|
||||
} else {
|
||||
@ -223,7 +224,8 @@ async fn process(ctx: &ClientCtx, request: Request) -> Reply {
|
||||
.ipc_outputs()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(&active_output)
|
||||
.values()
|
||||
.find(|o| o.name == active_output)
|
||||
.cloned()
|
||||
});
|
||||
|
||||
|
@ -1264,6 +1264,43 @@ impl<W: LayoutElement> Layout<W> {
|
||||
monitor.focus_column_left_or_last();
|
||||
}
|
||||
|
||||
pub fn focus_window_up_or_output(&mut self, output: &Output) -> bool {
|
||||
if let Some(monitor) = self.active_monitor() {
|
||||
let workspace = monitor.active_workspace();
|
||||
|
||||
if !workspace.columns.is_empty() {
|
||||
let curr_idx = workspace.columns[workspace.active_column_idx].active_tile_idx;
|
||||
let new_idx = curr_idx.saturating_sub(1);
|
||||
if curr_idx != new_idx {
|
||||
workspace.focus_up();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.focus_output(output);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn focus_window_down_or_output(&mut self, output: &Output) -> bool {
|
||||
if let Some(monitor) = self.active_monitor() {
|
||||
let workspace = monitor.active_workspace();
|
||||
|
||||
if !workspace.columns.is_empty() {
|
||||
let column = &workspace.columns[workspace.active_column_idx];
|
||||
let curr_idx = column.active_tile_idx;
|
||||
let new_idx = min(column.active_tile_idx + 1, column.tiles.len() - 1);
|
||||
if curr_idx != new_idx {
|
||||
workspace.focus_down();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.focus_output(output);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn focus_column_left_or_output(&mut self, output: &Output) -> bool {
|
||||
if let Some(monitor) = self.active_monitor() {
|
||||
let workspace = monitor.active_workspace();
|
||||
@ -2728,6 +2765,8 @@ mod tests {
|
||||
FocusColumnLast,
|
||||
FocusColumnRightOrFirst,
|
||||
FocusColumnLeftOrLast,
|
||||
FocusWindowOrMonitorUp(#[proptest(strategy = "1..=2u8")] u8),
|
||||
FocusWindowOrMonitorDown(#[proptest(strategy = "1..=2u8")] u8),
|
||||
FocusColumnOrMonitorLeft(#[proptest(strategy = "1..=2u8")] u8),
|
||||
FocusColumnOrMonitorRight(#[proptest(strategy = "1..=2u8")] u8),
|
||||
FocusWindowDown,
|
||||
@ -3055,6 +3094,22 @@ mod tests {
|
||||
Op::FocusColumnLast => layout.focus_column_last(),
|
||||
Op::FocusColumnRightOrFirst => layout.focus_column_right_or_first(),
|
||||
Op::FocusColumnLeftOrLast => layout.focus_column_left_or_last(),
|
||||
Op::FocusWindowOrMonitorUp(id) => {
|
||||
let name = format!("output{id}");
|
||||
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
||||
return;
|
||||
};
|
||||
|
||||
layout.focus_window_up_or_output(&output);
|
||||
}
|
||||
Op::FocusWindowOrMonitorDown(id) => {
|
||||
let name = format!("output{id}");
|
||||
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
||||
return;
|
||||
};
|
||||
|
||||
layout.focus_window_down_or_output(&output);
|
||||
}
|
||||
Op::FocusColumnOrMonitorLeft(id) => {
|
||||
let name = format!("output{id}");
|
||||
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
||||
@ -3299,6 +3354,8 @@ mod tests {
|
||||
Op::FocusColumnRight,
|
||||
Op::FocusColumnRightOrFirst,
|
||||
Op::FocusColumnLeftOrLast,
|
||||
Op::FocusWindowOrMonitorUp(0),
|
||||
Op::FocusWindowOrMonitorDown(1),
|
||||
Op::FocusColumnOrMonitorLeft(0),
|
||||
Op::FocusColumnOrMonitorRight(1),
|
||||
Op::FocusWindowUp,
|
||||
@ -3476,6 +3533,8 @@ mod tests {
|
||||
Op::FocusColumnRight,
|
||||
Op::FocusColumnRightOrFirst,
|
||||
Op::FocusColumnLeftOrLast,
|
||||
Op::FocusWindowOrMonitorUp(0),
|
||||
Op::FocusWindowOrMonitorDown(1),
|
||||
Op::FocusColumnOrMonitorLeft(0),
|
||||
Op::FocusColumnOrMonitorRight(1),
|
||||
Op::FocusWindowUp,
|
||||
|
50
src/niri.rs
50
src/niri.rs
@ -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")]
|
||||
@ -155,7 +156,7 @@ pub struct Niri {
|
||||
/// This does not include transient output config changes done via IPC. It is only used when
|
||||
/// reloading the config from disk to determine if the output configuration should be reloaded
|
||||
/// (and transient changes dropped).
|
||||
pub config_file_output_config: Vec<niri_config::Output>,
|
||||
pub config_file_output_config: niri_config::Outputs,
|
||||
|
||||
pub event_loop: LoopHandle<'static, State>,
|
||||
pub scheduler: Scheduler<()>,
|
||||
@ -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,
|
||||
@ -232,6 +234,7 @@ pub struct Niri {
|
||||
/// Scancodes of the keys to suppress.
|
||||
pub suppressed_keys: HashSet<u32>,
|
||||
pub bind_cooldown_timers: HashMap<Key, RegistrationToken>,
|
||||
pub bind_repeat_timer: Option<RegistrationToken>,
|
||||
pub keyboard_focus: KeyboardFocus,
|
||||
pub idle_inhibiting_surfaces: HashSet<WlSurface>,
|
||||
pub is_fdo_idle_inhibited: Arc<AtomicBool>,
|
||||
@ -1051,15 +1054,12 @@ 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();
|
||||
let config = self.niri.config.borrow_mut();
|
||||
let config = config
|
||||
.outputs
|
||||
.iter()
|
||||
.find(|o| o.name.eq_ignore_ascii_case(&name));
|
||||
let config = config.outputs.find(&name);
|
||||
|
||||
let scale = config
|
||||
.and_then(|c| c.scale)
|
||||
@ -1103,23 +1103,22 @@ 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) {
|
||||
{
|
||||
let mut config = self.niri.config.borrow_mut();
|
||||
let config = if let Some(config) = config
|
||||
.outputs
|
||||
.iter_mut()
|
||||
.find(|o| o.name.eq_ignore_ascii_case(name))
|
||||
{
|
||||
let config = if let Some(config) = config.outputs.find_mut(name) {
|
||||
config
|
||||
} else {
|
||||
config.outputs.push(niri_config::Output {
|
||||
config.outputs.0.push(niri_config::Output {
|
||||
name: String::from(name),
|
||||
..Default::default()
|
||||
});
|
||||
config.outputs.last_mut().unwrap()
|
||||
config.outputs.0.last_mut().unwrap()
|
||||
};
|
||||
|
||||
match action {
|
||||
@ -1166,18 +1165,21 @@ impl State {
|
||||
|
||||
let _span = tracy_client::span!("State::refresh_ipc_outputs");
|
||||
|
||||
for (name, ipc_output) in self.backend.ipc_outputs().lock().unwrap().iter_mut() {
|
||||
for ipc_output in self.backend.ipc_outputs().lock().unwrap().values_mut() {
|
||||
let logical = self
|
||||
.niri
|
||||
.global_space
|
||||
.outputs()
|
||||
.find(|output| output.name() == *name)
|
||||
.find(|output| output.name() == ipc_output.name)
|
||||
.map(logical_output);
|
||||
ipc_output.logical = logical;
|
||||
}
|
||||
|
||||
#[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 +1484,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 +1633,7 @@ impl Niri {
|
||||
layer_shell_state,
|
||||
session_lock_state,
|
||||
foreign_toplevel_state,
|
||||
output_management_state,
|
||||
screencopy_state,
|
||||
viewporter_state,
|
||||
xdg_foreign_state,
|
||||
@ -1650,6 +1658,7 @@ impl Niri {
|
||||
popup_grab: None,
|
||||
suppressed_keys: HashSet::new(),
|
||||
bind_cooldown_timers: HashMap::new(),
|
||||
bind_repeat_timer: Option::default(),
|
||||
presentation_state,
|
||||
security_context_state,
|
||||
gamma_control_manager_state,
|
||||
@ -1739,11 +1748,7 @@ impl Niri {
|
||||
for output in self.global_space.outputs().chain(new_output) {
|
||||
let name = output.name();
|
||||
let position = self.global_space.output_geometry(output).map(|geo| geo.loc);
|
||||
let config = config
|
||||
.outputs
|
||||
.iter()
|
||||
.find(|o| o.name.eq_ignore_ascii_case(&name))
|
||||
.and_then(|c| c.position);
|
||||
let config = config.outputs.find(&name).and_then(|c| c.position);
|
||||
|
||||
outputs.push(Data {
|
||||
output: output.clone(),
|
||||
@ -1848,10 +1853,7 @@ impl Niri {
|
||||
let name = output.name();
|
||||
|
||||
let config = self.config.borrow();
|
||||
let c = config
|
||||
.outputs
|
||||
.iter()
|
||||
.find(|o| o.name.eq_ignore_ascii_case(&name));
|
||||
let c = config.outputs.find(&name);
|
||||
let scale = c.and_then(|c| c.scale).map(|s| s.0).unwrap_or_else(|| {
|
||||
let size_mm = output.physical_properties().size;
|
||||
let resolution = output.current_mode().unwrap().size;
|
||||
|
@ -1,3 +1,4 @@
|
||||
pub mod foreign_toplevel;
|
||||
pub mod gamma_control;
|
||||
pub mod output_management;
|
||||
pub mod screencopy;
|
||||
|
889
src/protocols/output_management.rs
Normal file
889
src/protocols/output_management.rs
Normal file
@ -0,0 +1,889 @@
|
||||
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: niri_config::Outputs,
|
||||
}
|
||||
|
||||
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: niri_config::Outputs);
|
||||
}
|
||||
|
||||
#[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: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_config_changed(&mut self, new_config: niri_config::Outputs) {
|
||||
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
|
||||
.find(¤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
|
||||
.find(¤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));
|
||||
}
|
@ -50,6 +50,15 @@ For this reason, most of the default keys use the `Mod` modifier.
|
||||
> Here, look at `sym: Left` and `sym: Right`: these are the key names.
|
||||
> I was pressing the left and the right arrow in this example.
|
||||
|
||||
Binds will do key repeat by default (i.e. holding down a bind will make it trigger repeatedly).
|
||||
You can disable that for specific binds with `repeat=false`:
|
||||
|
||||
```
|
||||
binds {
|
||||
Mod+T repeat=false { spawn "alacritty"; }
|
||||
}
|
||||
```
|
||||
|
||||
Binds can also have a cooldown, which will rate-limit the bind and prevent it from repeatedly triggering too quickly.
|
||||
|
||||
```
|
||||
|
Loading…
Reference in New Issue
Block a user