Skip to content

Paste/drop images into terminal as files (cmux drop-to-file), incl. SSH #28

@mvbmir

Description

@mvbmir

Summary

Port cmux's "drop/paste image → file → inject path" feature to limux so images can be pasted or dropped into a terminal running an agent CLI (Claude Code, Codex, etc.), including over SSH.

Today a pasted screenshot is dropped or turned into garbage: image bytes can't ride a PTY, and a TUI like Claude Code can't receive them. cmux solves this by intercepting the paste/drop client-side, writing the PNG to a real file, and injecting the file path as plain text. The agent's file-read tool then opens a real local file. No image bytes ever travel through the terminal stream.

How cmux does it (reference)

limux current state

  • File drag-drop from a file manager works: DropTarget<FileList> in terminal.rs (~1830) pastes shell-escaped file paths via ghostty_surface_text.
  • clipboard_formats_include_image helper exists in terminal.rs (~979) but is only used in a test — never wired into paste/drop.
  • No clipboard image paste handling, and no image byte drop handling (only FileList).
  • Nothing detects the foreground process / SSH target of a pane.

So the feature does not exist. This is a net-new port.

Proposed design

Milestone 1 — Local (small, ships independently)

  1. Paste: on the terminal paste path, if the clipboard offers an image format (reuse clipboard_formats_include_image), read the GdkTexture, encode PNG, write /tmp/limux-drop-<uuid>.png, inject the shell-escaped path via ghostty_surface_text (same sink the file-drop already uses).
  2. Drop: extend the terminal.rs DropTarget to also accept GdkTexture / image MIME types (currently FileList only); same write-file-and-inject path.
  3. Path injection mirrors dropped_file_text (shell-escaping).

Works for agents running on the same machine as limux. Self-contained; good first PR.

Milestone 2 — SSH parity (the harder part)

Mirror cmux:

  1. Detect the pane's foreground process by TTY. Find the controlling pty of the ghostty surface, then the foreground process group leader; if it's ssh, parse the target (user@host, -p port, -o opts, host alias). (limux already does /proc scanning in claude_session.rs::find_tab_claude_pid — same technique applies.)
  2. Upload over the existing connection. scp/sftp the PNG to remote /tmp/limux-drop-<uuid>.png reusing the user's SSH ControlMaster socket so there's no re-auth. Likely requires limux to ensure ssh invocations use ControlMaster=auto + a known ControlPath (inject -o defaults, or document/config it).
  3. Inject the remote path as text.
  4. Fallback to local (Milestone 1) when the foreground process isn't ssh.

Integration points

  • rust/limux-host-linux/src/terminal.rs — paste handling + DropTarget (extend ~1830), reuse clipboard_formats_include_image, write-file helper, ghostty_surface_text injection.
  • New helper module (or extend claude_session.rs) for /proc foreground + ssh-target detection.
  • Possibly control_bridge if we want a CLI surface for it.

Open questions / risks

  • PTY/TTY access: ghostty owns the pty; limux doesn't currently track the surface's child pid/tty. Need a way to get it (ghostty FFI accessor? ghostty_surface_pwd exists for cwd — check for a pid/tty accessor, else derive). This is the main feasibility unknown for M2.
  • ControlMaster: relies on the user's ssh using connection multiplexing; otherwise scp re-prompts/fails. Decide: inject -o ControlMaster=auto -o ControlPath=~/.ssh/limux-%r@%h:%p into ssh, or require user config.
  • Auth: agent forwarding / ControlMaster reuse so scp doesn't prompt.
  • Security: writing to remote /tmp; cleanup of stale drop files; size limits.
  • Agent read access: the agent must be allowed to Read /tmp/*-drop-*.png.

Plan

  • M1: local paste/drop → file → path injection (PR)
  • M2 spike: confirm we can get the surface's tty/foreground pid (gate for SSH work)
  • M2: ssh detection + ControlMaster scp + remote path injection (PR)

Once M1 lands and the M2 spike confirms feasibility, open PR(s). Upstreaming to am-will/limux after it works in dev.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions