1
0
Fork 0
forked from wry/wry

control-center: add in-process control center

This commit is contained in:
Julian Orth 2026-03-07 19:20:39 +01:00
parent 008e8a671a
commit 186d5b694b
28 changed files with 859 additions and 14 deletions

View file

@ -1,6 +1,7 @@
mod clients;
mod color;
mod color_management;
mod control_center;
mod damage_tracking;
mod duration;
mod generate;
@ -97,6 +98,8 @@ pub enum Cmd {
Clients(ClientsArgs),
/// Inspect the surface tree.
Tree(TreeArgs),
/// Opens the control center.
ControlCenter,
/// Prints the Jay version and exits.
Version,
/// Prints the Jay PID and exits.
@ -243,5 +246,6 @@ pub fn main() {
#[cfg(feature = "it")]
Cmd::RunTests => crate::it::run_tests(),
Cmd::Reexec(a) => reexec::main(cli.global, a),
Cmd::ControlCenter => control_center::main(cli.global),
}
}

32
src/cli/control_center.rs Normal file
View file

@ -0,0 +1,32 @@
use {
crate::{
cli::GlobalArgs,
tools::tool_client::{Handle, ToolClient, with_tool_client},
wire::{jay_compositor, jay_open_control_center_request},
},
std::rc::Rc,
};
pub fn main(global: GlobalArgs) {
with_tool_client(global.log_level, |tc| async move {
let cc = ControlCenter { tc: tc.clone() };
cc.run().await;
});
}
struct ControlCenter {
tc: Rc<ToolClient>,
}
impl ControlCenter {
async fn run(self) {
let tc = &self.tc;
let comp = tc.jay_compositor().await;
let id = tc.id();
tc.send(jay_compositor::OpenControlCenter { self_id: comp, id });
jay_open_control_center_request::Failed::handle(&tc, id, (), |_, ev| {
fatal!("Could not open the control center: {}", ev.msg);
});
tc.round_trip().await;
}
}

View file

@ -14,6 +14,7 @@ use {
clientmem::{self, ClientMemError},
cmm::{cmm_manager::ColorManager, cmm_primaries::Primaries},
config::ConfigProxy,
control_center::redraw_control_centers,
copy_device::CopyDeviceRegistry,
cpu_worker::{CpuWorker, CpuWorkerError},
criteria::{
@ -393,6 +394,7 @@ fn start_compositor2(
lazy_event_sources: Default::default(),
bo_drop_queue: Rc::new(ObjectDropQueue::new(&ring)),
egg_state: Default::default(),
control_centers: Default::default(),
});
state.tracker.register(ClientId::from_raw(0));
create_dummy_output(&state);
@ -417,6 +419,7 @@ fn start_compositor2(
let _compositor = engine.spawn("compositor", start_compositor3(state.clone(), test_future));
ring.run()?;
state.clear();
engine.clear();
Ok(())
}
@ -597,6 +600,10 @@ fn start_global_event_handlers(state: &Rc<State>) -> Vec<SpawnedFuture<()>> {
"lazy event sources",
handle_lazy_event_sources(state.clone()),
),
eng.spawn(
"redraw control centers",
redraw_control_centers(state.clone()),
),
]
}

View file

@ -1811,6 +1811,12 @@ impl ConfigProxyHandler {
self.state.set_egui_fonts(proportional, monospace);
}
fn handle_open_control_center(&self) {
if let Err(e) = self.state.open_control_center() {
log::error!("Could not open control center: {}", ErrorFmt(e));
}
}
fn handle_set_log_level(&self, level: ConfigLogLevel) {
self.state.set_log_level(level.into());
}
@ -3319,6 +3325,7 @@ impl ConfigProxyHandler {
proportional,
monospace,
} => self.handle_set_egui_fonts(proportional, monospace),
ClientMessage::OpenControlCenter => self.handle_open_control_center(),
}
Ok(())
}

606
src/control_center.rs Normal file
View file

