control-center: add clients pane
This commit is contained in:
parent
78d59927ee
commit
aefd1fbbdb
6 changed files with 644 additions and 7 deletions
|
|
@ -1,9 +1,15 @@
|
|||
use {
|
||||
crate::{
|
||||
control_center::{
|
||||
cc_color_management::ColorManagementPane, cc_compositor::CompositorPane,
|
||||
cc_gpus::GpusPane, cc_idle::IdlePane, cc_input::InputPane,
|
||||
cc_look_and_feel::LookAndFeelPane, cc_outputs::OutputsPane, cc_xwayland::XwaylandPane,
|
||||
cc_clients::{ClientPane, ClientsPane},
|
||||
cc_color_management::ColorManagementPane,
|
||||
cc_compositor::CompositorPane,
|
||||
cc_gpus::GpusPane,
|
||||
cc_idle::IdlePane,
|
||||
cc_input::InputPane,
|
||||
cc_look_and_feel::LookAndFeelPane,
|
||||
cc_outputs::OutputsPane,
|
||||
cc_xwayland::XwaylandPane,
|
||||
},
|
||||
egui_adapter::egui_platform::{
|
||||
EggError, EggWindow, EggWindowOwner,
|
||||
|
|
@ -34,8 +40,10 @@ use {
|
|||
thiserror::Error,
|
||||
};
|
||||
|
||||
mod cc_clients;
|
||||
mod cc_color_management;
|
||||
mod cc_compositor;
|
||||
mod cc_criterion;
|
||||
mod cc_gpus;
|
||||
mod cc_idle;
|
||||
mod cc_input;
|
||||
|
|
@ -131,10 +139,11 @@ enum PaneType {
|
|||
GPUs(GpusPane),
|
||||
Input(InputPane),
|
||||
LookAndFeel(LookAndFeelPane),
|
||||
Clients(ClientsPane),
|
||||
Client(ClientPane),
|
||||
}
|
||||
|
||||
struct CcBehavior<'a> {
|
||||
#[expect(dead_code)]
|
||||
cc: &'a Rc<ControlCenterInner>,
|
||||
close: Option<TileId>,
|
||||
open: Option<PaneType>,
|
||||
|
|
@ -157,6 +166,8 @@ impl Pane {
|
|||
PaneType::GPUs(v) => v.title(res),
|
||||
PaneType::Input(v) => v.title(res),
|
||||
PaneType::LookAndFeel(v) => v.title(res),
|
||||
PaneType::Clients(v) => v.title(res),
|
||||
PaneType::Client(v) => v.title(res),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -170,6 +181,8 @@ impl Pane {
|
|||
PaneType::GPUs(p) => p.show(ui),
|
||||
PaneType::Input(p) => p.show(&mut self.ps, ui),
|
||||
PaneType::LookAndFeel(p) => p.show(ui),
|
||||
PaneType::Clients(p) => p.show(behavior, ui),
|
||||
PaneType::Client(p) => p.show(behavior, ui),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -185,6 +198,8 @@ impl PaneType {
|
|||
PaneType::GPUs(_) => CCI_GPUS,
|
||||
PaneType::Input(_) => CCI_INPUT,
|
||||
PaneType::LookAndFeel(_) => CCI_LOOK_AND_FEEL,
|
||||
PaneType::Clients(_) => ControlCenterInterest::none(),
|
||||
PaneType::Client(_) => ControlCenterInterest::none(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
362
src/control_center/cc_clients.rs
Normal file
362
src/control_center/cc_clients.rs
Normal file
|
|
@ -0,0 +1,362 @@
|
|||
use {
|
||||
crate::{
|
||||
client::{Client, ClientId},
|
||||
control_center::{
|
||||
CcBehavior, ControlCenterInner, PaneType,
|
||||
cc_criterion::{CcCriterion, CritImpl, CritRegex},
|
||||
grid, icon_label, label, read_only_bool,
|
||||
},
|
||||
criteria::{CritMgrExt, CritUpstreamNode, crit_leaf::CritLeafMatcher},
|
||||
egui_adapter::egui_platform::icons::ICON_OPEN_IN_NEW,
|
||||
state::State,
|
||||
utils::{copyhashmap::CopyHashMap, static_text::StaticText},
|
||||
},
|
||||
egui::{CollapsingHeader, DragValue, Sense, TextFormat, Ui, Widget, text::LayoutJob},
|
||||
linearize::Linearize,
|
||||
std::rc::{Rc, Weak},
|
||||
};
|
||||
|
||||
pub enum ClientCrit {
|
||||
SandboxEngine(CritRegex),
|
||||
SandboxAppId(CritRegex),
|
||||
SandboxInstanceId(CritRegex),
|
||||
Sandboxed,
|
||||
Uid(i32),
|
||||
Pid(i32),
|
||||
IsXwayland,
|
||||
Comm(CritRegex),
|
||||
Exe(CritRegex),
|
||||
Tag(CritRegex),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Linearize)]
|
||||
pub enum ClientCritTy {
|
||||
SandboxEngine,
|
||||
SandboxAppId,
|
||||
SandboxInstanceId,
|
||||
Sandboxed,
|
||||
Uid,
|
||||
Pid,
|
||||
IsXwayland,
|
||||
Comm,
|
||||
Exe,
|
||||
Tag,
|
||||
}
|
||||
|
||||
impl Default for ClientCrit {
|
||||
fn default() -> Self {
|
||||
ClientCrit::Comm(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl StaticText for ClientCritTy {
|
||||
fn text(&self) -> &'static str {
|
||||
match self {
|
||||
ClientCritTy::SandboxEngine => "Sandbox Engine",
|
||||
ClientCritTy::SandboxAppId => "Sandbox App ID",
|
||||
ClientCritTy::SandboxInstanceId => "Sandbox Instance ID",
|
||||
ClientCritTy::Sandboxed => "Sandboxed",
|
||||
ClientCritTy::Uid => "UID",
|
||||
ClientCritTy::Pid => "PID",
|
||||
ClientCritTy::IsXwayland => "Is Xwayland",
|
||||
ClientCritTy::Comm => "Comm",
|
||||
ClientCritTy::Exe => "Exe",
|
||||
ClientCritTy::Tag => "Tag",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CritImpl for ClientCrit {
|
||||
type Type = ClientCritTy;
|
||||
type Target = Rc<Client>;
|
||||
|
||||
fn ty(&self) -> Self::Type {
|
||||
macro_rules! map {
|
||||
($($n:ident,)*) => {
|
||||
match self {
|
||||
$(
|
||||
Self::$n { .. } => ClientCritTy::$n,
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
map! {
|
||||
SandboxEngine,
|
||||
SandboxAppId,
|
||||
SandboxInstanceId,
|
||||
Sandboxed,
|
||||
Uid,
|
||||
Pid,
|
||||
IsXwayland,
|
||||
Comm,
|
||||
Exe,
|
||||
Tag,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_ty(ty: Self::Type) -> Self {
|
||||
match ty {
|
||||
ClientCritTy::SandboxEngine => Self::SandboxEngine(Default::default()),
|
||||
ClientCritTy::SandboxAppId => Self::SandboxAppId(Default::default()),
|
||||
ClientCritTy::SandboxInstanceId => Self::SandboxInstanceId(Default::default()),
|
||||
ClientCritTy::Sandboxed => Self::Sandboxed,
|
||||
ClientCritTy::Uid => Self::Uid(Default::default()),
|
||||
ClientCritTy::Pid => Self::Pid(Default::default()),
|
||||
ClientCritTy::IsXwayland => Self::IsXwayland,
|
||||
ClientCritTy::Comm => Self::Comm(Default::default()),
|
||||
ClientCritTy::Exe => Self::Exe(Default::default()),
|
||||
ClientCritTy::Tag => Self::Tag(Default::default()),
|
||||
}
|
||||
}
|
||||
|
||||
fn show(&mut self, ui: &mut Ui) -> bool {
|
||||
match self {
|
||||
ClientCrit::SandboxEngine(v) => v.show(ui),
|
||||
ClientCrit::SandboxAppId(v) => v.show(ui),
|
||||
ClientCrit::SandboxInstanceId(v) => v.show(ui),
|
||||
ClientCrit::Sandboxed => false,
|
||||
ClientCrit::Uid(v) => DragValue::new(v).ui(ui).changed(),
|
||||
ClientCrit::Pid(v) => DragValue::new(v).ui(ui).changed(),
|
||||
ClientCrit::IsXwayland => false,
|
||||
ClientCrit::Comm(v) => v.show(ui),
|
||||
ClientCrit::Exe(v) => v.show(ui),
|
||||
ClientCrit::Tag(v) => v.show(ui),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_crit(&self, state: &Rc<State>) -> Option<Rc<dyn CritUpstreamNode<Self::Target>>> {
|
||||
let m = &state.cl_matcher_manager;
|
||||
let res = match self {
|
||||
ClientCrit::SandboxEngine(v) => m.sandbox_engine(v.to_crit()?),
|
||||
ClientCrit::SandboxAppId(v) => m.sandbox_app_id(v.to_crit()?),
|
||||
ClientCrit::SandboxInstanceId(v) => m.sandbox_instance_id(v.to_crit()?),
|
||||
ClientCrit::Sandboxed => m.sandboxed(),
|
||||
ClientCrit::Uid(v) => m.uid(*v),
|
||||
ClientCrit::Pid(v) => m.pid(*v),
|
||||
ClientCrit::IsXwayland => m.is_xwayland(),
|
||||
ClientCrit::Comm(v) => m.comm(v.to_crit()?),
|
||||
ClientCrit::Exe(v) => m.exe(v.to_crit()?),
|
||||
ClientCrit::Tag(v) => m.tag(v.to_crit()?),
|
||||
};
|
||||
Some(res)
|
||||
}
|
||||
|
||||
fn not(
|
||||
state: &State,
|
||||
upstream: &Rc<dyn CritUpstreamNode<Self::Target>>,
|
||||
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
|
||||
state.cl_matcher_manager.not(upstream)
|
||||
}
|
||||
|
||||
fn list(
|
||||
state: &State,
|
||||
upstream: &[Rc<dyn CritUpstreamNode<Self::Target>>],
|
||||
all: bool,
|
||||
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
|
||||
state.cl_matcher_manager.list(upstream, all)
|
||||
}
|
||||
|
||||
fn exactly(
|
||||
state: &State,
|
||||
n: usize,
|
||||
upstream: &[Rc<dyn CritUpstreamNode<Self::Target>>],
|
||||
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
|
||||
state.cl_matcher_manager.exactly(upstream, n)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClientsPane {
|
||||
state: Rc<State>,
|
||||
filter: bool,
|
||||
criterion: CcCriterion<ClientCrit>,
|
||||
matched: Rc<Matched>,
|
||||
leaf: Option<Rc<CritLeafMatcher<Rc<Client>>>>,
|
||||
}
|
||||
|
||||
struct Matched {
|
||||
slf: Weak<ControlCenterInner>,
|
||||
clients: CopyHashMap<ClientId, ()>,
|
||||
}
|
||||
|
||||
impl Matched {
|
||||
fn request_frame(&self) {
|
||||
if let Some(slf) = self.slf.upgrade() {
|
||||
slf.window.request_redraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ControlCenterInner {
|
||||
pub fn create_clients_pane(self: &Rc<Self>) -> ClientsPane {
|
||||
let mut pane = ClientsPane {
|
||||
state: self.state.clone(),
|
||||
filter: false,
|
||||
criterion: Default::default(),
|
||||
matched: Rc::new(Matched {
|
||||
slf: Rc::downgrade(self),
|
||||
clients: Default::default(),
|
||||
}),
|
||||
leaf: Default::default(),
|
||||
};
|
||||
pane.update_matcher();
|
||||
pane
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientsPane {
|
||||
pub fn title(&self, res: &mut String) {
|
||||
res.push_str("Clients");
|
||||
}
|
||||
|
||||
pub fn show(&mut self, behavior: &mut CcBehavior<'_>, ui: &mut Ui) {
|
||||
if ui.checkbox(&mut self.filter, "Filter").changed() && !self.filter {
|
||||
self.criterion = Default::default();
|
||||
self.update_matcher();
|
||||
}
|
||||
let mut clear_clients = false;
|
||||
if self.filter && self.criterion.show(ui) {
|
||||
clear_clients = self.update_matcher();
|
||||
}
|
||||
ui.separator();
|
||||
let mut clients: Vec<_> = self.matched.clients.lock().keys().copied().collect();
|
||||
clients.sort();
|
||||
for id in clients {
|
||||
let Ok(client) = self.state.clients.get(id) else {
|
||||
continue;
|
||||
};
|
||||
show_client_collapsible(behavior, ui, &client);
|
||||
}
|
||||
if clear_clients {
|
||||
self.matched.clients.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn update_matcher(&mut self) -> bool {
|
||||
let mut clear_clients = false;
|
||||
let state = &self.state;
|
||||
if let Some(new) = self.criterion.to_crit(state) {
|
||||
clear_clients = true;
|
||||
let matched = self.matched.clone();
|
||||
let leaf = state.cl_matcher_manager.leaf(&new, move |data| {
|
||||
matched.clients.set(data, ());
|
||||
matched.request_frame();
|
||||
Box::new({
|
||||
let matched = matched.clone();
|
||||
move || {
|
||||
matched.clients.remove(&data);
|
||||
matched.request_frame();
|
||||
}
|
||||
})
|
||||
});
|
||||
state.cl_matcher_manager.rematch_all(state);
|
||||
self.leaf = Some(leaf);
|
||||
}
|
||||
clear_clients
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClientPane {
|
||||
client: Rc<Client>,
|
||||
}
|
||||
|
||||
impl ControlCenterInner {
|
||||
pub fn create_client_pane(self: &Rc<Self>, client: &Rc<Client>) -> ClientPane {
|
||||
ClientPane {
|
||||
client: client.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientPane {
|
||||
pub fn title(&self, res: &mut String) {
|
||||
res.push_str("Client ");
|
||||
res.push_str(&self.client.pid_info.comm);
|
||||
}
|
||||
|
||||
pub fn show(&mut self, behavior: &mut CcBehavior<'_>, ui: &mut Ui) {
|
||||
show_client(behavior, ui, &self.client);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn show_client_collapsible(behavior: &mut CcBehavior, ui: &mut Ui, client: &Rc<Client>) {
|
||||
let mut layout_job = LayoutJob::default();
|
||||
layout_job.append(
|
||||
"Client",
|
||||
0.0,
|
||||
TextFormat {
|
||||
color: ui.style().visuals.widgets.inactive.text_color(),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
layout_job.append(
|
||||
&client.id.to_string(),
|
||||
10.0,
|
||||
TextFormat {
|
||||
color: ui.style().visuals.widgets.active.text_color(),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
layout_job.append(
|
||||
&client.pid_info.comm,
|
||||
10.0,
|
||||
TextFormat {
|
||||
color: ui.style().visuals.widgets.inactive.text_color(),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
CollapsingHeader::new(layout_job)
|
||||
.id_salt(("client", client.id))
|
||||
.show(ui, |ui| {
|
||||
if icon_label(ICON_OPEN_IN_NEW)
|
||||
.sense(Sense::CLICK)
|
||||
.ui(ui)
|
||||
.clicked()
|
||||
{
|
||||
behavior.open = Some(PaneType::Client(behavior.cc.create_client_pane(client)));
|
||||
}
|
||||
show_client(behavior, ui, client)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn show_client(_behavior: &mut CcBehavior<'_>, ui: &mut Ui, client: &Client) {
|
||||
grid(ui, ("client", client.id), |ui| {
|
||||
label(ui, "ID", client.id.to_string());
|
||||
label(ui, "PID", client.pid_info.pid.to_string());
|
||||
label(ui, "UID", client.pid_info.uid.to_string());
|
||||
label(ui, "comm", &client.pid_info.comm);
|
||||
label(ui, "exe", &client.pid_info.exe);
|
||||
if client.acceptor.sandboxed {
|
||||
read_only_bool(ui, "Sandboxed", true);
|
||||
}
|
||||
if client.acceptor.secure {
|
||||
read_only_bool(ui, "Secure", true);
|
||||
}
|
||||
if client.is_xwayland {
|
||||
read_only_bool(ui, "Xwayland", true);
|
||||
}
|
||||
if let Some(v) = &client.acceptor.sandbox_engine {
|
||||
label(ui, "Sandbox Engine", v);
|
||||
}
|
||||
if let Some(v) = &client.acceptor.app_id {
|
||||
label(ui, "App ID", v);
|
||||
}
|
||||
if let Some(v) = &client.acceptor.instance_id {
|
||||
label(ui, "Instance ID", v);
|
||||
}
|
||||
if let Some(v) = &client.acceptor.tag {
|
||||
label(ui, "Tag", v);
|
||||
}
|
||||
});
|
||||
if ui.button("Kill").clicked() {
|
||||
client.state.clients.kill(client.id);
|
||||
}
|
||||
ui.collapsing("Capabilities", |ui| {
|
||||
ui.add_enabled_ui(false, |ui| {
|
||||
for (k, v) in client.effective_caps.get().to_map() {
|
||||
if v {
|
||||
ui.checkbox(&mut true, k.text());
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
254
src/control_center/cc_criterion.rs
Normal file
254
src/control_center/cc_criterion.rs
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
use {
|
||||
crate::{
|
||||
criteria::{CritLiteralOrRegex, CritUpstreamNode},
|
||||
egui_adapter::egui_platform::icons::ICON_CLOSE,
|
||||
state::State,
|
||||
utils::{numcell::NumCell, static_text::StaticText},
|
||||
},
|
||||
ahash::AHashSet,
|
||||
egui::{ComboBox, DragValue, Ui, UiBuilder, Widget},
|
||||
isnt::std_1::collections::IsntHashSetExt,
|
||||
linearize::{Linearize, LinearizeExt},
|
||||
regex::Regex,
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
||||
pub enum CcCriterion<T> {
|
||||
Not(Box<Self>),
|
||||
List(Vec<Self>, bool),
|
||||
Exactly(usize, Vec<Self>),
|
||||
T(T),
|
||||
}
|
||||
|
||||
impl<T> Default for CcCriterion<T>
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::T(T::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Linearize)]
|
||||
enum CompoundCritTy {
|
||||
Not,
|
||||
All,
|
||||
Any,
|
||||
Exactly,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
enum CritTy<T> {
|
||||
Compound(CompoundCritTy),
|
||||
T(T),
|
||||
}
|
||||
|
||||
impl StaticText for CompoundCritTy {
|
||||
fn text(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Not => "Not",
|
||||
Self::All => "All",
|
||||
Self::Any => "Any",
|
||||
Self::Exactly => "Exactly",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> StaticText for CritTy<T>
|
||||
where
|
||||
T: StaticText,
|
||||
{
|
||||
fn text(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Compound(t) => t.text(),
|
||||
Self::T(t) => t.text(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CritImpl: Default {
|
||||
type Type: Copy + Eq + PartialEq + StaticText + Linearize;
|
||||
type Target;
|
||||
|
||||
fn ty(&self) -> Self::Type;
|
||||
fn from_ty(ty: Self::Type) -> Self;
|
||||
#[must_use]
|
||||
fn show(&mut self, ui: &mut Ui) -> bool;
|
||||
|
||||
fn to_crit(&self, state: &Rc<State>) -> Option<Rc<dyn CritUpstreamNode<Self::Target>>>;
|
||||
fn not(
|
||||
state: &State,
|
||||
upstream: &Rc<dyn CritUpstreamNode<Self::Target>>,
|
||||
) -> Rc<dyn CritUpstreamNode<Self::Target>>;
|
||||
fn list(
|
||||
state: &State,
|
||||
upstream: &[Rc<dyn CritUpstreamNode<Self::Target>>],
|
||||
all: bool,
|
||||
) -> Rc<dyn CritUpstreamNode<Self::Target>>;
|
||||
fn exactly(
|
||||
state: &State,
|
||||
n: usize,
|
||||
upstream: &[Rc<dyn CritUpstreamNode<Self::Target>>],
|
||||
) -> Rc<dyn CritUpstreamNode<Self::Target>>;
|
||||
}
|
||||
|
||||
impl<T> CcCriterion<T>
|
||||
where
|
||||
T: CritImpl,
|
||||
{
|
||||
#[must_use]
|
||||
pub fn show(&mut self, ui: &mut Ui) -> bool {
|
||||
let mut changed = false;
|
||||
ui.vertical(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
let mut v = self.ty();
|
||||
let old = v;
|
||||
ComboBox::from_id_salt("ty")
|
||||
.selected_text(v.text())
|
||||
.show_ui(ui, |ui| {
|
||||
for s in CompoundCritTy::variants() {
|
||||
ui.selectable_value(&mut v, CritTy::Compound(s), s.text());
|
||||
}
|
||||
for s in T::Type::variants() {
|
||||
ui.selectable_value(&mut v, CritTy::T(s), s.text());
|
||||
}
|
||||
});
|
||||
if old != v {
|
||||
*self = match v {
|
||||
CritTy::Compound(CompoundCritTy::Not) => {
|
||||
CcCriterion::Not(Default::default())
|
||||
}
|
||||
CritTy::Compound(CompoundCritTy::All) => {
|
||||
CcCriterion::List(Default::default(), true)
|
||||
}
|
||||
CritTy::Compound(CompoundCritTy::Any) => {
|
||||
CcCriterion::List(Default::default(), false)
|
||||
}
|
||||
CritTy::Compound(CompoundCritTy::Exactly) => {
|
||||
CcCriterion::Exactly(1, Default::default())
|
||||
}
|
||||
CritTy::T(t) => CcCriterion::T(T::from_ty(t)),
|
||||
};
|
||||
changed = true;
|
||||
}
|
||||
match self {
|
||||
CcCriterion::Not(n) => changed |= n.show(ui),
|
||||
CcCriterion::List(_, _) => {}
|
||||
CcCriterion::Exactly(n, _) => {
|
||||
changed |= DragValue::new(n).ui(ui).changed();
|
||||
}
|
||||
CcCriterion::T(t) => changed |= t.show(ui),
|
||||
}
|
||||
});
|
||||
match self {
|
||||
CcCriterion::Not(_) => {}
|
||||
CcCriterion::List(v, _) | CcCriterion::Exactly(_, v) => {
|
||||
ui.indent("compound", |ui| {
|
||||
let mut to_remove = AHashSet::new();
|
||||
for (idx, v) in v.iter_mut().enumerate() {
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button(ICON_CLOSE).clicked() {
|
||||
changed = true;
|
||||
to_remove.insert(idx);
|
||||
}
|
||||
ui.scope_builder(UiBuilder::new().id_salt(idx), |ui| {
|
||||
changed |= v.show(ui);
|
||||
});
|
||||
});
|
||||
}
|
||||
let i = NumCell::new(0);
|
||||
v.retain(|_| to_remove.not_contains(&i.fetch_add(1)));
|
||||
if ui.button("Add").clicked() {
|
||||
v.push(CcCriterion::default());
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
CcCriterion::T(_) => {}
|
||||
}
|
||||
});
|
||||
changed
|
||||
}
|
||||
|
||||
fn ty(&self) -> CritTy<T::Type> {
|
||||
match self {
|
||||
CcCriterion::Not(_) => CritTy::Compound(CompoundCritTy::Not),
|
||||
CcCriterion::List(_, true) => CritTy::Compound(CompoundCritTy::All),
|
||||
CcCriterion::List(_, false) => CritTy::Compound(CompoundCritTy::Any),
|
||||
CcCriterion::Exactly(_, _) => CritTy::Compound(CompoundCritTy::Exactly),
|
||||
CcCriterion::T(t) => CritTy::T(t.ty()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_crit(&self, state: &Rc<State>) -> Option<Rc<dyn CritUpstreamNode<T::Target>>> {
|
||||
match self {
|
||||
CcCriterion::Not(t) => Some(T::not(state, &t.to_crit(state)?)),
|
||||
CcCriterion::List(v, all) => {
|
||||
let mut upstream = Vec::with_capacity(v.len());
|
||||
for v in v {
|
||||
upstream.push(v.to_crit(state)?);
|
||||
}
|
||||
Some(T::list(state, &upstream, *all))
|
||||
}
|
||||
CcCriterion::Exactly(n, v) => {
|
||||
let mut upstream = Vec::with_capacity(v.len());
|
||||
for v in v {
|
||||
upstream.push(v.to_crit(state)?);
|
||||
}
|
||||
Some(T::exactly(state, *n, &upstream))
|
||||
}
|
||||
CcCriterion::T(t) => t.to_crit(state),
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
pub fn any(&self, mut any: impl FnMut(&T) -> bool) -> bool {
|
||||
self.any_(&mut any)
|
||||
}
|
||||
|
||||
fn any_(&self, any: &mut impl FnMut(&T) -> bool) -> bool {
|
||||
match self {
|
||||
CcCriterion::Not(v) => v.any_(any),
|
||||
CcCriterion::List(v, _) => v.iter().any(|v| v.any_(any)),
|
||||
CcCriterion::Exactly(_, v) => v.iter().any(|v| v.any_(any)),
|
||||
CcCriterion::T(t) => any(t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CritRegex {
|
||||
pub text: String,
|
||||
pub regex: Option<Option<Regex>>,
|
||||
}
|
||||
|
||||
impl Default for CritRegex {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
text: Default::default(),
|
||||
regex: Some(Some(Regex::new("").unwrap())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CritRegex {
|
||||
pub fn show(&mut self, ui: &mut Ui) -> bool {
|
||||
let mut is_regex = self.regex.is_some();
|
||||
let mut changed = false;
|
||||
changed |= ui.text_edit_singleline(&mut self.text).changed();
|
||||
changed |= ui.checkbox(&mut is_regex, "Regex").changed();
|
||||
if changed {
|
||||
self.regex = is_regex.then(|| Regex::new(&self.text).ok());
|
||||
}
|
||||
if let Some(None) = self.regex {
|
||||
ui.label("Error: Invalid regex");
|
||||
}
|
||||
changed
|
||||
}
|
||||
|
||||
pub fn to_crit(&self) -> Option<CritLiteralOrRegex> {
|
||||
match &self.regex {
|
||||
None => Some(CritLiteralOrRegex::Literal(self.text.clone())),
|
||||
Some(v) => Some(CritLiteralOrRegex::Regex(v.clone()?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ enum PaneName {
|
|||
GPUs,
|
||||
Input,
|
||||
LookAndFeel,
|
||||
Clients,
|
||||
}
|
||||
|
||||
impl PaneName {
|
||||
|
|
@ -29,6 +30,7 @@ impl PaneName {
|
|||
PaneName::GPUs => "GPUs",
|
||||
PaneName::Input => "Input",
|
||||
PaneName::LookAndFeel => "Look and Feel",
|
||||
PaneName::Clients => "Clients",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -71,6 +73,7 @@ impl ControlCenterInner {
|
|||
PaneName::LookAndFeel => {
|
||||
PaneType::LookAndFeel(self.create_look_and_feel_pane())
|
||||
}
|
||||
PaneName::Clients => PaneType::Clients(self.create_clients_pane()),
|
||||
};
|
||||
self.open(tree, ty);
|
||||
ui.ctx().request_repaint();
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ use {
|
|||
crate::{
|
||||
compositor::DISPLAY,
|
||||
control_center::{
|
||||
CcBehavior, ControlCenterInner, bool, combo_box_ui, grid, label, read_only_bool, tip,
|
||||
CcBehavior, ControlCenterInner, bool, cc_clients::show_client_collapsible,
|
||||
combo_box_ui, grid, label, read_only_bool, tip,
|
||||
},
|
||||
state::State,
|
||||
utils::{errorfmt::ErrorFmt, oserror::OsError, static_text::StaticText},
|
||||
|
|
@ -45,7 +46,7 @@ impl XwaylandPane {
|
|||
res.push_str("Xwayland");
|
||||
}
|
||||
|
||||
pub fn show(&mut self, _behavior: &mut CcBehavior<'_>, ui: &mut Ui) {
|
||||
pub fn show(&mut self, behavior: &mut CcBehavior<'_>, ui: &mut Ui) {
|
||||
let s = &self.state;
|
||||
grid(ui, "settings", |ui| {
|
||||
bool(ui, "Enabled", s.xwayland.enabled.get(), |b| {
|
||||
|
|
@ -83,5 +84,8 @@ impl XwaylandPane {
|
|||
{
|
||||
log::error!("Could not kill Xwayland: {}", ErrorFmt(OsError::from(e)));
|
||||
}
|
||||
if let Some(client) = self.state.xwayland.client.get() {
|
||||
show_client_collapsible(behavior, ui, &client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,7 +114,6 @@ pub mod icons {
|
|||
pub const ICON_CLOSE: &str = "\u{e5cd}";
|
||||
pub const ICON_DRAG_INDICATOR: &str = "\u{e945}";
|
||||
pub const ICON_INFO: &str = "\u{e88e}";
|
||||
#[expect(dead_code)]
|
||||
pub const ICON_OPEN_IN_NEW: &str = "\u{e89e}";
|
||||
pub const ICON_PENDING: &str = "\u{ef64}";
|
||||
pub const ICON_REMOVE: &str = "\u{e15b}";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue