use std::ffi::{CStr, CString};
use std::mem;
use std::os::raw::c_char;
use std::ptr::{null_mut, NonNull};
use rusb::{DeviceHandle, UsbContext};
use crate::callback::CallbackData;
use array_iterator::ArrayIterator;
use dir::{DirItem, DirList};
pub use error::*;
use info::Info;
use libnspire_sys::{
free, nspire_attr, nspire_device_info, nspire_devinfo, nspire_dir_create, nspire_dir_delete,
nspire_dirlist, nspire_file_copy, nspire_file_delete, nspire_file_move, nspire_file_read,
nspire_file_write, nspire_free, nspire_handle, nspire_image, nspire_init, nspire_os_send,
nspire_screenshot,
};
use std::convert::TryFrom;
mod callback;
pub mod dir;
mod error;
pub mod info;
pub const VID: u16 = 0x0451;
pub const PID: u16 = 0xe012;
pub const PID_CX2: u16 = 0xe022;
pub struct Handle<T: UsbContext> {
handle: NonNull<nspire_handle>,
device: DeviceHandle<T>,
}
impl<T: UsbContext> Handle<T> {
pub fn new(device: DeviceHandle<T>) -> Result<Self> {
let mut handle: *mut nspire_handle = null_mut();
err(unsafe { nspire_init(&mut handle, device.as_raw() as _) })?;
Ok(Handle {
handle: NonNull::new(handle).ok_or(Error::NoDevice)?,
device,
})
}
pub fn is_cx_ii(&self) -> Result<bool> {
Ok(self.device.device().device_descriptor()?.product_id() == PID_CX2)
}
pub fn info(&self) -> Result<Info> {
unsafe {
let mut info: nspire_devinfo = mem::zeroed();
err(nspire_device_info(self.handle.as_ptr(), &mut info))?;
Ok(info.into())
}
}
pub fn screenshot(&self) -> Result<Image> {
unsafe {
let mut image: *mut nspire_image = null_mut();
err(nspire_screenshot(self.handle.as_ptr(), &mut image))?;
let width = (*image).width;
let height = (*image).height;
let bbp = (*image).bbp;
let len = (width as u32 * height as u32 * bbp as u32) / 8;
let data = (*image).data.as_slice(len as usize).into();
free(image as _);
Ok(Image {
width,
height,
bpp: bbp,
data,
})
}
}
pub fn move_file(&self, src: &str, dest: &str) -> Result<()> {
let src = CString::new(src)?;
let dest = CString::new(dest)?;
unsafe {
err(nspire_file_move(
self.handle.as_ptr(),
src.as_ptr(),
dest.as_ptr(),
))
}
}
pub fn file_attr(&self, src: &str) -> Result<DirItem> {
let src = CString::new(src)?;
unsafe {
let mut item = mem::zeroed();
err(nspire_attr(self.handle.as_ptr(), src.as_ptr(), &mut item))?;
Ok(item.into())
}
}
pub fn copy_file(&self, src: &str, dest: &str) -> Result<()> {
let src = CString::new(src)?;
let dest = CString::new(dest)?;
unsafe {
err(nspire_file_copy(
self.handle.as_ptr(),
src.as_ptr(),
dest.as_ptr(),
))
}
}
pub fn delete_file(&self, path: &str) -> Result<()> {
let path = CString::new(path)?;
unsafe { err(nspire_file_delete(self.handle.as_ptr(), path.as_ptr())) }
}
pub fn read_file(
&self,
path: &str,
buf: &mut [u8],
progress: &mut dyn FnMut(usize),
) -> Result<usize> {
let path = CString::new(path)?;
let mut bytes = 0;
let mut cb = CallbackData(progress);
unsafe {
err(nspire_file_read(
self.handle.as_ptr(),
path.as_ptr(),
buf.as_mut_ptr() as _,
buf.len() as _,
&mut bytes,
Some(CallbackData::callback),
cb.as_mut_void(),
))?;
}
Ok(bytes as usize)
}
pub fn write_file(
&self,
path: &str,
buf: &[u8],
progress: &mut dyn FnMut(usize),
) -> Result<()> {
let path = CString::new(path)?;
let mut cb = CallbackData(progress);
unsafe {
err(nspire_file_write(
self.handle.as_ptr(),
path.as_ptr(),
buf.as_ptr() as _,
buf.len() as _,
Some(CallbackData::callback),
cb.as_mut_void(),
))
}
}
pub fn send_os(&self, buf: &[u8], progress: &mut dyn FnMut(usize)) -> Result<()> {
let mut cb = CallbackData(progress);
unsafe {
err(nspire_os_send(
self.handle.as_ptr(),
buf.as_ptr() as _,
buf.len() as _,
Some(CallbackData::callback),
cb.as_mut_void(),
))
}
}
pub fn create_dir(&self, path: &str) -> Result<()> {
let path = CString::new(path)?;
unsafe { err(nspire_dir_create(self.handle.as_ptr(), path.as_ptr())) }
}
pub fn delete_dir(&self, path: &str) -> Result<()> {
let path = CString::new(path)?;
unsafe { err(nspire_dir_delete(self.handle.as_ptr(), path.as_ptr())) }
}
pub fn list_dir(&self, path: &str) -> Result<DirList> {
let path = CString::new(path)?;
unsafe {
let mut list = null_mut();
err(nspire_dirlist(
self.handle.as_ptr(),
path.as_ptr(),
&mut list,
))?;
Ok(DirList::from_raw(list))
}
}
}
unsafe impl<T: UsbContext> Send for Handle<T> {}
unsafe impl<T: UsbContext> Sync for Handle<T> {}
impl<T: UsbContext> TryFrom<DeviceHandle<T>> for Handle<T> {
type Error = Error;
fn try_from(device: DeviceHandle<T>) -> Result<Self> {
Handle::new(device)
}
}
impl<T: UsbContext> Drop for Handle<T> {
fn drop(&mut self) {
unsafe { nspire_free(self.handle.as_ptr()) }
}
}
pub struct Image {
pub width: u16,
pub height: u16,
pub bpp: u8,
pub data: Vec<u8>,
}
const MAX_R: u8 = ((1usize << 5) - 1) as u8;
const MAX_G: u8 = ((1usize << 6) - 1) as u8;
const MAX_B: u8 = ((1usize << 5) - 1) as u8;
const fn convert_channel(value: u8, from_max: u8) -> u8 {
((value as u16 * 255u16 + from_max as u16 / 2) / from_max as u16) as u8
}
#[cfg(feature = "image")]
impl TryFrom<Image> for image::DynamicImage {
type Error = Error;
fn try_from(image: Image) -> Result<Self> {
use image::ImageBuffer;
match image.bpp {
8 => Ok(image::DynamicImage::ImageLuma8(
ImageBuffer::from_vec(image.width as u32, image.height as u32, image.data).unwrap(),
)),
16 => {
let data: Vec<u8> = image
.data
.chunks(2)
.flat_map(|d| {
let color = u16::from_ne_bytes([d[0], d[1]]);
ArrayIterator::new([
convert_channel(color as u8 & MAX_R, MAX_R),
convert_channel((color >> 5) as u8 & MAX_G, MAX_G),
convert_channel((color >> 11) as u8 & MAX_B, MAX_B),
])
})
.collect();
dbg!(data.len());
Ok(image::DynamicImage::ImageRgb8(
ImageBuffer::from_vec(image.width as u32, image.height as u32, data).unwrap(),
))
}
other => Err(Error::UnknownBpp(other)),
}
}
}
unsafe fn c_str(s: &[c_char]) -> String {
CStr::from_ptr(s.as_ptr()).to_string_lossy().to_string()
}