From bd9aaea4b108c7f37cd459761710787dac675a6c Mon Sep 17 00:00:00 2001 From: gram Date: Wed, 17 Sep 2025 08:44:15 +0200 Subject: [PATCH 1/4] move cheat, monitor, and logs into runtime --- src/args.rs | 18 ++++++++--------- src/cli.rs | 57 ++++++++++++++++++++++++++++++----------------------- 2 files changed, 41 insertions(+), 34 deletions(-) diff --git a/src/args.rs b/src/args.rs index 2df94e0..1cf060d 100644 --- a/src/args.rs +++ b/src/args.rs @@ -51,15 +51,6 @@ pub enum Commands { /// Show the full path to the virtual filesystem. Vfs, - /// Send a cheat code into a running game. - Cheat(CheatArgs), - - /// Show runtime stats for a running device (or emulator). - Monitor(MonitorArgs), - - /// Show live runtime logs from a running device. - Logs(LogsArgs), - /// Control a running device or emulator. Runtime(RuntimeArgs), @@ -346,6 +337,15 @@ pub struct RuntimeArgs { #[derive(Debug, Parser)] pub enum RuntimeCommands { + /// Send a cheat code. + Cheat(CheatArgs), + + /// Show runtime stats. + Monitor(MonitorArgs), + + /// Show live runtime logs. + Logs(LogsArgs), + /// Restart the running app. #[clap(alias("reload"))] Restart, diff --git a/src/cli.rs b/src/cli.rs index 52041ac..ffaf794 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -4,36 +4,43 @@ use std::fmt::Display; use std::path::PathBuf; pub fn run_command(vfs: PathBuf, command: &Commands) -> anyhow::Result<()> { + use Commands::*; match command { - Commands::Build(args) => cmd_build(vfs, args), - Commands::Export(args) => cmd_export(&vfs, args), - Commands::Import(args) => cmd_import(&vfs, args), - Commands::New(args) => cmd_new(args), - Commands::Emulator(args) => cmd_emulator(args), - Commands::Badges(args) => cmd_badges(&vfs, args), - Commands::Boards(args) => cmd_boards(&vfs, args), - Commands::Cheat(args) => cmd_cheat(args), - Commands::Monitor(args) => cmd_monitor(&vfs, args), - Commands::Logs(args) => cmd_logs(args), - Commands::Inspect(args) => cmd_inspect(&vfs, args), - Commands::Repl(args) => cmd_repl(&vfs, args), - Commands::Shots(ShotsCommands::Download(args)) => cmd_shots_download(&vfs, args), - Commands::Key(KeyCommands::New(args)) => cmd_key_new(&vfs, args), - Commands::Key(KeyCommands::Add(args)) => cmd_key_add(&vfs, args), - Commands::Key(KeyCommands::Pub(args)) => cmd_key_pub(&vfs, args), - Commands::Key(KeyCommands::Priv(args)) => cmd_key_priv(&vfs, args), - Commands::Key(KeyCommands::Rm(args)) => cmd_key_rm(&vfs, args), - Commands::Catalog(CatalogCommands::List(args)) => cmd_catalog_list(args), - Commands::Catalog(CatalogCommands::Show(args)) => cmd_catalog_show(args), - Commands::Name(NameCommands::Get) => cmd_name_get(&vfs), - Commands::Name(NameCommands::Set(args)) => cmd_name_set(&vfs, args), - Commands::Name(NameCommands::Generate) => cmd_name_generate(&vfs), - Commands::Runtime(root_args) => match &root_args.command { + Build(args) => cmd_build(vfs, args), + Export(args) => cmd_export(&vfs, args), + Import(args) => cmd_import(&vfs, args), + New(args) => cmd_new(args), + Emulator(args) => cmd_emulator(args), + Badges(args) => cmd_badges(&vfs, args), + Boards(args) => cmd_boards(&vfs, args), + Inspect(args) => cmd_inspect(&vfs, args), + Repl(args) => cmd_repl(&vfs, args), + Shots(ShotsCommands::Download(args)) => cmd_shots_download(&vfs, args), + Key(command) => match command { + KeyCommands::New(args) => cmd_key_new(&vfs, args), + KeyCommands::Add(args) => cmd_key_add(&vfs, args), + KeyCommands::Pub(args) => cmd_key_pub(&vfs, args), + KeyCommands::Priv(args) => cmd_key_priv(&vfs, args), + KeyCommands::Rm(args) => cmd_key_rm(&vfs, args), + }, + Catalog(command) => match command { + CatalogCommands::List(args) => cmd_catalog_list(args), + CatalogCommands::Show(args) => cmd_catalog_show(args), + }, + Name(command) => match command { + NameCommands::Get => cmd_name_get(&vfs), + NameCommands::Set(args) => cmd_name_set(&vfs, args), + NameCommands::Generate => cmd_name_generate(&vfs), + }, + Runtime(root_args) => match &root_args.command { RuntimeCommands::Restart => cmd_restart(root_args), RuntimeCommands::Exit => cmd_exit(root_args), RuntimeCommands::Id => cmd_id(root_args), + RuntimeCommands::Cheat(args) => cmd_cheat(args), + RuntimeCommands::Monitor(args) => cmd_monitor(&vfs, args), + RuntimeCommands::Logs(args) => cmd_logs(args), }, - Commands::Vfs => cmd_vfs(), + Vfs => cmd_vfs(), } } From 38c1490fafa6514d15b4f3bfb9c4733026c72392 Mon Sep 17 00:00:00 2001 From: gram Date: Wed, 17 Sep 2025 08:48:12 +0200 Subject: [PATCH 2/4] use the consistent args order --- src/args.rs | 32 ++------------------------------ src/cli.rs | 6 +++--- src/commands/cheat.rs | 6 +++--- src/commands/logs.rs | 7 +++---- src/commands/monitor.rs | 11 +++++------ 5 files changed, 16 insertions(+), 46 deletions(-) diff --git a/src/args.rs b/src/args.rs index 1cf060d..2716b84 100644 --- a/src/args.rs +++ b/src/args.rs @@ -262,27 +262,6 @@ pub struct ShotsDownloadArgs { pub output: Option, } -#[derive(Debug, Parser)] -pub struct MonitorArgs { - /// Path to serial port to connect to a running device. - #[arg(long, default_value = None)] - pub port: Option, - - #[arg(long, default_value_t = 115_200)] - pub baud_rate: u32, -} - -#[derive(Debug, Parser)] -pub struct LogsArgs { - /// Path to serial port to connect to a running device. - #[arg(long)] - pub port: String, - - /// The serial port Baud rate. - #[arg(long, default_value_t = 115_200)] - pub baud_rate: u32, -} - #[derive(Debug, Parser)] pub struct InspectArgs { /// ID of the ROM to inspect. @@ -310,13 +289,6 @@ pub struct CheatArgs { #[arg()] pub value: String, - /// Path to serial port to connect to a running device. - #[arg(long, default_value = None)] - pub port: Option, - - #[arg(long, default_value_t = 115_200)] - pub baud_rate: u32, - /// Path to the project root. #[arg(default_value = ".")] pub root: PathBuf, @@ -341,10 +313,10 @@ pub enum RuntimeCommands { Cheat(CheatArgs), /// Show runtime stats. - Monitor(MonitorArgs), + Monitor, /// Show live runtime logs. - Logs(LogsArgs), + Logs, /// Restart the running app. #[clap(alias("reload"))] diff --git a/src/cli.rs b/src/cli.rs index ffaf794..56a6b80 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -36,9 +36,9 @@ pub fn run_command(vfs: PathBuf, command: &Commands) -> anyhow::Result<()> { RuntimeCommands::Restart => cmd_restart(root_args), RuntimeCommands::Exit => cmd_exit(root_args), RuntimeCommands::Id => cmd_id(root_args), - RuntimeCommands::Cheat(args) => cmd_cheat(args), - RuntimeCommands::Monitor(args) => cmd_monitor(&vfs, args), - RuntimeCommands::Logs(args) => cmd_logs(args), + RuntimeCommands::Cheat(args) => cmd_cheat(root_args, args), + RuntimeCommands::Monitor => cmd_monitor(root_args), + RuntimeCommands::Logs => cmd_logs(root_args), }, Vfs => cmd_vfs(), } diff --git a/src/commands/cheat.rs b/src/commands/cheat.rs index 05ee24f..ae21fda 100644 --- a/src/commands/cheat.rs +++ b/src/commands/cheat.rs @@ -1,13 +1,13 @@ -use crate::args::CheatArgs; +use crate::args::{CheatArgs, RuntimeArgs}; use crate::config::Config; use crate::net::connect; use anyhow::{bail, Context, Result}; use firefly_types::serial; use std::path::Path; -pub fn cmd_cheat(args: &CheatArgs) -> Result<()> { +pub fn cmd_cheat(root_args: &RuntimeArgs, args: &CheatArgs) -> Result<()> { println!("⏳️ connecting..."); - let mut stream = connect(&args.port)?; + let mut stream = connect(&root_args.port)?; stream.set_timeout(2); { diff --git a/src/commands/logs.rs b/src/commands/logs.rs index 2450328..349d697 100644 --- a/src/commands/logs.rs +++ b/src/commands/logs.rs @@ -1,4 +1,4 @@ -use crate::args::LogsArgs; +use crate::args::RuntimeArgs; use crate::net::connect; use anyhow::{Context, Result}; use crossterm::cursor::MoveToColumn; @@ -8,9 +8,8 @@ use crossterm::terminal::{Clear, ClearType}; use firefly_types::serial::Response; use std::io::{stdout, Write}; -pub fn cmd_logs(args: &LogsArgs) -> Result<()> { - let port = Some(args.port.to_string()); - let mut stream = connect(&port).context("open the serial port")?; +pub fn cmd_logs(root_args: &RuntimeArgs) -> Result<()> { + let mut stream = connect(&root_args.port).context("open the serial port")?; stream.set_timeout(3600); println!("listening..."); let mut prev_time = chrono::Local::now(); // when the previous record was received diff --git a/src/commands/monitor.rs b/src/commands/monitor.rs index c009264..9ccc2cd 100644 --- a/src/commands/monitor.rs +++ b/src/commands/monitor.rs @@ -1,10 +1,9 @@ -use crate::args::MonitorArgs; +use crate::args::RuntimeArgs; use crate::net::{connect, is_timeout, Stream}; use anyhow::{Context, Result}; use crossterm::{cursor, event, execute, style, terminal}; use firefly_types::serial; use std::io; -use std::path::Path; use std::time::{Duration, Instant}; const COL1: u16 = 8; @@ -35,19 +34,19 @@ impl Stats { } } -pub fn cmd_monitor(_vfs: &Path, args: &MonitorArgs) -> Result<()> { +pub fn cmd_monitor(root_args: &RuntimeArgs) -> Result<()> { execute!(io::stdout(), terminal::EnterAlternateScreen).context("enter alt screen")?; execute!(io::stdout(), cursor::Hide).context("hide cursor")?; terminal::enable_raw_mode().context("enable raw mode")?; - let res = monitor_inner(args); + let res = monitor_inner(root_args); terminal::disable_raw_mode().context("disable raw mode")?; execute!(io::stdout(), cursor::Show).context("show cursor")?; execute!(io::stdout(), terminal::LeaveAlternateScreen).context("leave alt screen")?; res } -fn monitor_inner(args: &MonitorArgs) -> Result<()> { - let mut stream = connect(&args.port)?; +fn monitor_inner(root_args: &RuntimeArgs) -> Result<()> { + let mut stream = connect(&root_args.port)?; stream.set_timeout(3600); let mut stats = Stats::default(); request_device_stats(&mut *stream, &mut stats)?; From 1355f1c2712bcc586a4302e5b8ea1e3740446da6 Mon Sep 17 00:00:00 2001 From: gram Date: Wed, 17 Sep 2025 08:50:40 +0200 Subject: [PATCH 3/4] respect baud rate --- src/commands/cheat.rs | 2 +- src/commands/logs.rs | 2 +- src/commands/monitor.rs | 2 +- src/commands/runtime.rs | 6 +++--- src/net.rs | 11 +++++------ 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/commands/cheat.rs b/src/commands/cheat.rs index ae21fda..f83e8a8 100644 --- a/src/commands/cheat.rs +++ b/src/commands/cheat.rs @@ -7,7 +7,7 @@ use std::path::Path; pub fn cmd_cheat(root_args: &RuntimeArgs, args: &CheatArgs) -> Result<()> { println!("⏳️ connecting..."); - let mut stream = connect(&root_args.port)?; + let mut stream = connect(root_args)?; stream.set_timeout(2); { diff --git a/src/commands/logs.rs b/src/commands/logs.rs index 349d697..9f430f8 100644 --- a/src/commands/logs.rs +++ b/src/commands/logs.rs @@ -9,7 +9,7 @@ use firefly_types::serial::Response; use std::io::{stdout, Write}; pub fn cmd_logs(root_args: &RuntimeArgs) -> Result<()> { - let mut stream = connect(&root_args.port).context("open the serial port")?; + let mut stream = connect(root_args).context("open the serial port")?; stream.set_timeout(3600); println!("listening..."); let mut prev_time = chrono::Local::now(); // when the previous record was received diff --git a/src/commands/monitor.rs b/src/commands/monitor.rs index 9ccc2cd..970986f 100644 --- a/src/commands/monitor.rs +++ b/src/commands/monitor.rs @@ -46,7 +46,7 @@ pub fn cmd_monitor(root_args: &RuntimeArgs) -> Result<()> { } fn monitor_inner(root_args: &RuntimeArgs) -> Result<()> { - let mut stream = connect(&root_args.port)?; + let mut stream = connect(root_args)?; stream.set_timeout(3600); let mut stats = Stats::default(); request_device_stats(&mut *stream, &mut stats)?; diff --git a/src/commands/runtime.rs b/src/commands/runtime.rs index 7b188b7..ca882e6 100644 --- a/src/commands/runtime.rs +++ b/src/commands/runtime.rs @@ -5,7 +5,7 @@ use firefly_types::serial; pub fn cmd_exit(root_args: &RuntimeArgs) -> Result<()> { println!("⏳️ connecting..."); - let mut stream = connect(&root_args.port)?; + let mut stream = connect(root_args)?; stream.set_timeout(2); println!("⌛ exiting the running app..."); @@ -24,7 +24,7 @@ pub fn cmd_exit(root_args: &RuntimeArgs) -> Result<()> { pub fn cmd_restart(root_args: &RuntimeArgs) -> Result<()> { println!("⏳️ connecting..."); - let mut stream = connect(&root_args.port)?; + let mut stream = connect(root_args)?; stream.set_timeout(2); let (author_id, app_id) = read_app_id(&mut *stream).context("fetch ID")?; @@ -45,7 +45,7 @@ pub fn cmd_restart(root_args: &RuntimeArgs) -> Result<()> { pub fn cmd_id(root_args: &RuntimeArgs) -> Result<()> { eprintln!("⏳️ connecting..."); - let mut stream = connect(&root_args.port)?; + let mut stream = connect(root_args)?; stream.set_timeout(2); let (author_id, app_id) = read_app_id(&mut *stream).context("fetch ID")?; eprintln!("✅ got the ID:"); diff --git a/src/net.rs b/src/net.rs index ddcb645..6bf8219 100644 --- a/src/net.rs +++ b/src/net.rs @@ -1,3 +1,4 @@ +use crate::args::RuntimeArgs; use anyhow::{Context, Result}; use firefly_types::serial::{Request, Response}; use firefly_types::Encode; @@ -11,18 +12,16 @@ static IP: IpAddr = IpAddr::V4(Ipv4Addr::UNSPECIFIED); const TCP_PORT_MIN: u16 = 3210; const TCP_PORT_MAX: u16 = 3217; -#[expect(clippy::ref_option)] -pub fn connect(port: &Option) -> Result> { - let stream: Box = if let Some(port) = port { - Box::new(connect_device(port)?) +pub fn connect(root_args: &RuntimeArgs) -> Result> { + let stream: Box = if let Some(port) = &root_args.port { + Box::new(connect_device(port, root_args.baud_rate)?) } else { Box::new(connect_emulator()?) }; Ok(stream) } -fn connect_device(port: &str) -> Result { - let baud_rate = 115_200; +fn connect_device(port: &str, baud_rate: u32) -> Result { let port = serialport::new(port, baud_rate) .open() .context("open the serial port")?; From 644f9cf3f095d493226ee4a4849ed9260e1ff11a Mon Sep 17 00:00:00 2001 From: gram Date: Wed, 17 Sep 2025 09:36:28 +0200 Subject: [PATCH 4/4] add runtime screenshot command --- src/args.rs | 4 ++++ src/cli.rs | 1 + src/commands/mod.rs | 2 +- src/commands/runtime.rs | 53 ++++++++++++++++++++++------------------- 4 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/args.rs b/src/args.rs index 2716b84..6cdfe7f 100644 --- a/src/args.rs +++ b/src/args.rs @@ -318,6 +318,10 @@ pub enum RuntimeCommands { /// Show live runtime logs. Logs, + /// Take a screenshot. + #[clap(alias("shot"), alias("snap"), alias("photo"))] + Screenshot, + /// Restart the running app. #[clap(alias("reload"))] Restart, diff --git a/src/cli.rs b/src/cli.rs index 56a6b80..d3a8bb8 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -36,6 +36,7 @@ pub fn run_command(vfs: PathBuf, command: &Commands) -> anyhow::Result<()> { RuntimeCommands::Restart => cmd_restart(root_args), RuntimeCommands::Exit => cmd_exit(root_args), RuntimeCommands::Id => cmd_id(root_args), + RuntimeCommands::Screenshot => cmd_screenshot(root_args), RuntimeCommands::Cheat(args) => cmd_cheat(root_args, args), RuntimeCommands::Monitor => cmd_monitor(root_args), RuntimeCommands::Logs => cmd_logs(root_args), diff --git a/src/commands/mod.rs b/src/commands/mod.rs index a0e795f..cac4d46 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -32,6 +32,6 @@ pub use monitor::cmd_monitor; pub use name::{cmd_name_generate, cmd_name_get, cmd_name_set}; pub use new::cmd_new; pub use repl::cmd_repl; -pub use runtime::{cmd_exit, cmd_id, cmd_restart}; +pub use runtime::{cmd_exit, cmd_id, cmd_restart, cmd_screenshot}; pub use shots::cmd_shots_download; pub use vfs::cmd_vfs; diff --git a/src/commands/runtime.rs b/src/commands/runtime.rs index ca882e6..5bd50c1 100644 --- a/src/commands/runtime.rs +++ b/src/commands/runtime.rs @@ -11,15 +11,7 @@ pub fn cmd_exit(root_args: &RuntimeArgs) -> Result<()> { println!("⌛ exiting the running app..."); let req = serial::Request::Exit; stream.send(&req).context("send request")?; - - for _ in 0..5 { - let resp = stream.next()?; - if matches!(resp, serial::Response::Ok) { - println!("✅ exited"); - return Ok(()); - } - } - bail!("timed out waiting for response") + wait_for_ok(stream) } pub fn cmd_restart(root_args: &RuntimeArgs) -> Result<()> { @@ -28,37 +20,38 @@ pub fn cmd_restart(root_args: &RuntimeArgs) -> Result<()> { stream.set_timeout(2); let (author_id, app_id) = read_app_id(&mut *stream).context("fetch ID")?; - println!("⌛ restarting {author_id}.{app_id}..."); let req = serial::Request::Launch((author_id, app_id)); stream.send(&req).context("send request")?; - - for _ in 0..5 { - let resp = stream.next()?; - if matches!(resp, serial::Response::Ok) { - println!("✅ restarted"); - return Ok(()); - } - } - bail!("timed out waiting for response") + wait_for_ok(stream) } pub fn cmd_id(root_args: &RuntimeArgs) -> Result<()> { eprintln!("⏳️ connecting..."); let mut stream = connect(root_args)?; stream.set_timeout(2); + let (author_id, app_id) = read_app_id(&mut *stream).context("fetch ID")?; eprintln!("✅ got the ID:"); println!("{author_id}.{app_id}"); Ok(()) } -pub fn read_app_id(stream: &mut dyn Stream) -> Result<(String, String)> { - println!("⌛ fetching running app ID..."); - stream - .send(&serial::Request::AppId) - .context("send request")?; +pub fn cmd_screenshot(root_args: &RuntimeArgs) -> Result<()> { + eprintln!("⏳️ connecting..."); + let mut stream = connect(root_args)?; + stream.set_timeout(2); + println!("⌛ sending request..."); + let req = serial::Request::Screenshot; + stream.send(&req).context("send request")?; + wait_for_ok(stream) +} + +fn read_app_id(stream: &mut dyn Stream) -> Result<(String, String)> { + println!("⌛ fetching running app ID..."); + let req = serial::Request::AppId; + stream.send(&req).context("send request")?; for _ in 0..5 { let resp = stream.next()?; if let serial::Response::AppID(id) = resp { @@ -67,3 +60,15 @@ pub fn read_app_id(stream: &mut dyn Stream) -> Result<(String, String)> { } bail!("timed out waiting for response") } + +fn wait_for_ok(stream: Box) -> Result<()> { + let mut stream = stream; + for _ in 0..5 { + let resp = stream.next()?; + if matches!(resp, serial::Response::Ok) { + println!("✅ done"); + return Ok(()); + } + } + bail!("timed out waiting for response") +}