use std::f64;
use std::rc::Rc;
use anyhow::{anyhow, Context, Result};
use ordered_float::OrderedFloat;
use xcb_util::ewmh;
use crate::text::{ComputedText, Text};
fn get_root_visual_type(conn: &xcb::Connection, screen: &xcb::Screen<'_>) -> xcb::Visualtype {
for root in conn.get_setup().roots() {
for allowed_depth in root.allowed_depths() {
for visual in allowed_depth.visuals() {
if visual.visual_id() == screen.root_visual() {
return visual;
}
}
}
}
panic!("No visual type found");
}
fn cairo_surface_for_xcb_window(
conn: &xcb::Connection,
screen: &xcb::Screen<'_>,
id: u32,
width: i32,
height: i32,
) -> Result<cairo::XCBSurface> {
let cairo_conn = unsafe {
cairo::XCBConnection::from_raw_none(conn.get_raw_conn() as *mut cairo_sys::xcb_connection_t)
};
let visual = unsafe {
cairo::XCBVisualType::from_raw_none(
&mut get_root_visual_type(conn, screen).base as *mut xcb::ffi::xcb_visualtype_t
as *mut cairo_sys::xcb_visualtype_t,
)
};
let drawable = cairo::XCBDrawable(id);
let surface = cairo::XCBSurface::create(&cairo_conn, &drawable, &visual, width, height)
.map_err(|status| anyhow!("XCBSurface::create: {}", status))?;
Ok(surface)
}
fn create_surface(
conn: &xcb::Connection,
screen_idx: usize,
window_id: u32,
height: u16,
) -> Result<(u16, cairo::XCBSurface)> {
let screen = conn
.get_setup()
.roots()
.nth(screen_idx)
.ok_or_else(|| anyhow!("Invalid screen"))?;
let values = [
(xcb::CW_BACK_PIXEL, screen.black_pixel()),
(xcb::CW_EVENT_MASK, xcb::EVENT_MASK_EXPOSURE),
];
let width = screen.width_in_pixels();
xcb::create_window(
&conn,
xcb::COPY_FROM_PARENT as u8,
window_id,
screen.root(),
0,
0,
width,
height,
0,
xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
screen.root_visual(),
&values,
);
let surface = cairo_surface_for_xcb_window(
&conn,
&screen,
window_id,
i32::from(width),
i32::from(height),
)?;
Ok((width, surface))
}
#[derive(Clone, Debug)]
pub enum Position {
Top,
Bottom,
}
pub struct Bar {
position: Position,
conn: Rc<ewmh::Connection>,
screen_idx: usize,
window_id: u32,
surface: cairo::XCBSurface,
width: u16,
height: u16,
contents: Vec<Vec<ComputedText>>,
}
impl Bar {
pub fn new(position: Position) -> Result<Bar> {
let (conn, screen_idx) =
xcb::Connection::connect(None).context("Failed to connect to X server")?;
let screen_idx = screen_idx as usize;
let window_id = conn.generate_id();
let height = 1;
let (width, surface) = create_surface(&conn, screen_idx, window_id, height)?;
let ewmh_conn = ewmh::Connection::connect(conn)
.map_err(|(e, _)| e)
.context("Failed to wrap xcb::Connection in ewmh::Connection")?;
let bar = Bar {
conn: Rc::new(ewmh_conn),
window_id,
screen_idx,
surface,
width,
height,
position,
contents: Vec::new(),
};
bar.set_ewmh_properties();
bar.flush();
Ok(bar)
}
fn flush(&self) {
self.conn.flush();
}
fn map_window(&self) {
xcb::map_window(&self.conn, self.window_id);
}
fn set_ewmh_properties(&self) {
ewmh::set_wm_window_type(
&self.conn,
self.window_id,
&[self.conn.WM_WINDOW_TYPE_DOCK()],
);
let mut strut_partial = ewmh::StrutPartial {
left: 0,
right: 0,
top: 0,
bottom: 0,
left_start_y: 0,
left_end_y: 0,
right_start_y: 0,
right_end_y: 0,
top_start_x: 0,
top_end_x: 0,
bottom_start_x: 0,
bottom_end_x: 0,
};
match self.position {
Position::Top => strut_partial.top = u32::from(self.height),
Position::Bottom => strut_partial.bottom = u32::from(self.height),
}
ewmh::set_wm_strut_partial(&self.conn, self.window_id, strut_partial);
}
fn screen(&self) -> Result<xcb::Screen<'_>> {
let screen = self
.conn
.get_setup()
.roots()
.nth(self.screen_idx)
.ok_or_else(|| anyhow!("Invalid screen"))?;
Ok(screen)
}
fn update_bar_height(&mut self, height: u16) -> Result<()> {
if self.height != height {
self.height = height;
let y = match self.position {
Position::Top => 0,
Position::Bottom => self.screen()?.height_in_pixels() - self.height,
};
let values = [
(xcb::CONFIG_WINDOW_Y as u16, u32::from(y)),
(xcb::CONFIG_WINDOW_HEIGHT as u16, u32::from(self.height)),
(xcb::CONFIG_WINDOW_STACK_MODE as u16, xcb::STACK_MODE_ABOVE),
];
xcb::configure_window(&self.conn, self.window_id, &values);
self.map_window();
self.surface
.set_size(i32::from(self.width), i32::from(self.height))
.unwrap();
self.set_ewmh_properties();
}
Ok(())
}
pub fn connection(&self) -> &Rc<ewmh::Connection> {
&self.conn
}
pub fn process_event(&mut self, event: xcb::GenericEvent) -> Result<()> {
let expose = event.response_type() & !0x80 == xcb::EXPOSE;
if expose {
println!("Redrawing entire bar - expose event.");
self.redraw_entire_bar()?;
}
Ok(())
}
pub fn add_content(&mut self, content: Vec<Text>) -> Result<usize> {
let idx = self.contents.len();
self.contents.push(Vec::new());
self.update_content(idx, content)?;
Ok(idx)
}
pub fn update_content(&mut self, idx: usize, content: Vec<Text>) -> Result<()> {
let old = &self.contents[idx];
if &content == old {
return Ok(());
}
let mut new = content
.into_iter()
.map(|text| text.compute(&self.surface))
.collect::<Result<Vec<_>>>()?;
let error_margin = f64::EPSILON;
let redraw_entire_bar = old.len() != new.len()
|| old
.iter()
.zip(&new)
.any(|(old, new)| ((old.width - new.width).abs() < error_margin) && !new.stretch);
for (new, old) in new.iter_mut().zip(old.iter()) {
new.x = old.x;
new.y = old.y;
new.height = old.height;
if new.stretch {
new.width = old.width;
}
}
self.contents[idx] = new;
if !redraw_entire_bar {
println!("Redrawing one");
self.redraw_content(idx)?;
} else {
println!("Redrawing entire bar - widget update");
self.redraw_entire_bar()?;
}
Ok(())
}
fn redraw_content(&mut self, idx: usize) -> Result<()> {
for text in &mut self.contents[idx] {
text.render(&self.surface)?;
}
self.flush();
Ok(())
}
pub fn redraw_entire_bar(&mut self) -> Result<()> {
self.recompute_dimensions()?;
for idx in 0..self.contents.len() {
self.redraw_content(idx)?;
}
Ok(())
}
fn recompute_dimensions(&mut self) -> Result<()> {
let height = self
.contents
.iter()
.flatten()
.map(|text| text.height)
.max_by_key(|height| OrderedFloat(*height))
.unwrap_or(0.0);
for text in self.contents.iter_mut().flatten() {
text.height = height;
}
self.update_bar_height(height as u16)?;
let used: f64 = self
.contents
.iter()
.flatten()
.filter(|text| !text.stretch)
.map(|text| text.width)
.sum();
let remaining = f64::from(self.width) - used;
let stretches_count = self
.contents
.iter()
.flatten()
.filter(|text| text.stretch)
.count();
let stretch_width = remaining / (stretches_count as f64);
let stretches = self
.contents
.iter_mut()
.flatten()
.filter(|text| text.stretch);
for text in stretches {
text.width = stretch_width;
}
let mut x = 0.0;
for text in self.contents.iter_mut().flatten() {
text.x = x;
x += text.width;
}
Ok(())
}
}