mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-26 21:58:10 +01:00
Merge pull request #197 from pinnacle-comp/gamma_control
Implement wlr-gamma-control
This commit is contained in:
commit
89a3e592a1
10 changed files with 739 additions and 124 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1759,6 +1759,7 @@ version = "0.0.1"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.5.0",
|
||||
"bytemuck",
|
||||
"chrono",
|
||||
"clap",
|
||||
"cliclack",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
118
src/backend/udev/gamma.rs
Normal 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())
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
pub mod gamma_control;
|
||||
pub mod screencopy;
|
||||
|
|
295
src/protocol/gamma_control.rs
Normal file
295
src/protocol/gamma_control.rs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
17
src/state.rs
17
src/state.rs
|
@ -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(),
|
||||
|
||||
|
|
Loading…
Reference in a new issue