VGA console: one console operation per VM, a data websocket per SPICE channel

spice-html5 opens a websocket per channel (main, display, inputs, cursor, playback), and
each one was making Ham Lab open a new console operation, which Incus refuses as already
connected. Now the first channel opens the operation and holds its control websocket; every
further channel is one more data websocket on the same operation, which Incus allows; the
operation is let go when the last channel closes. A stale operation from a previous Ham Lab
process is taken over with force.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Mitchell-Johnson
2026-10-09 22:32:08 -07:00
co-authored by Claude Fable 5.1
parent a5ebfdc9ce
commit 80a5b4b464
3 changed files with 109 additions and 43 deletions
+2 -13
View File
@@ -535,24 +535,14 @@ func (s *Server) virtVGA(w http.ResponseWriter, r *http.Request) {
conn.SetReadLimit(64 << 20)
ctx, cancel := context.WithCancel(r.Context())
defer cancel()
display, control, err := s.virt.VGA(ctx, name)
display, release, err := s.virt.VGAChannel(ctx, name)
if err != nil {
log.Printf("virt: vga %s refused: %v (from %s)", name, err, clientIP(r))
conn.Close(websocket.StatusInternalError, truncate(err.Error(), 120))
return
}
log.Printf("virt: vga console of %s opened (from %s)", name, clientIP(r))
defer release()
defer display.Close(websocket.StatusNormalClosure, "")
if control != nil {
defer control.Close(websocket.StatusNormalClosure, "")
go func() { // the control channel carries nothing we send; keep it open and drain it
for {
if _, _, err := control.Read(ctx); err != nil {
return
}
}
}()
}
go func() {
defer cancel()
for {
@@ -576,7 +566,6 @@ func (s *Server) virtVGA(w http.ResponseWriter, r *http.Request) {
}
cancel()
conn.Close(websocket.StatusNormalClosure, "")
log.Printf("virt: vga console of %s closed", name)
}
func truncate(s string, n int) string {