<commit_before>use std::fs::{OpenOptions, File};
use std::io::Read;
use std::result::Result::{self, Ok, Err};

use libmodbus_rs::{Modbus, ModbusClient, ModbusRTU, SerialMode, Timeout};

use retry::{Retry, RetryError};

use hex_slice::AsHex;

pub trait SunSaverConnection {
    fn read_registers(&mut self) ->  [u16; 44];

    fn read_response(&mut self) -> SunSaverResponse {
        SunSaverResponse::from_raw_bits(self.read_registers())
    }
}

pub struct ModbusSunSaverConnection {
    connection: Modbus,
}

impl ModbusSunSaverConnection {
    pub fn open(device: &str) -> ModbusSunSaverConnection {
        /* A Meterbus to Serial Converter (MSC) is required to adapt the Meter interface to an isolated RS-232 interface**.
           The SunSaver MPPT supports RTU mode only.
           16bit MODBUS® addresses (per the modbus.org spec)
           The serial communication parameters are:
             * BPS: 9600 baud
             * Parity: None
             * Data bits: 8
             * Stop bits: 2
             * Flow control: None
            All addresses listed are for the request PDU.
            The SunSaver MPPT default server address: 0x01. */
        debug!("Configuring device {}", device);
        let mut connection = Modbus::new_rtu(device, 9600, 'N', 8, 2).unwrap();
        assert!(connection.set_slave(0x01).is_ok());
        assert!(connection.rtu_set_serial_mode(SerialMode::MODBUS_RTU_RS232).is_ok());
        assert!(connection.set_response_timeout( Timeout { sec: 1, usec: 0 } ).is_ok());
        connection.set_debug(false).unwrap();

        let timeout = connection.get_response_timeout();
        info!("Timout {:?}", timeout);

        assert!(connection.connect().is_ok());
        debug!("Connected");

        ModbusSunSaverConnection {
            connection: connection,
        }
    }

    fn read_registers_retry(&self, address: i32, num_bit: i32, dest: &mut [u16]) -> Result<usize, RetryError> {
        match Retry::new(
            &mut || self.connection.read_registers(address, num_bit, dest),
            &mut |response| response.is_ok()
            ).try(3).wait(100).execute() {
            Ok(response) => Ok(response.unwrap() as usize),
            Err(error) => Err(error),
        }
    }
}

impl SunSaverConnection for ModbusSunSaverConnection {
    fn read_registers(&mut self) -> [u16; 44] {
        let mut response_register = [0u16; 44 as usize];
        let mut num_read_bytes = 0;
        num_read_bytes += self.read_registers_retry(0x08, 22, &mut response_register[0..22]).unwrap();
        num_read_bytes += self.read_registers_retry(0x1E, 22, &mut response_register[23..44]).unwrap();
        //if num_read_bytes != 44 {
        //    panic!("Failed to read all registers! Required 44 got {}", num_read_bytes);
        //}
        debug!("Read {} bytes", num_read_bytes);
        debug!("read reg 0x08 + 44: {:#x}", response_register.as_hex());

        response_register
    }
}

#[derive(Debug)]
pub struct FileSunSaverConnection {
    file: File,
}

impl FileSunSaverConnection {
    pub fn open(filename: &str) -> FileSunSaverConnection {
        let file = OpenOptions::new().read(true).write(false).open(filename).unwrap();

        FileSunSaverConnection {
            file: file,
        }
    }
}

impl SunSaverConnection for FileSunSaverConnection {
    fn read_registers(&mut self) -> [u16; 44] {
        let mut response_register_u8 = [0u8; 88 as usize];
        assert!(self.file.read_exact(&mut response_register_u8).is_ok());

        let response_register_vec_u16: Vec<u16> = response_register_u8.chunks(2)
        .map(|items| {
            u16::from_le(((items[0] as u16) << 8) + (items[1] as u16))
        })
        .collect();

        let mut response_register_u16 = [0u16; 44 as usize];
        response_register_u16.clone_from_slice(&response_register_vec_u16);
        response_register_u16
    }
}

#[derive(Debug, Clone, Serialize)]
pub struct SunSaverResponse {
    // Adc_vb_f
    // [09][0x0008] (V). battery voltage, filtered.
    // Voltage measured directly at the battery connection on the SunSaver MPPT.
    adc_vb_f: u16,
    // Adc_va_f
    // [10][0x0009] (V). solar input voltage.
    // Va is the terminal voltage of the solar input connection.
    adc_va_f: u16,
    // Adc_vl_f
    // [11][0x000A] (V). load voltage.
    // Vl is the terminal voltage of the load output connection.
    adc_vl_f: u16,
}

macro_rules! conv_100_2_15_scale {
    ($expression:expr) => (
        (($expression as f32) * 100.0) / 32768.0
    )
}

impl SunSaverResponse {
    fn from_raw_bits(raw_data: [u16; 44]) -> SunSaverResponse {
        SunSaverResponse {
            adc_vb_f: raw_data[0],
            adc_va_f: raw_data[1],
            adc_vl_f: raw_data[2],
        }
    }

    pub fn battery_voltage_filtered(&self) -> f32 {
        conv_100_2_15_scale!(self.adc_vb_f)
    }

    pub fn solar_input_voltage_filtered(&self) -> f32 {
        conv_100_2_15_scale!(self.adc_va_f)
    }

    pub fn load_voltage_filtered(&self) -> f32 {
        conv_100_2_15_scale!(self.adc_vl_f)
    }
}

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

    #[test]
    fn sunsaverresponse_from_raw_bits() {
        let raw_bits = [
            0x1079, 0x11c9, 0x1074, 0x0035, 0x009a, 0x0017, 0x0017, 0x0017,
            0x0019, 0x0005, 0x0000, 0x1079, 0x1200, 0x0000, 0x1712, 0x0000,
            0x1712, 0x004e, 0x0001, 0x0000, 0x0e13, 0x0000, 0x0000, 0x0f2a,
            0x0000, 0x0f2a, 0x0000, 0x26b6, 0x0000, 0x0001, 0x000b, 0x0006,
            0x006b, 0x10b1, 0x0123, 0x1640, 0x1009, 0x11f1, 0x004e, 0x002b,
            0x0000, 0x0000, 0x0000, 0x0001
        ];
        let response = SunSaverResponse::from_raw_bits(raw_bits);

        assert_eq!(response.adc_vb_f, 0x1079);
    }
}
<commit_msg>Add more tests<commit_after>use std::fs::{OpenOptions, File};
use std::io::Read;
use std::result::Result::{self, Ok, Err};

use libmodbus_rs::{Modbus, ModbusClient, ModbusRTU, SerialMode, Timeout};

use retry::{Retry, RetryError};

use hex_slice::AsHex;

pub trait SunSaverConnection {
    fn read_registers(&mut self) ->  [u16; 44];

    fn read_response(&mut self) -> SunSaverResponse {
        SunSaverResponse::from_raw_bits(self.read_registers())
    }
}

pub struct ModbusSunSaverConnection {
    connection: Modbus,
}

impl ModbusSunSaverConnection {
    pub fn open(device: &str) -> ModbusSunSaverConnection {
        /* A Meterbus to Serial Converter (MSC) is required to adapt the Meter interface to an isolated RS-232 interface**.
           The SunSaver MPPT supports RTU mode only.
           16bit MODBUS® addresses (per the modbus.org spec)
           The serial communication parameters are:
             * BPS: 9600 baud
             * Parity: None
             * Data bits: 8
             * Stop bits: 2
             * Flow control: None
            All addresses listed are for the request PDU.
            The SunSaver MPPT default server address: 0x01. */
        debug!("Configuring device {}", device);
        let mut connection = Modbus::new_rtu(device, 9600, 'N', 8, 2).unwrap();
        assert!(connection.set_slave(0x01).is_ok());
        assert!(connection.rtu_set_serial_mode(SerialMode::MODBUS_RTU_RS232).is_ok());
        assert!(connection.set_response_timeout( Timeout { sec: 1, usec: 0 } ).is_ok());
        connection.set_debug(false).unwrap();

        let timeout = connection.get_response_timeout();
        info!("Timout {:?}", timeout);

        assert!(connection.connect().is_ok());
        debug!("Connected");

        ModbusSunSaverConnection {
            connection: connection,
        }
    }

    fn read_registers_retry(&self, address: i32, num_bit: i32, dest: &mut [u16]) -> Result<usize, RetryError> {
        match Retry::new(
            &mut || self.connection.read_registers(address, num_bit, dest),
            &mut |response| response.is_ok()
            ).try(3).wait(100).execute() {
            Ok(response) => Ok(response.unwrap() as usize),
            Err(error) => Err(error),
        }
    }
}

impl SunSaverConnection for ModbusSunSaverConnection {
    fn read_registers(&mut self) -> [u16; 44] {
        let mut response_register = [0u16; 44 as usize];
        let mut num_read_bytes = 0;
        num_read_bytes += self.read_registers_retry(0x08, 22, &mut response_register[0..22]).unwrap();
        num_read_bytes += self.read_registers_retry(0x1E, 22, &mut response_register[23..44]).unwrap();
        //if num_read_bytes != 44 {
        //    panic!("Failed to read all registers! Required 44 got {}", num_read_bytes);
        //}
        debug!("Read {} bytes", num_read_bytes);
        debug!("read reg 0x08 + 44: {:#x}", response_register.as_hex());

        response_register
    }
}

#[derive(Debug)]
pub struct FileSunSaverConnection {
    file: File,
}

impl FileSunSaverConnection {
    pub fn open(filename: &str) -> FileSunSaverConnection {
        let file = OpenOptions::new().read(true).write(false).open(filename).unwrap();

        FileSunSaverConnection {
            file: file,
        }
    }
}

impl SunSaverConnection for FileSunSaverConnection {
    fn read_registers(&mut self) -> [u16; 44] {
        let mut response_register_u8 = [0u8; 88 as usize];
        assert!(self.file.read_exact(&mut response_register_u8).is_ok());

        let response_register_vec_u16: Vec<u16> = response_register_u8.chunks(2)
        .map(|items| {
            u16::from_le(((items[0] as u16) << 8) + (items[1] as u16))
        })
        .collect();

        let mut response_register_u16 = [0u16; 44 as usize];
        response_register_u16.clone_from_slice(&response_register_vec_u16);
        response_register_u16
    }
}

#[derive(Debug, Clone, Serialize)]
pub struct SunSaverResponse {
    // Adc_vb_f
    // [09][0x0008] (V). battery voltage, filtered.
    // Voltage measured directly at the battery connection on the SunSaver MPPT.
    adc_vb_f: u16,
    // Adc_va_f
    // [10][0x0009] (V). solar input voltage.
    // Va is the terminal voltage of the solar input connection.
    adc_va_f: u16,
    // Adc_vl_f
    // [11][0x000A] (V). load voltage.
    // Vl is the terminal voltage of the load output connection.
    adc_vl_f: u16,
}

macro_rules! conv_100_2_15_scale {
    ($expression:expr) => (
        (($expression as f32) * 100.0) / 32768.0
    )
}

impl SunSaverResponse {
    fn from_raw_bits(raw_data: [u16; 44]) -> SunSaverResponse {
        SunSaverResponse {
            adc_vb_f: raw_data[0],
            adc_va_f: raw_data[1],
            adc_vl_f: raw_data[2],
        }
    }

    pub fn battery_voltage_filtered(&self) -> f32 {
        conv_100_2_15_scale!(self.adc_vb_f)
    }

    pub fn solar_input_voltage_filtered(&self) -> f32 {
        conv_100_2_15_scale!(self.adc_va_f)
    }

    pub fn load_voltage_filtered(&self) -> f32 {
        conv_100_2_15_scale!(self.adc_vl_f)
    }
}

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

    const DEFAULT_TEST_RAW_BITS: [u16; 44] = [
        0x1079, 0x11c9, 0x1074, 0x0035, 0x009a, 0x0017, 0x0017, 0x0017,
        0x0019, 0x0005, 0x0000, 0x1079, 0x1200, 0x0000, 0x1712, 0x0000,
        0x1712, 0x004e, 0x0001, 0x0000, 0x0e13, 0x0000, 0x0000, 0x0f2a,
        0x0000, 0x0f2a, 0x0000, 0x26b6, 0x0000, 0x0001, 0x000b, 0x0006,
        0x006b, 0x10b1, 0x0123, 0x1640, 0x1009, 0x11f1, 0x004e, 0x002b,
        0x0000, 0x0000, 0x0000, 0x0001
    ];

    #[test]
    fn sunsaverresponse_from_raw_bits() {
        let response = SunSaverResponse::from_raw_bits(DEFAULT_TEST_RAW_BITS);

        assert_eq!(response.adc_vb_f, 0x1079);
        assert_eq!(response.adc_va_f, 0x11c9);
        assert_eq!(response.adc_vl_f, 0x1074);
    }

    #[test]
    fn sunsaverresponse_from_raw_bits_converted() {
        let response = SunSaverResponse::from_raw_bits(DEFAULT_TEST_RAW_BITS);

        assert_eq!(response.battery_voltage_filtered(), 12.869263);
        assert_eq!(response.solar_input_voltage_filtered(), 13.894653);
        assert_eq!(response.load_voltage_filtered(), 12.854004);
    }
}
<|endoftext|>


<commit_before>use nalgebra::UnitQuaternion;
use std::default::Default;

pub use luminance::linear::M44;
pub use nalgebra::{Matrix4, Vector3};

/// Class of types that can yield transformation matrices.
pub trait Transformable {
  fn transform(&self) -> M44;
}

pub type Translation = Vector3<f32>;
pub type Axis = Vector3<f32>;
pub type Position = Vector3<f32>;
pub type Orientation = UnitQuaternion<f32>;

pub const X_AXIS: Axis = Axis { x: 1., y: 0., z: 0. };
pub const Y_AXIS: Axis = Axis { x: 0., y: 1., z: 0. };
pub const Z_AXIS: Axis = Axis { x: 0., y: 0., z: 1. };

/// Arbritrary scale.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Scale {
  pub x: f32,
  pub y: f32,
  pub z: f32
}

impl Scale {
  pub fn new(x: f32, y: f32, z: f32) -> Self {
    Scale {
      x: x,
      y: y,
      z: z
    }
  }

  pub fn uni(x: f32) -> Self {
    Scale {
      x: x,
      y: x,
      z: x
    }
  }

  pub fn to_mat(&self) -> Matrix4<f32> {
    Matrix4::new(
      self.x,     0.,     0., 0.,
          0., self.y,     0., 0.,
          0.,     0., self.z, 0.,
          0.,     0.,     0., 1.
    )
  }
}

impl Default for Scale {
  fn default() -> Self { Scale::new(1., 1., 1.) }
}

impl<'a> From<&'a [f32; 3]> for Scale {
  fn from(slice: &[f32; 3]) -> Self {
    Scale {
      x: slice[0],
      y: slice[1],
      z: slice[2]
    }
  }
}

impl<'a> From<&'a Scale> for [f32; 3] {
  fn from(scale: &Scale) -> Self {
    [scale.x, scale.y, scale.z]
  }
}

pub fn translation_matrix(v: Translation) -> Matrix4<f32> {
  Matrix4::new(
    1., 0., 0., v.x,
    0., 1., 0., v.y,
    0., 0., 1., v.z,
    0., 0., 0.,  1.,
  )
}

//pub fn instance_matrix(&self) -> Matrix4<f32> {
//  translation_matrix(self.translation) * self.scale.to_mat() * self.orientation.to_rotation_matrix().to_homogeneous()
//}
//
//pub fn view_matrix(&self) -> Matrix4<f32> {
//  self.orientation.to_rotation_matrix().to_homogeneous() * translation_matrix(self.translation) * self.scale.to_mat()
//}
<commit_msg>Removed useless stuff.<commit_after>use nalgebra::UnitQuaternion;
use std::default::Default;

pub use luminance::linear::M44;
pub use nalgebra::{Matrix4, Vector3};

/// Class of types that can yield transformation matrices.
pub trait Transformable {
  fn transform(&self) -> M44;
}

pub type Translation = Vector3<f32>;
pub type Axis = Vector3<f32>;
pub type Position = Vector3<f32>;
pub type Orientation = UnitQuaternion<f32>;

pub const X_AXIS: Axis = Axis { x: 1., y: 0., z: 0. };
pub const Y_AXIS: Axis = Axis { x: 0., y: 1., z: 0. };
pub const Z_AXIS: Axis = Axis { x: 0., y: 0., z: 1. };

/// Arbritrary scale.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Scale {
  pub x: f32,
  pub y: f32,
  pub z: f32
}

impl Scale {
  pub fn new(x: f32, y: f32, z: f32) -> Self {
    Scale {
      x: x,
      y: y,
      z: z
    }
  }

  pub fn uni(x: f32) -> Self {
    Scale {
      x: x,
      y: x,
      z: x
    }
  }

  pub fn to_mat(&self) -> Matrix4<f32> {
    Matrix4::new(
      self.x,     0.,     0., 0.,
          0., self.y,     0., 0.,
          0.,     0., self.z, 0.,
          0.,     0.,     0., 1.
    )
  }
}

impl Default for Scale {
  fn default() -> Self { Scale::new(1., 1., 1.) }
}

impl<'a> From<&'a [f32; 3]> for Scale {
  fn from(slice: &[f32; 3]) -> Self {
    Scale {
      x: slice[0],
      y: slice[1],
      z: slice[2]
    }
  }
}

impl<'a> From<&'a Scale> for [f32; 3] {
  fn from(scale: &Scale) -> Self {
    [scale.x, scale.y, scale.z]
  }
}

pub fn translation_matrix(v: Translation) -> Matrix4<f32> {
  Matrix4::new(
    1., 0., 0., v.x,
    0., 1., 0., v.y,
    0., 0., 1., v.z,
    0., 0., 0.,  1.,
  )
}
<|endoftext|>


<commit_before>// Copyright (c) 2014, 2015 Robert Clipsham <robert@octarineparrot.com>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Support for implementing transport layer protocols
//!
//! The transport module provides the ability to send and receive packets at
//! the transport layer using IPv4 or IPv6. It also enables layer 3 networking
//! for specific transport protocols, using IPv4 only.
//!
//! Note that this is limited by operating system support - for example, on OS
//! X and FreeBSD, it is impossible to implement protocols which are already
//! implemented in the kernel such as TCP and UDP.

#![macro_use]

extern crate libc;

use self::TransportProtocol::{Ipv4, Ipv6};
use self::TransportChannelType::{Layer3, Layer4};

use std::io;
use std::io::Error;
use std::iter::repeat;
use std::net::{self, IpAddr};
use std::mem;
use std::sync::Arc;

use packet::Packet;
use packet::ip::IpNextHeaderProtocol;
use packet::ipv4::Ipv4Packet;
use packet::udp::UdpPacket;

use internal;
use sockets;

/// Represents a transport layer protocol
#[derive(Clone, Copy)]
pub enum TransportProtocol {
    /// Represents a transport protocol built on top of IPv4
    Ipv4(IpNextHeaderProtocol),
    /// Represents a transport protocol built on top of IPv6
    Ipv6(IpNextHeaderProtocol),
}

/// Type of transport channel to present
#[derive(Clone, Copy)]
pub enum TransportChannelType {
    /// The application will send and receive transport layer packets
    Layer4(TransportProtocol),
    /// The application will send and receive IPv4 packets, with the specified transport protocol
    Layer3(IpNextHeaderProtocol),
}

/// Structure used for sending at the transport layer. Should be created with transport_channel()
pub struct TransportSender {
    socket: Arc<internal::FileDesc>,
    _channel_type: TransportChannelType,
}

/// Structure used for sending at the transport layer. Should be created with transport_channel()
pub struct TransportReceiver {
    socket: Arc<internal::FileDesc>,
    buffer: Vec<u8>,
    channel_type: TransportChannelType,
}

/// Create a new (TransportSender, TransportReceiver) pair
///
/// This allows for sending and receiving packets at the transport layer. The buffer size should be
/// large enough to handle the largest packet you wish to receive.
///
/// The channel type specifies what layer to send and receive packets at, and the transport
/// protocol you wish to implement. For example, `Layer4(Ipv4(IpNextHeaderProtocols::Udp))` would
/// allow sending and receiving UDP packets using IPv4; whereas Layer3(IpNextHeaderProtocols::Udp)
/// would include the IPv4 Header in received values, and require manual construction of an IP
/// header when sending.
pub fn transport_channel(buffer_size: usize,
                         channel_type: TransportChannelType)
    -> io::Result<(TransportSender, TransportReceiver)> {
    use std::net;

    // This hack makes sure that winsock is initialised
    let _ = {
        let ip = net::Ipv4Addr::new(255, 255, 255, 255);
        let sockaddr = net::SocketAddr::V4(net::SocketAddrV4::new(ip, 0));

        net::UdpSocket::bind(sockaddr)
    };

    let socket = unsafe {
        match channel_type {
            Layer4(Ipv4(IpNextHeaderProtocol(proto))) | Layer3(IpNextHeaderProtocol(proto)) =>
                sockets::socket(sockets::AF_INET, sockets::SOCK_RAW, proto as libc::c_int),
            Layer4(Ipv6(IpNextHeaderProtocol(proto))) =>
                sockets::socket(sockets::AF_INET6, sockets::SOCK_RAW, proto as libc::c_int),
        }
    };
    if socket == sockets::INVALID_SOCKET {
        return Err(Error::last_os_error());
    }

    if match channel_type {
        Layer3(_) | Layer4(Ipv4(_)) => true,
        _ => false,
    } {
        let hincl: libc::c_int = match channel_type {
            Layer4(..) => 0,
            _ => 1,
        };
        let res = unsafe {
            sockets::setsockopt(socket,
                                sockets::IPPROTO_IP,
                                sockets::IP_HDRINCL,
                                (&hincl as *const libc::c_int) as sockets::Buf,
                                mem::size_of::<libc::c_int>() as sockets::SockLen)
        };
        if res == -1 {
            let err = Error::last_os_error();
            unsafe {
                sockets::close(socket);
            }
            return Err(err);
        }
    }

    let sock = Arc::new(internal::FileDesc { fd: socket });
    let sender = TransportSender {
        socket: sock.clone(),
        _channel_type: channel_type,
    };
    let receiver = TransportReceiver {
        socket: sock,
        buffer: repeat(0u8).take(buffer_size).collect(),
        channel_type: channel_type,
    };

    Ok((sender, receiver))
}

impl TransportSender {
    fn send<T: Packet>(&mut self, packet: T, dst: IpAddr) -> io::Result<usize> {
        let mut caddr = unsafe { mem::zeroed() };
        let sockaddr = match dst {
            IpAddr::V4(ip_addr) => net::SocketAddr::V4(net::SocketAddrV4::new(ip_addr, 0)),
            IpAddr::V6(ip_addr) => net::SocketAddr::V6(net::SocketAddrV6::new(ip_addr, 0, 0, 0)),
        };
        let slen = internal::addr_to_sockaddr(sockaddr, &mut caddr);
        let caddr_ptr = (&caddr as *const sockets::SockAddrStorage) as *const sockets::SockAddr;

        internal::send_to(self.socket.fd, packet.packet(), caddr_ptr, slen)
    }

    /// Send a packet to the provided desination
    #[inline]
    pub fn send_to<T: Packet>(&mut self, packet: T, destination: IpAddr) -> io::Result<usize> {
        self.send_to_impl(packet, destination)
    }

    #[cfg(all(not(target_os = "freebsd"), not(target_os = "macos")))]
    fn send_to_impl<T: Packet>(&mut self, packet: T, dst: IpAddr) -> io::Result<usize> {
        self.send(packet, dst)
    }

    #[cfg(any(target_os = "freebsd", target_os = "macos"))]
    fn send_to_impl<T: Packet>(&mut self, packet: T, dst: IpAddr) -> io::Result<usize> {
        use packet::MutablePacket;
        use packet::ipv4::MutableIpv4Packet;

        // FreeBSD and OS X expect total length and fragment offset fields of IPv4
        // packets to be in host byte order rather than network byte order. Fragment offset is the
        // ip_off field in the ip struct and contains both the offset and the three flag bits.
        // See `man 4 ip`/Raw IP Sockets)
        if let Layer3(_) = self._channel_type {
            let mut mut_slice: Vec<u8> = repeat(0u8).take(packet.packet().len()).collect();

            let mut new_packet = MutableIpv4Packet::new(&mut mut_slice[..]).unwrap();
            new_packet.clone_from(&packet);
            let length = new_packet.get_total_length().to_be();
            new_packet.set_total_length(length);
            {
                // Turn fragment offset into host order
                let d = new_packet.packet_mut();
                let host_order = u16::from_be((d[6] as u16) << 8 | d[7] as u16);
                d[6] = (host_order >> 8) as u8;
                d[7] = host_order as u8;
            }
            return self.send(new_packet, dst);
        }

        self.send(packet, dst)
    }
}

/// Create an iterator for some packet type.
///
/// Usage:
/// ```
/// transport_channel_iterator!(Ipv4Packet, // Type to iterate over
///                             Ipv4TransportChannelIterator, // Name for iterator struct
///                             ipv4_packet_iter) // Name of function to create iterator
/// ```
#[macro_export]
macro_rules! transport_channel_iterator {
    ($ty:ident, $iter:ident, $func:ident) => (
        /// An iterator over packets of type $ty
        pub struct $iter<'a> {
            tr: &'a mut TransportReceiver
        }
        /// Return a packet iterator with packets of type $ty for some transport receiver
        pub fn $func(tr: &mut TransportReceiver) -> $iter {
            $iter {
                tr: tr
            }
        }
        impl<'a> $iter<'a> {
            /// Get the next ($ty, IpAddr) pair for the given channel
            pub fn next(&mut self) -> io::Result<($ty, IpAddr)> {
                let mut caddr: sockets::SockAddrStorage = unsafe { mem::zeroed() };
                let res = internal::recv_from(self.tr.socket.fd,
                                              &mut self.tr.buffer[..],
                                              &mut caddr);

                let offset = match self.tr.channel_type {
                    Layer4(Ipv4(_)) => {
                        let ip_header = Ipv4Packet::new(&self.tr.buffer[..]).unwrap();

                        ip_header.get_header_length() as usize * 4usize
                    },
                    Layer3(_) => {
                        fixup_packet(&mut self.tr.buffer[..]);

                        0
                    },
                    _ => 0
                };
                return match res {
                    Ok(len) => {
                        let packet = $ty::new(&self.tr.buffer[offset..len]).unwrap();
                        let addr = internal::sockaddr_to_addr(
                                        &caddr,
                                        mem::size_of::<sockets::SockAddrStorage>()
                                   );
                        let ip = match addr.unwrap() {
                            net::SocketAddr::V4(sa) => IpAddr::V4(*sa.ip()),
                            net::SocketAddr::V6(sa) => IpAddr::V6(*sa.ip()),
                        };
                        Ok((packet, ip))
                    },
                    Err(e) => Err(e),
                };

                #[cfg(any(target_os = "freebsd", target_os = "macos"))]
                fn fixup_packet(buffer: &mut [u8]) {
                    use packet::ipv4::MutableIpv4Packet;

                    let buflen = buffer.len();
                    let mut new_packet = MutableIpv4Packet::new(buffer).unwrap();

                    let length = u16::from_be(new_packet.get_total_length());
                    new_packet.set_total_length(length);

                    // OS X does this awesome thing where it removes the header length
                    // from the total length sometimes.
                    let length = new_packet.get_total_length() as usize +
                                 (new_packet.get_header_length() as usize * 4usize);
                    if length == buflen {
                        new_packet.set_total_length(length as u16)
                    }

                    let offset = u16::from_be(new_packet.get_fragment_offset());
                    new_packet.set_fragment_offset(offset);
                }

                #[cfg(all(not(target_os = "freebsd"), not(target_os = "macos")))]
                fn fixup_packet(_buffer: &mut [u8]) {}
            }
        }
    )
}

transport_channel_iterator!(Ipv4Packet,
                            Ipv4TransportChannelIterator,
                            ipv4_packet_iter);

transport_channel_iterator!(UdpPacket,
                            UdpTransportChannelIterator,
                            udp_packet_iter);
<commit_msg>Auto merge of #210 - mohamedhayibor:master, r=mrmonday<commit_after>// Copyright (c) 2014, 2015 Robert Clipsham <robert@octarineparrot.com>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Support for implementing transport layer protocols
//!
//! The transport module provides the ability to send and receive packets at
//! the transport layer using IPv4 or IPv6. It also enables layer 3 networking
//! for specific transport protocols, using IPv4 only.
//!
//! Note that this is limited by operating system support - for example, on OS
//! X and FreeBSD, it is impossible to implement protocols which are already
//! implemented in the kernel such as TCP and UDP.

#![macro_use]

extern crate libc;

use self::TransportProtocol::{Ipv4, Ipv6};
use self::TransportChannelType::{Layer3, Layer4};

use std::io;
use std::io::Error;
use std::iter::repeat;
use std::net::{self, IpAddr};
use std::mem;
use std::sync::Arc;

use packet::Packet;
use packet::ip::IpNextHeaderProtocol;
use packet::ipv4::Ipv4Packet;
use packet::udp::UdpPacket;

use internal;
use sockets;

/// Represents a transport layer protocol
#[derive(Clone, Copy)]
pub enum TransportProtocol {
    /// Represents a transport protocol built on top of IPv4
    Ipv4(IpNextHeaderProtocol),
    /// Represents a transport protocol built on top of IPv6
    Ipv6(IpNextHeaderProtocol),
}

/// Type of transport channel to present
#[derive(Clone, Copy)]
pub enum TransportChannelType {
    /// The application will send and receive transport layer packets
    Layer4(TransportProtocol),
    /// The application will send and receive IPv4 packets, with the specified transport protocol
    Layer3(IpNextHeaderProtocol),
}

/// Structure used for sending at the transport layer. Should be created with transport_channel()
pub struct TransportSender {
    socket: Arc<internal::FileDesc>,
    _channel_type: TransportChannelType,
}

/// Structure used for receiving at the transport layer. Should be created with transport_channel()
pub struct TransportReceiver {
    socket: Arc<internal::FileDesc>,
    buffer: Vec<u8>,
    channel_type: TransportChannelType,
}

/// Create a new (TransportSender, TransportReceiver) pair
///
/// This allows for sending and receiving packets at the transport layer. The buffer size should be
/// large enough to handle the largest packet you wish to receive.
///
/// The channel type specifies what layer to send and receive packets at, and the transport
/// protocol you wish to implement. For example, `Layer4(Ipv4(IpNextHeaderProtocols::Udp))` would
/// allow sending and receiving UDP packets using IPv4; whereas Layer3(IpNextHeaderProtocols::Udp)
/// would include the IPv4 Header in received values, and require manual construction of an IP
/// header when sending.
pub fn transport_channel(buffer_size: usize,
                         channel_type: TransportChannelType)
    -> io::Result<(TransportSender, TransportReceiver)> {
    use std::net;

    // This hack makes sure that winsock is initialised
    let _ = {
        let ip = net::Ipv4Addr::new(255, 255, 255, 255);
        let sockaddr = net::SocketAddr::V4(net::SocketAddrV4::new(ip, 0));

        net::UdpSocket::bind(sockaddr)
    };

    let socket = unsafe {
        match channel_type {
            Layer4(Ipv4(IpNextHeaderProtocol(proto))) | Layer3(IpNextHeaderProtocol(proto)) =>
                sockets::socket(sockets::AF_INET, sockets::SOCK_RAW, proto as libc::c_int),
            Layer4(Ipv6(IpNextHeaderProtocol(proto))) =>
                sockets::socket(sockets::AF_INET6, sockets::SOCK_RAW, proto as libc::c_int),
        }
    };
    if socket == sockets::INVALID_SOCKET {
        return Err(Error::last_os_error());
    }

    if match channel_type {
        Layer3(_) | Layer4(Ipv4(_)) => true,
        _ => false,
    } {
        let hincl: libc::c_int = match channel_type {
            Layer4(..) => 0,
            _ => 1,
        };
        let res = unsafe {
            sockets::setsockopt(socket,
                                sockets::IPPROTO_IP,
                                sockets::IP_HDRINCL,
                                (&hincl as *const libc::c_int) as sockets::Buf,
                                mem::size_of::<libc::c_int>() as sockets::SockLen)
        };
        if res == -1 {
            let err = Error::last_os_error();
            unsafe {
                sockets::close(socket);
            }
            return Err(err);
        }
    }

    let sock = Arc::new(internal::FileDesc { fd: socket });
    let sender = TransportSender {
        socket: sock.clone(),
        _channel_type: channel_type,
    };
    let receiver = TransportReceiver {
        socket: sock,
        buffer: repeat(0u8).take(buffer_size).collect(),
        channel_type: channel_type,
    };

    Ok((sender, receiver))
}

impl TransportSender {
    fn send<T: Packet>(&mut self, packet: T, dst: IpAddr) -> io::Result<usize> {
        let mut caddr = unsafe { mem::zeroed() };
        let sockaddr = match dst {
            IpAddr::V4(ip_addr) => net::SocketAddr::V4(net::SocketAddrV4::new(ip_addr, 0)),
            IpAddr::V6(ip_addr) => net::SocketAddr::V6(net::SocketAddrV6::new(ip_addr, 0, 0, 0)),
        };
        let slen = internal::addr_to_sockaddr(sockaddr, &mut caddr);
        let caddr_ptr = (&caddr as *const sockets::SockAddrStorage) as *const sockets::SockAddr;

        internal::send_to(self.socket.fd, packet.packet(), caddr_ptr, slen)
    }

    /// Send a packet to the provided destination
    #[inline]
    pub fn send_to<T: Packet>(&mut self, packet: T, destination: IpAddr) -> io::Result<usize> {
        self.send_to_impl(packet, destination)
    }

    #[cfg(all(not(target_os = "freebsd"), not(target_os = "macos")))]
    fn send_to_impl<T: Packet>(&mut self, packet: T, dst: IpAddr) -> io::Result<usize> {
        self.send(packet, dst)
    }

    #[cfg(any(target_os = "freebsd", target_os = "macos"))]
    fn send_to_impl<T: Packet>(&mut self, packet: T, dst: IpAddr) -> io::Result<usize> {
        use packet::MutablePacket;
        use packet::ipv4::MutableIpv4Packet;

        // FreeBSD and OS X expect total length and fragment offset fields of IPv4
        // packets to be in host byte order rather than network byte order. Fragment offset is the
        // ip_off field in the ip struct and contains both the offset and the three flag bits.
        // See `man 4 ip`/Raw IP Sockets)
        if let Layer3(_) = self._channel_type {
            let mut mut_slice: Vec<u8> = repeat(0u8).take(packet.packet().len()).collect();

            let mut new_packet = MutableIpv4Packet::new(&mut mut_slice[..]).unwrap();
            new_packet.clone_from(&packet);
            let length = new_packet.get_total_length().to_be();
            new_packet.set_total_length(length);
            {
                // Turn fragment offset into host order
                let d = new_packet.packet_mut();
                let host_order = u16::from_be((d[6] as u16) << 8 | d[7] as u16);
                d[6] = (host_order >> 8) as u8;
                d[7] = host_order as u8;
            }
            return self.send(new_packet, dst);
        }

        self.send(packet, dst)
    }
}

/// Create an iterator for some packet type.
///
/// Usage:
/// ```
/// transport_channel_iterator!(Ipv4Packet, // Type to iterate over
///                             Ipv4TransportChannelIterator, // Name for iterator struct
///                             ipv4_packet_iter) // Name of function to create iterator
/// ```
#[macro_export]
macro_rules! transport_channel_iterator {
    ($ty:ident, $iter:ident, $func:ident) => (
        /// An iterator over packets of type $ty
        pub struct $iter<'a> {
            tr: &'a mut TransportReceiver
        }
        /// Return a packet iterator with packets of type $ty for some transport receiver
        pub fn $func(tr: &mut TransportReceiver) -> $iter {
            $iter {
                tr: tr
            }
        }
        impl<'a> $iter<'a> {
            /// Get the next ($ty, IpAddr) pair for the given channel
            pub fn next(&mut self) -> io::Result<($ty, IpAddr)> {
                let mut caddr: sockets::SockAddrStorage = unsafe { mem::zeroed() };
                let res = internal::recv_from(self.tr.socket.fd,
                                              &mut self.tr.buffer[..],
                                              &mut caddr);

                let offset = match self.tr.channel_type {
                    Layer4(Ipv4(_)) => {
                        let ip_header = Ipv4Packet::new(&self.tr.buffer[..]).unwrap();

                        ip_header.get_header_length() as usize * 4usize
                    },
                    Layer3(_) => {
                        fixup_packet(&mut self.tr.buffer[..]);

                        0
                    },
                    _ => 0
                };
                return match res {
                    Ok(len) => {
                        let packet = $ty::new(&self.tr.buffer[offset..len]).unwrap();
                        let addr = internal::sockaddr_to_addr(
                                        &caddr,
                                        mem::size_of::<sockets::SockAddrStorage>()
                                   );
                        let ip = match addr.unwrap() {
                            net::SocketAddr::V4(sa) => IpAddr::V4(*sa.ip()),
                            net::SocketAddr::V6(sa) => IpAddr::V6(*sa.ip()),
                        };
                        Ok((packet, ip))
                    },
                    Err(e) => Err(e),
                };

                #[cfg(any(target_os = "freebsd", target_os = "macos"))]
                fn fixup_packet(buffer: &mut [u8]) {
                    use packet::ipv4::MutableIpv4Packet;

                    let buflen = buffer.len();
                    let mut new_packet = MutableIpv4Packet::new(buffer).unwrap();

                    let length = u16::from_be(new_packet.get_total_length());
                    new_packet.set_total_length(length);

                    // OS X does this awesome thing where it removes the header length
                    // from the total length sometimes.
                    let length = new_packet.get_total_length() as usize +
                                 (new_packet.get_header_length() as usize * 4usize);
                    if length == buflen {
                        new_packet.set_total_length(length as u16)
                    }

                    let offset = u16::from_be(new_packet.get_fragment_offset());
                    new_packet.set_fragment_offset(offset);
                }

                #[cfg(all(not(target_os = "freebsd"), not(target_os = "macos")))]
                fn fixup_packet(_buffer: &mut [u8]) {}
            }
        }
    )
}

transport_channel_iterator!(Ipv4Packet,
                            Ipv4TransportChannelIterator,
                            ipv4_packet_iter);

transport_channel_iterator!(UdpPacket,
                            UdpTransportChannelIterator,
                            udp_packet_iter);
<|endoftext|>


<commit_before>// Copied from rustc. isatty crate did not work as expected
#[cfg(unix)]
pub fn stderr_isatty() -> bool {
    isatty(libc::STDERR_FILENO)
}

// FIXME: Unfortunately this doesn't detect msys terminals so rustup
// is always colorless there (just like rustc and cargo).
#[cfg(windows)]
pub fn stderr_isatty() -> bool {
    isatty(winapi::um::winbase::STD_ERROR_HANDLE)
}

#[cfg(unix)]
pub fn stdout_isatty() -> bool {
    isatty(libc::STDOUT_FILENO)
}

#[cfg(windows)]
pub fn stdout_isatty() -> bool {
    isatty(winapi::um::winbase::STD_OUTPUT_HANDLE)
}

#[inline]
#[cfg(unix)]
fn isatty(fd: libc::c_int) -> bool {
    unsafe { libc::isatty(fd) == 1 }
}

#[inline]
#[cfg(windows)]
fn isatty(fd: winapi::shared::minwindef::DWORD) -> bool {
    use winapi::um::{consoleapi::GetConsoleMode, processenv::GetStdHandle};
    unsafe {
        let handle = GetStdHandle(fd);
        let mut out = 0;
        GetConsoleMode(handle, &mut out) != 0
    }
}
<commit_msg>Disable broken color in MSYS2 shells.<commit_after>// Copied from rustc. isatty crate did not work as expected
#[cfg(unix)]
pub fn stderr_isatty() -> bool {
    isatty(libc::STDERR_FILENO)
}

#[cfg(windows)]
pub fn stderr_isatty() -> bool {
    isatty(winapi::um::winbase::STD_ERROR_HANDLE)
}

#[cfg(unix)]
pub fn stdout_isatty() -> bool {
    isatty(libc::STDOUT_FILENO)
}

#[cfg(windows)]
pub fn stdout_isatty() -> bool {
    isatty(winapi::um::winbase::STD_OUTPUT_HANDLE)
}

#[inline]
#[cfg(unix)]
fn isatty(fd: libc::c_int) -> bool {
    unsafe { libc::isatty(fd) == 1 }
}

#[inline]
#[cfg(windows)]
fn isatty(fd: winapi::shared::minwindef::DWORD) -> bool {
    if std::env::var("MSYSTEM").is_ok() {
        // FIXME: No color is better than broken color codes in MSYS shells
        //        https://github.com/rust-lang/rustup/issues/2292
        return false;
    }
    use winapi::um::{consoleapi::GetConsoleMode, processenv::GetStdHandle};
    unsafe {
        let handle = GetStdHandle(fd);
        let mut out = 0;
        GetConsoleMode(handle, &mut out) != 0
    }
}
<|endoftext|>


<commit_before>use std::collections::VecDeque;
use std::io::Write;
use std::mem::zeroed;
use std::os::raw::{c_int, c_uchar, c_uint};
use std::rc::Rc;

use atoms;
use client::{ClientW, Rect};
use config::Config;
use layout::Layout;
use util;

use x11::xlib;

pub enum FocusShift {
    Forward,
    Backward,
}

pub struct Workspace {
    anchor_window: xlib::Window,
    pub config: Rc<Config>,
    client_current: Option<ClientW>,
    clients_prev: VecDeque<ClientW>,
    clients_next: VecDeque<ClientW>,
    description: Option<String>,
    layout: Box<Layout + 'static>,
    pub tag: c_uchar,
}

impl Workspace {
    pub fn new(config: Rc<Config>,
               anchor_window: xlib::Window,
               tag: c_uchar,
               description: Option<String>,
               layout: Box<Layout + 'static>)
               -> Self {
        Workspace {
            anchor_window: anchor_window,
            client_current: None,
            clients_prev: VecDeque::new(),
            clients_next: VecDeque::new(),
            description: description,
            config: config,
            layout: layout,
            tag: tag,
        }
    }

    pub fn circle_focus(&mut self, direction: FocusShift) {
        match direction {
            FocusShift::Forward => {
                if self.clients_next.len() > 0 {
                    self.shift_focus(direction);
                } else {
                    while self.clients_prev.len() > 0 {
                        self.shift_focus(FocusShift::Backward);
                    }
                }
            }
            FocusShift::Backward => {
                if self.clients_prev.len() > 0 {
                    self.shift_focus(direction);
                } else {
                    while self.clients_next.len() > 0 {
                        self.shift_focus(FocusShift::Forward);
                    }
                }
            }
        }
        self.restack();
    }

    pub fn clear(&mut self) {
        self.clients_prev.clear();
        self.clients_next.clear();
        self.client_current = None;
    }

    pub fn detach_current(&mut self) -> Option<ClientW> {
        if self.client_current.is_some() {
            if self.clients_next.len() > 0 {
                self.shift_focus(FocusShift::Forward);
                return self.clients_prev.pop_back();
            } else if self.clients_prev.len() > 0 {
                self.shift_focus(FocusShift::Backward);
                return self.clients_next.pop_front();
            }
        }
        self.client_current.take()
    }

    pub fn get_client_by_window(&self, window: xlib::Window) -> Option<ClientW> {
        if let Some(c) = self.client_current.as_ref() {
            if c.window() == window {
                return Some(c.clone());
            }
        }
        self.clients_prev
            .iter()
            .find(|c| c.window() == window)
            .map(|c| c.clone())
            .or_else(|| {
                self.clients_next
                    .iter()
                    .find(|c| c.window() == window)
                    .map(|c| c.clone())
            })
    }

    pub fn get_current_focused(&self) -> Option<ClientW> {
        self.client_current.as_ref().map(|c| c.clone())
    }

    pub fn get_description(&self) -> Option<&String> {
        self.description.as_ref()
    }

    pub fn get_layout(&self, rect: Rect) -> Vec<(ClientW, Rect)> {
        self.layout.layout(self, rect)
    }

    pub fn kill_client(&mut self) {
        self.detach_current().map(|client| {
            let atom = atoms::wm_delete();
            if !client.send_event(atom) {
                x_disable_error_unsafe!(client.display(), {
                    xlib::XSetCloseDownMode(client.display(), xlib::DestroyAll);
                    xlib::XKillClient(client.display(), client.borrow().window);
                });
            }
        });
    }

    pub fn new_client(&mut self, client: ClientW, at_focus: bool) {
        if !at_focus {
            self.clients_prev.push_front(client);
            while self.clients_prev.len() > 0 {
                self.shift_focus(FocusShift::Backward);
            }
        } else {
            self.clients_prev.push_back(client);
            self.shift_focus(FocusShift::Backward);
        }
    }

    fn push_next(&mut self, client: Option<ClientW>) {
        if let Some(c) = client {
            self.clients_next.push_front(c);
        }
    }

    fn push_prev(&mut self, client: Option<ClientW>) {
        if let Some(c) = client {
            self.clients_prev.push_back(c);
        }
    }

    pub fn remove_client(&mut self, client: ClientW) {
        if let Some(position) = self.clients_prev
            .iter()
            .position(|c| c.window() == client.window()) {
            self.clients_prev.remove(position);
            return;
        }
        if let Some(position) = self.clients_next
            .iter()
            .position(|c| c.window() == client.window()) {
            self.clients_next.remove(position);
            return;
        }
        let cmp = self.client_current.clone().map(|c| c.window() == client.window());
        if cmp.is_some() && cmp.unwrap() {
            self.detach_current();
        }
    }

    pub fn restack(&mut self) {
        if let Some(focus) = self.get_current_focused() {
            focus.focus(true);
            if focus.is_floating() {
                focus.raise_window();
                return;
            }

            unsafe {
                let mut wc: xlib::XWindowChanges = zeroed();
                wc.stack_mode = xlib::Below;
                wc.sibling = self.anchor_window;
                xlib::XConfigureWindow(focus.display(),
                                       focus.window(),
                                       xlib::CWSibling as c_uint | xlib::CWStackMode as c_uint,
                                       &mut wc);
                let mut xevent: xlib::XEvent = zeroed();
                xlib::XSync(focus.display(), 0);
                while xlib::XCheckMaskEvent(focus.display(),
                                            xlib::EnterWindowMask,
                                            &mut xevent) != 0 {}
            }
        }
    }

    pub fn set_description<T: Into<String>>(&mut self, description: T) {
        self.description = Some(description.into());
    }

    pub fn set_focus(&mut self, client: ClientW) {
        if let &mut Some(ref mut c) = &mut self.client_current {
            if c.window() == client.window() {
                // c.focus(true);
                c.grab_buttons(true);
                return;
            }
        }
        if let Some(position) = self.clients_prev
            .iter()
            .position(|c| c.window() == client.window()) {
            for _ in 0..self.clients_prev.len() - position {
                self.shift_focus(FocusShift::Backward);
            }
            return;
        }
        if let Some(position) = self.clients_next
            .iter()
            .position(|c| c.window() == client.window()) {
            for _ in 0..position + 1 {
                self.shift_focus(FocusShift::Forward);
            }
            return;
        }
    }

    pub fn select_clients(&self, pred: &Fn(&&ClientW) -> bool) -> Vec<ClientW> {
        let mut result: Vec<ClientW> =
            self.clients_prev.iter().filter(pred).map(|c| c.clone()).collect();
        if let &Some(ref c) = &self.client_current {
            if pred(&c) {
                result.push(c.clone());
            }
        }
        result.extend(self.clients_next
            .iter()
            .filter(pred)
            .map(|c| c.clone()));
        result
    }

    pub fn show(&mut self, visible: bool) {
        for c in &mut self.clients_prev {
            c.show(visible);
        }

        if let Some(c) = self.client_current.as_mut() {
            c.show(visible);
        }

        for c in &mut self.clients_next {
            c.show(visible);
        }
    }

    pub fn shift_focus(&mut self, shift: FocusShift) {
        match shift {
            FocusShift::Forward => {
                if let Some(mut next_client) = self.clients_next.pop_front() {
                    let current = self.client_current.take();
                    next_client.grab_buttons(true);
                    self.client_current = Some(next_client);
                    self.push_prev(current.map(|c| {
                        c.focus(false);
                        c.clone().grab_buttons(false);
                        c
                    }));
                }
            }
            FocusShift::Backward => {
                if let Some(mut prev_client) = self.clients_prev.pop_back() {
                    let current = self.client_current.take();
                    prev_client.grab_buttons(true);
                    self.client_current = Some(prev_client);
                    self.push_next(current.map(|c| {
                        c.focus(false);
                        c.clone().grab_buttons(false);
                        c
                    }));
                }
            }
        }
    }

    pub fn zoom(&mut self) {
        self.detach_current().map(|c| self.new_client(c, false));
    }
}
<commit_msg>not detaching current window upon killing.<commit_after>use std::collections::VecDeque;
use std::io::Write;
use std::mem::zeroed;
use std::os::raw::{c_int, c_uchar, c_uint};
use std::rc::Rc;

use atoms;
use client::{ClientW, Rect};
use config::Config;
use layout::Layout;
use util;

use x11::xlib;

pub enum FocusShift {
    Forward,
    Backward,
}

pub struct Workspace {
    anchor_window: xlib::Window,
    pub config: Rc<Config>,
    client_current: Option<ClientW>,
    clients_prev: VecDeque<ClientW>,
    clients_next: VecDeque<ClientW>,
    description: Option<String>,
    layout: Box<Layout + 'static>,
    pub tag: c_uchar,
}

impl Workspace {
    pub fn new(config: Rc<Config>,
               anchor_window: xlib::Window,
               tag: c_uchar,
               description: Option<String>,
               layout: Box<Layout + 'static>)
               -> Self {
        Workspace {
            anchor_window: anchor_window,
            client_current: None,
            clients_prev: VecDeque::new(),
            clients_next: VecDeque::new(),
            description: description,
            config: config,
            layout: layout,
            tag: tag,
        }
    }

    pub fn circle_focus(&mut self, direction: FocusShift) {
        match direction {
            FocusShift::Forward => {
                if self.clients_next.len() > 0 {
                    self.shift_focus(direction);
                } else {
                    while self.clients_prev.len() > 0 {
                        self.shift_focus(FocusShift::Backward);
                    }
                }
            }
            FocusShift::Backward => {
                if self.clients_prev.len() > 0 {
                    self.shift_focus(direction);
                } else {
                    while self.clients_next.len() > 0 {
                        self.shift_focus(FocusShift::Forward);
                    }
                }
            }
        }
        self.restack();
    }

    pub fn clear(&mut self) {
        self.clients_prev.clear();
        self.clients_next.clear();
        self.client_current = None;
    }

    pub fn detach_current(&mut self) -> Option<ClientW> {
        if self.client_current.is_some() {
            if self.clients_next.len() > 0 {
                self.shift_focus(FocusShift::Forward);
                return self.clients_prev.pop_back();
            } else if self.clients_prev.len() > 0 {
                self.shift_focus(FocusShift::Backward);
                return self.clients_next.pop_front();
            }
        }
        self.client_current.take()
    }

    pub fn get_client_by_window(&self, window: xlib::Window) -> Option<ClientW> {
        if let Some(c) = self.client_current.as_ref() {
            if c.window() == window {
                return Some(c.clone());
            }
        }
        self.clients_prev
            .iter()
            .find(|c| c.window() == window)
            .map(|c| c.clone())
            .or_else(|| {
                self.clients_next
                    .iter()
                    .find(|c| c.window() == window)
                    .map(|c| c.clone())
            })
    }

    pub fn get_current_focused(&self) -> Option<ClientW> {
        self.client_current.as_ref().map(|c| c.clone())
    }

    pub fn get_description(&self) -> Option<&String> {
        self.description.as_ref()
    }

    pub fn get_layout(&self, rect: Rect) -> Vec<(ClientW, Rect)> {
        self.layout.layout(self, rect)
    }

    pub fn kill_client(&mut self) {
        self.client_current.as_mut().map(|client| {
            let atom = atoms::wm_delete();
            if !client.send_event(atom) {
                x_disable_error_unsafe!(client.display(), {
                    xlib::XSetCloseDownMode(client.display(), xlib::DestroyAll);
                    xlib::XKillClient(client.display(), client.borrow().window);
                });
            }
        });
    }

    pub fn new_client(&mut self, client: ClientW, at_focus: bool) {
        if !at_focus {
            self.clients_prev.push_front(client);
            while self.clients_prev.len() > 0 {
                self.shift_focus(FocusShift::Backward);
            }
        } else {
            self.clients_prev.push_back(client);
            self.shift_focus(FocusShift::Backward);
        }
    }

    fn push_next(&mut self, client: Option<ClientW>) {
        if let Some(c) = client {
            self.clients_next.push_front(c);
        }
    }

    fn push_prev(&mut self, client: Option<ClientW>) {
        if let Some(c) = client {
            self.clients_prev.push_back(c);
        }
    }

    pub fn remove_client(&mut self, client: ClientW) {
        if let Some(position) = self.clients_prev
            .iter()
            .position(|c| c.window() == client.window()) {
            self.clients_prev.remove(position);
            return;
        }
        if let Some(position) = self.clients_next
            .iter()
            .position(|c| c.window() == client.window()) {
            self.clients_next.remove(position);
            return;
        }
        let cmp = self.client_current.clone().map(|c| c.window() == client.window());
        if cmp.is_some() && cmp.unwrap() {
            self.detach_current();
        }
    }

    pub fn restack(&mut self) {
        if let Some(focus) = self.get_current_focused() {
            focus.focus(true);
            if focus.is_floating() {
                focus.raise_window();
                return;
            }

            unsafe {
                let mut wc: xlib::XWindowChanges = zeroed();
                wc.stack_mode = xlib::Below;
                wc.sibling = self.anchor_window;
                xlib::XConfigureWindow(focus.display(),
                                       focus.window(),
                                       xlib::CWSibling as c_uint | xlib::CWStackMode as c_uint,
                                       &mut wc);
                let mut xevent: xlib::XEvent = zeroed();
                xlib::XSync(focus.display(), 0);
                while xlib::XCheckMaskEvent(focus.display(),
                                            xlib::EnterWindowMask,
                                            &mut xevent) != 0 {}
            }
        }
    }

    pub fn set_description<T: Into<String>>(&mut self, description: T) {
        self.description = Some(description.into());
    }

    pub fn set_focus(&mut self, client: ClientW) {
        if let &mut Some(ref mut c) = &mut self.client_current {
            if c.window() == client.window() {
                // c.focus(true);
                c.grab_buttons(true);
                return;
            }
        }
        if let Some(position) = self.clients_prev
            .iter()
            .position(|c| c.window() == client.window()) {
            for _ in 0..self.clients_prev.len() - position {
                self.shift_focus(FocusShift::Backward);
            }
            return;
        }
        if let Some(position) = self.clients_next
            .iter()
            .position(|c| c.window() == client.window()) {
            for _ in 0..position + 1 {
                self.shift_focus(FocusShift::Forward);
            }
            return;
        }
    }

    pub fn select_clients(&self, pred: &Fn(&&ClientW) -> bool) -> Vec<ClientW> {
        let mut result: Vec<ClientW> =
            self.clients_prev.iter().filter(pred).map(|c| c.clone()).collect();
        if let &Some(ref c) = &self.client_current {
            if pred(&c) {
                result.push(c.clone());
            }
        }
        result.extend(self.clients_next
            .iter()
            .filter(pred)
            .map(|c| c.clone()));
        result
    }

    pub fn show(&mut self, visible: bool) {
        for c in &mut self.clients_prev {
            c.show(visible);
        }

        if let Some(c) = self.client_current.as_mut() {
            c.show(visible);
        }

        for c in &mut self.clients_next {
            c.show(visible);
        }
    }

    pub fn shift_focus(&mut self, shift: FocusShift) {
        match shift {
            FocusShift::Forward => {
                if let Some(mut next_client) = self.clients_next.pop_front() {
                    let current = self.client_current.take();
                    next_client.grab_buttons(true);
                    self.client_current = Some(next_client);
                    self.push_prev(current.map(|c| {
                        c.focus(false);
                        c.clone().grab_buttons(false);
                        c
                    }));
                }
            }
            FocusShift::Backward => {
                if let Some(mut prev_client) = self.clients_prev.pop_back() {
                    let current = self.client_current.take();
                    prev_client.grab_buttons(true);
                    self.client_current = Some(prev_client);
                    self.push_next(current.map(|c| {
                        c.focus(false);
                        c.clone().grab_buttons(false);
                        c
                    }));
                }
            }
        }
    }

    pub fn zoom(&mut self) {
        self.detach_current().map(|c| self.new_client(c, false));
    }
}
<|endoftext|>


<commit_before>use super::component::log::StatusLog;
use super::{Series, SeriesStatus, StatusBarState, UIState, WatchState};
use crate::err::{self, Result};
use crate::util;
use anime::remote::ScoreParser;
use chrono::{Duration, Utc};
use smallvec::SmallVec;
use snafu::ResultExt;
use std::borrow::Cow;
use std::io;
use std::sync::mpsc;
use std::thread;
use termion::event::Key;
use termion::input::TermRead;
use termion::raw::{IntoRawMode, RawTerminal};
use tui::backend::{self, Backend};
use tui::layout::{Alignment, Constraint, Direction, Layout, Rect};
use tui::style::{Color, Modifier, Style};
use tui::terminal::{Frame, Terminal};
use tui::widgets::{Block, Borders, Paragraph, SelectableList, Text, Widget};

pub struct UI<'a, B>
where
    B: Backend,
{
    terminal: Terminal<B>,
    pub status_log: StatusLog<'a>,
    status_log_rect: Rect,
    cursor_visible: bool,
}

macro_rules! create_stat_list {
    ($($header:expr => $value:expr),+) => {
        [$(
            create_stat_list!(h $header),
            create_stat_list!(v $value, $header.len()),
        )+]
    };

    (h $header:expr) => {
        Text::styled(concat!($header, "\n"), Style::default().modifier(Modifier::BOLD))
    };

    (v $value:expr, $len:expr) => {
        Text::styled(format!("{:^width$}\n\n", $value, width = $len), Style::default().modifier(Modifier::ITALIC))
    };
}

impl<'a, B> UI<'a, B>
where
    B: Backend,
{
    pub fn clear(&mut self) -> Result<()> {
        self.terminal.clear().context(err::IO)
    }

    pub fn draw<S>(&mut self, state: &UIState, score_parser: &S) -> Result<()>
    where
        S: ScoreParser + ?Sized,
    {
        let status_log = &mut self.status_log;
        let status_log_rect = &mut self.status_log_rect;

        self.terminal
            .draw(|mut frame| {
                // Top panels for series information
                let horiz_splitter = Layout::default()
                    .direction(Direction::Horizontal)
                    .constraints([Constraint::Min(20), Constraint::Percentage(70)].as_ref())
                    .split(frame.size());

                UI::draw_top_panels(state, &horiz_splitter, &mut frame);

                // Series info panel vertical splitter
                let info_panel_splitter = Layout::default()
                    .direction(Direction::Vertical)
                    .constraints([Constraint::Percentage(80), Constraint::Percentage(20)].as_ref())
                    .split(horiz_splitter[1]);

                UI::draw_info_panel(state, score_parser, &info_panel_splitter, &mut frame);

                *status_log_rect = info_panel_splitter[1];
                UI::draw_status_bar(state, status_log, *status_log_rect, &mut frame);
            })
            .context(err::IO)
    }

    pub fn adjust_cursor(&mut self, state: &UIState) -> Result<()> {
        use std::io::Write;

        const BORDER_SIZE: u16 = 2;

        match &state.status_bar_state {
            // We want to position the cursor inside of the command prompt, but only if we can fit it
            StatusBarState::CommandPrompt(prompt)
                if self.status_log_rect.height > BORDER_SIZE
                    && self.status_log_rect.width > BORDER_SIZE =>
            {
                if !self.cursor_visible {
                    self.terminal.show_cursor().context(err::IO)?;
                    self.cursor_visible = true;
                }

                let input_width = prompt.width() as u16;
                let prompt_width = self.status_log_rect.width.saturating_sub(BORDER_SIZE);

                let (len, line_num) = if prompt_width > 0 {
                    let line_num = input_width / prompt_width;
                    let max_line = self.status_log_rect.height - BORDER_SIZE - 1;

                    // We want to cap the position of the cursor to the last character of the last line
                    if line_num > max_line {
                        (prompt_width - 1, max_line)
                    } else {
                        (input_width % prompt_width, line_num)
                    }
                } else {
                    (input_width, 0)
                };

                let x = 1 + self.status_log_rect.left() + len;
                let y = 1 + self.status_log_rect.top() + line_num;

                self.terminal.set_cursor(x, y).context(err::IO)?;

                // Since stdout is buffered, we want to flush it immediately so the cursor follows certain inputs like spaces
                io::stdout().flush().ok();
            }
            StatusBarState::CommandPrompt(_) | StatusBarState::Log if self.cursor_visible => {
                self.terminal.hide_cursor().context(err::IO)?;
                self.cursor_visible = false;
            }
            StatusBarState::CommandPrompt(_) => (),
            StatusBarState::Log => (),
        }

        Ok(())
    }

    fn draw_top_panels(state: &UIState, layout: &[Rect], frame: &mut Frame<B>) {
        let series_names = state
            .series
            .iter()
            .map(|series| series.nickname())
            .collect::<SmallVec<[_; 8]>>();

        let mut series_list = SelectableList::default()
            .block(Block::default().title("Series").borders(Borders::ALL))
            .items(&series_names)
            .select(Some(state.selected_series))
            .style(Style::default().fg(Color::White))
            .highlight_style(Style::default().fg(Color::Green).modifier(Modifier::ITALIC))
            .highlight_symbol(">");

        series_list.render(frame, layout[0]);
    }

    fn draw_info_panel<S>(state: &UIState, score_parser: &S, layout: &[Rect], frame: &mut Frame<B>)
    where
        S: ScoreParser + ?Sized,
    {
        Block::default()
            .title("Info")
            .borders(Borders::ALL)
            .render(frame, layout[0]);

        let info_layout = Layout::default()
            .direction(Direction::Vertical)
            .constraints(
                [
                    Constraint::Length(4),
                    Constraint::Percentage(70),
                    Constraint::Length(4),
                ]
                .as_ref(),
            )
            .margin(2)
            .split(layout[0]);

        match state.cur_series_status() {
            Some(SeriesStatus::Valid(series)) => {
                UI::draw_series_info(state, series, score_parser, &info_layout, frame)
            }
            Some(SeriesStatus::Invalid(_, reason)) => {
                UI::draw_invalid_series(reason, &info_layout, frame)
            }
            Some(SeriesStatus::Unloaded(_)) => (),
            None => {
                let header =
                    Text::styled("No Series Found", Style::default().modifier(Modifier::BOLD));

                Paragraph::new([header].iter())
                    .alignment(Alignment::Center)
                    .render(frame, info_layout[0]);

                let body = Text::raw(
                    "Add one by launching the program with a \
                    \nkeyword similar to the series name on disk. \

                    \n\nFor example: assuming the path \"~/anime/Jigoku Shoujo\" \
                    \nexists, launching the program with the keyword 'shoujo' \
                    \nwill add it to the series list. \

                    \n\nYou can also use a non-similar keyword along with \
                    \nthe -p flag to manually specify the path to the series. \

                    \n\nNote that by default, the program will look for a series \
                    \nin \"~/anime/\". You can change this in the config file.",
                );

                Paragraph::new([body].iter())
                    .alignment(Alignment::Center)
                    .wrap(true)
                    .render(frame, info_layout[1]);
            }
        }
    }

    fn draw_series_info<S>(
        state: &UIState,
        series: &Series,
        score_parser: &S,
        info_layout: &[Rect],
        frame: &mut Frame<B>,
    ) where
        S: ScoreParser + ?Sized,
    {
        let info = &series.info;
        let entry = &series.entry;

        // Series title
        {
            let text_items = {
                let mut items = SmallVec::<[_; 2]>::new();

                items.push(Text::styled(
                    &info.title.preferred,
                    Style::default().modifier(Modifier::BOLD),
                ));

                if entry.needs_sync() {
                    items.push(Text::styled(
                        " [*]",
                        Style::default().modifier(Modifier::ITALIC),
                    ));
                }

                items
            };

            Paragraph::new(text_items.iter())
                .alignment(Alignment::Center)
                .render(frame, info_layout[0]);
        }

        // Items in panel
        let stat_layout = Layout::default()
            .direction(Direction::Horizontal)
            .constraints(
                [
                    Constraint::Percentage(33),
                    Constraint::Percentage(33),
                    Constraint::Percentage(33),
                ]
                .as_ref(),
            )
            .split(info_layout[1]);

        {
            let left_items = create_stat_list!(
                "Watch Time" => {
                    let watch_time_mins = info.episodes * info.episode_length;
                    util::hm_from_mins(watch_time_mins as f32)
                },
                "Time Left" => {
                    let eps_left = info.episodes - entry.watched_eps().min(info.episodes);
                    let time_left_mins = eps_left * info.episode_length;

                    util::hm_from_mins(time_left_mins as f32)
                },
                "Episode Length" => format!("{}M", info.episode_length)
            );

            Paragraph::new(left_items.iter())
                .alignment(Alignment::Center)
                .render(frame, stat_layout[0]);
        }

        {
            let center_items = create_stat_list!(
                "Progress" => format!("{}|{}", entry.watched_eps(), info.episodes),
                "Score" => match entry.score() {
                    Some(score) => score_parser.score_to_str(score),
                    None => "??".into(),
                },
                "Status" => entry.status()
            );

            Paragraph::new(center_items.iter())
                .alignment(Alignment::Center)
                .render(frame, stat_layout[1]);
        }

        {
            let right_items = create_stat_list!(
                "Start Date" => match entry.start_date() {
                    Some(date) => format!("{}", date.format("%D")).into(),
                    None => Cow::Borrowed("??"),
                },
                "Finish Date" => match entry.end_date() {
                    Some(date) => format!("{}", date.format("%D")).into(),
                    None => Cow::Borrowed("??"),
                },
                "Rewatched" => entry.times_rewatched()
            );

            Paragraph::new(right_items.iter())
                .alignment(Alignment::Center)
                .render(frame, stat_layout[2]);
        }

        // Watch time needed indicator at bottom
        match state.watch_state {
            WatchState::Idle => (),
            WatchState::Watching(progress_time, _) => {
                let watch_time = progress_time - Utc::now();
                let watch_secs = watch_time.num_seconds();

                if watch_secs > 0 {
                    let remaining_mins = watch_secs as f32 / 60.0;

                    let text_str = format!(
                        "{} Remaining Until Progression",
                        util::ms_from_mins(remaining_mins)
                    );

                    let text = Text::styled(text_str, Style::default().modifier(Modifier::BOLD));

                    Paragraph::new([text].iter())
                        .alignment(Alignment::Center)
                        .render(frame, info_layout[2]);
                }
            }
        }
    }

    fn draw_invalid_series(reason: &str, layout: &[Rect], frame: &mut Frame<B>) {
        let header = Text::styled(
            "Error Processing Series",
            Style::default().modifier(Modifier::BOLD),
        );

        Paragraph::new([header].iter())
            .alignment(Alignment::Center)
            .render(frame, layout[0]);

        let body = Text::styled(reason, Style::default().fg(Color::Red));

        Paragraph::new([body].iter())
            .alignment(Alignment::Center)
            .wrap(true)
            .render(frame, layout[1]);
    }

    fn draw_status_bar(state: &UIState, log: &mut StatusLog, layout: Rect, frame: &mut Frame<B>) {
        match &state.status_bar_state {
            StatusBarState::Log => {
                log.adjust_to_size(layout, true);

                Paragraph::new(log.draw_items_iter())
                    .block(
                        Block::default()
                            .title("Status [':' for command entry]")
                            .borders(Borders::ALL),
                    )
                    .wrap(true)
                    .render(frame, layout);
            }
            StatusBarState::CommandPrompt(prompt) => {
                Paragraph::new(prompt.draw_items().iter())
                    .block(
                        Block::default()
                            .title("Enter Command")
                            .borders(Borders::ALL),
                    )
                    .wrap(true)
                    .render(frame, layout);
            }
        }
    }
}

pub type TermionBackend = backend::TermionBackend<RawTerminal<io::Stdout>>;

impl<'a> UI<'a, TermionBackend> {
    pub fn init() -> Result<UI<'a, TermionBackend>> {
        let stdout = io::stdout().into_raw_mode().context(err::IO)?;
        let backend = TermionBackend::new(stdout);
        let mut terminal = Terminal::new(backend).context(err::IO)?;

        terminal.clear().context(err::IO)?;
        terminal.hide_cursor().context(err::IO)?;

        Ok(UI {
            terminal,
            status_log_rect: Rect::default(),
            status_log: StatusLog::new(),
            cursor_visible: false,
        })
    }
}

pub enum Event {
    Input(Key),
    Tick,
}

pub struct Events(mpsc::Receiver<Event>);

impl Events {
    pub fn new(tick_rate: Duration) -> Events {
        let (tx, rx) = mpsc::channel();

        Events::spawn_key_handler(tx.clone());
        Events::spawn_tick_handler(tick_rate, tx);

        Events(rx)
    }

    fn spawn_key_handler(tx: mpsc::Sender<Event>) -> thread::JoinHandle<()> {
        let stdin = io::stdin();

        thread::spawn(move || {
            for event in stdin.keys() {
                if let Ok(key) = event {
                    tx.send(Event::Input(key)).unwrap();
                }
            }
        })
    }

    fn spawn_tick_handler(tick_rate: Duration, tx: mpsc::Sender<Event>) -> thread::JoinHandle<()> {
        let tick_rate = tick_rate
            .to_std()
            .unwrap_or_else(|_| std::time::Duration::from_secs(1));

        thread::spawn(move || loop {
            thread::sleep(tick_rate);
            tx.send(Event::Tick).unwrap();
        })
    }

    pub fn next(&self) -> Result<Event> {
        self.0.recv().context(err::MPSCRecv)
    }
}
<commit_msg>anup: update help text that displays when no series are present<commit_after>use super::component::log::StatusLog;
use super::{Series, SeriesStatus, StatusBarState, UIState, WatchState};
use crate::err::{self, Result};
use crate::util;
use anime::remote::ScoreParser;
use chrono::{Duration, Utc};
use smallvec::SmallVec;
use snafu::ResultExt;
use std::borrow::Cow;
use std::io;
use std::sync::mpsc;
use std::thread;
use termion::event::Key;
use termion::input::TermRead;
use termion::raw::{IntoRawMode, RawTerminal};
use tui::backend::{self, Backend};
use tui::layout::{Alignment, Constraint, Direction, Layout, Rect};
use tui::style::{Color, Modifier, Style};
use tui::terminal::{Frame, Terminal};
use tui::widgets::{Block, Borders, Paragraph, SelectableList, Text, Widget};

pub struct UI<'a, B>
where
    B: Backend,
{
    terminal: Terminal<B>,
    pub status_log: StatusLog<'a>,
    status_log_rect: Rect,
    cursor_visible: bool,
}

macro_rules! create_stat_list {
    ($($header:expr => $value:expr),+) => {
        [$(
            create_stat_list!(h $header),
            create_stat_list!(v $value, $header.len()),
        )+]
    };

    (h $header:expr) => {
        Text::styled(concat!($header, "\n"), Style::default().modifier(Modifier::BOLD))
    };

    (v $value:expr, $len:expr) => {
        Text::styled(format!("{:^width$}\n\n", $value, width = $len), Style::default().modifier(Modifier::ITALIC))
    };
}

impl<'a, B> UI<'a, B>
where
    B: Backend,
{
    pub fn clear(&mut self) -> Result<()> {
        self.terminal.clear().context(err::IO)
    }

    pub fn draw<S>(&mut self, state: &UIState, score_parser: &S) -> Result<()>
    where
        S: ScoreParser + ?Sized,
    {
        let status_log = &mut self.status_log;
        let status_log_rect = &mut self.status_log_rect;

        self.terminal
            .draw(|mut frame| {
                // Top panels for series information
                let horiz_splitter = Layout::default()
                    .direction(Direction::Horizontal)
                    .constraints([Constraint::Min(20), Constraint::Percentage(70)].as_ref())
                    .split(frame.size());

                UI::draw_top_panels(state, &horiz_splitter, &mut frame);

                // Series info panel vertical splitter
                let info_panel_splitter = Layout::default()
                    .direction(Direction::Vertical)
                    .constraints([Constraint::Percentage(80), Constraint::Percentage(20)].as_ref())
                    .split(horiz_splitter[1]);

                UI::draw_info_panel(state, score_parser, &info_panel_splitter, &mut frame);

                *status_log_rect = info_panel_splitter[1];
                UI::draw_status_bar(state, status_log, *status_log_rect, &mut frame);
            })
            .context(err::IO)
    }

    pub fn adjust_cursor(&mut self, state: &UIState) -> Result<()> {
        use std::io::Write;

        const BORDER_SIZE: u16 = 2;

        match &state.status_bar_state {
            // We want to position the cursor inside of the command prompt, but only if we can fit it
            StatusBarState::CommandPrompt(prompt)
                if self.status_log_rect.height > BORDER_SIZE
                    && self.status_log_rect.width > BORDER_SIZE =>
            {
                if !self.cursor_visible {
                    self.terminal.show_cursor().context(err::IO)?;
                    self.cursor_visible = true;
                }

                let input_width = prompt.width() as u16;
                let prompt_width = self.status_log_rect.width.saturating_sub(BORDER_SIZE);

                let (len, line_num) = if prompt_width > 0 {
                    let line_num = input_width / prompt_width;
                    let max_line = self.status_log_rect.height - BORDER_SIZE - 1;

                    // We want to cap the position of the cursor to the last character of the last line
                    if line_num > max_line {
                        (prompt_width - 1, max_line)
                    } else {
                        (input_width % prompt_width, line_num)
                    }
                } else {
                    (input_width, 0)
                };

                let x = 1 + self.status_log_rect.left() + len;
                let y = 1 + self.status_log_rect.top() + line_num;

                self.terminal.set_cursor(x, y).context(err::IO)?;

                // Since stdout is buffered, we want to flush it immediately so the cursor follows certain inputs like spaces
                io::stdout().flush().ok();
            }
            StatusBarState::CommandPrompt(_) | StatusBarState::Log if self.cursor_visible => {
                self.terminal.hide_cursor().context(err::IO)?;
                self.cursor_visible = false;
            }
            StatusBarState::CommandPrompt(_) => (),
            StatusBarState::Log => (),
        }

        Ok(())
    }

    fn draw_top_panels(state: &UIState, layout: &[Rect], frame: &mut Frame<B>) {
        let series_names = state
            .series
            .iter()
            .map(|series| series.nickname())
            .collect::<SmallVec<[_; 8]>>();

        let mut series_list = SelectableList::default()
            .block(Block::default().title("Series").borders(Borders::ALL))
            .items(&series_names)
            .select(Some(state.selected_series))
            .style(Style::default().fg(Color::White))
            .highlight_style(Style::default().fg(Color::Green).modifier(Modifier::ITALIC))
            .highlight_symbol(">");

        series_list.render(frame, layout[0]);
    }

    fn draw_info_panel<S>(state: &UIState, score_parser: &S, layout: &[Rect], frame: &mut Frame<B>)
    where
        S: ScoreParser + ?Sized,
    {
        Block::default()
            .title("Info")
            .borders(Borders::ALL)
            .render(frame, layout[0]);

        let info_layout = Layout::default()
            .direction(Direction::Vertical)
            .constraints(
                [
                    Constraint::Length(4),
                    Constraint::Percentage(70),
                    Constraint::Length(4),
                ]
                .as_ref(),
            )
            .margin(2)
            .split(layout[0]);

        match state.cur_series_status() {
            Some(SeriesStatus::Valid(series)) => {
                UI::draw_series_info(state, series, score_parser, &info_layout, frame)
            }
            Some(SeriesStatus::Invalid(_, reason)) => {
                UI::draw_invalid_series(reason, &info_layout, frame)
            }
            Some(SeriesStatus::Unloaded(_)) => (),
            None => {
                let header =
                    Text::styled("No Series Found", Style::default().modifier(Modifier::BOLD));

                Paragraph::new([header].iter())
                    .alignment(Alignment::Center)
                    .render(frame, info_layout[0]);

                let body = Text::raw(
                    "Add one by pressing ':' and using the 'add' command \

                    \n\nThe command requires a nickname for the series \
                    \nyou want to add. For automatic detection, the nickname \
                    \nshould be similar to the name of the folder the series \
                    \nis in on disk. Any tags in the series folder name \
                    \nshould not affect the detection algorithm. \

                    \n\nIf you don't care about automatic detection, \
                    \nyou can set the ID of the series by appending 'id=<value>' \
                    \nto the 'add' command and use any nickname you wish. \

                    \n\nBy default, the program will look for series in '~/anime/'. \
                    \nYou can change this in `~/.config/anup/config.toml`. \
                    \nYou can also manually specify the path to a series by appending \
                    \n'path=\"<value>\"' to the 'add' command when adding a new series, \
                    \nor the 'set' command when modifying an existing one.",
                );

                Paragraph::new([body].iter())
                    .alignment(Alignment::Center)
                    .wrap(true)
                    .render(frame, info_layout[1]);
            }
        }
    }

    fn draw_series_info<S>(
        state: &UIState,
        series: &Series,
        score_parser: &S,
        info_layout: &[Rect],
        frame: &mut Frame<B>,
    ) where
        S: ScoreParser + ?Sized,
    {
        let info = &series.info;
        let entry = &series.entry;

        // Series title
        {
            let text_items = {
                let mut items = SmallVec::<[_; 2]>::new();

                items.push(Text::styled(
                    &info.title.preferred,
                    Style::default().modifier(Modifier::BOLD),
                ));

                if entry.needs_sync() {
                    items.push(Text::styled(
                        " [*]",
                        Style::default().modifier(Modifier::ITALIC),
                    ));
                }

                items
            };

            Paragraph::new(text_items.iter())
                .alignment(Alignment::Center)
                .render(frame, info_layout[0]);
        }

        // Items in panel
        let stat_layout = Layout::default()
            .direction(Direction::Horizontal)
            .constraints(
                [
                    Constraint::Percentage(33),
                    Constraint::Percentage(33),
                    Constraint::Percentage(33),
                ]
                .as_ref(),
            )
            .split(info_layout[1]);

        {
            let left_items = create_stat_list!(
                "Watch Time" => {
                    let watch_time_mins = info.episodes * info.episode_length;
                    util::hm_from_mins(watch_time_mins as f32)
                },
                "Time Left" => {
                    let eps_left = info.episodes - entry.watched_eps().min(info.episodes);
                    let time_left_mins = eps_left * info.episode_length;

                    util::hm_from_mins(time_left_mins as f32)
                },
                "Episode Length" => format!("{}M", info.episode_length)
            );

            Paragraph::new(left_items.iter())
                .alignment(Alignment::Center)
                .render(frame, stat_layout[0]);
        }

        {
            let center_items = create_stat_list!(
                "Progress" => format!("{}|{}", entry.watched_eps(), info.episodes),
                "Score" => match entry.score() {
                    Some(score) => score_parser.score_to_str(score),
                    None => "??".into(),
                },
                "Status" => entry.status()
            );

            Paragraph::new(center_items.iter())
                .alignment(Alignment::Center)
                .render(frame, stat_layout[1]);
        }

        {
            let right_items = create_stat_list!(
                "Start Date" => match entry.start_date() {
                    Some(date) => format!("{}", date.format("%D")).into(),
                    None => Cow::Borrowed("??"),
                },
                "Finish Date" => match entry.end_date() {
                    Some(date) => format!("{}", date.format("%D")).into(),
                    None => Cow::Borrowed("??"),
                },
                "Rewatched" => entry.times_rewatched()
            );

            Paragraph::new(right_items.iter())
                .alignment(Alignment::Center)
                .render(frame, stat_layout[2]);
        }

        // Watch time needed indicator at bottom
        match state.watch_state {
            WatchState::Idle => (),
            WatchState::Watching(progress_time, _) => {
                let watch_time = progress_time - Utc::now();
                let watch_secs = watch_time.num_seconds();

                if watch_secs > 0 {
                    let remaining_mins = watch_secs as f32 / 60.0;

                    let text_str = format!(
                        "{} Remaining Until Progression",
                        util::ms_from_mins(remaining_mins)
                    );

                    let text = Text::styled(text_str, Style::default().modifier(Modifier::BOLD));

                    Paragraph::new([text].iter())
                        .alignment(Alignment::Center)
                        .render(frame, info_layout[2]);
                }
            }
        }
    }

    fn draw_invalid_series(reason: &str, layout: &[Rect], frame: &mut Frame<B>) {
        let header = Text::styled(
            "Error Processing Series",
            Style::default().modifier(Modifier::BOLD),
        );

        Paragraph::new([header].iter())
            .alignment(Alignment::Center)
            .render(frame, layout[0]);

        let body = Text::styled(reason, Style::default().fg(Color::Red));

        Paragraph::new([body].iter())
            .alignment(Alignment::Center)
            .wrap(true)
            .render(frame, layout[1]);
    }

    fn draw_status_bar(state: &UIState, log: &mut StatusLog, layout: Rect, frame: &mut Frame<B>) {
        match &state.status_bar_state {
            StatusBarState::Log => {
                log.adjust_to_size(layout, true);

                Paragraph::new(log.draw_items_iter())
                    .block(
                        Block::default()
                            .title("Status [':' for command entry]")
                            .borders(Borders::ALL),
                    )
                    .wrap(true)
                    .render(frame, layout);
            }
            StatusBarState::CommandPrompt(prompt) => {
                Paragraph::new(prompt.draw_items().iter())
                    .block(
                        Block::default()
                            .title("Enter Command")
                            .borders(Borders::ALL),
                    )
                    .wrap(true)
                    .render(frame, layout);
            }
        }
    }
}

pub type TermionBackend = backend::TermionBackend<RawTerminal<io::Stdout>>;

impl<'a> UI<'a, TermionBackend> {
    pub fn init() -> Result<UI<'a, TermionBackend>> {
        let stdout = io::stdout().into_raw_mode().context(err::IO)?;
        let backend = TermionBackend::new(stdout);
        let mut terminal = Terminal::new(backend).context(err::IO)?;

        terminal.clear().context(err::IO)?;
        terminal.hide_cursor().context(err::IO)?;

        Ok(UI {
            terminal,
            status_log_rect: Rect::default(),
            status_log: StatusLog::new(),
            cursor_visible: false,
        })
    }
}

pub enum Event {
    Input(Key),
    Tick,
}

pub struct Events(mpsc::Receiver<Event>);

impl Events {
    pub fn new(tick_rate: Duration) -> Events {
        let (tx, rx) = mpsc::channel();

        Events::spawn_key_handler(tx.clone());
        Events::spawn_tick_handler(tick_rate, tx);

        Events(rx)
    }

    fn spawn_key_handler(tx: mpsc::Sender<Event>) -> thread::JoinHandle<()> {
        let stdin = io::stdin();

        thread::spawn(move || {
            for event in stdin.keys() {
                if let Ok(key) = event {
                    tx.send(Event::Input(key)).unwrap();
                }
            }
        })
    }

    fn spawn_tick_handler(tick_rate: Duration, tx: mpsc::Sender<Event>) -> thread::JoinHandle<()> {
        let tick_rate = tick_rate
            .to_std()
            .unwrap_or_else(|_| std::time::Duration::from_secs(1));

        thread::spawn(move || loop {
            thread::sleep(tick_rate);
            tx.send(Event::Tick).unwrap();
        })
    }

    pub fn next(&self) -> Result<Event> {
        self.0.recv().context(err::MPSCRecv)
    }
}
<|endoftext|>