use component::bar_component::BarComponent;
use image::{DynamicImage, GenericImage};
use xcb::{self, randr, Rectangle};
use component::{img, Component};
use util::geometry::Geometry;
use std::sync::{Arc, Mutex};
use builder::BarBuilder;
use util::color::Color;
use event::Event;
use std::thread;
use error::*;
use render;
use chan;
use util;
#[derive(Clone)]
pub struct Bar {
pub(crate) conn: Arc<xcb::Connection>,
pub(crate) geometry: Geometry,
pub(crate) window: u32,
pub(crate) window_pict: u32,
pub(crate) gcontext: u32,
pub(crate) background: u32,
pub(crate) font: Option<String>,
pub(crate) components: Arc<Mutex<Vec<BarComponent>>>,
pub(crate) format32: u32,
pub(crate) format24: u32,
pub(crate) color: Color,
pub(crate) component_ids: [u32; 3],
pub(crate) text_yoffset: i16,
}
impl Bar {
pub(crate) fn new(builder: BarBuilder) -> ::std::result::Result<Self, BarError> {
let conn = xcb::Connection::connect(None).map_err(|_| BarErrorKind::ConnectionRefused)?;
let conn = Arc::new(conn.0);
let info = screen_info(&conn, builder.output)?;
let geometry = Geometry::new(info.x(), info.y(), info.width(), builder.height);
let name = builder.name.as_bytes();
let window = create_window(&conn, geometry, builder.background_color, name)?;
let (format24, format32) = image_formats(&conn);
let gcontext = {
let pix32 = conn.generate_id();
xcb::create_pixmap_checked(&conn, 32, pix32, window, 1, 1)
.request_check()
.expect("Unable to create GC dummy pixmap");
let gc = conn.generate_id();
xcb::create_gc_checked(&conn, gc, pix32, &[])
.request_check()
.expect("Unable to create GC");
xcb::free_pixmap_checked(&conn, pix32)
.request_check()
.expect("Unable to free GC dummy pixmap");
gc
};
let window_pict = conn.generate_id();
xcb::render::create_picture_checked(&conn, window_pict, window, format24, &[])
.request_check()
.expect("Unable to create window picture");
let (bg_col, bg_img) = (builder.background_color, builder.background_image);
let background =
create_background_picture(&conn, window, gcontext, format32, geometry, bg_col, bg_img);
Ok(Bar {
conn,
window,
geometry,
gcontext,
format24,
format32,
background,
window_pict,
font: builder.font,
component_ids: [0, 1, 2],
color: builder.foreground_color,
text_yoffset: builder.text_yoffset,
components: Arc::new(Mutex::new(Vec::new())),
})
}
pub fn start_event_loop(&self) {
info!("Started event loop");
loop {
if let Some(event) = self.conn.wait_for_event() {
let r = event.response_type();
if r == xcb::EXPOSE {
debug!("Received expose event, redrawing…");
let (w, h) = (self.geometry.width, self.geometry.height);
let res = self.composite_picture(self.background, 0, 0, w, h);
err!(res, "Unable to composite background");
let components = self.components.lock().unwrap();
for component in &*components {
let geometry = component.geometry;
if geometry.width > 0 && geometry.height > 0 {
let res = component.redraw(self);
err!(res, "Unable to redraw component");
}
}
} else if r == xcb::MOTION_NOTIFY {
let event: &xcb::MotionNotifyEvent = unsafe { xcb::cast_event(&event) };
debug!("Mouse moved to {}-{}", event.event_x(), event.event_y());
self.propagate_event(event.into());
} else if r == xcb::BUTTON_PRESS || r == xcb::BUTTON_RELEASE {
let event: &xcb::ButtonPressEvent = unsafe { xcb::cast_event(&event) };
debug!(
"Mouse button {} pressed at {}",
event.detail(),
event.event_x()
);
self.propagate_event(event.into());
}
}
}
}
fn propagate_event(&self, mut event: Event) {
let x = match event {
Event::ClickEvent(ref e) => e.position.x,
Event::MotionEvent(ref e) => e.position.x,
};
let components = self.components.lock().unwrap();
for component in &(*components) {
let geo = component.geometry;
if geo.x < x && geo.x as u16 + geo.width > x as u16 {
match event {
Event::ClickEvent(ref mut e) => e.position.x -= geo.x + 1,
Event::MotionEvent(ref mut e) => e.position.x -= geo.x + 1,
}
if let Some(ref interrupt) = component.interrupt {
interrupt.send(event);
debug!("Event propagated to component {}", component.id);
}
break;
}
}
}
#[allow(unused_mut)]
pub fn add<T: 'static + Component + Send>(&mut self, mut component: T) {
let id = component.alignment().id(&mut self.component_ids);
debug!("Adding component {}", id);
let bar_component = BarComponent::new(id, &self.conn);
{
let mut components = self.components.lock().unwrap();
(*components).push(bar_component);
}
let bar = self.clone();
thread::spawn(move || {
let redraw_timer = component.redraw_timer();
loop {
if component.update() {
let res = render::render(&bar, &mut component, id);
err!(res, "Component {}", id);
}
let (tx, rx) = chan::async();
{
let mut components = bar.components.lock().unwrap();
let comp_index = components.binary_search_by_key(&id, |c| c.id).unwrap_or(0);
components[comp_index].interrupt = Some(tx.clone());
}
loop {
chan_select! {
rx.recv() -> event => {
if let Some(event) = event {
debug!("Component {} received event.", id);
if component.event(event) {
debug!("Component {} requested redraw after event.", id);
break;
}
}
},
redraw_timer.recv() -> ping => {
if ping.is_some() {
debug!("Component {} requested redraw without event.", id);
break;
} else {
debug!("Component {} disconnected.", id);
return;
}
},
}
}
}
});
}
pub(crate) fn composite_picture(
&self,
pic: u32,
srcx: i16,
tarx: i16,
w: u16,
h: u16,
) -> Result<()> {
let win = self.window_pict;
let op = xcb::render::PICT_OP_OVER as u8;
xcb::render::composite_checked(&self.conn, op, pic, 0, win, srcx, 0, 0, 0, tarx, 0, w, h)
.request_check()
.map_err(|e| ErrorKind::XError(format!("Unable to composite picture: {}", e)))?;
Ok(())
}
}
fn image_formats(conn: &Arc<xcb::Connection>) -> (u32, u32) {
let formats = xcb::render::query_pict_formats(conn)
.get_reply()
.expect("Unable to query picture formats")
.formats();
let mut format24 = None;
let mut format32 = None;
for fmt in formats {
let direct = fmt.direct();
if fmt.depth() == 32 && direct.alpha_shift() == 24 && direct.red_shift() == 16
&& direct.green_shift() == 8 && direct.blue_shift() == 0
{
format32 = Some(fmt);
}
if fmt.depth() == 24 && direct.red_shift() == 16 && direct.green_shift() == 8
&& direct.blue_shift() == 0
{
format24 = Some(fmt);
}
if format32.is_some() && format24.is_some() {
break;
}
}
match (format24, format32) {
(Some(f_24), Some(f_32)) => (f_24.id(), f_32.id()),
_ => panic!("Unable to find 32 or 24 depth picture formats"),
}
}
fn screen_info(
conn: &Arc<xcb::Connection>,
query_output_name: Option<String>,
) -> ::std::result::Result<xcb::Reply<xcb::ffi::randr::xcb_randr_get_crtc_info_reply_t>, BarError> {
let root = util::screen(conn).expect("Root screen not found").root();
if query_output_name.is_none() {
return primary_screen_info(conn, root);
}
let query_output_name = query_output_name.unwrap();
let res_cookie = randr::get_screen_resources(conn, root);
let res_reply = res_cookie
.get_reply()
.expect("Unable to get screen resources");
let crtcs = res_reply.crtcs();
for crtc in crtcs {
let crtc_info_cookie = randr::get_crtc_info(conn, *crtc, 0);
let crtc_info_reply = crtc_info_cookie.get_reply();
if let Ok(reply) = crtc_info_reply {
if reply.width() == 0 || reply.num_outputs() == 0 {
continue;
}
let output = reply.outputs()[0];
let output_info_cookie = randr::get_output_info(conn, output, 0);
let output_info_reply = output_info_cookie.get_reply();
let mut output_name = String::new();
if let Ok(output_info_reply) = output_info_reply {
output_name = String::from_utf8_lossy(output_info_reply.name()).into();
}
if output_name == query_output_name {
return Ok(reply);
}
}
}
Err(BarErrorKind::OutputNotFound.into())
}
fn primary_screen_info(
conn: &Arc<xcb::Connection>,
root: u32,
) -> ::std::result::Result<xcb::Reply<xcb::ffi::randr::xcb_randr_get_crtc_info_reply_t>, BarError> {
let output_cookie = randr::get_output_primary(conn, root);
let output_reply = output_cookie
.get_reply()
.expect("Unable to find primary output");
let output = output_reply.output();
let output_info_cookie = randr::get_output_info(conn, output, 0);
let output_info_reply = output_info_cookie
.get_reply()
.map_err(|_| BarErrorKind::NoPrimaryOutput)?;
let crtc = output_info_reply.crtc();
let crtc_info_cookie = randr::get_crtc_info(conn, crtc, 0);
Ok(crtc_info_cookie
.get_reply()
.expect("Unable to get primary output crtc information"))
}
fn create_window(
conn: &Arc<xcb::Connection>,
geometry: Geometry,
background_color: Color,
window_title: &[u8],
) -> ::std::result::Result<u32, BarError> {
let screen = util::screen(conn).expect("Root screen not found");
let window = conn.generate_id();
xcb::create_window(
conn,
xcb::WINDOW_CLASS_COPY_FROM_PARENT as u8,
window,
screen.root(),
geometry.x,
geometry.y,
geometry.width,
geometry.height,
0,
xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
screen.root_visual(),
&[
(xcb::CW_BACK_PIXEL, background_color.into()),
(
xcb::CW_EVENT_MASK,
xcb::EVENT_MASK_EXPOSURE | xcb::EVENT_MASK_POINTER_MOTION
| xcb::EVENT_MASK_BUTTON_PRESS | xcb::EVENT_MASK_BUTTON_RELEASE,
),
(xcb::CW_OVERRIDE_REDIRECT, 0),
],
);
let start_x = geometry.x as u32;
let end_x = start_x + u32::from(geometry.width) - 1;
let height = u32::from(geometry.height);
let struts = [0, 0, height, 0, 0, 0, 0, 0, start_x, end_x, 0, 0];
set_prop!(conn, window, "_NET_WM_STRUT", &struts[0..4]);
set_prop!(conn, window, "_NET_WM_STRUT_PARTIAL", &struts);
set_prop!(conn, window, "_NET_WM_WINDOW_TYPE", @atom "_NET_WM_WINDOW_TYPE_DOCK");
set_prop!(conn, window, "_NET_WM_STATE", @atom "_NET_WM_STATE_STICKY");
set_prop!(conn, window, "_NET_WM_DESKTOP", &[-1]);
set_prop!(conn, window, "_NET_WM_NAME", window_title, "UTF8_STRING", 8);
set_prop!(conn, window, "WM_NAME", window_title, "STRING", 8);
xcb::map_window(conn, window);
info!("Created bar window");
Ok(window)
}
fn create_background_picture(
conn: &Arc<xcb::Connection>,
window: u32,
gcontext: u32,
format32: u32,
geometry: Geometry,
bg_color: Color,
background_image: Option<DynamicImage>,
) -> u32 {
let (w, h) = (geometry.width, geometry.height);
let pix = conn.generate_id();
xcb::create_pixmap_checked(conn, 32, pix, window, w, h)
.request_check()
.expect("Unable to create pixmap for bg image");
let col_gc = conn.generate_id();
let col = [(xcb::ffi::xproto::XCB_GC_FOREGROUND, bg_color.into())];
xcb::create_gc_checked(conn, col_gc, pix, &col)
.request_check()
.expect("Unable to create background color GC");
xcb::poly_fill_rectangle_checked(conn, pix, col_gc, &[Rectangle::new(0, 0, w, h)])
.request_check()
.expect("Unable to fill background pixmap with GC color");
xcb::free_gc(conn, col_gc);
if let Some(background_image) = background_image {
let w = background_image.width() as u16;
let h = background_image.height() as u16;
let data = img::convert_image(&background_image);
xcb::put_image_checked(conn, 2u8, pix, gcontext, w, h, 0, 0, 0, 32, &data)
.request_check()
.expect("Unable to copy image to bg pixmap");
}
let bg = conn.generate_id();
xcb::render::create_picture_checked(conn, bg, pix, format32, &[])
.request_check()
.expect("Unable to create bg picture");
xcb::free_pixmap_checked(conn, pix)
.request_check()
.expect("Unable to free temporary bg pixmap");
bg
}