1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
//! # Penrose: a library for building your very own tiling window manager
//!
//! Penrose is inspired by similar projects such as [dwm][1], [xmonad][2] and [qtile][3] which
//! allow you to configure your window manager in code and compile it for your system. It is most
//! similar to `xmonad` in that it is more of a library for building a window manager (with low
//! level details taken care of for you) rather than a minimal window manager that you edit and
//! patch directly (such as `dwm`). Penrose strives to be as simple as possible in its
//! implementation in order to make the guts of the window manager easier to understand. Given the
//! nature of what this involves, this is not always possible but effort has been made to keep the
//! source readable and with relatively free of magic.
//!
//!
//! ## Using Penrose
//!
//! Penrose itself is not a binary application that you can build, install and run. You need to
//! write your own **main.rs** as a rust binary crate that uses Penrose as a dependency to set up,
//! configure and run your very own window manager exactly how you want it. In short, you *will*
//! need to write some code and you *will* need to know Rust to some degree.
//!
//! For learning rust itself, there are some fantastic official [guides][4] available on
//! rust-lang.org and if you are sticking to using the out of the box functionality provided
//! by the penrose crate, working through [The Rust Book][5] before diving into penrose should be more
//! than enough to get you started.
//!
//! On GitHub you can find up to date [examples][6] of how to set up and configure a window manager
//! using penrose, ranging from bare bones minimal to custom extensions and hooks.
//!
//! > **NOTE**: in order to use the xcb implementation of penrose, you will need to install the C
//! > libraries that are dependencies (namely xcb, Cairo and Pango).
//!
//!
//! ## Digging into the API
//!
//! The suggested reading order for getting to grips with the penrose API is to first look at the
//! [pure][7] data structures that represent the logical state of your window manager before digging
//! in to the [core][8] module which contains the majority of the functionality you are likely to
//! want to work with. If you are interested in the lower level X11 interactions (or need to make
//! requests to the X server directly) you should check out the [x][9] module and its associated
//! traits. To add functionality and flexability to your window manager, there are the [builtin][10]
//! and [extensions][11] modules which offer capabilities built on top of the rest of penrose.
//!
//! [1]: https://dwm.suckless.org/
//! [2]: https://xmonad.org/
//! [3]: http://www.qtile.org/
//! [4]: https://www.rust-lang.org/learn
//! [5]: https://doc.rust-lang.org/book/
//! [6]: https://github.com/sminez/penrose/tree/develop/examples
//! [7]: crate::pure
//! [8]: crate::core
//! [9]: crate::x
//! [10]: crate::builtin
//! [11]: crate::extensions
#![warn(
clippy::complexity,
clippy::correctness,
clippy::style,
future_incompatible,
missing_debug_implementations,
missing_docs,
rust_2018_idioms,
rustdoc::all,
clippy::undocumented_unsafe_blocks
)]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/sminez/penrose/develop/icon.svg",
issue_tracker_base_url = "https://github.com/sminez/penrose/issues/"
)]
#[cfg(feature = "x11rb")]
use ::x11rb::{
errors::{ConnectError, ConnectionError, ReplyError, ReplyOrIdError},
x11_utils::X11Error,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::any::TypeId;
pub mod builtin;
pub mod core;
pub mod extensions;
mod macros;
pub mod pure;
pub mod util;
pub mod x;
#[cfg(feature = "x11rb")]
pub mod x11rb;
#[doc(inline)]
pub use crate::core::Xid;
/// Error variants from the core penrose library.
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// An operation requiring the client to be on a screen was requested on a client window that
/// is not currently visible
#[error("Client {0} is not currently visible")]
ClientIsNotVisible(Xid),
/// A custom error message from user code or extensions
#[error("{0}")]
Custom(String),
/// There were not enough workspaces to cover the number of connected screens
#[error("Only {n_ws} workspaces were provided but at least {n_screens} are required")]
InsufficientWorkspaces {
/// Number of provided workspaces
n_ws: usize,
/// Number of connected screens
n_screens: usize,
},
/// Data received as part of a client message had an invalid format
#[error("invalid client message data: format={format}")]
InvalidClientMessage {
/// The format received
format: u8,
},
/// Attempt to create a `Color` from an invalid hex string
#[error("Invalid Hex color code: '{hex_code}'")]
InvalidHexColor {
/// The string that was used
hex_code: String,
},
/// A window hints message was received but unable to be parsed
#[error("Invalid window hints message: {reason}")]
InvalidHints {
/// Why parsing failed
reason: String,
},
/// IO error
#[error(transparent)]
Io(#[from] std::io::Error),
/// Invalid UTF8 encoded string
#[error(transparent)]
InvalidUtf8(#[from] std::string::FromUtf8Error),
/// Data received from the X server when requesting a window property was invalid
#[error("{ty} property '{prop}' for {id} contained invalid data")]
InvalidPropertyData {
/// The window that was queried
id: Xid,
/// The type of property that was queried
ty: String,
/// The name of the property that was queried
prop: String,
},
/// Duplicate tags were provided for one or more workspaces
#[error("The following tags have been used multiple times for different workspaces: {tags:?}")]
NonUniqueTags {
/// The set of non-unique tags
tags: Vec<String>,
},
/// Penrose is running without any screens to connect to
#[error("There are no screens available")]
NoScreens,
/// ParseIntError
#[error(transparent)]
ParseInt(#[from] std::num::ParseIntError),
/// There was a problem initialising randr
#[error("Error initialising randr: {0}")]
Randr(String),
/// An operation was requested on a client window that is unknown
#[error("Client {0} is not in found")]
UnknownClient(Xid),
/// A keybinding has been specified for an unknown key name for this machine.
#[error("'{name}' is not a known key name")]
UnknownKeyName {
/// The name of the unknown key
name: String,
},
/// An unknown character has been used to specify a modifier key
#[error("'{name}' is not a known modifier key")]
UnknownModifier {
/// The unrecognised modifier name
name: String,
},
/// An unknown mouse button was pressed
#[error("{button} is not a supported mouse button")]
UnknownMouseButton {
/// The button ID that was pressed
button: u8,
},
/// An attempt was made to fetch a state extension for a type that has not been stored
#[error("{type_id:?} was requested as a state extension but not found")]
UnknownStateExtension {
/// The type ID of the type that was requested
type_id: TypeId,
},
// TODO: These backend specific errors should be abstracted out to a
// set of common error variants that they can be mapped to without
// needing to extend the enum conditionally when flags are enabled
/// An error that occurred while connecting to an X11 server
#[cfg(feature = "x11rb")]
#[error(transparent)]
X11rbConnect(#[from] ConnectError),
/// An error that occurred on an already established X11 connection
#[cfg(feature = "x11rb")]
#[error(transparent)]
X11rbConnection(#[from] ConnectionError),
/// An error that occurred with some request.
#[cfg(feature = "x11rb")]
#[error(transparent)]
X11rbReplyError(#[from] ReplyError),
/// An error caused by some request or by the exhaustion of IDs.
#[cfg(feature = "x11rb")]
#[error(transparent)]
X11rbReplyOrIdError(#[from] ReplyOrIdError),
/// Representation of an X11 error packet that was sent by the server.
#[cfg(feature = "x11rb")]
#[error("X11 error: {0:?}")]
X11rbX11Error(X11Error),
}
/// A Result where the error type is a penrose [Error]
pub type Result<T> = std::result::Result<T, Error>;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
/// A simple RGBA based color
pub struct Color {
rgba_hex: u32,
}
impl Color {
/// Create a new Color from a hex encoded u32: 0xRRGGBB or 0xRRGGBBAA
pub fn new_from_hex(rgba_hex: u32) -> Self {
Self { rgba_hex }
}
/// The RGB information of this color as 0.0-1.0 range floats representing
/// proportions of 255 for each of R, G, B
pub fn rgb(&self) -> (f64, f64, f64) {
let (r, g, b, _) = self.rgba();
(r, g, b)
}
/// The RGBA information of this color as 0.0-1.0 range floats representing
/// proportions of 255 for each of R, G, B, A
pub fn rgba(&self) -> (f64, f64, f64, f64) {
let floats: Vec<f64> = self
.rgba_hex
.to_be_bytes()
.iter()
.map(|n| *n as f64 / 255.0)
.collect();
(floats[0], floats[1], floats[2], floats[3])
}
/// Render this color as a #RRGGBB hew color string
pub fn as_rgb_hex_string(&self) -> String {
format!("#{:x}", self.rgb_u32())
}
/// 0xRRGGBB representation of this Color (no alpha information)
pub fn rgb_u32(&self) -> u32 {
self.rgba_hex >> 8
}
/// 0xRRGGBBAA representation of this Color
pub fn rgba_u32(&self) -> u32 {
self.rgba_hex
}
/// 0xAARRGGBB representation of this Color
pub fn argb_u32(&self) -> u32 {
((self.rgba_hex & 0x000000FF) << 24) + (self.rgba_hex >> 8)
}
}
impl From<u32> for Color {
fn from(hex: u32) -> Self {
Self::new_from_hex(hex)
}
}
macro_rules! _f2u { { $f:expr, $s:expr } => { (($f * 255.0) as u32) << $s } }
impl From<(f64, f64, f64)> for Color {
fn from(rgb: (f64, f64, f64)) -> Self {
let (r, g, b) = rgb;
let rgba_hex = _f2u!(r, 24) + _f2u!(g, 16) + _f2u!(b, 8) + _f2u!(1.0, 0);
Self { rgba_hex }
}
}
impl From<(f64, f64, f64, f64)> for Color {
fn from(rgba: (f64, f64, f64, f64)) -> Self {
let (r, g, b, a) = rgba;
let rgba_hex = _f2u!(r, 24) + _f2u!(g, 16) + _f2u!(b, 8) + _f2u!(a, 0);
Self { rgba_hex }
}
}
impl TryFrom<String> for Color {
type Error = Error;
fn try_from(s: String) -> Result<Self> {
(&s[..]).try_into()
}
}
impl TryFrom<&str> for Color {
type Error = Error;
fn try_from(s: &str) -> Result<Self> {
let hex = u32::from_str_radix(s.strip_prefix('#').unwrap_or(s), 16)?;
if s.len() == 7 {
Ok(Self::new_from_hex((hex << 8) + 0xFF))
} else if s.len() == 9 {
Ok(Self::new_from_hex(hex))
} else {
Err(Error::InvalidHexColor { hex_code: s.into() })
}
}
}