From b6c857598891b49b9b9a5eda584734a52b6d5b8f Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 18 Mar 2026 14:32:19 +0100 Subject: [PATCH] head-management: pull persistent state upon first change --- src/compositor.rs | 2 + src/control_center/cc_outputs.rs | 13 ++-- src/ifs/head_management.rs | 67 +++++++++++++------ .../jay_head_manager_session_v1.rs | 5 +- src/state.rs | 41 +++++++++++- src/tasks/connector.rs | 44 +++--------- 6 files changed, 107 insertions(+), 65 deletions(-) diff --git a/src/compositor.rs b/src/compositor.rs index 95e8463a..c48af940 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -681,6 +681,7 @@ fn create_dummy_output(state: &Rc) { let name = Rc::new("Dummy".to_string()); let head_name = state.head_names.next(); let head_state = HeadState { + connector_id: id, name: RcEq(name.clone()), position: (0, 0), size: (0, 0), @@ -708,6 +709,7 @@ fn create_dummy_output(state: &Rc) { blend_space: BlendSpace::Srgb, use_native_gamut: false, vrr_cursor_hz: None, + persistent_state: Some(RcEq(persistent_state.clone())), }; let connector_data = Rc::new(ConnectorData { id, diff --git a/src/control_center/cc_outputs.rs b/src/control_center/cc_outputs.rs index d5027777..9c48cc83 100644 --- a/src/control_center/cc_outputs.rs +++ b/src/control_center/cc_outputs.rs @@ -842,6 +842,7 @@ impl OutputsPaneInner { let Some(desired) = &head.changed_state else { continue; }; + desired.flush_persistent_state(&self.state); if let Some(output) = self.state.outputs.get(&head.id) && let Some(node) = &output.node { @@ -957,7 +958,7 @@ fn show_connector(state: &State, settings: &Settings, head: &mut CompleteHead, u grid(ui, ("settings", head.name), |ui| { let mut diff = false; show_serial_number(ui, m); - diff |= show_enablement(ui, m, t); + diff |= show_enablement(state, ui, m, t); diff |= show_position(ui, m, t); diff |= show_scale(ui, m, t); diff |= show_mode(ui, m, t); @@ -969,7 +970,7 @@ fn show_connector(state: &State, settings: &Settings, head: &mut CompleteHead, u diff |= show_format(ui, m, t); diff |= show_tearing(ui, m, t); diff |= show_vrr(ui, m, t); - diff |= show_non_desktop(ui, m, t); + diff |= show_non_desktop(state, ui, m, t); diff |= show_blend_space(ui, m, t); diff |= show_use_native_gamut(ui, m, t); show_native_gamut(ui, m); @@ -993,7 +994,7 @@ fn show_serial_number(ui: &mut Ui, m: &HeadState) { } } -fn show_enablement(ui: &mut Ui, m: &HeadState, t: &mut Option) -> bool { +fn show_enablement(state: &State, ui: &mut Ui, m: &HeadState, t: &mut Option) -> bool { let ui = &mut *ui.row(); grid_label(ui, "Enabled"); let mut v = effective!(m, t).connector_enabled; @@ -1001,7 +1002,7 @@ fn show_enablement(ui: &mut Ui, m: &HeadState, t: &mut Option) -> boo if changed { let t = modify!(m, t); t.connector_enabled = v; - t.update_in_compositor_space(m.wl_output); + t.update_in_compositor_space(state, m.wl_output); } let diff = v != m.connector_enabled; if diff { @@ -1550,7 +1551,7 @@ fn show_vrr(ui: &mut Ui, m: &HeadState, t: &mut Option) -> bool { diff } -fn show_non_desktop(ui: &mut Ui, m: &HeadState, t: &mut Option) -> bool { +fn show_non_desktop(state: &State, ui: &mut Ui, m: &HeadState, t: &mut Option) -> bool { { let ui = &mut *ui.row(); grid_label(ui, "Non-desktop"); @@ -1580,7 +1581,7 @@ fn show_non_desktop(ui: &mut Ui, m: &HeadState, t: &mut Option) -> bo if changed { let t = modify!(m, t); t.override_non_desktop = v; - t.update_in_compositor_space(m.wl_output); + t.update_in_compositor_space(state, m.wl_output); } let diff = v != m.override_non_desktop; if diff { diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index 46dd514b..f10ace52 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -12,16 +12,17 @@ use { head_management_macros::HeadExts, jay_head_manager_session_v1::JayHeadManagerSessionV1, jay_head_v1::JayHeadV1, }, - wl_output::BlendSpace, + wl_output::{BlendSpace, PersistentOutputState}, }, scale::Scale, - state::OutputData, + state::{OutputData, State}, tree::{OutputNode, TearingMode, Transform, VrrMode}, utils::{copyhashmap::CopyHashMap, hash_map_ext::HashMapExt, rc_eq::RcEq}, wire::JayHeadManagerSessionV1Id, }, std::{ cell::{Cell, Ref, RefCell}, + collections::hash_map::Entry, rc::Rc, }, thiserror::Error, @@ -71,6 +72,7 @@ struct HeadCommon { #[derive(Clone, PartialEq)] pub struct HeadState { + pub connector_id: ConnectorId, pub name: RcEq, pub wl_output: Option, pub connector_enabled: bool, @@ -98,6 +100,7 @@ pub struct HeadState { pub blend_space: BlendSpace, pub use_native_gamut: bool, pub vrr_cursor_hz: Option, + pub persistent_state: Option>, } pub struct ReadOnlyHeadState { @@ -111,7 +114,7 @@ impl ReadOnlyHeadState { } impl HeadState { - pub fn update_in_compositor_space(&mut self, wl_output: Option) { + pub fn update_in_compositor_space(&mut self, state: &State, wl_output: Option) { self.in_compositor_space = false; self.wl_output = None; if !self.connector_enabled { @@ -128,6 +131,26 @@ impl HeadState { } self.in_compositor_space = true; self.wl_output = wl_output; + if self.persistent_state.is_none() { + let ds = state + .persistent_output_states + .get(&mi.output_id) + .unwrap_or_else(|| state.new_persistent_output_state()); + self.position = ds.pos.get(); + self.transform = ds.transform.get(); + self.vrr_mode = ds.vrr_mode.get(); + self.tearing_mode = ds.tearing_mode.get(); + self.brightness = ds.brightness.get(); + self.blend_space = ds.blend_space.get(); + self.use_native_gamut = ds.use_native_gamut.get(); + self.vrr_cursor_hz = ds.vrr_cursor_hz.get(); + self.scale = ds.scale.get(); + self.persistent_state = Some(RcEq(ds)); + if let Some(c) = state.connectors.get(&self.connector_id) { + self.mode = c.state.borrow().mode; + } + self.update_size(); + } } pub fn update_size(&mut self) { @@ -135,6 +158,18 @@ impl HeadState { OutputNode::calculate_extents_(self.mode, self.transform, self.scale, self.position) .size(); } + + pub fn flush_persistent_state(&self, state: &State) { + if let Some(mi) = &self.monitor_info + && let Some(ds) = &self.persistent_state + && let Entry::Vacant(v) = state + .persistent_output_states + .lock() + .entry(mi.output_id.clone()) + { + v.insert(ds.0.clone()); + } + } } enum HeadOp { @@ -249,24 +284,13 @@ impl HeadManagers { } } - pub fn handle_output_connected(&self, output: &OutputData) { + pub fn handle_output_connected(&self, s: &State, output: &OutputData) { let state = &mut *self.state.borrow_mut(); state.connected = true; state.monitor_info = Some(RcEq(output.monitor_info.clone())); + state.persistent_state = None; state.inherent_non_desktop = output.monitor_info.non_desktop; - state.update_in_compositor_space(output.node.as_ref().map(|n| n.global.name)); - if let Some(n) = &output.node { - state.position = n.global.pos.get().position(); - state.size = n.global.pos.get().size(); - state.mode = n.global.mode.get(); - state.transform = n.global.persistent.transform.get(); - state.vrr_mode = n.global.persistent.vrr_mode.get(); - state.tearing_mode = n.global.persistent.tearing_mode.get(); - state.brightness = n.global.persistent.brightness.get(); - state.blend_space = n.global.persistent.blend_space.get(); - state.use_native_gamut = n.global.persistent.use_native_gamut.get(); - state.vrr_cursor_hz = n.global.persistent.vrr_cursor_hz.get(); - } + state.update_in_compositor_space(s, output.node.as_ref().map(|n| n.global.name)); for head in self.managers.lock().values() { skip_in_transaction!(head); if let Some(ext) = &head.ext.connector_info_v1 { @@ -321,11 +345,12 @@ impl HeadManagers { } } - pub fn handle_output_disconnected(&self) { + pub fn handle_output_disconnected(&self, s: &State) { let state = &mut *self.state.borrow_mut(); state.connected = false; state.monitor_info = None; - state.update_in_compositor_space(None); + state.persistent_state = None; + state.update_in_compositor_space(s, None); for head in self.managers.lock().values() { skip_in_transaction!(head); if let Some(ext) = &head.ext.compositor_space_info_v1 { @@ -406,10 +431,10 @@ impl HeadManagers { } } - pub fn handle_enabled_change(&self, enabled: bool) { + pub fn handle_enabled_change(&self, s: &State, enabled: bool) { let state = &mut *self.state.borrow_mut(); state.connector_enabled = enabled; - state.update_in_compositor_space(state.wl_output); + state.update_in_compositor_space(s, state.wl_output); for head in self.managers.lock().values() { skip_in_transaction!(head); if let Some(ext) = &head.ext.compositor_space_info_v1 { diff --git a/src/ifs/head_management/jay_head_manager_session_v1.rs b/src/ifs/head_management/jay_head_manager_session_v1.rs index 15dbe174..f1316c5e 100644 --- a/src/ifs/head_management/jay_head_manager_session_v1.rs +++ b/src/ifs/head_management/jay_head_manager_session_v1.rs @@ -404,7 +404,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { } HeadOp::SetConnectorEnabled(enabled) => { state.connector_enabled = enabled; - state.update_in_compositor_space(snapshot.wl_output); + state.update_in_compositor_space(&self.client.state, snapshot.wl_output); to_send |= COMPOSITOR_SPACE_INFO_FULL; to_send |= COMPOSITOR_SPACE_INFO_ENABLED; to_send |= CORE_INFO; @@ -435,7 +435,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { } HeadOp::SetNonDesktopOverride(m) => { state.override_non_desktop = m; - state.update_in_compositor_space(snapshot.wl_output); + state.update_in_compositor_space(&self.client.state, snapshot.wl_output); to_send |= COMPOSITOR_SPACE_INFO_FULL; to_send |= CORE_INFO; to_send |= NON_DESKTOP_INFO; @@ -557,6 +557,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { } for head in self.heads.lock().values() { let desired = &*head.common.transaction_state.borrow(); + desired.flush_persistent_state(&self.client.state); if let Some(output) = self.client.state.outputs.get(&head.common.id) && let Some(node) = &output.node { diff --git a/src/state.rs b/src/state.rs index ad537ee1..04fb9cbd 100644 --- a/src/state.rs +++ b/src/state.rs @@ -62,7 +62,7 @@ use { jay_seat_events::JaySeatEvents, jay_workspace_watcher::JayWorkspaceWatcher, wl_buffer::WlBuffer, - wl_output::{OutputGlobalOpt, OutputId, PersistentOutputState}, + wl_output::{BlendSpace, OutputGlobalOpt, OutputId, PersistentOutputState}, wl_seat::{ PhysicalKeyboardId, PhysicalKeyboardIds, PositionHintRequest, SeatIds, WlSeatGlobal, @@ -504,7 +504,7 @@ impl ConnectorData { }}; } if b!(old.enabled != s.enabled) { - self.head_managers.handle_enabled_change(s.enabled); + self.head_managers.handle_enabled_change(state, s.enabled); } if b!(old.active != s.active) { self.head_managers.handle_active_change(s.active); @@ -1961,6 +1961,43 @@ impl State { colored.field(&self.theme).set(v); self.colors_changed(); } + + pub fn ensure_persistent_output_state( + &self, + output_id: &Rc, + ) -> Rc { + match self.persistent_output_states.get(output_id) { + Some(ds) => ds, + _ => { + let ds = self.new_persistent_output_state(); + self.persistent_output_states + .set(output_id.clone(), ds.clone()); + ds + } + } + } + + pub fn new_persistent_output_state(&self) -> Rc { + let x1 = self + .root + .outputs + .lock() + .values() + .map(|o| o.global.pos.get().x2()) + .max() + .unwrap_or(0); + Rc::new(PersistentOutputState { + transform: Default::default(), + scale: Default::default(), + pos: Cell::new((x1, 0)), + vrr_mode: Cell::new(self.default_vrr_mode.get()), + vrr_cursor_hz: Cell::new(self.default_vrr_cursor_hz.get()), + tearing_mode: Cell::new(self.default_tearing_mode.get()), + brightness: Cell::new(None), + blend_space: Cell::new(BlendSpace::Srgb), + use_native_gamut: Cell::new(false), + }) + } } #[derive(Debug, Error)] diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 2b6af537..cdf3cf44 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -6,7 +6,7 @@ use { ifs::{ head_management::{HeadManagers, HeadState}, jay_tray_v1::JayTrayV1Global, - wl_output::{BlendSpace, PersistentOutputState, WlOutputGlobal}, + wl_output::{BlendSpace, WlOutputGlobal}, }, output_schedule::OutputSchedule, state::{ConnectorData, OutputData, State}, @@ -35,6 +35,7 @@ pub fn handle(state: &Rc, connector: &Rc) { let id = connector.id(); let name = Rc::new(connector.name()); let head_state = HeadState { + connector_id: id, name: RcEq(name.clone()), position: (0, 0), size: (0, 0), @@ -45,7 +46,7 @@ pub fn handle(state: &Rc, connector: &Rc) { wl_output: None, connector_enabled: backend_state.enabled, in_compositor_space: false, - mode: Default::default(), + mode: backend_state.mode, monitor_info: None, inherent_non_desktop: false, override_non_desktop: backend_state.non_desktop_override, @@ -62,6 +63,7 @@ pub fn handle(state: &Rc, connector: &Rc) { blend_space: BlendSpace::Srgb, use_native_gamut: false, vrr_cursor_hz: None, + persistent_state: None, }; let data = Rc::new(ConnectorData { id, @@ -152,7 +154,9 @@ impl ConnectorHandler { self.handle_desktop_connected(info, name).await; } self.data.connected.set(false); - self.data.head_managers.handle_output_disconnected(); + self.data + .head_managers + .handle_output_disconnected(&self.state); self.state.trigger_cci(CCI_OUTPUTS); for head in self.data.wlr_output_heads.lock().drain_values() { head.handle_disconnected(); @@ -162,35 +166,7 @@ impl ConnectorHandler { async fn handle_desktop_connected(&self, info: MonitorInfo, name: GlobalName) { let output_id = info.output_id.clone(); - let desired_state = match self.state.persistent_output_states.get(&output_id) { - Some(ds) => ds, - _ => { - let x1 = self - .state - .root - .outputs - .lock() - .values() - .map(|o| o.global.pos.get().x2()) - .max() - .unwrap_or(0); - let ds = Rc::new(PersistentOutputState { - transform: Default::default(), - scale: Default::default(), - pos: Cell::new((x1, 0)), - vrr_mode: Cell::new(self.state.default_vrr_mode.get()), - vrr_cursor_hz: Cell::new(self.state.default_vrr_cursor_hz.get()), - tearing_mode: Cell::new(self.state.default_tearing_mode.get()), - brightness: Cell::new(None), - blend_space: Cell::new(BlendSpace::Srgb), - use_native_gamut: Cell::new(false), - }); - self.state - .persistent_output_states - .set(output_id.clone(), ds.clone()); - ds - } - }; + let desired_state = self.state.ensure_persistent_output_state(&output_id); let global = Rc::new(WlOutputGlobal::new( name, &self.state, @@ -327,7 +303,7 @@ impl ConnectorHandler { self.state.workspace_managers.announce_output(&on); self.data .head_managers - .handle_output_connected(&output_data); + .handle_output_connected(&self.state, &output_data); self.state.trigger_cci(CCI_OUTPUTS); self.state.wlr_output_managers.announce_head(&output_data); 'outer: loop { @@ -454,7 +430,7 @@ impl ConnectorHandler { } self.data .head_managers - .handle_output_connected(&output_data); + .handle_output_connected(&self.state, &output_data); self.state.trigger_cci(CCI_OUTPUTS); self.state.wlr_output_managers.announce_head(&output_data); 'outer: loop {