1
0
Fork 0
forked from wry/wry

control-center: add outputs pane

This commit is contained in:
Julian Orth 2026-03-07 14:04:04 +01:00
parent dee142b3bb
commit d328655f8b
13 changed files with 1775 additions and 36 deletions

View file

@ -744,8 +744,7 @@ fn create_dummy_output(state: &Rc<State>) {
wlr_output_heads: Default::default(), wlr_output_heads: Default::default(),
}); });
let schedule = Rc::new(OutputSchedule::new( let schedule = Rc::new(OutputSchedule::new(
&state.ring, state,
&state.eng,
&connector_data, &connector_data,
&persistent_state, &persistent_state,
)); ));

View file

@ -1484,7 +1484,7 @@ impl ConfigProxyHandler {
match connector { match connector {
Some(c) => { Some(c) => {
let connector = self.get_output_node(c)?; let connector = self.get_output_node(c)?;
connector.schedule.set_cursor_hz(hz); connector.schedule.set_cursor_hz(&self.state, hz);
} }
_ => { _ => {
let Some((hz, _)) = map_cursor_hz(hz) else { let Some((hz, _)) = map_cursor_hz(hz) else {

View file

@ -2,7 +2,7 @@ use {
crate::{ crate::{
control_center::{ control_center::{
cc_color_management::ColorManagementPane, cc_compositor::CompositorPane, cc_color_management::ColorManagementPane, cc_compositor::CompositorPane,
cc_idle::IdlePane, cc_xwayland::XwaylandPane, cc_idle::IdlePane, cc_outputs::OutputsPane, cc_xwayland::XwaylandPane,
}, },
egui_adapter::egui_platform::{ egui_adapter::egui_platform::{
EggError, EggWindow, EggWindowOwner, EggError, EggWindow, EggWindowOwner,
@ -36,6 +36,7 @@ use {
mod cc_color_management; mod cc_color_management;
mod cc_compositor; mod cc_compositor;
mod cc_idle; mod cc_idle;
mod cc_outputs;
mod cc_sidebar; mod cc_sidebar;
mod cc_xwayland; mod cc_xwayland;
@ -74,6 +75,7 @@ bitflags! {
CCI_IDLE, CCI_IDLE,
CCI_COLOR_MANAGEMENT, CCI_COLOR_MANAGEMENT,
CCI_XWAYLAND, CCI_XWAYLAND,
CCI_OUTPUTS,
} }
pub struct ControlCenter { pub struct ControlCenter {
@ -118,6 +120,7 @@ enum PaneType {
Idle(IdlePane), Idle(IdlePane),
ColorManagement(ColorManagementPane), ColorManagement(ColorManagementPane),
Xwayland(XwaylandPane), Xwayland(XwaylandPane),
Outputs(Box<OutputsPane>),
} }
struct CcBehavior<'a> { struct CcBehavior<'a> {
@ -140,6 +143,7 @@ impl Pane {
PaneType::Idle(v) => v.title(res), PaneType::Idle(v) => v.title(res),
PaneType::ColorManagement(v) => v.title(res), PaneType::ColorManagement(v) => v.title(res),
PaneType::Xwayland(v) => v.title(res), PaneType::Xwayland(v) => v.title(res),
PaneType::Outputs(v) => v.title(res),
} }
} }
@ -149,6 +153,7 @@ impl Pane {
PaneType::Idle(p) => p.show(ui), PaneType::Idle(p) => p.show(ui),
PaneType::ColorManagement(p) => p.show(ui), PaneType::ColorManagement(p) => p.show(ui),
PaneType::Xwayland(p) => p.show(behavior, ui), PaneType::Xwayland(p) => p.show(behavior, ui),
PaneType::Outputs(p) => p.show(&mut self.ps, ui),
} }
} }
} }
@ -160,6 +165,7 @@ impl PaneType {
PaneType::Idle(_) => CCI_IDLE, PaneType::Idle(_) => CCI_IDLE,
PaneType::ColorManagement(_) => CCI_COLOR_MANAGEMENT, PaneType::ColorManagement(_) => CCI_COLOR_MANAGEMENT,
PaneType::Xwayland(_) => CCI_XWAYLAND, PaneType::Xwayland(_) => CCI_XWAYLAND,
PaneType::Outputs(_) => CCI_OUTPUTS,
} }
} }
} }
@ -415,7 +421,6 @@ fn icon_label(icon: &str) -> Label {
Label::new(icon).selectable(false) Label::new(icon).selectable(false)
} }
#[expect(dead_code)]
fn grid_label(ui: &mut Ui, label: &str) { fn grid_label(ui: &mut Ui, label: &str) {
ui.with_layout(Layout::right_to_left(Align::Center), |ui| { ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
ui.label(label); ui.label(label);

File diff suppressed because it is too large Load diff

View file

@ -12,6 +12,7 @@ enum PaneName {
Idle, Idle,
ColorManagement, ColorManagement,
Xwayland, Xwayland,
Outputs,
} }
impl PaneName { impl PaneName {
@ -21,6 +22,7 @@ impl PaneName {
PaneName::Idle => "Idle", PaneName::Idle => "Idle",
PaneName::ColorManagement => "Color Management", PaneName::ColorManagement => "Color Management",
PaneName::Xwayland => "Xwayland", PaneName::Xwayland => "Xwayland",
PaneName::Outputs => "Outputs",
} }
} }
} }
@ -55,6 +57,9 @@ impl ControlCenterInner {
PaneName::Xwayland => { PaneName::Xwayland => {
PaneType::Xwayland(self.create_xwayland_pane()) PaneType::Xwayland(self.create_xwayland_pane())
} }
PaneName::Outputs => {
PaneType::Outputs(Box::new(self.create_outputs_pane()))
}
}; };
self.open(tree, ty); self.open(tree, ty);
ui.ctx().request_repaint(); ui.ctx().request_repaint();

