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}