<commit_before>//! Utility for controlling RGB header on MSI boards
//!
//! The RGB header is controlled by the NCT6795D Super I/O chip.
//!
//! The Advanced "mode" is enabled by writing 87 87 to PORT 1. It is later disabled by writing AA
//! to that same port. `07 12` then selects the bank 12h. This bank then looks like this:
//!
//! 00 |  ...
//! 10 |  ...
//! .  |
//! .  |
//! .  |
//! E0 | EE XX XX XX  PX XX XX XX  XX XX XX XX  XX XX XX XX
//! F0 | RR RR RR RR  GG GG GG GG  BB BB BB BB  XX XX TT TX
//!     --------------------------------------------------
//!      00 01 02 03  04 05 06 07  08 09 0A 0B  0C 0D 0E 0F
//!
//! Here:
//!
//! `EE` – firstly sets RGB to enabled.
//!
//! Purpose of following three most significant bits in `EE` is known:
//!
//! `0b10000000` - unknown
//! `0b01000000` - unknown
//! `0b00100000` - unknown
//!
//!
//!
//! `R` - intensity of the red colour
//! `G` - intensity of the green colour
//! `B` - intensity of the blue colour
//!
//! There’s 8 distinct steps that can be specified, hence the four instances of `RR`, `GG` and
//! `BB`. These colours change every given interval specified in the `TTT` bytes. TTT has the bit
//! format like this: `bgrdt tttt tttt`
//!
//! Here `t` bits are a duration between changes from one colour to another (takes the next column
//! of RR GG BB);
//!
//! `d` bit specifies whether the RGB header is turned on (distinct from the motherboard lights).;
//!
//! `bgr` invert the intensity (`F` is 0%, `0` is 100%) for blue, green and red channels
//! respectively.
//!
//! `P` here is another bitmask of the form `pbbb`, where `p` specifies whether smooth pulsing
//! behaviour is enabled. `bbb` specifies duration between blinks. If `bbb` is `001`,
//! all lightning is turned off, including the one on the motherboard itself. `000` is always on.

extern crate clap;
#[macro_use]
extern crate error_chain;

use std::io::{self, Seek, Write, Read};
use std::fs;
use clap::{Arg, App, ArgMatches};

error_chain! {
    foreign_links {
        Io(::std::io::Error);
        Parse(::std::num::ParseIntError);
    }
}

const RGB_BANK: u8 = 0x12;
const NCT6795D_MASK: u16 = 0xD350;

const REG_DEVID_MSB: u8 = 0x20;
const REG_DEVID_LSB: u8 = 0x21;
const REDCELL: u8 = 0xf0;
const GREENCELL: u8 = 0xf4;
const BLUECELL: u8 = 0xf8;

fn inb(f: &mut fs::File, port: u16) -> io::Result<u8> {
    let mut d = [0u8];
    f.seek(io::SeekFrom::Start(port.into()))?;
    f.read(&mut d)?;
    Ok(d[0])
}

fn outb(f: &mut fs::File, port: u16, data: u8) -> io::Result<()> {
    f.seek(io::SeekFrom::Start(port.into()))?;
    f.write(&[data])?;
    Ok(())
}

fn write_byte_to_cell(f: &mut fs::File, base_port: u16, cell: u8, data: u8) -> io::Result<()> {
    outb(f, base_port, cell)?;
    outb(f, base_port + 1, data)
}

fn write_colour(f: &mut fs::File, base_port: u16, cell_offset: u8, data: u32) -> io::Result<()> {
    write_byte_to_cell(f, base_port, cell_offset, (data >> 24) as u8)?;
    write_byte_to_cell(f, base_port, cell_offset + 1, (data >> 16) as u8)?;
    write_byte_to_cell(f, base_port, cell_offset + 2, (data >> 8) as u8)?;
    write_byte_to_cell(f, base_port, cell_offset + 3, data as u8)
}

fn run<'a>(f: &mut fs::File, base_port: u16, matches: ArgMatches<'a>) -> Result<()> {
    let disable = matches.is_present("DISABLE");
    let pulse = matches.is_present("PULSE");
    let ignore = matches.is_present("IGNORECHECK");
    let flash = matches.value_of("BLINK").expect("bug: BLINK argument").parse::<u8>()?;
    let red = u32::from_str_radix(matches.value_of("RED").expect("bug: RED argument"), 16)?;
    let green = u32::from_str_radix(matches.value_of("GREEN").expect("bug: GREEN argument"), 16)?;
    let blue = u32::from_str_radix(matches.value_of("BLUE").expect("bug: BLUE argument"), 16)?;
    let step_duration = matches.value_of("STEPDURATION").expect("bug: STEPDURATION argument")
                               .parse::<u16>()?;
    let invs = matches.values_of("INVHALF").map(|i| i.collect()).unwrap_or(Vec::new());

    // Check if indeed a NCT6795D
    if !ignore {
        outb(f, base_port, REG_DEVID_MSB)?;
        let msb = inb(f, base_port + 1)?;
        outb(f, base_port, REG_DEVID_LSB)?;
        let ident = (msb as u16) << 8 | inb(f, base_port + 1)? as u16;
        if ident & 0xFFF0 != NCT6795D_MASK {
            let err: Result<()> = Err("`--ignore-check` flag, which would skip the check, \
                                       is not specified (may be dangerous); \
                                       also try `--base-port`".into());
            return err.chain_err(|| format!("The sI/O chip identifies as {:x}, which does not \
                                            seem to be NCT6795D", ident));
        }
    }

    // Select the 0x12th bank.
    outb(f, base_port, 0x07)?;
    outb(f, base_port + 1, RGB_BANK)?;

    // Check if RGB control enabled?
    outb(f, base_port, 0xe0)?;
    let d = inb(f, base_port + 1)?;
    if d & 0xe0 != 0xe0 {
        outb(f, base_port + 1, 0xe0 | (d & !0xe0))?;
    }

    let e4_val = if disable { 1 } else { 0 } |
                 if pulse { 0b1000 } else { 0 } |
                 if flash == 0 { 0 } else { (flash + 1) & 0b111 };
    write_byte_to_cell(f, base_port, 0xe4, e4_val)?;

    write_byte_to_cell(f, base_port, 0xfe, step_duration as u8)?;
    let ff_val = (step_duration >> 8) as u8 & 1 |
                 0b10 | // if 0 disable lights on rgb header only, not on board
                 if invs.contains(&"b") { 0b10000 } else { 0 } |
                 if invs.contains(&"g") { 0b01000 } else { 0 } |
                 if invs.contains(&"r") { 0b00100 } else { 0 };
    write_byte_to_cell(f, base_port, 0xff, ff_val)?;

    write_colour(f, base_port, REDCELL, red)?;
    write_colour(f, base_port, GREENCELL, green)?;
    write_colour(f, base_port, BLUECELL, blue)?;

    Ok(())
}

/// Wrapper which enables and disables the advanced mode
fn run_wrap<'a>(matches: ArgMatches<'a>) -> Result<()> {
    let base_port = u16::from_str_radix(matches.value_of("BASEPORT")
                                               .expect("bug: BASEPORT argument"), 16)?;

    let mut f = fs::OpenOptions::new().read(true).write(true).open("/dev/port")
        .chain_err(|| { "could not open /dev/port; try sudo?" })?;
    // Enable the advanced mode.
    outb(&mut f, base_port, 0x87).chain_err(|| "could not enable advanced mode")?;
    outb(&mut f, base_port, 0x87).chain_err(|| "could not enable advanced mode")?;

    // These are something the built-in app does during initialization…
    // Purpose unclear
    // outb(&mut f, base_port, 0x07)?;
    // outb(&mut f, base_port + 1, 0x0B)?;
    // outb(&mut f, base_port, 0x60)?;
    // let a = inb(&mut f, base_port + 1)?;
    // outb(&mut f, base_port, 0x61)?;
    // let b = inb(&mut f, base_port + 1)?;
    // println!("{:x} {:x}", a, b);

    // Without this pulsing does not work
    outb(&mut f, base_port, 0x07)?;
    outb(&mut f, base_port + 1, 0x09)?;
    outb(&mut f, base_port, 0x2c)?;
    let c = inb(&mut f, base_port + 1)?;
    if c & 0x10 != 0x10 {
        outb(&mut f, base_port + 1, c | 0x10)?;
    }

    let r = run(&mut f, base_port, matches);
    // Disable the advanced mode.
    outb(&mut f, base_port, 0xAA).chain_err(|| "could not disable advanced mode")?;
    r.chain_err(|| "could not set the colour")
}

fn main() {
    let matches = App::new("msi-rgb")
        .version(option_env!("CARGO_PKG_VERSION").unwrap_or("0.0.0"))
        .about(option_env!("CARGO_PKG_DESCRIPTION").unwrap_or(""))
        .setting(clap::AppSettings::ArgRequiredElseHelp)
        .arg(Arg::with_name("RED").required(true)
             .help("values of red colour (32 bit hex number, up to FFFFFFFF)"))
        .arg(Arg::with_name("GREEN").required(true)
             .help("values of green colour (32 bit hex number, up to FFFFFFFF)"))
        .arg(Arg::with_name("BLUE").required(true)
             .help("values of blue colour (32 bit hex number, up to FFFFFFFF)"))
        .arg(Arg::with_name("INVHALF").long("invert").short("i").multiple(true)
             .takes_value(true).possible_values(&["r","g","b"])
             .help("invert the specified channel(s)"))
        .arg(Arg::with_name("PULSE").long("pulse").short("p")
             .help("smooth pulsing"))
        .arg(Arg::with_name("STEPDURATION").long("duration").short("d").takes_value(true)
             .default_value("25").validator(|s| { match s.parse::<u16>() {
                 Err(e) => Err(format!("{}", e)),
                 Ok(s) => if s > 511 { Err("duration must not exceed 511".into()) } else { Ok(()) }
             }})
             .help("duration between distinct steps of colours (0 - fastest, 511 - slowest"))
        .arg(Arg::with_name("BLINK").long("blink").short("b").takes_value(true).default_value("0")
             .possible_values(&["0", "1", "2", "3", "4", "5", "6"])
             .help("duration between blinks (from 0 to 6, 0 is always on, 6 is slowest)"))
        .arg(Arg::with_name("DISABLE").long("disable").short("x")
             .help("disable the RGB subsystem altogether"))
        .arg(Arg::with_name("IGNORECHECK").long("ignore-check")
             .help("ignore the result of sI/O identification check"))
        .arg(Arg::with_name("BASEPORT").long("base-port").default_value("4e")
             .help("base port to use. Values known to be in use are 4e and 2e"))
        .get_matches();

    ::std::process::exit(if let Err(e) = run_wrap(matches) {
        let _ = writeln!(::std::io::stderr(), "error: {}", e);
        for e in e.iter().skip(1) {
            let _ = writeln!(::std::io::stderr(), "    caused by: {}", e);
        }
        1
    } else {
        0
    });
}
<commit_msg>Add code that enables pulsing<commit_after>//! Utility for controlling RGB header on MSI boards
//!
//! The RGB header is controlled by the NCT6795D Super I/O chip.
//!
//! The Advanced "mode" is enabled by writing 87 87 to PORT 1. It is later disabled by writing AA
//! to that same port. `07 12` then selects the bank 12h. This bank then looks like this:
//!
//! 00 |  ...
//! 10 |  ...
//! .  |
//! .  |
//! .  |
//! E0 | EE XX XX XX  PX XX XX XX  XX XX XX XX  XX XX XX XX
//! F0 | RR RR RR RR  GG GG GG GG  BB BB BB BB  XX XX TT TX
//!     --------------------------------------------------
//!      00 01 02 03  04 05 06 07  08 09 0A 0B  0C 0D 0E 0F
//!
//! Here:
//!
//! `EE` – firstly sets RGB to enabled.
//!
//! Purpose of following three most significant bits in `EE` is known:
//!
//! `0b10000000` - unknown
//! `0b01000000` - unknown
//! `0b00100000` - unknown
//!
//!
//!
//! `R` - intensity of the red colour
//! `G` - intensity of the green colour
//! `B` - intensity of the blue colour
//!
//! There’s 8 distinct steps that can be specified, hence the four instances of `RR`, `GG` and
//! `BB`. These colours change every given interval specified in the `TTT` bytes. TTT has the bit
//! format like this: `bgrdt tttt tttt`
//!
//! Here `t` bits are a duration between changes from one colour to another (takes the next column
//! of RR GG BB);
//!
//! `d` bit specifies whether the RGB header is turned on (distinct from the motherboard lights).;
//!
//! `bgr` invert the intensity (`F` is 0%, `0` is 100%) for blue, green and red channels
//! respectively.
//!
//! `P` here is another bitmask of the form `pbbb`, where `p` specifies whether smooth pulsing
//! behaviour is enabled. `bbb` specifies duration between blinks. If `bbb` is `001`,
//! all lightning is turned off, including the one on the motherboard itself. `000` is always on.

extern crate clap;
#[macro_use]
extern crate error_chain;

use std::io::{self, Seek, Write, Read};
use std::fs;
use clap::{Arg, App, ArgMatches};

error_chain! {
    foreign_links {
        Io(::std::io::Error);
        Parse(::std::num::ParseIntError);
    }
}

const RGB_BANK: u8 = 0x12;
const NCT6795D_MASK: u16 = 0xD350;

const REG_DEVID_MSB: u8 = 0x20;
const REG_DEVID_LSB: u8 = 0x21;
const REDCELL: u8 = 0xf0;
const GREENCELL: u8 = 0xf4;
const BLUECELL: u8 = 0xf8;

fn inb(f: &mut fs::File, port: u16) -> io::Result<u8> {
    let mut d = [0u8];
    f.seek(io::SeekFrom::Start(port.into()))?;
    f.read(&mut d)?;
    Ok(d[0])
}

fn outb(f: &mut fs::File, port: u16, data: u8) -> io::Result<()> {
    f.seek(io::SeekFrom::Start(port.into()))?;
    f.write(&[data])?;
    Ok(())
}

fn write_byte_to_cell(f: &mut fs::File, base_port: u16, cell: u8, data: u8) -> io::Result<()> {
    outb(f, base_port, cell)?;
    outb(f, base_port + 1, data)
}

fn write_colour(f: &mut fs::File, base_port: u16, cell_offset: u8, data: u32) -> io::Result<()> {
    write_byte_to_cell(f, base_port, cell_offset, (data >> 24) as u8)?;
    write_byte_to_cell(f, base_port, cell_offset + 1, (data >> 16) as u8)?;
    write_byte_to_cell(f, base_port, cell_offset + 2, (data >> 8) as u8)?;
    write_byte_to_cell(f, base_port, cell_offset + 3, data as u8)
}

fn run<'a>(f: &mut fs::File, base_port: u16, matches: ArgMatches<'a>) -> Result<()> {
    let disable = matches.is_present("DISABLE");
    let pulse = matches.is_present("PULSE");
    let ignore = matches.is_present("IGNORECHECK");
    let flash = matches.value_of("BLINK").expect("bug: BLINK argument").parse::<u8>()?;
    let red = u32::from_str_radix(matches.value_of("RED").expect("bug: RED argument"), 16)?;
    let green = u32::from_str_radix(matches.value_of("GREEN").expect("bug: GREEN argument"), 16)?;
    let blue = u32::from_str_radix(matches.value_of("BLUE").expect("bug: BLUE argument"), 16)?;
    let step_duration = matches.value_of("STEPDURATION").expect("bug: STEPDURATION argument")
                               .parse::<u16>()?;
    let invs = matches.values_of("INVHALF").map(|i| i.collect()).unwrap_or(Vec::new());

    // Check if indeed a NCT6795D
    if !ignore {
        outb(f, base_port, REG_DEVID_MSB)?;
        let msb = inb(f, base_port + 1)?;
        outb(f, base_port, REG_DEVID_LSB)?;
        let ident = (msb as u16) << 8 | inb(f, base_port + 1)? as u16;
        if ident & 0xFFF0 != NCT6795D_MASK {
            let err: Result<()> = Err("`--ignore-check` flag, which would skip the check, \
                                       is not specified (may be dangerous); \
                                       also try `--base-port`".into());
            return err.chain_err(|| format!("The sI/O chip identifies as {:x}, which does not \
                                            seem to be NCT6795D", ident));
        }
    }

    // Without this pulsing does not work
    outb(&mut f, base_port, 0x07)?;
    outb(&mut f, base_port + 1, 0x09)?;
    outb(&mut f, base_port, 0x2c)?;
    let c = inb(&mut f, base_port + 1)?;
    if c & 0x10 != 0x10 {
        outb(&mut f, base_port + 1, c | 0x10)?;
    }

    // Select the 0x12th bank.
    outb(f, base_port, 0x07)?;
    outb(f, base_port + 1, RGB_BANK)?;

    // Check if RGB control enabled?
    outb(f, base_port, 0xe0)?;
    let d = inb(f, base_port + 1)?;
    if d & 0xe0 != 0xe0 {
        outb(f, base_port + 1, 0xe0 | (d & !0xe0))?;
    }

    let e4_val = if disable { 1 } else { 0 } |
                 if pulse { 0b1000 } else { 0 } |
                 if flash == 0 { 0 } else { (flash + 1) & 0b111 };
    write_byte_to_cell(f, base_port, 0xe4, e4_val)?;

    write_byte_to_cell(f, base_port, 0xfe, step_duration as u8)?;
    let ff_val = (step_duration >> 8) as u8 & 1 |
                 0b10 | // if 0 disable lights on rgb header only, not on board
                 if invs.contains(&"b") { 0b10000 } else { 0 } |
                 if invs.contains(&"g") { 0b01000 } else { 0 } |
                 if invs.contains(&"r") { 0b00100 } else { 0 };
    write_byte_to_cell(f, base_port, 0xff, ff_val)?;

    write_colour(f, base_port, REDCELL, red)?;
    write_colour(f, base_port, GREENCELL, green)?;
    write_colour(f, base_port, BLUECELL, blue)?;

    Ok(())
}

/// Wrapper which enables and disables the advanced mode
fn run_wrap<'a>(matches: ArgMatches<'a>) -> Result<()> {
    let base_port = u16::from_str_radix(matches.value_of("BASEPORT")
                                               .expect("bug: BASEPORT argument"), 16)?;

    let mut f = fs::OpenOptions::new().read(true).write(true).open("/dev/port")
        .chain_err(|| { "could not open /dev/port; try sudo?" })?;
    // Enable the advanced mode.
    outb(&mut f, base_port, 0x87).chain_err(|| "could not enable advanced mode")?;
    outb(&mut f, base_port, 0x87).chain_err(|| "could not enable advanced mode")?;

    // These are something the built-in app does during initialization…
    // Purpose unclear
    // outb(&mut f, base_port, 0x07)?;
    // outb(&mut f, base_port + 1, 0x0B)?;
    // outb(&mut f, base_port, 0x60)?;
    // let a = inb(&mut f, base_port + 1)?;
    // outb(&mut f, base_port, 0x61)?;
    // let b = inb(&mut f, base_port + 1)?;
    // println!("{:x} {:x}", a, b);

    let r = run(&mut f, base_port, matches);
    // Disable the advanced mode.
    outb(&mut f, base_port, 0xAA).chain_err(|| "could not disable advanced mode")?;
    r.chain_err(|| "could not set the colour")
}

fn main() {
    let matches = App::new("msi-rgb")
        .version(option_env!("CARGO_PKG_VERSION").unwrap_or("0.0.0"))
        .about(option_env!("CARGO_PKG_DESCRIPTION").unwrap_or(""))
        .setting(clap::AppSettings::ArgRequiredElseHelp)
        .arg(Arg::with_name("RED").required(true)
             .help("values of red colour (32 bit hex number, up to FFFFFFFF)"))
        .arg(Arg::with_name("GREEN").required(true)
             .help("values of green colour (32 bit hex number, up to FFFFFFFF)"))
        .arg(Arg::with_name("BLUE").required(true)
             .help("values of blue colour (32 bit hex number, up to FFFFFFFF)"))
        .arg(Arg::with_name("INVHALF").long("invert").short("i").multiple(true)
             .takes_value(true).possible_values(&["r","g","b"])
             .help("invert the specified channel(s)"))
        .arg(Arg::with_name("PULSE").long("pulse").short("p")
             .help("smooth pulsing"))
        .arg(Arg::with_name("STEPDURATION").long("duration").short("d").takes_value(true)
             .default_value("25").validator(|s| { match s.parse::<u16>() {
                 Err(e) => Err(format!("{}", e)),
                 Ok(s) => if s > 511 { Err("duration must not exceed 511".into()) } else { Ok(()) }
             }})
             .help("duration between distinct steps of colours (0 - fastest, 511 - slowest"))
        .arg(Arg::with_name("BLINK").long("blink").short("b").takes_value(true).default_value("0")
             .possible_values(&["0", "1", "2", "3", "4", "5", "6"])
             .help("duration between blinks (from 0 to 6, 0 is always on, 6 is slowest)"))
        .arg(Arg::with_name("DISABLE").long("disable").short("x")
             .help("disable the RGB subsystem altogether"))
        .arg(Arg::with_name("IGNORECHECK").long("ignore-check")
             .help("ignore the result of sI/O identification check"))
        .arg(Arg::with_name("BASEPORT").long("base-port").default_value("4e")
             .help("base port to use. Values known to be in use are 4e and 2e"))
        .get_matches();

    ::std::process::exit(if let Err(e) = run_wrap(matches) {
        let _ = writeln!(::std::io::stderr(), "error: {}", e);
        for e in e.iter().skip(1) {
            let _ = writeln!(::std::io::stderr(), "    caused by: {}", e);
        }
        1
    } else {
        0
    });
}
<|endoftext|>


<commit_before>#![feature(env)]
#![feature(old_io)]
#![feature(old_path)]
#![feature(os)]

extern crate mesh;

use std::old_io::BufferedReader;
use std::old_io::fs::File;
use mesh::StlFile;

fn main() {

    let mut args = std::env::args();
    args.next();  // skip arg0
    let meshname = args.next().expect(
        "Usage: ./meshman <path/to/mesh>"
    );

    let meshfile = match File::open(&Path::new(meshname)) {
        Ok(f) => f,
        Err(e) => panic!("file error: {}", e),
    };

    StlFile::read(&mut BufferedReader::new(meshfile));
}
<commit_msg>cleaned up warnings<commit_after>#![feature(env)]
#![feature(old_io)]
#![feature(old_path)]

extern crate mesh;

use std::old_io::BufferedReader;
use std::old_io::fs::File;
use mesh::StlFile;

fn main() {

    let mut args = std::env::args();
    args.next();  // skip arg0
    let meshname = args.next().expect(
        "Usage: ./meshman <path/to/mesh>"
    );

    let meshfile = match File::open(&Path::new(meshname)) {
        Ok(f) => f,
        Err(e) => panic!("file error: {}", e),
    };

    StlFile::read(&mut BufferedReader::new(meshfile));
}
<|endoftext|>


<commit_before>use anyhow::Result;
use crossterm::event::{KeyEvent, KeyModifiers};
use crossterm::{
    event::{self, DisableMouseCapture, EnableMouseCapture, Event as CEvent, KeyCode},
    execute,
    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use histogram::Histogram;
use pinger::{ping, PingResult};
use std::io;
use std::io::Write;
use std::ops::Add;
use std::sync::atomic::Ordering;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use structopt::StructOpt;
use tui::backend::CrosstermBackend;
use tui::layout::{Constraint, Direction, Layout};
use tui::style::{Color, Style};
use tui::text::Span;
use tui::widgets::{Axis, Block, Borders, Chart, Dataset, GraphType, Paragraph};
use tui::{symbols, Terminal};

#[derive(Debug, StructOpt)]
#[structopt(name = "gping", about = "Ping, but with a graph.")]
struct Args {
    #[structopt(help = "Host or IP to ping")]
    host: String,
    #[structopt(
        short,
        long,
        default_value = "100",
        help = "Determines the number pings to display."
    )]
    buffer: usize,
}

struct App {
    data: Vec<(f64, f64)>,
    capacity: usize,
    idx: i64,
    window: [f64; 2],
}

impl App {
    fn new(capacity: usize) -> Self {
        App {
            data: Vec::with_capacity(capacity),
            capacity,
            idx: 0,
            window: [0.0, capacity as f64],
        }
    }
    fn update(&mut self, item: Option<Duration>) {
        self.idx += 1;
        if self.data.len() >= self.capacity {
            self.data.remove(0);
            self.window[0] += 1_f64;
            self.window[1] += 1_f64;
        }
        match item {
            Some(dur) => self.data.push((self.idx as f64, dur.as_micros() as f64)),
            None => self.data.push((self.idx as f64, 0_f64)),
        }
    }
    fn stats(&self) -> Histogram {
        let mut hist = Histogram::new();

        for (_, val) in self.data.iter().filter(|v| v.1 != 0f64) {
            hist.increment(*val as u64).unwrap_or(());
        }

        hist
    }
    fn y_axis_bounds(&self) -> [f64; 2] {
        let min = self
            .data
            .iter()
            .map(|v| v.1)
            .fold(f64::INFINITY, |a, b| a.min(b));
        let max = self.data.iter().map(|v| v.1).fold(0f64, |a, b| a.max(b));
        // Add a 10% buffer to the top and bottom
        let max_10_percent = (max * 10_f64) / 100_f64;
        let min_10_percent = (min * 10_f64) / 100_f64;
        [min - min_10_percent, max + max_10_percent]
    }
    fn y_axis_labels(&self, bounds: [f64; 2]) -> Vec<Span> {
        // Split into 5 sections
        let min = bounds[0];
        let max = bounds[1];

        let difference = max - min;
        let increment = Duration::from_micros((difference / 3f64) as u64);
        let duration = Duration::from_micros(min as u64);

        (0..7)
            .map(|i| Span::raw(format!("{:?}", duration.add(increment * i))))
            .collect()
    }
}

#[derive(Debug)]
enum Event {
    Update(PingResult),
    Input(KeyEvent),
}

fn main() -> Result<()> {
    let args = Args::from_args();
    let mut app = App::new(args.buffer);
    enable_raw_mode()?;
    let mut stdout = io::stdout();
    execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
    let backend = CrosstermBackend::new(stdout);

    let mut terminal = Terminal::new(backend)?;

    terminal.clear()?;

    let (key_tx, rx) = mpsc::channel();

    let ping_tx = key_tx.clone();

    let killed = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));

    let host = args.host.clone();

    // Pump ping messages into the queue
    let killed_ping = std::sync::Arc::clone(&killed);
    let ping_thread = thread::spawn(move || -> Result<()> {
        let stream = ping(host).expect("Error pinging");
        while !killed_ping.load(Ordering::Acquire) {
            ping_tx.send(Event::Update(stream.recv()?))?;
        }
        Ok(())
    });

    // Pump keyboard messages into the queue
    let killed_thread = std::sync::Arc::clone(&killed);
    let key_thread = thread::spawn(move || -> Result<()> {
        while !killed_thread.load(Ordering::Acquire) {
            if event::poll(Duration::from_secs(1)).unwrap() {
                if let CEvent::Key(key) = event::read().unwrap() {
                    key_tx.send(Event::Input(key)).unwrap();
                }
            }
        }
        Ok(())
    });

    loop {
        match rx.recv()? {
            Event::Update(ping_result) => {
                match ping_result {
                    PingResult::Pong(duration) => app.update(Some(duration)),
                    PingResult::Timeout => app.update(None),
                };
                terminal.draw(|f| {
                    let chunks = Layout::default()
                        .direction(Direction::Vertical)
                        .margin(2)
                        .constraints(
                            [Constraint::Percentage(5), Constraint::Percentage(10)].as_ref(),
                        )
                        .split(f.size());

                    let header_layout = Layout::default()
                        .direction(Direction::Horizontal)
                        .constraints(
                            [
                                Constraint::Percentage(25),
                                Constraint::Percentage(25),
                                Constraint::Percentage(25),
                                Constraint::Percentage(25),
                            ]
                            .as_ref(),
                        )
                        .split(chunks[0]);

                    f.render_widget(
                        Paragraph::new(format!("Pinging {}", &args.host)),
                        header_layout[0],
                    );

                    let stats = app.stats();

                    f.render_widget(
                        Paragraph::new(format!(
                            "min {:?}",
                            Duration::from_micros(stats.minimum().unwrap_or(0))
                        )),
                        header_layout[1],
                    );
                    f.render_widget(
                        Paragraph::new(format!(
                            "max {:?}",
                            Duration::from_micros(stats.maximum().unwrap_or(0))
                        )),
                        header_layout[2],
                    );
                    f.render_widget(
                        Paragraph::new(format!(
                            "p95 {:?}",
                            Duration::from_micros(stats.percentile(95.0).unwrap_or(0))
                        )),
                        header_layout[3],
                    );

                    let dataset = Dataset::default()
                        .marker(symbols::Marker::Braille)
                        .style(Style::default().fg(Color::Cyan))
                        .graph_type(GraphType::Line)
                        .data(&app.data);

                    let y_axis_bounds = app.y_axis_bounds();

                    let chart = Chart::new(vec![dataset])
                        .block(Block::default().borders(Borders::NONE))
                        .x_axis(
                            Axis::default()
                                .style(Style::default().fg(Color::Gray))
                                .bounds(app.window),
                        )
                        .y_axis(
                            Axis::default()
                                .style(Style::default().fg(Color::Gray))
                                .bounds(y_axis_bounds)
                                .labels(app.y_axis_labels(y_axis_bounds)),
                        );
                    f.render_widget(chart, chunks[1]);
                })?;
            }
            Event::Input(input) => match input.code {
                KeyCode::Char('q') | KeyCode::Esc => {
                    killed.store(true, Ordering::Release);
                    break;
                }
                KeyCode::Char('c') if input.modifiers == KeyModifiers::CONTROL => {
                    killed.store(true, Ordering::Release);
                    break;
                }
                _ => {}
            },
        }
    }

    let _ = ping_thread.join().unwrap().unwrap();
    let _ = key_thread.join().unwrap().unwrap();

    disable_raw_mode()?;
    execute!(
        terminal.backend_mut(),
        LeaveAlternateScreen,
        DisableMouseCapture
    )?;
    terminal.show_cursor()?;

    Ok(())
}
<commit_msg>Use as little unwrap as possible.<commit_after>use anyhow::Result;
use crossterm::event::{KeyEvent, KeyModifiers};
use crossterm::{
    event::{self, DisableMouseCapture, EnableMouseCapture, Event as CEvent, KeyCode},
    execute,
    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use histogram::Histogram;
use pinger::{ping, PingResult};
use std::io;
use std::io::Write;
use std::ops::Add;
use std::sync::atomic::Ordering;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use structopt::StructOpt;
use tui::backend::CrosstermBackend;
use tui::layout::{Constraint, Direction, Layout};
use tui::style::{Color, Style};
use tui::text::Span;
use tui::widgets::{Axis, Block, Borders, Chart, Dataset, GraphType, Paragraph};
use tui::{symbols, Terminal};

#[derive(Debug, StructOpt)]
#[structopt(name = "gping", about = "Ping, but with a graph.")]
struct Args {
    #[structopt(help = "Host or IP to ping")]
    host: String,
    #[structopt(
        short,
        long,
        default_value = "100",
        help = "Determines the number pings to display."
    )]
    buffer: usize,
}

struct App {
    data: Vec<(f64, f64)>,
    capacity: usize,
    idx: i64,
    window: [f64; 2],
}

impl App {
    fn new(capacity: usize) -> Self {
        App {
            data: Vec::with_capacity(capacity),
            capacity,
            idx: 0,
            window: [0.0, capacity as f64],
        }
    }
    fn update(&mut self, item: Option<Duration>) {
        self.idx += 1;
        if self.data.len() >= self.capacity {
            self.data.remove(0);
            self.window[0] += 1_f64;
            self.window[1] += 1_f64;
        }
        match item {
            Some(dur) => self.data.push((self.idx as f64, dur.as_micros() as f64)),
            None => self.data.push((self.idx as f64, 0_f64)),
        }
    }
    fn stats(&self) -> Histogram {
        let mut hist = Histogram::new();

        for (_, val) in self.data.iter().filter(|v| v.1 != 0f64) {
            hist.increment(*val as u64).unwrap_or(());
        }

        hist
    }
    fn y_axis_bounds(&self) -> [f64; 2] {
        let min = self
            .data
            .iter()
            .map(|v| v.1)
            .fold(f64::INFINITY, |a, b| a.min(b));
        let max = self.data.iter().map(|v| v.1).fold(0f64, |a, b| a.max(b));
        // Add a 10% buffer to the top and bottom
        let max_10_percent = (max * 10_f64) / 100_f64;
        let min_10_percent = (min * 10_f64) / 100_f64;
        [min - min_10_percent, max + max_10_percent]
    }
    fn y_axis_labels(&self, bounds: [f64; 2]) -> Vec<Span> {
        // Split into 5 sections
        let min = bounds[0];
        let max = bounds[1];

        let difference = max - min;
        let increment = Duration::from_micros((difference / 3f64) as u64);
        let duration = Duration::from_micros(min as u64);

        (0..7)
            .map(|i| Span::raw(format!("{:?}", duration.add(increment * i))))
            .collect()
    }
}

#[derive(Debug)]
enum Event {
    Update(PingResult),
    Input(KeyEvent),
}

fn main() -> Result<()> {
    let args = Args::from_args();
    let mut app = App::new(args.buffer);
    enable_raw_mode()?;
    let mut stdout = io::stdout();
    execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
    let backend = CrosstermBackend::new(stdout);

    let mut terminal = Terminal::new(backend)?;

    terminal.clear()?;

    let (key_tx, rx) = mpsc::channel();

    let ping_tx = key_tx.clone();

    let killed = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));

    let host = args.host.clone();

    // Pump ping messages into the queue
    let killed_ping = std::sync::Arc::clone(&killed);
    let ping_thread = thread::spawn(move || -> Result<()> {
        let stream = ping(host)?;
        while !killed_ping.load(Ordering::Acquire) {
            ping_tx.send(Event::Update(stream.recv()?))?;
        }
        Ok(())
    });

    // Pump keyboard messages into the queue
    let killed_thread = std::sync::Arc::clone(&killed);
    let key_thread = thread::spawn(move || -> Result<()> {
        while !killed_thread.load(Ordering::Acquire) {
            if event::poll(Duration::from_secs(1))? {
                if let CEvent::Key(key) = event::read()? {
                    key_tx.send(Event::Input(key))?;
                }
            }
        }
        Ok(())
    });

    loop {
        match rx.recv()? {
            Event::Update(ping_result) => {
                match ping_result {
                    PingResult::Pong(duration) => app.update(Some(duration)),
                    PingResult::Timeout => app.update(None),
                };
                terminal.draw(|f| {
                    let chunks = Layout::default()
                        .direction(Direction::Vertical)
                        .margin(2)
                        .constraints(
                            [Constraint::Percentage(5), Constraint::Percentage(10)].as_ref(),
                        )
                        .split(f.size());

                    let header_layout = Layout::default()
                        .direction(Direction::Horizontal)
                        .constraints(
                            [
                                Constraint::Percentage(25),
                                Constraint::Percentage(25),
                                Constraint::Percentage(25),
                                Constraint::Percentage(25),
                            ]
                            .as_ref(),
                        )
                        .split(chunks[0]);

                    f.render_widget(
                        Paragraph::new(format!("Pinging {}", &args.host)),
                        header_layout[0],
                    );

                    let stats = app.stats();

                    f.render_widget(
                        Paragraph::new(format!(
                            "min {:?}",
                            Duration::from_micros(stats.minimum().unwrap_or(0))
                        )),
                        header_layout[1],
                    );
                    f.render_widget(
                        Paragraph::new(format!(
                            "max {:?}",
                            Duration::from_micros(stats.maximum().unwrap_or(0))
                        )),
                        header_layout[2],
                    );
                    f.render_widget(
                        Paragraph::new(format!(
                            "p95 {:?}",
                            Duration::from_micros(stats.percentile(95.0).unwrap_or(0))
                        )),
                        header_layout[3],
                    );

                    let dataset = Dataset::default()
                        .marker(symbols::Marker::Braille)
                        .style(Style::default().fg(Color::Cyan))
                        .graph_type(GraphType::Line)
                        .data(&app.data);

                    let y_axis_bounds = app.y_axis_bounds();

                    let chart = Chart::new(vec![dataset])
                        .block(Block::default().borders(Borders::NONE))
                        .x_axis(
                            Axis::default()
                                .style(Style::default().fg(Color::Gray))
                                .bounds(app.window),
                        )
                        .y_axis(
                            Axis::default()
                                .style(Style::default().fg(Color::Gray))
                                .bounds(y_axis_bounds)
                                .labels(app.y_axis_labels(y_axis_bounds)),
                        );
                    f.render_widget(chart, chunks[1]);
                })?;
            }
            Event::Input(input) => match input.code {
                KeyCode::Char('q') | KeyCode::Esc => {
                    killed.store(true, Ordering::Release);
                    break;
                }
                KeyCode::Char('c') if input.modifiers == KeyModifiers::CONTROL => {
                    killed.store(true, Ordering::Release);
                    break;
                }
                _ => {}
            },
        }
    }

    let _ = ping_thread.join().unwrap()?;
    let _ = key_thread.join().unwrap()?;

    disable_raw_mode()?;
    execute!(
        terminal.backend_mut(),
        LeaveAlternateScreen,
        DisableMouseCapture
    )?;
    terminal.show_cursor()?;

    Ok(())
}
<|endoftext|>


<commit_before>extern crate termion;
extern crate thread_scoped;
extern crate time;
extern crate voodoo;

mod level;
mod program;

use std::io::{Write};
use std::sync::mpsc::channel;

use termion::event::{Key, Event, MouseEvent};
use termion::input::{TermRead};

use voodoo::color::ColorValue;
use voodoo::window::{Point, TermCell, Window};

use level::Level;
use program::{Program, ProgramRef};

const LEVEL_DESCR: [&'static str; 22] = [
    "                                                          ",
    "                                                          ",
    "                                                          ",
    "                                                          ",
    "          .........................                       ",
    "          .........................                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "   o................             .....                    ",
    "   o................             .....                    ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          .........................                       ",
    "          .........................                       ",
    "                                                          ",
    "                                                          ",
    "                                                          ",
];

enum UiState {
    Unselected,
    Selected,
    Damage(ProgramRef, usize),
}

enum UiEvent {
    Tick,
    Test,
    Click(Point),
    // Movement
}

enum GameState {
    Setup,
    PlayerTurn,
    AITurn,
    AIExecute,
    Quit,
}

struct InfoView {
    window: Window,
}

impl InfoView {
    fn new(window: Window) -> InfoView {
        InfoView {
            window: window,
        }
    }

    fn refresh(&mut self, stdout: &mut std::io::Stdout) {
        self.window.refresh(stdout);
    }

    fn clear(&mut self) {
        for col in 2..self.window.width - 2 {
            self.window.put_at(Point::new(col, 2), ' ');
        }
    }

    fn display_program(&mut self, program: &Program) {
        self.window.print_at(Point::new(2, 2), &program.name);
    }
}

struct MapView {
    window: Window,
    highlight: Option<ProgramRef>,
    overlay: Vec<(Point, TermCell)>,
}

impl MapView {
    fn new(window: Window) -> MapView {
        MapView {
            window: window,
            highlight: None,
            overlay: Vec::new(),
        }
    }

    fn from_global_frame(&self, p: Point) -> Option<Point> {
        self.window.position.from_global_frame(p)
    }

    fn display(&mut self, level: &Level) {
        for (y, line) in level.layout.iter().enumerate() {
            let y = y + 1;
            for (x, tile) in line.chars().enumerate() {
                let x = x + 1;
                match Level::convert(tile) {
                    Some(c) => self.window.put_at(Point::new(x as u16, y as u16), c),
                    None => {},
                }
            }
        }

        for program in level.player_programs.iter() {
            program.borrow().display_color(ColorValue::Green, &mut self.window);
        }

        for program in level.enemy_programs.iter() {
            program.borrow().display_color(ColorValue::Red, &mut self.window);
        }

        if let Some(ref program) = self.highlight {
            program.borrow().display_color(ColorValue::Blue, &mut self.window);
        }

        for &(p, c) in self.overlay.iter() {
            self.window.put_at(p, c);
        }
    }

    fn refresh(&mut self, stdout: &mut std::io::Stdout) {
        self.window.refresh(stdout);
    }

    fn highlight(&mut self, program: ProgramRef, level: &Level) {
        self.highlight = Some(program.clone());
        self.update_highlight(level);
    }

    fn update_highlight(&mut self, level: &Level) {
        if let Some(ref program) = self.highlight {
            self.overlay.clear();
            let program = program.borrow();

            if !program.can_move() {
                return;
            }

            let Point { x, y } = program.position;
            let east = Point::new(x + 1, y);
            if level.passable(east) {
                self.overlay.push((east, '→'.into()));
            }
            let west = Point::new(x - 1, y);
            if level.passable(west) {
                self.overlay.push((west, '←'.into()));
            }
            let north = Point::new(x, y - 1);
            if level.passable(north) {
                self.overlay.push((north, '↑'.into()));
            }
            let south = Point::new(x, y + 1);
            if level.passable(south) {
                self.overlay.push((south, '↓'.into()));
            }
        }
    }

    fn clear_highlight(&mut self) {
        self.highlight = None;
        self.overlay.clear();
    }
}

struct UiModelView {
    info: InfoView,
    map: MapView,
}

impl UiState {
    fn translate_click(click: Point, map: &MapView) -> Option<Point> {
        for &(point, _) in map.overlay.iter() {
            if click == point {
                return Some(point);
            }
        }
        None
    }

    fn next(self, event: UiEvent, level: &mut Level, mv: &mut UiModelView) -> UiState {
        use UiEvent::*;
        use UiState::*;

        let UiModelView { ref mut info, ref mut map } = *mv;

        match (self, event) {
            (Unselected, Click(p)) => {
                for program in level.player_programs.iter() {
                    if program.borrow().intersects(p) {
                        map.highlight(program.clone(), &level);
                        info.display_program(&program.borrow());
                        return Selected;
                    }
                }
                Unselected
            }
            (Selected, Click(p)) => {
                let result = Self::translate_click(p, map);
                if let Some(p) = result {
                    if let Some(ref mut program) = map.highlight {
                        program.borrow_mut().move_to(p);
                    }
                    map.update_highlight(&level);
                    Selected
                }
                else {
                    map.clear_highlight();
                    info.clear();
                    Unselected
                }
            }
            (Damage(program, damage), Tick) => {
                if damage == 0 {
                    if map.highlight.is_some() {
                        Selected
                    }
                    else {
                        Unselected
                    }
                }
                else {
                    if program.borrow_mut().damage() {
                        Damage(program, damage - 1)
                    }
                    else {
                        level.remove_program_at(program.borrow().position);
                        map.clear_highlight();
                        Damage(program, 0)
                    }
                }
            }
            (Damage(program, damage), _) => {
                Damage(program, damage)
            }
            (state, Tick) => { state }
            (Selected, Test) => {
                if let Some(ref program) = map.highlight {
                    Damage(program.clone(), 2)
                }
                else {
                    Selected
                }
            }
            (state, Test) => { state },
        }
    }
}

struct GameModelView {
    ui_modelview: UiModelView,
}

struct State(GameState, UiState);

impl State {
    fn next(self, event: termion::event::Event, level: &mut Level, mv: &mut GameModelView) -> State {
        use GameState::*;
        match self {
            State(PlayerTurn, ui) => Self::next_player_turn(ui, event, level, mv),
            _ => unimplemented!(),
        }
    }

    fn next_player_turn(ui_state: UiState, event: termion::event::Event, level: &mut Level, mv: &mut GameModelView) -> State {
        use GameState::*;
        match event {
            Event::Key(Key::Char('q')) => State(Quit, ui_state),
            Event::Key(Key::Char(' ')) => {
                let new_state = ui_state.next(UiEvent::Test, level, &mut mv.ui_modelview);
                State(PlayerTurn, new_state)
            },
            Event::Mouse(me) => {
                match me {
                    MouseEvent::Press(_, x, y) => {
                        if let Some(p) = mv.ui_modelview.map.from_global_frame(Point::new(x, y)) {
                            let new_state = ui_state.next(
                                UiEvent::Click(p),
                                level,
                                &mut mv.ui_modelview,
                            );
                            State(PlayerTurn, new_state)
                        }
                        else {
                            State(PlayerTurn, ui_state)
                        }
                    },
                    _ => State(PlayerTurn, ui_state),
                }
            }
            _ => State(PlayerTurn, ui_state),
        }
    }
}

fn main() {
    use std::sync::mpsc::TryRecvError::*;
    use std::thread;
    use std::time::Duration;

    use voodoo::terminal::{Mode, Terminal};

    let mut level = Level::new(&LEVEL_DESCR);
    level.add_player_program(Program::new(Point::new(11, 11), "Hack"));
    level.add_player_program(Program::new(Point::new(5, 12), "Hack"));
    level.add_enemy_program(Program::new(Point::new(7, 12), "Hack"));

    let mut terminal = Terminal::new();
    terminal.cursor(Mode::Disabled);
    terminal.clear_color(ColorValue::Black);
    let Terminal { ref mut stdin, ref mut stdout } = terminal;

    stdout.flush().unwrap();

    let mut info = voodoo::window::Window::new(Point::new(0, 0), 20, 24);
    let mut map = voodoo::window::Window::new(Point::new(20, 0), 60, 24);
    info.border();
    map.border();

    let mut info_view = InfoView::new(info);
    let mut map_view = MapView::new(map);
    info_view.refresh(stdout);
    map_view.display(&level);
    map_view.refresh(stdout);

    let mut ui_modelview = UiModelView {
        info: info_view,
        map: map_view,
    };
    let mut ui_state = UiState::Unselected;

    let mut state = State(GameState::PlayerTurn, ui_state);
    let mut mv = GameModelView {
        ui_modelview: ui_modelview,
    };

    let (tx, rx) = channel();
    let guard = unsafe {
        thread_scoped::scoped(move || {
            for c in stdin.events() {
                let evt = c.unwrap();
                if let Event::Key(Key::Char('q')) = evt {
                    break;
                }
                tx.send(evt).unwrap();
            }
        })
    };

    let mut t = time::precise_time_ns();
    let mut dt = 0;

    'main: loop {
        loop {
            // Handle all pending events
            let msg = rx.try_recv();
            match msg {
                Ok(evt) => {
                    state = state.next(evt, &mut level, &mut mv);
                    if let State(GameState::Quit, _) = state {
                        break 'main;
                    }
                },
                Err(Disconnected) => break 'main,
                Err(Empty) => break,
            }
        }

        let now = time::precise_time_ns();
        dt += now - t;

        // TODO: use constant
        while dt >= 100000000 {
            // state = state.next(UiEvent::Tick, &mut level, &mut mv);
            // ui_state = ui_state.next(UiEvent::Tick, &mut level, &mut ui_modelview);
            dt -= 100000000;
        }

        mv.ui_modelview.info.refresh(stdout);
        mv.ui_modelview.map.display(&level);
        mv.ui_modelview.map.refresh(stdout);
        t = now;

        thread::sleep(Duration::from_millis(100 - dt / 1000000));
    }
    guard.join();
}
<commit_msg>refactor: separate event translation and dispatching<commit_after>extern crate termion;
extern crate thread_scoped;
extern crate time;
extern crate voodoo;

mod level;
mod program;

use std::io::{Write};
use std::sync::mpsc::channel;

use termion::event::{Key, Event, MouseEvent};
use termion::input::{TermRead};

use voodoo::color::ColorValue;
use voodoo::window::{Point, TermCell, Window};

use level::Level;
use program::{Program, ProgramRef};

const LEVEL_DESCR: [&'static str; 22] = [
    "                                                          ",
    "                                                          ",
    "                                                          ",
    "                                                          ",
    "          .........................                       ",
    "          .........................                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "   o................             .....                    ",
    "   o................             .....                    ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          .........................                       ",
    "          .........................                       ",
    "                                                          ",
    "                                                          ",
    "                                                          ",
];

enum UiState {
    Unselected,
    Selected,
    Damage(ProgramRef, usize),
}

enum UiEvent {
    Quit,
    Tick,
    Test,
    Click(Point),
    // Movement
}

enum GameState {
    Setup,
    PlayerTurn,
    AITurn,
    AIExecute,
    Quit,
}

struct InfoView {
    window: Window,
}

impl InfoView {
    fn new(window: Window) -> InfoView {
        InfoView {
            window: window,
        }
    }

    fn refresh(&mut self, stdout: &mut std::io::Stdout) {
        self.window.refresh(stdout);
    }

    fn clear(&mut self) {
        for col in 2..self.window.width - 2 {
            self.window.put_at(Point::new(col, 2), ' ');
        }
    }

    fn display_program(&mut self, program: &Program) {
        self.window.print_at(Point::new(2, 2), &program.name);
    }
}

struct MapView {
    window: Window,
    highlight: Option<ProgramRef>,
    overlay: Vec<(Point, TermCell)>,
}

impl MapView {
    fn new(window: Window) -> MapView {
        MapView {
            window: window,
            highlight: None,
            overlay: Vec::new(),
        }
    }

    fn from_global_frame(&self, p: Point) -> Option<Point> {
        self.window.position.from_global_frame(p)
    }

    fn display(&mut self, level: &Level) {
        for (y, line) in level.layout.iter().enumerate() {
            let y = y + 1;
            for (x, tile) in line.chars().enumerate() {
                let x = x + 1;
                match Level::convert(tile) {
                    Some(c) => self.window.put_at(Point::new(x as u16, y as u16), c),
                    None => {},
                }
            }
        }

        for program in level.player_programs.iter() {
            program.borrow().display_color(ColorValue::Green, &mut self.window);
        }

        for program in level.enemy_programs.iter() {
            program.borrow().display_color(ColorValue::Red, &mut self.window);
        }

        if let Some(ref program) = self.highlight {
            program.borrow().display_color(ColorValue::Blue, &mut self.window);
        }

        for &(p, c) in self.overlay.iter() {
            self.window.put_at(p, c);
        }
    }

    fn refresh(&mut self, stdout: &mut std::io::Stdout) {
        self.window.refresh(stdout);
    }

    fn highlight(&mut self, program: ProgramRef, level: &Level) {
        self.highlight = Some(program.clone());
        self.update_highlight(level);
    }

    fn update_highlight(&mut self, level: &Level) {
        if let Some(ref program) = self.highlight {
            self.overlay.clear();
            let program = program.borrow();

            if !program.can_move() {
                return;
            }

            let Point { x, y } = program.position;
            let east = Point::new(x + 1, y);
            if level.passable(east) {
                self.overlay.push((east, '→'.into()));
            }
            let west = Point::new(x - 1, y);
            if level.passable(west) {
                self.overlay.push((west, '←'.into()));
            }
            let north = Point::new(x, y - 1);
            if level.passable(north) {
                self.overlay.push((north, '↑'.into()));
            }
            let south = Point::new(x, y + 1);
            if level.passable(south) {
                self.overlay.push((south, '↓'.into()));
            }
        }
    }

    fn clear_highlight(&mut self) {
        self.highlight = None;
        self.overlay.clear();
    }
}

struct UiModelView {
    info: InfoView,
    map: MapView,
}

impl UiState {
    fn translate_click(click: Point, map: &MapView) -> Option<Point> {
        for &(point, _) in map.overlay.iter() {
            if click == point {
                return Some(point);
            }
        }
        None
    }

    fn next(self, event: UiEvent, level: &mut Level, mv: &mut UiModelView) -> UiState {
        use UiEvent::*;
        use UiState::*;

        let UiModelView { ref mut info, ref mut map } = *mv;

        match (self, event) {
            (Unselected, Click(p)) => {
                for program in level.player_programs.iter() {
                    if program.borrow().intersects(p) {
                        map.highlight(program.clone(), &level);
                        info.display_program(&program.borrow());
                        return Selected;
                    }
                }
                Unselected
            }
            (Selected, Click(p)) => {
                let result = Self::translate_click(p, map);
                if let Some(p) = result {
                    if let Some(ref mut program) = map.highlight {
                        program.borrow_mut().move_to(p);
                    }
                    map.update_highlight(&level);
                    Selected
                }
                else {
                    map.clear_highlight();
                    info.clear();
                    Unselected
                }
            }
            (Damage(program, damage), Tick) => {
                if damage == 0 {
                    if map.highlight.is_some() {
                        Selected
                    }
                    else {
                        Unselected
                    }
                }
                else {
                    if program.borrow_mut().damage() {
                        Damage(program.clone(), damage - 1)
                    }
                    else {
                        level.remove_program_at(program.borrow().position);
                        map.clear_highlight();
                        Damage(program.clone(), 0)
                    }
                }
            }
            (Damage(program, damage), _) => {
                Damage(program, damage)
            }
            (state, Tick) => { state }
            (Selected, Test) => {
                if let Some(ref program) = map.highlight {
                    Damage(program.clone(), 2)
                }
                else {
                    Selected
                }
            }
            (state, Test) => { state },
            (state, Quit) => { state },
        }
    }
}

struct GameModelView {
    ui_modelview: UiModelView,
}

struct State(GameState, UiState);

impl State {
    fn translate_event(&self, event: termion::event::Event, level: &mut Level, mv: &mut GameModelView) -> Option<UiEvent> {
        use GameState::*;
        match (self, event) {
            (_, Event::Key(Key::Char('q'))) => Some(UiEvent::Quit),
            (&State(PlayerTurn, _), Event::Mouse(MouseEvent::Press(_, x, y))) => {
                if let Some(p) = mv.ui_modelview.map.from_global_frame(Point::new(x, y)) {
                    Some(UiEvent::Click(p))
                }
                else {
                    None
                }
            }
            _ => None,
        }
    }

    fn next(self, event: termion::event::Event, level: &mut Level, mv: &mut GameModelView) -> State {
        use GameState::*;
        if let Some(event) = self.translate_event(event, level, mv) {
            match self {
                State(PlayerTurn, ui) => Self::next_player_turn(ui, event, level, mv),
                _ => unimplemented!(),
            }
        }
        else {
            self
        }
    }

    fn next_player_turn(ui_state: UiState, event: UiEvent, level: &mut Level, mv: &mut GameModelView) -> State {
        use GameState::*;
        match event {
            UiEvent::Quit => State(Quit, ui_state),
            click@UiEvent::Click(_) => {
                State(PlayerTurn, ui_state.next(click, level, &mut mv.ui_modelview))
            }
            _ => State(PlayerTurn, ui_state),
        }
    }
}

fn main() {
    use std::sync::mpsc::TryRecvError::*;
    use std::thread;
    use std::time::Duration;

    use voodoo::terminal::{Mode, Terminal};

    let mut level = Level::new(&LEVEL_DESCR);
    level.add_player_program(Program::new(Point::new(11, 11), "Hack"));
    level.add_player_program(Program::new(Point::new(5, 12), "Hack"));
    level.add_enemy_program(Program::new(Point::new(7, 12), "Hack"));

    let mut terminal = Terminal::new();
    terminal.cursor(Mode::Disabled);
    terminal.clear_color(ColorValue::Black);
    let Terminal { ref mut stdin, ref mut stdout } = terminal;

    stdout.flush().unwrap();

    let mut info = voodoo::window::Window::new(Point::new(0, 0), 20, 24);
    let mut map = voodoo::window::Window::new(Point::new(20, 0), 60, 24);
    info.border();
    map.border();

    let mut info_view = InfoView::new(info);
    let mut map_view = MapView::new(map);
    info_view.refresh(stdout);
    map_view.display(&level);
    map_view.refresh(stdout);

    let mut ui_modelview = UiModelView {
        info: info_view,
        map: map_view,
    };
    let mut ui_state = UiState::Unselected;

    let mut state = State(GameState::PlayerTurn, ui_state);
    let mut mv = GameModelView {
        ui_modelview: ui_modelview,
    };

    let (tx, rx) = channel();
    let guard = unsafe {
        thread_scoped::scoped(move || {
            for c in stdin.events() {
                let evt = c.unwrap();
                if let Event::Key(Key::Char('q')) = evt {
                    break;
                }
                tx.send(evt).unwrap();
            }
        })
    };

    let mut t = time::precise_time_ns();
    let mut dt = 0;

    'main: loop {
        loop {
            // Handle all pending events
            let msg = rx.try_recv();
            match msg {
                Ok(evt) => {
                    state = state.next(evt, &mut level, &mut mv);
                    if let State(GameState::Quit, _) = state {
                        break 'main;
                    }
                },
                Err(Disconnected) => break 'main,
                Err(Empty) => break,
            }
        }

        let now = time::precise_time_ns();
        dt += now - t;

        // TODO: use constant
        while dt >= 100000000 {
            // state = state.next(UiEvent::Tick, &mut level, &mut mv);
            // ui_state = ui_state.next(UiEvent::Tick, &mut level, &mut ui_modelview);
            dt -= 100000000;
        }

        mv.ui_modelview.info.refresh(stdout);
        mv.ui_modelview.map.display(&level);
        mv.ui_modelview.map.refresh(stdout);
        t = now;

        thread::sleep(Duration::from_millis(100 - dt / 1000000));
    }
    guard.join();
}
<|endoftext|>


<commit_before>extern crate libc;
extern crate termios;
extern crate ioctl_rs as ioctl;
use std::os::unix::io::RawFd;
use std::io::{self, Read, Write, BufRead};
use libc::c_ushort;

use termios::*;

const VERSION: &'static str = "0.0.1";
const CLEAR: &'static [u8] = b"\x1b[2J";
const CLEAR_LINE: &'static [u8] = b"\x1b[K";
const SET_CURSOR: &'static [u8] = b"\x1b[H";
const DISABLE_CURSOR: &'static [u8] = b"\x1b[?25l";
const ENABLE_CURSOR: &'static [u8] = b"\x1b[?25h";

struct RawTerm(Termios);

struct Term {
    term: RawTerm,
    screenrows: u16,
    screencols: u16,
    cx: u16,
    cy: u16,
}

#[repr(C)]
#[derive(Default)]
struct Winsize {
    ws_row: c_ushort, /* rows, in characters */
    ws_col: c_ushort, /* columns, in characters */
    ws_xpixel: c_ushort, /* horizontal size, pixels */
    ws_ypixel: c_ushort, /* vertical size, pixels */
}

fn ioctl_window_size() -> io::Result<(u16, u16)> {
    //Err(io::Error::new(io::ErrorKind::Other, "oh no!"))
    let mut w: Winsize = Default::default();
    match unsafe { libc::ioctl(libc::STDIN_FILENO, libc::TIOCGWINSZ, &mut w) } {
            0 => Ok((w.ws_row, w.ws_col)),
            _ => Err(io::Error::last_os_error()),
        }
        .and_then(|(row, col)| if col == 0 {
            Err(io::Error::new(io::ErrorKind::Other, "ioctl error"))
        } else {
            Ok((row, col))
        })
}

fn tty_window_size() -> io::Result<(u16, u16)> {
    io::stdout().write_all(b"\x1b[999C\x1b[999B")?;
    io::stdout().flush()?;
    get_cursor_position()
}

fn get_window_size() -> io::Result<(u16, u16)> {
    ioctl_window_size().or_else(|_| tty_window_size())
}

impl RawTerm {
    fn from(fd: RawFd) -> io::Result<RawTerm> {
        let term = RawTerm(Termios::from_fd(fd)?);
        term.enable_raw()?;
        Ok(term)
    }

    fn enable_raw(&self) -> io::Result<()> {
        let mut raw = self.0.clone();
        raw.c_cflag |= CS8;
        raw.c_iflag &= !(BRKINT | INPCK | ISTRIP | IXON | ICRNL);
        raw.c_oflag &= !(OPOST);
        raw.c_lflag &= !(ECHO | ICANON | ISIG | IEXTEN);

        raw.c_cc[VMIN] = 0;
        raw.c_cc[VTIME] = 1;
        tcsetattr(libc::STDIN_FILENO, TCSANOW, &raw)
    }
}

impl Term {
    fn from(rt: RawTerm) -> io::Result<Term> {
        let (rows, cols) = get_window_size()?;
        Ok(Term {
            term: rt,
            screenrows: rows,
            screencols: cols,
            cx: 0,
            cy: 0,
        })
    }

    fn move_cursor(&mut self, key: char) {
        match key {
            'a' => self.cx = if self.cx == 0 { 0 } else { self.cx - 1 },
            'd' => self.cx += 1,
            'w' => self.cy = if self.cy == 0 { 0 } else { self.cy - 1 },
            's' => self.cy += 1,
            _ => (),
        }
    }
}

impl Drop for RawTerm {
    fn drop(&mut self) {
        println!("restoring terminal");
        execute(CLEAR).unwrap();
        execute(SET_CURSOR).unwrap();
        tcsetattr(libc::STDIN_FILENO, TCSANOW, &self.0).unwrap();
    }
}

fn ctrled(c: char) -> u8 {
    (c as u8) & 0x1f
}

fn editor_read_key(handle: &mut Read) -> char {
    let mut buffer: [u8; 1] = [0];
    loop {
        match handle.take(1).read(&mut buffer) {
            Ok(1) => return buffer[0] as char,
            Ok(0) => continue,
            Ok(_) | Err(_) => panic!("error"),
        };
    }
}

fn editor_process_keypress(term: &mut Term) -> bool {
    let handle = &mut io::stdin();
    let c = editor_read_key(handle);
    match c {
        'a' | 'd' | 'w' | 's' => term.move_cursor(c),
        _ => (),
    }
    c as u8 != ctrled('q')
}

fn editor_draw_rows(term: &Term) -> Vec<u8> {
    let mut v = Vec::new();
    for y in 0..term.screenrows - 1 {
        v.extend(CLEAR_LINE);
        if y == term.screenrows / 3 {
            let welcome = format!("Kilo editor -- version {}\r\n", VERSION);
            let msglen = std::cmp::min(welcome.len(), term.screencols as usize);
            let padding = ((term.screencols as usize) - msglen) / 2;
            for i in 0..padding {
                v.push(if i == 0 { b'~' } else { b' ' });
            }
            v.extend(&welcome.as_bytes()[0..msglen]);
        } else {
            v.extend(b"~\r\n");
        }
    }
    v.extend(CLEAR_LINE);
    v.extend(b"~");
    v
}
fn editor_refresh_screen(term: &Term) -> io::Result<()> {
    let mut v = Vec::new();
    v.extend(DISABLE_CURSOR);
    v.extend(SET_CURSOR);
    v.extend(editor_draw_rows(term));
    v.extend(to_pos(term.cx, term.cy));
    v.extend(ENABLE_CURSOR);
    execute(&v)
}

fn to_pos(x: u16, y: u16) -> Vec<u8> {
    let cmd = format!("\x1b[{};{}H", y + 1, x + 1);
    cmd.into_bytes()
}

fn execute(v: &[u8]) -> io::Result<()> {
    io::stdout().write_all(v)?;
    io::stdout().flush()
}

// terminal commands

fn get_cursor_position() -> io::Result<(u16, u16)> {
    execute(b"\x1b[6n")?;

    let mut vec = Vec::new();

    let stdin = io::stdin();
    let mut handle = stdin.lock();
    handle.read_until(b'R', &mut vec)?;

    //println!("{:?}", &vec);
    //println!("{}", std::str::from_utf8(&vec[2..]).unwrap());
    if vec[0] != 0x1b || vec[1] != b'[' {
        return Err(io::Error::new(io::ErrorKind::Other, "invalid tty response"));
    }
    let dim_str = std::str::from_utf8(&vec[2..vec.len() - 1]).unwrap();
    let v: Vec<u16> = dim_str.split(';')
        .flat_map(|s| s.parse::<u16>().into_iter())
        .collect();
    if v.len() != 2 {
        return Err(io::Error::new(io::ErrorKind::Other, "parse error with tty dim"));
    }
    Ok((v[0], v[1]))
}

fn main() {
    let rawterm = RawTerm::from(libc::STDIN_FILENO).unwrap();
    let mut term = Term::from(rawterm).unwrap();
    loop {
        editor_refresh_screen(&term).unwrap();
        if !editor_process_keypress(&mut term) {
            break;
        }
    }

}
<commit_msg>movement commands, end of ch3<commit_after>extern crate libc;
extern crate termios;
extern crate ioctl_rs as ioctl;
use std::os::unix::io::RawFd;
use std::io::{self, Read, Write, BufRead};
use libc::c_ushort;

use termios::*;

const VERSION: &'static str = "0.0.1";
const CLEAR: &'static [u8] = b"\x1b[2J";
const CLEAR_LINE: &'static [u8] = b"\x1b[K";
const SET_CURSOR: &'static [u8] = b"\x1b[H";
const DISABLE_CURSOR: &'static [u8] = b"\x1b[?25l";
const ENABLE_CURSOR: &'static [u8] = b"\x1b[?25h";

#[derive(PartialEq, Eq, Clone, Copy)]
enum Arrow {
    Left,
    Right,
    Up,
    Down,
}

#[derive(PartialEq, Eq)]
enum Special {
    Del,
    Home,
    End,
    Page(Arrow),
}

enum Keypress {
    Ar(Arrow),
    Ch(char),
    Sp(Special),
}

struct RawTerm(Termios);

struct Editor {
    term: RawTerm,
    screenrows: u16,
    screencols: u16,
    cx: u16,
    cy: u16,
}

#[repr(C)]
#[derive(Default)]
struct Winsize {
    ws_row: c_ushort, /* rows, in characters */
    ws_col: c_ushort, /* columns, in characters */
    ws_xpixel: c_ushort, /* horizontal size, pixels */
    ws_ypixel: c_ushort, /* vertical size, pixels */
}

fn ioctl_window_size() -> io::Result<(u16, u16)> {
    let mut w: Winsize = Default::default();
    match unsafe { libc::ioctl(libc::STDIN_FILENO, libc::TIOCGWINSZ, &mut w) } {
            0 => Ok((w.ws_row, w.ws_col)),
            _ => Err(io::Error::last_os_error()),
        }
        .and_then(|(row, col)| if col == 0 {
            Err(io::Error::new(io::ErrorKind::Other, "ioctl error"))
        } else {
            Ok((row, col))
        })
}

fn tty_window_size() -> io::Result<(u16, u16)> {
    io::stdout().write_all(b"\x1b[999C\x1b[999B")?;
    io::stdout().flush()?;
    get_cursor_position()
}

fn get_window_size() -> io::Result<(u16, u16)> {
    ioctl_window_size().or_else(|_| tty_window_size())
}

impl RawTerm {
    fn from(fd: RawFd) -> io::Result<RawTerm> {
        let term = RawTerm(Termios::from_fd(fd)?);
        term.enable_raw()?;
        Ok(term)
    }

    fn enable_raw(&self) -> io::Result<()> {
        let mut raw = self.0;
        raw.c_cflag |= CS8;
        raw.c_iflag &= !(BRKINT | INPCK | ISTRIP | IXON | ICRNL);
        raw.c_oflag &= !(OPOST);
        raw.c_lflag &= !(ECHO | ICANON | ISIG | IEXTEN);

        raw.c_cc[VMIN] = 0;
        raw.c_cc[VTIME] = 1;
        tcsetattr(libc::STDIN_FILENO, TCSANOW, &raw)
    }
}

impl Editor {
    fn from(rt: RawTerm) -> io::Result<Editor> {
        let (rows, cols) = get_window_size()?;
        Ok(Editor {
            term: rt,
            screenrows: rows,
            screencols: cols,
            cx: 0,
            cy: 0,
        })
    }

    fn move_cursor(&mut self, key: Arrow) {
        match key {
            Arrow::Left => self.cx = if self.cx == 0 { 0 } else { self.cx - 1 },
            Arrow::Right => {
                if self.cx != self.screenrows - 1 {
                    self.cx += 1
                }
            }
            Arrow::Up => self.cy = if self.cy == 0 { 0 } else { self.cy - 1 },
            Arrow::Down => {
                if self.cy != self.screencols - 1 {
                    self.cy += 1
                }
            }
        }
    }

    fn process_keypress(&mut self) -> bool {
        let handle = &mut io::stdin();
        let kp = editor_read_key(handle);
        match kp {
            Keypress::Ar(arrow) => {
                self.move_cursor(arrow);
                true
            }
            Keypress::Ch(c) => c as u8 != ctrled('q'),
            Keypress::Sp(special) => {
                match special {
                    Special::Page(dir) => {
                        for _ in 0..self.screenrows {
                            self.move_cursor(dir);
                        }
                    }
                    Special::Home => self.cx = 0,
                    Special::End => self.cx = self.screencols,
                    _ => (),
                };
                true
            }
        }
    }

    fn refresh_screen(&mut self) -> io::Result<()> {
        let mut v = Vec::new();
        v.extend(DISABLE_CURSOR);
        v.extend(SET_CURSOR);
        v.extend(self.draw_rows());
        v.extend(to_pos(self.cx, self.cy));
        v.extend(ENABLE_CURSOR);
        execute(&v)
    }

    fn draw_rows(&mut self) -> Vec<u8> {
        let mut v = Vec::new();
        for y in 0..self.screenrows - 1 {
            v.extend(CLEAR_LINE);
            if y == self.screenrows / 3 {
                let welcome = format!("Kilo editor -- version {}\r\n", VERSION);
                let msglen = std::cmp::min(welcome.len(), self.screencols as usize);
                let padding = ((self.screencols as usize) - msglen) / 2;
                for i in 0..padding {
                    v.push(if i == 0 { b'~' } else { b' ' });
                }
                v.extend(&welcome.as_bytes()[0..msglen]);
            } else {
                v.extend(b"~\r\n");
            }
        }
        v.extend(CLEAR_LINE);
        v.extend(b"~");
        v
    }
}

impl Drop for RawTerm {
    fn drop(&mut self) {
        println!("restoring terminal");
        execute(CLEAR).unwrap();
        execute(SET_CURSOR).unwrap();
        tcsetattr(libc::STDIN_FILENO, TCSANOW, &self.0).unwrap();
    }
}

fn ctrled(c: char) -> u8 {
    (c as u8) & 0x1f
}

fn read1(handle: &mut Read) -> Option<u8> {
    let mut buffer: [u8; 1] = [0];
    match handle.take(1).read(&mut buffer) {
        Ok(1) => Some(buffer[0]),
        Ok(0) => None,
        Ok(_) | Err(_) => panic!("error"),
    }
}

fn editor_read_key(handle: &mut Read) -> Keypress {
    let c;
    loop {
        match read1(handle) {
            Some(x) => {
                c = x;
                break;
            }
            None => continue,
        }
    }
    if c == b'\x1b' {
        if let (Some(c1), Some(c2)) = (read1(handle), read1(handle)) {
            match (c1, c2) {
                (b'[', b'0'...b'9') => {
                    if let Some(c3) = read1(handle) {
                        match (c3, c2) {
                            (b'~', b'1') | (b'~', b'7') => return Keypress::Sp(Special::Home),
                            (b'~', b'4') | (b'~', b'8') => return Keypress::Sp(Special::End),
                            (b'~', b'3') => return Keypress::Sp(Special::Del),
                            (b'~', b'5') => return Keypress::Sp(Special::Page(Arrow::Up)),
                            (b'~', b'6') => return Keypress::Sp(Special::Page(Arrow::Down)),
                            (_, _) => (),
                        }
                    }
                }
                (b'[', b'A') => return Keypress::Ar(Arrow::Up),
                (b'[', b'B') => return Keypress::Ar(Arrow::Down),
                (b'[', b'C') => return Keypress::Ar(Arrow::Right),
                (b'[', b'D') => return Keypress::Ar(Arrow::Left),
                (b'[', b'H') | (b'O', b'H') => return Keypress::Sp(Special::Home),
                (b'[', b'F') | (b'O', b'F') => return Keypress::Sp(Special::End),
                _ => (),
            }
        }
    }
    Keypress::Ch(c as char)

}

// terminal commands

fn to_pos(x: u16, y: u16) -> Vec<u8> {
    let cmd = format!("\x1b[{};{}H", y + 1, x + 1);
    cmd.into_bytes()
}

fn execute(v: &[u8]) -> io::Result<()> {
    io::stdout().write_all(v)?;
    io::stdout().flush()
}

fn get_cursor_position() -> io::Result<(u16, u16)> {
    execute(b"\x1b[6n")?;

    let mut vec = Vec::new();

    let stdin = io::stdin();
    let mut handle = stdin.lock();
    handle.read_until(b'R', &mut vec)?;

    //println!("{:?}", &vec);
    //println!("{}", std::str::from_utf8(&vec[2..]).unwrap());
    if vec[0] != 0x1b || vec[1] != b'[' {
        return Err(io::Error::new(io::ErrorKind::Other, "invalid tty response"));
    }
    let dim_str = std::str::from_utf8(&vec[2..vec.len() - 1]).unwrap();
    let v: Vec<u16> = dim_str.split(';')
        .flat_map(|s| s.parse::<u16>().into_iter())
        .collect();
    if v.len() != 2 {
        return Err(io::Error::new(io::ErrorKind::Other, "parse error with tty dim"));
    }
    Ok((v[0], v[1]))
}

fn main() {
    let rawterm = RawTerm::from(libc::STDIN_FILENO).unwrap();
    let mut editor = Editor::from(rawterm).unwrap();
    loop {
        editor.refresh_screen().unwrap();
        if !editor.process_keypress() {
            break;
        }
    }

}
<|endoftext|>


<commit_before>#![recursion_limit = "1024"] // error_chain

extern crate websocket;
extern crate env_logger;
#[macro_use]
extern crate log;
#[macro_use]
extern crate error_chain;
extern crate url;
extern crate clap;

use std::thread;
use std::io::{stdin,stdout};

use websocket::{Message, Sender, Receiver, DataFrame};
use websocket::message::Type;
use websocket::client::request::Url;
use websocket::Client;

use std::borrow::Borrow;
use std::io::{Error as IoError, ErrorKind as IoErrorKind, Write, Read};

error_chain! {
    foreign_links {
        Io(::std::io::Error);
        Log(log::SetLoggerError);
        Url(::url::ParseError);
        Ws(::websocket::result::WebSocketError);
        VarError(::std::env::VarError);
        RE(std::sync::mpsc::RecvError);
    }
}

// Initialize logger with default "info" log level:
fn init_logger() -> Result<()> {
    let mut builder = env_logger::LogBuilder::new();
    builder.filter(None, log::LogLevelFilter::Info);
    if ::std::env::var("RUST_LOG").is_ok() {
       builder.parse(&::std::env::var("RUST_LOG")?);
    }
    builder.init()?;
    Ok(())
}

struct SenderWrapper<T: Sender> (T);

impl<T: Sender> ::std::io::Write for SenderWrapper<T> {
    fn write(&mut self, buf: &[u8]) -> ::std::io::Result<usize> {
        let message = Message::binary(buf);
        let ret;
        let len = buf.len();
        if len > 0 {
            debug!("Sending message of {} bytes", len);
            ret = self.0.send_message(&message);
        } else {
            // Interpret zero length buffer is request
            // to close communication

            debug!("Sending the closing message");
            ret = self.0.send_message(&Message::close());
        }
        ret.map_err(|e|IoError::new(IoErrorKind::BrokenPipe, e))?;
        Ok(len)
    }
    fn flush(&mut self) -> ::std::io::Result<()> {
        Ok(())
    }
}

struct ReceiverWrapper<T: Receiver<DataFrame>> (T);

impl<T:Receiver<DataFrame>> ::std::io::Read for ReceiverWrapper<T> {
    fn read(&mut self, buf: &mut [u8]) -> ::std::io::Result<usize> {
        let ret = self.0.recv_message();
        let msg : Message = ret.map_err(|e|IoError::new(IoErrorKind::BrokenPipe, e))?;

        match msg.opcode {
            Type::Close => {
                Ok(0)
            }
            Type::Ping => {
                // Sender used to be in a separate thread with a channel
                // now there's no channel, so trickier to combine ping replies
                // and usual data exchange
                unimplemented!();
            }
            _ => {
                let msgpayload : &[u8] = msg.payload.borrow();
                let len = msgpayload.len();
                debug!("Received message of {} bytes", len);

                assert!(buf.len() >= len);

                buf[0..len].clone_from_slice(msgpayload);

                Ok(len)
            }
        }
    }
}

struct Peer<R, W>
    where R : Read + Send + 'static, W: Write + Send + 'static
{
    reader: R,
    writer: W,
}

struct DataExchangeSession<R1, R2, W1, W2>
    where R1 : Read  + Send + 'static,
          R2 : Read  + Send + 'static,
          W1 : Write + Send + 'static,
          W2 : Write + Send + 'static,
{
    peer1: Peer<R1, W1>,
    peer2: Peer<R2, W2>,
}

impl<R1,R2,W1,W2> DataExchangeSession<R1,R2,W1,W2>
    where R1 : Read  + Send + 'static,
          R2 : Read  + Send + 'static,
          W1 : Write + Send + 'static,
          W2 : Write + Send + 'static,
{
    fn data_exchange(self) -> Result<()> {

        let mut reader1 = self.peer1.reader;
        let mut writer1 = self.peer1.writer;
        let mut reader2 = self.peer2.reader;
        let mut writer2 = self.peer2.writer;

        let receive_loop = thread::spawn(move || -> Result<()> {
            // Actual data transfer happens here
            ::std::io::copy(&mut reader1, &mut writer2)?;
            writer2.write(b"")?; // signal close
            Ok(())
        });

        // Actual data transfer happens here
        ::std::io::copy(&mut reader2, &mut writer1)?;
        writer1.write(b"")?; // Signal close

        info!("Waiting for child threads to exit");

        receive_loop.join().map_err(|x|format!("{:?}",x))?
    }
}

fn try_main() -> Result<()> {
    //env_logger::init()?;
    init_logger()?;

    // setup command line arguments
    let matches = ::clap::App::new("WS Command Line Client")
        .version("0.1")
        .author("Vitaly \"_Vi\" Shukela <vi0oss@gmail.com>")
        .about("Send binary data from stdin to a WebSocket and back to stdout.")
        .arg(::clap::Arg::with_name("URL")
             .help("The URL of the WebSocket server.")
             .required(true)
             .index(1)).get_matches();

    let url = Url::parse(matches.value_of("URL").ok_or("no URL")?)?;

    info!("Connecting to {}", url);

    let request = Client::connect(url)?;

    let response = request.send()?; // Send the request and retrieve a response

    info!("Validating response...");

    response.validate()?; // Validate the response

    info!("Successfully connected");

    let (sender, receiver) = response.begin().split();

    let des = DataExchangeSession {
        peer1 : Peer {
            reader : ReceiverWrapper(receiver),
            writer : SenderWrapper(sender),
        },
        peer2 : Peer {
            reader : stdin(),
            writer : stdout(),
        },
    };

    des.data_exchange()?;

    info!("Exited");
    Ok(())
}

fn main() {
    if let Err(x) = try_main() {
        let _ = writeln!(::std::io::stderr(), "{:?}", x);
    }
}

<commit_msg>copy_from_slice<commit_after>#![recursion_limit = "1024"] // error_chain

extern crate websocket;
extern crate env_logger;
#[macro_use]
extern crate log;
#[macro_use]
extern crate error_chain;
extern crate url;
extern crate clap;

use std::thread;
use std::io::{stdin,stdout};

use websocket::{Message, Sender, Receiver, DataFrame};
use websocket::message::Type;
use websocket::client::request::Url;
use websocket::Client;

use std::borrow::Borrow;
use std::io::{Error as IoError, ErrorKind as IoErrorKind, Write, Read};

error_chain! {
    foreign_links {
        Io(::std::io::Error);
        Log(log::SetLoggerError);
        Url(::url::ParseError);
        Ws(::websocket::result::WebSocketError);
        VarError(::std::env::VarError);
        RE(std::sync::mpsc::RecvError);
    }
}

// Initialize logger with default "info" log level:
fn init_logger() -> Result<()> {
    let mut builder = env_logger::LogBuilder::new();
    builder.filter(None, log::LogLevelFilter::Info);
    if ::std::env::var("RUST_LOG").is_ok() {
       builder.parse(&::std::env::var("RUST_LOG")?);
    }
    builder.init()?;
    Ok(())
}

struct SenderWrapper<T: Sender> (T);

impl<T: Sender> ::std::io::Write for SenderWrapper<T> {
    fn write(&mut self, buf: &[u8]) -> ::std::io::Result<usize> {
        let message = Message::binary(buf);
        let ret;
        let len = buf.len();
        if len > 0 {
            debug!("Sending message of {} bytes", len);
            ret = self.0.send_message(&message);
        } else {
            // Interpret zero length buffer is request
            // to close communication

            debug!("Sending the closing message");
            ret = self.0.send_message(&Message::close());
        }
        ret.map_err(|e|IoError::new(IoErrorKind::BrokenPipe, e))?;
        Ok(len)
    }
    fn flush(&mut self) -> ::std::io::Result<()> {
        Ok(())
    }
}

struct ReceiverWrapper<T: Receiver<DataFrame>> (T);

impl<T:Receiver<DataFrame>> ::std::io::Read for ReceiverWrapper<T> {
    fn read(&mut self, buf: &mut [u8]) -> ::std::io::Result<usize> {
        let ret = self.0.recv_message();
        let msg : Message = ret.map_err(|e|IoError::new(IoErrorKind::BrokenPipe, e))?;

        match msg.opcode {
            Type::Close => {
                Ok(0)
            }
            Type::Ping => {
                // Sender used to be in a separate thread with a channel
                // now there's no channel, so trickier to combine ping replies
                // and usual data exchange
                unimplemented!();
            }
            _ => {
                let msgpayload : &[u8] = msg.payload.borrow();
                let len = msgpayload.len();
                debug!("Received message of {} bytes", len);

                assert!(buf.len() >= len);

                buf[0..len].copy_from_slice(msgpayload);

                Ok(len)
            }
        }
    }
}

struct Peer<R, W>
    where R : Read + Send + 'static, W: Write + Send + 'static
{
    reader: R,
    writer: W,
}

struct DataExchangeSession<R1, R2, W1, W2>
    where R1 : Read  + Send + 'static,
          R2 : Read  + Send + 'static,
          W1 : Write + Send + 'static,
          W2 : Write + Send + 'static,
{
    peer1: Peer<R1, W1>,
    peer2: Peer<R2, W2>,
}

impl<R1,R2,W1,W2> DataExchangeSession<R1,R2,W1,W2>
    where R1 : Read  + Send + 'static,
          R2 : Read  + Send + 'static,
          W1 : Write + Send + 'static,
          W2 : Write + Send + 'static,
{
    fn data_exchange(self) -> Result<()> {

        let mut reader1 = self.peer1.reader;
        let mut writer1 = self.peer1.writer;
        let mut reader2 = self.peer2.reader;
        let mut writer2 = self.peer2.writer;

        let receive_loop = thread::spawn(move || -> Result<()> {
            // Actual data transfer happens here
            ::std::io::copy(&mut reader1, &mut writer2)?;
            writer2.write(b"")?; // signal close
            Ok(())
        });

        // Actual data transfer happens here
        ::std::io::copy(&mut reader2, &mut writer1)?;
        writer1.write(b"")?; // Signal close

        info!("Waiting for child threads to exit");

        receive_loop.join().map_err(|x|format!("{:?}",x))?
    }
}

fn try_main() -> Result<()> {
    //env_logger::init()?;
    init_logger()?;

    // setup command line arguments
    let matches = ::clap::App::new("WS Command Line Client")
        .version("0.1")
        .author("Vitaly \"_Vi\" Shukela <vi0oss@gmail.com>")
        .about("Send binary data from stdin to a WebSocket and back to stdout.")
        .arg(::clap::Arg::with_name("URL")
             .help("The URL of the WebSocket server.")
             .required(true)
             .index(1)).get_matches();

    let url = Url::parse(matches.value_of("URL").ok_or("no URL")?)?;

    info!("Connecting to {}", url);

    let request = Client::connect(url)?;

    let response = request.send()?; // Send the request and retrieve a response

    info!("Validating response...");

    response.validate()?; // Validate the response

    info!("Successfully connected");

    let (sender, receiver) = response.begin().split();

    let des = DataExchangeSession {
        peer1 : Peer {
            reader : ReceiverWrapper(receiver),
            writer : SenderWrapper(sender),
        },
        peer2 : Peer {
            reader : stdin(),
            writer : stdout(),
        },
    };

    des.data_exchange()?;

    info!("Exited");
    Ok(())
}

fn main() {
    if let Err(x) = try_main() {
        let _ = writeln!(::std::io::stderr(), "{:?}", x);
    }
}

<|endoftext|>


<commit_before>extern crate docopt;
extern crate serde_json;
extern crate vegas_lattice;

use std::fs::File;
use std::io::{self, Read};

use docopt::{ArgvMap, Docopt};
use vegas_lattice::{Axis, Lattice};

const USAGE: &'static str = "
Vegas lattice.

Usage:
    vegas-lattice check [--compressed] [<input>]
    vegas-lattice drop [-x -y -z] [--compressed] [<input>]
    vegas-lattice (-h | --help)
    vegas-lattice --version

Options:
    -h --help       Show this message.
    --version       Show version and exit.
";

fn read(input: &str) -> Result<Lattice, Box<std::error::Error>> {
    let mut data = String::new();
    if !input.is_empty() {
        let mut file = File::open(input)?;
        file.read_to_string(&mut data)?;
    } else {
        io::stdin().read_to_string(&mut data)?;
    };
    let lattice: Lattice = data.parse()?;
    Ok(lattice)
}

fn write(lattice: Lattice, compressed: bool) {
    if compressed {
        println!("{}", serde_json::to_string(&lattice).unwrap());
    } else {
        println!("{}", serde_json::to_string_pretty(&lattice).unwrap());
    }
}

fn check(args: ArgvMap) -> Result<(), Box<std::error::Error>> {
    let lattice = read(args.get_str("<input>"))?;
    write(lattice, args.get_bool("--compressed"));
    Ok(())
}

fn drop(args: ArgvMap) -> Result<(), Box<std::error::Error>> {
    let mut lattice = read(args.get_str("<input>"))?;
    if args.get_bool("-x") {
        lattice = lattice.drop(Axis::X);
    }
    if args.get_bool("-y") {
        lattice = lattice.drop(Axis::Y);
    }
    if args.get_bool("-z") {
        lattice = lattice.drop(Axis::Z);
    }
    write(lattice, args.get_bool("--compressed"));
    Ok(())
}

fn check_error(res: Result<(), Box<std::error::Error>>) {
    match res {
        Err(e) => {
            eprintln!("{}", e);
            std::process::exit(1);
        },
        _ => {},
    }
}

fn main() {
    let args = Docopt::new(USAGE)
        .and_then(|doc| doc.version(Some("0.0.1".to_string())).parse())
        .unwrap_or_else(|e| e.exit());

    if args.get_bool("check") {
        check_error(check(args));
    } else if args.get_bool("drop") {
        check_error(drop(args));
    } else {
        println!("{:?}", args);
    }
}<commit_msg>Compress was way too long<commit_after>extern crate docopt;
extern crate serde_json;
extern crate vegas_lattice;

use std::fs::File;
use std::io::{self, Read};

use docopt::{ArgvMap, Docopt};
use vegas_lattice::{Axis, Lattice};

const USAGE: &'static str = "
Vegas lattice.

Usage:
    vegas-lattice check [--compress] [<input>]
    vegas-lattice drop [-x -y -z] [--compress] [<input>]
    vegas-lattice (-h | --help)
    vegas-lattice --version

Options:
    -c --compress   Compress the output
    -h --help       Show this message.
    --version       Show version and exit.
";

fn read(input: &str) -> Result<Lattice, Box<std::error::Error>> {
    let mut data = String::new();
    if !input.is_empty() {
        let mut file = File::open(input)?;
        file.read_to_string(&mut data)?;
    } else {
        io::stdin().read_to_string(&mut data)?;
    };
    let lattice: Lattice = data.parse()?;
    Ok(lattice)
}

fn write(lattice: Lattice, compress: bool) {
    if compress {
        println!("{}", serde_json::to_string(&lattice).unwrap());
    } else {
        println!("{}", serde_json::to_string_pretty(&lattice).unwrap());
    }
}

fn check(args: ArgvMap) -> Result<(), Box<std::error::Error>> {
    let lattice = read(args.get_str("<input>"))?;
    write(lattice, args.get_bool("--compress"));
    Ok(())
}

fn drop(args: ArgvMap) -> Result<(), Box<std::error::Error>> {
    let mut lattice = read(args.get_str("<input>"))?;
    if args.get_bool("-x") {
        lattice = lattice.drop(Axis::X);
    }
    if args.get_bool("-y") {
        lattice = lattice.drop(Axis::Y);
    }
    if args.get_bool("-z") {
        lattice = lattice.drop(Axis::Z);
    }
    write(lattice, args.get_bool("--compress"));
    Ok(())
}

fn check_error(res: Result<(), Box<std::error::Error>>) {
    match res {
        Err(e) => {
            eprintln!("{}", e);
            std::process::exit(1);
        },
        _ => {},
    }
}

fn main() {
    let args = Docopt::new(USAGE)
        .and_then(|doc| doc.version(Some("0.0.1".to_string())).parse())
        .unwrap_or_else(|e| e.exit());

    if args.get_bool("check") {
        check_error(check(args));
    } else if args.get_bool("drop") {
        check_error(drop(args));
    } else {
        println!("{:?}", args);
    }
}<|endoftext|>


<commit_before>fn main() {
    println!("Hello, world!");
}
<commit_msg>Add some juicy code The answer yes/no isn't working as now, I'm trying to figuring out, but playtime is finished :/ !<commit_after>#![feature(collections)]

use std::io::prelude::*;

fn display_choices<T:std::fmt::Display>(choices: &[T]) -> i64 {
    let mut ret;

    loop
    {
        for (index, choice) in choices.iter().enumerate() {
            println!("{}. {}", index + 1, choice);
        }

        let stdin = std::io::stdin();
        let mut choice_text = String::new();
        stdin.lock().read_line(&mut choice_text).ok().expect("failed to read choice");
        let choice_opt: Option<i64> = choice_text.trim().parse::<i64>().ok();

        let choice = match choice_opt {
            Some(choice)             => choice,
            None                     => -1
        };

        if choice < 1 || choice > choices.len() as i64 {
            println!("Please input a logical number as choice!");
            continue;
        }

        ret = choice;
        break;
    }

    ret
}

use std::convert::AsRef;

fn does_user_answer_yes() -> bool {
    let mut answer = String::new();
    let stdin = std::io::stdin();
    stdin.lock().read_line(&mut answer).ok().expect("failed to read answer");

    let mut does_he_answer_yes;

    match answer.to_lowercase().as_ref() {
        "yes" | "yeah" | "yea" | "sure" => {
            does_he_answer_yes = true;
        },
        _ => {
            does_he_answer_yes = false;
        }
    }

    does_he_answer_yes
}

fn read_line<T:std::str::FromStr>() -> T {
    let mut t_text = String::new();
    let stdin = std::io::stdin();
    stdin.lock().read_line(&mut t_text).ok().expect("failed to read something");

    let t_opt: Option<T> = t_text.trim().parse::<T>().ok();

    let _t = match t_opt {
        Some(_t) => _t,
        None     => unimplemented!()
    };

    _t
}

fn add_nginx_proxy_service() {
    println!("Does your service run inside a Docker container ?");

    let is_in_docker = does_user_answer_yes();

    if is_in_docker {
        println!("Can you give me the ID or the name of the container?");
        unimplemented!();
    } else {
        println!("Oooh... Does it runs on localhost ?");
        let is_in_localhost = does_user_answer_yes();

        if is_in_localhost {
            println!("Then, it is possible ! On which port is it running?");
            let port = read_line::<u32>();
            unimplemented!();
        } else {
            println!("Hmm, it will be complicated. As now, I can't do it.");
        }
    }

}

fn display_help() {
}

fn exit() {
}

fn main() {
    println!("Welcome into the Rust Manager, select what you want to do:");

    let actions = ["Add a nginx proxy service", "Help", "Exit"];
    let action_choice = display_choices(&actions);

    match action_choice {
        1 => add_nginx_proxy_service(),
        2 => display_help(),
        3 => exit(),
        _ => unreachable!()
    }
}
<|endoftext|>