diff --git a/desktop/README.md b/desktop/README.md index 30c74a1..c52da88 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -22,7 +22,9 @@ installers next to the wheel and the VS Code extension: settings and logs too. - **Debian, Ubuntu and their relatives:** `mpftp__amd64.deb`, which uses the system's WebKitGTK. Install it with - `sudo apt install ./mpftp__amd64.deb` and start `mpftp`. + `sudo apt install ./mpftp__amd64.deb` and start `mpftp`. With + arguments, such as `mpftp --help` or `mpftp ls -d /dev/ttyACM0`, the same + command is the CLI, run with the Python the package carries. To serial ports on Linux, your user needs to be in the `dialout` group (or `uucp` on some distributions). diff --git a/desktop/scripts/smoke_linux.sh b/desktop/scripts/smoke_linux.sh index 4988f0f..b100539 100755 --- a/desktop/scripts/smoke_linux.sh +++ b/desktop/scripts/smoke_linux.sh @@ -3,7 +3,8 @@ # the window comes up on a virtual display, the app starts its Python host # and the sidecar with the Python it carries, and closing the app leaves no # Python behind. CI runs this on every build; it needs xvfb-run and, for a -# .deb, sudo to install it. +# .deb, sudo to install it. It also checks that `mpftp` with arguments is +# the CLI, not the window. # # smoke_linux.sh deb path/to/mpftp_X.Y.Z_amd64.deb # smoke_linux.sh appimage path/to/mpftp_X.Y.Z_amd64.AppImage @@ -32,6 +33,16 @@ esac [[ -x "$python" ]] || { echo "::error::no bundled python3 in the $kind"; exit 1; } "$python" -I -c "import sys, mpftp.desktop_host, mpftp.sidecar; print(sys.version)" +# With arguments the package's mpftp is the CLI: it prints and exits, and +# no window or Python host starts. +cli_out="$(timeout 60 "$app" --help 2>&1)" \ + || { echo "::error::'mpftp --help' from the $kind failed"; echo "$cli_out"; exit 1; } +grep -q '^usage: mpftp' <<<"$cli_out" \ + || { echo "::error::'mpftp --help' from the $kind didn't print the CLI's help"; echo "$cli_out"; exit 1; } +[[ -z "$(pgrep -f -- '-m mpftp.desktop_host' || true)" ]] \ + || { echo "::error::'mpftp --help' from the $kind started the desktop host"; exit 1; } +timeout 60 "$app" --version + logs="$HOME/.local/share/com.pydevices.mpftp/logs" rm -rf "$logs" xvfb-run -a "$app" >"$work/app.out" 2>&1 & diff --git a/desktop/src-tauri/src/main.rs b/desktop/src-tauri/src/main.rs index 9d7285a..001db50 100644 --- a/desktop/src-tauri/src/main.rs +++ b/desktop/src-tauri/src/main.rs @@ -579,7 +579,52 @@ fn supervise(app: AppHandle, host: Arc, emu: Arc, python: PathBuf, lo } } +/// On Linux the package puts this app on PATH as `mpftp`, the CLI's own +/// name. Started with arguments (`mpftp --help`, `mpftp ls -d ...`) it is the +/// CLI: it runs the bundled Python's mpftp in its place and never opens a +/// window. With none, from the launcher or a bare `mpftp`, it is the app. +#[cfg(unix)] +fn run_cli_if_asked() { + use std::os::unix::process::CommandExt; + + let args: Vec = std::env::args_os().skip(1).collect(); + if args.is_empty() { + return; + } + let python = std::env::var_os("MPFTP_DESKTOP_PYTHON") + .map(PathBuf::from) + .or_else(|| { + let exe = std::env::current_exe().ok()?.canonicalize().ok()?; + let dir = exe.parent()?; + // Installed: /usr/bin/mpftp with its files in /usr/lib/mpftp + // (an AppImage keeps the same layout). A dev build keeps them + // beside the binary. + [ + dir.join("../lib/mpftp/python/bin/python3"), + dir.join("python/bin/python3"), + ] + .into_iter() + .find(|p| p.is_file()) + }); + let Some(python) = python else { + eprintln!("mpftp: can't find the bundled Python, so the command line isn't available from this install"); + std::process::exit(2); + }; + let err = Command::new(&python) + .args(["-m", "mpftp"]) + .args(&args) + // The CLI starts its sidecar with this same Python. + .env("MPFTP_PYTHON", &python) + .env("PYTHONNOUSERSITE", "1") + .exec(); + eprintln!("mpftp: could not run {}: {err}", python.display()); + std::process::exit(2); +} + fn main() { + #[cfg(unix)] + run_cli_if_asked(); + let host = Arc::new(Host::default()); let host_for_exit = host.clone(); let emu = Arc::new(Emu::default());