1
0
Fork 0
forked from wry/wry

refactor: split cargo workspace

This commit is contained in:
kossLAN 2026-06-05 11:56:21 -04:00
parent 5db14936e7
commit 1c21bd1259
695 changed files with 32023 additions and 44964 deletions

View file

@ -0,0 +1,15 @@
pub mod client;
mod logging;
pub use crate::protocol::{
ClientCriterionPayload, ClientCriterionStringField, ConfigEntry, ConfigHandler,
DEFAULT_SEAT_NAME, GenericCriterionPayload, PollableId, ServerHandler, Unref, VERSION,
WindowCriterionPayload, WindowCriterionStringField, WireMode,
};
pub mod messages {
pub use crate::protocol::{
ClientMessage, InitMessage, Response, ServerFeature, ServerMessage, V1InitMessage,
WorkspaceSource,
};
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,61 @@
use {
crate::logging::LogLevel,
backtrace::Backtrace,
log::{Level, LevelFilter, Log, Metadata, Record},
};
pub fn init() {
let _ = log::set_logger(&Logger);
log::set_max_level(LevelFilter::Trace);
std::panic::set_hook(Box::new(|p| {
if let Some(loc) = p.location() {
log::error!(
"Panic at {} line {} column {}",
loc.file(),
loc.line(),
loc.column()
);
} else {
log::error!("Panic at unknown location");
}
if let Some(msg) = p.payload().downcast_ref::<&str>() {
log::error!("Message: {}", msg);
}
if let Some(msg) = p.payload().downcast_ref::<String>() {
log::error!("Message: {}", msg);
}
log::error!("Backtrace:\n{:?}", Backtrace::new());
}));
}
struct Logger;
impl Log for Logger {
fn enabled(&self, _metadata: &Metadata) -> bool {
true
}
fn log(&self, record: &Record) {
let client = get!();
let level = match record.level() {
Level::Error => LogLevel::Error,
Level::Warn => LogLevel::Warn,
Level::Info => LogLevel::Info,
Level::Debug => LogLevel::Debug,
Level::Trace => LogLevel::Trace,
};
let formatted;
let msg = match record.args().as_str() {
Some(s) => s,
_ => {
formatted = record.args().to_string();
&formatted
}
};
client.log(level, msg, record.file(), record.line());
}
fn flush(&self) {
// nothing
}
}

View file

@ -0,0 +1,155 @@
//! Tools for inspecting and manipulating clients.
use {
serde::{Deserialize, Serialize},
std::ops::Deref,
};
/// A client connected to the compositor.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct Client(pub u64);
impl Client {
/// Returns whether the client exists.
pub fn exists(self) -> bool {
self.0 != 0 && get!(false).client_exists(self)
}
/// Returns whether the client does not exist.
///
/// This is a shorthand for `!self.exists()`.
pub fn does_not_exist(self) -> bool {
!self.exists()
}
/// Returns whether this client is XWayland.
pub fn is_xwayland(self) -> bool {
get!(false).client_is_xwayland(self)
}
/// Disconnects the client.
pub fn kill(self) {
get!().client_kill(self)
}
}
/// Returns all current clients.
pub fn clients() -> Vec<Client> {
get!().clients()
}
/// A client matcher.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct ClientMatcher(pub u64);
/// A matched client.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct MatchedClient {
pub(crate) matcher: ClientMatcher,
pub(crate) client: Client,
}
/// A criterion for matching a client.
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
#[non_exhaustive]
pub enum ClientCriterion<'a> {
/// Matches if the contained matcher matches.
Matcher(ClientMatcher),
/// Matches if the contained criterion does not match.
Not(&'a ClientCriterion<'a>),
/// Matches if all of the contained criteria match.
All(&'a [ClientCriterion<'a>]),
/// Matches if any of the contained criteria match.
Any(&'a [ClientCriterion<'a>]),
/// Matches if an exact number of the contained criteria match.
Exactly(usize, &'a [ClientCriterion<'a>]),
/// Matches the engine name of the client's sandbox verbatim.
SandboxEngine(&'a str),
/// Matches the engine name of the client's sandbox with a regular expression.
SandboxEngineRegex(&'a str),
/// Matches the app id of the client's sandbox verbatim.
SandboxAppId(&'a str),
/// Matches the app id of the client's sandbox with a regular expression.
SandboxAppIdRegex(&'a str),
/// Matches the instance id of the client's sandbox verbatim.
SandboxInstanceId(&'a str),
/// Matches the instance id of the client's sandbox with a regular expression.
SandboxInstanceIdRegex(&'a str),
/// Matches if the client is sandboxed.
Sandboxed,
/// Matches the user ID of the client.
Uid(i32),
/// Matches the process ID of the client.
Pid(i32),
/// Matches if the client is Xwayland.
IsXwayland,
/// Matches the `/proc/pid/comm` of the client verbatim.
Comm(&'a str),
/// Matches the `/proc/pid/comm` of the client with a regular expression.
CommRegex(&'a str),
/// Matches the `/proc/pid/exe` of the client verbatim.
Exe(&'a str),
/// Matches the `/proc/pid/exe` of the client with a regular expression.
ExeRegex(&'a str),
/// Matches the tag of the client verbatim.
Tag(&'a str),
/// Matches the tag of the client with a regular expression.
TagRegex(&'a str),
}
impl ClientCriterion<'_> {
/// Converts the criterion to a matcher.
pub fn to_matcher(self) -> ClientMatcher {
get!(ClientMatcher(0)).create_client_matcher(self)
}
/// Binds a function to execute when the criterion matches a client.
///
/// This leaks the matcher.
pub fn bind<F: FnMut(MatchedClient) + 'static>(self, cb: F) {
self.to_matcher().bind(cb);
}
}
impl ClientMatcher {
/// Destroys the matcher.
///
/// Any bound callback will no longer be executed.
pub fn destroy(self) {
get!().destroy_client_matcher(self);
}
/// Sets a function to execute when the criterion matches a client.
///
/// Replaces any already bound callback.
pub fn bind<F: FnMut(MatchedClient) + 'static>(self, cb: F) {
get!().set_client_matcher_handler(self, cb);
}
}
impl MatchedClient {
/// Returns the client that matched.
pub fn client(self) -> Client {
self.client
}
/// Returns the matcher.
pub fn matcher(self) -> ClientMatcher {
self.matcher
}
/// Latches a function to be executed when the client no longer matches the criteria.
pub fn latch<F: FnOnce() + 'static>(self, cb: F) {
get!().set_client_matcher_latch_handler(self.matcher, self.client, cb);
}
}
impl Deref for MatchedClient {
type Target = Client;
fn deref(&self) -> &Self::Target {
&self.client
}
}

View file

@ -0,0 +1,11 @@
//! Tools to configure the compositor in embedded environments.
use crate::input::InputDevice;
/// Grab the input device.
///
/// This usually only works if the compositor is running as an application under X. It will
/// probably not work under XWayland.
pub fn grab_input_device(kb: InputDevice, grab: bool) {
get!().grab(kb, grab);
}

View file

@ -0,0 +1,99 @@
//! Tools for spawning programs.
use std::{cell::RefCell, collections::HashMap, os::fd::OwnedFd};
/// Sets an environment variable.
///
/// This does not affect the compositor itself but only programs spawned by the compositor.
pub fn set_env(key: &str, val: &str) {
get!().set_env(key, val);
}
/// Unsets an environment variable.
///
/// This does not affect the compositor itself but only programs spawned by the compositor.
pub fn unset_env(key: &str) {
get!().unset_env(key);
}
/// A command to be spawned.
pub struct Command {
pub(crate) prog: String,
pub(crate) args: Vec<String>,
pub(crate) env: HashMap<String, String>,
pub(crate) fds: RefCell<HashMap<i32, OwnedFd>>,
pub(crate) tag: Option<String>,
}
impl Command {
/// Creates a new command to be spawned.
///
/// `prog` should be the path to the program being spawned. If `prog` does not contain
/// a `/`, then it will be searched in `PATH` similar to how a shell would do it.
///
/// The first argument passed to `prog`, `argv[0]`, is `prog` itself.
pub fn new(prog: &str) -> Self {
Self {
prog: prog.to_string(),
args: vec![],
env: Default::default(),
fds: Default::default(),
tag: Default::default(),
}
}
/// Adds an argument to be passed to the command.
pub fn arg(&mut self, arg: &str) -> &mut Self {
self.args.push(arg.to_string());
self
}
/// Sets an environment variable for this command only.
pub fn env(&mut self, key: &str, val: &str) -> &mut Self {
self.env.insert(key.to_string(), val.to_string());
self
}
/// Sets a file descriptor of the process.
///
/// By default, the process starts with exactly stdin, stdout, and stderr open and all
/// pointing to `/dev/null`.
pub fn fd<F: Into<OwnedFd>>(&mut self, idx: i32, fd: F) -> &mut Self {
self.fds.borrow_mut().insert(idx, fd.into());
self
}
/// Sets the stdin of the process.
///
/// This is equivalent to `fd(0, fd)`.
pub fn stdin<F: Into<OwnedFd>>(&mut self, fd: F) -> &mut Self {
self.fd(0, fd)
}
/// Sets the stdout of the process.
///
/// This is equivalent to `fd(1, fd)`.
pub fn stdout<F: Into<OwnedFd>>(&mut self, fd: F) -> &mut Self {
self.fd(1, fd)
}
/// Sets the stderr of the process.
///
/// This is equivalent to `fd(2, fd)`.
pub fn stderr<F: Into<OwnedFd>>(&mut self, fd: F) -> &mut Self {
self.fd(2, fd)
}
/// Adds a tag to Wayland connections created by the spawned command.
pub fn tag(&mut self, tag: &str) -> &mut Self {
self.tag = Some(tag.to_owned());
self
}
/// Executes the command.
///
/// This consumes all attached file descriptors.
pub fn spawn(&self) {
get!().spawn(self);
}
}

View file

@ -0,0 +1,865 @@
//! Tools for configuring input devices.
pub mod acceleration;
pub mod capability;
pub mod clickmethod;
use {
crate::{
Axis, Direction, ModifiedKeySym, Workspace,
input::{acceleration::AccelProfile, capability::Capability, clickmethod::ClickMethod},
keyboard::{Keymap, mods::Modifiers, syms::KeySym},
protocol::{DEFAULT_SEAT_NAME, WorkspaceSource},
video::Connector,
window::Window,
},
serde::{Deserialize, Serialize},
std::time::Duration,
};
/// An input device.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct InputDevice(pub u64);
impl InputDevice {
/// Assigns the input device to a seat.
pub fn set_seat(self, seat: Seat) {
get!().set_seat(self, seat)
}
/// Sets the keymap of the device.
///
/// This overrides the keymap set for the seat. The keymap becomes active when a key
/// on the device is pressed.
///
/// Setting the invalid keymap reverts to the seat keymap.
pub fn set_keymap(self, keymap: Keymap) {
get!().set_device_keymap(self, keymap)
}
/// Returns whether the device has the specified capability.
pub fn has_capability(self, cap: Capability) -> bool {
get!(false).has_capability(self, cap)
}
/// Sets the device to be left handed.
///
/// This has the effect of swapping the left and right mouse button. See the libinput
/// documentation for more details.
pub fn set_left_handed(self, left_handed: bool) {
get!().set_left_handed(self, left_handed);
}
/// Sets the acceleration profile of the device.
///
/// This corresponds to the libinput setting of the same name.
pub fn set_accel_profile(self, profile: AccelProfile) {
get!().set_accel_profile(self, profile);
}
/// Sets the acceleration speed of the device.
///
/// This corresponds to the libinput setting of the same name.
pub fn set_accel_speed(self, speed: f64) {
get!().set_accel_speed(self, speed);
}
/// Sets the transformation matrix of the device.
///
/// This is not a libinput setting but a setting of the compositor. It currently affects
/// relative mouse motions in that the matrix is applied to the motion. To reduce the mouse
/// speed to 35%, use the following matrix:
///
/// ```text
/// [
/// [0.35, 1.0],
/// [1.0, 0.35],
/// ]
/// ```
///
/// This might give you better results than using `set_accel_profile` and `set_accel_speed`.
pub fn set_transform_matrix(self, matrix: [[f64; 2]; 2]) {
get!().set_transform_matrix(self, matrix);
}
/// Sets the calibration matrix of the device.
///
/// This corresponds to the libinput setting of the same name.
pub fn set_calibration_matrix(self, matrix: [[f32; 3]; 2]) {
get!().set_calibration_matrix(self, matrix);
}
/// Returns the name of the device.
pub fn name(self) -> String {
get!(String::new()).device_name(self)
}
/// Sets how many pixel to scroll per scroll wheel dedent.
///
/// Default: `15.0`
///
/// This setting has no effect on non-wheel input such as touchpads.
///
/// Some mouse wheels support high-resolution scrolling without discrete steps. In
/// this case a value proportional to this setting will be used.
pub fn set_px_per_wheel_scroll(self, px: f64) {
get!().set_px_per_wheel_scroll(self, px);
}
/// Sets whether tap-to-click is enabled for this device.
///
/// See <https://wayland.freedesktop.org/libinput/doc/latest/tapping.html>
pub fn set_tap_enabled(self, enabled: bool) {
get!().set_input_tap_enabled(self, enabled);
}
/// Sets whether tap-and-drag is enabled for this device.
///
/// See <https://wayland.freedesktop.org/libinput/doc/latest/tapping.html>
pub fn set_drag_enabled(self, enabled: bool) {
get!().set_input_drag_enabled(self, enabled);
}
/// Sets whether drag lock is enabled for this device.
///
/// See <https://wayland.freedesktop.org/libinput/doc/latest/tapping.html>
pub fn set_drag_lock_enabled(self, enabled: bool) {
get!().set_input_drag_lock_enabled(self, enabled);
}
/// Sets whether natural scrolling is enabled for this device.
///
/// See <https://wayland.freedesktop.org/libinput/doc/latest/scrolling.html>
pub fn set_natural_scrolling_enabled(self, enabled: bool) {
get!().set_input_natural_scrolling_enabled(self, enabled);
}
/// Sets the click method of the device.
///
/// See <https://wayland.freedesktop.org/libinput/doc/latest/configuration.html#click-method>
pub fn set_click_method(self, method: ClickMethod) {
get!().set_input_click_method(self, method);
}
/// Sets whether middle button emulation is enabled for this device.
///
/// See <https://wayland.freedesktop.org/libinput/doc/latest/configuration.html#middle-button-emulation>
pub fn set_middle_button_emulation_enabled(self, enabled: bool) {
get!().set_input_middle_button_emulation_enabled(self, enabled);
}
/// Returns the syspath of this device.
///
/// E.g. `/sys/devices/pci0000:00/0000:00:08.1/0000:14:00.4/usb5/5-1/5-1.1/5-1.1.3/5-1.1.3:1.0`.
pub fn syspath(self) -> String {
get!(String::new()).input_device_syspath(self)
}
/// Returns the devnode of this device.
///
/// E.g. `/dev/input/event7`.
pub fn devnode(self) -> String {
get!(String::new()).input_device_devnode(self)
}
/// Sets a callback that will be run if this device triggers a switch event.
pub fn on_switch_event<F: FnMut(SwitchEvent) + 'static>(self, f: F) {
get!().on_switch_event(self, f)
}
/// Maps this input device to a connector.
///
/// The connector should be connected.
///
/// This should be used for touch screens and graphics tablets.
pub fn set_connector(self, connector: Connector) {
get!().set_input_device_connector(self, connector);
}
/// Removes the mapping of this device to a connector.
pub fn remove_mapping(self) {
get!().remove_input_mapping(self);
}
}
/// A direction in a timeline.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub enum Timeline {
Older,
Newer,
}
/// A direction for layer traversal.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub enum LayerDirection {
Below,
Above,
}
/// A seat.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct Seat(pub u64);
impl Seat {
pub const INVALID: Self = Self(0);
/// Returns whether the seat is invalid.
pub fn is_invalid(self) -> bool {
self == Self::INVALID
}
#[doc(hidden)]
pub fn raw(self) -> u64 {
self.0
}
#[doc(hidden)]
pub fn from_raw(raw: u64) -> Self {
Self(raw)
}
/// Sets whether this seat's cursor uses the hardware cursor if available.
///
/// Only one seat at a time can use the hardware cursor. Setting this to `true` for a
/// seat automatically unsets it for all other seats.
///
/// By default, the first created seat uses the hardware cursor.
pub fn use_hardware_cursor(self, use_hardware_cursor: bool) {
get!().set_use_hardware_cursor(self, use_hardware_cursor);
}
/// Sets the size of the cursor theme.
///
/// Default: 16.
pub fn set_cursor_size(self, size: i32) {
get!().set_cursor_size(self, size)
}
/// Creates a compositor-wide hotkey.
///
/// The closure is invoked when the user presses the last key of the modified keysym.
/// Note that the keysym is calculated without modifiers applied. To perform an action
/// when `SHIFT+k` is pressed, use `SHIFT | SYM_k` not `SHIFT | SYM_K`.
///
/// CapsLock and NumLock are ignored during modifier evaluation. Therefore, bindings
/// containing these modifiers will never be invoked.
pub fn bind<T: Into<ModifiedKeySym>, F: FnMut() + 'static>(self, mod_sym: T, f: F) {
self.bind_masked(Modifiers(!0), mod_sym, f)
}
/// Creates a compositor-wide hotkey while ignoring some modifiers.
///
/// This is similar to `bind` except that only the masked modifiers are considered.
///
/// For example, if this function is invoked with `mod_mask = Modifiers::NONE` and
/// `mod_sym = SYM_XF86AudioRaiseVolume`, then the callback will be invoked whenever
/// `SYM_XF86AudioRaiseVolume` is pressed. Even if the user is simultaneously holding
/// the shift key which would otherwise prevent the callback from taking effect.
///
/// For example, if this function is invoked with `mod_mask = CTRL | SHIFT` and
/// `mod_sym = CTRL | SYM_x`, then the callback will be invoked whenever the user
/// presses `ctrl+x` without pressing the shift key. Even if the user is
/// simultaneously holding the alt key.
///
/// If `mod_sym` contains any modifiers, then these modifiers are automatically added
/// to the mask. The synthetic `RELEASE` modifier is always added to the mask.
pub fn bind_masked<T: Into<ModifiedKeySym>, F: FnMut() + 'static>(
self,
mod_mask: Modifiers,
mod_sym: T,
f: F,
) {
get!().bind_masked(self, mod_mask, mod_sym.into(), f)
}
/// Registers a callback to be executed when the currently pressed key is released.
///
/// This should only be called in callbacks for key-press binds.
///
/// The callback will be executed once when the key is released regardless of any
/// modifiers.
pub fn latch<F: FnOnce() + 'static>(self, f: F) {
get!().latch(self, f)
}
/// Unbinds a hotkey.
pub fn unbind<T: Into<ModifiedKeySym>>(self, mod_sym: T) {
get!().unbind(self, mod_sym.into())
}
/// Moves the focus in the focus history.
pub fn focus_history(self, timeline: Timeline) {
get!().seat_focus_history(self, timeline)
}
/// Configures whether the focus history only includes visible windows.
///
/// If this is `false`, then hidden windows will be made visible before moving the
/// focus to them.
///
/// The default is `false`.
pub fn focus_history_set_only_visible(self, only_visible: bool) {
get!().seat_focus_history_set_only_visible(self, only_visible)
}
/// Configures whether the focus history only includes windows on the same workspace
/// as the currently focused window.
///
/// The default is `false`.
pub fn focus_history_set_same_workspace(self, same_workspace: bool) {
get!().seat_focus_history_set_same_workspace(self, same_workspace)
}
/// Moves the keyboard focus of the seat to the layer above or below the current
/// layer.
pub fn focus_layer_rel(self, direction: LayerDirection) {
get!().seat_focus_layer_rel(self, direction)
}
/// Moves the keyboard focus to the tile layer.
pub fn focus_tiles(self) {
get!().seat_focus_tiles(self)
}
/// Moves the keyboard focus to the topmost floating window.
pub fn focus_floats(self) {
get!().seat_focus_floats(self)
}
/// Toggles keyboard focus between the floating and tiled layers.
///
/// If focus is on the tiled or fullscreen layer, focus moves to the topmost float.
/// If focus is on the floating layer, focus moves to the tiled layer.
pub fn toggle_focus_float_tiled(self) {
get!().seat_toggle_focus_float_tiled(self)
}
/// Moves the keyboard focus of the seat in the specified direction.
pub fn focus(self, direction: Direction) {
get!().seat_focus(self, direction)
}
/// Moves the focused window in the specified direction.
pub fn move_(self, direction: Direction) {
get!().seat_move(self, direction)
}
/// Sets the keymap of the seat.
pub fn set_keymap(self, keymap: Keymap) {
get!().seat_set_keymap(self, keymap)
}
/// Returns the repeat rate of the seat.
///
/// The returned tuple is `(rate, delay)` where `rate` is the number of times keys repeat per second
/// and `delay` is the time after the button press after which keys start repeating.
pub fn repeat_rate(self) -> (i32, i32) {
get!((25, 250)).seat_get_repeat_rate(self)
}
/// Sets the repeat rate of the seat.
pub fn set_repeat_rate(self, rate: i32, delay: i32) {
get!().seat_set_repeat_rate(self, rate, delay)
}
/// Returns whether the parent-container of the currently focused window is in mono-mode.
pub fn mono(self) -> bool {
get!(false).seat_mono(self)
}
/// Sets whether the parent-container of the currently focused window is in mono-mode.
pub fn set_mono(self, mono: bool) {
get!().set_seat_mono(self, mono)
}
/// Toggles whether the parent-container of the currently focused window is in mono-mode.
pub fn toggle_mono(self) {
self.set_mono(!self.mono());
}
/// Returns the split axis of the parent-container of the currently focused window.
pub fn split(self) -> Axis {
get!(Axis::Horizontal).seat_split(self)
}
/// Sets the split axis of the parent-container of the currently focused window.
pub fn set_split(self, axis: Axis) {
get!().set_seat_split(self, axis)
}
/// Toggles the split axis of the parent-container of the currently focused window.
pub fn toggle_split(self) {
self.set_split(self.split().other());
}
/// Returns the input devices assigned to this seat.
pub fn input_devices(self) -> Vec<InputDevice> {
get!().get_input_devices(Some(self))
}
/// Creates a new container with the specified split in place of the currently focused window.
pub fn create_split(self, axis: Axis) {
get!().create_seat_split(self, axis);
}
/// Focuses the parent node of the currently focused window.
pub fn focus_parent(self) {
get!().focus_seat_parent(self);
}
/// Requests the currently focused window to be closed.
pub fn close(self) {
get!().seat_close(self);
}
/// Returns whether the currently focused window is floating.
pub fn get_floating(self) -> bool {
get!().get_seat_floating(self)
}
/// Sets whether the currently focused window is floating.
pub fn set_floating(self, floating: bool) {
get!().set_seat_floating(self, floating);
}
/// Toggles whether the currently focused window is floating.
///
/// You can do the same by double-clicking on the header.
pub fn toggle_floating(self) {
get!().toggle_seat_floating(self);
}
/// Returns the workspace that is currently active on the output that contains the seat's
/// cursor.
///
/// If no such workspace exists, `exists` returns `false` for the returned workspace.
pub fn get_workspace(self) -> Workspace {
get!(Workspace(0)).get_seat_cursor_workspace(self)
}
/// Returns the workspace that is currently active on the output that contains the seat's
/// keyboard focus.
///
/// If no such workspace exists, `exists` returns `false` for the returned workspace.
pub fn get_keyboard_workspace(self) -> Workspace {
get!(Workspace(0)).get_seat_keyboard_workspace(self)
}
/// Shows the workspace and sets the keyboard focus of the seat to that workspace.
///
/// If the workspace doesn't currently exist, it is created on the output that contains the
/// seat's cursor.
pub fn show_workspace(self, workspace: Workspace) {
get!().show_workspace(self, workspace)
}
/// Shows the workspace and sets the keyboard focus of the seat to that workspace.
///
/// If the workspace doesn't currently exist and the connector is connected, the
/// workspace is created on the given connector. If the connector is not connected,
/// the workspace is created on the output that contains the seat's cursor.
pub fn show_workspace_on(self, workspace: Workspace, connector: Connector) {
get!().show_workspace_on(self, workspace, connector)
}
/// Moves the currently focused window to the workspace.
pub fn set_workspace(self, workspace: Workspace) {
get!().set_seat_workspace(self, workspace)
}
/// Sends the currently focused window to a scratchpad.
///
/// Use an empty string for the default scratchpad.
pub fn send_to_scratchpad(self, name: &str) {
get!().seat_send_to_scratchpad(self, name)
}
/// Toggles a scratchpad.
///
/// If the scratchpad has a visible window, that window is hidden. Otherwise, the
/// most recently hidden window in the scratchpad is shown on the current workspace.
/// Scratchpad windows are always shown floating.
/// Use an empty string for the default scratchpad.
pub fn toggle_scratchpad(self, name: &str) {
get!().seat_toggle_scratchpad(self, name)
}
/// Cycles through the windows of a scratchpad, one at a time.
///
/// With nothing shown, the first window is brought up; each further invocation
/// hides the current window and shows the next; after the last window the
/// scratchpad is hidden again. Scratchpad windows are always shown floating.
/// Use an empty string for the default scratchpad.
pub fn cycle_scratchpad(self, name: &str) {
get!().seat_cycle_scratchpad(self, name)
}
/// Toggles whether the currently focused window is fullscreen.
pub fn toggle_fullscreen(self) {
let c = get!();
c.set_seat_fullscreen(self, !c.get_seat_fullscreen(self));
}
/// Returns whether the currently focused window is fullscreen.
pub fn fullscreen(self) -> bool {
get!(false).get_seat_fullscreen(self)
}
/// Sets whether the currently focused window is fullscreen.
pub fn set_fullscreen(self, fullscreen: bool) {
get!().set_seat_fullscreen(self, fullscreen)
}
/// Disables the currently active pointer constraint on this seat.
pub fn disable_pointer_constraint(self) {
get!().disable_pointer_constraint(self)
}
/// Moves the currently focused workspace to another output.
pub fn move_to_output(self, connector: Connector) {
get!().move_to_output(WorkspaceSource::Seat(self), connector);
}
/// Set whether the current key event is forwarded to the focused client.
///
/// This only has an effect if called from a keyboard shortcut.
///
/// By default, release events are forwarded and press events are consumed. Note that
/// consuming release events can cause clients to get stuck in the pressed state.
pub fn set_forward(self, forward: bool) {
get!().set_forward(self, forward);
}
/// This is a shorthand for `set_forward(true)`.
pub fn forward(self) {
self.set_forward(true)
}
/// This is a shorthand for `set_forward(false)`.
pub fn consume(self) {
self.set_forward(false)
}
/// Sets the focus-follows-mouse mode.
pub fn set_focus_follows_mouse_mode(self, mode: FocusFollowsMouseMode) {
get!().set_focus_follows_mouse_mode(self, mode);
}
/// Sets the fallback output mode.
///
/// The default is `Cursor`.
pub fn set_fallback_output_mode(self, mode: FallbackOutputMode) {
get!().set_fallback_output_mode(self, mode);
}
/// Enables or disable window management mode.
///
/// In window management mode, floating windows can be moved by pressing the left
/// mouse button and all windows can be resize by pressing the right mouse button.
pub fn set_window_management_enabled(self, enabled: bool) {
get!().set_window_management_enabled(self, enabled);
}
/// Sets a key that enables window management mode while pressed.
///
/// This is a shorthand for
///
/// ```rust,ignore
/// self.bind(mod_sym, move || {
/// self.set_window_management_enabled(true);
/// self.forward();
/// self.latch(move || {
/// self.set_window_management_enabled(false);
/// });
/// });
/// ```
pub fn set_window_management_key<T: Into<ModifiedKeySym>>(self, mod_sym: T) {
self.bind(mod_sym, move || {
self.set_window_management_enabled(true);
self.forward();
self.latch(move || {
self.set_window_management_enabled(false);
});
});
}
/// Gets whether the currently focused window is pinned.
///
/// If a floating window is pinned, it will stay visible even when switching to a
/// different workspace.
pub fn float_pinned(self) -> bool {
get!().get_pinned(self)
}
/// Sets whether the currently focused window is pinned.
pub fn set_float_pinned(self, pinned: bool) {
get!().set_pinned(self, pinned);
}
/// Toggles whether the currently focused window is pinned.
pub fn toggle_float_pinned(self) {
self.set_float_pinned(!self.float_pinned());
}
/// Returns the focused window.
///
/// If no window is focused, [`Window::exists`] returns false.
pub fn window(self) -> Window {
get!(Window(0)).get_seat_keyboard_window(self)
}
/// Puts the keyboard focus on the window.
///
/// This has no effect if the window is not visible.
pub fn focus_window(self, window: Window) {
get!().focus_window(self, window)
}
/// Sets the key that can be used to revert the pointer to the default state.
///
/// Pressing this key cancels any grabs, drags, selections, etc.
///
/// The default is `SYM_Escape`. Setting this to `SYM_NoSymbol` effectively disables
/// this functionality.
pub fn set_pointer_revert_key(self, sym: KeySym) {
get!().set_pointer_revert_key(self, sym);
}
/// Creates a mark for the currently focused window.
///
/// `kc` should be an evdev keycode. If `kc` is none, then the keycode will be
/// inferred from the next key press. Pressing escape during this interactive
/// selection aborts the process.
///
/// Currently very few `u32` are valid keycodes. Large numbers can therefore be used
/// to create marks that do not correspond to a key. However, `kc` should always be
/// less than `u32::MAX - 8`.
pub fn create_mark(self, kc: Option<u32>) {
get!().seat_create_mark(self, kc);
}
/// Moves the keyboard focus to a window identified by a mark.
///
/// See [`Seat::create_mark`] for information about the `kc` parameter.
pub fn jump_to_mark(self, kc: Option<u32>) {
get!().seat_jump_to_mark(self, kc);
}
/// Copies a mark from one keycode to another.
///
/// If the `src` keycode identifies a mark before this function is called, the `dst`
/// keycode will identify the same mark afterwards.
pub fn copy_mark(self, src: u32, dst: u32) {
get!().seat_copy_mark(self, src, dst);
}
/// Sets whether the simple, XCompose based input method is enabled.
///
/// Regardless of this setting, this input method is not used if an external input
/// method is running.
///
/// The default is `true`.
pub fn set_simple_im_enabled(self, enabled: bool) {
get!().seat_set_simple_im_enabled(self, enabled);
}
/// Returns whether the simple, XCompose based input method is enabled.
pub fn simple_im_enabled(self) -> bool {
get!(true).seat_get_simple_im_enabled(self)
}
/// Toggles whether the simple, XCompose based input method is enabled.
pub fn toggle_simple_im_enabled(self) {
let get = get!();
get.seat_set_simple_im_enabled(self, !get.seat_get_simple_im_enabled(self));
}
/// Reloads the simple, XCompose based input method.
///
/// This is useful if you change the XCompose files after starting the compositor.
pub fn reload_simple_im(self) {
get!().seat_reload_simple_im(self);
}
/// Enables Unicode input in the simple, XCompose based input method.
///
/// This has no effect if the simple IM is not currently active.
pub fn enable_unicode_input(self) {
get!().seat_enable_unicode_input(self);
}
/// Warps the cursor to the center of the currently focused window.
pub fn warp_mouse_to_focus(self) {
get!().seat_warp_mouse_to_focus(self)
}
/// Resizes the focused window.
pub fn resize(self, dx1: i32, dy1: i32, dx2: i32, dy2: i32) {
self.window().resize(dx1, dy1, dx2, dy2);
}
/// Sets whether the cursor should automatically move to the center of a window
/// when focus changes via keyboard commands (move-left, focus-right, show-workspace, etc.).
///
/// The default is `false`.
#[deprecated = "This setting is unstable and might be removed in the future"]
pub fn unstable_set_mouse_follows_focus(self, enabled: bool) {
get!().seat_set_mouse_follows_focus(self, enabled)
}
/// Toggles tabbed mode on the focused window's parent container.
pub fn toggle_tab(self) {
get!().seat_toggle_tab(self)
}
/// Wraps the focused child in a new sub-container with the given split axis.
pub fn make_group(self, axis: Axis, ephemeral: bool) {
get!().seat_make_group(self, axis, ephemeral)
}
/// Toggles the parent container's split direction (H↔V).
pub fn change_group_opposite(self) {
get!().seat_change_group_opposite(self)
}
/// Resets all siblings' size factors to equal.
pub fn equalize(self, recursive: bool) {
get!().seat_equalize(self, recursive)
}
/// Move the active tab left or right within the current tab bar.
///
/// Equivalent to hy3's `movewindow` within a tabbed group.
pub fn move_tab(self, right: bool) {
get!().seat_move_tab(self, right)
}
}
/// A focus-follows-mouse mode.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub enum FocusFollowsMouseMode {
/// When the mouse moves and enters a toplevel, that toplevel gets the keyboard focus.
True,
/// The keyboard focus changes only when clicking on a window or the previously
/// focused window becomes invisible.
False,
}
/// Defines which output is used when no particular output is specified.
///
/// This configures where to place a newly opened window or workspace, what window to focus when a
/// window is closed, which workspace is moved with [`Seat::move_to_output`], and similar actions.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
#[non_exhaustive]
pub enum FallbackOutputMode {
/// Use the output the cursor is on.
Cursor,
/// Use the output the focus is on (highlighted window).
Focus,
}
/// Returns all seats.
pub fn get_seats() -> Vec<Seat> {
get!().seats()
}
/// Returns all input devices.
pub fn input_devices() -> Vec<InputDevice> {
get!().get_input_devices(None)
}
/// Returns or creates a seat.
///
/// Seats are identified by their name. If no seat with the name exists, a new seat will be created.
///
/// NOTE: You should prefer [`get_default_seat`] instead. Most applications cannot handle more than
/// one seat and will only process input from one of the seats.
pub fn get_seat(name: &str) -> Seat {
get!(Seat(0)).get_seat(name)
}
/// Returns or creates the default seat.
///
/// This is equivalent to `get_seat("default")`.
pub fn get_default_seat() -> Seat {
get_seat(DEFAULT_SEAT_NAME)
}
/// Sets a closure to run when a new seat has been created.
pub fn on_new_seat<F: FnMut(Seat) + 'static>(f: F) {
get!().on_new_seat(f)
}
/// Sets a closure to run when a new input device has been added.
pub fn on_new_input_device<F: FnMut(InputDevice) + 'static>(f: F) {
get!().on_new_input_device(f)
}
/// Sets a closure to run when an input device has been removed.
pub fn on_input_device_removed<F: FnMut(InputDevice) + 'static>(f: F) {
get!().on_input_device_removed(f)
}
/// Sets the maximum time between two clicks to be registered as a double click by the
/// compositor.
///
/// This only affects interactions with the compositor UI and has no effect on
/// applications.
///
/// The default is 400 ms.
pub fn set_double_click_time(duration: Duration) {
let usec = duration.as_micros().min(u64::MAX as u128);
get!().set_double_click_interval(usec as u64)
}
/// Sets the maximum distance between two clicks to be registered as a double click by the
/// compositor.
///
/// This only affects interactions with the compositor UI and has no effect on
/// applications.
///
/// Setting a negative distance disables double clicks.
///
/// The default is 5.
pub fn set_double_click_distance(distance: i32) {
get!().set_double_click_distance(distance)
}
/// Disables the creation of a default seat.
///
/// Unless this function is called at startup of the compositor, a seat called `default`
/// will automatically be created.
///
/// When a new input device is attached and a seat called `default` exists, the input
/// device is initially attached to this seat.
pub fn disable_default_seat() {
get!().disable_default_seat();
}
/// An event generated by a switch.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub enum SwitchEvent {
/// The lid of the device (usually a laptop) has been opened.
///
/// This is the default state.
LidOpened,
/// The lid of the device (usually a laptop) has been closed.
///
/// If the device is already in this state when the device is discovered, a synthetic
/// event of this kind is generated.
LidClosed,
/// The device has been converted from tablet to laptop mode.
///
/// This is the default state.
ConvertedToLaptop,
/// The device has been converted from laptop to tablet mode.
///
/// If the device is already in this state when the device is discovered, a synthetic
/// event of this kind is generated.
ConvertedToTablet,
}
/// Enables or disables the unauthenticated libei socket.
///
/// The default is `false`.
pub fn set_libei_socket_enabled(enabled: bool) {
get!().set_ei_socket_enabled(enabled);
}

