feat: prompt command (MessageBox), admin status in banner
This commit is contained in:
parent
fdd2124da8
commit
e0edf60461
6 changed files with 100 additions and 0 deletions
|
|
@ -35,4 +35,5 @@ windows = { version = "0.54", features = [
|
|||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
"Win32_System_Threading",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
"Win32_UI_Shell",
|
||||
] }
|
||||
|
|
|
|||
|
|
@ -23,9 +23,26 @@ mod windows_mgmt;
|
|||
fn banner() {
|
||||
println!();
|
||||
println!(" {} HELIOS REMOTE v{} ({})", "☀".yellow().bold(), env!("CARGO_PKG_VERSION"), env!("GIT_COMMIT"));
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let admin = is_admin();
|
||||
let admin_str = if admin {
|
||||
"admin".green().bold()
|
||||
} else {
|
||||
"user (no admin)".yellow()
|
||||
};
|
||||
println!(" {} running as {}", "─".repeat(20).dimmed(), admin_str);
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
println!(" {}", "─".repeat(45).dimmed());
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_admin() -> bool {
|
||||
use windows::Win32::UI::Shell::IsUserAnAdmin;
|
||||
unsafe { IsUserAnAdmin().as_bool() }
|
||||
}
|
||||
|
||||
macro_rules! log_status {
|
||||
($($arg:tt)*) => {
|
||||
println!(" {} {}", "→".cyan().bold(), format!($($arg)*));
|
||||
|
|
@ -325,6 +342,37 @@ async fn handle_message(
|
|||
}
|
||||
}
|
||||
|
||||
ServerMessage::PromptRequest { request_id, message, title } => {
|
||||
let title = title.unwrap_or_else(|| "Helios Remote".to_string());
|
||||
log_cmd!("prompt › {}", &message[..message.len().min(60)]);
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use windows::core::PCWSTR;
|
||||
use windows::Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_OK, MB_ICONINFORMATION, HWND_DESKTOP};
|
||||
let msg_wide: Vec<u16> = message.encode_utf16().chain(std::iter::once(0)).collect();
|
||||
let title_wide: Vec<u16> = title.encode_utf16().chain(std::iter::once(0)).collect();
|
||||
// Run blocking MessageBox in a thread so we don't block the async runtime
|
||||
let msg_clone = message.clone();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
unsafe {
|
||||
MessageBoxW(
|
||||
HWND_DESKTOP,
|
||||
PCWSTR(msg_wide.as_ptr()),
|
||||
PCWSTR(title_wide.as_ptr()),
|
||||
MB_OK | MB_ICONINFORMATION,
|
||||
);
|
||||
}
|
||||
}).await.ok();
|
||||
log_ok!("User confirmed: {}", &msg_clone[..msg_clone.len().min(40)]);
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
// On non-Windows just log it
|
||||
println!(" [PROMPT] {}", message);
|
||||
}
|
||||
ClientMessage::Ack { request_id }
|
||||
}
|
||||
|
||||
ServerMessage::ExecRequest { request_id, command } => {
|
||||
// Truncate long commands for display
|
||||
let cmd_display = if command.len() > 60 {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,14 @@ pub struct WindowInfo {
|
|||
pub enum ServerMessage {
|
||||
/// Request a screenshot from the client
|
||||
ScreenshotRequest { request_id: Uuid },
|
||||
/// Show a MessageBox on the client asking the user to do something.
|
||||
/// Blocks until the user clicks OK — use this when you need the user
|
||||
/// to perform a manual action before continuing.
|
||||
PromptRequest {
|
||||
request_id: Uuid,
|
||||
message: String,
|
||||
title: Option<String>,
|
||||
},
|
||||
/// Execute a shell command on the client
|
||||
ExecRequest {
|
||||
request_id: Uuid,
|
||||
|
|
|
|||
|
|
@ -479,6 +479,30 @@ pub async fn clipboard_set(
|
|||
}
|
||||
}
|
||||
|
||||
/// POST /sessions/:id/prompt
|
||||
#[derive(Deserialize)]
|
||||
pub struct PromptBody {
|
||||
pub message: String,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn prompt_user(
|
||||
Path(session_id): Path<String>,
|
||||
State(state): State<AppState>,
|
||||
Json(body): Json<PromptBody>,
|
||||
) -> impl IntoResponse {
|
||||
match dispatch(&state, &session_id, "prompt", |rid| ServerMessage::PromptRequest {
|
||||
request_id: rid,
|
||||
message: body.message.clone(),
|
||||
title: body.title.clone(),
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => (StatusCode::OK, Json(serde_json::json!({ "ok": true }))).into_response(),
|
||||
Err(e) => e.into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
/// POST /sessions/:id/label
|
||||
#[derive(Deserialize)]
|
||||
pub struct LabelBody {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ async fn main() -> anyhow::Result<()> {
|
|||
.route("/sessions/:id/click", post(api::request_click))
|
||||
.route("/sessions/:id/type", post(api::request_type))
|
||||
.route("/sessions/:id/label", post(api::set_label))
|
||||
.route("/sessions/:id/prompt", post(api::prompt_user))
|
||||
.route("/sessions/:id/windows", get(api::list_windows))
|
||||
.route("/sessions/:id/windows/minimize-all", post(api::minimize_all))
|
||||
.route("/sessions/:id/windows/:window_id/focus", post(api::focus_window))
|
||||
|
|
|
|||
|
|
@ -287,6 +287,18 @@ def cmd_find_window(args):
|
|||
print(f"{wid:<20} {title}")
|
||||
|
||||
|
||||
def cmd_prompt(args):
|
||||
"""Show a MessageBox on the remote PC asking the user to do something.
|
||||
Blocks until the user clicks OK — use this when the AI needs the user
|
||||
to perform a manual action (e.g. click a button, confirm a dialog)."""
|
||||
sid = resolve_session(args.session_id)
|
||||
body = {"message": args.message}
|
||||
if args.title:
|
||||
body["title"] = args.title
|
||||
_req("POST", f"/sessions/{sid}/prompt", json=body)
|
||||
print(f"User confirmed prompt on session {sid!r}.")
|
||||
|
||||
|
||||
def cmd_run(args):
|
||||
"""Launch a program on the remote session (fire-and-forget)."""
|
||||
sid = resolve_session(args.session_id)
|
||||
|
|
@ -374,6 +386,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
fwp.add_argument("session_id")
|
||||
fwp.add_argument("title", help="Substring to search for (case-insensitive)")
|
||||
|
||||
pp = sub.add_parser("prompt", help="Show a MessageBox asking the user to do something manually")
|
||||
pp.add_argument("session_id")
|
||||
pp.add_argument("message", help="What to ask the user (e.g. 'Please click Save, then OK')")
|
||||
pp.add_argument("--title", default=None, help="Dialog title (default: Helios Remote)")
|
||||
pp.set_defaults(func=cmd_prompt)
|
||||
|
||||
rp = sub.add_parser("run", help="Launch a program on the remote session (fire-and-forget)")
|
||||
rp.add_argument("session_id")
|
||||
rp.add_argument("program", help="Program to launch (e.g. notepad.exe)")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue