1
0
Fork 0
forked from wry/wry

cli: add commands to inspect the tree

This commit is contained in:
Julian Orth 2025-05-06 18:08:14 +02:00
parent bd04b09171
commit 38d7a60d00
14 changed files with 1072 additions and 8 deletions

View file

@ -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
View 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;
}
}
}

View file

@ -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;

View file

@ -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
View 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);

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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,
),

View file

@ -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 {

View file

@ -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,
}

View file

@ -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
View 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,
}