main
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}