init: 自 zomaintain/backend/rdplib 平移独立成库; module path 从上游 github.com/nakagami/grdp 改为 git.zeroonesoft.cn/golib/rdplib
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
// Package rdpedisp implements the RDP Display Update Virtual Channel
|
||||
// (MS-RDPEDISP), which allows the client to request a resolution change
|
||||
// while connected. The channel name is:
|
||||
//
|
||||
// "Microsoft::Windows::RDS::DisplayControl"
|
||||
//
|
||||
// Typical usage:
|
||||
//
|
||||
// 1. Register the handler with the DVC client before connecting.
|
||||
// 2. After the session is established, call SendMonitorLayout to request
|
||||
// a new resolution. The server will reshape the desktop and send a
|
||||
// fresh RDPGFX ResetGraphics command.
|
||||
package rdpedisp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"log/slog"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ChannelName is the well-known DVC name for the Display Update channel.
|
||||
const ChannelName = "Microsoft::Windows::RDS::DisplayControl"
|
||||
|
||||
// PDU types (MS-RDPEDISP 2.2.1.2.1)
|
||||
const (
|
||||
pduTypeCaps = 0x00000005
|
||||
pduTypeMonitorLayout = 0x00000002
|
||||
)
|
||||
|
||||
// DISPLAYCONTROL_MONITOR_PRIMARY marks the primary monitor.
|
||||
const MonitorFlagPrimary = uint32(0x00000001)
|
||||
|
||||
// monitorLayoutSize is the fixed per-monitor record size required by the spec.
|
||||
const monitorLayoutSize = 40
|
||||
|
||||
// Monitor describes a single monitor in a MONITOR_LAYOUT PDU.
|
||||
// Width and Height must each be at least 200 and Width must be even.
|
||||
// Set PhysicalWidth/Height to 0 when the physical dimensions are unknown.
|
||||
// Orientation: 0=landscape (normal), 90, 180, 270.
|
||||
// DesktopScaleFactor: one of 100, 125, 150, 175, 200 (use 100 if unsure).
|
||||
// DeviceScaleFactor: one of 100, 140, 180 (use 100 if unsure).
|
||||
type Monitor struct {
|
||||
Flags uint32
|
||||
Left int32
|
||||
Top int32
|
||||
Width uint32
|
||||
Height uint32
|
||||
PhysicalWidth uint32
|
||||
PhysicalHeight uint32
|
||||
Orientation uint32
|
||||
DesktopScaleFactor uint32
|
||||
DeviceScaleFactor uint32
|
||||
}
|
||||
|
||||
// Handler is the DVC handler for the Display Update channel.
|
||||
// It implements the drdynvc.DvcChannelHandler interface and the optional
|
||||
// SetSendFunc / OnChannelCreated extension interfaces.
|
||||
type Handler struct {
|
||||
mu sync.Mutex
|
||||
send func([]byte)
|
||||
capsReceived bool
|
||||
maxMonitors uint32
|
||||
pendingMonitors []Monitor
|
||||
}
|
||||
|
||||
// NewHandler returns a new Handler.
|
||||
// width and height are the desired initial desktop dimensions; when both are
|
||||
// non-zero the handler queues an initial layout change that is sent as soon as
|
||||
// the server advertises its capabilities (MS-RDPEDISP CAPS PDU), prompting
|
||||
// servers such as GNOME Remote Desktop (headless) to resize.
|
||||
func NewHandler(width, height uint32) *Handler {
|
||||
h := &Handler{}
|
||||
if width > 0 && height > 0 {
|
||||
h.pendingMonitors = []Monitor{
|
||||
{
|
||||
Flags: MonitorFlagPrimary,
|
||||
Left: 0,
|
||||
Top: 0,
|
||||
Width: width,
|
||||
Height: height,
|
||||
PhysicalWidth: 0,
|
||||
PhysicalHeight: 0,
|
||||
Orientation: 0,
|
||||
DesktopScaleFactor: 100,
|
||||
DeviceScaleFactor: 100,
|
||||
},
|
||||
}
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// SetSendFunc is called by the DVC client to provide a write-back function.
|
||||
// Required by the drdynvc channel plumbing.
|
||||
func (h *Handler) SetSendFunc(f func([]byte)) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
h.send = f
|
||||
}
|
||||
|
||||
// OnChannelCreated is called by the DVC client after the CREATE_RSP has been sent.
|
||||
func (h *Handler) OnChannelCreated() {
|
||||
slog.Debug("rdpedisp: channel created")
|
||||
}
|
||||
|
||||
// Process handles incoming data from the server (CAPS PDU, etc.).
|
||||
func (h *Handler) Process(data []byte) {
|
||||
if len(data) < 8 {
|
||||
return
|
||||
}
|
||||
pduType := binary.LittleEndian.Uint32(data[0:4])
|
||||
switch pduType {
|
||||
case pduTypeCaps:
|
||||
if len(data) >= 20 {
|
||||
maxMonitors := binary.LittleEndian.Uint32(data[8:12])
|
||||
slog.Debug("rdpedisp: server CAPS", "maxMonitors", maxMonitors)
|
||||
h.mu.Lock()
|
||||
h.capsReceived = true
|
||||
h.maxMonitors = maxMonitors
|
||||
pending := h.pendingMonitors
|
||||
h.pendingMonitors = nil
|
||||
h.mu.Unlock()
|
||||
|
||||
if len(pending) > 0 {
|
||||
h.SendMonitorLayout(pending)
|
||||
}
|
||||
}
|
||||
default:
|
||||
slog.Debug("rdpedisp: unknown PDU type", "type", pduType)
|
||||
}
|
||||
}
|
||||
|
||||
// SendMonitorLayout sends a DISPLAYCONTROL_MONITOR_LAYOUT_PDU to the server,
|
||||
// requesting the given monitor configuration. Per MS-RDPEDISP 3.2.5.1, the
|
||||
// client MUST NOT send a layout PDU before receiving the server CAPS PDU; if
|
||||
// called before CAPS is received, the layout is queued and sent automatically
|
||||
// when CAPS arrives.
|
||||
//
|
||||
// The server will apply the new layout and—if using the RDPGFX pipeline—send
|
||||
// a ResetGraphics command that resets surface dimensions to match.
|
||||
func (h *Handler) SendMonitorLayout(monitors []Monitor) {
|
||||
h.mu.Lock()
|
||||
if !h.capsReceived {
|
||||
slog.Debug("rdpedisp: queuing MonitorLayout (waiting for server CAPS)", "numMonitors", len(monitors))
|
||||
h.pendingMonitors = monitors
|
||||
h.mu.Unlock()
|
||||
return
|
||||
}
|
||||
send := h.send
|
||||
h.mu.Unlock()
|
||||
|
||||
if send == nil {
|
||||
slog.Warn("rdpedisp: SendMonitorLayout: channel not open")
|
||||
return
|
||||
}
|
||||
|
||||
numMonitors := uint32(len(monitors))
|
||||
// PDU layout: 8-byte DISPLAYCONTROL_HEADER +
|
||||
// 4 bytes MonitorLayoutSize +
|
||||
// 4 bytes NumMonitors +
|
||||
// numMonitors * monitorLayoutSize bytes
|
||||
pduLen := uint32(8 + 4 + 4 + int(numMonitors)*monitorLayoutSize)
|
||||
pdu := make([]byte, pduLen)
|
||||
|
||||
binary.LittleEndian.PutUint32(pdu[0:], pduTypeMonitorLayout)
|
||||
binary.LittleEndian.PutUint32(pdu[4:], pduLen)
|
||||
binary.LittleEndian.PutUint32(pdu[8:], monitorLayoutSize) // fixed record size
|
||||
binary.LittleEndian.PutUint32(pdu[12:], numMonitors)
|
||||
|
||||
off := 16
|
||||
for _, m := range monitors {
|
||||
binary.LittleEndian.PutUint32(pdu[off+0:], m.Flags)
|
||||
binary.LittleEndian.PutUint32(pdu[off+4:], uint32(m.Left))
|
||||
binary.LittleEndian.PutUint32(pdu[off+8:], uint32(m.Top))
|
||||
binary.LittleEndian.PutUint32(pdu[off+12:], m.Width)
|
||||
binary.LittleEndian.PutUint32(pdu[off+16:], m.Height)
|
||||
binary.LittleEndian.PutUint32(pdu[off+20:], m.PhysicalWidth)
|
||||
binary.LittleEndian.PutUint32(pdu[off+24:], m.PhysicalHeight)
|
||||
binary.LittleEndian.PutUint32(pdu[off+28:], m.Orientation)
|
||||
binary.LittleEndian.PutUint32(pdu[off+32:], m.DesktopScaleFactor)
|
||||
binary.LittleEndian.PutUint32(pdu[off+36:], m.DeviceScaleFactor)
|
||||
off += monitorLayoutSize
|
||||
}
|
||||
|
||||
slog.Debug("rdpedisp: sending MonitorLayout", "numMonitors", numMonitors)
|
||||
send(pdu)
|
||||
}
|
||||
Reference in New Issue
Block a user