@ -0,0 +1,606 @@
use {
crate::{
egui_adapter::egui_platform::{
EggError, EggWindow, EggWindowOwner,
icons::{ICON_CLOSE, ICON_DRAG_INDICATOR, ICON_INFO},
},
macros::Bitflag,
state::State,
utils::{
asyncevent::AsyncEvent, copyhashmap::CopyHashMap, numcell::NumCell,
static_text::StaticText,
},
},
egui::{
Align, CentralPanel, Checkbox, Color32, ComboBox, Context, CursorIcon, DragValue, Frame,
Grid, InnerResponse, Label, Layout, Response, Rgba, RichText, ScrollArea, Sense, SidePanel,
Stroke, TextBuffer, TextEdit, Ui, UiBuilder, Visuals, Widget, WidgetText, emath::Numeric,
vec2,
},
egui_tiles::{ResizeState, TabState, Tile, TileId, Tiles, Tree},
linearize::{Linearize, LinearizeExt},
std::{
cell::RefCell,
hash::Hash,
mem,
ops::{Deref, DerefMut, RangeInclusive},
rc::Rc,
},
thiserror::Error,
};
mod cc_sidebar;
#[derive(Debug, Error)]
pub enum ControlCenterError {
#[error("Could not get the egg context")]
GetEggContext(#[source] EggError),
}
linear_ids!(ControlCenterIds, ControlCenterId, u64);
pub async fn redraw_control_centers(state: Rc<State>) {
let cc = &state.control_centers;
loop {
cc.redraw.triggered().await;
let interests = cc.change.take();
for cc in cc.control_centers.lock().values() {
if cc.inner.interests.interests.get().intersects(interests) {
cc.inner.window.request_redraw();
}
}
}
}
#[derive(Default)]
pub struct ControlCenters {
ids: ControlCenterIds,
change: NumCell<ControlCenterInterest>,
redraw: AsyncEvent,
control_centers: CopyHashMap<ControlCenterId, Rc<ControlCenter>>,
}
bitflags! {
ControlCenterInterest: u32;
_UNUSED,
}
pub struct ControlCenter {
inner: Rc<ControlCenterInner>,
}
linear_ids!(PaneIds, PaneId, u64);
struct ControlCenterInner {
id: ControlCenterId,
state: Rc<State>,
tree: RefCell<Settings>,
window: Rc<EggWindow>,
pane_ids: PaneIds,
interests: Rc<Interests>,
}
#[derive(Default)]
struct Interests {
interests: NumCell<ControlCenterInterest>,
interests_array: [NumCell<u64>; <ControlCenterInterest as Bitflag>::Type::BITS as usize],
}
struct Settings {
tree: Tree<Pane>,
}
struct Pane {
id: PaneId,
ps: PaneState,
own_interests: ControlCenterInterest,
cc_interests: Rc<Interests>,
ty: PaneType,
}
struct PaneState {
errors: Vec<String>,
}
enum PaneType {}
struct CcBehavior<'a> {
#[expect(dead_code)]
cc: &'a Rc<ControlCenterInner>,
close: Option<TileId>,
open: Option<PaneType>,
}
impl ControlCenters {
pub fn clear(&self) {
self.control_centers.clear();
}
}
impl Pane {
fn title(&self, _res: &mut String) {
match self.ty {}
}
fn show(&mut self, _behavior: &mut CcBehavior<'_>, _ui: &mut Ui) {
match self.ty {}
}
}
impl PaneType {
fn interest(&self) -> ControlCenterInterest {
match *self {}
}
}
impl egui_tiles::Behavior<Pane> for CcBehavior<'_> {
fn pane_ui(&mut self, ui: &mut Ui, tile_id: TileId, pane: &mut Pane) -> egui_tiles::UiResponse {
let mut drag = false;
Frame::central_panel(ui.style()).show(ui, |ui| {
ui.horizontal(|ui| {
drag = ui
.add(icon_label(ICON_DRAG_INDICATOR).sense(Sense::drag()))
.total_drag_delta()
.map(|d| d.length() >= 5.0)
.unwrap_or(false);
let mut title = String::new();
pane.title(&mut title);
if ui
.add(icon_label(&title).sense(Sense::click()))
.middle_clicked()
{
self.close = Some(tile_id);
}
if ui
.add(icon_label(ICON_CLOSE).sense(Sense::click()))
.clicked()
{
self.close = Some(tile_id);
}
});
ui.separator();
show_errors(ui, &mut pane.ps);
ui.scope_builder(UiBuilder::new().id(("pane", pane.id)), |ui| {
ScrollArea::vertical().show(ui, |ui| {
ui.allocate_space(vec2(ui.available_width(), 0.0));
pane.show(self, ui);
});
});
});
if drag {
egui_tiles::UiResponse::DragStarted
} else {
egui_tiles::UiResponse::None
}
}
fn tab_title_for_pane(&mut self, _pane: &Pane) -> WidgetText {
"".into()
}
fn tab_hover_cursor_icon(&self) -> CursorIcon {
CursorIcon::Default
}
fn tab_title_for_tile(&mut self, tiles: &Tiles<Pane>, tile_id: TileId) -> WidgetText {
fn add_title(tiles: &Tiles<Pane>, res: &mut String, first: &mut bool, tile_id: TileId) {
if !mem::take(first) {
res.push_str("/");
}
let Some(tile) = tiles.get(tile_id) else {
res.push_str("MISSING TILE");
return;
};
match tile {
Tile::Pane(p) => p.title(res),
Tile::Container(c) => {
let mut first = true;
for &tile_id in c.children() {
add_title(tiles, res, &mut first, tile_id);
}
}
}
}
let mut res = String::new();
let mut first = true;
add_title(tiles, &mut res, &mut first, tile_id);
res.into()
}
fn on_tab_button(
&mut self,
_tiles: &Tiles<Pane>,
tile_id: TileId,
button_response: Response,
) -> Response {
if button_response.middle_clicked() {
self.close = Some(tile_id);
}
button_response
}
fn resize_stroke(&self, style: &egui::Style, resize_state: ResizeState) -> Stroke {
match resize_state {
ResizeState::Idle => style.visuals.widgets.noninteractive.bg_stroke,
ResizeState::Hovering => style.visuals.widgets.hovered.fg_stroke,
ResizeState::Dragging => style.visuals.widgets.active.fg_stroke,
}
}
fn tab_bar_color(&self, visuals: &Visuals) -> Color32 {
(Rgba::from(visuals.panel_fill) * Rgba::from_gray(0.8)).into()
}
fn tab_bg_color(
&self,
visuals: &Visuals,
_tiles: &Tiles<Pane>,
_tile_id: TileId,
state: &TabState,
) -> Color32 {
match state.active {
true => visuals.panel_fill,
false => self.tab_bar_color(visuals),
}
}
}
impl EggWindowOwner for ControlCenterInner {
fn close(&self) {
self.close();
}
fn render(self: Rc<Self>, ctx: &Context) {
let settings = &mut *self.tree.borrow_mut();
SidePanel::left("sidebar").show(ctx, |ui| self.show_sidebar(&mut settings.tree, ui));
CentralPanel::default()
.frame(
Frame::central_panel(&ctx.style())
.outer_margin(0.0)
.inner_margin(0.0),
)
.show(ctx, |ui| {
let tree = &mut settings.tree;
let mut behavior = CcBehavior {
cc: &self,
close: Default::default(),
open: Default::default(),
};
tree.ui(&mut behavior, ui);
if let Some(close) = behavior.close {
tree.set_visible(close, false);
tree.remove_recursively(close);
ui.ctx().request_repaint();
}
if let Some(ty) = behavior.open {
self.open(tree, ty);
ui.ctx().request_repaint();
}
});
}
}
impl State {
pub fn open_control_center(self: &Rc<Self>) -> Result<Rc<ControlCenter>, ControlCenterError> {
let ctx = self
.get_egg_context()
.map_err(ControlCenterError::GetEggContext)?;
let window = ctx.create_window("Control Center");
let cc = Rc::new(ControlCenter {
inner: Rc::new(ControlCenterInner {
id: self.control_centers.ids.next(),
window,
state: self.clone(),
tree: RefCell::new(Settings {
tree: Tree::new_tabs("abcd", vec![]),
}),
pane_ids: Default::default(),
interests: Default::default(),
}),
});
cc.inner.window.set_owner(Some(cc.inner.clone()));
self.control_centers
.control_centers
.set(cc.inner.id, cc.clone());
Ok(cc)
}
#[expect(dead_code)]
pub fn trigger_cci(&self, cci: ControlCenterInterest) {
self.control_centers.change.or_assign(cci);
self.control_centers.redraw.trigger();
}
}
impl ControlCenterInner {
fn close(&self) {
self.window.set_owner(None);
self.tree.borrow_mut().tree = Tree::empty("");
self.state.control_centers.control_centers.remove(&self.id);
}
}
impl Drop for ControlCenter {
fn drop(&mut self) {
self.inner.close();
}
}
impl ControlCenterInner {
fn create_pane(&self, ty: PaneType) -> Pane {
let pane = Pane {
id: self.pane_ids.next(),
ps: PaneState {
errors: Default::default(),
},
own_interests: ty.interest(),
cc_interests: self.interests.clone(),
ty,
};
let own = pane.own_interests;
for (idx, v) in pane.cc_interests.interests_array.iter().enumerate() {
let interest = ControlCenterInterest(1 << idx);
if own.intersects(interest) && v.fetch_add(1) == 0 {
pane.cc_interests.interests.or_assign(interest);
}
}
pane
}
fn open(&self, tree: &mut Tree<Pane>, ty: PaneType) {
let _ = tree;
#[expect(unused_variables, unreachable_code)]
let pane = self.create_pane(ty);
let id = tree.tiles.insert_pane(pane);
if let Some(root) = tree.root
&& let Some(tile) = tree.tiles.get_mut(root)
{
match tile {
Tile::Container(c) => {
c.add_child(id);
}
Tile::Pane(_) => {
let root = tree.tiles.insert_tab_tile(vec![root, id]);
tree.root = Some(root);
}
}
} else {
tree.root = Some(id);
}
tree.make_active(|t, _| t == id);
}
}
impl Drop for Pane {
fn drop(&mut self) {
let own = self.own_interests;
for (idx, v) in self.cc_interests.interests_array.iter().enumerate() {
let interest = ControlCenterInterest(1 << idx);
if own.intersects(interest) && v.fetch_sub(1) == 1 {
self.cc_interests.interests.and_assign(!interest);
}
}
}
}
fn icon_label(icon: &str) -> Label {
Label::new(icon).selectable(false)
}
#[expect(dead_code)]
fn grid_label(ui: &mut Ui, label: &str) {
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
ui.label(label);
});
}
fn grid_label_ui<R>(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
ui.with_layout(Layout::right_to_left(Align::Center), add_contents)
}
#[expect(dead_code)]
fn tip(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui)) {
icon_label(ICON_INFO).ui(ui).on_hover_ui(add_contents);
}
#[expect(dead_code)]
fn text_edit(ui: &mut Ui, v: &mut dyn TextBuffer) -> Response {
TextEdit::singleline(v)
.clip_text(false)
.min_size(vec2(200.0, 0.0))
.ui(ui)
}
fn show_errors(ui: &mut Ui, pane: &mut PaneState) {
if pane.errors.is_empty() {
return;
}
let mut to_remove = None;
for (idx, e) in pane.errors.iter().enumerate() {
ui.horizontal(|ui| {
Frame::new().inner_margin(5.0).show(ui, |ui| {
if ui.button(ICON_CLOSE).clicked() {
to_remove = Some(idx);
}
ui.label(
RichText::new("Error:")
.strong()
.color(ui.style().visuals.error_fg_color),
);
ui.add(Label::new(e).wrap());
});
});
}
if let Some(idx) = to_remove {
pane.errors.remove(idx);
ui.ctx().request_repaint();
}
ui.separator();
}
#[expect(dead_code)]
fn grid<R>(
ui: &mut Ui,
id_salt: impl Hash,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
let mut spacing = ui.spacing().item_spacing;
spacing.x *= 3.0;
Grid::new(id_salt).spacing(spacing).show(ui, add_contents)
}
fn row<R>(ui: &mut Ui, name: &str, add_contents: impl FnOnce(&mut Ui) -> R) -> R {
row_ui(ui, name, |_| (), add_contents)
}
fn row_ui<R, S>(
ui: &mut Ui,
name: &str,
label: impl FnOnce(&mut Ui) -> S,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> R {
let ui = &mut *ui.row();
grid_label_ui(ui, |ui| {
ui.label(name);
label(ui);
});
add_contents(ui)
}
#[expect(dead_code)]
fn bool(ui: &mut Ui, name: &str, old: bool, set: impl FnOnce(bool)) {
bool_ui(ui, name, |_| (), old, set);
}
fn bool_ui<R>(
ui: &mut Ui,
name: &str,
label: impl FnOnce(&mut Ui) -> R,
mut v: bool,
set: impl FnOnce(bool),
) {
row_ui(ui, name, label, |ui| {
if Checkbox::without_text(&mut v).ui(ui).changed() {
set(v);
}
});
}
#[expect(dead_code)]
fn read_only_bool(ui: &mut Ui, name: &str, old: bool) {
read_only_bool_ui(ui, name, |_| (), old);
}
fn read_only_bool_ui<R>(ui: &mut Ui, name: &str, label: impl FnOnce(&mut Ui) -> R, mut v: bool) {
row_ui(ui, name, label, |ui| {
ui.add_enabled_ui(false, |ui| Checkbox::without_text(&mut v).ui(ui));
});
}
#[expect(dead_code)]
fn combo_box<T>(ui: &mut Ui, name: &str, old: T, set: impl FnOnce(T))
where
T: StaticText + Linearize + PartialEq + Copy,
{
combo_box_ui(ui, name, |_| (), old, set);
}
fn combo_box_ui<R, T>(
ui: &mut Ui,
name: &str,
label: impl FnOnce(&mut Ui) -> R,
mut v: T,
set: impl FnOnce(T),
) where
T: StaticText + Linearize + PartialEq + Copy,
{
row_ui(ui, name, label, |ui| {
let old = v;
ComboBox::from_id_salt(name)
.selected_text(v.text())
.show_ui(ui, |ui| {
for s in T::variants() {
ui.selectable_value(&mut v, s, s.text());
}
});
if old != v {
set(v);
}
});
}
#[expect(dead_code)]
fn drag_value<N>(
ui: &mut Ui,
name: &str,
old: N,
range: RangeInclusive<N>,
speed: f64,
set: impl FnOnce(N),
) where
N: Numeric,
{
drag_value_ui(ui, name, |_| (), old, range, speed, set);
}
fn drag_value_ui<R, N>(
ui: &mut Ui,
name: &str,
label: impl FnOnce(&mut Ui) -> R,
mut v: N,
range: RangeInclusive<N>,
speed: f64,
set: impl FnOnce(N),
) where
N: Numeric,
{
row_ui(ui, name, label, |ui| {
if DragValue::new(&mut v)
.range(range)
.speed(speed)
.ui(ui)
.changed()
{
set(v);
}
});
}
#[expect(dead_code)]
fn label(ui: &mut Ui, name: &str, text: impl Into<WidgetText>) {
row(ui, name, |ui| ui.label(text));
}
trait GridExt {
fn row(&mut self) -> impl DerefMut<Target = Ui>;
}
impl GridExt for Ui {
fn row(&mut self) -> impl DerefMut<Target = Ui> {
GridRow { ui: self }
}
}
struct GridRow<'a> {
ui: &'a mut Ui,
}
impl Deref for GridRow<'_> {
type Target = Ui;
fn deref(&self) -> &Self::Target {
self.ui
}
}
impl DerefMut for GridRow<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut *self.ui
}
}
impl Drop for GridRow<'_> {
fn drop(&mut self) {
self.end_row();
}
}

