main
1// Package input provides input handling utilities for VNC clients.
2package input
3
4import (
5 "context"
6 "io"
7 "time"
8
9 "goVNC/pkg/rfb"
10)
11
12// KeySender can send key events to a VNC server.
13type KeySender interface {
14 KeyEvent(ctx context.Context, key uint32, down bool) error
15}
16
17// Keyboard handles keyboard input processing.
18type Keyboard struct {
19 sender KeySender
20 delay time.Duration
21}
22
23// KeyboardOption configures a Keyboard.
24type KeyboardOption func(*Keyboard)
25
26// WithKeyDelay sets a delay between keystrokes.
27func WithKeyDelay(d time.Duration) KeyboardOption {
28 return func(k *Keyboard) {
29 k.delay = d
30 }
31}
32
33// NewKeyboard creates a new keyboard handler.
34func NewKeyboard(sender KeySender, opts ...KeyboardOption) *Keyboard {
35 k := &Keyboard{
36 sender: sender,
37 }
38 for _, opt := range opts {
39 opt(k)
40 }
41 return k
42}
43
44// Press sends a key down event.
45func (k *Keyboard) Press(ctx context.Context, key uint32) error {
46 return k.sender.KeyEvent(ctx, key, true)
47}
48
49// Release sends a key up event.
50func (k *Keyboard) Release(ctx context.Context, key uint32) error {
51 return k.sender.KeyEvent(ctx, key, false)
52}
53
54// Tap presses and releases a key.
55func (k *Keyboard) Tap(ctx context.Context, key uint32) error {
56 if err := k.Press(ctx, key); err != nil {
57 return err
58 }
59 return k.Release(ctx, key)
60}
61
62// Type types a string, converting each character to key events.
63func (k *Keyboard) Type(ctx context.Context, text string) error {
64 for _, r := range text {
65 select {
66 case <-ctx.Done():
67 return ctx.Err()
68 default:
69 }
70
71 key := rfb.RuneToKeysym(r)
72 if err := k.Tap(ctx, key); err != nil {
73 return err
74 }
75
76 if k.delay > 0 {
77 time.Sleep(k.delay)
78 }
79 }
80 return nil
81}
82
83// Combo executes a key combination (e.g., Ctrl+C).
84// All modifier keys are pressed, then the final key is tapped,
85// then all modifiers are released in reverse order.
86func (k *Keyboard) Combo(ctx context.Context, keys ...uint32) error {
87 if len(keys) == 0 {
88 return nil
89 }
90
91 // Press all but last key
92 modifiers := keys[:len(keys)-1]
93 finalKey := keys[len(keys)-1]
94
95 // Press modifiers
96 for _, mod := range modifiers {
97 if err := k.Press(ctx, mod); err != nil {
98 return err
99 }
100 }
101
102 // Tap final key
103 if err := k.Tap(ctx, finalKey); err != nil {
104 return err
105 }
106
107 // Release modifiers in reverse order
108 for i := len(modifiers) - 1; i >= 0; i-- {
109 if err := k.Release(ctx, modifiers[i]); err != nil {
110 return err
111 }
112 }
113
114 return nil
115}
116
117// Writer returns an io.Writer that types input to the VNC session.
118func (k *Keyboard) Writer(ctx context.Context) io.Writer {
119 return &keyboardWriter{
120 ctx: ctx,
121 keyboard: k,
122 }
123}
124
125type keyboardWriter struct {
126 ctx context.Context
127 keyboard *Keyboard
128}
129
130func (w *keyboardWriter) Write(p []byte) (int, error) {
131 err := w.keyboard.Type(w.ctx, string(p))
132 if err != nil {
133 return 0, err
134 }
135 return len(p), nil
136}
137
138// ParseKeyName converts a key name to a keysym.
139// Supports names like "ctrl", "shift", "alt", "return", "tab", etc.
140func ParseKeyName(name string) (uint32, bool) {
141 switch name {
142 case "ctrl", "control":
143 return rfb.KeyControlL, true
144 case "shift":
145 return rfb.KeyShiftL, true
146 case "alt":
147 return rfb.KeyAltL, true
148 case "meta", "super", "win", "cmd":
149 return rfb.KeySuperL, true
150 case "return", "enter":
151 return rfb.KeyReturn, true
152 case "tab":
153 return rfb.KeyTab, true
154 case "escape", "esc":
155 return rfb.KeyEscape, true
156 case "backspace":
157 return rfb.KeyBackspace, true
158 case "delete", "del":
159 return rfb.KeyDelete, true
160 case "insert", "ins":
161 return rfb.KeyInsert, true
162 case "home":
163 return rfb.KeyHome, true
164 case "end":
165 return rfb.KeyEnd, true
166 case "pageup", "pgup":
167 return rfb.KeyPageUp, true
168 case "pagedown", "pgdn":
169 return rfb.KeyPageDown, true
170 case "left":
171 return rfb.KeyLeft, true
172 case "right":
173 return rfb.KeyRight, true
174 case "up":
175 return rfb.KeyUp, true
176 case "down":
177 return rfb.KeyDown, true
178 case "f1":
179 return rfb.KeyF1, true
180 case "f2":
181 return rfb.KeyF2, true
182 case "f3":
183 return rfb.KeyF3, true
184 case "f4":
185 return rfb.KeyF4, true
186 case "f5":
187 return rfb.KeyF5, true
188 case "f6":
189 return rfb.KeyF6, true
190 case "f7":
191 return rfb.KeyF7, true
192 case "f8":
193 return rfb.KeyF8, true
194 case "f9":
195 return rfb.KeyF9, true
196 case "f10":
197 return rfb.KeyF10, true
198 case "f11":
199 return rfb.KeyF11, true
200 case "f12":
201 return rfb.KeyF12, true
202 default:
203 return 0, false
204 }
205}