Your Windows apps as native, GPU-accelerated windows on your Mac.
One app, one Mac window — with the app's own name in the title bar, its own icon in the Dock, resizable for real. Full GPU encode (NVENC / AMF / QuickSync), 60 fps+, low latency, on the PC's already-running session: no RDP login, no locked screen, no second account.
The usual options each miss something:
| Native windows | Full GPU / high fps | Reuses live session | No login required | |
|---|---|---|---|---|
| RDP RemoteApp | ✅ | ❌ | ❌ (new session) | ❌ (needs password) |
| Parsec / VNC (full screen) | ❌ | ✅ | ✅ | ✅ |
| WinFleet | ✅ | ✅ | ✅ | ✅ |
WinFleet doesn't reinvent the hard part. It stands on Sunshine (host) and Moonlight (client) — the open-source, gaming-grade streaming stack — and adds the orchestration that turns them into a per-app, windowed experience.
winfleet search telegram # find it on the PC and open it
winfleet open "Microsoft Edge"
winfleet windows # what's open, and what's free
winfleet stop 2 # close window 2 (or `stop` for all)Not a desktop with the app in it, and not a scaled picture of one. The window is the app: drag its corner and the app on Windows is resized to match, live, in any shape you like. No pause, no reconnect, no black bars.
That works because each app gets a screen of its own. WinFleet plugs virtual monitors into the Parsec Virtual Display Adapter — already present on most gaming PCs, so there is usually nothing to install — and runs one Sunshine instance per monitor. Isolation then costs nothing: no taskbar, no other windows, no wallpaper, because nothing else ever draws on that screen. The PC's real desktop is left completely alone, and several apps stream at the same time, one per monitor.
The obvious way to make the Windows side follow is to change the virtual monitor's resolution. It works, and it costs about a second every time — the Windows mode set plus the host rebuilding its encoder, both visible in Sunshine's own log. A window that freezes for a second on every drag is not a resize, it is a series of jumps.
So WinFleet does not touch the resolution at all. The virtual monitor stays the size it
was born at; what follows the Mac window is the app's own window on Windows, which is
one SetWindowPos — a frame. Everything else on that screen would show around it, so the
client draws only the rectangle the app occupies.
That last part is the one thing Moonlight cannot do, and the only reason WinFleet carries
a fork: fork/crop.patch teaches it to sample a sub-rectangle of the stream, which
libplacebo already supports natively (pl_frame.crop). Two call sites and a small helper
that re-reads the rectangle when it changes; everything else is stock Moonlight, fetched
at build time. fork/build.sh clones, patches, builds and installs it.
Without the fork WinFleet still works — it falls back to following the resolution, with that one-second cost per resize.
winfleet dock Telegram "Microsoft Edge" # launchers with the real Windows icons- Title bar shows
Telegram. On macOS, Moonlight titles the window with the host's name and nothing else, so WinFleet renames the instance to the app before connecting. - Dock, ⌘-Tab and the menu bar show the app's name and its Windows icon, because the stream is drawn by a copy of Moonlight that carries the app's identity. On APFS the copy is a clone: instant, and it costs no disk space.
- Spotlight finds it, and a click on the Dock icon opens the window.
The icon is pulled off the Windows executable at full 256×256 through the shell's image factory — the same path Explorer uses — so it is the real icon, transparency included, not a blurry 32-pixel stamp.
winfleet panelA menu bar item, not a Dock app, because it is something you reach for while doing something else. It rebuilds itself every time you open it — a list of windows is only true at the moment you look at it — and it shows: the windows open on the Mac (click to focus), every app installed on the PC (type to jump: macOS menus search themselves, so there is no search box to build), the windows currently open on Windows (click to raise one over there), and File Explorer / Task Manager / Control Panel / Command Prompt.
winfleet scan reads the PC's Start Menu and its packaged apps. That second part
matters: Notepad, Calculator, Photos and most of what ships with Windows 11 have no
shortcut on disk at all, so a Start-Menu-only scan silently misses half the Start menu.
$ winfleet search blocco
Blocco note → finestra 1 sul Mac (WindowsAtti.local, 1280x800, 60 fps)git clone https://github.com/zorahrel/winfleet.git
cd winfleet
./install.sh # Moonlight (if needed) + the `winfleet` command
winfleet setup # addresses, mDNS name, how many windows, SSHwinfleet push copies host/*.ps1 to C:\winfleet over SSH and checks they compile
there. Then, on the PC:
.\setup.ps1 -WebPass '<choose-a-password>' # Sunshine, encoder, firewall
.\setup-vdd.ps1 -Slots 2 # 2 virtual monitors = 2 windows
schtasks /run /tn winfleet-vdd
.\wf-instance.ps1 -Slot 0 # one Sunshine instance per monitor
.\wf-instance.ps1 -Slot 1Open Moonlight, click the padlocked WinFleet N, and type the PIN into that instance's
web UI (https://<host>:48090, :48190, …). The CLI never prints the PIN, so this step
is done from the GUI. Then winfleet scan and you're set.
The mDNS name (
PCdiCasa.local) is not cosmetic: Moonlight compares known hosts by address and ignores the port, so with a bare IP every instance looks like the same PC.
winfleet open <app> open an app in a window
winfleet search <text> find an app on the PC and open it
winfleet windows which windows are open, and on what
winfleet dock <app...> make .app launchers (real name, real icon)
winfleet apps launchers made so far
winfleet fit snap windows back to the pixels they receive
winfleet stop [n] close one window, or all of them
winfleet scan re-read the PC's app list
winfleet push upload the host scripts to C:\winfleet
winfleet panel menu bar panel: open windows, app search, Windows tools
winfleet clean drop Moonlight hosts that answer with another identity
winfleet doctor diagnosticsWINFLEET_TRACE=1 writes what the window supervisor sees to
~/.config/winfleet/trace.log — the only sane way to debug a window that "resizes itself".
- Native pixels. The Mac window is exactly as many pixels as the virtual monitor, so nothing is rescaled at either end and there are no black bars. Without an explicit resolution Moonlight asks for 1280x720 and the host downscales — soft no matter how much bitrate you throw at it.
- Bitrate.
BITRATE=autoscales with pixel count (20 Mbps at 720p up to 80 Mbps above 1440p). On a wired LAN with NVENC you can push higher — set a number instead. - fps. 60 by default, the host advertises up to 120. Set
FPSin the config. - Codec. Sunshine negotiates HEVC when the client supports it (Apple Silicon does), noticeably cleaner than H.264 at the same bitrate.
- Path. LAN first (full MTU, no VPN overhead), Tailscale when you're away. Probed before every launch.
- Mouse.
--absolute-mouse, the right pointer mode for productivity apps.
wf-vdd.ps1 |
plugs the virtual monitors, keeps them alive, changes their resolution on request |
wf-instance.ps1 |
creates a Sunshine instance bound to one monitor, on its own ports |
wf-inst-ctl.ps1 |
starts / stops an instance |
wf-rename.ps1 |
renames an instance — that is what names the window on the Mac |
wf-place.ps1 |
opens the app and holds it at the size the Mac window asks for |
wf-icon.ps1 |
hands back an app's icon as a PNG |
wf-agent.ps1 |
resizes the app window on request and answers with the size it really got |
mac/wf-panel.m |
the menu bar panel |
scan-apps.ps1 |
lists the installed apps, shortcuts and packaged alike |
bin/winfleet |
picks a free window, follows resizes, keeps the Mac window honest |
mac/wf-chrome.m |
injected into the client: drops the macOS title bar, publishes the window rectangle |
fork/crop.patch |
the one change Moonlight needs: draw a sub-rectangle of the stream |
All of them are commented where they bite, so nobody re-discovers them the hard way.
- A service cannot open your windows, and your session cannot see your screens.
Sunshine as a service lives in session 0 and can only launch apps by duplicating the
console token — a privilege only LocalSystem holds, so an instance running as you fails
with
ACCESS_DENIEDeven elevated. Meanwhile a plain process in session 0 enumerates no displays at all. So Sunshine only streams, and WinFleet opens apps from a scheduled task in your own session. - Sunshine is a console program. Owned by a scheduled task it takes
CTRL_CLOSEwhen that console goes away — a remote shell disconnecting was enough to kill a session mid-stream. The task now only launches it, detached. - An indirect display driver refuses
ChangeDisplaySettingsExwith a position, or staged withCDS_NORESET:DISP_CHANGE_FAILEDevery time. Resolution alone, applied immediately, works — Windows re-lays-out the desktop itself. Set-Content -Encoding UTF8writes a BOM, which Sunshine treats as a brokenapps.jsonand silently replaces with its defaults.- Resolving an mDNS name costs 230 ms, every single time. By address it is 19 ms. That one lookup, repeated per request, was nearly all of the latency you could feel while dragging a window. Resolved once and kept.
- Never kill
explorer.exe. It is the Windows shell, not an app: the "close other instances first" rule that makes single-instance apps open on the right screen would take the taskbar, the icons and the desktop with it. - A failing
sshcan kill a window silently. The supervisor runs underset -e, so one non-zero exit from a fire-and-forget host command ended it with no log line anywhere and no window on screen. Every host call is now explicitly non-fatal. - Moonlight sizes the window itself, and not only once: if the stream doesn't fit the display SDL thinks is active, it rebuilds the window at 80% — seconds after opening it at the right size. WinFleet holds the size for a while before it starts watching, otherwise that correction reads as "the user resized" and the window walks down a resolution per reconnect.
- Spaces don't survive the trip. Through ssh → cmd → PowerShell, quoting collapses and
Microsoft Edgearrives as two arguments. Names and paths travel base64. - AppleScript resolves
every process whose unix id is Nby index, so with several Moonlight processes you drive the wrong window.tell (first process whose …)is the form that works. - Finder's desktop rectangle is the union of all displays. With two monitors it promises room a window cannot have, and macOS truncates the window silently. WinFleet measures the real maximum once, with a window in hand, and remembers it.
winfleet open Desktop still streams the PC's live session in a single window, at native
resolution, aspect-locked. No virtual monitors involved.
winfleet doctorshows the host unreachable — PC off, Sunshine stopped, or firewall. On the PC:Get-Service SunshineService.- Moonlight can't reach the LAN IP but Tailscale works — grant Moonlight the macOS Local Network permission (System Settings → Privacy & Security → Local Network).
- A window won't open and the log says the control stream failed — that instance is
down or unpaired.
winfleet windowstells you which; restart it withwf-inst-ctl.ps1 -Slot N -Action restart. - Old hosts with warning icons pile up in Moonlight —
winfleet clean. It only drops entries whose address answers with a different identity; a PC that is merely off stays.
winfleet/
├── bin/winfleet # Mac: open / search / windows / dock / clean / doctor
├── fork/
│ ├── crop.patch # l'unica modifica a Moonlight: disegna un ritaglio
│ └── build.sh # clona, applica, compila, installa
├── mac/wf-chrome.m # iniettata nel client: via la barra, pubblica il rettangolo
├── host/
│ ├── setup.ps1 # Sunshine: install, encoder, firewall
│ ├── setup-vdd.ps1 # registers the virtual-monitor manager
│ ├── wf-vdd.ps1 # virtual monitors: attach, keep-alive, resolution
│ ├── wf-instance.ps1 # one Sunshine instance bound to one monitor
│ ├── wf-inst-ctl.ps1 # start / stop / inspect an instance
│ ├── wf-rename.ps1 # rename an instance = name the Mac window
│ ├── wf-place.ps1 # open the app and hold it over its monitor
│ ├── wf-icon.ps1 # an app's icon, as PNG
│ └── scan-apps.ps1 # installed apps, shortcuts and packaged
└── install.sh # Mac: Moonlight + the winfleet command
Config in ~/.config/winfleet/config.env (addresses, host SSH, how many windows, fps,
bitrate, per-app window sizes) — never committed.
MIT — see LICENSE.
Built on the shoulders of Sunshine and Moonlight. WinFleet is the glue that makes them feel like native Windows-app windows on macOS.