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
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}