main
Raw Download raw file
  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}