Procházet zdrojové kódy

Merge branch 'unifyevent'

Thomas Dy před 7 roky
rodič
revize
b1c5221510
17 změnil soubory, kde provedl 1516 přidání a 98 odebrání
  1. 7 1
      Cargo.toml
  2. 53 44
      src/main.rs
  3. 2 26
      src/sensors/mod.rs
  4. 156 0
      src/ui/bspwm.rs
  5. 49 0
      src/ui/ext.rs
  6. 247 0
      src/ui/font.rs
  7. 90 0
      src/ui/mod.rs
  8. 116 0
      src/ui/music.rs
  9. 215 0
      src/ui/panel.rs
  10. 60 0
      src/ui/sensors.rs
  11. 24 0
      src/ui/spacer.rs
  12. 89 0
      src/ui/title.rs
  13. 170 0
      src/ui/tray.rs
  14. 74 0
      src/ui/util.rs
  15. 127 0
      src/ui/widget.rs
  16. 34 24
      src/ui/x11.rs
  17. 3 3
      src/wm.rs

+ 7 - 1
Cargo.toml

@@ -7,4 +7,10 @@ authors = ["Thomas Dy <thatsmydoing@gmail.com>"]
 time = "0.1"
 toml = "0.1"
 mpd = "0.0.12"
-xcb = "0.8.1"
+simple-signal = "1.1.0"
+freetype-rs = "0.11"
+silverknife-fontconfig-sys = "0.1"
+
+[dependencies.xcb]
+version = "0.8.1"
+features = [ "render", "thread" ]

+ 53 - 44
src/main.rs

@@ -1,58 +1,67 @@
 extern crate xcb;
+extern crate mpd;
+extern crate simple_signal;
+
+extern crate freetype;
+extern crate fontconfig_sys;
 
 mod sensors;
-mod comm;
+//mod comm;
 mod config;
-mod bspwm;
-mod bar;
-mod tray;
-mod store;
-mod music;
-mod wm;
-mod x11;
-
-use comm::{Channel, Message};
-use config::Config;
+//mod bspwm;
+//mod bar;
+//mod tray;
+//mod store;
+//mod music;
+//mod wm;
+//mod x11;
+mod ui;
+
+//use comm::{Channel, Message};
+//use config::Config;
 use std::env;
-use std::sync::Arc;
-use std::sync::mpsc;
-use std::thread;
+//use std::sync::Arc;
+//use std::sync::mpsc;
+use std::process;
+//use std::thread;
 
 fn main() {
     let config_path = env::args().nth(1).unwrap_or("./panel.toml".to_string());
     let cfg = config::load(&config_path);
+//
+//    let mut topbar = bar::Bar::new(true, &cfg);
+//    let _tray = tray::Tray::new();
+//
+//    let cfg = Arc::new(cfg);
+//    let (tx, rx) = mpsc::channel::<Message>();
+//    make_thread(&tx, &cfg, bspwm::bspwm);
+//    make_thread(&tx, &cfg, sensors::sensors);
+//    make_thread(&tx, &cfg, music::music);
+//    make_thread(&tx, &cfg, wm::wm);
+//
+//    let mut data = store::Store::new();
+//
+//    loop {
+//        let msg = rx.recv().unwrap();
+//        data.save(msg);
+//
+//        topbar.send(&format!("{}| {}%{{r}}{}{}",
+//            data.get("desktops"),
+//            data.get("title"),
+//            data.get("sensors"),
+//            data.get("spacer")
+//        ));
+//    }
 
-    let mut topbar = bar::Bar::new(true, &cfg);
-    let _tray = tray::Tray::new();
-
-    let cfg = Arc::new(cfg);
-    let (tx, rx) = mpsc::channel::<Message>();
-    make_thread(&tx, &cfg, bspwm::bspwm);
-    make_thread(&tx, &cfg, sensors::sensors);
-    make_thread(&tx, &cfg, music::music);
-    make_thread(&tx, &cfg, wm::wm);
-
-    let mut data = store::Store::new();
-
-    loop {
-        let msg = rx.recv().unwrap();
-        data.save(msg);
-
-        topbar.send(&format!("{}| {}%{{r}}{}{}",
-            data.get("desktops"),
-            data.get("title"),
-            data.get("sensors"),
-            data.get("spacer")
-        ));
-    }
+    process::exit(ui::ui_main(&cfg));
 }
 
-fn make_thread(tx: &Channel, cfg: &Arc<Config>, func: fn(&Channel, &Config) -> ()) {
-    let thread_tx = tx.clone();
-    let thread_cfg = cfg.clone();
-    thread::spawn(move || {
-        func(&thread_tx, &thread_cfg);
-    });
-}
+//fn make_thread(tx: &Channel, cfg: &Arc<Config>, func: fn(&Channel, &Config) -> ()) {
+//    let thread_tx = tx.clone();
+//    let thread_cfg = cfg.clone();
+//    thread::spawn(move || {
+//        func(&thread_tx, &thread_cfg);
+//    });
+//}
 
 

+ 2 - 26
src/sensors/mod.rs

@@ -5,11 +5,7 @@ mod netspeed;
 mod time;
 mod temperature;
 
-use comm;
-use comm::Channel;
 use config::Config;
-use std::thread;
-use std::time::Duration;
 use self::battery::BatterySensor;
 use self::tp_battery::TPBatterySensor;
 use self::disk::DiskSensor;
@@ -23,7 +19,7 @@ pub trait Sensor {
     fn process(&mut self);
 }
 
-pub fn sensors(tx: &Channel, config: &Config) {
+pub fn sensor_list(config: &Config) -> Vec<Box<Sensor>> {
     let zone = config.lookup("sensors.thermal_zone").unwrap();
     let zone = zone.as_str().unwrap();
 
@@ -49,25 +45,5 @@ pub fn sensors(tx: &Channel, config: &Config) {
         items.iter().flat_map(|elem| elem.as_str()).collect::<Vec<&str>>()
     });
     tp_bat.map(|bats| sensors.insert(1, Box::new(TPBatterySensor::new(&bats))));
-
-    let delay = Duration::from_secs(1);
-    loop {
-        let status = sensors.iter_mut()
-            .map(|sensor| {
-                sensor.process();
-                format!(
-                    "%{{B#666666}} {} %{{B-}} {}",
-                    sensor.icon(),
-                    sensor.status()
-                )
-            })
-            .collect::<Vec<String>>();
-
-        comm::send(tx, "sensors", &reduce(status));
-        thread::sleep(delay);
-    }
-}
-
-fn reduce(arr: Vec<String>) -> String {
-    arr.into_iter().fold("".to_string(), |acc, elem| format!("{} {}", acc, elem))
+    sensors
 }

+ 156 - 0
src/ui/bspwm.rs

@@ -0,0 +1,156 @@
+use std::io::prelude::*;
+use std::io::BufReader;
+use std::process::{Command, Stdio};
+use std::thread;
+use ui::widget;
+
+const MARGIN: u16 = 7;
+
+struct Desktop {
+    name: String,
+    selected: bool,
+    urgent: bool,
+    position: u16,
+    width: u16
+}
+
+impl Desktop {
+    fn contains(&self, x: u16) -> bool {
+        x >= self.position && x < self.position + self.width
+    }
+}
+
+pub struct Bspwm {
+    context: widget::DrawContext,
+    tx: widget::MessageSender,
+    desktops: Vec<Desktop>
+}
+
+impl Bspwm {
+    pub fn new(tx: widget::MessageSender, context: widget::DrawContext) -> Bspwm {
+        Bspwm {
+            context: context,
+            tx: tx,
+            desktops: vec![]
+        }
+    }
+
+    fn parse_bspwm(&mut self, line: &str) {
+        let (kind, line) = line.split_at(1);
+        if kind != "W" { return };
+
+        let mut desktops = vec![];
+        let elems = line.split(':');
+
+        let mut pos = 0;
+
+        for elem in elems {
+            let mut chars = elem.chars();
+            let kind = chars.next().unwrap();
+            let name = chars.collect::<String>();
+
+            if kind == 'M' || kind == 'm' {}
+            else if kind == 'L' {}
+            else if kind == 'G' {}
+            else if kind == 'T' {}
+            else {
+                let empty = kind == 'f';
+                let urgent = kind == 'U' || kind == 'u';
+                let selected = kind.is_uppercase();
+
+                if !empty {
+                    let width = self.context.measure_text(&name) + MARGIN * 2;
+                    desktops.push(Desktop {
+                        name: name,
+                        selected: selected,
+                        urgent: urgent,
+                        position: pos,
+                        width: width
+                    });
+                    pos += width;
+                }
+            }
+        }
+
+        self.desktops = desktops;
+    }
+}
+
+impl widget::Widget for Bspwm {
+    fn init(&mut self) {
+        let tx = self.tx.clone();
+        thread::spawn(move || monitor_thread(tx));
+    }
+
+    fn render(&mut self, x: u16) {
+        for desktop in self.desktops.iter() {
+            if desktop.selected {
+                self.context.set_bg_color(0x6666, 0xCCCC, 0x6666, 0xFFFF);
+            }
+            else if desktop.urgent {
+                self.context.set_bg_color(0xCCCC, 0x0, 0x3333, 0xFFFF);
+            }
+            else {
+                self.context.set_bg_color(0x0, 0x0, 0x0, 0xFFFF);
+            }
+            self.context.draw_bg(x + desktop.position, desktop.width);
+            self.context.draw_text(&desktop.name, x + desktop.position + MARGIN);
+        }
+    }
+
+    fn width(&mut self) -> u16 {
+        let mut sum = 0;
+        for desktop in self.desktops.iter() {
+            sum += self.context.measure_text(&desktop.name) + MARGIN * 2;
+        }
+        sum
+    }
+
+    fn handle_event(&mut self, event: &widget::Message) -> bool {
+        match event {
+            &widget::Message::BspwmEvent(ref line) => {
+                self.parse_bspwm(line);
+                self.tx.send(widget::Message::Relayout).expect("Failed to send relayout");
+            },
+            &widget::Message::MousePress(x) => {
+                for desktop in self.desktops.iter() {
+                    if desktop.contains(x) {
+                        switch_desktop(&desktop.name);
+                    }
+                }
+            },
+            _ => {}
+        }
+        false
+    }
+}
+
+fn monitor_thread(tx: widget::MessageSender) {
+    let bspc = Command::new("bspc")
+        .arg("subscribe")
+        .arg("report")
+        .stdout(Stdio::piped())
+        .spawn()
+        .ok()
+        .expect("Failed to start bspc subscribe");
+
+    let stdout = bspc.stdout.unwrap();
+    let mut reader = BufReader::new(stdout);
+
+    let mut line = String::new();
+    loop {
+        line.clear();
+        reader.read_line(&mut line).ok().expect("Failed to read line");
+        let event = widget::Message::BspwmEvent(line.clone());
+        tx.send(event).ok();
+    }
+}
+
+fn switch_desktop(name: &str) {
+    Command::new("bspc")
+        .arg("desktop")
+        .arg("-f")
+        .arg(name)
+        .output()
+        .ok();
+}

+ 49 - 0
src/ui/ext.rs

@@ -0,0 +1,49 @@
+use xcb;
+
+pub enum PictFormat {
+    RGB24,
+    ARGB32
+}
+
+fn format_rgb24(format: &xcb::render::Pictforminfo) -> bool {
+    let depth = format.depth();
+    let dformat = format.direct();
+    depth == 24
+        && dformat.red_mask() == 255
+        && dformat.green_mask() == 255
+        && dformat.blue_mask() == 255
+        && dformat.red_shift() == 16
+        && dformat.green_shift() == 8
+        && dformat.blue_shift() == 0
+}
+
+fn format_argb32(format: &xcb::render::Pictforminfo) -> bool {
+    let depth = format.depth();
+    let dformat = format.direct();
+    depth == 32
+        && dformat.alpha_mask() == 255
+        && dformat.red_mask() == 255
+        && dformat.green_mask() == 255
+        && dformat.blue_mask() == 255
+        && dformat.alpha_shift() == 24
+        && dformat.red_shift() == 16
+        && dformat.green_shift() == 8
+        && dformat.blue_shift() == 0
+}
+
+pub trait ConnectionExt {
+    fn get_pict_format(&self, format: PictFormat) -> xcb::render::Pictformat;
+}
+
+impl ConnectionExt for xcb::Connection {
+    fn get_pict_format(&self, format: PictFormat) -> xcb::render::Pictformat {
+        let cookie = xcb::render::query_pict_formats(self);
+        let reply = cookie.get_reply().unwrap();
+        let spec: fn(&xcb::render::Pictforminfo) -> bool = match format {
+            PictFormat::RGB24 => format_rgb24,
+            PictFormat::ARGB32 => format_argb32
+        };
+        let format = reply.formats().find(spec).expect("Could not find PictFormat");
+        format.id()
+    }
+}

+ 247 - 0
src/ui/font.rs

@@ -0,0 +1,247 @@
+use config::Config;
+use freetype as ft;
+use fontconfig_sys::*;
+use ui::ext;
+use ui::ext::ConnectionExt;
+use ui::util;
+use xcb;
+
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::ffi::{CString, CStr};
+use std::mem;
+use std::ptr;
+use std::sync::{Arc, Once, ONCE_INIT};
+
+static FC_INIT: Once = ONCE_INIT;
+
+struct Font {
+    pub id: xcb::render::Glyphset,
+    face: ft::Face<'static>,
+    char_widths: RefCell<HashMap<char, u16>>
+}
+
+impl Font {
+    pub fn new(conn: &xcb::Connection, face: ft::Face<'static>) -> Font {
+        let format = conn.get_pict_format(ext::PictFormat::ARGB32);
+        let id = conn.generate_id();
+        xcb::render::create_glyph_set(conn, id, format);
+
+        Font {
+            id: id,
+            face: face,
+            char_widths: RefCell::new(HashMap::new())
+        }
+    }
+
+    pub fn load_glyph(&self, conn: &xcb::Connection, charcode: char) -> Option<u16> {
+        if self.char_widths.borrow().contains_key(&charcode) {
+            return self.char_widths.borrow().get(&charcode).map(|r| *r)
+        }
+        if self.face.get_char_index(charcode as usize) == 0 {
+            return None
+        }
+        match self.face.load_char(charcode as usize, ft::face::RENDER | ft::face::TARGET_LCD) {
+            Ok(_) => {
+                let glyph = self.face.glyph();
+                let bitmap = glyph.bitmap();
+
+                let pixel_width = (bitmap.width() / 3) as usize;
+                let pixel_height = bitmap.rows() as usize;
+
+                let info = xcb::render::Glyphinfo::new(
+                    pixel_width as u16,
+                    pixel_height as u16,
+                    -glyph.bitmap_left() as i16,
+                    glyph.bitmap_top() as i16,
+                    (glyph.advance().x / 64) as i16,
+                    (glyph.advance().y / 64) as i16
+                );
+
+                let stride = bitmap.width() as usize + (pixel_width & 3); // round up to 4 somehow
+                let source = bitmap.buffer();
+                let pixels = pixel_height * pixel_width;
+
+                let mut buf = Vec::with_capacity(pixels * 4);
+                for i in 0..pixel_height {
+                    let offset = i * stride;
+                    for j in 0..pixel_width {
+                        let offset = offset + j * 3;
+                        buf.push(source[offset+2]);
+                        buf.push(source[offset+1]);
+                        buf.push(source[offset]);
+                        buf.push(0);
+                    }
+                }
+                let width = (glyph.advance().x / 64) as u16;
+                xcb::render::add_glyphs(conn, self.id, &[charcode as u32], &[info], buf.as_slice());
+                self.char_widths.borrow_mut().insert(charcode, width);
+                Some(width)
+            },
+            _ => None
+        }
+    }
+
+    pub fn baseline_offset(&self, height: u16) -> i16 {
+        self.face.load_char('X' as usize, ft::face::LoadFlag::empty()).unwrap();
+        let ascender = self.face.glyph().metrics().horiBearingY / 64;
+        (height as i16 - ascender as i16) / 2
+    }
+}
+
+pub struct TextGroup<'a> {
+    pub text: &'a str,
+    pub glyphset: xcb::render::Glyphset
+}
+
+pub struct RenderableText<'a> {
+    pub width: u16,
+    groups: Vec<TextGroup<'a>>
+}
+
+impl<'a> RenderableText<'a> {
+    pub fn render(&self, conn: &xcb::Connection, pen: xcb::render::Picture, pic: xcb::render::Picture, x: u16, y: u16) {
+        let mut cmds = util::GlyphCmd::new();
+        let mut default_glyphset = 0;
+        cmds.set_position(x, y);
+        for group in self.groups.iter() {
+            default_glyphset = group.glyphset;
+            cmds.set_glyphset(group.glyphset);
+            cmds.add_text(group.text);
+        }
+        xcb::render::composite_glyphs_32(
+            conn,
+            xcb::render::PICT_OP_OVER as u8,
+            pen,
+            pic,
+            0,
+            default_glyphset,
+            0, 0,
+            cmds.as_ref()
+        );
+    }
+}
+
+pub struct FontLoader {
+    conn: Arc<xcb::Connection>,
+    library: ft::Library,
+    fonts: Vec<Font>
+}
+
+impl FontLoader {
+    pub fn new(conn: Arc<xcb::Connection>) -> Self {
+        FC_INIT.call_once(|| unsafe {
+            assert_eq!(FcInit(), 1);
+        });
+
+        let library = ft::Library::init().unwrap();
+        unsafe {
+            ft::ffi::FT_Library_SetLcdFilter(library.raw(), ft::ffi::FT_LCD_FILTER_DEFAULT);
+        };
+        FontLoader {
+            conn: conn,
+            library: library,
+            fonts: vec![]
+        }
+    }
+
+    pub fn from_config(conn: Arc<xcb::Connection>, cfg: &Config) -> Self {
+        let val = cfg.lookup("bar.fonts").unwrap();
+        let fonts = val.as_slice().unwrap();
+        let fonts = fonts.iter().flat_map(|elem| elem.as_str());
+        let mut font_loader = Self::new(conn);
+        for font in fonts {
+            font_loader.load(font);
+        }
+        font_loader
+    }
+
+    fn find_face(&self, name: &str) -> Option<ft::Face<'static>> {
+        unsafe {
+            let slice = CString::new(name.to_string()).unwrap();
+            let pattern = FcNameParse(slice.as_ptr() as *const u8);
+            FcConfigSubstitute(ptr::null_mut(), pattern, FcMatchPattern);
+            FcDefaultSubstitute(pattern);
+
+            let mut result: FcResult = mem::uninitialized();
+            let font = FcFontMatch(ptr::null_mut(), pattern, &mut result);
+            if !font.is_null() {
+                let field = CString::new("file").unwrap();
+                let mut ret: *mut FcChar8 = mem::uninitialized();
+                if FcPatternGetString(font, field.as_ptr(), 0, &mut ret) == FcResultMatch {
+                    let file = CStr::from_ptr(ret as *const i8);
+                    let path = file.to_string_lossy().to_string();
+                    let face = self.library.new_face(&path, 0).unwrap();
+
+                    let field = CString::new("dpi").unwrap();
+                    let mut dpi = 0.0;
+                    if FcPatternGetDouble(font, field.as_ptr(), 0, &mut dpi) != FcResultMatch {
+                        dpi = 75.0;
+                    }
+
+                    let field = CString::new("pixelsize").unwrap();
+                    let mut pixel_size = 0.0;
+                    if FcPatternGetDouble(font, field.as_ptr(), 0, &mut pixel_size) == FcResultMatch {
+                        face.set_pixel_sizes((96.0 / dpi * pixel_size) as u32, 0).unwrap();
+                    }
+                    return Some(face)
+                }
+            }
+            None
+        }
+    }
+
+    pub fn load(&mut self, name: &str) {
+        if let Some(face) = self.find_face(name) {
+            let font = Font::new(&self.conn, face);
+            self.fonts.push(font);
+        }
+        else {
+            panic!("Could not load font {}", name);
+        }
+    }
+
+    pub fn default_offset(&self, height: u16) -> i16 {
+        self.fonts[0].baseline_offset(height)
+    }
+
+    fn glyphset_and_width_for_char(&self, charcode: char) -> Option<(xcb::render::Glyphset, u16)> {
+        for font in self.fonts.iter() {
+            if let Some(width) = font.load_glyph(&self.conn, charcode) {
+                return Some((font.id, width))
+            }
+        }
+        None
+    }
+
+    pub fn create_renderable_text<'a>(&self, string: &'a str) -> RenderableText<'a> {
+        let mut ret = vec![];
+        let mut prev = 0;
+        let mut id = 0;
+        let mut width = 0;
+        for (i, charcode) in string.char_indices() {
+            let (this_id, char_width) = self.glyphset_and_width_for_char(charcode).unwrap();
+            width += char_width;
+            if i == 0 {
+                id = this_id;
+            }
+            else if id != this_id {
+                ret.push(TextGroup {
+                    text: &string[prev..i],
+                    glyphset: id
+                });
+                id = this_id;
+                prev = i;
+            }
+        }
+        ret.push(TextGroup {
+            text: &string[prev..string.len()],
+            glyphset: id
+        });
+
+        RenderableText {
+            width: width,
+            groups: ret
+        }
+    }
+}

+ 90 - 0
src/ui/mod.rs

@@ -0,0 +1,90 @@
+mod font;
+mod panel;
+mod ext;
+mod title;
+mod tray;
+mod sensors;
+mod bspwm;
+mod spacer;
+mod music;
+mod util;
+mod widget;
+mod x11;
+
+use simple_signal;
+use simple_signal::Signal;
+use config::Config;
+
+use std::sync::mpsc;
+use std::thread;
+use std::time;
+
+pub const SIZE: u16 = 20;
+
+pub fn ui_main(cfg: &Config) -> i32 {
+    if let Some(conn) = x11::Connection::new() {
+        let (tx, rx) = mpsc::channel();
+        {
+            let tx = tx.clone();
+            simple_signal::set_handler(&[Signal::Int, Signal::Term], move |_signals| {
+                tx.send(widget::Message::Quit).expect("Failed to send quit");
+            });
+        }
+        {
+            let tx = tx.clone();
+            let conn = conn.clone_connection();
+            thread::spawn(move || {
+                loop {
+                    match conn.wait_for_event() {
+                        Some(event) => {
+                            let message = widget::Message::XcbEvent(event);
+                            tx.send(message).expect("Failed to send xcb event");
+                        },
+                        None => { break; }
+                    }
+                }
+            });
+        }
+
+        let mut panel = panel::Panel::new(conn, cfg);
+
+        let bspwm = bspwm::Bspwm::new(tx.clone(), panel.make_draw_context());
+        panel.add_left_widget(Box::new(bspwm));
+
+        let spacer = spacer::create(panel.make_draw_context(), 4, 0x6666, 0x6666, 0x6666, 0xFFFF);
+        panel.add_left_widget(Box::new(spacer));
+
+        let title = title::Title::new(panel.conn.clone(), panel.make_draw_context());
+        panel.add_left_widget(Box::new(title));
+
+        let tray = tray::Tray::new(tx.clone(), panel.conn.clone(), panel.window);
+        panel.add_right_widget(Box::new(tray));
+
+        let sensors = sensors::Sensors::new(panel.make_draw_context(), cfg);
+        panel.add_right_widget(Box::new(sensors));
+
+        let music = music::mpd(tx.clone(), panel.make_draw_context());
+        panel.add_right_widget(Box::new(music));
+
+        panel.create();
+
+        {
+            let tx = tx.clone();
+            thread::spawn(move || {
+                loop {
+                    tx.send(widget::Message::Update).expect("Failed to send update");
+                    thread::sleep(time::Duration::from_secs(1));
+                }
+            });
+        }
+
+        loop {
+            let event = rx.recv();
+            if panel.handle_event(event.unwrap()) {
+                println!("Exiting");
+                break;
+            }
+        }
+    }
+    0
+}

+ 116 - 0
src/ui/music.rs

@@ -0,0 +1,116 @@
+use mpd;
+use mpd::status::State;
+use mpd::idle;
+use mpd::idle::Idle;
+use std::thread;
+use ui::widget;
+use ui::widget::Widget;
+
+const MARGIN: u16 = 7;
+const WIDTH: u16 = 250;
+
+pub struct Music {
+    context: widget::DrawContext,
+    tx: widget::MessageSender,
+    state: State,
+    artist: String,
+    title: String,
+    last_pos: u16
+}
+
+pub fn mpd(tx: widget::MessageSender, context: widget::DrawContext) -> Music {
+    Music {
+        context: context,
+        tx: tx,
+        state: State::Stop,
+        artist: "".to_string(),
+        title: "".to_string(),
+        last_pos: 0
+    }
+}
+
+impl Music {
+    fn icon(&self) -> &str {
+        match self.state {
+            State::Play => "",
+            State::Pause => "",
+            State::Stop => ""
+        }
+    }
+
+    fn redraw(&mut self) {
+        let x = self.last_pos;
+        self.context.set_bg_color(0x0, 0x0, 0x0, 0xFFFF);
+        self.context.draw_bg(x, WIDTH);
+        self.render(x);
+        self.context.flush();
+    }
+
+}
+
+impl widget::Widget for Music {
+    fn init(&mut self) {
+        let tx = self.tx.clone();
+        thread::spawn(move || monitor_thread(tx));
+    }
+
+    fn render(&mut self, x: u16) {
+        self.last_pos = x;
+        let icon_width = self.context.measure_text(self.icon());
+
+        self.context.set_bg_color(0x6666, 0x6666, 0x6666, 0xFFFF);
+        self.context.draw_bg(x, icon_width + MARGIN * 2);
+        self.context.draw_text(self.icon(), x + MARGIN);
+
+
+        let text = if self.state == State::Stop {
+            "Stopped".to_string()
+        }
+        else {
+            format!("{} - {}", self.artist, self.title)
+        };
+        let text_width = WIDTH - MARGIN * 4 - icon_width;
+        self.context.set_bg_color(0x0, 0x0, 0x0, 0xFFFF);
+        self.context.draw_bg(x + icon_width + MARGIN * 3, text_width);
+        self.context.draw_text_until(&text, x + icon_width + MARGIN * 3, text_width);
+    }
+
+    fn width(&mut self) -> u16 {
+        WIDTH
+    }
+
+    fn handle_event(&mut self, event: &widget::Message) -> bool {
+        match event {
+            &widget::Message::MpdEvent(ref state, ref artist, ref title) => {
+                self.state = state.clone();
+                self.artist = artist.clone();
+                self.title = title.clone();
+                self.redraw();
+            },
+            &widget::Message::MousePress(_x) => {
+                toggle();
+            },
+            _ => {}
+        }
+        false
+    }
+}
+
+fn monitor_thread(tx: widget::MessageSender) {
+    let mut conn = mpd::client::Client::connect("127.0.0.1:6600").unwrap();
+    loop {
+        let unknown = "Unknown".to_string();
+        let state = conn.status().unwrap().state;
+        let song = conn.currentsong().unwrap().unwrap();
+        let artist = song.tags.get("Artist").unwrap_or(&unknown);
+        let title = song.title.as_ref().unwrap_or(&unknown);
+
+        tx.send(widget::Message::MpdEvent(state, artist.clone(), title.clone())).expect("Failed to send mpd event");
+        conn.wait(&[idle::Subsystem::Player]).ok();
+    }
+}
+
+fn toggle() {
+    let mut conn = mpd::client::Client::connect("127.0.0.1:6600").unwrap();
+    conn.toggle_pause().expect("Failed to pause");
+}

+ 215 - 0
src/ui/panel.rs

@@ -0,0 +1,215 @@
+use config::Config;
+use xcb;
+use ui::ext;
+use ui::ext::ConnectionExt;
+use ui::font;
+use ui::widget;
+use ui::widget::Widget;
+use ui::x11;
+
+use std::rc::Rc;
+use std::sync::Arc;
+use std::iter;
+use std::slice;
+
+struct WidgetState {
+    widget: Box<Widget>,
+    position: u16,
+    width: u16
+}
+
+impl WidgetState {
+    fn contains(&self, x: u16) -> bool {
+        x >= self.position && x < self.position + self.width
+    }
+}
+
+pub struct Panel {
+    pub conn: Arc<x11::Connection>,
+    pub window: xcb::Window,
+    pub width: u16,
+    left_widgets: Vec<WidgetState>,
+    right_widgets: Vec<WidgetState>,
+    fonts: Rc<font::FontLoader>,
+    picture: xcb::render::Picture,
+    finishing: bool
+}
+
+impl Panel {
+    pub fn new(conn: x11::Connection, cfg: &Config) -> Panel {
+        let conn = Arc::new(conn);
+        let window = conn.generate_id();
+        let picture = conn.generate_id();
+
+        let width = conn.default_screen().width;
+        let font_loader = Rc::new(font::FontLoader::from_config(conn.clone_connection(), cfg));
+
+        Panel {
+            conn: conn,
+            width: width,
+            window: window,
+            left_widgets: vec![],
+            right_widgets: vec![],
+            fonts: font_loader,
+            picture: picture,
+            finishing: false
+        }
+    }
+
+    pub fn create(&mut self) {
+        let (root, width) = {
+            let screen = self.conn.default_screen();
+            (screen.root, screen.width)
+        };
+
+        xcb::create_window(
+            &self.conn,
+            xcb::COPY_FROM_PARENT as u8,
+            self.window,
+            root,
+            0, 0,
+            width, 20,
+            0,
+            xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
+            xcb::COPY_FROM_PARENT,
+            &[
+                (xcb::CW_BACK_PIXEL, 0xFF000000),
+                (xcb::CW_EVENT_MASK, xcb::EVENT_MASK_EXPOSURE | xcb::EVENT_MASK_PROPERTY_CHANGE | xcb::EVENT_MASK_STRUCTURE_NOTIFY | xcb::EVENT_MASK_BUTTON_PRESS)
+            ]
+        );
+        self.set_property(
+            self.conn.atom(x11::_NET_WM_WINDOW_TYPE),
+            xcb::ATOM_ATOM,
+            32,
+            &[self.conn.atom(x11::_NET_WM_WINDOW_TYPE_DOCK)]
+        );
+        xcb::map_window(&self.conn, self.window);
+        xcb::render::create_picture(&self.conn, self.picture, self.window, self.conn.get_pict_format(ext::PictFormat::RGB24), &[
+            (xcb::render::CP_POLY_EDGE, xcb::render::POLY_EDGE_SMOOTH),
+            (xcb::render::CP_POLY_MODE, xcb::render::POLY_MODE_IMPRECISE)
+        ]);
+        self.conn.flush();
+        for state in self.widgets_iter() {
+            state.widget.init();
+        }
+        self.conn.flush();
+    }
+
+    pub fn make_draw_context(&self) -> widget::DrawContext {
+        widget::DrawContext::new(self.conn.clone_connection(), self.window, self.picture, self.fonts.clone())
+    }
+
+    fn widgets_iter(&mut self) -> iter::Chain<slice::IterMut<WidgetState>, slice::IterMut<WidgetState>>{
+        self.left_widgets.iter_mut().chain(self.right_widgets.iter_mut())
+    }
+
+    pub fn set_property<T>(&self, name: xcb::Atom, type_: xcb::Atom, format: u8, data: &[T]) {
+        xcb::change_property(
+            &self.conn,
+            xcb::PROP_MODE_REPLACE as u8,
+            self.window,
+            name,
+            type_,
+            format,
+            data
+        );
+    }
+
+    pub fn handle_event(&mut self, event: widget::Message) -> bool {
+        let finishing = self.finishing;
+        let mut should_exit = false;
+        if !finishing {
+            for state in self.widgets_iter() {
+                should_exit |= state.widget.handle_event(&event);
+            }
+        }
+        match event {
+            widget::Message::XcbEvent(event) => {
+                if event.response_type() == xcb::EXPOSE {
+                    self.relayout();
+                };
+                if event.response_type() == xcb::BUTTON_PRESS {
+                    let event: &xcb::ButtonPressEvent = unsafe { xcb::cast_event(&event) };
+                    let x = event.root_x() as u16;
+                    for state in self.widgets_iter() {
+                        if state.contains(x) {
+                            let offset_x = x - state.position;
+                            let event = widget::Message::MousePress(offset_x);
+                            state.widget.handle_event(&event);
+                        }
+                    }
+                };
+                if finishing && event.response_type() == xcb::DESTROY_NOTIFY {
+                    let event: &xcb::DestroyNotifyEvent = unsafe { xcb::cast_event(&event) };
+                    if event.window() == self.window {
+                        return true
+                    }
+                }
+            },
+            widget::Message::Update =>
+                self.relayout(),
+            widget::Message::Relayout =>
+                self.relayout(),
+            widget::Message::Quit =>
+                self.finish(),
+            _ => {}
+        }
+        should_exit
+    }
+
+    pub fn add_left_widget(&mut self, widget: Box<Widget>) {
+        self.left_widgets.push(WidgetState{
+            widget: widget,
+            position: 0,
+            width: 0
+        });
+    }
+
+    pub fn add_right_widget(&mut self, widget: Box<Widget>) {
+        self.right_widgets.push(WidgetState{
+            widget: widget,
+            position: 0,
+            width: 0
+        });
+    }
+
+    pub fn relayout(&mut self) {
+        let color = xcb::render::Color::new(0, 0, 0, 0xFFFF);
+        xcb::render::fill_rectangles(
+            &self.conn,
+            xcb::render::PICT_OP_SRC as u8,
+            self.picture,
+            color,
+            &[xcb::Rectangle::new(0, 0, self.width, 20)]
+        );
+
+        // measure
+        let mut left_pos = 0;
+        for state in self.left_widgets.iter_mut() {
+            state.position = left_pos;
+            state.width = state.widget.width();
+            left_pos += state.width;
+        }
+        let mut right_pos = self.width;
+        for state in self.right_widgets.iter_mut() {
+            state.width = state.widget.width();
+            right_pos -= state.width;
+            state.position = right_pos;
+        }
+
+        // render
+        for state in self.widgets_iter() {
+            state.widget.render(state.position);
+        }
+        self.conn.flush();
+    }
+
+    pub fn finish(&mut self) {
+        self.finishing = true;
+        for state in self.widgets_iter() {
+            state.widget.finish();
+        }
+        xcb::destroy_window(&self.conn, self.window);
+        self.conn.flush();
+    }
+}

+ 60 - 0
src/ui/sensors.rs

@@ -0,0 +1,60 @@
+use config::Config;
+use super::super::sensors;
+use super::super::sensors::Sensor;
+use ui::widget;
+
+const MARGIN: u16 = 7;
+
+pub struct Sensors {
+    context: widget::DrawContext,
+    sensors: Vec<Box<Sensor>>
+}
+
+impl Sensors {
+    pub fn new(context: widget::DrawContext, config: &Config) -> Sensors {
+        Sensors {
+            context: context,
+            sensors: sensors::sensor_list(config)
+        }
+    }
+}
+
+impl widget::Widget for Sensors {
+    fn render(&mut self, x: u16) {
+        let mut offset = x;
+        for ref sensor in self.sensors.iter() {
+            let icon_width = self.context.measure_text(&sensor.icon());
+            let status_width = self.context.measure_text(&sensor.status());
+
+            self.context.set_bg_color(0x6666, 0x6666, 0x6666, 0xFFFF);
+            self.context.draw_bg(offset, icon_width + MARGIN * 2);
+            self.context.draw_text(&sensor.icon(), offset + MARGIN);
+            offset += icon_width + MARGIN * 2;
+
+            self.context.set_bg_color(0x0, 0x0, 0x0, 0xFFFF);
+            self.context.draw_text(&sensor.status(), offset + MARGIN);
+            offset += status_width + MARGIN * 2;
+        }
+    }
+
+    fn width(&mut self) -> u16 {
+        let mut sum = 0;
+        for ref sensor in self.sensors.iter() {
+            let text = format!("{}{}", sensor.icon(), sensor.status());
+            sum += self.context.measure_text(&text) + MARGIN * 4;
+        }
+        sum
+    }
+
+    fn handle_event(&mut self, event: &widget::Message) -> bool {
+        match event {
+            &widget::Message::Update =>
+                for ref mut sensor in self.sensors.iter_mut() {
+                    sensor.process()
+                },
+            _ => {}
+        }
+
+        false
+    }
+}

+ 24 - 0
src/ui/spacer.rs

@@ -0,0 +1,24 @@
+use ui::widget;
+
+pub struct Spacer {
+    context: widget::DrawContext,
+    width: u16
+}
+
+pub fn create(mut context: widget::DrawContext, width: u16, red: u16, green: u16, blue: u16, alpha: u16) -> Spacer {
+    context.set_bg_color(red, green, blue, alpha);
+    Spacer {
+        context: context,
+        width: width
+    }
+}
+
+impl widget::Widget for Spacer {
+    fn render(&mut self, x: u16) {
+        self.context.draw_bg(x, self.width);
+    }
+
+    fn width(&mut self) -> u16 {
+        self.width
+    }
+}

+ 89 - 0
src/ui/title.rs

@@ -0,0 +1,89 @@
+use ui::widget;
+use ui::widget::Widget;
+use ui::x11;
+use xcb;
+
+use std::sync::Arc;
+
+const MARGIN: u16 = 7;
+
+pub struct Title {
+    conn: Arc<x11::Connection>,
+    context: widget::DrawContext,
+    title: String,
+    last_pos: u16,
+    last_width: u16,
+    last_win: xcb::Window
+}
+
+impl Title {
+    pub fn new(conn: Arc<x11::Connection>, context: widget::DrawContext) -> Title {
+        Title {
+            conn: conn,
+            context: context,
+            title: "".to_string(),
+            last_pos: 0,
+            last_width: 0,
+            last_win: 0
+        }
+    }
+
+    pub fn redraw(&mut self) {
+        self.context.draw_bg(self.last_pos, self.last_width);
+        self.context.draw_text(&self.title, self.last_pos + MARGIN);
+        self.last_width = self.context.measure_text(&self.title) + MARGIN;
+        self.conn.flush();
+    }
+}
+
+impl Widget for Title {
+    fn init(&mut self) {
+        let screen = self.conn.default_screen();
+        self.last_win = screen.get_active_window();
+        self.title = self.conn.get_window_name(self.last_win);
+        self.conn.watch(screen.root, true);
+        self.conn.watch(self.last_win, true);
+    }
+
+    fn render(&mut self, x: u16) {
+        self.last_pos = x;
+        self.context.draw_text(&self.title, self.last_pos + MARGIN);
+        self.last_width = self.context.measure_text(&self.title) + MARGIN;
+    }
+
+    fn width(&mut self) -> u16 {
+        0
+    }
+
+    fn handle_event(&mut self, event: &widget::Message) -> bool {
+        match event {
+            &widget::Message::XcbEvent(ref event) =>
+                if event.response_type() == xcb::PROPERTY_NOTIFY {
+                    let event: &xcb::PropertyNotifyEvent = unsafe { xcb::cast_event(&event) };
+                    if event.atom() == self.conn.atom(x11::_NET_ACTIVE_WINDOW) {
+                        let new_win = {
+                            let screen = self.conn.default_screen();
+                            screen.get_active_window()
+                        };
+                        self.conn.watch(self.last_win, false);
+                        self.conn.watch(new_win, true);
+                        self.last_win = new_win;
+                        self.title = self.conn.get_window_name(new_win);
+                        self.redraw();
+                    }
+                    else if event.atom() == self.conn.atom(x11::_NET_WM_NAME) {
+                        self.title = self.conn.get_window_name(self.last_win);
+                        self.redraw();
+                    }
+                },
+            _ => {}
+        }
+        false
+    }
+
+    fn finish(&mut self) {
+        let screen = self.conn.default_screen();
+        self.conn.watch(screen.root, false);
+        self.conn.watch(self.last_win, false);
+    }
+}

+ 170 - 0
src/ui/tray.rs

@@ -0,0 +1,170 @@
+use ui;
+use ui::widget;
+use ui::x11;
+use xcb;
+
+use std::sync::Arc;
+
+const CLIENT_MESSAGE: u8 = xcb::CLIENT_MESSAGE | 0x80; // 0x80 flag for client messages
+
+pub struct Tray {
+    tx: widget::MessageSender,
+    conn: Arc<x11::Connection>,
+    window: xcb::Window,
+    children: Vec<xcb::Window>,
+    timestamp: xcb::Timestamp,
+}
+
+impl Tray {
+    pub fn new(tx: widget::MessageSender, conn: Arc<x11::Connection>, window: xcb::Window) -> Tray {
+        Tray {
+            conn: conn,
+            tx: tx,
+            window: window,
+            children: vec![],
+            timestamp: 0
+        }
+    }
+
+    pub fn take_selection(&mut self, timestamp: xcb::Timestamp) -> bool {
+        let selection = self.conn.atom(x11::_NET_SYSTEM_TRAY_S0);
+        xcb::set_selection_owner(&self.conn, self.window, selection, timestamp);
+        let owner = xcb::get_selection_owner(&self.conn, selection).get_reply().unwrap().owner();
+        let ok = owner == self.window;
+        if ok {
+            self.timestamp = timestamp;
+            let screen = self.conn.default_screen();
+
+            let client_event = xcb::ClientMessageEvent::new(
+                32, // 32 bits (refers to data)
+                screen.root,
+                self.conn.atom(x11::MANAGER),
+                xcb::ClientMessageData::from_data32([timestamp, selection, self.window, 0, 0])
+            );
+            xcb::send_event(&self.conn, false, screen.root, xcb::EVENT_MASK_STRUCTURE_NOTIFY, &client_event);
+            self.conn.flush();
+        }
+        ok
+    }
+
+    pub fn adopt(&mut self, window: xcb::Window) {
+        let conn = &self.conn;
+        xcb::change_window_attributes(conn, window, &[
+            (xcb::CW_EVENT_MASK, xcb::EVENT_MASK_STRUCTURE_NOTIFY)
+        ]);
+        xcb::reparent_window(conn, window, self.window, 0, 0);
+        xcb::map_window(conn, window);
+        self.force_size(window, None);
+        conn.flush();
+        self.children.push(window);
+        self.relayout();
+    }
+
+    pub fn forget(&mut self, window: xcb::Window) {
+        self.children.retain(|child| *child != window);
+        self.relayout();
+    }
+
+    fn relayout(&self) {
+        self.tx.send(widget::Message::Relayout).expect("Failed to send relayout");
+    }
+
+    pub fn force_size(&self, window: xcb::Window, dimensions: Option<(u16, u16)>) {
+        let conn = &self.conn;
+        let dimensions = dimensions.unwrap_or_else(|| {
+            let geometry = xcb::get_geometry(conn, window).get_reply().unwrap();
+            (geometry.width(), geometry.height())
+        });
+        if dimensions != (ui::SIZE, ui::SIZE) {
+            xcb::configure_window(conn, window, &[
+                (xcb::CONFIG_WINDOW_WIDTH as u16, ui::SIZE as u32),
+                (xcb::CONFIG_WINDOW_HEIGHT as u16, ui::SIZE as u32)
+            ]);
+            conn.flush();
+        }
+    }
+}
+
+impl widget::Widget for Tray {
+    fn width(&mut self) -> u16 {
+        (self.children.len() * 20) as u16
+    }
+
+    fn render(&mut self, x: u16) {
+        for (index, child) in self.children.iter().enumerate() {
+            let window = *child;
+            let xpos = x as u32 + index as u32 * 20;
+            xcb::configure_window(&self.conn, window, &[
+                (xcb::CONFIG_WINDOW_X as u16, xpos)
+            ]);
+        }
+    }
+
+    fn handle_event(&mut self, event: &widget::Message) -> bool {
+        match event {
+            &widget::Message::XcbEvent(ref event) =>
+                match event.response_type() {
+                    xcb::PROPERTY_NOTIFY if self.timestamp == 0 => {
+                        let event: &xcb::PropertyNotifyEvent = unsafe { xcb::cast_event(&event) };
+                        if !self.take_selection(event.time()) {
+                            println!("Could not take ownership of tray selection. Maybe another tray is also running?");
+                            return true
+                        }
+                    },
+                    CLIENT_MESSAGE => {
+                        let event: &xcb::ClientMessageEvent = unsafe { xcb::cast_event(&event) };
+                        if event.type_() == self.conn.atom(x11::_NET_SYSTEM_TRAY_OPCODE) {
+                            let data = event.data().data32();
+                            let opcode = data[1];
+                            let window = data[2];
+                            if opcode == 0 {
+                                self.adopt(window);
+                            }
+                        }
+                    },
+                    xcb::REPARENT_NOTIFY => {
+                        let event: &xcb::ReparentNotifyEvent = unsafe { xcb::cast_event(&event) };
+                        if event.parent() != self.window {
+                            self.forget(event.window());
+                        }
+                    },
+                    xcb::DESTROY_NOTIFY => {
+                        let event: &xcb::DestroyNotifyEvent = unsafe { xcb::cast_event(&event) };
+                        self.forget(event.window());
+                    },
+                    xcb::CONFIGURE_NOTIFY => {
+                        let event: &xcb::ConfigureNotifyEvent = unsafe { xcb::cast_event(&event) };
+                        let window = event.window();
+                        if window != self.window {
+                            self.force_size(window, Some((event.width(), event.height())));
+                        }
+                    },
+                    xcb::SELECTION_CLEAR => {
+                        self.finish();
+                    },
+                    _ => {}
+                },
+                _ => {}
+        }
+        false
+    }
+
+    fn finish(&mut self) {
+        let conn = &self.conn;
+        let screen = conn.default_screen();
+
+        for child in self.children.iter() {
+            let window = *child;
+            xcb::change_window_attributes(conn, window, &[
+                (xcb::CW_EVENT_MASK, xcb::EVENT_MASK_NO_EVENT)
+            ]);
+            xcb::unmap_window(conn, window);
+            xcb::reparent_window(conn, window, screen.root, 0, 0);
+        }
+        xcb::change_window_attributes(conn, self.window, &[
+            (xcb::CW_EVENT_MASK, xcb::EVENT_MASK_STRUCTURE_NOTIFY)
+        ]);
+        xcb::set_selection_owner(conn, xcb::NONE, conn.atom(x11::_NET_SYSTEM_TRAY_S0), self.timestamp);
+        conn.flush();
+    }
+}

+ 74 - 0
src/ui/util.rs

@@ -0,0 +1,74 @@
+use xcb;
+
+use std::mem;
+
+#[repr(C)]
+struct FontHeader {
+    marker: u8,
+    padding: [u8; 7],
+    id: u32
+}
+
+#[repr(C)]
+struct ELTHeader {
+    len: u32,
+    x: u16,
+    y: u16
+}
+
+pub struct GlyphCmd {
+    buf: Vec<u8>
+}
+
+impl GlyphCmd {
+    pub fn new() -> Self {
+        GlyphCmd { buf: vec![] }
+    }
+
+    pub fn set_glyphset(&mut self, id: xcb::render::Glyphset) {
+        let header = FontHeader {
+            marker: 0xFF,
+            padding: [0; 7],
+            id: id
+        };
+        let bytes: [u8; 12] = unsafe {
+            mem::transmute(header)
+        };
+        self.buf.extend_from_slice(&bytes);
+    }
+
+    pub fn set_position(&mut self, x: u16, y: u16) {
+        self.add_text_at("", x, y);
+    }
+
+    pub fn add_text(&mut self, string: &str) {
+        self.add_text_at(string, 0, 0);
+    }
+
+    pub fn add_text_at(&mut self, string: &str, x: u16, y: u16) {
+        let mut count = 0;
+        let mut temp = vec![];
+        for charcode in string.chars() {
+            count += 1;
+            let bytes: [u8; 4] = unsafe {
+                mem::transmute(charcode)
+            };
+            temp.extend_from_slice(&bytes);
+        }
+        let header = ELTHeader {
+            len: count,
+            x: x,
+            y: y
+        };
+        let bytes: [u8; 8] = unsafe {
+            mem::transmute(header)
+        };
+        self.buf.extend_from_slice(&bytes);
+        self.buf.extend_from_slice(&temp);
+    }
+
+    pub fn as_ref(&self) -> &[u8] {
+        self.buf.as_slice()
+    }
+}
+

+ 127 - 0
src/ui/widget.rs

@@ -0,0 +1,127 @@
+use mpd;
+use xcb;
+use ui::ext;
+use ui::ext::ConnectionExt;
+use ui::font;
+
+use std::cmp;
+use std::rc::Rc;
+use std::sync::Arc;
+use std::sync::mpsc;
+
+pub type MessageSender = mpsc::Sender<Message>;
+
+pub enum Message {
+    Relayout,
+    Update,
+    Quit,
+    MousePress(u16),
+    BspwmEvent(String),
+    MpdEvent(mpd::status::State, String, String),
+    XcbEvent(xcb::GenericEvent)
+}
+
+pub trait Widget {
+    fn init(&mut self) {}
+    fn render(&mut self, x: u16);
+    fn width(&mut self) -> u16;
+    fn handle_event(&mut self, _event: &Message) -> bool {
+        false
+    }
+    fn finish(&mut self) {}
+}
+
+pub struct DrawContext {
+    conn: Arc<xcb::Connection>,
+    window: xcb::Window,
+    picture: xcb::render::Picture,
+    pen: xcb::render::Picture,
+    fonts: Rc<font::FontLoader>,
+    pen_color: xcb::render::Color,
+    bg_color: xcb::render::Color
+}
+
+impl DrawContext {
+    pub fn new(conn: Arc<xcb::Connection>, window: xcb::Window, picture: xcb::render::Picture, fonts: Rc<font::FontLoader>) -> DrawContext {
+        let pen = conn.generate_id();
+        let color = xcb::render::Color::new(0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF);
+        xcb::render::create_solid_fill(
+            &conn,
+            pen,
+            color
+        );
+
+        DrawContext {
+            conn: conn,
+            window: window,
+            picture: picture,
+            pen: pen,
+            fonts: fonts,
+            pen_color: color,
+            bg_color: xcb::render::Color::new(0, 0, 0, 0xFFFF)
+        }
+    }
+
+    pub fn draw_bg(&self, x: u16, width: u16) {
+        xcb::render::fill_rectangles(
+            &self.conn,
+            xcb::render::PICT_OP_SRC as u8,
+            self.picture,
+            self.bg_color,
+            &[xcb::Rectangle::new(x as i16, 0, width, 20)]
+        );
+    }
+
+    pub fn measure_text(&self, name: &str) -> u16 {
+        let text = self.fonts.create_renderable_text(name);
+        text.width
+    }
+
+    pub fn set_bg_color(&mut self, red: u16, blue: u16, green: u16, alpha: u16) {
+        self.bg_color = xcb::render::Color::new(red, blue, green, alpha);
+    }
+
+
+    pub fn draw_text(&self, name: &str, x: u16) {
+        if !name.is_empty() {
+            let text = self.fonts.create_renderable_text(name);
+            let baseline = 20 - self.fonts.default_offset(20);
+            text.render(&self.conn, self.pen, self.picture, x, baseline as u16);
+        }
+    }
+
+    pub fn draw_text_until(&self, name: &str, x: u16, width: u16) {
+        if !name.is_empty() {
+            let text = self.fonts.create_renderable_text(name);
+            let baseline = 20 - self.fonts.default_offset(20);
+
+            // weird things happen if you draw too little
+            let width = cmp::min(width, text.width);
+
+            let pixmap = self.conn.generate_id();
+            xcb::create_pixmap(&self.conn, 32, pixmap, self.window, x + width, 1);
+            let pen = self.conn.generate_id();
+            xcb::render::create_picture(
+                &self.conn,
+                pen,
+                pixmap,
+                self.conn.get_pict_format(ext::PictFormat::ARGB32),
+                &[]
+            );
+            xcb::render::fill_rectangles(
+                &self.conn,
+                xcb::render::PICT_OP_SRC as u8,
+                pen,
+                self.pen_color,
+                &[xcb::Rectangle::new(x as i16, 0, width, 1)]
+            );
+            text.render(&self.conn, pen, self.picture, x, baseline as u16);
+            xcb::render::free_picture(&self.conn, pen);
+            xcb::free_pixmap(&self.conn, pixmap);
+        }
+    }
+
+    pub fn flush(&self) {
+        self.conn.flush();
+    }
+}

+ 34 - 24
src/x11.rs → src/ui/x11.rs

@@ -1,30 +1,27 @@
+#![allow(dead_code)]
+
 use xcb;
+use std::cell::RefCell;
+use std::collections::HashMap;
 use std::mem;
 use std::ops::Deref;
-
-fn load_atom(conn: &xcb::Connection, name: &str) -> xcb::Atom {
-    let cookie = xcb::intern_atom(conn, true, name);
-    cookie.get_reply().unwrap().atom()
-}
+use std::sync::Arc;
 
 macro_rules! atoms {
     ( $( $x:ident ),* ) => {
         #[allow(non_snake_case)]
-        pub struct Atoms {
-            $(pub $x: xcb::Atom),*
-        }
-
-        pub fn load_atoms(conn: &xcb::Connection) -> Atoms {
-            Atoms {
-                $($x: load_atom(conn, stringify!($x))),*
-            }
-        }
+        $(pub const $x: &'static str = stringify!($x);)*
     }
 }
 
 atoms!(
     _NET_ACTIVE_WINDOW,
+    _NET_SYSTEM_TRAY_S0,
+    _NET_SYSTEM_TRAY_OPCODE,
     _NET_WM_NAME,
+    _NET_WM_WINDOW_TYPE,
+    _NET_WM_WINDOW_TYPE_DOCK,
+    MANAGER,
     UTF8_STRING
 );
 
@@ -34,18 +31,18 @@ pub struct WinClass {
 }
 
 pub struct Connection {
-    conn: xcb::Connection,
+    conn: Arc<xcb::Connection>,
     default_screen: i32,
-    pub atoms: Atoms
+    cache: RefCell<HashMap<String, xcb::Atom>>
 }
 
 impl Connection {
     pub fn new() -> Option<Connection> {
         if let Ok((conn, default_screen)) = xcb::Connection::connect(None) {
             Some(Connection {
-                atoms: load_atoms(&conn),
+                conn: Arc::new(conn),
                 default_screen: default_screen,
-                conn: conn
+                cache: RefCell::new(HashMap::new())
             })
         }
         else {
@@ -53,6 +50,22 @@ impl Connection {
         }
     }
 
+    pub fn clone_connection(&self) -> Arc<xcb::Connection> {
+        self.conn.clone()
+    }
+
+    pub fn atom(&self, name: &str) -> xcb::Atom {
+        let mut cache = self.cache.borrow_mut();
+        if cache.contains_key(name) {
+            *cache.get(name).unwrap()
+        }
+        else {
+            let atom = xcb::intern_atom(&self.conn, false, name).get_reply().unwrap().atom();
+            cache.insert(name.to_string(), atom);
+            atom
+        }
+    }
+
     pub fn default_screen<'a>(&'a self) -> Screen<'a> {
         self.screen(self.default_screen as usize)
     }
@@ -62,7 +75,6 @@ impl Connection {
         let screen = setup.roots().nth(index).unwrap();
         Screen {
             conn: &self,
-            atoms: &self.atoms,
             root: screen.root(),
             width: screen.width_in_pixels(),
             index: index
@@ -80,7 +92,7 @@ impl Connection {
     }
 
     pub fn get_window_name(&self, win: xcb::Window) -> String {
-        let cookie = xcb::get_property(&self.conn, false, win, self.atoms._NET_WM_NAME, self.atoms.UTF8_STRING, 0, 100);
+        let cookie = xcb::get_property(&self.conn, false, win, self.atom(_NET_WM_NAME), self.atom(UTF8_STRING), 0, 100);
         let reply = cookie.get_reply();
         let value: &str = match reply {
             Ok(reply) => unsafe { mem::transmute(reply.value::<u8>()) },
@@ -144,7 +156,6 @@ impl Deref for Connection {
 
 pub struct Screen<'a> {
     conn: &'a Connection,
-    pub atoms: &'a Atoms,
     pub root: xcb::Window,
     pub width: u16,
     pub index: usize
@@ -152,14 +163,13 @@ pub struct Screen<'a> {
 
 impl<'a> Screen<'a> {
     pub fn get_active_window(&self) -> xcb::Window {
-        let cookie = xcb::get_property(self.conn, false, self.root, self.atoms._NET_ACTIVE_WINDOW, xcb::ATOM_WINDOW, 0, 4);
+        let cookie = xcb::get_property(self.conn, false, self.root, self.conn.atom(_NET_ACTIVE_WINDOW), xcb::ATOM_WINDOW, 0, 4);
         let reply = cookie.get_reply().unwrap();
         if reply.value_len() == 0 {
             0
         }
         else {
-            let value: &xcb::Window = unsafe { mem::transmute(&(reply.value::<u8>()[0])) };
-            *value
+            reply.value::<u32>()[0]
         }
     }
 

+ 3 - 3
src/wm.rs

@@ -41,8 +41,8 @@ pub fn wm(tx: &Channel, _config: &Config) {
         conn.event_loop(&mut |event: &xcb::GenericEvent| {
             match event.response_type() {
                 xcb::PROPERTY_NOTIFY => {
-                    let prop_event: &xcb::PropertyNotifyEvent = unsafe { xcb::cast_event(event) };
-                    if prop_event.atom() == conn.atoms._NET_ACTIVE_WINDOW {
+                    let prop_event: &xcb::PropertyNotifyEvent = xcb::cast_event(event);
+                    if prop_event.atom() == conn.atom(x11::_NET_ACTIVE_WINDOW) {
                         let new_win = screen.get_active_window();
                         conn.watch(last_win, false);
                         conn.watch(new_win, true);
@@ -50,7 +50,7 @@ pub fn wm(tx: &Channel, _config: &Config) {
                         last_win = new_win;
                         comm::send(tx, "title", conn.get_window_name(last_win).as_ref());
                     }
-                    else if prop_event.atom() == conn.atoms._NET_WM_NAME {
+                    else if prop_event.atom() == conn.atom(x11::_NET_WM_NAME) {
                         comm::send(tx, "title", conn.get_window_name(last_win).as_ref());
                     }
                 },