Browse Source

Implement mpd widget

Thomas Dy 7 years ago
parent
commit
3df37be404
5 changed files with 158 additions and 2 deletions
  1. 1 0
      src/main.rs
  2. 4 0
      src/ui/mod.rs
  3. 107 0
      src/ui/music.rs
  4. 1 1
      src/ui/panel.rs
  5. 45 1
      src/ui/widget.rs

+ 1 - 0
src/main.rs

@@ -1,4 +1,5 @@
 extern crate xcb;
+extern crate mpd;
 extern crate simple_signal;
 
 extern crate freetype;

+ 4 - 0
src/ui/mod.rs

@@ -6,6 +6,7 @@ mod tray;
 mod sensors;
 mod bspwm;
 mod spacer;
+mod music;
 mod util;
 mod widget;
 mod x11;
@@ -62,6 +63,9 @@ pub fn ui_main(cfg: &Config) -> i32 {
         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();
 
         {

+ 107 - 0
src/ui/music.rs

@@ -0,0 +1,107 @@
+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();
+            },
+            _ => {}
+        }
+        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();
+    }
+}

+ 1 - 1
src/ui/panel.rs

@@ -84,7 +84,7 @@ impl Panel {
     }
 
     pub fn make_draw_context(&self) -> widget::DrawContext {
-        widget::DrawContext::new(self.conn.clone_connection(), self.picture, self.fonts.clone())
+        widget::DrawContext::new(self.conn.clone_connection(), self.window, self.picture, self.fonts.clone())
     }
 
     pub fn widgets_iter(&mut self) -> iter::Chain<slice::IterMut<Box<Widget>>, slice::IterMut<Box<Widget>>>{

+ 45 - 1
src/ui/widget.rs

@@ -1,6 +1,10 @@
+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;
@@ -12,6 +16,7 @@ pub enum Message {
     Update,
     Quit,
     BspwmEvent(String),
+    MpdEvent(mpd::status::State, String, String),
     XcbEvent(xcb::GenericEvent)
 }
 
@@ -27,14 +32,16 @@ pub trait Widget {
 
 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>, picture: xcb::render::Picture, fonts: Rc<font::FontLoader>) -> 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(
@@ -45,9 +52,11 @@ impl DrawContext {
 
         DrawContext {
             conn: conn,
+            window: window,
             picture: picture,
             pen: pen,
             fonts: fonts,
+            pen_color: color,
             bg_color: xcb::render::Color::new(0, 0, 0, 0xFFFF)
         }
     }
@@ -79,4 +88,39 @@ impl DrawContext {
             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();
+    }
 }