From a6a8417d182e5c4b5e5b77d987f329aa54d78a5f Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Fri, 28 Aug 2026 19:01:00 +0000 Subject: [PATCH] Attach display adapters to Windows guests --- lib/hypervisor/config.go | 7 +++++++ lib/hypervisor/qemu/config.go | 10 ++++++++-- lib/hypervisor/qemu/config_test.go | 19 +++++++++++++++++++ lib/instances/windows.go | 10 +++++++--- lib/instances/windows_test.go | 3 +++ 5 files changed, 44 insertions(+), 5 deletions(-) diff --git a/lib/hypervisor/config.go b/lib/hypervisor/config.go index 07f392e8d..ff7074430 100644 --- a/lib/hypervisor/config.go +++ b/lib/hypervisor/config.go @@ -18,6 +18,7 @@ type VMConfig struct { // Console SerialLogPath string + Display *DisplayConfig // Vsock VsockCID int64 @@ -41,6 +42,12 @@ type VMConfig struct { EnableRosetta bool } +// DisplayConfig describes an optional virtual display adapter. +type DisplayConfig struct { + Device string + SocketPath string +} + // GuestMemoryConfig contains hypervisor-agnostic guest memory feature toggles. type GuestMemoryConfig struct { EnableBalloon bool diff --git a/lib/hypervisor/qemu/config.go b/lib/hypervisor/qemu/config.go index b3959a29a..bb8bf9738 100644 --- a/lib/hypervisor/qemu/config.go +++ b/lib/hypervisor/qemu/config.go @@ -151,8 +151,14 @@ func buildArgs(cfg hypervisor.VMConfig, machine MachineType) []string { args = append(args, "-serial", "stdio") } - // No graphics - args = append(args, "-nographic") + if cfg.Display != nil { + args = append(args, "-device", cfg.Display.Device, "-display", "none") + if cfg.Display.SocketPath != "" { + args = append(args, "-vnc", "unix:"+cfg.Display.SocketPath) + } + } else { + args = append(args, "-nographic") + } // Disable default devices we don't need args = append(args, "-nodefaults") diff --git a/lib/hypervisor/qemu/config_test.go b/lib/hypervisor/qemu/config_test.go index 4d4ae0595..4e442cccc 100644 --- a/lib/hypervisor/qemu/config_test.go +++ b/lib/hypervisor/qemu/config_test.go @@ -49,6 +49,25 @@ func TestBuildArgs_Basic(t *testing.T) { assert.Contains(t, args, "-nographic") } +func TestBuildArgs_Display(t *testing.T) { + cfg := hypervisor.VMConfig{ + VCPUs: 2, + MemoryBytes: 1024 * 1024 * 1024, + Display: &hypervisor.DisplayConfig{ + Device: "VGA", + SocketPath: "/instance/display.sock", + }, + } + + args := BuildArgs(cfg) + assert.Contains(t, args, "VGA") + assert.Contains(t, args, "-display") + assert.Contains(t, args, "none") + assert.Contains(t, args, "-vnc") + assert.Contains(t, args, "unix:/instance/display.sock") + assert.NotContains(t, args, "-nographic") +} + func TestBuildArgs_Disks(t *testing.T) { cfg := hypervisor.VMConfig{ VCPUs: 1, diff --git a/lib/instances/windows.go b/lib/instances/windows.go index adf073880..1c32511d4 100644 --- a/lib/instances/windows.go +++ b/lib/instances/windows.go @@ -218,9 +218,13 @@ func (m *manager) buildWindowsHypervisorConfig(inst *Instance, image *images.Ima Disks: []hypervisor.DiskConfig{{Path: m.paths.InstanceWindowsDisk(inst.Id), Format: hypervisor.DiskFormatQCOW2, IOBps: ioBps, IOBurstBps: burstBps}}, Networks: networks, SerialLogPath: m.paths.InstanceAppLog(inst.Id), - VsockCID: inst.VsockCID, - VsockSocket: inst.VsockSocket, - BootMode: hypervisor.BootModeUEFI, + Display: &hypervisor.DisplayConfig{ + Device: "VGA", + SocketPath: m.paths.InstanceSocket(inst.Id, "display.sock"), + }, + VsockCID: inst.VsockCID, + VsockSocket: inst.VsockSocket, + BootMode: hypervisor.BootModeUEFI, Firmware: &hypervisor.FirmwareConfig{ CodePath: m.paths.InstanceOVMFCode(inst.Id), VarsPath: m.paths.InstanceOVMFVars(inst.Id), diff --git a/lib/instances/windows_test.go b/lib/instances/windows_test.go index 0754bd96e..aa811d8d7 100644 --- a/lib/instances/windows_test.go +++ b/lib/instances/windows_test.go @@ -127,6 +127,9 @@ func TestBuildWindowsHypervisorConfig(t *testing.T) { require.NoError(t, err) assert.Equal(t, hypervisor.BootModeUEFI, config.BootMode) assert.True(t, config.Firmware.SecureBoot) + require.NotNil(t, config.Display) + assert.Equal(t, "VGA", config.Display.Device) + assert.Equal(t, p.InstanceSocket(stored.Id, "display.sock"), config.Display.SocketPath) assert.Equal(t, p.InstanceTPMDir(stored.Id), config.TPM.StateDir) require.Len(t, config.Disks, 1) assert.Equal(t, hypervisor.DiskFormatQCOW2, config.Disks[0].Format)