#![doc = include_str!("../README.md")]
use xcb::x::Atom;
use xcb::{Xid, XidNew, randr, x};
pub mod err;
pub mod img;
pub use err::Error;
pub type Result<T = (), E = Error> = core::result::Result<T, E>;
pub struct Display {
conn: xcb::Connection,
screen_num: i32,
}
impl Display {
pub fn open() -> Result<Self> {
let (conn, screen_num) = xcb::Connection::connect(None)?;
Self::from_xcb(conn, screen_num)
}
pub fn from_xcb(conn: xcb::Connection, screen_num: i32) -> Result<Self> {
usize::try_from(screen_num)
.map(|_| Self { conn, screen_num })
.map_err(|_| Error::BadScreenNumber(screen_num))
}
pub fn into_xcb(self) -> (xcb::Connection, i32) {
(self.conn, self.screen_num)
}
pub fn conn(&self) -> &xcb::Connection { &self.conn }
pub fn default_screen_num(&self) -> i32 { self.screen_num }
pub fn default_screen(&self) -> Result<&x::Screen, err::BadScreenNumber> {
self.conn
.get_setup()
.roots()
.nth(self.screen_num as usize)
.ok_or(err::BadScreenNumber(self.screen_num))
}
pub fn monitors(&self) -> Result<Vec<Monitor>> {
let cookie = self.conn.send_request(&randr::GetMonitors {
window: self.default_screen()?.root(),
get_active: true,
});
Ok(self
.conn
.wait_for_reply(cookie)?
.monitors()
.map(|mon| Monitor {
name: self.get_atom_name(mon.name()),
primary: mon.primary(),
x: mon.x(),
y: mon.y(),
width: mon.width(),
height: mon.height(),
width_in_millimeters: mon.width_in_millimeters(),
height_in_millimeters: mon.height_in_millimeters(),
})
.collect())
}
pub fn root_pixmap(&self) -> Result<RootPixmap<'_>> {
RootPixmap::new(self.conn(), self.default_screen()?)
}
fn get_atom_name(&self, atom: Atom) -> Option<String> {
let cookie = self.conn.send_request(&x::GetAtomName { atom });
let reply = self.conn.wait_for_reply(cookie).ok()?;
let name = reply.name();
(name.len() != 0).then(|| name.to_utf8().into_owned())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Monitor {
pub name: Option<String>,
pub primary: bool,
pub x: i16,
pub y: i16,
pub width: u16,
pub height: u16,
pub width_in_millimeters: u32,
pub height_in_millimeters: u32,
}
pub struct RootPixmap<'a> {
conn: &'a xcb::Connection,
screen: &'a x::Screen,
pixmap: x::Pixmap,
gc: x::Gcontext,
rgb_shifts: img::RgbShifts,
}
impl core::ops::Drop for RootPixmap<'_> {
fn drop(&mut self) {
self.conn.send_request(&x::FreeGc { gc: self.gc });
self.conn.send_request(&x::FreePixmap { pixmap: self.pixmap });
}
}
impl<'a> RootPixmap<'a> {
pub fn new(conn: &'a xcb::Connection, scr: &'a x::Screen) -> Result<Self> {
let rgb_shifts = Self::get_rgb_shifts(scr)?;
let pixmap = conn.generate_id::<x::Pixmap>();
conn.send_and_check_request(&x::CreatePixmap {
depth: scr.root_depth(),
pid: pixmap,
drawable: x::Drawable::Window(scr.root()),
width: scr.width_in_pixels(),
height: scr.height_in_pixels(),
})?;
let gc = conn.generate_id::<x::Gcontext>();
let cookie = conn.send_request_checked(&x::CreateGc {
cid: gc,
drawable: x::Drawable::Pixmap(pixmap),
value_list: &[],
});
conn.check_request(cookie).inspect_err(|_| {
conn.send_request(&x::FreePixmap { pixmap });
})?;
Ok(Self { conn, screen: scr, pixmap, gc, rgb_shifts })
}
fn get_rgb_shifts(scr: &'a x::Screen) -> Result<img::RgbShifts> {
fn get_shift(mask: u32) -> Option<u8> {
let shift = mask.trailing_zeros();
((mask >> shift) == 0xff).then_some(shift as u8)
}
let root_depth = scr.root_depth();
let root_visual = scr.root_visual();
scr.allowed_depths()
.filter(|depth| depth.depth() == root_depth)
.flat_map(|depth| depth.visuals())
.find(|vis| vis.visual_id() == root_visual)
.ok_or(Error::CouldNotFindRootVisual(root_visual))
.and_then(|vis| {
if vis.class() == x::VisualClass::TrueColor &&
(root_depth == 24 || root_depth == 32)
{
let shifts = get_shift(vis.red_mask())
.zip(get_shift(vis.green_mask()))
.zip(get_shift(vis.blue_mask()));
if let Some(((r, g), b)) = shifts {
return Ok(img::RgbShifts { r, g, b });
}
}
Err(Error::UnsupportedVisual(root_depth, vis.class()))
})
}
pub fn rgb_shifts(&self) -> img::RgbShifts { self.rgb_shifts }
pub fn put_image<'b>(
&self,
dst_x: i16,
dst_y: i16,
img: impl img::IntoXBuffer<'b>,
) -> Result {
let (width, height) = img.dimensions()?;
let buffer = img.into_x_buffer(self.rgb_shifts)?;
let buffer = buffer.as_ref();
if usize::from(width) * usize::from(height) * 4 == buffer.len() {
self.put_raw_impl(dst_x, dst_y, width, height, buffer)
} else {
Err(Error::BadBufferSize(buffer.len(), width, height))
}
}
#[inline]
pub fn put_raw(
&self,
dst_x: i16,
dst_y: i16,
width: u16,
height: u16,
data: &[u32],
) -> Result {
if usize::from(width) * usize::from(height) == data.len() {
let data = bytemuck::must_cast_slice(data);
self.put_raw_impl(dst_x, dst_y, width, height, data)
} else {
Err(Error::BadBufferSize(data.len() * 4, width, height))
}
}
fn put_raw_impl(
&self,
dst_x: i16,
dst_y: i16,
width: u16,
height: u16,
data: &[u8],
) -> Result {
self.conn
.send_and_check_request(&x::PutImage {
format: x::ImageFormat::ZPixmap,
drawable: x::Drawable::Pixmap(self.pixmap),
gc: self.gc,
width,
height,
dst_x,
dst_y,
left_pad: 0,
depth: self.screen.root_depth(),
data,
})
.map_err(Error::from)
}
pub fn set_background(&self) -> Result {
self.set_root_atoms();
self.conn.send_request(&x::KillClient {
resource: 0, });
self.conn.send_request(&x::SetCloseDownMode {
mode: x::CloseDown::RetainTemporary,
});
self.conn.send_and_check_request(&x::ChangeWindowAttributes {
window: self.screen.root(),
value_list: &[x::Cw::BackPixmap(self.pixmap)],
})?;
self.conn.send_request(&x::ClearArea {
exposures: false,
window: self.screen.root(),
x: 0,
y: 0,
width: self.screen.width_in_pixels(),
height: self.screen.height_in_pixels(),
});
Ok(())
}
fn set_root_atoms(&self) {
let mut killed = x::Pixmap::none();
for name in ["_XROOTPMAP_ID", "ESETROOT_PMAP_ID"] {
let mut intern_request =
x::InternAtom { only_if_exists: true, name: name.as_bytes() };
let cookie = self.conn.send_request(&intern_request);
let atom = self.conn.wait_for_reply(cookie).and_then(|reply| {
let atom = reply.atom();
if atom.is_none() {
intern_request.only_if_exists = false;
let cookie = self.conn.send_request(&intern_request);
self.conn.wait_for_reply(cookie).map(|reply| reply.atom())
} else {
self.clean_root_atom(atom, &mut killed);
Ok(atom)
}
});
let atom = match atom {
Err(_err) => {
return;
}
Ok(atom) if atom.is_none() => {
return;
}
Ok(atom) => atom,
};
self.conn.send_request(&x::ChangeProperty {
mode: x::PropMode::Replace,
window: self.screen.root(),
property: atom,
r#type: x::ATOM_PIXMAP,
data: &[self.pixmap.resource_id()],
});
}
}
fn clean_root_atom(&self, atom: Atom, prev_killed: &mut x::Pixmap) {
let cookie = self.conn.send_request(&x::GetProperty {
delete: false,
window: self.screen.root(),
property: atom,
r#type: x::ATOM_ANY,
long_offset: 0,
long_length: 1, });
let reply = match self.conn.wait_for_reply(cookie) {
Ok(reply) => reply,
Err(_err) => {
return;
}
};
if reply.r#type() == x::ATOM_PIXMAP &&
reply.format() == 32 &&
let &[resource] = reply.value::<u32>() &&
resource != 0 &&
resource != prev_killed.resource_id()
{
self.conn.send_request(&x::KillClient { resource });
*prev_killed = x::Pixmap::new(resource);
}
}
}