spice

package module
v0.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 28, 2025 License: MIT Imports: 25 Imported by: 1

README

GoDoc

Spice

Spice protocol is a kind of remote control protocol commonly used with libvirt and Qemu.

More information on spice: https://www.spice-space.org/

Features:

  • Display support (single display)
  • Mouse and keyboard control (client mode)
  • Audio playback
  • Clipboard
  • Audio recording
  • USB Support
  • File transfer
  • Video streaming

Getting Started

To connect to a SPICE host, you need to implement two interfaces:

  1. spice.Connector - Provides network connectivity to the SPICE server
  2. spice.Driver - Handles display rendering, input events, and clipboard operations
The Connector Interface

The Connector interface establishes network connections to the SPICE server:

type Connector interface {
    SpiceConnect(compress bool) (net.Conn, error)
}
The Driver Interface

The Driver interface handles display updates, user input, cursor, and clipboard:

type Driver interface {
    // Display management
    DisplayInit(image.Image)
    DisplayRefresh()

    // Input and channel setup
    SetEventsTarget(*ChInputs)
    SetMainTarget(*ChMain)
    SetCursor(img image.Image, x, y uint16)

    // Clipboard operations
    ClipboardGrabbed(selection SpiceClipboardSelection, clipboardTypes []SpiceClipboardFormat)
    ClipboardFetch(selection SpiceClipboardSelection, clType SpiceClipboardFormat) ([]byte, error)
    ClipboardRelease(selection SpiceClipboardSelection)
}

Connection Examples

Basic TCP Connection
package main

import (
    "fmt"
    "net"
    "github.com/Shells-com/spice"
)

// SimpleConnector implements spice.Connector for direct TCP connections
type SimpleConnector struct {
    Host string
    Port int
}

func (c *SimpleConnector) SpiceConnect(compress bool) (net.Conn, error) {
    return net.Dial("tcp", fmt.Sprintf("%s:%d", c.Host, c.Port))
}

func main() {
    // Create connector
    connector := &SimpleConnector{
        Host: "localhost",
        Port: 5900,
    }

    // Create driver (implement spice.Driver interface - see below)
    driver := &MyDriver{}

    // Connect to SPICE server
    client, err := spice.New(connector, driver, "yourpassword")
    if err != nil {
        panic(err)
    }

    // Client is now connected and ready to use
    fmt.Println("Connected to SPICE server!")

    // Access various features
    if fileTransfer := client.GetFileTransfer(); fileTransfer != nil {
        // File transfer is available
    }
}
TLS Connection
import (
    "crypto/tls"
    "fmt"
    "net"
)

type TLSConnector struct {
    Host string
    Port int
}

func (c *TLSConnector) SpiceConnect(compress bool) (net.Conn, error) {
    config := &tls.Config{
        // Configure TLS settings as needed
        InsecureSkipVerify: false, // Set to true only for testing
    }

    addr := fmt.Sprintf("%s:%d", c.Host, c.Port)
    return tls.Dial("tcp", addr, config)
}
WebSocket Connection
import (
    "net"
    "github.com/gorilla/websocket"
)

type WebSocketConnector struct {
    URL string
}

func (c *WebSocketConnector) SpiceConnect(compress bool) (net.Conn, error) {
    conn, _, err := websocket.DefaultDialer.Dial(c.URL, nil)
    if err != nil {
        return nil, err
    }

    // Wrap websocket connection to implement net.Conn
    return &wsConn{conn: conn}, nil
}

// wsConn wraps a websocket.Conn to implement net.Conn interface
type wsConn struct {
    conn *websocket.Conn
}

func (w *wsConn) Read(p []byte) (n int, err error) {
    _, data, err := w.conn.ReadMessage()
    if err != nil {
        return 0, err
    }
    return copy(p, data), nil
}

func (w *wsConn) Write(p []byte) (n int, err error) {
    err = w.conn.WriteMessage(websocket.BinaryMessage, p)
    return len(p), err
}

// Implement other net.Conn methods...
Minimal Driver Implementation

For headless operation or testing, you can implement a minimal driver:

package main

import (
    "image"
    "github.com/Shells-com/spice"
)

type MinimalDriver struct {
    inputs *spice.ChInputs
    main   *spice.ChMain
}

func (d *MinimalDriver) DisplayInit(img image.Image) {
    // Store or render the initial display image
}

func (d *MinimalDriver) DisplayRefresh() {
    // Refresh the display
}

func (d *MinimalDriver) SetEventsTarget(inputs *spice.ChInputs) {
    d.inputs = inputs
}

func (d *MinimalDriver) SetMainTarget(main *spice.ChMain) {
    d.main = main
}

func (d *MinimalDriver) SetCursor(img image.Image, x, y uint16) {
    // Update cursor position and image
}

func (d *MinimalDriver) ClipboardGrabbed(selection spice.SpiceClipboardSelection,
    clipboardTypes []spice.SpiceClipboardFormat) {
    // Handle clipboard grab event
}

func (d *MinimalDriver) ClipboardFetch(selection spice.SpiceClipboardSelection,
    clType spice.SpiceClipboardFormat) ([]byte, error) {
    // Return clipboard data
    return nil, nil
}

func (d *MinimalDriver) ClipboardRelease(selection spice.SpiceClipboardSelection) {
    // Handle clipboard release
}

For a complete GUI implementation, see the spicefyne package which provides a full-featured driver using the Fyne UI toolkit.

Usage Examples

Sending Keyboard Input

Once connected, you can send keyboard events through the inputs channel:

// After connection, the driver's SetEventsTarget will be called with the inputs channel
// Store it in your driver implementation, then use it to send events

// Send a key press
inputs.KeyDown(spice.KEY_A)

// Send a key release
inputs.KeyUp(spice.KEY_A)

// Type a character
inputs.KeyPress(spice.KEY_H)  // Press and release
Sending Mouse Input
// Move mouse to absolute position
inputs.MouseMove(100, 200)

// Mouse button press
inputs.MouseDown(spice.MOUSE_BUTTON_LEFT)

// Mouse button release
inputs.MouseUp(spice.MOUSE_BUTTON_LEFT)

// Mouse click (press and release)
inputs.MouseClick(spice.MOUSE_BUTTON_LEFT)

// Mouse wheel scroll
inputs.MouseScroll(0, -1)  // Scroll up
inputs.MouseScroll(0, 1)   // Scroll down
Audio Playback

The SPICE client automatically connects to the audio playback channel if available:

client, err := spice.New(connector, driver, password)
if err != nil {
    panic(err)
}

// Audio is automatically streamed to the system audio output
// The client handles all audio decoding and playback internally
Audio Recording

To capture audio from the client and send it to the server:

// Get the audio recording interface
recorder := client.GetAudioRecorder()
if recorder != nil {
    // Start recording with specific audio format
    err := recorder.Start(spice.AUDIO_FMT_S16, 2, 44100)
    if err != nil {
        log.Printf("Failed to start recording: %v", err)
    }

    // Send audio data
    audioData := []int16{ /* PCM audio samples */ }
    recorder.SendSamples(audioData)

    // Stop recording
    recorder.Stop()
}
Clipboard Operations

Clipboard integration allows copy/paste between client and server:

// In your Driver implementation:

func (d *MyDriver) ClipboardGrabbed(selection spice.SpiceClipboardSelection,
    clipboardTypes []spice.SpiceClipboardFormat) {
    // Server has grabbed the clipboard
    // You can now request clipboard data if needed
}

func (d *MyDriver) ClipboardFetch(selection spice.SpiceClipboardSelection,
    clType spice.SpiceClipboardFormat) ([]byte, error) {
    // Server is requesting clipboard data from the client
    if clType == spice.SPICE_CLIPBOARD_FORMAT_TEXT {
        return []byte("clipboard text content"), nil
    }
    return nil, fmt.Errorf("unsupported clipboard format")
}

func (d *MyDriver) ClipboardRelease(selection spice.SpiceClipboardSelection) {
    // Server has released the clipboard
}

// To grab clipboard from client side:
// Use the main channel to announce clipboard grab
main.GrabClipboard(spice.SPICE_CLIPBOARD_SELECTION_CLIPBOARD,
    []spice.SpiceClipboardFormat{spice.SPICE_CLIPBOARD_FORMAT_TEXT})

File Transfer Example

// Get the file transfer interface
fileTransfer := client.GetFileTransfer()
if fileTransfer != nil {
    // Create a progress callback
    progressCb := func(progress spice.FileTransferProgress) {
        fmt.Printf("Transfer %s: %.1f%% (%d/%d bytes)\n", 
            progress.FileName, 
            progress.Percentage, 
            progress.BytesSent, 
            progress.TotalSize)
            
        if progress.Error != nil {
            fmt.Printf("Error: %v\n", progress.Error)
        }
    }
    
    // Send a file to the guest
    transferID, err := fileTransfer.SendFile("/path/to/file.txt", progressCb)
    if err != nil {
        log.Fatalf("Failed to start file transfer: %v", err)
    }
    
    fmt.Printf("File transfer started with ID: %d\n", transferID)
    
    // To cancel a transfer (if needed):
    // fileTransfer.CancelTransfer(transferID)
    
    // Send multiple files
    ids, err := fileTransfer.SendFiles([]string{
        "/path/to/file1.txt",
        "/path/to/file2.png",
    }, progressCb)
    if err != nil {
        log.Printf("Some transfers failed: %v", err)
    }
    fmt.Printf("Started %d file transfers\n", len(ids))
}

Connection Flow

When you call spice.New(connector, driver, password), the following happens:

  1. Main Channel Connection: The client establishes a connection to the main SPICE channel using your Connector. This channel is responsible for:

    • Authentication using the provided password
    • Retrieving the list of available channels from the server
    • Coordinating overall session management
  2. Capability Negotiation: The client and server exchange supported capabilities to determine which features are available (compression, audio formats, etc.)

  3. Parallel Channel Setup: Once the main channel is established, the client connects to all available channels in parallel:

    • Display Channel: Receives screen updates and rendering commands
    • Inputs Channel: Sends keyboard and mouse events to the server
    • Cursor Channel: Receives cursor shape and position updates
    • Playback Channel: Streams audio from the server (if available)
    • Record Channel: Sends audio to the server (if available)
    • WebDAV Channel: Handles file transfer operations (if available)
  4. Driver Initialization: As channels connect, your Driver methods are called:

    • SetMainTarget(): Provides access to the main channel
    • SetEventsTarget(): Provides access to the inputs channel
    • DisplayInit(): Called when the initial screen image is ready
    • SetCursor(): Called with initial cursor information
  5. Ready to Use: Once spice.New() returns, all channels are connected and the client is ready for interaction.

