main
Raw Download raw file
  1// Package web provides HTTP/WebSocket server for VNC proxy.
  2package web
  3
  4import (
  5	"encoding/json"
  6	"net/http"
  7	"time"
  8
  9	"goVNC/pkg/input"
 10	"goVNC/pkg/rfb"
 11)
 12
 13// API provides REST API handlers for VNC operations.
 14type API struct {
 15	vnc *rfb.Client
 16}
 17
 18// NewAPI creates a new API handler.
 19func NewAPI(vnc *rfb.Client) *API {
 20	return &API{vnc: vnc}
 21}
 22
 23// ClipboardRequest is the request body for PUT /api/clipboard.
 24type ClipboardRequest struct {
 25	Text string `json:"text"`
 26}
 27
 28// ClipboardResponse is the response for GET /api/clipboard.
 29type ClipboardResponse struct {
 30	Text      string    `json:"text"`
 31	Timestamp time.Time `json:"timestamp"`
 32}
 33
 34// KeysRequest is the request body for PUT /api/keys.
 35type KeysRequest struct {
 36	// Text to type (simple mode)
 37	Text string `json:"text,omitempty"`
 38
 39	// DelayMs between keystrokes
 40	DelayMs int `json:"delay_ms,omitempty"`
 41
 42	// Keys for raw key events
 43	Keys []KeyEvent `json:"keys,omitempty"`
 44}
 45
 46// KeyEvent represents a single key event.
 47type KeyEvent struct {
 48	Key  string `json:"key"`
 49	Down bool   `json:"down"`
 50}
 51
 52// SessionResponse is the response for GET /api/session.
 53type SessionResponse struct {
 54	Name      string `json:"name"`
 55	Width     int    `json:"width"`
 56	Height    int    `json:"height"`
 57	Connected bool   `json:"connected"`
 58}
 59
 60// HandleClipboard handles clipboard API requests.
 61func (a *API) HandleClipboard(w http.ResponseWriter, r *http.Request) {
 62	switch r.Method {
 63	case http.MethodGet:
 64		a.getClipboard(w, r)
 65	case http.MethodPut:
 66		a.setClipboard(w, r)
 67	default:
 68		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
 69	}
 70}
 71
 72func (a *API) getClipboard(w http.ResponseWriter, _ *http.Request) {
 73	text, timestamp := a.vnc.GetLastClipboard()
 74	resp := ClipboardResponse{
 75		Text:      text,
 76		Timestamp: timestamp,
 77	}
 78	w.Header().Set("Content-Type", "application/json")
 79	json.NewEncoder(w).Encode(resp)
 80}
 81
 82func (a *API) setClipboard(w http.ResponseWriter, r *http.Request) {
 83	var req ClipboardRequest
 84	err := json.NewDecoder(r.Body).Decode(&req)
 85	if err != nil {
 86		http.Error(w, "Invalid JSON", http.StatusBadRequest)
 87		return
 88	}
 89
 90	err = a.vnc.SetClipboard(r.Context(), req.Text)
 91	if err != nil {
 92		http.Error(w, err.Error(), http.StatusInternalServerError)
 93		return
 94	}
 95
 96	w.WriteHeader(http.StatusNoContent)
 97}
 98
 99// HandleKeys handles keyboard API requests.
100func (a *API) HandleKeys(w http.ResponseWriter, r *http.Request) {
101	if r.Method != http.MethodPut {
102		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
103		return
104	}
105
106	var req KeysRequest
107	err := json.NewDecoder(r.Body).Decode(&req)
108	if err != nil {
109		http.Error(w, "Invalid JSON", http.StatusBadRequest)
110		return
111	}
112
113	ctx := r.Context()
114
115	// If keys array is provided, send raw key events
116	if len(req.Keys) > 0 {
117		for _, ke := range req.Keys {
118			key, ok := input.ParseKeyName(ke.Key)
119			if !ok {
120				// Try as single character
121				if len(ke.Key) == 1 {
122					key = rfb.RuneToKeysym(rune(ke.Key[0]))
123				} else {
124					http.Error(w, "Unknown key: "+ke.Key, http.StatusBadRequest)
125					return
126				}
127			}
128			err = a.vnc.KeyEvent(ctx, key, ke.Down)
129			if err != nil {
130				http.Error(w, err.Error(), http.StatusInternalServerError)
131				return
132			}
133		}
134		w.WriteHeader(http.StatusNoContent)
135		return
136	}
137
138	// Otherwise, type the text
139	if req.Text != "" {
140		// Add delay between keystrokes if specified
141		for _, ch := range req.Text {
142			key := rfb.RuneToKeysym(ch)
143			err = a.vnc.Tap(ctx, key)
144			if err != nil {
145				http.Error(w, err.Error(), http.StatusInternalServerError)
146				return
147			}
148			if req.DelayMs > 0 {
149				time.Sleep(time.Duration(req.DelayMs) * time.Millisecond)
150			}
151		}
152	}
153
154	w.WriteHeader(http.StatusNoContent)
155}
156
157// HandleSession handles session info API requests.
158func (a *API) HandleSession(w http.ResponseWriter, r *http.Request) {
159	if r.Method != http.MethodGet {
160		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
161		return
162	}
163
164	session := a.vnc.Session()
165	resp := SessionResponse{
166		Connected: session != nil,
167	}
168	if session != nil {
169		resp.Name = session.Name
170		resp.Width = int(session.Width)
171		resp.Height = int(session.Height)
172	}
173
174	w.Header().Set("Content-Type", "application/json")
175	json.NewEncoder(w).Encode(resp)
176}