-
Notifications
You must be signed in to change notification settings - Fork 3
/
screen.go
283 lines (240 loc) · 7.95 KB
/
screen.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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
// This file is part of https://github.com/racingmars/go3270/
// Copyright 2020 by Matthew R. Wilson, licensed under the MIT license. See
// LICENSE in the project root for license information.
package go3270
import (
"bytes"
"net"
"strings"
)
// Field is a field on the 3270 screen.
type Field struct {
// Row is the row, 0-based, that the field attribute character should
// begin at. This library currently only supports 24 rows, so Row must
// be 0-23.
Row int
// Col is the column, 0-based, that the field attribute character should
// begin at. This library currently only supposed 80 columns, so Column
// must be 0-79.
Col int
// Text is the content of the field to display.
Content string
// Write allows the user to edit the value of the field.
Write bool
// Autoskip causes protected (Write = false) fields to automatically be
// skipped and the cursor should move to the next field upon encountering
// this field. Autoskip is ignored on fields with Write = true.
Autoskip bool
// Intense indicates this field should be displayed with high intensity.
Intense bool
// Hidden indicates the field content should not be displayed (e.g. a
// password input field).
Hidden bool
// NumericOnly indicates that only numbers may be entered into the field.
// Very fiew 3270 clients support this, so you must always still validate
// the input on the server side.
NumericOnly bool
// Color is the field color. The default value is the default color.
Color Color
// Highlighting is the highlight attribute for the field. The default value
// is the default (i.e. no) highlighting.
Highlighting Highlight
// Name is the name of this field, which is used to get the user-entered
// data. All writeable fields on a screen must have a unique name.
Name string
// KeepSpaces will prevent the strings.TrimSpace() function from being
// called on the field value. Generally you want leading and trailing
// spaces trimmed from fields in 3270 before processing, but if you are
// building a whitespace-sensitive application, you can ask for the
// original, un-trimmed value for a field by setting this to true.
KeepSpaces bool
}
// Color is a 3270 extended field attribute color value
type Color byte
// The valid 3270 colors
const (
DefaultColor Color = 0
Blue Color = 0xf1
Red Color = 0xf2
Pink Color = 0xf3
Green Color = 0xf4
Turquoise Color = 0xf5
Yellow Color = 0xf6
White Color = 0xf7
)
// Highlight is a 3270 extended field attribute highlighting method
type Highlight byte
// The valid 3270 highlights
const (
DefaultHighlight Highlight = 0
Blink Highlight = 0xf1
ReverseVideo Highlight = 0xf2
Underscore Highlight = 0xf4
)
// Screen is an array of Fields which compose a complete 3270 screen.
// No checking is performed for lack of overlapping fields, unique field
// names,
type Screen []Field
// fieldmap is a map of field buffer addresses and the corresponding field
// name.
type fieldmap map[int]string
// ShowScreen writes the 3270 datastream for the screen to a connection.
// Fields that aren't valid (e.g. outside of the 24x80 screen) are silently
// ignored. If a named field has an entry in the values map, the content of
// the field from the values map is used INSTEAD OF the Field struct's Content
// field. The values map may be nil if no overrides are needed. After writing
// the fields, the cursor is set to crow, ccol, which are 0-based positions:
// row 0-23 and col 0-79. Errors from conn.Write() are returned if
// encountered.
func ShowScreen(screen Screen, values map[string]string, crow, ccol int,
conn net.Conn) (Response, error) {
var b bytes.Buffer
var fm = make(fieldmap) // field buffer positions -> name
b.WriteByte(0xf5) // Erase/Write to terminal
b.WriteByte(0xc3) // WCC = Reset, Unlock Keyboard, Reset MDT
// Build the commands for each field on the screen
for _, fld := range screen {
if fld.Row < 0 || fld.Row > 23 || fld.Col < 0 || fld.Col > 79 {
// Invalid field position
continue
}
b.Write(sba(fld.Row, fld.Col))
b.Write(buildField(fld))
// Use fld.Content, unless the field is named and appears in the
// value map.
content := fld.Content
if fld.Name != "" {
if val, ok := values[fld.Name]; ok {
content = val
}
}
if content != "" {
b.Write(a2e([]byte(content)))
}
// If a writable field, add it to the field map. We add 1 to bufaddr
// to make the value match the reported position (I'm guessing it's
// because we get the position of the field's first input position,
// not the position of the field attribute byte).
if fld.Write {
bufaddr := fld.Row*80 + fld.Col
fm[bufaddr+1] = fld.Name
}
}
// Set cursor position. Correct out-of-bounds values to 0.
if crow < 0 || crow > 23 {
crow = 0
}
if ccol < 0 || ccol > 79 {
ccol = 0
}
b.Write(ic(crow, ccol))
b.Write([]byte{0xff, 0xef}) // Telnet IAC EOR
// Now write the datastream to the writer, returning any potential error.
debugf("sending datastream: %x\n", b.Bytes())
if _, err := conn.Write(b.Bytes()); err != nil {
return Response{}, err
}
response, err := readResponse(conn, fm)
if err != nil {
return response, err
}
// Strip leading+trailing spaces from field values
for _, fld := range screen {
if !fld.KeepSpaces {
if _, ok := response.Values[fld.Name]; ok {
response.Values[fld.Name] =
strings.TrimSpace(response.Values[fld.Name])
}
}
}
return response, nil
}
// sba is the "set buffer address" 3270 command.
func sba(row, col int) []byte {
result := make([]byte, 1, 3)
result[0] = 0x11 // SBA
result = append(result, getpos(row, col)...)
return result
}
// buildField will return either an sf or sfe command depending for the
// field.
func buildField(f Field) []byte {
var buf bytes.Buffer
if f.Color == DefaultColor && f.Highlighting == DefaultHighlight {
// this is a traditional field, issue a normal sf command
buf.WriteByte(0x1d) // sf - "start field"
buf.WriteByte(sfAttribute(f.Write, f.Intense, f.Hidden, f.Autoskip,
f.NumericOnly))
return buf.Bytes()
}
// Otherwise, this needs an extended attribute field
buf.WriteByte(0x29) // sfe - "start field extended"
var paramCount byte = 1 // we will always have the basic field attribute
if f.Color != DefaultColor {
paramCount++
}
if f.Highlighting != DefaultHighlight {
paramCount++
}
buf.WriteByte(paramCount)
// Write the basic field attribute
buf.WriteByte(0xc0)
buf.WriteByte(sfAttribute(f.Write, f.Intense, f.Hidden, f.Autoskip,
f.NumericOnly))
// Write the highlighting attribute
if f.Highlighting != DefaultHighlight {
buf.WriteByte(0x41)
buf.WriteByte(byte(f.Highlighting))
}
// Write the color attribute
if f.Color != DefaultColor {
buf.WriteByte(0x42)
buf.WriteByte(byte(f.Color))
}
return buf.Bytes()
}
// sfAttribute builds the attribute byte for the "start field" 3270 command
func sfAttribute(write, intense, hidden, skip, numeric bool) byte {
var attribute byte
if !write {
attribute |= 1 << 5 // set "bit 2"
if skip {
attribute |= 1 << 4 // set "bit 3"
}
} else {
// The MDT bit -- we always want writable field values returned,
// even if unchanged
attribute |= 1 // set "bit 7"
if numeric {
attribute |= 1 << 4 // set "bit 3"
}
}
if intense {
attribute |= 1 << 3 // set "bit 4"
}
if hidden {
attribute |= 1 << 3 // set "bit 4"
attribute |= 1 << 2 // set "bit 5"
}
// Fill in top 2 bits with appropriate values
attribute = codes[attribute]
return attribute
}
// ic is the "insert cursor" 3270 command. This function will include the
// appropriate SBA command.
func ic(row, col int) []byte {
result := make([]byte, 0, 3)
result = append(result, sba(row, col)...)
result = append(result, 0x13) // IC
return result
}
// getpos translates row and col to buffer address control characters.
func getpos(row, col int) []byte {
result := make([]byte, 2)
address := row*80 + col
hi := (address & 0xfc0) >> 6
lo := address & 0x3f
result[0] = codes[hi]
result[1] = codes[lo]
return result
}