Connection Notes
  • The compress parameter in SpiceConnect() indicates whether the display channel should use compression. The client will request this when connecting to the display channel.
  • Connections may be established multiple times during the session (one per channel: main, display, inputs, cursor, audio, etc.)
  • The client handles all protocol details, compression, and channel multiplexing automatically
  • Your Connector should return a fresh net.Conn for each call to SpiceConnect()

Advanced Topics

Handling Connection Failures
client, err := spice.New(connector, driver, password)
if err != nil {
    // Connection failed - could be:
    // - Network error (cannot reach server)
    // - Authentication error (wrong password)
    // - Protocol error (incompatible versions)
    log.Printf("Failed to connect: %v", err)
    return
}
Debug Logging

Enable debug logging to see protocol details:

client, err := spice.New(connector, driver, password)
if err != nil {
    panic(err)
}

// Enable debug logging
client.Debug = log.New(os.Stdout, "SPICE: ", log.LstdFlags)
Checking Available Features

Not all SPICE servers support all features. Check availability before use:

// Check file transfer support
if fileTransfer := client.GetFileTransfer(); fileTransfer != nil {
    // File transfer is available
} else {
    log.Println("File transfer not supported by this server")
}

// Check audio recording support
if recorder := client.GetAudioRecorder(); recorder != nil {
    // Audio recording is available
} else {
    log.Println("Audio recording not supported by this server")
}

License

See the LICENSE file in the repository root.

Documentation

Overview

Package spice implements a client for the SPICE remote desktop protocol This file implements the display channel handling image rendering and updates

Package spice implements a client for the SPICE remote desktop protocol This file implements the record channel for audio recording functionality

Package spice implements a client for the SPICE remote desktop protocol commonly used with QEMU and libvirt virtualization systems.

Package spice implements a client for the SPICE remote desktop protocol This file implements the core connection handling and protocol-level operations

Index

Constants

View Source
const (
	SPICE_MSG_CURSOR_INIT      = 101
	SPICE_MSG_CURSOR_RESET     = 102
	SPICE_MSG_CURSOR_SET       = 103
	SPICE_MSG_CURSOR_MOVE      = 104
	SPICE_MSG_CURSOR_HIDE      = 105
	SPICE_MSG_CURSOR_TRAIL     = 106
	SPICE_MSG_CURSOR_INVAL_ONE = 107
	SPICE_MSG_CURSOR_INVAL_ALL = 108

	SPICE_CURSOR_TYPE_ALPHA   = 0
	SPICE_CURSOR_TYPE_MONO    = 1
	SPICE_CURSOR_TYPE_COLOR4  = 2
	SPICE_CURSOR_TYPE_COLOR8  = 3
	SPICE_CURSOR_TYPE_COLOR16 = 4
	SPICE_CURSOR_TYPE_COLOR24 = 5
	SPICE_CURSOR_TYPE_COLOR32 = 6
)
View Source
const (
	SPICE_MSG_DISPLAY_MODE      = 101
	SPICE_MSG_DISPLAY_MARK      = 102
	SPICE_MSG_DISPLAY_RESET     = 103
	SPICE_MSG_DISPLAY_COPY_BITS = 104

	SPICE_MSG_DISPLAY_INVAL_LIST         = 105
	SPICE_MSG_DISPLAY_INVAL_ALL_PIXMAPS  = 106
	SPICE_MSG_DISPLAY_INVAL_PALETTE      = 107
	SPICE_MSG_DISPLAY_INVAL_ALL_PALETTES = 108

	SPICE_MSG_DISPLAY_STREAM_CREATE      = 122
	SPICE_MSG_DISPLAY_STREAM_DATA        = 123
	SPICE_MSG_DISPLAY_STREAM_CLIP        = 124
	SPICE_MSG_DISPLAY_STREAM_DESTROY     = 125
	SPICE_MSG_DISPLAY_STREAM_DESTROY_ALL = 126

	SPICE_MSG_DISPLAY_DRAW_FILL              = 302
	SPICE_MSG_DISPLAY_DRAW_OPAQUE            = 303
	SPICE_MSG_DISPLAY_DRAW_COPY              = 304
	SPICE_MSG_DISPLAY_DRAW_BLEND             = 305
	SPICE_MSG_DISPLAY_DRAW_BLACKNESS         = 306
	SPICE_MSG_DISPLAY_DRAW_WHITENESS         = 307
	SPICE_MSG_DISPLAY_DRAW_INVERS            = 308
	SPICE_MSG_DISPLAY_DRAW_ROP3              = 309
	SPICE_MSG_DISPLAY_DRAW_STROKE            = 310
	SPICE_MSG_DISPLAY_DRAW_TEXT              = 311
	SPICE_MSG_DISPLAY_DRAW_TRANSPARENT       = 312
	SPICE_MSG_DISPLAY_DRAW_ALPHA_BLEND       = 313
	SPICE_MSG_DISPLAY_SURFACE_CREATE         = 314
	SPICE_MSG_DISPLAY_SURFACE_DESTROY        = 315
	SPICE_MSG_DISPLAY_STREAM_DATA_SIZED      = 316
	SPICE_MSG_DISPLAY_MONITORS_CONFIG        = 317
	SPICE_MSG_DISPLAY_DRAW_COMPOSITE         = 318
	SPICE_MSG_DISPLAY_STREAM_ACTIVATE_REPORT = 319
	SPICE_MSG_DISPLAY_GL_SCANOUT_UNIX        = 320
	SPICE_MSG_DISPLAY_GL_DRAW                = 321
	SPICE_MSG_DISPLAY_QUALITY_INDICATOR      = 322

	SPICE_MSGC_DISPLAY_INIT                       = 101
	SPICE_MSGC_DISPLAY_STREAM_REPORT              = 102
	SPICE_MSGC_DISPLAY_PREFERRED_COMPRESSION      = 103
	SPICE_MSGC_DISPLAY_GL_DRAW_DONE               = 104
	SPICE_MSGC_DISPLAY_PREFERRED_VIDEO_CODEC_TYPE = 105
)
View Source
const (
	// 4178 = 1000001010010
	SPICE_DISPLAY_CAP_SIZED_STREAM    uint32 = iota
	SPICE_DISPLAY_CAP_MONITORS_CONFIG        // X
	SPICE_DISPLAY_CAP_COMPOSITE
	SPICE_DISPLAY_CAP_A8_SURFACE
	SPICE_DISPLAY_CAP_STREAM_REPORT // X
	SPICE_DISPLAY_CAP_LZ4_COMPRESSION
	SPICE_DISPLAY_CAP_PREF_COMPRESSION // X
	SPICE_DISPLAY_CAP_GL_SCANOUT
	SPICE_DISPLAY_CAP_MULTI_CODEC
	SPICE_DISPLAY_CAP_CODEC_MJPEG
	SPICE_DISPLAY_CAP_CODEC_VP8
	SPICE_DISPLAY_CAP_CODEC_H264
	SPICE_DISPLAY_CAP_PREF_VIDEO_CODEC_TYPE // X
	SPICE_DISPLAY_CAP_CODEC_VP9
	SPICE_DISPLAY_CAP_CODEC_H265
	SPICE_DISPLAY_CAP_GL_SCANOUT2
)
View Source
const (
	SPICE_IMAGE_COMPRESSION_INVALID uint8 = iota
	SPICE_IMAGE_COMPRESSION_OFF
	SPICE_IMAGE_COMPRESSION_AUTO_GLZ
	SPICE_IMAGE_COMPRESSION_AUTO_LZ
	SPICE_IMAGE_COMPRESSION_QUIC
	SPICE_IMAGE_COMPRESSION_GLZ
	SPICE_IMAGE_COMPRESSION_LZ
	SPICE_IMAGE_COMPRESSION_LZ4
)
View Source
const (
	SPICE_MSGC_INPUTS_KEY_DOWN      = 101
	SPICE_MSGC_INPUTS_KEY_UP        = 102
	SPICE_MSGC_INPUTS_KEY_MODIFIERS = 103

	SPICE_MSGC_INPUTS_MOUSE_MOTION   = 111
	SPICE_MSGC_INPUTS_MOUSE_POSITION = 112
	SPICE_MSGC_INPUTS_MOUSE_PRESS    = 113
	SPICE_MSGC_INPUTS_MOUSE_RELEASE  = 114

	SPICE_MSG_INPUTS_INIT          = 101
	SPICE_MSG_INPUTS_KEY_MODIFIERS = 102

	SPICE_MSG_INPUTS_MOUSE_MOTION_ACK = 111

	// Keyboard led bits
	SPICE_SCROLL_LOCK_MODIFIER = 1
	SPICE_NUM_LOCK_MODIFIER    = 2
	SPICE_CAPS_LOCK_MODIFIER   = 4

	SPICE_INPUTS_CAP_KEY_SCANCODE = 0
)
View Source
const (
	SPICE_MSG_MAIN_MIGRATE_BEGIN    = 101
	SPICE_MSG_MAIN_MIGRATE_CANCEL   = 102
	SPICE_MSG_MAIN_INIT             = 103
	SPICE_MSG_MAIN_CHANNELS_LIST    = 104
	SPICE_MSG_MAIN_MOUSE_MODE       = 105
	SPICE_MSG_MAIN_MULTI_MEDIA_TIME = 106

	SPICE_MSG_MAIN_AGENT_CONNECTED    = 107
	SPICE_MSG_MAIN_AGENT_DISCONNECTED = 108
	SPICE_MSG_MAIN_AGENT_DATA         = 109
	SPICE_MSG_MAIN_AGENT_TOKEN        = 110

	SPICE_MSG_MAIN_MIGRATE_SWITCH_HOST       = 111
	SPICE_MSG_MAIN_MIGRATE_END               = 112
	SPICE_MSG_MAIN_NAME                      = 113
	SPICE_MSG_MAIN_UUID                      = 114
	SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS    = 115
	SPICE_MSG_MAIN_MIGRATE_BEGIN_SEAMLESS    = 116
	SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK  = 117
	SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_NACK = 118

	SPICE_MSGC_MAIN_CLIENT_INFO           = 101
	SPICE_MSGC_MAIN_MIGRATE_CONNECTED     = 102
	SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR = 103
	SPICE_MSGC_MAIN_ATTACH_CHANNELS       = 104
	SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST    = 105

	SPICE_MSGC_MAIN_AGENT_START = 106
	SPICE_MSGC_MAIN_AGENT_DATA  = 107
	SPICE_MSGC_MAIN_AGENT_TOKEN = 108

	VD_AGENT_PROTOCOL = 1
)
View Source
const (
	SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE = iota
	SPICE_MAIN_CAP_NAME_AND_UUID
	SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS
	SPICE_MAIN_CAP_SEAMLESS_MIGRATE
)
View Source
const (
	VD_AGENT_MOUSE_STATE = iota + 1
	VD_AGENT_MONITORS_CONFIG
	VD_AGENT_REPLY
	VD_AGENT_CLIPBOARD
	VD_AGENT_DISPLAY_CONFIG
	VD_AGENT_ANNOUNCE_CAPABILITIES
	VD_AGENT_CLIPBOARD_GRAB
	VD_AGENT_CLIPBOARD_REQUEST
	VD_AGENT_CLIPBOARD_RELEASE
	VD_AGENT_FILE_XFER_START
	VD_AGENT_FILE_XFER_STATUS
	VD_AGENT_FILE_XFER_DATA
	VD_AGENT_CLIENT_DISCONNECTED
	VD_AGENT_MAX_CLIPBOARD
	VD_AGENT_AUDIO_VOLUME_SYNC
	VD_AGENT_GRAPHICS_DEVICE_INFO
)
View Source
const (
	VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA = iota
	VD_AGENT_FILE_XFER_STATUS_CANCELLED
	VD_AGENT_FILE_XFER_STATUS_ERROR
	VD_AGENT_FILE_XFER_STATUS_SUCCESS
	VD_AGENT_FILE_XFER_STATUS_NOT_ENOUGH_SPACE
	VD_AGENT_FILE_XFER_STATUS_SESSION_LOCKED
	VD_AGENT_FILE_XFER_STATUS_VDAGENT_NOT_CONNECTED
	VD_AGENT_FILE_XFER_STATUS_DISABLED
)
View Source
const (
	// caps from server: 36327 =   1000110111100111
	// caps from server (win32): 1719 = 11010110111
	VD_AGENT_CAP_MOUSE_STATE               uint32 = iota // X
	VD_AGENT_CAP_MONITORS_CONFIG                         // X
	VD_AGENT_CAP_REPLY                                   // X
	VD_AGENT_CAP_CLIPBOARD                               // (unused)
	VD_AGENT_CAP_DISPLAY_CONFIG                          // (unused) win only
	VD_AGENT_CAP_CLIPBOARD_BY_DEMAND                     // X
	VD_AGENT_CAP_CLIPBOARD_SELECTION                     // X linux only
	VD_AGENT_CAP_SPARSE_MONITORS_CONFIG                  // X
	VD_AGENT_CAP_GUEST_LINEEND_LF                        // X
	VD_AGENT_CAP_GUEST_LINEEND_CRLF                      // (windows only?)
	VD_AGENT_CAP_MAX_CLIPBOARD                           // X
	VD_AGENT_CAP_AUDIO_VOLUME_SYNC                       // X
	VD_AGENT_CAP_MONITORS_CONFIG_POSITION                // (unused)
	VD_AGENT_CAP_FILE_XFER_DISABLED                      // (unused)
	VD_AGENT_CAP_FILE_XFER_DETAILED_ERRORS               // (unused)
	VD_AGENT_CAP_GRAPHICS_DEVICE_INFO                    // X
	VD_AGENT_CAP_CLIPBOARD_NO_RELEASE_ON_REGRAB
	VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL
)
View Source
const (
	SPICE_MSG_PLAYBACK_DATA    = 101
	SPICE_MSG_PLAYBACK_MODE    = 102
	SPICE_MSG_PLAYBACK_START   = 103
	SPICE_MSG_PLAYBACK_STOP    = 104
	SPICE_MSG_PLAYBACK_VOLUME  = 105
	SPICE_MSG_PLAYBACK_MUTE    = 106
	SPICE_MSG_PLAYBACK_LATENCY = 107

	SPICE_AUDIO_DATA_MODE_RAW        = 1
	SPICE_AUDIO_DATA_MODE_CELT_0_5_1 = 2
	SPICE_AUDIO_DATA_MODE_OPUS       = 3

	// need to send capabilities
	SPICE_PLAYBACK_CAP_CELT_0_5_1 = 0
	SPICE_PLAYBACK_CAP_VOLUME     = 1
	SPICE_PLAYBACK_CAP_LATENCY    = 2
	SPICE_PLAYBACK_CAP_OPUS       = 3
)
View Source
const (
	// Messages from server to client
	SPICE_MSG_RECORD_START  = 101 // Start audio recording
	SPICE_MSG_RECORD_STOP   = 102 // Stop audio recording
	SPICE_MSG_RECORD_VOLUME = 103 // Set recording volume
	SPICE_MSG_RECORD_MUTE   = 104 // Mute/unmute recording

	// Messages from client to server
	SPICE_MSGC_RECORD_DATA       = 101 // Send recorded audio data
	SPICE_MSGC_RECORD_MODE       = 102 // Set audio recording mode
	SPICE_MSGC_RECORD_START_MARK = 103 // Mark recording start time

	// Capability flags for record channel
	SPICE_RECORD_CAP_CELT_0_5_1 = 0 // Support for CELT 0.5.1 codec (deprecated)
	SPICE_RECORD_CAP_VOLUME     = 1 // Support for volume control
	SPICE_RECORD_CAP_OPUS       = 2 // Support for Opus audio codec
)
View Source
const (
	SPICE_VERSION_MAJOR = 2
	SPICE_VERSION_MINOR = 2

	SPICE_TICKET_PUBKEY_BYTES = 162 // bytes needed to store 1024bits RSA key in PKIX format

	SPICE_WARN_GENERAL = 0
	SPICE_INFO_GENERAL = 0

	SPICE_MAGIC = "REDQ" // magic bytes. can feel the redhat

	// common messages
	SPICE_MSG_MIGRATE           = 1
	SPICE_MSG_MIGRATE_DATA      = 2
	SPICE_MSG_SET_ACK           = 3
	SPICE_MSG_PING              = 4
	SPICE_MSG_WAIT_FOR_CHANNELS = 5
	SPICE_MSG_DISCONNECTING     = 6
	SPICE_MSG_NOTIFY            = 7

	SPICE_MSG_FIRST_AVAIL = 101

	//
	SPICE_MSGC_ACK_SYNC           = 1
	SPICE_MSGC_ACK                = 2
	SPICE_MSGC_PONG               = 3
	SPICE_MSGC_MIGRATE_FLUSH_MARK = 4
	SPICE_MSGC_MIGRATE_DATA       = 5
	SPICE_MSGC_DISCONNECTING      = 6

	SPICE_MSGC_FIRST_AVAIL = 101

	SPICE_MOUSE_MODE_SERVER = 1
	SPICE_MOUSE_MODE_CLIENT = 2

	SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION = 0
	SPICE_COMMON_CAP_AUTH_SPICE              = 1
	SPICE_COMMON_CAP_AUTH_SASL               = 2
	SPICE_COMMON_CAP_MINI_HEADER             = 3
)
View Source
const (
	SPICE_WEBDAV_MSG_INVALID          = 0
	SPICE_WEBDAV_MSG_FILE_XFER_START  = 1
	SPICE_WEBDAV_MSG_FILE_XFER_DATA   = 2
	SPICE_WEBDAV_MSG_FILE_XFER_STATUS = 3
)

WebDAV message types

View Source
const VD_AGENT_MAX_DATA_SIZE = 2048
View Source
const VD_AGENT_SERVER_TOKEN_AMOUNT = 10

Variables

This section is empty.

Functions

func NewTimeBuffer

func NewTimeBuffer(cl *Client, d *ChPlayback) *timeBuffer

Types

type ActiveTransfer added in v0.0.4

type ActiveTransfer struct {
	ID           uint32               // Unique transfer ID
	File         *os.File             // File handle
	FileName     string               // File name (used in progress reporting)
	OriginalPath string               // Original path on the host
	TotalSize    int64                // Total file size
	BytesSent    int64                // Bytes sent so far
	Callback     FileTransferCallback // Progress callback
}

ActiveTransfer represents an active file transfer

type BitmapImageType

type BitmapImageType uint8
const (
	BITMAP_IMAGE_TYPE_INVALID BitmapImageType = iota
	BITMAP_IMAGE_TYPE_1BIT_LE
	BITMAP_IMAGE_TYPE_1BIT_BE
	BITMAP_IMAGE_TYPE_4BIT_LE
	BITMAP_IMAGE_TYPE_4BIT_BE
	BITMAP_IMAGE_TYPE_8BIT
	BITMAP_IMAGE_TYPE_16BIT
	BITMAP_IMAGE_TYPE_24BIT
	BITMAP_IMAGE_TYPE_32BIT
	BITMAP_IMAGE_TYPE_RGBA
	BITMAP_IMAGE_TYPE_8BIT_A
)

type ChCursor

type ChCursor struct {
	// contains filtered or unexported fields
}

type ChInputs

type ChInputs struct {
	// contains filtered or unexported fields
}

func (*ChInputs) MouseDown

func (input *ChInputs) MouseDown(btn uint8, x, y uint32)

func (*ChInputs) MousePosition

func (input *ChInputs) MousePosition(x, y uint32)

func (*ChInputs) MouseUp

func (input *ChInputs) MouseUp(btn uint8, x, y uint32)

func (*ChInputs) OnKeyDown

func (input *ChInputs) OnKeyDown(k []byte)

func (*ChInputs) OnKeyUp

func (input *ChInputs) OnKeyUp(k []byte)

type ChMain

type ChMain struct {
	// contains filtered or unexported fields
}

func (*ChMain) AgentWrite

func (m *ChMain) AgentWrite(typ uint32, data ...interface{}) error

func (*ChMain) DisplayConfig

func (m *ChMain) DisplayConfig(flags, depth uint32) error

func (*ChMain) MonitorConfig

func (m *ChMain) MonitorConfig(flags uint32, mons []SpiceMonitor) error

func (*ChMain) MouseModeRequest

func (m *ChMain) MouseModeRequest(mod uint32) error

func (*ChMain) RequestClipboard

func (m *ChMain) RequestClipboard(selection SpiceClipboardSelection, clipboardType SpiceClipboardFormat) ([]byte, error)

func (*ChMain) SendGrabClipboard

func (m *ChMain) SendGrabClipboard(selection SpiceClipboardSelection, formatTypes []SpiceClipboardFormat) error

type ChPlayback

type ChPlayback struct {
	// contains filtered or unexported fields
}

type ChRecord added in v0.0.3

type ChRecord struct {
	// contains filtered or unexported fields
}

ChRecord handles the audio recording channel for capturing audio from the client and sending it to the SPICE server

type Channel

type Channel uint8
const (
	ChannelMain Channel = iota + 1
	ChannelDisplay
	ChannelInputs
	ChannelCursor
	ChannelPlayback
	ChannelRecord
	ChannelTunnel // obsolete
	ChannelSmartcard
	ChannelUsbRedir
	ChannelPort
	ChannelWebdav
)

func (Channel) String

func (c Channel) String() string

type Client

type Client struct {
	Debug *log.Logger // Optional logger for debug information
	// contains filtered or unexported fields
}

Client represents a SPICE protocol client connection It manages all channel connections and coordinates communication

func New

func New(c Connector, driver Driver, password string) (*Client, error)

New creates a new SPICE client and establishes connection to all available channels It requires a Connector for network access, a Driver for GUI interaction, and the password for SPICE authentication

func (*Client) GetFileTransfer added in v0.0.4

func (client *Client) GetFileTransfer() *SpiceWebdav

GetFileTransfer returns the WebDAV file transfer interface if available

func (*Client) GetMute

func (client *Client) GetMute() bool

func (*Client) MediaTill

func (client *Client) MediaTill(t uint32) time.Duration

func (*Client) MediaTime

func (client *Client) MediaTime() uint32

func (*Client) SetMute

func (client *Client) SetMute(muted bool)

func (*Client) ToggleMute

func (client *Client) ToggleMute()

func (*Client) UpdateView

func (client *Client) UpdateView(w, h int)

type ClipboardData

type ClipboardData struct {
	// contains filtered or unexported fields
}

ClipboardData identifies one specific request

type Connector

type Connector interface {
	SpiceConnect(compress bool) (net.Conn, error)
}

Connector is an interface that can establish network connections to a SPICE server with optional compression for the display channel

type DisplayBase

type DisplayBase struct {
	Surface  uint32
	Box      Rect   // Rect box
	ClipType uint8  // 0=NONE, 1=RECTS
	NumRects uint32 // if clip_type=1
	Rects    []Rect // if clip_type=1
}

func (*DisplayBase) Decode

func (res *DisplayBase) Decode(r *bytes.Reader) error

type Driver

type Driver interface {
	// DisplayInit initializes the display with the given image
	DisplayInit(image.Image)
	// DisplayRefresh triggers a refresh of the display
	DisplayRefresh()
	// SetEventsTarget sets the input events channel for sending user input
	SetEventsTarget(*ChInputs)
	// SetMainTarget sets the main channel for server communication
	SetMainTarget(*ChMain)
	// SetCursor updates the cursor image and position
	SetCursor(img image.Image, x, y uint16)

	// Clipboard related methods
	// ClipboardGrabbed is called when the server grabs the clipboard
	ClipboardGrabbed(selection SpiceClipboardSelection, clipboardTypes []SpiceClipboardFormat)
	// ClipboardFetch retrieves clipboard data from the client
	ClipboardFetch(selection SpiceClipboardSelection, clType SpiceClipboardFormat) ([]byte, error)
	// ClipboardRelease is called when the server releases the clipboard
	ClipboardRelease(selection SpiceClipboardSelection)
}

Driver is the interface that must be implemented by clients to handle display updates, input events, cursor changes and clipboard operations

type FileTransferCallback added in v0.0.4

type FileTransferCallback func(progress FileTransferProgress)

FileTransferCallback is called when file transfer status changes

type FileTransferProgress added in v0.0.4

type FileTransferProgress struct {
	FileName   string  // Name of the file being transferred
	TotalSize  int64   // Total file size in bytes
	BytesSent  int64   // Bytes sent so far
	Percentage float64 // Progress percentage (0-100)
	Status     uint32  // Current status (one of VD_AGENT_FILE_XFER_STATUS_*)
	Error      error   // Error if any
}

FileTransferProgress represents the progress of a file transfer

type Image

type Image struct {
	image.Image

	ID     uint64
	Type   uint8 // 0=bitmap 1=quic 2=reserved 100=lz_plt 101=lz_rgb 102=glz_rgb 103=from_cache 104=surface 105=jpeg 106=from_cache_lossless 107=ZLIB_GLZ_RGB 108=jpeg_alpha 109=lz4
	Flags  uint8 // 1=cache_me 2=high_bits_set 4=cache_replace_me
	Width  uint32
	Height uint32
}

func DecodeImage

func DecodeImage(buf []byte) (*Image, error)

type ImageScaleMode

type ImageScaleMode uint8
const (
	ImageScaleModeInterpolate ImageScaleMode = iota
	ImageScaleModeNearest
)

type LzImageType

type LzImageType uint32
const (
	LZ_IMAGE_TYPE_INVALID LzImageType = iota
	LZ_IMAGE_TYPE_PLT1_LE
	LZ_IMAGE_TYPE_PLT1_BE // PLT stands for palette
	LZ_IMAGE_TYPE_PLT4_LE
	LZ_IMAGE_TYPE_PLT4_BE
	LZ_IMAGE_TYPE_PLT8
	LZ_IMAGE_TYPE_RGB16
	LZ_IMAGE_TYPE_RGB24
	LZ_IMAGE_TYPE_RGB32
	LZ_IMAGE_TYPE_RGBA
	LZ_IMAGE_TYPE_XXXA
	LZ_IMAGE_TYPE_A8
)

func (LzImageType) String

func (t LzImageType) String() string

type Point

type Point struct {
	X uint32
	Y uint32
}

func (*Point) Decode

func (point *Point) Decode(r *bytes.Reader) error

type QMask

type QMask struct {
	MaskFlags uint8 // 1=INVERS
	Pos       Point
	ImagePtr  uint32
	*Image
}

func (*QMask) Decode

func (qmask *QMask) Decode(r *bytes.Reader) error

type Rect

type Rect struct {
	Top    uint32
	Left   uint32
	Bottom uint32
	Right  uint32
}

func (*Rect) Decode

func (rect *Rect) Decode(r *bytes.Reader) error

func (*Rect) Rectangle

func (rect *Rect) Rectangle() image.Rectangle

type Ropd

type Ropd uint16
const (
	SpiceRopdInversSrc Ropd = 1 << iota
	SpiceRopdInversBrush
	SpiceRopdInversDest
	SpiceRopdOpPut
	SpiceRopdOpOr
	SpiceRopdOpAnd
	SpiceRopdOpXor
	SpiceRopdOpBlackness
	SpiceRopdOpWhiteness
	SpiceRopdOpInvers
	SpiceRopdInversRes
)

type SpiceChannelInfo

type SpiceChannelInfo struct {
	// contains filtered or unexported fields
}

type SpiceClipboardFormat

type SpiceClipboardFormat uint32
const (
	VD_AGENT_CLIPBOARD_NONE SpiceClipboardFormat = iota
	VD_AGENT_CLIPBOARD_UTF8_TEXT
	VD_AGENT_CLIPBOARD_IMAGE_PNG
	VD_AGENT_CLIPBOARD_IMAGE_BMP
	VD_AGENT_CLIPBOARD_IMAGE_TIFF
	VD_AGENT_CLIPBOARD_IMAGE_JPG
)

type SpiceClipboardSelection

type SpiceClipboardSelection uint8
const (
	VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD SpiceClipboardSelection = iota
	VD_AGENT_CLIPBOARD_SELECTION_PRIMARY
	VD_AGENT_CLIPBOARD_SELECTION_SECONDARY
)

type SpiceConn

type SpiceConn struct {
	// contains filtered or unexported fields
}

SpiceConn represents a connection to a specific SPICE channel It handles the protocol-level communication including message framing, authentication, and capability negotiation

func (*SpiceConn) Close

func (c *SpiceConn) Close() error

func (*SpiceConn) Read

func (c *SpiceConn) Read(buf []byte) (int, error)

func (*SpiceConn) ReadData

func (c *SpiceConn) ReadData(cb func(typ uint16, data []byte) error) error

func (*SpiceConn) ReadError

func (c *SpiceConn) ReadError() error

func (*SpiceConn) ReadFull

func (c *SpiceConn) ReadFull(buf []byte) error

func (*SpiceConn) ReadLoop

func (c *SpiceConn) ReadLoop()

ReadLoop continuously reads and processes incoming messages from the SPICE server It handles message acknowledgment according to the server's requirements and dispatches messages to the appropriate handlers

func (*SpiceConn) String

func (c *SpiceConn) String() string

func (*SpiceConn) Write

func (c *SpiceConn) Write(buf []byte) (int, error)

func (*SpiceConn) WriteMessage

func (c *SpiceConn) WriteMessage(typ uint16, data ...interface{}) error

type SpiceDisplay

type SpiceDisplay struct {
	// contains filtered or unexported fields
}

SpiceDisplay handles the display channel for rendering remote desktop content It processes drawing commands and updates the local display image

type SpiceError

type SpiceError uint32
const (
	ErrSpiceLinkOk SpiceError = iota
	ErrSpiceLinkError
	ErrSpiceLinkInvalidMagic
	ErrSpiceLinkInvalidData
	ErrSpiceLinkVersionMismatch
	ErrSpiceLinkNeedSecured
	ErrSpiceLinkNeedUnsecured
	ErrSpiceLinkPermissionDenied
	ErrSpiceLinkBadConnectionId
	ErrSpiceLinkChannelNotAvailable
)

func (SpiceError) Error

func (e SpiceError) Error() string

type SpiceMonitor

type SpiceMonitor struct {
	Height uint32
	Width  uint32
	Depth  uint32
	X      uint32
	Y      uint32
}

type SpiceWebdav

type SpiceWebdav struct {
	// contains filtered or unexported fields
}

SpiceWebdav handles the WebDAV channel communication for file transfers

func (*SpiceWebdav) CancelTransfer added in v0.0.4

func (d *SpiceWebdav) CancelTransfer(id uint32) error

CancelTransfer cancels an active file transfer

func (*SpiceWebdav) SendFile added in v0.0.4

func (d *SpiceWebdav) SendFile(filePath string, callback FileTransferCallback) (uint32, error)

SendFile initiates a file transfer to the guest

func (*SpiceWebdav) SendFiles added in v0.0.4

func (d *SpiceWebdav) SendFiles(filePaths []string, callback FileTransferCallback) ([]uint32, error)

SendFiles sends multiple files to the guest

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL