Add DevInfo.SupportsGE() function

This commit is contained in:
Matthew R. Wilson committed 2025-12-15 15:36:18 -08:00
1 parent 6c0b3b038a
commit bdc1d714d2
3 files changed
+46 -24

No files matched your search

+1 -1
View File
@@ -88,7 +88,7 @@ func init() {
With the new codepage detection support, you shouldn't typically rely on the default codepage: you should always pass the `DevInfo.Codepage()` value to the `ShowScreenOpts()` function or as the last optional argument to the `HandleScreen()` or `HandleScreenAlt()` functions. The global default should only be a fallback if the client code page isn't detected correctly.
Additionally, most characters from the "graphic escape" code page 310 are supported in all of the go3270-provided codepage implementations. Correct display on the client will depend on its support of graphic escape and correct characters being available in its font. Use the corresponding Unicode characters in your Go UTF-8 strings and they will be sent as the EBCDIC two-byte sequence of 0x08 followed by the position in code page 310. GE sequences are also processed on incoming field values.
Additionally, most characters from the "graphic escape" code page 310 are supported in all of the go3270-provided codepage implementations. Correct display on the client will depend on its support of graphic escape and correct characters being available in its font. The `DevInfo.SupportsGE()` function returns a boolean indicating that the client supports the graphic escape alternate code page. Use the corresponding Unicode characters in your Go UTF-8 strings and they will be sent as the EBCDIC two-byte sequence of 0x08 followed by the position in code page 310. GE sequences are also processed on incoming field values.
3270 information
----------------
+3
View File
@@ -53,6 +53,8 @@ var screen1 = go3270.Screen{
{Row: 15, Col: 0, Content: "Detected code page:"},
{Row: 15, Col: 20, Name: "codepage"},
{Row: 16, Col: 0, Content: "The following should be left and right square brackets: [ ]"},
{Row: 17, Col: 0, Content: "Graphic escape support:"},
{Row: 17, Col: 24, Name: "gesupport"},
{Row: 22, Col: 0, Content: "PF3 Exit"},
}
@@ -124,6 +126,7 @@ mainLoop:
} else {
fieldValues["codepage"] = devinfo.Codepage().ID()
}
fieldValues["gesupport"] = fmt.Sprintf("%v", devinfo.SupportsGE())
screen1Loop:
for {
+42 -23
View File
@@ -41,6 +41,10 @@ type DevInfo interface {
// default codepage).
Codepage() Codepage
// SupportsGE returns true if the client indicated it supports graphic
// escape to code page 310.
SupportsGE() bool
// Private version of AltDimensions() so callers can't fake us out; only
// real implementations returned by NegotiateTelnet() will work.
altDimensions() (rows, cols int)
@@ -293,6 +297,7 @@ var modelRegex = regexp.MustCompile(`^IBM-\d{4}-([2-5])`)
func makeDeviceInfo(conn net.Conn, termtype string) (DevInfo, error) {
var rows, cols, cpid int
var hasGE bool
var codepage Codepage
var isx3270 bool
@@ -355,7 +360,7 @@ func makeDeviceInfo(conn net.Conn, termtype string) (DevInfo, error) {
// Timeout. In this case, we'll assume it's because the client didn't
// reply to our query command. In that case, we'll return whatever
// we're already assuming.
return &deviceInfo{24, 80, termtype, nil}, nil
return &deviceInfo{24, 80, termtype, nil, false}, nil
} else if err != nil {
return nil, err
}
@@ -365,7 +370,7 @@ func makeDeviceInfo(conn net.Conn, termtype string) (DevInfo, error) {
// structured field queries? If we're not getting the kind of reply
// we're expecting, we'll return whatever we're already assuming.
flushConnection(conn, 50*time.Millisecond)
return &deviceInfo{24, 80, termtype, nil}, nil
return &deviceInfo{24, 80, termtype, nil, false}, nil
}
// There are an arbitrary number of query reply structured fields. We are
@@ -404,7 +409,7 @@ func makeDeviceInfo(conn net.Conn, termtype string) (DevInfo, error) {
}
} else if buf[0] == 0x81 && buf[1] == 0x85 {
// Character Sets
cpid = getCodepageID(buf)
cpid, hasGE = getCodepageID(buf)
} else if buf[0] == 0x81 && buf[1] == 0xA1 {
// RPQ Names. We use this to determine if the client is x3270
// family.
@@ -429,7 +434,7 @@ func makeDeviceInfo(conn net.Conn, termtype string) (DevInfo, error) {
codepage = nil
}
return &deviceInfo{rows, cols, termtype, codepage}, nil
return &deviceInfo{rows, cols, termtype, codepage, hasGE}, nil
}
// getUsableArea processes the "Query Reply (Usable Area)" response to return
@@ -465,43 +470,52 @@ func getUsableArea(buf []byte) (rows, cols int, err error) {
}
// getCodepageID processes the "Query Reply (Character Sets)" response to
// return the integer code page number if present. If unable, returns 0. The
// byte slice passed in to buf must begin with {0x81, 0x85}.
func getCodepageID(buf []byte) int {
// return the integer code page number if present. Also returns a boolean
// indicating if graphic escape to code page 310 is supported. If unable,
// returns 0. The byte slice passed in to buf must begin with {0x81, 0x85}.
func getCodepageID(buf []byte) (int, bool) {
// Initial validity check.
if len(buf) < 11 || buf[0] != 0x81 || buf[1] != 0x85 {
return 0
return 0, false
}
// If the GF bit is not set, no point in continuing.
if buf[2]&(1<<1) != 1<<1 {
return 0
return 0, false
}
// ALT flag = graphic escape supported
hasGE := buf[2]&(1<<7) == 1<<7
// Descriptor length
dl := int(buf[10])
// There may be more than one descriptors, and we need to find the first
// one with local ID 0.
// There may be more than one descriptors, and we need to find local ID 0,
// and possibly local ID 1 (the alternate codepage).
var primaryCPID, altCPID int
pos := 11 // first descriptor
for {
if len(buf) < pos+dl {
// No more descriptors and we haven't found anything yet
return 0
// No more descriptors
break
}
if buf[pos] != 0 {
// not the descriptor we're looking for, try the next one
pos += dl
continue
}
// This is the first descriptor we've seen with ID 0, we'll use it.
// No matter how long the descriptor is, the code page will 2-byte big
// endian integer in the last two bytes.
// CPID of this descriptor. No matter how long the descriptor is, the
// code page will be 2-byte big endian integer in the last two bytes.
cpid := int(buf[pos+dl-2])<<8 + int(buf[pos+dl-1])
return cpid
// First byte of the descriptor is the local ID; 0 is the base set and
// 1 is the alternate (graphic escape) set.
if buf[pos] == 0 && primaryCPID == 0 {
primaryCPID = cpid
} else if buf[pos] == 1 {
altCPID = cpid
}
pos += dl
}
return primaryCPID, hasGE && altCPID == 310
}
// getRPGNames checks the "Query Reply (RPQ NAMES)" response to see if the
@@ -656,6 +670,7 @@ type deviceInfo struct {
rows, cols int
termtype string
codepage Codepage
hasGE bool
}
func (d *deviceInfo) AltDimensions() (rows, cols int) {
@@ -673,3 +688,7 @@ func (d *deviceInfo) altDimensions() (rows, cols int) {
func (d *deviceInfo) Codepage() Codepage {
return d.codepage
}
func (d *deviceInfo) SupportsGE() bool {
return d.hasGE
}