jay_config/lib.rs
1//! This crate allows you to configure the Jay compositor.
2//!
3//! A minimal example configuration looks as follows:
4//!
5//! ```rust
6//! use jay_config::{config, quit, reload};
7//! use jay_config::input::get_default_seat;
8//! use jay_config::keyboard::mods::ALT;
9//! use jay_config::keyboard::syms::{SYM_q, SYM_r};
10//!
11//! fn configure() {
12//! let seat = get_default_seat();
13//! // Create a key binding to exit the compositor.
14//! seat.bind(ALT | SYM_q, || quit());
15//! // Reload the configuration.
16//! seat.bind(ALT | SYM_r, || reload());
17//! }
18//!
19//! config!(configure);
20//! ```
21//!
22//! You should configure your crate to be compiled as a shared library:
23//!
24//! ```toml
25//! [lib]
26//! crate-type = ["cdylib"]
27//! ```
28//!
29//! After compiling it, copy the shared library to `$HOME/.config/jay/config.so` and restart
30//! the compositor. It should then use your configuration file.
31//!
32//! Note that you do not have to restart the compositor every time you want to reload your
33//! configuration afterwards. Instead, simply invoke the [`reload`] function via a shortcut.
34
35#![allow(
36 clippy::zero_prefixed_literal,
37 clippy::manual_range_contains,
38 clippy::uninlined_format_args,
39 clippy::len_zero,
40 clippy::single_char_pattern,
41 clippy::single_char_add_str,
42 clippy::single_match
43)]
44#![warn(unsafe_op_in_unsafe_fn)]
45
46use {
47 crate::{
48 _private::{WorkspaceShowOpV1, WorkspaceShowOpV2, ipc::WorkspaceSource},
49 input::{FallbackOutputMode, Seat},
50 keyboard::ModifiedKeySym,
51 video::Connector,
52 window::Window,
53 },
54 serde::{Deserialize, Serialize},
55 std::{
56 fmt::{Debug, Display, Formatter},
57 time::Duration,
58 },
59};
60
61#[macro_use]
62mod macros;
63#[doc(hidden)]
64pub mod _private;
65pub mod client;
66pub mod embedded;
67pub mod exec;
68pub mod input;
69pub mod io;
70pub mod keyboard;
71pub mod logging;
72pub mod status;
73pub mod tasks;
74pub mod theme;
75pub mod timer;
76pub mod video;
77pub mod window;
78pub mod workspace;
79pub mod xwayland;
80
81/// A planar direction.
82#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq)]
83pub enum Direction {
84 Left,
85 Down,
86 Up,
87 Right,
88}
89
90/// A planar axis.
91#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
92pub enum Axis {
93 Horizontal,
94 Vertical,
95}
96
97impl Axis {
98 /// Returns the axis orthogonal to `self`.
99 pub fn other(self) -> Self {
100 match self {
101 Self::Horizontal => Self::Vertical,
102 Self::Vertical => Self::Horizontal,
103 }
104 }
105}
106
107/// Exits the compositor.
108pub fn quit() {
109 get!().quit()
110}
111
112/// Switches to a different VT.
113pub fn switch_to_vt(n: u32) {
114 get!().switch_to_vt(n)
115}
116
117/// Reloads the configuration.
118///
119/// If the configuration cannot be reloaded, this function has no effect.
120pub fn reload() {
121 get!().reload()
122}
123
124/// Returns whether this execution of the configuration function is due to a reload.
125///
126/// This can be used to decide whether the configuration should auto-start programs.
127pub fn is_reload() -> bool {
128 get!(false).is_reload()
129}
130
131/// Sets whether new workspaces are captured by default.
132///
133/// The default is `true`.
134pub fn set_default_workspace_capture(capture: bool) {
135 get!().set_default_workspace_capture(capture)
136}
137
138/// Returns whether new workspaces are captured by default.
139pub fn get_default_workspace_capture() -> bool {
140 get!(true).get_default_workspace_capture()
141}
142
143/// Toggles whether new workspaces are captured by default.
144pub fn toggle_default_workspace_capture() {
145 let get = get!();
146 get.set_default_workspace_capture(!get.get_default_workspace_capture());
147}
148
149/// A workspace.
150#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
151pub struct Workspace(pub u64);
152
153impl Workspace {
154 /// Returns whether this workspace existed at the time `Seat::get_workspace` was called.
155 pub fn exists(self) -> bool {
156 self.0 != 0
157 }
158
159 /// Sets whether the workspaces is captured.
160 ///
161 /// The default is determined by `set_default_workspace_capture`.
162 pub fn set_capture(self, capture: bool) {
163 get!().set_workspace_capture(self, capture)
164 }
165
166 /// Returns whether the workspaces is captured.
167 pub fn get_capture(self) -> bool {
168 get!(true).get_workspace_capture(self)
169 }
170
171 /// Toggles whether the workspaces is captured.
172 pub fn toggle_capture(self) {
173 let get = get!();
174 get.set_workspace_capture(self, !get.get_workspace_capture(self));
175 }
176
177 /// Moves this workspace to another output.
178 ///
179 /// This has no effect if the workspace is not currently being shown.
180 pub fn move_to_output(self, output: Connector) {
181 get!().move_to_output(WorkspaceSource::Explicit(self), output);
182 }
183
184 /// Returns the root container of this workspace.
185 ///
186 /// If no such container exists, [`Window::exists`] returns false.
187 pub fn window(self) -> Window {
188 get!(Window(0)).get_workspace_window(self)
189 }
190
191 /// Returns the connector that contains this workspace.
192 ///
193 /// If no such connector exists, [`Connector::exists`] returns false.
194 pub fn connector(self) -> Connector {
195 get!(Connector(0)).get_workspace_connector(self)
196 }
197
198 /// Creates an operation to show this workspace.
199 ///
200 /// Does nothing until [`WorkspaceShowOp::exec`] is called.
201 pub fn show(self) -> WorkspaceShowOp {
202 WorkspaceShowOp {
203 v1: WorkspaceShowOpV1 {
204 workspace: self,
205 connector: None,
206 move_to_connector: false,
207 seat: None,
208 fallback_output_mode: None,
209 focus: true,
210 },
211 v2: Default::default(),
212 }
213 }
214
215 /// Returns the kind of this workspace.
216 pub fn kind(self) -> WorkspaceKind {
217 get!(WorkspaceKind::Normal).get_workspace_kind(self)
218 }
219
220 /// Hides this workspace.
221 ///
222 /// This has no effect for normal workspaces.
223 pub fn hide(self) {
224 get!().hide_workspace(self);
225 }
226}
227
228/// Returns the workspace with the given name.
229///
230/// Workspaces are identified by their name. Calling this function alone does not create the
231/// workspace if it doesn't already exist.
232///
233/// Workspaces and overlays share the same namespace. If an overlay with the same name
234/// already exists, this request changes the pending kind of the workspace from `Overlay`
235/// to `Normal`.
236pub fn get_workspace(name: &str) -> Workspace {
237 get!(Workspace(0)).get_workspace(name)
238}
239
240/// An operation to show a workspace.
241///
242/// Create this using [`Workspace::show`].
243#[must_use]
244pub struct WorkspaceShowOp {
245 v1: WorkspaceShowOpV1,
246 v2: WorkspaceShowOpV2,
247}
248
249impl WorkspaceShowOp {
250 /// Runs this operation.
251 pub fn exec(self) {
252 get!().show_workspace_3(self);
253 }
254
255 /// The connector on which to show the workspace.
256 ///
257 /// If the workspace does not already exist, it will be shown on this connector.
258 /// Otherwise, see [`WorkspaceShowOp::move_to_connector`].
259 ///
260 /// By default, this workspace is determined via the [`WorkspaceShowOp::seat`].
261 pub fn connector(mut self, c: Connector) -> Self {
262 self.v1.connector = Some(c);
263 self
264 }
265
266 /// Whether to move the workspace to the target connector if it already exists.
267 ///
268 /// The default is `false`.
269 pub fn move_to_connector(mut self, move_to_connector: bool) -> Self {
270 self.v1.move_to_connector = move_to_connector;
271 self
272 }
273
274 /// The reference seat.
275 ///
276 /// If no connector was explicitly set, this seat will be used to determine the target
277 /// output.
278 pub fn seat(mut self, s: Seat) -> Self {
279 self.v1.seat = Some(s);
280 self
281 }
282
283 /// The fallback output mode to use when the target output is determined via the
284 /// [WorkspaceShowOp::seat].
285 ///
286 /// The default is determined via [`Seat::set_fallback_output_mode`].
287 pub fn fallback_output_mode(mut self, mode: FallbackOutputMode) -> Self {
288 self.v1.fallback_output_mode = Some(mode);
289 self
290 }
291
292 /// Whether the workspace should grab the focus of the [`WorkspaceShowOp::seat`].
293 ///
294 /// The default is `true`.
295 pub fn focus(mut self, focus: bool) -> Self {
296 self.v1.focus = focus;
297 self
298 }
299
300 /// Whether this operation should hide the workspace if it is already visible.
301 ///
302 /// This has no effect for normal workspaces.
303 pub fn toggle(mut self, toggle: bool) -> Self {
304 self.v2.toggle = Some(toggle);
305 self
306 }
307}
308
309/// The kind of a workspace.
310#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
311#[non_exhaustive]
312pub enum WorkspaceKind {
313 /// A normal workspace.
314 Normal,
315 /// An overlay workspace.
316 Overlay,
317}
318
319/// Returns the overlay with the given name.
320///
321/// Overlays are identified by their name. Calling this function alone does not create the
322/// overlay if it doesn't already exist.
323///
324/// Workspaces and overlays share the same namespace. If a workspace with the same name
325/// already exists, this request changes the pending kind of the workspace from `Normal`
326/// to `Overlay`.
327pub fn get_overlay(name: &str) -> Workspace {
328 get!(Workspace(0)).get_overlay(name)
329}
330
331/// Hides all overlays.
332pub fn hide_overlays() {
333 get!().hide_overlays();
334}
335
336/// A PCI ID.
337///
338/// PCI IDs can be used to identify a hardware component. See the Debian [documentation][pci].
339///
340/// [pci]: https://wiki.debian.org/HowToIdentifyADevice/PCI
341#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, Eq, PartialEq, Default)]
342pub struct PciId {
343 pub vendor: u32,
344 pub model: u32,
345}
346
347impl Display for PciId {
348 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
349 write!(f, "{:04x}:{:04x}", self.vendor, self.model)
350 }
351}
352
353/// Sets the callback to be called when the display goes idle.
354pub fn on_idle<F: FnMut() + 'static>(f: F) {
355 get!().on_idle(f)
356}
357
358/// Sets the callback to be called when all devices have been enumerated.
359///
360/// This callback is only invoked once during the lifetime of the compositor. This is a
361/// good place to select the DRM device used for rendering.
362pub fn on_devices_enumerated<F: FnOnce() + 'static>(f: F) {
363 get!().on_devices_enumerated(f)
364}
365
366/// Returns the Jay config directory.
367pub fn config_dir() -> String {
368 get!().config_dir()
369}
370
371/// Returns all visible workspaces.
372pub fn workspaces() -> Vec<Workspace> {
373 get!().workspaces()
374}
375
376/// Configures the idle timeout.
377///
378/// `None` disables the timeout.
379///
380/// The default is 10 minutes.
381pub fn set_idle(timeout: Option<Duration>) {
382 get!().set_idle(timeout.unwrap_or_default())
383}
384
385/// Configures the idle grace period.
386///
387/// The grace period starts after the idle timeout expires. During the grace period, the
388/// screen goes black but the displays are not yet disabled and the idle callback (set
389/// with [`on_idle`]) is not yet called. This is a purely visual effect to inform the user
390/// that the machine will soon go idle.
391///
392/// The default is 5 seconds.
393pub fn set_idle_grace_period(timeout: Duration) {
394 get!().set_idle_grace_period(timeout)
395}
396
397/// Enables or disables explicit sync.
398///
399/// Calling this after the compositor has started has no effect.
400///
401/// The default is `true`.
402pub fn set_explicit_sync_enabled(enabled: bool) {
403 get!().set_explicit_sync_enabled(enabled);
404}
405
406/// Enables or disables dragging of tiles and workspaces.
407///
408/// The default is `true`.
409pub fn set_ui_drag_enabled(enabled: bool) {
410 get!().set_ui_drag_enabled(enabled);
411}
412
413/// Sets the distance at which ui dragging starts.
414///
415/// The default is `10`.
416pub fn set_ui_drag_threshold(threshold: i32) {
417 get!().set_ui_drag_threshold(threshold);
418}
419
420/// Enables or disables the color-management protocol.
421///
422/// The default is `false`.
423///
424/// Affected applications must be restarted for this to take effect.
425pub fn set_color_management_enabled(enabled: bool) {
426 get!().set_color_management_enabled(enabled);
427}
428
429/// Enables or disables the session-management protocol.
430///
431/// The default is `true`.
432///
433/// Affected applications must be restarted for this to take effect.
434pub fn set_session_management_enabled(enabled: bool) {
435 get!().set_session_management_enabled(enabled);
436}
437
438/// Sets whether floating windows are shown above fullscreen windows.
439///
440/// The default is `false`.
441pub fn set_float_above_fullscreen(above: bool) {
442 get!().set_float_above_fullscreen(above);
443}
444
445/// Gets whether floating windows are shown above fullscreen windows.
446pub fn get_float_above_fullscreen() -> bool {
447 get!().get_float_above_fullscreen()
448}
449
450/// Toggles whether floating windows are shown above fullscreen windows.
451///
452/// The default is `false`.
453pub fn toggle_float_above_fullscreen() {
454 set_float_above_fullscreen(!get_float_above_fullscreen())
455}
456
457/// Sets whether floating windows always show a pin icon.
458///
459/// Clicking on the pin icon toggles the pin mode. See [`Seat::toggle_float_pinned`].
460///
461/// The icon is always shown if the window is pinned. This setting only affects unpinned
462/// windows.
463pub fn set_show_float_pin_icon(show: bool) {
464 get!().set_show_float_pin_icon(show);
465}
466
467/// Sets whether the built-in bar is shown.
468///
469/// The default is `true`.
470pub fn set_show_bar(show: bool) {
471 get!().set_show_bar(show)
472}
473
474/// Returns whether the built-in bar is shown.
475pub fn get_show_bar() -> bool {
476 get!(true).get_show_bar()
477}
478
479/// Toggles whether the built-in bar is shown.
480pub fn toggle_show_bar() {
481 let get = get!();
482 get.set_show_bar(!get.get_show_bar());
483}
484
485/// Sets whether title bars on windows are shown.
486///
487/// The default is `true`.
488pub fn set_show_titles(show: bool) {
489 get!().set_show_titles(show)
490}
491
492/// Returns whether title bars on windows are shown.
493pub fn get_show_titles() -> bool {
494 get!(true).get_show_titles()
495}
496
497/// Toggles whether title bars on windows are shown.
498pub fn toggle_show_titles() {
499 let get = get!();
500 get.set_show_titles(!get.get_show_titles());
501}
502
503/// Sets a callback to run when this config is unloaded.
504///
505/// Only one callback can be set at a time. If another callback is already set, it will be
506/// dropped without being run.
507///
508/// This function can be used to terminate threads and clear reference cycles.
509pub fn on_unload(f: impl FnOnce() + 'static) {
510 get!().on_unload(f);
511}
512
513/// Enables or disables middle-click pasting.
514///
515/// This has no effect on applications that are already running.
516///
517/// The default is `true`.
518#[doc(alias("primary-selection", "primary_selection"))]
519pub fn set_middle_click_paste_enabled(enabled: bool) {
520 get!().set_middle_click_paste_enabled(enabled);
521}
522
523/// Opens the control center.
524pub fn open_control_center() {
525 get!().open_control_center();
526}