View file

@ -0,0 +1,14 @@
//! Constants determining the acceleration profile of a device.
//!
//! See the libinput documentation for details.
use serde::{Deserialize, Serialize};
/// The acceleration profile of a device.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct AccelProfile(pub u32);
/// A flat acceleration profile.
pub const ACCEL_PROFILE_FLAT: AccelProfile = AccelProfile(1 << 0);
/// An adaptive acceleration profile.
pub const ACCEL_PROFILE_ADAPTIVE: AccelProfile = AccelProfile(1 << 1);

View file

@ -0,0 +1,17 @@
//! Constants specifying the capabilities of an input device.
//!
//! See the libinput documentation for the meanings of these constants.
use serde::{Deserialize, Serialize};
/// A capability of an input device.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct Capability(pub u32);
pub const CAP_KEYBOARD: Capability = Capability(0);
pub const CAP_POINTER: Capability = Capability(1);
pub const CAP_TOUCH: Capability = Capability(2);
pub const CAP_TABLET_TOOL: Capability = Capability(3);
pub const CAP_TABLET_PAD: Capability = Capability(4);
pub const CAP_GESTURE: Capability = Capability(5);
pub const CAP_SWITCH: Capability = Capability(6);

View file

@ -0,0 +1,18 @@
//! Constants determining the click method of a device.
//!
//! See the libinput documentation for details.
use serde::{Deserialize, Serialize};
/// The click method of a device.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct ClickMethod(pub u32);
/// No click method handling
pub const CLICK_METHOD_NONE: ClickMethod = ClickMethod(0);
/// Button area
pub const CLICK_METHOD_BUTTON_AREAS: ClickMethod = ClickMethod(1 << 0);
/// Clickfinger
pub const CLICK_METHOD_CLICKFINGER: ClickMethod = ClickMethod(1 << 1);

194
crates/jay-config/src/io.rs Normal file
View file

@ -0,0 +1,194 @@
//! Tools for IO operations.
use {
crate::protocol::PollableId,
futures_util::{AsyncWrite, io::AsyncRead},
std::{
future::poll_fn,
io::{self, ErrorKind, IoSlice, IoSliceMut, Read, Write},
os::fd::{AsFd, AsRawFd},
pin::Pin,
task::{Context, Poll, ready},
},
thiserror::Error,
uapi::c,
};
#[derive(Debug, Error)]
enum AsyncError {
#[error("Could not retrieve the file description flags")]
GetFl(#[source] io::Error),
#[error("Could not set the file description flags")]
SetFl(#[source] io::Error),
#[error("This configuration has already been destroyed")]
Destroyed,
#[error("The compositor could not create the necessary data structures: {0}")]
CompositorSetup(String),
#[error("Could not poll the file description: {0}")]
Poll(String),
}
impl From<AsyncError> for io::Error {
fn from(value: AsyncError) -> Self {
io::Error::other(value)
}
}
/// An async adapter for types implementing [`AsFd`].
pub struct Async<T> {
id: PollableIdWrapper,
t: Option<T>,
}
impl<T> Unpin for Async<T> {}
struct PollableIdWrapper {
id: PollableId,
}
impl Drop for PollableIdWrapper {
fn drop(&mut self) {
get!().remove_pollable(self.id);
}
}
impl<T> Async<T>
where
T: AsFd,
{
/// Creates a new async adapter.
///
/// This takes ownership of the file description and duplicates the file descriptor.
/// You should not modify the file description while this object is in use, otherwise
/// the behavior is undefined.
pub fn new(t: T) -> Result<Self, io::Error> {
Ok(Self::new_(t)?)
}
fn new_(t: T) -> Result<Self, AsyncError> {
let fd = t.as_fd();
let fl = uapi::fcntl_getfl(fd.as_raw_fd())
.map_err(|e| AsyncError::GetFl(io::Error::from_raw_os_error(e.0)))?;
uapi::fcntl_setfl(fd.as_raw_fd(), fl | c::O_NONBLOCK)
.map_err(|e| AsyncError::SetFl(io::Error::from_raw_os_error(e.0)))?;
let id = get!(Err(AsyncError::Destroyed))
.create_pollable(fd.as_raw_fd())
.map_err(AsyncError::CompositorSetup)?;
Ok(Self {
id: PollableIdWrapper { id },
t: Some(t),
})
}
}
impl<T> Async<T> {
/// Unwraps the underlying object.
///
/// Note that the underlying object is still non-blocking at this point.
pub fn unwrap(self) -> T {
self.t.unwrap()
}
fn poll_(&self, writable: bool, cx: &mut Context<'_>) -> Poll<Result<(), AsyncError>> {
get!(Poll::Ready(Err(AsyncError::Destroyed)))
.poll_io(self.id.id, writable, cx)
.map_err(AsyncError::Poll)
}
async fn poll(&self, writable: bool) -> Result<(), io::Error> {
poll_fn(|cx| self.poll_(writable, cx)).await?;
Ok(())
}
/// Waits for the file description to become readable.
pub async fn readable(&self) -> Result<(), io::Error> {
self.poll(false).await
}
/// Waits for the file description to become writable.
pub async fn writable(&self) -> Result<(), io::Error> {
self.poll(true).await
}
}
impl<T> AsRef<T> for Async<T> {
fn as_ref(&self) -> &T {
self.t.as_ref().unwrap()
}
}
impl<T> AsMut<T> for Async<T> {
fn as_mut(&mut self) -> &mut T {
self.t.as_mut().unwrap()
}
}
fn poll_io<T, R>(
slf: &mut Async<T>,
writable: bool,
cx: &mut Context<'_>,
mut f: impl FnMut(&mut Async<T>) -> io::Result<R>,
) -> Poll<io::Result<R>> {
loop {
ready!(slf.poll_(writable, cx))?;
match f(slf) {
Err(e) if e.kind() == ErrorKind::WouldBlock => {}
res => return Poll::Ready(res),
}
}
}
impl<T> AsyncRead for Async<T>
where
T: Read,
{
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
poll_io(self.get_mut(), false, cx, |slf| slf.as_mut().read(buf))
}
fn poll_read_vectored(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &mut [IoSliceMut<'_>],
) -> Poll<io::Result<usize>> {
poll_io(self.get_mut(), false, cx, |slf| {
slf.as_mut().read_vectored(bufs)
})
}
}
impl<T> AsyncWrite for Async<T>
where
T: Write,
{
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
poll_io(self.get_mut(), true, cx, |slf| slf.as_mut().write(buf))
}
fn poll_write_vectored(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &[IoSlice<'_>],
) -> Poll<io::Result<usize>> {
poll_io(self.get_mut(), true, cx, |slf| {
slf.as_mut().write_vectored(bufs)
})
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
poll_io(self.get_mut(), true, cx, |slf| slf.as_mut().flush())
}
fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.get_mut().t.take();
Poll::Ready(Ok(()))
}
}

View file

@ -0,0 +1,156 @@
//! Tools affecting the keyboard behavior.
use {
crate::keyboard::{mods::Modifiers, syms::KeySym},
serde::{Deserialize, Serialize},
std::ops::{BitOr, BitOrAssign},
};
pub mod mods;
pub mod syms;
/// A keysym with zero or more modifiers
#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct ModifiedKeySym {
pub mods: Modifiers,
pub sym: KeySym,
}
impl From<KeySym> for ModifiedKeySym {
fn from(sym: KeySym) -> Self {
Self {
mods: Modifiers(0),
sym,
}
}
}
impl BitOr<Modifiers> for ModifiedKeySym {
type Output = ModifiedKeySym;
fn bitor(self, rhs: Modifiers) -> Self::Output {
ModifiedKeySym {
mods: self.mods | rhs,
sym: self.sym,
}
}
}
impl BitOrAssign<Modifiers> for ModifiedKeySym {
fn bitor_assign(&mut self, rhs: Modifiers) {
self.mods |= rhs;
}
}
/// A keymap.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct Keymap(pub u64);
impl Keymap {
/// The invalid keymap.
pub const INVALID: Self = Self(0);
/// Returns whether this keymap is valid.
pub fn is_valid(self) -> bool {
self != Self::INVALID
}
/// Returns whether this keymap is invalid.
pub fn is_invalid(self) -> bool {
self == Self::INVALID
}
/// Destroys this reference to the keymap.
///
/// Seats that are currently using this keymap are unaffected.
pub fn destroy(self) {
if self.is_valid() {
get!().destroy_keymap(self);
}
}
}
/// An RMLVO group consisting of a layout and a variant.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct Group<'a> {
/// The layout of the group.
pub layout: &'a str,
/// The variant of the group. Can be an empty string.
pub variant: &'a str,
}
/// Parses a keymap.
///
/// The returned keymap can later be used to set the keymap of a seat. If the keymap cannot
/// be parsed, returns an invalid keymap. Trying to set a seat's keymap to an invalid keymap
/// has no effect.
///
/// A simple keymap looks as follows:
///
/// ```text
/// xkb_keymap {
/// xkb_keycodes { include "evdev+aliases(qwerty)" };
/// xkb_types { include "complete" };
/// xkb_compat { include "complete" };
/// xkb_symbols { include "pc+inet(evdev)+us(basic)" };
/// };
/// ```
///
/// To use a programmer Dvorak, replace the corresponding line by
///
/// ```text
/// xkb_symbols { include "pc+inet(evdev)+us(dvp)" };
/// ```
///
/// To use a German keymap, replace the corresponding line by
///
/// ```text
/// xkb_symbols { include "pc+inet(evdev)+de(basic)" };
/// ```
///
/// You can also use a completely custom keymap that doesn't use any includes. See the
/// [default][default] Jay keymap for an example.
///
/// General information about the keymap format can be found in the [arch wiki][wiki].
///
/// [default]: https://github.com/mahkoh/jay/tree/master/src/keymap.xkb
/// [wiki]: https://wiki.archlinux.org/title/X_keyboard_extension
pub fn parse_keymap(keymap: &str) -> Keymap {
get!(Keymap::INVALID).parse_keymap(keymap)
}
/// Creates a keymap from RMLVO names.
///
/// If a parameter is not given, a value from the environment or a default is used:
///
/// | name | default |
/// | ---------------------- | ---------------------- |
/// | `XKB_DEFAULT_RULES` | `evdev` |
/// | `XKB_DEFAULT_MODEL` | `pc105` |
/// | `XKB_DEFAULT_LAYOUT` | `us` |
/// | `XKB_DEFAULT_VARIANTS` | |
/// | `XKB_DEFAULT_OPTIONS` | |
///
/// `XKB_DEFAULT_LAYOUT` and `XKB_DEFAULT_VARIANTS` are parsed into the `groups` parameter like this example:
/// ```
/// XKB_DEFAULT_LAYOUT = "us,il,ru,de,jp"
/// XKB_DEFAULT_VARIANTS = ",,phonetic,neo"
/// ```
/// produces:
/// ```
/// [
/// Group { layout: "us", variant: "" },
/// Group { layout: "il", variant: "" },
/// Group { layout: "ru", variant: "phonetic" },
/// Group { layout: "de", variant: "neo" },
/// Group { layout: "jp", variant: "" },
/// ]
/// ```
pub fn keymap_from_names(
rules: Option<&str>,
model: Option<&str>,
groups: Option<&[Group<'_>]>,
options: Option<&[&str]>,
) -> Keymap {
get!(Keymap::INVALID).keymap_from_names(rules, model, groups, options)
}

View file

@ -0,0 +1,60 @@
//! Keyboard modifiers
use {
crate::{ModifiedKeySym, keyboard::syms::KeySym},
serde::{Deserialize, Serialize},
std::ops::BitOr,
};
bitflags! {
/// Zero or more keyboard modifiers
#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Default, Hash)]
pub struct Modifiers(pub u32) {
/// The Shift modifier
pub const SHIFT = 1 << 0,
/// The CapsLock modifier.
pub const LOCK = 1 << 1,
/// The Ctrl modifier.
pub const CTRL = 1 << 2,
/// The Mod1 modifier, i.e., Alt.
pub const MOD1 = 1 << 3,
/// The Mod2 modifier, i.e., NumLock.
pub const MOD2 = 1 << 4,
/// The Mod3 modifier.
pub const MOD3 = 1 << 5,
/// The Mod4 modifier, i.e., Logo.
pub const MOD4 = 1 << 6,
/// The Mod5 modifier.
pub const MOD5 = 1 << 7,
/// Synthetic modifier matching key release events.
///
/// This can be used to execute a callback on key release.
pub const RELEASE = 1 << 31,
}
}
impl Modifiers {
/// No modifiers.
pub const NONE: Self = Modifiers(0);
}
/// Alias for `LOCK`.
pub const CAPS: Modifiers = LOCK;
/// Alias for `MOD1`.
pub const ALT: Modifiers = MOD1;
/// Alias for `MOD2`.
pub const NUM: Modifiers = MOD2;
/// Alias for `MOD4`.
pub const LOGO: Modifiers = MOD4;
impl BitOr<KeySym> for Modifiers {
type Output = ModifiedKeySym;
fn bitor(self, rhs: KeySym) -> Self::Output {
ModifiedKeySym {
mods: self,
sym: rhs,
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,487 @@
//! Internal Rust configuration API used by Jay's built-in TOML configuration
//! implementation.
#![allow(
clippy::zero_prefixed_literal,
clippy::manual_range_contains,
clippy::uninlined_format_args,
clippy::len_zero,
clippy::single_char_pattern,
clippy::single_char_add_str,
clippy::single_match
)]
#![warn(unsafe_op_in_unsafe_fn)]
#[expect(unused_imports)]
use crate::input::Seat;
use {
crate::{
keyboard::ModifiedKeySym, protocol::WorkspaceSource, video::Connector, window::Window,
},
serde::{Deserialize, Serialize},
std::{
fmt::{Debug, Display, Formatter},
time::Duration,
},
};
#[macro_use]
mod macros;
#[doc(hidden)]
pub mod _private;
pub mod client;
pub mod embedded;
pub mod exec;
pub mod input;
pub mod io;
pub mod keyboard;
pub mod logging;
pub mod protocol;
pub mod status;
pub mod tasks;
pub mod theme;
pub mod timer;
pub mod video;
pub mod window;
pub mod workspace;
pub mod xwayland;
/// A planar direction.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq)]
pub enum Direction {
Left,
Down,
Up,
Right,
}
/// A planar axis.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub enum Axis {
Horizontal,
Vertical,
}
impl Axis {
/// Returns the axis orthogonal to `self`.
pub fn other(self) -> Self {
match self {
Self::Horizontal => Self::Vertical,
Self::Vertical => Self::Horizontal,
}
}
}
/// The curve used for tiled window animations.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct AnimationCurve(pub u32);
impl AnimationCurve {
pub const LINEAR: Self = Self(0);
pub const EASE: Self = Self(1);
pub const EASE_IN: Self = Self(2);
pub const EASE_OUT: Self = Self(3);
pub const EASE_IN_OUT: Self = Self(4);
}
/// The presentation style used for tiled window movement animations.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct AnimationStyle(pub u32);
impl AnimationStyle {
pub const PLAIN: Self = Self(0);
pub const MULTIPHASE: Self = Self(1);
}
/// Exits the compositor.
pub fn quit() {
get!().quit()
}
/// Switches to a different VT.
pub fn switch_to_vt(n: u32) {
get!().switch_to_vt(n)
}
/// Reloads the configuration.
///
/// If the configuration cannot be reloaded, this function has no effect.
pub fn reload() {
get!().reload()
}
/// Returns whether this execution of the configuration function is due to a reload.
///
/// This can be used to decide whether the configuration should auto-start programs.
pub fn is_reload() -> bool {
get!(false).is_reload()
}
/// Sets whether new workspaces are captured by default.
///
/// The default is `true`.
pub fn set_default_workspace_capture(capture: bool) {
get!().set_default_workspace_capture(capture)
}
/// Returns whether new workspaces are captured by default.
pub fn get_default_workspace_capture() -> bool {
get!(true).get_default_workspace_capture()
}
/// Toggles whether new workspaces are captured by default.
pub fn toggle_default_workspace_capture() {
let get = get!();
get.set_default_workspace_capture(!get.get_default_workspace_capture());
}
/// A workspace.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct Workspace(pub u64);
impl Workspace {
/// Returns whether this workspace existed at the time `Seat::get_workspace` was called.
pub fn exists(self) -> bool {
self.0 != 0
}
/// Sets whether the workspaces is captured.
///
/// The default is determined by `set_default_workspace_capture`.
pub fn set_capture(self, capture: bool) {
get!().set_workspace_capture(self, capture)
}
/// Returns whether the workspaces is captured.
pub fn get_capture(self) -> bool {
get!(true).get_workspace_capture(self)
}
/// Toggles whether the workspaces is captured.
pub fn toggle_capture(self) {
let get = get!();
get.set_workspace_capture(self, !get.get_workspace_capture(self));
}
/// Moves this workspace to another output.
///
/// This has no effect if the workspace is not currently being shown.
pub fn move_to_output(self, output: Connector) {
get!().move_to_output(WorkspaceSource::Explicit(self), output);
}
/// Returns the root container of this workspace.
///
/// If no such container exists, [`Window::exists`] returns false.
pub fn window(self) -> Window {
get!(Window(0)).get_workspace_window(self)
}
/// Returns the connector that contains this workspace.
///
/// If no such connector exists, [`Connector::exists`] returns false.
pub fn connector(self) -> Connector {
get!(Connector(0)).get_workspace_connector(self)
}
}
/// Returns the workspace with the given name.
///
/// Workspaces are identified by their name. Calling this function alone does not create the
/// workspace if it doesn't already exist.
pub fn get_workspace(name: &str) -> Workspace {
get!(Workspace(0)).get_workspace(name)
}
/// A PCI ID.
///
/// PCI IDs can be used to identify a hardware component. See the Debian [documentation][pci].
///
/// [pci]: https://wiki.debian.org/HowToIdentifyADevice/PCI
#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, Eq, PartialEq, Default)]
pub struct PciId {
pub vendor: u32,
pub model: u32,
}
impl Display for PciId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:04x}:{:04x}", self.vendor, self.model)
}
}
/// Sets the callback to be called when the display goes idle.
pub fn on_idle<F: FnMut() + 'static>(f: F) {
get!().on_idle(f)
}
/// Sets the callback to be called when all devices have been enumerated.
///
/// This callback is only invoked once during the lifetime of the compositor. This is a
/// good place to select the DRM device used for rendering.
pub fn on_devices_enumerated<F: FnOnce() + 'static>(f: F) {
get!().on_devices_enumerated(f)
}
/// Returns the Jay config directory.
pub fn config_dir() -> String {
get!().config_dir()
}
/// Returns all visible workspaces.
pub fn workspaces() -> Vec<Workspace> {
get!().workspaces()
}
/// Configures the idle timeout.
///
/// `None` disables the timeout.
///
/// The default is 10 minutes.
pub fn set_idle(timeout: Option<Duration>) {
get!().set_idle(timeout.unwrap_or_default())
}
/// Configures whether a key press turns monitors back on after `jay dpms off`.
///
/// The default is `false`.
pub fn set_key_press_enables_dpms(enabled: bool) {
get!().set_key_press_enables_dpms(enabled)
}
/// Configures whether mouse movement turns monitors back on after `jay dpms off`.
///
/// The default is `false`.
pub fn set_mouse_move_enables_dpms(enabled: bool) {
get!().set_mouse_move_enables_dpms(enabled)
}
/// Configures the idle grace period.
///
/// The grace period starts after the idle timeout expires. During the grace period, the
/// screen goes black but the displays are not yet disabled and the idle callback (set
/// with [`on_idle`]) is not yet called. This is a purely visual effect to inform the user
/// that the machine will soon go idle.
///
/// The default is 5 seconds.
pub fn set_idle_grace_period(timeout: Duration) {
get!().set_idle_grace_period(timeout)
}
/// Enables or disables explicit sync.
///
/// Calling this after the compositor has started has no effect.
///
/// The default is `true`.
pub fn set_explicit_sync_enabled(enabled: bool) {
get!().set_explicit_sync_enabled(enabled);
}
/// Enables or disables dragging of tiles and workspaces.
///
/// The default is `true`.
pub fn set_ui_drag_enabled(enabled: bool) {
get!().set_ui_drag_enabled(enabled);
}
/// Sets the distance at which ui dragging starts.
///
/// The default is `10`.
pub fn set_ui_drag_threshold(threshold: i32) {
get!().set_ui_drag_threshold(threshold);
}
/// Enables or disables tiled window animations.
///
/// The default is `false`.
pub fn set_animations_enabled(enabled: bool) {
get!().set_animations_enabled(enabled);
}
/// Sets the duration of tiled window animations in milliseconds.
///
/// The default is `160`.
pub fn set_animation_duration_ms(duration_ms: u32) {
get!().set_animation_duration_ms(duration_ms);
}
/// Sets the curve used by tiled window animations.
///
/// The default is [`AnimationCurve::EASE_OUT`].
pub fn set_animation_curve(curve: AnimationCurve) {
get!().set_animation_curve(curve.0);
}
/// Sets the presentation style used for tiled window movement animations.
///
/// The default is [`AnimationStyle::MULTIPHASE`].
pub fn set_animation_style(style: AnimationStyle) {
get!().set_animation_style(style.0);
}
/// Sets a custom cubic-bezier curve used by tiled window animations.
///
/// `x1` and `x2` must be between `0.0` and `1.0`. The curve starts at `(0, 0)`
/// and ends at `(1, 1)`.
pub fn set_animation_cubic_bezier(x1: f32, y1: f32, x2: f32, y2: f32) {
get!().set_animation_cubic_bezier(x1, y1, x2, y2);
}
/// Enables or disables the color-management protocol.
///
/// The default is `false`.
///
/// Affected applications must be restarted for this to take effect.
pub fn set_color_management_enabled(enabled: bool) {
get!().set_color_management_enabled(enabled);
}
/// Sets whether floating windows are shown above fullscreen windows.
///
/// The default is `false`.
pub fn set_float_above_fullscreen(above: bool) {
get!().set_float_above_fullscreen(above);
}
/// Gets whether floating windows are shown above fullscreen windows.
pub fn get_float_above_fullscreen() -> bool {
get!().get_float_above_fullscreen()
}
/// Toggles whether floating windows are shown above fullscreen windows.
///
/// The default is `false`.
pub fn toggle_float_above_fullscreen() {
set_float_above_fullscreen(!get_float_above_fullscreen())
}
/// Sets whether floating windows always show a pin icon.
///
/// Clicking on the pin icon toggles the pin mode. See [`Seat::toggle_float_pinned`].
///
/// The icon is always shown if the window is pinned. This setting only affects unpinned
/// windows.
pub fn set_show_float_pin_icon(show: bool) {
get!().set_show_float_pin_icon(show);
}
/// Sets whether the built-in bar is shown.
///
/// The default is `true`.
pub fn set_show_bar(show: bool) {
get!().set_show_bar(show)
}
/// Returns whether the built-in bar is shown.
pub fn get_show_bar() -> bool {
get!(true).get_show_bar()
}
/// Toggles whether the built-in bar is shown.
pub fn toggle_show_bar() {
let get = get!();
get.set_show_bar(!get.get_show_bar());
}
/// Sets whether title bars on windows are shown.
///
/// The default is `true`.
pub fn set_show_titles(show: bool) {
get!().set_show_titles(show)
}
/// Returns whether title bars on windows are shown.
pub fn get_show_titles() -> bool {
get!(true).get_show_titles()
}
/// Toggles whether title bars on windows are shown.
pub fn toggle_show_titles() {
let get = get!();
get.set_show_titles(!get.get_show_titles());
}
/// Sets whether title bars float above window content instead of being attached.
///
/// When enabled, title bars are rendered on top of window content without consuming
/// layout space. Window borders do not extend to include the title bar area.
///
/// The default is `false`.
pub fn set_floating_titles(floating: bool) {
get!().set_floating_titles(floating)
}
/// Returns whether title bars float above window content.
pub fn get_floating_titles() -> bool {
get!(false).get_floating_titles()
}
/// Toggles whether title bars float above window content.
pub fn toggle_floating_titles() {
let get = get!();
get.set_floating_titles(!get.get_floating_titles());
}
/// Sets the corner radius for window borders.
///
/// A radius of 0 means square corners. The radius is in logical pixels.
pub fn set_corner_radius(radius: f32) {
get!().set_corner_radius(radius)
}
/// Returns the current corner radius for window borders.
pub fn get_corner_radius() -> f32 {
get!(0.0).get_corner_radius()
}
/// Enables or disables autotiling.
///
/// When enabled, newly tiled windows alternate split orientation from the
/// focused tiled window: the first split uses the containing group's direction,
/// then subsequent splits wrap the focused window in the perpendicular
/// direction.
///
/// The default is `false`.
pub fn set_autotile(enabled: bool) {
get!().set_autotile(enabled)
}
/// Returns whether autotiling is enabled.
pub fn get_autotile() -> bool {
get!(false).get_autotile()
}
/// Sets the horizontal alignment of title text within tab buttons.
///
/// - `"start"` — left-aligned (default)
/// - `"center"` — centered
/// - `"end"` — right-aligned
pub fn set_tab_title_align(align: &str) {
let val = match align {
"center" => 1,
"end" => 2,
_ => 0, // start
};
get!().set_tab_title_align(val)
}
/// Sets a callback to run when this config is unloaded.
///
/// Only one callback can be set at a time. If another callback is already set, it will be
/// dropped without being run.
///
/// This function can be used to terminate threads and clear reference cycles.
pub fn on_unload(f: impl FnOnce() + 'static) {
get!().on_unload(f);
}
/// Enables or disables middle-click pasting.
///
/// This has no effect on applications that are already running.
///
/// The default is `true`.
#[doc(alias("primary-selection", "primary_selection"))]
pub fn set_middle_click_paste_enabled(enabled: bool) {
get!().set_middle_click_paste_enabled(enabled);
}

View file

@ -0,0 +1,34 @@
//! Tools for modifying the logging behavior of the compositor.
//!
//! Note that you can use the `log` crate for logging. All invocations of `log::info` etc.
//! automatically log into the compositors log.
use {
serde::{Deserialize, Serialize},
std::time::SystemTime,
};
/// The log level of the compositor or a log message.
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
pub enum LogLevel {
Error,
Warn,
Info,
Debug,
Trace,
}
/// Sets the log level of the compositor.
pub fn set_log_level(level: LogLevel) {
get!().set_log_level(level);
}
/// If this function is called during startup, Jay's log files before `time` are deleted.
///
/// The current log file is never deleted, nor are any other logfiles of active Jay instances (e.g.
/// on another VT), even if `time` is in the future.
///
/// Calling this function after startup has no effect.
pub fn clean_logs_older_than(time: SystemTime) {
get!().clean_logs_older_than(time);
}

View file

@ -0,0 +1,113 @@
macro_rules! try_get {
() => {{
unsafe {
let client = crate::_private::client::CLIENT.with(|client| client.get());
if client.is_null() {
None
} else {
Some(&*client)
}
}
}};
}
macro_rules! get {
() => {{ get!(Default::default()) }};
($def:expr) => {{
let client = unsafe {
let client = crate::_private::client::CLIENT.with(|client| client.get());
if client.is_null() {
return $def;
}
&*client
};
client
}};
}
macro_rules! bitflags {
(
$(#[$attr1:meta])*
$vis1:vis struct $name:ident($vis2:vis $rep:ty) {
$(
$(#[$attr2:meta])*
$vis3:vis const $var:ident = $val:expr,
)*
}
) => {
$(#[$attr1])*
$vis1 struct $name($vis2 $rep);
$(
$(#[$attr2])*
$vis3 const $var: $name = $name($val);
)*
impl std::ops::BitOr for $name {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
Self(self.0 | rhs.0)
}
}
impl std::ops::BitAnd for $name {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
Self(self.0 & rhs.0)
}
}
impl std::ops::BitOrAssign for $name {
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
impl std::ops::BitAndAssign for $name {
fn bitand_assign(&mut self, rhs: Self) {
self.0 &= rhs.0;
}
}
impl std::ops::BitXorAssign for $name {
fn bitxor_assign(&mut self, rhs: Self) {
self.0 ^= rhs.0;
}
}
impl std::ops::Not for $name {
type Output = Self;
fn not(self) -> Self::Output {
Self(!self.0)
}
}
impl std::fmt::Debug for $name {
#[allow(clippy::allow_attributes, clippy::bad_bit_mask, unused_mut)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut any = false;
let mut v = self.0;
$(
if $val != 0 && v & $val == $val {
if any {
write!(f, "|")?;
}
any = true;
write!(f, "{}", stringify!($var))?;
v &= !$val;
}
)*
if !any || v != 0 {
if any {
write!(f, "|")?;
}
write!(f, "0x{:x}", v)?;
}
Ok(())
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,259 @@
//! Knobs for changing the status text.
use {
crate::{exec::Command, io::Async, tasks::spawn},
bstr::ByteSlice,
error_reporter::Report,
futures_util::{AsyncBufReadExt, io::BufReader},
serde::Deserialize,
std::borrow::BorrowMut,
uapi::{OwnedFd, c},
};
/// Sets the status text.
///
/// The status text is displayed at the right end of the bar.
///
/// The status text should be specified in [pango][pango] markup language.
///
/// [pango]: https://docs.gtk.org/Pango/pango_markup.html
pub fn set_status(status: &str) {
get!().set_status(status);
}
/// The format of a status command output.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum MessageFormat {
/// The output is plain text.
///
/// The command should output one line every time it wants to change the status.
/// The content of the line will be interpreted as plain text.
Plain,
/// The output uses [pango][pango] markup.
///
/// The command should output one line every time it wants to change the status.
/// The content of the line will be interpreted as pango markup.
///
/// [pango]: https://docs.gtk.org/Pango/pango_markup.html
Pango,
/// The output uses the [i3bar][i3bar] protocol.
///
/// The separator between individual components can be set using [`set_i3bar_separator`].
///
/// [i3bar]: https://github.com/i3/i3/blob/next/docs/i3bar-protocol
I3Bar,
}
/// Sets a command whose output will be used as the status text.
///
/// The [`stdout`](Command::stdout) and [`stderr`](Command::stderr)` of the command will
/// be overwritten by this function. The stdout will be used for the status text and the
/// stderr will be appended to the compositor log.
///
/// The format of stdout is determined by the `format` parameter.
pub fn set_status_command(format: MessageFormat, mut command: impl BorrowMut<Command>) {
macro_rules! pipe {
() => {{
let (read, write) = match uapi::pipe2(c::O_CLOEXEC) {
Ok(p) => p,
Err(e) => {
log::error!("Could not create a pipe: {}", Report::new(e));
return;
}
};
let read = match Async::new(read) {
Ok(r) => BufReader::new(r),
Err(e) => {
log::error!("Could not create an Async object: {}", Report::new(e));
return;
}
};
(read, write)
}};
}
let (mut read, write) = pipe!();
let (mut stderr_read, stderr_write) = pipe!();
let command = command.borrow_mut();
command.stdout(write).stderr(stderr_write).spawn();
let name = command.prog.clone();
let name2 = command.prog.clone();
let stderr_handle = spawn(async move {
let mut line = vec![];
loop {
line.clear();
if let Err(e) = stderr_read.read_until(b'\n', &mut line).await {
log::warn!("Could not read from {name2} stderr: {}", Report::new(e));
return;
}
if line.len() == 0 {
return;
}
log::warn!(
"{name2} emitted a message on stderr: {}",
line.trim_with(|c| c == '\n').as_bstr()
);
}
});
let handle = spawn(async move {
if format == MessageFormat::I3Bar {
handle_i3bar(name, read).await;
return;
}
let mut line = String::new();
let mut cleaned = String::new();
loop {
line.clear();
if let Err(e) = read.read_line(&mut line).await {
log::error!("Could not read from `{name}`: {}", Report::new(e));
return;
}
if line.is_empty() {
log::info!("{name} closed stdout");
return;
}
let line = line.strip_suffix("\n").unwrap_or(&line);
cleaned.clear();
if format != MessageFormat::Pango && escape_pango(line, &mut cleaned) {
set_status(&cleaned);
} else {
set_status(line);
}
}
});
get!().set_status_tasks(vec![handle, stderr_handle]);
}
/// Unsets the previously set status command.
pub fn unset_status_command() {
get!().set_status_tasks(vec![]);
}
/// Sets the separator for i3bar status commands.
///
/// The separator should be specified in [pango][pango] markup language.
///
/// [pango]: https://docs.gtk.org/Pango/pango_markup.html
pub fn set_i3bar_separator(separator: &str) {
get!().set_i3bar_separator(separator);
}
async fn handle_i3bar(name: String, mut read: BufReader<Async<OwnedFd>>) {
use std::fmt::Write;
#[derive(Deserialize)]
struct Version {
version: i32,
}
#[derive(Deserialize)]
struct Component {
markup: Option<String>,
full_text: String,
color: Option<String>,
background: Option<String>,
}
let mut line = String::new();
macro_rules! read_line {
() => {{
line.clear();
if let Err(e) = read.read_line(&mut line).await {
log::error!("Could not read from `{name}`: {}", Report::new(e));
return;
}
if line.is_empty() {
log::info!("{name} closed stdout");
return;
}
}};
}
read_line!();
match serde_json::from_str::<Version>(&line) {
Ok(v) if v.version == 1 => {}
Ok(v) => log::warn!("Unexpected i3bar format version: {}", v.version),
Err(e) => {
log::warn!(
"Could not deserialize i3bar version message: {}",
Report::new(e)
);
return;
}
}
read_line!();
let mut status = String::new();
loop {
read_line!();
let mut line = line.trim();
if let Some(l) = line.strip_prefix(",") {
line = l;
}
if let Some(l) = line.strip_suffix(",") {
line = l;
}
let components = match serde_json::from_str::<Vec<Component>>(line) {
Ok(c) => c,
Err(e) => {
log::warn!(
"Could not deserialize i3bar status message: {}",
Report::new(e)
);
continue;
}
};
let separator = get!().get_i3bar_separator();
let separator = match &separator {
Some(s) => s.as_str(),
_ => r##" <span color="#333333">|</span> "##,
};
status.clear();
let mut first = true;
for component in &components {
if component.full_text.is_empty() {
continue;
}
if !first {
status.push_str(separator);
}
first = false;
let have_span = component.color.is_some() || component.background.is_some();
if have_span {
status.push_str("<span");
if let Some(color) = &component.color {
let _ = write!(status, r#" color="{color}""#);
}
if let Some(color) = &component.background {
let _ = write!(status, r#" bgcolor="{color}""#);
}
status.push_str(">");
}
if component.markup.as_deref() == Some("pango")
|| !escape_pango(&component.full_text, &mut status)
{
status.push_str(&component.full_text);
}
if have_span {
status.push_str("</span>");
}
}
set_status(&status);
}
}
fn escape_pango(src: &str, dst: &mut String) -> bool {
if src
.bytes()
.any(|b| matches!(b, b'&' | b'<' | b'>' | b'\'' | b'"'))
{
for c in src.chars() {
match c {
'&' => dst.push_str("&amp;"),
'<' => dst.push_str("&lt;"),
'>' => dst.push_str("&gt;"),
'\'' => dst.push_str("&apos;"),
'"' => dst.push_str("&quot;"),
_ => dst.push(c),
}
}
true
} else {
false
}
}

View file

@ -0,0 +1,69 @@
//! Tools for async task management.
use std::{
cell::Cell,
fmt::{Debug, Formatter},
future::Future,
pin::Pin,
rc::Rc,
task::{Context, Poll, Waker},
};
/// Spawns an asynchronous task that will run in the background.
pub fn spawn<T, F>(f: F) -> JoinHandle<T>
where
T: 'static,
F: Future<Output = T> + 'static,
{
let slot = match try_get!() {
None => Rc::new(JoinSlot {
task_id: 0,
slot: Cell::new(None),
waker: Cell::new(None),
}),
Some(c) => c.spawn_task(f),
};
JoinHandle { slot }
}
pub(crate) struct JoinSlot<T> {
pub task_id: u64,
pub slot: Cell<Option<T>>,
pub waker: Cell<Option<Waker>>,
}
/// A handle to join or abort a spawned task.
///
/// When the handle is dropped, the task continues to run in the background.
pub struct JoinHandle<T> {
slot: Rc<JoinSlot<T>>,
}
impl<T> Debug for JoinHandle<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("JoinHandle")
.field("task_id", &self.slot.task_id)
.finish_non_exhaustive()
}
}
impl<T> Unpin for JoinHandle<T> {}
impl<T> JoinHandle<T> {
/// Aborts the task immediately.
pub fn abort(self) {
get!().abort_task(self.slot.task_id);
}
}
impl<T> Future for JoinHandle<T> {
type Output = T;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if let Some(t) = self.slot.slot.take() {
return Poll::Ready(t);
}
self.slot.waker.set(Some(cx.waker().clone()));
Poll::Pending
}
}

View file

@ -0,0 +1,438 @@
//! Tools for configuring the look of the compositor.
use serde::{Deserialize, Serialize};
/// A color.
///
/// When specifying RGBA values of a color, the RGB values can either be specified
/// *straight* or *premultiplied*. Premultiplied means that the RGB values have already
/// been multiplied by the alpha value.
///
/// Given a color, to reduce its opacity by half,
///
/// - if you're working with premultiplied values, you would multiply each component by `0.5`;
/// - if you're working with straight values, you would multiply only the alpha component by `0.5`.
///
/// When using hexadecimal notation, `#RRGGBBAA`, the RGB values are usually straight.
// values are stored premultiplied
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
pub struct Color {
r: f32,
g: f32,
b: f32,
a: f32,
}
fn to_f32(c: u8) -> f32 {
c as f32 / 255f32
}
fn to_u8(c: f32) -> u8 {
(c * 255f32) as u8
}
fn validate_f32(f: f32) -> bool {
f >= 0.0 && f <= 1.0
}
fn validate_f32_all(f: [f32; 4]) -> bool {
if !f.into_iter().all(validate_f32) {
log::warn!(
"f32 values {:?} are not in the valid color range. Using solid black instead xyz",
f
);
return false;
}
true
}
impl Color {
/// Solid black.
pub const BLACK: Self = Self {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
/// Creates a new color from `u8` RGB values.
pub fn new(r: u8, g: u8, b: u8) -> Self {
Self {
r: to_f32(r),
g: to_f32(g),
b: to_f32(b),
a: 1.0,
}
}
/// Creates a new color from straight `u8` RGBA values.
pub fn new_straight(r: u8, g: u8, b: u8, a: u8) -> Self {
Self::new_f32_straight(to_f32(r), to_f32(g), to_f32(b), to_f32(a))
}
/// Creates a new color from premultiplied `f32` RGBA values.
pub fn new_f32_premultiplied(r: f32, g: f32, b: f32, a: f32) -> Self {
if !validate_f32_all([r, g, b, a]) {
Self::BLACK
} else if r > a || g > a || b > a {
log::warn!(
"f32 values {:?} are not valid for a premultiplied color. Using solid black instead.",
[r, g, b, a]
);
Self::BLACK
} else {
Self { r, g, b, a }
}
}
/// Creates a new color from straight `f32` RGBA values.
pub fn new_f32_straight(r: f32, g: f32, b: f32, a: f32) -> Self {
if !validate_f32_all([r, g, b, a]) {
Self::BLACK
} else {
Self {
r: r * a,
g: g * a,
b: b * a,
a,
}
}
}
/// Creates a new color from `f32` RGB values.
pub fn new_f32(r: f32, g: f32, b: f32) -> Self {
Self { r, g, b, a: 1.0 }
}
/// Converts the color to its premultiplied `f32` RGBA values.
pub fn to_f32_premultiplied(&self) -> [f32; 4] {
[self.r, self.g, self.b, self.a]
}
/// Converts the color to its straight `f32` RGBA values.
pub fn to_f32_straight(&self) -> [f32; 4] {
if self.a == 0.0 {
[0.0, 0.0, 0.0, 0.0]
} else {
let a = self.a;
[self.r / a, self.g / a, self.b / a, a]
}
}
/// Converts the color to its straight `u8` RGBA values.
pub fn to_u8_straight(&self) -> [u8; 4] {
let [r, g, b, a] = self.to_f32_straight();
[to_u8(r), to_u8(g), to_u8(b), to_u8(a)]
}
}
/// Resets all sizes to their defaults.
pub fn reset_sizes() {
get!().reset_sizes();
}
/// Resets all colors to their defaults.
pub fn reset_colors() {
get!().reset_colors();
}
/// Returns the current font.
pub fn get_font() -> String {
get!().get_font()
}
/// Sets the font.
///
/// Default: `monospace 8`.
///
/// See also [`set_bar_font`] and [`set_title_font`].
///
/// The font name should be specified in [pango][pango] syntax.
///
/// [pango]: https://docs.gtk.org/Pango/type_func.FontDescription.from_string.html
pub fn set_font(font: &str) {
get!().set_font(font)
}
/// Sets the font used by the bar.
///
/// If this function is not called, the font set by [`set_font`] is used. See that
/// function for more details.
pub fn set_bar_font(font: &str) {
get!().set_bar_font(font)
}
/// Sets the font used by window titles.
///
/// If this function is not called, the font set by [`set_font`] is used. See that
/// function for more details.
pub fn set_title_font(font: &str) {
get!().set_title_font(font)
}
/// Resets the fonts to the defaults.
///
/// Currently the default is `monospace 8`.
pub fn reset_font() {
get!().reset_font()
}
#[non_exhaustive]
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Default)]
pub enum BarPosition {
#[default]
Top,
Bottom,
}
/// Sets the position of the bar.
///
/// Default: `Top`.
pub fn set_bar_position(position: BarPosition) {
get!().set_bar_position(position);
}
/// Gets the position of the bar.
pub fn get_bar_position() -> BarPosition {
get!(BarPosition::Top).get_bar_position()
}
/// Elements of the compositor whose color can be changed.
pub mod colors {
use {
crate::theme::Color,
serde::{Deserialize, Serialize},
};
/// An element of the GUI whose color can be changed.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct Colorable(#[doc(hidden)] pub u32);
impl Colorable {
/// Sets the color to an RGB value.
pub fn set(self, r: u8, g: u8, b: u8) {
let color = Color::new(r, g, b);
get!().set_color(self, color);
}
/// Sets the color to a `Color` that might contain an alpha component.
pub fn set_color(self, color: Color) {
get!().set_color(self, color);
}
/// Gets the current color.
pub fn get(self) -> Color {
get!(Color::BLACK).get_color(self)
}
}
macro_rules! colors {
($($(#[$attr:meta])* const $n:expr => $name:ident,)*) => {
$(
$(#[$attr])*
pub const $name: Colorable = Colorable($n);
)*
}
}
colors! {
/// The title background color of an unfocused window.
///
/// Default: `#222222`.
const 01 => UNFOCUSED_TITLE_BACKGROUND_COLOR,
/// The title background color of a focused window.
///
/// Default: `#285577`.
const 02 => FOCUSED_TITLE_BACKGROUND_COLOR,
/// The title background color of an unfocused window that was the last focused
/// window in its container.
///
/// Default: `#5f676a`.
const 03 => FOCUSED_INACTIVE_TITLE_BACKGROUND_COLOR,
/// The background color of the desktop.
///
/// Default: `#001019`.
///
/// You can use an application such as [swaybg][swaybg] to further customize the background.
///
/// [swaybg]: https://github.com/swaywm/swaybg
const 04 => BACKGROUND_COLOR,
/// The background color of the bar.
///
/// Default: `#000000`.
const 05 => BAR_BACKGROUND_COLOR,
/// The color of the 1px separator below window titles.
///
/// Default: `#333333`.
const 06 => SEPARATOR_COLOR,
/// The color of the border between windows.
///
/// Default: `#3f474a`.
const 07 => BORDER_COLOR,
/// The color of the border around active windows.
///
/// Default: `#285577`.
const 24 => ACTIVE_BORDER_COLOR,
/// The title text color of an unfocused window.
///
/// Default: `#888888`.
const 08 => UNFOCUSED_TITLE_TEXT_COLOR,
/// The title text color of a focused window.
///
/// Default: `#ffffff`.
const 09 => FOCUSED_TITLE_TEXT_COLOR,
/// The title text color of an unfocused window that was the last focused
/// window in its container.
///
/// Default: `#ffffff`.
const 10 => FOCUSED_INACTIVE_TITLE_TEXT_COLOR,
/// The color of the status text in the bar.
///
/// Default: `#ffffff`.
const 11 => BAR_STATUS_TEXT_COLOR,
/// The title background color of an unfocused window that might be captured.
///
/// Default: `#220303`.
const 12 => CAPTURED_UNFOCUSED_TITLE_BACKGROUND_COLOR,
/// The title background color of a focused window that might be captured.
///
/// Default: `#772831`.
const 13 => CAPTURED_FOCUSED_TITLE_BACKGROUND_COLOR,
/// The title background color of a window that has requested attention.
///
/// Default: `#23092c`.
const 14 => ATTENTION_REQUESTED_BACKGROUND_COLOR,
/// Color used to highlight parts of the UI.
///
/// Default: `#9d28c67f`.
const 15 => HIGHLIGHT_COLOR,
/// The background color of an active (focused) tab.
///
/// Default: `#33ccff40`.
const 16 => TAB_ACTIVE_BACKGROUND_COLOR,
/// The border color of an active (focused) tab.
///
/// Default: `#33ccffee`.
const 17 => TAB_ACTIVE_BORDER_COLOR,
/// The background color of an inactive tab.
///
/// Default: `#222222`.
const 18 => TAB_INACTIVE_BACKGROUND_COLOR,
/// The border color of an inactive tab.
///
/// Default: `#333333`.
const 19 => TAB_INACTIVE_BORDER_COLOR,
/// The text color of an active (focused) tab.
///
/// Default: `#ffffff`.
const 20 => TAB_ACTIVE_TEXT_COLOR,
/// The text color of an inactive tab.
///
/// Default: `#888888`.
const 21 => TAB_INACTIVE_TEXT_COLOR,
/// The background color of the tab bar strip.
///
/// Default: `#111111`.
const 22 => TAB_BAR_BACKGROUND_COLOR,
/// The background color of a tab that has requested attention.
///
/// Default: `#23092c`.
const 23 => TAB_ATTENTION_BACKGROUND_COLOR,
}
/// Sets the color of GUI element.
pub fn set_color(element: Colorable, color: Color) {
get!().set_color(element, color);
}
/// Gets the color of GUI element.
pub fn get_color(element: Colorable) -> Color {
get!(Color::BLACK).get_color(element)
}
}
/// Elements of the compositor whose size can be changed.
pub mod sized {
use serde::{Deserialize, Serialize};
/// An element of the GUI whose size can be changed.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct Resizable(#[doc(hidden)] pub u32);
impl Resizable {
/// Gets the current size.
pub fn get(self) -> i32 {
get!(0).get_size(self)
}
/// Sets the size.
pub fn set(self, size: i32) {
get!().set_size(self, size)
}
}
macro_rules! sizes {
($($(#[$attr:meta])* const $n:expr => $name:ident,)*) => {
$(
$(#[$attr])*
pub const $name: Resizable = Resizable($n);
)*
}
}
sizes! {
/// The height of window titles.
///
/// Default: 17
const 01 => TITLE_HEIGHT,
/// The width of borders between windows.
///
/// Default: 4
const 02 => BORDER_WIDTH,
/// The height of the bar.
///
/// Defaults to the TITLE_HEIGHT if not set explicitly.
///
/// Default: 17
const 03 => BAR_HEIGHT,
/// The width of the bar's separator.
///
/// Default: 1
const 04 => BAR_SEPARATOR_WIDTH,
/// The gap between tiled windows in pixels.
///
/// When set to a value greater than 0, windows are separated by this
/// gap rather than by the border width.
///
/// Default: 0
const 05 => GAP,
/// The gap between the titlebar and the window content in pixels.
///
/// Default: 0
const 06 => TITLE_GAP,
/// The height of the tab bar in pixels.
///
/// Default: 22
const 07 => TAB_BAR_HEIGHT,
/// The padding between tabs in the tab bar in pixels.
///
/// Default: 6
const 08 => TAB_BAR_PADDING,
/// The corner radius of tabs in the tab bar in pixels.
///
/// Default: 6
const 09 => TAB_BAR_RADIUS,
/// The border width of tabs in the tab bar in pixels.
///
/// Default: 2
const 10 => TAB_BAR_BORDER_WIDTH,
/// The horizontal padding within each tab for text in pixels.
///
/// Default: 4
const 11 => TAB_BAR_TEXT_PADDING,
/// The gap between the tab bar and the window content below in pixels.
///
/// Default: 4
const 12 => TAB_BAR_GAP,
}
}

View file

@ -0,0 +1,92 @@
//! Timers for one-time or repeated actions.
use {
serde::{Deserialize, Serialize},
std::time::{Duration, SystemTime, UNIX_EPOCH},
};
/// A timer.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct Timer(pub u64);
/// Creates a new timer or returns an existing one.
///
/// Timers are identified by their name and their lifetime is bound by the lifetime of
/// the configuration. Reloading the configuration destroys all existing timers.
///
/// Within the same configuration, calling this function multiple times with the same name
/// will return the same timer.
///
/// Timers can be deleted by calling `remove`. At that point all existing references to
/// the timer become invalid and `get_timer` will return a new timer.
pub fn get_timer(name: &str) -> Timer {
get!(Timer(0)).get_timer(name)
}
impl Timer {
/// Programs the timer to fire once.
pub fn once(self, initial: Duration) {
get!().program_timer(self, Some(initial), None);
}
/// Programs the timer to fire repeatedly.
///
/// `initial` is the period after which the timer expires for the first time.
pub fn repeated(self, initial: Duration, period: Duration) {
get!().program_timer(self, Some(initial), Some(period));
}
/// Cancels the timer.
///
/// The timer remains valid but will never expire. It can be reprogrammed by calling
/// `once` or `repeated`.
pub fn cancel(self) {
get!().program_timer(self, None, None);
}
/// Removes the time.
///
/// This reference to the timer becomes invalid as do all other existing references.
/// A new timer with the same name can be created by calling `get_timer`.
pub fn remove(self) {
get!().remove_timer(self);
}
/// Sets the function to be executed when the timer expires.
pub fn on_tick<F: FnMut() + 'static>(self, f: F) {
get!().on_timer_tick(self, f);
}
}
/// Returns the duration until the wall clock is a multiple of `duration`.
///
/// # Example
///
/// Execute a timer every time the wall clock becomes a multiple of 5 seconds:
///
/// ```rust,ignore
/// let period = Duration::from_secs(5);
/// let timer = get_timer("status_timer");
/// timer.repeated(
/// duration_until_wall_clock_is_multiple_of(period),
/// period,
/// );
/// timer.on_tick(|| todo!());
/// ```
pub fn duration_until_wall_clock_is_multiple_of(duration: Duration) -> Duration {
let now = match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(n) => n,
_ => return Duration::from_secs(0),
};
let now = now.as_nanos();
let duration = duration.as_nanos();
if duration == 0 {
return Duration::from_secs(0);
}
let nanos = duration - now % duration;
if nanos == duration {
Duration::from_secs(0)
} else {
Duration::from_nanos(nanos as _)
}
}

View file

@ -0,0 +1,832 @@
//! Tools for configuring graphics cards and monitors.
use {
crate::{
Direction, PciId, Workspace,
protocol::WireMode,
video::connector_type::{
CON_9PIN_DIN, CON_COMPONENT, CON_COMPOSITE, CON_DISPLAY_PORT, CON_DPI, CON_DSI,
CON_DVIA, CON_DVID, CON_DVII, CON_EDP, CON_EMBEDDED_WINDOW, CON_HDMIA, CON_HDMIB,
CON_LVDS, CON_SPI, CON_SVIDEO, CON_TV, CON_UNKNOWN, CON_USB, CON_VGA, CON_VIRTUAL,
CON_WRITEBACK, ConnectorType,
},
},
serde::{Deserialize, Serialize},
std::{str::FromStr, time::Duration},
};
/// The mode of a connector.
///
/// Currently a mode consists of three properties:
///
/// - width in pixels
/// - height in pixels
/// - refresh rate in mhz.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct Mode {
pub(crate) width: i32,
pub(crate) height: i32,
pub(crate) refresh_millihz: u32,
}
impl Mode {
/// Returns the width of the mode.
pub fn width(&self) -> i32 {
self.width
}
/// Returns the height of the mode.
pub fn height(&self) -> i32 {
self.height
}
/// Returns the refresh rate of the mode in mhz.
///
/// For a 60hz monitor, this function would return 60_000.
pub fn refresh_rate(&self) -> u32 {
self.refresh_millihz
}
pub(crate) fn zeroed() -> Self {
Self {
width: 0,
height: 0,
refresh_millihz: 0,
}
}
}
/// A connector that is potentially connected to an output device.
///
/// A connector is the part that sticks out of your graphics card. A graphics card usually
/// has many connectors but one few of them are actually connected to a monitor.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct Connector(pub u64);
impl Connector {
/// Returns whether this connector existed at the time `get_connector` was called.
///
/// This only implies existence at the time `get_connector` was called. Even if this
/// function returns true, the connector might since have disappeared.
pub fn exists(self) -> bool {
self.0 != 0
}
/// Returns whether the connector is connected to an output device.
pub fn connected(self) -> bool {
if !self.exists() {
return false;
}
get!(false).connector_connected(self)
}
/// Returns the scale of the currently connected monitor.
pub fn scale(self) -> f64 {
if !self.exists() {
return 1.0;
}
get!(1.0).connector_get_scale(self)
}
/// Sets the scale to use for the currently connected monitor.
pub fn set_scale(self, scale: f64) {
if !self.exists() {
return;
}
log::info!("setting scale to {}", scale);
get!().connector_set_scale(self, scale);
}
/// Returns the connector type.
pub fn ty(self) -> ConnectorType {
if !self.exists() {
return CON_UNKNOWN;
}
get!(CON_UNKNOWN).connector_type(self)
}
/// Returns the current mode of the connector.
pub fn mode(self) -> Mode {
if !self.exists() {
return Mode::zeroed();
}
get!(Mode::zeroed()).connector_mode(self)
}
/// Tries to set the mode of the connector.
///
/// If the refresh rate is not specified, tries to use the first mode with the given
/// width and height.
///
/// The default mode is the first mode advertised by the connector. This is usually
/// the native mode.
pub fn set_mode(self, width: i32, height: i32, refresh_millihz: Option<u32>) {
if !self.exists() {
log::warn!("set_mode called on a connector that does not exist");
return;
}
let refresh_millihz = match refresh_millihz {
Some(r) => r,
_ => match self
.modes()
.iter()
.find(|m| m.width == width && m.height == height)
{
Some(m) => m.refresh_millihz,
_ => {
log::warn!("Could not find any mode with width {width} and height {height}");
return;
}
},
};
get!().connector_set_mode(
self,
WireMode {
width,
height,
refresh_millihz,
},
)
}
/// Returns the available modes of the connector.
pub fn modes(self) -> Vec<Mode> {
if !self.exists() {
return Vec::new();
}
get!(Vec::new()).connector_modes(self)
}
/// Returns whether this connector supports arbitrary modes.
pub fn supports_arbitrary_modes(self) -> bool {
if !self.exists() {
return false;
}
get!(false).connector_supports_arbitrary_modes(self)
}
/// Returns the logical width of the connector.
///
/// The returned value will be different from `mode().width()` if the scale is not 1.
pub fn width(self) -> i32 {
get!().connector_size(self).0
}
/// Returns the logical height of the connector.
///
/// The returned value will be different from `mode().height()` if the scale is not 1.
pub fn height(self) -> i32 {
get!().connector_size(self).1
}
/// Returns the refresh rate in mhz of the current mode of the connector.
///
/// This is a shortcut for `mode().refresh_rate()`.
pub fn refresh_rate(self) -> u32 {
self.mode().refresh_millihz
}
/// Retrieves the position of the output in the global compositor space.
pub fn position(self) -> (i32, i32) {
if !self.connected() {
return (0, 0);
}
get!().connector_get_position(self)
}
/// Sets the position of the connector in the global compositor space.
///
/// `x` and `y` must be non-negative and must not exceed a currently unspecified limit.
/// Any reasonable values for `x` and `y` should work.
///
/// This function allows the connector to overlap with other connectors, however, such
/// configurations are not supported and might result in unexpected behavior.
pub fn set_position(self, x: i32, y: i32) {
if !self.exists() {
log::warn!("set_position called on a connector that does not exist");
return;
}
get!().connector_set_position(self, x, y);
}
/// Enables or disables the connector.
///
/// By default, all connectors are enabled.
pub fn set_enabled(self, enabled: bool) {
if !self.exists() {
log::warn!("set_enabled called on a connector that does not exist");
return;
}
get!().connector_set_enabled(self, enabled);
}
/// Sets the transformation to apply to the content of this connector.
pub fn set_transform(self, transform: Transform) {
if !self.exists() {
log::warn!("set_transform called on a connector that does not exist");
return;
}
get!().connector_set_transform(self, transform);
}
pub fn name(self) -> String {
if !self.exists() {
return String::new();
}
get!(String::new()).connector_get_name(self)
}
pub fn model(self) -> String {
if !self.exists() {
return String::new();
}
get!(String::new()).connector_get_model(self)
}
pub fn manufacturer(self) -> String {
if !self.exists() {
return String::new();
}
get!(String::new()).connector_get_manufacturer(self)
}
pub fn serial_number(self) -> String {
if !self.exists() {
return String::new();
}
get!(String::new()).connector_get_serial_number(self)
}
/// Sets the VRR mode.
pub fn set_vrr_mode(self, mode: VrrMode) {
get!().set_vrr_mode(Some(self), mode)
}
/// Sets the VRR cursor refresh rate.
///
/// Limits the rate at which cursors are updated on screen when VRR is active.
///
/// Setting this to infinity disables the limiter.
pub fn set_vrr_cursor_hz(self, hz: f64) {
get!().set_vrr_cursor_hz(Some(self), hz)
}
/// Sets the tearing mode.
pub fn set_tearing_mode(self, mode: TearingMode) {
get!().set_tearing_mode(Some(self), mode)
}
/// Sets the format to use for framebuffers.
pub fn set_format(self, format: Format) {
get!().connector_set_format(self, format);
}
/// Sets the color space and EOTF of the connector.
///
/// By default, the default values are used which usually means sRGB color space with
/// gamma22 EOTF.
///
/// If the output supports it, HDR10 can be enabled by setting the color space to
/// BT.2020 and the EOTF to PQ.
///
/// Note that some displays might ignore incompatible settings.
pub fn set_colors(self, color_space: ColorSpace, eotf: Eotf) {
get!().connector_set_colors(self, color_space, eotf);
}
/// Sets the space in which blending is performed for this output.
///
/// The default is [`BlendSpace::SRGB`]
pub fn set_blend_space(self, blend_space: BlendSpace) {
get!().connector_set_blend_space(self, blend_space);
}
/// Sets the brightness of the output.
///
/// By default or when `brightness` is `None`, the brightness depends on the
/// EOTF:
///
/// - [`Eotf::DEFAULT`]: The maximum brightness of the output.
/// - [`Eotf::PQ`]: 203 cd/m^2.
///
/// This should only be used with the PQ transfer function. If the default transfer
/// function is used, you should instead calibrate the hardware directly.
///
/// When used with the default transfer function, the default brightness is anchored
/// at 80 cd/m^2. That is, setting this to 40 cd/m^2 makes everything appear half as
/// bright as normal and creates 50% HDR headroom.
///
/// This has no effect unless the vulkan renderer is used.
pub fn set_brightness(self, brightness: Option<f64>) {
get!().connector_set_brightness(self, brightness);
}
/// Get the currently visible/active workspace.
///
/// If this connector is not connected, or is there no active workspace, returns a
/// workspace whose `exists()` returns false.
pub fn active_workspace(self) -> Workspace {
get!(Workspace(0)).get_connector_active_workspace(self)
}
/// Get all workspaces on this connector.
///
/// If this connector is not connected, returns an empty list.
pub fn workspaces(self) -> Vec<Workspace> {
get!().get_connector_workspaces(self)
}
/// Find the closest connector in the given direction.
///
/// Uses center-to-center distance calculation and prefers outputs better aligned
/// with the movement axis.
///
/// If no connector exists in the given direction, returns a connector whose
/// `exists()` returns false.
pub fn connector_in_direction(self, direction: Direction) -> Connector {
get!(Connector(0)).get_connector_in_direction(self, direction)
}
/// Configures whether the display primaries are used.
///
/// By default, Jay pretends that the display uses sRGB primaries. This is also how
/// most other systems behave. In reality, most displays use a much larger gamut. For
/// example, they advertise that they support 95% of the DCI-P3 gamut. If the display
/// is interpreting colors in their native gamut, then colors will appear more
/// saturated than their specification.
///
/// If this is set to `true`, Jay assumes that the display uses the primaries
/// advertised in its EDID. This might produce more accurate colors while also
/// allowing color-managed applications to use the full gamut of the display.
///
/// This setting has no effect when the display is explicitly operating in a wide
/// color space.
///
/// The default is `false`.
pub fn set_use_native_gamut(self, use_native_gamut: bool) {
get!().connector_set_use_native_gamut(self, use_native_gamut);
}
}
/// Returns all available DRM devices.
pub fn drm_devices() -> Vec<DrmDevice> {
get!().drm_devices()
}
/// Sets the callback to be called when a new DRM device appears.
pub fn on_new_drm_device<F: FnMut(DrmDevice) + 'static>(f: F) {
get!().on_new_drm_device(f)
}
/// Sets the callback to be called when a DRM device is removed.
pub fn on_drm_device_removed<F: FnMut(DrmDevice) + 'static>(f: F) {
get!().on_del_drm_device(f)
}
/// Sets the callback to be called when a new connector appears.
pub fn on_new_connector<F: FnMut(Connector) + 'static>(f: F) {
get!().on_new_connector(f)
}
/// Sets the callback to be called when a connector becomes connected to an output device.
pub fn on_connector_connected<F: FnMut(Connector) + 'static>(f: F) {
get!().on_connector_connected(f)
}
/// Sets the callback to be called when a connector is disconnected from an output device.
pub fn on_connector_disconnected<F: FnMut(Connector) + 'static>(f: F) {
get!().on_connector_disconnected(f)
}
/// Sets the callback to be called when the graphics of the compositor have been initialized.
///
/// This callback is only invoked once during the lifetime of the compositor. This is a good place
/// to auto start graphical applications.
pub fn on_graphics_initialized<F: FnOnce() + 'static>(f: F) {
get!().on_graphics_initialized(f)
}
pub fn connectors() -> Vec<Connector> {
get!().connectors(None)
}
/// Returns the connector with the given id.
///
/// The linux kernel identifies connectors by a (type, idx) tuple, e.g., `DP-0`.
/// If the connector does not exist at the time this function is called, a sentinel value is
/// returned. This can be checked by calling `exists()` on the returned connector.
///
/// The `id` argument can either be an explicit tuple, e.g. `(CON_DISPLAY_PORT, 0)`, or a string
/// that can be parsed to such a tuple, e.g. `"DP-0"`.
///
/// The following string prefixes exist:
///
/// - `DP`
/// - `eDP`
/// - `HDMI-A`
/// - `HDMI-B`
/// - `EmbeddedWindow` - this is an implementation detail of the compositor and used if it
/// runs as an embedded application.
/// - `VGA`
/// - `DVI-I`
/// - `DVI-D`
/// - `DVI-A`
/// - `Composite`
/// - `SVIDEO`
/// - `LVDS`
/// - `Component`
/// - `DIN`
/// - `TV`
/// - `Virtual`
/// - `DSI`
/// - `DPI`
/// - `Writeback`
/// - `SPI`
/// - `USB`
pub fn get_connector(id: impl ToConnectorId) -> Connector {
let (ty, idx) = match id.to_connector_id() {
Ok(id) => id,
Err(e) => {
log::error!("{}", e);
return Connector(0);
}
};
get!(Connector(0)).get_connector(ty, idx)
}
/// Returns the connector with the given name.
///
/// Unlike [`get_connector`], this function can also be used for connectors whose names
/// don't follow the `<type>-<id>` pattern.
pub fn get_connector_by_name(name: &str) -> Connector {
get!(Connector(0)).get_connector_by_name(name)
}
/// A type that can be converted to a `(ConnectorType, idx)` tuple.
pub trait ToConnectorId {
fn to_connector_id(&self) -> Result<(ConnectorType, u32), String>;
}
impl ToConnectorId for (ConnectorType, u32) {
fn to_connector_id(&self) -> Result<(ConnectorType, u32), String> {
Ok(*self)
}
}
impl ToConnectorId for &'_ str {
fn to_connector_id(&self) -> Result<(ConnectorType, u32), String> {
let pairs = [
("DP-", CON_DISPLAY_PORT),
("eDP-", CON_EDP),
("HDMI-A-", CON_HDMIA),
("HDMI-B-", CON_HDMIB),
("EmbeddedWindow-", CON_EMBEDDED_WINDOW),
("VGA-", CON_VGA),
("DVI-I-", CON_DVII),
("DVI-D-", CON_DVID),
("DVI-A-", CON_DVIA),
("Composite-", CON_COMPOSITE),
("SVIDEO-", CON_SVIDEO),
("LVDS-", CON_LVDS),
("Component-", CON_COMPONENT),
("DIN-", CON_9PIN_DIN),
("TV-", CON_TV),
("Virtual-", CON_VIRTUAL),
("DSI-", CON_DSI),
("DPI-", CON_DPI),
("Writeback-", CON_WRITEBACK),
("SPI-", CON_SPI),
("USB-", CON_USB),
];
for (prefix, ty) in pairs {
if let Some(suffix) = self.strip_prefix(prefix)
&& let Ok(idx) = u32::from_str(suffix)
{
return Ok((ty, idx));
}
}
Err(format!("`{}` is not a valid connector identifier", self))
}
}
/// Module containing all known connector types.
pub mod connector_type {
use serde::{Deserialize, Serialize};
/// The type of a connector.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct ConnectorType(pub u32);
pub const CON_UNKNOWN: ConnectorType = ConnectorType(0);
pub const CON_VGA: ConnectorType = ConnectorType(1);
pub const CON_DVII: ConnectorType = ConnectorType(2);
pub const CON_DVID: ConnectorType = ConnectorType(3);
pub const CON_DVIA: ConnectorType = ConnectorType(4);
pub const CON_COMPOSITE: ConnectorType = ConnectorType(5);
pub const CON_SVIDEO: ConnectorType = ConnectorType(6);
pub const CON_LVDS: ConnectorType = ConnectorType(7);
pub const CON_COMPONENT: ConnectorType = ConnectorType(8);
pub const CON_9PIN_DIN: ConnectorType = ConnectorType(9);
pub const CON_DISPLAY_PORT: ConnectorType = ConnectorType(10);
pub const CON_HDMIA: ConnectorType = ConnectorType(11);
pub const CON_HDMIB: ConnectorType = ConnectorType(12);
pub const CON_TV: ConnectorType = ConnectorType(13);
pub const CON_EDP: ConnectorType = ConnectorType(14);
pub const CON_VIRTUAL: ConnectorType = ConnectorType(15);
pub const CON_DSI: ConnectorType = ConnectorType(16);
pub const CON_DPI: ConnectorType = ConnectorType(17);
pub const CON_WRITEBACK: ConnectorType = ConnectorType(18);
pub const CON_SPI: ConnectorType = ConnectorType(19);
pub const CON_USB: ConnectorType = ConnectorType(20);
pub const CON_EMBEDDED_WINDOW: ConnectorType = ConnectorType(u32::MAX);
pub const CON_VIRTUAL_OUTPUT: ConnectorType = ConnectorType(u32::MAX - 1);
}
/// A *Direct Rendering Manager* (DRM) device.
///
/// It's easiest to think of a DRM device as a graphics card.
/// There are also DRM devices that are emulated in software but you are unlikely to encounter
/// those accidentally.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct DrmDevice(pub u64);
impl DrmDevice {
/// Returns the connectors of this device.
pub fn connectors(self) -> Vec<Connector> {
get!().connectors(Some(self))
}
/// Returns the devnode of this device.
///
/// E.g. `/dev/dri/card0`.
pub fn devnode(self) -> String {
get!().drm_device_devnode(self)
}
/// Returns the syspath of this device.
///
/// E.g. `/sys/devices/pci0000:00/0000:00:03.1/0000:07:00.0`.
pub fn syspath(self) -> String {
get!().drm_device_syspath(self)
}
/// Returns the vendor of this device.
///
/// E.g. `Advanced Micro Devices, Inc. [AMD/ATI]`.
pub fn vendor(self) -> String {
get!().drm_device_vendor(self)
}
/// Returns the model of this device.
///
/// E.g. `Ellesmere [Radeon RX 470/480/570/570X/580/580X/590] (Radeon RX 570 Armor 8G OC)`.
pub fn model(self) -> String {
get!().drm_device_model(self)
}
/// Returns the PIC ID of this device.
///
/// E.g. `1002:67DF`.
pub fn pci_id(self) -> PciId {
get!().drm_device_pci_id(self)
}
/// Makes this device the render device.
pub fn make_render_device(self) {
get!().make_render_device(self);
}
/// Sets the preferred graphics API for this device.
///
/// If the API cannot be used, the compositor will try other APIs.
pub fn set_gfx_api(self, gfx_api: GfxApi) {
get!().set_gfx_api(Some(self), gfx_api);
}
/// Enables or disables direct scanout of client surfaces for this device.
pub fn set_direct_scanout_enabled(self, enabled: bool) {
get!().set_direct_scanout_enabled(Some(self), enabled);
}
/// Sets the flip margin of this device.
///
/// This is duration between the compositor initiating a page flip and the output's
/// vblank event. This determines the minimum input latency. The default is 1.5 ms.
///
/// Note that if the margin is too small, the compositor will dynamically increase it.
pub fn set_flip_margin(self, margin: Duration) {
get!().set_flip_margin(self, margin);
}
}
/// A graphics API.
#[non_exhaustive]
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub enum GfxApi {
OpenGl,
Vulkan,
}
/// Sets the default graphics API.
///
/// If the API cannot be used, the compositor will try other APIs.
///
/// This setting can be overwritten per-device with [DrmDevice::set_gfx_api].
///
/// This call has no effect on devices that have already been initialized.
pub fn set_gfx_api(gfx_api: GfxApi) {
get!().set_gfx_api(None, gfx_api);
}
/// Enables or disables direct scanout of client surfaces.
///
/// The default is `true`.
///
/// This setting can be overwritten per-device with [DrmDevice::set_direct_scanout_enabled].
pub fn set_direct_scanout_enabled(enabled: bool) {
get!().set_direct_scanout_enabled(None, enabled);
}
/// A transformation.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash, Default)]
pub enum Transform {
/// No transformation.
#[default]
None,
/// Rotate 90 degrees counter-clockwise.
Rotate90,
/// Rotate 180 degrees counter-clockwise.
Rotate180,
/// Rotate 270 degrees counter-clockwise.
Rotate270,
/// Flip around the vertical axis.
Flip,
/// Flip around the vertical axis, then rotate 90 degrees counter-clockwise.
FlipRotate90,
/// Flip around the vertical axis, then rotate 180 degrees counter-clockwise.
FlipRotate180,
/// Flip around the vertical axis, then rotate 270 degrees counter-clockwise.
FlipRotate270,
}
/// The VRR mode of a connector.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash, Default)]
pub struct VrrMode(pub u32);
impl VrrMode {
/// VRR is never enabled.
pub const NEVER: Self = Self(0);
/// VRR is always enabled.
pub const ALWAYS: Self = Self(1);
/// VRR is enabled when one or more applications are displayed fullscreen.
pub const VARIANT_1: Self = Self(2);
/// VRR is enabled when a single application is displayed fullscreen.
pub const VARIANT_2: Self = Self(3);
/// VRR is enabled when a single game or video is displayed fullscreen.
pub const VARIANT_3: Self = Self(4);
}
/// Sets the default VRR mode.
///
/// This setting can be overwritten on a per-connector basis with [Connector::set_vrr_mode].
pub fn set_vrr_mode(mode: VrrMode) {
get!().set_vrr_mode(None, mode)
}
/// Sets the VRR cursor refresh rate.
///
/// Limits the rate at which cursors are updated on screen when VRR is active.
///
/// Setting this to infinity disables the limiter.
///
/// This setting can be overwritten on a per-connector basis with [Connector::set_vrr_cursor_hz].
pub fn set_vrr_cursor_hz(hz: f64) {
get!().set_vrr_cursor_hz(None, hz)
}
/// The tearing mode of a connector.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash, Default)]
pub struct TearingMode(pub u32);
impl TearingMode {
/// Tearing is never enabled.
pub const NEVER: Self = Self(0);
/// Tearing is always enabled.
pub const ALWAYS: Self = Self(1);
/// Tearing is enabled when one or more applications are displayed fullscreen.
pub const VARIANT_1: Self = Self(2);
/// Tearing is enabled when a single application is displayed fullscreen.
pub const VARIANT_2: Self = Self(3);
/// Tearing is enabled when a single application is displayed fullscreen and the
/// application has requested tearing.
///
/// This is the default.
pub const VARIANT_3: Self = Self(4);
}
/// Sets the default tearing mode.
///
/// This setting can be overwritten on a per-connector basis with [Connector::set_tearing_mode].
pub fn set_tearing_mode(mode: TearingMode) {
get!().set_tearing_mode(None, mode)
}
/// Creates a virtual output with the given name.
///
/// This is a no-op if a virtual output with that name already exists.
///
/// The created connector can be accessed with [`get_connector_by_name("VO-{name}")`].
///
/// A newly created connector is initially disabled. When a connector is destroyed and
/// later recreated, its previous state is restored.
pub fn create_virtual_output(name: &str) {
get!().create_virtual_output(name);
}
/// Removes the virtual output with the given name.
///
/// This is a no-op if a virtual output with that name does not exist.
pub fn remove_virtual_output(name: &str) {
get!().remove_virtual_output(name);
}
/// A graphics format.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct Format(pub u32);
impl Format {
pub const ARGB8888: Self = Self(0);
pub const XRGB8888: Self = Self(1);
pub const ABGR8888: Self = Self(2);
pub const XBGR8888: Self = Self(3);
pub const R8: Self = Self(4);
pub const GR88: Self = Self(5);
pub const RGB888: Self = Self(6);
pub const BGR888: Self = Self(7);
pub const RGBA4444: Self = Self(8);
pub const RGBX4444: Self = Self(9);
pub const BGRA4444: Self = Self(10);
pub const BGRX4444: Self = Self(11);
pub const RGB565: Self = Self(12);
pub const BGR565: Self = Self(13);
pub const RGBA5551: Self = Self(14);
pub const RGBX5551: Self = Self(15);
pub const BGRA5551: Self = Self(16);
pub const BGRX5551: Self = Self(17);
pub const ARGB1555: Self = Self(18);
pub const XRGB1555: Self = Self(19);
pub const ARGB2101010: Self = Self(20);
pub const XRGB2101010: Self = Self(21);
pub const ABGR2101010: Self = Self(22);
pub const XBGR2101010: Self = Self(23);
pub const ABGR16161616: Self = Self(24);
pub const XBGR16161616: Self = Self(25);
pub const ABGR16161616F: Self = Self(26);
pub const XBGR16161616F: Self = Self(27);
pub const BGR161616: Self = Self(28);
pub const R16F: Self = Self(29);
pub const GR1616F: Self = Self(30);
pub const BGR161616F: Self = Self(31);
pub const R32F: Self = Self(32);
pub const GR3232F: Self = Self(33);
pub const BGR323232F: Self = Self(34);
pub const ABGR32323232F: Self = Self(35);
}
/// A color space.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct ColorSpace(pub u32);
impl ColorSpace {
/// The default color space (usually sRGB).
pub const DEFAULT: Self = Self(0);
/// The BT.2020 color space.
pub const BT2020: Self = Self(1);
}
/// An electro-optical transfer function (EOTF).
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct Eotf(pub u32);
#[deprecated = "use the Eotf type instead"]
pub type TransferFunction = Eotf;
impl Eotf {
/// The default EOTF (usually gamma22).
pub const DEFAULT: Self = Self(0);
/// The PQ EOTF.
pub const PQ: Self = Self(1);
}
/// A space in which color blending is performed.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct BlendSpace(pub u32);
impl BlendSpace {
/// The sRGB blend space with sRGB primaries and gamma22 transfer function. This is
/// the classic desktop blend space.
pub const SRGB: Self = Self(0);
/// The linear blend space performs blending in linear space, which is more physically
/// correct but leads to much lighter output when blending light and dark colors.
pub const LINEAR: Self = Self(1);
}

View file

@ -0,0 +1,422 @@
//! Tools for inspecting and manipulating windows.
use {
crate::{
Axis, Direction, Workspace,
client::{Client, ClientCriterion},
input::Seat,
},
serde::{Deserialize, Serialize},
std::ops::Deref,
};
/// A toplevel window.
///
/// A toplevel window is anything that can be stored within a container tile or within a
/// floating window.
///
/// There are currently four types of windows:
///
/// - Containers
/// - Placeholders that take the place of a window when it goes fullscreen
/// - XDG toplevels
/// - X windows
///
/// You can find out the type of a window by using the [`Window::type_`] function.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct Window(pub u64);
bitflags! {
/// The type of a window.
#[derive(Serialize, Deserialize, Copy, Clone, Hash, Eq, PartialEq)]
pub struct WindowType(pub u64) {
/// A container.
pub const CONTAINER = 1 << 0,
/// A placeholder.
pub const PLACEHOLDER = 1 << 1,
/// An XDG toplevel.
pub const XDG_TOPLEVEL = 1 << 2,
/// An X window.
pub const X_WINDOW = 1 << 3,
}
}
bitflags! {
/// The content type of a window.
#[derive(Serialize, Deserialize, Copy, Clone, Hash, Eq, PartialEq)]
pub struct ContentType(pub u64) {
/// No content type.
pub const NO_CONTENT_TYPE = 1 << 0,
/// Photo content type.
pub const PHOTO_CONTENT = 1 << 1,
/// Video content type.
pub const VIDEO_CONTENT = 1 << 2,
/// Game content type.
pub const GAME_CONTENT = 1 << 3,
}
}
/// The tile state of a window.
#[non_exhaustive]
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub enum TileState {
/// The window is tiled.
Tiled,
/// The window is floating.
Floating,
}
/// A window created by a client.
///
/// This is the same as `XDG_TOPLEVEL | X_WINDOW`.
pub const CLIENT_WINDOW: WindowType = WindowType(XDG_TOPLEVEL.0 | X_WINDOW.0);
impl Window {
/// Returns whether the window exists.
pub fn exists(self) -> bool {
self.0 != 0 && get!(false).window_exists(self)
}
/// Returns whether the window does not exist.
///
/// This is a shorthand for `!self.exists()`.
pub fn does_not_exist(self) -> bool {
!self.exists()
}
/// Returns the client of the window.
///
/// If the window does not have a client, [`Client::exists`] return false.
pub fn client(self) -> Client {
get!(Client(0)).window_client(self)
}
/// Returns the title of the window.
pub fn title(self) -> String {
get!().window_title(self)
}
/// Returns the type of the window.
pub fn type_(self) -> WindowType {
get!(WindowType(0)).window_type(self)
}
/// Returns the content type of the window.
pub fn content_type(self) -> ContentType {
get!(ContentType(0)).content_type(self)
}
/// Returns the identifier of the window.
///
/// This is the identifier used in the `ext-foreign-toplevel-list-v1` protocol.
pub fn id(self) -> String {
get!().window_id(self)
}
/// Returns whether this window is visible.
pub fn is_visible(self) -> bool {
get!().window_is_visible(self)
}
/// Returns the parent of this window.
///
/// If this window has no parent, [`Window::exists`] returns false.
pub fn parent(self) -> Window {
get!(Window(0)).window_parent(self)
}
/// Returns the children of this window.
///
/// Only containers have children.
pub fn children(self) -> Vec<Window> {
get!().window_children(self)
}
/// Moves the window in the specified direction.
pub fn move_(self, direction: Direction) {
get!().window_move(self, direction)
}
/// Returns whether the parent-container of the window is in mono-mode.
pub fn mono(self) -> bool {
get!(false).window_mono(self)
}
/// Sets whether the parent-container of the window is in mono-mode.
pub fn set_mono(self, mono: bool) {
get!().set_window_mono(self, mono)
}
/// Toggles whether the parent-container of the window is in mono-mode.
pub fn toggle_mono(self) {
self.set_mono(!self.mono());
}
/// Returns the split axis of the parent-container of the window.
pub fn split(self) -> Axis {
get!(Axis::Horizontal).window_split(self)
}
/// Sets the split axis of the parent-container of the window.
pub fn set_split(self, axis: Axis) {
get!().set_window_split(self, axis)
}
/// Toggles the split axis of the parent-container of the window.
pub fn toggle_split(self) {
self.set_split(self.split().other());
}
/// Creates a new container with the specified split in place of the window.
pub fn create_split(self, axis: Axis) {
get!().create_window_split(self, axis);
}
/// Requests the window to be closed.
pub fn close(self) {
get!().close_window(self);
}
/// Returns whether the window is floating.
pub fn floating(self) -> bool {
get!().get_window_floating(self)
}
/// Sets whether the window is floating.
pub fn set_floating(self, floating: bool) {
get!().set_window_floating(self, floating);
}
/// Toggles whether the window is floating.
///
/// You can do the same by double-clicking on the header.
pub fn toggle_floating(self) {
self.set_floating(!self.floating());
}
/// Returns the workspace that this window belongs to.
///
/// If no such workspace exists, `exists` returns `false` for the returned workspace.
pub fn workspace(self) -> Workspace {
get!(Workspace(0)).get_window_workspace(self)
}
/// Moves the window to the workspace.
pub fn set_workspace(self, workspace: Workspace) {
get!().set_window_workspace(self, workspace)
}
/// Sends the window to a scratchpad.
///
/// Use an empty string for the default scratchpad.
pub fn send_to_scratchpad(self, name: &str) {
get!().window_send_to_scratchpad(self, name)
}
/// Toggles whether the currently focused window is fullscreen.
pub fn toggle_fullscreen(self) {
self.set_fullscreen(!self.fullscreen())
}
/// Returns whether the window is fullscreen.
pub fn fullscreen(self) -> bool {
get!(false).get_window_fullscreen(self)
}
/// Sets whether the window is fullscreen.
pub fn set_fullscreen(self, fullscreen: bool) {
get!().set_window_fullscreen(self, fullscreen)
}
/// Gets whether the window is pinned.
///
/// If a floating window is pinned, it will stay visible even when switching to a
/// different workspace.
pub fn float_pinned(self) -> bool {
get!().get_window_pinned(self)
}
/// Sets whether the window is pinned.
pub fn set_float_pinned(self, pinned: bool) {
get!().set_window_pinned(self, pinned);
}
/// Toggles whether the window is pinned.
pub fn toggle_float_pinned(self) {
self.set_float_pinned(!self.float_pinned());
}
/// Resizes the window.
pub fn resize(self, dx1: i32, dy1: i32, dx2: i32, dy2: i32) {
get!().resize_window(self, dx1, dy1, dx2, dy2);
}
}
/// A window matcher.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct WindowMatcher(pub u64);
/// A matched window.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct MatchedWindow {
pub(crate) matcher: WindowMatcher,
pub(crate) window: Window,
}
/// A criterion for matching a window.
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
#[non_exhaustive]
pub enum WindowCriterion<'a> {
/// Matches if the contained matcher matches.
Matcher(WindowMatcher),
/// Matches if the contained criterion does not match.
Not(&'a WindowCriterion<'a>),
/// Matches if the window has one of the types.
Types(WindowType),
/// Matches if all of the contained criteria match.
All(&'a [WindowCriterion<'a>]),
/// Matches if any of the contained criteria match.
Any(&'a [WindowCriterion<'a>]),
/// Matches if an exact number of the contained criteria match.
Exactly(usize, &'a [WindowCriterion<'a>]),
/// Matches if the window's client matches the client criterion.
Client(&'a ClientCriterion<'a>),
/// Matches the title of the window verbatim.
Title(&'a str),
/// Matches the title of the window with a regular expression.
TitleRegex(&'a str),
/// Matches the app-id of the window verbatim.
AppId(&'a str),
/// Matches the app-id of the window with a regular expression.
AppIdRegex(&'a str),
/// Matches if the window is floating.
Floating,
/// Matches if the window is visible.
Visible,
/// Matches if the window has the urgency flag set.
Urgent,
/// Matches if the window has the keyboard focus of the seat.
Focus(Seat),
/// Matches if the window is fullscreen.
Fullscreen,
/// Matches if the window has/hasn't just been mapped.
///
/// This is true for one iteration of the compositor's main loop immediately after the
/// window has been mapped.
JustMapped,
/// Matches the toplevel-tag of the window verbatim.
Tag(&'a str),
/// Matches the toplevel-tag of the window with a regular expression.
TagRegex(&'a str),
/// Matches the X class of the window verbatim.
XClass(&'a str),
/// Matches the X class of the window with a regular expression.
XClassRegex(&'a str),
/// Matches the X instance of the window verbatim.
XInstance(&'a str),
/// Matches the X instance of the window with a regular expression.
XInstanceRegex(&'a str),
/// Matches the X role of the window verbatim.
XRole(&'a str),
/// Matches the X role of the window with a regular expression.
XRoleRegex(&'a str),
/// Matches the workspace the window.
Workspace(Workspace),
/// Matches the workspace name of the window verbatim.
WorkspaceName(&'a str),
/// Matches the workspace name of the window with a regular expression.
WorkspaceNameRegex(&'a str),
/// Matches if the window has one of the content types.
ContentTypes(ContentType),
}
impl WindowCriterion<'_> {
/// Converts the criterion to a matcher.
pub fn to_matcher(self) -> WindowMatcher {
get!(WindowMatcher(0)).create_window_matcher(self)
}
/// Binds a function to execute when the criterion matches a window.
///
/// This leaks the matcher.
pub fn bind<F: FnMut(MatchedWindow) + 'static>(self, cb: F) {
self.to_matcher().bind(cb);
}
/// Sets whether newly mapped windows that match this criterion get the keyboard focus.
///
/// If a window matches any criterion for which this is false, the window will not be
/// automatically focused.
///
/// This leaks the matcher.
pub fn set_auto_focus(self, auto_focus: bool) {
self.to_matcher().set_auto_focus(auto_focus);
}
/// Sets whether newly mapped windows that match this matcher are mapped tiling or
/// floating.
///
/// If multiple such window matchers match a window, the used tile state is
/// unspecified.
///
/// This leaks the matcher.
pub fn set_initial_tile_state(self, tile_state: TileState) {
self.to_matcher().set_initial_tile_state(tile_state);
}
}
impl WindowMatcher {
/// Destroys the matcher.
///
/// Any bound callback will no longer be executed.
pub fn destroy(self) {
get!().destroy_window_matcher(self);
}
/// Sets a function to execute when the criterion matches a window.
///
/// Replaces any already bound callback.
pub fn bind<F: FnMut(MatchedWindow) + 'static>(self, cb: F) {
get!().set_window_matcher_handler(self, cb);
}
/// Sets whether newly mapped windows that match this matcher get the keyboard focus.
///
/// If a window matches any matcher for which this is false, the window will not be
/// automatically focused.
pub fn set_auto_focus(self, auto_focus: bool) {
get!().set_window_matcher_auto_focus(self, auto_focus);
}
/// Sets whether newly mapped windows that match this matcher are mapped tiling or
/// floating.
///
/// If multiple such window matchers match a window, the used tile state is
/// unspecified.
pub fn set_initial_tile_state(self, tile_state: TileState) {
get!().set_window_matcher_initial_tile_state(self, tile_state);
}
}
impl MatchedWindow {
/// Returns the window that matched.
pub fn window(self) -> Window {
self.window
}
/// Returns the matcher.
pub fn matcher(self) -> WindowMatcher {
self.matcher
}
/// Latches a function to be executed when the window no longer matches the criteria.
pub fn latch<F: FnOnce() + 'static>(self, cb: F) {
get!().set_window_matcher_latch_handler(self.matcher, self.window, cb);
}
}
impl Deref for MatchedWindow {
type Target = Window;
fn deref(&self) -> &Self::Target {
&self.window
}
}

View file

@ -0,0 +1,19 @@
//! Tools for configuring workspaces.
use serde::{Deserialize, Serialize};
/// How workspaces should be ordered in the UI.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub enum WorkspaceDisplayOrder {
/// Workspaces are not sorted and can be manually dragged.
Manual,
/// Workspaces are sorted alphabetically and cannot be manually dragged.
Sorted,
}
/// Sets how workspaces should be ordered in the UI.
///
/// The default is `WorkspaceDisplayOrder::Manual`.
pub fn set_workspace_display_order(order: WorkspaceDisplayOrder) {
get!().set_workspace_display_order(order);
}

View file

@ -0,0 +1,40 @@
//! Tools for configuring Xwayland.
use serde::{Deserialize, Serialize};
/// Sets whether Xwayland is enabled
///
/// The default is `true`.
pub fn set_x_wayland_enabled(enabled: bool) {
get!().set_x_wayland_enabled(enabled)
}
/// The scaling mode of X windows.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash, Default)]
pub struct XScalingMode(pub u32);
impl XScalingMode {
/// The default mode.
///
/// Currently this means that windows are rendered at the lowest scale and then
/// upscaled if necessary.
pub const DEFAULT: Self = Self(0);
/// Windows are rendered at the highest integer scale and then downscaled.
///
/// This has significant performance implications unless the window is running on the
/// output with the highest scale and that scale is an integer scale.
///
/// For example, on a 3840x2160 output with a 1.5 scale, a fullscreen window will be
/// rendered at 3840x2160 * 2 / 1.5 = 5120x2880 pixels and then downscaled to
/// 3840x2160. This overhead gets worse the lower the scale of the output is.
///
/// Additionally, this mode requires the X window to scale its contents itself. In the
/// example above, you might achieve this by setting the environment variable
/// `GDK_SCALE=2`.
pub const DOWNSCALED: Self = Self(1);
}
/// Sets the scaling mode for X windows.
pub fn set_x_scaling_mode(mode: XScalingMode) {
get!().set_x_scaling_mode(mode)
}