Add output disabling

Still needs an API call
This commit is contained in:
Ottatop 2024-06-02 18:52:35 -05:00
parent a3226a3c62
commit 4b3fbd716f
9 changed files with 274 additions and 138 deletions

View file

@ -1035,9 +1035,14 @@ impl output_service_server::OutputService for OutputService {
if let Some(y) = y {
loc.y = y;
}
state
.pinnacle
.change_output_state(&output, None, None, None, Some(loc));
state.pinnacle.change_output_state(
&mut state.backend,
&output,
None,
None,
None,
Some(loc),
);
debug!("Mapping output {} to {loc:?}", output.name());
state.pinnacle.request_layout(&output);
})
@ -1067,7 +1072,15 @@ impl output_service_server::OutputService for OutputService {
return;
};
state.resize_output(&output, mode);
state.pinnacle.change_output_state(
&mut state.backend,
&output,
Some(mode),
None,
None,
None,
);
state.pinnacle.request_layout(&output);
})
.await
}
@ -1102,6 +1115,7 @@ impl output_service_server::OutputService for OutputService {
});
state.pinnacle.change_output_state(
&mut state.backend,
&output,
None,
None,
@ -1154,9 +1168,14 @@ impl output_service_server::OutputService for OutputService {
return;
};
state
.pinnacle
.change_output_state(&output, None, Some(smithay_transform), None, None);
state.pinnacle.change_output_state(
&mut state.backend,
&output,
None,
Some(smithay_transform),
None,
None,
);
state.pinnacle.request_layout(&output);
state.schedule_render(&output);
})

View file

@ -151,6 +151,8 @@ pub trait BackendData: 'static {
// INFO: only for udev in anvil, maybe shouldn't be a trait fn?
fn early_import(&mut self, surface: &WlSurface);
fn set_output_mode(&mut self, output: &Output, mode: smithay::output::Mode);
}
impl BackendData for Backend {
@ -180,6 +182,15 @@ impl BackendData for Backend {
Backend::Dummy(dummy) => dummy.early_import(surface),
}
}
fn set_output_mode(&mut self, output: &Output, mode: smithay::output::Mode) {
match self {
Backend::Winit(winit) => winit.set_output_mode(output, mode),
Backend::Udev(udev) => udev.set_output_mode(output, mode),
#[cfg(feature = "testing")]
Backend::Dummy(dummy) => dummy.set_output_mode(output, mode),
}
}
}
/// Update surface primary scanout outputs and send frames and dmabuf feedback to visible windows

View file

@ -1,6 +1,4 @@
use pinnacle_api_defs::pinnacle::signal::v0alpha1::{
OutputConnectResponse, OutputDisconnectResponse,
};
use pinnacle_api_defs::pinnacle::signal::v0alpha1::OutputConnectResponse;
use smithay::backend::renderer::test::DummyRenderer;
use smithay::backend::renderer::ImportMemWl;
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
@ -50,6 +48,10 @@ impl BackendData for Dummy {
fn reset_buffers(&mut self, _output: &Output) {}
fn early_import(&mut self, _surface: &WlSurface) {}
fn set_output_mode(&mut self, _output: &Output, _mode: smithay::output::Mode) {
// TODO:
}
}
impl Dummy {
@ -141,14 +143,4 @@ impl Pinnacle {
});
});
}
pub fn remove_output(&mut self, output: &Output) {
self.space.unmap_output(output);
self.signal_state.output_disconnect.signal(|buffer| {
buffer.push_back(OutputDisconnectResponse {
output_name: Some(output.name()),
})
});
}
}

View file

