-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathclient.go
152 lines (132 loc) · 4.03 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package bring
import (
"errors"
"image"
"strconv"
"github.com/deluan/bring/protocol"
)
// ErrInvalidKeyCode is returned by SendKey if an invalid code is passed
var ErrInvalidKeyCode = errors.New("invalid key code")
// OnSyncFunc is the signature for OnSync event handlers. It will receive the current screen image and the
// timestamp of the last update.
type OnSyncFunc = func(image image.Image, lastUpdate int64)
// Client is the main struct in this library, it represents the Guacamole protocol client.
// Automatically handles incoming and outgoing Guacamole instructions, updating its display
// using one or more graphic primitives.
type Client struct {
session *session
display *display
streams streams
logger Logger
onSync OnSyncFunc
}
// NewClient creates a Client and connects it to the guacd server with the provided configuration. Logger is optional
func NewClient(addr string, remoteProtocol string, config map[string]string, logger ...Logger) (*Client, error) {
var log Logger
if len(logger) > 0 {
log = logger[0]
} else {
log = &DefaultLogger{}
}
s, err := newSession(addr, remoteProtocol, config, log)
if err != nil {
return nil, err
}
c := &Client{
session: s,
display: newDisplay(log),
streams: newStreams(),
logger: log,
}
return c, nil
}
// Start the Client's main loop. It is a blocking call, so it
// should be called in its on goroutine
func (c *Client) Start() {
for {
select {
case ins := <-c.session.In:
h, ok := handlers[ins.Opcode]
if !ok {
c.logger.Errorf("Instruction not implemented: %s", ins.Opcode)
continue
}
err := h(c, ins.Args)
if err != nil {
c.session.Terminate()
}
}
}
}
// OnSync sets a function that will be called on every sync instruction received. This event
// usually happens after a batch of updates are received from the guacd server, making it a
// perfect way to get the current screenshot without having to poll with Screen().
// The handler is expected to be called frequently, so avoid adding any blocking behaviour.
// If your handler is slow, consider using a concurrent pattern (using goroutines)
func (c *Client) OnSync(f OnSyncFunc) {
c.onSync = f
}
// Screen returns a snapshot of the current screen, together with the last updated timestamp
func (c *Client) Screen() (image image.Image, lastUpdate int64) {
return c.display.getCanvas()
}
// State returns the current session state
func (c *Client) State() SessionState {
return c.session.State
}
// SendMouse sends mouse events to the server. An event is composed by position of the
// cursor, and a list of any currently pressed MouseButtons
func (c *Client) SendMouse(p image.Point, pressedButtons ...MouseButton) error {
if c.session.State != SessionActive {
return ErrNotConnected
}
buttonMask := 0
for _, b := range pressedButtons {
buttonMask |= int(b)
}
c.display.moveCursor(p.X, p.Y)
err := c.session.Send(protocol.NewInstruction("mouse", strconv.Itoa(p.X), strconv.Itoa(p.Y), strconv.Itoa(buttonMask)))
if err != nil {
return err
}
return nil
}
// SendText sends the sequence of characters as they were typed. Only works with simple chars
// (no combination with control keys)
func (c *Client) SendText(sequence string) error {
if c.session.State != SessionActive {
return ErrNotConnected
}
for _, ch := range sequence {
keycode := strconv.Itoa(int(ch))
err := c.session.Send(protocol.NewInstruction("key", keycode, "1"))
if err != nil {
return nil
}
err = c.session.Send(protocol.NewInstruction("key", keycode, "0"))
if err != nil {
return nil
}
}
return nil
}
// SendKey sends key presses and releases.
func (c *Client) SendKey(key KeyCode, pressed bool) error {
if c.session.State != SessionActive {
return ErrNotConnected
}
keySym, ok := keySyms[key]
if !ok {
return ErrInvalidKeyCode
}
p := "0"
if pressed {
p = "1"
}
var instructions []*protocol.Instruction
for _, k := range keySym {
keycode := strconv.Itoa(k)
instructions = append(instructions, protocol.NewInstruction("key", keycode, p))
}
return c.session.Send(instructions...)
}