Skip to main content

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    /// Sets the connector on which this workspace is initially created.
228    ///
229    /// This setting can be overwritten with [`WorkspaceShowOp::connector`]. If that
230    /// function is not used or the workspace is created via some other mechanism, it will
231    /// be created on the connector set via this function, if possible.
232    pub fn set_initial_connector(self, connector: Option<Connector>) {
233        get!().set_workspace_initial_connector(self, connector);
234    }
235}
236
237/// Returns the workspace with the given name.
238///
239/// Workspaces are identified by their name. Calling this function alone does not create the
240/// workspace if it doesn't already exist.
241///
242/// Workspaces and overlays share the same namespace. If an overlay with the same name
243/// already exists, this request changes the pending kind of the workspace from `Overlay`
244/// to `Normal`.
245pub fn get_workspace(name: &str) -> Workspace {
246    get!(Workspace(0)).get_workspace(name)
247}
248
249/// An operation to show a workspace.
250///
251/// Create this using [`Workspace::show`].
252#[must_use]
253pub struct WorkspaceShowOp {
254    v1: WorkspaceShowOpV1,
255    v2: WorkspaceShowOpV2,
256}
257
258impl WorkspaceShowOp {
259    /// Runs this operation.
260    pub fn exec(self) {
261        get!().show_workspace_3(self);
262    }
263
264    /// The connector on which to show the workspace.
265    ///
266    /// If the workspace does not already exist, it will be shown on this connector.
267    /// Otherwise, see [`WorkspaceShowOp::move_to_connector`].
268    ///
269    /// By default, this workspace is determined via the [`WorkspaceShowOp::seat`].
270    pub fn connector(mut self, c: Connector) -> Self {
271        self.v1.connector = Some(c);
272        self
273    }
274
275    /// Whether to move the workspace to the target connector if it already exists.
276    ///
277    /// The default is `false`.
278    pub fn move_to_connector(mut self, move_to_connector: bool) -> Self {
279        self.v1.move_to_connector = move_to_connector;
280        self
281    }
282
283    /// The reference seat.
284    ///
285    /// If no connector was explicitly set, this seat will be used to determine the target
286    /// output.
287    pub fn seat(mut self, s: Seat) -> Self {
288        self.v1.seat = Some(s);
289        self
290    }
291
292    /// The fallback output mode to use when the target output is determined via the
293    /// [WorkspaceShowOp::seat].
294    ///
295    /// The default is determined via [`Seat::set_fallback_output_mode`].
296    pub fn fallback_output_mode(mut self, mode: FallbackOutputMode) -> Self {
297        self.v1.fallback_output_mode = Some(mode);
298        self
299    }
300
301    /// Whether the workspace should grab the focus of the [`WorkspaceShowOp::seat`].
302    ///
303    /// The default is `true`.
304    pub fn focus(mut self, focus: bool) -> Self {
305        self.v1.focus = focus;
306        self
307    }
308
309    /// Whether this operation should hide the workspace if it is already visible.
310    ///
311    /// This has no effect for normal workspaces.
312    pub fn toggle(mut self, toggle: bool) -> Self {
313        self.v2.toggle = Some(toggle);
314        self
315    }
316}
317
318/// The kind of a workspace.
319#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
320#[non_exhaustive]
321pub enum WorkspaceKind {
322    /// A normal workspace.
323    Normal,
324    /// An overlay workspace.
325    Overlay,
326}
327
328/// Returns the overlay with the given name.
329///
330/// Overlays are identified by their name. Calling this function alone does not create the
331/// overlay if it doesn't already exist.
332///
333/// Workspaces and overlays share the same namespace. If a workspace with the same name
334/// already exists, this request changes the pending kind of the workspace from `Normal`
335/// to `Overlay`.
336pub fn get_overlay(name: &str) -> Workspace {
337    get!(Workspace(0)).get_overlay(name)
338}
339
340/// Hides all overlays.
341pub fn hide_overlays() {
342    get!().hide_overlays();
343}
344
345/// A PCI ID.
346///
347/// PCI IDs can be used to identify a hardware component. See the Debian [documentation][pci].
348///
349/// [pci]: https://wiki.debian.org/HowToIdentifyADevice/PCI
350#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, Eq, PartialEq, Default)]
351pub struct PciId {
352    pub vendor: u32,
353    pub model: u32,
354}
355
356impl Display for PciId {
357    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
358        write!(f, "{:04x}:{:04x}", self.vendor, self.model)
359    }
360}
361
362/// Sets the callback to be called when the display goes idle.
363pub fn on_idle<F: FnMut() + 'static>(f: F) {
364    get!().on_idle(f)
365}
366
367/// Sets the callback to be called when all devices have been enumerated.
368///
369/// This callback is only invoked once during the lifetime of the compositor. This is a
370/// good place to select the DRM device used for rendering.
371pub fn on_devices_enumerated<F: FnOnce() + 'static>(f: F) {
372    get!().on_devices_enumerated(f)
373}
374
375/// Returns the Jay config directory.
376pub fn config_dir() -> String {
377    get!().config_dir()
378}
379
380/// Returns all visible workspaces.
381pub fn workspaces() -> Vec<Workspace> {
382    get!().workspaces()
383}
384
385/// Configures the idle timeout.
386///
387/// `None` disables the timeout.
388///
389/// The default is 10 minutes.
390pub fn set_idle(timeout: Option<Duration>) {
391    get!().set_idle(timeout.unwrap_or_default())
392}
393
394/// Configures the idle grace period.
395///
396/// The grace period starts after the idle timeout expires. During the grace period, the
397/// screen goes black but the displays are not yet disabled and the idle callback (set
398/// with [`on_idle`]) is not yet called. This is a purely visual effect to inform the user
399/// that the machine will soon go idle.
400///
401/// The default is 5 seconds.
402pub fn set_idle_grace_period(timeout: Duration) {
403    get!().set_idle_grace_period(timeout)
404}
405
406/// Enables or disables explicit sync.
407///
408/// Calling this after the compositor has started has no effect.
409///
410/// The default is `true`.
411pub fn set_explicit_sync_enabled(enabled: bool) {
412    get!().set_explicit_sync_enabled(enabled);
413}
414
415/// Enables or disables dragging of tiles and workspaces.
416///
417/// The default is `true`.
418pub fn set_ui_drag_enabled(enabled: bool) {
419    get!().set_ui_drag_enabled(enabled);
420}
421
422/// Sets the distance at which ui dragging starts.
423///
424/// The default is `10`.
425pub fn set_ui_drag_threshold(threshold: i32) {
426    get!().set_ui_drag_threshold(threshold);
427}
428
429/// Enables or disables the color-management protocol.
430///
431/// The default is `false`.
432///
433/// Affected applications must be restarted for this to take effect.
434pub fn set_color_management_enabled(enabled: bool) {
435    get!().set_color_management_enabled(enabled);
436}
437
438/// Enables or disables the session-management protocol.
439///
440/// The default is `true`.
441///
442/// Affected applications must be restarted for this to take effect.
443pub fn set_session_management_enabled(enabled: bool) {
444    get!().set_session_management_enabled(enabled);
445}
446
447/// Sets whether floating windows are shown above fullscreen windows.
448///
449/// The default is `false`.
450pub fn set_float_above_fullscreen(above: bool) {
451    get!().set_float_above_fullscreen(above);
452}
453
454/// Gets whether floating windows are shown above fullscreen windows.
455pub fn get_float_above_fullscreen() -> bool {
456    get!().get_float_above_fullscreen()
457}
458
459/// Toggles whether floating windows are shown above fullscreen windows.
460///
461/// The default is `false`.
462pub fn toggle_float_above_fullscreen() {
463    set_float_above_fullscreen(!get_float_above_fullscreen())
464}
465
466/// Sets whether floating windows always show a pin icon.
467///
468/// Clicking on the pin icon toggles the pin mode. See [`Seat::toggle_float_pinned`].
469///
470/// The icon is always shown if the window is pinned. This setting only affects unpinned
471/// windows.
472pub fn set_show_float_pin_icon(show: bool) {
473    get!().set_show_float_pin_icon(show);
474}
475
476/// Sets whether the built-in bar is shown.
477///
478/// The default is `true`.
479pub fn set_show_bar(show: bool) {
480    get!().set_show_bar(show)
481}
482
483/// Returns whether the built-in bar is shown.
484pub fn get_show_bar() -> bool {
485    get!(true).get_show_bar()
486}
487
488/// Toggles whether the built-in bar is shown.
489pub fn toggle_show_bar() {
490    let get = get!();
491    get.set_show_bar(!get.get_show_bar());
492}
493
494/// Sets whether title bars on windows are shown.
495///
496/// The default is `true`.
497pub fn set_show_titles(show: bool) {
498    get!().set_show_titles(show)
499}
500
501/// Returns whether title bars on windows are shown.
502pub fn get_show_titles() -> bool {
503    get!(true).get_show_titles()
504}
505
506/// Toggles whether title bars on windows are shown.
507pub fn toggle_show_titles() {
508    let get = get!();
509    get.set_show_titles(!get.get_show_titles());
510}
511
512/// Sets a callback to run when this config is unloaded.
513///
514/// Only one callback can be set at a time. If another callback is already set, it will be
515/// dropped without being run.
516///
517/// This function can be used to terminate threads and clear reference cycles.
518pub fn on_unload(f: impl FnOnce() + 'static) {
519    get!().on_unload(f);
520}
521
522/// Enables or disables middle-click pasting.
523///
524/// This has no effect on applications that are already running.
525///
526/// The default is `true`.
527#[doc(alias("primary-selection", "primary_selection"))]
528pub fn set_middle_click_paste_enabled(enabled: bool) {
529    get!().set_middle_click_paste_enabled(enabled);
530}
531
532/// Opens the control center.
533pub fn open_control_center() {
534    get!().open_control_center();
535}
536
537/// Sets whether compositing is visualized with an overlay icon in the top left.
538///
539/// Regardless of this setting, the icon is hidden if direct scanout is active. This
540/// allows detecting direct scanout.
541///
542/// The default is `false`.
543pub fn set_visualize_compositing(visualize: bool) {
544    get!().set_visualize_compositing(visualize);
545}
546
547/// Gets whether compositing is visualized with an overlay icon in the top left.
548pub fn get_visualize_compositing() -> bool {
549    get!().get_visualize_compositing()
550}
551
552/// Toggles whether compositing is visualized with an overlay icon in the top left.
553pub fn toggle_visualize_compositing() {
554    set_visualize_compositing(!get_visualize_compositing());
555}
556
557/// Sets the timeout for desktop transactions.
558///
559/// The default is 50 milliseconds.
560///
561/// See the book for details.
562pub fn set_transaction_timeout(timeout: Duration) {
563    get!().set_transaction_timeout(timeout);
564}
565
566/// Sets the timeout for configuration sequences.
567///
568/// The default is 50 milliseconds.
569///
570/// See the book for details.
571pub fn set_configure_timeout(timeout: Duration) {
572    get!().set_configure_timeout(timeout);
573}
574
575/// Sets the callback to be called when the screen is locked or unlocked.
576///
577/// When the config is reloaded and the screen is already locked, this callback is called
578/// immediately after the initial configuration.
579pub fn on_locked<F: FnMut(bool) + 'static>(f: F) {
580    get!().on_locked(f)
581}