refactor: split cargo workspace
This commit is contained in:
parent
5db14936e7
commit
1c21bd1259
695 changed files with 32023 additions and 44964 deletions
15
crates/jay-config/src/_private.rs
Normal file
15
crates/jay-config/src/_private.rs
Normal 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,
|
||||
};
|
||||
}
|
||||
2371
crates/jay-config/src/_private/client.rs
Normal file
2371
crates/jay-config/src/_private/client.rs
Normal file
File diff suppressed because it is too large
Load diff
61
crates/jay-config/src/_private/logging.rs
Normal file
61
crates/jay-config/src/_private/logging.rs
Normal 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
|
||||
}
|
||||
}
|
||||
155
crates/jay-config/src/client.rs
Normal file
155
crates/jay-config/src/client.rs
Normal 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
|
||||
}
|
||||
}
|
||||
11
crates/jay-config/src/embedded.rs
Normal file
11
crates/jay-config/src/embedded.rs
Normal 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);
|
||||
}
|
||||
99
crates/jay-config/src/exec.rs
Normal file
99
crates/jay-config/src/exec.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
865
crates/jay-config/src/input.rs
Normal file
865
crates/jay-config/src/input.rs
Normal 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);
|
||||
}
|
||||
14
crates/jay-config/src/input/acceleration.rs
Normal file
14
crates/jay-config/src/input/acceleration.rs
Normal 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);
|
||||
17
crates/jay-config/src/input/capability.rs
Normal file
17
crates/jay-config/src/input/capability.rs
Normal 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);
|
||||
18
crates/jay-config/src/input/clickmethod.rs
Normal file
18
crates/jay-config/src/input/clickmethod.rs
Normal 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
194
crates/jay-config/src/io.rs
Normal 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(()))
|
||||
}
|
||||
}
|
||||
156
crates/jay-config/src/keyboard/mod.rs
Normal file
156
crates/jay-config/src/keyboard/mod.rs
Normal 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)
|
||||
}
|
||||
60
crates/jay-config/src/keyboard/mods.rs
Normal file
60
crates/jay-config/src/keyboard/mods.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
2636
crates/jay-config/src/keyboard/syms.rs
Normal file
2636
crates/jay-config/src/keyboard/syms.rs
Normal file
File diff suppressed because it is too large
Load diff
487
crates/jay-config/src/lib.rs
Normal file
487
crates/jay-config/src/lib.rs
Normal 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);
|
||||
}
|
||||
34
crates/jay-config/src/logging.rs
Normal file
34
crates/jay-config/src/logging.rs
Normal 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);
|
||||
}
|
||||
113
crates/jay-config/src/macros.rs
Normal file
113
crates/jay-config/src/macros.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1340
crates/jay-config/src/protocol.rs
Normal file
1340
crates/jay-config/src/protocol.rs
Normal file
File diff suppressed because it is too large
Load diff
259
crates/jay-config/src/status.rs
Normal file
259
crates/jay-config/src/status.rs
Normal 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("&"),
|
||||
'<' => dst.push_str("<"),
|
||||
'>' => dst.push_str(">"),
|
||||
'\'' => dst.push_str("'"),
|
||||
'"' => dst.push_str("""),
|
||||
_ => dst.push(c),
|
||||
}
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
69
crates/jay-config/src/tasks.rs
Normal file
69
crates/jay-config/src/tasks.rs
Normal 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
|
||||
}
|
||||
}
|
||||
438
crates/jay-config/src/theme.rs
Normal file
438
crates/jay-config/src/theme.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
92
crates/jay-config/src/timer.rs
Normal file
92
crates/jay-config/src/timer.rs
Normal 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 _)
|
||||
}
|
||||
}
|
||||
832
crates/jay-config/src/video.rs
Normal file
832
crates/jay-config/src/video.rs
Normal 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);
|
||||
}
|
||||
422
crates/jay-config/src/window.rs
Normal file
422
crates/jay-config/src/window.rs
Normal 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
|
||||
}
|
||||
}
|
||||
19
crates/jay-config/src/workspace.rs
Normal file
19
crates/jay-config/src/workspace.rs
Normal 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);
|
||||
}
|
||||
40
crates/jay-config/src/xwayland.rs
Normal file
40
crates/jay-config/src/xwayland.rs
Normal 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)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue