<commit_before>use std::fs::OpenOptions;
use std::io::{Read, Write, BufReader, BufRead, Seek, SeekFrom};
use std::path::PathBuf;

use errors::DecodeError;
use crc32;
use constants::{NUL, CR, LF, ESCAPE, SPACE, DEFAULT_LINE_SIZE};

#[derive(Default,Debug)]
struct MetaData {
    name: Option<String>,
    line_length: Option<u16>,
    size: Option<usize>,
    crc32: Option<u32>,
    part: Option<u32>,
    begin: Option<usize>,
    end: Option<usize>,
}

/// Decodes the input file in a new output file.
///
/// If ok, returns the path of the decoded file.
///
/// # Example
/// ```rust,no_run
/// yenc::ydecode_file("test2.bin.yenc", "test2.bin");
/// ```
/// # Errors
/// - when the output file already exists
///
pub fn ydecode_file(input_filename: &str, output_path: &str) -> Result<String, DecodeError> {
    let mut input_file = OpenOptions::new().read(true).open(input_filename)?;
    ydecode_stream(&mut input_file, output_path)
}

/// Decodes the data from a stream in a new output file.
///
/// Writes the output to a file with the filename from the header line, and places it in the
/// output path. The path of the output file is returned.
pub fn ydecode_stream<R>(read_stream: &mut R, output_path: &str) -> Result<String, DecodeError>
    where R: Read
{
    let mut rdr = BufReader::new(read_stream);
    let mut output_pathbuf = PathBuf::new();
    output_pathbuf.push(output_path);

    let mut checksum = crc32::Crc32::new();
    let mut yenc_block_found = false;
    let mut metadata: MetaData = Default::default();

    while !yenc_block_found {
        let mut line_buf = Vec::<u8>::with_capacity(2 * DEFAULT_LINE_SIZE as usize);
        let length = rdr.read_until(LF, &mut line_buf)?;
        if length == 0 {
            break;
        }
        if line_buf.starts_with(b"=ybegin ") {
            yenc_block_found = true;
            // parse header line and determine output filename
            metadata = parse_header_line(&line_buf, 8)?;
            output_pathbuf.push(metadata.name.unwrap().to_string().trim());
        }
    }

    if yenc_block_found {
        let mut output_file =
            OpenOptions::new().create(true).write(true).open(output_pathbuf.as_path())?;

        let mut footer_found = false;
        while !footer_found {
            let mut line_buf = Vec::<u8>::with_capacity(2 * DEFAULT_LINE_SIZE as usize);
            let length = rdr.read_until(LF, &mut line_buf)?;
            if length == 0 {
                break;
            }
            if line_buf.starts_with(b"=ypart ") {
                let part_metadata = parse_header_line(&line_buf, 7)?;
                metadata.part = part_metadata.part;
                metadata.begin = part_metadata.begin;
                metadata.end = part_metadata.end;
                if let Some(begin) = metadata.begin {
                    output_file.seek(SeekFrom::Start((begin - 1) as u64))?;
                }
            } else if line_buf.starts_with(b"=yend ") {
                footer_found = true;
                let mm = parse_header_line(&line_buf, 6)?;
                metadata.size = mm.size;
                metadata.crc32 = mm.crc32;
            } else {
                let decoded = ydecode_buffer(&line_buf[0..length])?;
                checksum.update_with_slice(decoded.as_slice());
                output_file.write_all(decoded.as_slice())?;
            }
        }
        if footer_found {
            if let Some(expected_size) = metadata.size {
                if expected_size != checksum.num_bytes {
                    return Err(DecodeError::IncompleteData {
                        expected_size: expected_size,
                        actual_size: checksum.num_bytes,
                    });
                }
            }
            if let Some(expected_crc) = metadata.crc32 {
                if expected_crc != checksum.crc {
                    return Err(DecodeError::InvalidChecksum);
                }
            }
        } else if let Some(expected_size) = metadata.size {
            if expected_size != checksum.num_bytes {
                return Err(DecodeError::IncompleteData {
                    expected_size: expected_size,
                    actual_size: checksum.num_bytes,
                });
            }
        }

    }
    Ok(output_pathbuf.to_str().unwrap().to_string())
}

/// Decode the yEncoded byte slice into a vector of bytes.
///
/// Carriage Return (CR) and Line Feed (LF) are ignored.
pub fn ydecode_buffer(input: &[u8]) -> Result<Vec<u8>, DecodeError> {
    // TODO remove heap allocation
    let mut output = Vec::<u8>::with_capacity((input.len() as f64 * 1.02) as usize);
    let mut iter = input.iter();
    while let Some(byte) = iter.next() {
        let mut byte = *byte;
        match byte {
            NUL | CR | LF => {
                // for now, just continue
                continue;
            }
            ESCAPE => {
                match iter.next() {
                    Some(b) => {
                        byte = b.overflowing_sub(64).0;
                    }
                    None => {
                        // for now, just continue, the only
                        continue;
                    }
                }
            }
            _ => {}
        }
        output.push(byte.overflowing_sub(42).0);
    }
    Ok(output)
}

fn parse_header_line(line_buf: &[u8], offset: usize) -> Result<MetaData, DecodeError> {
    #[derive(Debug)]
    enum State {
        Keyword,
        Value,
        End,
    };

    let mut metadata: MetaData = Default::default();
    let mut state = State::Keyword;
    let mut keyword = Vec::<u8>::with_capacity(6);
    let mut value = Vec::<u8>::with_capacity(96);

    let header_line = String::from_utf8_lossy(line_buf).to_string();
    for (i, &c) in line_buf[offset..].iter().enumerate() {
        let position = i + offset;
        match state {
            State::End => unreachable!(),
            State::Keyword => {
                match c {
                    b'a'...b'z' | b'0'...b'9' => keyword.push(c),
                    b'=' => {
                        if keyword.is_empty() || !is_known_keyword(&keyword) {
                            return Err(DecodeError::InvalidHeader {
                                line: header_line,
                                position: position,
                            });
                        } else {
                            state = State::Value;
                        }
                    }
                    CR | LF => {}
                    _ => {
                        return Err(DecodeError::InvalidHeader {
                            line: header_line,
                            position: position,
                        });
                    }
                }
            }
            State::Value => {
                match keyword.as_slice() {
                    b"name" => {
                        match c {
                            CR => {}
                            LF => {
                                state = State::End;
                                metadata.name = Some(String::from_utf8_lossy(&value).to_string());
                            }
                            _ => value.push(c),
                        }
                    }
                    b"size" => {
                        match c {
                            b'0'...b'9' => value.push(c),
                            SPACE => {
                                metadata.size =
                                    Some(usize::from_str_radix(&String::from_utf8_lossy(&value),
                                                               10)
                                        .unwrap());
                                state = State::Keyword;
                                keyword.clear();
                                value.clear();
                            }
                            _ => {
                                return Err(DecodeError::InvalidHeader {
                                    line: header_line,
                                    position: position,
                                });
                            }
                        }
                    }
                    b"begin" => {
                        match c {
                            b'0'...b'9' => value.push(c),
                            SPACE => {
                                metadata.begin =
                                    Some(usize::from_str_radix(&String::from_utf8_lossy(&value),
                                                               10)
                                        .unwrap());
                                state = State::Keyword;
                                keyword.clear();
                                value.clear();
                            }
                            _ => {
                                return Err(DecodeError::InvalidHeader {
                                    line: header_line,
                                    position: position,
                                });
                            }
                        }
                    }
                    b"end" => {
                        match c {
                            b'0'...b'9' => value.push(c),
                            SPACE | LF | CR => {
                                metadata.end =
                                    Some(usize::from_str_radix(&String::from_utf8_lossy(&value),
                                                               10)
                                        .unwrap());
                                state = State::Keyword;
                                keyword.clear();
                                value.clear();
                            }
                            _ => {
                                return Err(DecodeError::InvalidHeader {
                                    line: header_line,
                                    position: position,
                                });
                            }
                        }
                    }
                    b"line" => {
                        match c {
                            b'0'...b'9' => value.push(c),
                            SPACE => {
                                metadata.line_length =
                                    Some(u16::from_str_radix(&String::from_utf8_lossy(&value), 10)
                                        .unwrap());
                                state = State::Keyword;
                                keyword.clear();
                                value.clear();
                            }
                            _ => {
                                return Err(DecodeError::InvalidHeader {
                                    line: header_line,
                                    position: position,
                                });
                            }
                        }
                    }
                    b"part" => {
                        match c {
                            b'0'...b'9' => value.push(c),
                            SPACE => {
                                metadata.part =
                                    Some(u32::from_str_radix(&String::from_utf8_lossy(&value), 10)
                                        .unwrap());
                                state = State::Keyword;
                                keyword.clear();
                                value.clear();
                            }
                            _ => {
                                return Err(DecodeError::InvalidHeader {
                                    line: header_line,
                                    position: position,
                                });
                            }
                        }
                    }
                    b"crc32" | b"pcrc32" => {
                        match c {
                            b'0'...b'9' | b'A'...b'F' | b'a'...b'f' => value.push(c),
                            SPACE => {
                                state = State::Keyword;
                                metadata.crc32 =
                                    Some(u32::from_str_radix(&String::from_utf8_lossy(&value), 16)
                                        .unwrap());
                                keyword.clear();
                                value.clear();
                            }
                            LF => {
                                state = State::End;
                                metadata.crc32 =
                                    Some(u32::from_str_radix(&String::from_utf8_lossy(&value), 16)
                                        .unwrap());
                                keyword.clear();
                                value.clear();
                            }
                            CR => {}
                            _ => {
                                return Err(DecodeError::InvalidHeader {
                                    line: header_line,
                                    position: position,
                                });
                            }
                        }
                    }
                    _ => unreachable!(),
                }
            }
        };
    }
    Ok(metadata)
}

fn is_known_keyword(keyword_slice: &[u8]) -> bool {
    keyword_slice == b"name" || keyword_slice == b"line" || keyword_slice == b"size" ||
    keyword_slice == b"part" || keyword_slice == b"begin" ||
    keyword_slice == b"end" || keyword_slice == b"pcrc32" ||
    keyword_slice == b"crc32"
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn decode_invalid() {
        assert!(ydecode_buffer(&[b'=']).unwrap().is_empty());
    }

    #[test]
    fn decode_valid_ff() {
        assert_eq!(&vec![0xff - 0x2A], &ydecode_buffer(&[0xff]).unwrap());
    }

    #[test]
    fn decode_valid_01() {
        assert_eq!(&vec![0xff - 0x28], &ydecode_buffer(&[0x01]).unwrap());
    }

    #[test]
    fn decode_valid_esc_ff() {
        assert_eq!(&vec![0xff - 0x40 - 0x2A],
                   &ydecode_buffer(&[b'=', 0xff]).unwrap());
    }

    #[test]
    fn decode_valid_esc_01() {
        assert_eq!(&vec![0xff - 0x40 - 0x2A + 2],
                   &ydecode_buffer(&[b'=', 0x01]).unwrap());
    }
}
<commit_msg>Remove heap allocations in parsing<commit_after>use std::fs::OpenOptions;
use std::io::{Read, Write, BufReader, BufRead, Seek, SeekFrom};
use std::path::PathBuf;

use errors::DecodeError;
use crc32;
use constants::{NUL, CR, LF, ESCAPE, SPACE, DEFAULT_LINE_SIZE};

#[derive(Default, Debug)]
struct MetaData {
    name: Option<String>,
    line_length: Option<u16>,
    size: Option<usize>,
    crc32: Option<u32>,
    part: Option<u32>,
    begin: Option<usize>,
    end: Option<usize>,
}

/// Decodes the input file in a new output file.
///
/// If ok, returns the path of the decoded file.
///
/// # Example
/// ```rust,no_run
/// yenc::ydecode_file("test2.bin.yenc", "test2.bin");
/// ```
/// # Errors
/// - when the output file already exists
///
pub fn ydecode_file(input_filename: &str, output_path: &str) -> Result<String, DecodeError> {
    let mut input_file = OpenOptions::new().read(true).open(input_filename)?;
    ydecode_stream(&mut input_file, output_path)
}

/// Decodes the data from a stream in a new output file.
///
/// Writes the output to a file with the filename from the header line, and places it in the
/// output path. The path of the output file is returned.
pub fn ydecode_stream<R>(read_stream: &mut R, output_path: &str) -> Result<String, DecodeError>
where
    R: Read,
{
    let mut rdr = BufReader::new(read_stream);
    let mut output_pathbuf = PathBuf::new();
    output_pathbuf.push(output_path);

    let mut checksum = crc32::Crc32::new();
    let mut yenc_block_found = false;
    let mut metadata: MetaData = Default::default();

    while !yenc_block_found {
        let mut line_buf = Vec::<u8>::with_capacity(2 * DEFAULT_LINE_SIZE as usize);
        let length = rdr.read_until(LF, &mut line_buf)?;
        if length == 0 {
            break;
        }
        if line_buf.starts_with(b"=ybegin ") {
            yenc_block_found = true;
            // parse header line and determine output filename
            metadata = parse_header_line(&line_buf, 8)?;
            output_pathbuf.push(metadata.name.unwrap().to_string().trim());
        }
    }

    if yenc_block_found {
        let mut output_file = OpenOptions::new().create(true).write(true).open(
            output_pathbuf
                .as_path(),
        )?;

        let mut footer_found = false;
        while !footer_found {
            let mut line_buf = Vec::<u8>::with_capacity(2 * DEFAULT_LINE_SIZE as usize);
            let length = rdr.read_until(LF, &mut line_buf)?;
            if length == 0 {
                break;
            }
            if line_buf.starts_with(b"=ypart ") {
                let part_metadata = parse_header_line(&line_buf, 7)?;
                metadata.part = part_metadata.part;
                metadata.begin = part_metadata.begin;
                metadata.end = part_metadata.end;
                if let Some(begin) = metadata.begin {
                    output_file.seek(SeekFrom::Start((begin - 1) as u64))?;
                }
            } else if line_buf.starts_with(b"=yend ") {
                footer_found = true;
                let mm = parse_header_line(&line_buf, 6)?;
                metadata.size = mm.size;
                metadata.crc32 = mm.crc32;
            } else {
                let decoded = ydecode_buffer(&line_buf[0..length])?;
                checksum.update_with_slice(decoded.as_slice());
                output_file.write_all(decoded.as_slice())?;
            }
        }
        if footer_found {
            if let Some(expected_size) = metadata.size {
                if expected_size != checksum.num_bytes {
                    return Err(DecodeError::IncompleteData {
                        expected_size: expected_size,
                        actual_size: checksum.num_bytes,
                    });
                }
            }
            if let Some(expected_crc) = metadata.crc32 {
                if expected_crc != checksum.crc {
                    return Err(DecodeError::InvalidChecksum);
                }
            }
        } else if let Some(expected_size) = metadata.size {
            if expected_size != checksum.num_bytes {
                return Err(DecodeError::IncompleteData {
                    expected_size: expected_size,
                    actual_size: checksum.num_bytes,
                });
            }
        }

    }
    Ok(output_pathbuf.to_str().unwrap().to_string())
}

/// Decode the yEncoded byte slice into a vector of bytes.
///
/// Carriage Return (CR) and Line Feed (LF) are ignored.
pub fn ydecode_buffer(input: &[u8]) -> Result<Vec<u8>, DecodeError> {
    let mut output = Vec::<u8>::with_capacity((input.len() as f64 * 1.02) as usize);
    let mut iter = input.iter();
    while let Some(byte) = iter.next() {
        let mut byte = *byte;
        match byte {
            NUL | CR | LF => {
                // for now, just continue
                continue;
            }
            ESCAPE => {
                match iter.next() {
                    Some(b) => {
                        byte = b.overflowing_sub(64).0;
                    }
                    None => {
                        // for now, just continue
                        continue;
                    }
                }
            }
            _ => {}
        }
        output.push(byte.overflowing_sub(42).0);
    }
    Ok(output)
}

fn parse_header_line(line_buf: &[u8], offset: usize) -> Result<MetaData, DecodeError> {
    #[derive(Debug)]
    enum State {
        Keyword,
        Value,
        End,
    };

    let mut metadata: MetaData = Default::default();
    let mut state = State::Keyword;

    let mut keyword: &[u8] = &[];
    let mut keyword_start_idx: Option<usize> = None;
    let mut value: &[u8] = &[];
    let mut value_start_idx: Option<usize> = None;

    let header_line = String::from_utf8_lossy(line_buf).to_string();
    for (i, &c) in line_buf[offset..].iter().enumerate() {
        let position = i + offset;
        match state {
            State::End => unreachable!(),
            State::Keyword => {
                match c {
                    b'a'...b'z' | b'0'...b'9' => {
                        if keyword_start_idx.is_none() {
                            keyword_start_idx = Some(position);
                        }
                        keyword = &line_buf[keyword_start_idx.unwrap()..position + 1];
                    }
                    b'=' => {
                        if keyword.is_empty() || !is_known_keyword(keyword) {
                            return Err(DecodeError::InvalidHeader {
                                line: header_line,
                                position: position,
                            });
                        } else {
                            state = State::Value;
                        }
                    }
                    CR | LF => {}
                    _ => {
                        return Err(DecodeError::InvalidHeader {
                            line: header_line,
                            position: position,
                        });
                    }
                }
            }
            State::Value => {
                match keyword {
                    b"name" => {
                        match c {
                            CR => {}
                            LF => {
                                state = State::End;
                                metadata.name = Some(String::from_utf8_lossy(value).to_string());
                            }
                            _ => {
                                if value_start_idx.is_none() {
                                    value_start_idx = Some(position);
                                }
                                value = &line_buf[value_start_idx.unwrap()..position + 1];
                            }
                        }
                    }
                    b"size" => {
                        match c {
                            b'0'...b'9' => {
                                if value_start_idx.is_none() {
                                    value_start_idx = Some(position);
                                }
                                value = &line_buf[value_start_idx.unwrap()..position + 1];
                            }
                            SPACE => {
                                metadata.size =
                                    Some(
                                        usize::from_str_radix(&String::from_utf8_lossy(value), 10)
                                            .unwrap(),
                                    );
                                state = State::Keyword;
                                keyword_start_idx = None;
                                value_start_idx = None;
                            }
                            _ => {
                                return Err(DecodeError::InvalidHeader {
                                    line: header_line,
                                    position: position,
                                });
                            }
                        }
                    }
                    b"begin" => {
                        match c {
                            b'0'...b'9' => {
                                if value_start_idx.is_none() {
                                    value_start_idx = Some(position);
                                }
                                value = &line_buf[value_start_idx.unwrap()..position + 1];
                            }
                            SPACE => {
                                metadata.begin =
                                    Some(
                                        usize::from_str_radix(&String::from_utf8_lossy(value), 10)
                                            .unwrap(),
                                    );
                                state = State::Keyword;
                                keyword_start_idx = None;
                                value_start_idx = None;
                            }
                            _ => {
                                return Err(DecodeError::InvalidHeader {
                                    line: header_line,
                                    position: position,
                                });
                            }
                        }
                    }
                    b"end" => {
                        match c {
                            b'0'...b'9' => {
                                if value_start_idx.is_none() {
                                    value_start_idx = Some(position);
                                }
                                value = &line_buf[value_start_idx.unwrap()..position + 1];
                            }
                            SPACE | LF | CR => {
                                metadata.end =
                                    Some(
                                        usize::from_str_radix(&String::from_utf8_lossy(value), 10)
                                            .unwrap(),
                                    );
                                state = State::Keyword;
                                value_start_idx = None;
                            }
                            _ => {
                                return Err(DecodeError::InvalidHeader {
                                    line: header_line,
                                    position: position,
                                });
                            }
                        }
                    }
                    b"line" => {
                        match c {
                            b'0'...b'9' => {
                                if value_start_idx.is_none() {
                                    value_start_idx = Some(position);
                                }
                                value = &line_buf[value_start_idx.unwrap()..position + 1];
                            }
                            SPACE => {
                                metadata.line_length =
                                    Some(
                                        u16::from_str_radix(&String::from_utf8_lossy(value), 10)
                                            .unwrap(),
                                    );
                                state = State::Keyword;
                                keyword_start_idx = None;
                                value_start_idx = None;
                            }
                            _ => {
                                return Err(DecodeError::InvalidHeader {
                                    line: header_line,
                                    position: position,
                                });
                            }
                        }
                    }
                    b"part" => {
                        match c {
                            b'0'...b'9' => {
                                if value_start_idx.is_none() {
                                    value_start_idx = Some(position);
                                }
                                value = &line_buf[value_start_idx.unwrap()..position + 1];
                            }
                            SPACE => {
                                metadata.part =
                                    Some(
                                        u32::from_str_radix(&String::from_utf8_lossy(value), 10)
                                            .unwrap(),
                                    );
                                state = State::Keyword;
                                keyword_start_idx = None;
                                value_start_idx = None;
                            }
                            _ => {
                                return Err(DecodeError::InvalidHeader {
                                    line: header_line,
                                    position: position,
                                });
                            }
                        }
                    }
                    b"crc32" | b"pcrc32" => {
                        match c {
                            b'0'...b'9' | b'A'...b'F' | b'a'...b'f' => {
                                if value_start_idx.is_none() {
                                    value_start_idx = Some(position);
                                }
                                value = &line_buf[value_start_idx.unwrap()..position + 1];
                            }
                            SPACE => {
                                state = State::Keyword;
                                metadata.crc32 =
                                    Some(
                                        u32::from_str_radix(&String::from_utf8_lossy(value), 16)
                                            .unwrap(),
                                    );
                                value_start_idx = None;
                            }
                            LF => {
                                state = State::End;
                                metadata.crc32 =
                                    Some(
                                        u32::from_str_radix(&String::from_utf8_lossy(value), 16)
                                            .unwrap(),
                                    );
                                value_start_idx = None;
                            }
                            CR => {}
                            _ => {
                                return Err(DecodeError::InvalidHeader {
                                    line: header_line,
                                    position: position,
                                });
                            }
                        }
                    }
                    _ => unreachable!(),
                }
            }
        };
    }
    Ok(metadata)
}

fn is_known_keyword(keyword_slice: &[u8]) -> bool {
    keyword_slice == b"name" || keyword_slice == b"line" || keyword_slice == b"size" ||
        keyword_slice == b"part" ||
        keyword_slice == b"begin" || keyword_slice == b"end" || keyword_slice == b"pcrc32" ||
        keyword_slice == b"crc32"
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn decode_invalid() {
        assert!(ydecode_buffer(&[b'=']).unwrap().is_empty());
    }

    #[test]
    fn decode_valid_ff() {
        assert_eq!(&vec![0xff - 0x2A], &ydecode_buffer(&[0xff]).unwrap());
    }

    #[test]
    fn decode_valid_01() {
        assert_eq!(&vec![0xff - 0x28], &ydecode_buffer(&[0x01]).unwrap());
    }

    #[test]
    fn decode_valid_esc_ff() {
        assert_eq!(
            &vec![0xff - 0x40 - 0x2A],
            &ydecode_buffer(&[b'=', 0xff]).unwrap()
        );
    }

    #[test]
    fn decode_valid_esc_01() {
        assert_eq!(
            &vec![0xff - 0x40 - 0x2A + 2],
            &ydecode_buffer(&[b'=', 0x01]).unwrap()
        );
    }

    #[test]
    fn parse_valid_header_begin() {
        let parse_result = parse_header_line(
            b"=ybegin part=1 line=128 size=189463 name=CatOnKeyboardInSpace001.jpg\n",
            8,
        );
        assert!(parse_result.is_ok());
        let metadata = parse_result.unwrap();
        assert_eq!(metadata.part, Some(1));
        assert_eq!(metadata.size, Some(189463));
        assert_eq!(metadata.line_length, Some(128));
        assert_eq!(
            metadata.name,
            Some("CatOnKeyboardInSpace001.jpg".to_string())
        );
    }

    #[test]
    fn parse_valid_header_part() {
        let parse_result = parse_header_line(b"=ypart begin=1 end=189463\n", 7);
        assert!(parse_result.is_ok());
        let metadata = parse_result.unwrap();
        assert_eq!(metadata.begin, Some(1));
        assert_eq!(metadata.end, Some(189463));
    }

    #[test]
    fn parse_valid_footer_end() {
        let parse_result = parse_header_line(b"=yend size=26624 part=1 pcrc32=ae052b48\n", 6);
        assert!(parse_result.is_ok());
        let metadata = parse_result.unwrap();
        assert_eq!(metadata.part, Some(1));
        assert_eq!(metadata.size, Some(26624));
        assert_eq!(metadata.crc32, Some(0xae052b48));
    }
}
<|endoftext|>


<commit_before>use cocoa::base::{id, nil};
use cocoa::foundation::{NSUInteger, NSString};
use objc::runtime::{BOOL, NO, YES};
use sys::{MTLCreateSystemDefaultDevice, MTLCopyAllDevices, MTLDevice};
use std::borrow::Cow;
use std::error::Error;
use std::ffi::CStr;
use std::fmt::{self, Display, Formatter};
use std::marker::PhantomData;
use {Size};

pub trait ReadOnlyDevice {
    fn is_depth24_stencil8_pixel_format_supported(&self) -> bool;
    fn is_headless(&self) -> bool;
    fn is_low_power(&self) -> bool;
    fn max_threads_per_group(&self) -> Size;
    fn get_name(&self) -> Cow<str>;
    fn supports_texture_sample_count(&self, sample_count: usize) -> bool;
}

pub struct Device(id);

impl Device {
    pub fn system_default_device() -> Result<Self, DeviceError> {
        let device = unsafe { MTLCreateSystemDefaultDevice() };
        if device != nil {
            Ok(Device(device))
        } else {
            Err(DeviceError::ConstructionFailed)
        }
    }

    #[allow(unused_mut, unused_variables)]
    pub fn enumerate_all_system_devices() -> Vec<Result<Self, DeviceError>> {
        let all_devices = unsafe { MTLCopyAllDevices() };
        let mut devices_vec = vec![];

        devices_vec
    }

    /// Get the underlying pointer to the device. Releases ownership.
    pub unsafe fn into_raw(self) -> id {
        self.0
    }

    /// Create a `Device` from a raw pointer. Does no error checking, so it
    /// will cause errors if the device is nil, or does not conform to the
    /// MTLDevice protocol.
    pub unsafe fn from_raw_unchecked(device_ptr: id) -> Self {
        Device(device_ptr)
    }

    /// Create a `Device` from a raw pointer. If the pointer is not nil and
    /// conforms to the `MTLDevice` protocol, then a `Device` will be created,
    /// otherwise returns a `DeviceError`.
    pub fn from_raw(device_ptr: id) -> Result<Self, DeviceError> {
        #[link(name = "Foundation", kind = "framework")]
        extern {
            fn NSProtocolFromString(namestr: id) -> id;
        }

        let conforms_to_protocol: BOOL = unsafe {
            let mtl_device_protocol_str = NSString::alloc(nil).init_str("MTLDevice");
            let mtl_device_protocol = NSProtocolFromString(mtl_device_protocol_str);
            msg_send![device_ptr, conformsToProtocol:mtl_device_protocol]
        };

        if device_ptr == nil {
            Err(DeviceError::ConstructedFromNil)
        } else if conforms_to_protocol == NO {
            Err(DeviceError::ConstructedFromWrongPointerType)
        } else {
            Ok(Device(device_ptr))
        }
    }
}

impl ReadOnlyDevice for Device {
    fn is_depth24_stencil8_pixel_format_supported(&self) -> bool {
        unsafe { self.0.depth24Stencil8PixelFormatSupported() == YES }
    }

    fn is_headless(&self) -> bool {
        unsafe { self.0.headless() == YES }
    }

    fn is_low_power(&self) -> bool {
        unsafe { self.0.lowPower() == YES }
    }

    fn max_threads_per_group(&self) -> Size {
        unsafe { self.0.maxThreadsPerGroup().into() }
    }

    fn get_name(&self) -> Cow<str> {
        unsafe { CStr::from_ptr(self.0.name().UTF8String()).to_string_lossy() }
    }

    fn supports_texture_sample_count(&self, sample_count: usize) -> bool {
        unsafe { self.0.supportsTextureSampleCount(sample_count as NSUInteger) == YES }
    }
}

/// Internal utility function to get a Device's id without consuming it.
/// Not exported publicly from this crate.
#[doc(hidden)]
pub unsafe fn _device_get_raw(device: &Device) -> id {
    device.0
}

pub struct DeviceRef<'a>(id, PhantomData<&'a id>);

impl<'a> DeviceRef<'a> {
    pub fn is_reference_to<'d>(&'a self, device: &'d Device) -> bool {
        self.0 == device.0
    }
}

impl<'a> ReadOnlyDevice for DeviceRef<'a> {
    fn is_depth24_stencil8_pixel_format_supported(&self) -> bool {
        unsafe { self.0.depth24Stencil8PixelFormatSupported() == YES }
    }

    fn is_headless(&self) -> bool {
        unsafe { self.0.headless() == YES }
    }

    fn is_low_power(&self) -> bool {
        unsafe { self.0.lowPower() == YES }
    }

    fn max_threads_per_group(&self) -> Size {
        unsafe { self.0.maxThreadsPerGroup().into() }
    }

    fn get_name(&self) -> Cow<str> {
        unsafe { CStr::from_ptr(self.0.name().UTF8String()).to_string_lossy() }
    }

    fn supports_texture_sample_count(&self, sample_count: usize) -> bool {
        unsafe { self.0.supportsTextureSampleCount(sample_count as NSUInteger) == YES }
    }
}

/// Internal utility function to create a DeviceRef.
/// Not exported publicly from this crate.
#[doc(hidden)]
pub unsafe fn _make_device_ref<'a>(device: id) -> DeviceRef<'a> {
    DeviceRef(device, PhantomData)
}

#[derive(Clone, Debug)]
pub enum DeviceError {
    ConstructionFailed,
    ConstructedFromNil,
    ConstructedFromWrongPointerType,
    OtherRuntimeError
}

impl Display for DeviceError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        let descr = match *self {
            DeviceError::ConstructionFailed => "DeviceError::ConstructionFailed",
            DeviceError::ConstructedFromNil => "DeviceError::ConstructedFromNil",
            DeviceError::ConstructedFromWrongPointerType => "DeviceError::ConstructedFromWrongPointerType",
            DeviceError::OtherRuntimeError => "DeviceError::OtherRuntimeError"
        };
        write!(f, "{}", descr)
    }
}

impl Error for DeviceError {
    fn description(&self) -> &str {
        match *self {
            DeviceError::ConstructionFailed =>
                "Could not create a default device. Please ensure that you are using at least OSX 10.11 or iOS 8.0",
            DeviceError::ConstructedFromNil => "Attempted to create a device from a nil pointer",
            DeviceError::ConstructedFromWrongPointerType =>
                "Attempted to create a device from a pointer which does not implement MTLDevice",
            DeviceError::OtherRuntimeError => "An error occured in the Objective-C runtime"
        }
    }
}
<commit_msg>Make `DeviceRef` threadsafe by wrapping its id in an `Arc`<commit_after>use cocoa::base::{id, nil};
use cocoa::foundation::{NSUInteger, NSString};
use objc::runtime::{BOOL, NO, YES};
use sys::{MTLCreateSystemDefaultDevice, MTLCopyAllDevices, MTLDevice};
use std::borrow::Cow;
use std::error::Error;
use std::ffi::CStr;
use std::fmt::{self, Display, Formatter};
use std::marker::PhantomData;
use std::sync::Arc;
use {Size};

pub trait ReadOnlyDevice {
    fn is_depth24_stencil8_pixel_format_supported(&self) -> bool;
    fn is_headless(&self) -> bool;
    fn is_low_power(&self) -> bool;
    fn max_threads_per_group(&self) -> Size;
    fn get_name(&self) -> Cow<str>;
    fn supports_texture_sample_count(&self, sample_count: usize) -> bool;
}

pub struct Device(id);

impl Device {
    pub fn system_default_device() -> Result<Self, DeviceError> {
        let device = unsafe { MTLCreateSystemDefaultDevice() };
        if device != nil {
            Ok(Device(device))
        } else {
            Err(DeviceError::ConstructionFailed)
        }
    }

    #[allow(unused_mut, unused_variables)]
    pub fn enumerate_all_system_devices() -> Vec<Result<Self, DeviceError>> {
        let all_devices = unsafe { MTLCopyAllDevices() };
        let mut devices_vec = vec![];

        devices_vec
    }

    /// Get the underlying pointer to the device. Releases ownership.
    pub unsafe fn into_raw(self) -> id {
        self.0
    }

    /// Create a `Device` from a raw pointer. Does no error checking, so it
    /// will cause errors if the device is nil, or does not conform to the
    /// MTLDevice protocol.
    pub unsafe fn from_raw_unchecked(device_ptr: id) -> Self {
        Device(device_ptr)
    }

    /// Create a `Device` from a raw pointer. If the pointer is not nil and
    /// conforms to the `MTLDevice` protocol, then a `Device` will be created,
    /// otherwise returns a `DeviceError`.
    pub fn from_raw(device_ptr: id) -> Result<Self, DeviceError> {
        #[link(name = "Foundation", kind = "framework")]
        extern {
            fn NSProtocolFromString(namestr: id) -> id;
        }

        let conforms_to_protocol: BOOL = unsafe {
            let mtl_device_protocol_str = NSString::alloc(nil).init_str("MTLDevice");
            let mtl_device_protocol = NSProtocolFromString(mtl_device_protocol_str);
            msg_send![device_ptr, conformsToProtocol:mtl_device_protocol]
        };

        if device_ptr == nil {
            Err(DeviceError::ConstructedFromNil)
        } else if conforms_to_protocol == NO {
            Err(DeviceError::ConstructedFromWrongPointerType)
        } else {
            Ok(Device(device_ptr))
        }
    }
}

impl ReadOnlyDevice for Device {
    fn is_depth24_stencil8_pixel_format_supported(&self) -> bool {
        unsafe { self.0.depth24Stencil8PixelFormatSupported() == YES }
    }

    fn is_headless(&self) -> bool {
        unsafe { self.0.headless() == YES }
    }

    fn is_low_power(&self) -> bool {
        unsafe { self.0.lowPower() == YES }
    }

    fn max_threads_per_group(&self) -> Size {
        unsafe { self.0.maxThreadsPerGroup().into() }
    }

    fn get_name(&self) -> Cow<str> {
        unsafe { CStr::from_ptr(self.0.name().UTF8String()).to_string_lossy() }
    }

    fn supports_texture_sample_count(&self, sample_count: usize) -> bool {
        unsafe { self.0.supportsTextureSampleCount(sample_count as NSUInteger) == YES }
    }
}

/// Internal utility function to get a Device's id without consuming it.
/// Not exported publicly from this crate.
#[doc(hidden)]
pub unsafe fn _device_get_raw(device: &Device) -> id {
    device.0
}

pub struct DeviceRef<'a>(Arc<id>, PhantomData<&'a id>);

impl<'a> DeviceRef<'a> {
    pub fn is_reference_to<'d>(&'a self, device: &'d Device) -> bool {
        *self.0 == device.0
    }
}

impl<'a> ReadOnlyDevice for DeviceRef<'a> {
    fn is_depth24_stencil8_pixel_format_supported(&self) -> bool {
        unsafe { self.0.depth24Stencil8PixelFormatSupported() == YES }
    }

    fn is_headless(&self) -> bool {
        unsafe { self.0.headless() == YES }
    }

    fn is_low_power(&self) -> bool {
        unsafe { self.0.lowPower() == YES }
    }

    fn max_threads_per_group(&self) -> Size {
        unsafe { self.0.maxThreadsPerGroup().into() }
    }

    fn get_name(&self) -> Cow<str> {
        unsafe { CStr::from_ptr(self.0.name().UTF8String()).to_string_lossy() }
    }

    fn supports_texture_sample_count(&self, sample_count: usize) -> bool {
        unsafe { self.0.supportsTextureSampleCount(sample_count as NSUInteger) == YES }
    }
}

/// Internal utility function to create a DeviceRef.
/// Not exported publicly from this crate.
#[doc(hidden)]
pub unsafe fn _make_device_ref<'a>(device: id) -> DeviceRef<'a> {
    DeviceRef(Arc::new(device), PhantomData)
}

#[derive(Clone, Debug)]
pub enum DeviceError {
    ConstructionFailed,
    ConstructedFromNil,
    ConstructedFromWrongPointerType,
    OtherRuntimeError
}

impl Display for DeviceError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        let descr = match *self {
            DeviceError::ConstructionFailed => "DeviceError::ConstructionFailed",
            DeviceError::ConstructedFromNil => "DeviceError::ConstructedFromNil",
            DeviceError::ConstructedFromWrongPointerType => "DeviceError::ConstructedFromWrongPointerType",
            DeviceError::OtherRuntimeError => "DeviceError::OtherRuntimeError"
        };
        write!(f, "{}", descr)
    }
}

impl Error for DeviceError {
    fn description(&self) -> &str {
        match *self {
            DeviceError::ConstructionFailed =>
                "Could not create a default device. Please ensure that you are using at least OSX 10.11 or iOS 8.0",
            DeviceError::ConstructedFromNil => "Attempted to create a device from a nil pointer",
            DeviceError::ConstructedFromWrongPointerType =>
                "Attempted to create a device from a pointer which does not implement MTLDevice",
            DeviceError::OtherRuntimeError => "An error occured in the Objective-C runtime"
        }
    }
}
<|endoftext|>


<commit_before>use ll;
use util::{to_pa_result, pa_time_to_duration};
use hostapi::HostApiIndex;
use pa::PaError;
use std::time::duration::Duration;
use std::c_str::CString;

pub type DeviceIndex = uint;

pub struct DeviceInfo
{
    pub name: String,
    pub host_api: HostApiIndex,
    pub max_input_channels: uint,
    pub max_output_channels: uint,
    pub default_low_input_latency: Duration,
    pub default_low_output_latency: Duration,
    pub default_high_input_latency: Duration,
    pub default_high_output_latency: Duration,
    pub default_sample_rate: f64,
}

impl DeviceInfo
{
    fn from_ll(input: &ll::PaDeviceInfo) -> DeviceInfo
    {
        DeviceInfo
        {
            name: format!("{}", unsafe { CString::new(input.name, false) }),
            host_api: input.hostApi as HostApiIndex,
            max_input_channels: input.maxInputChannels as uint,
            max_output_channels: input.maxOutputChannels as uint,
            default_low_input_latency: pa_time_to_duration(input.defaultLowInputLatency),
            default_low_output_latency: pa_time_to_duration(input.defaultLowOutputLatency),
            default_high_input_latency: pa_time_to_duration(input.defaultHighInputLatency),
            default_high_output_latency: pa_time_to_duration(input.defaultHighOutputLatency),
            default_sample_rate: input.defaultSampleRate,
        }
    }
}

pub fn get_count() -> Result<uint, PaError>
{
    match unsafe { ll::Pa_GetDeviceCount() }
    {
        n if n >= 0 => Ok(n as uint),
        m => to_pa_result(m).map(|_| 0),
    }
}

pub fn get_default_input_index() -> Result<DeviceIndex, PaError>
{
    match unsafe { ll::Pa_GetDefaultInputDevice() }
    {
        n if n >= 0 => Ok(n as uint),
        m => to_pa_result(m).map(|_| 0),
    }
}

pub fn get_default_output_index() -> Result<DeviceIndex, PaError>
{
    match unsafe { ll::Pa_GetDefaultOutputDevice() }
    {
        n if n >= 0 => Ok(n as uint),
        m => to_pa_result(m).map(|_| 0),
    }
}

pub fn get_info(index: DeviceIndex) -> Option<DeviceInfo>
{
    unsafe
    {
        ll::Pa_GetDeviceInfo(index as i32)
            .to_option()
            .map(|s| DeviceInfo::from_ll(s))
    }
}

pub fn get_from_host_api_device_index(host_api: HostApiIndex, host_api_device_index: uint) -> Result<DeviceIndex, PaError>
{
    match unsafe { ll::Pa_HostApiDeviceIndexToDeviceIndex(host_api as i32, host_api_device_index as i32) }
    {
        n if n >= 0 => Ok(n as DeviceIndex),
        m => to_pa_result(m).map(|_| 0),
    }
}
<commit_msg>Add doc for device<commit_after>//! Info about connected audio devices

use ll;
use util::{to_pa_result, pa_time_to_duration};
use hostapi::HostApiIndex;
use pa::PaError;
use std::time::duration::Duration;
use std::c_str::CString;

pub type DeviceIndex = uint;

/// Information for a specific device
pub struct DeviceInfo
{
    /// Human readable name
    pub name: String,

    /// Index of the host API this device belongs to
    pub host_api: HostApiIndex,

    /// Maximal number of input channels that can be used
    pub max_input_channels: uint,

    /// Maximal number of ouput channels that can be used
    pub max_output_channels: uint,

    /// Default input latency for interactive performance
    pub default_low_input_latency: Duration,

    /// Default output latency for interactive performance
    pub default_low_output_latency: Duration,

    /// Default input latency for robust non-interactive applications
    pub default_high_input_latency: Duration,

    /// Default output latency for robust non-interactive applications
    pub default_high_output_latency: Duration,

    /// Default sample rate
    pub default_sample_rate: f64,
}

impl DeviceInfo
{
    fn from_ll(input: &ll::PaDeviceInfo) -> DeviceInfo
    {
        DeviceInfo
        {
            name: format!("{}", unsafe { CString::new(input.name, false) }),
            host_api: input.hostApi as HostApiIndex,
            max_input_channels: input.maxInputChannels as uint,
            max_output_channels: input.maxOutputChannels as uint,
            default_low_input_latency: pa_time_to_duration(input.defaultLowInputLatency),
            default_low_output_latency: pa_time_to_duration(input.defaultLowOutputLatency),
            default_high_input_latency: pa_time_to_duration(input.defaultHighInputLatency),
            default_high_output_latency: pa_time_to_duration(input.defaultHighOutputLatency),
            default_sample_rate: input.defaultSampleRate,
        }
    }
}

/// Retrieve the number of available devices.
pub fn get_count() -> Result<uint, PaError>
{
    match unsafe { ll::Pa_GetDeviceCount() }
    {
        n if n >= 0 => Ok(n as uint),
        m => to_pa_result(m).map(|_| 0),
    }
}

/// Retrieve the index of the default input device
///
/// Will return Err(NoDevice) when non are available.
pub fn get_default_input_index() -> Result<DeviceIndex, PaError>
{
    match unsafe { ll::Pa_GetDefaultInputDevice() }
    {
        n if n >= 0 => Ok(n as uint),
        m => to_pa_result(m).map(|_| 0),
    }
}

/// Retrieve the index of the default output device
///
/// Will return Err(NoDevice) when non are available.
pub fn get_default_output_index() -> Result<DeviceIndex, PaError>
{
    match unsafe { ll::Pa_GetDefaultOutputDevice() }
    {
        n if n >= 0 => Ok(n as uint),
        m => to_pa_result(m).map(|_| 0),
    }
}

/// Get info about a particular device
///
/// Returns None when the index is out of range.
pub fn get_info(index: DeviceIndex) -> Option<DeviceInfo>
{
    unsafe
    {
        ll::Pa_GetDeviceInfo(index as i32)
            .to_option()
            .map(|s| DeviceInfo::from_ll(s))
    }
}

/// Converts a device index from a specific host API to a global device index
///
/// Returns Err(InvalidHostApi) when the host_api is out of range, and Err(InvalidDevice) when
/// host_api_device_index is out of range.
///
/// ```
/// // We retrieve the index of device 3 of api 1
/// let device_index = match portaudio::device::get_from_host_api_device_index(1, 3)
/// {
///     Ok(n) => n,
///     Err(e) => { println!("Error: {}", e); return },
/// };
/// ```
pub fn get_from_host_api_device_index(host_api: HostApiIndex, host_api_device_index: uint) -> Result<DeviceIndex, PaError>
{
    match unsafe { ll::Pa_HostApiDeviceIndexToDeviceIndex(host_api as i32, host_api_device_index as i32) }
    {
        n if n >= 0 => Ok(n as DeviceIndex),
        m => to_pa_result(m).map(|_| 0),
    }
}
<|endoftext|>


<commit_before>#![feature(rustc_private)]

// FIXME: switch to something more ergonomic here, once available.
// (Currently there is no way to opt into sysroot crates without `extern crate`.)
#[allow(unused_extern_crates)]
extern crate rustc_driver;
#[allow(unused_extern_crates)]
extern crate rustc_interface;
#[allow(unused_extern_crates)]
extern crate rustc_plugin;

use rustc_interface::interface;
use rustc_tools_util::*;

use std::path::Path;
use std::process::{exit, Command};

mod lintlist;

/// If a command-line option matches `find_arg`, then apply the predicate `pred` on its value. If
/// true, then return it. The parameter is assumed to be either `--arg=value` or `--arg value`.
fn arg_value<'a>(
    args: impl IntoIterator<Item = &'a String>,
    find_arg: &str,
    pred: impl Fn(&str) -> bool,
) -> Option<&'a str> {
    let mut args = args.into_iter().map(String::as_str);

    while let Some(arg) = args.next() {
        let arg: Vec<_> = arg.splitn(2, '=').collect();
        if arg.get(0) != Some(&find_arg) {
            continue;
        }

        let value = arg.get(1).cloned().or_else(|| args.next());
        if value.as_ref().map_or(false, |p| pred(p)) {
            return value;
        }
    }
    None
}

#[test]
fn test_arg_value() {
    let args: Vec<_> = ["--bar=bar", "--foobar", "123", "--foo"]
        .iter()
        .map(std::string::ToString::to_string)
        .collect();

    assert_eq!(arg_value(None, "--foobar", |_| true), None);
    assert_eq!(arg_value(&args, "--bar", |_| false), None);
    assert_eq!(arg_value(&args, "--bar", |_| true), Some("bar"));
    assert_eq!(arg_value(&args, "--bar", |p| p == "bar"), Some("bar"));
    assert_eq!(arg_value(&args, "--bar", |p| p == "foo"), None);
    assert_eq!(arg_value(&args, "--foobar", |p| p == "foo"), None);
    assert_eq!(arg_value(&args, "--foobar", |p| p == "123"), Some("123"));
    assert_eq!(arg_value(&args, "--foo", |_| true), None);
}

#[allow(clippy::too_many_lines)]

struct ClippyCallbacks;

impl rustc_driver::Callbacks for ClippyCallbacks {
    fn after_parsing(&mut self, compiler: &interface::Compiler) -> bool {
        let sess = compiler.session();
        let mut registry = rustc_plugin::registry::Registry::new(
            sess,
            compiler
                .parse()
                .expect(
                    "at this compilation stage \
                     the crate must be parsed",
                )
                .peek()
                .span,
        );
        registry.args_hidden = Some(Vec::new());

        let conf = clippy_lints::read_conf(&registry);
        clippy_lints::register_plugins(&mut registry, &conf);

        let rustc_plugin::registry::Registry {
            early_lint_passes,
            late_lint_passes,
            lint_groups,
            llvm_passes,
            attributes,
            ..
        } = registry;
        let mut ls = sess.lint_store.borrow_mut();
        for pass in early_lint_passes {
            ls.register_early_pass(Some(sess), true, false, pass);
        }
        for pass in late_lint_passes {
            ls.register_late_pass(Some(sess), true, false, false, pass);
        }

        for (name, (to, deprecated_name)) in lint_groups {
            ls.register_group(Some(sess), true, name, deprecated_name, to);
        }
        clippy_lints::register_pre_expansion_lints(sess, &mut ls, &conf);
        clippy_lints::register_renamed(&mut ls);

        sess.plugin_llvm_passes.borrow_mut().extend(llvm_passes);
        sess.plugin_attributes.borrow_mut().extend(attributes);

        // Continue execution
        true
    }
}

#[allow(clippy::find_map, clippy::filter_map)]
fn describe_lints() {
    use lintlist::*;
    use std::collections::HashSet;

    println!(
        "
Available lint options:
    -W <foo>           Warn about <foo>
    -A <foo>           Allow <foo>
    -D <foo>           Deny <foo>
    -F <foo>           Forbid <foo> (deny <foo> and all attempts to override)

"
    );

    let lint_level = |lint: &Lint| {
        LINT_LEVELS
            .iter()
            .find(|level_mapping| level_mapping.0 == lint.group)
            .map(|(_, level)| match level {
                Level::Allow => "allow",
                Level::Warn => "warn",
                Level::Deny => "deny",
            })
            .unwrap()
    };

    let mut lints: Vec<_> = ALL_LINTS.iter().collect();
    // The sort doesn't case-fold but it's doubtful we care.
    lints.sort_by_cached_key(|x: &&Lint| (lint_level(x), x.name));

    let max_lint_name_len = lints
        .iter()
        .map(|lint| lint.name.len())
        .map(|len| len + "clippy::".len())
        .max()
        .unwrap_or(0);

    let padded = |x: &str| {
        let mut s = " ".repeat(max_lint_name_len - x.chars().count());
        s.push_str(x);
        s
    };

    let scoped = |x: &str| format!("clippy::{}", x);

    let lint_groups: HashSet<_> = lints.iter().map(|lint| lint.group).collect();

    println!("Lint checks provided by clippy:\n");
    println!("    {}  {:7.7}  meaning", padded("name"), "default");
    println!("    {}  {:7.7}  -------", padded("----"), "-------");

    let print_lints = |lints: &[&Lint]| {
        for lint in lints {
            let name = lint.name.replace("_", "-");
            println!(
                "    {}  {:7.7}  {}",
                padded(&scoped(&name)),
                lint_level(lint),
                lint.desc
            );
        }
        println!("\n");
    };

    print_lints(&lints);

    let max_group_name_len = std::cmp::max(
        "clippy::all".len(),
        lint_groups
            .iter()
            .map(|group| group.len())
            .map(|len| len + "clippy::".len())
            .max()
            .unwrap_or(0),
    );

    let padded_group = |x: &str| {
        let mut s = " ".repeat(max_group_name_len - x.chars().count());
        s.push_str(x);
        s
    };

    println!("Lint groups provided by clippy:\n");
    println!("    {}  sub-lints", padded_group("name"));
    println!("    {}  ---------", padded_group("----"));
    println!("    {}  the set of all clippy lints", padded_group("clippy::all"));

    let print_lint_groups = || {
        for group in lint_groups {
            let name = group.to_lowercase().replace("_", "-");
            let desc = lints
                .iter()
                .filter(|&lint| lint.group == group)
                .map(|lint| lint.name)
                .map(|name| name.replace("_", "-"))
                .collect::<Vec<String>>()
                .join(", ");
            println!("    {}  {}", padded_group(&scoped(&name)), desc);
        }
        println!("\n");
    };

    print_lint_groups();
}

fn display_help() {
    println!(
        "\
Checks a package to catch common mistakes and improve your Rust code.

Usage:
    cargo clippy [options] [--] [<opts>...]

Common options:
    -h, --help               Print this message
    -V, --version            Print version info and exit

Other options are the same as `cargo check`.

To allow or deny a lint from the command line you can use `cargo clippy --`
with:

    -W --warn OPT       Set lint warnings
    -A --allow OPT      Set lint allowed
    -D --deny OPT       Set lint denied
    -F --forbid OPT     Set lint forbidden

You can use tool lints to allow or deny lints from your code, eg.:

    #[allow(clippy::needless_lifetimes)]
"
    );
}

pub fn main() {
    rustc_driver::init_rustc_env_logger();
    exit(
        rustc_driver::report_ices_to_stderr_if_any(move || {
            use std::env;

            if std::env::args().any(|a| a == "--version" || a == "-V") {
                let version_info = rustc_tools_util::get_version_info!();
                println!("{}", version_info);
                exit(0);
            }

            let mut orig_args: Vec<String> = env::args().collect();

            // Get the sysroot, looking from most specific to this invocation to the least:
            // - command line
            // - runtime environment
            //    - SYSROOT
            //    - RUSTUP_HOME, MULTIRUST_HOME, RUSTUP_TOOLCHAIN, MULTIRUST_TOOLCHAIN
            // - sysroot from rustc in the path
            // - compile-time environment
            let sys_root_arg = arg_value(&orig_args, "--sysroot", |_| true);
            let have_sys_root_arg = sys_root_arg.is_some();
            let sys_root = sys_root_arg
                .map(std::string::ToString::to_string)
                .or_else(|| std::env::var("SYSROOT").ok())
                .or_else(|| {
                    let home = option_env!("RUSTUP_HOME").or(option_env!("MULTIRUST_HOME"));
                    let toolchain = option_env!("RUSTUP_TOOLCHAIN").or(option_env!("MULTIRUST_TOOLCHAIN"));
                    home.and_then(|home| toolchain.map(|toolchain| format!("{}/toolchains/{}", home, toolchain)))
                })
                .or_else(|| {
                    Command::new("rustc")
                        .arg("--print")
                        .arg("sysroot")
                        .output()
                        .ok()
                        .and_then(|out| String::from_utf8(out.stdout).ok())
                        .map(|s| s.trim().to_owned())
                })
                .or_else(|| option_env!("SYSROOT").map(String::from))
                .expect("need to specify SYSROOT env var during clippy compilation, or use rustup or multirust");

            // Setting RUSTC_WRAPPER causes Cargo to pass 'rustc' as the first argument.
            // We're invoking the compiler programmatically, so we ignore this/
            let wrapper_mode = Path::new(&orig_args[1]).file_stem() == Some("rustc".as_ref());

            if wrapper_mode {
                // we still want to be able to invoke it normally though
                orig_args.remove(1);
            }

            if !wrapper_mode && std::env::args().any(|a| a == "--help" || a == "-h") {
                display_help();
                exit(0);
            }

            let should_describe_lints = || {
                let args: Vec<_> = std::env::args().collect();
                args.windows(2).any(|args| {
                    args[1] == "help"
                        && match args[0].as_str() {
                            "-W" | "-A" | "-D" | "-F" => true,
                            _ => false,
                        }
                })
            };

            if !wrapper_mode && should_describe_lints() {
                describe_lints();
                exit(0);
            }

            // this conditional check for the --sysroot flag is there so users can call
            // `clippy_driver` directly
            // without having to pass --sysroot or anything
            let mut args: Vec<String> = if have_sys_root_arg {
                orig_args.clone()
            } else {
                orig_args
                    .clone()
                    .into_iter()
                    .chain(Some("--sysroot".to_owned()))
                    .chain(Some(sys_root))
                    .collect()
            };

            // this check ensures that dependencies are built but not linted and the final
            // crate is
            // linted but not built
            let clippy_enabled = env::var("CLIPPY_TESTS").ok().map_or(false, |val| val == "true")
                || arg_value(&orig_args, "--emit", |val| val.split(',').any(|e| e == "metadata")).is_some();

            if clippy_enabled {
                args.extend_from_slice(&["--cfg".to_owned(), r#"feature="cargo-clippy""#.to_owned()]);
                if let Ok(extra_args) = env::var("CLIPPY_ARGS") {
                    args.extend(extra_args.split("__CLIPPY_HACKERY__").filter_map(|s| {
                        if s.is_empty() {
                            None
                        } else {
                            Some(s.to_string())
                        }
                    }));
                }
            }

            let mut clippy = ClippyCallbacks;
            let mut default = rustc_driver::DefaultCallbacks;
            let callbacks: &mut (dyn rustc_driver::Callbacks + Send) =
                if clippy_enabled { &mut clippy } else { &mut default };
            let args = args;
            rustc_driver::run_compiler(&args, callbacks, None, None)
        })
        .and_then(|result| result)
        .is_err() as i32,
    )
}
<commit_msg>Build sys_root in driver with PathBuf instead of String<commit_after>#![feature(rustc_private)]

// FIXME: switch to something more ergonomic here, once available.
// (Currently there is no way to opt into sysroot crates without `extern crate`.)
#[allow(unused_extern_crates)]
extern crate rustc_driver;
#[allow(unused_extern_crates)]
extern crate rustc_interface;
#[allow(unused_extern_crates)]
extern crate rustc_plugin;

use rustc_interface::interface;
use rustc_tools_util::*;

use std::path::{Path, PathBuf};
use std::process::{exit, Command};

mod lintlist;

/// If a command-line option matches `find_arg`, then apply the predicate `pred` on its value. If
/// true, then return it. The parameter is assumed to be either `--arg=value` or `--arg value`.
fn arg_value<'a>(
    args: impl IntoIterator<Item = &'a String>,
    find_arg: &str,
    pred: impl Fn(&str) -> bool,
) -> Option<&'a str> {
    let mut args = args.into_iter().map(String::as_str);

    while let Some(arg) = args.next() {
        let arg: Vec<_> = arg.splitn(2, '=').collect();
        if arg.get(0) != Some(&find_arg) {
            continue;
        }

        let value = arg.get(1).cloned().or_else(|| args.next());
        if value.as_ref().map_or(false, |p| pred(p)) {
            return value;
        }
    }
    None
}

#[test]
fn test_arg_value() {
    let args: Vec<_> = ["--bar=bar", "--foobar", "123", "--foo"]
        .iter()
        .map(std::string::ToString::to_string)
        .collect();

    assert_eq!(arg_value(None, "--foobar", |_| true), None);
    assert_eq!(arg_value(&args, "--bar", |_| false), None);
    assert_eq!(arg_value(&args, "--bar", |_| true), Some("bar"));
    assert_eq!(arg_value(&args, "--bar", |p| p == "bar"), Some("bar"));
    assert_eq!(arg_value(&args, "--bar", |p| p == "foo"), None);
    assert_eq!(arg_value(&args, "--foobar", |p| p == "foo"), None);
    assert_eq!(arg_value(&args, "--foobar", |p| p == "123"), Some("123"));
    assert_eq!(arg_value(&args, "--foo", |_| true), None);
}

#[allow(clippy::too_many_lines)]

struct ClippyCallbacks;

impl rustc_driver::Callbacks for ClippyCallbacks {
    fn after_parsing(&mut self, compiler: &interface::Compiler) -> bool {
        let sess = compiler.session();
        let mut registry = rustc_plugin::registry::Registry::new(
            sess,
            compiler
                .parse()
                .expect(
                    "at this compilation stage \
                     the crate must be parsed",
                )
                .peek()
                .span,
        );
        registry.args_hidden = Some(Vec::new());

        let conf = clippy_lints::read_conf(&registry);
        clippy_lints::register_plugins(&mut registry, &conf);

        let rustc_plugin::registry::Registry {
            early_lint_passes,
            late_lint_passes,
            lint_groups,
            llvm_passes,
            attributes,
            ..
        } = registry;
        let mut ls = sess.lint_store.borrow_mut();
        for pass in early_lint_passes {
            ls.register_early_pass(Some(sess), true, false, pass);
        }
        for pass in late_lint_passes {
            ls.register_late_pass(Some(sess), true, false, false, pass);
        }

        for (name, (to, deprecated_name)) in lint_groups {
            ls.register_group(Some(sess), true, name, deprecated_name, to);
        }
        clippy_lints::register_pre_expansion_lints(sess, &mut ls, &conf);
        clippy_lints::register_renamed(&mut ls);

        sess.plugin_llvm_passes.borrow_mut().extend(llvm_passes);
        sess.plugin_attributes.borrow_mut().extend(attributes);

        // Continue execution
        true
    }
}

#[allow(clippy::find_map, clippy::filter_map)]
fn describe_lints() {
    use lintlist::*;
    use std::collections::HashSet;

    println!(
        "
Available lint options:
    -W <foo>           Warn about <foo>
    -A <foo>           Allow <foo>
    -D <foo>           Deny <foo>
    -F <foo>           Forbid <foo> (deny <foo> and all attempts to override)

"
    );

    let lint_level = |lint: &Lint| {
        LINT_LEVELS
            .iter()
            .find(|level_mapping| level_mapping.0 == lint.group)
            .map(|(_, level)| match level {
                Level::Allow => "allow",
                Level::Warn => "warn",
                Level::Deny => "deny",
            })
            .unwrap()
    };

    let mut lints: Vec<_> = ALL_LINTS.iter().collect();
    // The sort doesn't case-fold but it's doubtful we care.
    lints.sort_by_cached_key(|x: &&Lint| (lint_level(x), x.name));

    let max_lint_name_len = lints
        .iter()
        .map(|lint| lint.name.len())
        .map(|len| len + "clippy::".len())
        .max()
        .unwrap_or(0);

    let padded = |x: &str| {
        let mut s = " ".repeat(max_lint_name_len - x.chars().count());
        s.push_str(x);
        s
    };

    let scoped = |x: &str| format!("clippy::{}", x);

    let lint_groups: HashSet<_> = lints.iter().map(|lint| lint.group).collect();

    println!("Lint checks provided by clippy:\n");
    println!("    {}  {:7.7}  meaning", padded("name"), "default");
    println!("    {}  {:7.7}  -------", padded("----"), "-------");

    let print_lints = |lints: &[&Lint]| {
        for lint in lints {
            let name = lint.name.replace("_", "-");
            println!(
                "    {}  {:7.7}  {}",
                padded(&scoped(&name)),
                lint_level(lint),
                lint.desc
            );
        }
        println!("\n");
    };

    print_lints(&lints);

    let max_group_name_len = std::cmp::max(
        "clippy::all".len(),
        lint_groups
            .iter()
            .map(|group| group.len())
            .map(|len| len + "clippy::".len())
            .max()
            .unwrap_or(0),
    );

    let padded_group = |x: &str| {
        let mut s = " ".repeat(max_group_name_len - x.chars().count());
        s.push_str(x);
        s
    };

    println!("Lint groups provided by clippy:\n");
    println!("    {}  sub-lints", padded_group("name"));
    println!("    {}  ---------", padded_group("----"));
    println!("    {}  the set of all clippy lints", padded_group("clippy::all"));

    let print_lint_groups = || {
        for group in lint_groups {
            let name = group.to_lowercase().replace("_", "-");
            let desc = lints
                .iter()
                .filter(|&lint| lint.group == group)
                .map(|lint| lint.name)
                .map(|name| name.replace("_", "-"))
                .collect::<Vec<String>>()
                .join(", ");
            println!("    {}  {}", padded_group(&scoped(&name)), desc);
        }
        println!("\n");
    };

    print_lint_groups();
}

fn display_help() {
    println!(
        "\
Checks a package to catch common mistakes and improve your Rust code.

Usage:
    cargo clippy [options] [--] [<opts>...]

Common options:
    -h, --help               Print this message
    -V, --version            Print version info and exit

Other options are the same as `cargo check`.

To allow or deny a lint from the command line you can use `cargo clippy --`
with:

    -W --warn OPT       Set lint warnings
    -A --allow OPT      Set lint allowed
    -D --deny OPT       Set lint denied
    -F --forbid OPT     Set lint forbidden

You can use tool lints to allow or deny lints from your code, eg.:

    #[allow(clippy::needless_lifetimes)]
"
    );
}

pub fn main() {
    rustc_driver::init_rustc_env_logger();
    exit(
        rustc_driver::report_ices_to_stderr_if_any(move || {
            use std::env;

            if std::env::args().any(|a| a == "--version" || a == "-V") {
                let version_info = rustc_tools_util::get_version_info!();
                println!("{}", version_info);
                exit(0);
            }

            let mut orig_args: Vec<String> = env::args().collect();

            // Get the sysroot, looking from most specific to this invocation to the least:
            // - command line
            // - runtime environment
            //    - SYSROOT
            //    - RUSTUP_HOME, MULTIRUST_HOME, RUSTUP_TOOLCHAIN, MULTIRUST_TOOLCHAIN
            // - sysroot from rustc in the path
            // - compile-time environment
            let sys_root_arg = arg_value(&orig_args, "--sysroot", |_| true);
            let have_sys_root_arg = sys_root_arg.is_some();
            let sys_root = sys_root_arg
                .map(PathBuf::from)
                .or_else(|| std::env::var("SYSROOT").ok().map(PathBuf::from))
                .or_else(|| {
                    let home = option_env!("RUSTUP_HOME").or(option_env!("MULTIRUST_HOME"));
                    let toolchain = option_env!("RUSTUP_TOOLCHAIN").or(option_env!("MULTIRUST_TOOLCHAIN"));
                    home.and_then(|home| {
                        toolchain.map(|toolchain| {
                            let mut path = PathBuf::from(home);
                            path.push("toolchains");
                            path.push(toolchain);
                            path
                        })
                    })
                })
                .or_else(|| {
                    Command::new("rustc")
                        .arg("--print")
                        .arg("sysroot")
                        .output()
                        .ok()
                        .and_then(|out| String::from_utf8(out.stdout).ok())
                        .map(|s| PathBuf::from(s.trim()))
                })
                .or_else(|| option_env!("SYSROOT").map(PathBuf::from))
                .map(|pb| pb.to_string_lossy().to_string())
                .expect("need to specify SYSROOT env var during clippy compilation, or use rustup or multirust");

            // Setting RUSTC_WRAPPER causes Cargo to pass 'rustc' as the first argument.
            // We're invoking the compiler programmatically, so we ignore this/
            let wrapper_mode = Path::new(&orig_args[1]).file_stem() == Some("rustc".as_ref());

            if wrapper_mode {
                // we still want to be able to invoke it normally though
                orig_args.remove(1);
            }

            if !wrapper_mode && std::env::args().any(|a| a == "--help" || a == "-h") {
                display_help();
                exit(0);
            }

            let should_describe_lints = || {
                let args: Vec<_> = std::env::args().collect();
                args.windows(2).any(|args| {
                    args[1] == "help"
                        && match args[0].as_str() {
                            "-W" | "-A" | "-D" | "-F" => true,
                            _ => false,
                        }
                })
            };

            if !wrapper_mode && should_describe_lints() {
                describe_lints();
                exit(0);
            }

            // this conditional check for the --sysroot flag is there so users can call
            // `clippy_driver` directly
            // without having to pass --sysroot or anything
            let mut args: Vec<String> = if have_sys_root_arg {
                orig_args.clone()
            } else {
                orig_args
                    .clone()
                    .into_iter()
                    .chain(Some("--sysroot".to_owned()))
                    .chain(Some(sys_root))
                    .collect()
            };

            // this check ensures that dependencies are built but not linted and the final
            // crate is
            // linted but not built
            let clippy_enabled = env::var("CLIPPY_TESTS").ok().map_or(false, |val| val == "true")
                || arg_value(&orig_args, "--emit", |val| val.split(',').any(|e| e == "metadata")).is_some();

            if clippy_enabled {
                args.extend_from_slice(&["--cfg".to_owned(), r#"feature="cargo-clippy""#.to_owned()]);
                if let Ok(extra_args) = env::var("CLIPPY_ARGS") {
                    args.extend(extra_args.split("__CLIPPY_HACKERY__").filter_map(|s| {
                        if s.is_empty() {
                            None
                        } else {
                            Some(s.to_string())
                        }
                    }));
                }
            }

            let mut clippy = ClippyCallbacks;
            let mut default = rustc_driver::DefaultCallbacks;
            let callbacks: &mut (dyn rustc_driver::Callbacks + Send) =
                if clippy_enabled { &mut clippy } else { &mut default };
            let args = args;
            rustc_driver::run_compiler(&args, callbacks, None, None)
        })
        .and_then(|result| result)
        .is_err() as i32,
    )
}
<|endoftext|>


<commit_before>/*!
    Low level events functions
*/
/*
    Copyright (C) 2016  Gabriel Dubé

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

use std::mem;
use std::ptr;
use std::hash::Hash;
use std::any::TypeId;

use winapi::{HWND, HMENU, UINT, WPARAM, LPARAM, UINT_PTR, DWORD_PTR, LRESULT, DWORD};

use ui::UiInner;
use events::{Event, EventArgs};
use controls::{ControlType, AnyHandle, Timer};

/// A magic number to identify the NWG subclass that dispatches events
const EVENTS_DISPATCH_ID: UINT_PTR = 2465;

fn parse_listbox_command(id: u64, ncode: u32) -> Option<(u64, Event, EventArgs)> {
  use low::defs::{LBN_SELCHANGE, LBN_DBLCLK, LBN_SETFOCUS, LBN_KILLFOCUS};

  match ncode {
    LBN_SELCHANGE => Some((id, Event::SelectionChanged, EventArgs::None)),
    LBN_DBLCLK => Some((id, Event::DoubleClick, EventArgs::None)),
    LBN_SETFOCUS | LBN_KILLFOCUS => Some((id, Event::Focus, EventArgs::Focus(ncode==LBN_SETFOCUS))),
    _ => None
  }
}

fn parse_button_command(id: u64, ncode: u32) -> Option<(u64, Event, EventArgs)> {
  use low::defs::{BN_CLICKED, BN_DBLCLK, BN_SETFOCUS, BN_KILLFOCUS};
  match ncode {
    BN_CLICKED => Some((id, Event::Click, EventArgs::None)),
    BN_DBLCLK => Some((id, Event::DoubleClick, EventArgs::None)),
    BN_SETFOCUS | BN_KILLFOCUS => Some((id, Event::Focus, EventArgs::Focus(ncode==BN_SETFOCUS))),
    _ => None
  }
}

fn parse_edit_command(id: u64, ncode: u32) -> Option<(u64, Event, EventArgs)> {
  use low::defs::{EN_SETFOCUS, EN_KILLFOCUS, EN_UPDATE, EN_MAXTEXT};
  match ncode {
    EN_UPDATE => Some((id, Event::ValueChanged, EventArgs::None)),
    EN_MAXTEXT => Some((id, Event::LimitReached, EventArgs::None)),
    EN_SETFOCUS | EN_KILLFOCUS => Some((id, Event::Focus, EventArgs::Focus(ncode==EN_SETFOCUS))),
    _ => None
  }
}

fn parse_static_command(id: u64, ncode: u32) -> Option<(u64, Event, EventArgs)> {
  use low::defs::{STN_CLICKED, STN_DBLCLK};
  match ncode {
    STN_CLICKED => Some((id, Event::Click, EventArgs::None)),
    STN_DBLCLK => Some((id, Event::DoubleClick, EventArgs::None)),
    _ => None
  }
}

/**
  Parse the common controls notification passed through the `WM_COMMAND` message.
*/
#[inline(always)]
fn parse_command(id: u64, control_type: ControlType, w: WPARAM) -> Option<(u64, Event, EventArgs)> {
  use winapi::HIWORD;

  let ncode = HIWORD(w as DWORD) as u32;
  match control_type {
    ControlType::ListBox => parse_listbox_command(id, ncode),
    ControlType::Button => parse_button_command(id, ncode),
    ControlType::TextInput => parse_edit_command(id, ncode),
    ControlType::Label => parse_static_command(id, ncode),
    _ => None
  }
}

/**
  Proc that dispatches the NWG events
*/
#[allow(unused_variables)]
unsafe extern "system" fn process_events<ID: Hash+Clone+'static>(hwnd: HWND, msg: UINT, w: WPARAM, l: LPARAM, id: UINT_PTR, data: DWORD_PTR) -> LRESULT {
  use comctl32::{DefSubclassProc};
  use winapi::{WM_KEYDOWN, WM_KEYUP, WM_UNICHAR, WM_CHAR, UNICODE_NOCHAR, WM_MENUCOMMAND, WM_CLOSE, WM_LBUTTONUP, WM_LBUTTONDOWN,
    WM_RBUTTONUP, WM_RBUTTONDOWN, WM_MBUTTONUP, WM_MBUTTONDOWN, WM_COMMAND, WM_TIMER, WM_MOVE, WM_SIZING, c_int,
    LOWORD, HIWORD, RECT};
  use low::menu_helper::get_menu_id;

  let inner: &mut UiInner<ID> = mem::transmute(data);
  let inner_id: u64;

  let callback_data = match msg {
    WM_COMMAND => {
      if l == 0 {
        None
      } else {
        // Somehow, WM_COMMAND messages get sent while freeing and so inner_id_from_handle can fail...
        let nhandle: HWND = mem::transmute(l);
        if let Some(id) = inner.inner_id_from_handle( &AnyHandle::HWND(nhandle) ) {
          let control_type = (&mut *inner.controls.get(&id).expect("Could not find a control with with the specified type ID").as_ptr()).control_type();
          parse_command(id, control_type, w)
        } else {
          None
        }
      }
    },
    WM_LBUTTONUP | WM_RBUTTONUP  | WM_MBUTTONUP => {
      inner_id = inner.inner_id_from_handle( &AnyHandle::HWND(hwnd) ).expect("Could not match system handle to ui control (msg: WM_LBUTTONUP | WM_RBUTTONUP  | WM_MBUTTONUP)");;
      Some( (inner_id, Event::MouseUp, parse_mouse_click(msg, l)) )
    },
    WM_LBUTTONDOWN | WM_RBUTTONDOWN | WM_MBUTTONDOWN => {
      inner_id = inner.inner_id_from_handle( &AnyHandle::HWND(hwnd) ).expect("Could not match system handle to ui control (msg: WM_LBUTTONDOWN | WM_RBUTTONDOWN | WM_MBUTTONDOWN)");;
      Some( (inner_id, Event::MouseDown, parse_mouse_click(msg, l)) )
    },
    WM_KEYDOWN | WM_KEYUP => {
      inner_id = inner.inner_id_from_handle( &AnyHandle::HWND(hwnd) ).expect("Could not match system handle to ui control (msg: WM_KEYDOWN | WM_KEYUP)");;
      let evt = if msg == WM_KEYDOWN { Event::KeyDown } else { Event::KeyUp };
      Some( (inner_id, evt, EventArgs::Key(w as u32)) )
    },
    WM_MENUCOMMAND => {
      let parent_menu: HMENU = mem::transmute(l);
      let handle = AnyHandle::HMENU_ITEM(parent_menu, get_menu_id(parent_menu, w as c_int));

      // Custom controls might have their own way to handle the message
      if let Some(inner_id) = inner.inner_id_from_handle( &handle ) {
        Some( (inner_id, Event::Triggered, EventArgs::None) )
      } else {
        None
      }
    },
    WM_UNICHAR | WM_CHAR => {
      inner_id = inner.inner_id_from_handle( &AnyHandle::HWND(hwnd) ).expect("Could not match system handle to ui control (msg: WM_UNICHAR | WM_CHAR)");;
      if w == UNICODE_NOCHAR { return 1; }
      if let Some(c) = ::std::char::from_u32(w as u32) {
        Some( (inner_id, Event::Char, EventArgs::Char( c )) )
      } else {
        None
      }
    },
    WM_TIMER => {
      let handle = AnyHandle::Custom(TypeId::of::<Timer>(), w as usize);

      // Here I assume WM_TIMER will only be sent by built-in timers. Using a user event might be a better idea.
      // Custom controls might have their own way to handle the message
      if let Some(inner_id) = inner.inner_id_from_handle( &handle ) {
        let timer: &mut Box<Timer> = mem::transmute( inner.controls.get(&inner_id).unwrap().as_ptr() );
        Some( (inner_id, Event::Tick, EventArgs::Tick(timer.elapsed())) )
      } else {
        None
      }
    },
    WM_MOVE => {
      inner_id = inner.inner_id_from_handle( &AnyHandle::HWND(hwnd) ).expect("Could not match system handle to ui control (msg: WM_MOVE)");
      let (x, y) = (LOWORD(l as u32), HIWORD(l as u32));
      Some( (inner_id, Event::Moved, EventArgs::Position(x as i32, y as i32)) )
    },
    WM_SIZING => {
      inner_id = inner.inner_id_from_handle( &AnyHandle::HWND(hwnd) ).expect("Could not match system handle to ui control (msg: WM_SIZING)");;
      let r: &RECT = mem::transmute(l);
      let w: u32 = (r.right-r.left) as u32;
      let h: u32 = (r.bottom-r.top) as u32;
      Some( (inner_id, Event::Resized, EventArgs::Size(w, h)) )
    },
    WM_CLOSE => {
      inner_id = inner.inner_id_from_handle( &AnyHandle::HWND(hwnd) ).expect("Could not match system handle to ui control (msg: WM_CLOSE)");
      Some( (inner_id, Event::Closed, EventArgs::None) )
    },
    _ => { None }
  };

  if let Some((inner_id, evt, params)) = callback_data {
    inner.trigger(inner_id, evt, params);
  }

  DefSubclassProc(hwnd, msg, w, l)
}

/**
    Add a subclass that dispatches the system event to the application callbacks to a window control.
*/
pub fn hook_window_events<ID: Hash+Clone+'static>(uiinner: &mut UiInner<ID>, handle: HWND) { unsafe {
  use comctl32::SetWindowSubclass;

  // While definitely questionable in term of safeness, the reference to the UiInner is actually (always)
  // a raw pointer belonging to a Ui. Also, when the Ui goes out of scope, every window control
  // gets destroyed BEFORE the UiInner, this guarantees that uinner lives long enough.
  let ui_inner_raw: *mut UiInner<ID> = uiinner as *mut UiInner<ID>;
  SetWindowSubclass(handle, Some(process_events::<ID>), EVENTS_DISPATCH_ID, mem::transmute(ui_inner_raw));
}}

/**
  Remove a subclass and free the associated data
*/
pub fn unhook_window_events<ID: Hash+Clone+'static>(handle: HWND) { unsafe {
  use comctl32::{RemoveWindowSubclass, GetWindowSubclass};
  use winapi::{TRUE, DWORD_PTR};

  let mut data: DWORD_PTR = 0;
  if GetWindowSubclass(handle, Some(process_events::<ID>), EVENTS_DISPATCH_ID, &mut data) == TRUE {
    RemoveWindowSubclass(handle, Some(process_events::<ID>), EVENTS_DISPATCH_ID);
  }
}}

/**
  Check if a window is hooked by nwg. If it is, return its ID, if not return None
*/
pub unsafe fn window_id<ID: Clone+Hash>(handle: HWND, inner_ref: *mut UiInner<ID>) -> Option<u64> {
  use comctl32::GetWindowSubclass;
  use winapi::{TRUE, DWORD_PTR};

  let mut data: DWORD_PTR = 0;
  if GetWindowSubclass(handle, Some(process_events::<ID>), EVENTS_DISPATCH_ID, &mut data) == TRUE {
    let data: *mut UiInner<ID> = mem::transmute(data);
    if data == inner_ref {
      (&*data).inner_id_from_handle( &AnyHandle::HWND(handle) )
    } else {
      None
    }
  } else {
    None
  }
}

/**
    Dispatch the messages waiting the the system message queue to the associated Uis. This includes NWG custom messages.

    Return once a quit event was received.
*/
#[inline(always)]
pub unsafe fn dispatch_events() {
  use winapi::MSG;
  use user32::{GetMessageW, TranslateMessage, DispatchMessageW};

  let mut msg: MSG = mem::uninitialized();
  while GetMessageW(&mut msg, ptr::null_mut(), 0, 0) != 0 {
      TranslateMessage(&msg);
      DispatchMessageW(&msg);
      // TODO dispatch events sent from other thread / other processes ( after first stable release )
  }
}

/**
    Send a WM_QUIT to the system queue. Breaks the dispatch_events loop.
*/
#[inline(always)]
pub unsafe fn exit() {
  use user32::PostMessageW;
  use winapi::WM_QUIT;

  PostMessageW(ptr::null_mut(), WM_QUIT, 0, 0);
}

fn parse_mouse_click(msg: UINT, l: LPARAM) -> EventArgs {
  use defs::MouseButton;
  use winapi::{WM_LBUTTONUP, WM_LBUTTONDOWN, WM_RBUTTONUP, WM_RBUTTONDOWN, WM_MBUTTONUP, WM_MBUTTONDOWN,
    GET_X_LPARAM, GET_Y_LPARAM};

  let btn = match msg {
    WM_LBUTTONUP | WM_LBUTTONDOWN => MouseButton::Left,
    WM_RBUTTONUP | WM_RBUTTONDOWN => MouseButton::Right,
    WM_MBUTTONUP | WM_MBUTTONDOWN => MouseButton::Middle,
    _ => MouseButton::Left
  };

  let x = GET_X_LPARAM(l) as i32;
  let y = GET_Y_LPARAM(l) as i32;

  EventArgs::MouseClick{btn: btn, pos: (x, y)}
}<commit_msg>client size fix<commit_after>/*!
    Low level events functions
*/
/*
    Copyright (C) 2016  Gabriel Dubé

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

use std::mem;
use std::ptr;
use std::hash::Hash;
use std::any::TypeId;

use winapi::{HWND, HMENU, UINT, WPARAM, LPARAM, UINT_PTR, DWORD_PTR, LRESULT, DWORD};

use ui::UiInner;
use events::{Event, EventArgs};
use controls::{ControlType, AnyHandle, Timer};

/// A magic number to identify the NWG subclass that dispatches events
const EVENTS_DISPATCH_ID: UINT_PTR = 2465;

fn parse_listbox_command(id: u64, ncode: u32) -> Option<(u64, Event, EventArgs)> {
  use low::defs::{LBN_SELCHANGE, LBN_DBLCLK, LBN_SETFOCUS, LBN_KILLFOCUS};

  match ncode {
    LBN_SELCHANGE => Some((id, Event::SelectionChanged, EventArgs::None)),
    LBN_DBLCLK => Some((id, Event::DoubleClick, EventArgs::None)),
    LBN_SETFOCUS | LBN_KILLFOCUS => Some((id, Event::Focus, EventArgs::Focus(ncode==LBN_SETFOCUS))),
    _ => None
  }
}

fn parse_button_command(id: u64, ncode: u32) -> Option<(u64, Event, EventArgs)> {
  use low::defs::{BN_CLICKED, BN_DBLCLK, BN_SETFOCUS, BN_KILLFOCUS};
  match ncode {
    BN_CLICKED => Some((id, Event::Click, EventArgs::None)),
    BN_DBLCLK => Some((id, Event::DoubleClick, EventArgs::None)),
    BN_SETFOCUS | BN_KILLFOCUS => Some((id, Event::Focus, EventArgs::Focus(ncode==BN_SETFOCUS))),
    _ => None
  }
}

fn parse_edit_command(id: u64, ncode: u32) -> Option<(u64, Event, EventArgs)> {
  use low::defs::{EN_SETFOCUS, EN_KILLFOCUS, EN_UPDATE, EN_MAXTEXT};
  match ncode {
    EN_UPDATE => Some((id, Event::ValueChanged, EventArgs::None)),
    EN_MAXTEXT => Some((id, Event::LimitReached, EventArgs::None)),
    EN_SETFOCUS | EN_KILLFOCUS => Some((id, Event::Focus, EventArgs::Focus(ncode==EN_SETFOCUS))),
    _ => None
  }
}

fn parse_static_command(id: u64, ncode: u32) -> Option<(u64, Event, EventArgs)> {
  use low::defs::{STN_CLICKED, STN_DBLCLK};
  match ncode {
    STN_CLICKED => Some((id, Event::Click, EventArgs::None)),
    STN_DBLCLK => Some((id, Event::DoubleClick, EventArgs::None)),
    _ => None
  }
}

/**
  Parse the common controls notification passed through the `WM_COMMAND` message.
*/
#[inline(always)]
fn parse_command(id: u64, control_type: ControlType, w: WPARAM) -> Option<(u64, Event, EventArgs)> {
  use winapi::HIWORD;

  let ncode = HIWORD(w as DWORD) as u32;
  match control_type {
    ControlType::ListBox => parse_listbox_command(id, ncode),
    ControlType::Button => parse_button_command(id, ncode),
    ControlType::TextInput => parse_edit_command(id, ncode),
    ControlType::Label => parse_static_command(id, ncode),
    _ => None
  }
}

/**
  Proc that dispatches the NWG events
*/
#[allow(unused_variables)]
unsafe extern "system" fn process_events<ID: Hash+Clone+'static>(hwnd: HWND, msg: UINT, w: WPARAM, l: LPARAM, id: UINT_PTR, data: DWORD_PTR) -> LRESULT {
  use comctl32::DefSubclassProc;
  use user32::GetClientRect;
  use winapi::{WM_KEYDOWN, WM_KEYUP, WM_UNICHAR, WM_CHAR, UNICODE_NOCHAR, WM_MENUCOMMAND, WM_CLOSE, WM_LBUTTONUP, WM_LBUTTONDOWN,
    WM_RBUTTONUP, WM_RBUTTONDOWN, WM_MBUTTONUP, WM_MBUTTONDOWN, WM_COMMAND, WM_TIMER, WM_MOVE, WM_SIZING, WM_EXITSIZEMOVE, WM_SIZE,
    c_int, LOWORD, HIWORD, RECT};
  use low::menu_helper::get_menu_id;

  let inner: &mut UiInner<ID> = mem::transmute(data);
  let inner_id: u64;

  let callback_data = match msg {
    WM_COMMAND => {
      if l == 0 {
        None
      } else {
        // Somehow, WM_COMMAND messages get sent while freeing and so inner_id_from_handle can fail...
        let nhandle: HWND = mem::transmute(l);
        if let Some(id) = inner.inner_id_from_handle( &AnyHandle::HWND(nhandle) ) {
          let control_type = (&mut *inner.controls.get(&id).expect("Could not find a control with with the specified type ID").as_ptr()).control_type();
          parse_command(id, control_type, w)
        } else {
          None
        }
      }
    },
    WM_LBUTTONUP | WM_RBUTTONUP  | WM_MBUTTONUP => {
      inner_id = inner.inner_id_from_handle( &AnyHandle::HWND(hwnd) ).expect("Could not match system handle to ui control (msg: WM_LBUTTONUP | WM_RBUTTONUP  | WM_MBUTTONUP)");;
      Some( (inner_id, Event::MouseUp, parse_mouse_click(msg, l)) )
    },
    WM_LBUTTONDOWN | WM_RBUTTONDOWN | WM_MBUTTONDOWN => {
      inner_id = inner.inner_id_from_handle( &AnyHandle::HWND(hwnd) ).expect("Could not match system handle to ui control (msg: WM_LBUTTONDOWN | WM_RBUTTONDOWN | WM_MBUTTONDOWN)");;
      Some( (inner_id, Event::MouseDown, parse_mouse_click(msg, l)) )
    },
    WM_KEYDOWN | WM_KEYUP => {
      inner_id = inner.inner_id_from_handle( &AnyHandle::HWND(hwnd) ).expect("Could not match system handle to ui control (msg: WM_KEYDOWN | WM_KEYUP)");;
      let evt = if msg == WM_KEYDOWN { Event::KeyDown } else { Event::KeyUp };
      Some( (inner_id, evt, EventArgs::Key(w as u32)) )
    },
    WM_MENUCOMMAND => {
      let parent_menu: HMENU = mem::transmute(l);
      let handle = AnyHandle::HMENU_ITEM(parent_menu, get_menu_id(parent_menu, w as c_int));

      // Custom controls might have their own way to handle the message
      if let Some(inner_id) = inner.inner_id_from_handle( &handle ) {
        Some( (inner_id, Event::Triggered, EventArgs::None) )
      } else {
        None
      }
    },
    WM_UNICHAR | WM_CHAR => {
      inner_id = inner.inner_id_from_handle( &AnyHandle::HWND(hwnd) ).expect("Could not match system handle to ui control (msg: WM_UNICHAR | WM_CHAR)");;
      if w == UNICODE_NOCHAR { return 1; }
      if let Some(c) = ::std::char::from_u32(w as u32) {
        Some( (inner_id, Event::Char, EventArgs::Char( c )) )
      } else {
        None
      }
    },
    WM_TIMER => {
      let handle = AnyHandle::Custom(TypeId::of::<Timer>(), w as usize);

      // Here I assume WM_TIMER will only be sent by built-in timers. Using a user event might be a better idea.
      // Custom controls might have their own way to handle the message
      if let Some(inner_id) = inner.inner_id_from_handle( &handle ) {
        let timer: &mut Box<Timer> = mem::transmute( inner.controls.get(&inner_id).unwrap().as_ptr() );
        Some( (inner_id, Event::Tick, EventArgs::Tick(timer.elapsed())) )
      } else {
        None
      }
    },
    WM_MOVE => {
      inner_id = inner.inner_id_from_handle( &AnyHandle::HWND(hwnd) ).expect("Could not match system handle to ui control (msg: WM_MOVE)");
      let (x, y) = (LOWORD(l as u32), HIWORD(l as u32));
      Some( (inner_id, Event::Moved, EventArgs::Position(x as i32, y as i32)) )
    },
    WM_SIZING | WM_SIZE=> {
      inner_id = inner.inner_id_from_handle( &AnyHandle::HWND(hwnd) ).expect("Could not match system handle to ui control (msg: WM_SIZING)");
      let mut r: RECT = mem::uninitialized();
      GetClientRect(hwnd, &mut r);
      let w: u32 = (r.right-r.left) as u32;
      let h: u32 = (r.bottom-r.top) as u32;
      Some( (inner_id, Event::Resized, EventArgs::Size(w, h)) )
    },
    WM_EXITSIZEMOVE => {
      inner_id = inner.inner_id_from_handle( &AnyHandle::HWND(hwnd) ).expect("Could not match system handle to ui control (msg: WM_SIZING)");
      let mut r: RECT = mem::uninitialized();
      GetClientRect(hwnd, &mut r);
      let w: u32 = (r.right-r.left) as u32;
      let h: u32 = (r.bottom-r.top) as u32;
      Some( (inner_id, Event::Resized, EventArgs::Size(w, h)) )
    }
    WM_CLOSE => {
      inner_id = inner.inner_id_from_handle( &AnyHandle::HWND(hwnd) ).expect("Could not match system handle to ui control (msg: WM_CLOSE)");
      Some( (inner_id, Event::Closed, EventArgs::None) )
    },
    _ => { None }
  };

  if let Some((inner_id, evt, params)) = callback_data {
    inner.trigger(inner_id, evt, params);
  }

  DefSubclassProc(hwnd, msg, w, l)
}

/**
    Add a subclass that dispatches the system event to the application callbacks to a window control.
*/
pub fn hook_window_events<ID: Hash+Clone+'static>(uiinner: &mut UiInner<ID>, handle: HWND) { unsafe {
  use comctl32::SetWindowSubclass;

  // While definitely questionable in term of safeness, the reference to the UiInner is actually (always)
  // a raw pointer belonging to a Ui. Also, when the Ui goes out of scope, every window control
  // gets destroyed BEFORE the UiInner, this guarantees that uinner lives long enough.
  let ui_inner_raw: *mut UiInner<ID> = uiinner as *mut UiInner<ID>;
  SetWindowSubclass(handle, Some(process_events::<ID>), EVENTS_DISPATCH_ID, mem::transmute(ui_inner_raw));
}}

/**
  Remove a subclass and free the associated data
*/
pub fn unhook_window_events<ID: Hash+Clone+'static>(handle: HWND) { unsafe {
  use comctl32::{RemoveWindowSubclass, GetWindowSubclass};
  use winapi::{TRUE, DWORD_PTR};

  let mut data: DWORD_PTR = 0;
  if GetWindowSubclass(handle, Some(process_events::<ID>), EVENTS_DISPATCH_ID, &mut data) == TRUE {
    RemoveWindowSubclass(handle, Some(process_events::<ID>), EVENTS_DISPATCH_ID);
  }
}}

/**
  Check if a window is hooked by nwg. If it is, return its ID, if not return None
*/
pub unsafe fn window_id<ID: Clone+Hash>(handle: HWND, inner_ref: *mut UiInner<ID>) -> Option<u64> {
  use comctl32::GetWindowSubclass;
  use winapi::{TRUE, DWORD_PTR};

  let mut data: DWORD_PTR = 0;
  if GetWindowSubclass(handle, Some(process_events::<ID>), EVENTS_DISPATCH_ID, &mut data) == TRUE {
    let data: *mut UiInner<ID> = mem::transmute(data);
    if data == inner_ref {
      (&*data).inner_id_from_handle( &AnyHandle::HWND(handle) )
    } else {
      None
    }
  } else {
    None
  }
}

/**
    Dispatch the messages waiting the the system message queue to the associated Uis. This includes NWG custom messages.

    Return once a quit event was received.
*/
#[inline(always)]
pub unsafe fn dispatch_events() {
  use winapi::MSG;
  use user32::{GetMessageW, TranslateMessage, DispatchMessageW};

  let mut msg: MSG = mem::uninitialized();
  while GetMessageW(&mut msg, ptr::null_mut(), 0, 0) != 0 {
      TranslateMessage(&msg);
      DispatchMessageW(&msg);
      // TODO dispatch events sent from other thread / other processes ( after first stable release )
  }
}

/**
    Send a WM_QUIT to the system queue. Breaks the dispatch_events loop.
*/
#[inline(always)]
pub unsafe fn exit() {
  use user32::PostMessageW;
  use winapi::WM_QUIT;

  PostMessageW(ptr::null_mut(), WM_QUIT, 0, 0);
}

fn parse_mouse_click(msg: UINT, l: LPARAM) -> EventArgs {
  use defs::MouseButton;
  use winapi::{WM_LBUTTONUP, WM_LBUTTONDOWN, WM_RBUTTONUP, WM_RBUTTONDOWN, WM_MBUTTONUP, WM_MBUTTONDOWN,
    GET_X_LPARAM, GET_Y_LPARAM};

  let btn = match msg {
    WM_LBUTTONUP | WM_LBUTTONDOWN => MouseButton::Left,
    WM_RBUTTONUP | WM_RBUTTONDOWN => MouseButton::Right,
    WM_MBUTTONUP | WM_MBUTTONDOWN => MouseButton::Middle,
    _ => MouseButton::Left
  };

  let x = GET_X_LPARAM(l) as i32;
  let y = GET_Y_LPARAM(l) as i32;

  EventArgs::MouseClick{btn: btn, pos: (x, y)}
}<|endoftext|>