use crate::backend::ConnectorId;
use crate::backend::transaction::BackendConnectorTransactionError;
use crate::backend::transaction::ConnectorTransaction;
use crate::backend::transaction::PreparedConnectorTransaction;
use crate::client::Client;
use crate::client::LookupError;
use crate::ifs::wlr_output_manager::zwlr_output_configuration_head::ZwlrOutputConfigurationHeadV1;
use crate::ifs::wlr_output_manager::zwlr_output_manager_v1::ZwlrOutputManagerV1;
use crate::leaks::Tracker;
use crate::object::Version;
use crate::utils::copyhashmap::CopyHashMap;
use crate::utils::errorfmt::ErrorFmt;
use crate::utils::hash_map_ext::HashMapExt;
use crate::wire::ZwlrOutputConfigurationV1Id;
use crate::wire::zwlr_output_configuration_v1::*;
use jay_proc::Object;
use std::cell::Cell;
use std::rc::Rc;
use thiserror::Error;
#[derive(Object)]
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 {
fn send_succeeded(&self) {
self.client.event(Succeeded { self_id: self.id });
}
fn send_failed(&self) {
self.client.event(Failed { self_id: self.id });
}
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.borrow().clone();
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(())
}
}
#[derive(Debug, Error)]
pub enum ZwlrOutputConfigurationV1Error {
#[error(transparent)]
Lookup(#[from] LookupError),
#[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,
}