View file

@ -6,7 +6,6 @@ use {
egui::{Color32, Rgba}, egui::{Color32, Rgba},
}; };
#[expect(dead_code)]
pub trait Color32Ext { pub trait Color32Ext {
fn to_oklab(self) -> Oklab; fn to_oklab(self) -> Oklab;
fn to_oklch(self) -> Oklch; fn to_oklch(self) -> Oklch;

View file

@ -110,7 +110,6 @@ pub enum EggError {
} }
pub mod icons { pub mod icons {
#[expect(dead_code)]
pub const ICON_ADD: &str = "\u{e145}"; pub const ICON_ADD: &str = "\u{e145}";
pub const ICON_CLOSE: &str = "\u{e5cd}"; pub const ICON_CLOSE: &str = "\u{e5cd}";
pub const ICON_DRAG_INDICATOR: &str = "\u{e945}"; pub const ICON_DRAG_INDICATOR: &str = "\u{e945}";
@ -119,7 +118,6 @@ pub mod icons {
pub const ICON_OPEN_IN_NEW: &str = "\u{e89e}"; pub const ICON_OPEN_IN_NEW: &str = "\u{e89e}";
#[expect(dead_code)] #[expect(dead_code)]
pub const ICON_PENDING: &str = "\u{ef64}"; pub const ICON_PENDING: &str = "\u{ef64}";
#[expect(dead_code)]
pub const ICON_REMOVE: &str = "\u{e15b}"; pub const ICON_REMOVE: &str = "\u{e15b}";
} }

View file

@ -105,14 +105,13 @@ pub struct ReadOnlyHeadState {
} }
impl ReadOnlyHeadState { impl ReadOnlyHeadState {
#[expect(dead_code)]
pub fn borrow(&self) -> Ref<'_, HeadState> { pub fn borrow(&self) -> Ref<'_, HeadState> {
self.state.borrow() self.state.borrow()
} }
} }
impl HeadState { impl HeadState {
fn update_in_compositor_space(&mut self, wl_output: Option<GlobalName>) { pub fn update_in_compositor_space(&mut self, wl_output: Option<GlobalName>) {
self.in_compositor_space = false; self.in_compositor_space = false;
self.wl_output = None; self.wl_output = None;
if !self.connector_enabled { if !self.connector_enabled {
@ -131,7 +130,7 @@ impl HeadState {
self.wl_output = wl_output; self.wl_output = wl_output;
} }
fn update_size(&mut self) { pub fn update_size(&mut self) {
self.size = self.size =
OutputNode::calculate_extents_(self.mode, self.transform, self.scale, self.position) OutputNode::calculate_extents_(self.mode, self.transform, self.scale, self.position)
.size(); .size();
@ -213,7 +212,7 @@ pub enum HeadCommonError {
} }
pub struct HeadManagers { pub struct HeadManagers {
name: HeadName, pub name: HeadName,
state: Rc<RefCell<HeadState>>, state: Rc<RefCell<HeadState>>,
managers: CopyHashMap<(ClientId, JayHeadManagerSessionV1Id), Rc<Head>>, managers: CopyHashMap<(ClientId, JayHeadManagerSessionV1Id), Rc<Head>>,
} }
@ -235,7 +234,6 @@ impl HeadManagers {
} }
} }
#[expect(dead_code)]
pub fn state(&self) -> ReadOnlyHeadState { pub fn state(&self) -> ReadOnlyHeadState {
ReadOnlyHeadState { ReadOnlyHeadState {
state: self.state.clone(), state: self.state.clone(),

View file

@ -456,7 +456,7 @@ impl JayRandrRequestHandler for JayRandr {
let Some(c) = self.get_output_node(req.output) else { let Some(c) = self.get_output_node(req.output) else {
return Ok(()); return Ok(());
}; };
c.schedule.set_cursor_hz(req.hz); c.schedule.set_cursor_hz(&self.state, req.hz);
Ok(()) Ok(())
} }

View file

@ -2,9 +2,10 @@ use {
crate::{ crate::{
async_engine::AsyncEngine, async_engine::AsyncEngine,
backend::HardwareCursor, backend::HardwareCursor,
control_center::CCI_OUTPUTS,
ifs::wl_output::PersistentOutputState, ifs::wl_output::PersistentOutputState,
io_uring::{IoUring, IoUringError}, io_uring::{IoUring, IoUringError},
state::ConnectorData, state::{ConnectorData, State},
utils::{ utils::{
asyncevent::AsyncEvent, cell_ext::CellExt, clonecell::CloneCell, errorfmt::ErrorFmt, asyncevent::AsyncEvent, cell_ext::CellExt, clonecell::CloneCell, errorfmt::ErrorFmt,
numcell::NumCell, numcell::NumCell,
@ -51,8 +52,7 @@ pub struct OutputSchedule {
impl OutputSchedule { impl OutputSchedule {
pub fn new( pub fn new(
ring: &Rc<IoUring>, state: &State,
eng: &Rc<AsyncEngine>,
connector: &Rc<ConnectorData>, connector: &Rc<ConnectorData>,
persistent: &Rc<PersistentOutputState>, persistent: &Rc<PersistentOutputState>,
) -> Self { ) -> Self {
@ -60,8 +60,8 @@ impl OutputSchedule {
changed: Default::default(), changed: Default::default(),
run: Default::default(), run: Default::default(),
connector: connector.clone(), connector: connector.clone(),
ring: ring.clone(), ring: state.ring.clone(),
eng: eng.clone(), eng: state.eng.clone(),
vrr_enabled: Default::default(), vrr_enabled: Default::default(),
hardware_cursor_change: Cell::new(Change::None), hardware_cursor_change: Cell::new(Change::None),
software_cursor_change: Cell::new(Change::None), software_cursor_change: Cell::new(Change::None),
@ -72,7 +72,7 @@ impl OutputSchedule {
iteration: Default::default(), iteration: Default::default(),
}; };
if let Some(hz) = persistent.vrr_cursor_hz.get() { if let Some(hz) = persistent.vrr_cursor_hz.get() {
slf.set_cursor_hz(hz); slf.set_cursor_hz(state, hz);
} }
slf slf
} }
@ -118,7 +118,7 @@ impl OutputSchedule {
self.trigger(); self.trigger();
} }
pub fn set_cursor_hz(&self, hz: f64) { pub fn set_cursor_hz(&self, state: &State, hz: f64) {
let (hz, delta) = match map_cursor_hz(hz) { let (hz, delta) = match map_cursor_hz(hz) {
None => { None => {
log::warn!("Ignoring cursor frequency {hz}"); log::warn!("Ignoring cursor frequency {hz}");
@ -128,6 +128,7 @@ impl OutputSchedule {
}; };
self.persistent.vrr_cursor_hz.set(hz); self.persistent.vrr_cursor_hz.set(hz);
self.connector.head_managers.handle_cursor_hz_change(hz); self.connector.head_managers.handle_cursor_hz_change(hz);
state.trigger_cci(CCI_OUTPUTS);
self.cursor_delta_nsec.set(delta); self.cursor_delta_nsec.set(delta);
self.trigger(); self.trigger();
} }

View file

@ -17,7 +17,8 @@ use {
compositor::{LIBEI_SOCKET, LogLevel}, compositor::{LIBEI_SOCKET, LogLevel},
config::ConfigProxy, config::ConfigProxy,
control_center::{ control_center::{
CCI_COLOR_MANAGEMENT, CCI_COMPOSITOR, CCI_IDLE, CCI_XWAYLAND, ControlCenters, CCI_COLOR_MANAGEMENT, CCI_COMPOSITOR, CCI_IDLE, CCI_OUTPUTS, CCI_XWAYLAND,
ControlCenters,
}, },
copy_device::CopyDeviceRegistry, copy_device::CopyDeviceRegistry,
cpu_worker::CpuWorker, cpu_worker::CpuWorker,
@ -493,30 +494,39 @@ impl ConnectorData {
return; return;
} }
*self.state.borrow_mut() = s.clone(); *self.state.borrow_mut() = s.clone();
if old.enabled != s.enabled { macro_rules! b {
($expr:expr) => {{
let e = $expr;
if e {
state.trigger_cci(CCI_OUTPUTS);
}
e
}};
}
if b!(old.enabled != s.enabled) {
self.head_managers.handle_enabled_change(s.enabled); self.head_managers.handle_enabled_change(s.enabled);
} }
if old.active != s.active { if b!(old.active != s.active) {
self.head_managers.handle_active_change(s.active); self.head_managers.handle_active_change(s.active);
} }
if old.non_desktop_override != s.non_desktop_override { if b!(old.non_desktop_override != s.non_desktop_override) {
self.head_managers self.head_managers
.handle_non_desktop_override_changed(s.non_desktop_override); .handle_non_desktop_override_changed(s.non_desktop_override);
} }
if old.vrr != s.vrr { if b!(old.vrr != s.vrr) {
self.head_managers.handle_vrr_change(s.vrr); self.head_managers.handle_vrr_change(s.vrr);
} }
if old.tearing != s.tearing { if b!(old.tearing != s.tearing) {
self.head_managers.handle_tearing_enabled_change(s.tearing); self.head_managers.handle_tearing_enabled_change(s.tearing);
} }
if old.format != s.format { if b!(old.format != s.format) {
self.head_managers.handle_format_change(s.format); self.head_managers.handle_format_change(s.format);
} }
if (old.color_space, old.eotf) != (s.color_space, s.eotf) { if b!((old.color_space, old.eotf) != (s.color_space, s.eotf)) {
self.head_managers self.head_managers
.handle_colors_change(s.color_space, s.eotf); .handle_colors_change(s.color_space, s.eotf);
} }
if old.mode != s.mode { if b!(old.mode != s.mode) {
self.head_managers.handle_mode_change(s.mode); self.head_managers.handle_mode_change(s.mode);
for head in self.wlr_output_heads.lock().values() { for head in self.wlr_output_heads.lock().values() {
head.handle_mode_change(s.mode); head.handle_mode_change(s.mode);

View file

@ -4,6 +4,7 @@ use {
BackendConnectorState, BackendConnectorStateSerial, Connector, ConnectorEvent, BackendConnectorState, BackendConnectorStateSerial, Connector, ConnectorEvent,
ConnectorId, MonitorInfo, ConnectorId, MonitorInfo,
}, },
control_center::CCI_OUTPUTS,
format::XRGB8888, format::XRGB8888,
globals::GlobalName, globals::GlobalName,
ifs::{ ifs::{
@ -108,6 +109,7 @@ pub fn handle(state: &Rc<State>, connector: &Rc<dyn Connector>) {
for mgr in state.head_managers.lock().values() { for mgr in state.head_managers.lock().values() {
mgr.announce(&data); mgr.announce(&data);
} }
state.trigger_cci(CCI_OUTPUTS);
if state.connectors.set(id, data).is_some() { if state.connectors.set(id, data).is_some() {
panic!("Connector id has been reused"); panic!("Connector id has been reused");
} }
@ -147,6 +149,7 @@ impl ConnectorHandler {
self.data.handler.set(None); self.data.handler.set(None);
self.state.connectors.remove(&self.id); self.state.connectors.remove(&self.id);
self.data.head_managers.handle_removed(); self.data.head_managers.handle_removed();
self.state.trigger_cci(CCI_OUTPUTS);
} }
async fn handle_connected(&self, info: MonitorInfo) { async fn handle_connected(&self, info: MonitorInfo) {
@ -162,6 +165,7 @@ impl ConnectorHandler {
} }
self.data.connected.set(false); self.data.connected.set(false);
self.data.head_managers.handle_output_disconnected(); self.data.head_managers.handle_output_disconnected();
self.state.trigger_cci(CCI_OUTPUTS);
for head in self.data.wlr_output_heads.lock().drain_values() { for head in self.data.wlr_output_heads.lock().drain_values() {
head.handle_disconnected(); head.handle_disconnected();
} }
@ -213,12 +217,7 @@ impl ConnectorHandler {
info.primaries, info.primaries,
info.luminance, info.luminance,
)); ));
let schedule = Rc::new(OutputSchedule::new( let schedule = Rc::new(OutputSchedule::new(&self.state, &self.data, &desired_state));
&self.state.ring,
&self.state.eng,
&self.data,
&desired_state,
));
let _schedule = self let _schedule = self
.state .state
.eng .eng
@ -341,6 +340,7 @@ impl ConnectorHandler {
self.data self.data
.head_managers .head_managers
.handle_output_connected(&output_data); .handle_output_connected(&output_data);
self.state.trigger_cci(CCI_OUTPUTS);
self.state.wlr_output_managers.announce_head(&output_data); self.state.wlr_output_managers.announce_head(&output_data);
'outer: loop { 'outer: loop {
while let Some(event) = self.data.connector.event() { while let Some(event) = self.data.connector.event() {
@ -353,6 +353,7 @@ impl ConnectorHandler {
} }
ConnectorEvent::FormatsChanged(formats) => { ConnectorEvent::FormatsChanged(formats) => {
self.data.head_managers.handle_formats_change(&formats); self.data.head_managers.handle_formats_change(&formats);
self.state.trigger_cci(CCI_OUTPUTS);
on.global.formats.set(formats); on.global.formats.set(formats);
} }
ConnectorEvent::State(state) => { ConnectorEvent::State(state) => {
@ -466,6 +467,7 @@ impl ConnectorHandler {
self.data self.data
.head_managers .head_managers
.handle_output_connected(&output_data); .handle_output_connected(&output_data);
self.state.trigger_cci(CCI_OUTPUTS);
self.state.wlr_output_managers.announce_head(&output_data); self.state.wlr_output_managers.announce_head(&output_data);
'outer: loop { 'outer: loop {
while let Some(event) = self.data.connector.event() { while let Some(event) = self.data.connector.event() {

View file

@ -6,6 +6,7 @@ use {
}, },
client::ClientId, client::ClientId,
cmm::cmm_description::ColorDescription, cmm::cmm_description::ColorDescription,
control_center::CCI_OUTPUTS,
cursor::KnownCursor, cursor::KnownCursor,
fixed::Fixed, fixed::Fixed,
gfx_api::{AcquireSync, BufferResv, GfxTexture, ReleaseSync}, gfx_api::{AcquireSync, BufferResv, GfxTexture, ReleaseSync},
@ -243,6 +244,7 @@ impl OutputNode {
.connector .connector
.head_managers .head_managers
.handle_tearing_active_change(tearing); .handle_tearing_active_change(tearing);
self.state.trigger_cci(CCI_OUTPUTS);
} }
} }
@ -501,6 +503,7 @@ impl OutputNode {
.connector .connector
.head_managers .head_managers
.handle_scale_change(scale); .handle_scale_change(scale);
self.state.trigger_cci(CCI_OUTPUTS);
for head in self.global.connector.wlr_output_heads.lock().values() { for head in self.global.connector.wlr_output_heads.lock().values() {
head.handle_new_scale(scale); head.handle_new_scale(scale);
} }
@ -873,6 +876,7 @@ impl OutputNode {
.connector .connector
.head_managers .head_managers
.handle_transform_change(transform); .handle_transform_change(transform);
self.state.trigger_cci(CCI_OUTPUTS);
for head in self.global.connector.wlr_output_heads.lock().values() { for head in self.global.connector.wlr_output_heads.lock().values() {
head.hande_transform_change(transform); head.hande_transform_change(transform);
} }
@ -935,6 +939,7 @@ impl OutputNode {
.connector .connector
.head_managers .head_managers
.handle_position_size_change(self); .handle_position_size_change(self);
self.state.trigger_cci(CCI_OUTPUTS);
} }
pub fn update_state(self: &Rc<Self>, old: BackendConnectorState, state: BackendConnectorState) { pub fn update_state(self: &Rc<Self>, old: BackendConnectorState, state: BackendConnectorState) {
@ -989,6 +994,7 @@ impl OutputNode {
.connector .connector
.head_managers .head_managers
.handle_brightness_change(brightness); .handle_brightness_change(brightness);
self.state.trigger_cci(CCI_OUTPUTS);
} }
} }
@ -1004,6 +1010,7 @@ impl OutputNode {
.connector .connector
.head_managers .head_managers
.handle_use_native_gamut_change(use_native_gamut); .handle_use_native_gamut_change(use_native_gamut);
self.state.trigger_cci(CCI_OUTPUTS);
} }
} }
@ -1015,6 +1022,7 @@ impl OutputNode {
.connector .connector
.head_managers .head_managers
.handle_blend_space_change(blend_space); .handle_blend_space_change(blend_space);
self.state.trigger_cci(CCI_OUTPUTS);
} }
} }
fn find_stacked_at( fn find_stacked_at(
@ -1480,6 +1488,7 @@ impl OutputNode {
.connector .connector
.head_managers .head_managers
.handle_vrr_mode_change(mode); .handle_vrr_mode_change(mode);
self.state.trigger_cci(CCI_OUTPUTS);
for head in self.global.connector.wlr_output_heads.lock().values() { for head in self.global.connector.wlr_output_heads.lock().values() {
head.handle_vrr_mode_change(mode); head.handle_vrr_mode_change(mode);
} }
@ -1494,6 +1503,7 @@ impl OutputNode {
.connector .connector
.head_managers .head_managers
.handle_tearing_mode_change(mode); .handle_tearing_mode_change(mode);
self.state.trigger_cci(CCI_OUTPUTS);
} }
} }
@ -1543,6 +1553,7 @@ impl OutputNode {
pub fn set_flip_margin(&self, margin_ns: u64) { pub fn set_flip_margin(&self, margin_ns: u64) {
self.flip_margin_ns.set(Some(margin_ns)); self.flip_margin_ns.set(Some(margin_ns));
self.state.trigger_cci(CCI_OUTPUTS);
} }
} }