Merge pull request #451 from mahkoh/jorth/window-rules
Add client and window rules
This commit is contained in:
commit
401e8bb0be
112 changed files with 10610 additions and 455 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -602,6 +602,7 @@ dependencies = [
|
|||
"pin-project",
|
||||
"png",
|
||||
"rand",
|
||||
"regex",
|
||||
"repc",
|
||||
"rustc-demangle",
|
||||
"serde",
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ rustc-demangle = { version = "0.1.24", optional = true }
|
|||
tracy-client-sys = { version = "0.24.1", features = ["ondemand", "manual-lifetime", "debuginfod", "demangle"], optional = true }
|
||||
kbvm = "0.1.4"
|
||||
tiny-skia = { version = "0.11.4", default-features = false, features = ["std"] }
|
||||
regex = "1.11.1"
|
||||
|
||||
[build-dependencies]
|
||||
repc = "0.1.1"
|
||||
|
|
|
|||
|
|
@ -513,3 +513,7 @@ The default configuration will try to start [wl-tray-bridge] to give you access
|
|||
icons and menus.
|
||||
|
||||
[wl-tray-bridge]: https://github.com/mahkoh/wl-tray-bridge
|
||||
|
||||
### Window and Client Rules
|
||||
|
||||
This is described in [window-and-client-rules.md](window-and-client-rules.md).
|
||||
|
|
|
|||
|
|
@ -49,6 +49,10 @@ Commands:
|
|||
portal Run the desktop portal
|
||||
randr Inspect/modify graphics card and connector settings
|
||||
input Inspect/modify input settings
|
||||
xwayland Inspect/modify xwayland settings
|
||||
color-management Inspect/modify the color-management settings
|
||||
clients Inspect/manipulate the connected clients
|
||||
tree Inspect the surface tree
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
|
|
@ -138,6 +142,12 @@ Jay uses frame scheduling to achieve input latency as low as 1.5 ms.
|
|||
|
||||
Jay supports the color management protocol and HDR10.
|
||||
|
||||
## Window and Client Rules
|
||||
|
||||
Jay supports powerful window and client rules.
|
||||
|
||||
See [window-and-client-rules.md](window-and-client-rules.md) for more details.
|
||||
|
||||
## Protocol Support
|
||||
|
||||
Jay supports the following wayland protocols:
|
||||
|
|
|
|||
259
docs/window-and-client-rules.md
Normal file
259
docs/window-and-client-rules.md
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
# Window and Client Rules
|
||||
|
||||
Jay supports powerful window and client rules similar to i3.
|
||||
|
||||
## Example
|
||||
|
||||
```toml
|
||||
# Move spotify to workspace 3 and fullscreen it.
|
||||
[[windows]]
|
||||
match.client.sandbox-app-id = "com.spotify.Client"
|
||||
action = [
|
||||
{ type = "move-to-workspace", name = "3" },
|
||||
"enter-fullscreen",
|
||||
]
|
||||
|
||||
# Spawn the Chromium screen sharing window, the GIMP splash screen, and the
|
||||
# JetBrains splash screen floating and without focus stealing.
|
||||
[[windows]]
|
||||
match.any = [
|
||||
{ title-regex = 'is sharing (your screen|a window)\.$', client.comm = "chromium" },
|
||||
{ title = "GIMP Startup", app-id = "gimp" },
|
||||
{ title = "splash", x-class-regex = "^jetbrains-(clion|rustrover)$" }
|
||||
]
|
||||
initial-tile-state = "floating"
|
||||
auto-focus = false
|
||||
|
||||
# Spawn the JetBrains project selector floating.
|
||||
[[windows]]
|
||||
match.title-regex = "^Welcome to (RustRover|CLion)$"
|
||||
match.x-class-regex = "^jetbrains-(clion|rustrover)$"
|
||||
initial-tile-state = "floating"
|
||||
```
|
||||
|
||||
## General Principles
|
||||
|
||||
Each rule consists of three components:
|
||||
|
||||
1. Criteria that determine which clients/windows the rule applies to.
|
||||
2. An action to execute when a client/window starts matching the rule.
|
||||
3. An action to execute when a client/window stops matching the rule.
|
||||
|
||||
Each rule can be assigned a name which allows other rules to refer to it.
|
||||
|
||||
Additionally, rules have ad-hoc properties for things that are not easily
|
||||
expressed via actions, such as whether a window should be mapped floating or
|
||||
tiled.
|
||||
|
||||
```toml
|
||||
[[windows]]
|
||||
name = "..." # the rule name
|
||||
match = { } # the rule criteria
|
||||
action = "..." # the action to run on start
|
||||
latch = "..." # the action to run on stop
|
||||
```
|
||||
|
||||
Rules are re-evaluated whenever any of the referenced criteria changes. That is,
|
||||
if you have the following rule
|
||||
|
||||
```toml
|
||||
[[windows]]
|
||||
match.title = "VIM"
|
||||
action = "enter-fullscreen"
|
||||
```
|
||||
|
||||
then the window will enter fullscreen whenever title changes from something that
|
||||
is not `VIM` to `VIM`. For window rules, if you only want to match windows that
|
||||
have just been mapped, you can set the `just-mapped` criterion to `true`:
|
||||
|
||||
```toml
|
||||
[[windows]]
|
||||
match.title = "VIM"
|
||||
match.just-mapped = true
|
||||
action = "enter-fullscreen"
|
||||
```
|
||||
|
||||
This is similar to the `initial-title` criterion found in some other
|
||||
compositors.
|
||||
|
||||
Rules can trigger each other. For example:
|
||||
|
||||
```toml
|
||||
[[windows]]
|
||||
match.fullscreen = false
|
||||
action = "enter-fullscreen"
|
||||
|
||||
[[windows]]
|
||||
match.fullscreen = true
|
||||
action = "exit-fullscreen"
|
||||
```
|
||||
|
||||
This causes an infinite repetition of switching between windowed and fullscreen.
|
||||
Jay prevents such loops from locking up the compositor by never performing more
|
||||
than 1000 action callbacks before yielding to other work. However, they will
|
||||
still cause the compositor to use 100% CPU and will likely cause affected
|
||||
clients to be killed, since they won't be able to receive wayland messages fast
|
||||
enough.
|
||||
|
||||
## Combining Criteria
|
||||
|
||||
Criteria can be combined with the following operations:
|
||||
|
||||
- `any` - match if any of a number of criteria match
|
||||
- `all` - match if all of a number of criteria match
|
||||
- `not` - match if a criterion does not match
|
||||
- `exactly` - match if an exact number of criteria match
|
||||
- `name` - match if another window rule with that name matches
|
||||
|
||||
```toml
|
||||
# match windows that have the title `chromium` or `spotify`
|
||||
match.any = [
|
||||
{ title = "chromium" },
|
||||
{ title = "spotify" },
|
||||
]
|
||||
|
||||
# match windows whose title match both `chro` and `mium`
|
||||
match.all = [
|
||||
{ title-regex = "chro" },
|
||||
{ title-regex = "mium" },
|
||||
]
|
||||
|
||||
# match windows whose title is not `firefox`
|
||||
match.not.title = "firefox"
|
||||
|
||||
# match windows whose title is `VIM` or whose clients are sandboxed, but not
|
||||
# both
|
||||
match.exactly.num = 1
|
||||
match.exactly.list = [
|
||||
{ title = "VIM" },
|
||||
{ client.sandboxed = true },
|
||||
]
|
||||
|
||||
# match if another rule called `another-rule-name` matches
|
||||
match.name = "another-rule-name"
|
||||
```
|
||||
|
||||
A criterion object has multiple fields, for example
|
||||
|
||||
```toml
|
||||
match.title = "abc"
|
||||
match.app-id = "xyz"
|
||||
```
|
||||
|
||||
These fields are implicitly combined with `all` operator. That is, this behaves
|
||||
just like
|
||||
|
||||
```toml
|
||||
match.all = [
|
||||
{ title = "abc" },
|
||||
{ app-id = "xyz" },
|
||||
]
|
||||
```
|
||||
|
||||
## Finding Criteria Values
|
||||
|
||||
To determine which values to use in criteria, the `jay` executable provides the
|
||||
subcommands `jay clients` and `jay tree` to inspect currently active clients and
|
||||
open windows. For example
|
||||
|
||||
```text
|
||||
~$ jay tree query select-window
|
||||
- xdg-toplevel:
|
||||
id: 258ae697663a1b8abc7e4da9570ad36f
|
||||
pos: 1920x36 + 1920x1044
|
||||
client:
|
||||
id: 15
|
||||
uid: 1000
|
||||
pid: 2159136
|
||||
comm: chromium
|
||||
exe: /usr/lib/chromium/chromium
|
||||
title: YouTube - Chromium
|
||||
app-id: chromium
|
||||
workspace: 2
|
||||
visible
|
||||
```
|
||||
|
||||
In this case, `select-window` allows you to interactively select a window and
|
||||
then prints its properties.
|
||||
|
||||
## Client Rules
|
||||
|
||||
```toml
|
||||
# start executable `b` whenever a client with executable `A` connects
|
||||
[[clients]]
|
||||
match.exe = "A"
|
||||
action = { type = "exec", exec = "b" }
|
||||
```
|
||||
|
||||
All properties that can be referred to in client criteria are currently
|
||||
constant over the lifetime of the client.
|
||||
|
||||
### Client Criteria
|
||||
|
||||
The full specification of client criteria can be found in
|
||||
[spec.generated.md](../toml-spec/spec/spec.generated.md).
|
||||
|
||||
- `sandboxed` - Matches clients that are/aren't sandboxed.
|
||||
- `sandbox-engine`, `sandbox-engine-regex` - Matches the sandbox engine that was
|
||||
used to wrap this client. Usually `org.flatpak`.
|
||||
- `sandbox-app-id`, `sandbox-app-id-regex` - Matches the app-id provided by the
|
||||
sandbox engine
|
||||
- `sandbox-instance-id-id`, `sandbox-instance-id-regex` - Matches the
|
||||
instance-id provided by the sandbox engine
|
||||
- `uid`, `pid` - Matches the UID/PID of the client.
|
||||
- `is-xwayland` - Matches if the client is/isn't Xwayland.
|
||||
- `comm`, `comm-regex` - Matches the `/proc/self/comm` of the client.
|
||||
- `exe`, `exe-regex` - Matches the `/proc/self/exe` of the client.
|
||||
|
||||
## Window Rules
|
||||
|
||||
## Ad-hoc Window Rules
|
||||
|
||||
Rule actions are evaluated asynchronously. For window rules, this means that
|
||||
they are evaluated after the window has been mapped but before it is displayed
|
||||
for the first time. This makes them ill-suited for things that need to be fixed
|
||||
during the mapping process. Ad-hoc window rules can be used to bridge this gap:
|
||||
|
||||
```toml
|
||||
[[windows]]
|
||||
match.title = "chromium"
|
||||
initial-tile-state = "floating"
|
||||
auto-focus = false
|
||||
```
|
||||
|
||||
The `initial-tile-state` rule can be used to define whether the window is mapped
|
||||
tiled or floating. If no such rule exists, this is determined via heuristics.
|
||||
If multiple such rules exist and match a window, the compositor picks one at
|
||||
random.
|
||||
|
||||
The `auto-focus` rule determines if the window is automatically focused when it
|
||||
is mapped. If no such rule exists, newly mapped windows always get the keyboard
|
||||
focus except in some cases involving Xwayland. If multiple such rules exist and
|
||||
match a window, then the window _does not_ get the focus if _any_ of them is set
|
||||
to `false`.
|
||||
|
||||
## Window Criteria
|
||||
|
||||
The full specification of window criteria can be found in
|
||||
[spec.generated.md](../toml-spec/spec/spec.generated.md).
|
||||
|
||||
- `types` - Matches the type of a window. Currently there are four types:
|
||||
containers, placeholders, xdg toplevels, and X windows. If the rule does not
|
||||
contain such a criterion, the rule will only match windows created by clients,
|
||||
that is, xdg toplevels and X windows.
|
||||
- `client` - This is a client criterion. See above.
|
||||
- `title`, `title-regex` - Matches the title of the window.
|
||||
- `app-id`, `app-id-regex` - Matches the XDG app-id of the window.
|
||||
- `floating` - Matches if the window is/isn't floating.
|
||||
- `visible` - Matches if the window is/isn't visible.
|
||||
- `urgent` - Matches if the window wants/doesn't want attentions.
|
||||
- `focused` - Matches if the window is/isn't focused.
|
||||
- `fullscreen` - Matches if the window is/isn't fullscreen.
|
||||
- `just-mapped` - Matches if the window has/hasn't just been mapped. This is
|
||||
- `just-mapped` - Matches if the window has/hasn't just been mapped. This is
|
||||
true for a single frame after the window has been mapped.
|
||||
- `tag`, `tag-regex` - Matches the XDG toplevel tag of the window.
|
||||
- `x-class`, `x-class-regex` - Matches the X class of the window.
|
||||
- `x-instance`, `x-instance-regex` - Matches the X instance of the window.
|
||||
- `x-role`, `x-role-regex` - Matches the X role of the window.
|
||||
- `workspace`, `workspace-regex` - Matches the workspace of the window.
|
||||
|
|
@ -4,7 +4,13 @@ mod logging;
|
|||
pub(crate) mod string_error;
|
||||
|
||||
use {
|
||||
crate::video::Mode,
|
||||
crate::{
|
||||
Workspace,
|
||||
client::ClientMatcher,
|
||||
input::Seat,
|
||||
video::Mode,
|
||||
window::{WindowMatcher, WindowType},
|
||||
},
|
||||
bincode::Options,
|
||||
serde::{Deserialize, Serialize},
|
||||
std::marker::PhantomData,
|
||||
|
|
@ -64,3 +70,64 @@ impl WireMode {
|
|||
pub struct PollableId(pub u64);
|
||||
|
||||
pub const DEFAULT_SEAT_NAME: &str = "default";
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub enum GenericCriterionIpc<T> {
|
||||
Matcher(T),
|
||||
Not(T),
|
||||
List { list: Vec<T>, all: bool },
|
||||
Exactly { list: Vec<T>, num: usize },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub enum ClientCriterionIpc {
|
||||
Generic(GenericCriterionIpc<ClientMatcher>),
|
||||
String {
|
||||
string: String,
|
||||
field: ClientCriterionStringField,
|
||||
regex: bool,
|
||||
},
|
||||
Sandboxed,
|
||||
Uid(i32),
|
||||
Pid(i32),
|
||||
IsXwayland,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub enum ClientCriterionStringField {
|
||||
SandboxEngine,
|
||||
SandboxAppId,
|
||||
SandboxInstanceId,
|
||||
Comm,
|
||||
Exe,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub enum WindowCriterionIpc {
|
||||
Generic(GenericCriterionIpc<WindowMatcher>),
|
||||
String {
|
||||
string: String,
|
||||
field: WindowCriterionStringField,
|
||||
regex: bool,
|
||||
},
|
||||
Types(WindowType),
|
||||
Client(ClientMatcher),
|
||||
Floating,
|
||||
Visible,
|
||||
Urgent,
|
||||
SeatFocus(Seat),
|
||||
Fullscreen,
|
||||
JustMapped,
|
||||
Workspace(Workspace),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub enum WindowCriterionStringField {
|
||||
Title,
|
||||
AppId,
|
||||
Tag,
|
||||
XClass,
|
||||
XInstance,
|
||||
XRole,
|
||||
Workspace,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,16 @@
|
|||
use {
|
||||
crate::{
|
||||
_private::{
|
||||
Config, ConfigEntry, ConfigEntryGen, PollableId, VERSION, WireMode, bincode_ops,
|
||||
ClientCriterionIpc, ClientCriterionStringField, Config, ConfigEntry, ConfigEntryGen,
|
||||
GenericCriterionIpc, PollableId, VERSION, WindowCriterionIpc,
|
||||
WindowCriterionStringField, WireMode, bincode_ops,
|
||||
ipc::{
|
||||
ClientMessage, InitMessage, Response, ServerFeature, ServerMessage, WorkspaceSource,
|
||||
},
|
||||
logging,
|
||||
},
|
||||
Axis, Direction, ModifiedKeySym, PciId, Workspace,
|
||||
client::{Client, ClientCriterion, ClientMatcher, MatchedClient},
|
||||
exec::Command,
|
||||
input::{
|
||||
FocusFollowsMouseMode, InputDevice, Seat, SwitchEvent, acceleration::AccelProfile,
|
||||
|
|
@ -29,6 +32,7 @@ use {
|
|||
Transform, VrrMode,
|
||||
connector_type::{CON_UNKNOWN, ConnectorType},
|
||||
},
|
||||
window::{MatchedWindow, TileState, Window, WindowCriterion, WindowMatcher, WindowType},
|
||||
xwayland::XScalingMode,
|
||||
},
|
||||
bincode::Options,
|
||||
|
|
@ -81,7 +85,7 @@ struct KeyHandler {
|
|||
latched: Vec<Box<dyn FnOnce()>>,
|
||||
}
|
||||
|
||||
pub(crate) struct Client {
|
||||
pub(crate) struct ConfigClient {
|
||||
configure: extern "C" fn(),
|
||||
srv_data: *const u8,
|
||||
srv_unref: unsafe extern "C" fn(data: *const u8),
|
||||
|
|
@ -110,10 +114,22 @@ pub(crate) struct Client {
|
|||
status_task: Cell<Vec<JoinHandle<()>>>,
|
||||
i3bar_separator: RefCell<Option<Rc<String>>>,
|
||||
pressed_keysym: Cell<Option<KeySym>>,
|
||||
client_match_handlers: RefCell<HashMap<ClientMatcher, ClientMatchHandler>>,
|
||||
window_match_handlers: RefCell<HashMap<WindowMatcher, WindowMatchHandler>>,
|
||||
|
||||
feat_mod_mask: Cell<bool>,
|
||||
}
|
||||
|
||||
struct ClientMatchHandler {
|
||||
cb: Callback<MatchedClient>,
|
||||
latched: HashMap<Client, Box<dyn FnOnce()>>,
|
||||
}
|
||||
|
||||
struct WindowMatchHandler {
|
||||
cb: Callback<MatchedWindow>,
|
||||
latched: HashMap<Window, Box<dyn FnOnce()>>,
|
||||
}
|
||||
|
||||
struct Interest {
|
||||
result: Option<Result<(), String>>,
|
||||
waker: Option<Waker>,
|
||||
|
|
@ -145,7 +161,7 @@ struct Task {
|
|||
waker: Waker,
|
||||
}
|
||||
|
||||
impl Drop for Client {
|
||||
impl Drop for ConfigClient {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(self.srv_unref)(self.srv_data);
|
||||
|
|
@ -154,13 +170,13 @@ impl Drop for Client {
|
|||
}
|
||||
|
||||
thread_local! {
|
||||
pub(crate) static CLIENT: Cell<*const Client> = const { Cell::new(ptr::null()) };
|
||||
pub(crate) static CLIENT: Cell<*const ConfigClient> = const { Cell::new(ptr::null()) };
|
||||
}
|
||||
|
||||
unsafe fn with_client<T, F: FnOnce(&Client) -> T>(data: *const u8, f: F) -> T {
|
||||
unsafe fn with_client<T, F: FnOnce(&ConfigClient) -> T>(data: *const u8, f: F) -> T {
|
||||
struct Reset<'a> {
|
||||
cell: &'a Cell<*const Client>,
|
||||
val: *const Client,
|
||||
cell: &'a Cell<*const ConfigClient>,
|
||||
val: *const ConfigClient,
|
||||
}
|
||||
impl Drop for Reset<'_> {
|
||||
fn drop(&mut self) {
|
||||
|
|
@ -168,7 +184,7 @@ unsafe fn with_client<T, F: FnOnce(&Client) -> T>(data: *const u8, f: F) -> T {
|
|||
}
|
||||
}
|
||||
CLIENT.with(|cell| unsafe {
|
||||
let client = data as *const Client;
|
||||
let client = data as *const ConfigClient;
|
||||
Rc::increment_strong_count(client);
|
||||
let client = Rc::from_raw(client);
|
||||
let old = cell.replace(client.deref());
|
||||
|
|
@ -214,7 +230,7 @@ pub unsafe extern "C" fn init(
|
|||
size: usize,
|
||||
f: extern "C" fn(),
|
||||
) -> *const u8 {
|
||||
let client = Rc::new(Client {
|
||||
let client = Rc::new(ConfigClient {
|
||||
configure: f,
|
||||
srv_data,
|
||||
srv_unref,
|
||||
|
|
@ -243,6 +259,8 @@ pub unsafe extern "C" fn init(
|
|||
status_task: Default::default(),
|
||||
i3bar_separator: Default::default(),
|
||||
pressed_keysym: Cell::new(None),
|
||||
client_match_handlers: Default::default(),
|
||||
window_match_handlers: Default::default(),
|
||||
feat_mod_mask: Cell::new(false),
|
||||
});
|
||||
let init = unsafe { slice::from_raw_parts(init, size) };
|
||||
|
|
@ -251,7 +269,7 @@ pub unsafe extern "C" fn init(
|
|||
}
|
||||
|
||||
pub unsafe extern "C" fn unref(data: *const u8) {
|
||||
let client = data as *const Client;
|
||||
let client = data as *const ConfigClient;
|
||||
unsafe {
|
||||
drop(Rc::from_raw(client));
|
||||
}
|
||||
|
|
@ -278,7 +296,17 @@ macro_rules! get_response {
|
|||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
enum GenericCriterion<'a, Crit, Matcher> {
|
||||
Matcher(Matcher),
|
||||
Not(&'a Crit),
|
||||
All(&'a [Crit]),
|
||||
Any(&'a [Crit]),
|
||||
Exactly(usize, &'a [Crit]),
|
||||
}
|
||||
|
||||
impl ConfigClient {
|
||||
fn send(&self, msg: &ClientMessage) {
|
||||
let mut buf = self.bufs.borrow_mut().pop().unwrap_or_default();
|
||||
buf.clear();
|
||||
|
|
@ -333,12 +361,80 @@ impl Client {
|
|||
self.send(&ClientMessage::GrabKb { kb, grab });
|
||||
}
|
||||
|
||||
pub fn focus(&self, seat: Seat, direction: Direction) {
|
||||
self.send(&ClientMessage::Focus { seat, direction });
|
||||
pub fn seat_focus(&self, seat: Seat, direction: Direction) {
|
||||
self.send(&ClientMessage::SeatFocus { seat, direction });
|
||||
}
|
||||
|
||||
pub fn move_(&self, seat: Seat, direction: Direction) {
|
||||
self.send(&ClientMessage::Move { seat, direction });
|
||||
pub fn seat_move(&self, seat: Seat, direction: Direction) {
|
||||
self.send(&ClientMessage::SeatMove { seat, direction });
|
||||
}
|
||||
|
||||
pub fn window_move(&self, window: Window, direction: Direction) {
|
||||
self.send(&ClientMessage::WindowMove { window, direction });
|
||||
}
|
||||
|
||||
pub fn window_exists(&self, window: Window) -> bool {
|
||||
let res = self.send_with_response(&ClientMessage::WindowExists { window });
|
||||
get_response!(res, false, WindowExists { exists });
|
||||
exists
|
||||
}
|
||||
|
||||
pub fn window_client(&self, window: Window) -> Client {
|
||||
let res = self.send_with_response(&ClientMessage::GetWindowClient { window });
|
||||
get_response!(res, Client(0), GetWindowClient { client });
|
||||
client
|
||||
}
|
||||
|
||||
pub fn get_workspace_window(&self, workspace: Workspace) -> Window {
|
||||
let res = self.send_with_response(&ClientMessage::GetWorkspaceWindow { workspace });
|
||||
get_response!(res, Window(0), GetWorkspaceWindow { window });
|
||||
window
|
||||
}
|
||||
|
||||
pub fn get_seat_keyboard_window(&self, seat: Seat) -> Window {
|
||||
let res = self.send_with_response(&ClientMessage::GetSeatKeyboardWindow { seat });
|
||||
get_response!(res, Window(0), GetSeatKeyboardWindow { window });
|
||||
window
|
||||
}
|
||||
|
||||
pub fn focus_window(&self, seat: Seat, window: Window) {
|
||||
self.send(&ClientMessage::SeatFocusWindow { seat, window });
|
||||
}
|
||||
|
||||
pub fn window_title(&self, window: Window) -> String {
|
||||
let res = self.send_with_response(&ClientMessage::GetWindowTitle { window });
|
||||
get_response!(res, String::new(), GetWindowTitle { title });
|
||||
title
|
||||
}
|
||||
|
||||
pub fn window_type(&self, window: Window) -> WindowType {
|
||||
let res = self.send_with_response(&ClientMessage::GetWindowType { window });
|
||||
get_response!(res, WindowType(0), GetWindowType { kind });
|
||||
kind
|
||||
}
|
||||
|
||||
pub fn window_id(&self, window: Window) -> String {
|
||||
let res = self.send_with_response(&ClientMessage::GetWindowId { window });
|
||||
get_response!(res, String::new(), GetWindowId { id });
|
||||
id
|
||||
}
|
||||
|
||||
pub fn window_parent(&self, window: Window) -> Window {
|
||||
let res = self.send_with_response(&ClientMessage::GetWindowParent { window });
|
||||
get_response!(res, Window(0), GetWindowParent { window });
|
||||
window
|
||||
}
|
||||
|
||||
pub fn window_children(&self, window: Window) -> Vec<Window> {
|
||||
let res = self.send_with_response(&ClientMessage::GetWindowChildren { window });
|
||||
get_response!(res, vec![], GetWindowChildren { windows });
|
||||
windows
|
||||
}
|
||||
|
||||
pub fn window_is_visible(&self, window: Window) -> bool {
|
||||
let res = self.send_with_response(&ClientMessage::GetWindowIsVisible { window });
|
||||
get_response!(res, false, GetWindowIsVisible { visible });
|
||||
visible
|
||||
}
|
||||
|
||||
pub fn unbind<T: Into<ModifiedKeySym>>(&self, seat: Seat, mod_sym: T) {
|
||||
|
|
@ -367,12 +463,18 @@ impl Client {
|
|||
seats
|
||||
}
|
||||
|
||||
pub fn mono(&self, seat: Seat) -> bool {
|
||||
let res = self.send_with_response(&ClientMessage::GetMono { seat });
|
||||
pub fn seat_mono(&self, seat: Seat) -> bool {
|
||||
let res = self.send_with_response(&ClientMessage::GetSeatMono { seat });
|
||||
get_response!(res, false, GetMono { mono });
|
||||
mono
|
||||
}
|
||||
|
||||
pub fn window_mono(&self, window: Window) -> bool {
|
||||
let res = self.send_with_response(&ClientMessage::GetWindowMono { window });
|
||||
get_response!(res, false, GetWindowMono { mono });
|
||||
mono
|
||||
}
|
||||
|
||||
pub fn get_timer(&self, name: &str) -> Timer {
|
||||
let res = self.send_with_response(&ClientMessage::GetTimer { name });
|
||||
get_response!(res, Timer(0), GetTimer { timer });
|
||||
|
|
@ -420,6 +522,12 @@ impl Client {
|
|||
workspace
|
||||
}
|
||||
|
||||
pub fn get_window_workspace(&self, window: Window) -> Workspace {
|
||||
let res = self.send_with_response(&ClientMessage::GetWindowWorkspace { window });
|
||||
get_response!(res, Workspace(0), GetWindowWorkspace { workspace });
|
||||
workspace
|
||||
}
|
||||
|
||||
pub fn get_seat_keyboard_workspace(&self, seat: Seat) -> Workspace {
|
||||
let res = self.send_with_response(&ClientMessage::GetSeatKeyboardWorkspace { seat });
|
||||
get_response!(res, Workspace(0), GetSeatKeyboardWorkspace { workspace });
|
||||
|
|
@ -450,16 +558,26 @@ impl Client {
|
|||
self.send(&ClientMessage::ShowWorkspace { seat, workspace });
|
||||
}
|
||||
|
||||
pub fn set_workspace(&self, seat: Seat, workspace: Workspace) {
|
||||
self.send(&ClientMessage::SetWorkspace { seat, workspace });
|
||||
pub fn set_seat_workspace(&self, seat: Seat, workspace: Workspace) {
|
||||
self.send(&ClientMessage::SetSeatWorkspace { seat, workspace });
|
||||
}
|
||||
|
||||
pub fn split(&self, seat: Seat) -> Axis {
|
||||
let res = self.send_with_response(&ClientMessage::GetSplit { seat });
|
||||
pub fn set_window_workspace(&self, window: Window, workspace: Workspace) {
|
||||
self.send(&ClientMessage::SetWindowWorkspace { window, workspace });
|
||||
}
|
||||
|
||||
pub fn seat_split(&self, seat: Seat) -> Axis {
|
||||
let res = self.send_with_response(&ClientMessage::GetSeatSplit { seat });
|
||||
get_response!(res, Axis::Horizontal, GetSplit { axis });
|
||||
axis
|
||||
}
|
||||
|
||||
pub fn window_split(&self, window: Window) -> Axis {
|
||||
let res = self.send_with_response(&ClientMessage::GetWindowSplit { window });
|
||||
get_response!(res, Axis::Horizontal, GetWindowSplit { axis });
|
||||
axis
|
||||
}
|
||||
|
||||
pub fn disable_pointer_constraint(&self, seat: Seat) {
|
||||
self.send(&ClientMessage::DisablePointerConstraint { seat });
|
||||
}
|
||||
|
|
@ -471,16 +589,26 @@ impl Client {
|
|||
});
|
||||
}
|
||||
|
||||
pub fn set_fullscreen(&self, seat: Seat, fullscreen: bool) {
|
||||
self.send(&ClientMessage::SetFullscreen { seat, fullscreen });
|
||||
pub fn set_seat_fullscreen(&self, seat: Seat, fullscreen: bool) {
|
||||
self.send(&ClientMessage::SetSeatFullscreen { seat, fullscreen });
|
||||
}
|
||||
|
||||
pub fn get_fullscreen(&self, seat: Seat) -> bool {
|
||||
let res = self.send_with_response(&ClientMessage::GetFullscreen { seat });
|
||||
pub fn get_seat_fullscreen(&self, seat: Seat) -> bool {
|
||||
let res = self.send_with_response(&ClientMessage::GetSeatFullscreen { seat });
|
||||
get_response!(res, false, GetFullscreen { fullscreen });
|
||||
fullscreen
|
||||
}
|
||||
|
||||
pub fn set_window_fullscreen(&self, window: Window, fullscreen: bool) {
|
||||
self.send(&ClientMessage::SetWindowFullscreen { window, fullscreen });
|
||||
}
|
||||
|
||||
pub fn get_window_fullscreen(&self, window: Window) -> bool {
|
||||
let res = self.send_with_response(&ClientMessage::GetWindowFullscreen { window });
|
||||
get_response!(res, false, GetWindowFullscreen { fullscreen });
|
||||
fullscreen
|
||||
}
|
||||
|
||||
pub fn reset_font(&self) {
|
||||
self.send(&ClientMessage::ResetFont);
|
||||
}
|
||||
|
|
@ -495,18 +623,28 @@ impl Client {
|
|||
font
|
||||
}
|
||||
|
||||
pub fn get_floating(&self, seat: Seat) -> bool {
|
||||
let res = self.send_with_response(&ClientMessage::GetFloating { seat });
|
||||
pub fn get_seat_floating(&self, seat: Seat) -> bool {
|
||||
let res = self.send_with_response(&ClientMessage::GetSeatFloating { seat });
|
||||
get_response!(res, false, GetFloating { floating });
|
||||
floating
|
||||
}
|
||||
|
||||
pub fn set_floating(&self, seat: Seat, floating: bool) {
|
||||
self.send(&ClientMessage::SetFloating { seat, floating });
|
||||
pub fn set_seat_floating(&self, seat: Seat, floating: bool) {
|
||||
self.send(&ClientMessage::SetSeatFloating { seat, floating });
|
||||
}
|
||||
|
||||
pub fn toggle_floating(&self, seat: Seat) {
|
||||
self.set_floating(seat, !self.get_floating(seat));
|
||||
pub fn toggle_seat_floating(&self, seat: Seat) {
|
||||
self.set_seat_floating(seat, !self.get_seat_floating(seat));
|
||||
}
|
||||
|
||||
pub fn get_window_floating(&self, window: Window) -> bool {
|
||||
let res = self.send_with_response(&ClientMessage::GetWindowFloating { window });
|
||||
get_response!(res, false, GetWindowFloating { floating });
|
||||
floating
|
||||
}
|
||||
|
||||
pub fn set_window_floating(&self, window: Window, floating: bool) {
|
||||
self.send(&ClientMessage::SetWindowFloating { window, floating });
|
||||
}
|
||||
|
||||
pub fn reset_colors(&self) {
|
||||
|
|
@ -548,8 +686,12 @@ impl Client {
|
|||
self.send(&ClientMessage::SetSize { sized, size })
|
||||
}
|
||||
|
||||
pub fn set_mono(&self, seat: Seat, mono: bool) {
|
||||
self.send(&ClientMessage::SetMono { seat, mono });
|
||||
pub fn set_seat_mono(&self, seat: Seat, mono: bool) {
|
||||
self.send(&ClientMessage::SetSeatMono { seat, mono });
|
||||
}
|
||||
|
||||
pub fn set_window_mono(&self, window: Window, mono: bool) {
|
||||
self.send(&ClientMessage::SetWindowMono { window, mono });
|
||||
}
|
||||
|
||||
pub fn set_env(&self, key: &str, val: &str) {
|
||||
|
|
@ -582,20 +724,32 @@ impl Client {
|
|||
self.i3bar_separator.borrow().clone()
|
||||
}
|
||||
|
||||
pub fn set_split(&self, seat: Seat, axis: Axis) {
|
||||
self.send(&ClientMessage::SetSplit { seat, axis });
|
||||
pub fn set_seat_split(&self, seat: Seat, axis: Axis) {
|
||||
self.send(&ClientMessage::SetSeatSplit { seat, axis });
|
||||
}
|
||||
|
||||
pub fn create_split(&self, seat: Seat, axis: Axis) {
|
||||
self.send(&ClientMessage::CreateSplit { seat, axis });
|
||||
pub fn set_window_split(&self, window: Window, axis: Axis) {
|
||||
self.send(&ClientMessage::SetWindowSplit { window, axis });
|
||||
}
|
||||
|
||||
pub fn close(&self, seat: Seat) {
|
||||
self.send(&ClientMessage::Close { seat });
|
||||
pub fn create_seat_split(&self, seat: Seat, axis: Axis) {
|
||||
self.send(&ClientMessage::CreateSeatSplit { seat, axis });
|
||||
}
|
||||
|
||||
pub fn focus_parent(&self, seat: Seat) {
|
||||
self.send(&ClientMessage::FocusParent { seat });
|
||||
pub fn create_window_split(&self, window: Window, axis: Axis) {
|
||||
self.send(&ClientMessage::CreateWindowSplit { window, axis });
|
||||
}
|
||||
|
||||
pub fn seat_close(&self, seat: Seat) {
|
||||
self.send(&ClientMessage::SeatClose { seat });
|
||||
}
|
||||
|
||||
pub fn close_window(&self, window: Window) {
|
||||
self.send(&ClientMessage::WindowClose { window });
|
||||
}
|
||||
|
||||
pub fn focus_seat_parent(&self, seat: Seat) {
|
||||
self.send(&ClientMessage::FocusSeatParent { seat });
|
||||
}
|
||||
|
||||
pub fn get_seat(&self, name: &str) -> Seat {
|
||||
|
|
@ -792,13 +946,23 @@ impl Client {
|
|||
}
|
||||
|
||||
pub fn get_pinned(&self, seat: Seat) -> bool {
|
||||
let res = self.send_with_response(&ClientMessage::GetFloatPinned { seat });
|
||||
let res = self.send_with_response(&ClientMessage::GetSeatFloatPinned { seat });
|
||||
get_response!(res, false, GetFloatPinned { pinned });
|
||||
pinned
|
||||
}
|
||||
|
||||
pub fn set_pinned(&self, seat: Seat, pinned: bool) {
|
||||
self.send(&ClientMessage::SetFloatPinned { seat, pinned });
|
||||
self.send(&ClientMessage::SetSeatFloatPinned { seat, pinned });
|
||||
}
|
||||
|
||||
pub fn get_window_pinned(&self, window: Window) -> bool {
|
||||
let res = self.send_with_response(&ClientMessage::GetWindowFloatPinned { window });
|
||||
get_response!(res, false, GetWindowFloatPinned { pinned });
|
||||
pinned
|
||||
}
|
||||
|
||||
pub fn set_window_pinned(&self, window: Window, pinned: bool) {
|
||||
self.send(&ClientMessage::SetWindowFloatPinned { window, pinned });
|
||||
}
|
||||
|
||||
pub fn connector_connected(&self, connector: Connector) -> bool {
|
||||
|
|
@ -1259,6 +1423,319 @@ impl Client {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn clients(&self) -> Vec<Client> {
|
||||
let res = self.send_with_response(&ClientMessage::GetClients);
|
||||
get_response!(res, vec!(), GetClients { clients });
|
||||
clients
|
||||
}
|
||||
|
||||
pub fn client_exists(&self, client: Client) -> bool {
|
||||
let res = self.send_with_response(&ClientMessage::ClientExists { client });
|
||||
get_response!(res, false, ClientExists { exists });
|
||||
exists
|
||||
}
|
||||
|
||||
pub fn client_is_xwayland(&self, client: Client) -> bool {
|
||||
let res = self.send_with_response(&ClientMessage::ClientIsXwayland { client });
|
||||
get_response!(res, false, ClientIsXwayland { is_xwayland });
|
||||
is_xwayland
|
||||
}
|
||||
|
||||
pub fn client_kill(&self, client: Client) {
|
||||
self.send(&ClientMessage::ClientKill { client });
|
||||
}
|
||||
|
||||
fn create_generic_matcher<Crit, Matcher>(
|
||||
&self,
|
||||
criterion: GenericCriterion<'_, Crit, Matcher>,
|
||||
child: bool,
|
||||
create_child_matcher: impl Fn(Crit) -> (Matcher, bool),
|
||||
create_matcher: impl Fn(GenericCriterionIpc<Matcher>) -> Matcher,
|
||||
destroy_matcher: impl Fn(Matcher),
|
||||
) -> (Matcher, bool)
|
||||
where
|
||||
Crit: Copy,
|
||||
Matcher: Copy,
|
||||
{
|
||||
let mut ad_hoc = vec![];
|
||||
let mut create_child_matcher = |c: Crit| {
|
||||
let (m, original) = create_child_matcher(c);
|
||||
if original {
|
||||
ad_hoc.push(m);
|
||||
}
|
||||
m
|
||||
};
|
||||
let mut create_vec = |l: &[Crit]| {
|
||||
let mut list = Vec::with_capacity(l.len());
|
||||
for c in l {
|
||||
list.push(create_child_matcher(*c));
|
||||
}
|
||||
list
|
||||
};
|
||||
let criterion = match criterion {
|
||||
GenericCriterion::Matcher(m) => {
|
||||
if child {
|
||||
return (m, false);
|
||||
}
|
||||
GenericCriterionIpc::Matcher(m)
|
||||
}
|
||||
GenericCriterion::Not(c) => GenericCriterionIpc::Not(create_child_matcher(*c)),
|
||||
GenericCriterion::All(l) => GenericCriterionIpc::List {
|
||||
list: create_vec(l),
|
||||
all: true,
|
||||
},
|
||||
GenericCriterion::Any(l) => GenericCriterionIpc::List {
|
||||
list: create_vec(l),
|
||||
all: false,
|
||||
},
|
||||
GenericCriterion::Exactly(num, l) => GenericCriterionIpc::Exactly {
|
||||
list: create_vec(l),
|
||||
num,
|
||||
},
|
||||
};
|
||||
let matcher = create_matcher(criterion);
|
||||
for matcher in ad_hoc {
|
||||
destroy_matcher(matcher);
|
||||
}
|
||||
(matcher, true)
|
||||
}
|
||||
|
||||
pub fn create_client_matcher(&self, criterion: ClientCriterion<'_>) -> ClientMatcher {
|
||||
self.create_client_matcher_(criterion, false).0
|
||||
}
|
||||
|
||||
fn create_client_matcher_(
|
||||
&self,
|
||||
criterion: ClientCriterion<'_>,
|
||||
child: bool,
|
||||
) -> (ClientMatcher, bool) {
|
||||
macro_rules! string {
|
||||
($t:expr, $field:ident, $regex:expr) => {
|
||||
ClientCriterionIpc::String {
|
||||
string: $t.to_string(),
|
||||
field: ClientCriterionStringField::$field,
|
||||
regex: $regex,
|
||||
}
|
||||
};
|
||||
}
|
||||
let create_matcher = |criterion| {
|
||||
let res = self.send_with_response(&ClientMessage::CreateClientMatcher {
|
||||
criterion: ClientCriterionIpc::Generic(criterion),
|
||||
});
|
||||
get_response!(res, ClientMatcher(0), CreateClientMatcher { matcher });
|
||||
matcher
|
||||
};
|
||||
let destroy_matcher = |matcher| {
|
||||
self.send(&ClientMessage::DestroyClientMatcher { matcher });
|
||||
};
|
||||
let generic = |crit: GenericCriterion<ClientCriterion, ClientMatcher>| {
|
||||
self.create_generic_matcher::<ClientCriterion, ClientMatcher>(
|
||||
crit,
|
||||
child,
|
||||
|c| self.create_client_matcher_(c, true),
|
||||
create_matcher,
|
||||
destroy_matcher,
|
||||
)
|
||||
};
|
||||
let criterion = match criterion {
|
||||
ClientCriterion::Matcher(m) => return generic(GenericCriterion::Matcher(m)),
|
||||
ClientCriterion::Not(c) => return generic(GenericCriterion::Not(c)),
|
||||
ClientCriterion::All(c) => return generic(GenericCriterion::All(c)),
|
||||
ClientCriterion::Any(c) => return generic(GenericCriterion::Any(c)),
|
||||
ClientCriterion::Exactly(n, c) => return generic(GenericCriterion::Exactly(n, c)),
|
||||
ClientCriterion::SandboxEngine(t) => string!(t, SandboxEngine, false),
|
||||
ClientCriterion::SandboxEngineRegex(t) => string!(t, SandboxEngine, true),
|
||||
ClientCriterion::SandboxAppId(t) => string!(t, SandboxAppId, false),
|
||||
ClientCriterion::SandboxAppIdRegex(t) => string!(t, SandboxAppId, true),
|
||||
ClientCriterion::SandboxInstanceId(t) => string!(t, SandboxInstanceId, false),
|
||||
ClientCriterion::SandboxInstanceIdRegex(t) => string!(t, SandboxInstanceId, true),
|
||||
ClientCriterion::Sandboxed => ClientCriterionIpc::Sandboxed,
|
||||
ClientCriterion::Uid(p) => ClientCriterionIpc::Uid(p),
|
||||
ClientCriterion::Pid(p) => ClientCriterionIpc::Pid(p),
|
||||
ClientCriterion::IsXwayland => ClientCriterionIpc::IsXwayland,
|
||||
ClientCriterion::Comm(t) => string!(t, Comm, false),
|
||||
ClientCriterion::CommRegex(t) => string!(t, Comm, true),
|
||||
ClientCriterion::Exe(t) => string!(t, Exe, false),
|
||||
ClientCriterion::ExeRegex(t) => string!(t, Exe, true),
|
||||
};
|
||||
let res = self.send_with_response(&ClientMessage::CreateClientMatcher { criterion });
|
||||
get_response!(
|
||||
res,
|
||||
(ClientMatcher(0), false),
|
||||
CreateClientMatcher { matcher }
|
||||
);
|
||||
(matcher, true)
|
||||
}
|
||||
|
||||
pub fn set_client_matcher_handler(
|
||||
&self,
|
||||
matcher: ClientMatcher,
|
||||
cb: impl FnMut(MatchedClient) + 'static,
|
||||
) {
|
||||
let cb = Rc::new(RefCell::new(cb));
|
||||
let handlers = &mut *self.client_match_handlers.borrow_mut();
|
||||
let handler = handlers.entry(matcher).or_insert_with(|| {
|
||||
self.send(&ClientMessage::EnableClientMatcherEvents { matcher });
|
||||
ClientMatchHandler {
|
||||
cb: cb.clone(),
|
||||
latched: Default::default(),
|
||||
}
|
||||
});
|
||||
handler.cb = cb.clone();
|
||||
}
|
||||
|
||||
pub fn set_client_matcher_latch_handler(
|
||||
&self,
|
||||
matcher: ClientMatcher,
|
||||
client: Client,
|
||||
cb: impl FnOnce() + 'static,
|
||||
) {
|
||||
let handlers = &mut *self.client_match_handlers.borrow_mut();
|
||||
if let Some(handler) = handlers.get_mut(&matcher) {
|
||||
handler.latched.insert(client, Box::new(cb));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn destroy_client_matcher(&self, matcher: ClientMatcher) {
|
||||
self.send(&ClientMessage::DestroyClientMatcher { matcher });
|
||||
self.client_match_handlers.borrow_mut().remove(&matcher);
|
||||
}
|
||||
|
||||
pub fn create_window_matcher(&self, criterion: WindowCriterion) -> WindowMatcher {
|
||||
self.create_window_matcher_(criterion, false).0
|
||||
}
|
||||
|
||||
fn create_window_matcher_(
|
||||
&self,
|
||||
criterion: WindowCriterion,
|
||||
child: bool,
|
||||
) -> (WindowMatcher, bool) {
|
||||
macro_rules! string {
|
||||
($t:expr, $field:ident, $regex:expr) => {
|
||||
WindowCriterionIpc::String {
|
||||
string: $t.to_string(),
|
||||
field: WindowCriterionStringField::$field,
|
||||
regex: $regex,
|
||||
}
|
||||
};
|
||||
}
|
||||
let create_matcher = |criterion| {
|
||||
let res = self.send_with_response(&ClientMessage::CreateWindowMatcher {
|
||||
criterion: WindowCriterionIpc::Generic(criterion),
|
||||
});
|
||||
get_response!(res, WindowMatcher(0), CreateWindowMatcher { matcher });
|
||||
matcher
|
||||
};
|
||||
let destroy_matcher = |matcher| {
|
||||
self.send(&ClientMessage::DestroyWindowMatcher { matcher });
|
||||
};
|
||||
let generic = |crit: GenericCriterion<WindowCriterion, WindowMatcher>| {
|
||||
self.create_generic_matcher(
|
||||
crit,
|
||||
child,
|
||||
|c| self.create_window_matcher_(c, true),
|
||||
create_matcher,
|
||||
destroy_matcher,
|
||||
)
|
||||
};
|
||||
let _destroy_client_matcher;
|
||||
let criterion = match criterion {
|
||||
WindowCriterion::Matcher(m) => return generic(GenericCriterion::Matcher(m)),
|
||||
WindowCriterion::Not(c) => return generic(GenericCriterion::Not(c)),
|
||||
WindowCriterion::All(c) => return generic(GenericCriterion::All(c)),
|
||||
WindowCriterion::Any(c) => return generic(GenericCriterion::Any(c)),
|
||||
WindowCriterion::Exactly(n, c) => return generic(GenericCriterion::Exactly(n, c)),
|
||||
WindowCriterion::Types(t) => WindowCriterionIpc::Types(t),
|
||||
WindowCriterion::Client(c) => {
|
||||
let (matcher, original) = self.create_client_matcher_(*c, true);
|
||||
if original {
|
||||
_destroy_client_matcher = on_drop(move || matcher.destroy());
|
||||
}
|
||||
WindowCriterionIpc::Client(matcher)
|
||||
}
|
||||
WindowCriterion::Title(t) => string!(t, Title, false),
|
||||
WindowCriterion::TitleRegex(t) => string!(t, Title, true),
|
||||
WindowCriterion::AppId(t) => string!(t, AppId, false),
|
||||
WindowCriterion::AppIdRegex(t) => string!(t, AppId, true),
|
||||
WindowCriterion::Floating => WindowCriterionIpc::Floating,
|
||||
WindowCriterion::Visible => WindowCriterionIpc::Visible,
|
||||
WindowCriterion::Urgent => WindowCriterionIpc::Urgent,
|
||||
WindowCriterion::Focus(seat) => WindowCriterionIpc::SeatFocus(seat),
|
||||
WindowCriterion::Fullscreen => WindowCriterionIpc::Fullscreen,
|
||||
WindowCriterion::JustMapped => WindowCriterionIpc::JustMapped,
|
||||
WindowCriterion::Tag(t) => string!(t, Tag, false),
|
||||
WindowCriterion::TagRegex(t) => string!(t, Tag, true),
|
||||
WindowCriterion::XClass(t) => string!(t, XClass, false),
|
||||
WindowCriterion::XClassRegex(t) => string!(t, XClass, true),
|
||||
WindowCriterion::XInstance(t) => string!(t, XInstance, false),
|
||||
WindowCriterion::XInstanceRegex(t) => string!(t, XInstance, true),
|
||||
WindowCriterion::XRole(t) => string!(t, XRole, false),
|
||||
WindowCriterion::XRoleRegex(t) => string!(t, XRole, true),
|
||||
WindowCriterion::Workspace(t) => WindowCriterionIpc::Workspace(t),
|
||||
WindowCriterion::WorkspaceName(t) => string!(t, Workspace, false),
|
||||
WindowCriterion::WorkspaceNameRegex(t) => string!(t, Workspace, true),
|
||||
};
|
||||
let res = self.send_with_response(&ClientMessage::CreateWindowMatcher { criterion });
|
||||
get_response!(
|
||||
res,
|
||||
(WindowMatcher(0), false),
|
||||
CreateWindowMatcher { matcher }
|
||||
);
|
||||
(matcher, true)
|
||||
}
|
||||
|
||||
pub fn set_window_matcher_handler(
|
||||
&self,
|
||||
matcher: WindowMatcher,
|
||||
cb: impl FnMut(MatchedWindow) + 'static,
|
||||
) {
|
||||
let cb = Rc::new(RefCell::new(cb));
|
||||
let handlers = &mut *self.window_match_handlers.borrow_mut();
|
||||
let handler = handlers.entry(matcher).or_insert_with(|| {
|
||||
self.send(&ClientMessage::EnableWindowMatcherEvents { matcher });
|
||||
WindowMatchHandler {
|
||||
cb: cb.clone(),
|
||||
latched: Default::default(),
|
||||
}
|
||||
});
|
||||
handler.cb = cb.clone();
|
||||
}
|
||||
|
||||
pub fn set_window_matcher_auto_focus(&self, matcher: WindowMatcher, auto_focus: bool) {
|
||||
self.send(&ClientMessage::SetWindowMatcherAutoFocus {
|
||||
matcher,
|
||||
auto_focus,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_window_matcher_initial_tile_state(
|
||||
&self,
|
||||
matcher: WindowMatcher,
|
||||
tile_state: TileState,
|
||||
) {
|
||||
self.send(&ClientMessage::SetWindowMatcherInitialTileState {
|
||||
matcher,
|
||||
tile_state,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_window_matcher_latch_handler(
|
||||
&self,
|
||||
matcher: WindowMatcher,
|
||||
window: Window,
|
||||
cb: impl FnOnce() + 'static,
|
||||
) {
|
||||
let handlers = &mut *self.window_match_handlers.borrow_mut();
|
||||
if let Some(handler) = handlers.get_mut(&matcher) {
|
||||
handler.latched.insert(window, Box::new(cb));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn destroy_window_matcher(&self, matcher: WindowMatcher) {
|
||||
self.send(&ClientMessage::DestroyWindowMatcher { matcher });
|
||||
self.window_match_handlers.borrow_mut().remove(&matcher);
|
||||
}
|
||||
|
||||
fn handle_msg(&self, msg: &[u8]) {
|
||||
self.handle_msg2(msg);
|
||||
self.dispatch_futures();
|
||||
|
|
@ -1521,6 +1998,54 @@ impl Client {
|
|||
run_cb("switch event", &cb, event);
|
||||
}
|
||||
}
|
||||
ServerMessage::ClientMatcherMatched { matcher, client } => {
|
||||
let cb = {
|
||||
let handlers = self.client_match_handlers.borrow();
|
||||
let Some(handler) = handlers.get(&matcher) else {
|
||||
return;
|
||||
};
|
||||
handler.cb.clone()
|
||||
};
|
||||
let matched = MatchedClient { matcher, client };
|
||||
cb.borrow_mut()(matched);
|
||||
}
|
||||
ServerMessage::ClientMatcherUnmatched { matcher, client } => {
|
||||
let cb = {
|
||||
let mut handlers = self.client_match_handlers.borrow_mut();
|
||||
let Some(handler) = handlers.get_mut(&matcher) else {
|
||||
return;
|
||||
};
|
||||
let Some(cb) = handler.latched.remove(&client) else {
|
||||
return;
|
||||
};
|
||||
cb
|
||||
};
|
||||
cb();
|
||||
}
|
||||
ServerMessage::WindowMatcherMatched { matcher, window } => {
|
||||
let cb = {
|
||||
let handlers = self.window_match_handlers.borrow();
|
||||
let Some(handler) = handlers.get(&matcher) else {
|
||||
return;
|
||||
};
|
||||
handler.cb.clone()
|
||||
};
|
||||
let matched = MatchedWindow { matcher, window };
|
||||
cb.borrow_mut()(matched);
|
||||
}
|
||||
ServerMessage::WindowMatcherUnmatched { matcher, window } => {
|
||||
let cb = {
|
||||
let mut handlers = self.window_match_handlers.borrow_mut();
|
||||
let Some(handler) = handlers.get_mut(&matcher) else {
|
||||
return;
|
||||
};
|
||||
let Some(cb) = handler.latched.remove(&window) else {
|
||||
return;
|
||||
};
|
||||
cb
|
||||
};
|
||||
cb();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
use {
|
||||
crate::{
|
||||
_private::{PollableId, WireMode},
|
||||
_private::{ClientCriterionIpc, PollableId, WindowCriterionIpc, WireMode},
|
||||
Axis, Direction, PciId, Workspace,
|
||||
client::{Client, ClientMatcher},
|
||||
input::{
|
||||
FocusFollowsMouseMode, InputDevice, Seat, SwitchEvent, acceleration::AccelProfile,
|
||||
capability::Capability,
|
||||
|
|
@ -14,6 +15,7 @@ use {
|
|||
ColorSpace, Connector, DrmDevice, Format, GfxApi, TearingMode, TransferFunction,
|
||||
Transform, VrrMode, connector_type::ConnectorType,
|
||||
},
|
||||
window::{TileState, Window, WindowMatcher, WindowType},
|
||||
xwayland::XScalingMode,
|
||||
},
|
||||
serde::{Deserialize, Serialize},
|
||||
|
|
@ -92,6 +94,22 @@ pub enum ServerMessage {
|
|||
input_device: InputDevice,
|
||||
event: SwitchEvent,
|
||||
},
|
||||
ClientMatcherMatched {
|
||||
matcher: ClientMatcher,
|
||||
client: Client,
|
||||
},
|
||||
ClientMatcherUnmatched {
|
||||
matcher: ClientMatcher,
|
||||
client: Client,
|
||||
},
|
||||
WindowMatcherMatched {
|
||||
matcher: WindowMatcher,
|
||||
window: Window,
|
||||
},
|
||||
WindowMatcherUnmatched {
|
||||
matcher: WindowMatcher,
|
||||
window: Window,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
@ -129,20 +147,20 @@ pub enum ClientMessage<'a> {
|
|||
rate: i32,
|
||||
delay: i32,
|
||||
},
|
||||
GetSplit {
|
||||
GetSeatSplit {
|
||||
seat: Seat,
|
||||
},
|
||||
SetStatus {
|
||||
status: &'a str,
|
||||
},
|
||||
SetSplit {
|
||||
SetSeatSplit {
|
||||
seat: Seat,
|
||||
axis: Axis,
|
||||
},
|
||||
GetMono {
|
||||
GetSeatMono {
|
||||
seat: Seat,
|
||||
},
|
||||
SetMono {
|
||||
SetSeatMono {
|
||||
seat: Seat,
|
||||
mono: bool,
|
||||
},
|
||||
|
|
@ -168,11 +186,11 @@ pub enum ClientMessage<'a> {
|
|||
args: Vec<String>,
|
||||
env: Vec<(String, String)>,
|
||||
},
|
||||
Focus {
|
||||
SeatFocus {
|
||||
seat: Seat,
|
||||
direction: Direction,
|
||||
},
|
||||
Move {
|
||||
SeatMove {
|
||||
seat: Seat,
|
||||
direction: Direction,
|
||||
},
|
||||
|
|
@ -196,20 +214,20 @@ pub enum ClientMessage<'a> {
|
|||
colorable: Colorable,
|
||||
color: Color,
|
||||
},
|
||||
CreateSplit {
|
||||
CreateSeatSplit {
|
||||
seat: Seat,
|
||||
axis: Axis,
|
||||
},
|
||||
Close {
|
||||
SeatClose {
|
||||
seat: Seat,
|
||||
},
|
||||
FocusParent {
|
||||
FocusSeatParent {
|
||||
seat: Seat,
|
||||
},
|
||||
GetFloating {
|
||||
GetSeatFloating {
|
||||
seat: Seat,
|
||||
},
|
||||
SetFloating {
|
||||
SetSeatFloating {
|
||||
seat: Seat,
|
||||
floating: bool,
|
||||
},
|
||||
|
|
@ -261,7 +279,7 @@ pub enum ClientMessage<'a> {
|
|||
seat: Seat,
|
||||
workspace: Workspace,
|
||||
},
|
||||
SetWorkspace {
|
||||
SetSeatWorkspace {
|
||||
seat: Seat,
|
||||
workspace: Workspace,
|
||||
},
|
||||
|
|
@ -280,11 +298,11 @@ pub enum ClientMessage<'a> {
|
|||
key: &'a str,
|
||||
val: &'a str,
|
||||
},
|
||||
SetFullscreen {
|
||||
SetSeatFullscreen {
|
||||
seat: Seat,
|
||||
fullscreen: bool,
|
||||
},
|
||||
GetFullscreen {
|
||||
GetSeatFullscreen {
|
||||
seat: Seat,
|
||||
},
|
||||
GetDeviceConnectors {
|
||||
|
|
@ -546,10 +564,10 @@ pub enum ClientMessage<'a> {
|
|||
above: bool,
|
||||
},
|
||||
GetFloatAboveFullscreen,
|
||||
GetFloatPinned {
|
||||
GetSeatFloatPinned {
|
||||
seat: Seat,
|
||||
},
|
||||
SetFloatPinned {
|
||||
SetSeatFloatPinned {
|
||||
seat: Seat,
|
||||
pinned: bool,
|
||||
},
|
||||
|
|
@ -565,6 +583,129 @@ pub enum ClientMessage<'a> {
|
|||
GetConnectorWorkspaces {
|
||||
connector: Connector,
|
||||
},
|
||||
GetClients,
|
||||
ClientExists {
|
||||
client: Client,
|
||||
},
|
||||
ClientKill {
|
||||
client: Client,
|
||||
},
|
||||
ClientIsXwayland {
|
||||
client: Client,
|
||||
},
|
||||
WindowExists {
|
||||
window: Window,
|
||||
},
|
||||
GetWindowClient {
|
||||
window: Window,
|
||||
},
|
||||
GetWorkspaceWindow {
|
||||
workspace: Workspace,
|
||||
},
|
||||
GetSeatKeyboardWindow {
|
||||
seat: Seat,
|
||||
},
|
||||
SeatFocusWindow {
|
||||
seat: Seat,
|
||||
window: Window,
|
||||
},
|
||||
GetWindowTitle {
|
||||
window: Window,
|
||||
},
|
||||
GetWindowType {
|
||||
window: Window,
|
||||
},
|
||||
GetWindowId {
|
||||
window: Window,
|
||||
},
|
||||
GetWindowIsVisible {
|
||||
window: Window,
|
||||
},
|
||||
GetWindowParent {
|
||||
window: Window,
|
||||
},
|
||||
GetWindowWorkspace {
|
||||
window: Window,
|
||||
},
|
||||
GetWindowChildren {
|
||||
window: Window,
|
||||
},
|
||||
GetWindowSplit {
|
||||
window: Window,
|
||||
},
|
||||
SetWindowSplit {
|
||||
window: Window,
|
||||
axis: Axis,
|
||||
},
|
||||
GetWindowMono {
|
||||
window: Window,
|
||||
},
|
||||
SetWindowMono {
|
||||
window: Window,
|
||||
mono: bool,
|
||||
},
|
||||
WindowMove {
|
||||
window: Window,
|
||||
direction: Direction,
|
||||
},
|
||||
CreateWindowSplit {
|
||||
window: Window,
|
||||
axis: Axis,
|
||||
},
|
||||
WindowClose {
|
||||
window: Window,
|
||||
},
|
||||
GetWindowFloating {
|
||||
window: Window,
|
||||
},
|
||||
SetWindowFloating {
|
||||
window: Window,
|
||||
floating: bool,
|
||||
},
|
||||
SetWindowWorkspace {
|
||||
window: Window,
|
||||
workspace: Workspace,
|
||||
},
|
||||
SetWindowFullscreen {
|
||||
window: Window,
|
||||
fullscreen: bool,
|
||||
},
|
||||
GetWindowFullscreen {
|
||||
window: Window,
|
||||
},
|
||||
GetWindowFloatPinned {
|
||||
window: Window,
|
||||
},
|
||||
SetWindowFloatPinned {
|
||||
window: Window,
|
||||
pinned: bool,
|
||||
},
|
||||
CreateClientMatcher {
|
||||
criterion: ClientCriterionIpc,
|
||||
},
|
||||
DestroyClientMatcher {
|
||||
matcher: ClientMatcher,
|
||||
},
|
||||
EnableClientMatcherEvents {
|
||||
matcher: ClientMatcher,
|
||||
},
|
||||
CreateWindowMatcher {
|
||||
criterion: WindowCriterionIpc,
|
||||
},
|
||||
DestroyWindowMatcher {
|
||||
matcher: WindowMatcher,
|
||||
},
|
||||
EnableWindowMatcherEvents {
|
||||
matcher: WindowMatcher,
|
||||
},
|
||||
SetWindowMatcherAutoFocus {
|
||||
matcher: WindowMatcher,
|
||||
auto_focus: bool,
|
||||
},
|
||||
SetWindowMatcherInitialTileState {
|
||||
matcher: WindowMatcher,
|
||||
tile_state: TileState,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
@ -728,6 +869,69 @@ pub enum Response {
|
|||
GetConnectorWorkspaces {
|
||||
workspaces: Vec<Workspace>,
|
||||
},
|
||||
GetClients {
|
||||
clients: Vec<Client>,
|
||||
},
|
||||
ClientExists {
|
||||
exists: bool,
|
||||
},
|
||||
ClientIsXwayland {
|
||||
is_xwayland: bool,
|
||||
},
|
||||
WindowExists {
|
||||
exists: bool,
|
||||
},
|
||||
GetWindowClient {
|
||||
client: Client,
|
||||
},
|
||||
GetSeatKeyboardWindow {
|
||||
window: Window,
|
||||
},
|
||||
GetWorkspaceWindow {
|
||||
window: Window,
|
||||
},
|
||||
GetWindowParent {
|
||||
window: Window,
|
||||
},
|
||||
GetWindowChildren {
|
||||
windows: Vec<Window>,
|
||||
},
|
||||
GetWindowTitle {
|
||||
title: String,
|
||||
},
|
||||
GetWindowType {
|
||||
kind: WindowType,
|
||||
},
|
||||
GetWindowId {
|
||||
id: String,
|
||||
},
|
||||
GetWindowWorkspace {
|
||||
workspace: Workspace,
|
||||
},
|
||||
GetWindowFloating {
|
||||
floating: bool,
|
||||
},
|
||||
GetWindowSplit {
|
||||
axis: Axis,
|
||||
},
|
||||
GetWindowMono {
|
||||
mono: bool,
|
||||
},
|
||||
GetWindowFullscreen {
|
||||
fullscreen: bool,
|
||||
},
|
||||
GetWindowFloatPinned {
|
||||
pinned: bool,
|
||||
},
|
||||
GetWindowIsVisible {
|
||||
visible: bool,
|
||||
},
|
||||
CreateClientMatcher {
|
||||
matcher: ClientMatcher,
|
||||
},
|
||||
CreateWindowMatcher {
|
||||
matcher: WindowMatcher,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
|
|||
149
jay-config/src/client.rs
Normal file
149
jay-config/src/client.rs
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
//! Tools for inspecting and manipulating clients.
|
||||
|
||||
use {
|
||||
serde::{Deserialize, Serialize},
|
||||
std::ops::Deref,
|
||||
};
|
||||
|
||||
/// A client connected to the compositor.
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct Client(pub u64);
|
||||
|
||||
impl Client {
|
||||
/// Returns whether the client exists.
|
||||
pub fn exists(self) -> bool {
|
||||
self.0 != 0 && get!(false).client_exists(self)
|
||||
}
|
||||
|
||||
/// Returns whether the client does not exist.
|
||||
///
|
||||
/// This is a shorthand for `!self.exists()`.
|
||||
pub fn does_not_exist(self) -> bool {
|
||||
!self.exists()
|
||||
}
|
||||
|
||||
/// Returns whether this client is XWayland.
|
||||
pub fn is_xwayland(self) -> bool {
|
||||
get!(false).client_is_xwayland(self)
|
||||
}
|
||||
|
||||
/// Disconnects the client.
|
||||
pub fn kill(self) {
|
||||
get!().client_kill(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns all current clients.
|
||||
pub fn clients() -> Vec<Client> {
|
||||
get!().clients()
|
||||
}
|
||||
|
||||
/// A client matcher.
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct ClientMatcher(pub u64);
|
||||
|
||||
/// A matched client.
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct MatchedClient {
|
||||
pub(crate) matcher: ClientMatcher,
|
||||
pub(crate) client: Client,
|
||||
}
|
||||
|
||||
/// A criterion for matching a client.
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum ClientCriterion<'a> {
|
||||
/// Matches if the contained matcher matches.
|
||||
Matcher(ClientMatcher),
|
||||
/// Matches if the contained criterion does not match.
|
||||
Not(&'a ClientCriterion<'a>),
|
||||
/// Matches if all of the contained criteria match.
|
||||
All(&'a [ClientCriterion<'a>]),
|
||||
/// Matches if any of the contained criteria match.
|
||||
Any(&'a [ClientCriterion<'a>]),
|
||||
/// Matches if an exact number of the contained criteria match.
|
||||
Exactly(usize, &'a [ClientCriterion<'a>]),
|
||||
/// Matches the engine name of the client's sandbox verbatim.
|
||||
SandboxEngine(&'a str),
|
||||
/// Matches the engine name of the client's sandbox with a regular expression.
|
||||
SandboxEngineRegex(&'a str),
|
||||
/// Matches the app id of the client's sandbox verbatim.
|
||||
SandboxAppId(&'a str),
|
||||
/// Matches the app id of the client's sandbox with a regular expression.
|
||||
SandboxAppIdRegex(&'a str),
|
||||
/// Matches the instance id of the client's sandbox verbatim.
|
||||
SandboxInstanceId(&'a str),
|
||||
/// Matches the instance id of the client's sandbox with a regular expression.
|
||||
SandboxInstanceIdRegex(&'a str),
|
||||
/// Matches if the client is sandboxed.
|
||||
Sandboxed,
|
||||
/// Matches the user ID of the client.
|
||||
Uid(i32),
|
||||
/// Matches the process ID of the client.
|
||||
Pid(i32),
|
||||
/// Matches if the client is Xwayland.
|
||||
IsXwayland,
|
||||
/// Matches the `/proc/pid/comm` of the client verbatim.
|
||||
Comm(&'a str),
|
||||
/// Matches the `/proc/pid/comm` of the client with a regular expression.
|
||||
CommRegex(&'a str),
|
||||
/// Matches the `/proc/pid/exe` of the client verbatim.
|
||||
Exe(&'a str),
|
||||
/// Matches the `/proc/pid/exe` of the client with a regular expression.
|
||||
ExeRegex(&'a str),
|
||||
}
|
||||
|
||||
impl ClientCriterion<'_> {
|
||||
/// Converts the criterion to a matcher.
|
||||
pub fn to_matcher(self) -> ClientMatcher {
|
||||
get!(ClientMatcher(0)).create_client_matcher(self)
|
||||
}
|
||||
|
||||
/// Binds a function to execute when the criterion matches a client.
|
||||
///
|
||||
/// This leaks the matcher.
|
||||
pub fn bind<F: FnMut(MatchedClient) + 'static>(self, cb: F) {
|
||||
self.to_matcher().bind(cb);
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientMatcher {
|
||||
/// Destroys the matcher.
|
||||
///
|
||||
/// Any bound callback will no longer be executed.
|
||||
pub fn destroy(self) {
|
||||
get!().destroy_client_matcher(self);
|
||||
}
|
||||
|
||||
/// Sets a function to execute when the criterion matches a client.
|
||||
///
|
||||
/// Replaces any already bound callback.
|
||||
pub fn bind<F: FnMut(MatchedClient) + 'static>(self, cb: F) {
|
||||
get!().set_client_matcher_handler(self, cb);
|
||||
}
|
||||
}
|
||||
|
||||
impl MatchedClient {
|
||||
/// Returns the client that matched.
|
||||
pub fn client(self) -> Client {
|
||||
self.client
|
||||
}
|
||||
|
||||
/// Returns the matcher.
|
||||
pub fn matcher(self) -> ClientMatcher {
|
||||
self.matcher
|
||||
}
|
||||
|
||||
/// Latches a function to be executed when the client no longer matches the criteria.
|
||||
pub fn latch<F: FnOnce() + 'static>(self, cb: F) {
|
||||
get!().set_client_matcher_latch_handler(self.matcher, self.client, cb);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for MatchedClient {
|
||||
type Target = Client;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.client
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ use {
|
|||
input::{acceleration::AccelProfile, capability::Capability},
|
||||
keyboard::{Keymap, mods::Modifiers},
|
||||
video::Connector,
|
||||
window::Window,
|
||||
},
|
||||
serde::{Deserialize, Serialize},
|
||||
std::time::Duration,
|
||||
|
|
@ -259,12 +260,12 @@ impl Seat {
|
|||
|
||||
/// Moves the keyboard focus of the seat in the specified direction.
|
||||
pub fn focus(self, direction: Direction) {
|
||||
get!().focus(self, direction)
|
||||
get!().seat_focus(self, direction)
|
||||
}
|
||||
|
||||
/// Moves the focused window in the specified direction.
|
||||
pub fn move_(self, direction: Direction) {
|
||||
get!().move_(self, direction)
|
||||
get!().seat_move(self, direction)
|
||||
}
|
||||
|
||||
/// Sets the keymap of the seat.
|
||||
|
|
@ -287,12 +288,12 @@ impl Seat {
|
|||
|
||||
/// Returns whether the parent-container of the currently focused window is in mono-mode.
|
||||
pub fn mono(self) -> bool {
|
||||
get!(false).mono(self)
|
||||
get!(false).seat_mono(self)
|
||||
}
|
||||
|
||||
/// Sets whether the parent-container of the currently focused window is in mono-mode.
|
||||
pub fn set_mono(self, mono: bool) {
|
||||
get!().set_mono(self, mono)
|
||||
get!().set_seat_mono(self, mono)
|
||||
}
|
||||
|
||||
/// Toggles whether the parent-container of the currently focused window is in mono-mode.
|
||||
|
|
@ -302,12 +303,12 @@ impl Seat {
|
|||
|
||||
/// Returns the split axis of the parent-container of the currently focused window.
|
||||
pub fn split(self) -> Axis {
|
||||
get!(Axis::Horizontal).split(self)
|
||||
get!(Axis::Horizontal).seat_split(self)
|
||||
}
|
||||
|
||||
/// Sets the split axis of the parent-container of the currently focused window.
|
||||
pub fn set_split(self, axis: Axis) {
|
||||
get!().set_split(self, axis)
|
||||
get!().set_seat_split(self, axis)
|
||||
}
|
||||
|
||||
/// Toggles the split axis of the parent-container of the currently focused window.
|
||||
|
|
@ -322,33 +323,33 @@ impl Seat {
|
|||
|
||||
/// Creates a new container with the specified split in place of the currently focused window.
|
||||
pub fn create_split(self, axis: Axis) {
|
||||
get!().create_split(self, axis);
|
||||
get!().create_seat_split(self, axis);
|
||||
}
|
||||
|
||||
/// Focuses the parent node of the currently focused window.
|
||||
pub fn focus_parent(self) {
|
||||
get!().focus_parent(self);
|
||||
get!().focus_seat_parent(self);
|
||||
}
|
||||
|
||||
/// Requests the currently focused window to be closed.
|
||||
pub fn close(self) {
|
||||
get!().close(self);
|
||||
get!().seat_close(self);
|
||||
}
|
||||
|
||||
/// Returns whether the currently focused window is floating.
|
||||
pub fn get_floating(self) -> bool {
|
||||
get!().get_floating(self)
|
||||
get!().get_seat_floating(self)
|
||||
}
|
||||
/// Sets whether the currently focused window is floating.
|
||||
pub fn set_floating(self, floating: bool) {
|
||||
get!().set_floating(self, floating);
|
||||
get!().set_seat_floating(self, floating);
|
||||
}
|
||||
|
||||
/// Toggles whether the currently focused window is floating.
|
||||
///
|
||||
/// You can do the same by double-clicking on the header.
|
||||
pub fn toggle_floating(self) {
|
||||
get!().toggle_floating(self);
|
||||
get!().toggle_seat_floating(self);
|
||||
}
|
||||
|
||||
/// Returns the workspace that is currently active on the output that contains the seat's
|
||||
|
|
@ -377,22 +378,22 @@ impl Seat {
|
|||
|
||||
/// Moves the currently focused window to the workspace.
|
||||
pub fn set_workspace(self, workspace: Workspace) {
|
||||
get!().set_workspace(self, workspace)
|
||||
get!().set_seat_workspace(self, workspace)
|
||||
}
|
||||
|
||||
/// Toggles whether the currently focused window is fullscreen.
|
||||
pub fn toggle_fullscreen(self) {
|
||||
let c = get!();
|
||||
c.set_fullscreen(self, !c.get_fullscreen(self));
|
||||
c.set_seat_fullscreen(self, !c.get_seat_fullscreen(self));
|
||||
}
|
||||
/// Returns whether the currently focused window is fullscreen.
|
||||
pub fn fullscreen(self) -> bool {
|
||||
get!(false).get_fullscreen(self)
|
||||
get!(false).get_seat_fullscreen(self)
|
||||
}
|
||||
|
||||
/// Sets whether the currently focused window is fullscreen.
|
||||
pub fn set_fullscreen(self, fullscreen: bool) {
|
||||
get!().set_fullscreen(self, fullscreen)
|
||||
get!().set_seat_fullscreen(self, fullscreen)
|
||||
}
|
||||
|
||||
/// Disables the currently active pointer constraint on this seat.
|
||||
|
|
@ -478,6 +479,20 @@ impl Seat {
|
|||
pub fn toggle_float_pinned(self) {
|
||||
self.set_float_pinned(!self.float_pinned());
|
||||
}
|
||||
|
||||
/// Returns the focused window.
|
||||
///
|
||||
/// If no window is focused, [`Window::exists`] returns false.
|
||||
pub fn window(self) -> Window {
|
||||
get!(Window(0)).get_seat_keyboard_window(self)
|
||||
}
|
||||
|
||||
/// Puts the keyboard focus on the window.
|
||||
///
|
||||
/// This has no effect if the window is not visible.
|
||||
pub fn focus_window(self, window: Window) {
|
||||
get!().focus_window(self, window)
|
||||
}
|
||||
}
|
||||
|
||||
/// A focus-follows-mouse mode.
|
||||
|
|
|
|||
|
|
@ -3,35 +3,42 @@
|
|||
use {
|
||||
crate::{ModifiedKeySym, keyboard::syms::KeySym},
|
||||
serde::{Deserialize, Serialize},
|
||||
std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign},
|
||||
std::ops::BitOr,
|
||||
};
|
||||
|
||||
/// Zero or more keyboard modifiers
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Default, Hash, Debug)]
|
||||
pub struct Modifiers(pub u32);
|
||||
bitflags! {
|
||||
/// Zero or more keyboard modifiers
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Default, Hash)]
|
||||
pub struct Modifiers(pub u32) {
|
||||
/// The Shift modifier
|
||||
pub const SHIFT = 1 << 0,
|
||||
/// The CapsLock modifier.
|
||||
pub const LOCK = 1 << 1,
|
||||
/// The Ctrl modifier.
|
||||
pub const CTRL = 1 << 2,
|
||||
/// The Mod1 modifier, i.e., Alt.
|
||||
pub const MOD1 = 1 << 3,
|
||||
/// The Mod2 modifier, i.e., NumLock.
|
||||
pub const MOD2 = 1 << 4,
|
||||
/// The Mod3 modifier.
|
||||
pub const MOD3 = 1 << 5,
|
||||
/// The Mod4 modifier, i.e., Logo.
|
||||
pub const MOD4 = 1 << 6,
|
||||
/// The Mod5 modifier.
|
||||
pub const MOD5 = 1 << 7,
|
||||
|
||||
/// Synthetic modifier matching key release events.
|
||||
///
|
||||
/// This can be used to execute a callback on key release.
|
||||
pub const RELEASE = 1 << 31,
|
||||
}
|
||||
}
|
||||
|
||||
impl Modifiers {
|
||||
/// No modifiers.
|
||||
pub const NONE: Self = Modifiers(0);
|
||||
}
|
||||
|
||||
/// The Shift modifier
|
||||
pub const SHIFT: Modifiers = Modifiers(1 << 0);
|
||||
/// The CapsLock modifier.
|
||||
pub const LOCK: Modifiers = Modifiers(1 << 1);
|
||||
/// The Ctrl modifier.
|
||||
pub const CTRL: Modifiers = Modifiers(1 << 2);
|
||||
/// The Mod1 modifier, i.e., Alt.
|
||||
pub const MOD1: Modifiers = Modifiers(1 << 3);
|
||||
/// The Mod2 modifier, i.e., NumLock.
|
||||
pub const MOD2: Modifiers = Modifiers(1 << 4);
|
||||
/// The Mod3 modifier.
|
||||
pub const MOD3: Modifiers = Modifiers(1 << 5);
|
||||
/// The Mod4 modifier, i.e., Logo.
|
||||
pub const MOD4: Modifiers = Modifiers(1 << 6);
|
||||
/// The Mod5 modifier.
|
||||
pub const MOD5: Modifiers = Modifiers(1 << 7);
|
||||
|
||||
/// Alias for `LOCK`.
|
||||
pub const CAPS: Modifiers = LOCK;
|
||||
/// Alias for `MOD1`.
|
||||
|
|
@ -41,19 +48,6 @@ pub const NUM: Modifiers = MOD2;
|
|||
/// Alias for `MOD4`.
|
||||
pub const LOGO: Modifiers = MOD4;
|
||||
|
||||
/// Synthetic modifier matching key release events.
|
||||
///
|
||||
/// This can be used to execute a callback on key release.
|
||||
pub const RELEASE: Modifiers = Modifiers(1 << 31);
|
||||
|
||||
impl BitOr for Modifiers {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 | rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr<KeySym> for Modifiers {
|
||||
type Output = ModifiedKeySym;
|
||||
|
||||
|
|
@ -64,23 +58,3 @@ impl BitOr<KeySym> for Modifiers {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAnd for Modifiers {
|
||||
type Output = Self;
|
||||
|
||||
fn bitand(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 & rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOrAssign for Modifiers {
|
||||
fn bitor_assign(&mut self, rhs: Self) {
|
||||
self.0 |= rhs.0
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAndAssign for Modifiers {
|
||||
fn bitand_assign(&mut self, rhs: Self) {
|
||||
self.0 &= rhs.0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,9 @@
|
|||
#[expect(unused_imports)]
|
||||
use crate::input::Seat;
|
||||
use {
|
||||
crate::{_private::ipc::WorkspaceSource, keyboard::ModifiedKeySym, video::Connector},
|
||||
crate::{
|
||||
_private::ipc::WorkspaceSource, keyboard::ModifiedKeySym, video::Connector, window::Window,
|
||||
},
|
||||
serde::{Deserialize, Serialize},
|
||||
std::{
|
||||
fmt::{Debug, Display, Formatter},
|
||||
|
|
@ -58,6 +60,7 @@ use {
|
|||
mod macros;
|
||||
#[doc(hidden)]
|
||||
pub mod _private;
|
||||
pub mod client;
|
||||
pub mod embedded;
|
||||
pub mod exec;
|
||||
pub mod input;
|
||||
|
|
@ -69,6 +72,7 @@ pub mod tasks;
|
|||
pub mod theme;
|
||||
pub mod timer;
|
||||
pub mod video;
|
||||
pub mod window;
|
||||
pub mod xwayland;
|
||||
|
||||
/// A planar direction.
|
||||
|
|
@ -173,6 +177,13 @@ impl Workspace {
|
|||
pub fn move_to_output(self, output: Connector) {
|
||||
get!().move_to_output(WorkspaceSource::Explicit(self), output);
|
||||
}
|
||||
|
||||
/// Returns the root container of this workspace.
|
||||
///
|
||||
/// If no such container exists, [`Window::exists`] returns false.
|
||||
pub fn window(self) -> Window {
|
||||
get!(Window(0)).get_workspace_window(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the workspace with the given name.
|
||||
|
|
|
|||
|
|
@ -43,40 +43,89 @@ macro_rules! get {
|
|||
}};
|
||||
}
|
||||
|
||||
// #[macro_export]
|
||||
// macro_rules! log {
|
||||
// ($lvl:expr, $($arg:tt)+) => ({
|
||||
// $crate::log(
|
||||
// $lvl,
|
||||
// &format!($($args)*),
|
||||
// );
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// #[macro_export]
|
||||
// macro_rules! trace {
|
||||
// ($($arg:tt)+) => {
|
||||
// $crate::log!($crate::LogLevel::Trace, $($arg)+)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #[macro_export]
|
||||
// macro_rules! debug {
|
||||
// ($($arg:tt)+) => {
|
||||
// $crate::log!($crate::LogLevel::Debug, $($arg)+)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #[macro_export]
|
||||
// macro_rules! info {
|
||||
// ($($arg:tt)+) => {
|
||||
// $crate::log!($crate::LogLevel::Info, $($arg)+)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #[macro_export]
|
||||
// macro_rules! info {
|
||||
// ($($arg:tt)+) => {
|
||||
// $crate::log!($crate::LogLevel::Info, $($arg)+)
|
||||
// }
|
||||
// }
|
||||
macro_rules! bitflags {
|
||||
(
|
||||
$(#[$attr1:meta])*
|
||||
$vis1:vis struct $name:ident($vis2:vis $rep:ty) {
|
||||
$(
|
||||
$(#[$attr2:meta])*
|
||||
$vis3:vis const $var:ident = $val:expr,
|
||||
)*
|
||||
}
|
||||
) => {
|
||||
$(#[$attr1])*
|
||||
$vis1 struct $name($vis2 $rep);
|
||||
|
||||
$(
|
||||
$(#[$attr2])*
|
||||
$vis3 const $var: $name = $name($val);
|
||||
)*
|
||||
|
||||
impl std::ops::BitOr for $name {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 | rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::BitAnd for $name {
|
||||
type Output = Self;
|
||||
|
||||
fn bitand(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 & rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::BitOrAssign for $name {
|
||||
fn bitor_assign(&mut self, rhs: Self) {
|
||||
self.0 |= rhs.0;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::BitAndAssign for $name {
|
||||
fn bitand_assign(&mut self, rhs: Self) {
|
||||
self.0 &= rhs.0;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::BitXorAssign for $name {
|
||||
fn bitxor_assign(&mut self, rhs: Self) {
|
||||
self.0 ^= rhs.0;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Not for $name {
|
||||
type Output = Self;
|
||||
|
||||
fn not(self) -> Self::Output {
|
||||
Self(!self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for $name {
|
||||
#[allow(clippy::allow_attributes, clippy::bad_bit_mask, unused_mut)]
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut any = false;
|
||||
let mut v = self.0;
|
||||
$(
|
||||
if $val != 0 && v & $val == $val {
|
||||
if any {
|
||||
write!(f, "|")?;
|
||||
}
|
||||
any = true;
|
||||
write!(f, "{}", stringify!($var))?;
|
||||
v &= !$val;
|
||||
}
|
||||
)*
|
||||
if !any || v != 0 {
|
||||
if any {
|
||||
write!(f, "|")?;
|
||||
}
|
||||
write!(f, "0x{:x}", v)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
388
jay-config/src/window.rs
Normal file
388
jay-config/src/window.rs
Normal file
|
|
@ -0,0 +1,388 @@
|
|||
//! Tools for inspecting and manipulating windows.
|
||||
|
||||
use {
|
||||
crate::{
|
||||
Axis, Direction, Workspace,
|
||||
client::{Client, ClientCriterion},
|
||||
input::Seat,
|
||||
},
|
||||
serde::{Deserialize, Serialize},
|
||||
std::ops::Deref,
|
||||
};
|
||||
|
||||
/// A toplevel window.
|
||||
///
|
||||
/// A toplevel window is anything that can be stored within a container tile or within a
|
||||
/// floating window.
|
||||
///
|
||||
/// There are currently four types of windows:
|
||||
///
|
||||
/// - Containers
|
||||
/// - Placeholders that take the place of a window when it goes fullscreen
|
||||
/// - XDG toplevels
|
||||
/// - X windows
|
||||
///
|
||||
/// You can find out the type of a window by using the [`Window::type_`] function.
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct Window(pub u64);
|
||||
|
||||
bitflags! {
|
||||
/// The type of a window.
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Hash, Eq, PartialEq)]
|
||||
pub struct WindowType(pub u64) {
|
||||
/// A container.
|
||||
pub const CONTAINER = 1 << 0,
|
||||
/// A placeholder.
|
||||
pub const PLACEHOLDER = 1 << 1,
|
||||
/// An XDG toplevel.
|
||||
pub const XDG_TOPLEVEL = 1 << 2,
|
||||
/// An X window.
|
||||
pub const X_WINDOW = 1 << 3,
|
||||
}
|
||||
}
|
||||
|
||||
/// The tile state of a window.
|
||||
#[non_exhaustive]
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub enum TileState {
|
||||
/// The window is tiled.
|
||||
Tiled,
|
||||
/// The window is floating.
|
||||
Floating,
|
||||
}
|
||||
|
||||
/// A window created by a client.
|
||||
///
|
||||
/// This is the same as `XDG_TOPLEVEL | X_WINDOW`.
|
||||
pub const CLIENT_WINDOW: WindowType = WindowType(XDG_TOPLEVEL.0 | X_WINDOW.0);
|
||||
|
||||
impl Window {
|
||||
/// Returns whether the window exists.
|
||||
pub fn exists(self) -> bool {
|
||||
self.0 != 0 && get!(false).window_exists(self)
|
||||
}
|
||||
|
||||
/// Returns whether the window does not exist.
|
||||
///
|
||||
/// This is a shorthand for `!self.exists()`.
|
||||
pub fn does_not_exist(self) -> bool {
|
||||
!self.exists()
|
||||
}
|
||||
|
||||
/// Returns the client of the window.
|
||||
///
|
||||
/// If the window does not have a client, [`Client::exists`] return false.
|
||||
pub fn client(self) -> Client {
|
||||
get!(Client(0)).window_client(self)
|
||||
}
|
||||
|
||||
/// Returns the title of the window.
|
||||
pub fn title(self) -> String {
|
||||
get!().window_title(self)
|
||||
}
|
||||
|
||||
/// Returns the type of the window.
|
||||
pub fn type_(self) -> WindowType {
|
||||
get!(WindowType(0)).window_type(self)
|
||||
}
|
||||
|
||||
/// Returns the identifier of the window.
|
||||
///
|
||||
/// This is the identifier used in the `ext-foreign-toplevel-list-v1` protocol.
|
||||
pub fn id(self) -> String {
|
||||
get!().window_id(self)
|
||||
}
|
||||
|
||||
/// Returns whether this window is visible.
|
||||
pub fn is_visible(self) -> bool {
|
||||
get!().window_is_visible(self)
|
||||
}
|
||||
|
||||
/// Returns the parent of this window.
|
||||
///
|
||||
/// If this window has no parent, [`Window::exists`] returns false.
|
||||
pub fn parent(self) -> Window {
|
||||
get!(Window(0)).window_parent(self)
|
||||
}
|
||||
|
||||
/// Returns the children of this window.
|
||||
///
|
||||
/// Only containers have children.
|
||||
pub fn children(self) -> Vec<Window> {
|
||||
get!().window_children(self)
|
||||
}
|
||||
|
||||
/// Moves the window in the specified direction.
|
||||
pub fn move_(self, direction: Direction) {
|
||||
get!().window_move(self, direction)
|
||||
}
|
||||
|
||||
/// Returns whether the parent-container of the window is in mono-mode.
|
||||
pub fn mono(self) -> bool {
|
||||
get!(false).window_mono(self)
|
||||
}
|
||||
|
||||
/// Sets whether the parent-container of the window is in mono-mode.
|
||||
pub fn set_mono(self, mono: bool) {
|
||||
get!().set_window_mono(self, mono)
|
||||
}
|
||||
|
||||
/// Toggles whether the parent-container of the window is in mono-mode.
|
||||
pub fn toggle_mono(self) {
|
||||
self.set_mono(!self.mono());
|
||||
}
|
||||
|
||||
/// Returns the split axis of the parent-container of the window.
|
||||
pub fn split(self) -> Axis {
|
||||
get!(Axis::Horizontal).window_split(self)
|
||||
}
|
||||
|
||||
/// Sets the split axis of the parent-container of the window.
|
||||
pub fn set_split(self, axis: Axis) {
|
||||
get!().set_window_split(self, axis)
|
||||
}
|
||||
|
||||
/// Toggles the split axis of the parent-container of the window.
|
||||
pub fn toggle_split(self) {
|
||||
self.set_split(self.split().other());
|
||||
}
|
||||
|
||||
/// Creates a new container with the specified split in place of the window.
|
||||
pub fn create_split(self, axis: Axis) {
|
||||
get!().create_window_split(self, axis);
|
||||
}
|
||||
|
||||
/// Requests the window to be closed.
|
||||
pub fn close(self) {
|
||||
get!().close_window(self);
|
||||
}
|
||||
|
||||
/// Returns whether the window is floating.
|
||||
pub fn floating(self) -> bool {
|
||||
get!().get_window_floating(self)
|
||||
}
|
||||
/// Sets whether the window is floating.
|
||||
pub fn set_floating(self, floating: bool) {
|
||||
get!().set_window_floating(self, floating);
|
||||
}
|
||||
|
||||
/// Toggles whether the window is floating.
|
||||
///
|
||||
/// You can do the same by double-clicking on the header.
|
||||
pub fn toggle_floating(self) {
|
||||
self.set_floating(!self.floating());
|
||||
}
|
||||
|
||||
/// Returns the workspace that this window belongs to.
|
||||
///
|
||||
/// If no such workspace exists, `exists` returns `false` for the returned workspace.
|
||||
pub fn workspace(self) -> Workspace {
|
||||
get!(Workspace(0)).get_window_workspace(self)
|
||||
}
|
||||
|
||||
/// Moves the window to the workspace.
|
||||
pub fn set_workspace(self, workspace: Workspace) {
|
||||
get!().set_window_workspace(self, workspace)
|
||||
}
|
||||
|
||||
/// Toggles whether the currently focused window is fullscreen.
|
||||
pub fn toggle_fullscreen(self) {
|
||||
self.set_fullscreen(!self.fullscreen())
|
||||
}
|
||||
/// Returns whether the window is fullscreen.
|
||||
pub fn fullscreen(self) -> bool {
|
||||
get!(false).get_window_fullscreen(self)
|
||||
}
|
||||
|
||||
/// Sets whether the window is fullscreen.
|
||||
pub fn set_fullscreen(self, fullscreen: bool) {
|
||||
get!().set_window_fullscreen(self, fullscreen)
|
||||
}
|
||||
|
||||
/// Gets whether the window is pinned.
|
||||
///
|
||||
/// If a floating window is pinned, it will stay visible even when switching to a
|
||||
/// different workspace.
|
||||
pub fn float_pinned(self) -> bool {
|
||||
get!().get_window_pinned(self)
|
||||
}
|
||||
|
||||
/// Sets whether the window is pinned.
|
||||
pub fn set_float_pinned(self, pinned: bool) {
|
||||
get!().set_window_pinned(self, pinned);
|
||||
}
|
||||
|
||||
/// Toggles whether the window is pinned.
|
||||
pub fn toggle_float_pinned(self) {
|
||||
self.set_float_pinned(!self.float_pinned());
|
||||
}
|
||||
}
|
||||
|
||||
/// A window matcher.
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct WindowMatcher(pub u64);
|
||||
|
||||
/// A matched window.
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct MatchedWindow {
|
||||
pub(crate) matcher: WindowMatcher,
|
||||
pub(crate) window: Window,
|
||||
}
|
||||
|
||||
/// A criterion for matching a window.
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum WindowCriterion<'a> {
|
||||
/// Matches if the contained matcher matches.
|
||||
Matcher(WindowMatcher),
|
||||
/// Matches if the contained criterion does not match.
|
||||
Not(&'a WindowCriterion<'a>),
|
||||
/// Matches if the window has one of the types.
|
||||
Types(WindowType),
|
||||
/// Matches if all of the contained criteria match.
|
||||
All(&'a [WindowCriterion<'a>]),
|
||||
/// Matches if any of the contained criteria match.
|
||||
Any(&'a [WindowCriterion<'a>]),
|
||||
/// Matches if an exact number of the contained criteria match.
|
||||
Exactly(usize, &'a [WindowCriterion<'a>]),
|
||||
/// Matches if the window's client matches the client criterion.
|
||||
Client(&'a ClientCriterion<'a>),
|
||||
/// Matches the title of the window verbatim.
|
||||
Title(&'a str),
|
||||
/// Matches the title of the window with a regular expression.
|
||||
TitleRegex(&'a str),
|
||||
/// Matches the app-id of the window verbatim.
|
||||
AppId(&'a str),
|
||||
/// Matches the app-id of the window with a regular expression.
|
||||
AppIdRegex(&'a str),
|
||||
/// Matches if the window is floating.
|
||||
Floating,
|
||||
/// Matches if the window is visible.
|
||||
Visible,
|
||||
/// Matches if the window has the urgency flag set.
|
||||
Urgent,
|
||||
/// Matches if the window has the keyboard focus of the seat.
|
||||
Focus(Seat),
|
||||
/// Matches if the window is fullscreen.
|
||||
Fullscreen,
|
||||
/// Matches if the window has/hasn't just been mapped.
|
||||
///
|
||||
/// This is true for one iteration of the compositor's main loop immediately after the
|
||||
/// window has been mapped.
|
||||
JustMapped,
|
||||
/// Matches the toplevel-tag of the window verbatim.
|
||||
Tag(&'a str),
|
||||
/// Matches the toplevel-tag of the window with a regular expression.
|
||||
TagRegex(&'a str),
|
||||
/// Matches the X class of the window verbatim.
|
||||
XClass(&'a str),
|
||||
/// Matches the X class of the window with a regular expression.
|
||||
XClassRegex(&'a str),
|
||||
/// Matches the X instance of the window verbatim.
|
||||
XInstance(&'a str),
|
||||
/// Matches the X instance of the window with a regular expression.
|
||||
XInstanceRegex(&'a str),
|
||||
/// Matches the X role of the window verbatim.
|
||||
XRole(&'a str),
|
||||
/// Matches the X role of the window with a regular expression.
|
||||
XRoleRegex(&'a str),
|
||||
/// Matches the workspace the window.
|
||||
Workspace(Workspace),
|
||||
/// Matches the workspace name of the window verbatim.
|
||||
WorkspaceName(&'a str),
|
||||
/// Matches the workspace name of the window with a regular expression.
|
||||
WorkspaceNameRegex(&'a str),
|
||||
}
|
||||
|
||||
impl WindowCriterion<'_> {
|
||||
/// Converts the criterion to a matcher.
|
||||
pub fn to_matcher(self) -> WindowMatcher {
|
||||
get!(WindowMatcher(0)).create_window_matcher(self)
|
||||
}
|
||||
|
||||
/// Binds a function to execute when the criterion matches a window.
|
||||
///
|
||||
/// This leaks the matcher.
|
||||
pub fn bind<F: FnMut(MatchedWindow) + 'static>(self, cb: F) {
|
||||
self.to_matcher().bind(cb);
|
||||
}
|
||||
|
||||
/// Sets whether newly mapped windows that match this criterion get the keyboard focus.
|
||||
///
|
||||
/// If a window matches any criterion for which this is false, the window will not be
|
||||
/// automatically focused.
|
||||
///
|
||||
/// This leaks the matcher.
|
||||
pub fn set_auto_focus(self, auto_focus: bool) {
|
||||
self.to_matcher().set_auto_focus(auto_focus);
|
||||
}
|
||||
|
||||
/// Sets whether newly mapped windows that match this matcher are mapped tiling or
|
||||
/// floating.
|
||||
///
|
||||
/// If multiple such window matchers match a window, the used tile state is
|
||||
/// unspecified.
|
||||
///
|
||||
/// This leaks the matcher.
|
||||
pub fn set_initial_tile_state(self, tile_state: TileState) {
|
||||
self.to_matcher().set_initial_tile_state(tile_state);
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowMatcher {
|
||||
/// Destroys the matcher.
|
||||
///
|
||||
/// Any bound callback will no longer be executed.
|
||||
pub fn destroy(self) {
|
||||
get!().destroy_window_matcher(self);
|
||||
}
|
||||
|
||||
/// Sets a function to execute when the criterion matches a window.
|
||||
///
|
||||
/// Replaces any already bound callback.
|
||||
pub fn bind<F: FnMut(MatchedWindow) + 'static>(self, cb: F) {
|
||||
get!().set_window_matcher_handler(self, cb);
|
||||
}
|
||||
|
||||
/// Sets whether newly mapped windows that match this matcher get the keyboard focus.
|
||||
///
|
||||
/// If a window matches any matcher for which this is false, the window will not be
|
||||
/// automatically focused.
|
||||
pub fn set_auto_focus(self, auto_focus: bool) {
|
||||
get!().set_window_matcher_auto_focus(self, auto_focus);
|
||||
}
|
||||
|
||||
/// Sets whether newly mapped windows that match this matcher are mapped tiling or
|
||||
/// floating.
|
||||
///
|
||||
/// If multiple such window matchers match a window, the used tile state is
|
||||
/// unspecified.
|
||||
pub fn set_initial_tile_state(self, tile_state: TileState) {
|
||||
get!().set_window_matcher_initial_tile_state(self, tile_state);
|
||||
}
|
||||
}
|
||||
|
||||
impl MatchedWindow {
|
||||
/// Returns the window that matched.
|
||||
pub fn window(self) -> Window {
|
||||
self.window
|
||||
}
|
||||
|
||||
/// Returns the matcher.
|
||||
pub fn matcher(self) -> WindowMatcher {
|
||||
self.matcher
|
||||
}
|
||||
|
||||
/// Latches a function to be executed when the window no longer matches the criteria.
|
||||
pub fn latch<F: FnOnce() + 'static>(self, cb: F) {
|
||||
get!().set_window_matcher_latch_handler(self.matcher, self.window, cb);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for MatchedWindow {
|
||||
type Target = Window;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.window
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,10 @@
|
|||
[shortcuts]
|
||||
alt-x = "$switch-to-next"
|
||||
```
|
||||
- Add client and window rules. This is described in detail in
|
||||
[window-and-client-rules.md](./docs/window-and-client-rules.md).
|
||||
- Add client and tree CLI subcommands to inspect clients and windows, primarily
|
||||
to facilitate the writing of window and client rules.
|
||||
|
||||
# 1.10.0 (2025-04-22)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use {
|
|||
crate::{
|
||||
async_engine::SpawnedFuture,
|
||||
client::{CAPS_DEFAULT, ClientCaps},
|
||||
security_context_acceptor::AcceptorMetadata,
|
||||
state::State,
|
||||
utils::{errorfmt::ErrorFmt, oserror::OsError, xrd::xrd},
|
||||
},
|
||||
|
|
@ -170,6 +171,7 @@ impl Acceptor {
|
|||
}
|
||||
|
||||
async fn accept(fd: Rc<OwnedFd>, state: Rc<State>, effective_caps: ClientCaps) {
|
||||
let metadata = Rc::new(AcceptorMetadata::default());
|
||||
loop {
|
||||
let fd = match state.ring.accept(&fd, c::SOCK_CLOEXEC).await {
|
||||
Ok(fd) => fd,
|
||||
|
|
@ -179,9 +181,10 @@ async fn accept(fd: Rc<OwnedFd>, state: Rc<State>, effective_caps: ClientCaps) {
|
|||
}
|
||||
};
|
||||
let id = state.clients.id();
|
||||
if let Err(e) = state
|
||||
.clients
|
||||
.spawn(id, &state, fd, effective_caps, ClientCaps::all())
|
||||
if let Err(e) =
|
||||
state
|
||||
.clients
|
||||
.spawn(id, &state, fd, effective_caps, ClientCaps::all(), &metadata)
|
||||
{
|
||||
log::error!("Could not spawn a client: {}", ErrorFmt(e));
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -105,7 +105,6 @@ impl AsyncEngine {
|
|||
break;
|
||||
}
|
||||
self.now.take();
|
||||
self.iteration.fetch_add(1);
|
||||
let mut phase = 0;
|
||||
while phase < NUM_PHASES {
|
||||
self.queues[phase].swap(&mut *stash);
|
||||
|
|
@ -121,6 +120,7 @@ impl AsyncEngine {
|
|||
}
|
||||
}
|
||||
}
|
||||
self.iteration.fetch_add(1);
|
||||
self.yields.swap(&mut *yield_stash);
|
||||
while let Some(waker) = yield_stash.pop_front() {
|
||||
waker.wake();
|
||||
|
|
@ -153,7 +153,7 @@ impl AsyncEngine {
|
|||
self.yields.push(waker);
|
||||
}
|
||||
|
||||
fn iteration(&self) -> u64 {
|
||||
pub fn iteration(&self) -> u64 {
|
||||
self.iteration.get()
|
||||
}
|
||||
|
||||
|
|
|
|||
14
src/cli.rs
14
src/cli.rs
|
|
@ -1,3 +1,4 @@
|
|||
mod clients;
|
||||
mod color;
|
||||
mod color_management;
|
||||
mod damage_tracking;
|
||||
|
|
@ -13,15 +14,16 @@ mod run_privileged;
|
|||
pub mod screenshot;
|
||||
mod seat_test;
|
||||
mod set_log_level;
|
||||
mod tree;
|
||||
mod unlock;
|
||||
mod xwayland;
|
||||
|
||||
use {
|
||||
crate::{
|
||||
cli::{
|
||||
color_management::ColorManagementArgs, damage_tracking::DamageTrackingArgs,
|
||||
idle::IdleCmd, input::InputArgs, randr::RandrArgs, reexec::ReexecArgs,
|
||||
xwayland::XwaylandArgs,
|
||||
clients::ClientsArgs, color_management::ColorManagementArgs,
|
||||
damage_tracking::DamageTrackingArgs, idle::IdleCmd, input::InputArgs, randr::RandrArgs,
|
||||
reexec::ReexecArgs, tree::TreeArgs, xwayland::XwaylandArgs,
|
||||
},
|
||||
compositor::start_compositor,
|
||||
format::{Format, ref_formats},
|
||||
|
|
@ -86,6 +88,10 @@ pub enum Cmd {
|
|||
/// Replace the compositor by another process. (Only for development.)
|
||||
#[clap(hide = true)]
|
||||
Reexec(ReexecArgs),
|
||||
/// Inspect/manipulate the connected clients.
|
||||
Clients(ClientsArgs),
|
||||
/// Inspect the surface tree.
|
||||
Tree(TreeArgs),
|
||||
#[cfg(feature = "it")]
|
||||
RunTests,
|
||||
}
|
||||
|
|
@ -244,6 +250,8 @@ pub fn main() {
|
|||
Cmd::DamageTracking(a) => damage_tracking::main(cli.global, a),
|
||||
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),
|
||||
|
|
|
|||
243
src/cli/clients.rs
Normal file
243
src/cli/clients.rs
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
use {
|
||||
crate::{
|
||||
cli::GlobalArgs,
|
||||
tools::tool_client::{Handle, ToolClient, with_tool_client},
|
||||
wire::{JayClientQueryId, jay_client_query, jay_compositor},
|
||||
},
|
||||
ahash::AHashMap,
|
||||
clap::{Args, Subcommand},
|
||||
std::{cell::RefCell, mem, rc::Rc},
|
||||
uapi::c,
|
||||
};
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct ClientsArgs {
|
||||
#[clap(subcommand)]
|
||||
cmd: Option<ClientsCmd>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum ClientsCmd {
|
||||
/// Show information about clients.
|
||||
Show(ShowArgs),
|
||||
/// Disconnect a client.
|
||||
Kill(KillArgs),
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
struct ShowArgs {
|
||||
#[clap(subcommand)]
|
||||
cmd: ShowCmd,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum ShowCmd {
|
||||
/// Show all clients.
|
||||
All,
|
||||
/// Show a client with a given ID.
|
||||
Id(ShowIdArgs),
|
||||
/// Interactively select a window and show information about its client.
|
||||
SelectWindow,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
struct ShowIdArgs {
|
||||
/// The ID of the client.
|
||||
id: u64,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
struct KillArgs {
|
||||
#[clap(subcommand)]
|
||||
cmd: KillCmd,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum KillCmd {
|
||||
/// Kill the client with a given ID.
|
||||
Id(KillIdArgs),
|
||||
/// Interactively select a window and kill its client.
|
||||
SelectWindow,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
struct KillIdArgs {
|
||||
/// The ID of the client.
|
||||
id: u64,
|
||||
}
|
||||
|
||||
pub fn main(global: GlobalArgs, clients_args: ClientsArgs) {
|
||||
with_tool_client(global.log_level.into(), |tc| async move {
|
||||
let clients = Rc::new(Clients { tc: tc.clone() });
|
||||
clients.run(clients_args).await;
|
||||
});
|
||||
}
|
||||
|
||||
struct Clients {
|
||||
tc: Rc<ToolClient>,
|
||||
}
|
||||
|
||||
impl Clients {
|
||||
async fn run(&self, args: ClientsArgs) {
|
||||
let tc = &self.tc;
|
||||
let comp = tc.jay_compositor().await;
|
||||
let cmd = args
|
||||
.cmd
|
||||
.unwrap_or(ClientsCmd::Show(ShowArgs { cmd: ShowCmd::All }));
|
||||
match cmd {
|
||||
ClientsCmd::Show(a) => {
|
||||
let id = tc.id();
|
||||
tc.send(jay_compositor::CreateClientQuery { self_id: comp, id });
|
||||
match a.cmd {
|
||||
ShowCmd::All => {
|
||||
tc.send(jay_client_query::AddAll { self_id: id });
|
||||
}
|
||||
ShowCmd::Id(a) => {
|
||||
tc.send(jay_client_query::AddId {
|
||||
self_id: id,
|
||||
id: a.id,
|
||||
});
|
||||
}
|
||||
ShowCmd::SelectWindow => {
|
||||
let client_id = tc.select_toplevel_client().await;
|
||||
if client_id == 0 {
|
||||
fatal!("Did not select a window");
|
||||
}
|
||||
tc.send(jay_client_query::AddId {
|
||||
self_id: id,
|
||||
id: client_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
tc.send(jay_client_query::Execute { self_id: id });
|
||||
let clients = handle_client_query(tc, id).await;
|
||||
let mut clients = clients.values().collect::<Vec<_>>();
|
||||
clients.sort_by_key(|c| c.id);
|
||||
let mut prefix = " ".to_string();
|
||||
let mut printer = ClientPrinter {
|
||||
prefix: &mut prefix,
|
||||
};
|
||||
for client in clients {
|
||||
println!("- client:");
|
||||
printer.print_client(client);
|
||||
}
|
||||
}
|
||||
ClientsCmd::Kill(a) => match a.cmd {
|
||||
KillCmd::Id(id) => {
|
||||
tc.send(jay_compositor::KillClient {
|
||||
self_id: comp,
|
||||
id: id.id,
|
||||
});
|
||||
}
|
||||
KillCmd::SelectWindow => {
|
||||
let client_id = tc.select_toplevel_client().await;
|
||||
if client_id == 0 {
|
||||
fatal!("Did not select a window");
|
||||
}
|
||||
tc.send(jay_compositor::KillClient {
|
||||
self_id: comp,
|
||||
id: client_id,
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
tc.round_trip().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Client {
|
||||
pub id: u64,
|
||||
pub sandboxed: bool,
|
||||
pub sandbox_engine: Option<String>,
|
||||
pub sandbox_app_id: Option<String>,
|
||||
pub sandbox_instance_id: Option<String>,
|
||||
pub uid: Option<c::uid_t>,
|
||||
pub pid: Option<c::pid_t>,
|
||||
pub is_xwayland: bool,
|
||||
pub comm: Option<String>,
|
||||
pub exe: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn handle_client_query(
|
||||
tl: &Rc<ToolClient>,
|
||||
id: JayClientQueryId,
|
||||
) -> AHashMap<u64, Client> {
|
||||
use jay_client_query::*;
|
||||
let c = Rc::new(RefCell::new(Vec::<Client>::new()));
|
||||
macro_rules! last {
|
||||
($c:ident) => {
|
||||
$c.borrow_mut().last_mut().unwrap()
|
||||
};
|
||||
}
|
||||
Start::handle(tl, id, c.clone(), |c, event| {
|
||||
c.borrow_mut().push(Client::default());
|
||||
last!(c).id = event.id;
|
||||
});
|
||||
Sandboxed::handle(tl, id, c.clone(), |c, _event| {
|
||||
last!(c).sandboxed = true;
|
||||
});
|
||||
SandboxEngine::handle(tl, id, c.clone(), |c, event| {
|
||||
last!(c).sandbox_engine = Some(event.engine.to_string());
|
||||
});
|
||||
SandboxAppId::handle(tl, id, c.clone(), |c, event| {
|
||||
last!(c).sandbox_app_id = Some(event.app_id.to_string());
|
||||
});
|
||||
SandboxInstanceId::handle(tl, id, c.clone(), |c, event| {
|
||||
last!(c).sandbox_instance_id = Some(event.instance_id.to_string());
|
||||
});
|
||||
Uid::handle(tl, id, c.clone(), |c, event| {
|
||||
last!(c).uid = Some(event.uid);
|
||||
});
|
||||
Pid::handle(tl, id, c.clone(), |c, event| {
|
||||
last!(c).pid = Some(event.pid);
|
||||
});
|
||||
IsXwayland::handle(tl, id, c.clone(), |c, _event| {
|
||||
last!(c).is_xwayland = true;
|
||||
});
|
||||
Comm::handle(tl, id, c.clone(), |c, event| {
|
||||
last!(c).comm = Some(event.comm.to_string());
|
||||
});
|
||||
Exe::handle(tl, id, c.clone(), |c, event| {
|
||||
last!(c).exe = Some(event.exe.to_string());
|
||||
});
|
||||
tl.round_trip().await;
|
||||
mem::take(&mut *c.borrow_mut())
|
||||
.into_iter()
|
||||
.map(|c| (c.id, c))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub struct ClientPrinter<'a> {
|
||||
pub prefix: &'a mut String,
|
||||
}
|
||||
|
||||
impl ClientPrinter<'_> {
|
||||
pub fn print_client(&mut self, c: &Client) {
|
||||
let p = &self.prefix;
|
||||
macro_rules! opt {
|
||||
($field:ident, $pretty:expr) => {
|
||||
if let Some(v) = &c.$field {
|
||||
println!("{p}{}: {}", $pretty, v);
|
||||
}
|
||||
};
|
||||
}
|
||||
macro_rules! bol {
|
||||
($field:ident, $pretty:expr) => {
|
||||
if c.$field {
|
||||
println!("{p}{}", $pretty);
|
||||
}
|
||||
};
|
||||
}
|
||||
println!("{p}id: {}", c.id);
|
||||
bol!(sandboxed, "sandboxed");
|
||||
opt!(sandbox_engine, "sandbox engine");
|
||||
opt!(sandbox_app_id, "sandbox app id");
|
||||
opt!(sandbox_instance_id, "sandbox instance id");
|
||||
opt!(uid, "uid");
|
||||
opt!(pid, "pid");
|
||||
bol!(is_xwayland, "xwayland");
|
||||
opt!(comm, "comm");
|
||||
opt!(exe, "exe");
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,10 @@ use {
|
|||
crate::{
|
||||
async_engine::SpawnedFuture,
|
||||
client::{error::LookupError, objects::Objects},
|
||||
criteria::{
|
||||
CritDestroyListener, CritMatcherId,
|
||||
clm::{CL_CHANGED_DESTROYED, CL_CHANGED_NEW, ClMatcherChange},
|
||||
},
|
||||
ifs::{
|
||||
wl_display::WlDisplay,
|
||||
wl_registry::WlRegistry,
|
||||
|
|
@ -9,6 +13,7 @@ use {
|
|||
},
|
||||
leaks::Tracker,
|
||||
object::{Interface, Object, ObjectId, WL_DISPLAY_ID},
|
||||
security_context_acceptor::AcceptorMetadata,
|
||||
state::State,
|
||||
utils::{
|
||||
activation_token::ActivationToken,
|
||||
|
|
@ -19,6 +24,7 @@ use {
|
|||
numcell::NumCell,
|
||||
pending_serial::PendingSerial,
|
||||
pid_info::{PidInfo, get_pid_info, get_socket_creds},
|
||||
pidfd_send_signal::pidfd_send_signal,
|
||||
},
|
||||
wire::WlRegistryId,
|
||||
},
|
||||
|
|
@ -30,7 +36,7 @@ use {
|
|||
fmt::{Debug, Display, Formatter},
|
||||
mem,
|
||||
ops::DerefMut,
|
||||
rc::Rc,
|
||||
rc::{Rc, Weak},
|
||||
},
|
||||
uapi::{OwnedFd, c},
|
||||
};
|
||||
|
|
@ -105,7 +111,6 @@ impl Clients {
|
|||
ClientId(self.next_client_id.fetch_add(1))
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "it"), expect(dead_code))]
|
||||
pub fn get(&self, id: ClientId) -> Result<Rc<Client>, ClientError> {
|
||||
let clients = self.clients.borrow();
|
||||
match clients.get(&id) {
|
||||
|
|
@ -121,6 +126,7 @@ impl Clients {
|
|||
socket: Rc<OwnedFd>,
|
||||
effective_caps: ClientCaps,
|
||||
bounding_caps: ClientCaps,
|
||||
acceptor: &Rc<AcceptorMetadata>,
|
||||
) -> Result<(), ClientError> {
|
||||
let Some((uid, pid)) = get_socket_creds(&socket) else {
|
||||
return Ok(());
|
||||
|
|
@ -134,6 +140,7 @@ impl Clients {
|
|||
effective_caps,
|
||||
bounding_caps,
|
||||
false,
|
||||
acceptor,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -148,6 +155,7 @@ impl Clients {
|
|||
effective_caps: ClientCaps,
|
||||
bounding_caps: ClientCaps,
|
||||
is_xwayland: bool,
|
||||
acceptor: &Rc<AcceptorMetadata>,
|
||||
) -> Result<Rc<Client>, ClientError> {
|
||||
let data = Rc::new_cyclic(|slf| Client {
|
||||
id,
|
||||
|
|
@ -177,6 +185,9 @@ impl Clients {
|
|||
)),
|
||||
wire_scale: Default::default(),
|
||||
focus_stealing_serial: Default::default(),
|
||||
changed_properties: Default::default(),
|
||||
destroyed: Default::default(),
|
||||
acceptor: acceptor.clone(),
|
||||
});
|
||||
track!(data, data);
|
||||
let display = Rc::new(WlDisplay::new(&data));
|
||||
|
|
@ -196,6 +207,7 @@ impl Clients {
|
|||
data.pid_info.comm,
|
||||
effective_caps,
|
||||
);
|
||||
client.data.property_changed(CL_CHANGED_NEW);
|
||||
self.clients.borrow_mut().insert(client.data.id, client);
|
||||
Ok(data)
|
||||
}
|
||||
|
|
@ -251,6 +263,14 @@ impl Drop for ClientHolder {
|
|||
self.data.surfaces_by_xwayland_serial.clear();
|
||||
self.data.remove_activation_tokens();
|
||||
self.data.commit_timelines.clear();
|
||||
self.data.property_changed(CL_CHANGED_DESTROYED);
|
||||
if self.data.is_xwayland {
|
||||
if let Some(pidfd) = self.data.state.xwayland.pidfd.get() {
|
||||
if let Err(e) = pidfd_send_signal(&pidfd, c::SIGKILL) {
|
||||
log::error!("Could not kill Xwayland: {}", ErrorFmt(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -289,6 +309,9 @@ pub struct Client {
|
|||
pub commit_timelines: Rc<CommitTimelines>,
|
||||
pub wire_scale: Cell<Option<i32>>,
|
||||
pub focus_stealing_serial: Cell<Option<u64>>,
|
||||
pub changed_properties: Cell<ClMatcherChange>,
|
||||
pub destroyed: CopyHashMap<CritMatcherId, Weak<dyn CritDestroyListener<Rc<Self>>>>,
|
||||
pub acceptor: Rc<AcceptorMetadata>,
|
||||
}
|
||||
|
||||
pub const NUM_CACHED_SERIAL_RANGES: usize = 64;
|
||||
|
|
@ -494,6 +517,14 @@ impl Client {
|
|||
self.state.activation_tokens.remove(token);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn property_changed(self: &Rc<Self>, change: ClMatcherChange) {
|
||||
let props = self.changed_properties.get();
|
||||
self.changed_properties.set(props | change);
|
||||
if props.is_none() && change.is_some() {
|
||||
self.state.cl_matcher_manager.changed(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WaylandObject: Object {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,13 @@ use {
|
|||
cmm::{cmm_manager::ColorManager, cmm_primaries::Primaries},
|
||||
config::ConfigProxy,
|
||||
cpu_worker::{CpuWorker, CpuWorkerError},
|
||||
criteria::{
|
||||
CritMatcherIds,
|
||||
clm::{ClMatcherManager, handle_cl_changes, handle_cl_leaf_events},
|
||||
tlm::{
|
||||
TlMatcherManager, handle_tl_changes, handle_tl_just_mapped, handle_tl_leaf_events,
|
||||
},
|
||||
},
|
||||
damage::{DamageVisualizer, visualize_damage},
|
||||
dbus::Dbus,
|
||||
ei::ei_client::EiClients,
|
||||
|
|
@ -156,6 +163,7 @@ fn start_compositor2(
|
|||
scales.add(Scale::from_int(1));
|
||||
let cpu_worker = Rc::new(CpuWorker::new(&ring, &engine)?);
|
||||
let color_manager = ColorManager::new();
|
||||
let crit_ids = Rc::new(CritMatcherIds::default());
|
||||
let state = Rc::new(State {
|
||||
kb_ctx,
|
||||
backend: CloneCell::new(Rc::new(DummyBackend)),
|
||||
|
|
@ -218,11 +226,13 @@ fn start_compositor2(
|
|||
run_args,
|
||||
xwayland: XWaylandState {
|
||||
enabled: Cell::new(true),
|
||||
pidfd: Default::default(),
|
||||
handler: Default::default(),
|
||||
queue: Default::default(),
|
||||
ipc_device_ids: Default::default(),
|
||||
use_wire_scale: Default::default(),
|
||||
wire_scale: Default::default(),
|
||||
windows: Default::default(),
|
||||
},
|
||||
acceptor: Default::default(),
|
||||
serial: Default::default(),
|
||||
|
|
@ -292,6 +302,8 @@ fn start_compositor2(
|
|||
float_above_fullscreen: Cell::new(false),
|
||||
icons: Default::default(),
|
||||
show_pin_icon: Cell::new(false),
|
||||
cl_matcher_manager: ClMatcherManager::new(&crit_ids),
|
||||
tl_matcher_manager: TlMatcherManager::new(&crit_ids),
|
||||
});
|
||||
state.tracker.register(ClientId::from_raw(0));
|
||||
create_dummy_output(&state);
|
||||
|
|
@ -464,6 +476,21 @@ fn start_global_event_handlers(
|
|||
"workspace manager done",
|
||||
workspace_manager_done(state.clone()),
|
||||
),
|
||||
eng.spawn("cl matcher manager", handle_cl_changes(state.clone())),
|
||||
eng.spawn(
|
||||
"cl matcher leaf events",
|
||||
handle_cl_leaf_events(state.clone()),
|
||||
),
|
||||
eng.spawn("tl matcher manager", handle_tl_changes(state.clone())),
|
||||
eng.spawn(
|
||||
"tl matcher leaf events",
|
||||
handle_tl_leaf_events(state.clone()),
|
||||
),
|
||||
eng.spawn2(
|
||||
"tl matcher just mapped",
|
||||
Phase::Layout,
|
||||
handle_tl_just_mapped(state.clone()),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,9 +8,10 @@ use {
|
|||
config::handler::ConfigProxyHandler,
|
||||
ifs::wl_seat::SeatId,
|
||||
state::State,
|
||||
tree::ToplevelData,
|
||||
utils::{
|
||||
clonecell::CloneCell, numcell::NumCell, ptr_ext::PtrExt, unlink_on_drop::UnlinkOnDrop,
|
||||
xrd::xrd,
|
||||
clonecell::CloneCell, numcell::NumCell, ptr_ext::PtrExt,
|
||||
toplevel_identifier::ToplevelIdentifier, unlink_on_drop::UnlinkOnDrop, xrd::xrd,
|
||||
},
|
||||
},
|
||||
bincode::Options,
|
||||
|
|
@ -22,6 +23,7 @@ use {
|
|||
input::{InputDevice, Seat, SwitchEvent},
|
||||
keyboard::{mods::Modifiers, syms::KeySym},
|
||||
video::{Connector, DrmDevice},
|
||||
window::{self, TileState},
|
||||
},
|
||||
libloading::Library,
|
||||
std::{cell::Cell, io, mem, ptr, rc::Rc},
|
||||
|
|
@ -151,6 +153,26 @@ impl ConfigProxy {
|
|||
event,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn toplevel_removed(&self, id: ToplevelIdentifier) {
|
||||
let Some(handler) = self.handler.get() else {
|
||||
return;
|
||||
};
|
||||
if let Some(win) = handler.windows_from_tl_id.remove(&id) {
|
||||
handler.windows_to_tl_id.remove(&win);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn auto_focus(&self, data: &ToplevelData) -> bool {
|
||||
let Some(handler) = self.handler.get() else {
|
||||
return true;
|
||||
};
|
||||
handler.auto_focus(data)
|
||||
}
|
||||
|
||||
pub fn initial_tile_state(&self, data: &ToplevelData) -> Option<TileState> {
|
||||
self.handler.get()?.initial_tile_state(data)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ConfigProxy {
|
||||
|
|
@ -202,6 +224,20 @@ impl ConfigProxy {
|
|||
timers_by_id: Default::default(),
|
||||
pollable_id: Default::default(),
|
||||
pollables: Default::default(),
|
||||
window_ids: NumCell::new(1),
|
||||
windows_from_tl_id: Default::default(),
|
||||
windows_to_tl_id: Default::default(),
|
||||
client_matcher_ids: NumCell::new(1),
|
||||
client_matchers: Default::default(),
|
||||
client_matcher_cache: Default::default(),
|
||||
client_matcher_leafs: Default::default(),
|
||||
window_matcher_ids: NumCell::new(1),
|
||||
window_matchers: Default::default(),
|
||||
window_matcher_cache: Default::default(),
|
||||
window_matcher_leafs: Default::default(),
|
||||
window_matcher_std_kinds: state.tl_matcher_manager.kind(window::CLIENT_WINDOW),
|
||||
window_matcher_no_auto_focus: Default::default(),
|
||||
window_matcher_initial_tile_state: Default::default(),
|
||||
});
|
||||
let init_msg = bincode_ops()
|
||||
.serialize(&InitMessage::V1(V1InitMessage {}))
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
96
src/criteria.rs
Normal file
96
src/criteria.rs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
pub mod clm;
|
||||
mod crit_graph;
|
||||
pub mod crit_leaf;
|
||||
mod crit_matchers;
|
||||
mod crit_per_target_data;
|
||||
pub mod tlm;
|
||||
|
||||
use {
|
||||
crate::{
|
||||
criteria::{
|
||||
crit_graph::{CritMgr, CritMiddle, CritRoot, CritRootCriterion, CritRootFixed},
|
||||
crit_leaf::CritLeafMatcher,
|
||||
crit_matchers::{critm_any_or_all::CritMatchAnyOrAll, critm_exactly::CritMatchExactly},
|
||||
},
|
||||
utils::copyhashmap::CopyHashMap,
|
||||
},
|
||||
linearize::StaticMap,
|
||||
regex::Regex,
|
||||
std::rc::{Rc, Weak},
|
||||
};
|
||||
pub use {
|
||||
crit_graph::{CritTarget, CritUpstreamNode},
|
||||
crit_per_target_data::CritDestroyListener,
|
||||
};
|
||||
|
||||
linear_ids!(CritMatcherIds, CritMatcherId, u64);
|
||||
|
||||
type RootMatcherMap<Target, T> = CopyHashMap<CritMatcherId, Weak<CritRoot<Target, T>>>;
|
||||
type FixedRootMatcher<Target, T> = StaticMap<bool, Rc<CritRoot<Target, CritRootFixed<Target, T>>>>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum CritLiteralOrRegex {
|
||||
Literal(String),
|
||||
Regex(Regex),
|
||||
}
|
||||
|
||||
impl CritLiteralOrRegex {
|
||||
fn matches(&self, string: &str) -> bool {
|
||||
match self {
|
||||
CritLiteralOrRegex::Literal(p) => string == p,
|
||||
CritLiteralOrRegex::Regex(r) => r.is_match(string),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CritMgrExt: CritMgr {
|
||||
fn list(
|
||||
&self,
|
||||
upstream: &[Rc<dyn CritUpstreamNode<Self::Target>>],
|
||||
all: bool,
|
||||
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
|
||||
if upstream.is_empty() {
|
||||
return self.match_constant()[all].clone();
|
||||
}
|
||||
CritMiddle::new(self, upstream, CritMatchAnyOrAll::new(upstream, all))
|
||||
}
|
||||
|
||||
fn exactly(
|
||||
&self,
|
||||
upstream: &[Rc<dyn CritUpstreamNode<Self::Target>>],
|
||||
num: usize,
|
||||
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
|
||||
if num > upstream.len() {
|
||||
return self.match_constant()[false].clone();
|
||||
}
|
||||
if num == 0 {
|
||||
let upstream: Vec<_> = upstream.iter().map(|u| u.not(self)).collect();
|
||||
return self.list(&upstream, true);
|
||||
}
|
||||
CritMiddle::new(self, upstream, CritMatchExactly::new(upstream, num))
|
||||
}
|
||||
|
||||
fn leaf(
|
||||
&self,
|
||||
upstream: &Rc<dyn CritUpstreamNode<Self::Target>>,
|
||||
on_match: impl Fn(<Self::Target as CritTarget>::LeafData) -> Box<dyn FnOnce()> + 'static,
|
||||
) -> Rc<CritLeafMatcher<Self::Target>> {
|
||||
CritLeafMatcher::new(self, upstream, on_match)
|
||||
}
|
||||
|
||||
fn not(
|
||||
&self,
|
||||
upstream: &Rc<dyn CritUpstreamNode<Self::Target>>,
|
||||
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
|
||||
upstream.not(self)
|
||||
}
|
||||
|
||||
fn root<T>(&self, criterion: T) -> Rc<dyn CritUpstreamNode<Self::Target>>
|
||||
where
|
||||
T: CritRootCriterion<Self::Target>,
|
||||
{
|
||||
CritRoot::new(self.roots(), self.id(), criterion)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> CritMgrExt for T where T: CritMgr {}
|
||||
270
src/criteria/clm.rs
Normal file
270
src/criteria/clm.rs
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
pub mod clm_matchers;
|
||||
|
||||
use {
|
||||
crate::{
|
||||
client::{Client, ClientId},
|
||||
criteria::{
|
||||
CritDestroyListener, CritLiteralOrRegex, CritMatcherId, CritMatcherIds, CritMgrExt,
|
||||
CritUpstreamNode, FixedRootMatcher, RootMatcherMap,
|
||||
clm::clm_matchers::{
|
||||
clmm_is_xwayland::ClmMatchIsXwayland,
|
||||
clmm_pid::ClmMatchPid,
|
||||
clmm_sandboxed::ClmMatchSandboxed,
|
||||
clmm_string::{
|
||||
ClmMatchComm, ClmMatchExe, ClmMatchSandboxAppId, ClmMatchSandboxEngine,
|
||||
ClmMatchSandboxInstanceId,
|
||||
},
|
||||
clmm_uid::ClmMatchUid,
|
||||
},
|
||||
crit_graph::{
|
||||
CritMgr, CritRoot, CritRootFixed, CritTarget, CritTargetOwner, WeakCritTargetOwner,
|
||||
},
|
||||
crit_leaf::{CritLeafEvent, CritLeafMatcher},
|
||||
crit_matchers::critm_constant::CritMatchConstant,
|
||||
},
|
||||
state::State,
|
||||
utils::{copyhashmap::CopyHashMap, hash_map_ext::HashMapExt, queue::AsyncQueue},
|
||||
},
|
||||
linearize::static_map,
|
||||
std::{
|
||||
marker::PhantomData,
|
||||
rc::{Rc, Weak},
|
||||
},
|
||||
};
|
||||
|
||||
bitflags! {
|
||||
ClMatcherChange: u32;
|
||||
CL_CHANGED_DESTROYED = 1 << 0,
|
||||
CL_CHANGED_NEW = 1 << 1,
|
||||
}
|
||||
|
||||
type ClmFixedRootMatcher<T> = FixedRootMatcher<Rc<Client>, T>;
|
||||
|
||||
pub struct ClMatcherManager {
|
||||
ids: Rc<CritMatcherIds>,
|
||||
changes: AsyncQueue<Rc<Client>>,
|
||||
leaf_events: Rc<AsyncQueue<CritLeafEvent<Rc<Client>>>>,
|
||||
constant: ClmFixedRootMatcher<CritMatchConstant<Rc<Client>>>,
|
||||
sandboxed: ClmFixedRootMatcher<ClmMatchSandboxed>,
|
||||
is_xwayland: ClmFixedRootMatcher<ClmMatchIsXwayland>,
|
||||
matchers: Rc<RootMatchers>,
|
||||
}
|
||||
|
||||
type ClmRootMatcherMap<T> = RootMatcherMap<Rc<Client>, T>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RootMatchers {
|
||||
sandbox_app_id: ClmRootMatcherMap<ClmMatchSandboxAppId>,
|
||||
sandbox_engine: ClmRootMatcherMap<ClmMatchSandboxEngine>,
|
||||
sandbox_instance_id: ClmRootMatcherMap<ClmMatchSandboxInstanceId>,
|
||||
uid: ClmRootMatcherMap<ClmMatchUid>,
|
||||
pid: ClmRootMatcherMap<ClmMatchPid>,
|
||||
comm: ClmRootMatcherMap<ClmMatchComm>,
|
||||
exe: ClmRootMatcherMap<ClmMatchExe>,
|
||||
}
|
||||
|
||||
pub async fn handle_cl_changes(state: Rc<State>) {
|
||||
let mgr = &state.cl_matcher_manager;
|
||||
loop {
|
||||
let tl = mgr.changes.pop().await;
|
||||
mgr.update_matches(&tl);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_cl_leaf_events(state: Rc<State>) {
|
||||
let mgr = &state.cl_matcher_manager;
|
||||
let debouncer = state.ring.debouncer(1000);
|
||||
loop {
|
||||
let event = mgr.leaf_events.pop().await;
|
||||
event.run();
|
||||
debouncer.debounce().await;
|
||||
}
|
||||
}
|
||||
|
||||
pub type ClmUpstreamNode = dyn CritUpstreamNode<Rc<Client>>;
|
||||
pub type ClmLeafMatcher = CritLeafMatcher<Rc<Client>>;
|
||||
|
||||
impl ClMatcherManager {
|
||||
pub fn new(ids: &Rc<CritMatcherIds>) -> Self {
|
||||
let matchers = Rc::new(RootMatchers::default());
|
||||
macro_rules! bool {
|
||||
($name:ident) => {{
|
||||
static_map! {
|
||||
v => CritRoot::new(
|
||||
&matchers,
|
||||
ids.next(),
|
||||
CritRootFixed($name(v), PhantomData),
|
||||
)
|
||||
}
|
||||
}};
|
||||
}
|
||||
Self {
|
||||
constant: CritMatchConstant::create(&matchers, ids),
|
||||
sandboxed: bool!(ClmMatchSandboxed),
|
||||
is_xwayland: bool!(ClmMatchIsXwayland),
|
||||
changes: Default::default(),
|
||||
leaf_events: Default::default(),
|
||||
ids: ids.clone(),
|
||||
matchers,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&self) {
|
||||
self.changes.clear();
|
||||
self.leaf_events.clear();
|
||||
}
|
||||
|
||||
pub fn rematch_all(&self, state: &Rc<State>) {
|
||||
for client in state.clients.clients.borrow().values() {
|
||||
client.data.property_changed(CL_CHANGED_NEW);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn changed(&self, client: &Rc<Client>) {
|
||||
self.changes.push(client.clone());
|
||||
}
|
||||
|
||||
fn update_matches(&self, data: &Rc<Client>) {
|
||||
let mut changed = data.changed_properties.take();
|
||||
if changed.contains(CL_CHANGED_DESTROYED) {
|
||||
for destroyed in data.destroyed.lock().drain_values() {
|
||||
if let Some(destroyed) = destroyed.upgrade() {
|
||||
destroyed.destroyed(data.id);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
macro_rules! handlers {
|
||||
($name:ident) => {
|
||||
self.matchers
|
||||
.$name
|
||||
.lock()
|
||||
.values()
|
||||
.filter_map(|m| m.upgrade())
|
||||
};
|
||||
}
|
||||
macro_rules! fixed {
|
||||
($name:ident) => {
|
||||
self.$name[false].handle(data);
|
||||
self.$name[true].handle(data);
|
||||
};
|
||||
}
|
||||
if changed.contains(CL_CHANGED_NEW) {
|
||||
changed |= ClMatcherChange::all();
|
||||
macro_rules! unconditional {
|
||||
($field:ident) => {
|
||||
for m in handlers!($field) {
|
||||
m.handle(data);
|
||||
}
|
||||
};
|
||||
}
|
||||
unconditional!(sandbox_instance_id);
|
||||
unconditional!(sandbox_app_id);
|
||||
unconditional!(sandbox_engine);
|
||||
unconditional!(uid);
|
||||
unconditional!(pid);
|
||||
unconditional!(comm);
|
||||
unconditional!(exe);
|
||||
fixed!(sandboxed);
|
||||
fixed!(is_xwayland);
|
||||
self.constant[true].handle(data);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sandbox_engine(&self, string: CritLiteralOrRegex) -> Rc<ClmUpstreamNode> {
|
||||
self.root(ClmMatchSandboxEngine::new(string))
|
||||
}
|
||||
|
||||
pub fn sandbox_app_id(&self, string: CritLiteralOrRegex) -> Rc<ClmUpstreamNode> {
|
||||
self.root(ClmMatchSandboxAppId::new(string))
|
||||
}
|
||||
|
||||
pub fn sandbox_instance_id(&self, string: CritLiteralOrRegex) -> Rc<ClmUpstreamNode> {
|
||||
self.root(ClmMatchSandboxInstanceId::new(string))
|
||||
}
|
||||
|
||||
pub fn sandboxed(&self) -> Rc<ClmUpstreamNode> {
|
||||
self.sandboxed[true].clone()
|
||||
}
|
||||
|
||||
pub fn uid(&self, pid: i32) -> Rc<ClmUpstreamNode> {
|
||||
self.root(ClmMatchUid(pid as _))
|
||||
}
|
||||
|
||||
pub fn pid(&self, pid: i32) -> Rc<ClmUpstreamNode> {
|
||||
self.root(ClmMatchPid(pid as _))
|
||||
}
|
||||
|
||||
pub fn is_xwayland(&self) -> Rc<ClmUpstreamNode> {
|
||||
self.is_xwayland[true].clone()
|
||||
}
|
||||
|
||||
pub fn comm(&self, string: CritLiteralOrRegex) -> Rc<ClmUpstreamNode> {
|
||||
self.root(ClmMatchComm::new(string))
|
||||
}
|
||||
|
||||
pub fn exe(&self, string: CritLiteralOrRegex) -> Rc<ClmUpstreamNode> {
|
||||
self.root(ClmMatchExe::new(string))
|
||||
}
|
||||
}
|
||||
|
||||
impl CritTarget for Rc<Client> {
|
||||
type Id = ClientId;
|
||||
type Mgr = ClMatcherManager;
|
||||
type RootMatchers = RootMatchers;
|
||||
type LeafData = ClientId;
|
||||
type Owner = Weak<Client>;
|
||||
|
||||
fn owner(&self) -> Self::Owner {
|
||||
Rc::downgrade(self)
|
||||
}
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn destroyed(&self) -> &CopyHashMap<CritMatcherId, Weak<dyn CritDestroyListener<Self>>> {
|
||||
&self.destroyed
|
||||
}
|
||||
|
||||
fn leaf_data(&self) -> Self::LeafData {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl CritTargetOwner for Rc<Client> {
|
||||
type Target = Rc<Client>;
|
||||
|
||||
fn data(&self) -> &Self::Target {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl WeakCritTargetOwner for Weak<Client> {
|
||||
type Target = Rc<Client>;
|
||||
type Owner = Rc<Client>;
|
||||
|
||||
fn upgrade(&self) -> Option<Self::Owner> {
|
||||
self.upgrade()
|
||||
}
|
||||
}
|
||||
|
||||
impl CritMgr for ClMatcherManager {
|
||||
type Target = Rc<Client>;
|
||||
|
||||
fn id(&self) -> CritMatcherId {
|
||||
self.ids.next()
|
||||
}
|
||||
|
||||
fn leaf_events(&self) -> &Rc<AsyncQueue<CritLeafEvent<Self::Target>>> {
|
||||
&self.leaf_events
|
||||
}
|
||||
|
||||
fn match_constant(&self) -> &FixedRootMatcher<Self::Target, CritMatchConstant<Self::Target>> {
|
||||
&self.constant
|
||||
}
|
||||
|
||||
fn roots(&self) -> &Rc<<Self::Target as CritTarget>::RootMatchers> {
|
||||
&self.matchers
|
||||
}
|
||||
}
|
||||
24
src/criteria/clm/clm_matchers.rs
Normal file
24
src/criteria/clm/clm_matchers.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
macro_rules! fixed_root_criterion {
|
||||
($ty:ty, $field:ident) => {
|
||||
impl crate::criteria::crit_graph::CritFixedRootCriterionBase<Rc<crate::client::Client>>
|
||||
for $ty
|
||||
{
|
||||
fn constant(&self) -> bool {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn not<'a>(
|
||||
&self,
|
||||
mgr: &'a crate::criteria::clm::ClMatcherManager,
|
||||
) -> &'a crate::criteria::FixedRootMatcher<Rc<crate::client::Client>, Self> {
|
||||
&mgr.$field
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub mod clmm_is_xwayland;
|
||||
pub mod clmm_pid;
|
||||
pub mod clmm_sandboxed;
|
||||
pub mod clmm_string;
|
||||
pub mod clmm_uid;
|
||||
14
src/criteria/clm/clm_matchers/clmm_is_xwayland.rs
Normal file
14
src/criteria/clm/clm_matchers/clmm_is_xwayland.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
use {
|
||||
crate::{client::Client, criteria::crit_graph::CritFixedRootCriterion},
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
||||
pub struct ClmMatchIsXwayland(pub bool);
|
||||
|
||||
fixed_root_criterion!(ClmMatchIsXwayland, is_xwayland);
|
||||
|
||||
impl CritFixedRootCriterion<Rc<Client>> for ClmMatchIsXwayland {
|
||||
fn matches(&self, data: &Rc<Client>) -> bool {
|
||||
data.is_xwayland
|
||||
}
|
||||
}
|
||||
20
src/criteria/clm/clm_matchers/clmm_pid.rs
Normal file
20
src/criteria/clm/clm_matchers/clmm_pid.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use {
|
||||
crate::{
|
||||
client::Client,
|
||||
criteria::{RootMatcherMap, clm::RootMatchers, crit_graph::CritRootCriterion},
|
||||
},
|
||||
std::rc::Rc,
|
||||
uapi::c,
|
||||
};
|
||||
|
||||
pub struct ClmMatchPid(pub c::pid_t);
|
||||
|
||||
impl CritRootCriterion<Rc<Client>> for ClmMatchPid {
|
||||
fn matches(&self, data: &Rc<Client>) -> bool {
|
||||
data.pid_info.pid == self.0
|
||||
}
|
||||
|
||||
fn nodes(roots: &RootMatchers) -> Option<&RootMatcherMap<Rc<Client>, Self>> {
|
||||
Some(&roots.pid)
|
||||
}
|
||||
}
|
||||
14
src/criteria/clm/clm_matchers/clmm_sandboxed.rs
Normal file
14
src/criteria/clm/clm_matchers/clmm_sandboxed.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
use {
|
||||
crate::{client::Client, criteria::crit_graph::CritFixedRootCriterion},
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
||||
pub struct ClmMatchSandboxed(pub bool);
|
||||
|
||||
fixed_root_criterion!(ClmMatchSandboxed, sandboxed);
|
||||
|
||||
impl CritFixedRootCriterion<Rc<Client>> for ClmMatchSandboxed {
|
||||
fn matches(&self, data: &Rc<Client>) -> bool {
|
||||
data.acceptor.sandboxed
|
||||
}
|
||||
}
|
||||
103
src/criteria/clm/clm_matchers/clmm_string.rs
Normal file
103
src/criteria/clm/clm_matchers/clmm_string.rs
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
use {
|
||||
crate::{
|
||||
client::Client,
|
||||
criteria::{
|
||||
clm::{ClmRootMatcherMap, RootMatchers},
|
||||
crit_matchers::critm_string::{CritMatchString, StringAccess},
|
||||
},
|
||||
security_context_acceptor::AcceptorMetadata,
|
||||
},
|
||||
std::{marker::PhantomData, rc::Rc},
|
||||
};
|
||||
|
||||
pub type ClmMatchString<T> = CritMatchString<Rc<Client>, T>;
|
||||
|
||||
pub type ClmMatchSandboxEngine = ClmMatchString<AcceptorMetadataAccess<SandboxEngineField>>;
|
||||
pub type ClmMatchSandboxAppId = ClmMatchString<AcceptorMetadataAccess<SandboxAppIdField>>;
|
||||
pub type ClmMatchSandboxInstanceId = ClmMatchString<AcceptorMetadataAccess<SandboxInstanceIdField>>;
|
||||
pub type ClmMatchComm = ClmMatchString<CommAccess>;
|
||||
pub type ClmMatchExe = ClmMatchString<ExeAccess>;
|
||||
|
||||
pub struct AcceptorMetadataAccess<T>(PhantomData<T>);
|
||||
pub struct CommAccess;
|
||||
pub struct ExeAccess;
|
||||
|
||||
trait SandboxField: Sized + 'static {
|
||||
fn field(meta: &AcceptorMetadata) -> &Option<String>;
|
||||
fn nodes(
|
||||
roots: &RootMatchers,
|
||||
) -> &ClmRootMatcherMap<ClmMatchString<AcceptorMetadataAccess<Self>>>;
|
||||
}
|
||||
|
||||
pub struct SandboxEngineField;
|
||||
pub struct SandboxAppIdField;
|
||||
pub struct SandboxInstanceIdField;
|
||||
|
||||
impl<T> StringAccess<Rc<Client>> for AcceptorMetadataAccess<T>
|
||||
where
|
||||
T: SandboxField,
|
||||
{
|
||||
fn with_string(data: &Rc<Client>, f: impl FnOnce(&str) -> bool) -> bool {
|
||||
f(T::field(&data.acceptor).as_deref().unwrap_or_default())
|
||||
}
|
||||
|
||||
fn nodes(roots: &RootMatchers) -> &ClmRootMatcherMap<ClmMatchString<Self>> {
|
||||
T::nodes(roots)
|
||||
}
|
||||
}
|
||||
|
||||
impl SandboxField for SandboxEngineField {
|
||||
fn field(meta: &AcceptorMetadata) -> &Option<String> {
|
||||
&meta.sandbox_engine
|
||||
}
|
||||
|
||||
fn nodes(
|
||||
roots: &RootMatchers,
|
||||
) -> &ClmRootMatcherMap<ClmMatchString<AcceptorMetadataAccess<Self>>> {
|
||||
&roots.sandbox_engine
|
||||
}
|
||||
}
|
||||
|
||||
impl SandboxField for SandboxAppIdField {
|
||||
fn field(meta: &AcceptorMetadata) -> &Option<String> {
|
||||
&meta.app_id
|
||||
}
|
||||
|
||||
fn nodes(
|
||||
roots: &RootMatchers,
|
||||
) -> &ClmRootMatcherMap<ClmMatchString<AcceptorMetadataAccess<Self>>> {
|
||||
&roots.sandbox_app_id
|
||||
}
|
||||
}
|
||||
|
||||
impl SandboxField for SandboxInstanceIdField {
|
||||
fn field(meta: &AcceptorMetadata) -> &Option<String> {
|
||||
&meta.instance_id
|
||||
}
|
||||
|
||||
fn nodes(
|
||||
roots: &RootMatchers,
|
||||
) -> &ClmRootMatcherMap<ClmMatchString<AcceptorMetadataAccess<Self>>> {
|
||||
&roots.sandbox_instance_id
|
||||
}
|
||||
}
|
||||
|
||||
impl StringAccess<Rc<Client>> for CommAccess {
|
||||
fn with_string(data: &Rc<Client>, f: impl FnOnce(&str) -> bool) -> bool {
|
||||
f(&data.pid_info.comm)
|
||||
}
|
||||
|
||||
fn nodes(roots: &RootMatchers) -> &ClmRootMatcherMap<ClmMatchString<Self>> {
|
||||
&roots.comm
|
||||
}
|
||||
}
|
||||
|
||||
impl StringAccess<Rc<Client>> for ExeAccess {
|
||||
fn with_string(data: &Rc<Client>, f: impl FnOnce(&str) -> bool) -> bool {
|
||||
f(&data.pid_info.exe)
|
||||
}
|
||||
|
||||
fn nodes(roots: &RootMatchers) -> &ClmRootMatcherMap<ClmMatchString<Self>> {
|
||||
&roots.exe
|
||||
}
|
||||
}
|
||||
20
src/criteria/clm/clm_matchers/clmm_uid.rs
Normal file
20
src/criteria/clm/clm_matchers/clmm_uid.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use {
|
||||
crate::{
|
||||
client::Client,
|
||||
criteria::{RootMatcherMap, clm::RootMatchers, crit_graph::CritRootCriterion},
|
||||
},
|
||||
std::rc::Rc,
|
||||
uapi::c,
|
||||
};
|
||||
|
||||
pub struct ClmMatchUid(pub c::uid_t);
|
||||
|
||||
impl CritRootCriterion<Rc<Client>> for ClmMatchUid {
|
||||
fn matches(&self, data: &Rc<Client>) -> bool {
|
||||
data.pid_info.uid == self.0
|
||||
}
|
||||
|
||||
fn nodes(roots: &RootMatchers) -> Option<&RootMatcherMap<Rc<Client>, Self>> {
|
||||
Some(&roots.uid)
|
||||
}
|
||||
}
|
||||
18
src/criteria/crit_graph.rs
Normal file
18
src/criteria/crit_graph.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
mod crit_downstream;
|
||||
mod crit_middle;
|
||||
mod crit_root;
|
||||
mod crit_target;
|
||||
mod crit_upstream;
|
||||
|
||||
pub use {
|
||||
crit_downstream::{CritDownstream, CritDownstreamData},
|
||||
crit_middle::{CritMiddle, CritMiddleCriterion},
|
||||
crit_root::{
|
||||
CritFixedRootCriterion, CritFixedRootCriterionBase, CritRoot, CritRootCriterion,
|
||||
CritRootFixed,
|
||||
},
|
||||
crit_target::{CritMgr, CritTarget, CritTargetOwner, WeakCritTargetOwner},
|
||||
crit_upstream::{
|
||||
CritUpstreamData, CritUpstreamNode, CritUpstreamNodeBase, CritUpstreamNodeData,
|
||||
},
|
||||
};
|
||||
52
src/criteria/crit_graph/crit_downstream.rs
Normal file
52
src/criteria/crit_graph/crit_downstream.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
use {
|
||||
crate::criteria::{
|
||||
CritMatcherId,
|
||||
crit_graph::{CritTarget, crit_upstream::CritUpstreamNode},
|
||||
},
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
||||
pub struct CritDownstreamData<Target>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
id: CritMatcherId,
|
||||
pub(super) upstream: Vec<Rc<dyn CritUpstreamNode<Target>>>,
|
||||
}
|
||||
|
||||
pub trait CritDownstream<Target>: 'static {
|
||||
fn update_matched(self: Rc<Self>, target: &Target, matched: bool);
|
||||
}
|
||||
|
||||
impl<Target> CritDownstreamData<Target>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
pub fn new(id: CritMatcherId, upstream: &[Rc<dyn CritUpstreamNode<Target>>]) -> Self {
|
||||
Self {
|
||||
id,
|
||||
upstream: upstream.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attach(&self, slf: &Rc<impl CritDownstream<Target>>) {
|
||||
for upstream in &self.upstream {
|
||||
upstream.attach(self.id, slf.clone() as _);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn not(&self, mgr: &Target::Mgr) -> Vec<Rc<dyn CritUpstreamNode<Target>>> {
|
||||
self.upstream.iter().map(|n| n.not(mgr)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target> Drop for CritDownstreamData<Target>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
for el in &self.upstream {
|
||||
el.detach(self.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
111
src/criteria/crit_graph/crit_middle.rs
Normal file
111
src/criteria/crit_graph/crit_middle.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
use {
|
||||
crate::criteria::{
|
||||
CritUpstreamNode,
|
||||
crit_graph::{
|
||||
CritDownstream, CritDownstreamData, CritTarget, CritUpstreamData,
|
||||
crit_target::CritMgr,
|
||||
crit_upstream::{CritUpstreamNodeBase, CritUpstreamNodeData},
|
||||
},
|
||||
crit_per_target_data::{CritDestroyListenerBase, CritPerTargetData},
|
||||
},
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
||||
pub struct CritMiddle<Target, T>
|
||||
where
|
||||
Target: CritTarget,
|
||||
T: CritMiddleCriterion<Target>,
|
||||
{
|
||||
upstream: CritDownstreamData<Target>,
|
||||
downstream: CritUpstreamData<Target, CritMiddleData<T::Data>>,
|
||||
criterion: T,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CritMiddleData<T> {
|
||||
matches: usize,
|
||||
data: T,
|
||||
}
|
||||
|
||||
pub trait CritMiddleCriterion<Target>: Sized + 'static {
|
||||
type Data: Default;
|
||||
type Not: CritMiddleCriterion<Target>;
|
||||
|
||||
fn update_matched(&self, target: &Target, node: &mut Self::Data, matched: bool) -> bool;
|
||||
fn pull(&self, upstream: &[Rc<dyn CritUpstreamNode<Target>>], target: &Target) -> bool;
|
||||
fn not(&self) -> Self::Not;
|
||||
}
|
||||
|
||||
impl<Target, T> CritMiddle<Target, T>
|
||||
where
|
||||
Target: CritTarget,
|
||||
T: CritMiddleCriterion<Target>,
|
||||
{
|
||||
pub fn new(
|
||||
mgr: &Target::Mgr,
|
||||
upstream: &[Rc<dyn CritUpstreamNode<Target>>],
|
||||
criterion: T,
|
||||
) -> Rc<Self> {
|
||||
let id = mgr.id();
|
||||
let slf = Rc::new_cyclic(|slf| Self {
|
||||
upstream: CritDownstreamData::new(id, upstream),
|
||||
downstream: CritUpstreamData::new(slf, id),
|
||||
criterion,
|
||||
});
|
||||
slf.upstream.attach(&slf);
|
||||
slf
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target, T> CritDownstream<Target> for CritMiddle<Target, T>
|
||||
where
|
||||
Target: CritTarget,
|
||||
T: CritMiddleCriterion<Target>,
|
||||
{
|
||||
fn update_matched(self: Rc<Self>, target: &Target, matched: bool) {
|
||||
let mut node = self.downstream.get_or_create(target);
|
||||
let change = self
|
||||
.criterion
|
||||
.update_matched(target, &mut node.data, matched);
|
||||
let matches = match matched {
|
||||
true => node.matches + 1,
|
||||
false => node.matches - 1,
|
||||
};
|
||||
node.matches = matches;
|
||||
self.downstream
|
||||
.update_matched(target, node, change, matches == 0);
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target, T> CritUpstreamNodeBase<Target> for CritMiddle<Target, T>
|
||||
where
|
||||
Target: CritTarget,
|
||||
T: CritMiddleCriterion<Target>,
|
||||
{
|
||||
type Data = CritMiddleData<T::Data>;
|
||||
|
||||
fn data(&self) -> &CritUpstreamData<Target, Self::Data> {
|
||||
&self.downstream
|
||||
}
|
||||
|
||||
fn not(&self, mgr: &Target::Mgr) -> Rc<dyn CritUpstreamNode<Target>> {
|
||||
let upstream = self.upstream.not(mgr);
|
||||
CritMiddle::new(mgr, &upstream, self.criterion.not())
|
||||
}
|
||||
|
||||
fn pull(&self, target: &Target) -> bool {
|
||||
self.criterion.pull(&self.upstream.upstream, target)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target, T> CritDestroyListenerBase<Target> for CritMiddle<Target, T>
|
||||
where
|
||||
Target: CritTarget,
|
||||
T: CritMiddleCriterion<Target>,
|
||||
{
|
||||
type Data = CritUpstreamNodeData<Target, CritMiddleData<T::Data>>;
|
||||
|
||||
fn data(&self) -> &CritPerTargetData<Target, Self::Data> {
|
||||
&self.downstream.nodes
|
||||
}
|
||||
}
|
||||
171
src/criteria/crit_graph/crit_root.rs
Normal file
171
src/criteria/crit_graph/crit_root.rs
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
use {
|
||||
crate::criteria::{
|
||||
CritMatcherId, CritUpstreamNode, FixedRootMatcher, RootMatcherMap,
|
||||
crit_graph::{
|
||||
CritTarget, CritUpstreamData,
|
||||
crit_target::CritMgr,
|
||||
crit_upstream::{CritUpstreamNodeBase, CritUpstreamNodeData},
|
||||
},
|
||||
crit_per_target_data::{CritDestroyListenerBase, CritPerTargetData},
|
||||
},
|
||||
std::{marker::PhantomData, rc::Rc},
|
||||
};
|
||||
|
||||
pub struct CritRoot<Target, T>
|
||||
where
|
||||
Target: CritTarget,
|
||||
T: CritRootCriterion<Target>,
|
||||
{
|
||||
id: CritMatcherId,
|
||||
downstream: CritUpstreamData<Target, ()>,
|
||||
not: bool,
|
||||
criterion: Rc<T>,
|
||||
roots: Rc<Target::RootMatchers>,
|
||||
}
|
||||
|
||||
pub struct CritRootFixed<Target, Crit>(pub Crit, pub PhantomData<fn(&Target)>);
|
||||
|
||||
pub trait CritRootCriterion<Target>: Sized + 'static
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
fn matches(&self, data: &Target) -> bool;
|
||||
fn nodes(roots: &Target::RootMatchers) -> Option<&RootMatcherMap<Target, Self>> {
|
||||
let _ = roots;
|
||||
None
|
||||
}
|
||||
fn not(&self, mgr: &Target::Mgr) -> Option<Rc<dyn CritUpstreamNode<Target>>> {
|
||||
let _ = mgr;
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CritFixedRootCriterionBase<Target>: Sized + 'static
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
fn constant(&self) -> bool;
|
||||
fn not<'a>(&self, mgr: &'a Target::Mgr) -> &'a FixedRootMatcher<Target, Self>
|
||||
where
|
||||
Self: CritFixedRootCriterion<Target>;
|
||||
}
|
||||
|
||||
pub trait CritFixedRootCriterion<Target>: CritFixedRootCriterionBase<Target>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
const COMPARE: bool = true;
|
||||
|
||||
fn matches(&self, data: &Target) -> bool;
|
||||
}
|
||||
|
||||
impl<Target, T> CritRootCriterion<Target> for CritRootFixed<Target, T>
|
||||
where
|
||||
Target: CritTarget,
|
||||
T: CritFixedRootCriterion<Target>,
|
||||
{
|
||||
fn matches(&self, data: &Target) -> bool {
|
||||
let mut res = self.0.matches(data);
|
||||
if T::COMPARE {
|
||||
res = res == self.0.constant();
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn not(&self, mgr: &Target::Mgr) -> Option<Rc<dyn CritUpstreamNode<Target>>> {
|
||||
Some(self.0.not(mgr)[!self.0.constant()].clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target, T> CritUpstreamNodeBase<Target> for CritRoot<Target, T>
|
||||
where
|
||||
Target: CritTarget,
|
||||
T: CritRootCriterion<Target>,
|
||||
{
|
||||
type Data = ();
|
||||
|
||||
fn data(&self) -> &CritUpstreamData<Target, Self::Data> {
|
||||
&self.downstream
|
||||
}
|
||||
|
||||
fn not(&self, mgr: &Target::Mgr) -> Rc<dyn CritUpstreamNode<Target>> {
|
||||
if let Some(node) = self.criterion.not(mgr) {
|
||||
return node;
|
||||
}
|
||||
let id = mgr.id();
|
||||
Self::new_(&self.roots, id, self.criterion.clone(), !self.not)
|
||||
}
|
||||
|
||||
fn pull(&self, target: &Target) -> bool {
|
||||
self.criterion.matches(target) ^ self.not
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target, T> CritRoot<Target, T>
|
||||
where
|
||||
Target: CritTarget,
|
||||
T: CritRootCriterion<Target>,
|
||||
{
|
||||
pub fn new(roots: &Rc<Target::RootMatchers>, id: CritMatcherId, criterion: T) -> Rc<Self> {
|
||||
Self::new_(roots, id, Rc::new(criterion), false)
|
||||
}
|
||||
|
||||
fn new_(
|
||||
roots: &Rc<Target::RootMatchers>,
|
||||
id: CritMatcherId,
|
||||
criterion: Rc<T>,
|
||||
not: bool,
|
||||
) -> Rc<Self> {
|
||||
let slf = Rc::new_cyclic(|slf| Self {
|
||||
id,
|
||||
downstream: CritUpstreamData::new(slf, id),
|
||||
not,
|
||||
criterion,
|
||||
roots: roots.clone(),
|
||||
});
|
||||
if let Some(nodes) = T::nodes(roots) {
|
||||
nodes.set(id, Rc::downgrade(&slf));
|
||||
}
|
||||
slf
|
||||
}
|
||||
|
||||
pub fn handle(&self, target: &Target) {
|
||||
let new = self.criterion.matches(target) ^ self.not;
|
||||
let node = match new {
|
||||
true => self.downstream.get_or_create(target),
|
||||
false => match self.downstream.get(target) {
|
||||
Some(n) => n,
|
||||
None => return,
|
||||
},
|
||||
};
|
||||
self.downstream.update_matched(target, node, new, !new);
|
||||
}
|
||||
|
||||
pub fn has_downstream(&self) -> bool {
|
||||
self.downstream.has_downstream()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target, T> CritDestroyListenerBase<Target> for CritRoot<Target, T>
|
||||
where
|
||||
Target: CritTarget,
|
||||
T: CritRootCriterion<Target>,
|
||||
{
|
||||
type Data = CritUpstreamNodeData<Target, ()>;
|
||||
|
||||
fn data(&self) -> &CritPerTargetData<Target, Self::Data> {
|
||||
&self.downstream.nodes
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target, T> Drop for CritRoot<Target, T>
|
||||
where
|
||||
Target: CritTarget,
|
||||
T: CritRootCriterion<Target>,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
if let Some(nodes) = T::nodes(&self.roots) {
|
||||
nodes.remove(&self.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
48
src/criteria/crit_graph/crit_target.rs
Normal file
48
src/criteria/crit_graph/crit_target.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
use {
|
||||
crate::{
|
||||
criteria::{
|
||||
CritDestroyListener, CritMatcherId, FixedRootMatcher, crit_leaf::CritLeafEvent,
|
||||
crit_matchers::critm_constant::CritMatchConstant,
|
||||
},
|
||||
utils::{copyhashmap::CopyHashMap, queue::AsyncQueue},
|
||||
},
|
||||
std::{
|
||||
hash::Hash,
|
||||
rc::{Rc, Weak},
|
||||
},
|
||||
};
|
||||
|
||||
pub trait CritMgr: 'static {
|
||||
type Target: CritTarget<Mgr = Self>;
|
||||
|
||||
fn id(&self) -> CritMatcherId;
|
||||
fn leaf_events(&self) -> &Rc<AsyncQueue<CritLeafEvent<Self::Target>>>;
|
||||
fn match_constant(&self) -> &FixedRootMatcher<Self::Target, CritMatchConstant<Self::Target>>;
|
||||
fn roots(&self) -> &Rc<<Self::Target as CritTarget>::RootMatchers>;
|
||||
}
|
||||
|
||||
pub trait CritTarget: 'static {
|
||||
type Id: Copy + Hash + Eq;
|
||||
type Mgr: CritMgr<Target = Self>;
|
||||
type RootMatchers;
|
||||
type LeafData: Copy + Eq;
|
||||
type Owner: WeakCritTargetOwner<Target = Self>;
|
||||
|
||||
fn owner(&self) -> Self::Owner;
|
||||
fn id(&self) -> Self::Id;
|
||||
fn destroyed(&self) -> &CopyHashMap<CritMatcherId, Weak<dyn CritDestroyListener<Self>>>;
|
||||
fn leaf_data(&self) -> Self::LeafData;
|
||||
}
|
||||
|
||||
pub trait CritTargetOwner: 'static {
|
||||
type Target: CritTarget;
|
||||
|
||||
fn data(&self) -> &Self::Target;
|
||||
}
|
||||
|
||||
pub trait WeakCritTargetOwner: 'static {
|
||||
type Target: CritTarget;
|
||||
type Owner: CritTargetOwner<Target = Self::Target>;
|
||||
|
||||
fn upgrade(&self) -> Option<Self::Owner>;
|
||||
}
|
||||
175
src/criteria/crit_graph/crit_upstream.rs
Normal file
175
src/criteria/crit_graph/crit_upstream.rs
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
use {
|
||||
crate::{
|
||||
criteria::{
|
||||
CritDestroyListener, CritMatcherId,
|
||||
crit_graph::{
|
||||
WeakCritTargetOwner,
|
||||
crit_downstream::CritDownstream,
|
||||
crit_target::{CritTarget, CritTargetOwner},
|
||||
},
|
||||
crit_per_target_data::CritPerTargetData,
|
||||
},
|
||||
utils::copyhashmap::CopyHashMap,
|
||||
},
|
||||
std::{
|
||||
cell::RefMut,
|
||||
mem,
|
||||
ops::{Deref, DerefMut},
|
||||
rc::{Rc, Weak},
|
||||
},
|
||||
};
|
||||
|
||||
pub struct CritUpstreamData<Target, T>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
downstream: CopyHashMap<CritMatcherId, Weak<dyn CritDownstream<Target>>>,
|
||||
pub nodes: CritPerTargetData<Target, CritUpstreamNodeData<Target, T>>,
|
||||
}
|
||||
|
||||
pub struct CritUpstreamNodeData<Target, T>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
matched: bool,
|
||||
tl: Target::Owner,
|
||||
data: T,
|
||||
}
|
||||
|
||||
pub trait CritUpstreamNodeBase<Target>: 'static
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
type Data;
|
||||
|
||||
fn data(&self) -> &CritUpstreamData<Target, Self::Data>;
|
||||
fn not(&self, mgr: &Target::Mgr) -> Rc<dyn CritUpstreamNode<Target>>;
|
||||
fn pull(&self, target: &Target) -> bool;
|
||||
}
|
||||
|
||||
pub trait CritUpstreamNode<Target>: 'static
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
fn attach(&self, id: CritMatcherId, downstream: Rc<dyn CritDownstream<Target>>);
|
||||
fn detach(&self, id: CritMatcherId);
|
||||
fn not(&self, mgr: &Target::Mgr) -> Rc<dyn CritUpstreamNode<Target>>;
|
||||
fn pull(&self, target: &Target) -> bool;
|
||||
fn get(&self, target: &Target) -> bool;
|
||||
}
|
||||
|
||||
impl<Target, T> CritUpstreamNode<Target> for T
|
||||
where
|
||||
Target: CritTarget,
|
||||
T: CritUpstreamNodeBase<Target>,
|
||||
{
|
||||
fn attach(&self, id: CritMatcherId, downstream: Rc<dyn CritDownstream<Target>>) {
|
||||
let data = self.data();
|
||||
for n in data.nodes.borrow_mut().values_mut() {
|
||||
if !n.matched {
|
||||
continue;
|
||||
}
|
||||
let Some(target) = n.tl.upgrade() else {
|
||||
continue;
|
||||
};
|
||||
downstream.clone().update_matched(target.data(), true);
|
||||
}
|
||||
data.downstream.set(id, Rc::downgrade(&downstream));
|
||||
}
|
||||
|
||||
fn detach(&self, id: CritMatcherId) {
|
||||
self.data().downstream.remove(&id);
|
||||
}
|
||||
|
||||
fn not(&self, mgr: &Target::Mgr) -> Rc<dyn CritUpstreamNode<Target>> {
|
||||
<T as CritUpstreamNodeBase<Target>>::not(self, mgr)
|
||||
}
|
||||
|
||||
fn pull(&self, target: &Target) -> bool {
|
||||
<T as CritUpstreamNodeBase<Target>>::pull(self, target)
|
||||
}
|
||||
|
||||
fn get(&self, target: &Target) -> bool {
|
||||
<T as CritUpstreamNodeBase<Target>>::data(self).matched(target)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target, T> Deref for CritUpstreamNodeData<Target, T>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target, T> DerefMut for CritUpstreamNodeData<Target, T>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target, T> CritUpstreamData<Target, T>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
pub fn new(slf: &Weak<impl CritDestroyListener<Target>>, id: CritMatcherId) -> Self {
|
||||
Self {
|
||||
downstream: Default::default(),
|
||||
nodes: CritPerTargetData::new(slf, id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_matched(
|
||||
&self,
|
||||
target: &Target,
|
||||
mut node: RefMut<CritUpstreamNodeData<Target, T>>,
|
||||
matched: bool,
|
||||
remove: bool,
|
||||
) {
|
||||
let unchanged = mem::replace(&mut node.matched, matched) == matched;
|
||||
drop(node);
|
||||
if remove {
|
||||
self.nodes.remove(target.id());
|
||||
}
|
||||
if unchanged {
|
||||
return;
|
||||
}
|
||||
for el in self.downstream.lock().values() {
|
||||
if let Some(el) = el.upgrade() {
|
||||
el.update_matched(target, matched);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_or_create(&self, target: &Target) -> RefMut<CritUpstreamNodeData<Target, T>>
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
self.nodes.get_or_create(target, || CritUpstreamNodeData {
|
||||
matched: false,
|
||||
tl: target.owner(),
|
||||
data: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get(&self, target: &Target) -> Option<RefMut<CritUpstreamNodeData<Target, T>>> {
|
||||
self.nodes.get(target)
|
||||
}
|
||||
|
||||
pub fn has_downstream(&self) -> bool {
|
||||
self.downstream.is_not_empty()
|
||||
}
|
||||
|
||||
pub fn matched(&self, target: &Target) -> bool {
|
||||
let Some(node) = self.nodes.get(target) else {
|
||||
return false;
|
||||
};
|
||||
node.matched
|
||||
}
|
||||
}
|
||||
155
src/criteria/crit_leaf.rs
Normal file
155
src/criteria/crit_leaf.rs
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
use {
|
||||
crate::{
|
||||
criteria::{
|
||||
CritUpstreamNode,
|
||||
crit_graph::{CritDownstream, CritDownstreamData, CritMgr, CritTarget},
|
||||
crit_per_target_data::{CritDestroyListenerBase, CritPerTargetData},
|
||||
},
|
||||
utils::{cell_ext::CellExt, queue::AsyncQueue},
|
||||
},
|
||||
std::{
|
||||
cell::Cell,
|
||||
rc::{Rc, Weak},
|
||||
},
|
||||
};
|
||||
|
||||
pub struct CritLeafMatcher<Target>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
upstream: CritDownstreamData<Target>,
|
||||
on_match: Box<dyn Fn(Target::LeafData) -> Box<dyn FnOnce()>>,
|
||||
targets: CritPerTargetData<Target, NodeHolder<Target>>,
|
||||
events: Rc<AsyncQueue<CritLeafEvent<Target>>>,
|
||||
}
|
||||
|
||||
pub(in crate::criteria) struct NodeHolder<Target>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
node: Rc<Node<Target>>,
|
||||
}
|
||||
|
||||
struct Node<Target>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
leaf: Weak<CritLeafMatcher<Target>>,
|
||||
target_id: Target::Id,
|
||||
needs_event: Cell<bool>,
|
||||
new_data: Cell<Option<Target::LeafData>>,
|
||||
data: Cell<Option<Target::LeafData>>,
|
||||
on_unmatch: Cell<Option<Box<dyn FnOnce()>>>,
|
||||
}
|
||||
|
||||
pub struct CritLeafEvent<Target>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
node: Rc<Node<Target>>,
|
||||
}
|
||||
|
||||
impl<Target> CritDownstream<Target> for CritLeafMatcher<Target>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
fn update_matched(self: Rc<Self>, data: &Target, matched: bool) {
|
||||
let node = &self
|
||||
.targets
|
||||
.get_or_create(data, || {
|
||||
let node = Rc::new(Node {
|
||||
leaf: Rc::downgrade(&self),
|
||||
target_id: data.id(),
|
||||
needs_event: Cell::new(true),
|
||||
new_data: Cell::new(None),
|
||||
data: Cell::new(None),
|
||||
on_unmatch: Cell::new(None),
|
||||
});
|
||||
NodeHolder { node: node.clone() }
|
||||
})
|
||||
.node;
|
||||
self.push_event(node, matched.then_some(data.leaf_data()));
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target> CritLeafMatcher<Target>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
pub(in crate::criteria) fn new(
|
||||
mgr: &Target::Mgr,
|
||||
upstream: &Rc<dyn CritUpstreamNode<Target>>,
|
||||
on_match: impl Fn(Target::LeafData) -> Box<dyn FnOnce()> + 'static,
|
||||
) -> Rc<Self> {
|
||||
let id = mgr.id();
|
||||
let slf = Rc::new_cyclic(|slf| Self {
|
||||
targets: CritPerTargetData::new(slf, id),
|
||||
on_match: Box::new(on_match),
|
||||
events: mgr.leaf_events().clone(),
|
||||
upstream: CritDownstreamData::new(id, &[upstream.clone()]),
|
||||
});
|
||||
slf.upstream.attach(&slf);
|
||||
slf
|
||||
}
|
||||
|
||||
fn push_event(&self, node: &Rc<Node<Target>>, new_data: Option<Target::LeafData>) {
|
||||
node.new_data.set(new_data);
|
||||
if node.needs_event.replace(false) {
|
||||
self.events.push(CritLeafEvent { node: node.clone() });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target> CritLeafEvent<Target>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
pub fn run(self) {
|
||||
let n = self.node;
|
||||
n.needs_event.set(true);
|
||||
if n.new_data != n.data {
|
||||
if let Some(on_unmatch) = n.on_unmatch.take() {
|
||||
if n.leaf.strong_count() == 0 {
|
||||
return;
|
||||
}
|
||||
on_unmatch();
|
||||
}
|
||||
}
|
||||
n.data.set(n.new_data.get());
|
||||
if n.data.is_some() != n.on_unmatch.is_some() {
|
||||
let Some(leaf) = n.leaf.upgrade() else {
|
||||
return;
|
||||
};
|
||||
if let Some(id) = n.data.get() {
|
||||
n.on_unmatch.set(Some((leaf.on_match)(id)));
|
||||
} else {
|
||||
if let Some(on_unmatch) = n.on_unmatch.take() {
|
||||
on_unmatch();
|
||||
}
|
||||
leaf.targets.remove(n.target_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target> Drop for NodeHolder<Target>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
if let Some(leaf) = self.node.leaf.upgrade() {
|
||||
leaf.push_event(&self.node, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target> CritDestroyListenerBase<Target> for CritLeafMatcher<Target>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
type Data = NodeHolder<Target>;
|
||||
|
||||
fn data(&self) -> &CritPerTargetData<Target, Self::Data> {
|
||||
&self.targets
|
||||
}
|
||||
}
|
||||
4
src/criteria/crit_matchers.rs
Normal file
4
src/criteria/crit_matchers.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
pub mod critm_any_or_all;
|
||||
pub mod critm_constant;
|
||||
pub mod critm_exactly;
|
||||
pub mod critm_string;
|
||||
73
src/criteria/crit_matchers/critm_any_or_all.rs
Normal file
73
src/criteria/crit_matchers/critm_any_or_all.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
use {
|
||||
crate::criteria::crit_graph::{CritMiddleCriterion, CritTarget, CritUpstreamNode},
|
||||
std::{marker::PhantomData, rc::Rc},
|
||||
};
|
||||
|
||||
pub struct CritMatchAnyOrAll<Target>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
all: bool,
|
||||
total: usize,
|
||||
_phantom: PhantomData<fn(&Target)>,
|
||||
}
|
||||
|
||||
impl<Target> CritMatchAnyOrAll<Target>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
pub fn new(upstream: &[Rc<dyn CritUpstreamNode<Target>>], all: bool) -> Self {
|
||||
Self {
|
||||
all,
|
||||
total: upstream.len(),
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target> CritMiddleCriterion<Target> for CritMatchAnyOrAll<Target>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
type Data = usize;
|
||||
type Not = Self;
|
||||
|
||||
fn update_matched(&self, _data: &Target, node: &mut usize, matched: bool) -> bool {
|
||||
if matched {
|
||||
*node += 1;
|
||||
} else {
|
||||
*node -= 1;
|
||||
}
|
||||
if self.all {
|
||||
*node == self.total
|
||||
} else {
|
||||
*node > 0
|
||||
}
|
||||
}
|
||||
|
||||
fn pull(&self, upstream: &[Rc<dyn CritUpstreamNode<Target>>], node: &Target) -> bool {
|
||||
for upstream in upstream {
|
||||
if upstream.pull(node) {
|
||||
if !self.all {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if self.all {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.all
|
||||
}
|
||||
|
||||
fn not(&self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Self {
|
||||
all: !self.all,
|
||||
total: self.total,
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
58
src/criteria/crit_matchers/critm_constant.rs
Normal file
58
src/criteria/crit_matchers/critm_constant.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use {
|
||||
crate::criteria::{
|
||||
CritMatcherIds, FixedRootMatcher,
|
||||
crit_graph::{
|
||||
CritFixedRootCriterion, CritFixedRootCriterionBase, CritMgr, CritRoot, CritRootFixed,
|
||||
CritTarget,
|
||||
},
|
||||
},
|
||||
linearize::static_map,
|
||||
std::{marker::PhantomData, rc::Rc},
|
||||
};
|
||||
|
||||
pub struct CritMatchConstant<Target>(pub bool, pub PhantomData<fn(&Target)>);
|
||||
|
||||
impl<Target> CritMatchConstant<Target>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
pub fn create(
|
||||
roots: &Rc<Target::RootMatchers>,
|
||||
ids: &CritMatcherIds,
|
||||
) -> FixedRootMatcher<Target, CritMatchConstant<Target>> {
|
||||
static_map! {
|
||||
v => CritRoot::new(
|
||||
roots,
|
||||
ids.next(),
|
||||
CritRootFixed(Self(v, PhantomData), PhantomData),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target> CritFixedRootCriterionBase<Target> for CritMatchConstant<Target>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
fn constant(&self) -> bool {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn not<'a>(&self, mgr: &'a Target::Mgr) -> &'a FixedRootMatcher<Target, Self>
|
||||
where
|
||||
Self: CritFixedRootCriterion<Target>,
|
||||
{
|
||||
mgr.match_constant()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target> CritFixedRootCriterion<Target> for CritMatchConstant<Target>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
const COMPARE: bool = false;
|
||||
|
||||
fn matches(&self, _data: &Target) -> bool {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
61
src/criteria/crit_matchers/critm_exactly.rs
Normal file
61
src/criteria/crit_matchers/critm_exactly.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
use {
|
||||
crate::criteria::crit_graph::{CritMiddleCriterion, CritTarget, CritUpstreamNode},
|
||||
std::{marker::PhantomData, rc::Rc},
|
||||
};
|
||||
|
||||
pub struct CritMatchExactly<Target> {
|
||||
total: usize,
|
||||
num: usize,
|
||||
not: bool,
|
||||
_phantom: PhantomData<fn(&Target)>,
|
||||
}
|
||||
|
||||
impl<Target> CritMatchExactly<Target> {
|
||||
pub fn new(upstream: &[Rc<dyn CritUpstreamNode<Target>>], num: usize) -> Self {
|
||||
Self {
|
||||
total: upstream.len(),
|
||||
num,
|
||||
not: false,
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target> CritMiddleCriterion<Target> for CritMatchExactly<Target>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
type Data = usize;
|
||||
type Not = Self;
|
||||
|
||||
fn update_matched(&self, _data: &Target, node: &mut usize, matched: bool) -> bool {
|
||||
if matched {
|
||||
*node += 1;
|
||||
} else {
|
||||
*node -= 1;
|
||||
}
|
||||
(*node == self.num) ^ self.not
|
||||
}
|
||||
|
||||
fn pull(&self, upstream: &[Rc<dyn CritUpstreamNode<Target>>], node: &Target) -> bool {
|
||||
let mut n = 0;
|
||||
for upstream in upstream {
|
||||
if upstream.pull(node) {
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
(n == self.num) ^ self.not
|
||||
}
|
||||
|
||||
fn not(&self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Self {
|
||||
total: self.total,
|
||||
num: self.total - self.num,
|
||||
not: !self.not,
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
45
src/criteria/crit_matchers/critm_string.rs
Normal file
45
src/criteria/crit_matchers/critm_string.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use {
|
||||
crate::criteria::{
|
||||
CritLiteralOrRegex, RootMatcherMap,
|
||||
crit_graph::{CritRootCriterion, CritTarget},
|
||||
},
|
||||
std::marker::PhantomData,
|
||||
};
|
||||
|
||||
pub struct CritMatchString<Target, A> {
|
||||
string: CritLiteralOrRegex,
|
||||
_phantom: PhantomData<(fn(&Target), A)>,
|
||||
}
|
||||
|
||||
pub trait StringAccess<Target>: Sized + 'static
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
fn with_string(data: &Target, f: impl FnOnce(&str) -> bool) -> bool;
|
||||
fn nodes(
|
||||
roots: &Target::RootMatchers,
|
||||
) -> &RootMatcherMap<Target, CritMatchString<Target, Self>>;
|
||||
}
|
||||
|
||||
impl<Target, A> CritMatchString<Target, A> {
|
||||
pub fn new(string: CritLiteralOrRegex) -> Self {
|
||||
Self {
|
||||
string,
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target, A> CritRootCriterion<Target> for CritMatchString<Target, A>
|
||||
where
|
||||
Target: CritTarget,
|
||||
A: StringAccess<Target>,
|
||||
{
|
||||
fn matches(&self, data: &Target) -> bool {
|
||||
A::with_string(data, |s| self.string.matches(s))
|
||||
}
|
||||
|
||||
fn nodes(roots: &Target::RootMatchers) -> Option<&RootMatcherMap<Target, Self>> {
|
||||
Some(A::nodes(roots))
|
||||
}
|
||||
}
|
||||
135
src/criteria/crit_per_target_data.rs
Normal file
135
src/criteria/crit_per_target_data.rs
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
use {
|
||||
crate::criteria::{
|
||||
CritMatcherId,
|
||||
crit_graph::{CritTarget, CritTargetOwner, WeakCritTargetOwner},
|
||||
},
|
||||
ahash::AHashMap,
|
||||
std::{
|
||||
cell::{RefCell, RefMut},
|
||||
ops::{Deref, DerefMut},
|
||||
rc::Weak,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct CritPerTargetData<Target, T>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
id: CritMatcherId,
|
||||
slf: Weak<dyn CritDestroyListener<Target>>,
|
||||
data: RefCell<AHashMap<Target::Id, PerTreeNodeData<Target, T>>>,
|
||||
}
|
||||
|
||||
pub struct PerTreeNodeData<Target, T>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
node: Target::Owner,
|
||||
data: T,
|
||||
}
|
||||
|
||||
pub(super) trait CritDestroyListenerBase<Target>: 'static
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
type Data;
|
||||
|
||||
fn data(&self) -> &CritPerTargetData<Target, Self::Data>;
|
||||
}
|
||||
|
||||
pub trait CritDestroyListener<Target>: 'static
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
fn destroyed(&self, target_id: Target::Id);
|
||||
}
|
||||
|
||||
impl<Target, T> CritPerTargetData<Target, T>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
pub fn new(slf: &Weak<impl CritDestroyListener<Target>>, id: CritMatcherId) -> Self {
|
||||
Self {
|
||||
id,
|
||||
slf: slf.clone() as _,
|
||||
data: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_or_create(&self, target: &Target, default: impl FnOnce() -> T) -> RefMut<T> {
|
||||
RefMut::map(self.data.borrow_mut(), |d| {
|
||||
&mut d
|
||||
.entry(target.id())
|
||||
.or_insert_with(|| {
|
||||
target.destroyed().set(self.id, self.slf.clone());
|
||||
PerTreeNodeData {
|
||||
node: target.owner(),
|
||||
data: default(),
|
||||
}
|
||||
})
|
||||
.data
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get(&self, target: &Target) -> Option<RefMut<T>> {
|
||||
RefMut::filter_map(self.data.borrow_mut(), |d| {
|
||||
d.get_mut(&target.id()).map(|d| &mut d.data)
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub fn remove(&self, target_id: Target::Id) {
|
||||
if let Some(node) = self.data.borrow_mut().remove(&target_id) {
|
||||
if let Some(node) = node.node.upgrade() {
|
||||
node.data().destroyed().remove(&self.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn borrow_mut(&self) -> RefMut<'_, AHashMap<Target::Id, PerTreeNodeData<Target, T>>> {
|
||||
self.data.borrow_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target, T> Drop for CritPerTargetData<Target, T>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
for d in self.data.borrow().values() {
|
||||
if let Some(n) = d.node.upgrade() {
|
||||
n.data().destroyed().remove(&self.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target, T> CritDestroyListener<Target> for T
|
||||
where
|
||||
Target: CritTarget,
|
||||
T: CritDestroyListenerBase<Target>,
|
||||
{
|
||||
fn destroyed(&self, target_id: Target::Id) {
|
||||
let _v = self.data().data.borrow_mut().remove(&target_id);
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target, T> Deref for PerTreeNodeData<Target, T>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target, T> DerefMut for PerTreeNodeData<Target, T>
|
||||
where
|
||||
Target: CritTarget,
|
||||
{
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.data
|
||||
}
|
||||
}
|
||||
436
src/criteria/tlm.rs
Normal file
436
src/criteria/tlm.rs
Normal file
|
|
@ -0,0 +1,436 @@
|
|||
pub mod tlm_matchers;
|
||||
|
||||
use {
|
||||
crate::{
|
||||
criteria::{
|
||||
CritDestroyListener, CritLiteralOrRegex, CritMatcherId, CritMatcherIds, CritMgrExt,
|
||||
CritUpstreamNode, FixedRootMatcher, RootMatcherMap,
|
||||
clm::ClmUpstreamNode,
|
||||
crit_graph::{
|
||||
CritMgr, CritRoot, CritRootFixed, CritTarget, CritTargetOwner, WeakCritTargetOwner,
|
||||
},
|
||||
crit_leaf::{CritLeafEvent, CritLeafMatcher},
|
||||
crit_matchers::critm_constant::CritMatchConstant,
|
||||
tlm::tlm_matchers::{
|
||||
tlmm_client::TlmMatchClient,
|
||||
tlmm_floating::TlmMatchFloating,
|
||||
tlmm_fullscreen::TlmMatchFullscreen,
|
||||
tlmm_just_mapped::TlmMatchJustMapped,
|
||||
tlmm_kind::TlmMatchKind,
|
||||
tlmm_seat_focus::TlmMatchSeatFocus,
|
||||
tlmm_string::{
|
||||
TlmMatchAppId, TlmMatchClass, TlmMatchInstance, TlmMatchRole, TlmMatchTag,
|
||||
TlmMatchTitle, TlmMatchWorkspace,
|
||||
},
|
||||
tlmm_urgent::TlmMatchUrgent,
|
||||
tlmm_visible::TlmMatchVisible,
|
||||
},
|
||||
},
|
||||
ifs::wl_seat::WlSeatGlobal,
|
||||
state::State,
|
||||
tree::{NodeId, ToplevelData, ToplevelNode},
|
||||
utils::{
|
||||
copyhashmap::CopyHashMap, hash_map_ext::HashMapExt, queue::AsyncQueue,
|
||||
toplevel_identifier::ToplevelIdentifier,
|
||||
},
|
||||
},
|
||||
jay_config::window::WindowType,
|
||||
linearize::static_map,
|
||||
std::{
|
||||
marker::PhantomData,
|
||||
rc::{Rc, Weak},
|
||||
},
|
||||
};
|
||||
|
||||
bitflags! {
|
||||
TlMatcherChange: u32;
|
||||
TL_CHANGED_DESTROYED = 1 << 0,
|
||||
TL_CHANGED_NEW = 1 << 1,
|
||||
TL_CHANGED_TITLE = 1 << 2,
|
||||
TL_CHANGED_APP_ID = 1 << 3,
|
||||
TL_CHANGED_FLOATING = 1 << 4,
|
||||
TL_CHANGED_VISIBLE = 1 << 5,
|
||||
TL_CHANGED_URGENT = 1 << 6,
|
||||
TL_CHANGED_SEAT_FOCI = 1 << 7,
|
||||
TL_CHANGED_FULLSCREEN = 1 << 8,
|
||||
TL_CHANGED_JUST_MAPPED = 1 << 9,
|
||||
TL_CHANGED_TAG = 1 << 10,
|
||||
TL_CHANGED_CLASS_INST = 1 << 11,
|
||||
TL_CHANGED_ROLE = 1 << 12,
|
||||
TL_CHANGED_WORKSPACE = 1 << 13,
|
||||
}
|
||||
|
||||
type TlmFixedRootMatcher<T> = FixedRootMatcher<ToplevelData, T>;
|
||||
|
||||
pub struct TlMatcherManager {
|
||||
ids: Rc<CritMatcherIds>,
|
||||
changes: AsyncQueue<Rc<dyn ToplevelNode>>,
|
||||
leaf_events: Rc<AsyncQueue<CritLeafEvent<ToplevelData>>>,
|
||||
handle_just_mapped: AsyncQueue<Rc<dyn ToplevelNode>>,
|
||||
constant: TlmFixedRootMatcher<CritMatchConstant<ToplevelData>>,
|
||||
floating: TlmFixedRootMatcher<TlmMatchFloating>,
|
||||
visible: TlmFixedRootMatcher<TlmMatchVisible>,
|
||||
urgent: TlmFixedRootMatcher<TlmMatchUrgent>,
|
||||
fullscreen: TlmFixedRootMatcher<TlmMatchFullscreen>,
|
||||
just_mapped: TlmFixedRootMatcher<TlmMatchJustMapped>,
|
||||
matchers: Rc<RootMatchers>,
|
||||
}
|
||||
|
||||
type TlmRootMatcherMap<T> = RootMatcherMap<ToplevelData, T>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RootMatchers {
|
||||
kinds: TlmRootMatcherMap<TlmMatchKind>,
|
||||
clients: CopyHashMap<CritMatcherId, Weak<TlmMatchClient>>,
|
||||
title: TlmRootMatcherMap<TlmMatchTitle>,
|
||||
tag: TlmRootMatcherMap<TlmMatchTag>,
|
||||
app_id: TlmRootMatcherMap<TlmMatchAppId>,
|
||||
seat_foci: TlmRootMatcherMap<TlmMatchSeatFocus>,
|
||||
class: TlmRootMatcherMap<TlmMatchClass>,
|
||||
instance: TlmRootMatcherMap<TlmMatchInstance>,
|
||||
role: TlmRootMatcherMap<TlmMatchRole>,
|
||||
workspace: TlmRootMatcherMap<TlmMatchWorkspace>,
|
||||
}
|
||||
|
||||
pub async fn handle_tl_changes(state: Rc<State>) {
|
||||
let mgr = &state.tl_matcher_manager;
|
||||
loop {
|
||||
let tl = mgr.changes.pop().await;
|
||||
mgr.update_matches(tl);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_tl_leaf_events(state: Rc<State>) {
|
||||
let mgr = &state.tl_matcher_manager;
|
||||
let debouncer = state.ring.debouncer(1000);
|
||||
loop {
|
||||
let event = mgr.leaf_events.pop().await;
|
||||
event.run();
|
||||
debouncer.debounce().await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_tl_just_mapped(state: Rc<State>) {
|
||||
let mgr = &state.tl_matcher_manager;
|
||||
loop {
|
||||
let tl = mgr.handle_just_mapped.pop().await;
|
||||
let data = tl.tl_data();
|
||||
data.just_mapped_scheduled.set(false);
|
||||
data.property_changed(TL_CHANGED_JUST_MAPPED);
|
||||
}
|
||||
}
|
||||
|
||||
pub type TlmUpstreamNode = dyn CritUpstreamNode<ToplevelData>;
|
||||
pub type TlmLeafMatcher = CritLeafMatcher<ToplevelData>;
|
||||
|
||||
impl TlMatcherManager {
|
||||
pub fn new(ids: &Rc<CritMatcherIds>) -> Self {
|
||||
let matchers = Rc::new(RootMatchers::default());
|
||||
macro_rules! bool {
|
||||
($name:ident) => {{
|
||||
static_map! {
|
||||
v => CritRoot::new(
|
||||
&matchers,
|
||||
ids.next(),
|
||||
CritRootFixed($name(v), PhantomData),
|
||||
)
|
||||
}
|
||||
}};
|
||||
}
|
||||
Self {
|
||||
constant: CritMatchConstant::create(&matchers, ids),
|
||||
floating: bool!(TlmMatchFloating),
|
||||
visible: bool!(TlmMatchVisible),
|
||||
urgent: bool!(TlmMatchUrgent),
|
||||
fullscreen: bool!(TlmMatchFullscreen),
|
||||
just_mapped: bool!(TlmMatchJustMapped),
|
||||
changes: Default::default(),
|
||||
leaf_events: Default::default(),
|
||||
ids: ids.clone(),
|
||||
matchers,
|
||||
handle_just_mapped: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&self) {
|
||||
self.changes.clear();
|
||||
self.leaf_events.clear();
|
||||
self.handle_just_mapped.clear();
|
||||
}
|
||||
|
||||
pub fn rematch_all(&self, state: &Rc<State>) {
|
||||
for tl in state.toplevels.lock().values() {
|
||||
if let Some(tl) = tl.upgrade() {
|
||||
tl.tl_data().property_changed(TL_CHANGED_NEW);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_seat_foci(&self) -> bool {
|
||||
self.matchers.seat_foci.is_not_empty()
|
||||
}
|
||||
|
||||
pub fn has_no_interest(&self, data: &ToplevelData, change: TlMatcherChange) -> bool {
|
||||
!self.has_interest(data, change)
|
||||
}
|
||||
|
||||
pub fn has_interest(&self, data: &ToplevelData, mut change: TlMatcherChange) -> bool {
|
||||
if change.contains(TL_CHANGED_DESTROYED) && data.destroyed.is_not_empty() {
|
||||
return true;
|
||||
}
|
||||
macro_rules! fixed {
|
||||
($name:ident) => {
|
||||
if self.$name[false].has_downstream() || self.$name[true].has_downstream() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
if change.contains(TL_CHANGED_NEW) {
|
||||
macro_rules! unconditional {
|
||||
($field:ident) => {
|
||||
if self.matchers.$field.is_not_empty() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
unconditional!(kinds);
|
||||
unconditional!(clients);
|
||||
if self.constant[true].has_downstream() {
|
||||
return true;
|
||||
}
|
||||
change |= TlMatcherChange::all();
|
||||
}
|
||||
macro_rules! conditional {
|
||||
($change:expr, $field:ident) => {
|
||||
if change.contains($change) && self.matchers.$field.is_not_empty() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
macro_rules! fixed_conditional {
|
||||
($change:expr, $field:ident) => {
|
||||
if change.contains($change) {
|
||||
fixed!($field);
|
||||
}
|
||||
};
|
||||
}
|
||||
conditional!(TL_CHANGED_TITLE, title);
|
||||
conditional!(TL_CHANGED_APP_ID, app_id);
|
||||
conditional!(TL_CHANGED_SEAT_FOCI, seat_foci);
|
||||
conditional!(TL_CHANGED_TAG, tag);
|
||||
conditional!(TL_CHANGED_CLASS_INST, class);
|
||||
conditional!(TL_CHANGED_CLASS_INST, instance);
|
||||
conditional!(TL_CHANGED_ROLE, role);
|
||||
conditional!(TL_CHANGED_WORKSPACE, workspace);
|
||||
fixed_conditional!(TL_CHANGED_FLOATING, floating);
|
||||
fixed_conditional!(TL_CHANGED_VISIBLE, visible);
|
||||
fixed_conditional!(TL_CHANGED_URGENT, urgent);
|
||||
fixed_conditional!(TL_CHANGED_FULLSCREEN, fullscreen);
|
||||
fixed_conditional!(TL_CHANGED_JUST_MAPPED, just_mapped);
|
||||
false
|
||||
}
|
||||
|
||||
pub fn changed(&self, node: Rc<dyn ToplevelNode>) {
|
||||
self.changes.push(node);
|
||||
}
|
||||
|
||||
fn update_matches(&self, node: Rc<dyn ToplevelNode>) {
|
||||
let data = node.tl_data();
|
||||
let mut changed = data.changed_properties.replace(TlMatcherChange::none());
|
||||
if changed.contains(TL_CHANGED_DESTROYED) {
|
||||
for destroyed in data.destroyed.lock().drain_values() {
|
||||
if let Some(destroyed) = destroyed.upgrade() {
|
||||
destroyed.destroyed(data.node_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if data.parent.is_none() {
|
||||
return;
|
||||
}
|
||||
macro_rules! handlers {
|
||||
($name:ident) => {
|
||||
self.matchers
|
||||
.$name
|
||||
.lock()
|
||||
.values()
|
||||
.filter_map(|m| m.upgrade())
|
||||
};
|
||||
}
|
||||
macro_rules! fixed {
|
||||
($name:ident) => {
|
||||
self.$name[false].handle(data);
|
||||
self.$name[true].handle(data);
|
||||
};
|
||||
}
|
||||
if changed.contains(TL_CHANGED_NEW) {
|
||||
changed |= TlMatcherChange::all();
|
||||
macro_rules! unconditional {
|
||||
($field:ident) => {
|
||||
for m in handlers!($field) {
|
||||
m.handle(data);
|
||||
}
|
||||
};
|
||||
}
|
||||
unconditional!(kinds);
|
||||
unconditional!(clients);
|
||||
self.constant[true].handle(data);
|
||||
}
|
||||
macro_rules! conditional {
|
||||
($change:expr, $field:ident) => {
|
||||
if changed.contains($change) {
|
||||
for m in handlers!($field) {
|
||||
m.handle(data);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
macro_rules! fixed_conditional {
|
||||
($change:expr, $field:ident) => {
|
||||
if changed.contains($change) {
|
||||
fixed!($field);
|
||||
}
|
||||
};
|
||||
}
|
||||
conditional!(TL_CHANGED_TITLE, title);
|
||||
conditional!(TL_CHANGED_APP_ID, app_id);
|
||||
conditional!(TL_CHANGED_SEAT_FOCI, seat_foci);
|
||||
conditional!(TL_CHANGED_TAG, tag);
|
||||
conditional!(TL_CHANGED_CLASS_INST, class);
|
||||
conditional!(TL_CHANGED_CLASS_INST, instance);
|
||||
conditional!(TL_CHANGED_ROLE, role);
|
||||
conditional!(TL_CHANGED_WORKSPACE, workspace);
|
||||
fixed_conditional!(TL_CHANGED_FLOATING, floating);
|
||||
fixed_conditional!(TL_CHANGED_VISIBLE, visible);
|
||||
fixed_conditional!(TL_CHANGED_URGENT, urgent);
|
||||
fixed_conditional!(TL_CHANGED_FULLSCREEN, fullscreen);
|
||||
fixed_conditional!(TL_CHANGED_JUST_MAPPED, just_mapped);
|
||||
if changed.contains(TL_CHANGED_JUST_MAPPED)
|
||||
&& data.just_mapped()
|
||||
&& (self.just_mapped[false].has_downstream() || self.just_mapped[true].has_downstream())
|
||||
&& !data.just_mapped_scheduled.replace(true)
|
||||
{
|
||||
self.handle_just_mapped.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn title(&self, string: CritLiteralOrRegex) -> Rc<TlmUpstreamNode> {
|
||||
self.root(TlmMatchTitle::new(string))
|
||||
}
|
||||
|
||||
pub fn app_id(&self, string: CritLiteralOrRegex) -> Rc<TlmUpstreamNode> {
|
||||
self.root(TlmMatchAppId::new(string))
|
||||
}
|
||||
|
||||
pub fn tag(&self, string: CritLiteralOrRegex) -> Rc<TlmUpstreamNode> {
|
||||
self.root(TlmMatchTag::new(string))
|
||||
}
|
||||
|
||||
pub fn floating(&self) -> Rc<TlmUpstreamNode> {
|
||||
self.floating[true].clone()
|
||||
}
|
||||
|
||||
pub fn kind(&self, kind: WindowType) -> Rc<TlmUpstreamNode> {
|
||||
self.root(TlmMatchKind::new(kind))
|
||||
}
|
||||
|
||||
pub fn client(&self, state: &Rc<State>, client: &Rc<ClmUpstreamNode>) -> Rc<TlmUpstreamNode> {
|
||||
TlmMatchClient::new(state, client)
|
||||
}
|
||||
|
||||
pub fn visible(&self) -> Rc<TlmUpstreamNode> {
|
||||
self.visible[true].clone()
|
||||
}
|
||||
|
||||
pub fn fullscreen(&self) -> Rc<TlmUpstreamNode> {
|
||||
self.fullscreen[true].clone()
|
||||
}
|
||||
|
||||
pub fn urgent(&self) -> Rc<TlmUpstreamNode> {
|
||||
self.urgent[true].clone()
|
||||
}
|
||||
|
||||
pub fn just_mapped(&self) -> Rc<TlmUpstreamNode> {
|
||||
self.just_mapped[true].clone()
|
||||
}
|
||||
|
||||
pub fn seat_focus(&self, seat: &WlSeatGlobal) -> Rc<TlmUpstreamNode> {
|
||||
self.root(TlmMatchSeatFocus::new(seat.id()))
|
||||
}
|
||||
|
||||
pub fn class(&self, string: CritLiteralOrRegex) -> Rc<TlmUpstreamNode> {
|
||||
self.root(TlmMatchClass::new(string))
|
||||
}
|
||||
|
||||
pub fn instance(&self, string: CritLiteralOrRegex) -> Rc<TlmUpstreamNode> {
|
||||
self.root(TlmMatchInstance::new(string))
|
||||
}
|
||||
|
||||
pub fn role(&self, string: CritLiteralOrRegex) -> Rc<TlmUpstreamNode> {
|
||||
self.root(TlmMatchRole::new(string))
|
||||
}
|
||||
|
||||
pub fn workspace(&self, string: CritLiteralOrRegex) -> Rc<TlmUpstreamNode> {
|
||||
self.root(TlmMatchWorkspace::new(string))
|
||||
}
|
||||
}
|
||||
|
||||
impl CritTarget for ToplevelData {
|
||||
type Id = NodeId;
|
||||
type Mgr = TlMatcherManager;
|
||||
type RootMatchers = RootMatchers;
|
||||
type LeafData = ToplevelIdentifier;
|
||||
type Owner = Weak<dyn ToplevelNode>;
|
||||
|
||||
fn owner(&self) -> Self::Owner {
|
||||
self.slf.clone()
|
||||
}
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
self.node_id
|
||||
}
|
||||
|
||||
fn destroyed(&self) -> &CopyHashMap<CritMatcherId, Weak<dyn CritDestroyListener<Self>>> {
|
||||
&self.destroyed
|
||||
}
|
||||
|
||||
fn leaf_data(&self) -> Self::LeafData {
|
||||
self.identifier.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl CritTargetOwner for Rc<dyn ToplevelNode> {
|
||||
type Target = ToplevelData;
|
||||
|
||||
fn data(&self) -> &Self::Target {
|
||||
self.tl_data()
|
||||
}
|
||||
}
|
||||
|
||||
impl WeakCritTargetOwner for Weak<dyn ToplevelNode> {
|
||||
type Target = ToplevelData;
|
||||
type Owner = Rc<dyn ToplevelNode>;
|
||||
|
||||
fn upgrade(&self) -> Option<Self::Owner> {
|
||||
self.upgrade()
|
||||
}
|
||||
}
|
||||
|
||||
impl CritMgr for TlMatcherManager {
|
||||
type Target = ToplevelData;
|
||||
|
||||
fn id(&self) -> CritMatcherId {
|
||||
self.ids.next()
|
||||
}
|
||||
|
||||
fn leaf_events(&self) -> &Rc<AsyncQueue<CritLeafEvent<Self::Target>>> {
|
||||
&self.leaf_events
|
||||
}
|
||||
|
||||
fn match_constant(&self) -> &FixedRootMatcher<Self::Target, CritMatchConstant<Self::Target>> {
|
||||
&self.constant
|
||||
}
|
||||
|
||||
fn roots(&self) -> &Rc<<Self::Target as CritTarget>::RootMatchers> {
|
||||
&self.matchers
|
||||
}
|
||||
}
|
||||
28
src/criteria/tlm/tlm_matchers.rs
Normal file
28
src/criteria/tlm/tlm_matchers.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
macro_rules! fixed_root_criterion {
|
||||
($ty:ty, $field:ident) => {
|
||||
impl crate::criteria::crit_graph::CritFixedRootCriterionBase<crate::tree::ToplevelData>
|
||||
for $ty
|
||||
{
|
||||
fn constant(&self) -> bool {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn not<'a>(
|
||||
&self,
|
||||
mgr: &'a crate::criteria::tlm::TlMatcherManager,
|
||||
) -> &'a crate::criteria::FixedRootMatcher<crate::tree::ToplevelData, Self> {
|
||||
&mgr.$field
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub mod tlmm_client;
|
||||
pub mod tlmm_floating;
|
||||
pub mod tlmm_fullscreen;
|
||||
pub mod tlmm_just_mapped;
|
||||
pub mod tlmm_kind;
|
||||
pub mod tlmm_seat_focus;
|
||||
pub mod tlmm_string;
|
||||
pub mod tlmm_urgent;
|
||||
pub mod tlmm_visible;
|
||||
117
src/criteria/tlm/tlm_matchers/tlmm_client.rs
Normal file
117
src/criteria/tlm/tlm_matchers/tlmm_client.rs
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
use {
|
||||
crate::{
|
||||
client::Client,
|
||||
criteria::{
|
||||
CritMatcherId, CritUpstreamNode,
|
||||
clm::ClmUpstreamNode,
|
||||
crit_graph::{
|
||||
CritDownstream, CritDownstreamData, CritMgr, CritUpstreamData,
|
||||
CritUpstreamNodeBase, CritUpstreamNodeData,
|
||||
},
|
||||
crit_per_target_data::{CritDestroyListenerBase, CritPerTargetData},
|
||||
tlm::TlMatcherManager,
|
||||
},
|
||||
state::State,
|
||||
tree::{ToplevelData, ToplevelNodeBase},
|
||||
},
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
||||
pub struct TlmMatchClient {
|
||||
id: CritMatcherId,
|
||||
state: Rc<State>,
|
||||
node: Rc<ClmUpstreamNode>,
|
||||
upstream: CritDownstreamData<Rc<Client>>,
|
||||
downstream: CritUpstreamData<ToplevelData, ()>,
|
||||
}
|
||||
|
||||
impl TlmMatchClient {
|
||||
pub fn new(state: &Rc<State>, node: &Rc<ClmUpstreamNode>) -> Rc<TlmMatchClient> {
|
||||
let id = state.tl_matcher_manager.id();
|
||||
let slf = Rc::new_cyclic(|slf| Self {
|
||||
id,
|
||||
state: state.clone(),
|
||||
node: node.clone(),
|
||||
upstream: CritDownstreamData::new(id, &[node.clone()]),
|
||||
downstream: CritUpstreamData::new(slf, id),
|
||||
});
|
||||
slf.upstream.attach(&slf);
|
||||
state
|
||||
.tl_matcher_manager
|
||||
.matchers
|
||||
.clients
|
||||
.set(id, Rc::downgrade(&slf));
|
||||
slf
|
||||
}
|
||||
|
||||
pub fn handle(&self, node: &ToplevelData) {
|
||||
if let Some(client) = &node.client {
|
||||
if self.node.get(client) {
|
||||
let data = self.downstream.get_or_create(node);
|
||||
self.downstream.update_matched(node, data, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CritUpstreamNodeBase<ToplevelData> for TlmMatchClient {
|
||||
type Data = ();
|
||||
|
||||
fn data(&self) -> &CritUpstreamData<ToplevelData, Self::Data> {
|
||||
&self.downstream
|
||||
}
|
||||
|
||||
fn not(&self, _mgr: &TlMatcherManager) -> Rc<dyn CritUpstreamNode<ToplevelData>> {
|
||||
Self::new(&self.state, &self.node.not(&self.state.cl_matcher_manager))
|
||||
}
|
||||
|
||||
fn pull(&self, target: &ToplevelData) -> bool {
|
||||
if let Some(client) = &target.client {
|
||||
return self.node.pull(client);
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl CritDownstream<Rc<Client>> for TlmMatchClient {
|
||||
fn update_matched(self: Rc<Self>, target: &Rc<Client>, matched: bool) {
|
||||
let handle = |data: &ToplevelData| {
|
||||
let node = match matched {
|
||||
true => self.downstream.get_or_create(data),
|
||||
false => match self.downstream.get(data) {
|
||||
Some(n) => n,
|
||||
None => return,
|
||||
},
|
||||
};
|
||||
self.downstream
|
||||
.update_matched(data, node, matched, !matched);
|
||||
};
|
||||
if target.is_xwayland {
|
||||
for tl in self.state.xwayland.windows.lock().values() {
|
||||
handle(tl.tl_data());
|
||||
}
|
||||
} else {
|
||||
for tl in target.objects.xdg_toplevel.lock().values() {
|
||||
handle(tl.tl_data());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CritDestroyListenerBase<ToplevelData> for TlmMatchClient {
|
||||
type Data = CritUpstreamNodeData<ToplevelData, ()>;
|
||||
|
||||
fn data(&self) -> &CritPerTargetData<ToplevelData, Self::Data> {
|
||||
&self.downstream.nodes
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TlmMatchClient {
|
||||
fn drop(&mut self) {
|
||||
self.state
|
||||
.tl_matcher_manager
|
||||
.matchers
|
||||
.clients
|
||||
.remove(&self.id);
|
||||
}
|
||||
}
|
||||
11
src/criteria/tlm/tlm_matchers/tlmm_floating.rs
Normal file
11
src/criteria/tlm/tlm_matchers/tlmm_floating.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
use crate::{criteria::crit_graph::CritFixedRootCriterion, tree::ToplevelData};
|
||||
|
||||
pub struct TlmMatchFloating(pub bool);
|
||||
|
||||
fixed_root_criterion!(TlmMatchFloating, floating);
|
||||
|
||||
impl CritFixedRootCriterion<ToplevelData> for TlmMatchFloating {
|
||||
fn matches(&self, data: &ToplevelData) -> bool {
|
||||
data.is_floating.get()
|
||||
}
|
||||
}
|
||||
11
src/criteria/tlm/tlm_matchers/tlmm_fullscreen.rs
Normal file
11
src/criteria/tlm/tlm_matchers/tlmm_fullscreen.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
use crate::{criteria::crit_graph::CritFixedRootCriterion, tree::ToplevelData};
|
||||
|
||||
pub struct TlmMatchFullscreen(pub bool);
|
||||
|
||||
fixed_root_criterion!(TlmMatchFullscreen, fullscreen);
|
||||
|
||||
impl CritFixedRootCriterion<ToplevelData> for TlmMatchFullscreen {
|
||||
fn matches(&self, data: &ToplevelData) -> bool {
|
||||
data.is_fullscreen.get()
|
||||
}
|
||||
}
|
||||
11
src/criteria/tlm/tlm_matchers/tlmm_just_mapped.rs
Normal file
11
src/criteria/tlm/tlm_matchers/tlmm_just_mapped.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
use crate::{criteria::crit_graph::CritFixedRootCriterion, tree::ToplevelData};
|
||||
|
||||
pub struct TlmMatchJustMapped(pub bool);
|
||||
|
||||
fixed_root_criterion!(TlmMatchJustMapped, just_mapped);
|
||||
|
||||
impl CritFixedRootCriterion<ToplevelData> for TlmMatchJustMapped {
|
||||
fn matches(&self, data: &ToplevelData) -> bool {
|
||||
data.just_mapped()
|
||||
}
|
||||
}
|
||||
31
src/criteria/tlm/tlm_matchers/tlmm_kind.rs
Normal file
31
src/criteria/tlm/tlm_matchers/tlmm_kind.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
use {
|
||||
crate::{
|
||||
criteria::{
|
||||
crit_graph::CritRootCriterion,
|
||||
tlm::{RootMatchers, TlmRootMatcherMap},
|
||||
},
|
||||
tree::ToplevelData,
|
||||
utils::bitflags::BitflagsExt,
|
||||
},
|
||||
jay_config::window::WindowType,
|
||||
};
|
||||
|
||||
pub struct TlmMatchKind {
|
||||
kind: WindowType,
|
||||
}
|
||||
|
||||
impl TlmMatchKind {
|
||||
pub fn new(kind: WindowType) -> TlmMatchKind {
|
||||
Self { kind }
|
||||
}
|
||||
}
|
||||
|
||||
impl CritRootCriterion<ToplevelData> for TlmMatchKind {
|
||||
fn matches(&self, data: &ToplevelData) -> bool {
|
||||
self.kind.0.contains(data.kind.to_window_type().0)
|
||||
}
|
||||
|
||||
fn nodes(roots: &RootMatchers) -> Option<&TlmRootMatcherMap<Self>> {
|
||||
Some(&roots.kinds)
|
||||
}
|
||||
}
|
||||
28
src/criteria/tlm/tlm_matchers/tlmm_seat_focus.rs
Normal file
28
src/criteria/tlm/tlm_matchers/tlmm_seat_focus.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
use crate::{
|
||||
criteria::{
|
||||
crit_graph::CritRootCriterion,
|
||||
tlm::{RootMatchers, TlmRootMatcherMap},
|
||||
},
|
||||
ifs::wl_seat::SeatId,
|
||||
tree::ToplevelData,
|
||||
};
|
||||
|
||||
pub struct TlmMatchSeatFocus {
|
||||
id: SeatId,
|
||||
}
|
||||
|
||||
impl TlmMatchSeatFocus {
|
||||
pub fn new(id: SeatId) -> TlmMatchSeatFocus {
|
||||
Self { id }
|
||||
}
|
||||
}
|
||||
|
||||
impl CritRootCriterion<ToplevelData> for TlmMatchSeatFocus {
|
||||
fn matches(&self, data: &ToplevelData) -> bool {
|
||||
data.seat_foci.contains(&self.id)
|
||||
}
|
||||
|
||||
fn nodes(roots: &RootMatchers) -> Option<&TlmRootMatcherMap<Self>> {
|
||||
Some(&roots.seat_foci)
|
||||
}
|
||||
}
|
||||
110
src/criteria/tlm/tlm_matchers/tlmm_string.rs
Normal file
110
src/criteria/tlm/tlm_matchers/tlmm_string.rs
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
use crate::{
|
||||
criteria::{
|
||||
crit_matchers::critm_string::{CritMatchString, StringAccess},
|
||||
tlm::{RootMatchers, TlmRootMatcherMap},
|
||||
},
|
||||
tree::{ToplevelData, ToplevelType},
|
||||
};
|
||||
|
||||
pub type TlmMatchString<T> = CritMatchString<ToplevelData, T>;
|
||||
|
||||
pub type TlmMatchTitle = TlmMatchString<TitleAccess>;
|
||||
pub type TlmMatchAppId = TlmMatchString<AppIdAccess>;
|
||||
pub type TlmMatchTag = TlmMatchString<TagAccess>;
|
||||
pub type TlmMatchClass = TlmMatchString<ClassAccess>;
|
||||
pub type TlmMatchInstance = TlmMatchString<InstanceAccess>;
|
||||
pub type TlmMatchRole = TlmMatchString<RoleAccess>;
|
||||
pub type TlmMatchWorkspace = TlmMatchString<WorkspaceAccess>;
|
||||
|
||||
pub struct TitleAccess;
|
||||
pub struct AppIdAccess;
|
||||
pub struct TagAccess;
|
||||
pub struct ClassAccess;
|
||||
pub struct InstanceAccess;
|
||||
pub struct RoleAccess;
|
||||
pub struct WorkspaceAccess;
|
||||
|
||||
impl StringAccess<ToplevelData> for TitleAccess {
|
||||
fn with_string(data: &ToplevelData, f: impl FnOnce(&str) -> bool) -> bool {
|
||||
f(&data.title.borrow())
|
||||
}
|
||||
|
||||
fn nodes(roots: &RootMatchers) -> &TlmRootMatcherMap<TlmMatchString<Self>> {
|
||||
&roots.title
|
||||
}
|
||||
}
|
||||
|
||||
impl StringAccess<ToplevelData> for AppIdAccess {
|
||||
fn with_string(data: &ToplevelData, f: impl FnOnce(&str) -> bool) -> bool {
|
||||
f(&data.app_id.borrow())
|
||||
}
|
||||
|
||||
fn nodes(roots: &RootMatchers) -> &TlmRootMatcherMap<TlmMatchString<Self>> {
|
||||
&roots.app_id
|
||||
}
|
||||
}
|
||||
|
||||
impl StringAccess<ToplevelData> for TagAccess {
|
||||
fn with_string(data: &ToplevelData, f: impl FnOnce(&str) -> bool) -> bool {
|
||||
if let ToplevelType::XdgToplevel(data) = &data.kind {
|
||||
return f(&data.tag.borrow());
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn nodes(roots: &RootMatchers) -> &TlmRootMatcherMap<TlmMatchString<Self>> {
|
||||
&roots.tag
|
||||
}
|
||||
}
|
||||
|
||||
impl StringAccess<ToplevelData> for ClassAccess {
|
||||
fn with_string(data: &ToplevelData, f: impl FnOnce(&str) -> bool) -> bool {
|
||||
if let ToplevelType::XWindow(data) = &data.kind {
|
||||
return f(&data.info.class.borrow().as_deref().unwrap_or_default());
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn nodes(roots: &RootMatchers) -> &TlmRootMatcherMap<TlmMatchString<Self>> {
|
||||
&roots.class
|
||||
}
|
||||
}
|
||||
|
||||
impl StringAccess<ToplevelData> for InstanceAccess {
|
||||
fn with_string(data: &ToplevelData, f: impl FnOnce(&str) -> bool) -> bool {
|
||||
if let ToplevelType::XWindow(data) = &data.kind {
|
||||
return f(&data.info.instance.borrow().as_deref().unwrap_or_default());
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn nodes(roots: &RootMatchers) -> &TlmRootMatcherMap<TlmMatchString<Self>> {
|
||||
&roots.instance
|
||||
}
|
||||
}
|
||||
|
||||
impl StringAccess<ToplevelData> for RoleAccess {
|
||||
fn with_string(data: &ToplevelData, f: impl FnOnce(&str) -> bool) -> bool {
|
||||
if let ToplevelType::XWindow(data) = &data.kind {
|
||||
return f(&data.info.role.borrow().as_deref().unwrap_or_default());
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn nodes(roots: &RootMatchers) -> &TlmRootMatcherMap<TlmMatchString<Self>> {
|
||||
&roots.role
|
||||
}
|
||||
}
|
||||
|
||||
impl StringAccess<ToplevelData> for WorkspaceAccess {
|
||||
fn with_string(data: &ToplevelData, f: impl FnOnce(&str) -> bool) -> bool {
|
||||
if let Some(ws) = data.workspace.get() {
|
||||
return f(&ws.name);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn nodes(roots: &RootMatchers) -> &TlmRootMatcherMap<TlmMatchString<Self>> {
|
||||
&roots.workspace
|
||||
}
|
||||
}
|
||||
11
src/criteria/tlm/tlm_matchers/tlmm_urgent.rs
Normal file
11
src/criteria/tlm/tlm_matchers/tlmm_urgent.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
use crate::{criteria::crit_graph::CritFixedRootCriterion, tree::ToplevelData};
|
||||
|
||||
pub struct TlmMatchUrgent(pub bool);
|
||||
|
||||
fixed_root_criterion!(TlmMatchUrgent, urgent);
|
||||
|
||||
impl CritFixedRootCriterion<ToplevelData> for TlmMatchUrgent {
|
||||
fn matches(&self, data: &ToplevelData) -> bool {
|
||||
data.wants_attention.get()
|
||||
}
|
||||
}
|
||||
11
src/criteria/tlm/tlm_matchers/tlmm_visible.rs
Normal file
11
src/criteria/tlm/tlm_matchers/tlmm_visible.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
use crate::{criteria::crit_graph::CritFixedRootCriterion, tree::ToplevelData};
|
||||
|
||||
pub struct TlmMatchVisible(pub bool);
|
||||
|
||||
fixed_root_criterion!(TlmMatchVisible, visible);
|
||||
|
||||
impl CritFixedRootCriterion<ToplevelData> for TlmMatchVisible {
|
||||
fn matches(&self, data: &ToplevelData) -> bool {
|
||||
data.visible.get()
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ pub mod ext_output_image_capture_source_manager_v1;
|
|||
pub mod ext_session_lock_manager_v1;
|
||||
pub mod ext_session_lock_v1;
|
||||
pub mod ipc;
|
||||
pub mod jay_client_query;
|
||||
pub mod jay_color_management;
|
||||
pub mod jay_compositor;
|
||||
pub mod jay_damage_tracking;
|
||||
|
|
@ -30,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;
|
||||
|
|
|
|||
141
src/ifs/jay_client_query.rs
Normal file
141
src/ifs/jay_client_query.rs
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
use {
|
||||
crate::{
|
||||
client::{Client, ClientError, ClientId},
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
utils::copyhashmap::CopyHashMap,
|
||||
wire::{
|
||||
JayClientQueryId,
|
||||
jay_client_query::{
|
||||
AddAll, AddId, Comm, Destroy, Done, End, Exe, Execute, IsXwayland,
|
||||
JayClientQueryRequestHandler, Pid, SandboxAppId, SandboxEngine, SandboxInstanceId,
|
||||
Sandboxed, Start, Uid,
|
||||
},
|
||||
},
|
||||
},
|
||||
std::{cell::Cell, rc::Rc},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub struct JayClientQuery {
|
||||
pub id: JayClientQueryId,
|
||||
pub client: Rc<Client>,
|
||||
pub tracker: Tracker<Self>,
|
||||
pub version: Version,
|
||||
ids: CopyHashMap<ClientId, ()>,
|
||||
all: Cell<bool>,
|
||||
}
|
||||
|
||||
impl JayClientQuery {
|
||||
pub fn new(client: &Rc<Client>, id: JayClientQueryId, version: Version) -> Self {
|
||||
Self {
|
||||
id,
|
||||
client: client.clone(),
|
||||
tracker: Default::default(),
|
||||
version,
|
||||
ids: Default::default(),
|
||||
all: Cell::new(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JayClientQueryRequestHandler for JayClientQuery {
|
||||
type Error = JayClientQueryError;
|
||||
|
||||
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 handle_client = |client: &Rc<Client>| {
|
||||
self.client.event(Start {
|
||||
self_id: self.id,
|
||||
id: client.id.raw(),
|
||||
});
|
||||
if !client.is_xwayland {
|
||||
self.client.event(Uid {
|
||||
self_id: self.id,
|
||||
uid: client.pid_info.uid,
|
||||
});
|
||||
self.client.event(Pid {
|
||||
self_id: self.id,
|
||||
pid: client.pid_info.pid,
|
||||
});
|
||||
self.client.event(Comm {
|
||||
self_id: self.id,
|
||||
comm: &client.pid_info.comm,
|
||||
});
|
||||
self.client.event(Exe {
|
||||
self_id: self.id,
|
||||
exe: &client.pid_info.exe,
|
||||
});
|
||||
}
|
||||
if client.acceptor.sandboxed {
|
||||
self.client.event(Sandboxed { self_id: self.id });
|
||||
}
|
||||
if client.is_xwayland {
|
||||
self.client.event(IsXwayland { self_id: self.id });
|
||||
}
|
||||
if let Some(engine) = &client.acceptor.sandbox_engine {
|
||||
self.client.event(SandboxEngine {
|
||||
self_id: self.id,
|
||||
engine,
|
||||
});
|
||||
}
|
||||
if let Some(app_id) = &client.acceptor.app_id {
|
||||
self.client.event(SandboxAppId {
|
||||
self_id: self.id,
|
||||
app_id,
|
||||
});
|
||||
}
|
||||
if let Some(instance_id) = &client.acceptor.instance_id {
|
||||
self.client.event(SandboxInstanceId {
|
||||
self_id: self.id,
|
||||
instance_id,
|
||||
});
|
||||
}
|
||||
self.client.event(End { self_id: self.id });
|
||||
};
|
||||
if self.all.get() {
|
||||
for client in self.client.state.clients.clients.borrow().values() {
|
||||
handle_client(&client.data);
|
||||
}
|
||||
} else {
|
||||
for &id in self.ids.lock().keys() {
|
||||
let Ok(client) = self.client.state.clients.get(id) else {
|
||||
continue;
|
||||
};
|
||||
handle_client(&client);
|
||||
}
|
||||
}
|
||||
self.client.event(Done { self_id: self.id });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_all(&self, _req: AddAll, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.all.set(true);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_id(&self, req: AddId, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.ids.set(ClientId::from_raw(req.id), ());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
object_base! {
|
||||
self = JayClientQuery;
|
||||
version = self.version;
|
||||
}
|
||||
|
||||
impl Object for JayClientQuery {}
|
||||
|
||||
simple_add_obj!(JayClientQuery);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum JayClientQueryError {
|
||||
#[error(transparent)]
|
||||
ClientError(Box<ClientError>),
|
||||
}
|
||||
efrom!(JayClientQueryError, ClientError);
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
use {
|
||||
crate::{
|
||||
cli::CliLogLevel,
|
||||
client::{CAP_JAY_COMPOSITOR, Client, ClientCaps, ClientError},
|
||||
client::{CAP_JAY_COMPOSITOR, Client, ClientCaps, ClientError, ClientId},
|
||||
globals::{Global, GlobalName},
|
||||
ifs::{
|
||||
jay_client_query::JayClientQuery,
|
||||
jay_color_management::JayColorManagement,
|
||||
jay_ei_session_builder::JayEiSessionBuilder,
|
||||
jay_idle::JayIdle,
|
||||
|
|
@ -19,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,
|
||||
},
|
||||
|
|
@ -26,7 +28,10 @@ use {
|
|||
object::{Object, Version},
|
||||
screenshoter::take_screenshot,
|
||||
utils::{errorfmt::ErrorFmt, toplevel_identifier::ToplevelIdentifier},
|
||||
wire::{JayCompositorId, JayScreenshotId, jay_compositor::*},
|
||||
wire::{
|
||||
JayCompositorId, JayScreenshotId,
|
||||
jay_compositor::{self, *},
|
||||
},
|
||||
},
|
||||
bstr::ByteSlice,
|
||||
log::Level,
|
||||
|
|
@ -74,7 +79,7 @@ impl Global for JayCompositorGlobal {
|
|||
}
|
||||
|
||||
fn version(&self) -> u32 {
|
||||
17
|
||||
18
|
||||
}
|
||||
|
||||
fn required_caps(&self) -> ClientCaps {
|
||||
|
|
@ -223,7 +228,7 @@ impl JayCompositorRequestHandler for JayCompositor {
|
|||
}
|
||||
|
||||
fn get_client_id(&self, _req: GetClientId, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.client.event(ClientId {
|
||||
self.client.event(jay_compositor::ClientId {
|
||||
self_id: self.id,
|
||||
client_id: self.client.id.raw(),
|
||||
});
|
||||
|
|
@ -367,7 +372,6 @@ impl JayCompositorRequestHandler for JayCompositor {
|
|||
}
|
||||
|
||||
fn select_toplevel(&self, req: SelectToplevel, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
let seat = self.client.lookup(req.seat)?;
|
||||
let obj = JaySelectToplevel::new(&self.client, req.id, self.version);
|
||||
track!(self.client, obj);
|
||||
self.client.add_client_obj(&obj)?;
|
||||
|
|
@ -375,12 +379,22 @@ impl JayCompositorRequestHandler for JayCompositor {
|
|||
tl: Default::default(),
|
||||
jst: obj.clone(),
|
||||
};
|
||||
seat.global.select_toplevel(selector);
|
||||
let seat = if req.seat.is_none() {
|
||||
match self.client.state.seat_queue.last() {
|
||||
Some(s) => s.deref().clone(),
|
||||
None => {
|
||||
obj.done(None);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.client.lookup(req.seat)?.global.clone()
|
||||
};
|
||||
seat.select_toplevel(selector);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn select_workspace(&self, req: SelectWorkspace, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
let seat = self.client.lookup(req.seat)?;
|
||||
let obj = Rc::new(JaySelectWorkspace {
|
||||
id: req.id,
|
||||
client: self.client.clone(),
|
||||
|
|
@ -393,7 +407,15 @@ impl JayCompositorRequestHandler for JayCompositor {
|
|||
ws: Default::default(),
|
||||
jsw: obj.clone(),
|
||||
};
|
||||
seat.global.select_workspace(selector);
|
||||
let seat = if req.seat.is_none() {
|
||||
match self.client.state.seat_queue.last() {
|
||||
Some(s) => s.deref().clone(),
|
||||
None => return Ok(()),
|
||||
}
|
||||
} else {
|
||||
self.client.lookup(req.seat)?.global.clone()
|
||||
};
|
||||
seat.select_workspace(selector);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -470,6 +492,29 @@ impl JayCompositorRequestHandler for JayCompositor {
|
|||
self.client.add_client_obj(&obj)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_client_query(
|
||||
&self,
|
||||
req: CreateClientQuery,
|
||||
_slf: &Rc<Self>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let obj = Rc::new(JayClientQuery::new(&self.client, req.id, self.version));
|
||||
track!(self.client, obj);
|
||||
self.client.add_client_obj(&obj)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn kill_client(&self, req: KillClient, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
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! {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use {
|
|||
crate::{
|
||||
client::{Client, ClientError},
|
||||
ifs::{
|
||||
jay_toplevel::{ID_SINCE, JayToplevel},
|
||||
jay_toplevel::{CLIENT_ID_SINCE, ID_SINCE, JayToplevel},
|
||||
wl_seat::ToplevelSelector,
|
||||
},
|
||||
leaks::Tracker,
|
||||
|
|
@ -78,6 +78,9 @@ impl JaySelectToplevel {
|
|||
self.send_done(jtl.id);
|
||||
if jtl.version >= ID_SINCE {
|
||||
jtl.send_id();
|
||||
if jtl.version >= CLIENT_ID_SINCE {
|
||||
jtl.send_client_id();
|
||||
}
|
||||
jtl.send_done();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use {
|
|||
};
|
||||
|
||||
pub const ID_SINCE: Version = Version(12);
|
||||
pub const CLIENT_ID_SINCE: Version = Version(18);
|
||||
|
||||
pub struct JayToplevel {
|
||||
pub id: JayToplevelId,
|
||||
|
|
@ -47,6 +48,15 @@ impl JayToplevel {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn send_client_id(&self) {
|
||||
if let Some(cl) = &self.toplevel.tl_data().client {
|
||||
self.client.event(ClientId {
|
||||
self_id: self.id,
|
||||
id: cl.id.raw(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_done(&self) {
|
||||
self.client.event(Done { self_id: self.id })
|
||||
}
|
||||
|
|
|
|||
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);
|
||||
|
|
@ -79,7 +79,8 @@ use {
|
|||
state::{DeviceHandlerData, State},
|
||||
tree::{
|
||||
ContainerNode, ContainerSplit, Direction, FoundNode, Node, OutputNode, ToplevelNode,
|
||||
WorkspaceNode, generic_node_visitor,
|
||||
WorkspaceNode, generic_node_visitor, toplevel_create_split, toplevel_parent_container,
|
||||
toplevel_set_floating, toplevel_set_workspace,
|
||||
},
|
||||
utils::{
|
||||
asyncevent::AsyncEvent, bindings::PerClientBindings, clonecell::CloneCell,
|
||||
|
|
@ -401,6 +402,10 @@ impl WlSeatGlobal {
|
|||
self.cursor_user_group.latest_output()
|
||||
}
|
||||
|
||||
pub fn get_keyboard_node(&self) -> Rc<dyn Node> {
|
||||
self.keyboard_node.get()
|
||||
}
|
||||
|
||||
pub fn get_keyboard_output(&self) -> Option<Rc<OutputNode>> {
|
||||
self.keyboard_node.get().node_output()
|
||||
}
|
||||
|
|
@ -410,38 +415,7 @@ impl WlSeatGlobal {
|
|||
Some(tl) => tl,
|
||||
_ => return,
|
||||
};
|
||||
if tl.tl_data().is_fullscreen.get() {
|
||||
return;
|
||||
}
|
||||
let old_ws = match tl.tl_data().workspace.get() {
|
||||
Some(ws) => ws,
|
||||
_ => return,
|
||||
};
|
||||
if old_ws.id == ws.id {
|
||||
return;
|
||||
}
|
||||
let cn = match tl.tl_data().parent.get() {
|
||||
Some(cn) => cn,
|
||||
_ => return,
|
||||
};
|
||||
let kb_foci = collect_kb_foci(tl.clone());
|
||||
cn.cnode_remove_child2(&*tl, true);
|
||||
if !ws.visible.get() {
|
||||
for focus in kb_foci {
|
||||
old_ws.clone().node_do_focus(&focus, Direction::Unspecified);
|
||||
}
|
||||
}
|
||||
if tl.tl_data().is_floating.get() {
|
||||
self.state.map_floating(
|
||||
tl.clone(),
|
||||
tl.tl_data().float_width.get(),
|
||||
tl.tl_data().float_height.get(),
|
||||
ws,
|
||||
None,
|
||||
);
|
||||
} else {
|
||||
self.state.map_tiled_on(tl, ws);
|
||||
}
|
||||
toplevel_set_workspace(&self.state, tl, ws);
|
||||
}
|
||||
|
||||
pub fn mark_last_active(self: &Rc<Self>) {
|
||||
|
|
@ -556,11 +530,7 @@ impl WlSeatGlobal {
|
|||
|
||||
pub fn kb_parent_container(&self) -> Option<Rc<ContainerNode>> {
|
||||
if let Some(tl) = self.keyboard_node.get().node_toplevel() {
|
||||
if let Some(parent) = tl.tl_data().parent.get() {
|
||||
if let Some(container) = parent.node_into_container() {
|
||||
return Some(container);
|
||||
}
|
||||
}
|
||||
return toplevel_parent_container(&*tl);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
|
@ -595,21 +565,7 @@ impl WlSeatGlobal {
|
|||
Some(tl) => tl,
|
||||
_ => return,
|
||||
};
|
||||
if tl.tl_data().is_fullscreen.get() {
|
||||
return;
|
||||
}
|
||||
let ws = match tl.tl_data().workspace.get() {
|
||||
Some(ws) => ws,
|
||||
_ => return,
|
||||
};
|
||||
let pn = match tl.tl_data().parent.get() {
|
||||
Some(pn) => pn,
|
||||
_ => return,
|
||||
};
|
||||
if let Some(pn) = pn.node_into_containing_node() {
|
||||
let cn = ContainerNode::new(&self.state, &ws, tl.clone(), axis);
|
||||
pn.cnode_replace_child(&*tl, cn);
|
||||
}
|
||||
toplevel_create_split(&self.state, tl, axis);
|
||||
}
|
||||
|
||||
pub fn focus_parent(self: &Rc<Self>) {
|
||||
|
|
@ -634,29 +590,7 @@ impl WlSeatGlobal {
|
|||
Some(tl) => tl,
|
||||
_ => return,
|
||||
};
|
||||
self.set_tl_floating(tl, floating);
|
||||
}
|
||||
|
||||
pub fn set_tl_floating(self: &Rc<Self>, tl: Rc<dyn ToplevelNode>, floating: bool) {
|
||||
let data = tl.tl_data();
|
||||
if data.is_fullscreen.get() {
|
||||
return;
|
||||
}
|
||||
if data.is_floating.get() == floating {
|
||||
return;
|
||||
}
|
||||
let parent = match data.parent.get() {
|
||||
Some(p) => p,
|
||||
_ => return,
|
||||
};
|
||||
if !floating {
|
||||
parent.cnode_remove_child2(&*tl, true);
|
||||
self.state.map_tiled(tl);
|
||||
} else if let Some(ws) = data.workspace.get() {
|
||||
parent.cnode_remove_child2(&*tl, true);
|
||||
let (width, height) = data.float_size(&ws);
|
||||
self.state.map_floating(tl, width, height, &ws, None);
|
||||
}
|
||||
toplevel_set_floating(&self.state, tl, floating);
|
||||
}
|
||||
|
||||
pub fn get_rate(&self) -> (i32, i32) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use {
|
||||
crate::{
|
||||
ifs::wl_seat::WlSeatGlobal, tree::Node, utils::clonecell::CloneCell,
|
||||
xwayland::XWaylandEvent,
|
||||
criteria::tlm::TL_CHANGED_SEAT_FOCI, ifs::wl_seat::WlSeatGlobal, tree::Node,
|
||||
utils::clonecell::CloneCell, xwayland::XWaylandEvent,
|
||||
},
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
|
@ -61,6 +61,18 @@ impl KbOwner for DefaultKbOwner {
|
|||
}
|
||||
|
||||
fn set_kb_node(&self, seat: &Rc<WlSeatGlobal>, node: Rc<dyn Node>, serial: u64) {
|
||||
macro_rules! notify_matcher {
|
||||
($node:expr, $data:ident, $block:expr) => {
|
||||
if let Some(tl) = $node.clone().node_toplevel() {
|
||||
let $data = tl.tl_data();
|
||||
$block;
|
||||
if seat.state.tl_matcher_manager.has_seat_foci() {
|
||||
$data.property_changed(TL_CHANGED_SEAT_FOCI);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let old = seat.keyboard_node.get();
|
||||
if old.node_id() == node.node_id() {
|
||||
return;
|
||||
|
|
@ -70,6 +82,7 @@ impl KbOwner for DefaultKbOwner {
|
|||
seat.state.xwayland.queue.push(XWaylandEvent::ActivateRoot);
|
||||
}
|
||||
old.node_on_unfocus(seat);
|
||||
notify_matcher!(old, data, data.seat_foci.remove(&seat.id));
|
||||
if old.node_seat_state().unfocus(seat) {
|
||||
old.node_active_changed(false);
|
||||
}
|
||||
|
|
@ -79,6 +92,7 @@ impl KbOwner for DefaultKbOwner {
|
|||
}
|
||||
// log::info!("focus {}", node.node_id());
|
||||
node.clone().node_on_focus(seat);
|
||||
notify_matcher!(node, data, data.seat_foci.set(seat.id, ()));
|
||||
seat.keyboard_node_serial.set(serial);
|
||||
seat.keyboard_node.set(node.clone());
|
||||
seat.tablet_on_keyboard_node_change();
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use {
|
|||
tree::{
|
||||
ContainerSplit, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId,
|
||||
NodeVisitor, OutputNode, StackedNode, TileDragDestination, ToplevelData, ToplevelNode,
|
||||
ToplevelNodeBase, WorkspaceNode, default_tile_drag_destination,
|
||||
ToplevelNodeBase, ToplevelType, WorkspaceNode, default_tile_drag_destination,
|
||||
},
|
||||
utils::{clonecell::CloneCell, copyhashmap::CopyHashMap, linkedlist::LinkedNode},
|
||||
wire::WlSurfaceId,
|
||||
|
|
@ -21,6 +21,7 @@ use {
|
|||
xwayland::XWaylandEvent,
|
||||
},
|
||||
bstr::BString,
|
||||
jay_config::window::TileState,
|
||||
std::{
|
||||
cell::{Cell, RefCell},
|
||||
ops::{Deref, Not},
|
||||
|
|
@ -90,10 +91,10 @@ pub struct XwindowInfo {
|
|||
pub override_redirect: Cell<bool>,
|
||||
pub extents: Cell<Rect>,
|
||||
pub pending_extents: Cell<Rect>,
|
||||
pub instance: RefCell<Option<BString>>,
|
||||
pub class: RefCell<Option<BString>>,
|
||||
pub instance: RefCell<Option<String>>,
|
||||
pub class: RefCell<Option<String>>,
|
||||
pub title: RefCell<Option<String>>,
|
||||
pub role: RefCell<Option<BString>>,
|
||||
pub role: RefCell<Option<String>>,
|
||||
pub protocols: CopyHashMap<u32, ()>,
|
||||
pub window_types: CopyHashMap<u32, ()>,
|
||||
pub never_focus: Cell<bool>,
|
||||
|
|
@ -205,16 +206,19 @@ impl Xwindow {
|
|||
if xsurface.xwindow.is_some() {
|
||||
return Err(XWindowError::AlreadyAttached);
|
||||
}
|
||||
let id = data.state.node_ids.next();
|
||||
let slf = Rc::new_cyclic(|weak| {
|
||||
let tld = ToplevelData::new(
|
||||
&data.state,
|
||||
data.info.title.borrow_mut().clone().unwrap_or_default(),
|
||||
Some(surface.client.clone()),
|
||||
ToplevelType::XWindow(data.clone()),
|
||||
id,
|
||||
weak,
|
||||
);
|
||||
tld.pos.set(surface.extents.get());
|
||||
Self {
|
||||
id: data.state.node_ids.next(),
|
||||
id,
|
||||
data: data.clone(),
|
||||
display_link: Default::default(),
|
||||
toplevel_data: tld,
|
||||
|
|
@ -235,6 +239,13 @@ impl Xwindow {
|
|||
self.tl_destroy();
|
||||
self.x.surface.set_toplevel(None);
|
||||
self.x.xwindow.set(None);
|
||||
self.x
|
||||
.surface
|
||||
.client
|
||||
.state
|
||||
.xwayland
|
||||
.windows
|
||||
.remove(&self.id);
|
||||
}
|
||||
|
||||
pub fn is_mapped(&self) -> bool {
|
||||
|
|
@ -256,6 +267,14 @@ impl Xwindow {
|
|||
pub fn map_status_changed(self: &Rc<Self>) {
|
||||
let map_change = self.map_change();
|
||||
let override_redirect = self.data.info.override_redirect.get();
|
||||
let map_floating = match self
|
||||
.toplevel_data
|
||||
.state
|
||||
.initial_tile_state(&self.toplevel_data)
|
||||
{
|
||||
None => self.data.info.wants_floating.get(),
|
||||
Some(m) => m == TileState::Floating,
|
||||
};
|
||||
match map_change {
|
||||
Change::None => return,
|
||||
Change::Unmap => {
|
||||
|
|
@ -272,7 +291,7 @@ impl Xwindow {
|
|||
Some(self.data.state.root.stacked.add_last(self.clone()));
|
||||
self.data.state.tree_changed();
|
||||
}
|
||||
Change::Map if self.data.info.wants_floating.get() => {
|
||||
Change::Map if map_floating => {
|
||||
let ws = self.data.state.float_map_ws();
|
||||
let ext = self.data.info.pending_extents.get();
|
||||
self.data
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -27,12 +27,14 @@ use {
|
|||
tree::{
|
||||
ContainerSplit, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId,
|
||||
NodeVisitor, OutputNode, TileDragDestination, ToplevelData, ToplevelNode,
|
||||
ToplevelNodeBase, ToplevelNodeId, WorkspaceNode, default_tile_drag_destination,
|
||||
ToplevelNodeBase, ToplevelNodeId, ToplevelType, WorkspaceNode,
|
||||
default_tile_drag_destination,
|
||||
},
|
||||
utils::{clonecell::CloneCell, hash_map_ext::HashMapExt},
|
||||
wire::{XdgToplevelId, xdg_toplevel::*},
|
||||
},
|
||||
ahash::{AHashMap, AHashSet},
|
||||
jay_config::window::TileState,
|
||||
num_derive::FromPrimitive,
|
||||
std::{
|
||||
cell::{Cell, RefCell},
|
||||
|
|
@ -91,6 +93,11 @@ pub enum Decoration {
|
|||
Server,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XdgToplevelToplevelData {
|
||||
pub tag: RefCell<String>,
|
||||
}
|
||||
|
||||
pub struct XdgToplevel {
|
||||
pub id: XdgToplevelId,
|
||||
pub state: Rc<State>,
|
||||
|
|
@ -111,6 +118,7 @@ pub struct XdgToplevel {
|
|||
is_mapped: Cell<bool>,
|
||||
dialog: CloneCell<Option<Rc<XdgDialogV1>>>,
|
||||
extents_set: Cell<bool>,
|
||||
pub data: Rc<XdgToplevelToplevelData>,
|
||||
}
|
||||
|
||||
impl Debug for XdgToplevel {
|
||||
|
|
@ -133,11 +141,15 @@ impl XdgToplevel {
|
|||
states.insert(STATE_CONSTRAINED_BOTTOM);
|
||||
}
|
||||
let state = &surface.surface.client.state;
|
||||
let node_id = state.node_ids.next();
|
||||
let data = Rc::new(XdgToplevelToplevelData {
|
||||
tag: Default::default(),
|
||||
});
|
||||
Self {
|
||||
id,
|
||||
state: state.clone(),
|
||||
xdg: surface.clone(),
|
||||
node_id: state.node_ids.next(),
|
||||
node_id,
|
||||
parent: Default::default(),
|
||||
children: RefCell::new(Default::default()),
|
||||
states: RefCell::new(states),
|
||||
|
|
@ -152,12 +164,15 @@ impl XdgToplevel {
|
|||
state,
|
||||
String::new(),
|
||||
Some(surface.surface.client.clone()),
|
||||
ToplevelType::XdgToplevel(data.clone()),
|
||||
node_id,
|
||||
slf,
|
||||
),
|
||||
drag: Default::default(),
|
||||
is_mapped: Cell::new(false),
|
||||
dialog: Default::default(),
|
||||
extents_set: Cell::new(false),
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -367,6 +382,31 @@ impl XdgToplevelRequestHandler for XdgToplevel {
|
|||
}
|
||||
|
||||
impl XdgToplevel {
|
||||
fn map(
|
||||
self: &Rc<Self>,
|
||||
parent: Option<&XdgToplevel>,
|
||||
pos: Option<(&Rc<OutputNode>, i32, i32)>,
|
||||
) {
|
||||
if let Some(state) = self.state.initial_tile_state(&self.toplevel_data) {
|
||||
match state {
|
||||
TileState::Floating => {
|
||||
let mut ws = None;
|
||||
if let Some(parent) = parent {
|
||||
ws = parent.xdg.workspace.get();
|
||||
}
|
||||
let ws = ws.unwrap_or_else(|| self.state.ensure_map_workspace(None));
|
||||
self.map_floating(&ws, pos.map(|p| (p.1, p.2)));
|
||||
}
|
||||
_ => self.map_tiled(),
|
||||
}
|
||||
return;
|
||||
}
|
||||
match parent {
|
||||
None => self.map_tiled(),
|
||||
Some(p) => self.map_child(p, pos),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_floating(self: &Rc<Self>, workspace: &Rc<WorkspaceNode>, abs_pos: Option<(i32, i32)>) {
|
||||
let (width, height) = self.toplevel_data.float_size(workspace);
|
||||
self.state
|
||||
|
|
@ -460,11 +500,7 @@ impl XdgToplevel {
|
|||
}
|
||||
self.state.tree_changed();
|
||||
} else {
|
||||
if let Some(parent) = self.parent.get() {
|
||||
self.map_child(&parent, pos);
|
||||
} else {
|
||||
self.map_tiled();
|
||||
}
|
||||
self.map(self.parent.get().as_deref(), pos);
|
||||
self.extents_changed();
|
||||
if let Some(workspace) = self.xdg.workspace.get() {
|
||||
let output = workspace.output.get();
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
use {
|
||||
crate::{
|
||||
client::{Client, ClientError},
|
||||
criteria::tlm::TL_CHANGED_TAG,
|
||||
globals::{Global, GlobalName},
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
tree::ToplevelNodeBase,
|
||||
wire::{XdgToplevelTagManagerV1Id, xdg_toplevel_tag_manager_v1::*},
|
||||
},
|
||||
std::rc::Rc,
|
||||
|
|
@ -72,9 +74,17 @@ impl XdgToplevelTagManagerV1RequestHandler for XdgToplevelTagManagerV1 {
|
|||
|
||||
fn set_toplevel_tag(
|
||||
&self,
|
||||
_req: SetToplevelTag<'_>,
|
||||
req: SetToplevelTag<'_>,
|
||||
_slf: &Rc<Self>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let tl = self.client.lookup(req.toplevel)?;
|
||||
let tag = &mut *tl.data.tag.borrow_mut();
|
||||
if tag == req.tag {
|
||||
return Ok(());
|
||||
}
|
||||
tag.clear();
|
||||
tag.push_str(req.tag);
|
||||
tl.tl_data().property_changed(TL_CHANGED_TAG);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use {
|
|||
crate::{
|
||||
async_engine::AsyncEngine,
|
||||
io_uring::{
|
||||
debounce::Debouncer,
|
||||
ops::{
|
||||
accept::AcceptTask, async_cancel::AsyncCancelTask, connect::ConnectTask,
|
||||
poll::PollTask, poll_external::PollExternalTask, read_write::ReadWriteTask,
|
||||
|
|
@ -29,6 +30,7 @@ use {
|
|||
copyhashmap::CopyHashMap,
|
||||
errorfmt::ErrorFmt,
|
||||
mmap::{Mmapped, mmap},
|
||||
numcell::NumCell,
|
||||
oserror::OsError,
|
||||
ptr_ext::{MutPtrExt, PtrExt},
|
||||
stack::Stack,
|
||||
|
|
@ -42,6 +44,7 @@ use {
|
|||
AtomicU32,
|
||||
Ordering::{Acquire, Relaxed, Release},
|
||||
},
|
||||
task::Waker,
|
||||
},
|
||||
thiserror::Error,
|
||||
uapi::{
|
||||
|
|
@ -61,6 +64,7 @@ macro_rules! map_err {
|
|||
}};
|
||||
}
|
||||
|
||||
mod debounce;
|
||||
mod ops;
|
||||
mod pending_result;
|
||||
mod sys;
|
||||
|
|
@ -242,6 +246,8 @@ impl IoUring {
|
|||
cached_connects: Default::default(),
|
||||
cached_accepts: Default::default(),
|
||||
fd_ids_scratch: Default::default(),
|
||||
iteration: Default::default(),
|
||||
yields: Default::default(),
|
||||
});
|
||||
Ok(Rc::new(Self { ring: data }))
|
||||
}
|
||||
|
|
@ -259,6 +265,15 @@ impl IoUring {
|
|||
pub fn cancel(&self, id: IoUringTaskId) {
|
||||
self.ring.cancel_task(id);
|
||||
}
|
||||
|
||||
pub fn debouncer(&self, max: u64) -> Debouncer {
|
||||
Debouncer {
|
||||
cur: Default::default(),
|
||||
max,
|
||||
iteration: Cell::new(self.ring.iteration.get()),
|
||||
ring: self.ring.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct IoUringData {
|
||||
|
|
@ -306,6 +321,9 @@ struct IoUringData {
|
|||
cached_accepts: Stack<Box<AcceptTask>>,
|
||||
|
||||
fd_ids_scratch: RefCell<Vec<c::c_int>>,
|
||||
|
||||
iteration: NumCell<u64>,
|
||||
yields: SyncQueue<Waker>,
|
||||
}
|
||||
|
||||
unsafe trait Task {
|
||||
|
|
@ -326,6 +344,10 @@ impl IoUringData {
|
|||
fn run(&self) -> Result<(), IoUringError> {
|
||||
let mut to_submit = 0;
|
||||
loop {
|
||||
self.iteration.fetch_add(1);
|
||||
while let Some(ev) = self.yields.pop() {
|
||||
ev.wake();
|
||||
}
|
||||
loop {
|
||||
self.eng.dispatch();
|
||||
if self.destroyed.get() {
|
||||
|
|
@ -336,12 +358,18 @@ impl IoUringData {
|
|||
}
|
||||
}
|
||||
to_submit += self.encode();
|
||||
let res = if to_submit == 0 {
|
||||
io_uring_enter(self.fd.raw(), 0, 1, IORING_ENTER_GETEVENTS)
|
||||
} else if self.to_encode.is_empty() {
|
||||
io_uring_enter(self.fd.raw(), to_submit as _, 1, IORING_ENTER_GETEVENTS)
|
||||
} else {
|
||||
io_uring_enter(self.fd.raw(), !0, 0, 0)
|
||||
let res = {
|
||||
let (to_submit, mut min_complete, flags) = if to_submit == 0 {
|
||||
(0, 1, IORING_ENTER_GETEVENTS)
|
||||
} else if self.to_encode.is_empty() {
|
||||
(to_submit as _, 1, IORING_ENTER_GETEVENTS)
|
||||
} else {
|
||||
(!0, 0, 0)
|
||||
};
|
||||
if self.yields.is_not_empty() {
|
||||
min_complete = 0;
|
||||
}
|
||||
io_uring_enter(self.fd.raw(), to_submit, min_complete, flags)
|
||||
};
|
||||
let mut submitted_any = false;
|
||||
match res {
|
||||
|
|
|
|||
32
src/io_uring/debounce.rs
Normal file
32
src/io_uring/debounce.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use {
|
||||
crate::{io_uring::IoUringData, utils::numcell::NumCell},
|
||||
std::{cell::Cell, future::poll_fn, rc::Rc, task::Poll},
|
||||
};
|
||||
|
||||
pub struct Debouncer {
|
||||
pub(super) cur: NumCell<u64>,
|
||||
pub(super) max: u64,
|
||||
pub(super) iteration: Cell<u64>,
|
||||
pub(super) ring: Rc<IoUringData>,
|
||||
}
|
||||
|
||||
impl Debouncer {
|
||||
pub async fn debounce(&self) {
|
||||
let iteration = self.ring.iteration.get();
|
||||
if self.iteration.replace(iteration) != iteration {
|
||||
self.cur.set(0);
|
||||
}
|
||||
if self.cur.fetch_add(1) > self.max {
|
||||
poll_fn(|ctx| {
|
||||
if self.ring.iteration.get() > iteration {
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
self.ring.yields.push(ctx.waker().clone());
|
||||
Poll::Pending
|
||||
}
|
||||
})
|
||||
.await;
|
||||
self.cur.set(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -124,6 +124,10 @@ unsafe extern "C" fn handle_msg(data: *const u8, msg: *const u8, size: usize) {
|
|||
ServerMessage::InterestReady { .. } => {}
|
||||
ServerMessage::Features { .. } => {}
|
||||
ServerMessage::SwitchEvent { .. } => {}
|
||||
ServerMessage::ClientMatcherMatched { .. } => {}
|
||||
ServerMessage::ClientMatcherUnmatched { .. } => {}
|
||||
ServerMessage::WindowMatcherMatched { .. } => {}
|
||||
ServerMessage::WindowMatcherUnmatched { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -214,14 +218,14 @@ impl TestConfig {
|
|||
}
|
||||
|
||||
pub fn create_split(&self, seat: SeatId, axis: Axis) -> TestResult {
|
||||
self.send(ClientMessage::CreateSplit {
|
||||
self.send(ClientMessage::CreateSeatSplit {
|
||||
seat: Seat(seat.raw() as _),
|
||||
axis,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_mono(&self, seat: SeatId, mono: bool) -> TestResult {
|
||||
self.send(ClientMessage::SetMono {
|
||||
self.send(ClientMessage::SetSeatMono {
|
||||
seat: Seat(seat.raw() as _),
|
||||
mono,
|
||||
})
|
||||
|
|
@ -248,14 +252,14 @@ impl TestConfig {
|
|||
}
|
||||
|
||||
pub fn focus(&self, seat: SeatId, direction: Direction) -> TestResult {
|
||||
self.send(ClientMessage::Focus {
|
||||
self.send(ClientMessage::SeatFocus {
|
||||
seat: Seat(seat.raw() as _),
|
||||
direction,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_fullscreen(&self, seat: SeatId, fs: bool) -> TestResult {
|
||||
self.send(ClientMessage::SetFullscreen {
|
||||
self.send(ClientMessage::SetSeatFullscreen {
|
||||
seat: Seat(seat.raw() as _),
|
||||
fullscreen: fs,
|
||||
})
|
||||
|
|
@ -270,7 +274,7 @@ impl TestConfig {
|
|||
}
|
||||
|
||||
pub fn set_floating(&self, seat: SeatId, floating: bool) -> TestResult {
|
||||
self.send(ClientMessage::SetFloating {
|
||||
self.send(ClientMessage::SetSeatFloating {
|
||||
seat: Seat(seat.raw() as _),
|
||||
floating,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -181,10 +181,10 @@ macro_rules! shared_ids {
|
|||
}
|
||||
|
||||
macro_rules! linear_ids {
|
||||
($ids:ident, $id:ident $(,)?) => {
|
||||
linear_ids!($ids, $id, u32);
|
||||
($(#[$attr1:meta])* $ids:ident, $id:ident $(,)?) => {
|
||||
linear_ids!($(#[$attr1])* $ids, $id, u32);
|
||||
};
|
||||
($ids:ident, $id:ident, $ty:ty $(,)?) => {
|
||||
($(#[$attr1:meta])* $ids:ident, $id:ident, $ty:ty $(,)?) => {
|
||||
pub struct $ids {
|
||||
next: crate::utils::numcell::NumCell<$ty>,
|
||||
}
|
||||
|
|
@ -197,6 +197,7 @@ macro_rules! linear_ids {
|
|||
}
|
||||
}
|
||||
|
||||
$(#[$attr1])*
|
||||
impl $ids {
|
||||
pub fn next(&self) -> $id {
|
||||
$id(self.next.fetch_add(1))
|
||||
|
|
@ -474,6 +475,10 @@ macro_rules! bitflags {
|
|||
self.0 != 0
|
||||
}
|
||||
|
||||
pub fn is_none(self) -> bool {
|
||||
self.0 == 0
|
||||
}
|
||||
|
||||
pub fn all() -> Self {
|
||||
Self(0 $(| $val)*)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ mod cmm;
|
|||
mod compositor;
|
||||
mod config;
|
||||
mod cpu_worker;
|
||||
mod criteria;
|
||||
mod cursor;
|
||||
mod cursor_user;
|
||||
mod damage;
|
||||
|
|
|
|||
|
|
@ -24,9 +24,7 @@ linear_ids!(AcceptorIds, AcceptorId, u64);
|
|||
struct Acceptor {
|
||||
id: AcceptorId,
|
||||
state: Rc<State>,
|
||||
sandbox_engine: Option<String>,
|
||||
app_id: Option<String>,
|
||||
instance_id: Option<String>,
|
||||
metadata: Rc<AcceptorMetadata>,
|
||||
listen_fd: Rc<OwnedFd>,
|
||||
close_fd: Rc<OwnedFd>,
|
||||
caps: ClientCaps,
|
||||
|
|
@ -34,6 +32,14 @@ struct Acceptor {
|
|||
close_future: Cell<Option<SpawnedFuture<()>>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AcceptorMetadata {
|
||||
pub sandboxed: bool,
|
||||
pub sandbox_engine: Option<String>,
|
||||
pub app_id: Option<String>,
|
||||
pub instance_id: Option<String>,
|
||||
}
|
||||
|
||||
impl SecurityContextAcceptors {
|
||||
pub fn clear(&self) {
|
||||
for acceptor in self.acceptors.lock().drain_values() {
|
||||
|
|
@ -54,9 +60,12 @@ impl SecurityContextAcceptors {
|
|||
let acceptor = Rc::new(Acceptor {
|
||||
id: self.ids.next(),
|
||||
state: state.clone(),
|
||||
sandbox_engine,
|
||||
app_id,
|
||||
instance_id,
|
||||
metadata: Rc::new(AcceptorMetadata {
|
||||
sandboxed: true,
|
||||
sandbox_engine,
|
||||
app_id,
|
||||
instance_id,
|
||||
}),
|
||||
listen_fd: listen_fd.clone(),
|
||||
close_fd: close_fd.clone(),
|
||||
caps,
|
||||
|
|
@ -100,7 +109,10 @@ impl Acceptor {
|
|||
}
|
||||
};
|
||||
let id = s.clients.id();
|
||||
if let Err(e) = s.clients.spawn(id, s, fd, self.caps, self.caps) {
|
||||
if let Err(e) = s
|
||||
.clients
|
||||
.spawn(id, s, fd, self.caps, self.caps, &self.metadata)
|
||||
{
|
||||
log::error!("Could not spawn a client: {}", ErrorFmt(e));
|
||||
break;
|
||||
}
|
||||
|
|
@ -119,9 +131,9 @@ impl Display for Acceptor {
|
|||
write!(
|
||||
f,
|
||||
"{}/{}/{}",
|
||||
self.sandbox_engine.as_deref().unwrap_or(""),
|
||||
self.app_id.as_deref().unwrap_or(""),
|
||||
self.instance_id.as_deref().unwrap_or(""),
|
||||
self.metadata.sandbox_engine.as_deref().unwrap_or(""),
|
||||
self.metadata.app_id.as_deref().unwrap_or(""),
|
||||
self.metadata.instance_id.as_deref().unwrap_or(""),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
71
src/state.rs
71
src/state.rs
|
|
@ -15,6 +15,7 @@ use {
|
|||
compositor::LIBEI_SOCKET,
|
||||
config::ConfigProxy,
|
||||
cpu_worker::CpuWorker,
|
||||
criteria::{clm::ClMatcherManager, tlm::TlMatcherManager},
|
||||
cursor::{Cursor, ServerCursors},
|
||||
cursor_user::{CursorUserGroup, CursorUserGroupId, CursorUserGroupIds, CursorUserIds},
|
||||
damage::DamageVisualizer,
|
||||
|
|
@ -56,6 +57,7 @@ use {
|
|||
NoneSurfaceExt,
|
||||
tray::TrayItemIds,
|
||||
wl_subsurface::SubsurfaceIds,
|
||||
x_surface::xwindow::{Xwindow, XwindowId},
|
||||
zwp_idle_inhibitor_v1::{IdleInhibitorId, IdleInhibitorIds, ZwpIdleInhibitorV1},
|
||||
zwp_input_popup_surface_v2::ZwpInputPopupSurfaceV2,
|
||||
},
|
||||
|
|
@ -80,8 +82,8 @@ use {
|
|||
time::Time,
|
||||
tree::{
|
||||
ContainerNode, ContainerSplit, Direction, DisplayNode, FloatNode, LatchListener, Node,
|
||||
NodeIds, NodeVisitorBase, OutputNode, PlaceholderNode, TearingMode, ToplevelNode,
|
||||
ToplevelNodeBase, VrrMode, WorkspaceNode, generic_node_visitor,
|
||||
NodeIds, NodeVisitorBase, OutputNode, PlaceholderNode, TearingMode, ToplevelData,
|
||||
ToplevelNode, ToplevelNodeBase, VrrMode, WorkspaceNode, generic_node_visitor,
|
||||
},
|
||||
utils::{
|
||||
activation_token::ActivationToken, asyncevent::AsyncEvent, bindings::Bindings,
|
||||
|
|
@ -110,6 +112,7 @@ use {
|
|||
jay_config::{
|
||||
PciId,
|
||||
video::{GfxApi, Transform},
|
||||
window::TileState,
|
||||
},
|
||||
std::{
|
||||
cell::{Cell, RefCell},
|
||||
|
|
@ -121,6 +124,7 @@ use {
|
|||
time::Duration,
|
||||
},
|
||||
thiserror::Error,
|
||||
uapi::OwnedFd,
|
||||
};
|
||||
|
||||
pub struct State {
|
||||
|
|
@ -240,6 +244,8 @@ pub struct State {
|
|||
pub float_above_fullscreen: Cell<bool>,
|
||||
pub icons: Icons,
|
||||
pub show_pin_icon: Cell<bool>,
|
||||
pub cl_matcher_manager: ClMatcherManager,
|
||||
pub tl_matcher_manager: TlMatcherManager,
|
||||
}
|
||||
|
||||
// impl Drop for State {
|
||||
|
|
@ -261,11 +267,13 @@ pub struct ScreenlockState {
|
|||
|
||||
pub struct XWaylandState {
|
||||
pub enabled: Cell<bool>,
|
||||
pub pidfd: CloneCell<Option<Rc<OwnedFd>>>,
|
||||
pub handler: RefCell<Option<SpawnedFuture<()>>>,
|
||||
pub queue: Rc<AsyncQueue<XWaylandEvent>>,
|
||||
pub ipc_device_ids: XIpcDeviceIds,
|
||||
pub use_wire_scale: Cell<bool>,
|
||||
pub wire_scale: Cell<Option<i32>>,
|
||||
pub windows: CopyHashMap<XwindowId, Rc<Xwindow>>,
|
||||
}
|
||||
|
||||
pub struct IdleState {
|
||||
|
|
@ -655,23 +663,24 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn map_tiled(self: &Rc<Self>, node: Rc<dyn ToplevelNode>) {
|
||||
let seat = self.seat_queue.last();
|
||||
self.do_map_tiled(seat.as_deref(), node.clone());
|
||||
if node.node_visible() {
|
||||
if let Some(seat) = seat {
|
||||
node.node_do_focus(&seat, Direction::Unspecified);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn do_map_tiled(self: &Rc<Self>, seat: Option<&Rc<WlSeatGlobal>>, node: Rc<dyn ToplevelNode>) {
|
||||
let output = seat
|
||||
pub fn ensure_map_workspace(&self, seat: Option<&Rc<WlSeatGlobal>>) -> Rc<WorkspaceNode> {
|
||||
seat.cloned()
|
||||
.or_else(|| self.seat_queue.last().map(|s| s.deref().clone()))
|
||||
.map(|s| s.get_output())
|
||||
.or_else(|| self.root.outputs.lock().values().next().cloned())
|
||||
.or_else(|| self.dummy_output.get())
|
||||
.unwrap();
|
||||
let ws = output.ensure_workspace();
|
||||
.unwrap()
|
||||
.ensure_workspace()
|
||||
}
|
||||
|
||||
pub fn map_tiled(self: &Rc<Self>, node: Rc<dyn ToplevelNode>) {
|
||||
let seat = self.seat_queue.last();
|
||||
self.do_map_tiled(seat.as_deref(), node.clone());
|
||||
self.focus_after_map(node, seat.as_deref());
|
||||
}
|
||||
|
||||
fn do_map_tiled(self: &Rc<Self>, seat: Option<&Rc<WlSeatGlobal>>, node: Rc<dyn ToplevelNode>) {
|
||||
let ws = self.ensure_map_workspace(seat);
|
||||
self.map_tiled_on(node, &ws);
|
||||
}
|
||||
|
||||
|
|
@ -732,11 +741,22 @@ impl State {
|
|||
Rect::new_sized(x1, y1, width, height).unwrap()
|
||||
};
|
||||
FloatNode::new(self, workspace, position, node.clone());
|
||||
if node.node_visible() {
|
||||
if let Some(seat) = self.seat_queue.last() {
|
||||
node.node_do_focus(&seat, Direction::Unspecified);
|
||||
self.focus_after_map(node, self.seat_queue.last().as_deref());
|
||||
}
|
||||
|
||||
fn focus_after_map(&self, node: Rc<dyn ToplevelNode>, seat: Option<&Rc<WlSeatGlobal>>) {
|
||||
if !node.node_visible() {
|
||||
return;
|
||||
}
|
||||
let Some(seat) = seat else {
|
||||
return;
|
||||
};
|
||||
if let Some(config) = self.config.get() {
|
||||
if !config.auto_focus(node.tl_data()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
node.node_do_focus(&seat, Direction::Unspecified);
|
||||
}
|
||||
|
||||
pub fn show_workspace(&self, seat: &Rc<WlSeatGlobal>, name: &str) {
|
||||
|
|
@ -947,6 +967,15 @@ impl State {
|
|||
self.slow_ei_clients.clear();
|
||||
self.toplevels.clear();
|
||||
self.workspace_managers.clear();
|
||||
self.cl_matcher_manager.clear();
|
||||
self.tl_matcher_manager.clear();
|
||||
}
|
||||
|
||||
pub fn remove_toplevel_id(&self, id: ToplevelIdentifier) {
|
||||
self.toplevels.remove(&id);
|
||||
if let Some(config) = self.config.get() {
|
||||
config.toplevel_removed(id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn damage_hardware_cursors(&self, render: bool) {
|
||||
|
|
@ -1361,6 +1390,10 @@ impl State {
|
|||
};
|
||||
ctx.supports_color_management()
|
||||
}
|
||||
|
||||
pub fn initial_tile_state(&self, data: &ToplevelData) -> Option<TileState> {
|
||||
self.config.get()?.initial_tile_state(data)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
|
|
|||
|
|
@ -23,8 +23,10 @@ use {
|
|||
},
|
||||
wheel::{Wheel, WheelError},
|
||||
wire::{
|
||||
JayCompositor, JayCompositorId, JayDamageTracking, JayDamageTrackingId, WlCallbackId,
|
||||
WlRegistryId, 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,
|
||||
|
|
@ -63,8 +65,8 @@ pub enum ToolClientError {
|
|||
UnalignedMessage,
|
||||
#[error(transparent)]
|
||||
BufFdError(#[from] BufFdError),
|
||||
#[error("The size of the message is not a multiple of 4")]
|
||||
Parsing(&'static str, MsgParserError),
|
||||
#[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")]
|
||||
|
|
@ -195,6 +197,7 @@ impl ToolClient {
|
|||
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(&ObjectId::from_raw(val.id));
|
||||
tc.obj_ids.borrow_mut().release(val.id);
|
||||
});
|
||||
slf.incoming.set(Some(
|
||||
|
|
@ -332,7 +335,7 @@ impl ToolClient {
|
|||
self_id: s.registry,
|
||||
name: s.jay_compositor.0,
|
||||
interface: JayCompositor.name(),
|
||||
version: s.jay_compositor.1.min(17),
|
||||
version: s.jay_compositor.1.min(18),
|
||||
id: id.into(),
|
||||
});
|
||||
self.jay_compositor.set(Some(id));
|
||||
|
|
@ -359,6 +362,88 @@ impl ToolClient {
|
|||
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 struct Singletons {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ use {
|
|||
tree::{
|
||||
ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId,
|
||||
OutputNode, TddType, TileDragDestination, ToplevelData, ToplevelNode, ToplevelNodeBase,
|
||||
WorkspaceNode, default_tile_drag_bounds, walker::NodeVisitor,
|
||||
ToplevelType, WorkspaceNode, default_tile_drag_bounds, toplevel_set_floating,
|
||||
walker::NodeVisitor,
|
||||
},
|
||||
utils::{
|
||||
asyncevent::AsyncEvent,
|
||||
|
|
@ -212,8 +213,9 @@ impl ContainerNode {
|
|||
let child_node_ref = child_node.clone();
|
||||
let mut child_nodes = AHashMap::new();
|
||||
child_nodes.insert(child.node_id(), child_node);
|
||||
let id = state.node_ids.next();
|
||||
let slf = Rc::new_cyclic(|weak| Self {
|
||||
id: state.node_ids.next(),
|
||||
id,
|
||||
split: Cell::new(split),
|
||||
mono_child: CloneCell::new(None),
|
||||
mono_body: Cell::new(Default::default()),
|
||||
|
|
@ -237,7 +239,14 @@ impl ContainerNode {
|
|||
state: state.clone(),
|
||||
render_data: Default::default(),
|
||||
scroller: Default::default(),
|
||||
toplevel_data: ToplevelData::new(state, Default::default(), None, weak),
|
||||
toplevel_data: ToplevelData::new(
|
||||
state,
|
||||
Default::default(),
|
||||
None,
|
||||
ToplevelType::Container,
|
||||
id,
|
||||
weak,
|
||||
),
|
||||
attention_requests: Default::default(),
|
||||
});
|
||||
child.tl_set_parent(slf.clone());
|
||||
|
|
@ -998,6 +1007,9 @@ impl ContainerNode {
|
|||
if let Some(parent) = self.toplevel_data.parent.get() {
|
||||
if !self.toplevel_data.is_fullscreen.get() && parent.cnode_accepts_child(&*child) {
|
||||
parent.cnode_replace_child(self.deref(), child.clone());
|
||||
self.toplevel_data.parent.take();
|
||||
self.child_nodes.borrow_mut().clear();
|
||||
self.tl_destroy();
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
|
@ -1140,7 +1152,7 @@ impl ContainerNode {
|
|||
fn mod_attention_requests(&self, set: bool) {
|
||||
let propagate = self.attention_requests.adj(set);
|
||||
if set || propagate {
|
||||
self.toplevel_data.wants_attention.set(set);
|
||||
self.toplevel_data.set_wants_attention(set);
|
||||
}
|
||||
if propagate {
|
||||
if let Some(parent) = self.toplevel_data.parent.get() {
|
||||
|
|
@ -1236,7 +1248,7 @@ impl ContainerNode {
|
|||
&& kind == SeatOpKind::Move
|
||||
{
|
||||
drop(seat_datas);
|
||||
seat.set_tl_floating(child.node.clone(), true);
|
||||
toplevel_set_floating(&self.state, child.node.clone(), true);
|
||||
return;
|
||||
}
|
||||
seat_data.op = Some(SeatOp {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ use {
|
|||
tree::{
|
||||
ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId,
|
||||
OutputNode, PinnedNode, StackedNode, TileDragDestination, ToplevelNode, WorkspaceNode,
|
||||
walker::NodeVisitor,
|
||||
toplevel_set_floating, walker::NodeVisitor,
|
||||
},
|
||||
utils::{
|
||||
asyncevent::AsyncEvent, clonecell::CloneCell, double_click_state::DoubleClickState,
|
||||
|
|
@ -603,7 +603,7 @@ impl FloatNode {
|
|||
{
|
||||
if let Some(tl) = self.child.get() {
|
||||
drop(cursors);
|
||||
seat.set_tl_floating(tl, false);
|
||||
toplevel_set_floating(&self.state, tl, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use {
|
|||
tree::{
|
||||
ContainerSplit, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId,
|
||||
NodeVisitor, OutputNode, TileDragDestination, ToplevelData, ToplevelNode,
|
||||
ToplevelNodeBase, default_tile_drag_destination,
|
||||
ToplevelNodeBase, ToplevelType, default_tile_drag_destination,
|
||||
},
|
||||
utils::{
|
||||
asyncevent::AsyncEvent, errorfmt::ErrorFmt, on_drop_event::OnDropEvent,
|
||||
|
|
@ -49,12 +49,15 @@ pub async fn placeholder_render_textures(state: Rc<State>) {
|
|||
|
||||
impl PlaceholderNode {
|
||||
pub fn new_for(state: &Rc<State>, node: Rc<dyn ToplevelNode>, slf: &Weak<Self>) -> Self {
|
||||
let id = state.node_ids.next();
|
||||
Self {
|
||||
id: state.node_ids.next(),
|
||||
id,
|
||||
toplevel: ToplevelData::new(
|
||||
state,
|
||||
node.tl_data().title.borrow().clone(),
|
||||
node.node_client(),
|
||||
ToplevelType::Placeholder(Some(node.tl_data().identifier.get())),
|
||||
id,
|
||||
slf,
|
||||
),
|
||||
destroyed: Default::default(),
|
||||
|
|
@ -65,9 +68,17 @@ impl PlaceholderNode {
|
|||
}
|
||||
|
||||
pub fn new_empty(state: &Rc<State>, slf: &Weak<Self>) -> Self {
|
||||
let id = state.node_ids.next();
|
||||
Self {
|
||||
id: state.node_ids.next(),
|
||||
toplevel: ToplevelData::new(state, String::new(), None, slf),
|
||||
id,
|
||||
toplevel: ToplevelData::new(
|
||||
state,
|
||||
String::new(),
|
||||
None,
|
||||
ToplevelType::Placeholder(None),
|
||||
id,
|
||||
slf,
|
||||
),
|
||||
destroyed: Default::default(),
|
||||
update_textures_scheduled: Default::default(),
|
||||
state: state.clone(),
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,25 @@
|
|||
use {
|
||||
crate::{
|
||||
client::{Client, ClientId},
|
||||
criteria::{
|
||||
CritDestroyListener, CritMatcherId,
|
||||
tlm::{
|
||||
TL_CHANGED_APP_ID, TL_CHANGED_DESTROYED, TL_CHANGED_FLOATING,
|
||||
TL_CHANGED_FULLSCREEN, TL_CHANGED_NEW, TL_CHANGED_TITLE, TL_CHANGED_URGENT,
|
||||
TL_CHANGED_VISIBLE, TL_CHANGED_WORKSPACE, TlMatcherChange,
|
||||
},
|
||||
},
|
||||
ifs::{
|
||||
ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
|
||||
ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1,
|
||||
ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1,
|
||||
jay_screencast::JayScreencast,
|
||||
jay_toplevel::JayToplevel,
|
||||
wl_seat::{NodeSeatState, collect_kb_foci, collect_kb_foci2},
|
||||
wl_surface::WlSurface,
|
||||
wl_seat::{NodeSeatState, SeatId, collect_kb_foci, collect_kb_foci2},
|
||||
wl_surface::{
|
||||
WlSurface, x_surface::xwindow::XwindowData,
|
||||
xdg_surface::xdg_toplevel::XdgToplevelToplevelData,
|
||||
},
|
||||
},
|
||||
rect::Rect,
|
||||
state::State,
|
||||
|
|
@ -30,6 +41,7 @@ use {
|
|||
JayToplevelId,
|
||||
},
|
||||
},
|
||||
jay_config::{window, window::WindowType},
|
||||
std::{
|
||||
cell::{Cell, RefCell},
|
||||
ops::Deref,
|
||||
|
|
@ -87,12 +99,22 @@ impl<T: ToplevelNodeBase> ToplevelNode for T {
|
|||
.clone_from(&title);
|
||||
data.placeholder.tl_title_changed();
|
||||
}
|
||||
data.property_changed(TL_CHANGED_TITLE);
|
||||
}
|
||||
|
||||
fn tl_set_parent(&self, parent: Rc<dyn ContainingNode>) {
|
||||
let data = self.tl_data();
|
||||
data.parent.set(Some(parent.clone()));
|
||||
data.is_floating.set(parent.node_is_float());
|
||||
let parent_was_none = data.parent.set(Some(parent.clone())).is_none();
|
||||
if parent_was_none {
|
||||
data.mapped_during_iteration.set(data.state.eng.iteration());
|
||||
data.property_changed(TL_CHANGED_NEW);
|
||||
}
|
||||
let was_floating = data.is_floating.get();
|
||||
let is_floating = parent.node_is_float();
|
||||
if was_floating != is_floating {
|
||||
data.property_changed(TL_CHANGED_FLOATING);
|
||||
}
|
||||
data.is_floating.set(is_floating);
|
||||
self.tl_set_workspace(&parent.cnode_workspace());
|
||||
}
|
||||
|
||||
|
|
@ -109,6 +131,7 @@ impl<T: ToplevelNodeBase> ToplevelNode for T {
|
|||
let data = self.tl_data();
|
||||
let prev = data.workspace.set(Some(ws.clone()));
|
||||
self.tl_set_workspace_ext(ws);
|
||||
self.tl_data().property_changed(TL_CHANGED_WORKSPACE);
|
||||
let prev_id = prev.map(|p| p.output.get().id);
|
||||
let new_id = Some(ws.output.get().id);
|
||||
if prev_id != new_id {
|
||||
|
|
@ -254,7 +277,27 @@ impl ToplevelOpt {
|
|||
}
|
||||
}
|
||||
|
||||
pub enum ToplevelType {
|
||||
Container,
|
||||
Placeholder(Option<ToplevelIdentifier>),
|
||||
XdgToplevel(Rc<XdgToplevelToplevelData>),
|
||||
XWindow(Rc<XwindowData>),
|
||||
}
|
||||
|
||||
impl ToplevelType {
|
||||
pub fn to_window_type(&self) -> WindowType {
|
||||
match self {
|
||||
ToplevelType::Container => window::CONTAINER,
|
||||
ToplevelType::Placeholder { .. } => window::PLACEHOLDER,
|
||||
ToplevelType::XdgToplevel { .. } => window::XDG_TOPLEVEL,
|
||||
ToplevelType::XWindow { .. } => window::X_WINDOW,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ToplevelData {
|
||||
pub node_id: NodeId,
|
||||
pub kind: ToplevelType,
|
||||
pub self_active: Cell<bool>,
|
||||
pub client: Option<Rc<Client>>,
|
||||
pub state: Rc<State>,
|
||||
|
|
@ -269,6 +312,7 @@ pub struct ToplevelData {
|
|||
pub workspace: CloneCell<Option<Rc<WorkspaceNode>>>,
|
||||
pub title: RefCell<String>,
|
||||
pub parent: CloneCell<Option<Rc<dyn ContainingNode>>>,
|
||||
pub mapped_during_iteration: Cell<u64>,
|
||||
pub pos: Cell<Rect>,
|
||||
pub desired_extents: Cell<Rect>,
|
||||
pub seat_state: NodeSeatState,
|
||||
|
|
@ -284,6 +328,10 @@ pub struct ToplevelData {
|
|||
pub ext_copy_sessions:
|
||||
CopyHashMap<(ClientId, ExtImageCopyCaptureSessionV1Id), Rc<ExtImageCopyCaptureSessionV1>>,
|
||||
pub slf: Weak<dyn ToplevelNode>,
|
||||
pub destroyed: CopyHashMap<CritMatcherId, Weak<dyn CritDestroyListener<ToplevelData>>>,
|
||||
pub changed_properties: Cell<TlMatcherChange>,
|
||||
pub just_mapped_scheduled: Cell<bool>,
|
||||
pub seat_foci: CopyHashMap<SeatId, ()>,
|
||||
}
|
||||
|
||||
impl ToplevelData {
|
||||
|
|
@ -291,11 +339,16 @@ impl ToplevelData {
|
|||
state: &Rc<State>,
|
||||
title: String,
|
||||
client: Option<Rc<Client>>,
|
||||
kind: ToplevelType,
|
||||
node_id: impl Into<NodeId>,
|
||||
slf: &Weak<T>,
|
||||
) -> Self {
|
||||
let node_id = node_id.into();
|
||||
let id = toplevel_identifier();
|
||||
state.toplevels.set(id, slf.clone());
|
||||
Self {
|
||||
node_id,
|
||||
kind,
|
||||
self_active: Cell::new(false),
|
||||
client,
|
||||
state: state.clone(),
|
||||
|
|
@ -310,6 +363,7 @@ impl ToplevelData {
|
|||
workspace: Default::default(),
|
||||
title: RefCell::new(title),
|
||||
parent: Default::default(),
|
||||
mapped_during_iteration: Cell::new(0),
|
||||
pos: Default::default(),
|
||||
desired_extents: Default::default(),
|
||||
seat_state: Default::default(),
|
||||
|
|
@ -323,6 +377,10 @@ impl ToplevelData {
|
|||
jay_screencasts: Default::default(),
|
||||
ext_copy_sessions: Default::default(),
|
||||
slf: slf.clone(),
|
||||
destroyed: Default::default(),
|
||||
changed_properties: Default::default(),
|
||||
just_mapped_scheduled: Cell::new(false),
|
||||
seat_foci: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -359,6 +417,20 @@ impl ToplevelData {
|
|||
(width, height)
|
||||
}
|
||||
|
||||
pub fn property_changed(&self, change: TlMatcherChange) {
|
||||
let mgr = &self.state.tl_matcher_manager;
|
||||
let props = self.changed_properties.get();
|
||||
if props.is_none() && mgr.has_no_interest(self, change) {
|
||||
return;
|
||||
}
|
||||
self.changed_properties.set(props | change);
|
||||
if props.is_none() && change.is_some() {
|
||||
if let Some(node) = self.slf.upgrade() {
|
||||
mgr.changed(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn destroy_node(&self, node: &dyn Node) {
|
||||
for jay_tl in self.jay_toplevels.lock().drain_values() {
|
||||
jay_tl.destroy();
|
||||
|
|
@ -372,7 +444,7 @@ impl ToplevelData {
|
|||
{
|
||||
let id = toplevel_identifier();
|
||||
let prev = self.identifier.replace(id);
|
||||
self.state.toplevels.remove(&prev);
|
||||
self.state.remove_toplevel_id(prev);
|
||||
self.state.toplevels.set(id, self.slf.clone());
|
||||
}
|
||||
{
|
||||
|
|
@ -382,6 +454,7 @@ impl ToplevelData {
|
|||
}
|
||||
}
|
||||
self.detach_node(node);
|
||||
self.property_changed(TL_CHANGED_DESTROYED);
|
||||
}
|
||||
|
||||
pub fn detach_node(&self, node: &dyn Node) {
|
||||
|
|
@ -444,11 +517,16 @@ impl ToplevelData {
|
|||
}
|
||||
|
||||
pub fn set_app_id(&self, app_id: &str) {
|
||||
*self.app_id.borrow_mut() = app_id.to_string();
|
||||
let dst = &mut *self.app_id.borrow_mut();
|
||||
if *dst == app_id {
|
||||
return;
|
||||
}
|
||||
*dst = app_id.to_string();
|
||||
for handle in self.handles.lock().values() {
|
||||
handle.send_app_id(app_id);
|
||||
handle.send_done();
|
||||
}
|
||||
self.property_changed(TL_CHANGED_APP_ID)
|
||||
}
|
||||
|
||||
pub fn set_fullscreen(
|
||||
|
|
@ -510,6 +588,7 @@ impl ToplevelData {
|
|||
});
|
||||
drop(data);
|
||||
self.is_fullscreen.set(true);
|
||||
self.property_changed(TL_CHANGED_FULLSCREEN);
|
||||
node.tl_set_parent(ws.clone());
|
||||
ws.set_fullscreen_node(&node);
|
||||
node.clone()
|
||||
|
|
@ -532,6 +611,7 @@ impl ToplevelData {
|
|||
}
|
||||
};
|
||||
self.is_fullscreen.set(false);
|
||||
self.property_changed(TL_CHANGED_FULLSCREEN);
|
||||
match fd.workspace.fullscreen.get() {
|
||||
None => {
|
||||
log::error!(
|
||||
|
|
@ -552,7 +632,7 @@ impl ToplevelData {
|
|||
state.map_tiled(node);
|
||||
return;
|
||||
}
|
||||
let parent = fd.placeholder.tl_data().parent.get().unwrap();
|
||||
let parent = fd.placeholder.tl_data().parent.take().unwrap();
|
||||
parent.cnode_replace_child(fd.placeholder.deref(), node.clone());
|
||||
if node.node_visible() {
|
||||
let kb_foci = collect_kb_foci(fd.placeholder.clone());
|
||||
|
|
@ -560,13 +640,13 @@ impl ToplevelData {
|
|||
node.clone().node_do_focus(&seat, Direction::Unspecified);
|
||||
}
|
||||
}
|
||||
fd.placeholder
|
||||
.node_seat_state()
|
||||
.destroy_node(fd.placeholder.deref());
|
||||
fd.placeholder.tl_destroy();
|
||||
}
|
||||
|
||||
pub fn set_visible(&self, node: &dyn Node, visible: bool) {
|
||||
self.visible.set(visible);
|
||||
if self.visible.replace(visible) != visible {
|
||||
self.property_changed(TL_CHANGED_VISIBLE);
|
||||
}
|
||||
self.seat_state.set_visible(node, visible);
|
||||
for sc in self.jay_screencasts.lock().values() {
|
||||
sc.update_latch_listener();
|
||||
|
|
@ -580,7 +660,7 @@ impl ToplevelData {
|
|||
if !self.requested_attention.replace(false) {
|
||||
return;
|
||||
}
|
||||
self.wants_attention.set(false);
|
||||
self.set_wants_attention(false);
|
||||
if let Some(parent) = self.parent.get() {
|
||||
parent.cnode_child_attention_request_changed(node, false);
|
||||
}
|
||||
|
|
@ -593,12 +673,18 @@ impl ToplevelData {
|
|||
if self.requested_attention.replace(true) {
|
||||
return;
|
||||
}
|
||||
self.wants_attention.set(true);
|
||||
self.set_wants_attention(true);
|
||||
if let Some(parent) = self.parent.get() {
|
||||
parent.cnode_child_attention_request_changed(node, true);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_wants_attention(&self, value: bool) {
|
||||
if self.wants_attention.replace(value) != value {
|
||||
self.property_changed(TL_CHANGED_URGENT);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn output(&self) -> Rc<OutputNode> {
|
||||
match self.output_opt() {
|
||||
None => self.state.dummy_output.get().unwrap(),
|
||||
|
|
@ -618,11 +704,15 @@ impl ToplevelData {
|
|||
};
|
||||
(0, 0)
|
||||
}
|
||||
|
||||
pub fn just_mapped(&self) -> bool {
|
||||
self.mapped_during_iteration.get() == self.state.eng.iteration()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ToplevelData {
|
||||
fn drop(&mut self) {
|
||||
self.state.toplevels.remove(&self.identifier.get());
|
||||
self.state.remove_toplevel_id(self.identifier.get());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -664,3 +754,82 @@ pub fn default_tile_drag_bounds<T: ToplevelNodeBase + ?Sized>(t: &T, split: Cont
|
|||
ContainerSplit::Vertical => t.node_absolute_position().height() / FACTOR,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toplevel_parent_container(tl: &dyn ToplevelNode) -> Option<Rc<ContainerNode>> {
|
||||
if let Some(parent) = tl.tl_data().parent.get() {
|
||||
if let Some(container) = parent.node_into_container() {
|
||||
return Some(container);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn toplevel_create_split(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, axis: ContainerSplit) {
|
||||
if tl.tl_data().is_fullscreen.get() {
|
||||
return;
|
||||
}
|
||||
let ws = match tl.tl_data().workspace.get() {
|
||||
Some(ws) => ws,
|
||||
_ => return,
|
||||
};
|
||||
let pn = match tl.tl_data().parent.get() {
|
||||
Some(pn) => pn,
|
||||
_ => return,
|
||||
};
|
||||
if let Some(pn) = pn.node_into_containing_node() {
|
||||
let cn = ContainerNode::new(state, &ws, tl.clone(), axis);
|
||||
pn.cnode_replace_child(&*tl, cn);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toplevel_set_floating(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, floating: bool) {
|
||||
let data = tl.tl_data();
|
||||
if data.is_fullscreen.get() {
|
||||
return;
|
||||
}
|
||||
if data.is_floating.get() == floating {
|
||||
return;
|
||||
}
|
||||
let parent = match data.parent.get() {
|
||||
Some(p) => p,
|
||||
_ => return,
|
||||
};
|
||||
if !floating {
|
||||
parent.cnode_remove_child2(&*tl, true);
|
||||
state.map_tiled(tl);
|
||||
} else if let Some(ws) = data.workspace.get() {
|
||||
parent.cnode_remove_child2(&*tl, true);
|
||||
let (width, height) = data.float_size(&ws);
|
||||
state.map_floating(tl, width, height, &ws, None);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toplevel_set_workspace(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, ws: &Rc<WorkspaceNode>) {
|
||||
if tl.tl_data().is_fullscreen.get() {
|
||||
return;
|
||||
}
|
||||
let old_ws = match tl.tl_data().workspace.get() {
|
||||
Some(ws) => ws,
|
||||
_ => return,
|
||||
};
|
||||
if old_ws.id == ws.id {
|
||||
return;
|
||||
}
|
||||
let cn = match tl.tl_data().parent.get() {
|
||||
Some(cn) => cn,
|
||||
_ => return,
|
||||
};
|
||||
let kb_foci = collect_kb_foci(tl.clone());
|
||||
cn.cnode_remove_child2(&*tl, true);
|
||||
if !ws.visible.get() {
|
||||
for focus in kb_foci {
|
||||
old_ws.clone().node_do_focus(&focus, Direction::Unspecified);
|
||||
}
|
||||
}
|
||||
if tl.tl_data().is_floating.get() {
|
||||
let (width, height) = tl.tl_data().float_size(ws);
|
||||
state.map_floating(tl.clone(), width, height, ws, None);
|
||||
} else {
|
||||
state.map_tiled_on(tl, ws);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ pub mod oserror;
|
|||
pub mod page_size;
|
||||
pub mod pending_serial;
|
||||
pub mod pid_info;
|
||||
pub mod pidfd_send_signal;
|
||||
pub mod process_name;
|
||||
pub mod ptr_ext;
|
||||
pub mod queue;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
use {
|
||||
crate::utils::{
|
||||
linkedlist::NodeRef,
|
||||
ptr_ext::{MutPtrExt, PtrExt},
|
||||
crate::{
|
||||
tree::NodeId,
|
||||
utils::{
|
||||
linkedlist::NodeRef,
|
||||
ptr_ext::{MutPtrExt, PtrExt},
|
||||
},
|
||||
},
|
||||
jay_config::keyboard::mods::Modifiers,
|
||||
jay_config::{keyboard::mods::Modifiers, window::Window},
|
||||
std::{
|
||||
cell::UnsafeCell,
|
||||
fmt::{Debug, Formatter},
|
||||
|
|
@ -97,3 +100,7 @@ unsafe impl UnsafeCellCloneSafe for usize {}
|
|||
unsafe impl<A: UnsafeCellCloneSafe, B: UnsafeCellCloneSafe> UnsafeCellCloneSafe for (A, B) {}
|
||||
|
||||
unsafe impl UnsafeCellCloneSafe for Modifiers {}
|
||||
|
||||
unsafe impl UnsafeCellCloneSafe for NodeId {}
|
||||
|
||||
unsafe impl UnsafeCellCloneSafe for Window {}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use {
|
||||
crate::utils::{errorfmt::ErrorFmt, oserror::OsError},
|
||||
bstr::ByteSlice,
|
||||
std::os::unix::ffi::OsStrExt,
|
||||
uapi::{OwnedFd, c},
|
||||
};
|
||||
|
||||
|
|
@ -8,6 +9,7 @@ pub struct PidInfo {
|
|||
pub uid: c::uid_t,
|
||||
pub pid: c::pid_t,
|
||||
pub comm: String,
|
||||
pub exe: String,
|
||||
}
|
||||
|
||||
pub fn get_pid_info(uid: c::uid_t, pid: c::pid_t) -> PidInfo {
|
||||
|
|
@ -18,7 +20,24 @@ pub fn get_pid_info(uid: c::uid_t, pid: c::pid_t) -> PidInfo {
|
|||
"Unknown".to_string()
|
||||
}
|
||||
};
|
||||
PidInfo { uid, pid, comm }
|
||||
let exe = match std::fs::read_link(format!("/proc/{}/exe", pid)) {
|
||||
Ok(name) => name
|
||||
.as_os_str()
|
||||
.as_bytes()
|
||||
.trim_ascii_end()
|
||||
.as_bstr()
|
||||
.to_string(),
|
||||
Err(e) => {
|
||||
log::warn!("Could not read `exe` of pid {}: {}", pid, ErrorFmt(e));
|
||||
"Unknown".to_string()
|
||||
}
|
||||
};
|
||||
PidInfo {
|
||||
uid,
|
||||
pid,
|
||||
comm,
|
||||
exe,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_socket_creds(socket: &OwnedFd) -> Option<(c::uid_t, c::pid_t)> {
|
||||
|
|
|
|||
23
src/utils/pidfd_send_signal.rs
Normal file
23
src/utils/pidfd_send_signal.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
use {
|
||||
crate::utils::oserror::OsError,
|
||||
c::{c_int, syscall},
|
||||
std::{ptr, rc::Rc},
|
||||
uapi::{
|
||||
OwnedFd,
|
||||
c::{self, SYS_pidfd_send_signal, siginfo_t},
|
||||
map_err,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn pidfd_send_signal(pidfd: &Rc<OwnedFd>, signal: c_int) -> Result<(), OsError> {
|
||||
let res = unsafe {
|
||||
syscall(
|
||||
SYS_pidfd_send_signal,
|
||||
pidfd.raw(),
|
||||
signal,
|
||||
ptr::null_mut::<siginfo_t>(),
|
||||
0,
|
||||
)
|
||||
};
|
||||
map_err!(res).map(drop).map_err(|e| e.into())
|
||||
}
|
||||
|
|
@ -44,7 +44,6 @@ impl<T> SyncQueue<T> {
|
|||
unsafe { self.el.get().deref_mut().is_empty() }
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
pub fn is_not_empty(&self) -> bool {
|
||||
!self.is_empty()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
use {
|
||||
crate::utils::opaque::{OPAQUE_LEN, Opaque, OpaqueError, opaque},
|
||||
crate::utils::{
|
||||
clonecell::UnsafeCellCloneSafe,
|
||||
opaque::{OPAQUE_LEN, Opaque, OpaqueError, opaque},
|
||||
},
|
||||
arrayvec::ArrayString,
|
||||
std::{
|
||||
fmt::{Display, Formatter},
|
||||
|
|
@ -10,6 +13,8 @@ use {
|
|||
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
|
||||
pub struct ToplevelIdentifier(Opaque);
|
||||
|
||||
unsafe impl UnsafeCellCloneSafe for ToplevelIdentifier {}
|
||||
|
||||
pub fn toplevel_identifier() -> ToplevelIdentifier {
|
||||
ToplevelIdentifier(opaque())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ impl JayToplevelEventHandler for UsrJayToplevel {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn client_id(&self, _ev: ClientId, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn done(&self, _ev: Done, slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
if let Some(owner) = self.owner.get() {
|
||||
owner.done(slf);
|
||||
|
|
|
|||
|
|
@ -12,9 +12,12 @@ use {
|
|||
wl_surface::x_surface::xwindow::{Xwindow, XwindowData},
|
||||
},
|
||||
io_uring::IoUringError,
|
||||
security_context_acceptor::AcceptorMetadata,
|
||||
state::State,
|
||||
user_session::import_environment,
|
||||
utils::{buf::Buf, errorfmt::ErrorFmt, line_logger::log_lines, oserror::OsError},
|
||||
utils::{
|
||||
buf::Buf, errorfmt::ErrorFmt, line_logger::log_lines, on_drop::OnDrop, oserror::OsError,
|
||||
},
|
||||
wire::WlSurfaceId,
|
||||
xcon::XconError,
|
||||
xwayland::{
|
||||
|
|
@ -177,6 +180,7 @@ async fn run(
|
|||
ClientCaps::all(),
|
||||
ClientCaps::all(),
|
||||
true,
|
||||
&Rc::new(AcceptorMetadata::default()),
|
||||
);
|
||||
let client = match client {
|
||||
Ok(c) => c,
|
||||
|
|
@ -185,6 +189,10 @@ async fn run(
|
|||
state.update_xwayland_wire_scale();
|
||||
state.ring.readable(&Rc::new(dfdread)).await?;
|
||||
state.xwayland.queue.clear();
|
||||
state.xwayland.pidfd.set(Some(pidfd.clone()));
|
||||
let _remove_pidfd = OnDrop(|| {
|
||||
state.xwayland.pidfd.take();
|
||||
});
|
||||
{
|
||||
let shared = Rc::new(XwmShared::default());
|
||||
let wm = match Wm::get(state, client, wm1, &shared).await {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use {
|
|||
crate::{
|
||||
async_engine::SpawnedFuture,
|
||||
client::Client,
|
||||
criteria::tlm::{TL_CHANGED_CLASS_INST, TL_CHANGED_ROLE},
|
||||
ifs::{
|
||||
ipc::{
|
||||
DataOfferId, DataSourceId, DynDataOffer, DynDataSource, IpcLocation, IpcVtable,
|
||||
|
|
@ -59,7 +60,7 @@ use {
|
|||
xwayland::{XWaylandError, XWaylandEvent},
|
||||
},
|
||||
ahash::{AHashMap, AHashSet},
|
||||
bstr::ByteSlice,
|
||||
bstr::{ByteSlice, ByteVec},
|
||||
futures_util::{FutureExt, select},
|
||||
smallvec::SmallVec,
|
||||
std::{
|
||||
|
|
@ -1085,6 +1086,11 @@ impl Wm {
|
|||
}
|
||||
|
||||
async fn load_window_wm_window_role(&self, data: &Rc<XwindowData>) {
|
||||
let property_changed = || {
|
||||
if let Some(window) = data.window.get() {
|
||||
window.toplevel_data.property_changed(TL_CHANGED_ROLE);
|
||||
}
|
||||
};
|
||||
let mut buf = vec![];
|
||||
match self
|
||||
.c
|
||||
|
|
@ -1100,6 +1106,7 @@ impl Wm {
|
|||
}
|
||||
Err(XconError::PropertyUnavailable) => {
|
||||
data.info.role.borrow_mut().take();
|
||||
property_changed();
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
|
|
@ -1111,11 +1118,17 @@ impl Wm {
|
|||
}
|
||||
}
|
||||
// log::info!("{} role {}", data.window_id, buf.as_bstr());
|
||||
*data.info.role.borrow_mut() = Some(buf.into());
|
||||
*data.info.role.borrow_mut() = Some(buf.into_string_lossy());
|
||||
property_changed();
|
||||
}
|
||||
|
||||
async fn load_window_wm_class(&self, data: &Rc<XwindowData>) {
|
||||
let mut buf = vec![];
|
||||
let property_changed = || {
|
||||
if let Some(window) = data.window.get() {
|
||||
window.toplevel_data.property_changed(TL_CHANGED_CLASS_INST);
|
||||
}
|
||||
};
|
||||
match self
|
||||
.c
|
||||
.get_property::<u8>(data.window_id, ATOM_WM_CLASS, 0, &mut buf)
|
||||
|
|
@ -1130,6 +1143,7 @@ impl Wm {
|
|||
Err(XconError::PropertyUnavailable) => {
|
||||
data.info.instance.borrow_mut().take();
|
||||
data.info.class.borrow_mut().take();
|
||||
property_changed();
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
|
|
@ -1138,8 +1152,10 @@ impl Wm {
|
|||
}
|
||||
}
|
||||
let mut iter = buf.split(|c| *c == 0);
|
||||
*data.info.instance.borrow_mut() = Some(iter.next().unwrap_or(&[]).to_vec().into());
|
||||
*data.info.class.borrow_mut() = Some(iter.next().unwrap_or(&[]).to_vec().into());
|
||||
let mut map = || Some(iter.next().unwrap_or(&[]).to_str_lossy().into_owned());
|
||||
*data.info.instance.borrow_mut() = map();
|
||||
*data.info.class.borrow_mut() = map();
|
||||
property_changed();
|
||||
}
|
||||
|
||||
async fn load_window_wm_name2(&self, data: &Rc<XwindowData>, prop: u32, name: &str) {
|
||||
|
|
@ -1459,6 +1475,7 @@ impl Wm {
|
|||
return;
|
||||
}
|
||||
};
|
||||
self.state.xwayland.windows.set(window.id, window.clone());
|
||||
data.window.set(Some(window.clone()));
|
||||
{
|
||||
self.load_window_wm_class(data).await;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ use {
|
|||
status::MessageFormat,
|
||||
theme::Color,
|
||||
video::{ColorSpace, Format, GfxApi, TearingMode, TransferFunction, Transform, VrrMode},
|
||||
window::{TileState, WindowType},
|
||||
xwayland::XScalingMode,
|
||||
},
|
||||
std::{
|
||||
|
|
@ -53,15 +54,20 @@ pub enum SimpleCommand {
|
|||
ReloadConfigToml,
|
||||
Split(Axis),
|
||||
ToggleFloating,
|
||||
SetFloating(bool),
|
||||
ToggleFullscreen,
|
||||
SetFullscreen(bool),
|
||||
ToggleMono,
|
||||
SetMono(bool),
|
||||
ToggleSplit,
|
||||
SetSplit(Axis),
|
||||
Forward(bool),
|
||||
EnableWindowManagement(bool),
|
||||
SetFloatAboveFullscreen(bool),
|
||||
ToggleFloatAboveFullscreen,
|
||||
SetFloatPinned(bool),
|
||||
ToggleFloatPinned,
|
||||
KillClient,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -194,6 +200,85 @@ pub enum OutputMatch {
|
|||
},
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct GenericMatch<Match> {
|
||||
pub name: Option<String>,
|
||||
pub not: Option<Box<Match>>,
|
||||
pub all: Option<Vec<Match>>,
|
||||
pub any: Option<Vec<Match>>,
|
||||
pub exactly: Option<MatchExactly<Match>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MatchExactly<Match> {
|
||||
pub num: usize,
|
||||
pub list: Vec<Match>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ClientRule {
|
||||
pub name: Option<String>,
|
||||
pub match_: ClientMatch,
|
||||
pub action: Option<Action>,
|
||||
pub latch: Option<Action>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct ClientMatch {
|
||||
pub generic: GenericMatch<Self>,
|
||||
pub sandbox_engine: Option<String>,
|
||||
pub sandbox_engine_regex: Option<String>,
|
||||
pub sandbox_app_id: Option<String>,
|
||||
pub sandbox_app_id_regex: Option<String>,
|
||||
pub sandbox_instance_id: Option<String>,
|
||||
pub sandbox_instance_id_regex: Option<String>,
|
||||
pub sandboxed: Option<bool>,
|
||||
pub uid: Option<i32>,
|
||||
pub pid: Option<i32>,
|
||||
pub is_xwayland: Option<bool>,
|
||||
pub comm: Option<String>,
|
||||
pub comm_regex: Option<String>,
|
||||
pub exe: Option<String>,
|
||||
pub exe_regex: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WindowRule {
|
||||
pub name: Option<String>,
|
||||
pub match_: WindowMatch,
|
||||
pub action: Option<Action>,
|
||||
pub latch: Option<Action>,
|
||||
pub auto_focus: Option<bool>,
|
||||
pub initial_tile_state: Option<TileState>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct WindowMatch {
|
||||
pub generic: GenericMatch<Self>,
|
||||
pub types: Option<WindowType>,
|
||||
pub client: Option<ClientMatch>,
|
||||
pub title: Option<String>,
|
||||
pub title_regex: Option<String>,
|
||||
pub app_id: Option<String>,
|
||||
pub app_id_regex: Option<String>,
|
||||
pub floating: Option<bool>,
|
||||
pub visible: Option<bool>,
|
||||
pub urgent: Option<bool>,
|
||||
pub focused: Option<bool>,
|
||||
pub fullscreen: Option<bool>,
|
||||
pub just_mapped: Option<bool>,
|
||||
pub tag: Option<String>,
|
||||
pub tag_regex: Option<String>,
|
||||
pub x_class: Option<String>,
|
||||
pub x_class_regex: Option<String>,
|
||||
pub x_instance: Option<String>,
|
||||
pub x_instance_regex: Option<String>,
|
||||
pub x_role: Option<String>,
|
||||
pub x_role_regex: Option<String>,
|
||||
pub workspace: Option<String>,
|
||||
pub workspace_regex: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DrmDeviceMatch {
|
||||
Any(Vec<DrmDeviceMatch>),
|
||||
|
|
@ -391,6 +476,8 @@ pub struct Config {
|
|||
pub float: Option<Float>,
|
||||
pub named_actions: Vec<NamedAction>,
|
||||
pub max_action_depth: u64,
|
||||
pub client_rules: Vec<ClientRule>,
|
||||
pub window_rules: Vec<WindowRule>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ use {
|
|||
|
||||
pub mod action;
|
||||
mod actions;
|
||||
mod client_match;
|
||||
mod client_rule;
|
||||
mod color;
|
||||
pub mod color_management;
|
||||
pub mod config;
|
||||
|
|
@ -35,8 +37,12 @@ pub mod shortcuts;
|
|||
mod status;
|
||||
mod tearing;
|
||||
mod theme;
|
||||
mod tile_state;
|
||||
mod ui_drag;
|
||||
mod vrr;
|
||||
mod window_match;
|
||||
mod window_rule;
|
||||
mod window_type;
|
||||
mod xwayland;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
|
|
|||
|
|
@ -104,12 +104,20 @@ impl ActionParser<'_> {
|
|||
"split-horizontal" => Split(Horizontal),
|
||||
"split-vertical" => Split(Vertical),
|
||||
"toggle-split" => ToggleSplit,
|
||||
"tile-horizontal" => SetSplit(Horizontal),
|
||||
"tile-vertical" => SetSplit(Vertical),
|
||||
"toggle-mono" => ToggleMono,
|
||||
"show-single" => SetMono(true),
|
||||
"show-all" => SetMono(false),
|
||||
"toggle-fullscreen" => ToggleFullscreen,
|
||||
"enter-fullscreen" => SetFullscreen(true),
|
||||
"exit-fullscreen" => SetFullscreen(false),
|
||||
"focus-parent" => FocusParent,
|
||||
"close" => Close,
|
||||
"disable-pointer-constraint" => DisablePointerConstraint,
|
||||
"toggle-floating" => ToggleFloating,
|
||||
"float" => SetFloating(true),
|
||||
"tile" => SetFloating(false),
|
||||
"quit" => Quit,
|
||||
"reload-config-toml" => ReloadConfigToml,
|
||||
"reload-config-so" => ReloadConfigSo,
|
||||
|
|
@ -124,6 +132,7 @@ impl ActionParser<'_> {
|
|||
"pin-float" => SetFloatPinned(true),
|
||||
"unpin-float" => SetFloatPinned(false),
|
||||
"toggle-float-pinned" => ToggleFloatPinned,
|
||||
"kill-client" => KillClient,
|
||||
_ => {
|
||||
return Err(
|
||||
ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span)
|
||||
|
|
|
|||
160
toml-config/src/config/parsers/client_match.rs
Normal file
160
toml-config/src/config/parsers/client_match.rs
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
ClientMatch, GenericMatch, MatchExactly,
|
||||
context::Context,
|
||||
extractor::{Extractor, ExtractorError, arr, bol, n32, opt, s32, str, val},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
},
|
||||
toml::{
|
||||
toml_span::{DespanExt, Span, Spanned},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ClientMatchParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Extract(#[from] ExtractorError),
|
||||
}
|
||||
|
||||
pub struct ClientMatchParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl Parser for ClientMatchParser<'_> {
|
||||
type Value = ClientMatch;
|
||||
type Error = ClientMatchParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut ext = Extractor::new(self.0, span, table);
|
||||
let (
|
||||
(
|
||||
name,
|
||||
not_val,
|
||||
all_val,
|
||||
any_val,
|
||||
exactly_val,
|
||||
sandboxed,
|
||||
sandbox_engine,
|
||||
sandbox_engine_regex,
|
||||
sandbox_app_id,
|
||||
sandbox_app_id_regex,
|
||||
),
|
||||
(
|
||||
sandbox_instance_id,
|
||||
sandbox_instance_id_regex,
|
||||
uid,
|
||||
pid,
|
||||
is_xwayland,
|
||||
comm,
|
||||
comm_regex,
|
||||
exe,
|
||||
exe_regex,
|
||||
),
|
||||
) = ext.extract((
|
||||
(
|
||||
opt(str("name")),
|
||||
opt(val("not")),
|
||||
opt(arr("all")),
|
||||
opt(arr("any")),
|
||||
opt(val("exactly")),
|
||||
opt(bol("sandboxed")),
|
||||
opt(str("sandbox-engine")),
|
||||
opt(str("sandbox-engine-regex")),
|
||||
opt(str("sandbox-app-id")),
|
||||
opt(str("sandbox-app-id-regex")),
|
||||
),
|
||||
(
|
||||
opt(str("sandbox-instance-id")),
|
||||
opt(str("sandbox-instance-id-regex")),
|
||||
opt(s32("uid")),
|
||||
opt(s32("pid")),
|
||||
opt(bol("is-xwayland")),
|
||||
opt(str("comm")),
|
||||
opt(str("comm-regex")),
|
||||
opt(str("exe")),
|
||||
opt(str("exe-regex")),
|
||||
),
|
||||
))?;
|
||||
let mut not = None;
|
||||
if let Some(value) = not_val {
|
||||
not = Some(Box::new(value.parse(&mut ClientMatchParser(self.0))?));
|
||||
}
|
||||
macro_rules! list {
|
||||
($val:expr) => {{
|
||||
let mut list = None;
|
||||
if let Some(value) = $val {
|
||||
let mut res = vec![];
|
||||
for value in value.value {
|
||||
res.push(value.parse(&mut ClientMatchParser(self.0))?);
|
||||
}
|
||||
list = Some(res);
|
||||
}
|
||||
list
|
||||
}};
|
||||
}
|
||||
let all = list!(all_val);
|
||||
let any = list!(any_val);
|
||||
let mut exactly = None;
|
||||
if let Some(value) = exactly_val {
|
||||
exactly = Some(value.parse(&mut ClientMatchExactlyParser(self.0))?);
|
||||
}
|
||||
Ok(ClientMatch {
|
||||
generic: GenericMatch {
|
||||
name: name.despan_into(),
|
||||
not,
|
||||
all,
|
||||
any,
|
||||
exactly,
|
||||
},
|
||||
sandbox_engine: sandbox_engine.despan_into(),
|
||||
sandbox_engine_regex: sandbox_engine_regex.despan_into(),
|
||||
sandbox_app_id: sandbox_app_id.despan_into(),
|
||||
sandbox_app_id_regex: sandbox_app_id_regex.despan_into(),
|
||||
sandbox_instance_id: sandbox_instance_id.despan_into(),
|
||||
sandbox_instance_id_regex: sandbox_instance_id_regex.despan_into(),
|
||||
sandboxed: sandboxed.despan(),
|
||||
uid: uid.despan(),
|
||||
pid: pid.despan(),
|
||||
is_xwayland: is_xwayland.despan(),
|
||||
comm: comm.despan_into(),
|
||||
comm_regex: comm_regex.despan_into(),
|
||||
exe: exe.despan_into(),
|
||||
exe_regex: exe_regex.despan_into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClientMatchExactlyParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl Parser for ClientMatchExactlyParser<'_> {
|
||||
type Value = MatchExactly<ClientMatch>;
|
||||
type Error = ClientMatchParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut ext = Extractor::new(self.0, span, table);
|
||||
let (num, list_val) = ext.extract((n32("num"), arr("list")))?;
|
||||
let mut list = vec![];
|
||||
for el in list_val.value {
|
||||
list.push(el.parse(&mut ClientMatchParser(self.0))?);
|
||||
}
|
||||
Ok(MatchExactly {
|
||||
num: num.value as _,
|
||||
list,
|
||||
})
|
||||
}
|
||||
}
|
||||
104
toml-config/src/config/parsers/client_rule.rs
Normal file
104
toml-config/src/config/parsers/client_rule.rs
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
ClientMatch, ClientRule,
|
||||
context::Context,
|
||||
extractor::{Extractor, ExtractorError, opt, str, val},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
parsers::{
|
||||
action::{ActionParser, ActionParserError},
|
||||
client_match::{ClientMatchParser, ClientMatchParserError},
|
||||
},
|
||||
spanned::SpannedErrorExt,
|
||||
},
|
||||
toml::{
|
||||
toml_span::{DespanExt, Span, Spanned},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ClientRuleParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Extract(#[from] ExtractorError),
|
||||
#[error(transparent)]
|
||||
Match(#[from] ClientMatchParserError),
|
||||
#[error(transparent)]
|
||||
Action(ActionParserError),
|
||||
#[error(transparent)]
|
||||
Latch(ActionParserError),
|
||||
}
|
||||
|
||||
pub struct ClientRuleParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl Parser for ClientRuleParser<'_> {
|
||||
type Value = ClientRule;
|
||||
type Error = ClientRuleParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut ext = Extractor::new(self.0, span, table);
|
||||
let (name, match_val, action_val, latch_val) = ext.extract((
|
||||
opt(str("name")),
|
||||
opt(val("match")),
|
||||
opt(val("action")),
|
||||
opt(val("latch")),
|
||||
))?;
|
||||
let mut action = None;
|
||||
if let Some(value) = action_val {
|
||||
action = Some(
|
||||
value
|
||||
.parse(&mut ActionParser(self.0))
|
||||
.map_spanned_err(ClientRuleParserError::Action)?,
|
||||
);
|
||||
}
|
||||
let mut latch = None;
|
||||
if let Some(value) = latch_val {
|
||||
latch = Some(
|
||||
value
|
||||
.parse(&mut ActionParser(self.0))
|
||||
.map_spanned_err(ClientRuleParserError::Latch)?,
|
||||
);
|
||||
}
|
||||
let match_ = match match_val {
|
||||
None => ClientMatch::default(),
|
||||
Some(m) => m.parse_map(&mut ClientMatchParser(self.0))?,
|
||||
};
|
||||
Ok(ClientRule {
|
||||
name: name.despan_into(),
|
||||
match_,
|
||||
action,
|
||||
latch,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClientRulesParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl Parser for ClientRulesParser<'_> {
|
||||
type Value = Vec<ClientRule>;
|
||||
type Error = ClientRuleParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Array];
|
||||
|
||||
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||
let mut res = vec![];
|
||||
for el in array {
|
||||
match el.parse(&mut ClientRuleParser(self.0)) {
|
||||
Ok(o) => res.push(o),
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse client rule: {}", self.0.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ use {
|
|||
parsers::{
|
||||
action::ActionParser,
|
||||
actions::ActionsParser,
|
||||
client_rule::ClientRulesParser,
|
||||
color_management::ColorManagementParser,
|
||||
connector::ConnectorsParser,
|
||||
drm_device::DrmDevicesParser,
|
||||
|
|
@ -31,6 +32,7 @@ use {
|
|||
theme::ThemeParser,
|
||||
ui_drag::UiDragParser,
|
||||
vrr::VrrParser,
|
||||
window_rule::WindowRulesParser,
|
||||
xwayland::XwaylandParser,
|
||||
},
|
||||
spanned::SpannedErrorExt,
|
||||
|
|
@ -120,7 +122,14 @@ impl Parser for ConfigParser<'_> {
|
|||
ui_drag_val,
|
||||
xwayland_val,
|
||||
),
|
||||
(color_management_val, float_val, actions_val, max_action_depth_val),
|
||||
(
|
||||
color_management_val,
|
||||
float_val,
|
||||
actions_val,
|
||||
max_action_depth_val,
|
||||
client_rules_val,
|
||||
window_rules_val,
|
||||
),
|
||||
) = ext.extract((
|
||||
(
|
||||
opt(val("keymap")),
|
||||
|
|
@ -163,6 +172,8 @@ impl Parser for ConfigParser<'_> {
|
|||
opt(val("float")),
|
||||
opt(val("actions")),
|
||||
recover(opt(int("max-action-depth"))),
|
||||
opt(val("clients")),
|
||||
opt(val("windows")),
|
||||
),
|
||||
))?;
|
||||
let mut keymap = None;
|
||||
|
|
@ -419,6 +430,20 @@ impl Parser for ConfigParser<'_> {
|
|||
}
|
||||
max_action_depth = value.value as _;
|
||||
}
|
||||
let mut client_rules = vec![];
|
||||
if let Some(value) = client_rules_val {
|
||||
match value.parse(&mut ClientRulesParser(self.0)) {
|
||||
Ok(v) => client_rules = v,
|
||||
Err(e) => log::warn!("Could not parse the client rules: {}", self.0.error(e)),
|
||||
}
|
||||
}
|
||||
let mut window_rules = vec![];
|
||||
if let Some(value) = window_rules_val {
|
||||
match value.parse(&mut WindowRulesParser(self.0)) {
|
||||
Ok(v) => window_rules = v,
|
||||
Err(e) => log::warn!("Could not parse the window rules: {}", self.0.error(e)),
|
||||
}
|
||||
}
|
||||
Ok(Config {
|
||||
keymap,
|
||||
repeat_rate,
|
||||
|
|
@ -453,6 +478,8 @@ impl Parser for ConfigParser<'_> {
|
|||
float,
|
||||
named_actions,
|
||||
max_action_depth,
|
||||
client_rules,
|
||||
window_rules,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ pub struct OutputMatchParser<'a>(pub &'a Context<'a>);
|
|||
impl Parser for OutputMatchParser<'_> {
|
||||
type Value = OutputMatch;
|
||||
type Error = OutputMatchParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Table];
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
|
||||
|
||||
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||
let mut res = vec![];
|
||||
|
|
|
|||
35
toml-config/src/config/parsers/tile_state.rs
Normal file
35
toml-config/src/config/parsers/tile_state.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
use {
|
||||
crate::{
|
||||
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
toml::toml_span::{Span, SpannedExt},
|
||||
},
|
||||
jay_config::window::TileState,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum TileStateParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error("Unknown tile state `{}`", .0)]
|
||||
UnknownTileState(String),
|
||||
}
|
||||
|
||||
pub struct TileStateParser;
|
||||
|
||||
impl Parser for TileStateParser {
|
||||
type Value = TileState;
|
||||
type Error = TileStateParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::String];
|
||||
|
||||
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
|
||||
let ty = match string {
|
||||
"tiled" => TileState::Tiled,
|
||||
"floating" => TileState::Floating,
|
||||
_ => {
|
||||
return Err(TileStateParserError::UnknownTileState(string.to_owned()).spanned(span));
|
||||
}
|
||||
};
|
||||
Ok(ty)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue