implement wlr_output_management_unstable_v1
This commit is contained in:
parent
a3c0631f4e
commit
c6060a7389
23 changed files with 1349 additions and 32 deletions
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue