use std::{io::Write, mem::MaybeUninit, ptr};
use x11::xlib::{
XCloseDisplay, XDestroyImage, XGetImage, XGetPixel, XGetWindowAttributes, XGrabServer,
XOpenDisplay, XRootWindow, XUngrabServer, XWindowAttributes, ZPixmap, _XDisplay,
};
const MAGIC_BYTES: &[u8; 8] = b"farbfeld";
const ALPHA_BYTES: &[u8; 2] = &u16::MAX.to_be_bytes();
const ALL_PLANES: u64 = !0;
#[derive(thiserror::Error, Debug)]
pub enum TickError {
#[error("Can't open the selected X display")]
CantOpenDpy,
#[error("Can't get an image for the selected window")]
CantGetImage,
#[error("{0}")]
IOError(#[from] std::io::Error),
}
pub type Result<T> = std::result::Result<T, TickError>;
#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub enum DpyAddr {
Custom(String),
#[default]
Current,
}
impl DpyAddr {
fn ptr(&self) -> *const i8 {
match self {
DpyAddr::Custom(addr) => addr.as_ptr().cast(),
DpyAddr::Current => ptr::null(),
}
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub enum Window {
Custom(u64),
Root(i32),
}
impl Window {
fn id(&self, dpy: *mut _XDisplay) -> u64 {
match self {
Window::Custom(id) => id.to_owned(),
Window::Root(screen_num) => unsafe { XRootWindow(dpy, screen_num.to_owned()) },
}
}
}
pub type Point = (u32, u32);
pub type PointI = (i32, i32);
#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub enum Mode {
#[default]
Full,
Selection {
start: Option<PointI>,
end: Option<Point>,
},
}
impl Mode {
#[inline]
fn transform(&self, win_attr: XWindowAttributes) -> (PointI, Point) {
match self {
Mode::Full => ((0, 0), (win_attr.width as u32, win_attr.height as u32)),
Mode::Selection { start, end } => (
start.unwrap_or((0, 0)),
end.unwrap_or((win_attr.width as u32, win_attr.height as u32)),
),
}
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct TickTick {
pub dpy_addr: DpyAddr,
pub win: Window,
pub mode: Mode,
}
impl Default for TickTick {
fn default() -> Self {
Self {
dpy_addr: DpyAddr::default(),
win: Window::Root(0),
mode: Mode::default(),
}
}
}
pub trait LqthConfig {
fn dpy_addr(&self) -> &DpyAddr;
fn win(&self) -> Window;
fn mode(&self) -> Mode;
#[inline(always)]
fn tick<W: Write>(&self, out_buf: &mut W) -> Result<()> {
crate::tick(out_buf, self)
}
}
impl LqthConfig for TickTick {
#[inline(always)]
fn dpy_addr(&self) -> &DpyAddr {
&self.dpy_addr
}
#[inline(always)]
fn win(&self) -> Window {
self.win.clone()
}
#[inline(always)]
fn mode(&self) -> Mode {
self.mode.clone()
}
}
impl LqthConfig for () {
fn dpy_addr(&self) -> &DpyAddr {
&DpyAddr::Current
}
fn win(&self) -> Window {
Window::Root(0)
}
fn mode(&self) -> Mode {
Mode::default()
}
}
#[inline]
pub fn tick<W, C>(out_buf: &mut W, config: &C) -> Result<()>
where
W: Write,
C: LqthConfig + ?Sized,
{
let dpy = unsafe { XOpenDisplay(config.dpy_addr().ptr()) };
if dpy.is_null() {
return Err(TickError::CantOpenDpy);
}
let mut win_attr = MaybeUninit::uninit();
let win = config.win().id(dpy);
unsafe {
XGrabServer(dpy);
XGetWindowAttributes(dpy, win, win_attr.as_mut_ptr())
};
let win_attr = unsafe { win_attr.assume_init() };
let ((xs, ys), (xe, ye)) = config.mode().transform(win_attr);
let img_ptr = unsafe {
XGetImage(
dpy, win, xs, ys, xe, ye, ALL_PLANES, ZPixmap,
)
};
unsafe {
XUngrabServer(dpy);
XCloseDisplay(dpy);
}
if img_ptr.is_null() {
return Err(TickError::CantGetImage);
}
let img = unsafe { *img_ptr };
let sr: u8;
let sg: u8;
let fr: u16;
let fb: u16;
let fg: u16;
match img.bits_per_pixel {
16 => {
sr = 11;
sg = 5;
fr = 2047;
fb = 2047;
fg = 1023;
}
24 | 32 => {
sr = 16;
sg = 8;
fr = 257;
fg = 257;
fb = 257;
}
other => panic!("Unsupported bpp: {other}"),
}
out_buf.write_all(MAGIC_BYTES)?;
out_buf.write_all(&(img.width as u32).to_be_bytes())?;
out_buf.write_all(&(img.height as u32).to_be_bytes())?;
macro_rules! write_channel {
($out: ident; $cn: expr) => {
{
$out.write_all(&$cn.to_be_bytes())
}
};
($out: ident; $($cn: expr,)+) => {
$(write_channel!($out; $cn)?;)*
};
}
for h in 0..img.height {
for w in 0..img.width {
let p = unsafe { XGetPixel(img_ptr, w, h) };
write_channel! { out_buf;
((p & img.red_mask) >> sr) as u16 * fr,
((p & img.green_mask) >> sg) as u16 * fg,
(p & img.blue_mask) as u16 * fb,
};
out_buf.write_all(ALPHA_BYTES)?;
}
}
unsafe { XDestroyImage(img_ptr) };
Ok(())
}