474 lines
14 KiB
Rust
474 lines
14 KiB
Rust
use {
|
|
crate::{
|
|
cli::{
|
|
GlobalArgs,
|
|
clients::{Client, ClientPrinter, handle_client_query, make_json_client},
|
|
json::{JsonRect, JsonTreeNode, JsonTreeNodeType, jsonl},
|
|
},
|
|
ifs::jay_tree_query::{
|
|
TREE_TY_CONTAINER, TREE_TY_DISPLAY, TREE_TY_FLOAT, TREE_TY_LAYER_SURFACE,
|
|
TREE_TY_LOCK_SURFACE, TREE_TY_OUTPUT, TREE_TY_PLACEHOLDER, TREE_TY_WORKSPACE,
|
|
TREE_TY_X_WINDOW, TREE_TY_XDG_POPUP, TREE_TY_XDG_TOPLEVEL,
|
|
},
|
|
rect::Rect,
|
|
tools::tool_client::{Handle, ToolClient, with_tool_client},
|
|
wire::{JayCompositorId, JayTreeQueryId, jay_client_query, jay_compositor, jay_tree_query},
|
|
},
|
|
ahash::{AHashMap, AHashSet},
|
|
clap::{Args, Subcommand},
|
|
isnt::std_1::primitive::IsntSliceExt,
|
|
std::{cell::RefCell, rc::Rc},
|
|
};
|
|
|
|
#[derive(Args, Debug)]
|
|
pub struct TreeArgs {
|
|
#[clap(subcommand)]
|
|
cmd: TreeCmd,
|
|
}
|
|
|
|
#[derive(Subcommand, Debug)]
|
|
enum TreeCmd {
|
|
/// Query the tree.
|
|
Query(QueryArgs),
|
|
}
|
|
|
|
#[derive(Args, Debug)]
|
|
struct QueryArgs {
|
|
/// Whether to perform a recursive query.
|
|
#[arg(short, long)]
|
|
recursive: bool,
|
|
/// Whether to repeatedly print details of the same client.
|
|
#[arg(long)]
|
|
all_clients: bool,
|
|
#[clap(subcommand)]
|
|
cmd: QueryCmd,
|
|
}
|
|
|
|
#[derive(Subcommand, Debug)]
|
|
enum QueryCmd {
|
|
/// Query the entire display.
|
|
Root,
|
|
/// Query a workspace by name.
|
|
WorkspaceName(QueryWorkspaceNameArgs),
|
|
/// Interactively select a workspace to query.
|
|
SelectWorkspace,
|
|
/// Interactively select a window to query.
|
|
SelectWindow,
|
|
}
|
|
|
|
#[derive(Args, Debug)]
|
|
struct QueryWorkspaceNameArgs {
|
|
/// The name of the workspace.
|
|
name: String,
|
|
}
|
|
|
|
pub fn main(global: GlobalArgs, tree_args: TreeArgs) {
|
|
with_tool_client(global.log_level, |tc| async move {
|
|
let comp = tc.jay_compositor().await;
|
|
let tree = Rc::new(Tree {
|
|
tc: tc.clone(),
|
|
comp,
|
|
});
|
|
tree.run(&global, tree_args).await;
|
|
});
|
|
}
|
|
|
|
struct Tree {
|
|
tc: Rc<ToolClient>,
|
|
comp: JayCompositorId,
|
|
}
|
|
|
|
impl Tree {
|
|
async fn run(&self, global: &GlobalArgs, args: TreeArgs) {
|
|
match &args.cmd {
|
|
TreeCmd::Query(a) => self.query(global, a).await,
|
|
}
|
|
}
|
|
|
|
async fn query(&self, global: &GlobalArgs, args: &QueryArgs) {
|
|
let id = self.tc.id();
|
|
self.tc.send(jay_compositor::CreateTreeQuery {
|
|
self_id: self.comp,
|
|
id,
|
|
});
|
|
let mut query = Query {
|
|
tree: self,
|
|
tc: &self.tc,
|
|
id,
|
|
};
|
|
query.run(global, args).await;
|
|
}
|
|
}
|
|
|
|
struct Query<'a> {
|
|
tree: &'a Tree,
|
|
tc: &'a Rc<ToolClient>,
|
|
id: JayTreeQueryId,
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
struct Queried {
|
|
not_found: bool,
|
|
roots: Vec<Node>,
|
|
stack: Vec<Node>,
|
|
client_ids: AHashSet<u64>,
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
struct Node {
|
|
ty: u32,
|
|
children: Vec<Node>,
|
|
position: Option<Rect>,
|
|
toplevel_id: Option<String>,
|
|
client: Option<u64>,
|
|
title: Option<String>,
|
|
app_id: Option<String>,
|
|
tag: Option<String>,
|
|
x_class: Option<String>,
|
|
x_instance: Option<String>,
|
|
x_role: Option<String>,
|
|
workspace: Option<String>,
|
|
placeholder_for: Option<String>,
|
|
floating: bool,
|
|
visible: bool,
|
|
urgent: bool,
|
|
fullscreen: bool,
|
|
output: Option<String>,
|
|
content_type: Option<String>,
|
|
}
|
|
|
|
impl Query<'_> {
|
|
async fn run(&mut self, global: &GlobalArgs, args: &QueryArgs) {
|
|
match &args.cmd {
|
|
QueryCmd::Root => {
|
|
self.tc.send(SetRootDisplay { self_id: self.id });
|
|
}
|
|
QueryCmd::WorkspaceName(a) => {
|
|
self.tc.send(SetRootWorkspaceName {
|
|
self_id: self.id,
|
|
workspace: &a.name,
|
|
});
|
|
}
|
|
QueryCmd::SelectWorkspace => {
|
|
let id = self.tc.select_workspace().await;
|
|
if id.is_none() {
|
|
fatal!("Workspace selection failed");
|
|
}
|
|
self.tc.send(SetRootWorkspace {
|
|
self_id: self.id,
|
|
workspace: id,
|
|
});
|
|
}
|
|
QueryCmd::SelectWindow => {
|
|
let id = self.tc.select_toplevel().await;
|
|
if id.is_none() {
|
|
fatal!("Window selection failed");
|
|
}
|
|
self.tc.send(SetRootToplevel {
|
|
self_id: self.id,
|
|
toplevel: id,
|
|
});
|
|
}
|
|
}
|
|
let tl = self.tc;
|
|
let id = self.id;
|
|
let d = Rc::new(RefCell::new(Queried::default()));
|
|
use jay_tree_query::*;
|
|
macro_rules! last {
|
|
($d:ident, $n:ident) => {
|
|
let $d = &mut *$d.borrow_mut();
|
|
let $n = $d.stack.last_mut().unwrap();
|
|
};
|
|
}
|
|
NotFound::handle(tl, id, d.clone(), |d, _event| {
|
|
d.borrow_mut().not_found = true;
|
|
});
|
|
End::handle(tl, id, d.clone(), |d, _event| {
|
|
let d = &mut *d.borrow_mut();
|
|
let n = d.stack.pop().unwrap();
|
|
if let Some(p) = d.stack.last_mut() {
|
|
p.children.push(n);
|
|
} else {
|
|
d.roots.push(n);
|
|
}
|
|
});
|
|
Position::handle(tl, id, d.clone(), |d, event| {
|
|
last!(d, n);
|
|
n.position = Rect::new_sized(event.x, event.y, event.w, event.h);
|
|
});
|
|
Start::handle(tl, id, d.clone(), |d, event| {
|
|
let d = &mut *d.borrow_mut();
|
|
let node = Node {
|
|
ty: event.ty,
|
|
..Default::default()
|
|
};
|
|
d.stack.push(node);
|
|
});
|
|
OutputName::handle(tl, id, d.clone(), |d, event| {
|
|
last!(d, n);
|
|
n.output = Some(event.name.to_string());
|
|
});
|
|
WorkspaceName::handle(tl, id, d.clone(), |d, event| {
|
|
last!(d, n);
|
|
n.workspace = Some(event.name.to_string());
|
|
});
|
|
ToplevelId::handle(tl, id, d.clone(), |d, event| {
|
|
last!(d, n);
|
|
n.toplevel_id = Some(event.id.to_string());
|
|
});
|
|
ClientId::handle(tl, id, d.clone(), |d, event| {
|
|
last!(d, n);
|
|
n.client = Some(event.id);
|
|
d.client_ids.insert(event.id);
|
|
});
|
|
Title::handle(tl, id, d.clone(), |d, event| {
|
|
last!(d, n);
|
|
n.title = Some(event.title.to_string());
|
|
});
|
|
AppId::handle(tl, id, d.clone(), |d, event| {
|
|
last!(d, n);
|
|
n.app_id = Some(event.app_id.to_string());
|
|
});
|
|
Floating::handle(tl, id, d.clone(), |d, _event| {
|
|
last!(d, n);
|
|
n.floating = true;
|
|
});
|
|
Visible::handle(tl, id, d.clone(), |d, _event| {
|
|
last!(d, n);
|
|
n.visible = true;
|
|
});
|
|
Urgent::handle(tl, id, d.clone(), |d, _event| {
|
|
last!(d, n);
|
|
n.urgent = true;
|
|
});
|
|
Fullscreen::handle(tl, id, d.clone(), |d, _event| {
|
|
last!(d, n);
|
|
n.fullscreen = true;
|
|
});
|
|
Tag::handle(tl, id, d.clone(), |d, event| {
|
|
last!(d, n);
|
|
n.tag = Some(event.tag.to_string());
|
|
});
|
|
XClass::handle(tl, id, d.clone(), |d, event| {
|
|
last!(d, n);
|
|
n.x_class = Some(event.class.to_string());
|
|
});
|
|
XInstance::handle(tl, id, d.clone(), |d, event| {
|
|
last!(d, n);
|
|
n.x_instance = Some(event.instance.to_string());
|
|
});
|
|
XRole::handle(tl, id, d.clone(), |d, event| {
|
|
last!(d, n);
|
|
n.x_role = Some(event.role.to_string());
|
|
});
|
|
Workspace::handle(tl, id, d.clone(), |d, event| {
|
|
last!(d, n);
|
|
n.workspace = Some(event.name.to_string());
|
|
});
|
|
PlaceholderFor::handle(tl, id, d.clone(), |d, event| {
|
|
last!(d, n);
|
|
n.placeholder_for = Some(event.id.to_string());
|
|
});
|
|
ContentType::handle(tl, id, d.clone(), |d, event| {
|
|
last!(d, n);
|
|
n.content_type = Some(event.ty.to_string());
|
|
});
|
|
if args.recursive {
|
|
tl.send(SetRecursive {
|
|
self_id: id,
|
|
recursive: 1,
|
|
});
|
|
}
|
|
tl.send(Execute { self_id: id });
|
|
tl.round_trip().await;
|
|
let clients = {
|
|
let id = tl.id();
|
|
tl.send(jay_compositor::CreateClientQuery {
|
|
self_id: self.tree.comp,
|
|
id,
|
|
});
|
|
use jay_client_query::*;
|
|
for &client in &d.borrow().client_ids {
|
|
tl.send(AddId {
|
|
self_id: id,
|
|
id: client,
|
|
});
|
|
}
|
|
tl.send(Execute { self_id: id });
|
|
handle_client_query(tl, id).await
|
|
};
|
|
if global.json {
|
|
for node in &d.borrow().roots {
|
|
let node = make_json_tree_node(&clients, node);
|
|
jsonl(&node);
|
|
}
|
|
} else {
|
|
let mut printer = Printer {
|
|
clients,
|
|
printed_clients: Default::default(),
|
|
verbose: args.all_clients,
|
|
prefix: "".to_string(),
|
|
output_depth: 0,
|
|
workspace_depth: 0,
|
|
};
|
|
for node in &d.borrow().roots {
|
|
printer.print(node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn make_json_tree_node<'b>(clients: &'b AHashMap<u64, Client>, node: &'b Node) -> JsonTreeNode<'b> {
|
|
let position = node.position.map(|r| JsonRect {
|
|
x1: r.x1(),
|
|
y1: r.y1(),
|
|
x2: r.x2(),
|
|
y2: r.y2(),
|
|
width: r.width(),
|
|
height: r.height(),
|
|
});
|
|
let client = node
|
|
.client
|
|
.and_then(|client_id| clients.get(&client_id))
|
|
.map(make_json_client);
|
|
let children = node
|
|
.children
|
|
.iter()
|
|
.map(|c| make_json_tree_node(clients, c))
|
|
.collect();
|
|
JsonTreeNode {
|
|
ty: JsonTreeNodeType(node.ty),
|
|
output: node.output.as_deref(),
|
|
workspace: node.workspace.as_deref(),
|
|
toplevel_id: node.toplevel_id.as_deref(),
|
|
placeholder_for: node.placeholder_for.as_deref(),
|
|
position,
|
|
client,
|
|
title: node.title.as_deref(),
|
|
app_id: node.app_id.as_deref(),
|
|
tag: node.tag.as_deref(),
|
|
content_type: node.content_type.as_deref(),
|
|
x_class: node.x_class.as_deref(),
|
|
x_instance: node.x_instance.as_deref(),
|
|
x_role: node.x_role.as_deref(),
|
|
floating: node.floating,
|
|
visible: node.visible,
|
|
urgent: node.urgent,
|
|
fullscreen: node.fullscreen,
|
|
children,
|
|
}
|
|
}
|
|
|
|
struct Printer {
|
|
clients: AHashMap<u64, Client>,
|
|
printed_clients: AHashSet<u64>,
|
|
verbose: bool,
|
|
prefix: String,
|
|
output_depth: u32,
|
|
workspace_depth: u32,
|
|
}
|
|
|
|
impl Printer {
|
|
fn print(&mut self, node: &Node) {
|
|
let p = &self.prefix;
|
|
'ty: {
|
|
let n = match node.ty {
|
|
TREE_TY_DISPLAY => "display",
|
|
TREE_TY_OUTPUT => "output",
|
|
TREE_TY_WORKSPACE => "workspace",
|
|
TREE_TY_FLOAT => "float",
|
|
TREE_TY_CONTAINER => "container",
|
|
TREE_TY_PLACEHOLDER => "placeholder",
|
|
TREE_TY_XDG_TOPLEVEL => "xdg-toplevel",
|
|
TREE_TY_X_WINDOW => "x-window",
|
|
TREE_TY_XDG_POPUP => "xdg-popup",
|
|
TREE_TY_LAYER_SURFACE => "layer-surface",
|
|
TREE_TY_LOCK_SURFACE => "lock-surface",
|
|
_ => {
|
|
println!("{p}- unknown ({}):", node.ty);
|
|
break 'ty;
|
|
}
|
|
};
|
|
println!("{p}- {n}:");
|
|
}
|
|
macro_rules! opt {
|
|
($field:ident, $pretty:expr) => {
|
|
if let Some(v) = &node.$field {
|
|
println!("{p} {}: {}", $pretty, v);
|
|
}
|
|
};
|
|
}
|
|
macro_rules! bol {
|
|
($field:ident, $pretty:expr) => {
|
|
if node.$field {
|
|
println!("{p} {}", $pretty);
|
|
}
|
|
};
|
|
}
|
|
if node.ty == TREE_TY_OUTPUT {
|
|
opt!(output, "name");
|
|
}
|
|
if node.ty == TREE_TY_WORKSPACE {
|
|
opt!(workspace, "name");
|
|
}
|
|
opt!(toplevel_id, "id");
|
|
opt!(placeholder_for, "placeholder-for");
|
|
if let Some(r) = node.position {
|
|
println!(
|
|
"{p} pos: {}x{} + {}x{}",
|
|
r.x1(),
|
|
r.y1(),
|
|
r.width(),
|
|
r.height()
|
|
);
|
|
}
|
|
if let Some(client_id) = node.client {
|
|
let client = self.clients.get(&client_id);
|
|
if client.is_some() && (self.printed_clients.insert(client_id) || self.verbose) {
|
|
println!("{p} client:");
|
|
let mut prefix = format!("{} ", p);
|
|
let mut cp = ClientPrinter {
|
|
prefix: &mut prefix,
|
|
};
|
|
cp.print_client(client.unwrap());
|
|
} else {
|
|
println!("{p} client: {client_id}");
|
|
}
|
|
}
|
|
opt!(title, "title");
|
|
opt!(app_id, "app-id");
|
|
opt!(tag, "tag");
|
|
opt!(content_type, "content-type");
|
|
opt!(x_class, "x-class");
|
|
opt!(x_instance, "x-instance");
|
|
opt!(x_role, "x-role");
|
|
if self.workspace_depth == 0 && node.ty != TREE_TY_WORKSPACE {
|
|
opt!(workspace, "workspace");
|
|
}
|
|
if self.workspace_depth == 0 && self.output_depth == 0 && node.ty != TREE_TY_OUTPUT {
|
|
opt!(output, "output");
|
|
}
|
|
bol!(floating, "floating");
|
|
bol!(visible, "visible");
|
|
bol!(urgent, "urgent");
|
|
bol!(fullscreen, "fullscreen");
|
|
if node.children.is_not_empty() {
|
|
let (od, wd) = match node.ty {
|
|
TREE_TY_OUTPUT => (1, 0),
|
|
TREE_TY_WORKSPACE => (0, 1),
|
|
_ => (0, 0),
|
|
};
|
|
self.output_depth += od;
|
|
self.workspace_depth += wd;
|
|
println!("{p} children:");
|
|
let len = self.prefix.len();
|
|
self.prefix.push_str(" ");
|
|
for child in &node.children {
|
|
self.print(child);
|
|
}
|
|
self.prefix.truncate(len);
|
|
self.output_depth -= od;
|
|
self.workspace_depth -= wd;
|
|
}
|
|
}
|
|
}
|