@ -71,7 +71,6 @@ use smithay::{
presentation_time::server::wp_presentation_feedback,
},
wayland_server::{
backend::GlobalId,
protocol::{wl_shm, wl_surface::WlSurface},
DisplayHandle,
},
@ -527,9 +526,12 @@ impl Udev {
/// Schedule a new render that will cause the compositor to redraw everything.
pub fn schedule_render(&mut self, loop_handle: &LoopHandle<State>, output: &Output) {
let Some(surface) = render_surface_for_output(output, &mut self.backends) else {
tracing::info!("no render surface on output {}", output.name());
return;
};
// tracing::info!(state = ?surface.render_state, name = output.name());
match &surface.render_state {
RenderState::Idle => {
let output = output.clone();
@ -577,6 +579,12 @@ impl Udev {
{
warn!("Failed to reset compositor state on crtc {crtc:?}: {err}");
}
if let Some(surface) = render_surface_for_output(output, &mut self.backends) {
if let RenderState::Scheduled(idle) = std::mem::take(&mut surface.render_state) {
idle.cancel();
}
}
}
}
}
@ -638,51 +646,6 @@ impl State {
// );
}
}
/// Resize the output with the given mode.
///
/// TODO: This is in udev.rs but is also used in winit.rs.
/// | I've got no clue how to make things public without making a mess.
pub fn resize_output(&mut self, output: &Output, mode: smithay::output::Mode) {
if let Backend::Udev(udev) = &mut self.backend {
let drm_mode = udev.backends.iter().find_map(|(_, backend)| {
backend
.drm_scanner
.crtcs()
.find(|(_, handle)| {
output
.user_data()
.get::<UdevOutputData>()
.is_some_and(|data| &data.crtc == handle)
})
.and_then(|(info, _)| {
info.modes()
.iter()
.find(|m| smithay::output::Mode::from(**m) == mode)
})
.copied()
});
if let Some(drm_mode) = drm_mode {
if let Some(render_surface) = render_surface_for_output(output, &mut udev.backends)
{
match render_surface.compositor.use_mode(drm_mode) {
Ok(()) => {
self.pinnacle
.change_output_state(output, Some(mode), None, None, None);
}
Err(err) => error!("Failed to resize output: {err}"),
}
}
}
} else {
self.pinnacle
.change_output_state(output, Some(mode), None, None, None);
}
self.pinnacle.request_layout(output);
self.schedule_render(output);
}
}
impl BackendData for Udev {
@ -705,6 +668,45 @@ impl BackendData for Udev {
warn!("early buffer import failed: {}", err);
}
}
fn set_output_mode(&mut self, output: &Output, mode: smithay::output::Mode) {
let drm_mode = self.backends.iter().find_map(|(_, backend)| {
backend
.drm_scanner
.crtcs()
.find(|(_, handle)| {
output
.user_data()
.get::<UdevOutputData>()
.is_some_and(|data| &data.crtc == handle)
})
.and_then(|(info, _)| {
info.modes()
.iter()
.find(|m| smithay::output::Mode::from(**m) == mode)
})
.copied()
});
if let Some(drm_mode) = drm_mode {
if let Some(render_surface) = render_surface_for_output(output, &mut self.backends) {
match render_surface.compositor.use_mode(drm_mode) {
Ok(()) => {
output.change_current_state(Some(mode), None, None, None);
output.with_state_mut(|state| {
if !state.modes.contains(&mode) {
state.modes.push(mode);
}
});
}
Err(err) => warn!("Failed to resize output: {err}"),
}
}
} else {
// TODO: create new drm mode with cvt
tracing::info!("no drm mode for mode");
}
}
}
// TODO: document desperately
@ -810,7 +812,6 @@ enum RenderState {
/// The idle token from a render being scheduled.
/// This is used to cancel renders if, for example,
/// the output being rendered is removed.
#[allow(dead_code)] // TODO:
Idle<'static>,
),
/// A frame was rendered and scheduled and we are waiting for vblank.
@ -823,10 +824,6 @@ enum RenderState {
/// Render surface for an output.
struct RenderSurface {
/// The output global id.
global: Option<GlobalId>,
/// A display handle used to remove the global on drop.
display_handle: DisplayHandle,
/// The node from `connector_connected`.
device_id: DrmNode,
/// The node rendering to the screen? idk
@ -862,15 +859,6 @@ struct ScreencopyCommitState {
_cursor: CommitCounter,
}
impl Drop for RenderSurface {
// Stop advertising this output to clients on drop.
fn drop(&mut self) {
if let Some(global) = self.global.take() {
self.display_handle.remove_global::<State>(global);
}
}
}
type GbmDrmCompositor = DrmCompositor<
GbmAllocator<DrmDeviceFd>,
GbmDevice<DrmDeviceFd>,
@ -1055,11 +1043,12 @@ impl Udev {
);
let global = output.create_global::<State>(&self.display_handle);
pinnacle
.output_management_manager_state
.add_head::<State>(&output);
pinnacle.outputs.insert(output.clone(), global);
output.with_state_mut(|state| state.serial = serial);
output.with_state_mut(|state| {
state.serial = serial;
state.powered = true;
});
output.set_preferred(wl_mode);
@ -1071,6 +1060,10 @@ impl Udev {
.collect::<Vec<_>>();
output.with_state_mut(|state| state.modes = modes);
pinnacle
.output_management_manager_state
.add_head::<State>(&output);
let x = pinnacle.space.outputs().fold(0, |acc, o| {
let Some(geo) = pinnacle.space.output_geometry(o) else {
unreachable!()
@ -1131,10 +1124,8 @@ impl Udev {
);
let surface = RenderSurface {
display_handle: self.display_handle.clone(),
device_id: node,
render_node: device.render_node,
global: Some(global),
compositor,
dmabuf_feedback,
render_state: RenderState::Idle,
@ -1145,7 +1136,7 @@ impl Udev {
device.surfaces.insert(crtc, surface);
pinnacle.change_output_state(&output, Some(wl_mode), None, None, Some(position));
pinnacle.change_output_state(self, &output, Some(wl_mode), None, None, Some(position));
// If there is saved connector state, the connector was previously plugged in.
// In this case, restore its tags and location.
@ -1157,7 +1148,7 @@ impl Udev {
{
let ConnectorSavedState { loc, tags, scale } = saved_state;
output.with_state_mut(|state| state.tags.clone_from(tags));
pinnacle.change_output_state(&output, None, None, *scale, Some(*loc));
pinnacle.change_output_state(self, &output, None, None, *scale, Some(*loc));
} else {
pinnacle.signal_state.output_connect.signal(|buffer| {
buffer.push_back(OutputConnectResponse {
@ -1223,6 +1214,11 @@ impl Udev {
pinnacle
.output_management_manager_state
.remove_head(&output);
if let Some(global) = pinnacle.outputs.remove(&output) {
// TODO: disable ahead of time
pinnacle.display_handle.remove_global::<State>(global);
}
}
}
@ -1298,7 +1294,11 @@ impl Udev {
return;
};
let output = if let Some(output) = pinnacle.space.outputs().find(|o| {
let output = if let Some(output) = pinnacle
.outputs
.keys()
.chain(pinnacle.unmapped_outputs.iter())
.find(|o| {
let udev_op_data = o.user_data().get::<UdevOutputData>();
udev_op_data
.is_some_and(|data| data.device_id == surface.device_id && data.crtc == crtc)
@ -1394,6 +1394,11 @@ impl Udev {
assert!(matches!(surface.render_state, RenderState::Scheduled(_)));
if !pinnacle.outputs.contains_key(output) {
surface.render_state = RenderState::Idle;
return;
}
// TODO: possibly lift this out and make it so that scheduling a render
// does nothing on powered off outputs
if output.with_state(|state| !state.powered) {

View file

@ -67,6 +67,10 @@ impl BackendData for Winit {
}
fn early_import(&mut self, _surface: &WlSurface) {}
fn set_output_mode(&mut self, output: &Output, mode: smithay::output::Mode) {
output.change_current_state(Some(mode), None, None, None);
}
}
impl Backend {
@ -201,6 +205,7 @@ impl Winit {
refresh: 144_000,
};
state.pinnacle.change_output_state(
&mut state.backend,
&output,
Some(mode),
None,

View file

@ -22,7 +22,7 @@ use smithay::{
pointer::{CursorImageStatus, PointerHandle},
Seat, SeatHandler, SeatState,
},
output::{Output, Scale},
output::{Mode, Output, Scale},
reexports::{
calloop::Interest,
wayland_protocols::xdg::shell::server::xdg_positioner::ConstraintAdjustment,
@ -930,21 +930,55 @@ impl OutputManagementHandler for State {
fn apply_configuration(&mut self, config: HashMap<Output, OutputConfiguration>) -> bool {
for (output, config) in config {
match config {
OutputConfiguration::Disabled => todo!(),
OutputConfiguration::Disabled => {
self.pinnacle.set_output_enabled(&output, false);
// TODO: split
self.backend.set_output_powered(&output, false);
}
OutputConfiguration::Enabled {
mode,
position,
transform,
scale,
adaptive_sync,
adaptive_sync: _,
} => {
self.pinnacle.set_output_enabled(&output, true);
// TODO: split
self.backend.set_output_powered(&output, true);
self.schedule_render(&output);
let snapshots = self.backend.with_renderer(|renderer| {
capture_snapshots_on_output(&mut self.pinnacle, renderer, &output, [])
});
let mode = mode.map(|(size, refresh)| {
if let Some(refresh) = refresh {
Mode {
size,
refresh: refresh.get() as i32,
}
} else {
output
.with_state(|state| {
state
.modes
.iter()
.filter(|mode| mode.size == size)
.max_by_key(|mode| mode.refresh)
.copied()
})
.unwrap_or(Mode {
size,
refresh: 60_000,
})
}
});
self.pinnacle.change_output_state(
&mut self.backend,
&output,
None,
mode,
transform,
scale.map(Scale::Fractional),
position,

View file

@ -2,7 +2,9 @@
use std::{cell::RefCell, num::NonZeroU32};
use pinnacle_api_defs::pinnacle::signal::v0alpha1::{OutputMoveResponse, OutputResizeResponse};
use pinnacle_api_defs::pinnacle::signal::v0alpha1::{
OutputDisconnectResponse, OutputMoveResponse, OutputResizeResponse,
};
use smithay::{
desktop::layer_map_for_output,
output::{Mode, Output, Scale},
@ -12,6 +14,8 @@ use smithay::{
};
use crate::{
backend::BackendData,
config::ConnectorSavedState,
focus::WindowKeyboardFocusStack,
layout::transaction::{LayoutTransaction, SnapshotTarget},
protocol::screencopy::Screencopy,
@ -51,7 +55,7 @@ pub enum BlankingState {
}
/// The state of an output
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct OutputState {
pub tags: Vec<Tag>,
pub focus_stack: WindowKeyboardFocusStack,
@ -69,22 +73,6 @@ pub struct OutputState {
pub powered: bool,
}
impl Default for OutputState {
fn default() -> Self {
Self {
tags: Default::default(),
focus_stack: Default::default(),
screencopy: Default::default(),
serial: Default::default(),
modes: Default::default(),
lock_surface: Default::default(),
blanking_state: Default::default(),
layout_transaction: Default::default(),
powered: true,
}
}
}
impl WithState for Output {
type State = OutputState;
@ -135,10 +123,9 @@ impl OutputState {
}
impl Pinnacle {
/// A wrapper around [`Output::change_current_state`] that additionally sends an output
/// geometry signal.
pub fn change_output_state(
&mut self,
backend: &mut impl BackendData,
output: &Output,
mode: Option<Mode>,
transform: Option<Transform>,
@ -147,7 +134,7 @@ impl Pinnacle {
) {
let old_scale = output.current_scale().fractional_scale();
output.change_current_state(mode, transform, scale, location);
output.change_current_state(None, transform, scale, location);
if let Some(location) = location {
self.space.map_output(output, location);
self.signal_state.output_move.signal(|buf| {
@ -158,6 +145,11 @@ impl Pinnacle {
});
});
}
if let Some(mode) = mode {
backend.set_output_mode(output, mode);
}
if mode.is_some() || transform.is_some() || scale.is_some() {
layer_map_for_output(output).arrange();
self.signal_state.output_resize.signal(|buf| {
@ -169,10 +161,6 @@ impl Pinnacle {
});
});
}
if let Some(mode) = mode {
output.set_preferred(mode);
output.with_state_mut(|state| state.modes.push(mode));
}
if let Some(scale) = scale {
let pos_multiplier = old_scale / scale.fractional_scale();
@ -223,4 +211,79 @@ impl Pinnacle {
self.output_management_manager_state
.update_head::<State>(output);
}
pub fn set_output_enabled(&mut self, output: &Output, enabled: bool) {
if enabled {
self.unmapped_outputs.remove(output);
if !self.outputs.contains_key(output) {
let global = output.create_global::<State>(&self.display_handle);
self.outputs.insert(output.clone(), global);
}
self.space.map_output(output, output.current_location());
// TODO: output connect?
} else {
let global = self.outputs.remove(output);
if let Some(global) = global {
self.display_handle.remove_global::<State>(global);
}
self.space.unmap_output(output);
self.unmapped_outputs.insert(output.clone());
// TODO: should this trigger the signal?
self.signal_state.output_disconnect.signal(|buffer| {
buffer.push_back(OutputDisconnectResponse {
output_name: Some(output.name()),
})
});
self.gamma_control_manager_state.output_removed(output);
self.config.connector_saved_states.insert(
OutputName(output.name()),
ConnectorSavedState {
loc: output.current_location(),
tags: output.with_state(|state| state.tags.clone()),
scale: Some(output.current_scale()),
},
);
for layer in layer_map_for_output(output).layers() {
layer.layer_surface().send_close();
}
}
}
pub fn remove_output(&mut self, output: &Output) {
let global = self.outputs.remove(output);
if let Some(global) = global {
self.display_handle.remove_global::<State>(global);
}
self.unmapped_outputs.remove(output);
self.space.unmap_output(output);
self.gamma_control_manager_state.output_removed(output);
self.output_management_manager_state.remove_head(output);
self.signal_state.output_disconnect.signal(|buffer| {
buffer.push_back(OutputDisconnectResponse {
output_name: Some(output.name()),
})
});
self.config.connector_saved_states.insert(
OutputName(output.name()),
ConnectorSavedState {
loc: output.current_location(),
tags: output.with_state(|state| state.tags.clone()),
scale: Some(output.current_scale()),
},
);
for layer in layer_map_for_output(output).layers() {
layer.layer_surface().send_close();
}
}
}

View file

@ -101,7 +101,7 @@ pub struct OutputData {
position: Point<i32, Logical>,
transform: Transform,
scale: f64,
adaptive_sync: bool,
_adaptive_sync: bool,
}
impl OutputManagementManagerState {
@ -127,7 +127,7 @@ impl OutputManagementManagerState {
position: output.current_location(),
transform: output.current_transform(),
scale: output.current_scale().fractional_scale(),
adaptive_sync: false, // TODO:
_adaptive_sync: false, // TODO:
};
self.outputs.insert(output.clone(), output_data);
@ -336,7 +336,7 @@ where
head.physical_size(physical_props.size.w, physical_props.size.h);
let mut wlr_modes = Vec::new();
for mode in output.modes() {
for mode in output.with_state(|state| state.modes.clone()) {
let wlr_mode = client
.create_resource::<ZwlrOutputModeV1, _, D>(display, manager.version(), mode)
.unwrap();
@ -551,13 +551,13 @@ where
impl<D> Dispatch<ZwlrOutputHeadV1, Output, D> for OutputManagementManagerState {
fn request(
state: &mut D,
client: &Client,
resource: &ZwlrOutputHeadV1,
_state: &mut D,
_client: &Client,
_resource: &ZwlrOutputHeadV1,
request: <ZwlrOutputHeadV1 as Resource>::Request,
data: &Output,
dhandle: &DisplayHandle,
data_init: &mut DataInit<'_, D>,
_data: &Output,
_dhandle: &DisplayHandle,
_data_init: &mut DataInit<'_, D>,
) {
match request {
zwlr_output_head_v1::Request::Release => {
@ -570,13 +570,13 @@ impl<D> Dispatch<ZwlrOutputHeadV1, Output, D> for OutputManagementManagerState {
impl<D> Dispatch<ZwlrOutputModeV1, Mode, D> for OutputManagementManagerState {
fn request(
state: &mut D,
client: &Client,
resource: &ZwlrOutputModeV1,
_state: &mut D,
_client: &Client,
_resource: &ZwlrOutputModeV1,
request: <ZwlrOutputModeV1 as Resource>::Request,
data: &Mode,
dhandle: &DisplayHandle,
data_init: &mut DataInit<'_, D>,
_data: &Mode,
_dhandle: &DisplayHandle,
_data_init: &mut DataInit<'_, D>,
) {
match request {
zwlr_output_mode_v1::Request::Release => {

View file

@ -22,10 +22,11 @@ use pinnacle_api_defs::pinnacle::v0alpha1::ShutdownWatchResponse;
use smithay::{
desktop::{PopupManager, Space},
input::{keyboard::XkbConfig, pointer::CursorImageStatus, Seat, SeatState},
output::Output,
reexports::{
calloop::{generic::Generic, Interest, LoopHandle, LoopSignal, Mode, PostAction},
wayland_server::{
backend::{ClientData, ClientId, DisconnectReason},
backend::{ClientData, ClientId, DisconnectReason, GlobalId},
protocol::wl_surface::WlSurface,
Client, Display, DisplayHandle,
},
@ -149,6 +150,9 @@ pub struct Pinnacle {
/// WlSurfaces with an attached idle inhibitor.
pub idle_inhibiting_surfaces: HashSet<WlSurface>,
pub outputs: HashMap<Output, GlobalId>,
pub unmapped_outputs: HashSet<Output>,
}
impl State {
@ -343,6 +347,9 @@ impl Pinnacle {
root_surface_cache: HashMap::new(),
idle_inhibiting_surfaces: HashSet::new(),
outputs: HashMap::new(),
unmapped_outputs: HashSet::new(),
};
Ok(pinnacle)