use std::future::Future;
use std::pin::Pin;
use std::time::Duration;
use egui_inspection::protocol::{
EncodedPng, Request, Response, decode_frame_body, decode_frame_len, decode_handshake,
encode_frame,
};
use serde::Serialize;
use tokio::io::{AsyncRead, AsyncReadExt as _, AsyncWrite, AsyncWriteExt as _};
use tokio::net::TcpStream;
use tokio::sync::Mutex;
const DEFAULT_CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
pub trait Transport: Send + Sync {
fn request(&self, req: Request) -> BoxFuture<'_, Result<Response, String>>;
}
#[derive(Debug, Clone, Serialize)]
pub struct PeerInfo {
pub transport: String,
pub protocol_version: u32,
pub label: Option<String>,
}
struct Conn {
reader: Box<dyn AsyncRead + Unpin + Send>,
writer: Box<dyn AsyncWrite + Unpin + Send>,
}
pub struct FramedTransport {
conn: Mutex<Conn>,
}
impl FramedTransport {
pub fn new<R, W>(reader: R, writer: W) -> Self
where
R: AsyncRead + Unpin + Send + 'static,
W: AsyncWrite + Unpin + Send + 'static,
{
Self {
conn: Mutex::new(Conn {
reader: Box::new(reader),
writer: Box::new(writer),
}),
}
}
}
impl Transport for FramedTransport {
fn request(&self, req: Request) -> BoxFuture<'_, Result<Response, String>> {
Box::pin(async move {
let mut conn = self.conn.lock().await;
write_framed(&mut conn.writer, &req)
.await
.map_err(|e| e.to_string())?;
read_framed(&mut conn.reader)
.await
.map_err(|e| e.to_string())
})
}
}
pub struct Bridge {
transport: Box<dyn Transport>,
pub peer_info: PeerInfo,
}
impl Bridge {
pub async fn connect(host: &str, port: u16, timeout: Option<Duration>) -> Result<Self, String> {
let addr = format!("{host}:{port}");
let timeout = timeout.unwrap_or(DEFAULT_CONNECT_TIMEOUT);
let deadline = tokio::time::Instant::now() + timeout;
let stream = loop {
match TcpStream::connect(&addr).await {
Ok(s) => break s,
Err(e) => {
if tokio::time::Instant::now() >= deadline {
return Err(format!(
"connect to {addr} (is the app running with EGUI_INSPECTION set?): {e}"
));
}
tokio::time::sleep(Duration::from_millis(200)).await;
}
}
};
let _ignored = stream.set_nodelay(true);
let (mut reader, writer) = stream.into_split();
let mut handshake = [0u8; 8];
reader
.read_exact(&mut handshake)
.await
.map_err(|e| format!("read handshake: {e}"))?;
let protocol_version =
decode_handshake(handshake).map_err(|e| format!("inspection handshake: {e}"))?;
let mut bridge = Self::with_transport(
FramedTransport::new(reader, writer),
PeerInfo {
transport: addr,
protocol_version,
label: None,
},
);
bridge.peer_info.label = bridge.fetch_label().await?;
Ok(bridge)
}
pub fn with_transport<T: Transport + 'static>(transport: T, peer_info: PeerInfo) -> Self {
Self {
transport: Box::new(transport),
peer_info,
}
}
pub async fn request(&self, req: Request) -> Result<Response, String> {
self.transport.request(req).await
}
async fn fetch_label(&self) -> Result<Option<String>, String> {
match self.request(Request::GetInfo).await? {
Response::Info { label, .. } => Ok(label),
Response::Error { message } => Err(message),
_ => Err("unexpected response to GetInfo".to_owned()),
}
}
pub async fn fetch_tree(&self) -> Result<TreeSnapshot, String> {
match self.request(Request::GetTree).await? {
Response::Tree {
step,
pixels_per_point,
accesskit,
} => Ok(TreeSnapshot {
step,
pixels_per_point,
tree: accesskit.map(|update| accesskit_consumer::Tree::new(update, false)),
}),
Response::Error { message } => Err(message),
_ => Err("unexpected response to GetTree".to_owned()),
}
}
async fn request_done(&self, req: Request, what: &str) -> Result<(), String> {
match self.request(req).await? {
Response::Done => Ok(()),
Response::Error { message } => Err(message),
_ => Err(format!("unexpected response to {what}")),
}
}
pub async fn apply_events(&self, events: Vec<egui::Event>) -> Result<(), String> {
self.request_done(Request::ApplyEvents { events }, "ApplyEvents")
.await
}
pub async fn resize(&self, width: u32, height: u32) -> Result<(), String> {
self.request_done(Request::Resize { width, height }, "Resize")
.await
}
pub async fn screenshot(&self, pixels_per_point: f32) -> Result<EncodedPng, String> {
match self
.request(Request::GetScreenshot {
pixels_per_point: Some(pixels_per_point),
})
.await?
{
Response::Screenshot(png) => Ok(png),
Response::Error { message } => Err(message),
_ => Err("unexpected response to GetScreenshot".to_owned()),
}
}
}
pub struct TreeSnapshot {
pub step: u64,
pub pixels_per_point: f32,
pub tree: Option<accesskit_consumer::Tree>,
}
async fn write_framed<W, T>(writer: &mut W, value: &T) -> std::io::Result<()>
where
W: AsyncWrite + Unpin,
T: Serialize,
{
writer.write_all(&encode_frame(value)?).await?;
writer.flush().await?;
Ok(())
}
async fn read_framed<R, T>(reader: &mut R) -> std::io::Result<T>
where
R: AsyncRead + Unpin,
T: for<'de> serde::Deserialize<'de>,
{
let mut header = [0u8; 4];
reader.read_exact(&mut header).await?;
let mut body = vec![0u8; decode_frame_len(header)?];
reader.read_exact(&mut body).await?;
decode_frame_body(&body)
}