use crate::async_engine::AsyncEngine;
use crate::async_engine::SpawnedFuture;
use crate::client::EventFormatter;
use crate::client::MIN_SERVER_ID;
use crate::client::RequestParser;
use crate::env::WAYLAND_DISPLAY;
use crate::env::XDG_RUNTIME_DIR;
use crate::env::initial_log_level;
use crate::io_uring::IoUring;
use crate::io_uring::IoUringError;
use crate::logger::Logger;
use crate::object::Version;
use crate::object::WL_DISPLAY_ID;
use crate::utils::asyncevent::AsyncEvent;
use crate::utils::bhash::BHashMap;
use crate::utils::bitfield::Bitfield;
use crate::utils::buffd::BufFdError;
use crate::utils::buffd::BufFdOut;
use crate::utils::buffd::MsgFormatter;
use crate::utils::buffd::MsgParser;
use crate::utils::buffd::MsgParserError;
use crate::utils::buffd::OutBuffer;
use crate::utils::buffd::OutBufferSwapchain;
use crate::utils::buffd::WlBufFdIn;
use crate::utils::buffd::WlMessage;
use crate::utils::clonecell::CloneCell;
use crate::utils::errorfmt::ErrorFmt;
use crate::utils::exe::ExeError;
use crate::utils::exe::ensure_same_exe;
use crate::wheel::Wheel;
use crate::wheel::WheelError;
use crate::wire::JayClientMatchBuilderId;
use crate::wire::JayClientMatchId;
use crate::wire::JayCompositor;
use crate::wire::JayCompositorId;
use crate::wire::JayDamageTracking;
use crate::wire::JayDamageTrackingId;
use crate::wire::JayGenericMatchBuilderId;
use crate::wire::JayToplevelId;
use crate::wire::JayWindowMatchBuilderId;
use crate::wire::JayWindowMatchId;
use crate::wire::JayWorkspaceId;
use crate::wire::ObjectId;
use crate::wire::WlCallbackId;
use crate::wire::WlRegistryId;
use crate::wire::WlSeatId;
use crate::wire::jay_client_match_builder;
use crate::wire::jay_compositor;
use crate::wire::jay_compositor::CreateClientMatchBuilder;
use crate::wire::jay_compositor::CreateWindowMatchBuilder;
use crate::wire::jay_compositor::EnableSymmetricDelete;
use crate::wire::jay_generic_match_builder;
use crate::wire::jay_select_toplevel;
use crate::wire::jay_select_workspace;
use crate::wire::jay_toplevel;
use crate::wire::jay_window_match_builder;
use crate::wire::wl_callback;
use crate::wire::wl_display;
use crate::wire::wl_registry;
use isnt::std_1::primitive::IsntSliceExt;
use jay_algorithms::oserror::OsError;
use jay_algorithms::oserror::OsErrorExt2;
use jay_toml_config::ClientMatch;
use jay_toml_config::GenericMatch;
use jay_toml_config::WindowMatch;
use std::cell::Cell;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::mem;
use std::rc::Rc;
use std::sync::Arc;
use thiserror::Error;
use uapi::c;
use uapi::format_ustr;
#[derive(Debug, Error)]
pub enum ToolClientError {
#[error("Could not create a timer wheel")]
CreateWheel(#[source] WheelError),
#[error("Could not create an io-uring")]
CreateRing(#[source] IoUringError),
#[error("XDG_RUNTIME_DIR is not set")]
XrdNotSet,
#[error("WAYLAND_DISPLAY is not set")]
WaylandDisplayNotSet,
#[error("Could not create a socket")]
CreateSocket(#[source] OsError),
#[error("The socket path is too long")]
SocketPathTooLong,
#[error("Could not connect to the compositor")]
Connect(#[source] IoUringError),
#[error(transparent)]
BufFdError(#[from] BufFdError),
#[error("Could not parse a message of type {}", .0)]
Parsing(&'static str, #[source] MsgParserError),
#[error("Could not read from the compositor")]
Read(#[source] BufFdError),
#[error("Could not write to the compositor")]
Write(#[source] BufFdError),
}
pub struct ToolClient {
pub logger: Arc<Logger>,
pub ring: Rc<IoUring>,
_wheel: Rc<Wheel>,
pub eng: Rc<AsyncEngine>,
obj_ids: RefCell<Bitfield>,
handlers: RefCell<
BHashMap<
ObjectId,
BHashMap<u32, Rc<dyn Fn(&mut MsgParser<'_, '_>) -> Result<(), ToolClientError>>>,
>,
>,
swapchain: Rc<RefCell<OutBufferSwapchain>>,
flush_request: AsyncEvent,
incoming: Cell<Option<SpawnedFuture<()>>>,
outgoing: Cell<Option<SpawnedFuture<()>>>,
singletons: CloneCell<Option<Rc<Singletons>>>,
jay_compositor: Cell<Option<JayCompositorId>>,
jay_compositor_version: Cell<Option<Version>>,
jay_damage_tracking: Cell<Option<Option<JayDamageTrackingId>>>,
}
pub fn with_tool_client<F>(f: F)
where
F: AsyncFnOnce(Rc<ToolClient>) + 'static,
{
if let Err(e) = with_tool_client_(f) {
handle_error(e);
}
}
fn handle_error(e: ToolClientError) -> ! {
fatal!("Could not create a tool client: {}", ErrorFmt(e));
}
fn with_tool_client_<F>(f: F) -> Result<(), ToolClientError>
where
F: AsyncFnOnce(Rc<ToolClient>) + 'static,
{
reset_sigpipe();
let logger = Logger::install_stderr(initial_log_level());
let eng = AsyncEngine::new();
let ring = match IoUring::new(&eng, 32) {
Ok(e) => e,
Err(e) => return Err(ToolClientError::CreateRing(e)),
};
let eng2 = eng.clone();
let ring2 = ring.clone();
let _f = eng.spawn("tool client", async move {
let tc = match ToolClient::try_new(logger, eng2, ring2).await {
Ok(tc) => tc,
Err(e) => handle_error(e),
};
f(tc).await;
std::process::exit(0);
});
if let Err(e) = ring.run() {
fatal!("A fatal error occurred: {}", ErrorFmt(e));
}
Ok(())
}
fn reset_sigpipe() {
unsafe {
c::signal(c::SIGPIPE, c::SIG_DFL);
}
}
impl ToolClient {
async fn try_new(
logger: Arc<Logger>,
eng: Rc<AsyncEngine>,
ring: Rc<IoUring>,
) -> Result<Rc<Self>, ToolClientError> {
let wheel = match Wheel::new(&eng, &ring) {
Ok(w) => w,
Err(e) => return Err(ToolClientError::CreateWheel(e)),
};
let Some(xrd) = *XDG_RUNTIME_DIR else {
return Err(ToolClientError::XrdNotSet);
};
let Some(wd) = *WAYLAND_DISPLAY else {
return Err(ToolClientError::WaylandDisplayNotSet);
};
let mut path = format_ustr!("{}/{}", xrd, wd);
let suffix = b".jay";
if path.not_ends_with(suffix) {
path.push(suffix.as_slice());
}
let socket = uapi::socket(c::AF_UNIX, c::SOCK_STREAM | c::SOCK_CLOEXEC, 0)
.map(Rc::new)
.map_os_err(ToolClientError::CreateSocket)?;
let mut addr: c::sockaddr_un = uapi::pod_zeroed();
addr.sun_family = c::AF_UNIX as _;
if path.len() >= addr.sun_path.len() {
return Err(ToolClientError::SocketPathTooLong);
}
let sun_path = uapi::as_bytes_mut(&mut addr.sun_path[..]);
sun_path[..path.len()].copy_from_slice(path.as_bytes());
sun_path[path.len()] = 0;
if let Err(e) = ring.connect(&socket, &addr).await {
return Err(ToolClientError::Connect(e));
}
let mut obj_ids = Bitfield::default();
obj_ids.take(0);
obj_ids.take(1);
let slf = Rc::new(Self {
logger,
ring,
_wheel: wheel,
eng,
obj_ids: RefCell::new(obj_ids),
handlers: Default::default(),
swapchain: Default::default(),
flush_request: Default::default(),
incoming: Default::default(),
outgoing: Default::default(),
singletons: Default::default(),
jay_compositor: Default::default(),
jay_compositor_version: Default::default(),
jay_damage_tracking: Default::default(),
});
wl_display::Error::handle(&slf, WL_DISPLAY_ID, (), |_, val| {
fatal!("The compositor returned a fatal error: {}", val.message);
});
wl_display::DeleteId::handle(&slf, WL_DISPLAY_ID, slf.clone(), |tc, val| {
tc.handlers.borrow_mut().remove(&val.id);
if val.id.raw() < MIN_SERVER_ID {
tc.obj_ids.borrow_mut().release64(val.id.raw());
}
});
slf.incoming.set(Some(
slf.eng.spawn(
"tool client incoming",
Incoming {
tc: slf.clone(),
buf: WlBufFdIn::new(&socket, &slf.ring),
}
.run(),
),
));
slf.outgoing.set(Some(
slf.eng.spawn(
"tool client outgoing",
Outgoing {
tc: slf.clone(),
buf: BufFdOut::new(&socket, &slf.ring),
buffers: Default::default(),
}
.run(),
),
));
slf.send(EnableSymmetricDelete {
self_id: slf.jay_compositor().await,
});
Ok(slf)
}
fn handle<T, R, H>(self: &Rc<Self>, id: ObjectId, recv: R, h: H)
where
T: RequestParser<'static>,
R: 'static,
H: for<'a> Fn(&R, T::Generic<'a>) + 'static,
{
let handler: Rc<dyn Fn(&mut MsgParser<'_, '_>) -> Result<(), ToolClientError>> =
Rc::new(move |parser| {
let val = Self::parse::<T>(parser)?;
h(&recv, val);
Ok(())
});
self.add_handler(id, T::ID, handler);
}
fn parse<'a, T: RequestParser<'static>>(
parser: &mut MsgParser<'_, 'a>,
) -> Result<T::Generic<'a>, ToolClientError> {
match <T::Generic<'a> as RequestParser<'_>>::parse(parser) {
Ok(val) => Ok(val),
Err(e) => Err(ToolClientError::Parsing(std::any::type_name::<T>(), e)),
}
}
fn add_handler(
self: &Rc<Self>,
id: ObjectId,
req: u32,
handler: Rc<dyn Fn(&mut MsgParser<'_, '_>) -> Result<(), ToolClientError>>,
) {
let mut handlers = self.handlers.borrow_mut();
handlers.entry(id).or_default().insert(req, handler);
}
pub fn send<M: EventFormatter>(&self, msg: M) {
let mut fds = vec![];
let mut swapchain = self.swapchain.borrow_mut();
let mut fmt = MsgFormatter::new(&mut swapchain.cur, &mut fds);
msg.format(&mut fmt);
fmt.write_len();
if swapchain.cur.is_full() {
swapchain.commit();
}
self.flush_request.trigger();
}
pub fn id<T: From<ObjectId>>(&self) -> T {
let id = self.obj_ids.borrow_mut().acquire64();
ObjectId::from_raw(id).into()
}
pub async fn round_trip(self: &Rc<Self>) {
let callback: WlCallbackId = self.id();
self.send(wl_display::Sync {
self_id: WL_DISPLAY_ID,
callback,
});
let ah = Rc::new(AsyncEvent::default());
wl_callback::Done::handle(self, callback, ah.clone(), |ah, _| {
ah.trigger();
});
ah.triggered().await;
}
async fn singletons(self: &Rc<Self>) -> Rc<Singletons> {
if let Some(res) = self.singletons.get() {
return res;
}
#[derive(Default)]
struct S {
jay_compositor: Cell<Option<(u32, u32)>>,
jay_damage_tracking: Cell<Option<u32>>,
}
let s = Rc::new(S::default());
let registry: WlRegistryId = self.id();
self.send(wl_display::GetRegistry {
self_id: WL_DISPLAY_ID,
registry,
});
wl_registry::Global::handle(self, registry, s.clone(), |s, g| {
if g.interface == JayCompositor.name() {
s.jay_compositor.set(Some((g.name, g.version)));
} else if g.interface == JayDamageTracking.name() {
s.jay_damage_tracking.set(Some(g.name));
}
});
self.round_trip().await;
macro_rules! get {
($field:ident, $if:expr) => {
match s.$field.get() {
Some(j) => j,
_ => fatal!("Compositor does not provide the {} singleton", $if.name()),
}
};
}
let res = Rc::new(Singletons {
registry,
jay_compositor: get!(jay_compositor, JayCompositor),
jay_damage_tracking: s.jay_damage_tracking.get(),
});
self.singletons.set(Some(res.clone()));
res
}
pub async fn jay_compositor(self: &Rc<Self>) -> JayCompositorId {
if let Some(id) = self.jay_compositor.get() {
return id;
}
let s = self.singletons().await;
let id: JayCompositorId = self.id();
self.send(wl_registry::Bind {
self_id: s.registry,
name: s.jay_compositor.0,
interface: JayCompositor.name(),
version: s.jay_compositor.1.min(44),
id: id.into(),
});
self.jay_compositor.set(Some(id));
id
}
pub async fn jay_compositor_version(self: &Rc<Self>) -> Version {
if let Some(v) = self.jay_compositor_version.get() {
return v;
}
let v = Version(self.singletons().await.jay_compositor.1);
self.jay_compositor_version.set(Some(v));
v
}
pub async fn jay_damage_tracking(self: &Rc<Self>) -> Option<JayDamageTrackingId> {
if let Some(id) = self.jay_damage_tracking.get() {
return id;
}
let s = self.singletons().await;
let Some(name) = s.jay_damage_tracking else {
self.jay_damage_tracking.set(Some(None));
return None;
};
let id: JayDamageTrackingId = self.id();
self.send(wl_registry::Bind {
self_id: s.registry,
name,
interface: JayDamageTracking.name(),
version: 1,
id: id.into(),
});
self.jay_damage_tracking.set(Some(Some(id)));
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 {
self_id: self.jay_compositor().await,
id,
seat: WlSeatId::NONE,
});
let ae = Rc::new(AsyncEvent::default());
let client_id = Rc::new(Cell::new(0));
jay_select_toplevel::Done::handle(
self,
id,
(self.clone(), ae.clone(), client_id.clone()),
|(tc, ae, client_id), event| {
if event.id.is_some() {
jay_toplevel::ClientId::handle(
tc,
event.id,
client_id.clone(),
|client_id, event| {
client_id.set(event.id);
},
);
jay_toplevel::Done::handle(tc, event.id, ae.clone(), |ae, _event| {
ae.trigger();
});
} else {
ae.trigger();
}
},
);
ae.triggered().await;
client_id.get()
}
pub async fn ensure_same_exe(self: &Rc<Self>) -> Result<(), ExeError> {
let comp = self.jay_compositor().await;
let exe = Rc::new(Cell::new(None));
jay_compositor::Exe::handle(self, comp, exe.clone(), |exe, msg| {
exe.set(Some(msg.exe));
});
self.send(jay_compositor::GetExe { self_id: comp });
self.round_trip().await;
let exe = exe.take().unwrap();
ensure_same_exe(&exe)
}
}
pub struct Singletons {
registry: WlRegistryId,
jay_compositor: (u32, u32),
jay_damage_tracking: Option<u32>,
}
pub trait Handle: RequestParser<'static> {
fn handle<R, H>(tl: &Rc<ToolClient>, id: impl Into<ObjectId>, r: R, h: H)
where
R: 'static,
H: for<'a> Fn(&R, Self::Generic<'a>) + 'static;
}
impl<T: RequestParser<'static>> Handle for T {
fn handle<R, H>(tl: &Rc<ToolClient>, id: impl Into<ObjectId>, r: R, h: H)
where
R: 'static,
H: for<'a> Fn(&R, T::Generic<'a>) + 'static,
{
tl.handle::<Self, _, _>(id.into(), r, h);
}
}
struct Outgoing {
tc: Rc<ToolClient>,
buf: BufFdOut,
buffers: VecDeque<OutBuffer>,
}
impl Outgoing {
async fn run(mut self) {
loop {
self.tc.flush_request.triggered().await;
if let Err(e) = self.flush().await {
fatal!("Could not process an outgoing message: {}", ErrorFmt(e));
}
}
}
async fn flush(&mut self) -> Result<(), ToolClientError> {
{
let mut swapchain = self.tc.swapchain.borrow_mut();
swapchain.commit();
mem::swap(&mut swapchain.pending, &mut self.buffers);
}
while let Some(mut cur) = self.buffers.pop_front() {
if let Err(e) = self.buf.flush_no_timeout(&mut cur).await {
return Err(ToolClientError::Write(e));
}
self.tc.swapchain.borrow_mut().free.push(cur);
}
Ok(())
}
}
struct Incoming {
tc: Rc<ToolClient>,
buf: WlBufFdIn,
}
impl Incoming {
async fn run(mut self) {
loop {
if let Err(e) = self.handle_msg().await {
fatal!("Could not process an incoming message: {}", ErrorFmt(e));
}
}
}
async fn handle_msg(&mut self) -> Result<(), ToolClientError> {
let WlMessage {
obj_id,
message,
body,
fds,
} = self
.buf
.read_message()
.await
.map_err(ToolClientError::Read)?;
let mut handler = None;
{
let handlers = self.tc.handlers.borrow_mut();
if let Some(handlers) = handlers.get(&obj_id) {
handler = handlers.get(&message).cloned();
}
}
if let Some(handler) = handler {
let mut parser = MsgParser::new(fds, body);
handler(&mut parser)?;
}
Ok(())
}
}
macro_rules! declare_matcher_macros {
($slf:expr, $module:ident, $gmb:expr, $cmb:expr) => {
declare_matcher_macros!($slf, $module, $gmb, $cmb, $);
};
($slf:expr, $module:ident, $gmb:expr, $cmb:expr, $dol:tt) => {
#[allow(unused_macros)]
macro_rules! str {
($dol s:ident, $dol regex:ident, $dol m:ident $dol (,)?) => {
if let Some(v) = $dol s {
$slf.send($module::$dol m {
self_id: $cmb,
v,
regex: false,
});
}
if let Some(v) = $dol regex {
$slf.send($module::$dol m {
self_id: $cmb,
v,
regex: true,
});
}
};
}
#[allow(unused_macros)]
macro_rules! bool {
($dol s:ident, $dol m:ident) => {
if let Some(v) = *$dol s {
$slf.send($module::$dol m { self_id: $cmb });
$slf.generic_match_bool($gmb, v);
}
};
}
#[allow(unused_macros)]
macro_rules! num {
($dol s:ident, $dol m:ident) => {
if let Some(v) = *$dol s {
$slf.send($module::$dol m { self_id: $cmb, v });
}
};
}
#[allow(unused_macros)]
macro_rules! num2 {
($dol s:ident, $dol m:ident) => {
if let Some(v) = *$dol s {
$slf.send($module::$dol m { self_id: $cmb, v: v.0 });
}
};
}
};
}
impl ToolClient {
fn generic_match_bool(&self, gmb: JayGenericMatchBuilderId, v: bool) {
if !v {
self.send(jay_generic_match_builder::Not { self_id: gmb });
}
}
fn generic_match<T>(&self, gmb: JayGenericMatchBuilderId, m: &GenericMatch<T>, f: impl Fn(&T)) {
if let Some(v) = &m.not {
self.create_match(gmb, || f(v));
self.send(jay_generic_match_builder::Not { self_id: gmb });
}
macro_rules! list {
($f:ident, $all:expr) => {
if let Some(v) = &m.$f {
self.send(jay_generic_match_builder::Nest { self_id: gmb });
for v in v {
self.create_match(gmb, || f(v));
}
self.send(jay_generic_match_builder::List {
self_id: gmb,
all: $all,
});
}
};
}
list!(all, true);
list!(any, false);
if let Some(v) = &m.exactly {
self.send(jay_generic_match_builder::Nest { self_id: gmb });
for v in &v.list {
self.create_match(gmb, || f(v));
}
self.send(jay_generic_match_builder::Exactly {
self_id: gmb,
n: v.num,
});
}
}
fn create_match(&self, gmb: JayGenericMatchBuilderId, f: impl FnOnce()) {
self.send(jay_generic_match_builder::Nest { self_id: gmb });
f();
self.send(jay_generic_match_builder::List {
self_id: gmb,
all: true,
});
}
fn create_client_match_(
&self,
gmb: JayGenericMatchBuilderId,
cmb: JayClientMatchBuilderId,
m: &ClientMatch,
) {
declare_matcher_macros!(self, jay_client_match_builder, gmb, cmb);
let ClientMatch {
generic,
sandbox_engine,
sandbox_engine_regex,
sandbox_app_id,
sandbox_app_id_regex,
sandbox_instance_id,
sandbox_instance_id_regex,
sandboxed,
uid,
pid,
is_xwayland,
comm,
comm_regex,
exe,
exe_regex,
tag,
tag_regex,
} = m;
self.generic_match(gmb, generic, |m| self.create_client_match_(gmb, cmb, m));
str!(sandbox_engine, sandbox_engine_regex, SandboxEngine);
str!(sandbox_app_id, sandbox_app_id_regex, SandboxAppId);
str!(
sandbox_instance_id,
sandbox_instance_id_regex,
SandboxInstanceId,
);
str!(comm, comm_regex, Comm);
str!(exe, exe_regex, Exe);
str!(tag, tag_regex, Tag);
bool!(sandboxed, Sandboxed);
bool!(is_xwayland, IsXwayland);
num!(pid, Pid);
num!(uid, Uid);
}
pub fn create_client_match(&self, comp: JayCompositorId, m: &ClientMatch) -> JayClientMatchId {
let gmb = self.id();
let cmb = self.id();
self.send(CreateClientMatchBuilder {
self_id: comp,
gmb,
cmb,
});
self.create_match(gmb, || self.create_client_match_(gmb, cmb, m));
let jcm = self.id();
self.send(jay_client_match_builder::Get {
self_id: cmb,
id: jcm,
});
self.send(jay_generic_match_builder::Destroy { self_id: gmb });
self.send(jay_client_match_builder::Destroy { self_id: cmb });
jcm
}
fn create_window_match_(
self: &Rc<Self>,
comp: JayCompositorId,
gmb: JayGenericMatchBuilderId,
cmb: JayWindowMatchBuilderId,
m: &WindowMatch,
) {
declare_matcher_macros!(self, jay_window_match_builder, gmb, cmb);
let WindowMatch {
generic,
types,
client,
title,
title_regex,
app_id,
app_id_regex,
floating,
visible,
urgent,
focused,
fullscreen,
just_mapped,
tag,
tag_regex,
x_class,
x_class_regex,
x_instance,
x_instance_regex,
x_role,
x_role_regex,
workspace,
workspace_regex,
content_types,
is_workspace_container,
} = m;
self.generic_match(gmb, generic, |m| {
self.create_window_match_(comp, gmb, cmb, m);
});
if let Some(c) = client {
self.send(jay_window_match_builder::Client {
self_id: cmb,
v: self.create_client_match(comp, c),
});
}
str!(title, title_regex, Title);
str!(app_id, app_id_regex, AppId);
str!(tag, tag_regex, Tag);
str!(x_class, x_class_regex, XClass);
str!(x_instance, x_instance_regex, XInstance);
str!(x_role, x_role_regex, XRole);
str!(workspace, workspace_regex, Workspace);
bool!(floating, Floating);
bool!(visible, Visible);
bool!(urgent, Urgent);
bool!(focused, Focused);
bool!(fullscreen, Fullscreen);
bool!(just_mapped, JustMapped);
bool!(is_workspace_container, IsWorkspaceContainer);
num2!(types, Types);
num2!(content_types, ContentTypes);
}
pub fn create_window_match(
self: &Rc<Self>,
comp: JayCompositorId,
m: &WindowMatch,
) -> JayWindowMatchId {
let gmb = self.id();
let cmb = self.id();
self.send(CreateWindowMatchBuilder {
self_id: comp,
gmb,
cmb,
});
self.create_match(gmb, || self.create_window_match_(comp, gmb, cmb, m));
let jcm = self.id();
self.send(jay_window_match_builder::Get {
self_id: cmb,
id: jcm,
});
self.send(jay_generic_match_builder::Destroy { self_id: gmb });
self.send(jay_window_match_builder::Destroy { self_id: cmb });
jcm
}
}