463 lines
14 KiB
Rust
463 lines
14 KiB
Rust
use {
|
|
crate::{
|
|
client::{Client, ClientId},
|
|
control_center::{
|
|
CcBehavior, ControlCenterInner, PaneType,
|
|
cc_criterion::{CcCriterion, CritImpl, CritRegex},
|
|
cc_window::show_window_collapsible,
|
|
grid, icon_label, label, read_only_bool,
|
|
},
|
|
criteria::{CritMgrExt, CritUpstreamNode, crit_leaf::CritLeafMatcher},
|
|
egui_adapter::egui_platform::icons::ICON_OPEN_IN_NEW,
|
|
state::State,
|
|
tree::ToplevelData,
|
|
utils::{
|
|
copyhashmap::CopyHashMap, static_text::StaticText,
|
|
toplevel_identifier::ToplevelIdentifier,
|
|
},
|
|
},
|
|
ahash::AHashMap,
|
|
egui::{
|
|
CollapsingHeader, DragValue, Sense, TextFormat, Ui, Widget, cache::CacheTrait,
|
|
text::LayoutJob,
|
|
},
|
|
linearize::Linearize,
|
|
std::{
|
|
any::Any,
|
|
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());
|
|
}
|
|
}
|
|
});
|
|
});
|
|
ui.collapsing("Windows", |ui| {
|
|
let matcher = ui.memory_mut(|m| {
|
|
m.caches
|
|
.cache::<ClientWindowMatchersCache>()
|
|
.get(behavior.cc, client.id)
|
|
.clone()
|
|
});
|
|
let mut windows: Vec<_> = matcher.windows.lock().keys().copied().collect();
|
|
windows.sort();
|
|
for id in windows {
|
|
let Some(window) = client.state.toplevels.get(&id).and_then(|v| v.upgrade()) else {
|
|
continue;
|
|
};
|
|
show_window_collapsible(behavior, ui, &window);
|
|
}
|
|
});
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct ClientWindowMatchersCache {
|
|
generation: u64,
|
|
matchers: AHashMap<ClientId, CachedWindowMatcher>,
|
|
}
|
|
|
|
struct CachedWindowMatcher {
|
|
generation: u64,
|
|
_matcher: Rc<CritLeafMatcher<ToplevelData>>,
|
|
matchers: Rc<WindowMatchers>,
|
|
}
|
|
|
|
struct WindowMatchers {
|
|
cc: Weak<ControlCenterInner>,
|
|
windows: CopyHashMap<ToplevelIdentifier, ()>,
|
|
}
|
|
|
|
impl ClientWindowMatchersCache {
|
|
fn get(&mut self, cc: &Rc<ControlCenterInner>, id: ClientId) -> &Rc<WindowMatchers> {
|
|
let res = self.matchers.entry(id).or_insert_with(|| {
|
|
let state = &cc.state;
|
|
let node = state.cl_matcher_manager.id(id);
|
|
let node = state.tl_matcher_manager.client(state, &node);
|
|
let matchers = Rc::new(WindowMatchers {
|
|
cc: Rc::downgrade(&cc),
|
|
windows: Default::default(),
|
|
});
|
|
let matchers2 = matchers.clone();
|
|
let matcher = state.tl_matcher_manager.leaf(&node, move |id| {
|
|
matchers2.windows.set(id, ());
|
|
if let Some(cc) = matchers2.cc.upgrade() {
|
|
cc.window.request_redraw();
|
|
}
|
|
let matchers2 = matchers2.clone();
|
|
Box::new(move || {
|
|
matchers2.windows.remove(&id);
|
|
if let Some(cc) = matchers2.cc.upgrade() {
|
|
cc.window.request_redraw();
|
|
}
|
|
})
|
|
});
|
|
let res = CachedWindowMatcher {
|
|
generation: 0,
|
|
_matcher: matcher,
|
|
matchers,
|
|
};
|
|
state.cl_matcher_manager.rematch_all(state);
|
|
state.tl_matcher_manager.rematch_all(state);
|
|
res
|
|
});
|
|
res.generation = self.generation;
|
|
&res.matchers
|
|
}
|
|
}
|
|
|
|
unsafe impl Sync for ClientWindowMatchersCache {}
|
|
unsafe impl Send for ClientWindowMatchersCache {}
|
|
|
|
impl CacheTrait for ClientWindowMatchersCache {
|
|
fn update(&mut self) {
|
|
self.matchers.retain(|_, m| m.generation == self.generation);
|
|
self.generation += 1;
|
|
}
|
|
|
|
fn len(&self) -> usize {
|
|
self.matchers.len()
|
|
}
|
|
|
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
|
self
|
|
}
|
|
}
|