#![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;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Client {0} is not currently visible")]
ClientIsNotVisible(Xid),
#[error("{0}")]
Custom(String),
#[error("Only {n_ws} workspaces were provided but at least {n_screens} are required")]
InsufficientWorkspaces {
n_ws: usize,
n_screens: usize,
},
#[error("invalid client message data: format={format}")]
InvalidClientMessage {
format: u8,
},
#[error("Invalid Hex color code: '{hex_code}'")]
InvalidHexColor {
hex_code: String,
},
#[error("Invalid window hints message: {reason}")]
InvalidHints {
reason: String,
},
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
InvalidUtf8(#[from] std::string::FromUtf8Error),
#[error("{ty} property '{prop}' for {id} contained invalid data")]
InvalidPropertyData {
id: Xid,
ty: String,
prop: String,
},
#[error("The following tags have been used multiple times for different workspaces: {tags:?}")]
NonUniqueTags {
tags: Vec<String>,
},
#[error("There are no screens available")]
NoScreens,
#[error(transparent)]
ParseInt(#[from] std::num::ParseIntError),
#[error("Error initialising randr: {0}")]
Randr(String),
#[error("No client with id={0}")]
UnknownClient(Xid),
#[error("No workspace with tag={0}")]
UnknownWorkspace(String),
#[error("'{name}' is not a known key name")]
UnknownKeyName {
name: String,
},
#[error("'{name}' is not a known modifier key")]
UnknownModifier {
name: String,
},
#[error("{button} is not a supported mouse button")]
UnknownMouseButton {
button: u8,
},
#[error("{type_id:?} was requested as a state extension but not found")]
UnknownStateExtension {
type_id: TypeId,
},
#[cfg(feature = "x11rb")]
#[error(transparent)]
X11rbConnect(#[from] ConnectError),
#[cfg(feature = "x11rb")]
#[error(transparent)]
X11rbConnection(#[from] ConnectionError),
#[cfg(feature = "x11rb")]
#[error(transparent)]
X11rbReplyError(#[from] ReplyError),
#[cfg(feature = "x11rb")]
#[error(transparent)]
X11rbReplyOrIdError(#[from] ReplyOrIdError),
#[cfg(feature = "x11rb")]
#[error("X11 error: {0:?}")]
X11rbX11Error(X11Error),
}
pub type Result<T> = std::result::Result<T, Error>;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Color {
rgba_hex: u32,
}
impl Color {
pub fn new_from_hex(rgba_hex: u32) -> Self {
Self { rgba_hex }
}
pub fn rgb(&self) -> (f64, f64, f64) {
let (r, g, b, _) = self.rgba();
(r, g, b)
}
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])
}
pub fn as_rgb_hex_string(&self) -> String {
format!("#{:0>6X}", self.rgb_u32())
}
pub fn rgb_u32(&self) -> u32 {
self.rgba_hex >> 8
}
pub fn rgba_u32(&self) -> u32 {
self.rgba_hex
}
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() })
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use simple_test_case::test_case;
#[test_case(0xAABBCCDD, "#AABBCC"; "r g and b are correct")]
#[test_case(0x001122FF, "#001122"; "leading 0s are preserved")]
#[test_case(0x00000000, "#000000"; "black works")]
#[test]
fn as_rgb_hex_string_is_correct(rgba_hex: u32, expected: &str) {
let c: Color = rgba_hex.into();
assert_eq!(&c.as_rgb_hex_string(), expected);
}
}