View file

@ -0,0 +1,48 @@
use {
crate::control_center::{ControlCenterInner, Pane},
egui::{Align, Layout, ScrollArea, Ui, ViewportCommand},
egui_tiles::Tree,
linearize::{Linearize, LinearizeExt},
std::{rc::Rc, sync::LazyLock},
};
#[derive(Copy, Clone, Linearize)]
enum PaneName {}
impl PaneName {
fn name(self) -> &'static str {
match self {}
}
}
static TYPES: LazyLock<Vec<PaneName>> = LazyLock::new(|| {
let mut res: Vec<_> = PaneName::variants().collect();
res.sort_by_key(|t| t.name());
res
});
impl ControlCenterInner {
pub fn show_sidebar(self: &Rc<Self>, tree: &mut Tree<Pane>, ui: &mut Ui) {
ui.with_layout(
Layout::top_down(Align::Center).with_cross_justify(true),
|ui| {
ui.add_space(6.0);
if ui.button("Close").clicked() {
ui.ctx().send_viewport_cmd(ViewportCommand::Close);
}
ui.separator();
ScrollArea::vertical().show(ui, |ui| {
for &ty in &*TYPES {
if ui.button(ty.name()).clicked() {
let _ty = match ty {};
#[expect(unreachable_code)]
self.open(tree, _ty);
ui.ctx().request_repaint();
}
}
ui.add_space(3.0);
})
},
);
}
}

