Merge pull request #197 from pinnacle-comp/gamma_control

Implement wlr-gamma-control
This commit is contained in:
Ottatop 2024-04-10 09:45:25 -05:00 committed by GitHub
commit 89a3e592a1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 739 additions and 124 deletions

1
Cargo.lock generated
View file

@ -1759,6 +1759,7 @@ version = "0.0.1"
dependencies = [
"anyhow",
"bitflags 2.5.0",
"bytemuck",
"chrono",
"clap",
"cliclack",

View file

@ -71,6 +71,7 @@ nix = { version = "0.28.0", features = ["user", "resource"] }
pinnacle-api-defs = { workspace = true }
dircpy = "0.3.16"
chrono = "0.4.37"
bytemuck = "1.15.0"
[dependencies.smithay]
git = "https://github.com/Smithay/smithay"

View file

@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
mod drm_util;
mod gamma;
use std::{
collections::{HashMap, HashSet},
@ -57,7 +58,7 @@ use smithay::{
reexports::{
ash::vk::ExtPhysicalDeviceDrmFn,
calloop::{
self, generic::Generic, EventLoop, Idle, Interest, LoopHandle, PostAction,
self, generic::Generic, Dispatcher, EventLoop, Idle, Interest, LoopHandle, PostAction,
RegistrationToken,
},
drm::control::{connector, crtc, ModeTypeFlags},
@ -81,7 +82,7 @@ use smithay::{
},
};
use smithay_drm_extras::drm_scanner::{DrmScanEvent, DrmScanner};
use tracing::{error, info, trace, warn};
use tracing::{debug, error, info, trace, warn};
use crate::{
backend::Backend,
@ -89,7 +90,7 @@ use crate::{
output::OutputName,
render::{
pointer::PointerElement, pointer_render_elements, take_presentation_feedback,
OutputRenderElements,
OutputRenderElement,
},
state::{State, SurfaceDmabufFeedback, WithState},
};
@ -118,7 +119,7 @@ type UdevRenderFrameResult<'a> = RenderFrameResult<
'a,
BufferObject<()>,
GbmFramebuffer,
OutputRenderElements<UdevRenderer<'a>, WaylandSurfaceRenderElement<UdevRenderer<'a>>>,
OutputRenderElement<UdevRenderer<'a>, WaylandSurfaceRenderElement<UdevRenderer<'a>>>,
>;
/// Udev state attached to each [`Output`].
@ -133,6 +134,7 @@ struct UdevOutputData {
// TODO: document desperately
pub struct Udev {
pub session: LibSeatSession,
udev_dispatcher: Dispatcher<'static, UdevBackend, State>,
display_handle: DisplayHandle,
pub(super) dmabuf_state: Option<(DmabufState, DmabufGlobal)>,
pub(super) primary_gpu: DrmNode,
@ -192,45 +194,52 @@ impl State {
/// Does nothing when called on the winit backend.
pub fn switch_vt(&mut self, vt: i32) {
if let Backend::Udev(udev) = &mut self.backend {
for backend in udev.backends.values_mut() {
for surface in backend.surfaces.values_mut() {
// Clear the overlay planes on tty switch.
//
// On my machine, switching a tty would leave the topmost window on the
// screen. Smithay will render the topmost window on the overlay plane,
// so we clear it here.
let planes = surface.compositor.surface().planes().clone();
tracing::debug!("Clearing overlay planes");
for overlay_plane in planes.overlay {
if let Err(err) = surface
.compositor
.surface()
.clear_plane(overlay_plane.handle)
{
warn!("Failed to clear overlay planes: {err}");
}
}
}
if let Err(err) = udev.session.change_vt(vt) {
error!("Failed to switch to vt {vt}: {err}");
}
// TODO: uncomment this when `RenderFrameResult::blit_frame_result` is fixed for
// | overlay/cursor planes
// for backend in udev.backends.values_mut() {
// for surface in backend.surfaces.values_mut() {
// // Clear the overlay planes on tty switch.
// //
// // On my machine, switching a tty would leave the topmost window on the
// // screen. Smithay will render the topmost window on the overlay plane,
// // so we clear it here.
// let planes = surface.compositor.surface().planes().clone();
// tracing::debug!("Clearing overlay planes");
// for overlay_plane in planes.overlay {
// if let Err(err) = surface
// .compositor
// .surface()
// .clear_plane(overlay_plane.handle)
// {
// warn!("Failed to clear overlay planes: {err}");
// }
// }
// }
// }
// Wait for the clear to commit before switching
self.schedule(
|state| {
let udev = state.backend.udev();
!udev
.backends
.values()
.flat_map(|backend| backend.surfaces.values())
.map(|surface| surface.compositor.surface())
.any(|drm_surf| drm_surf.commit_pending())
},
move |state| {
let udev = state.backend.udev_mut();
if let Err(err) = udev.session.change_vt(vt) {
error!("Failed to switch to vt {vt}: {err}");
}
},
);
// self.schedule(
// |state| {
// let udev = state.backend.udev();
// !udev
// .backends
// .values()
// .flat_map(|backend| backend.surfaces.values())
// .map(|surface| surface.compositor.surface())
// .any(|drm_surf| drm_surf.commit_pending())
// },
// move |state| {
// let udev = state.backend.udev_mut();
// if let Err(err) = udev.session.change_vt(vt) {
// error!("Failed to switch to vt {vt}: {err}");
// }
// },
// );
}
}
@ -338,8 +347,43 @@ pub fn setup_udev(
// Ok(unsafe { GlesRenderer::with_capabilities(ctx, supported) }?)
// }))?;
// Initialize the udev backend
let udev_backend = UdevBackend::new(session.seat())?;
let udev_dispatcher =
Dispatcher::new(
udev_backend,
move |event, _, state: &mut State| match event {
// GPU connected
UdevEvent::Added { device_id, path } => {
if let Err(err) = DrmNode::from_dev_id(device_id)
.map_err(DeviceAddError::DrmNode)
.and_then(|node| state.device_added(node, &path))
{
error!("Skipping device {device_id}: {err}");
}
}
UdevEvent::Changed { device_id } => {
if let Ok(node) = DrmNode::from_dev_id(device_id) {
state.device_changed(node)
}
}
// GPU disconnected
UdevEvent::Removed { device_id } => {
if let Ok(node) = DrmNode::from_dev_id(device_id) {
state.device_removed(node)
}
}
},
);
event_loop
.handle()
.register_dispatcher(udev_dispatcher.clone())?;
let data = Udev {
display_handle: display.handle(),
udev_dispatcher,
dmabuf_state: None,
session,
primary_gpu,
@ -365,14 +409,20 @@ pub fn setup_udev(
config_dir,
)?;
// Initialize the udev backend
let udev_backend = UdevBackend::new(state.seat.name())?;
let things = state
.backend
.udev()
.udev_dispatcher
.as_source_ref()
.device_list()
.map(|(id, path)| (id, path.to_path_buf()))
.collect::<Vec<_>>();
// Create DrmNodes from already connected GPUs
for (device_id, path) in udev_backend.device_list() {
for (device_id, path) in things {
if let Err(err) = DrmNode::from_dev_id(device_id)
.map_err(DeviceAddError::DrmNode)
.and_then(|node| state.device_added(node, path))
.and_then(|node| state.device_added(node, &path))
{
error!("Skipping device {device_id}: {err}");
}
@ -380,32 +430,6 @@ pub fn setup_udev(
let udev = state.backend.udev_mut();
event_loop
.handle()
.insert_source(udev_backend, move |event, _, state| match event {
// GPU connected
UdevEvent::Added { device_id, path } => {
if let Err(err) = DrmNode::from_dev_id(device_id)
.map_err(DeviceAddError::DrmNode)
.and_then(|node| state.device_added(node, &path))
{
error!("Skipping device {device_id}: {err}");
}
}
UdevEvent::Changed { device_id } => {
if let Ok(node) = DrmNode::from_dev_id(device_id) {
state.device_changed(node)
}
}
// GPU disconnected
UdevEvent::Removed { device_id } => {
if let Ok(node) = DrmNode::from_dev_id(device_id) {
state.device_removed(node)
}
}
})
.expect("failed to insert udev_backend into event loop");
// Initialize libinput backend
let mut libinput_context = Libinput::new_with_udev::<LibinputSessionInterface<LibSeatSession>>(
udev.session.clone().into(),
@ -431,10 +455,9 @@ pub fn setup_udev(
event_loop
.handle()
.insert_source(notifier, move |event, _, state| {
let udev = state.backend.udev_mut();
match event {
session::Event::PauseSession => {
let udev = state.backend.udev_mut();
libinput_context.suspend();
info!("pausing session");
@ -445,44 +468,117 @@ pub fn setup_udev(
session::Event::ActivateSession => {
info!("resuming session");
if let Err(err) = libinput_context.resume() {
error!("Failed to resume libinput context: {:?}", err);
if libinput_context.resume().is_err() {
error!("Failed to resume libinput context");
}
for backend in udev.backends.values_mut() {
// TODO: this is false because i'm too lazy to remove the code directly
// | below it
backend.drm.activate(false).expect("failed to activate drm");
for surface in backend.surfaces.values_mut() {
if let Err(err) = surface.compositor.surface().reset_state() {
warn!("Failed to reset drm surface state: {}", err);
// TODO: All this dance around borrowing is a consequence of the fact that I haven't
// | split the State struct into a main State and a substruct like
// | Niri and cosmic-comp have done
let (mut device_list, connected_devices, disconnected_devices) = {
let udev = state.backend.udev();
let device_list = udev
.udev_dispatcher
.as_source_ref()
.device_list()
.flat_map(|(id, path)| {
Some((DrmNode::from_dev_id(id).ok()?, path.to_path_buf()))
})
.collect::<HashMap<_, _>>();
let (connected_devices, disconnected_devices) = udev
.backends
.keys()
.copied()
.partition::<Vec<_>, _>(|node| device_list.contains_key(node));
(device_list, connected_devices, disconnected_devices)
};
for node in disconnected_devices {
device_list.remove(&node);
state.device_removed(node);
}
for node in connected_devices {
device_list.remove(&node);
// TODO: split off the big State struct to avoid this bs
{
let udev = state.backend.udev_mut();
let Some(backend) = udev.backends.get_mut(&node) else {
unreachable!();
};
if let Err(err) = backend.drm.activate(true) {
error!("Error activating DRM device: {err}");
}
}
state.device_changed(node);
// Apply pending gammas
//
// Also welcome to some really doodoo code
let udev = state.backend.udev_mut();
let Some(backend) = udev.backends.get_mut(&node) else {
unreachable!();
};
for (crtc, surface) in backend.surfaces.iter_mut() {
match std::mem::take(&mut surface.pending_gamma_change) {
PendingGammaChange::Idle => {
debug!("Restoring from previous gamma");
if let Err(err) = Udev::set_gamma_internal(
&backend.drm,
crtc,
surface.previous_gamma.clone(),
) {
warn!("Failed to reset gamma: {err}");
surface.previous_gamma = None;
}
}
PendingGammaChange::Restore => {
debug!("Restoring to original gamma");
if let Err(err) = Udev::set_gamma_internal(
&backend.drm,
crtc,
None::<[&[u16]; 3]>,
) {
warn!("Failed to reset gamma: {err}");
}
surface.previous_gamma = None;
}
PendingGammaChange::Change(gamma) => {
debug!("Changing to pending gamma");
match Udev::set_gamma_internal(
&backend.drm,
crtc,
Some([&gamma[0], &gamma[1], &gamma[2]]),
) {
Ok(()) => {
surface.previous_gamma = Some(gamma);
}
Err(err) => {
warn!("Failed to set pending gamma: {err}");
surface.previous_gamma = None;
}
}
}
}
// reset the buffers after resume to trigger a full redraw
// this is important after a vt switch as the primary plane
// has no content and damage tracking may prevent a redraw
// otherwise
surface.compositor.reset_buffers();
}
}
let connectors = udev
.backends
.iter()
.map(|(node, backend)| {
let connectors = backend
.drm_scanner
.crtcs()
.map(|(info, crtc)| (info.clone(), crtc))
.collect::<Vec<_>>();
(*node, connectors)
})
.collect::<Vec<_>>();
for (node, connectors) in connectors {
for (connector, crtc) in connectors {
state.connector_disconnected(node, connector.clone(), crtc);
state.connector_connected(node, connector, crtc);
// Newly connected devices
for (node, path) in device_list.into_iter() {
if let Err(err) = state.device_added(node, &path) {
error!("Error adding device: {err}");
}
}
for output in state.space.outputs().cloned().collect::<Vec<_>>() {
state.schedule_render(&output);
}
@ -727,6 +823,20 @@ struct RenderSurface {
dmabuf_feedback: Option<DrmSurfaceDmabufFeedback>,
render_state: RenderState,
screencopy_commit_state: ScreencopyCommitState,
previous_gamma: Option<[Box<[u16]>; 3]>,
pending_gamma_change: PendingGammaChange,
}
#[derive(Debug, Clone, Default)]
enum PendingGammaChange {
/// No pending gamma
#[default]
Idle,
/// Restore the original gamma
Restore,
/// Change the gamma
Change([Box<[u16]>; 3]),
}
#[derive(Default, Debug, Clone, Copy)]
@ -758,7 +868,7 @@ type GbmDrmCompositor = DrmCompositor<
fn render_frame<'a>(
compositor: &mut GbmDrmCompositor,
renderer: &mut UdevRenderer<'a>,
elements: &'a [OutputRenderElements<
elements: &'a [OutputRenderElement<
UdevRenderer<'a>,
WaylandSurfaceRenderElement<UdevRenderer<'a>>,
>],
@ -1006,6 +1116,8 @@ impl State {
dmabuf_feedback,
render_state: RenderState::Idle,
screencopy_commit_state: ScreencopyCommitState::default(),
previous_gamma: None,
pending_gamma_change: PendingGammaChange::Idle,
};
device.surfaces.insert(crtc, surface);
@ -1074,6 +1186,7 @@ impl State {
},
);
self.space.unmap_output(&output);
self.gamma_control_manager_state.output_removed(&output);
}
}

118
src/backend/udev/gamma.rs Normal file
View file

@ -0,0 +1,118 @@
// Parts ripped out from Niri like that time Omni-man almost ripped out Donald's spine
// Not that I'm Omni-man, of course.
use anyhow::{ensure, Context};
use smithay::backend::drm::DrmDevice;
use smithay::reexports::drm::control::{crtc, Device};
use smithay::{backend::session::Session, output::Output};
use crate::backend::udev::{render_surface_for_output, PendingGammaChange};
use super::{Udev, UdevOutputData};
impl Udev {
pub fn set_gamma(&mut self, output: &Output, gamma: Option<[&[u16]; 3]>) -> anyhow::Result<()> {
if !self.session.is_active() {
render_surface_for_output(output, &mut self.backends)
.context("no render surface for output")?
.pending_gamma_change = match gamma {
Some([r, g, b]) => {
PendingGammaChange::Change([Box::from(r), Box::from(g), Box::from(b)])
}
None => PendingGammaChange::Restore,
};
tracing::info!("SET PENDING GAMMA WOO");
return Ok(());
}
let UdevOutputData { device_id, crtc } = output
.user_data()
.get()
.context("no udev output data for output")?;
let drm_device = &self
.backends
.get(device_id)
.context("no udev backend data for output")?
.drm;
let ret = Udev::set_gamma_internal(drm_device, crtc, gamma);
render_surface_for_output(output, &mut self.backends)
.context("no render surface for output")?
.previous_gamma = match ret.is_ok() {
true => gamma.map(|[r, g, b]| [r.into(), g.into(), b.into()]),
false => None,
};
ret
}
pub(super) fn set_gamma_internal(
drm_device: &DrmDevice,
crtc: &crtc::Handle,
gamma: Option<[impl AsRef<[u16]>; 3]>,
) -> anyhow::Result<()> {
let gamma = gamma
.as_ref()
.map(|[r, g, b]| [r.as_ref(), g.as_ref(), b.as_ref()]);
let crtc_info = drm_device.get_crtc(*crtc)?;
let gamma_size = crtc_info.gamma_length() as usize;
ensure!(gamma_size != 0, "setting gamma is not supported");
let mut temp_red;
let mut temp_green;
let mut temp_blue;
let [red, green, blue] = match gamma {
Some([red, green, blue]) => {
ensure!(red.len() == gamma_size, "wrong red gamma size");
ensure!(green.len() == gamma_size, "wrong green gamma size");
ensure!(blue.len() == gamma_size, "wrong blue gamma size");
[red, green, blue]
}
None => {
temp_red = vec![0u16; gamma_size];
temp_green = vec![0u16; gamma_size];
temp_blue = vec![0u16; gamma_size];
let denom = gamma_size as u64 - 1;
for i in 0..gamma_size {
let value = (0xFFFF * i as u64 / denom) as u16;
temp_red[i] = value;
temp_green[i] = value;
temp_blue[i] = value;
}
[
temp_red.as_slice(),
temp_green.as_slice(),
temp_blue.as_slice(),
]
}
};
drm_device.set_gamma(*crtc, red, green, blue)?;
Ok(())
}
pub fn gamma_size(&self, output: &Output) -> anyhow::Result<u32> {
let UdevOutputData { device_id, crtc } = output
.user_data()
.get()
.context("no udev output data for output")?;
let drm_device = &self
.backends
.get(device_id)
.context("no udev backend data for output")?
.drm;
let crtc_info = drm_device.get_crtc(*crtc)?;
Ok(crtc_info.gamma_length())
}
}

View file

@ -56,12 +56,16 @@ use smithay::{
},
xwayland::{X11Wm, XWaylandClientData},
};
use tracing::error;
use tracing::{error, warn};
use crate::{
delegate_screencopy,
backend::Backend,
delegate_gamma_control, delegate_screencopy,
focus::{keyboard::KeyboardFocusTarget, pointer::PointerFocusTarget},
protocol::screencopy::{Screencopy, ScreencopyHandler},
protocol::{
gamma_control::{GammaControlHandler, GammaControlManagerState},
screencopy::{Screencopy, ScreencopyHandler},
},
state::{ClientState, State, WithState},
};
@ -555,3 +559,54 @@ impl ScreencopyHandler for State {
}
}
delegate_screencopy!(State);
impl GammaControlHandler for State {
fn gamma_control_manager_state(&mut self) -> &mut GammaControlManagerState {
&mut self.gamma_control_manager_state
}
fn get_gamma_size(&mut self, output: &Output) -> Option<u32> {
let Backend::Udev(udev) = &self.backend else {
return None;
};
match udev.gamma_size(output) {
Ok(0) => None, // Setting gamma is not supported
Ok(size) => Some(size),
Err(err) => {
warn!(
"Failed to get gamma size for output {}: {err}",
output.name()
);
None
}
}
}
fn set_gamma(&mut self, output: &Output, gammas: [&[u16]; 3]) -> bool {
let Backend::Udev(udev) = &mut self.backend else {
warn!("Setting gamma is not supported on the winit backend");
return false;
};
match udev.set_gamma(output, Some(gammas)) {
Ok(_) => true,
Err(err) => {
warn!("Failed to set gamma for output {}: {err}", output.name());
false
}
}
}
fn gamma_control_destroyed(&mut self, output: &Output) {
let Backend::Udev(udev) = &mut self.backend else {
warn!("Resetting gamma is not supported on the winit backend");
return;
};
if let Err(err) = udev.set_gamma(output, None) {
warn!("Failed to set gamma for output {}: {err}", output.name());
}
}
}
delegate_gamma_control!(State);

View file

@ -154,6 +154,29 @@ impl XdgShellHandler for State {
.unwrap_or_else(|| (0, 0).into());
tracing::debug!(?loc);
break;
} else {
let layer_and_op = self.space.outputs().find_map(|op| {
let layer_map = layer_map_for_output(op);
let ret = layer_map
.layers()
.find(|l| l.wl_surface() == s)
.cloned()
.map(|layer| {
(
layer_map.layer_geometry(&layer).unwrap_or_default(),
op.clone(),
)
});
ret
});
if let Some((layer_geo, op)) = layer_and_op {
let op_loc = self.space.output_geometry(&op).unwrap_or_default().loc;
loc += layer_geo.loc + op_loc;
break;
}
}
}

View file

@ -1 +1,2 @@
pub mod gamma_control;
pub mod screencopy;

View file

@ -0,0 +1,295 @@
use std::{collections::HashMap, fs::File, io::Read};
use smithay::{
output::Output,
reexports::{
wayland_protocols_wlr::gamma_control::v1::server::{
zwlr_gamma_control_manager_v1::{self, ZwlrGammaControlManagerV1},
zwlr_gamma_control_v1::{self, ZwlrGammaControlV1},
},
wayland_server::{
self, backend::ClientId, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch,
Resource,
},
},
};
use tracing::warn;
const VERSION: u32 = 1;
pub struct GammaControlManagerState {
pub gamma_controls: HashMap<Output, ZwlrGammaControlV1>,
}
pub struct GammaControlManagerGlobalData {
filter: Box<dyn Fn(&Client) -> bool + Send + Sync>,
}
impl GammaControlManagerState {
pub fn new<D, F>(display: &DisplayHandle, filter: F) -> Self
where
D: GlobalDispatch<ZwlrGammaControlManagerV1, GammaControlManagerGlobalData>
+ Dispatch<ZwlrGammaControlManagerV1, ()>
+ Dispatch<ZwlrGammaControlV1, GammaControlState>
+ GammaControlHandler
+ 'static,
F: Fn(&Client) -> bool + Send + Sync + 'static,
{
let global_data = GammaControlManagerGlobalData {
filter: Box::new(filter),
};
display.create_global::<D, ZwlrGammaControlManagerV1, _>(VERSION, global_data);
Self {
gamma_controls: HashMap::new(),
}
}
pub fn output_removed(&mut self, output: &Output) {
if let Some(gamma_control) = self.gamma_controls.remove(output) {
gamma_control.failed();
}
}
}
pub struct GammaControlState {
gamma_size: u32,
}
impl<D> GlobalDispatch<ZwlrGammaControlManagerV1, GammaControlManagerGlobalData, D>
for GammaControlManagerState
where
D: GlobalDispatch<ZwlrGammaControlManagerV1, GammaControlManagerGlobalData>
+ Dispatch<ZwlrGammaControlManagerV1, ()>
+ Dispatch<ZwlrGammaControlV1, GammaControlState>
+ GammaControlHandler
+ 'static,
{
fn bind(
_state: &mut D,
_handle: &DisplayHandle,
_client: &Client,
resource: wayland_server::New<ZwlrGammaControlManagerV1>,
_global_data: &GammaControlManagerGlobalData,
data_init: &mut DataInit<'_, D>,
) {
data_init.init(resource, ());
}
fn can_view(client: Client, global_data: &GammaControlManagerGlobalData) -> bool {
(global_data.filter)(&client)
}
}
impl<D> Dispatch<ZwlrGammaControlManagerV1, (), D> for GammaControlManagerState
where
D: Dispatch<ZwlrGammaControlManagerV1, ()>
+ Dispatch<ZwlrGammaControlV1, GammaControlState>
+ GammaControlHandler
+ 'static,
{
fn request(
state: &mut D,
_client: &Client,
_manager: &ZwlrGammaControlManagerV1,
request: <ZwlrGammaControlManagerV1 as wayland_server::Resource>::Request,
_data: &(),
_dhandle: &DisplayHandle,
data_init: &mut DataInit<'_, D>,
) {
let (id, output) = match request {
zwlr_gamma_control_manager_v1::Request::GetGammaControl { id, output } => (id, output),
zwlr_gamma_control_manager_v1::Request::Destroy => return,
_ => unreachable!(),
};
let output = Output::from_resource(&output).expect("no output for resource");
match state
.gamma_control_manager_state()
.gamma_controls
.contains_key(&output)
{
true => {
// This wl_output already has exclusive access by another client
let gamma_control_state = GammaControlState { gamma_size: 0 };
let gamma_control = data_init.init(id, gamma_control_state);
gamma_control.failed();
}
false => {
let Some(gamma_size) = state.get_gamma_size(&output) else {
let gamma_control = data_init.init(id, GammaControlState { gamma_size: 0 });
gamma_control.failed();
return;
};
let gamma_control = data_init.init(id, GammaControlState { gamma_size });
gamma_control.gamma_size(gamma_size);
state
.gamma_control_manager_state()
.gamma_controls
.insert(output, gamma_control);
}
}
}
}
pub trait GammaControlHandler {
fn gamma_control_manager_state(&mut self) -> &mut GammaControlManagerState;
/// A new gamma control was requested on the given output.
///
/// This should return the length of the gamma on the output, if available.
fn get_gamma_size(&mut self, output: &Output) -> Option<u32>;
/// A client requested that the gamma be set on the given output.
///
/// `gammas` are the gammas for the red, green, and blue channels respectively.
///
/// Returns whether or not the operation completed successfully.
fn set_gamma(&mut self, output: &Output, gammas: [&[u16]; 3]) -> bool;
/// A client destroyed its gamma control object for the given output.
fn gamma_control_destroyed(&mut self, output: &Output);
}
#[allow(missing_docs)]
#[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::protocol::gamma_control::GammaControlManagerGlobalData
] => $crate::protocol::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::protocol::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::protocol::gamma_control::GammaControlState
] => $crate::protocol::gamma_control::GammaControlManagerState);
};
}
impl<D> Dispatch<ZwlrGammaControlV1, GammaControlState, D> for GammaControlManagerState
where
D: Dispatch<ZwlrGammaControlV1, GammaControlState> + GammaControlHandler + 'static,
{
fn request(
state: &mut D,
_client: &Client,
resource: &ZwlrGammaControlV1,
request: <ZwlrGammaControlV1 as Resource>::Request,
data: &GammaControlState,
_dhandle: &DisplayHandle,
_data_init: &mut DataInit<'_, D>,
) {
if matches!(request, zwlr_gamma_control_v1::Request::Destroy) {
return;
}
let Some(output) = state
.gamma_control_manager_state()
.gamma_controls
.iter()
.find(|(_, res)| *res == resource)
.map(|(output, _)| output)
.cloned()
else {
resource.failed();
return;
};
let GammaControlState { gamma_size } = data;
let gamma_size = *gamma_size as usize;
let fd = match request {
zwlr_gamma_control_v1::Request::SetGamma { fd } => fd,
zwlr_gamma_control_v1::Request::Destroy => return,
_ => unreachable!(),
};
let mut gammas = vec![0u16; gamma_size * 3];
{
let buf = bytemuck::cast_slice_mut(&mut gammas);
let mut file = File::from(fd);
let gamma_controls = &mut state.gamma_control_manager_state().gamma_controls;
if let Err(err) = file.read_exact(buf) {
warn!(
"Failed to read {} u16s from client gamma control fd: {err}",
gamma_size * 3
);
resource.failed();
gamma_controls.remove(&output);
state.gamma_control_destroyed(&output);
return;
}
#[allow(clippy::unused_io_amount)]
{
match file.read(&mut [0]) {
Ok(0) => (),
Ok(_) => {
warn!(
"Client gamma control sent more data than expected (expected {} u16s)",
gamma_size * 3,
);
resource.failed();
gamma_controls.remove(&output);
state.gamma_control_destroyed(&output);
return;
}
Err(err) => {
warn!(
"Failed to ensure client gamma control fd was the correct size: {err}"
);
resource.failed();
gamma_controls.remove(&output);
state.gamma_control_destroyed(&output);
return;
}
}
}
}
assert_eq!(gammas.len(), gamma_size * 3);
let gammas = gammas.chunks_exact(gamma_size).collect::<Vec<_>>();
let [red_gamma, green_gamma, blue_gamma] = gammas.as_slice() else {
unreachable!();
};
if !state.set_gamma(&output, [red_gamma, green_gamma, blue_gamma]) {
resource.failed();
state
.gamma_control_manager_state()
.gamma_controls
.remove(&output);
state.gamma_control_destroyed(&output);
}
}
fn destroyed(
state: &mut D,
_client: ClientId,
resource: &ZwlrGammaControlV1,
_data: &GammaControlState,
) {
let gamma_controls = &mut state.gamma_control_manager_state().gamma_controls;
let Some(output) = gamma_controls
.iter()
.find(|(_, res)| *res == resource)
.map(|(output, _)| output)
.cloned()
else {
return;
};
gamma_controls.remove(&output);
state.gamma_control_destroyed(&output);
}
}

View file

@ -46,7 +46,7 @@ render_elements! {
}
render_elements! {
pub OutputRenderElements<R, E> where R: ImportAll + ImportMem;
pub OutputRenderElement<R, E> where R: ImportAll + ImportMem;
Custom = Wrap<E>,
Surface = WaylandSurfaceRenderElement<R>,
Pointer = PointerRenderElement<R>,
@ -137,16 +137,13 @@ fn window_render_elements<R>(
renderer: &mut R,
scale: Scale<f64>,
) -> (
Vec<OutputRenderElements<R, WaylandSurfaceRenderElement<R>>>,
Vec<OutputRenderElements<R, WaylandSurfaceRenderElement<R>>>,
Vec<OutputRenderElement<R, WaylandSurfaceRenderElement<R>>>,
Vec<OutputRenderElement<R, WaylandSurfaceRenderElement<R>>>,
)
where
R: Renderer + ImportAll + ImportMem,
<R as Renderer>::TextureId: 'static,
{
// bot wwwwwFFww top
// rev wwFFwwwww
let mut last_fullscreen_split_at = 0;
let mut elements = windows
@ -180,9 +177,9 @@ where
Some(rect) => {
elems.into_iter().filter_map(|elem| {
CropRenderElement::from_element(elem, scale, rect.to_physical_precise_round(scale))
}).map(TransformRenderElement::from).map(OutputRenderElements::from).collect::<Vec<_>>()
}).map(TransformRenderElement::from).map(OutputRenderElement::from).collect::<Vec<_>>()
},
None => elems.into_iter().map(OutputRenderElements::from).collect(),
None => elems.into_iter().map(OutputRenderElement::from).collect(),
}
}).collect::<Vec<_>>();
@ -203,7 +200,7 @@ pub fn pointer_render_elements<R>(
cursor_status: &mut CursorImageStatus,
dnd_icon: Option<&WlSurface>,
pointer_element: &PointerElement<<R as Renderer>::TextureId>,
) -> Vec<OutputRenderElements<R, WaylandSurfaceRenderElement<R>>>
) -> Vec<OutputRenderElement<R, WaylandSurfaceRenderElement<R>>>
where
R: Renderer + ImportAll,
<R as Renderer>::TextureId: Clone + 'static,
@ -264,7 +261,7 @@ pub fn generate_render_elements<R, T>(
renderer: &mut R,
space: &Space<WindowElement>,
windows: &[WindowElement],
) -> Vec<OutputRenderElements<R, WaylandSurfaceRenderElement<R>>>
) -> Vec<OutputRenderElement<R, WaylandSurfaceRenderElement<R>>>
where
R: Renderer<TextureId = T> + ImportAll + ImportMem,
<R as Renderer>::TextureId: 'static,
@ -272,7 +269,7 @@ where
{
let scale = Scale::from(output.current_scale().fractional_scale());
let mut output_render_elements: Vec<OutputRenderElements<_, _>> = Vec::new();
let mut output_render_elements: Vec<OutputRenderElement<_, _>> = Vec::new();
let (windows, override_redirect_windows) = windows
.iter()
@ -309,7 +306,7 @@ where
// TODO: don't unconditionally render OR windows above fullscreen ones,
// | base it on if it's a descendant or not
output_render_elements.extend(o_r_elements.map(OutputRenderElements::from));
output_render_elements.extend(o_r_elements.map(OutputRenderElement::from));
let LayerRenderElements {
background,
@ -329,7 +326,7 @@ where
overlay
.into_iter()
.chain(top)
.map(OutputRenderElements::from),
.map(OutputRenderElement::from),
);
output_render_elements.extend(rest_of_window_elements);
@ -338,7 +335,7 @@ where
bottom
.into_iter()
.chain(background)
.map(OutputRenderElements::from),
.map(OutputRenderElement::from),
);
output_render_elements

View file

@ -1,9 +1,15 @@
// SPDX-License-Identifier: GPL-3.0-or-later
use crate::{
api::signal::SignalState, backend::Backend, config::Config, cursor::Cursor,
focus::OutputFocusStack, grab::resize_grab::ResizeSurfaceState, layout::LayoutState,
protocol::screencopy::ScreencopyManagerState, window::WindowElement,
api::signal::SignalState,
backend::Backend,
config::Config,
cursor::Cursor,
focus::OutputFocusStack,
grab::resize_grab::ResizeSurfaceState,
layout::LayoutState,
protocol::{gamma_control::GammaControlManagerState, screencopy::ScreencopyManagerState},
window::WindowElement,
};
use anyhow::Context;
use smithay::{
@ -69,6 +75,7 @@ pub struct State {
pub layer_shell_state: WlrLayerShellState,
pub data_control_state: DataControlState,
pub screencopy_manager_state: ScreencopyManagerState,
pub gamma_control_manager_state: GammaControlManagerState,
/// The state of key and mousebinds along with libinput settings
pub input_state: InputState,
@ -252,6 +259,10 @@ impl State {
&display_handle,
|_| true,
),
gamma_control_manager_state: GammaControlManagerState::new::<Self, _>(
&display_handle,
|_| true,
),
input_state: InputState::new(),