<commit_before>//! Code to tokenize a `BufRead` instance into an iterator of `LineType`s.

use std::io::BufRead;

use types::LineType;

/// A tokenizer is initialized with a BufReader instance that contains the
/// entire Tldr page. It then returns tokens as `Option<LineType>`.
#[derive(Debug)]
pub struct Tokenizer<R: BufRead> {
    /// An instance of `R: BufRead`.
    reader: R,
    /// Whether the first line has already been tokenized or not.
    first_line: bool,
    /// Buffer for the current line. Used internally.
    current_line: String,
}

impl<R> Tokenizer<R> where R: BufRead {
    pub fn new(reader: R) -> Tokenizer<R> {
        Tokenizer {
            reader: reader,
            first_line: true,
            current_line: String::new(),
        }
    }

    pub fn next_token(&mut self) -> Option<LineType> {
        self.current_line.clear();
        let bytes_read = self.reader.read_line(&mut self.current_line);
        match bytes_read {
            Ok(0) => None,
            Err(e) => {
                warn!("Could not read line from token reader: {:?}", e);
                None
            },
            Ok(_) => {
                // Handle new titles
                if self.first_line && !self.current_line.starts_with("#") {
                    // It's the new format! Drop next line.
                    // (Hmm, is there a way to do this without an allocation?)
                    let mut devnull = String::new();
                    if let Err(e) = self.reader.read_line(&mut devnull) {
                        warn!("Could not read line from token reader: {:?}", e);
                        return None;
                    }
                    self.first_line = false;
                    return Some(LineType::Title(self.current_line.trim_right().to_string()));
                }

                // Clear `first_line` flag
                self.first_line = false;

                // Convert line to a `LineType` instance
                Some(LineType::from(&self.current_line[..]))
            }
        }
    }
}

#[cfg(test)]
mod test {
    use super::Tokenizer;
    use types::LineType;

    #[test]
    fn test_first_line_old_format() {
        let input = "# The Title\n\n";
        let mut tokenizer = Tokenizer::new(input.as_bytes());
        let title = tokenizer.next_token().unwrap();
        assert_eq!(title, LineType::Title("The Title".to_string()));
        let empty = tokenizer.next_token().unwrap();
        assert_eq!(empty, LineType::Empty);
    }

    #[test]
    fn test_first_line_new_format() {
        let input = "The Title\n=========\n\n";
        let mut tokenizer = Tokenizer::new(input.as_bytes());
        let title = tokenizer.next_token().unwrap();
        assert_eq!(title, LineType::Title("The Title".to_string()));
        let empty = tokenizer.next_token().unwrap();
        assert_eq!(empty, LineType::Empty);
    }
}
<commit_msg>Detect and store format version<commit_after>//! Code to tokenize a `BufRead` instance into an iterator of `LineType`s.

use std::io::BufRead;

use types::LineType;

#[derive(Debug, PartialEq, Eq)]
pub enum TldrFormat {
    /// Not yet clear
    Undecided,
    /// The original format
    V1,
    /// The new format (see https://github.com/tldr-pages/tldr/pull/958)
    V2,
}

/// A tokenizer is initialized with a BufReader instance that contains the
/// entire Tldr page. It then returns tokens as `Option<LineType>`.
#[derive(Debug)]
pub struct Tokenizer<R: BufRead> {
    /// An instance of `R: BufRead`.
    reader: R,
    /// Whether the first line has already been tokenized or not.
    first_line: bool,
    /// Buffer for the current line. Used internally.
    current_line: String,
    /// The tldr page format.
    format: TldrFormat,
}

impl<R> Tokenizer<R> where R: BufRead {
    pub fn new(reader: R) -> Tokenizer<R> {
        Tokenizer {
            reader: reader,
            first_line: true,
            current_line: String::new(),
            format: TldrFormat::Undecided,
        }
    }

    pub fn next_token(&mut self) -> Option<LineType> {
        self.current_line.clear();
        let bytes_read = self.reader.read_line(&mut self.current_line);
        match bytes_read {
            Ok(0) => None,
            Err(e) => {
                warn!("Could not read line from token reader: {:?}", e);
                None
            },
            Ok(_) => {
                // Handle new titles
                if self.first_line && !self.current_line.starts_with("#") {
                    // It's the new format! Drop next line.
                    // (Hmm, is there a way to do this without an allocation?)
                    let mut devnull = String::new();
                    if let Err(e) = self.reader.read_line(&mut devnull) {
                        warn!("Could not read line from token reader: {:?}", e);
                        return None;
                    }
                    self.first_line = false;
                    self.format = TldrFormat::V2;
                    return Some(LineType::Title(self.current_line.trim_right().to_string()));
                }

                if self.first_line {
                    // Clear `first_line` flag
                    self.first_line = false;

                    // It's the old format.
                    self.format = TldrFormat::V1;
                }

                // Convert line to a `LineType` instance
                Some(LineType::from(&self.current_line[..]))
            }
        }
    }
}

#[cfg(test)]
mod test {
    use super::Tokenizer;
    use types::LineType;

    #[test]
    fn test_first_line_old_format() {
        let input = "# The Title\n\n";
        let mut tokenizer = Tokenizer::new(input.as_bytes());
        let title = tokenizer.next_token().unwrap();
        assert_eq!(title, LineType::Title("The Title".to_string()));
        let empty = tokenizer.next_token().unwrap();
        assert_eq!(empty, LineType::Empty);
    }

    #[test]
    fn test_first_line_new_format() {
        let input = "The Title\n=========\n\n";
        let mut tokenizer = Tokenizer::new(input.as_bytes());
        let title = tokenizer.next_token().unwrap();
        assert_eq!(title, LineType::Title("The Title".to_string()));
        let empty = tokenizer.next_token().unwrap();
        assert_eq!(empty, LineType::Empty);
    }
}
<|endoftext|>


<commit_before>#![crate_type = "bin"]

extern crate gtk;
extern crate glib;
extern crate sysinfo;

use gtk::{WindowTrait, ContainerTrait, WidgetTrait, ButtonSignals, BoxTrait};
use gtk::{Orientation};
use sysinfo::*;
use gtk::signal::Inhibit;
use gtk::signal::{WidgetSignals, TreeViewSignals};
use std::collections::HashMap;
use std::rc::Rc;
use std::cell::{RefCell};

struct NoteBook {
    notebook: gtk::NoteBook,
    tabs: Vec<gtk::Box>
}

impl NoteBook {
    fn new() -> NoteBook {
        NoteBook {
            notebook: gtk::NoteBook::new().unwrap(),
            tabs: Vec::new()
        }
    }

    fn create_tab<'a, Widget: gtk::WidgetTrait>(&mut self, title: &'a str, widget: &Widget) -> Option<u32> {
        let label = gtk::Label::new(title).unwrap();
        let tab = gtk::Box::new(Orientation::Horizontal, 0).unwrap();

        tab.pack_start(&label, false, false, 0);
        tab.show_all();

        let index = match self.notebook.append_page(widget, Some(&tab)) {
            Some(index) => index,
            _ => return None
        };

        self.tabs.push(tab);

        Some(index)
    }
}

struct Procs {
    left_tree: gtk::TreeView,
    scroll: gtk::ScrolledWindow,
    current_pid: Rc<RefCell<Option<i64>>>,
    kill_button: Rc<RefCell<gtk::Button>>,
    vertical_layout: gtk::Box,
    list_store: gtk::ListStore
}

impl Procs {
    pub fn new<'a>(proc_list: &HashMap<usize, Process>) -> Procs {
        let left_tree = gtk::TreeView::new().unwrap();
        let scroll = gtk::ScrolledWindow::new(None, None).unwrap();
        let current_pid = Rc::new(RefCell::new(None));
        let kill_button = Rc::new(RefCell::new(gtk::Button::new_with_label("End task").unwrap()));
        let current_pid1 = current_pid.clone();
        let current_pid2 = current_pid.clone();
        let kill_button1 = kill_button.clone();

        scroll.set_min_content_height(800);
        scroll.set_min_content_width(600);

        let mut columns : Vec<gtk::TreeViewColumn> = Vec::new();

        append_column("pid", &mut columns);
        append_column("process name", &mut columns);
        append_column("cpu usage", &mut columns);
        append_column("memory usage (in kB)", &mut columns);

        for i in columns {
            left_tree.append_column(&i);
        }

        let mut list_store = gtk::ListStore::new(&[glib::Type::ISize, glib::Type::String, glib::Type::String, glib::Type::USize]).unwrap();
        for (_, pro) in proc_list {
            create_and_fill_model(&mut list_store, pro.pid, &pro.cmd, &pro.name, pro.cpu_usage, pro.memory);
        }

        left_tree.set_model(&list_store.get_model().unwrap());
        left_tree.set_headers_visible(true);
        scroll.add(&left_tree);
        let vertical_layout = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();

        left_tree.connect_cursor_changed(move |tree_view| {
            match tree_view.get_selection() {
                Some(selection) => {
                    let model = tree_view.get_model().unwrap();
                    let mut iter = gtk::TreeIter::new();

                    if selection.get_selected(&model, &mut iter) {
                        let pid = Some(model.get_value(&iter, 0).get_long());
                        let mut tmp = current_pid1.borrow_mut();
                        *tmp = pid;
                    }
                    (*kill_button1.borrow_mut()).set_sensitive((*current_pid.borrow()).is_some());
                }
                None => {
                    let mut tmp = current_pid1.borrow_mut();
                    *tmp = None;
                }
            }
        });
        (*kill_button.borrow_mut()).set_sensitive(false);

        vertical_layout.add(&scroll);
        vertical_layout.add(&(*kill_button.borrow_mut()));
        Procs {
            left_tree: left_tree,
            scroll: scroll,
            current_pid: current_pid2.clone(),
            kill_button: kill_button,
            vertical_layout: vertical_layout,
            list_store: list_store
        }
    }
}

fn append_column(title: &str, v: &mut Vec<gtk::TreeViewColumn>) {
    let l = v.len();
    let renderer = gtk::CellRendererText::new().unwrap();

    v.push(gtk::TreeViewColumn::new().unwrap());
    let tmp = v.get_mut(l).unwrap();

    tmp.set_title(title);
    tmp.set_resizable(true);
    tmp.pack_start(&renderer, true);
    tmp.add_attribute(&renderer, "text", l as i32);
}

fn create_and_fill_model(list_store: &mut gtk::ListStore, pid: i64, cmdline: &str, name: &str, cpu: f32, memory: u64) {
    if cmdline.len() < 1 {
        return;
    }
    let mut top_level = gtk::TreeIter::new();

    let mut val1 = glib::Value::new();
    val1.init(glib::Type::ISize);
    val1.set_long(pid);
    let mut val2 = glib::Value::new();
    val2.init(glib::Type::USize);
    val2.set_ulong(memory);
    list_store.append(&mut top_level);
    list_store.set_value(&top_level, 0, &val1);
    list_store.set_string(&top_level, 1, name);
    list_store.set_string(&top_level, 2, &format!("{:.1}", cpu));
    list_store.set_value(&top_level, 3, &val2);
}

fn update_window(list: &mut gtk::ListStore, system: Rc<RefCell<sysinfo::System>>, info: Rc<RefCell<DisplaySysInfo>>) {
    let model = list.get_model().unwrap();
    let mut iter = gtk::TreeIter::new();

    (*system.borrow_mut()).refresh_all();
    let mut entries : HashMap<usize, Process> = ((*system.borrow()).get_process_list()).clone();
    let mut nb = model.iter_n_children(None);

    let sys = system.clone();
    (*info.borrow_mut()).update_ram_display(sys.clone());
    (*info.borrow_mut()).update_process_display(sys);

    let mut i = 0;
    while i < nb {
        if model.iter_nth_child(&mut iter, None, i) {
            let pid : i64 = model.get_value(&iter, 0).get_long();
            let mut to_delete = false;

            match entries.get(&(pid as usize)) {
                Some(p) => {
                    let mut val2 = glib::Value::new();
                    val2.init(glib::Type::USize);
                    val2.set_ulong(p.memory);
                    list.set_string(&iter, 2, &format!("{:.1}", p.cpu_usage));
                    list.set_value(&iter, 3, &val2);
                    to_delete = true;
                }
                None => {
                    list.remove(&iter);
                }
            }
            if to_delete {
                entries.remove(&(pid as usize));
                nb = model.iter_n_children(None);
                i += 1;
            }
        } else {
            i += 1;
        }
    }
    for (_, pro) in entries {
        create_and_fill_model(list, pro.pid, &pro.cmd, &pro.name, pro.cpu_usage, pro.memory);
    }
}

struct DisplaySysInfo {
    procs : Rc<RefCell<Vec<gtk::ProgressBar>>>,
    ram : Rc<RefCell<gtk::ProgressBar>>,
    swap : Rc<RefCell<gtk::ProgressBar>>,
    vertical_layout : gtk::Box
}

impl DisplaySysInfo {
    pub fn new(sys1: Rc<RefCell<sysinfo::System>>) -> DisplaySysInfo {
        let mut tmp = DisplaySysInfo {
            procs: Rc::new(RefCell::new(Vec::new())),
            ram: Rc::new(RefCell::new(gtk::ProgressBar::new().unwrap())),
            swap: Rc::new(RefCell::new(gtk::ProgressBar::new().unwrap())),
            vertical_layout: gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap()
        };

        let sys2 = sys1.clone();
        let v = tmp.procs.clone();

        (*tmp.ram.borrow_mut()).set_show_text(true);
        (*tmp.swap.borrow_mut()).set_show_text(true);

        tmp.vertical_layout.set_spacing(5);
        let mut i = 0;
        let mut total = false;

        tmp.vertical_layout.pack_start(&gtk::Label::new("Memory usage").unwrap(), false, false, 15);
        tmp.vertical_layout.add(&(*tmp.ram.borrow()));
        tmp.vertical_layout.pack_start(&gtk::Label::new("Swap usage").unwrap(), false, false, 15);
        tmp.vertical_layout.add(&(*tmp.swap.borrow()));
        tmp.vertical_layout.pack_start(&gtk::Label::new("Total CPU usage").unwrap(), false, false, 15);
        for pro in (*sys1.borrow()).get_processor_list() {
            if total {
                (*v.borrow_mut()).push(gtk::ProgressBar::new().unwrap());
                let p : &gtk::ProgressBar = &(*v.borrow())[i];
                let l = gtk::Label::new(&format!("{}", i)).unwrap();
                let horizontal_layout = gtk::Box::new(gtk::Orientation::Horizontal, 0).unwrap();

                p.set_text(&format!("{:.2} %", pro.get_cpu_usage() * 100.));
                p.set_show_text(true);
                p.set_fraction(pro.get_cpu_usage() as f64);
                horizontal_layout.pack_start(&l, false, false, 5);
                horizontal_layout.pack_start(p, true, true, 5);
                tmp.vertical_layout.add(&horizontal_layout);
            } else {
                (*v.borrow_mut()).push(gtk::ProgressBar::new().unwrap());
                let p : &gtk::ProgressBar = &(*v.borrow())[i];

                p.set_text(&format!("{:.2} %", pro.get_cpu_usage() * 100.));
                p.set_show_text(true);
                p.set_fraction(pro.get_cpu_usage() as f64);

                tmp.vertical_layout.add(p);
                tmp.vertical_layout.pack_start(&gtk::Label::new("Process usage").unwrap(), false, false, 15);
                total = true;
            }
            i += 1;
        }
        tmp.update_ram_display(sys2);
        tmp
    }

    pub fn update_ram_display(&mut self, sys: Rc<RefCell<sysinfo::System>>) {
        let total = (*sys.borrow()).get_total_memory();
        let used = (*sys.borrow()).get_used_memory();
        let disp = if total < 100000 {
            format!("{} / {}KB", used, total)
        } else if total < 10000000 {
            format!("{} / {}MB", used / 1000, total / 1000)
        } else if total < 10000000000 {
            format!("{} / {}GB", used / 1000000, total / 1000000)
        } else {
            format!("{} / {}TB", used / 1000000000, total / 1000000000)
        };

        (*self.ram.borrow_mut()).set_text(&disp);
        (*self.ram.borrow_mut()).set_fraction(used as f64 / total as f64);

        let total = (*sys.borrow()).get_total_swap();
        let used = total - (*sys.borrow()).get_used_swap();
        let disp = if total < 100000 {
            format!("{} / {}KB", used, total)
        } else if total < 10000000 {
            format!("{} / {}MB", used / 1000, total / 1000)
        } else if total < 10000000000 {
            format!("{} / {}GB", used / 1000000, total / 1000000)
        } else {
            format!("{} / {}TB", used / 1000000000, total / 1000000000)
        };

        (*self.swap.borrow_mut()).set_text(&disp);
        (*self.swap.borrow_mut()).set_fraction(used as f64 / total as f64);
    }

    pub fn update_process_display(&mut self, sys: Rc<RefCell<sysinfo::System>>) {
        let v = &*self.procs.borrow_mut();
        let mut i = 0;

        for pro in (*sys.borrow()).get_processor_list() {
            v[i].set_text(&format!("{:.1} %", pro.get_cpu_usage() * 100.));
            v[i].set_show_text(true);
            v[i].set_fraction(pro.get_cpu_usage() as f64);
            i += 1;
        }
    }
}

fn main() {
    gtk::init();

    let window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
    let sys : Rc<RefCell<sysinfo::System>> = Rc::new(RefCell::new(sysinfo::System::new()));
    let mut note = NoteBook::new();
    let mut procs = Procs::new((*sys.borrow()).get_process_list());
    let current_pid2 = procs.current_pid.clone();
    let sys1 = sys.clone();
    let sys2 = sys.clone();

    window.set_title("Process viewer");
    window.set_window_position(gtk::WindowPosition::Center);

    window.connect_delete_event(|_, _| {
        gtk::main_quit();
        Inhibit(true)
    });

    (*sys.borrow_mut()).refresh_all();
    (*procs.kill_button.borrow_mut()).connect_clicked(move |_| {
        let tmp = (*current_pid2.borrow_mut()).is_some() ;

        if tmp {
            let s = (*current_pid2.borrow()).clone();
            match (*sys.borrow_mut()).get_process(s.unwrap()) {
                Some(p) => {
                    p.kill(Signal::Kill);
                },
                None => {}
            };
        }
    });

    let display_tab = Rc::new(RefCell::new(DisplaySysInfo::new(sys2)));

    //window.add(&vertical_layout);
    note.create_tab("Process list", &procs.vertical_layout);
    //let t = gtk::Button::new_with_label("test").unwrap();
    note.create_tab("System usage", &(*display_tab.borrow()).vertical_layout);

    glib::timeout_add(1500, move || {
        update_window(&mut procs.list_store, sys1.clone(), display_tab.clone());
        glib::Continue(true)
    });

    window.add(&note.notebook);
    window.show_all();
    gtk::main();
}
<commit_msg>change TopLevel to Toplevel<commit_after>#![crate_type = "bin"]

extern crate gtk;
extern crate glib;
extern crate sysinfo;

use gtk::{WindowTrait, ContainerTrait, WidgetTrait, ButtonSignals, BoxTrait};
use gtk::{Orientation};
use sysinfo::*;
use gtk::signal::Inhibit;
use gtk::signal::{WidgetSignals, TreeViewSignals};
use std::collections::HashMap;
use std::rc::Rc;
use std::cell::{RefCell};

struct NoteBook {
    notebook: gtk::NoteBook,
    tabs: Vec<gtk::Box>
}

impl NoteBook {
    fn new() -> NoteBook {
        NoteBook {
            notebook: gtk::NoteBook::new().unwrap(),
            tabs: Vec::new()
        }
    }

    fn create_tab<'a, Widget: gtk::WidgetTrait>(&mut self, title: &'a str, widget: &Widget) -> Option<u32> {
        let label = gtk::Label::new(title).unwrap();
        let tab = gtk::Box::new(Orientation::Horizontal, 0).unwrap();

        tab.pack_start(&label, false, false, 0);
        tab.show_all();

        let index = match self.notebook.append_page(widget, Some(&tab)) {
            Some(index) => index,
            _ => return None
        };

        self.tabs.push(tab);

        Some(index)
    }
}

struct Procs {
    left_tree: gtk::TreeView,
    scroll: gtk::ScrolledWindow,
    current_pid: Rc<RefCell<Option<i64>>>,
    kill_button: Rc<RefCell<gtk::Button>>,
    vertical_layout: gtk::Box,
    list_store: gtk::ListStore
}

impl Procs {
    pub fn new<'a>(proc_list: &HashMap<usize, Process>) -> Procs {
        let left_tree = gtk::TreeView::new().unwrap();
        let scroll = gtk::ScrolledWindow::new(None, None).unwrap();
        let current_pid = Rc::new(RefCell::new(None));
        let kill_button = Rc::new(RefCell::new(gtk::Button::new_with_label("End task").unwrap()));
        let current_pid1 = current_pid.clone();
        let current_pid2 = current_pid.clone();
        let kill_button1 = kill_button.clone();

        scroll.set_min_content_height(800);
        scroll.set_min_content_width(600);

        let mut columns : Vec<gtk::TreeViewColumn> = Vec::new();

        append_column("pid", &mut columns);
        append_column("process name", &mut columns);
        append_column("cpu usage", &mut columns);
        append_column("memory usage (in kB)", &mut columns);

        for i in columns {
            left_tree.append_column(&i);
        }

        let mut list_store = gtk::ListStore::new(&[glib::Type::ISize, glib::Type::String, glib::Type::String, glib::Type::USize]).unwrap();
        for (_, pro) in proc_list {
            create_and_fill_model(&mut list_store, pro.pid, &pro.cmd, &pro.name, pro.cpu_usage, pro.memory);
        }

        left_tree.set_model(&list_store.get_model().unwrap());
        left_tree.set_headers_visible(true);
        scroll.add(&left_tree);
        let vertical_layout = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();

        left_tree.connect_cursor_changed(move |tree_view| {
            match tree_view.get_selection() {
                Some(selection) => {
                    let model = tree_view.get_model().unwrap();
                    let mut iter = gtk::TreeIter::new();

                    if selection.get_selected(&model, &mut iter) {
                        let pid = Some(model.get_value(&iter, 0).get_long());
                        let mut tmp = current_pid1.borrow_mut();
                        *tmp = pid;
                    }
                    (*kill_button1.borrow_mut()).set_sensitive((*current_pid.borrow()).is_some());
                }
                None => {
                    let mut tmp = current_pid1.borrow_mut();
                    *tmp = None;
                }
            }
        });
        (*kill_button.borrow_mut()).set_sensitive(false);

        vertical_layout.add(&scroll);
        vertical_layout.add(&(*kill_button.borrow_mut()));
        Procs {
            left_tree: left_tree,
            scroll: scroll,
            current_pid: current_pid2.clone(),
            kill_button: kill_button,
            vertical_layout: vertical_layout,
            list_store: list_store
        }
    }
}

fn append_column(title: &str, v: &mut Vec<gtk::TreeViewColumn>) {
    let l = v.len();
    let renderer = gtk::CellRendererText::new().unwrap();

    v.push(gtk::TreeViewColumn::new().unwrap());
    let tmp = v.get_mut(l).unwrap();

    tmp.set_title(title);
    tmp.set_resizable(true);
    tmp.pack_start(&renderer, true);
    tmp.add_attribute(&renderer, "text", l as i32);
}

fn create_and_fill_model(list_store: &mut gtk::ListStore, pid: i64, cmdline: &str, name: &str, cpu: f32, memory: u64) {
    if cmdline.len() < 1 {
        return;
    }
    let mut top_level = gtk::TreeIter::new();

    let mut val1 = glib::Value::new();
    val1.init(glib::Type::ISize);
    val1.set_long(pid);
    let mut val2 = glib::Value::new();
    val2.init(glib::Type::USize);
    val2.set_ulong(memory);
    list_store.append(&mut top_level);
    list_store.set_value(&top_level, 0, &val1);
    list_store.set_string(&top_level, 1, name);
    list_store.set_string(&top_level, 2, &format!("{:.1}", cpu));
    list_store.set_value(&top_level, 3, &val2);
}

fn update_window(list: &mut gtk::ListStore, system: Rc<RefCell<sysinfo::System>>, info: Rc<RefCell<DisplaySysInfo>>) {
    let model = list.get_model().unwrap();
    let mut iter = gtk::TreeIter::new();

    (*system.borrow_mut()).refresh_all();
    let mut entries : HashMap<usize, Process> = ((*system.borrow()).get_process_list()).clone();
    let mut nb = model.iter_n_children(None);

    let sys = system.clone();
    (*info.borrow_mut()).update_ram_display(sys.clone());
    (*info.borrow_mut()).update_process_display(sys);

    let mut i = 0;
    while i < nb {
        if model.iter_nth_child(&mut iter, None, i) {
            let pid : i64 = model.get_value(&iter, 0).get_long();
            let mut to_delete = false;

            match entries.get(&(pid as usize)) {
                Some(p) => {
                    let mut val2 = glib::Value::new();
                    val2.init(glib::Type::USize);
                    val2.set_ulong(p.memory);
                    list.set_string(&iter, 2, &format!("{:.1}", p.cpu_usage));
                    list.set_value(&iter, 3, &val2);
                    to_delete = true;
                }
                None => {
                    list.remove(&iter);
                }
            }
            if to_delete {
                entries.remove(&(pid as usize));
                nb = model.iter_n_children(None);
                i += 1;
            }
        } else {
            i += 1;
        }
    }
    for (_, pro) in entries {
        create_and_fill_model(list, pro.pid, &pro.cmd, &pro.name, pro.cpu_usage, pro.memory);
    }
}

struct DisplaySysInfo {
    procs : Rc<RefCell<Vec<gtk::ProgressBar>>>,
    ram : Rc<RefCell<gtk::ProgressBar>>,
    swap : Rc<RefCell<gtk::ProgressBar>>,
    vertical_layout : gtk::Box
}

impl DisplaySysInfo {
    pub fn new(sys1: Rc<RefCell<sysinfo::System>>) -> DisplaySysInfo {
        let mut tmp = DisplaySysInfo {
            procs: Rc::new(RefCell::new(Vec::new())),
            ram: Rc::new(RefCell::new(gtk::ProgressBar::new().unwrap())),
            swap: Rc::new(RefCell::new(gtk::ProgressBar::new().unwrap())),
            vertical_layout: gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap()
        };

        let sys2 = sys1.clone();
        let v = tmp.procs.clone();

        (*tmp.ram.borrow_mut()).set_show_text(true);
        (*tmp.swap.borrow_mut()).set_show_text(true);

        tmp.vertical_layout.set_spacing(5);
        let mut i = 0;
        let mut total = false;

        tmp.vertical_layout.pack_start(&gtk::Label::new("Memory usage").unwrap(), false, false, 15);
        tmp.vertical_layout.add(&(*tmp.ram.borrow()));
        tmp.vertical_layout.pack_start(&gtk::Label::new("Swap usage").unwrap(), false, false, 15);
        tmp.vertical_layout.add(&(*tmp.swap.borrow()));
        tmp.vertical_layout.pack_start(&gtk::Label::new("Total CPU usage").unwrap(), false, false, 15);
        for pro in (*sys1.borrow()).get_processor_list() {
            if total {
                (*v.borrow_mut()).push(gtk::ProgressBar::new().unwrap());
                let p : &gtk::ProgressBar = &(*v.borrow())[i];
                let l = gtk::Label::new(&format!("{}", i)).unwrap();
                let horizontal_layout = gtk::Box::new(gtk::Orientation::Horizontal, 0).unwrap();

                p.set_text(&format!("{:.2} %", pro.get_cpu_usage() * 100.));
                p.set_show_text(true);
                p.set_fraction(pro.get_cpu_usage() as f64);
                horizontal_layout.pack_start(&l, false, false, 5);
                horizontal_layout.pack_start(p, true, true, 5);
                tmp.vertical_layout.add(&horizontal_layout);
            } else {
                (*v.borrow_mut()).push(gtk::ProgressBar::new().unwrap());
                let p : &gtk::ProgressBar = &(*v.borrow())[i];

                p.set_text(&format!("{:.2} %", pro.get_cpu_usage() * 100.));
                p.set_show_text(true);
                p.set_fraction(pro.get_cpu_usage() as f64);

                tmp.vertical_layout.add(p);
                tmp.vertical_layout.pack_start(&gtk::Label::new("Process usage").unwrap(), false, false, 15);
                total = true;
            }
            i += 1;
        }
        tmp.update_ram_display(sys2);
        tmp
    }

    pub fn update_ram_display(&mut self, sys: Rc<RefCell<sysinfo::System>>) {
        let total = (*sys.borrow()).get_total_memory();
        let used = (*sys.borrow()).get_used_memory();
        let disp = if total < 100000 {
            format!("{} / {}KB", used, total)
        } else if total < 10000000 {
            format!("{} / {}MB", used / 1000, total / 1000)
        } else if total < 10000000000 {
            format!("{} / {}GB", used / 1000000, total / 1000000)
        } else {
            format!("{} / {}TB", used / 1000000000, total / 1000000000)
        };

        (*self.ram.borrow_mut()).set_text(&disp);
        (*self.ram.borrow_mut()).set_fraction(used as f64 / total as f64);

        let total = (*sys.borrow()).get_total_swap();
        let used = total - (*sys.borrow()).get_used_swap();
        let disp = if total < 100000 {
            format!("{} / {}KB", used, total)
        } else if total < 10000000 {
            format!("{} / {}MB", used / 1000, total / 1000)
        } else if total < 10000000000 {
            format!("{} / {}GB", used / 1000000, total / 1000000)
        } else {
            format!("{} / {}TB", used / 1000000000, total / 1000000000)
        };

        (*self.swap.borrow_mut()).set_text(&disp);
        (*self.swap.borrow_mut()).set_fraction(used as f64 / total as f64);
    }

    pub fn update_process_display(&mut self, sys: Rc<RefCell<sysinfo::System>>) {
        let v = &*self.procs.borrow_mut();
        let mut i = 0;

        for pro in (*sys.borrow()).get_processor_list() {
            v[i].set_text(&format!("{:.1} %", pro.get_cpu_usage() * 100.));
            v[i].set_show_text(true);
            v[i].set_fraction(pro.get_cpu_usage() as f64);
            i += 1;
        }
    }
}

fn main() {
    gtk::init();

    let window = gtk::Window::new(gtk::WindowType::Toplevel).unwrap();
    let sys : Rc<RefCell<sysinfo::System>> = Rc::new(RefCell::new(sysinfo::System::new()));
    let mut note = NoteBook::new();
    let mut procs = Procs::new((*sys.borrow()).get_process_list());
    let current_pid2 = procs.current_pid.clone();
    let sys1 = sys.clone();
    let sys2 = sys.clone();

    window.set_title("Process viewer");
    window.set_window_position(gtk::WindowPosition::Center);

    window.connect_delete_event(|_, _| {
        gtk::main_quit();
        Inhibit(true)
    });

    (*sys.borrow_mut()).refresh_all();
    (*procs.kill_button.borrow_mut()).connect_clicked(move |_| {
        let tmp = (*current_pid2.borrow_mut()).is_some() ;

        if tmp {
            let s = (*current_pid2.borrow()).clone();
            match (*sys.borrow_mut()).get_process(s.unwrap()) {
                Some(p) => {
                    p.kill(Signal::Kill);
                },
                None => {}
            };
        }
    });

    let display_tab = Rc::new(RefCell::new(DisplaySysInfo::new(sys2)));

    //window.add(&vertical_layout);
    note.create_tab("Process list", &procs.vertical_layout);
    //let t = gtk::Button::new_with_label("test").unwrap();
    note.create_tab("System usage", &(*display_tab.borrow()).vertical_layout);

    glib::timeout_add(1500, move || {
        update_window(&mut procs.list_store, sys1.clone(), display_tab.clone());
        glib::Continue(true)
    });

    window.add(&note.notebook);
    window.show_all();
    gtk::main();
}
<|endoftext|>


<commit_before>//! The YAML parser. Here is where we convert the yaml in to commands to be
/// processed later.

use yaml_rust::Yaml;
use command::{Command, Session, SendKeys, Split, Layout, Window, Attach};

#[cfg(test)] use yaml_rust::{YamlLoader};

/// Here was pass in the parsed yaml and project name. The purpose of this main
/// loop is to build the stack of commands that are run to setup a users tmux
/// session.
///
/// yaml_string: The parsed yaml from the config file.
/// project_name: The name of the project.
pub fn main(yaml_string: &Vec<Yaml>, project_name: &String, daemonize: bool) -> Result<Vec<Command>, String> {
    let mut commands: Vec<Command> = vec!();

    // There should only be one doc but it's a vec so loop it.
    for doc in yaml_string {
        let root = match doc["root"].as_str() {
            Some(x) => Some(x.to_string()),
            None    => None
        };

        let pre = match doc["pre"].as_str() {
            Some(x) => Some(x.to_string()),
            None    => None
        };

        // A clojure used to capture the current local root and pre Options.
        // This way we can call the clojure to create common SendKeys command
        // like changing the directory or executing a system command from the
        // `pre` option.
        let common_commands = |target: String| -> Vec<Command> {
            let mut commands2 = vec!();

            // SendKeys to change to the `root` directory
            if root.is_some() {
                let r = root.clone().unwrap();
                commands2.push(Command::SendKeys(SendKeys{
                    target: target.clone(),
                    exec: format!("cd {}", r)
                }));
            };

            // SendKeys for the Pre option
            if pre.is_some() {
                let p = pre.clone().unwrap();
                commands2.push(Command::SendKeys(SendKeys{
                    target: target.clone(),
                    exec: p
                }));
            };

            commands2
        };

        let windows = doc["windows"].as_vec().expect("No Windows have been defined.");

        for window in windows.iter() {
            match window {
                &Yaml::Hash(ref h)  => {
                    for (k, v) in h {
                        if v.as_hash().is_some() {
                            commands.push(Command::Window(Window{
                                session_name: project_name.clone(),
                                name: k.as_str().unwrap().to_string()
                            }));

                            if root.is_some() {
                              let r = root.clone().unwrap();
                              commands.push(Command::SendKeys(SendKeys{
                                  target: format!("{}:{}", project_name, k.as_str().unwrap().to_string()).to_string(),
                                  exec: format!("cd {}", r)
                              }));
                            };

                            commands.append(&mut try!(pane_matcher(&project_name, v, k.as_str().unwrap().to_string(), &pre, &root)));
                        } else {
                            commands.push(Command::Window(Window{
                                session_name: project_name.clone(),
                                name: try!(k.as_str().ok_or_else(|| "Windows require being named in your config.").map(|x| x.to_string()))
                            }));

                            let t = format!("{}:{}", project_name, k.as_str().unwrap().to_string()).to_string();
                            commands.append(&mut common_commands(t.to_string()));

                            // SendKeys for the exec command
                            if v.as_str().is_some() {
                                let ex = v.as_str().unwrap();
                                if !ex.is_empty() {
                                    commands.push(Command::SendKeys(SendKeys{
                                        target: format!("{}:{}", project_name, k.as_str().unwrap().to_string()).to_string(),
                                        exec: v.as_str().unwrap().to_string()
                                    }));
                                };
                            }
                        }
                    }
                },
                &Yaml::String(ref s) => {
                    commands.push(Command::Window(Window{
                        session_name: project_name.clone(),
                        name: s.clone()
                    }));

                    let t = format!("{}:{}", &project_name, &s);
                    commands.append(&mut common_commands(t.to_string()));
                },
                &Yaml::Integer(ref s) => {
                    commands.push(Command::Window(Window{
                        session_name: project_name.clone(),
                        name: s.to_string()
                    }));

                    let t = format!("{}:{}", &project_name, &s);
                    commands.append(&mut common_commands(t.to_string()));
                },
                _ => panic!("Muxed config file formatting isn't recognized.")
            };
        };
    };

    if !daemonize { commands.push(Command::Attach(Attach{name: project_name.clone()})) };

    let (first, commands) = commands.split_first().unwrap();
    let mut remains = commands.to_vec();

    match first {
        &Command::Window(ref w) => {
            remains.insert(0, Command::Session(Session{
                name: project_name.clone(),
                window_name: w.name.clone()
            }));
        },
        _ => {}
    };

    Ok(remains)
}

/// Pane matcher is for breaking apart the panes. Splitting windows when needed
/// and executing commands as needed.
fn pane_matcher(session: &String, window: &Yaml, window_name: String, pre: &Option<String>, root: &Option<String>) -> Result<Vec<Command>, String> {
    let mut commands = vec!();
    let panes = window["panes"].as_vec().expect("Something is wrong with panes.");

    for (i, pane) in panes.iter().enumerate() {
        // For every pane, we need one less split.
        // ex. An existing window to become 2 panes, needs 1 split.
        if i < (panes.len()-1) {
            commands.push(Command::Split(Split{
                target: format!("{}:{}.{}", session, window_name, i).to_string()
            }));
        };

        // SendKeys for the Pre option
        if pre.is_some() {
            let p = pre.clone().unwrap();
            commands.push(Command::SendKeys(SendKeys{
                target: format!("{}:{}.{}", session, window_name, i).to_string(),
                exec: p
            }));
        };

        // Execute given commands in each new pane after all splits are
        // complete.
        if pane.as_str().is_some() {
            let p = pane.as_str().unwrap();
            if !p.is_empty() {
                commands.push(Command::SendKeys(SendKeys{
                    target: format!("{}:{}.{}", session, window_name, i).to_string(),
                    exec: p.to_string()
                }));
            };
        };
    };

    // After all panes are split select the layout for the window
    if window["layout"].as_str().is_some() {
        let err = format!("A problem with the specified layout for the window: {}", &window_name);
        let layout = window["layout"].as_str().expect(err.as_str()).to_string();
        commands.push(Command::Layout(Layout{
            target: format!("{}:{}", session, window_name).to_string(),
            layout: layout
        }));
    };

    Ok(commands)
}

#[test]
pub fn expect_1_session() {
    let s = "---
windows: ['cargo', 'vim', 'git']
";
    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::Session(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 1)
}

#[test]
pub fn expect_2_windows_from_array() {
    let s = "---
windows: ['cargo', 'vim', 'git']
";
    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::Window(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 2)
}

#[test]
pub fn expect_1_attach() {
    let s = "---
windows: ['cargo', 'vim', 'git']
";
    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::Attach(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 1)
}

#[test]
pub fn expect_2_windows_with_mixed_type_names() {
    let s = "---
windows: [1, 'vim', 3]
";
    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::Window(_) => true,
        _ => false
    }).collect();
    assert_eq!(remains.len(), 2)
}

#[test]
pub fn expect_2_windows_from_list() {
    let s = "---
windows:
  - cargo: ''
  - vim: ''
  - git: ''
";
    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::Window(_) => true,
        _ => false
    }).collect();
    assert_eq!(remains.len(), 2)
}

#[test]
pub fn expect_ok_with_empty_syscommands() {
    let s = "---
windows:
  - editor:
";
    let yaml = YamlLoader::load_from_str(s).unwrap();
    let result = main(&yaml, &"muxed".to_string(), false);
    assert!(result.is_ok())
}

#[test]
pub fn expect_no_send_keys_commands() {
    let s = "---
windows:
  - editor:
";

    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::SendKeys(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 0)
}

#[test]
pub fn expect_err_with_nameless_window() {
    let s = "---
windows:
  - : ls
";
    let yaml = YamlLoader::load_from_str(s).unwrap();
    let result = main(&yaml, &"muxed".to_string(), false);
    assert!(result.is_err())
}

#[test]
pub fn expect_ok_with_empty_panes_syscommands() {
    let s = "---
windows:
  - cargo:
      layout: 'main-vertical'
      panes:
        -
";
    let yaml = YamlLoader::load_from_str(s).unwrap();
    let result = main(&yaml, &"muxed".to_string(), false);
    assert!(result.is_ok())
}

#[test]
pub fn expect_no_send_keys_with_empty_panes_syscommands() {
    let s = "---
windows:
  - editor:
      panes:
        -
";

    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::SendKeys(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 0)
}

#[test]
pub fn expect_1_split_window() {
    let s = "---
windows:
  - editor:
      layout: 'main-vertical'
      panes: ['vim', 'guard']
";

    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::Split(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 1)
}

#[test]
pub fn expect_1_layout() {
    let s = "---
windows:
  - editor:
      layout: 'main-vertical'
      panes: ['vim', 'guard']
";

    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::Layout(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 1)
}

#[test]
pub fn expect_1_session_with_panes_array() {
    let s = "---
windows:
  - editor:
      layout: 'main-vertical'
      panes: ['vim', 'guard']
";

    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::Session(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 1)
}

#[test]
pub fn expect_no_layout() {
    let s = "---
windows:
  - editor:
      panes: ['vim', 'guard']
";

    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::Layout(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 0)
}

#[test]
pub fn expect_three_send_keys_commands_from_pre() {
    let s = "---
pre: 'ls'
windows:
  - editor:
      panes:
        -
        -
  - logs:
";
    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::SendKeys(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 3)
}

#[test]
pub fn expect_no_send_keys_with_blank_panes() {
    let s = "---
windows:
  - editor:
      panes: ['','','']
";

    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::SendKeys(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 0)
}

#[test]
pub fn expect_no_send_keys_with_blank_window() {
    let s = "---
windows:
  - editor: ''
";

    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::SendKeys(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 0)
}
<commit_msg>Issue #3 Pass the clojure to pane matcher.<commit_after>//! The YAML parser. Here is where we convert the yaml in to commands to be
/// processed later.

use yaml_rust::Yaml;
use command::{Command, Session, SendKeys, Split, Layout, Window, Attach};

#[cfg(test)] use yaml_rust::{YamlLoader};

/// Here was pass in the parsed yaml and project name. The purpose of this main
/// loop is to build the stack of commands that are run to setup a users tmux
/// session.
///
/// yaml_string: The parsed yaml from the config file.
/// project_name: The name of the project.
pub fn main(yaml_string: &Vec<Yaml>, project_name: &String, daemonize: bool) -> Result<Vec<Command>, String> {
    let mut commands: Vec<Command> = vec!();

    // There should only be one doc but it's a vec so loop it.
    for doc in yaml_string {
        let root = match doc["root"].as_str() {
            Some(x) => Some(x.to_string()),
            None    => None
        };

        let pre = match doc["pre"].as_str() {
            Some(x) => Some(x.to_string()),
            None    => None
        };

        // A clojure used to capture the current local root and pre Options.
        // This way we can call the clojure to create common SendKeys command
        // like changing the directory or executing a system command from the
        // `pre` option.
        let common_commands = |target: String| -> Vec<Command> {
            let mut commands2 = vec!();

            // SendKeys to change to the `root` directory
            if root.is_some() {
                let r = root.clone().unwrap();
                commands2.push(Command::SendKeys(SendKeys{
                    target: target.clone(),
                    exec: format!("cd {}", r)
                }));
            };

            // SendKeys for the Pre option
            if pre.is_some() {
                let p = pre.clone().unwrap();
                commands2.push(Command::SendKeys(SendKeys{
                    target: target.clone(),
                    exec: p
                }));
            };

            commands2
        };

        let windows = doc["windows"].as_vec().expect("No Windows have been defined.");

        for window in windows.iter() {
            match window {
                &Yaml::Hash(ref h)  => {
                    for (k, v) in h {
                        if v.as_hash().is_some() {
                            commands.push(Command::Window(Window{
                                session_name: project_name.clone(),
                                name: k.as_str().unwrap().to_string()
                            }));

                            commands.append(&mut try!(pane_matcher(&project_name, v, k.as_str().unwrap().to_string(), &common_commands)));
                        } else {
                            commands.push(Command::Window(Window{
                                session_name: project_name.clone(),
                                name: try!(k.as_str().ok_or_else(|| "Windows require being named in your config.").map(|x| x.to_string()))
                            }));

                            let t = format!("{}:{}", project_name, k.as_str().unwrap().to_string()).to_string();
                            commands.append(&mut common_commands(t.to_string()));

                            // SendKeys for the exec command
                            if v.as_str().is_some() {
                                let ex = v.as_str().unwrap();
                                if !ex.is_empty() {
                                    commands.push(Command::SendKeys(SendKeys{
                                        target: format!("{}:{}", project_name, k.as_str().unwrap().to_string()).to_string(),
                                        exec: v.as_str().unwrap().to_string()
                                    }));
                                };
                            }
                        }
                    }
                },
                &Yaml::String(ref s) => {
                    commands.push(Command::Window(Window{
                        session_name: project_name.clone(),
                        name: s.clone()
                    }));

                    let t = format!("{}:{}", &project_name, &s);
                    commands.append(&mut common_commands(t.to_string()));
                },
                &Yaml::Integer(ref s) => {
                    commands.push(Command::Window(Window{
                        session_name: project_name.clone(),
                        name: s.to_string()
                    }));

                    let t = format!("{}:{}", &project_name, &s);
                    commands.append(&mut common_commands(t.to_string()));
                },
                _ => panic!("Muxed config file formatting isn't recognized.")
            };
        };
    };

    if !daemonize { commands.push(Command::Attach(Attach{name: project_name.clone()})) };

    let (first, commands) = commands.split_first().unwrap();
    let mut remains = commands.to_vec();

    match first {
        &Command::Window(ref w) => {
            remains.insert(0, Command::Session(Session{
                name: project_name.clone(),
                window_name: w.name.clone()
            }));
        },
        _ => {}
    };

    Ok(remains)
}

/// Pane matcher is for breaking apart the panes. Splitting windows when needed
/// and executing commands as needed.
fn pane_matcher(session: &String, window: &Yaml, window_name: String, common_commands: (&Fn(String) -> Vec<Command>)) -> Result<Vec<Command>, String> {
    let mut commands = vec!();
    let panes = window["panes"].as_vec().expect("Something is wrong with panes.");

    for (i, pane) in panes.iter().enumerate() {
        // For every pane, we need one less split.
        // ex. An existing window to become 2 panes, needs 1 split.
        if i < (panes.len()-1) {
            commands.push(Command::Split(Split{
                target: format!("{}:{}.{}", session, window_name, i).to_string()
            }));
        };

        // Call the common_commands clojure to execute `cd` and `pre` options in
        // pane splits.
        let t = format!("{}:{}.{}", session, window_name, i);
        commands.append(&mut common_commands(t.to_string()));

        // Execute given commands in each new pane after all splits are
        // complete.
        if pane.as_str().is_some() {
            let p = pane.as_str().unwrap();
            if !p.is_empty() {
                commands.push(Command::SendKeys(SendKeys{
                    target: format!("{}:{}.{}", session, window_name, i).to_string(),
                    exec: p.to_string()
                }));
            };
        };
    };

    // After all panes are split select the layout for the window
    if window["layout"].as_str().is_some() {
        let err = format!("A problem with the specified layout for the window: {}", &window_name);
        let layout = window["layout"].as_str().expect(err.as_str()).to_string();
        commands.push(Command::Layout(Layout{
            target: format!("{}:{}", session, window_name).to_string(),
            layout: layout
        }));
    };

    Ok(commands)
}

#[test]
pub fn expect_1_session() {
    let s = "---
windows: ['cargo', 'vim', 'git']
";
    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::Session(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 1)
}

#[test]
pub fn expect_2_windows_from_array() {
    let s = "---
windows: ['cargo', 'vim', 'git']
";
    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::Window(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 2)
}

#[test]
pub fn expect_1_attach() {
    let s = "---
windows: ['cargo', 'vim', 'git']
";
    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::Attach(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 1)
}

#[test]
pub fn expect_2_windows_with_mixed_type_names() {
    let s = "---
windows: [1, 'vim', 3]
";
    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::Window(_) => true,
        _ => false
    }).collect();
    assert_eq!(remains.len(), 2)
}

#[test]
pub fn expect_2_windows_from_list() {
    let s = "---
windows:
  - cargo: ''
  - vim: ''
  - git: ''
";
    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::Window(_) => true,
        _ => false
    }).collect();
    assert_eq!(remains.len(), 2)
}

#[test]
pub fn expect_ok_with_empty_syscommands() {
    let s = "---
windows:
  - editor:
";
    let yaml = YamlLoader::load_from_str(s).unwrap();
    let result = main(&yaml, &"muxed".to_string(), false);
    assert!(result.is_ok())
}

#[test]
pub fn expect_no_send_keys_commands() {
    let s = "---
windows:
  - editor:
";

    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::SendKeys(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 0)
}

#[test]
pub fn expect_err_with_nameless_window() {
    let s = "---
windows:
  - : ls
";
    let yaml = YamlLoader::load_from_str(s).unwrap();
    let result = main(&yaml, &"muxed".to_string(), false);
    assert!(result.is_err())
}

#[test]
pub fn expect_ok_with_empty_panes_syscommands() {
    let s = "---
windows:
  - cargo:
      layout: 'main-vertical'
      panes:
        -
";
    let yaml = YamlLoader::load_from_str(s).unwrap();
    let result = main(&yaml, &"muxed".to_string(), false);
    assert!(result.is_ok())
}

#[test]
pub fn expect_no_send_keys_with_empty_panes_syscommands() {
    let s = "---
windows:
  - editor:
      panes:
        -
";

    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::SendKeys(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 0)
}

#[test]
pub fn expect_1_split_window() {
    let s = "---
windows:
  - editor:
      layout: 'main-vertical'
      panes: ['vim', 'guard']
";

    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::Split(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 1)
}

#[test]
pub fn expect_1_layout() {
    let s = "---
windows:
  - editor:
      layout: 'main-vertical'
      panes: ['vim', 'guard']
";

    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::Layout(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 1)
}

#[test]
pub fn expect_1_session_with_panes_array() {
    let s = "---
windows:
  - editor:
      layout: 'main-vertical'
      panes: ['vim', 'guard']
";

    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::Session(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 1)
}

#[test]
pub fn expect_no_layout() {
    let s = "---
windows:
  - editor:
      panes: ['vim', 'guard']
";

    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::Layout(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 0)
}

#[test]
pub fn expect_three_send_keys_commands_from_pre() {
    let s = "---
pre: 'ls'
windows:
  - editor:
      panes:
        -
        -
  - logs:
";
    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::SendKeys(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 3)
}

#[test]
pub fn expect_no_send_keys_with_blank_panes() {
    let s = "---
windows:
  - editor:
      panes: ['','','']
";

    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::SendKeys(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 0)
}

#[test]
pub fn expect_no_send_keys_with_blank_window() {
    let s = "---
windows:
  - editor: ''
";

    let yaml = YamlLoader::load_from_str(s).unwrap();
    let remains: Vec<Command> = main(&yaml, &"muxed".to_string(), false).unwrap().into_iter().filter(|x| match x {
        &Command::SendKeys(_) => true,
        _ => false
    }).collect();

    assert_eq!(remains.len(), 0)
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use super::bridge_converter::{Api, Use};
use crate::{
    additional_cpp_generator::AdditionalNeed,
    types::{make_ident, Namespace},
};
use std::collections::HashSet;
use syn::{parse_quote, ForeignItem};

/// Adds items which we always add, cos they're useful.
pub(crate) fn generate_utilities(apis: &mut Vec<Api>) {
    // Unless we've been specifically asked not to do so, we always
    // generate a 'make_string' function. That pretty much *always* means
    // we run two passes through bindgen. i.e. the next 'if' is always true,
    // and we always generate an additional C++ file for our bindings additions,
    // unless the include_cpp macro has specified ExcludeUtilities.
    apis.push(Api {
        ns: Namespace::new(),
        id: make_ident("make_string"),
        use_stmt: Use::Used,
        extern_c_mod_item: Some(ForeignItem::Fn(parse_quote!(
            fn make_string(str_: &str) -> UniquePtr<CxxString>;
        ))),
        additional_cpp: Some(AdditionalNeed::MakeStringConstructor),
        deps: HashSet::new(),
        bridge_item: None,
        global_item: None,
        id_for_allowlist: None,
        bindgen_mod_item: None,
    });
}
<commit_msg>Add comment.<commit_after>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use super::bridge_converter::{Api, Use};
use crate::{
    additional_cpp_generator::AdditionalNeed,
    types::{make_ident, Namespace},
};
use std::collections::HashSet;
use syn::{parse_quote, ForeignItem};

/// Adds items which we always add, cos they're useful.
/// Any APIs or techniques which do not involve actual C++ interop
/// shouldn't go here, but instead should go into the main autocxx
/// src/lib.rs.
pub(crate) fn generate_utilities(apis: &mut Vec<Api>) {
    // Unless we've been specifically asked not to do so, we always
    // generate a 'make_string' function. That pretty much *always* means
    // we run two passes through bindgen. i.e. the next 'if' is always true,
    // and we always generate an additional C++ file for our bindings additions,
    // unless the include_cpp macro has specified ExcludeUtilities.
    apis.push(Api {
        ns: Namespace::new(),
        id: make_ident("make_string"),
        use_stmt: Use::Used,
        extern_c_mod_item: Some(ForeignItem::Fn(parse_quote!(
            fn make_string(str_: &str) -> UniquePtr<CxxString>;
        ))),
        additional_cpp: Some(AdditionalNeed::MakeStringConstructor),
        deps: HashSet::new(),
        bridge_item: None,
        global_item: None,
        id_for_allowlist: None,
        bindgen_mod_item: None,
    });
}
<|endoftext|>


<commit_before>use {Backend, QueueFamily};
use device::{Device, PhysicalDevice};
use internal::Channel;
use native;

use std::sync::Arc;

use hal::{self, format, image};
use hal::{Backbuffer, SwapchainConfig};
use hal::window::Extent2D;

use core_graphics::geometry::{CGRect, CGSize};
use foreign_types::{ForeignType, ForeignTypeRef};
use parking_lot::{Mutex, MutexGuard};
use metal;
use objc::rc::autoreleasepool;
use objc::runtime::Object;

pub type CAMetalLayer = *mut Object;

pub struct Surface {
    pub(crate) inner: Arc<SurfaceInner>,
    pub(crate) has_swapchain: bool
}

#[derive(Debug)]
pub(crate) struct SurfaceInner {
    pub(crate) view: *mut Object,
    pub(crate) render_layer: Mutex<CAMetalLayer>,
}

unsafe impl Send for SurfaceInner {}
unsafe impl Sync for SurfaceInner {}

impl Drop for SurfaceInner {
    fn drop(&mut self) {
        unsafe { msg_send![self.view, release]; }
    }
}

impl SurfaceInner {
    fn next_frame<'a>(&self, frames: &'a [Frame]) -> Result<(usize, MutexGuard<'a, FrameInner>), ()> {
        let layer_ref = self.render_layer.lock();
        autoreleasepool(|| { // for the drawable
            let (drawable, texture_temp): (&metal::DrawableRef, &metal::TextureRef) = unsafe {
                let drawable = msg_send![*layer_ref, nextDrawable];
                (drawable, msg_send![drawable, texture])
            };

            trace!("looking for {:?}", texture_temp);
            match frames.iter().position(|f| f.texture.as_ptr() == texture_temp.as_ptr()) {
                Some(index) => {
                    let mut frame = frames[index].inner.lock();
                    assert!(frame.drawable.is_none());
                    frame.drawable = Some(drawable.to_owned());

                    debug!("next is frame[{}]", index);
                    Ok((index, frame))
                }
                None => Err(()),
            }
        })
    }
}

#[derive(Debug)]
struct FrameInner {
    drawable: Option<metal::Drawable>,
    /// If there is a `drawable`, availability indicates if it's free for grabs.
    /// If there is `None`, `available == false` means that the frame has already
    /// been acquired and the `drawable` will appear at some point.
    available: bool,
    last_frame: usize,
}

#[derive(Debug)]
struct Frame {
    inner: Mutex<FrameInner>,
    texture: metal::Texture,
}

unsafe impl Send for Frame {}
unsafe impl Sync for Frame {}

impl Drop for Frame {
    fn drop(&mut self) {
        info!("dropping Frame");
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AcquireMode {
    Wait,
    Oldest,
}

pub struct Swapchain {
    frames: Arc<Vec<Frame>>,
    surface: Arc<SurfaceInner>,
    extent: Extent2D,
    last_frame: usize,
    image_ready_callbacks: Vec<Arc<Mutex<Option<SwapchainImage>>>>,
    pub acquire_mode: AcquireMode,
}

impl Drop for Swapchain {
    fn drop(&mut self) {
        info!("dropping Swapchain");
        for ir in self.image_ready_callbacks.drain(..) {
            if ir.lock().take().is_some() {
                debug!("\twith a callback");
            }
        }
    }
}

impl Swapchain {
    /// Returns the drawable for the specified swapchain image index,
    /// marks the index as free for future use.
    pub(crate) fn take_drawable(&self, index: hal::SwapImageIndex) -> metal::Drawable {
        let mut frame = self
            .frames[index as usize]
            .inner
            .lock();
        assert!(!frame.available);
        frame.available = true;
        frame.drawable
            .take()
            .expect("Drawable has not been acquired!")
    }

    fn signal_sync(&self, sync: hal::FrameSync<Backend>) {
        match sync {
            hal::FrameSync::Semaphore(semaphore) => {
                if let Some(ref system) = semaphore.system {
                    system.signal();
                }
            }
            hal::FrameSync::Fence(fence) => {
                *fence.0.borrow_mut() = native::FenceInner::Idle { signaled: true };
            }
        }
    }
}

#[derive(Debug)]
pub struct SwapchainImage {
    frames: Arc<Vec<Frame>>,
    surface: Arc<SurfaceInner>,
    index: hal::SwapImageIndex,
}

impl SwapchainImage {
    /// Waits until the specified swapchain index is available for rendering.
    /// Returns the number of frames it had to wait.
    pub fn wait_until_ready(&self) -> usize {
        // check the target frame first
        {
            let frame = self.frames[self.index as usize].inner.lock();
            assert!(!frame.available);
            if frame.drawable.is_some() {
                return 0;
            }
        }
        // wait for new frames to come until we meet the chosen one
        let mut count = 1;
        while self.surface.next_frame(&self.frames).unwrap().0 != self.index as usize {
            count += 1;
        }
        debug!("Swapchain image is ready after {} frames", count);
        count
    }
}

impl hal::Surface<Backend> for Surface {
    fn kind(&self) -> image::Kind {
        let ex = self.inner.dimensions();
        image::Kind::D2(ex.width, ex.height, 1, 1)
    }

    fn compatibility(
        &self, device: &PhysicalDevice,
    ) -> (hal::SurfaceCapabilities, Option<Vec<format::Format>>, Vec<hal::PresentMode>) {
        let current_extent = Some(self.inner.dimensions());

        let caps = hal::SurfaceCapabilities {
            //Note: this is hardcoded in `CAMetalLayer` documentation
            image_count: 2 .. 4,
            current_extent,
            extents: Extent2D { width: 4, height: 4} .. Extent2D { width: 4096, height: 4096 },
            max_image_layers: 1,
            usage: image::Usage::COLOR_ATTACHMENT | image::Usage::SAMPLED |
                image::Usage::TRANSFER_SRC | image::Usage::TRANSFER_DST,
        };

        let formats = vec![
            format::Format::Bgra8Unorm,
            format::Format::Bgra8Srgb,
            format::Format::Rgba16Float,
        ];

        let device_caps = &device.private_caps;
        let can_set_display_sync = device_caps.os_is_mac && device_caps.has_version_at_least(10, 13);

        let present_modes = if can_set_display_sync {
            vec![hal::PresentMode::Fifo, hal::PresentMode::Immediate]
        } else {
            vec![hal::PresentMode::Fifo]
        };

        (caps, Some(formats), present_modes)
    }

    fn supports_queue_family(&self, _queue_family: &QueueFamily) -> bool {
        // we only expose one family atm, so it's compatible
        true
    }
}

impl SurfaceInner {
    fn dimensions(&self) -> Extent2D {
        unsafe {
            // NSView/UIView bounds are measured in DIPs
            let bounds: CGRect = msg_send![self.view, bounds];
            //let bounds_pixel: NSRect = msg_send![self.nsview, convertRectToBacking:bounds];
            Extent2D {
                width: bounds.size.width as _,
                height: bounds.size.height as _,
            }
        }
    }
}

impl Device {
    pub(crate) fn build_swapchain(
        &self,
        surface: &mut Surface,
        config: SwapchainConfig,
    ) -> (Swapchain, Backbuffer<Backend>) {
        info!("build_swapchain {:?}", config);

        let caps = &self.private_caps;
        let mtl_format = caps
            .map_format(config.format)
            .expect("unsupported backbuffer format");

        let render_layer_borrow = surface.inner.render_layer.lock();
        let render_layer = *render_layer_borrow;
        let format_desc = config.format.surface_desc();
        let framebuffer_only = config.image_usage == image::Usage::COLOR_ATTACHMENT;
        let display_sync = config.present_mode != hal::PresentMode::Immediate;
        let can_set_display_sync = caps.os_is_mac && caps.has_version_at_least(10, 13);
        let device = self.shared.device.lock();
        let device_raw: &metal::DeviceRef = &*device;

        unsafe {
            msg_send![render_layer, setDevice: device_raw];
            msg_send![render_layer, setPixelFormat: mtl_format];
            msg_send![render_layer, setFramebufferOnly: framebuffer_only];
            msg_send![render_layer, setMaximumDrawableCount: config.image_count as u64];
            msg_send![render_layer, setDrawableSize: CGSize::new(config.extent.width as f64, config.extent.height as f64)];
            msg_send![render_layer, setAllowsNextDrawableTimeout:false];
            if can_set_display_sync {
                msg_send![render_layer, setDisplaySyncEnabled: display_sync];
            }
        };

        let frames = (0 .. config.image_count)
            .map(|index| autoreleasepool(|| { // for the drawable & texture
                let (drawable, texture) = unsafe {
                    let drawable: &metal::DrawableRef = msg_send![render_layer, nextDrawable];
                    assert!(!drawable.as_ptr().is_null());
                    let texture: &metal::TextureRef = msg_send![drawable, texture];
                    (drawable, texture)
                };
                trace!("\tframe[{}] = {:?}", index, texture);

                let drawable = if index == 0 && surface.has_swapchain {
                    // when resizing, this trick frees up the currently shown frame
                    // HACK: the has_swapchain is unfortunate, and might not be
                    //       correct in all cases.
                    drawable.present();
                    None
                } else {
                    Some(drawable.to_owned())
                };
                Frame {
                    inner: Mutex::new(FrameInner {
                        drawable,
                        available: true,
                        last_frame: 0,
                    }),
                    texture: texture.to_owned(),
                }
            }))
            .collect::<Vec<_>>();

        let images = frames
            .iter()
            .map(|frame| native::Image {
                raw: frame.texture.clone(),
                kind: image::Kind::D2(config.extent.width, config.extent.height, 1, 1),
                format_desc,
                shader_channel: Channel::Float,
                mtl_format,
                mtl_type: metal::MTLTextureType::D2,
            })
            .collect();

        surface.has_swapchain = true;

        let swapchain = Swapchain {
            frames: Arc::new(frames),
            surface: surface.inner.clone(),
            extent: config.extent,
            last_frame: 0,
            image_ready_callbacks: Vec::new(),
            acquire_mode: AcquireMode::Wait,
        };

        (swapchain, Backbuffer::Images(images))
    }
}

impl hal::Swapchain<Backend> for Swapchain {
    fn acquire_image(
        &mut self, _timeout_ns: u64, sync: hal::FrameSync<Backend>
    ) -> Result<hal::SwapImageIndex, hal::AcquireError> {
        self.last_frame += 1;

        //TODO: figure out a proper story of HiDPI
        if false && self.surface.dimensions() != self.extent {
            unimplemented!()
        }

        let mut oldest_index = 0;
        let mut oldest_frame = self.last_frame;

        for (index, frame_arc) in self.frames.iter().enumerate() {
            let mut frame = frame_arc.inner.lock();
            if !frame.available {
                continue
            }
            if frame.drawable.is_some() {
                frame.available = false;
                frame.last_frame = self.last_frame;
                self.signal_sync(sync);
                return Ok(index as _);
            }
            if frame.last_frame < oldest_frame {
                oldest_frame = frame.last_frame;
                oldest_index = index;
            }
        }

        let (index, mut frame) = match self.acquire_mode {
            AcquireMode::Wait => {
                self.surface.next_frame(&self.frames)
                    .map_err(|_| hal::AcquireError::OutOfDate)?
            }
            AcquireMode::Oldest => {
                self.image_ready_callbacks.retain(|ir| ir.lock().is_some());
                match sync {
                    hal::FrameSync::Semaphore(semaphore) => {
                        self.image_ready_callbacks.push(Arc::clone(&semaphore.image_ready));
                        let mut sw_image = semaphore.image_ready.lock();
                        assert!(sw_image.is_none());
                        *sw_image = Some(SwapchainImage {
                            frames: self.frames.clone(),
                            surface: self.surface.clone(),
                            index: oldest_index as _,
                        });
                    }
                    hal::FrameSync::Fence(_fence) => {
                        //TODO: need presentation handlers always created and setting a bool
                        unimplemented!()
                    }
                }

                let frame = self.frames[oldest_index].inner.lock();
                (oldest_index, frame)
            }
        };

        assert!(frame.available);
        frame.last_frame = self.last_frame;
        frame.available = false;

        Ok(index as _)
    }
}
<commit_msg>[mtl] Add version check to `allowsNextDrawableTimeout`<commit_after>use {Backend, QueueFamily};
use device::{Device, PhysicalDevice};
use internal::Channel;
use native;

use std::sync::Arc;

use hal::{self, format, image};
use hal::{Backbuffer, SwapchainConfig};
use hal::window::Extent2D;

use core_graphics::geometry::{CGRect, CGSize};
use foreign_types::{ForeignType, ForeignTypeRef};
use parking_lot::{Mutex, MutexGuard};
use metal;
use objc::rc::autoreleasepool;
use objc::runtime::Object;

pub type CAMetalLayer = *mut Object;

pub struct Surface {
    pub(crate) inner: Arc<SurfaceInner>,
    pub(crate) has_swapchain: bool
}

#[derive(Debug)]
pub(crate) struct SurfaceInner {
    pub(crate) view: *mut Object,
    pub(crate) render_layer: Mutex<CAMetalLayer>,
}

unsafe impl Send for SurfaceInner {}
unsafe impl Sync for SurfaceInner {}

impl Drop for SurfaceInner {
    fn drop(&mut self) {
        unsafe { msg_send![self.view, release]; }
    }
}

impl SurfaceInner {
    fn next_frame<'a>(&self, frames: &'a [Frame]) -> Result<(usize, MutexGuard<'a, FrameInner>), ()> {
        let layer_ref = self.render_layer.lock();
        autoreleasepool(|| { // for the drawable
            let (drawable, texture_temp): (&metal::DrawableRef, &metal::TextureRef) = unsafe {
                let drawable = msg_send![*layer_ref, nextDrawable];
                (drawable, msg_send![drawable, texture])
            };

            trace!("looking for {:?}", texture_temp);
            match frames.iter().position(|f| f.texture.as_ptr() == texture_temp.as_ptr()) {
                Some(index) => {
                    let mut frame = frames[index].inner.lock();
                    assert!(frame.drawable.is_none());
                    frame.drawable = Some(drawable.to_owned());

                    debug!("next is frame[{}]", index);
                    Ok((index, frame))
                }
                None => Err(()),
            }
        })
    }
}

#[derive(Debug)]
struct FrameInner {
    drawable: Option<metal::Drawable>,
    /// If there is a `drawable`, availability indicates if it's free for grabs.
    /// If there is `None`, `available == false` means that the frame has already
    /// been acquired and the `drawable` will appear at some point.
    available: bool,
    last_frame: usize,
}

#[derive(Debug)]
struct Frame {
    inner: Mutex<FrameInner>,
    texture: metal::Texture,
}

unsafe impl Send for Frame {}
unsafe impl Sync for Frame {}

impl Drop for Frame {
    fn drop(&mut self) {
        info!("dropping Frame");
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AcquireMode {
    Wait,
    Oldest,
}

pub struct Swapchain {
    frames: Arc<Vec<Frame>>,
    surface: Arc<SurfaceInner>,
    extent: Extent2D,
    last_frame: usize,
    image_ready_callbacks: Vec<Arc<Mutex<Option<SwapchainImage>>>>,
    pub acquire_mode: AcquireMode,
}

impl Drop for Swapchain {
    fn drop(&mut self) {
        info!("dropping Swapchain");
        for ir in self.image_ready_callbacks.drain(..) {
            if ir.lock().take().is_some() {
                debug!("\twith a callback");
            }
        }
    }
}

impl Swapchain {
    /// Returns the drawable for the specified swapchain image index,
    /// marks the index as free for future use.
    pub(crate) fn take_drawable(&self, index: hal::SwapImageIndex) -> metal::Drawable {
        let mut frame = self
            .frames[index as usize]
            .inner
            .lock();
        assert!(!frame.available);
        frame.available = true;
        frame.drawable
            .take()
            .expect("Drawable has not been acquired!")
    }

    fn signal_sync(&self, sync: hal::FrameSync<Backend>) {
        match sync {
            hal::FrameSync::Semaphore(semaphore) => {
                if let Some(ref system) = semaphore.system {
                    system.signal();
                }
            }
            hal::FrameSync::Fence(fence) => {
                *fence.0.borrow_mut() = native::FenceInner::Idle { signaled: true };
            }
        }
    }
}

#[derive(Debug)]
pub struct SwapchainImage {
    frames: Arc<Vec<Frame>>,
    surface: Arc<SurfaceInner>,
    index: hal::SwapImageIndex,
}

impl SwapchainImage {
    /// Waits until the specified swapchain index is available for rendering.
    /// Returns the number of frames it had to wait.
    pub fn wait_until_ready(&self) -> usize {
        // check the target frame first
        {
            let frame = self.frames[self.index as usize].inner.lock();
            assert!(!frame.available);
            if frame.drawable.is_some() {
                return 0;
            }
        }
        // wait for new frames to come until we meet the chosen one
        let mut count = 1;
        while self.surface.next_frame(&self.frames).unwrap().0 != self.index as usize {
            count += 1;
        }
        debug!("Swapchain image is ready after {} frames", count);
        count
    }
}

impl hal::Surface<Backend> for Surface {
    fn kind(&self) -> image::Kind {
        let ex = self.inner.dimensions();
        image::Kind::D2(ex.width, ex.height, 1, 1)
    }

    fn compatibility(
        &self, device: &PhysicalDevice,
    ) -> (hal::SurfaceCapabilities, Option<Vec<format::Format>>, Vec<hal::PresentMode>) {
        let current_extent = Some(self.inner.dimensions());

        let caps = hal::SurfaceCapabilities {
            //Note: this is hardcoded in `CAMetalLayer` documentation
            image_count: 2 .. 4,
            current_extent,
            extents: Extent2D { width: 4, height: 4} .. Extent2D { width: 4096, height: 4096 },
            max_image_layers: 1,
            usage: image::Usage::COLOR_ATTACHMENT | image::Usage::SAMPLED |
                image::Usage::TRANSFER_SRC | image::Usage::TRANSFER_DST,
        };

        let formats = vec![
            format::Format::Bgra8Unorm,
            format::Format::Bgra8Srgb,
            format::Format::Rgba16Float,
        ];

        let device_caps = &device.private_caps;
        let can_set_display_sync = device_caps.os_is_mac && device_caps.has_version_at_least(10, 13);

        let present_modes = if can_set_display_sync {
            vec![hal::PresentMode::Fifo, hal::PresentMode::Immediate]
        } else {
            vec![hal::PresentMode::Fifo]
        };

        (caps, Some(formats), present_modes)
    }

    fn supports_queue_family(&self, _queue_family: &QueueFamily) -> bool {
        // we only expose one family atm, so it's compatible
        true
    }
}

impl SurfaceInner {
    fn dimensions(&self) -> Extent2D {
        unsafe {
            // NSView/UIView bounds are measured in DIPs
            let bounds: CGRect = msg_send![self.view, bounds];
            //let bounds_pixel: NSRect = msg_send![self.nsview, convertRectToBacking:bounds];
            Extent2D {
                width: bounds.size.width as _,
                height: bounds.size.height as _,
            }
        }
    }
}

impl Device {
    pub(crate) fn build_swapchain(
        &self,
        surface: &mut Surface,
        config: SwapchainConfig,
    ) -> (Swapchain, Backbuffer<Backend>) {
        info!("build_swapchain {:?}", config);

        let caps = &self.private_caps;
        let mtl_format = caps
            .map_format(config.format)
            .expect("unsupported backbuffer format");

        let render_layer_borrow = surface.inner.render_layer.lock();
        let render_layer = *render_layer_borrow;
        let format_desc = config.format.surface_desc();
        let framebuffer_only = config.image_usage == image::Usage::COLOR_ATTACHMENT;
        let display_sync = config.present_mode != hal::PresentMode::Immediate;
        let is_mac = caps.os_is_mac;
        let can_set_next_drawable_timeout = if is_mac {
            caps.has_version_at_least(10, 13)
        } else {
            caps.has_version_at_least(11, 0)
        };
        let can_set_display_sync = is_mac && caps.has_version_at_least(10, 13);
        let device = self.shared.device.lock();
        let device_raw: &metal::DeviceRef = &*device;

        unsafe {
            msg_send![render_layer, setDevice: device_raw];
            msg_send![render_layer, setPixelFormat: mtl_format];
            msg_send![render_layer, setFramebufferOnly: framebuffer_only];
            msg_send![render_layer, setMaximumDrawableCount: config.image_count as u64];
            msg_send![render_layer, setDrawableSize: CGSize::new(config.extent.width as f64, config.extent.height as f64)];
            if can_set_next_drawable_timeout {
                msg_send![render_layer, setAllowsNextDrawableTimeout:false];
            }
            if can_set_display_sync {
                msg_send![render_layer, setDisplaySyncEnabled: display_sync];
            }
        };

        let frames = (0 .. config.image_count)
            .map(|index| autoreleasepool(|| { // for the drawable & texture
                let (drawable, texture) = unsafe {
                    let drawable: &metal::DrawableRef = msg_send![render_layer, nextDrawable];
                    assert!(!drawable.as_ptr().is_null());
                    let texture: &metal::TextureRef = msg_send![drawable, texture];
                    (drawable, texture)
                };
                trace!("\tframe[{}] = {:?}", index, texture);

                let drawable = if index == 0 && surface.has_swapchain {
                    // when resizing, this trick frees up the currently shown frame
                    // HACK: the has_swapchain is unfortunate, and might not be
                    //       correct in all cases.
                    drawable.present();
                    None
                } else {
                    Some(drawable.to_owned())
                };
                Frame {
                    inner: Mutex::new(FrameInner {
                        drawable,
                        available: true,
                        last_frame: 0,
                    }),
                    texture: texture.to_owned(),
                }
            }))
            .collect::<Vec<_>>();

        let images = frames
            .iter()
            .map(|frame| native::Image {
                raw: frame.texture.clone(),
                kind: image::Kind::D2(config.extent.width, config.extent.height, 1, 1),
                format_desc,
                shader_channel: Channel::Float,
                mtl_format,
                mtl_type: metal::MTLTextureType::D2,
            })
            .collect();

        surface.has_swapchain = true;

        let swapchain = Swapchain {
            frames: Arc::new(frames),
            surface: surface.inner.clone(),
            extent: config.extent,
            last_frame: 0,
            image_ready_callbacks: Vec::new(),
            acquire_mode: AcquireMode::Wait,
        };

        (swapchain, Backbuffer::Images(images))
    }
}

impl hal::Swapchain<Backend> for Swapchain {
    fn acquire_image(
        &mut self, _timeout_ns: u64, sync: hal::FrameSync<Backend>
    ) -> Result<hal::SwapImageIndex, hal::AcquireError> {
        self.last_frame += 1;

        //TODO: figure out a proper story of HiDPI
        if false && self.surface.dimensions() != self.extent {
            unimplemented!()
        }

        let mut oldest_index = 0;
        let mut oldest_frame = self.last_frame;

        for (index, frame_arc) in self.frames.iter().enumerate() {
            let mut frame = frame_arc.inner.lock();
            if !frame.available {
                continue
            }
            if frame.drawable.is_some() {
                frame.available = false;
                frame.last_frame = self.last_frame;
                self.signal_sync(sync);
                return Ok(index as _);
            }
            if frame.last_frame < oldest_frame {
                oldest_frame = frame.last_frame;
                oldest_index = index;
            }
        }

        let (index, mut frame) = match self.acquire_mode {
            AcquireMode::Wait => {
                self.surface.next_frame(&self.frames)
                    .map_err(|_| hal::AcquireError::OutOfDate)?
            }
            AcquireMode::Oldest => {
                self.image_ready_callbacks.retain(|ir| ir.lock().is_some());
                match sync {
                    hal::FrameSync::Semaphore(semaphore) => {
                        self.image_ready_callbacks.push(Arc::clone(&semaphore.image_ready));
                        let mut sw_image = semaphore.image_ready.lock();
                        assert!(sw_image.is_none());
                        *sw_image = Some(SwapchainImage {
                            frames: self.frames.clone(),
                            surface: self.surface.clone(),
                            index: oldest_index as _,
                        });
                    }
                    hal::FrameSync::Fence(_fence) => {
                        //TODO: need presentation handlers always created and setting a bool
                        unimplemented!()
                    }
                }

                let frame = self.frames[oldest_index].inner.lock();
                (oldest_index, frame)
            }
        };

        assert!(frame.available);
        frame.last_frame = self.last_frame;
        frame.available = false;

        Ok(index as _)
    }
}
<|endoftext|>


<commit_before>use std::fs::File;
use std::io::{Read};
use toml;
use context::Context;
use execution_timer::ExecutionTimer;

// Stores the configuration. Will be read from config.toml. Any values not
// present in the file will be defaulted using the 'default' method below.
#[derive(Serialize, Deserialize, Debug)]
#[serde(default)]
pub struct Configuration {
    max_mru_items: u32,
    max_open_files: u32
}

impl Default for Configuration {
    fn default() -> Configuration {
        Configuration {
            max_mru_items: 20,
            max_open_files: 200
        }
    }
}

const CONFIG_FILE : &'static str = "config.toml";

impl Configuration {
    pub fn load_user_configuration(context: &Context) -> Configuration {
        let _timer = ExecutionTimer::with_start_message("load_user_configuration");

        if !context.program_info().parsed_args().load_config() {
            info!("Loading of user configuration is disabled by command line option.");
            return Configuration::default();
        }

        let xdg = context.xdg();
        let dir = xdg.get_config_home();
        if !dir.exists() {
            warn!("The config_directory does not exist, no config will be loaded, config_directory: {:?}", dir);
            return Configuration::default();
        }

        if !dir.is_dir() {
            warn!("The config_directory is a file, not a directory, no config will be loaded, config_directory: {:?}", dir);
            return Configuration::default();
        }

        info!("Loading user configuration file {:?} from config_directory: {:?}", CONFIG_FILE, dir);

        let path = xdg.place_config_file(CONFIG_FILE);
        if path.is_err() {
            warn!("Could not locate {} file in xdg directory structure", CONFIG_FILE);
            return Configuration::default();
        }

        let path = path.unwrap();
        if !path.exists() {
            debug!("The file {:?} does not exist, no user config will be loaded", path);
            return Configuration::default();
        }

        if !path.is_file() {
            warn!("The user configuration file {:?} appears to be a directory, no user config will be loaded", path);
            return Configuration::default();
        }

        // Ok, the file exists and we can try to load it.
        let cfg = File::open(&path)
            .map_err(|err| err.to_string())
            .and_then(|mut f| {
                let mut contents = String::new();
                f.read_to_string(&mut contents)
                    .map_err(|err| err.to_string())
                    .map(|num_bytes_read| {
                        info!("Read {} bytes from {:?}", num_bytes_read, &path);
                        contents
                    })
            })
            .and_then(|contents| {
                toml::from_str::<Configuration>(&contents)
                    .map_err(|err| err.to_string())
            })
            .map_err(|err| warn!("Error reading {:?}: {:?}", CONFIG_FILE, err))
            .unwrap_or_default();

        info!("{:?}", cfg);

        cfg
    }
}
<commit_msg>Print number of lines read from config.toml.<commit_after>use std::fs::File;
use std::io::{Read};
use toml;
use context::Context;
use execution_timer::ExecutionTimer;

// Stores the configuration. Will be read from config.toml. Any values not
// present in the file will be defaulted using the 'default' method below.
#[derive(Serialize, Deserialize, Debug)]
#[serde(default)]
pub struct Configuration {
    max_mru_items: u32,
    max_open_files: u32
}

impl Default for Configuration {
    fn default() -> Configuration {
        Configuration {
            max_mru_items: 20,
            max_open_files: 200
        }
    }
}

const CONFIG_FILE : &'static str = "config.toml";

impl Configuration {
    pub fn load_user_configuration(context: &Context) -> Configuration {
        let _timer = ExecutionTimer::with_start_message("load_user_configuration");

        if !context.program_info().parsed_args().load_config() {
            info!("Loading of user configuration is disabled by command line option.");
            return Configuration::default();
        }

        let xdg = context.xdg();
        let dir = xdg.get_config_home();
        if !dir.exists() {
            warn!("The config_directory does not exist, no config will be loaded, config_directory: {:?}", dir);
            return Configuration::default();
        }

        if !dir.is_dir() {
            warn!("The config_directory is a file, not a directory, no config will be loaded, config_directory: {:?}", dir);
            return Configuration::default();
        }

        info!("Loading user configuration file {:?} from config_directory: {:?}", CONFIG_FILE, dir);

        let path = xdg.place_config_file(CONFIG_FILE);
        if path.is_err() {
            warn!("Could not locate {} file in xdg directory structure", CONFIG_FILE);
            return Configuration::default();
        }

        let path = path.unwrap();
        if !path.exists() {
            debug!("The file {:?} does not exist, no user config will be loaded", path);
            return Configuration::default();
        }

        if !path.is_file() {
            warn!("The user configuration file {:?} appears to be a directory, no user config will be loaded", path);
            return Configuration::default();
        }

        // Ok, the file exists and we can try to load it.
        let cfg = File::open(&path)
            .map_err(|err| err.to_string())
            .and_then(|mut f| {
                let mut contents = String::new();
                f.read_to_string(&mut contents)
                    .map_err(|err| err.to_string())
                    .map(|num_bytes_read| {
                        info!("Read {} lines ({} bytes) from {:?}", contents.lines().count(), num_bytes_read,  &path);
                        contents
                    })
            })
            .and_then(|contents| {
                toml::from_str::<Configuration>(&contents)
                    .map_err(|err| err.to_string())
            })
            .map_err(|err| warn!("Error reading {:?}: {:?}", CONFIG_FILE, err))
            .unwrap_or_default();

        info!("{:?}", cfg);

        cfg
    }
}
<|endoftext|>