diff --git a/crates/voidpin/proto/voidpin/v1/voidpin.proto b/crates/voidpin/proto/voidpin/v1/voidpin.proto index b0d8ea5..93aa847 100644 --- a/crates/voidpin/proto/voidpin/v1/voidpin.proto +++ b/crates/voidpin/proto/voidpin/v1/voidpin.proto @@ -2,12 +2,20 @@ syntax = "proto3"; package voidpin.v1; -service VoidPin { - rpc Copy(CopyRequest) returns (CopyResponse); -} - message CopyRequest { bytes content = 1; } message CopyResponse {} + +message PasteRequest { +} + +message PasteResponse { + bytes content = 1; +} + +service VoidPin { + rpc Copy(CopyRequest) returns (CopyResponse); + rpc Paste(PasteRequest) returns (PasteResponse); +} diff --git a/crates/voidpin/src/copy.rs b/crates/voidpin/src/copy.rs index 63035a6..f8c6cdc 100644 --- a/crates/voidpin/src/copy.rs +++ b/crates/voidpin/src/copy.rs @@ -1,7 +1,7 @@ use std::process::Stdio; use anyhow::Context; -use tokio::io::AsyncWriteExt; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; use crate::state::State; @@ -30,10 +30,6 @@ impl LocalCopier { #[cfg(target_os = "windows")] let mut copy_process = { todo!("windows not supported yet"); - - tokio::process::Command::new("wl-copy") - .stdin(Stdio::piped()) - .spawn()? }; if let Some(mut stdin_handle) = copy_process.stdin.take() { @@ -52,6 +48,40 @@ impl LocalCopier { Ok(()) } + + pub async fn paste(&self) -> anyhow::Result> { + // FIXME: hardcode for macos + #[cfg(target_os = "macos")] + let mut paste_process = { + tokio::process::Command::new("pbpaste") + .stdin(Stdio::piped()) + .spawn()? + }; + #[cfg(target_os = "linux")] + let mut paste_process = { + tokio::process::Command::new("wl-paste") + .stdin(Stdio::piped()) + .spawn()? + }; + #[cfg(target_os = "windows")] + let mut paste_process = { + todo!("windows not supported yet"); + }; + let mut buf = Vec::new(); + + if let Some(mut stdout_handle) = paste_process.stdout.take() { + stdout_handle + .read_to_end(&mut buf) + .await + .context("failed to write input to paste process")?; + } + + let status = paste_process.wait().await?; + + tracing::info!("paste process ended with status: {:?}", status); + + Ok(buf) + } } pub trait LocalCopierState { diff --git a/crates/voidpin/src/gen/voidpin.v1.rs b/crates/voidpin/src/gen/voidpin.v1.rs index da0f54a..2497878 100644 --- a/crates/voidpin/src/gen/voidpin.v1.rs +++ b/crates/voidpin/src/gen/voidpin.v1.rs @@ -10,5 +10,15 @@ pub struct CopyRequest { #[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct CopyResponse { } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct PasteRequest { +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PasteResponse { + #[prost(bytes="vec", tag="1")] + pub content: ::prost::alloc::vec::Vec, +} include!("voidpin.v1.tonic.rs"); // @@protoc_insertion_point(module) \ No newline at end of file diff --git a/crates/voidpin/src/gen/voidpin.v1.tonic.rs b/crates/voidpin/src/gen/voidpin.v1.tonic.rs index 3e3c970..1072a32 100644 --- a/crates/voidpin/src/gen/voidpin.v1.tonic.rs +++ b/crates/voidpin/src/gen/voidpin.v1.tonic.rs @@ -105,6 +105,26 @@ pub mod void_pin_client { req.extensions_mut().insert(GrpcMethod::new("voidpin.v1.VoidPin", "Copy")); self.inner.unary(req, path, codec).await } + /// + pub async fn paste( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static("/voidpin.v1.VoidPin/Paste"); + let mut req = request.into_request(); + req.extensions_mut().insert(GrpcMethod::new("voidpin.v1.VoidPin", "Paste")); + self.inner.unary(req, path, codec).await + } } } /// Generated server implementations. @@ -119,6 +139,11 @@ pub mod void_pin_server { &self, request: tonic::Request, ) -> std::result::Result, tonic::Status>; + /// + async fn paste( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; } /// #[derive(Debug)] @@ -244,6 +269,50 @@ pub mod void_pin_server { }; Box::pin(fut) } + "/voidpin.v1.VoidPin/Paste" => { + #[allow(non_camel_case_types)] + struct PasteSvc(pub Arc); + impl tonic::server::UnaryService + for PasteSvc { + type Response = super::PasteResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::paste(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = PasteSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } _ => { Box::pin(async move { Ok( diff --git a/crates/voidpin/src/grpc_server.rs b/crates/voidpin/src/grpc_server.rs index 36c8e33..fa0d5bc 100644 --- a/crates/voidpin/src/grpc_server.rs +++ b/crates/voidpin/src/grpc_server.rs @@ -1,4 +1,8 @@ -use crate::{copy::LocalCopierState, state::State}; +use crate::{ + copy::LocalCopierState, + grpc::{PasteRequest, PasteResponse}, + state::State, +}; #[derive(Clone)] pub struct GrpcServer { @@ -27,4 +31,20 @@ impl crate::grpc::void_pin_server::VoidPin for GrpcServer { Ok(tonic::Response::new(crate::grpc::CopyResponse {})) } + + async fn paste( + &self, + _request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + let output = self + .state + .local_copier() + .paste() + .await + .map_err(|e| tonic::Status::internal(e.to_string()))?; + + Ok(tonic::Response::new(crate::grpc::PasteResponse { + content: output, + })) + } } diff --git a/crates/voidpin/src/main.rs b/crates/voidpin/src/main.rs index 59c27ae..f4ced5b 100644 --- a/crates/voidpin/src/main.rs +++ b/crates/voidpin/src/main.rs @@ -7,6 +7,7 @@ use grpc::void_pin_server::VoidPinServer; use grpc_server::GrpcServer; use remote_copy::RemoteCopierState; use state::State; +use tokio::io::AsyncWriteExt; use tonic::transport; mod grpc { @@ -33,6 +34,7 @@ enum Commands { grpc: SocketAddr, }, Copy {}, + Paste {}, Remote { #[command(subcommand)] command: RemoteCommands, @@ -49,6 +51,14 @@ enum RemoteCommands { )] remote_host: String, }, + Paste { + #[arg( + long = "remote-host", + env = "VOIDPIN_REMOTE", + default_value = "http://0.0.0.0:7900" + )] + remote_host: String, + }, } #[tokio::main] @@ -103,6 +113,21 @@ async fn main() -> anyhow::Result<()> { tracing::debug!(content = &input, "found content"); state.local_copier().copy(input.as_bytes()).await?; } + Commands::Paste {} => { + let mut stdout = tokio::io::stdout(); + if let Ok(remote_host) = std::env::var("VOIDPIN_REMOTE") { + let output = state.remote_copier(&remote_host).paste().await?; + + stdout.write_all(&output).await?; + stdout.flush().await?; + + return Ok(()); + } + + let output = state.local_copier().paste().await?; + stdout.write_all(&output).await?; + stdout.flush().await?; + } Commands::Remote { command } => match command { RemoteCommands::Copy { remote_host } => { let mut input = String::new(); @@ -121,6 +146,13 @@ async fn main() -> anyhow::Result<()> { .copy(input.as_bytes()) .await?; } + RemoteCommands::Paste { remote_host } => { + let output = state.remote_copier(&remote_host).paste().await?; + + let mut stdout = tokio::io::stdout(); + stdout.write_all(&output).await?; + stdout.flush().await?; + } }, } diff --git a/crates/voidpin/src/remote_copy.rs b/crates/voidpin/src/remote_copy.rs index 2a5508a..d7fe2de 100644 --- a/crates/voidpin/src/remote_copy.rs +++ b/crates/voidpin/src/remote_copy.rs @@ -1,6 +1,9 @@ use tonic::transport::{Channel, ClientTlsConfig}; -use crate::{grpc::CopyRequest, state::State}; +use crate::{ + grpc::{CopyRequest, PasteRequest}, + state::State, +}; #[derive(Default)] pub struct RemoteCopier { @@ -37,6 +40,29 @@ impl RemoteCopier { Ok(()) } + + pub async fn paste(&self) -> anyhow::Result> { + let tls = ClientTlsConfig::new(); + let channel = Channel::from_shared(self.host.clone())? + .tls_config(if self.host.starts_with("https") { + tls.with_native_roots() + } else { + tls + })? + .connect() + .await?; + + tracing::debug!("establishing connection to remote"); + let mut client = crate::grpc::void_pin_client::VoidPinClient::new(channel); + + tracing::info!("sending paste request"); + let resp = client.paste(PasteRequest {}).await?; + + tracing::info!("received paste response"); + let output = resp.into_inner().content; + + Ok(output) + } } pub trait RemoteCopierState {