View file

@ -112,11 +112,8 @@ pub enum EggError {
pub mod icons {
#[expect(dead_code)]
pub const ICON_ADD: &str = "\u{e145}";
#[expect(dead_code)]
pub const ICON_CLOSE: &str = "\u{e5cd}";
#[expect(dead_code)]
pub const ICON_DRAG_INDICATOR: &str = "\u{e945}";
#[expect(dead_code)]
pub const ICON_INFO: &str = "\u{e88e}";
#[expect(dead_code)]
pub const ICON_OPEN_IN_NEW: &str = "\u{e89e}";
@ -361,7 +358,6 @@ impl EggState {
}
impl State {
#[expect(dead_code)]
pub fn get_egg_context(self: &Rc<Self>) -> Result<Rc<EggContext>, EggError> {
if let Some(ctx) = self.egg_state.ctx.get() {
return Ok(ctx);
@ -467,7 +463,6 @@ impl State {
}
impl EggContext {
#[expect(dead_code)]
pub fn create_window(self: &Rc<Self>, title: &str) -> Rc<EggWindow> {
let i = &self.inner;
let wl_surface = i.wl_compositor.create_surface();
@ -628,12 +623,10 @@ impl EggSeatInner {
}
impl EggWindow {
#[expect(dead_code)]
pub fn request_redraw(&self) {
self.inner.want_frame();
}
#[expect(dead_code)]
pub fn set_owner(&self, owner: Option<Rc<dyn EggWindowOwner>>) {
self.inner.owner.set(owner);
}

View file

@ -21,6 +21,7 @@ pub mod jay_ei_session_builder;
pub mod jay_idle;
pub mod jay_input;
pub mod jay_log_file;
pub mod jay_open_control_center_request;
pub mod jay_output;
pub mod jay_pointer;
pub mod jay_popup_ext_manager_v1;

View file

@ -11,6 +11,7 @@ use {
jay_idle::JayIdle,
jay_input::JayInput,
jay_log_file::JayLogFile,
jay_open_control_center_request::JayOpenControlCenterRequest,
jay_output::JayOutput,
jay_pointer::JayPointer,
jay_randr::JayRandr,
@ -77,7 +78,7 @@ global_base!(JayCompositorGlobal, JayCompositor, JayCompositorError);
impl Global for JayCompositorGlobal {
fn version(&self) -> u32 {
27
28
}
fn required_caps(&self) -> ClientCaps {
@ -541,6 +542,25 @@ impl JayCompositorRequestHandler for JayCompositor {
});
Ok(())
}
fn open_control_center(
&self,
req: OpenControlCenter,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
let obj = Rc::new(JayOpenControlCenterRequest {
id: req.id,
client: self.client.clone(),
tracker: Default::default(),
version: self.version,
});
track!(self.client, obj);
self.client.add_client_obj(&obj)?;
if let Err(e) = self.client.state.open_control_center() {
obj.send_failed(e);
}
Ok(())
}
}
object_base! {

View file

@ -0,0 +1,53 @@
use {
crate::{
client::{Client, ClientError},
leaks::Tracker,
object::{Object, Version},
utils::errorfmt::ErrorFmt,
wire::{JayOpenControlCenterRequestId, jay_open_control_center_request::*},
},
std::{error::Error, rc::Rc},
thiserror::Error,
};
pub struct JayOpenControlCenterRequest {
pub id: JayOpenControlCenterRequestId,
pub client: Rc<Client>,
pub tracker: Tracker<Self>,
pub version: Version,
}
impl JayOpenControlCenterRequest {
pub fn send_failed(&self, err: impl Error) {
let msg = &ErrorFmt(err).to_string();
self.client.event(Failed {
self_id: self.id,
msg,
});
}
}
impl JayOpenControlCenterRequestRequestHandler for JayOpenControlCenterRequest {
type Error = JayOpenControlCenterRequestError;
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.client.remove_obj(self)?;
Ok(())
}
}
object_base! {
self = JayOpenControlCenterRequest;
version = self.version;
}
impl Object for JayOpenControlCenterRequest {}
simple_add_obj!(JayOpenControlCenterRequest);
#[derive(Debug, Error)]
pub enum JayOpenControlCenterRequestError {
#[error(transparent)]
ClientError(Box<ClientError>),
}
efrom!(JayOpenControlCenterRequestError, ClientError);

View file

@ -58,6 +58,7 @@ mod clientmem;
mod cmm;
mod compositor;
mod config;
mod control_center;
mod copy_device;
mod cpu_worker;
mod criteria;

View file

@ -16,6 +16,7 @@ use {
cmm::{cmm_description::ColorDescription, cmm_manager::ColorManager},
compositor::{LIBEI_SOCKET, LogLevel},
config::ConfigProxy,
control_center::ControlCenters,
copy_device::CopyDeviceRegistry,
cpu_worker::CpuWorker,
criteria::{clm::ClMatcherManager, tlm::TlMatcherManager},
@ -297,6 +298,7 @@ pub struct State {
pub lazy_event_sources: Rc<LazyEventSources>,
pub bo_drop_queue: Rc<ObjectDropQueue<Rc<dyn BufferObject>>>,
pub egg_state: EggState,
pub control_centers: ControlCenters,
}
// impl Drop for State {
@ -1162,6 +1164,7 @@ impl State {
self.lazy_event_sources.clear();
self.bo_drop_queue.kill();
self.egg_state.clear();
self.control_centers.clear();
}
pub fn remove_toplevel_id(&self, id: ToplevelIdentifier) {

View file

@ -334,7 +334,7 @@ impl ToolClient {
self_id: s.registry,
name: s.jay_compositor.0,
interface: JayCompositor.name(),
version: s.jay_compositor.1.min(27),
version: s.jay_compositor.1.min(28),
id: id.into(),
});
self.jay_compositor.set(Some(id));

View file

@ -100,6 +100,14 @@ impl<T> NumCell<T> {
{
!self.is_zero()
}
#[inline(always)]
pub fn take(&self) -> T
where
T: Default,
{
self.t.replace(T::default())
}
}
impl<T: BitOr<Output = T> + Copy> BitOr<T> for &'_ NumCell<T> {