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 crate::_private::WorkspaceShowOpV1;
47use crate::_private::WorkspaceShowOpV2;
48use crate::_private::ipc::WorkspaceSource;
49use crate::input::FallbackOutputMode;
50use crate::input::Seat;
51use crate::keyboard::ModifiedKeySym;
52use crate::video::Connector;
53use crate::window::Window;
54use jay_proc::PrivateEnum;
55use serde::Deserialize;
56use serde::Serialize;
57use std::fmt::Debug;
58use std::fmt::Display;
59use std::fmt::Formatter;
60use std::time::Duration;
61
62#[macro_use]
63mod macros;
64#[doc(hidden)]
65pub mod _private;
66pub mod client;
67pub mod embedded;
68pub mod exec;
69pub mod input;
70pub mod io;
71pub mod keyboard;
72pub mod logging;
73pub mod status;
74pub mod tasks;
75pub mod theme;
76pub mod timer;
77pub mod video;
78pub mod window;
79pub mod workspace;
80pub mod xwayland;
81
82/// A planar direction.
83#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq)]
84pub enum Direction {
85    Left,
86    Down,
87    Up,
88    Right,
89}
90
91/// A planar axis.
92#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
93pub enum Axis {
94    Horizontal,
95    Vertical,
96}
97
98impl Axis {
99    /// Returns the axis orthogonal to `self`.
100    pub fn other(self) -> Self {
101        match self {
102            Self::Horizontal => Self::Vertical,
103            Self::Vertical => Self::Horizontal,
104        }
105    }
106}
107
108/// A planar axis relative to the dimensions of a node.
109///
110/// If the node is exactly as wide as it is high, `Major` is `Axis::Horizontal` and
111/// `Minor` is `Axis::Vertical`.
112#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq, PrivateEnum)]
113#[non_exhaustive]
114pub enum RelativeAxis {
115    /// The axis along the larger dimension of the node.
116    Major,
117    /// The axis along the smaller dimension of the node.
118    Minor,
119}
120
121/// The container that an action operates on.
122#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq, Default, PrivateEnum)]
123#[non_exhaustive]
124pub enum ContainerTarget {
125    /// The parent container of the window. This is the default.
126    #[default]
127    Parent,
128    /// The window itself.
129    ///
130    /// The action has no effect if the window is not a container.
131    Itself,
132    /// The window itself if it is a container, otherwise its parent container.
133    Auto,
134}
135
136/// Exits the compositor.
137pub fn quit() {
138    get!().quit()
139}
140
141/// Switches to a different VT.
142pub fn switch_to_vt(n: u32) {
143    get!().switch_to_vt(n)
144}
145
146/// Reloads the configuration.
147///
148/// If the configuration cannot be reloaded, this function has no effect.
149pub fn reload() {
150    get!().reload()
151}
152
153/// Returns whether this execution of the configuration function is due to a reload.
154///
155/// This can be used to decide whether the configuration should auto-start programs.
156pub fn is_reload() -> bool {
157    get!(false).is_reload()
158}
159
160/// Sets whether new workspaces are captured by default.
161///
162/// The default is `true`.
163pub fn set_default_workspace_capture(capture: bool) {
164    get!().set_default_workspace_capture(capture)
165}
166
167/// Returns whether new workspaces are captured by default.
168pub fn get_default_workspace_capture() -> bool {
169    get!(true).get_default_workspace_capture()
170}
171
172/// Toggles whether new workspaces are captured by default.
173pub fn toggle_default_workspace_capture() {
174    let get = get!();
175    get.set_default_workspace_capture(!get.get_default_workspace_capture());
176}
177
178/// A workspace.
179#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
180pub struct Workspace(pub u64);
181
182impl Workspace {
183    /// Returns whether this workspace existed at the time `Seat::get_workspace` was called.
184    pub fn exists(self) -> bool {
185        self.0 != 0
186    }
187
188    /// Sets whether the workspaces is captured.
189    ///
190    /// The default is determined by `set_default_workspace_capture`.
191    pub fn set_capture(self, capture: bool) {
192        get!().set_workspace_capture(self, capture)
193    }
194
195    /// Returns whether the workspaces is captured.
196    pub fn get_capture(self) -> bool {
197        get!(true).get_workspace_capture(self)
198    }
199
200    /// Toggles whether the workspaces is captured.
201    pub fn toggle_capture(self) {
202        let get = get!();
203        get.set_workspace_capture(self, !get.get_workspace_capture(self));
204    }
205
206    /// Moves this workspace to another output.
207    ///
208    /// This has no effect if the workspace is not currently being shown.
209    pub fn move_to_output(self, output: Connector) {
210        get!().move_to_output(WorkspaceSource::Explicit(self), output);
211    }
212
213    /// Returns the root container of this workspace.
214    ///
215    /// If no such container exists, [`Window::exists`] returns false.
216    pub fn window(self) -> Window {
217        get!(Window(0)).get_workspace_window(self)
218    }
219
220    /// Returns the connector that contains this workspace.
221    ///
222    /// If no such connector exists, [`Connector::exists`] returns false.
223    pub fn connector(self) -> Connector {
224        get!(Connector(0)).get_workspace_connector(self)
225    }
226
227    /// Creates an operation to show this workspace.
228    ///
229    /// Does nothing until [`WorkspaceShowOp::exec`] is called.
230    pub fn show(self) -> WorkspaceShowOp {
231        WorkspaceShowOp {
232            v1: WorkspaceShowOpV1 {
233                workspace: self,
234                connector: None,
235                move_to_connector: false,
236                seat: None,
237                fallback_output_mode: None,
238                focus: true,
239            },
240            v2: Default::default(),
241        }
242    }
243
244    /// Returns the kind of this workspace.
245    pub fn kind(self) -> WorkspaceKind {
246        get!(WorkspaceKind::Normal)
247            .get_workspace_kind(self)
248            .to_public()
249    }
250
251    /// Hides this workspace.
252    ///
253    /// This has no effect for normal workspaces.
254    pub fn hide(self) {
255        get!().hide_workspace(self);
256    }
257
258    /// Sets the connector on which this workspace is initially created.
259    ///
260    /// This setting can be overwritten with [`WorkspaceShowOp::connector`]. If that
261    /// function is not used or the workspace is created via some other mechanism, it will
262    /// be created on the connector set via this function, if possible.
263    pub fn set_initial_connector(self, connector: Option<Connector>) {
264        get!().set_workspace_initial_connector(self, connector);
265    }
266
267    /// Returns the position of the workspace in the global compositor space.
268    ///
269    /// This value is only accurate for visible workspaces.
270    pub fn position(self) -> (i32, i32) {
271        let (x, y, _, _) = get!((0, 0)).get_workspace_position(self);
272        (x, y)
273    }
274
275    /// Returns the size of the workspace.
276    ///
277    /// This value is only accurate for visible workspaces.
278    pub fn size(self) -> (i32, i32) {
279        let (_, _, width, height) = get!((0, 0)).get_workspace_position(self);
280        (width, height)
281    }
282}
283
284/// Returns the workspace with the given name.
285///
286/// Workspaces are identified by their name. Calling this function alone does not create the
287/// workspace if it doesn't already exist.
288///
289/// Workspaces and overlays share the same namespace. If an overlay with the same name
290/// already exists, this request changes the pending kind of the workspace from `Overlay`
291/// to `Normal`.
292pub fn get_workspace(name: &str) -> Workspace {
293    get!(Workspace(0)).get_workspace(name)
294}
295
296/// An operation to show a workspace.
297///
298/// Create this using [`Workspace::show`].
299#[must_use]
300pub struct WorkspaceShowOp {
301    v1: WorkspaceShowOpV1,
302    v2: WorkspaceShowOpV2,
303}
304
305impl WorkspaceShowOp {
306    /// Runs this operation.
307    pub fn exec(self) {
308        get!().show_workspace_3(self);
309    }
310
311    /// The connector on which to show the workspace.
312    ///
313    /// If the workspace does not already exist, it will be shown on this connector.
314    /// Otherwise, see [`WorkspaceShowOp::move_to_connector`].
315    ///
316    /// By default, this workspace is determined via the [`WorkspaceShowOp::seat`].
317    pub fn connector(mut self, c: Connector) -> Self {
318        self.v1.connector = Some(c);
319        self
320    }
321
322    /// Whether to move the workspace to the target connector if it already exists.
323    ///
324    /// The default is `false`.
325    pub fn move_to_connector(mut self, move_to_connector: bool) -> Self {
326        self.v1.move_to_connector = move_to_connector;
327        self
328    }
329
330    /// The reference seat.
331    ///
332    /// If no connector was explicitly set, this seat will be used to determine the target
333    /// output.
334    pub fn seat(mut self, s: Seat) -> Self {
335        self.v1.seat = Some(s);
336        self
337    }
338
339    /// The fallback output mode to use when the target output is determined via the
340    /// [WorkspaceShowOp::seat].
341    ///
342    /// The default is determined via [`Seat::set_fallback_output_mode`].
343    pub fn fallback_output_mode(mut self, mode: FallbackOutputMode) -> Self {
344        self.v1.fallback_output_mode = Some(mode.to_private());
345        self
346    }
347
348    /// Whether the workspace should grab the focus of the [`WorkspaceShowOp::seat`].
349    ///
350    /// The default is `true`.
351    pub fn focus(mut self, focus: bool) -> Self {
352        self.v1.focus = focus;
353        self
354    }
355
356    /// Whether this operation should hide the workspace if it is already visible.
357    ///
358    /// This has no effect for normal workspaces.
359    pub fn toggle(mut self, toggle: bool) -> Self {
360        self.v2.toggle = Some(toggle);
361        self
362    }
363}
364
365/// The kind of a workspace.
366#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, PrivateEnum)]
367#[non_exhaustive]
368pub enum WorkspaceKind {
369    /// A normal workspace.
370    Normal,
371    /// An overlay workspace.
372    Overlay,
373}
374
375/// Returns the overlay with the given name.
376///
377/// Overlays are identified by their name. Calling this function alone does not create the
378/// overlay if it doesn't already exist.
379///
380/// Workspaces and overlays share the same namespace. If a workspace with the same name
381/// already exists, this request changes the pending kind of the workspace from `Normal`
382/// to `Overlay`.
383pub fn get_overlay(name: &str) -> Workspace {
384    get!(Workspace(0)).get_overlay(name)
385}
386
387/// Hides all overlays.
388pub fn hide_overlays() {
389    get!().hide_overlays();
390}
391
392/// A PCI ID.
393///
394/// PCI IDs can be used to identify a hardware component. See the Debian [documentation][pci].
395///
396/// [pci]: https://wiki.debian.org/HowToIdentifyADevice/PCI
397#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, Eq, PartialEq, Default)]
398pub struct PciId {
399    pub vendor: u32,
400    pub model: u32,
401}
402
403impl Display for PciId {
404    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
405        write!(f, "{:04x}:{:04x}", self.vendor, self.model)
406    }
407}
408
409/// Sets the callback to be called when the display goes idle.
410pub fn on_idle<F: FnMut() + 'static>(f: F) {
411    get!().on_idle(f)
412}
413
414/// Sets the callback to be called when all devices have been enumerated.
415///
416/// This callback is only invoked once during the lifetime of the compositor. This is a
417/// good place to select the DRM device used for rendering.
418pub fn on_devices_enumerated<F: FnOnce() + 'static>(f: F) {
419    get!().on_devices_enumerated(f)
420}
421
422/// Returns the Jay config directory.
423pub fn config_dir() -> String {
424    get!().config_dir()
425}
426
427/// Returns all visible workspaces.
428pub fn workspaces() -> Vec<Workspace> {
429    get!().workspaces()
430}
431
432/// Configures the idle timeout.
433///
434/// `None` disables the timeout.
435///
436/// The default is 10 minutes.
437pub fn set_idle(timeout: Option<Duration>) {
438    get!().set_idle(timeout.unwrap_or_default())
439}
440
441/// Configures the idle grace period.
442///
443/// The grace period starts after the idle timeout expires. During the grace period, the
444/// screen goes black but the displays are not yet disabled and the idle callback (set
445/// with [`on_idle`]) is not yet called. This is a purely visual effect to inform the user
446/// that the machine will soon go idle.
447///
448/// The default is 5 seconds.
449pub fn set_idle_grace_period(timeout: Duration) {
450    get!().set_idle_grace_period(timeout)
451}
452
453/// Enables or disables explicit sync.
454///
455/// Calling this after the compositor has started has no effect.
456///
457/// The default is `true`.
458pub fn set_explicit_sync_enabled(enabled: bool) {
459    get!().set_explicit_sync_enabled(enabled);
460}
461
462/// Enables or disables dragging of tiles and workspaces.
463///
464/// The default is `true`.
465pub fn set_ui_drag_enabled(enabled: bool) {
466    get!().set_ui_drag_enabled(enabled);
467}
468
469/// Sets the distance at which ui dragging starts.
470///
471/// The default is `10`.
472pub fn set_ui_drag_threshold(threshold: i32) {
473    get!().set_ui_drag_threshold(threshold);
474}
475
476/// Enables or disables the color-management protocol.
477///
478/// The default is `false`.
479///
480/// Affected applications must be restarted for this to take effect.
481pub fn set_color_management_enabled(enabled: bool) {
482    get!().set_color_management_enabled(enabled);
483}
484
485/// Enables or disables the session-management protocol.
486///
487/// The default is `true`.
488///
489/// Affected applications must be restarted for this to take effect.
490pub fn set_session_management_enabled(enabled: bool) {
491    get!().set_session_management_enabled(enabled);
492}
493
494/// Sets whether floating windows are shown above fullscreen windows.
495///
496/// The default is `false`.
497pub fn set_float_above_fullscreen(above: bool) {
498    get!().set_float_above_fullscreen(above);
499}
500
501/// Gets whether floating windows are shown above fullscreen windows.
502pub fn get_float_above_fullscreen() -> bool {
503    get!().get_float_above_fullscreen()
504}
505
506/// Toggles whether floating windows are shown above fullscreen windows.
507///
508/// The default is `false`.
509pub fn toggle_float_above_fullscreen() {
510    set_float_above_fullscreen(!get_float_above_fullscreen())
511}
512
513/// Sets whether floating windows always show a pin icon.
514///
515/// Clicking on the pin icon toggles the pin mode. See [`Seat::toggle_float_pinned`].
516///
517/// The icon is always shown if the window is pinned. This setting only affects unpinned
518/// windows.
519pub fn set_show_float_pin_icon(show: bool) {
520    get!().set_show_float_pin_icon(show);
521}
522
523/// Sets whether splitting a window that is the only child of its container reuses
524/// that container.
525///
526/// If disabled, splitting such a window wraps it in another container. If enabled, the
527/// split direction of the existing container is changed instead.
528///
529/// The default is `false`.
530pub fn set_split_reuses_container(reuse: bool) {
531    get!().set_split_reuses_container(reuse)
532}
533
534/// Returns whether splitting a window that is the only child of its container reuses
535/// that container.
536pub fn get_split_reuses_container() -> bool {
537    get!(false).get_split_reuses_container()
538}
539
540/// Toggles whether splitting a window that is the only child of its container reuses
541/// that container.
542pub fn toggle_split_reuses_container() {
543    let get = get!();
544    get.set_split_reuses_container(!get.get_split_reuses_container());
545}
546
547/// Sets whether the built-in bar is shown.
548///
549/// The default is `true`.
550pub fn set_show_bar(show: bool) {
551    get!().set_show_bar(show)
552}
553
554/// Returns whether the built-in bar is shown.
555pub fn get_show_bar() -> bool {
556    get!(true).get_show_bar()
557}
558
559/// Toggles whether the built-in bar is shown.
560pub fn toggle_show_bar() {
561    let get = get!();
562    get.set_show_bar(!get.get_show_bar());
563}
564
565/// Sets whether title bars on windows are shown.
566///
567/// The default is `true`.
568pub fn set_show_titles(show: bool) {
569    get!().set_show_titles(show)
570}
571
572/// Returns whether title bars on windows are shown.
573pub fn get_show_titles() -> bool {
574    get!(true).get_show_titles()
575}
576
577/// Toggles whether title bars on windows are shown.
578pub fn toggle_show_titles() {
579    let get = get!();
580    get.set_show_titles(!get.get_show_titles());
581}
582
583/// Sets a callback to run when this config is unloaded.
584///
585/// Only one callback can be set at a time. If another callback is already set, it will be
586/// dropped without being run.
587///
588/// This function can be used to terminate threads and clear reference cycles.
589pub fn on_unload(f: impl FnOnce() + 'static) {
590    get!().on_unload(f);
591}
592
593/// Enables or disables middle-click pasting.
594///
595/// This has no effect on applications that are already running.
596///
597/// The default is `true`.
598#[doc(alias("primary-selection", "primary_selection"))]
599pub fn set_middle_click_paste_enabled(enabled: bool) {
600    get!().set_middle_click_paste_enabled(enabled);
601}
602
603/// Opens the control center.
604pub fn open_control_center() {
605    get!().open_control_center();
606}
607
608/// Sets whether compositing is visualized with an overlay icon in the top left.
609///
610/// Regardless of this setting, the icon is hidden if direct scanout is active. This
611/// allows detecting direct scanout.
612///
613/// The default is `false`.
614pub fn set_visualize_compositing(visualize: bool) {
615    get!().set_visualize_compositing(visualize);
616}
617
618/// Gets whether compositing is visualized with an overlay icon in the top left.
619pub fn get_visualize_compositing() -> bool {
620    get!().get_visualize_compositing()
621}
622
623/// Toggles whether compositing is visualized with an overlay icon in the top left.
624pub fn toggle_visualize_compositing() {
625    set_visualize_compositing(!get_visualize_compositing());
626}
627
628/// Sets the timeout for desktop transactions.
629///
630/// The default is 50 milliseconds.
631///
632/// See the book for details.
633pub fn set_transaction_timeout(timeout: Duration) {
634    get!().set_transaction_timeout(timeout);
635}
636
637/// Sets the timeout for configuration sequences.
638///
639/// The default is 50 milliseconds.
640///
641/// See the book for details.
642pub fn set_configure_timeout(timeout: Duration) {
643    get!().set_configure_timeout(timeout);
644}
645
646/// Sets the callback to be called when the screen is locked or unlocked.
647///
648/// When the config is reloaded and the screen is already locked, this callback is called
649/// immediately after the initial configuration.
650pub fn on_locked<F: FnMut(bool) + 'static>(f: F) {
651    get!().on_locked(f)
652}