feat: find-window, run, clipboard, label-routing, persistent session-id, exe icon
This commit is contained in:
parent
ef4ca0ccbb
commit
672676d3d7
9 changed files with 214 additions and 15 deletions
|
|
@ -16,6 +16,7 @@ serde_json = "1"
|
|||
toml = "0.8"
|
||||
chrono = "0.4"
|
||||
helios-common = { path = "../common" }
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
dirs = "5"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
|
|
|
|||
|
|
@ -8,4 +8,15 @@ fn main() {
|
|||
let hash = hash.trim();
|
||||
println!("cargo:rustc-env=GIT_COMMIT={}", if hash.is_empty() { "unknown" } else { hash });
|
||||
println!("cargo:rerun-if-changed=.git/HEAD");
|
||||
|
||||
// Embed Windows icon when cross-compiling for Windows
|
||||
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") {
|
||||
let mut res = winres::WindowsResource::new();
|
||||
res.set_icon("../../assets/logo.ico");
|
||||
// Set cross-compile toolkit (mingw-w64)
|
||||
res.set_toolkit_path("/usr");
|
||||
res.set_windres_path("x86_64-w64-mingw32-windres");
|
||||
res.set_ar_path("x86_64-w64-mingw32-ar");
|
||||
res.compile().unwrap_or_else(|e| eprintln!("winres warning: {e}"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use tokio_tungstenite::{connect_async_tls_with_config, tungstenite::Message, Con
|
|||
|
||||
use base64::Engine;
|
||||
use helios_common::{ClientMessage, ServerMessage};
|
||||
use uuid::Uuid;
|
||||
|
||||
mod shell;
|
||||
mod screenshot;
|
||||
|
|
@ -49,22 +50,14 @@ macro_rules! log_cmd {
|
|||
};
|
||||
}
|
||||
|
||||
fn session_id() -> String {
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
let t = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.subsec_nanos();
|
||||
format!("{:06x}", t & 0xFFFFFF)
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct Config {
|
||||
relay_url: String,
|
||||
api_key: String,
|
||||
label: Option<String>,
|
||||
session_id: Option<String>, // persistent UUID
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
|
@ -129,7 +122,7 @@ fn prompt_config() -> Config {
|
|||
}
|
||||
};
|
||||
|
||||
Config { relay_url, api_key, label }
|
||||
Config { relay_url, api_key, label, session_id: None }
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
|
|
@ -158,6 +151,20 @@ async fn main() {
|
|||
}
|
||||
};
|
||||
|
||||
// Resolve or generate persistent session UUID
|
||||
let sid: Uuid = match &config.session_id {
|
||||
Some(id) => Uuid::parse_str(id).unwrap_or_else(|_| Uuid::new_v4()),
|
||||
None => {
|
||||
let id = Uuid::new_v4();
|
||||
let mut cfg = config.clone();
|
||||
cfg.session_id = Some(id.to_string());
|
||||
if let Err(e) = cfg.save() {
|
||||
log_err!("Failed to save session_id: {e}");
|
||||
}
|
||||
id
|
||||
}
|
||||
};
|
||||
|
||||
let config = Arc::new(config);
|
||||
let shell = Arc::new(Mutex::new(shell::PersistentShell::new()));
|
||||
|
||||
|
|
@ -183,14 +190,13 @@ async fn main() {
|
|||
|
||||
match connect_async_tls_with_config(&config.relay_url, None, false, Some(connector)).await {
|
||||
Ok((ws_stream, _)) => {
|
||||
let sid = session_id();
|
||||
let label = config.label.clone().unwrap_or_else(|| hostname());
|
||||
log_ok!(
|
||||
"Connected {} {} {} Session {}",
|
||||
"·".dimmed(),
|
||||
label.bold(),
|
||||
"·".dimmed(),
|
||||
sid.dimmed()
|
||||
sid.to_string().dimmed()
|
||||
);
|
||||
println!();
|
||||
backoff = Duration::from_secs(1);
|
||||
|
|
@ -499,6 +505,51 @@ async fn handle_message(
|
|||
}
|
||||
}
|
||||
|
||||
ServerMessage::RunRequest { request_id, program, args } => {
|
||||
log_cmd!("run › {}", program);
|
||||
use std::process::Command as StdCommand;
|
||||
match StdCommand::new(&program).args(&args).spawn() {
|
||||
Ok(_) => {
|
||||
log_ok!("Started {}", program);
|
||||
ClientMessage::Ack { request_id }
|
||||
}
|
||||
Err(e) => {
|
||||
log_err!("run failed: {e}");
|
||||
ClientMessage::Error { request_id, message: format!("Failed to start '{}': {e}", program) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ServerMessage::ClipboardGetRequest { request_id } => {
|
||||
log_cmd!("clipboard-get");
|
||||
let out = tokio::process::Command::new("powershell.exe")
|
||||
.args(["-NoProfile", "-NonInteractive", "-Command", "Get-Clipboard"])
|
||||
.output().await;
|
||||
match out {
|
||||
Ok(o) => {
|
||||
let text = String::from_utf8_lossy(&o.stdout).trim().to_string();
|
||||
log_ok!("Got {} chars", text.len());
|
||||
ClientMessage::ClipboardGetResponse { request_id, text }
|
||||
}
|
||||
Err(e) => ClientMessage::Error { request_id, message: format!("Clipboard get failed: {e}") }
|
||||
}
|
||||
}
|
||||
|
||||
ServerMessage::ClipboardSetRequest { request_id, text } => {
|
||||
log_cmd!("clipboard-set › {} chars", text.len());
|
||||
let cmd = format!("Set-Clipboard -Value '{}'", text.replace('\'', "''"));
|
||||
let out = tokio::process::Command::new("powershell.exe")
|
||||
.args(["-NoProfile", "-NonInteractive", "-Command", &cmd])
|
||||
.output().await;
|
||||
match out {
|
||||
Ok(_) => {
|
||||
log_ok!("Set clipboard");
|
||||
ClientMessage::Ack { request_id }
|
||||
}
|
||||
Err(e) => ClientMessage::Error { request_id, message: format!("Clipboard set failed: {e}") }
|
||||
}
|
||||
}
|
||||
|
||||
ServerMessage::Ack { request_id } => {
|
||||
ClientMessage::Ack { request_id }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue