diff --git a/src/client/mod.rs b/src/client/mod.rs index 18bd3fc..94cb208 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,7 +1,6 @@ use crate::message::{Message, MessageReader, MessageWriter}; use anyhow::{bail, Result}; use bytes::BytesMut; -use copypasta::{ClipboardContext, ClipboardProvider}; use log::LevelFilter; use log::{debug, error, info, warn}; use std::collections::HashMap; @@ -213,16 +212,11 @@ async fn client_handle_messages( } ClipStart(id) => { - info!("Starting clip op {id}"); clipboard_messages.insert(id, Vec::new()); } ClipData(id, mut data) => match clipboard_messages.get_mut(&id) { Some(bytes) => { - info!( - "Received data for clip op {id} ({len} bytes)", - len = data.len() - ); if bytes.len() < MAX_CLIPBOARD_SIZE { bytes.append(&mut data); } @@ -243,9 +237,10 @@ async fn client_handle_messages( continue; }; - let mut ctx = ClipboardContext::new().unwrap(); - if let Err(e) = ctx.set_contents(data) { - error!("Unable to set clipboard data for op {id}: {e:?}"); + if let Err(e) = + events.send(ui::UIEvent::SetClipboard(data)).await + { + error!("Error sending clipboard request: {:?}", e); } } diff --git a/src/client/ui.rs b/src/client/ui.rs index a20a8e1..923d731 100644 --- a/src/client/ui.rs +++ b/src/client/ui.rs @@ -1,6 +1,7 @@ use super::{client_listen, config::ServerConfig}; use crate::message::PortDesc; use anyhow::Result; +use copypasta::{ClipboardContext, ClipboardProvider}; use crossterm::{ event::{Event, EventStream, KeyCode, KeyEvent, KeyModifiers}, execute, @@ -33,6 +34,7 @@ pub enum UIEvent { ServerLine(String), LogLine(log::Level, String), Ports(Vec), + SetClipboard(String), } pub enum UIReturn { @@ -166,7 +168,6 @@ impl Listener { } } -#[derive(Debug)] pub struct UI { events: mpsc::Receiver, ports: HashMap, @@ -179,6 +180,7 @@ pub struct UI { show_help: bool, alternate_screen: bool, raw_mode: bool, + clipboard: ClipboardContext, } impl UI { @@ -195,6 +197,8 @@ impl UI { config, alternate_screen: false, raw_mode: false, + clipboard: ClipboardContext::new() + .expect("Unable to initialize clipboard context"), } } @@ -621,6 +625,14 @@ impl UI { } self.lines.push_back(format!("[CLIENT] {line}")); } + Some(UIEvent::SetClipboard(contents)) => { + let length = contents.len(); + if let Err(e) = self.clipboard.set_contents(contents) { + error!("Error setting clipboard contents: {e:#}"); + } else { + info!("Received clipboard contents ({length} bytes)"); + } + } None => { self.running = false; } diff --git a/src/lib.rs b/src/lib.rs index a1e99a7..6920aa0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,10 @@ mod message; mod reverse; mod server; +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const REV: &str = env!("REPO_REV"); +pub const DIRTY: &str = env!("REPO_DIRTY"); + pub use client::run_client; pub use reverse::browse_url; pub use reverse::clip_file; diff --git a/src/main.rs b/src/main.rs index 6862124..7871d50 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,6 @@ // TODO: An actual proper command line parsing use indoc::indoc; -const VERSION: &str = env!("CARGO_PKG_VERSION"); -const REV: &str = env!("REPO_REV"); -const DIRTY: &str = env!("REPO_DIRTY"); - fn usage() { println!(indoc! {" usage: fwd [options] ( | browse | clip []) @@ -121,7 +117,7 @@ fn parse_args(args: Vec) -> Args { async fn browse_url(url: &str) { if let Err(e) = fwd::browse_url(url).await { eprintln!("Unable to open {url}"); - eprintln!("{}", e); + eprintln!("{:#}", e); std::process::exit(1); } } @@ -129,7 +125,7 @@ async fn browse_url(url: &str) { async fn clip_file(file: String) { if let Err(e) = fwd::clip_file(&file).await { eprintln!("Unable to copy to the clipboard"); - eprintln!("{}", e); + eprintln!("{:#}", e); std::process::exit(1); } } @@ -141,7 +137,7 @@ async fn main() { usage(); } Args::Version => { - println!("fwd {VERSION} (rev {REV}{DIRTY})"); + println!("fwd {} (rev {}{})", fwd::VERSION, fwd::REV, fwd::DIRTY); } Args::Server => { fwd::run_server().await; diff --git a/src/reverse.rs b/src/reverse.rs index a7d8534..f717dd9 100644 --- a/src/reverse.rs +++ b/src/reverse.rs @@ -6,16 +6,28 @@ use tokio::io::{AsyncRead, AsyncReadExt}; mod unix; #[cfg(target_family = "unix")] -pub use unix::{handle_reverse_connections, send_reverse_message}; +pub use unix::{handle_reverse_connections, ReverseConnection}; use crate::message::Message; #[cfg(not(target_family = "unix"))] -pub async fn send_reverse_message(_message: Message) -> Result<()> { - use anyhow::anyhow; - Err(anyhow!( - "Server-side operations are not supported on this platform" - )) +pub struct ReverseConnection {} + +#[cfg(not(target_family = "unix"))] +impl ReverseConnection { + pub async fn new() -> Result { + use anyhow::anyhow; + Err(anyhow!( + "Server-side operations are not supported on this platform" + )) + } + + pub async fn send(&mut self, message: Message) -> Result<()> { + use anyhow::anyhow; + Err(anyhow!( + "Server-side operations are not supported on this platform" + )) + } } #[cfg(not(target_family = "unix"))] @@ -27,12 +39,16 @@ pub async fn handle_reverse_connections( #[inline] pub async fn browse_url(url: &str) -> Result<()> { - send_reverse_message(Message::Browse(url.to_string())).await + ReverseConnection::new() + .await? + .send(Message::Browse(url.to_string())) + .await } async fn clip_reader(reader: &mut T) -> Result<()> { + let mut connection = ReverseConnection::new().await?; let clip_id: u64 = random(); - send_reverse_message(Message::ClipStart(clip_id)).await?; + connection.send(Message::ClipStart(clip_id)).await?; let mut count = 0; let mut buf = vec![0; 1024]; @@ -43,7 +59,7 @@ async fn clip_reader(reader: &mut T) -> Result<()> { } count += read; if count == buf.len() { - send_reverse_message(Message::ClipData(clip_id, buf)).await?; + connection.send(Message::ClipData(clip_id, buf)).await?; buf = vec![0; 1024]; count = 0; } @@ -51,10 +67,10 @@ async fn clip_reader(reader: &mut T) -> Result<()> { if count > 0 { buf.resize(count, 0); - send_reverse_message(Message::ClipData(clip_id, buf)).await?; + connection.send(Message::ClipData(clip_id, buf)).await?; } - send_reverse_message(Message::ClipEnd(clip_id)).await?; + connection.send(Message::ClipEnd(clip_id)).await?; Ok(()) } diff --git a/src/reverse/unix.rs b/src/reverse/unix.rs index 92025ce..d3cc801 100644 --- a/src/reverse/unix.rs +++ b/src/reverse/unix.rs @@ -9,32 +9,27 @@ use tokio::sync::mpsc; use crate::message::{Message, MessageReader, MessageWriter}; -pub async fn send_reverse_message(message: Message) -> Result<()> { - let path = socket_path().context("Error getting socket path")?; - let stream = match UnixStream::connect(&path).await { - Ok(s) => s, - Err(e) => bail!( - "Error connecting to socket: {e} (is fwd actually connected here?)" - ), - }; - let mut writer = MessageWriter::new(stream); - writer - .write(message) - .await - .context("Error sending browse message")?; - Ok(()) +pub struct ReverseConnection { + writer: MessageWriter, } -fn socket_directory() -> Result { - let base_directories = xdg::BaseDirectories::new() - .context("Error creating BaseDirectories")?; - match base_directories.place_runtime_file("fwd") { - Ok(path) => Ok(path), - Err(_) => { - let mut path = std::env::temp_dir(); - path.push(format!("fwd{}", users::get_current_uid())); - Ok(path) - } +impl ReverseConnection { + pub async fn new() -> Result { + let path = socket_path().context("Error getting socket path")?; + let stream = match UnixStream::connect(&path).await { + Ok(s) => s, + Err(e) => bail!("Error connecting to socket: {e} (is fwd actually connected here?)"), + }; + + Ok(ReverseConnection { writer: MessageWriter::new(stream) }) + } + + pub async fn send(&mut self, message: Message) -> Result<()> { + self.writer + .write(message) + .await + .context("Error sending reverse message")?; + Ok(()) } } @@ -53,6 +48,19 @@ pub fn socket_path() -> Result { Ok(socket_path) } +fn socket_directory() -> Result { + let base_directories = xdg::BaseDirectories::new() + .context("Error creating BaseDirectories")?; + match base_directories.place_runtime_file("fwd") { + Ok(path) => Ok(path), + Err(_) => { + let mut path = std::env::temp_dir(); + path.push(format!("fwd{}", users::get_current_uid())); + Ok(path) + } + } +} + pub async fn handle_reverse_connections( messages: mpsc::Sender, ) -> Result<()> { @@ -87,10 +95,18 @@ async fn handle_connection( sender: mpsc::Sender, ) -> Result<()> { let mut reader = MessageReader::new(socket); - let message = reader.read().await.context("Error reading message")?; - match message { - Message::Browse(url) => sender.send(Message::Browse(url)).await?, - _ => bail!("Unsupported message: {:?}", message), + while let Ok(message) = reader.read().await { + match message { + Message::Browse(url) => sender.send(Message::Browse(url)).await?, + Message::ClipStart(id) => { + sender.send(Message::ClipStart(id)).await? + } + Message::ClipData(id, data) => { + sender.send(Message::ClipData(id, data)).await? + } + Message::ClipEnd(id) => sender.send(Message::ClipEnd(id)).await?, + _ => bail!("Unsupported message: {:?}", message), + } } Ok(()) diff --git a/src/server/mod.rs b/src/server/mod.rs index 1188c98..804d877 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -26,12 +26,26 @@ async fn server_loop( ) -> Result<()> { // The first message we send must be an announcement. writer.send(Message::Hello(0, 2, vec![])).await?; - + let mut version_reported = false; loop { use Message::*; match reader.read().await? { Ping => (), Refresh => { + // Just log the version, if we haven't yet. We do this extra + // work to avoid spamming the log, but we wait until we + // receive the first message to be sure that the client is in + // a place to display our logging properly. + if !version_reported { + eprintln!( + "fwd server {} (rev {}{})", + crate::VERSION, + crate::REV, + crate::DIRTY + ); + version_reported = true; + } + let ports = match refresh::get_entries().await { Ok(ports) => ports, Err(e) => {