jay_config/window.rs
1//! Tools for inspecting and manipulating windows.
2
3use crate::_private::WindowThemeKind;
4use crate::Axis;
5use crate::ContainerTarget;
6use crate::Direction;
7use crate::RelativeAxis;
8use crate::Workspace;
9use crate::client::Client;
10use crate::client::ClientCriterion;
11use crate::input::Seat;
12use crate::theme::ContainerTheme;
13use crate::theme::ThemeOverrides;
14use crate::theme::WindowTheme;
15use jay_proc::PrivateEnum;
16use serde::Deserialize;
17use serde::Serialize;
18use std::ops::Deref;
19
20/// A toplevel window.
21///
22/// A toplevel window is anything that can be stored within a container tile or within a
23/// floating window.
24///
25/// There are currently four types of windows:
26///
27/// - Containers
28/// - Placeholders that take the place of a window when it goes fullscreen
29/// - XDG toplevels
30/// - X windows
31///
32/// You can find out the type of a window by using the [`Window::type_`] function.
33#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
34pub struct Window(pub u64);
35
36bitflags! {
37 /// The type of a window.
38 #[derive(Serialize, Deserialize, Copy, Clone, Hash, Eq, PartialEq)]
39 pub struct WindowType(pub u64) {
40 /// A container.
41 pub const CONTAINER = 1 << 0,
42 /// A placeholder.
43 pub const PLACEHOLDER = 1 << 1,
44 /// An XDG toplevel.
45 pub const XDG_TOPLEVEL = 1 << 2,
46 /// An X window.
47 pub const X_WINDOW = 1 << 3,
48 }
49}
50
51bitflags! {
52 /// The content type of a window.
53 #[derive(Serialize, Deserialize, Copy, Clone, Hash, Eq, PartialEq)]
54 pub struct ContentType(pub u64) {
55 /// No content type.
56 pub const NO_CONTENT_TYPE = 1 << 0,
57 /// Photo content type.
58 pub const PHOTO_CONTENT = 1 << 1,
59 /// Video content type.
60 pub const VIDEO_CONTENT = 1 << 2,
61 /// Game content type.
62 pub const GAME_CONTENT = 1 << 3,
63 }
64}
65
66/// The tile state of a window.
67#[non_exhaustive]
68#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq, PrivateEnum)]
69pub enum TileState {
70 /// The window is tiled.
71 Tiled,
72 /// The window is floating.
73 Floating,
74}
75
76/// A window created by a client.
77///
78/// This is the same as `XDG_TOPLEVEL | X_WINDOW`.
79pub const CLIENT_WINDOW: WindowType = WindowType(XDG_TOPLEVEL.0 | X_WINDOW.0);
80
81impl Window {
82 /// Returns whether the window exists.
83 pub fn exists(self) -> bool {
84 self.0 != 0 && get!(false).window_exists(self)
85 }
86
87 /// Returns whether the window does not exist.
88 ///
89 /// This is a shorthand for `!self.exists()`.
90 pub fn does_not_exist(self) -> bool {
91 !self.exists()
92 }
93
94 /// Returns the client of the window.
95 ///
96 /// If the window does not have a client, [`Client::exists`] return false.
97 pub fn client(self) -> Client {
98 get!(Client(0)).window_client(self)
99 }
100
101 /// Returns the title of the window.
102 pub fn title(self) -> String {
103 get!().window_title(self)
104 }
105
106 /// Returns the type of the window.
107 pub fn type_(self) -> WindowType {
108 get!(WindowType(0)).window_type(self)
109 }
110
111 /// Returns the content type of the window.
112 pub fn content_type(self) -> ContentType {
113 get!(ContentType(0)).content_type(self)
114 }
115
116 /// Returns the identifier of the window.
117 ///
118 /// This is the identifier used in the `ext-foreign-toplevel-list-v1` protocol.
119 pub fn id(self) -> String {
120 get!().window_id(self)
121 }
122
123 /// Returns whether this window is visible.
124 pub fn is_visible(self) -> bool {
125 get!().window_is_visible(self)
126 }
127
128 /// Returns the parent of this window.
129 ///
130 /// If this window has no parent, [`Window::exists`] returns false.
131 pub fn parent(self) -> Window {
132 get!(Window(0)).window_parent(self)
133 }
134
135 /// Returns the children of this window.
136 ///
137 /// Only containers have children.
138 pub fn children(self) -> Vec<Window> {
139 get!().window_children(self)
140 }
141
142 /// Moves the window in the specified direction.
143 pub fn move_(self, direction: Direction) {
144 get!().window_move(self, direction)
145 }
146
147 /// Returns whether the parent-container of the window is in mono-mode.
148 pub fn mono(self) -> bool {
149 get!(false).window_mono(self)
150 }
151
152 /// Sets whether the parent-container of the window is in mono-mode.
153 pub fn set_mono(self, mono: bool) {
154 get!().set_window_mono(self, mono)
155 }
156
157 /// Toggles whether the parent-container of the window is in mono-mode.
158 pub fn toggle_mono(self) {
159 self.set_mono(!self.mono());
160 }
161
162 /// Returns the split axis of the parent-container of the window.
163 pub fn split(self) -> Axis {
164 get!(Axis::Horizontal).window_split(self)
165 }
166
167 /// Sets the split axis of the parent-container of the window.
168 pub fn set_split(self, axis: Axis) {
169 get!().set_window_split(self, axis)
170 }
171
172 /// Toggles the split axis of the parent-container of the window.
173 pub fn toggle_split(self) {
174 self.set_split(self.split().other());
175 }
176
177 /// Returns whether the target container of the window is in mono-mode.
178 pub fn container_mono(self, target: ContainerTarget) -> bool {
179 get!(false).window_container_mono(self, target.to_private())
180 }
181
182 /// Sets whether the target container of the window is in mono-mode.
183 pub fn set_container_mono(self, target: ContainerTarget, mono: bool) {
184 get!().set_window_container_mono(self, target.to_private(), mono)
185 }
186
187 /// Toggles whether the target container of the window is in mono-mode.
188 pub fn toggle_container_mono(self, target: ContainerTarget) {
189 self.set_container_mono(target, !self.container_mono(target));
190 }
191
192 /// Returns the split axis of the target container of the window.
193 pub fn container_split(self, target: ContainerTarget) -> Axis {
194 get!(Axis::Horizontal).window_container_split(self, target.to_private())
195 }
196
197 /// Sets the split axis of the target container of the window.
198 pub fn set_container_split(self, target: ContainerTarget, axis: Axis) {
199 get!().set_window_container_split(self, target.to_private(), axis)
200 }
201
202 /// Toggles the split axis of the target container of the window.
203 pub fn toggle_container_split(self, target: ContainerTarget) {
204 self.set_container_split(target, self.container_split(target).other());
205 }
206
207 /// Sets the split axis of the target container of the window relative to the
208 /// dimensions of that container.
209 pub fn set_container_split_relative(self, target: ContainerTarget, axis: RelativeAxis) {
210 get!().set_window_container_split_relative(self, target.to_private(), axis.to_private())
211 }
212
213 /// Creates a new container with the specified split in place of the window.
214 ///
215 /// If the window is the only child of its container and
216 /// [`set_split_reuses_container`](crate::set_split_reuses_container) is enabled, the
217 /// split axis of that container is changed instead.
218 pub fn create_split(self, axis: Axis) {
219 get!().create_window_split(self, axis);
220 }
221
222 /// Creates a new container in place of the window with a split relative to the
223 /// dimensions of that window.
224 ///
225 /// If the window is the only child of its container and
226 /// [`set_split_reuses_container`](crate::set_split_reuses_container) is enabled, the
227 /// split axis of that container is changed instead.
228 pub fn create_split_relative(self, axis: RelativeAxis) {
229 get!().create_window_split_relative(self, axis.to_private());
230 }
231
232 /// Requests the window to be closed.
233 pub fn close(self) {
234 get!().close_window(self);
235 }
236
237 /// Returns whether the window is floating.
238 pub fn floating(self) -> bool {
239 get!().get_window_floating(self)
240 }
241 /// Sets whether the window is floating.
242 pub fn set_floating(self, floating: bool) {
243 get!().set_window_floating(self, floating);
244 }
245
246 /// Toggles whether the window is floating.
247 ///
248 /// You can do the same by double-clicking on the header.
249 pub fn toggle_floating(self) {
250 self.set_floating(!self.floating());
251 }
252
253 /// Returns the workspace that this window belongs to.
254 ///
255 /// If no such workspace exists, `exists` returns `false` for the returned workspace.
256 pub fn workspace(self) -> Workspace {
257 get!(Workspace(0)).get_window_workspace(self)
258 }
259
260 /// Moves the window to the workspace.
261 pub fn set_workspace(self, workspace: Workspace) {
262 get!().set_window_workspace(self, workspace)
263 }
264
265 /// Toggles whether the currently focused window is fullscreen.
266 pub fn toggle_fullscreen(self) {
267 self.set_fullscreen(!self.fullscreen())
268 }
269 /// Returns whether the window is fullscreen.
270 pub fn fullscreen(self) -> bool {
271 get!(false).get_window_fullscreen(self)
272 }
273
274 /// Sets whether the window is fullscreen.
275 pub fn set_fullscreen(self, fullscreen: bool) {
276 get!().set_window_fullscreen(self, fullscreen)
277 }
278
279 /// Gets whether the window is pinned.
280 ///
281 /// If a floating window is pinned, it will stay visible even when switching to a
282 /// different workspace.
283 pub fn float_pinned(self) -> bool {
284 get!().get_window_pinned(self)
285 }
286
287 /// Sets whether the window is pinned.
288 pub fn set_float_pinned(self, pinned: bool) {
289 get!().set_window_pinned(self, pinned);
290 }
291
292 /// Toggles whether the window is pinned.
293 pub fn toggle_float_pinned(self) {
294 self.set_float_pinned(!self.float_pinned());
295 }
296
297 /// Resizes the window.
298 pub fn resize(self, dx1: i32, dy1: i32, dx2: i32, dy2: i32) {
299 get!().resize_window(self, dx1, dy1, dx2, dy2);
300 }
301
302 /// Returns the position of the window in the global compositor space.
303 ///
304 /// This value is only accurate for visible windows.
305 pub fn position(self) -> (i32, i32) {
306 let (x, y, _, _) = get!((0, 0)).get_window_position(self);
307 (x, y)
308 }
309
310 /// Returns the size of the window.
311 ///
312 /// This value is only accurate for visible windows.
313 pub fn size(self) -> (i32, i32) {
314 let (_, _, width, height) = get!((0, 0)).get_window_position(self);
315 (width, height)
316 }
317
318 /// Returns the theme overrides of this window.
319 ///
320 /// See [`WindowTheme`] for details.
321 pub fn theme(self) -> WindowTheme {
322 WindowTheme(ThemeOverrides {
323 window: self,
324 kind: WindowThemeKind::ParentTheme,
325 })
326 }
327
328 /// Returns the theme overrides of how this container decorates its children.
329 ///
330 /// This is only supported for containers.
331 ///
332 /// See [`ContainerTheme`] for details.
333 pub fn container_theme(self) -> ContainerTheme {
334 ContainerTheme(ThemeOverrides {
335 window: self,
336 kind: WindowThemeKind::SelfTheme,
337 })
338 }
339}
340
341/// A window matcher.
342#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
343pub struct WindowMatcher(pub u64);
344
345/// A matched window.
346#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
347pub struct MatchedWindow {
348 pub(crate) matcher: WindowMatcher,
349 pub(crate) window: Window,
350}
351
352/// A criterion for matching a window.
353#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
354#[non_exhaustive]
355pub enum WindowCriterion<'a> {
356 /// Matches if the contained matcher matches.
357 Matcher(WindowMatcher),
358 /// Matches if the contained criterion does not match.
359 Not(&'a WindowCriterion<'a>),
360 /// Matches if the window has one of the types.
361 Types(WindowType),
362 /// Matches if all of the contained criteria match.
363 All(&'a [WindowCriterion<'a>]),
364 /// Matches if any of the contained criteria match.
365 Any(&'a [WindowCriterion<'a>]),
366 /// Matches if an exact number of the contained criteria match.
367 Exactly(usize, &'a [WindowCriterion<'a>]),
368 /// Matches if the window's client matches the client criterion.
369 Client(&'a ClientCriterion<'a>),
370 /// Matches the title of the window verbatim.
371 Title(&'a str),
372 /// Matches the title of the window with a regular expression.
373 TitleRegex(&'a str),
374 /// Matches the app-id of the window verbatim.
375 AppId(&'a str),
376 /// Matches the app-id of the window with a regular expression.
377 AppIdRegex(&'a str),
378 /// Matches if the window is floating.
379 Floating,
380 /// Matches if the window is visible.
381 Visible,
382 /// Matches if the window has the urgency flag set.
383 Urgent,
384 /// Matches if the window has the keyboard focus of the seat.
385 Focus(Seat),
386 /// Matches if the window is fullscreen.
387 Fullscreen,
388 /// Matches if the window has/hasn't just been mapped.
389 ///
390 /// This is true for one iteration of the compositor's main loop immediately after the
391 /// window has been mapped.
392 JustMapped,
393 /// Matches the toplevel-tag of the window verbatim.
394 Tag(&'a str),
395 /// Matches the toplevel-tag of the window with a regular expression.
396 TagRegex(&'a str),
397 /// Matches the X class of the window verbatim.
398 XClass(&'a str),
399 /// Matches the X class of the window with a regular expression.
400 XClassRegex(&'a str),
401 /// Matches the X instance of the window verbatim.
402 XInstance(&'a str),
403 /// Matches the X instance of the window with a regular expression.
404 XInstanceRegex(&'a str),
405 /// Matches the X role of the window verbatim.
406 XRole(&'a str),
407 /// Matches the X role of the window with a regular expression.
408 XRoleRegex(&'a str),
409 /// Matches the workspace the window.
410 Workspace(Workspace),
411 /// Matches the workspace name of the window verbatim.
412 WorkspaceName(&'a str),
413 /// Matches the workspace name of the window with a regular expression.
414 WorkspaceNameRegex(&'a str),
415 /// Matches if the window has one of the content types.
416 ContentTypes(ContentType),
417 /// Matches if the window is the root container of a workspace.
418 IsWorkspaceContainer,
419}
420
421impl WindowCriterion<'_> {
422 /// Converts the criterion to a matcher.
423 pub fn to_matcher(self) -> WindowMatcher {
424 get!(WindowMatcher(0)).create_window_matcher(self)
425 }
426
427 /// Binds a function to execute when the criterion matches a window.
428 ///
429 /// This leaks the matcher.
430 pub fn bind<F: FnMut(MatchedWindow) + 'static>(self, cb: F) {
431 self.to_matcher().bind(cb);
432 }
433
434 /// Sets whether newly mapped windows that match this criterion get the keyboard focus.
435 ///
436 /// If a window matches any criterion for which this is false, the window will not be
437 /// automatically focused.
438 ///
439 /// This leaks the matcher.
440 pub fn set_auto_focus(self, auto_focus: bool) {
441 self.to_matcher().set_auto_focus(auto_focus);
442 }
443
444 /// Sets whether newly mapped windows that match this matcher are mapped tiling or
445 /// floating.
446 ///
447 /// If multiple such window matchers match a window, the used tile state is
448 /// unspecified.
449 ///
450 /// This leaks the matcher.
451 pub fn set_initial_tile_state(self, tile_state: TileState) {
452 self.to_matcher().set_initial_tile_state(tile_state);
453 }
454
455 /// Sets the size that matched windows have when they are initially mapped floating.
456 ///
457 /// This has no effect on windows that are initially mapped tiled, even if they later
458 /// become floating.
459 ///
460 /// If multiple such window matchers match a window, the used size is unspecified.
461 ///
462 /// This leaks the matcher.
463 pub fn set_initial_floating_size(self, width: i32, height: i32) {
464 self.to_matcher().set_initial_floating_size(width, height);
465 }
466
467 /// Sets the position that matched windows have when they are initially mapped
468 /// floating.
469 ///
470 /// The position is relative to the top-left corner of the output that the window is
471 /// mapped on and refers to the top-left corner of the window itself, excluding any
472 /// decorations. It is clamped so that the window remains at least partially visible
473 /// on that output.
474 ///
475 /// This has no effect on windows that are initially mapped tiled, even if they later
476 /// become floating.
477 ///
478 /// If multiple such window matchers match a window, the used position is unspecified.
479 ///
480 /// This leaks the matcher.
481 pub fn set_initial_floating_position(self, x: i32, y: i32) {
482 self.to_matcher().set_initial_floating_position(x, y);
483 }
484}
485
486impl WindowMatcher {
487 /// Destroys the matcher.
488 ///
489 /// Any bound callback will no longer be executed.
490 pub fn destroy(self) {
491 get!().destroy_window_matcher(self);
492 }
493
494 /// Sets a function to execute when the criterion matches a window.
495 ///
496 /// Replaces any already bound callback.
497 pub fn bind<F: FnMut(MatchedWindow) + 'static>(self, cb: F) {
498 get!().set_window_matcher_handler(self, cb);
499 }
500
501 /// Sets whether newly mapped windows that match this matcher get the keyboard focus.
502 ///
503 /// If a window matches any matcher for which this is false, the window will not be
504 /// automatically focused.
505 pub fn set_auto_focus(self, auto_focus: bool) {
506 get!().set_window_matcher_auto_focus(self, auto_focus);
507 }
508
509 /// Sets whether newly mapped windows that match this matcher are mapped tiling or
510 /// floating.
511 ///
512 /// If multiple such window matchers match a window, the used tile state is
513 /// unspecified.
514 pub fn set_initial_tile_state(self, tile_state: TileState) {
515 get!().set_window_matcher_initial_tile_state(self, tile_state.to_private());
516 }
517
518 /// Sets the size that matched windows have when they are initially mapped floating.
519 ///
520 /// This has no effect on windows that are initially mapped tiled, even if they later
521 /// become floating.
522 ///
523 /// If multiple such window matchers match a window, the used size is unspecified.
524 pub fn set_initial_floating_size(self, width: i32, height: i32) {
525 get!().set_window_matcher_initial_floating_size(self, width, height);
526 }
527
528 /// Sets the position that matched windows have when they are initially mapped
529 /// floating.
530 ///
531 /// The position is relative to the top-left corner of the output that the window is
532 /// mapped on and refers to the top-left corner of the window itself, excluding any
533 /// decorations. It is clamped so that the window remains at least partially visible
534 /// on that output.
535 ///
536 /// This has no effect on windows that are initially mapped tiled, even if they later
537 /// become floating.
538 ///
539 /// If multiple such window matchers match a window, the used position is unspecified.
540 pub fn set_initial_floating_position(self, x: i32, y: i32) {
541 get!().set_window_matcher_initial_floating_position(self, x, y);
542 }
543}
544
545impl MatchedWindow {
546 /// Returns the window that matched.
547 pub fn window(self) -> Window {
548 self.window
549 }
550
551 /// Returns the matcher.
552 pub fn matcher(self) -> WindowMatcher {
553 self.matcher
554 }
555
556 /// Latches a function to be executed when the window no longer matches the criteria.
557 pub fn latch<F: FnOnce() + 'static>(self, cb: F) {
558 get!().set_window_matcher_latch_handler(self.matcher, self.window, cb);
559 }
560}
561
562impl Deref for MatchedWindow {
563 type Target = Window;
564
565 fn deref(&self) -> &Self::Target {
566 &self.window
567 }
568}