implement wlr_output_management_unstable_v1
This commit is contained in:
parent
a3c0631f4e
commit
c6060a7389
23 changed files with 1349 additions and 32 deletions
|
|
@ -35,6 +35,7 @@
|
|||
responsiveness under high system load. This is described in detail in
|
||||
[setup.md](docs/setup.md).
|
||||
- Implement wlr-foreign-toplevel-management-v1.
|
||||
- Implement wlr-output-management-v1.
|
||||
|
||||
# 1.10.0 (2025-04-22)
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ pub trait Backend: Any {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
|
||||
pub struct Mode {
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ use {
|
|||
WlSurface,
|
||||
xdg_surface::{XdgSurface, xdg_popup::XdgPopup, xdg_toplevel::XdgToplevel},
|
||||
},
|
||||
wlr_output_manager::{
|
||||
zwlr_output_head_v1::ZwlrOutputHeadV1, zwlr_output_mode_v1::ZwlrOutputModeV1,
|
||||
},
|
||||
workspace_manager::ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1,
|
||||
wp_drm_lease_connector_v1::WpDrmLeaseConnectorV1,
|
||||
wp_linux_drm_syncobj_timeline_v1::WpLinuxDrmSyncobjTimelineV1,
|
||||
|
|
@ -47,8 +50,8 @@ use {
|
|||
WlDataSourceId, WlOutputId, WlPointerId, WlRegionId, WlRegistryId, WlSeatId,
|
||||
WlSurfaceId, WpDrmLeaseConnectorV1Id, WpImageDescriptionV1Id,
|
||||
WpLinuxDrmSyncobjTimelineV1Id, XdgPopupId, XdgPositionerId, XdgSurfaceId,
|
||||
XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, ZwpPrimarySelectionSourceV1Id,
|
||||
ZwpTabletToolV2Id,
|
||||
XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, ZwlrOutputHeadV1Id,
|
||||
ZwlrOutputModeV1Id, ZwpPrimarySelectionSourceV1Id, ZwpTabletToolV2Id,
|
||||
},
|
||||
},
|
||||
std::{cell::RefCell, rc::Rc},
|
||||
|
|
@ -76,6 +79,8 @@ pub struct Objects {
|
|||
pub screencasts: CopyHashMap<JayScreencastId, Rc<JayScreencast>>,
|
||||
pub timelines: CopyHashMap<WpLinuxDrmSyncobjTimelineV1Id, Rc<WpLinuxDrmSyncobjTimelineV1>>,
|
||||
pub zwlr_data_sources: CopyHashMap<ZwlrDataControlSourceV1Id, Rc<ZwlrDataControlSourceV1>>,
|
||||
pub zwlr_output_heads: CopyHashMap<ZwlrOutputHeadV1Id, Rc<ZwlrOutputHeadV1>>,
|
||||
pub zwlr_output_modes: CopyHashMap<ZwlrOutputModeV1Id, Rc<ZwlrOutputModeV1>>,
|
||||
pub jay_toplevels: CopyHashMap<JayToplevelId, Rc<JayToplevel>>,
|
||||
pub drm_lease_outputs: CopyHashMap<WpDrmLeaseConnectorV1Id, Rc<WpDrmLeaseConnectorV1>>,
|
||||
pub tablet_tools: CopyHashMap<ZwpTabletToolV2Id, Rc<ZwpTabletToolV2>>,
|
||||
|
|
@ -119,6 +124,8 @@ impl Objects {
|
|||
screencasts: Default::default(),
|
||||
timelines: Default::default(),
|
||||
zwlr_data_sources: Default::default(),
|
||||
zwlr_output_heads: Default::default(),
|
||||
zwlr_output_modes: Default::default(),
|
||||
jay_toplevels: Default::default(),
|
||||
drm_lease_outputs: Default::default(),
|
||||
tablet_tools: Default::default(),
|
||||
|
|
@ -147,6 +154,8 @@ impl Objects {
|
|||
self.registry.clear();
|
||||
self.registries.clear();
|
||||
self.outputs.clear();
|
||||
self.zwlr_output_heads.clear();
|
||||
self.zwlr_output_modes.clear();
|
||||
self.surfaces.clear();
|
||||
self.xdg_surfaces.clear();
|
||||
self.xdg_toplevel.clear();
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ use {
|
|||
wl_output::{OutputId, PersistentOutputState, WlOutputGlobal},
|
||||
wl_seat::handle_position_hint_requests,
|
||||
wl_surface::{NoneSurfaceExt, zwp_input_popup_surface_v2::input_popup_positioning},
|
||||
wlr_output_manager::wlr_output_manager_done,
|
||||
workspace_manager::workspace_manager_done,
|
||||
},
|
||||
io_uring::{IoUring, IoUringError},
|
||||
|
|
@ -246,6 +247,7 @@ fn start_compositor2(
|
|||
logger: logger.clone(),
|
||||
connectors: Default::default(),
|
||||
outputs: Default::default(),
|
||||
wlr_output_managers: Default::default(),
|
||||
drm_devs: Default::default(),
|
||||
status: Default::default(),
|
||||
idle: IdleState {
|
||||
|
|
@ -474,6 +476,10 @@ fn start_global_event_handlers(state: &Rc<State>) -> Vec<SpawnedFuture<()>> {
|
|||
Phase::PostLayout,
|
||||
output_render_data(state.clone()),
|
||||
),
|
||||
eng.spawn(
|
||||
"wlr output manager done",
|
||||
wlr_output_manager_done(state.clone()),
|
||||
),
|
||||
eng.spawn2("float layout", Phase::Layout, float_layout(state.clone())),
|
||||
eng.spawn2(
|
||||
"float titles",
|
||||
|
|
@ -670,6 +676,7 @@ fn create_dummy_output(state: &Rc<State>) {
|
|||
handler: Cell::new(None),
|
||||
connected: Cell::new(true),
|
||||
name,
|
||||
description: Default::default(),
|
||||
drm_dev: None,
|
||||
async_event: Default::default(),
|
||||
damaged: Cell::new(false),
|
||||
|
|
@ -678,6 +685,7 @@ fn create_dummy_output(state: &Rc<State>) {
|
|||
damage_intersect: Default::default(),
|
||||
state: Cell::new(backend_state),
|
||||
head_managers: HeadManagers::new(head_name, head_state),
|
||||
wlr_output_heads: Default::default(),
|
||||
});
|
||||
let schedule = Rc::new(OutputSchedule::new(
|
||||
&state.ring,
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ use {
|
|||
wl_shm::WlShmGlobal,
|
||||
wl_subcompositor::WlSubcompositorGlobal,
|
||||
wl_surface::xwayland_shell_v1::XwaylandShellV1Global,
|
||||
wlr_output_manager::zwlr_output_manager_v1::ZwlrOutputManagerV1Global,
|
||||
workspace_manager::ext_workspace_manager_v1::ExtWorkspaceManagerV1Global,
|
||||
wp_alpha_modifier_v1::WpAlphaModifierV1Global,
|
||||
wp_commit_timing_manager_v1::WpCommitTimingManagerV1Global,
|
||||
|
|
@ -186,6 +187,7 @@ impl Globals {
|
|||
add_singleton!(OrgKdeKwinServerDecorationManagerGlobal);
|
||||
add_singleton!(ZwpPrimarySelectionDeviceManagerV1Global);
|
||||
add_singleton!(ZwlrLayerShellV1Global);
|
||||
add_singleton!(ZwlrOutputManagerV1Global);
|
||||
add_singleton!(ZxdgOutputManagerV1Global);
|
||||
add_singleton!(JayCompositorGlobal);
|
||||
add_singleton!(ZwlrScreencopyManagerV1Global);
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ pub mod wl_shm;
|
|||
pub mod wl_shm_pool;
|
||||
pub mod wl_subcompositor;
|
||||
pub mod wl_surface;
|
||||
pub mod wlr_output_manager;
|
||||
pub mod workspace_manager;
|
||||
pub mod wp_alpha_modifier_v1;
|
||||
pub mod wp_commit_timing_manager_v1;
|
||||
|
|
|
|||
|
|
@ -3,16 +3,13 @@ use {
|
|||
crate::{
|
||||
backend::transaction::{ConnectorTransaction, PreparedConnectorTransaction},
|
||||
client::{Client, ClientError},
|
||||
ifs::{
|
||||
head_management::{
|
||||
Head, HeadCommon, HeadCommonError, HeadMgrState, HeadName, HeadOp,
|
||||
HeadTransactionError,
|
||||
head_management_macros::{HeadExtension, MgrExts, announce_head, bind_extension},
|
||||
jay_head_manager_v1::JayHeadManagerV1,
|
||||
jay_head_transaction_result_v1::JayHeadTransactionResultV1,
|
||||
jay_head_v1::JayHeadV1,
|
||||
},
|
||||
wl_output::PersistentOutputState,
|
||||
ifs::head_management::{
|
||||
Head, HeadCommon, HeadCommonError, HeadMgrState, HeadName, HeadOp,
|
||||
HeadTransactionError,
|
||||
head_management_macros::{HeadExtension, MgrExts, announce_head, bind_extension},
|
||||
jay_head_manager_v1::JayHeadManagerV1,
|
||||
jay_head_transaction_result_v1::JayHeadTransactionResultV1,
|
||||
jay_head_v1::JayHeadV1,
|
||||
},
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
|
|
@ -566,22 +563,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 {
|
|||
node.set_brightness(desired.brightness);
|
||||
} else if let Some(mi) = &desired.monitor_info {
|
||||
let pos = &self.client.state.persistent_output_states;
|
||||
let pos = match pos.get(&mi.output_id) {
|
||||
Some(ps) => ps,
|
||||
_ => {
|
||||
let ps = Rc::new(PersistentOutputState {
|
||||
transform: Default::default(),
|
||||
scale: Default::default(),
|
||||
pos: Default::default(),
|
||||
vrr_mode: Cell::new(&VrrMode::Never),
|
||||
vrr_cursor_hz: Default::default(),
|
||||
tearing_mode: Cell::new(&TearingMode::Never),
|
||||
brightness: Default::default(),
|
||||
});
|
||||
pos.set(mi.output_id.clone(), ps.clone());
|
||||
ps
|
||||
}
|
||||
};
|
||||
let pos = pos.lock().entry(mi.output_id.clone()).or_default().clone();
|
||||
pos.pos.set(desired.position);
|
||||
pos.scale.set(desired.scale);
|
||||
pos.transform.set(desired.transform);
|
||||
|
|
|
|||
|
|
@ -125,6 +125,20 @@ pub struct PersistentOutputState {
|
|||
pub brightness: Cell<Option<f64>>,
|
||||
}
|
||||
|
||||
impl Default for PersistentOutputState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
transform: Default::default(),
|
||||
scale: Default::default(),
|
||||
pos: Default::default(),
|
||||
vrr_mode: Cell::new(&VrrMode::Never),
|
||||
vrr_cursor_hz: Default::default(),
|
||||
tearing_mode: Cell::new(&TearingMode::Never),
|
||||
brightness: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Hash, Debug)]
|
||||
pub struct OutputId {
|
||||
pub connector: Option<String>,
|
||||
|
|
|
|||
47
src/ifs/wlr_output_manager.rs
Normal file
47
src/ifs/wlr_output_manager.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
use {
|
||||
crate::{
|
||||
ifs::wlr_output_manager::zwlr_output_head_v1::WlrOutputHeadIds,
|
||||
state::{OutputData, State},
|
||||
utils::{copyhashmap::CopyHashMap, queue::AsyncQueue},
|
||||
},
|
||||
std::rc::Rc,
|
||||
zwlr_output_manager_v1::{WlrOutputManagerId, WlrOutputManagerIds, ZwlrOutputManagerV1},
|
||||
};
|
||||
|
||||
pub mod zwlr_output_configuration_head;
|
||||
pub mod zwlr_output_configuration_v1;
|
||||
pub mod zwlr_output_head_v1;
|
||||
pub mod zwlr_output_manager_v1;
|
||||
pub mod zwlr_output_mode_v1;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct WlrOutputManagerState {
|
||||
queue: AsyncQueue<Rc<ZwlrOutputManagerV1>>,
|
||||
ids: WlrOutputManagerIds,
|
||||
head_ids: WlrOutputHeadIds,
|
||||
managers: CopyHashMap<WlrOutputManagerId, Rc<ZwlrOutputManagerV1>>,
|
||||
}
|
||||
|
||||
impl WlrOutputManagerState {
|
||||
pub fn clear(&self) {
|
||||
self.managers.clear();
|
||||
self.queue.clear();
|
||||
}
|
||||
|
||||
pub fn announce_head(&self, on: &Rc<OutputData>) {
|
||||
for manager in self.managers.lock().values() {
|
||||
manager.announce_head(on);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn wlr_output_manager_done(state: Rc<State>) {
|
||||
loop {
|
||||
let manager = state.wlr_output_managers.queue.pop().await;
|
||||
if manager.destroyed.get() {
|
||||
continue;
|
||||
}
|
||||
manager.done_scheduled.set(false);
|
||||
manager.send_done();
|
||||
}
|
||||
}
|
||||
146
src/ifs/wlr_output_manager/zwlr_output_configuration_head.rs
Normal file
146
src/ifs/wlr_output_manager/zwlr_output_configuration_head.rs
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
use {
|
||||
crate::{
|
||||
backend::Mode,
|
||||
client::{Client, ClientError},
|
||||
fixed::Fixed,
|
||||
ifs::wlr_output_manager::zwlr_output_head_v1::{
|
||||
ADAPTIVE_SYNC_STATE_DISABLED, ADAPTIVE_SYNC_STATE_ENABLED, WlrOutputHeadId,
|
||||
},
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
scale::Scale,
|
||||
tree::VrrMode,
|
||||
utils::transform_ext::TransformExt,
|
||||
wire::{ZwlrOutputConfigurationHeadV1Id, zwlr_output_configuration_head_v1::*},
|
||||
},
|
||||
jay_config::video::Transform,
|
||||
std::{cell::RefCell, rc::Rc},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub struct ZwlrOutputConfigurationHeadV1 {
|
||||
pub(super) id: ZwlrOutputConfigurationHeadV1Id,
|
||||
pub(super) head_id: WlrOutputHeadId,
|
||||
pub(super) version: Version,
|
||||
pub(super) client: Rc<Client>,
|
||||
pub(super) config: RefCell<OutputConfig>,
|
||||
pub(super) tracker: Tracker<Self>,
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone)]
|
||||
pub struct OutputConfig {
|
||||
pub(super) transform: Option<Transform>,
|
||||
pub(super) scale: Option<Scale>,
|
||||
pub(super) vrr_mode: Option<&'static VrrMode>,
|
||||
pub(super) pos: Option<(i32, i32)>,
|
||||
pub(super) mode: Option<Mode>,
|
||||
}
|
||||
|
||||
impl ZwlrOutputConfigurationHeadV1RequestHandler for ZwlrOutputConfigurationHeadV1 {
|
||||
type Error = ZwlrOutputConfigurationHeadV1Error;
|
||||
|
||||
fn set_mode(&self, req: SetMode, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
let config = &mut *self.config.borrow_mut();
|
||||
if config.mode.is_some() {
|
||||
return Err(ZwlrOutputConfigurationHeadV1Error::AlreadySet);
|
||||
}
|
||||
let mode = self.client.lookup(req.mode)?;
|
||||
if self.head_id != mode.head_id {
|
||||
return Err(ZwlrOutputConfigurationHeadV1Error::InvalidMode);
|
||||
}
|
||||
config.mode = Some(mode.mode);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_custom_mode(&self, req: SetCustomMode, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
let config = &mut *self.config.borrow_mut();
|
||||
if config.mode.is_some() {
|
||||
return Err(ZwlrOutputConfigurationHeadV1Error::AlreadySet);
|
||||
}
|
||||
config.mode = Some(Mode {
|
||||
width: req.width,
|
||||
height: req.height,
|
||||
refresh_rate_millihz: req.refresh as u32,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_position(&self, req: SetPosition, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
let config = &mut *self.config.borrow_mut();
|
||||
if config.pos.is_some() {
|
||||
return Err(ZwlrOutputConfigurationHeadV1Error::AlreadySet);
|
||||
}
|
||||
config.pos = Some((req.x, req.y));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_transform(&self, req: SetTransform, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
let config = &mut *self.config.borrow_mut();
|
||||
if config.transform.is_some() {
|
||||
return Err(ZwlrOutputConfigurationHeadV1Error::AlreadySet);
|
||||
}
|
||||
let Some(transform) = Transform::from_wl(req.transform) else {
|
||||
return Err(ZwlrOutputConfigurationHeadV1Error::InvalidTransform(
|
||||
req.transform,
|
||||
));
|
||||
};
|
||||
config.transform = Some(transform);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_scale(&self, req: SetScale, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
let config = &mut *self.config.borrow_mut();
|
||||
if config.scale.is_some() {
|
||||
return Err(ZwlrOutputConfigurationHeadV1Error::AlreadySet);
|
||||
}
|
||||
if req.scale <= 0 {
|
||||
return Err(ZwlrOutputConfigurationHeadV1Error::InvalidScale(req.scale));
|
||||
}
|
||||
config.scale = Some(Scale::from_f64(req.scale.to_f64()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_adaptive_sync(&self, req: SetAdaptiveSync, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
let config = &mut *self.config.borrow_mut();
|
||||
if config.vrr_mode.is_some() {
|
||||
return Err(ZwlrOutputConfigurationHeadV1Error::AlreadySet);
|
||||
}
|
||||
let state = match req.state {
|
||||
ADAPTIVE_SYNC_STATE_DISABLED => VrrMode::NEVER,
|
||||
ADAPTIVE_SYNC_STATE_ENABLED => VrrMode::ALWAYS,
|
||||
_ => {
|
||||
return Err(
|
||||
ZwlrOutputConfigurationHeadV1Error::InvalidAdaptiveSyncState(req.state),
|
||||
);
|
||||
}
|
||||
};
|
||||
config.vrr_mode = Some(state);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
object_base! {
|
||||
self = ZwlrOutputConfigurationHeadV1;
|
||||
version = self.version;
|
||||
}
|
||||
|
||||
impl Object for ZwlrOutputConfigurationHeadV1 {}
|
||||
|
||||
simple_add_obj!(ZwlrOutputConfigurationHeadV1);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ZwlrOutputConfigurationHeadV1Error {
|
||||
#[error(transparent)]
|
||||
ClientError(Box<ClientError>),
|
||||
#[error("Property has already been set")]
|
||||
AlreadySet,
|
||||
#[error("Mode doesn't belong to head")]
|
||||
InvalidMode,
|
||||
#[error("Unknown transform {0}")]
|
||||
InvalidTransform(i32),
|
||||
#[error("Invalid scale {0}")]
|
||||
InvalidScale(Fixed),
|
||||
#[error("Invalid adaptive sync state {0}")]
|
||||
InvalidAdaptiveSyncState(u32),
|
||||
}
|
||||
efrom!(ZwlrOutputConfigurationHeadV1Error, ClientError);
|
||||
256
src/ifs/wlr_output_manager/zwlr_output_configuration_v1.rs
Normal file
256
src/ifs/wlr_output_manager/zwlr_output_configuration_v1.rs
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
use {
|
||||
crate::{
|
||||
backend::{
|
||||
ConnectorId,
|
||||
transaction::{
|
||||
BackendConnectorTransactionError, ConnectorTransaction,
|
||||
PreparedConnectorTransaction,
|
||||
},
|
||||
},
|
||||
client::{Client, ClientError},
|
||||
ifs::wlr_output_manager::{
|
||||
zwlr_output_configuration_head::ZwlrOutputConfigurationHeadV1,
|
||||
zwlr_output_manager_v1::ZwlrOutputManagerV1,
|
||||
},
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
utils::{copyhashmap::CopyHashMap, errorfmt::ErrorFmt, hash_map_ext::HashMapExt},
|
||||
wire::{ZwlrOutputConfigurationV1Id, zwlr_output_configuration_v1::*},
|
||||
},
|
||||
std::{cell::Cell, rc::Rc},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub struct ZwlrOutputConfigurationV1 {
|
||||
pub(super) id: ZwlrOutputConfigurationV1Id,
|
||||
pub(super) version: Version,
|
||||
pub(super) client: Rc<Client>,
|
||||
pub(super) tracker: Tracker<Self>,
|
||||
pub(super) serial: u64,
|
||||
pub(super) manager: Rc<ZwlrOutputManagerV1>,
|
||||
pub(super) used: Cell<bool>,
|
||||
pub(super) enabled_outputs: CopyHashMap<ConnectorId, Rc<ZwlrOutputConfigurationHeadV1>>,
|
||||
pub(super) configured_outputs: CopyHashMap<ConnectorId, ()>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum ConfigError {
|
||||
#[error("Serial is out of date")]
|
||||
OutOfDate,
|
||||
#[error("Unconfigured output {0}")]
|
||||
UnconfiguredOutput(Rc<String>),
|
||||
#[error("Could not add output to transaction")]
|
||||
AddToTransaction(#[source] BackendConnectorTransactionError),
|
||||
#[error("Could not prepare transaction")]
|
||||
PrepareTransaction(#[source] BackendConnectorTransactionError),
|
||||
#[error("Could not apply transaction")]
|
||||
ApplyTransaction(#[source] BackendConnectorTransactionError),
|
||||
}
|
||||
|
||||
impl ZwlrOutputConfigurationV1 {
|
||||
pub fn send_succeeded(&self) {
|
||||
self.client.event(Succeeded { self_id: self.id });
|
||||
}
|
||||
|
||||
pub fn send_failed(&self) {
|
||||
self.client.event(Failed { self_id: self.id });
|
||||
}
|
||||
|
||||
pub fn send_cancelled(&self) {
|
||||
self.client.event(Cancelled { self_id: self.id });
|
||||
}
|
||||
|
||||
fn assert_unused(&self) -> Result<(), ZwlrOutputConfigurationV1Error> {
|
||||
if self.used.get() {
|
||||
return Err(ZwlrOutputConfigurationV1Error::AlreadyUsed);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prepare_transaction(&self) -> Result<PreparedConnectorTransaction, ConfigError> {
|
||||
if self.serial < self.manager.serial.get() {
|
||||
return Err(ConfigError::OutOfDate);
|
||||
}
|
||||
let mut tran = ConnectorTransaction::new(&self.client.state);
|
||||
for output in self.client.state.outputs.lock().values() {
|
||||
let mut state = output.connector.state.get();
|
||||
match self.enabled_outputs.get(&output.connector.id) {
|
||||
None => {
|
||||
if self.configured_outputs.not_contains(&output.connector.id) {
|
||||
return Err(ConfigError::UnconfiguredOutput(
|
||||
output.connector.name.clone(),
|
||||
));
|
||||
}
|
||||
state.enabled = false;
|
||||
}
|
||||
Some(config) => {
|
||||
state.enabled = true;
|
||||
let config = *config.config.borrow();
|
||||
if let Some(mode) = config.mode {
|
||||
state.mode = mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
tran.add(&output.connector.connector, state)
|
||||
.map_err(ConfigError::AddToTransaction)?;
|
||||
}
|
||||
tran.prepare().map_err(ConfigError::PrepareTransaction)
|
||||
}
|
||||
|
||||
fn apply_transaction(&self) -> Result<(), ConfigError> {
|
||||
self.prepare_transaction()?
|
||||
.apply()
|
||||
.map_err(ConfigError::ApplyTransaction)?
|
||||
.commit();
|
||||
for output in self.client.state.outputs.lock().values() {
|
||||
let Some(config) = self.enabled_outputs.get(&output.connector.id) else {
|
||||
continue;
|
||||
};
|
||||
let config = *config.config.borrow();
|
||||
if let Some(node) = &output.node {
|
||||
if let Some(v) = config.transform {
|
||||
node.update_transform(v);
|
||||
}
|
||||
if let Some(v) = config.scale {
|
||||
node.set_preferred_scale(v);
|
||||
}
|
||||
if let Some(v) = config.vrr_mode {
|
||||
node.set_vrr_mode(v);
|
||||
}
|
||||
if let Some(v) = config.pos {
|
||||
node.set_position(v.0, v.1);
|
||||
}
|
||||
} else {
|
||||
let mi = &output.monitor_info;
|
||||
let pos = &self.client.state.persistent_output_states;
|
||||
let pos = pos.lock().entry(mi.output_id.clone()).or_default().clone();
|
||||
if let Some(v) = config.transform {
|
||||
pos.transform.set(v);
|
||||
}
|
||||
if let Some(v) = config.scale {
|
||||
pos.scale.set(v);
|
||||
}
|
||||
if let Some(v) = config.vrr_mode {
|
||||
pos.vrr_mode.set(v);
|
||||
}
|
||||
if let Some(v) = config.pos {
|
||||
pos.pos.set(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ZwlrOutputConfigurationV1RequestHandler for ZwlrOutputConfigurationV1 {
|
||||
type Error = ZwlrOutputConfigurationV1Error;
|
||||
|
||||
fn enable_head(&self, req: EnableHead, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.assert_unused()?;
|
||||
let head = self.client.lookup(req.head)?;
|
||||
if self.configured_outputs.set(head.connector_id, ()).is_some() {
|
||||
return Err(ZwlrOutputConfigurationV1Error::AlreadyConfiguredHead(
|
||||
head.output.connector.name.clone(),
|
||||
));
|
||||
}
|
||||
let configuration_head = Rc::new(ZwlrOutputConfigurationHeadV1 {
|
||||
id: req.id,
|
||||
head_id: head.head_id,
|
||||
version: self.version,
|
||||
client: self.client.clone(),
|
||||
config: Default::default(),
|
||||
tracker: Default::default(),
|
||||
});
|
||||
track!(self.client, configuration_head);
|
||||
self.client.add_client_obj(&configuration_head)?;
|
||||
self.enabled_outputs
|
||||
.set(head.connector_id, configuration_head);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn disable_head(&self, req: DisableHead, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.assert_unused()?;
|
||||
let head = self.client.lookup(req.head)?;
|
||||
if self.configured_outputs.set(head.connector_id, ()).is_some() {
|
||||
return Err(ZwlrOutputConfigurationV1Error::AlreadyConfiguredHead(
|
||||
head.output.connector.name.clone(),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply(&self, _req: Apply, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.assert_unused()?;
|
||||
self.used.set(true);
|
||||
let Err(e) = self.apply_transaction() else {
|
||||
self.send_succeeded();
|
||||
return Ok(());
|
||||
};
|
||||
log::error!("Could not apply output configuration: {}", ErrorFmt(&e));
|
||||
match e {
|
||||
ConfigError::UnconfiguredOutput(o) => {
|
||||
return Err(ZwlrOutputConfigurationV1Error::UnconfiguredHead(o));
|
||||
}
|
||||
ConfigError::OutOfDate => {
|
||||
self.send_cancelled();
|
||||
return Ok(());
|
||||
}
|
||||
ConfigError::AddToTransaction(_)
|
||||
| ConfigError::PrepareTransaction(_)
|
||||
| ConfigError::ApplyTransaction(_) => {}
|
||||
}
|
||||
self.send_failed();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test(&self, _req: Test, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.assert_unused()?;
|
||||
self.used.set(true);
|
||||
let Err(e) = self.prepare_transaction() else {
|
||||
self.send_succeeded();
|
||||
return Ok(());
|
||||
};
|
||||
log::error!("Could not test output configuration: {}", ErrorFmt(&e));
|
||||
match e {
|
||||
ConfigError::UnconfiguredOutput(o) => {
|
||||
return Err(ZwlrOutputConfigurationV1Error::UnconfiguredHead(o));
|
||||
}
|
||||
ConfigError::OutOfDate
|
||||
| ConfigError::AddToTransaction(_)
|
||||
| ConfigError::PrepareTransaction(_)
|
||||
| ConfigError::ApplyTransaction(_) => {}
|
||||
}
|
||||
self.send_failed();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.client.remove_obj(self)?;
|
||||
for head in self.enabled_outputs.lock().drain_values() {
|
||||
self.client.remove_obj(&*head)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
object_base! {
|
||||
self = ZwlrOutputConfigurationV1;
|
||||
version = self.version;
|
||||
}
|
||||
|
||||
impl Object for ZwlrOutputConfigurationV1 {}
|
||||
|
||||
simple_add_obj!(ZwlrOutputConfigurationV1);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ZwlrOutputConfigurationV1Error {
|
||||
#[error(transparent)]
|
||||
ClientError(Box<ClientError>),
|
||||
#[error("Head {0} has alread been configured")]
|
||||
AlreadyConfiguredHead(Rc<String>),
|
||||
#[error("Head {0} has not been configured")]
|
||||
UnconfiguredHead(Rc<String>),
|
||||
#[error("Configuration has already been tested or applied")]
|
||||
AlreadyUsed,
|
||||
}
|
||||
efrom!(ZwlrOutputConfigurationV1Error, ClientError);
|
||||
250
src/ifs/wlr_output_manager/zwlr_output_head_v1.rs
Normal file
250
src/ifs/wlr_output_manager/zwlr_output_head_v1.rs
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
use {
|
||||
crate::{
|
||||
backend::{self, ConnectorId},
|
||||
client::{Client, ClientError},
|
||||
fixed::Fixed,
|
||||
ifs::wlr_output_manager::{
|
||||
zwlr_output_manager_v1::{WlrOutputManagerId, ZwlrOutputManagerV1},
|
||||
zwlr_output_mode_v1::ZwlrOutputModeV1,
|
||||
},
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
scale,
|
||||
state::OutputData,
|
||||
tree::VrrMode,
|
||||
utils::transform_ext::TransformExt,
|
||||
wire::{ZwlrOutputHeadV1Id, zwlr_output_head_v1::*},
|
||||
},
|
||||
ahash::AHashMap,
|
||||
jay_config::video,
|
||||
std::rc::Rc,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub const MAKE_SINCE: Version = Version(2);
|
||||
pub const MODEL_SINCE: Version = Version(2);
|
||||
pub const SERIAL_NUMBER_SINCE: Version = Version(2);
|
||||
#[expect(dead_code)]
|
||||
pub const RELEASE_SINCE: Version = Version(3);
|
||||
pub const ADAPTIVE_SYNC_SINCE: Version = Version(4);
|
||||
|
||||
pub const HEAD_DISABLED: i32 = 0;
|
||||
pub const HEAD_ENABLED: i32 = 1;
|
||||
|
||||
pub const ADAPTIVE_SYNC_STATE_DISABLED: u32 = 0;
|
||||
pub const ADAPTIVE_SYNC_STATE_ENABLED: u32 = 1;
|
||||
|
||||
linear_ids!(WlrOutputHeadIds, WlrOutputHeadId, u64);
|
||||
|
||||
pub struct ZwlrOutputHeadV1 {
|
||||
pub(super) id: ZwlrOutputHeadV1Id,
|
||||
pub(super) version: Version,
|
||||
pub(super) client: Rc<Client>,
|
||||
pub(super) tracker: Tracker<Self>,
|
||||
pub(super) output: Rc<OutputData>,
|
||||
pub(super) manager_id: WlrOutputManagerId,
|
||||
pub(super) manager: Rc<ZwlrOutputManagerV1>,
|
||||
pub(super) head_id: WlrOutputHeadId,
|
||||
pub(super) connector_id: ConnectorId,
|
||||
pub(super) modes: AHashMap<backend::Mode, Rc<ZwlrOutputModeV1>>,
|
||||
}
|
||||
|
||||
impl ZwlrOutputHeadV1 {
|
||||
fn detach(&self) {
|
||||
self.output
|
||||
.connector
|
||||
.wlr_output_heads
|
||||
.remove(&self.manager_id);
|
||||
}
|
||||
}
|
||||
|
||||
impl ZwlrOutputHeadV1 {
|
||||
pub fn send_name(&self, name: &str) {
|
||||
self.client.event(Name {
|
||||
self_id: self.id,
|
||||
name,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_description(&self, description: &str) {
|
||||
self.client.event(Description {
|
||||
self_id: self.id,
|
||||
description,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_physical_size(&self, width: i32, height: i32) {
|
||||
self.client.event(PhysicalSize {
|
||||
self_id: self.id,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_mode(&self, mode: &ZwlrOutputModeV1) {
|
||||
self.client.event(Mode {
|
||||
self_id: self.id,
|
||||
mode: mode.id,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_enabled(&self, enabled: bool) {
|
||||
let enabled = if enabled { HEAD_ENABLED } else { HEAD_DISABLED };
|
||||
self.client.event(Enabled {
|
||||
self_id: self.id,
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_current_mode(&self, mode: &ZwlrOutputModeV1) {
|
||||
self.client.event(CurrentMode {
|
||||
self_id: self.id,
|
||||
mode: mode.id,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_position(&self, x: i32, y: i32) {
|
||||
self.client.event(Position {
|
||||
self_id: self.id,
|
||||
x,
|
||||
y,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_transform(&self, transform: video::Transform) {
|
||||
self.client.event(Transform {
|
||||
self_id: self.id,
|
||||
transform: transform.to_wl(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_scale(&self, scale: scale::Scale) {
|
||||
let scale = Fixed::from_f64(scale.to_f64());
|
||||
self.client.event(Scale {
|
||||
self_id: self.id,
|
||||
scale,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_finished(&self) {
|
||||
self.client.event(Finished { self_id: self.id })
|
||||
}
|
||||
|
||||
pub fn send_make(&self, make: &str) {
|
||||
self.client.event(Make {
|
||||
self_id: self.id,
|
||||
make,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_model(&self, model: &str) {
|
||||
self.client.event(Model {
|
||||
self_id: self.id,
|
||||
model,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_serial_number(&self, serial_number: &str) {
|
||||
self.client.event(SerialNumber {
|
||||
self_id: self.id,
|
||||
serial_number,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_adaptive_sync(&self, mode: &VrrMode) {
|
||||
let state = if *mode == VrrMode::Always {
|
||||
ADAPTIVE_SYNC_STATE_ENABLED
|
||||
} else {
|
||||
ADAPTIVE_SYNC_STATE_DISABLED
|
||||
};
|
||||
self.client.event(AdaptiveSync {
|
||||
self_id: self.id,
|
||||
state,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn announce_modes(&self, modes: &[Rc<ZwlrOutputModeV1>]) {
|
||||
for mode in modes {
|
||||
self.send_mode(mode);
|
||||
mode.send();
|
||||
if mode.initial_current {
|
||||
self.send_current_mode(mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hande_transform_change(&self, transform: video::Transform) {
|
||||
self.send_transform(transform);
|
||||
self.manager.schedule_done();
|
||||
}
|
||||
|
||||
pub fn handle_mode_change(&self, new: backend::Mode) {
|
||||
let Some(mode) = self.modes.get(&new) else {
|
||||
return;
|
||||
};
|
||||
if mode.destroyed.get() {
|
||||
return;
|
||||
}
|
||||
self.send_current_mode(mode);
|
||||
self.manager.schedule_done();
|
||||
}
|
||||
|
||||
pub fn handle_position_change(&self, x: i32, y: i32) {
|
||||
self.send_position(x, y);
|
||||
self.manager.schedule_done();
|
||||
}
|
||||
|
||||
pub fn handle_vrr_mode_change(&self, mode: &VrrMode) {
|
||||
if self.version < ADAPTIVE_SYNC_SINCE {
|
||||
return;
|
||||
}
|
||||
self.send_adaptive_sync(mode);
|
||||
self.manager.schedule_done();
|
||||
}
|
||||
|
||||
pub fn handle_new_scale(&self, scale: scale::Scale) {
|
||||
self.send_scale(scale);
|
||||
self.manager.schedule_done();
|
||||
}
|
||||
|
||||
pub fn handle_disconnected(&self) {
|
||||
self.send_finished();
|
||||
for mode in self.modes.values() {
|
||||
if !mode.destroyed.get() {
|
||||
mode.send_finished();
|
||||
}
|
||||
}
|
||||
self.manager.schedule_done();
|
||||
}
|
||||
}
|
||||
|
||||
impl ZwlrOutputHeadV1RequestHandler for ZwlrOutputHeadV1 {
|
||||
type Error = ZwlrOutputHeadV1Error;
|
||||
|
||||
fn release(&self, _req: Release, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.send_finished();
|
||||
self.detach();
|
||||
self.client.remove_obj(self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
object_base! {
|
||||
self = ZwlrOutputHeadV1;
|
||||
version = self.version;
|
||||
}
|
||||
|
||||
impl Object for ZwlrOutputHeadV1 {
|
||||
fn break_loops(&self) {
|
||||
self.detach();
|
||||
}
|
||||
}
|
||||
|
||||
dedicated_add_obj!(ZwlrOutputHeadV1, ZwlrOutputHeadV1Id, zwlr_output_heads);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ZwlrOutputHeadV1Error {
|
||||
#[error(transparent)]
|
||||
ClientError(Box<ClientError>),
|
||||
}
|
||||
efrom!(ZwlrOutputHeadV1Error, ClientError);
|
||||
289
src/ifs/wlr_output_manager/zwlr_output_manager_v1.rs
Normal file
289
src/ifs/wlr_output_manager/zwlr_output_manager_v1.rs
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
use {
|
||||
crate::{
|
||||
client::{CAP_HEAD_MANAGER, Client, ClientCaps, ClientError},
|
||||
globals::{Global, GlobalName},
|
||||
ifs::wlr_output_manager::{
|
||||
zwlr_output_configuration_v1::ZwlrOutputConfigurationV1,
|
||||
zwlr_output_head_v1::{
|
||||
ADAPTIVE_SYNC_SINCE, MAKE_SINCE, MODEL_SINCE, SERIAL_NUMBER_SINCE, ZwlrOutputHeadV1,
|
||||
},
|
||||
zwlr_output_mode_v1::ZwlrOutputModeV1,
|
||||
},
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
state::OutputData,
|
||||
utils::numcell::NumCell,
|
||||
wire::{ZwlrOutputManagerV1Id, zwlr_output_manager_v1::*},
|
||||
},
|
||||
ahash::AHashMap,
|
||||
isnt::std_1::string::IsntStringExt,
|
||||
std::{cell::Cell, rc::Rc},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
linear_ids!(WlrOutputManagerIds, WlrOutputManagerId, u64);
|
||||
|
||||
pub struct ZwlrOutputManagerV1Global {
|
||||
name: GlobalName,
|
||||
}
|
||||
|
||||
pub struct ZwlrOutputManagerV1 {
|
||||
pub(super) id: ZwlrOutputManagerV1Id,
|
||||
pub(super) manager_id: WlrOutputManagerId,
|
||||
pub(super) client: Rc<Client>,
|
||||
pub(super) version: Version,
|
||||
pub(super) tracker: Tracker<Self>,
|
||||
pub(super) done_scheduled: Cell<bool>,
|
||||
pub(super) serial: NumCell<u64>,
|
||||
pub(super) destroyed: Cell<bool>,
|
||||
}
|
||||
|
||||
impl ZwlrOutputManagerV1 {
|
||||
fn detach(&self) {
|
||||
self.client
|
||||
.state
|
||||
.wlr_output_managers
|
||||
.managers
|
||||
.remove(&self.manager_id);
|
||||
}
|
||||
}
|
||||
|
||||
impl ZwlrOutputManagerV1Global {
|
||||
pub fn new(name: GlobalName) -> Self {
|
||||
Self { name }
|
||||
}
|
||||
|
||||
fn bind_(
|
||||
self: Rc<Self>,
|
||||
id: ZwlrOutputManagerV1Id,
|
||||
client: &Rc<Client>,
|
||||
version: Version,
|
||||
) -> Result<(), ZwlrOutputManagerV1Error> {
|
||||
let obj = Rc::new(ZwlrOutputManagerV1 {
|
||||
id,
|
||||
manager_id: client.state.wlr_output_managers.ids.next(),
|
||||
client: client.clone(),
|
||||
tracker: Default::default(),
|
||||
version,
|
||||
done_scheduled: Cell::new(false),
|
||||
serial: Default::default(),
|
||||
destroyed: Cell::new(false),
|
||||
});
|
||||
track!(client, obj);
|
||||
client.add_client_obj(&obj)?;
|
||||
client
|
||||
.state
|
||||
.wlr_output_managers
|
||||
.managers
|
||||
.set(obj.manager_id, obj.clone());
|
||||
for output in client.state.outputs.lock().values() {
|
||||
obj.announce_head(output);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ZwlrOutputManagerV1RequestHandler for ZwlrOutputManagerV1 {
|
||||
type Error = ZwlrOutputManagerV1Error;
|
||||
|
||||
fn create_configuration(
|
||||
&self,
|
||||
req: CreateConfiguration,
|
||||
slf: &Rc<Self>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let last_serial = self.serial.get();
|
||||
let mut serial = (last_serial >> u32::BITS << u32::BITS) | (req.serial as u64);
|
||||
if serial > last_serial {
|
||||
serial = serial.saturating_sub(1 << u32::BITS);
|
||||
}
|
||||
let configuration = Rc::new(ZwlrOutputConfigurationV1 {
|
||||
id: req.id,
|
||||
client: self.client.clone(),
|
||||
version: self.version,
|
||||
tracker: Default::default(),
|
||||
serial,
|
||||
manager: slf.clone(),
|
||||
used: Cell::new(false),
|
||||
enabled_outputs: Default::default(),
|
||||
configured_outputs: Default::default(),
|
||||
});
|
||||
track!(self.client, configuration);
|
||||
self.client.add_client_obj(&configuration)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop(&self, _req: Stop, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.destroyed.set(true);
|
||||
self.detach();
|
||||
self.send_finished();
|
||||
self.client.remove_obj(self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ZwlrOutputManagerV1 {
|
||||
pub fn announce_head(self: &Rc<Self>, output: &Rc<OutputData>) {
|
||||
let id = match self.client.new_id() {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
self.client.error(e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let mi = &output.monitor_info;
|
||||
let state = output.connector.state.get();
|
||||
let head_id = self.client.state.wlr_output_managers.head_ids.next();
|
||||
let mut modes_list = vec![];
|
||||
let mut modes = AHashMap::new();
|
||||
let mut have_current = false;
|
||||
for (idx, mode) in mi.modes.iter().enumerate() {
|
||||
if modes.contains_key(mode) {
|
||||
continue;
|
||||
}
|
||||
let current = !have_current && *mode == state.mode;
|
||||
if current {
|
||||
have_current = true;
|
||||
}
|
||||
let id = match self.client.new_id() {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
self.client.error(e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let output_mode = Rc::new(ZwlrOutputModeV1 {
|
||||
id,
|
||||
head_id,
|
||||
client: self.client.clone(),
|
||||
tracker: Default::default(),
|
||||
version: self.version,
|
||||
mode: *mode,
|
||||
preferred: idx == 0,
|
||||
initial_current: current,
|
||||
destroyed: Cell::new(false),
|
||||
});
|
||||
track!(self.client, output_mode);
|
||||
self.client.add_server_obj(&output_mode);
|
||||
modes_list.push(output_mode.clone());
|
||||
modes.insert(*mode, output_mode);
|
||||
}
|
||||
let head = Rc::new(ZwlrOutputHeadV1 {
|
||||
id,
|
||||
client: self.client.clone(),
|
||||
tracker: Default::default(),
|
||||
version: self.version,
|
||||
manager_id: self.manager_id,
|
||||
manager: self.clone(),
|
||||
head_id,
|
||||
connector_id: output.connector.id,
|
||||
modes,
|
||||
output: output.clone(),
|
||||
});
|
||||
track!(self.client, head);
|
||||
self.client.add_server_obj(&head);
|
||||
output
|
||||
.connector
|
||||
.wlr_output_heads
|
||||
.set(self.manager_id, head.clone());
|
||||
self.send_head(&head);
|
||||
head.send_name(&output.connector.name);
|
||||
let description = &*output.connector.description.borrow();
|
||||
if description.is_not_empty() {
|
||||
head.send_description(description);
|
||||
}
|
||||
head.send_enabled(!mi.non_desktop_effective);
|
||||
head.announce_modes(&modes_list);
|
||||
head.send_physical_size(mi.width_mm, mi.height_mm);
|
||||
if mi.output_id.manufacturer.is_not_empty() && head.version >= MAKE_SINCE {
|
||||
head.send_make(&mi.output_id.manufacturer);
|
||||
}
|
||||
if mi.output_id.model.is_not_empty() && head.version >= MODEL_SINCE {
|
||||
head.send_model(&mi.output_id.model);
|
||||
}
|
||||
if mi.output_id.serial_number.is_not_empty() && head.version >= SERIAL_NUMBER_SINCE {
|
||||
head.send_serial_number(&mi.output_id.serial_number);
|
||||
}
|
||||
if let Some(node) = &output.node {
|
||||
let p = &node.global.persistent;
|
||||
head.send_scale(p.scale.get());
|
||||
head.send_position(p.pos.get().0, p.pos.get().1);
|
||||
head.send_transform(p.transform.get());
|
||||
if head.version >= ADAPTIVE_SYNC_SINCE {
|
||||
head.send_adaptive_sync(p.vrr_mode.get());
|
||||
}
|
||||
}
|
||||
self.schedule_done();
|
||||
}
|
||||
|
||||
pub fn send_head(&self, head: &ZwlrOutputHeadV1) {
|
||||
self.client.event(Head {
|
||||
self_id: self.id,
|
||||
head: head.id,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_done(&self) {
|
||||
self.client.event(Done {
|
||||
self_id: self.id,
|
||||
serial: self.serial.get() as u32,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_finished(&self) {
|
||||
self.client.event(Finished { self_id: self.id });
|
||||
}
|
||||
|
||||
pub(super) fn schedule_done(self: &Rc<Self>) {
|
||||
if self.done_scheduled.replace(true) {
|
||||
return;
|
||||
}
|
||||
self.serial.fetch_add(1);
|
||||
self.client
|
||||
.state
|
||||
.wlr_output_managers
|
||||
.queue
|
||||
.push(self.clone());
|
||||
}
|
||||
}
|
||||
|
||||
global_base!(
|
||||
ZwlrOutputManagerV1Global,
|
||||
ZwlrOutputManagerV1,
|
||||
ZwlrOutputManagerV1Error
|
||||
);
|
||||
|
||||
impl Global for ZwlrOutputManagerV1Global {
|
||||
fn singleton(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn version(&self) -> u32 {
|
||||
4
|
||||
}
|
||||
|
||||
fn required_caps(&self) -> ClientCaps {
|
||||
CAP_HEAD_MANAGER
|
||||
}
|
||||
}
|
||||
|
||||
simple_add_global!(ZwlrOutputManagerV1Global);
|
||||
|
||||
object_base! {
|
||||
self = ZwlrOutputManagerV1;
|
||||
version = self.version;
|
||||
}
|
||||
|
||||
simple_add_obj!(ZwlrOutputManagerV1);
|
||||
|
||||
impl Object for ZwlrOutputManagerV1 {
|
||||
fn break_loops(&self) {
|
||||
self.detach();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ZwlrOutputManagerV1Error {
|
||||
#[error(transparent)]
|
||||
ClientError(Box<ClientError>),
|
||||
}
|
||||
efrom!(ZwlrOutputManagerV1Error, ClientError);
|
||||
83
src/ifs/wlr_output_manager/zwlr_output_mode_v1.rs
Normal file
83
src/ifs/wlr_output_manager/zwlr_output_mode_v1.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
use {
|
||||
crate::{
|
||||
backend::Mode,
|
||||
client::{Client, ClientError},
|
||||
ifs::wlr_output_manager::zwlr_output_head_v1::WlrOutputHeadId,
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
wire::{ZwlrOutputModeV1Id, zwlr_output_mode_v1::*},
|
||||
},
|
||||
std::{cell::Cell, rc::Rc},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub struct ZwlrOutputModeV1 {
|
||||
pub(super) id: ZwlrOutputModeV1Id,
|
||||
pub(super) head_id: WlrOutputHeadId,
|
||||
pub(super) version: Version,
|
||||
pub(super) client: Rc<Client>,
|
||||
pub(super) tracker: Tracker<Self>,
|
||||
pub(super) mode: Mode,
|
||||
pub(super) preferred: bool,
|
||||
pub(super) initial_current: bool,
|
||||
pub(super) destroyed: Cell<bool>,
|
||||
}
|
||||
|
||||
impl ZwlrOutputModeV1 {
|
||||
pub fn send(&self) {
|
||||
self.send_size(self.mode.width, self.mode.height);
|
||||
self.send_refresh(self.mode.refresh_rate_millihz as _);
|
||||
if self.preferred {
|
||||
self.send_preferred();
|
||||
}
|
||||
}
|
||||
|
||||
fn send_size(&self, width: i32, height: i32) {
|
||||
self.client.event(Size {
|
||||
self_id: self.id,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
}
|
||||
|
||||
fn send_refresh(&self, refresh: i32) {
|
||||
self.client.event(Refresh {
|
||||
self_id: self.id,
|
||||
refresh,
|
||||
});
|
||||
}
|
||||
|
||||
fn send_preferred(&self) {
|
||||
self.client.event(Preferred { self_id: self.id });
|
||||
}
|
||||
|
||||
pub fn send_finished(&self) {
|
||||
self.client.event(Finished { self_id: self.id })
|
||||
}
|
||||
}
|
||||
|
||||
impl ZwlrOutputModeV1RequestHandler for ZwlrOutputModeV1 {
|
||||
type Error = ZwlrOutputModeV1Error;
|
||||
|
||||
fn release(&self, _req: Release, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.destroyed.set(true);
|
||||
self.client.remove_obj(self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
object_base! {
|
||||
self = ZwlrOutputModeV1;
|
||||
version = self.version;
|
||||
}
|
||||
|
||||
impl Object for ZwlrOutputModeV1 {}
|
||||
|
||||
dedicated_add_obj!(ZwlrOutputModeV1, ZwlrOutputModeV1Id, zwlr_output_modes);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ZwlrOutputModeV1Error {
|
||||
#[error(transparent)]
|
||||
ClientError(Box<ClientError>),
|
||||
}
|
||||
efrom!(ZwlrOutputModeV1Error, ClientError);
|
||||
|
|
@ -11,7 +11,6 @@ use {
|
|||
};
|
||||
|
||||
pub const NAME_SINCE: Version = Version(2);
|
||||
#[expect(dead_code)]
|
||||
pub const DESCRIPTION_SINCE: Version = Version(2);
|
||||
pub const NO_DONE_SINCE: Version = Version(3);
|
||||
|
||||
|
|
@ -53,7 +52,6 @@ impl ZxdgOutputV1 {
|
|||
});
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
pub fn send_description(&self, description: &str) {
|
||||
self.client.event(Description {
|
||||
self_id: self.id,
|
||||
|
|
@ -71,6 +69,9 @@ impl ZxdgOutputV1 {
|
|||
if self.version >= NAME_SINCE {
|
||||
self.send_name(&global.connector.name);
|
||||
}
|
||||
if self.version >= DESCRIPTION_SINCE {
|
||||
self.send_description(&global.connector.description.borrow());
|
||||
}
|
||||
if self.version >= NO_DONE_SINCE {
|
||||
if self.output.version >= SEND_DONE_SINCE {
|
||||
self.output.send_done();
|
||||
|
|
|
|||
11
src/state.rs
11
src/state.rs
|
|
@ -67,6 +67,10 @@ use {
|
|||
zwp_idle_inhibitor_v1::{IdleInhibitorId, IdleInhibitorIds, ZwpIdleInhibitorV1},
|
||||
zwp_input_popup_surface_v2::ZwpInputPopupSurfaceV2,
|
||||
},
|
||||
wlr_output_manager::{
|
||||
WlrOutputManagerState, zwlr_output_head_v1::ZwlrOutputHeadV1,
|
||||
zwlr_output_manager_v1::WlrOutputManagerId,
|
||||
},
|
||||
workspace_manager::WorkspaceManagerState,
|
||||
wp_drm_lease_connector_v1::WpDrmLeaseConnectorV1,
|
||||
wp_drm_lease_device_v1::WpDrmLeaseDeviceV1Global,
|
||||
|
|
@ -185,6 +189,7 @@ pub struct State {
|
|||
pub logger: Option<Arc<Logger>>,
|
||||
pub connectors: CopyHashMap<ConnectorId, Rc<ConnectorData>>,
|
||||
pub outputs: CopyHashMap<ConnectorId, Rc<OutputData>>,
|
||||
pub wlr_output_managers: WlrOutputManagerState,
|
||||
pub drm_devs: CopyHashMap<DrmDeviceId, Rc<DrmDevData>>,
|
||||
pub status: CloneCell<Rc<String>>,
|
||||
pub idle: IdleState,
|
||||
|
|
@ -382,6 +387,7 @@ pub struct ConnectorData {
|
|||
pub handler: Cell<Option<SpawnedFuture<()>>>,
|
||||
pub connected: Cell<bool>,
|
||||
pub name: Rc<String>,
|
||||
pub description: RefCell<String>,
|
||||
pub drm_dev: Option<Rc<DrmDevData>>,
|
||||
pub async_event: Rc<AsyncEvent>,
|
||||
pub damaged: Cell<bool>,
|
||||
|
|
@ -390,6 +396,7 @@ pub struct ConnectorData {
|
|||
pub damage_intersect: Cell<Rect>,
|
||||
pub state: Cell<BackendConnectorState>,
|
||||
pub head_managers: HeadManagers,
|
||||
pub wlr_output_heads: CopyHashMap<WlrOutputManagerId, Rc<ZwlrOutputHeadV1>>,
|
||||
}
|
||||
|
||||
pub struct OutputData {
|
||||
|
|
@ -468,6 +475,9 @@ impl ConnectorData {
|
|||
}
|
||||
if old.mode != s.mode {
|
||||
self.head_managers.handle_mode_change(s.mode);
|
||||
for head in self.wlr_output_heads.lock().values() {
|
||||
head.handle_mode_change(s.mode);
|
||||
}
|
||||
}
|
||||
if let Some(output) = state.outputs.get(&self.connector.id())
|
||||
&& let Some(node) = &output.node
|
||||
|
|
@ -1009,6 +1019,7 @@ impl State {
|
|||
for output in self.root.outputs.lock().values() {
|
||||
output.clear();
|
||||
}
|
||||
self.wlr_output_managers.clear();
|
||||
self.dbus.clear();
|
||||
self.pending_container_layout.clear();
|
||||
self.pending_container_render_positions.clear();
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ use {
|
|||
state::{ConnectorData, OutputData, State},
|
||||
tree::{OutputNode, WsMoveConfig, move_ws_to_output},
|
||||
utils::{
|
||||
asyncevent::AsyncEvent, clonecell::CloneCell, hash_map_ext::HashMapExt, rc_eq::RcEq,
|
||||
asyncevent::AsyncEvent, clonecell::CloneCell, debug_fn::debug_fn,
|
||||
hash_map_ext::HashMapExt, rc_eq::RcEq,
|
||||
},
|
||||
},
|
||||
jay_config::video::Transform,
|
||||
|
|
@ -76,6 +77,7 @@ pub fn handle(state: &Rc<State>, connector: &Rc<dyn Connector>) {
|
|||
handler: Default::default(),
|
||||
connected: Cell::new(false),
|
||||
name,
|
||||
description: Default::default(),
|
||||
drm_dev: drm_dev.clone(),
|
||||
async_event: Rc::new(AsyncEvent::default()),
|
||||
damaged: Cell::new(false),
|
||||
|
|
@ -84,6 +86,7 @@ pub fn handle(state: &Rc<State>, connector: &Rc<dyn Connector>) {
|
|||
damage_intersect: Default::default(),
|
||||
state: Cell::new(backend_state),
|
||||
head_managers: HeadManagers::new(state.head_names.next(), head_state),
|
||||
wlr_output_heads: Default::default(),
|
||||
});
|
||||
if let Some(dev) = drm_dev {
|
||||
dev.connectors.set(id, data.clone());
|
||||
|
|
@ -143,6 +146,7 @@ impl ConnectorHandler {
|
|||
log::info!("Connector {} connected", self.data.connector.kernel_id());
|
||||
self.data.connected.set(true);
|
||||
self.data.set_state(&self.state, info.state);
|
||||
*self.data.description.borrow_mut() = create_description(&info);
|
||||
let name = self.state.globals.name();
|
||||
if info.non_desktop_effective {
|
||||
self.handle_non_desktop_connected(info).await;
|
||||
|
|
@ -151,6 +155,9 @@ impl ConnectorHandler {
|
|||
}
|
||||
self.data.connected.set(false);
|
||||
self.data.head_managers.handle_output_disconnected();
|
||||
for head in self.data.wlr_output_heads.lock().drain_values() {
|
||||
head.handle_disconnected();
|
||||
}
|
||||
log::info!("Connector {} disconnected", self.data.connector.kernel_id());
|
||||
}
|
||||
|
||||
|
|
@ -316,6 +323,7 @@ impl ConnectorHandler {
|
|||
self.data
|
||||
.head_managers
|
||||
.handle_output_connected(&output_data);
|
||||
self.state.wlr_output_managers.announce_head(&output_data);
|
||||
'outer: loop {
|
||||
while let Some(event) = self.data.connector.event() {
|
||||
match event {
|
||||
|
|
@ -433,6 +441,7 @@ impl ConnectorHandler {
|
|||
self.data
|
||||
.head_managers
|
||||
.handle_output_connected(&output_data);
|
||||
self.state.wlr_output_managers.announce_head(&output_data);
|
||||
'outer: loop {
|
||||
while let Some(event) = self.data.connector.event() {
|
||||
match event {
|
||||
|
|
@ -451,3 +460,22 @@ impl ConnectorHandler {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_description(info: &MonitorInfo) -> String {
|
||||
debug_fn(|f| {
|
||||
let mut needs_space = false;
|
||||
let id = &info.output_id;
|
||||
for s in [&id.manufacturer, &id.model, &id.serial_number] {
|
||||
if s.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if needs_space {
|
||||
f.write_str(" ")?;
|
||||
}
|
||||
needs_space = true;
|
||||
f.write_str(s)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.to_string()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -483,6 +483,9 @@ impl OutputNode {
|
|||
.connector
|
||||
.head_managers
|
||||
.handle_scale_change(scale);
|
||||
for head in self.global.connector.wlr_output_heads.lock().values() {
|
||||
head.handle_new_scale(scale);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn schedule_update_render_data(self: &Rc<Self>) {
|
||||
|
|
@ -778,6 +781,9 @@ impl OutputNode {
|
|||
}
|
||||
let rect = pos.at_point(x, y);
|
||||
self.change_extents_(&rect);
|
||||
for head in self.global.connector.wlr_output_heads.lock().values() {
|
||||
head.handle_position_change(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_mode(self: &Rc<Self>, mode: Mode) {
|
||||
|
|
@ -817,6 +823,9 @@ impl OutputNode {
|
|||
.connector
|
||||
.head_managers
|
||||
.handle_transform_change(transform);
|
||||
for head in self.global.connector.wlr_output_heads.lock().values() {
|
||||
head.hande_transform_change(transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1339,6 +1348,9 @@ impl OutputNode {
|
|||
.connector
|
||||
.head_managers
|
||||
.handle_vrr_mode_change(mode.to_config());
|
||||
for head in self.global.connector.wlr_output_heads.lock().values() {
|
||||
head.handle_vrr_mode_change(mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
28
wire/zwlr_output_configuration_head_v1.txt
Normal file
28
wire/zwlr_output_configuration_head_v1.txt
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# requests
|
||||
|
||||
request set_mode {
|
||||
mode: id(zwlr_output_mode_v1),
|
||||
}
|
||||
|
||||
request set_custom_mode {
|
||||
width: i32,
|
||||
height: i32,
|
||||
refresh: i32,
|
||||
}
|
||||
|
||||
request set_position {
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
|
||||
request set_transform {
|
||||
transform: i32,
|
||||
}
|
||||
|
||||
request set_scale {
|
||||
scale: fixed,
|
||||
}
|
||||
|
||||
request set_adaptive_sync {
|
||||
state: u32,
|
||||
}
|
||||
36
wire/zwlr_output_configuration_v1.txt
Normal file
36
wire/zwlr_output_configuration_v1.txt
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# requests
|
||||
|
||||
request enable_head {
|
||||
id: id(zwlr_output_configuration_head_v1),
|
||||
head: id(zwlr_output_head_v1),
|
||||
}
|
||||
|
||||
request disable_head {
|
||||
head: id(zwlr_output_head_v1),
|
||||
}
|
||||
|
||||
request apply {
|
||||
|
||||
}
|
||||
|
||||
request test {
|
||||
|
||||
}
|
||||
|
||||
request destroy {
|
||||
|
||||
}
|
||||
|
||||
# events
|
||||
|
||||
event succeeded {
|
||||
|
||||
}
|
||||
|
||||
event failed {
|
||||
|
||||
}
|
||||
|
||||
event cancelled {
|
||||
|
||||
}
|
||||
65
wire/zwlr_output_head_v1.txt
Normal file
65
wire/zwlr_output_head_v1.txt
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# requests
|
||||
|
||||
request release (since = 3) {
|
||||
|
||||
}
|
||||
|
||||
# events
|
||||
|
||||
event name {
|
||||
name: str,
|
||||
}
|
||||
|
||||
event description {
|
||||
description: str,
|
||||
}
|
||||
|
||||
event physical_size {
|
||||
width: i32,
|
||||
height: i32
|
||||
}
|
||||
|
||||
event mode {
|
||||
mode: id(zwlr_output_mode_v1),
|
||||
}
|
||||
|
||||
event enabled {
|
||||
enabled: i32,
|
||||
}
|
||||
|
||||
event current_mode {
|
||||
mode: id(zwlr_output_mode_v1),
|
||||
}
|
||||
|
||||
event position {
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
|
||||
event transform {
|
||||
transform: i32,
|
||||
}
|
||||
|
||||
event scale {
|
||||
scale: fixed,
|
||||
}
|
||||
|
||||
event finished {
|
||||
|
||||
}
|
||||
|
||||
event make (since = 2) {
|
||||
make: str
|
||||
}
|
||||
|
||||
event model (since = 2) {
|
||||
model: str
|
||||
}
|
||||
|
||||
event serial_number (since = 2) {
|
||||
serial_number: str
|
||||
}
|
||||
|
||||
event adaptive_sync (since = 4) {
|
||||
state: u32,
|
||||
}
|
||||
24
wire/zwlr_output_manager_v1.txt
Normal file
24
wire/zwlr_output_manager_v1.txt
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# requests
|
||||
|
||||
request create_configuration {
|
||||
id: id(zwlr_output_configuration_v1),
|
||||
serial: u32,
|
||||
}
|
||||
|
||||
request stop {
|
||||
|
||||
}
|
||||
|
||||
# events
|
||||
|
||||
event head {
|
||||
head: id(zwlr_output_head_v1)
|
||||
}
|
||||
|
||||
event done {
|
||||
serial: u32
|
||||
}
|
||||
|
||||
event finished {
|
||||
|
||||
}
|
||||
24
wire/zwlr_output_mode_v1.txt
Normal file
24
wire/zwlr_output_mode_v1.txt
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# requests
|
||||
|
||||
request release (since = 3) {
|
||||
|
||||
}
|
||||
|
||||
# events
|
||||
|
||||
event size {
|
||||
width: i32,
|
||||
height: i32,
|
||||
}
|
||||
|
||||
event refresh {
|
||||
refresh: i32,
|
||||
}
|
||||
|
||||
event preferred {
|
||||
|
||||
}
|
||||
|
||||
event finished {
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue