aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--Cargo.toml1
-rw-r--r--src/backend/tty.rs61
-rw-r--r--src/handlers/mod.rs60
-rw-r--r--src/niri.rs12
-rw-r--r--src/protocols/gamma_control.rs238
-rw-r--r--src/protocols/mod.rs1
7 files changed, 372 insertions, 2 deletions
diff --git a/Cargo.lock b/Cargo.lock
index de6e6fb6..1cffecd9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2119,6 +2119,7 @@ dependencies = [
"async-channel",
"async-io 1.13.0",
"bitflags 2.4.2",
+ "bytemuck",
"calloop 0.13.0",
"clap",
"directories",
diff --git a/Cargo.toml b/Cargo.toml
index 291b3b43..7a487a0c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -73,6 +73,7 @@ tracy-client.workspace = true
url = { version = "2.5.0", optional = true }
xcursor = "0.3.5"
zbus = { version = "~3.15.2", optional = true }
+bytemuck = "1.14.3"
[dependencies.smithay]
workspace = true
diff --git a/src/backend/tty.rs b/src/backend/tty.rs
index 2b6ee89e..6d954a62 100644
--- a/src/backend/tty.rs
+++ b/src/backend/tty.rs
@@ -8,7 +8,7 @@ use std::sync::{Arc, Mutex};
use std::time::Duration;
use std::{io, mem};
-use anyhow::{anyhow, bail, Context};
+use anyhow::{anyhow, bail, ensure, Context};
use libc::dev_t;
use niri_config::Config;
use smithay::backend::allocator::dmabuf::Dmabuf;
@@ -1244,6 +1244,65 @@ impl Tty {
}
}
+ pub fn get_gamma_length(&self, output: &Output) -> Option<u32> {
+ let tty_state = output.user_data().get::<TtyOutputState>().unwrap();
+ let device = self.devices.get(&tty_state.node)?;
+ let crtc_info = device.drm.get_crtc(tty_state.crtc.clone()).ok()?;
+ return Some(crtc_info.gamma_length());
+ }
+
+ pub fn get_gamma(&self, output: &Output) -> Option<Vec<u16>> {
+ let tty_state = output.user_data().get::<TtyOutputState>().unwrap();
+ let device = self.devices.get(&tty_state.node)?;
+ let crtc_info = device.drm.get_crtc(tty_state.crtc.clone()).ok()?;
+ let crtc = tty_state.crtc.clone();
+ let gamma_length = crtc_info.gamma_length() as usize;
+ let mut red = vec![0; gamma_length];
+ let mut green = vec![0; gamma_length];
+ let mut blue = vec![0; gamma_length];
+ if let Err(err) = device.drm.get_gamma(crtc, &mut red, &mut green, &mut blue) {
+ warn!("error getting gamma for crtc {crtc:?}: {err:?}");
+ return None;
+ }
+ red.extend(green);
+ red.extend(blue);
+ return Some(red);
+ }
+
+ pub fn set_gamma(
+ &self,
+ niri: &mut Niri,
+ output: &Output,
+ ramp: Vec<u16>,
+ ) -> anyhow::Result<()> {
+ let tty_state = output.user_data().get::<TtyOutputState>().unwrap();
+ let device = self
+ .devices
+ .get(&tty_state.node)
+ .context("missing device")?;
+
+ let crtc_info = device.drm.get_crtc(tty_state.crtc.clone())?;
+ let crtc = tty_state.crtc.clone();
+ let gamma_length = crtc_info.gamma_length() as usize;
+
+ ensure!(ramp.len() == gamma_length * 3, "wrong gamma length");
+ let mut red = ramp.clone();
+ red.truncate(gamma_length);
+ let mut green = ramp.clone();
+ green.drain(0..gamma_length);
+ green.truncate(gamma_length);
+ let mut blue = ramp;
+ blue.drain(0..gamma_length * 2);
+ blue.truncate(gamma_length);
+
+ device
+ .drm
+ .set_gamma(crtc, &mut red, &mut green, &mut blue)
+ .context("error setting gamma")?;
+ niri.queue_redraw(output.clone());
+ Ok(())
+ }
+
fn refresh_ipc_outputs(&self) {
let _span = tracy_client::span!("Tty::refresh_ipc_outputs");
diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs
index f97129c6..54a9b451 100644
--- a/src/handlers/mod.rs
+++ b/src/handlers/mod.rs
@@ -8,6 +8,7 @@ use std::os::fd::OwnedFd;
use std::sync::Arc;
use std::thread;
+use anyhow::anyhow;
use smithay::backend::allocator::dmabuf::Dmabuf;
use smithay::backend::drm::DrmNode;
use smithay::desktop::{PopupKind, PopupManager};
@@ -15,6 +16,7 @@ use smithay::input::pointer::{CursorIcon, CursorImageStatus, PointerHandle};
use smithay::input::{keyboard, Seat, SeatHandler, SeatState};
use smithay::output::Output;
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
+use smithay::reexports::wayland_server::backend::ObjectId;
use smithay::reexports::wayland_server::protocol::wl_data_source::WlDataSource;
use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
@@ -58,9 +60,10 @@ use crate::niri::{ClientState, State};
use crate::protocols::foreign_toplevel::{
self, ForeignToplevelHandler, ForeignToplevelManagerState,
};
+use crate::protocols::gamma_control::{GammaControlHandler, GammaControlManagerState};
use crate::protocols::screencopy::{Screencopy, ScreencopyHandler};
use crate::utils::output_size;
-use crate::{delegate_foreign_toplevel, delegate_screencopy};
+use crate::{delegate_foreign_toplevel, delegate_gamma_control, delegate_screencopy};
impl SeatHandler for State {
type KeyboardFocus = WlSurface;
@@ -440,3 +443,58 @@ impl DrmLeaseHandler for State {
delegate_drm_lease!(State);
delegate_viewporter!(State);
+
+impl GammaControlHandler for State {
+ fn gamma_control_manager_state(&mut self) -> &mut GammaControlManagerState {
+ &mut self.niri.gamma_control_manager_state
+ }
+
+ fn set_gamma(
+ &mut self,
+ wl_output: &WlOutput,
+ ramp: Vec<u16>,
+ gamma_size: u32,
+ ) -> anyhow::Result<()> {
+ if ramp.len() != gamma_size as usize * 3 {
+ error!(
+ "gamma length wrong (expected {}, got {})",
+ gamma_size,
+ ramp.len()
+ );
+ return Err(anyhow!("Gamma length wrong"));
+ }
+
+ let Some(output) = Output::from_resource(wl_output) else {
+ return Err(anyhow!("No Output matching WlOutput"));
+ };
+
+ self.backend.tty().set_gamma(&mut self.niri, &output, ramp)
+ }
+
+ fn get_gamma(&mut self, wl_output: &WlOutput) -> Option<Vec<u16>> {
+ let output = Output::from_resource(wl_output)?;
+
+ let gamma_ramp = self.backend.tty().get_gamma(&output);
+ if gamma_ramp.is_none() {
+ warn!("Failed to get gamma ramp");
+ }
+ gamma_ramp
+ }
+
+ fn destroy(&mut self, output_id: ObjectId) {
+ self.niri
+ .gamma_control_manager_state
+ .destroy_gamma_control(output_id)
+ }
+
+ fn get_gamma_size(&mut self, wl_output: &WlOutput) -> Option<u32> {
+ let output = Output::from_resource(wl_output)?;
+ let gamma_size = self.backend.tty().get_gamma_length(&output);
+ if gamma_size.is_none() {
+ warn!("Failed to get gamma size");
+ }
+ gamma_size
+ }
+}
+
+delegate_gamma_control!(State);
diff --git a/src/niri.rs b/src/niri.rs
index 01dc9eb5..1e5b5c16 100644
--- a/src/niri.rs
+++ b/src/niri.rs
@@ -101,6 +101,7 @@ use crate::input::{apply_libinput_settings, TabletData};
use crate::ipc::server::IpcServer;
use crate::layout::{Layout, MonitorRenderElement};
use crate::protocols::foreign_toplevel::{self, ForeignToplevelManagerState};
+use crate::protocols::gamma_control::GammaControlManagerState;
use crate::protocols::screencopy::{Screencopy, ScreencopyManagerState};
use crate::pw_utils::{Cast, PipeWire};
use crate::render_helpers::renderer::NiriRenderer;
@@ -187,6 +188,7 @@ pub struct Niri {
pub popup_grab: Option<PopupGrabState>,
pub presentation_state: PresentationState,
pub security_context_state: SecurityContextState,
+ pub gamma_control_manager_state: GammaControlManagerState,
pub seat: Seat<State>,
/// Scancodes of the keys to suppress.
@@ -929,6 +931,15 @@ impl Niri {
let viewporter_state = ViewporterState::new::<State>(&display_handle);
let xdg_foreign_state = XdgForeignState::new::<State>(&display_handle);
+ let gamma_control_manager_state = GammaControlManagerState::new::<State, _>(
+ &display_handle,
+ match backend {
+ Backend::Tty(_) => true,
+ _ => false,
+ },
+ |client| !client.get_data::<ClientState>().unwrap().restricted,
+ );
+
let mut seat: Seat<State> = seat_state.new_wl_seat(&display_handle, backend.seat_name());
seat.add_keyboard(
config_.input.keyboard.xkb.to_xkb_config(),
@@ -1082,6 +1093,7 @@ impl Niri {
suppressed_keys: HashSet::new(),
presentation_state,
security_context_state,
+ gamma_control_manager_state,
seat,
keyboard_focus: None,
diff --git a/src/protocols/gamma_control.rs b/src/protocols/gamma_control.rs
new file mode 100644
index 00000000..947b5e28
--- /dev/null
+++ b/src/protocols/gamma_control.rs
@@ -0,0 +1,238 @@
+use std::collections::HashMap;
+use std::fs::File;
+use std::io::Read;
+
+use smithay::reexports::wayland_protocols_wlr;
+use smithay::reexports::wayland_server::backend::{ClientId, ObjectId};
+use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;
+use smithay::reexports::wayland_server::{
+ Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
+};
+use wayland_protocols_wlr::gamma_control::v1::server::{
+ zwlr_gamma_control_manager_v1, zwlr_gamma_control_v1,
+};
+use zwlr_gamma_control_manager_v1::ZwlrGammaControlManagerV1;
+use zwlr_gamma_control_v1::ZwlrGammaControlV1;
+
+const VERSION: u32 = 1;
+
+pub struct GammaControlManagerState {
+ gamma_controls: HashMap<WlOutput, ZwlrGammaControlV1>,
+}
+
+pub struct GammaControlManagerGlobalData {
+ filter: Box<dyn for<'c> Fn(&'c Client) -> bool + Send + Sync>,
+ can_view: bool,
+}
+
+pub trait GammaControlHandler {
+ fn gamma_control_manager_state(&mut self) -> &mut GammaControlManagerState;
+ fn set_gamma(
+ &mut self,
+ output: &WlOutput,
+ ramp: Vec<u16>,
+ gamma_size: u32,
+ ) -> anyhow::Result<()>;
+ fn get_gamma(&mut self, output: &WlOutput) -> Option<Vec<u16>>;
+ fn destroy(&mut self, output_id: ObjectId);
+ fn get_gamma_size(&mut self, output: &WlOutput) -> Option<u32>;
+}
+
+pub struct GammaControlState {
+ gamma_size: Option<u32>,
+ previous_gamma_ramp: Option<Vec<u16>>,
+ output: WlOutput,
+ failed: bool,
+}
+
+impl GammaControlManagerState {
+ pub fn new<D, F>(display: &DisplayHandle, can_view: bool, filter: F) -> Self
+ where
+ D: GlobalDispatch<ZwlrGammaControlManagerV1, GammaControlManagerGlobalData>,
+ D: Dispatch<ZwlrGammaControlManagerV1, ()>,
+ D: Dispatch<ZwlrGammaControlV1, GammaControlState>,
+ D: GammaControlHandler,
+ D: 'static,
+ F: for<'c> Fn(&'c Client) -> bool + Send + Sync + 'static,
+ {
+ let global_data = GammaControlManagerGlobalData {
+ filter: Box::new(filter),
+ can_view,
+ };
+ display.create_global::<D, ZwlrGammaControlManagerV1, _>(VERSION, global_data);
+
+ Self {
+ gamma_controls: HashMap::new(),
+ }
+ }
+ pub fn destroy_gamma_control(&mut self, output_id: ObjectId) {
+ self.gamma_controls.remove(&output_id);
+ }
+}
+
+impl<D> GlobalDispatch<ZwlrGammaControlManagerV1, GammaControlManagerGlobalData, D>
+ for GammaControlManagerState
+where
+ D: GlobalDispatch<ZwlrGammaControlManagerV1, GammaControlManagerGlobalData>,
+ D: Dispatch<ZwlrGammaControlManagerV1, ()>,
+ D: Dispatch<ZwlrGammaControlV1, GammaControlState>,
+ D: GammaControlHandler,
+ D: 'static,
+{
+ fn bind(
+ _state: &mut D,
+ _handle: &DisplayHandle,
+ _client: &Client,
+ manager: New<ZwlrGammaControlManagerV1>,
+ _manager_state: &GammaControlManagerGlobalData,
+ data_init: &mut DataInit<'_, D>,
+ ) {
+ data_init.init(manager, ());
+ }
+
+ fn can_view(client: Client, global_data: &GammaControlManagerGlobalData) -> bool {
+ global_data.can_view && (global_data.filter)(&client)
+ }
+}
+
+impl<D> Dispatch<ZwlrGammaControlManagerV1, (), D> for GammaControlManagerState
+where
+ D: Dispatch<ZwlrGammaControlManagerV1, ()>,
+ D: Dispatch<ZwlrGammaControlV1, GammaControlState>,
+ D: GammaControlHandler,
+ D: 'static,
+{
+ fn request(
+ state: &mut D,
+ _client: &Client,
+ _resource: &ZwlrGammaControlManagerV1,
+ request: <ZwlrGammaControlManagerV1 as Resource>::Request,
+ _data: &(),
+ _dhandle: &DisplayHandle,
+ data_init: &mut DataInit<'_, D>,
+ ) {
+ match request {
+ zwlr_gamma_control_manager_v1::Request::GetGammaControl { id, output } => {
+ let gamma_size = state.get_gamma_size(&output);
+ let previous_gamma_ramp = state.get_gamma(&output);
+
+ if state
+ .gamma_control_manager_state()
+ .gamma_controls
+ .contains_key(&output)
+ || gamma_size.is_none()
+ || previous_gamma_ramp.is_none()
+ {
+ data_init
+ .init(
+ id,
+ GammaControlState {
+ gamma_size: gamma_size.clone(),
+ previous_gamma_ramp: None,
+ output: output.clone(),
+ failed: true,
+ },
+ )
+ .failed();
+ return;
+ }
+
+ let zwlr_gamma_control = data_init.init(
+ id,
+ GammaControlState {
+ gamma_size: gamma_size.clone(),
+ previous_gamma_ramp,
+ output: output.clone(),
+ failed: false,
+ },
+ );
+
+ zwlr_gamma_control.gamma_size(gamma_size.unwrap());
+ state
+ .gamma_control_manager_state()
+ .gamma_controls
+ .insert(output, zwlr_gamma_control);
+ }
+ zwlr_gamma_control_manager_v1::Request::Destroy => (),
+ _ => unreachable!(),
+ }
+ }
+}
+
+impl<D> Dispatch<ZwlrGammaControlV1, GammaControlState, D> for GammaControlManagerState
+where
+ D: Dispatch<ZwlrGammaControlV1, GammaControlState>,
+ D: GammaControlHandler,
+ D: 'static,
+{
+ fn request(
+ state: &mut D,
+ _client: &Client,
+ resource: &ZwlrGammaControlV1,
+ request: <ZwlrGammaControlV1 as Resource>::Request,
+ data: &GammaControlState,
+ _dhandle: &DisplayHandle,
+ _data_init: &mut DataInit<'_, D>,
+ ) {
+ match request {
+ zwlr_gamma_control_v1::Request::SetGamma { fd } => {
+ if data.failed {
+ return;
+ }
+ debug!("setting gamma for output {:?}", data.output);
+ let buf = &mut Vec::new();
+ if File::from(fd).read_to_end(buf).is_err() {
+ warn!("failed to read gamma data for output {:?}", data.output);
+ resource.failed();
+ return;
+ }
+
+ let gamma = bytemuck::cast_slice(buf).to_vec();
+ let gamma_size = data.gamma_size.unwrap();
+
+ if let Err(err) = state.set_gamma(&data.output, gamma, gamma_size) {
+ warn!("error setting gamma: {err:?}");
+ resource.failed();
+ return;
+ }
+ }
+ zwlr_gamma_control_v1::Request::Destroy => (),
+ _ => unreachable!(),
+ }
+ }
+
+ fn destroyed(
+ state: &mut D,
+ _client: ClientId,
+ _resource: &ZwlrGammaControlV1,
+ data: &GammaControlState,
+ ) {
+ if data.failed {
+ return;
+ }
+ let ramp = data.previous_gamma_ramp.as_ref().unwrap();
+
+ if let Err(err) = state.set_gamma(&data.output, ramp.to_vec(), data.gamma_size.unwrap()) {
+ warn!("error resetting gamma: {err:?}");
+ }
+
+ state.destroy(data.output.id());
+ }
+}
+
+#[macro_export]
+macro_rules! delegate_gamma_control {
+ ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
+ smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
+ smithay::reexports::wayland_protocols_wlr::gamma_control::v1::server::zwlr_gamma_control_manager_v1::ZwlrGammaControlManagerV1: $crate::protocols::gamma_control::GammaControlManagerGlobalData
+ ] => $crate::protocols::gamma_control::GammaControlManagerState);
+
+ smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
+ smithay::reexports::wayland_protocols_wlr::gamma_control::v1::server::zwlr_gamma_control_manager_v1::ZwlrGammaControlManagerV1: ()
+ ] => $crate::protocols::gamma_control::GammaControlManagerState);
+
+ smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
+ smithay::reexports::wayland_protocols_wlr::gamma_control::v1::server::zwlr_gamma_control_v1::ZwlrGammaControlV1: $crate::protocols::gamma_control::GammaControlState
+ ] => $crate::protocols::gamma_control::GammaControlManagerState);
+ };
+}
diff --git a/src/protocols/mod.rs b/src/protocols/mod.rs
index 1e3ef42f..d1779b7e 100644
--- a/src/protocols/mod.rs
+++ b/src/protocols/mod.rs
@@ -1,2 +1,3 @@
pub mod foreign_toplevel;
+pub mod gamma_control;
pub mod screencopy;