cli: add commands to inspect the tree
This commit is contained in:
parent
bd04b09171
commit
38d7a60d00
14 changed files with 1072 additions and 8 deletions
|
|
@ -14,6 +14,7 @@ mod run_privileged;
|
|||
pub mod screenshot;
|
||||
mod seat_test;
|
||||
mod set_log_level;
|
||||
mod tree;
|
||||
mod unlock;
|
||||
mod xwayland;
|
||||
|
||||
|
|
@ -22,7 +23,7 @@ use {
|
|||
cli::{
|
||||
clients::ClientsArgs, color_management::ColorManagementArgs,
|
||||
damage_tracking::DamageTrackingArgs, idle::IdleCmd, input::InputArgs, randr::RandrArgs,
|
||||
reexec::ReexecArgs, xwayland::XwaylandArgs,
|
||||
reexec::ReexecArgs, tree::TreeArgs, xwayland::XwaylandArgs,
|
||||
},
|
||||
compositor::start_compositor,
|
||||
format::{Format, ref_formats},
|
||||
|
|
@ -89,6 +90,8 @@ pub enum Cmd {
|
|||
Reexec(ReexecArgs),
|
||||
/// Inspect/manipulate the connected clients.
|
||||
Clients(ClientsArgs),
|
||||
/// Inspect the surface tree.
|
||||
Tree(TreeArgs),
|
||||
#[cfg(feature = "it")]
|
||||
RunTests,
|
||||
}
|
||||
|
|
@ -248,6 +251,7 @@ pub fn main() {
|
|||
Cmd::Xwayland(a) => xwayland::main(cli.global, a),
|
||||
Cmd::ColorManagement(a) => color_management::main(cli.global, a),
|
||||
Cmd::Clients(a) => clients::main(cli.global, a),
|
||||
Cmd::Tree(a) => tree::main(cli.global, a),
|
||||
#[cfg(feature = "it")]
|
||||
Cmd::RunTests => crate::it::run_tests(),
|
||||
Cmd::Reexec(a) => reexec::main(cli.global, a),
|
||||
|
|
|
|||
419
src/cli/tree.rs
Normal file
419
src/cli/tree.rs
Normal file
|
|
@ -0,0 +1,419 @@
|
|||
use {
|
||||
crate::{
|
||||
cli::{
|
||||
GlobalArgs,
|
||||
clients::{Client, ClientPrinter, handle_client_query},
|
||||
},
|
||||
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.into(), |tc| async move {
|
||||
let comp = tc.jay_compositor().await;
|
||||
let tree = Rc::new(Tree {
|
||||
tc: tc.clone(),
|
||||
comp,
|
||||
});
|
||||
tree.run(tree_args).await;
|
||||
});
|
||||
}
|
||||
|
||||
struct Tree {
|
||||
tc: Rc<ToolClient>,
|
||||
comp: JayCompositorId,
|
||||
}
|
||||
|
||||
impl Tree {
|
||||
async fn run(&self, args: TreeArgs) {
|
||||
match &args.cmd {
|
||||
TreeCmd::Query(a) => self.query(a).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn query(&self, 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(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>,
|
||||
}
|
||||
|
||||
impl Query<'_> {
|
||||
async fn run(&mut self, 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());
|
||||
});
|
||||
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
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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!(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ pub mod jay_select_toplevel;
|
|||
pub mod jay_select_workspace;
|
||||
pub mod jay_toplevel;
|
||||
pub mod jay_tray_v1;
|
||||
pub mod jay_tree_query;
|
||||
pub mod jay_workspace;
|
||||
pub mod jay_workspace_watcher;
|
||||
pub mod jay_xwayland;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ use {
|
|||
jay_seat_events::JaySeatEvents,
|
||||
jay_select_toplevel::{JaySelectToplevel, JayToplevelSelector},
|
||||
jay_select_workspace::{JaySelectWorkspace, JayWorkspaceSelector},
|
||||
jay_tree_query::JayTreeQuery,
|
||||
jay_workspace_watcher::JayWorkspaceWatcher,
|
||||
jay_xwayland::JayXwayland,
|
||||
},
|
||||
|
|
@ -507,6 +508,13 @@ impl JayCompositorRequestHandler for JayCompositor {
|
|||
self.client.state.clients.kill(ClientId::from_raw(req.id));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_tree_query(&self, req: CreateTreeQuery, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
let obj = Rc::new(JayTreeQuery::new(&self.client, req.id, self.version));
|
||||
track!(self.client, obj);
|
||||
self.client.add_client_obj(&obj)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
object_base! {
|
||||
|
|
|
|||
458
src/ifs/jay_tree_query.rs
Normal file
458
src/ifs/jay_tree_query.rs
Normal file
|
|
@ -0,0 +1,458 @@
|
|||
use {
|
||||
crate::{
|
||||
client::{Client, ClientError},
|
||||
globals::GlobalBase,
|
||||
ifs::wl_surface::{
|
||||
ext_session_lock_surface_v1::ExtSessionLockSurfaceV1,
|
||||
x_surface::xwindow::Xwindow,
|
||||
xdg_surface::{xdg_popup::XdgPopup, xdg_toplevel::XdgToplevel},
|
||||
zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
|
||||
},
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
rect::Rect,
|
||||
tree::{
|
||||
self, ContainerNode, DisplayNode, FloatNode, Node, NodeVisitor, OutputNode,
|
||||
PlaceholderNode, ToplevelData, ToplevelNodeBase, ToplevelType, WorkspaceNode,
|
||||
},
|
||||
utils::{opaque::OpaqueError, opt::Opt, toplevel_identifier::ToplevelIdentifier},
|
||||
wire::{JayTreeQueryId, jay_tree_query::*},
|
||||
},
|
||||
isnt::std_1::primitive::IsntStrExt,
|
||||
std::{
|
||||
cell::{Cell, RefCell},
|
||||
ops::Deref,
|
||||
rc::Rc,
|
||||
str::FromStr,
|
||||
},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub const TREE_TY_DISPLAY: u32 = 1;
|
||||
pub const TREE_TY_OUTPUT: u32 = 2;
|
||||
pub const TREE_TY_WORKSPACE: u32 = 3;
|
||||
pub const TREE_TY_FLOAT: u32 = 4;
|
||||
pub const TREE_TY_CONTAINER: u32 = 5;
|
||||
pub const TREE_TY_PLACEHOLDER: u32 = 6;
|
||||
pub const TREE_TY_XDG_TOPLEVEL: u32 = 7;
|
||||
pub const TREE_TY_X_WINDOW: u32 = 8;
|
||||
pub const TREE_TY_XDG_POPUP: u32 = 9;
|
||||
pub const TREE_TY_LAYER_SURFACE: u32 = 10;
|
||||
pub const TREE_TY_LOCK_SURFACE: u32 = 11;
|
||||
|
||||
pub struct JayTreeQuery {
|
||||
pub id: JayTreeQueryId,
|
||||
pub client: Rc<Client>,
|
||||
pub tracker: Tracker<Self>,
|
||||
pub version: Version,
|
||||
recursive: Cell<bool>,
|
||||
root: RefCell<Option<Root>>,
|
||||
}
|
||||
|
||||
enum Root {
|
||||
Display,
|
||||
WorkspaceNode(Rc<Opt<WorkspaceNode>>),
|
||||
WorkspaceName(String),
|
||||
ToplevelId(ToplevelIdentifier),
|
||||
}
|
||||
|
||||
impl JayTreeQuery {
|
||||
pub fn new(client: &Rc<Client>, id: JayTreeQueryId, version: Version) -> Self {
|
||||
Self {
|
||||
id,
|
||||
client: client.clone(),
|
||||
tracker: Default::default(),
|
||||
version,
|
||||
recursive: Cell::new(false),
|
||||
root: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn send_node_position(&self, node: &dyn Node) {
|
||||
let rect = node.node_absolute_position();
|
||||
self.send_position(rect);
|
||||
}
|
||||
|
||||
fn send_position(&self, rect: Rect) {
|
||||
self.client.event(Position {
|
||||
self_id: self.id,
|
||||
x: rect.x1(),
|
||||
y: rect.y1(),
|
||||
w: rect.width(),
|
||||
h: rect.height(),
|
||||
});
|
||||
}
|
||||
|
||||
fn send_not_found(&self) {
|
||||
self.client.event(NotFound { self_id: self.id });
|
||||
}
|
||||
|
||||
fn send_done(&self) {
|
||||
self.client.event(Done { self_id: self.id });
|
||||
}
|
||||
|
||||
fn send_end(&self) {
|
||||
self.client.event(End { self_id: self.id });
|
||||
}
|
||||
|
||||
fn send_start(&self, ty: u32) {
|
||||
self.client.event(Start {
|
||||
self_id: self.id,
|
||||
ty,
|
||||
});
|
||||
}
|
||||
|
||||
fn send_client(&self, node: &impl Node) {
|
||||
if let Some(id) = node.node_client_id() {
|
||||
self.client.event(ClientId {
|
||||
self_id: self.id,
|
||||
id: id.raw(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn send_workspace_name(&self, name: &str) {
|
||||
self.client.event(WorkspaceName {
|
||||
self_id: self.id,
|
||||
name,
|
||||
});
|
||||
}
|
||||
|
||||
fn send_output_name(&self, name: &str) {
|
||||
self.client.event(OutputName {
|
||||
self_id: self.id,
|
||||
name,
|
||||
});
|
||||
}
|
||||
|
||||
fn send_toplevel(&self, data: &ToplevelData) {
|
||||
self.client.event(Start {
|
||||
self_id: self.id,
|
||||
ty: match &data.kind {
|
||||
ToplevelType::Container => TREE_TY_CONTAINER,
|
||||
ToplevelType::Placeholder(_) => TREE_TY_PLACEHOLDER,
|
||||
ToplevelType::XdgToplevel(_) => TREE_TY_XDG_TOPLEVEL,
|
||||
ToplevelType::XWindow(_) => TREE_TY_X_WINDOW,
|
||||
},
|
||||
});
|
||||
self.client.event(ToplevelId {
|
||||
self_id: self.id,
|
||||
id: &data.identifier.get().to_string(),
|
||||
});
|
||||
self.send_position(data.desired_extents.get());
|
||||
if let Some(cl) = data.client.as_ref().map(|c| c.id.raw()) {
|
||||
self.client.event(ClientId {
|
||||
self_id: self.id,
|
||||
id: cl,
|
||||
});
|
||||
}
|
||||
self.client.event(Title {
|
||||
self_id: self.id,
|
||||
title: &data.title.borrow(),
|
||||
});
|
||||
if let Some(w) = data.workspace.get() {
|
||||
self.send_workspace_name(&w.name);
|
||||
}
|
||||
match &data.kind {
|
||||
ToplevelType::Container => {}
|
||||
ToplevelType::Placeholder(id) => {
|
||||
if let Some(id) = *id {
|
||||
self.client.event(PlaceholderFor {
|
||||
self_id: self.id,
|
||||
id: &id.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
ToplevelType::XdgToplevel(d) => {
|
||||
self.client.event(AppId {
|
||||
self_id: self.id,
|
||||
app_id: &data.app_id.borrow(),
|
||||
});
|
||||
let tag = &*d.tag.borrow();
|
||||
if tag.is_not_empty() {
|
||||
self.client.event(Tag {
|
||||
self_id: self.id,
|
||||
tag,
|
||||
});
|
||||
}
|
||||
}
|
||||
ToplevelType::XWindow(d) => {
|
||||
if let Some(class) = &*d.info.class.borrow() {
|
||||
self.client.event(XClass {
|
||||
self_id: self.id,
|
||||
class,
|
||||
});
|
||||
}
|
||||
if let Some(instance) = &*d.info.instance.borrow() {
|
||||
self.client.event(XInstance {
|
||||
self_id: self.id,
|
||||
instance,
|
||||
});
|
||||
}
|
||||
if let Some(role) = &*d.info.role.borrow() {
|
||||
self.client.event(XRole {
|
||||
self_id: self.id,
|
||||
role,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if data.is_floating.get() {
|
||||
self.client.event(Floating { self_id: self.id });
|
||||
}
|
||||
if data.visible.get() {
|
||||
self.client.event(Visible { self_id: self.id });
|
||||
}
|
||||
if data.wants_attention.get() {
|
||||
self.client.event(Urgent { self_id: self.id });
|
||||
}
|
||||
for seat_id in data.seat_foci.lock().keys() {
|
||||
for seat in data.state.globals.seats.lock().values() {
|
||||
if seat.id() == *seat_id {
|
||||
self.client.event(Focused {
|
||||
self_id: self.id,
|
||||
global: seat.name().raw(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if data.is_fullscreen.get() {
|
||||
self.client.event(Fullscreen { self_id: self.id });
|
||||
}
|
||||
if let Some(ws) = data.workspace.get() {
|
||||
self.client.event(Workspace {
|
||||
self_id: self.id,
|
||||
name: &ws.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JayTreeQueryRequestHandler for JayTreeQuery {
|
||||
type Error = JayTreeQueryError;
|
||||
|
||||
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.client.remove_obj(self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute(&self, _req: Execute, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
let Some(root) = &*self.root.borrow() else {
|
||||
return Err(JayTreeQueryError::NoRootSet);
|
||||
};
|
||||
match root {
|
||||
Root::Display => Visitor(self).visit_display(&self.client.state.root),
|
||||
Root::WorkspaceNode(n) => match n.get() {
|
||||
Some(n) => Visitor(self).visit_workspace(&n),
|
||||
None => self.send_not_found(),
|
||||
},
|
||||
Root::WorkspaceName(n) => match self.client.state.workspaces.get(n) {
|
||||
Some(n) => Visitor(self).visit_workspace(&n),
|
||||
None => self.send_not_found(),
|
||||
},
|
||||
Root::ToplevelId(id) => match self
|
||||
.client
|
||||
.state
|
||||
.toplevels
|
||||
.get(id)
|
||||
.and_then(|t| t.upgrade())
|
||||
{
|
||||
Some(t) => t.node_visit(&mut Visitor(self)),
|
||||
None => self.send_not_found(),
|
||||
},
|
||||
}
|
||||
self.send_done();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_root_display(&self, _req: SetRootDisplay, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
*self.root.borrow_mut() = Some(Root::Display);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_recursive(&self, req: SetRecursive, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.recursive.set(req.recursive != 0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_root_workspace(
|
||||
&self,
|
||||
req: SetRootWorkspace,
|
||||
_slf: &Rc<Self>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let ws = self.client.lookup(req.workspace)?;
|
||||
let opt = match ws.workspace.get() {
|
||||
Some(ws) => ws.opt.clone(),
|
||||
_ => Default::default(),
|
||||
};
|
||||
let root = &mut *self.root.borrow_mut();
|
||||
*root = Some(Root::WorkspaceNode(opt));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_root_workspace_name(
|
||||
&self,
|
||||
req: SetRootWorkspaceName,
|
||||
_slf: &Rc<Self>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let root = &mut *self.root.borrow_mut();
|
||||
*root = Some(Root::WorkspaceName(req.workspace.to_owned()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_root_toplevel(&self, req: SetRootToplevel, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
let tl = self.client.lookup(req.toplevel)?;
|
||||
let root = &mut *self.root.borrow_mut();
|
||||
*root = Some(Root::ToplevelId(tl.toplevel.tl_data().identifier.get()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_root_window_id(
|
||||
&self,
|
||||
req: SetRootWindowId<'_>,
|
||||
_slf: &Rc<Self>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let id =
|
||||
ToplevelIdentifier::from_str(req.id).map_err(JayTreeQueryError::InvalidToplevelId)?;
|
||||
let root = &mut *self.root.borrow_mut();
|
||||
*root = Some(Root::ToplevelId(id));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct Visitor<'a>(&'a JayTreeQuery);
|
||||
|
||||
impl tree::NodeVisitorBase for Visitor<'_> {
|
||||
fn visit_container(&mut self, node: &Rc<ContainerNode>) {
|
||||
let s = self.0;
|
||||
s.send_toplevel(node.tl_data());
|
||||
if s.recursive.get() {
|
||||
node.node_visit_children(self);
|
||||
}
|
||||
s.send_end();
|
||||
}
|
||||
|
||||
fn visit_toplevel(&mut self, node: &Rc<XdgToplevel>) {
|
||||
let s = self.0;
|
||||
s.send_toplevel(node.tl_data());
|
||||
node.xdg.for_each_popup(|popup| {
|
||||
NodeVisitor::visit_popup(self, popup);
|
||||
});
|
||||
s.send_end();
|
||||
}
|
||||
|
||||
fn visit_popup(&mut self, node: &Rc<XdgPopup>) {
|
||||
let s = self.0;
|
||||
s.send_start(TREE_TY_XDG_POPUP);
|
||||
s.send_node_position(&**node);
|
||||
s.send_end();
|
||||
}
|
||||
|
||||
fn visit_display(&mut self, node: &Rc<DisplayNode>) {
|
||||
let s = self.0;
|
||||
s.send_start(TREE_TY_DISPLAY);
|
||||
s.send_node_position(&**node);
|
||||
if s.recursive.get() {
|
||||
for output in node.outputs.lock().values() {
|
||||
NodeVisitor::visit_output(self, output);
|
||||
}
|
||||
for stacked in node.stacked.iter() {
|
||||
if stacked.stacked_has_workspace_link() {
|
||||
continue;
|
||||
}
|
||||
stacked.deref().clone().node_visit(self);
|
||||
}
|
||||
}
|
||||
s.send_end();
|
||||
}
|
||||
|
||||
fn visit_output(&mut self, node: &Rc<OutputNode>) {
|
||||
let s = self.0;
|
||||
s.send_start(TREE_TY_OUTPUT);
|
||||
s.send_node_position(&**node);
|
||||
s.send_output_name(&node.global.connector.name);
|
||||
if s.recursive.get() {
|
||||
node.node_visit_children(self);
|
||||
}
|
||||
s.send_end();
|
||||
}
|
||||
|
||||
fn visit_float(&mut self, node: &Rc<FloatNode>) {
|
||||
let s = self.0;
|
||||
s.send_start(TREE_TY_FLOAT);
|
||||
s.send_node_position(&**node);
|
||||
if s.recursive.get() {
|
||||
node.node_visit_children(self);
|
||||
}
|
||||
s.send_end();
|
||||
}
|
||||
|
||||
fn visit_workspace(&mut self, node: &Rc<WorkspaceNode>) {
|
||||
let s = self.0;
|
||||
s.send_start(TREE_TY_WORKSPACE);
|
||||
s.send_node_position(&**node);
|
||||
s.send_workspace_name(&node.name);
|
||||
s.send_output_name(&node.output.get().global.connector.name);
|
||||
for stacked in node.stacked.iter() {
|
||||
if stacked.stacked_is_xdg_popup() {
|
||||
continue;
|
||||
}
|
||||
stacked.deref().clone().node_visit(self);
|
||||
}
|
||||
if s.recursive.get() {
|
||||
node.node_visit_children(self);
|
||||
}
|
||||
s.send_end();
|
||||
}
|
||||
|
||||
fn visit_layer_surface(&mut self, node: &Rc<ZwlrLayerSurfaceV1>) {
|
||||
let s = self.0;
|
||||
s.send_start(TREE_TY_LAYER_SURFACE);
|
||||
s.send_client(&**node);
|
||||
s.send_node_position(&**node);
|
||||
node.for_each_popup(|popup| {
|
||||
NodeVisitor::visit_popup(self, popup);
|
||||
});
|
||||
s.send_end();
|
||||
}
|
||||
|
||||
fn visit_xwindow(&mut self, node: &Rc<Xwindow>) {
|
||||
let s = self.0;
|
||||
s.send_toplevel(node.tl_data());
|
||||
s.send_end();
|
||||
}
|
||||
|
||||
fn visit_placeholder(&mut self, node: &Rc<PlaceholderNode>) {
|
||||
let s = self.0;
|
||||
s.send_toplevel(node.tl_data());
|
||||
s.send_end();
|
||||
}
|
||||
|
||||
fn visit_lock_surface(&mut self, node: &Rc<ExtSessionLockSurfaceV1>) {
|
||||
let s = self.0;
|
||||
s.send_start(TREE_TY_LOCK_SURFACE);
|
||||
s.send_client(&**node);
|
||||
s.send_node_position(&**node);
|
||||
s.send_end();
|
||||
}
|
||||
}
|
||||
|
||||
object_base! {
|
||||
self = JayTreeQuery;
|
||||
version = self.version;
|
||||
}
|
||||
|
||||
impl Object for JayTreeQuery {}
|
||||
|
||||
simple_add_obj!(JayTreeQuery);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum JayTreeQueryError {
|
||||
#[error(transparent)]
|
||||
ClientError(Box<ClientError>),
|
||||
#[error("Toplevel id is ill-formed")]
|
||||
InvalidToplevelId(OpaqueError),
|
||||
#[error("No root node was set")]
|
||||
NoRootSet,
|
||||
}
|
||||
efrom!(JayTreeQueryError, ClientError);
|
||||
|
|
@ -328,6 +328,12 @@ impl XdgSurface {
|
|||
popup.popup.xdg.set_popup_stack(stack);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn for_each_popup(&self, mut f: impl FnMut(&Rc<XdgPopup>)) {
|
||||
for popup in self.popups.lock().values() {
|
||||
f(&popup.popup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl XdgSurfaceRequestHandler for XdgSurface {
|
||||
|
|
|
|||
|
|
@ -382,6 +382,10 @@ impl StackedNode for XdgPopup {
|
|||
fn stacked_absolute_position_constrains_input(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn stacked_is_xdg_popup(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl XdgSurfaceExt for XdgPopup {
|
||||
|
|
|
|||
|
|
@ -210,6 +210,12 @@ impl ZwlrLayerSurfaceV1 {
|
|||
m.layer_surface.get_or_insert_default_ext()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn for_each_popup(&self, mut f: impl FnMut(&Rc<XdgPopup>)) {
|
||||
for popup in self.popups.lock().values() {
|
||||
f(&popup.popup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ZwlrLayerSurfaceV1RequestHandler for ZwlrLayerSurfaceV1 {
|
||||
|
|
|
|||
|
|
@ -23,9 +23,10 @@ use {
|
|||
},
|
||||
wheel::{Wheel, WheelError},
|
||||
wire::{
|
||||
JayCompositor, JayCompositorId, JayDamageTracking, JayDamageTrackingId, WlCallbackId,
|
||||
WlRegistryId, WlSeatId, jay_compositor, jay_select_toplevel, jay_toplevel, wl_callback,
|
||||
wl_display, wl_registry,
|
||||
JayCompositor, JayCompositorId, JayDamageTracking, JayDamageTrackingId, JayToplevelId,
|
||||
JayWorkspaceId, WlCallbackId, WlRegistryId, WlSeatId, jay_compositor,
|
||||
jay_select_toplevel, jay_select_workspace, jay_toplevel, wl_callback, wl_display,
|
||||
wl_registry,
|
||||
},
|
||||
},
|
||||
ahash::AHashMap,
|
||||
|
|
@ -362,6 +363,53 @@ impl ToolClient {
|
|||
Some(id)
|
||||
}
|
||||
|
||||
pub async fn select_workspace(self: &Rc<Self>) -> JayWorkspaceId {
|
||||
let id = self.id();
|
||||
self.send(jay_compositor::SelectWorkspace {
|
||||
self_id: self.jay_compositor().await,
|
||||
id,
|
||||
seat: WlSeatId::NONE,
|
||||
});
|
||||
let ae = Rc::new(AsyncEvent::default());
|
||||
let ws = Rc::new(Cell::new(JayWorkspaceId::NONE));
|
||||
jay_select_workspace::Cancelled::handle(self, id, ae.clone(), |ae, _event| {
|
||||
ae.trigger();
|
||||
});
|
||||
jay_select_workspace::Selected::handle(
|
||||
self,
|
||||
id,
|
||||
(ae.clone(), ws.clone()),
|
||||
|(ae, ws), event| {
|
||||
ws.set(event.id);
|
||||
ae.trigger();
|
||||
},
|
||||
);
|
||||
ae.triggered().await;
|
||||
ws.get()
|
||||
}
|
||||
|
||||
pub async fn select_toplevel(self: &Rc<Self>) -> JayToplevelId {
|
||||
let id = self.id();
|
||||
self.send(jay_compositor::SelectToplevel {
|
||||
self_id: self.jay_compositor().await,
|
||||
id,
|
||||
seat: WlSeatId::NONE,
|
||||
});
|
||||
let ae = Rc::new(AsyncEvent::default());
|
||||
let toplevel = Rc::new(Cell::new(JayToplevelId::NONE));
|
||||
jay_select_toplevel::Done::handle(
|
||||
self,
|
||||
id,
|
||||
(ae.clone(), toplevel.clone()),
|
||||
|(ae, toplevel), event| {
|
||||
toplevel.set(event.id);
|
||||
ae.trigger();
|
||||
},
|
||||
);
|
||||
ae.triggered().await;
|
||||
toplevel.get()
|
||||
}
|
||||
|
||||
pub async fn select_toplevel_client(self: &Rc<Self>) -> u64 {
|
||||
let id = self.id();
|
||||
self.send(jay_compositor::SelectToplevel {
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ impl PlaceholderNode {
|
|||
state,
|
||||
node.tl_data().title.borrow().clone(),
|
||||
node.node_client(),
|
||||
ToplevelType::Placeholder,
|
||||
ToplevelType::Placeholder(Some(node.tl_data().identifier.get())),
|
||||
id,
|
||||
slf,
|
||||
),
|
||||
|
|
@ -75,7 +75,7 @@ impl PlaceholderNode {
|
|||
state,
|
||||
String::new(),
|
||||
None,
|
||||
ToplevelType::Placeholder,
|
||||
ToplevelType::Placeholder(None),
|
||||
id,
|
||||
slf,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@ pub trait StackedNode: Node {
|
|||
fn stacked_absolute_position_constrains_input(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn stacked_is_xdg_popup(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PinnedNode: StackedNode {
|
||||
|
|
|
|||
|
|
@ -279,7 +279,7 @@ impl ToplevelOpt {
|
|||
|
||||
pub enum ToplevelType {
|
||||
Container,
|
||||
Placeholder,
|
||||
Placeholder(Option<ToplevelIdentifier>),
|
||||
XdgToplevel(Rc<XdgToplevelToplevelData>),
|
||||
XWindow(Rc<XwindowData>),
|
||||
}
|
||||
|
|
@ -288,7 +288,7 @@ impl ToplevelType {
|
|||
pub fn to_window_type(&self) -> WindowType {
|
||||
match self {
|
||||
ToplevelType::Container => window::CONTAINER,
|
||||
ToplevelType::Placeholder => window::PLACEHOLDER,
|
||||
ToplevelType::Placeholder { .. } => window::PLACEHOLDER,
|
||||
ToplevelType::XdgToplevel { .. } => window::XDG_TOPLEVEL,
|
||||
ToplevelType::XWindow { .. } => window::X_WINDOW,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,6 +117,10 @@ request kill_client (since = 18) {
|
|||
id: pod(u64),
|
||||
}
|
||||
|
||||
request create_tree_query (since = 18) {
|
||||
id: id(jay_tree_query),
|
||||
}
|
||||
|
||||
# events
|
||||
|
||||
event client_id {
|
||||
|
|
|
|||
102
wire/jay_tree_query.txt
Normal file
102
wire/jay_tree_query.txt
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
request destroy { }
|
||||
|
||||
request execute { }
|
||||
|
||||
request set_root_display { }
|
||||
|
||||
request set_recursive {
|
||||
recursive: u32,
|
||||
}
|
||||
|
||||
request set_root_workspace {
|
||||
workspace: id(jay_workspace),
|
||||
}
|
||||
|
||||
request set_root_workspace_name {
|
||||
workspace: str,
|
||||
}
|
||||
|
||||
request set_root_toplevel {
|
||||
toplevel: id(jay_toplevel),
|
||||
}
|
||||
|
||||
request set_root_window_id {
|
||||
id: str,
|
||||
}
|
||||
|
||||
event done { }
|
||||
|
||||
event not_found { }
|
||||
|
||||
event start {
|
||||
ty: u32,
|
||||
}
|
||||
|
||||
event end { }
|
||||
|
||||
event position {
|
||||
x: i32,
|
||||
y: i32,
|
||||
w: i32,
|
||||
h: i32,
|
||||
}
|
||||
|
||||
event workspace_name {
|
||||
name: str,
|
||||
}
|
||||
|
||||
event output_name {
|
||||
name: str,
|
||||
}
|
||||
|
||||
event toplevel_id {
|
||||
id: str,
|
||||
}
|
||||
|
||||
event client_id {
|
||||
id: pod(u64),
|
||||
}
|
||||
|
||||
event title {
|
||||
title: str,
|
||||
}
|
||||
|
||||
event app_id {
|
||||
app_id: str,
|
||||
}
|
||||
|
||||
event floating { }
|
||||
|
||||
event visible { }
|
||||
|
||||
event urgent { }
|
||||
|
||||
event focused {
|
||||
global: u32,
|
||||
}
|
||||
|
||||
event fullscreen { }
|
||||
|
||||
event tag {
|
||||
tag: str,
|
||||
}
|
||||
|
||||
event x_class {
|
||||
class: str,
|
||||
}
|
||||
|
||||
event x_instance {
|
||||
instance: str,
|
||||
}
|
||||
|
||||
event x_role {
|
||||
role: str,
|
||||
}
|
||||
|
||||
event workspace {
|
||||
name: str,
|
||||
}
|
||||
|
||||
event placeholder_for {
|
||||
id: str,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue