use std::error::Error;
use std::fmt::{Display, Formatter};
use std::io;
use std::io::{BufRead, BufReader, Write};
use std::net::TcpStream;
use std::process::{Command, Stdio};
#[derive(Debug)]
pub struct TevClient {
socket: TcpStream,
}
#[derive(Debug)]
pub enum TevError {
Command { io: std::io::Error },
Stdout { io: std::io::Error },
NoSocketResponse { read: String },
TcpConnect { host: String, io: std::io::Error },
IO { io: std::io::Error },
}
impl TevClient {
pub fn wrap(socket: TcpStream) -> Self {
TevClient { socket }
}
pub fn spawn_path_default() -> Result<TevClient, TevError> {
TevClient::spawn(Command::new("tev"))
}
pub fn spawn(mut command: Command) -> Result<TevClient, TevError> {
const PATTERNS: &[&str] = &[
"Initialized IPC, listening on ",
"Connected to primary instance at ",
];
let mut child = command.stdout(Stdio::piped()).spawn()
.map_err(|io| TevError::Command { io })?;
let reader = BufReader::new(child.stdout.take().unwrap());
let mut read = String::new();
for line in reader.lines() {
let line = line.map_err(|io| TevError::Stdout { io })?;
for pattern in PATTERNS {
if let Some(start) = line.find(pattern) {
let rest = &line[start + pattern.len()..];
let end = rest.find('\u{1b}').unwrap_or(rest.len());
let host = &rest[..end];
let socket = TcpStream::connect(host)
.map_err(|io| TevError::TcpConnect { host: host.to_string(), io })?;
return Ok(TevClient::wrap(socket));
}
}
read.push_str(&line);
read.push('\n');
}
Err(TevError::NoSocketResponse { read })
}
pub fn send(&mut self, packet: impl TevPacket) -> io::Result<()> {
let vec = vec![0, 0, 0, 0];
let mut target = TevWriter { target: vec };
packet.write_to(&mut target);
let mut vec = target.target;
let packet_length = vec.len() as u32;
vec[0..4].copy_from_slice(&packet_length.to_le_bytes());
self.socket.write_all(&vec)
}
}
#[derive(Debug)]
pub struct PacketOpenImage<'a> {
pub image_name: &'a str,
pub grab_focus: bool,
pub channel_selector: &'a str,
}
impl TevPacket for PacketOpenImage<'_> {
fn write_to(&self, writer: &mut TevWriter) {
writer.write(PacketType::OpenImageV2);
writer.write(self.grab_focus);
writer.write(self.image_name);
writer.write(self.channel_selector);
}
}
#[derive(Debug)]
pub struct PacketReloadImage<'a> {
pub image_name: &'a str,
pub grab_focus: bool,
}
impl TevPacket for PacketReloadImage<'_> {
fn write_to(&self, writer: &mut TevWriter) {
writer.write(PacketType::ReloadImage);
writer.write(self.grab_focus);
writer.write(self.image_name);
}
}
#[derive(Debug)]
pub struct PacketUpdateImage<'a, S: AsRef<str> + 'a> {
pub image_name: &'a str,
pub grab_focus: bool,
pub channel_names: &'a [S],
pub channel_offsets: &'a [u64],
pub channel_strides: &'a [u64],
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
pub data: &'a [f32],
}
impl<'a, S: AsRef<str> + 'a> TevPacket for PacketUpdateImage<'a, S> {
fn write_to(&self, writer: &mut TevWriter) {
let channel_count = self.channel_names.len();
assert_ne!(channel_count, 0, "Must update at least one channel");
assert_eq!(channel_count, self.channel_offsets.len(), "Channel count must be consistent");
assert_eq!(channel_count, self.channel_strides.len(), "Channel count must be consistent");
let pixel_count = (self.width as u64) * (self.height as u64);
assert_ne!(pixel_count, 0, "Must update at least one pixel");
let max_data_index_used = self.channel_offsets.iter().zip(self.channel_strides)
.map(|(&o, &s)| o + (pixel_count - 1) * s)
.max().unwrap();
assert_eq!(max_data_index_used + 1, self.data.len() as u64, "Data size does not match actually used data range");
writer.write(PacketType::UpdateImageV3);
writer.write(self.grab_focus);
writer.write(self.image_name);
writer.write(channel_count as u32);
writer.write_all(self.channel_names.iter().map(AsRef::as_ref));
writer.write(self.x);
writer.write(self.y);
writer.write(self.width);
writer.write(self.height);
writer.write_all(self.channel_offsets);
writer.write_all(self.channel_strides);
writer.write_all(self.data)
}
}
#[derive(Debug)]
pub struct PacketCloseImage<'a> {
pub image_name: &'a str,
}
impl TevPacket for PacketCloseImage<'_> {
fn write_to(&self, writer: &mut TevWriter) {
writer.write(PacketType::CloseImage);
writer.write(self.image_name);
}
}
#[derive(Debug)]
pub struct PacketCreateImage<'a, S: AsRef<str> + 'a> {
pub image_name: &'a str,
pub grab_focus: bool,
pub width: u32,
pub height: u32,
pub channel_names: &'a [S],
}
impl<'a, S: AsRef<str> + 'a> TevPacket for PacketCreateImage<'a, S> {
fn write_to(&self, writer: &mut TevWriter) {
writer.write(PacketType::CreateImage);
writer.write(self.grab_focus);
writer.write(self.image_name);
writer.write(self.width);
writer.write(self.height);
writer.write(self.channel_names.len() as u32);
writer.write_all(self.channel_names.iter().map(AsRef::as_ref));
}
}
#[doc(hidden)]
pub struct TevWriter {
target: Vec<u8>,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
enum PacketType {
ReloadImage = 1,
CloseImage = 2,
CreateImage = 4,
UpdateImageV3 = 6,
OpenImageV2 = 7,
}
impl TevWriter {
fn write(&mut self, value: impl TevWritable) {
value.write_to(self);
}
fn write_all(&mut self, values: impl IntoIterator<Item=impl TevWritable>) {
for value in values {
value.write_to(self);
}
}
}
#[doc(hidden)]
pub trait TevPacket {
fn write_to(&self, writer: &mut TevWriter);
}
trait TevWritable {
fn write_to(self, writer: &mut TevWriter);
}
impl<T: TevWritable + Copy> TevWritable for &T {
fn write_to(self, writer: &mut TevWriter) {
(*self).write_to(writer);
}
}
impl TevWritable for bool {
fn write_to(self, writer: &mut TevWriter) {
writer.target.push(self as u8);
}
}
impl TevWritable for PacketType {
fn write_to(self, writer: &mut TevWriter) {
writer.target.push(self as u8);
}
}
impl TevWritable for u32 {
fn write_to(self, writer: &mut TevWriter) {
writer.target.extend_from_slice(&self.to_le_bytes());
}
}
impl TevWritable for u64 {
fn write_to(self, writer: &mut TevWriter) {
writer.target.extend_from_slice(&self.to_le_bytes());
}
}
impl TevWritable for f32 {
fn write_to(self, writer: &mut TevWriter) {
writer.target.extend_from_slice(&self.to_le_bytes());
}
}
impl TevWritable for &'_ str {
fn write_to(self, writer: &mut TevWriter) {
assert!(!self.contains('\0'), "cannot send strings containing '\\0'");
writer.target.extend_from_slice(self.as_bytes());
writer.target.push(0);
}
}
impl From<std::io::Error> for TevError {
fn from(io: std::io::Error) -> Self {
TevError::IO { io }
}
}
impl Display for TevError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
TevError::Command { io } =>
write!(f, "error during command execution: {}", io),
TevError::Stdout { io } =>
write!(f, "error during stdout reading: {}", io),
TevError::NoSocketResponse { read } =>
write!(f, "stdout did not contain socket, got '{}'", read),
TevError::TcpConnect { host, io } =>
write!(f, "error during attempted tcp connection to '{}': {}", host, io),
TevError::IO { io } =>
write!(f, "generic IO error: {}", io),
}
}
}
impl std::error::Error for TevError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
TevError::Command { io } | TevError::Stdout { io } |
TevError::TcpConnect { host: _, io } | TevError::IO { io } =>
Some(io),
TevError::NoSocketResponse { read: _ } =>
None,
}
}
}