Skip to content

Instantly share code, notes, and snippets.

@startergo
Last active June 1, 2026 13:32
Show Gist options
  • Select an option

  • Save startergo/7d3c09b42ef72016e6255eff17633838 to your computer and use it in GitHub Desktop.

Select an option

Save startergo/7d3c09b42ef72016e6255eff17633838 to your computer and use it in GitHub Desktop.
Snow Leopard Qemu clipboard sync through SSH and Win98 clipboard sync

Clipboard Sync: macOS Host ↔ Snow Leopard VM

SPICE guest tools don't exist for macOS, so this uses SSH + pbcopy/pbpaste instead.

Prerequisites

  • Snow Leopard VM running in UTM (or any virtualizer)
  • Network connectivity between host and VM
  • Know the VM's IP address (ifconfig en0 in Snow Leopard)

Setup

1. Enable SSH in Snow Leopard

System Preferences → Sharing → Remote Login

2. Generate an SSH key on your Mac

ssh-keygen -t rsa -b 2048 -f ~/.ssh/sl_vm_key -N ""

3. Copy the public key to Snow Leopard

Replace 192.168.64.40 with your VM's actual IP. Will prompt for SL password once:

cat ~/.ssh/sl_vm_key.pub | ssh sl@192.168.64.40 \
  'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys'

4. Configure SSH

Snow Leopard's old OpenSSH only supports ssh-rsa. Modern macOS rejects this by default, so we explicitly enable it:

cat >> ~/.ssh/config << 'CFGEOF'

Host sl-vm
    HostName 192.168.64.40
    User sl
    HostKeyAlgorithms +ssh-rsa
    PubkeyAcceptedKeyTypes +ssh-rsa
    IdentityFile ~/.ssh/sl_vm_key
CFGEOF

5. Add shell aliases

cat >> ~/.zshrc << 'ALIASEOF'

# Snow Leopard VM clipboard sync
alias clip2sl='pbpaste | ssh sl-vm pbcopy'
alias clipfromsl='ssh sl-vm pbpaste | pbcopy'
ALIASEOF

source ~/.zshrc

Usage

Mac → Snow Leopard

  1. On Mac: copy text normally (⌘C)
  2. In Mac terminal: clip2sl
  3. In Snow Leopard: paste normally (⌘V)

Snow Leopard → Mac

  1. In Snow Leopard: copy text normally (⌘C)
  2. In Mac terminal: clipfromsl
  3. On Mac: paste normally (⌘V)

Test

echo "hello from mac" | pbcopy
clip2sl
# Now switch to Snow Leopard and paste — should see "hello from mac"

Troubleshooting

"no matching host key type found"

Snow Leopard offers only ssh-rsa / ssh-dss. Verify your ~/.ssh/config has the sl-vm block with both HostKeyAlgorithms +ssh-rsa and PubkeyAcceptedKeyTypes +ssh-rsa.

Asks for password every time

The SSH key isn't being used. Check:

ls -la ~/.ssh/sl_vm_key*
ssh -v sl-vm 2>&1 | grep -i identity

Verify the key is on the VM:

ssh sl-vm 'cat ~/.ssh/authorized_keys'

VM IP changed

UTM Shared Network can reassign IPs after restart. Find the new IP in Snow Leopard:

ifconfig en0 | grep "inet "

Update the HostName line in ~/.ssh/config.

Clipboard Sync: macOS Host ↔ Windows 98 QEMU VM

Windows 98 has no SSH and no SPICE guest tools, so QEMU's serial port is the cleanest path for true bidirectional clipboard sync.

Recommended: clip98 (via QEMU serial port)

The clip98 project provides bidirectional clipboard sync between any Windows machine (Windows 95 through Windows 10) and any Node.js host via QEMU's emulated serial port.

How it works

  • QEMU exposes a virtual serial port (-serial pty) to both sides
  • A small Windows 9x executable polls the clipboard and reads/writes the serial port
  • A Node.js script on the host does the same with macOS clipboard via pbcopy/pbpaste
  • When clipboard changes on either side, the new content is sent over serial

Setup

1. Start QEMU with serial port

Add -serial pty to your QEMU command line:

qemu-system-i386 \
  -drive file=win98.img,format=raw \
  -vga cirrus \
  -serial pty \
  ... (your other QEMU options)

On startup, QEMU prints a line like char device redirected to /dev/ttys019 (label serial0). Note the device path (here /dev/ttys019) — you will need it for the Node.js host script in the next step.

This path changes between QEMU restarts. You will need to update index.js accordingly, or symlink the path to a stable name after each launch.

2. Install a compatible Node.js version

clip98's host code depends on serialport@9.x which uses native bindings incompatible with modern Node (v20+). Use Node 18 via nvm:

# Install nvm if you don't have it
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

# Reload shell — restart your terminal OR run:
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

# Install and use Node 18
nvm install 18
nvm use 18

3. Set up the Node.js host side

The repo layout has two subdirectories: js/ (Node.js host code) and cpp/ (Windows guest code).

git clone https://github.com/giulioz/clip98.git
cd clip98/js
npm install

Update the serial port path in index.js to match what QEMU reported in step 1:

# Example for /dev/ttys019
sed -i '' 's|/dev/ttys000|/dev/ttys019|' index.js

Run it:

node index.js

The process sits waiting for serial activity — leave it running.

4. Get clip98.exe for the Windows 98 side

Three options:

a) Download the prebuilt binary (easiest)

curl -L https://github.com/giulioz/clip98/releases/download/latest/clip98.exe \
  -o ~/win98-share/clip98.exe

b) Build with Docker + Wine + VC++ 6.0 (no Windows needed)

The project author maintains a vc6-docker image that bundles Visual C++ 6.0 running under Wine. Build clip98.exe from your cloned repo with one command:

cd ~/clip98
docker run -it --rm -v $(pwd):/prj giulioz/vc6-docker \
  wine /opt/vc/BIN/CL.EXE z:\\prj\\cpp\\clip98.cpp \
  /IZ:\\opt\\vc\\INCLUDE \
  /link \
    /LIBPATH:Z:\\opt\\vc\\LIB user32.lib \
    /out:Z:\\prj\\clip98.exe

The resulting clip98.exe appears in the repo root.

c) Build inside Win98 with Visual C++ 6.0

Open cpp/clip98.dsw in Visual C++ 6.0 on the Win98 guest and build it there. Useful only if you already have VC++ 6.0 installed in the VM.

5. Run clip98.exe in Win98

Transfer clip98.exe into Win98 (via SMB share, ISO, or floppy image), then run it from a DOS prompt or the Run dialog. It runs in the background — no visible window.

6. Use normally

Copy on either side with Ctrl+C / ⌘C — the clipboard syncs automatically. No commands to run.

Order of operations

Each time you start the VM:

  1. Launch QEMU and note the /dev/ttysXXX path it prints
  2. Edit index.js with that path
  3. node index.js on the Mac
  4. Run clip98.exe in Win98

If you forget to update index.js, node will fail with cannot open /dev/ttys000 or similar.

Alternative: QEMU SMB shared folder (file-based)

Lower-tech approach using a shared folder for text transfer. Also useful for moving files in general (e.g. transferring clip98.exe into Win98 the first time).

Host setup

mkdir -p ~/win98-share

qemu-system-i386 \
  -netdev user,id=net0,smb=$HOME/win98-share \
  -device rtl8139,netdev=net0 \
  ... (your other QEMU options)

The folder appears in Win98 at \\10.0.2.4\qemu.

Workflow

Mac → Win98:

pbpaste > ~/win98-share/clip.txt

Then in Win98, open \\10.0.2.4\qemu\clip.txt in Notepad.

Win98 → Mac: save text from Notepad to \\10.0.2.4\qemu\clip.txt, then on Mac:

cat ~/win98-share/clip.txt | pbcopy

Aliases

cat >> ~/.zshrc << 'ALIASEOF'

# Windows 98 QEMU shared folder clipboard helpers
alias clip2win98='pbpaste > ~/win98-share/clip.txt && echo "Now open \\\\10.0.2.4\\qemu\\clip.txt in Notepad"'
alias clipfromwin98='cat ~/win98-share/clip.txt | pbcopy && echo "Mac clipboard updated from Win98"'
ALIASEOF

source ~/.zshrc

Why not SPICE / qemu-ga?

  • SPICE: no Windows 98 guest drivers exist; SPICE tools target XP and newer.
  • qemu-ga (QEMU guest agent): requires modern Windows; no Win9x port.
  • VirtIO-serial: drivers exist for XP+ but not Win9x.

The legacy serial port is the only standardized peripheral that both Win98 and modern QEMU agree on without any guest-side driver work.

Tips

  • The serial port is slow (~9600 baud by default) — only suitable for text clipboard, not large data or files.
  • For large files, combine clip98 (for text) with the SMB share (for files).
  • clip98.exe is a background process with no window. To stop it, end the task via Task Manager (Ctrl+Alt+Del) in Win98.
  • serialport@9.x is unmaintained and shows deprecation warnings on install. The bindings still build under Node 18 but not Node 20+.

Troubleshooting

Error: Operation not supported on socket

serialport only supports real serial device files, not Unix sockets or TCP. You must use -serial pty in QEMU and point index.js at the /dev/ttysXXX path it reports.

npm install fails with V8/nan compile errors

You are using a Node version newer than 18. Switch to Node 18 with nvm use 18 and retry rm -rf node_modules && npm install.

Path changes every time QEMU starts

This is normal for -serial pty. Either edit index.js each time, or after QEMU starts run:

ln -sf /dev/ttys019 /tmp/win98-serial   # use whatever path QEMU printed

Then keep index.js pointed at /tmp/win98-serial.

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment