Repository navigation
Releases: Amzi-01/Vypr
Release list
Vypr 0.5.0
Vypr no longer needs Looking Glass, and it is faster on both sides of the VM.
Frames travel through the guest's own RAM
Until now frames crossed the VM boundary through an IVSHMEM device, which needed
Looking Glass's kernel driver in Windows just to map a PCI BAR. Now there is no
driver on either side:
- The agent allocates each window's frame ring as locked physical pages, with
Windows' documented AWE calls, which report which pages it got. - vyprd finds those pages in QEMU's memory file and checks every one before
anything is shown. Each page carries a stamp, and all of them have to read
back exactly as the guest wrote them. - The host never writes the VM's memory. When the agent exits, Windows takes its
pages straight back, so a host store into one would land in some unrelated
process. vyprd maps guest RAM read-only, and everything the host decides lives
in a host-only region (/dev/shm/vypr-host). - The guest rarely has two free pages side by side, so a 4K ring is often
20,000 separate pieces. The window stitches them back into one mapping, so
the Vulkan presenter still uploads with no CPU copy.
A ring costs guest RAM only while its window is streamed, about 100 MiB at 4K.
The old IVSHMEM device held 512 MiB of host RAM all the time.
Faster
The new Vulkan presenter hands each ring to the GPU directly and copies frames
on its dedicated copy engine. A 4K upload went from ~11 ms of CPU to 0.1 ms. It
is the default, falling back to SDL_GPU and then SDL_Renderer.
In the guest, frames of 1080p and up are now read back from the GPU in eight
bands. Copying band k into the ring overlaps the GPU reading band k+1, and the
copy uses non-temporal stores. Measured old pipeline against new on the same
guest, from capture to publish:
| 0.4.13 | 0.5.0 | |
|---|---|---|
| 1920x1080 window | 5.98 ms | 5.02 ms |
| 2560x1440 window | 7.42 ms | 6.29 ms |
| 3200x1800 window | 10.8 ms | 8.7 ms |
| whole 4K desktop | 13.8 ms | 11.7 ms |
Frame rate is unchanged, at 58-60 fps with nothing dropped. So is 720p, which is
read back whole as before.
Three guest faults were costing more than the transport, and are fixed:
- Each frame was published only when the next one arrived, which on a still
screen held the last change back indefinitely. - The agent ran below normal priority, behind any game.
- A publish could become visible before its pixels.
Every latency figure Vypr printed before this release was too low. Frames
were stamped with WGC's SystemRelativeTime, which for a window drawing on
vsync is the next vblank, up to a frame in the future. The stamp is now
bounded by the time the frame arrived, so VYPR_STATS=1 and the agent's log
report honest numbers.
Also
- Damage streaming, off by default.
VYPR_DAMAGE=1sends only the parts of
a frame that changed. That is a big saving for a mostly still desktop app, and
no help for a game. - Faster first window. Windows the agent announced in its first burst of
messages waited for a round trip before attaching. They attach straight away
now. - Drag-out removed. It never worked end to end and had been switched off for
three releases. Dragging files in is unaffected. - Fixed:
vypr --debug anti-detectno longer prints a spurious "unbound
variable" error after it finishes.
Upgrading
Host and agent speak protocol 3 now, so replace both together.
- Add
group = "<your group>"next to theuser =line in
/etc/libvirt/qemu.conf, thensudo systemctl restart virtqemud. Vypr reads
frames out of QEMU's memory, and Linux only allows that when QEMU runs under
your group.install.shprints the exact line. - Run
./install/install.sh. It removes the old shared-memory device from the
VM and makes sure the VM's memory is shared with the host. - Run the new
vypr-setup.exe(attached) inside Windows, then restart
Windows. It gives the agent the "Lock pages in memory" right, which only
applies from the next sign-in. It no longer installs the Looking Glass
driver. - Restart the VM, so it boots without the old device.
- If an earlier install left it, remove the old region:
sudo rm -f /etc/tmpfiles.d/10-vypr.conf; rm -f /dev/shm/vypr. Once a VM
has run with the old device, it holds 512 MiB of RAM.
vypr doctor checks steps 1 and 2.
Parsec's driver is still the way to make the mouse work in games that read raw
input. It stays an optional part of vypr-setup.exe.
A passthrough GPU on a PCIe x4 link spends most of each frame being read back:
about 6 of the 7.5 ms at 1440p on the machine these numbers come from. An x16
slot would cut that roughly fourfold.
Vypr 0.4.13
A review of 0.4.12 found six faults in it. This is those.
The disk override was aiming at the wrong device
libvirt names a SATA device sata<controller>-<bus>-<unit>, and the override
that renames disks off "QEMU HARDDISK" built that name by counting disks
instead of reading the drive address. Those units are not contiguous - removing
the optical drive leaves 0 and 2 - so the counter drifted by one, the override
landed on a device that did not exist, and the disk it was meant for went on
reporting QEMU's identify string to Windows.
The script created that condition itself, by removing the optical drive, which
made every run after the first wrong. It reads the address now.
The fabricated serial was a constant
Worse than the empty serial it replaced. No serial is merely unusual; the same
fabricated string on every installation identifies the tool that wrote it. It
is derived from the domain's own UUID now, so it differs per guest, and carries
a VY- prefix so --undo can recognise its own work rather than removing a
serial somebody set deliberately.
--undo did not undo most of it
It reversed the first pass - CPUID, SMBIOS, the NIC prefix - and silently left
the ACPI override, the disk identity and the serials in place, while reporting
success. The installer tells people that command returns the domain to stock.
The ACPI arguments mattered most: x-oem-id is x- prefixed, which is QEMU's
marker for unstable, so a guest left carrying them will one day fail to start
on a QEMU that has dropped them - long after anyone remembers this was switched
on. --undo removes all of it now, and recognises 0.4.12's WD- serials as
well as the current format, because domains hardened by that release are
carrying them.
The task bar icon fix missed half the apps
The app id was lowercased, but registered entries keep the app id's case:
battlefieldV is written to vypr-battlefieldV.desktop. The window advertised
vypr-battlefieldv, matched no entry, and fell back to the placeholder the
change existed to remove. Case is preserved now.
Three smaller ones
An override was written for the optical drive and then the drive was deleted in
the same run, leaving an entry naming nothing. The ACPI argument was matched by
substring, so a stale value counted as correct and a guest moved to another
host would name one board in ACPI and a different one in SMBIOS - real firmware
agrees with itself. Argument parsing read past the end of argv when a flag came
last, turning a missing value into a traceback. And the temp directory was
cleaned by a RETURN trap in a function whose failure paths call die, which
exits rather than returning.
Verified
In the guest, after applying the fixed version: both disks report WDC
WDS500G2B0A with distinct UUID-derived serials, where before the fix the second
one kept QEMU's. --undo against the live domain leaves no residue of any
measure - checked element by element, including the serials written by the
previous release. The Vypr desktop still comes up over IVSHMEM.
The guest half is unchanged since 0.4.10 - vypr-setup.exe from that release
is still the current one.
Vypr 0.4.12
The guest stops announcing that it is a virtual machine.
What it was saying
A stock libvirt domain identifies itself immediately, in about six places, and
software that refuses to run under a hypervisor looks at exactly those. CPUID
leaf 0x40000000 returns KVMKVMKVM. SMBIOS reads QEMU and SeaBIOS. The NIC
carries QEMU's registered 52:54:00 prefix. ACPI table headers say BOCHS and
BXPC. Disks report QEMU HARDDISK with no serial, which Device Manager shows
verbatim and no real disk would say. And a CD-ROM sits there with a volume
label reading virtio-win-0.1.302.
None of that is load-bearing. The installer now closes all of it by default,
giving the guest this machine's own board and BIOS details so it claims to be
the computer it is actually running on, and the disks a plausible model and
serial. The optical drive is removed rather than renamed: an empty one still
reports QEMU DVD-ROM from a name Windows caches in the registry, so renaming
the device reaches QEMU but not an already-enumerated drive.
What it deliberately does not do
It does not clear the hypervisor-present CPU bit. That hides virtualisation
more thoroughly and it would cost every Hyper-V enlightenment the domain has -
vapic, spinlocks, stimer, avic - because all of them depend on Windows
being able to see a hypervisor at all. For a guest that exists to run games
that is a poor trade, and anything reading that bit reads the vendor string
next anyway. anti-detect.py --paranoid clears it for guests that would rather
hide than be fast.
An off switch, and why there is one
vypr --debug anti-detect apply
vypr --debug anti-detect --undo back to a stock domain
anti-detect.py --mild only the measures that cost nothing
Whether this should be on is not a technical question. The reason it exists is
software that refuses to run under a hypervisor. The reason to turn it off is
that some games forbid virtual machines in their terms and treat hiding one as
a bannable offence regardless of what it is being used for. Vypr cannot know
which games those are, so the installer now says so plainly instead of enabling
the capability silently, and leaves the compliance question where it belongs.
--undo does not restore the optical drive, because what was in it is not
recorded anywhere. The MAC goes back to QEMU's prefix rather than its previous
value, for the same reason - that prefix is what libvirt would have assigned.
What is still detectable
This is not an invisibility feature and should not be mistaken for one.
The IVSHMEM device is a Red Hat PCI device in Device Manager, and Vypr cannot
work without it. Timing behaviour under virtualisation differs measurably from
bare metal and no amount of XML changes that. Anything enumerating PCI devices
or measuring instruction timing finds a virtual machine whatever the strings
say. What this release buys is that software checking the common identifiers
will run.
Three things found the hard way
The disk identify property is model, not product. A SATA disk is an
ide-hd device; product belongs to scsi-hd, and asking ide-hd for it
does not degrade - the VM fails to start.
A second -machine argument merges with the one libvirt generates rather than
conflicting with it, which is what makes the ACPI override reachable at all.
Those properties are x- prefixed, so unstable by QEMU's own convention: a
later version may remove them, and the VM will then fail to start rather than
quietly losing the setting. Worth checking first if a VM stops booting after a
QEMU upgrade.
Changing the MAC means Windows sees a new adapter and takes a new DHCP lease.
Vypr asks libvirt for the guest's address rather than trusting the config, so
it copes, but anything else pinned to the old address will not.
Verified
In the guest, after a boot with everything applied: Win32_ComputerSystem
reports ASUS and ROG STRIX B550-F GAMING WIFI II with AMI BIOS 3621,
Win32_DiskDrive reports WDC WDS500G2B0A with serial numbers, and
Win32_CDROMDrive returns nothing at all. The Vypr desktop still comes up over
IVSHMEM afterwards. Apply, undo to nothing, and re-apply were each run against
the live domain.
The guest half is unchanged since 0.4.10 - vypr-setup.exe from that release
is still the current one.
Vypr 0.4.11
Windows in the task bar look like themselves, and the GPU can come back.
The task bar shows the right icon
Every Vypr window used to get the placeholder a compositor shows for a client it
cannot identify. The reason is that Wayland has no way for a client to hand over
an icon at all: xdg_toplevel carries an app_id and nothing else, and the
compositor looks that up as <app_id>.desktop to find the name and the Icon=
line. Vypr set no app_id, so there was nothing to look up. SDL_SetWindowIcon
cannot stand in for it either - it is X11-only and silently does nothing here,
which is probably why the missing call went unnoticed.
The whole-screen view now says vypr-desktop, matching the entry the installer
already writes. A streamed application says vypr-<app>, matching the entry
written when it was registered - so each one carries the icon taken from its own
executable rather than every window sharing Vypr's. It fixes task bar grouping
as a side effect, since that is keyed on app_id too.
Taking apps off the desktop again
vypr remove <app> refuses while the VM is down, so that a scheduled task in the
guest is never orphaned. That is right for one app and useless after a VM is
rebuilt: the tasks went with the old disk, every menu entry fails at launch with
an error about the guest, and the only way out was deleting three or four files
per app by hand.
vypr --debug clear-apps clears the lot, and only on this side. It lists what it
will remove before asking, needs nothing running, and leaves the guest alone -
there is nothing to orphan when the tasks are already gone, and when they are
not, vypr detect finds them again.
Experimental: moving the GPU on demand
A GPU given to a VM is normally given to it at boot - vfio-pci claims it before
the host driver loads, and Linux never sees it again until a reboot. That is a
whole graphics card doing nothing whenever the VM is off.
vypr --debug gpu-swap --enable wires a libvirt hook that hands the card over
when the guest starts and takes it back when it stops. It is off unless asked
for, acts only on the one domain named in its config, and checks before it moves
anything: nvidia-smi's process table is consulted, not sysfs, because the
enabled attribute on a connector is wrong in both directions under the
proprietary driver - it reads disabled for lit panels and enabled for an
output the compositor has switched off.
Where the hostdevs are managed='yes', libvirt already does the detaching, so
the hook checks the card is free and gets out of the way. Doing it as well is a
race for the same PCI device lock, and the hook's write to drivers_probe ends
up waiting on a reattach that is waiting for the hook to return.
It does not work everywhere, and the failure is expensive. If the card is the
firmware's primary display adapter, the host driver will not release it - the
detach reports a non-zero usage count, unbinds anyway, and the vfio bind that
follows never completes. The calling task is left unkillable and libvirt's daemon
goes with it, which takes a reboot to clear. Test with
virsh nodedev-detach pci_0000_XX_00_0 rather than by starting a VM: it fails in
isolation instead of taking the daemon down. Hence experimental, and off by
default.
The installer says which card that can be
Whether a machine can pass a GPU through at all is a question about IOMMU
groups, which nobody wants to answer by reading sysfs. The installer now answers
it: every graphics card, its group, and either "alone in its group, so it can be
handed over" or the list of what it would drag into the guest with it - the SATA
controller and the wired NIC, on a lot of consumer boards.
It then recommends one and asks. The recommendation is the card the domain
already passes through, which is not a guess, and failing that the isolated card
with the fewest processes on it.
The vfio-pci prerequisite is no longer a hard failure, because the swap makes it
wrong: a machine set up for it correctly has nothing on vfio-pci while the VM is
off, and the installer used to refuse to run on exactly the configuration it had
just produced.
A website
There is one now, at https://amzi-01.github.io/Vypr/, with the comparison table
from the README and no screenshots of other people's games.
Also
Matching address lines in a domain's XML does not find passed-through devices.
Every emulated controller has an <address type='pci'>, and each <hostdev>
carries a second one for where the device lands inside the guest, so the search
returned mostly guest slots that read like host addresses. Both places that did
this now parse <hostdev><source><address>.
A libvirt hook must never call virsh. libvirtd holds the domain's job for as
long as the hook runs, so the call waits on a lock only the hook it is blocking
can release; the orphaned child then keeps the hook's stdout pipe open, so
libvirt waits for EOF as well. The domain XML arrives on stdin for this reason,
and that is where it is read from now.
Verified
The task bar change was read back out of KWin, which reports
resourceClass='vypr-desktop' for the screen view. clear-apps was run against
eight registered applications and removed twenty-three files, leaving Vypr's own
two entries and no orphaned icons. The GPU swap was exercised against a real
domain on hardware where it fails, which is where its refusals and the libvirt
deadlock were found.
The guest half is unchanged since 0.4.10 - vypr-setup.exe from that release is
still the current one.
Vypr 0.4.10
The whole-screen view gets an ordinary title bar.
Something to drag it by
vypr --debug desktop was borderless, like every other Vypr window. That is
deliberate everywhere else: the captured image already contains the guest
window's own title bar, so a compositor decoration around it would be a second
set of chrome operating on a picture of the first.
A whole screen has no title bar inside it to grab. Undecorated, its only handle
was the strip the hit test invents for windows that report no chrome of their
own - 32 guest pixels across the top, taken out of the picture and turned into a
drag handle. That band is the top of the guest's desktop, which is somewhere one
actually clicks.
So the screen view is now created decorated, and gets no hit test at all. It is
dragged by a real title bar, and every pixel of the picture stays the guest's.
Application windows are untouched and stay borderless.
Verified
Against vypr-testsrc, the stand-in guest that exists so the host can be
exercised with Windows powered off - which it was. Two clients were run against
it and screenshotted: one on the desktop sentinel window id, which came up with
a title bar and working minimise, maximise and close, and one on an ordinary
window id, which stayed borderless with the picture flush to the top edge.
One consequence
DESKTOP_SIZE sizes the content area, not the frame, so the window is now a
title bar taller overall than the number in the config. If you had tuned it to
fit an exact area of your screen, it will overhang by that much.
Installing
Host-side only. docs/technical.md and one host source file, nothing under
guest/ or install/. The agent is byte-for-byte the same binary as 0.4.2 and
vypr-setup.exe is byte-identical to the 0.4.9 installer, so there is nothing
to install in Windows - rebuild the host and restart the session.
Vypr 0.4.9
The new logo, as vector.
The mark
The gradient letterform in a rounded tile is gone. In its place is the mark
chosen for the project: two ribbons on opposing slopes, the left one running the
full height to the point, the right one stopping short at two thirds and cut off
square, with a constant gap between them.
It was traced from the artwork rather than redrawn by eye - the geometry is two
directions at a slope of 0.61, bands 56 wide and 27 apart, and the wordmark is a
geometric sans at cap height 69 with the letters as outlines. Rendering the
result back at the source's own size and comparing it pixel by pixel puts it
within 1.34%, which is edge antialiasing and nothing else. Nothing depends on a
font being installed anywhere.
Six files, all on a transparent ground
vypr-mark-* the V alone, tight bounds, for headers
vypr-lockup-* the V above the VYPR wordmark
vypr-icon-* the V centred in a square, for icons
Black and white of each, because a single-colour mark on a transparent ground
needs both to be usable. The README header now chooses between them with
prefers-color-scheme, so it stays visible on either GitHub theme.
tools/gen-brand.py holds the geometry and writes all six, so a change to the
mark happens in one place instead of in six files by hand. The Linux app icon,
the 256px PNG and the Windows .ico are all regenerated from it.
Installing
This is the first release in a while where the installer is not the previous
one reused. vypr.ico is embedded in vypr-setup.exe as a resource, so
reusing the 0.4.8 binary would have shipped the old logo. It was rebuilt, and
all six icon sizes were checked as present in the new executable and absent
from the old one.
The agent inside it is untouched - byte-for-byte the same binary as 0.4.2, and
verified as embedded identically in both the 0.4.8 and 0.4.9 installers. So
there is no functional reason to reinstall: run the new setup only if you want
the new icon on the installer itself. On the Linux side, reinstall to pick up
the new desktop icon.
Vypr 0.4.8
Windows given the size they were asked for, instead of being stretched to it.
Applications were inheriting the screen view's size
DESKTOP_SIZE in the config is the size to show the guest's whole screen at,
and the README has always described it that way. The code did not agree: the
size reached the daemon as --window-size, and the daemon put --size on every
window client it spawned. Every application inherited it.
The result was a window pinned to dimensions that had nothing to do with it. A
CodeX window that opens at 1024x768 was pinned to 3400x1912 and scaled up about
two and a half times to fill the gap, which does not read as a large window - it
reads as a soft, smeared one, and it gets worse the larger the configured size
is. It looked convincingly like a rendering fault.
Only the whole-screen view is given the flag now. Every other window goes back
to adopting the guest window's own size, which is 1:1 and sharp.
--size now asks the guest to match
Where the flag is used deliberately, it sized the host window and nothing
else. The guest carried on rendering at whatever size the application had opened
itself at, and the difference was made up by scaling - so asking for a bigger
window produced a softer picture rather than a bigger one.
The guest is now asked to match, once, as soon as a frame proves the agent is
publishing and there is a real window id to name. VYPR_MSG_RESIZE and the
guest's handler already existed for the drag path, and that handler already
converts a captured-surface size into a window size, so nothing on the guest
side changed.
The whole-screen view is the exception and is still scaled: its size is the
guest's monitor, which no window resize can change. vypr --debug resolution
behaves exactly as before.
An application that clamps the request - a fixed-size dialog, say - is still
scaled to fit. --size means "fit this area of my screen", and shrinking the
window to whatever the guest settled on would break the thing that was asked
for.
Verified
Measured on a 4K guest at 125% scaling: a client started with --size 2000x1200
moved the guest window to 1614x967 logical pixels - 1600x960 after DPI, plus the
14x7 invisible resize border the guest handler adds back. Confirmed separately
that ordinary windows are now spawned with no --size at all.
Installing
Host-side only. The guest agent is unchanged since 0.4.2 and vypr-setup.exe is
byte-for-byte the 0.4.7 installer, so there is nothing to install in Windows -
rebuild the host and restart the session.
Vypr 0.4.7
Hardening, from an audit of everything the guest sends across to the host, plus
a fix for window positions that were being remembered under a name they would
never have again.
Hardened
Most of the boundary was already sound: message readers bound every length
against what actually arrived, the notification parse sums its string lengths in
64 bits before comparing, the publish path checks a frame against its slot from
both ends, and nothing passes guest text to a shell unquoted. Three places were
not.
- A window's dimensions were used in arithmetic without being checked. The
headroom added before allocating a frame ring iswidth + 256on a 32-bit
value, so a width near the maximum wraps and asks for a ring far smaller than
the window it is meant to hold. Nothing would have written past it — the
publish path checks — but an allocation whose size came from an overflow is
not a thing to keep and reason about later. Sizes beyond any real display are
refused. - A window title could forge log lines. Titles are guest-chosen text and are
written to a log thatvypr openreads back to find the window an application
opened. A newline in a title writes extra lines into that log, in the log's own
format, chosen by whatever is running in the VM. Control characters become
spaces; UTF-8 is untouched, so accented titles are unaffected. - A notification heading was passed to
notify-sendas a positional
argument, so a heading beginning with a dash was read as an option rather
than as text. It goes after--now.
Fixed
Window positions were remembered under a name that never came back.
Geometry was filed under the window's own title, and a title is not a name: it
carries the open document, and changes again when the application stops
responding. One Blender produced three entries — (Unsaved) - Blender 5.2.1 LTS,
the same with (Not Responding) appended, and Blender File View — none of
which matched the next launch. So a window was remembered under a name it would
never have again, and always opened wherever the guest put it.
It is filed under the watched title that matched instead, which is what the
session was told to look for and does not move when the window renames itself.
Entries written under the old scheme are still read once, so a window that had a
remembered position keeps it.
Not changed
The guest agent is identical to 0.4.2. Nothing to install in Windows if you have
run that already: ./install/install.sh from a fresh clone is the whole update.
The installer is attached unchanged for anyone new.
Vypr 0.4.6
One fix: a session that was running and could not be talked to.
Fixed
The daemon's sockets stayed open inside every window it launched.
Vypr starts a separate process for each guest window, and every descriptor the
daemon held was still open in that process afterwards — both listening sockets,
the connection to the guest agent, the audio link, and every other window's
socket.
The listening sockets are the ones that did damage. A window process holding the
listener keeps the address alive after the daemon has gone, so the socket file
looks like a working session while the process behind it will never answer:
connections are refused, and a new daemon cannot take the address either. From
the outside that is a session which is running and cannot be talked to — input
goes nowhere, and restarting appears to change nothing.
Sockets are now created close-on-exec, so a descriptor has to be asked for
rather than inherited by default.
Verified: the listening socket is held by the daemon alone, and killing the
daemon takes the socket with it instead of leaving one behind to refuse callers.
Not changed
The guest agent is identical to 0.4.2. Nothing to install in Windows if you have
run that already: ./install/install.sh from a fresh clone is the whole update.
The installer is attached unchanged for anyone new.
Vypr 0.4.5
A regression that shipped in 0.4.2, and the feature that came out of chasing it.
Fixed
Fullscreening an application left it boxed inside its old window, then pulled
it back out of fullscreen.
Restoring a remembered window size raises the same event as dragging the window's
edge, and nothing could tell them apart. So the client started the timer that
waits for a drag to finish — which suppresses adopting the size the guest is
sending — and when it expired, asked the guest to become the remembered size
instead. The window kept its old shape, the picture was centred inside it with a
margin, and the application was dragged back out of the fullscreen you had just
asked for.
Remembering window geometry and fitting rather than stretching both arrived in
0.4.2; the drag timer is much older. Fitting is what made it visible — stretching
would have filled the window and hidden the fact that the size was wrong.
New
vypr --debug resolution [WxH] — choose what size the guest's screen is shown
at.
The whole-screen view took the guest's own size, and the guest's own size is a
monitor: a 4K desktop made a 4K window, covering one of your screens entirely in
order to show you one of its. It was useful for looking into something and
unusable while looking at anything else.
vypr --debug resolution what it is now
vypr --debug resolution 2560x1440 show it at that size
vypr --debug resolution auto back to the guest's own size
The picture is scaled to fit and input is mapped through the same rectangle, so
clicks still land where they are pointed. Nothing is set by default — without it,
behaviour is exactly as before.
Not changed
The guest agent is identical to 0.4.2. Nothing to install in Windows if you have
run that already: ./install/install.sh from a fresh clone is the whole update.
The installer is attached unchanged for anyone new.