// 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 main import ( "fmt" "net" "strings" "time" "github.com/racingmars/go3270" ) func init() { // put the go3270 library in debug mode: // go3270.Debug = os.Stderr } // A Screen is an array of go3270.Field structs. We will build two screens, // a query screen and a result screen: var screen1 = go3270.Screen{ {Row: 0, Col: 27, Intense: true, Content: "3270 Example Application"}, {Row: 2, Col: 0, Content: "Welcome to the go3270 example application. Please enter your name."}, {Row: 4, Col: 0, Content: "First Name . . ."}, {Row: 4, Col: 19, Name: "fname", Write: true, Highlighting: go3270.Underscore}, {Row: 4, Col: 40, Autoskip: true}, // field "stop" character {Row: 5, Col: 0, Content: "Last Name . . . ."}, {Row: 5, Col: 19, Name: "lname", Write: true, Highlighting: go3270.Underscore}, {Row: 5, Col: 40, Autoskip: true}, // field "stop" character {Row: 6, Col: 0, Content: "Password . . . ."}, {Row: 6, Col: 19, Name: "password", Write: true, Hidden: true}, {Row: 6, Col: 40}, // field "stop" character {Row: 7, Col: 0, Content: "Employee ID . . ."}, {Row: 7, Col: 19, Name: "employeeID", Write: true, Highlighting: go3270.Underscore, NumericOnly: true}, {Row: 7, Col: 40}, // field "stop" character {Row: 8, Col: 0, Content: "Press"}, {Row: 8, Col: 6, Intense: true, Content: "enter"}, {Row: 8, Col: 12, Content: "to submit your name."}, {Row: 10, Col: 0, Intense: true, Color: go3270.Red, Name: "errormsg"}, // a blank field for error messages // Demonstrate the AttributeOnly property: {Row: 12, Col: 0, Content: "Demonstration of AttributeOnly to change format without a new field:"}, {Row: 13, Col: 1, AttributeOnly: true, Color: go3270.Green, Content: "████"}, {Row: 13, Col: 5, AttributeOnly: true, Color: go3270.Yellow, Content: "████"}, {Row: 13, Col: 9, AttributeOnly: true, Color: go3270.Red, Content: "████"}, {Row: 13, Col: 13, AttributeOnly: true, Color: go3270.Red, Highlighting: go3270.Blink, Content: "████"}, // When using extended attributes, need to explicitly reset to defaults, the next field won't necessarily clear the attributes out. {Row: 13, Col: 17, AttributeOnly: true, Color: go3270.DefaultColor, Highlighting: go3270.DefaultHighlight}, {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"}, // Can use PositionOnly attribute to place text beginning at a specific // position without introducing a new attribute byte. {Row: 19, Col: 0, PositionOnly: true, Content: "This text starts in first column"}, {Row: 22, Col: 0, Content: "PF3 Exit"}, } var screen2 = go3270.Screen{ {Row: 0, Col: 27, Intense: true, Content: "3270 Example Application"}, {Row: 2, Col: 0, Content: "Thank you for submitting your name. Here's what I know:"}, {Row: 4, Col: 0, Content: "Your first name is"}, {Row: 4, Col: 19, Name: "fname"}, // We're giving this field a name to replace its value at runtime {Row: 5, Col: 0, Content: "And your last name is"}, {Row: 5, Col: 22, Name: "lname"}, // We're giving this field a name to replace its value at runtime {Row: 6, Col: 0, Content: "And your employeed ID is"}, {Row: 6, Col: 22, Name: "employeeID"}, // We're giving this field a name to replace its value at runtime {Row: 7, Col: 0, Name: "passwordOutput"}, {Row: 9, Col: 0, Content: "Press"}, {Row: 9, Col: 6, Intense: true, Content: "enter"}, {Row: 9, Col: 12, Content: "to enter your name again, or"}, {Row: 9, Col: 41, Intense: true, Content: "PF3"}, {Row: 9, Col: 45, Content: "to quit and disconnect."}, {Row: 12, Col: 0, Color: go3270.Turquoise, Highlighting: go3270.ReverseVideo, Content: "Here is a field with extended attributes."}, {Row: 12, Col: 42}, // remember to "stop" fields with a regular field, to clear the reverse video for example {Row: 14, Col: 0, Name: "position"}, // will be filled when displaying {Row: 22, Col: 0, Content: "PF3 Exit"}, } var goodbyeScreen = go3270.Screen{ {Row: 0, Col: 27, Intense: true, Content: "3270 Example Application"}, {Row: 2, Col: 0, Content: "Thank you using this application. Goodbye."}, } func main() { ln, err := net.Listen("tcp", ":3270") if err != nil { panic(err) } fmt.Println("LISTENING ON PORT 3270 FOR CONNECTIONS") fmt.Println("Press Ctrl-C to end server.") for { conn, err := ln.Accept() if err != nil { panic(err) } go handle(conn) } } // handle is the handler for individual user connections. func handle(conn net.Conn) { defer conn.Close() // Always begin new connection by negotiating the telnet options devinfo, err := go3270.NegotiateTelnet(conn) if err != nil { fmt.Printf("ERROR: %v\n", err) return } fieldValues := make(map[string]string) var response go3270.Response // We will loop forever until the user quits with PF3 mainLoop: for { // Need to set the codepage display field value each time through the // loop so it persists between screen1->screen2->screen1 navigation. if devinfo.Codepage() == nil { fieldValues["codepage"] = "(unknown)" } else { fieldValues["codepage"] = devinfo.Codepage().ID() } fieldValues["gesupport"] = fmt.Sprintf("%v", devinfo.SupportsGE()) screen1Loop: for { // loop until the user passes input validation, or quits // Always reset password input to blank each time through the loop fieldValues["password"] = "" // Show the first screen, and wait to get a client response. Place // the cursor at the beginning of the first input field. We're // passing in the fieldValues map to carry values over from the // previous submission. We could pass nil, instead, if always want // the fields to start out blank. response, err = go3270.ShowScreenOpts(screen1, fieldValues, conn, go3270.ScreenOpts{ Codepage: devinfo.Codepage(), CursorRow: 4, CursorCol: 20, }) if err != nil { fmt.Println(err) return } // If the user pressed PF3, exit if response.AID == go3270.AIDPF3 { go3270.ShowScreenOpts(goodbyeScreen, nil, conn, go3270.ScreenOpts{ Codepage: devinfo.Codepage(), NoResponse: true, }) time.Sleep(2 * time.Second) break mainLoop } // If anything OTHER than "Enter", restart the loop if response.AID != go3270.AIDEnter { continue screen1Loop } // User must have pressed "Enter", so let's check the input. fieldValues = response.Values if strings.TrimSpace(fieldValues["fname"]) == "" && strings.TrimSpace(fieldValues["lname"]) == "" { fieldValues["errormsg"] = "First and Last Name fields are required." continue screen1Loop } if strings.TrimSpace(fieldValues["fname"]) == "" { fieldValues["errormsg"] = "First Name field is required." continue screen1Loop } if strings.TrimSpace(fieldValues["lname"]) == "" { fieldValues["errormsg"] = "Last Name field is required." continue screen1Loop } // At this point, we know the user provided both fields and had // hit enter, so we are going to reset the error message for the // next time through the loop, and break out of this loop so we // move on to screen 2. fieldValues["errormsg"] = "" break screen1Loop } // Now we're ready to display screen2 passwordLength := len(strings.TrimSpace(fieldValues["password"])) passwordPlural := "s" if passwordLength == 1 { passwordPlural = "" } fieldValues["passwordOutput"] = fmt.Sprintf( "Your password was %d character%s long", passwordLength, passwordPlural) fieldValues["position"] = fmt.Sprintf( "When you pressed enter the cursor was at row %d column %d.", response.Row+1, response.Col+1) response, err := go3270.ShowScreenOpts(screen2, fieldValues, conn, go3270.ScreenOpts{Codepage: devinfo.Codepage()}) if err != nil { fmt.Println(err) return } // If the user pressed PF3, exit if response.AID == go3270.AIDPF3 { go3270.ShowScreenOpts(goodbyeScreen, nil, conn, go3270.ScreenOpts{ Codepage: devinfo.Codepage(), NoResponse: true, }) time.Sleep(2 * time.Second) break } // If they pressed anything else, just let the loop continue... continue } fmt.Println("Connection closed") }