Grab Data from the Screen

Article ID: 57287

Q: We have a requirement to extract data from an existing green-screen display into an Attention-key program. The users run the green-screen application as usual, and when they get to the right screen, they hit the Esc key on their keyboard, which is mapped to the 5250 Attention key and causes my RPG program to run. My RPG program should read certain numbers off of the screen and then pass those numbers to a PC program (via the STRPCCMD command).

I can't change the existing green-screen program (it's third-party software, and I don't have the code), and the users need to continue to run the application interactively (I can't run the application in a separate screen-scraper job). So I need to extract the numbers straight from the screen image. I know the correct rows and columns to get the data from, but I can't figure out how to extract data from the screen! I've scoured the Internet looking for a solution to no avail. Do you have any advice?

A: I have an idea that just might do the trick.

Starting with V2R3, OS/400 has had an API called Read Screen (QsnReadScr). When you call the API, it asks the 5250 terminal (or emulator) to return the contents of the screen, and it stores that screen image in a buffer. If you overlay that buffer with an array, you can use that array to read the screen.

For the sake of demonstration, let's say your screen looks like this:

 10/08/08              Displaying Order Number: 72938                 OEICBDS2C 
                                                                                
  Customer: 8811       ACME INDUSTRIES             Order Taker:  SPONGEBOB      
   Bill to: 5800       123 IMPORTANT LN             Ordered on:  2007/12/27     
 Disc Type:            ANYTOWN, USA                         at:   8:37:08       
 Disc Rate:                                         Deliver on:  2008/01/07     
 Disc Amnt:            P.O.#:  837829               Route/Stop:  6R / 000       
                       Date Paid:  01/28/08                                     
    Ordered     Prod.                           Shipped     Billed              
 Quantity  Unit Code     Product Description      Qty.   Price   Unit Extension 
------------------------------------------------------------------------------- 
     126.00 BXS  5080 BLUE ROUND WIDGET          1512.00     1.99 LBS   2647.81 
      30.00 BXS  5212 RED SQUARE WIDGET           360.00     1.99 LBS    630.43 
     126.00 BXS  5660 GREEN PLUSH WIDGET         1512.00     1.99 LBS   2647.81 
     180.00 BXS  5690 PLAIN WHITE WIDGET         2160.00     1.99 LBS   3782.59 
     170.00 BXS  5720 ORANGE TRIANGLE WIDGET     2040.00     1.99 LBS   3572.45 
      80.00 BXS  5740 BLUE SQUARE WIDGET          960.00     1.99 LBS   1681.15 
     150.00 BXS  5745 ORANGE JUICE                150.00    23.88 BXS   3152.16 
     210.00 BXS  5750 RED ROUND WIDGET           2520.00     1.99 LBS   4413.02 
                                                                                
                                                                                
                                                                                
                                                                                
  F3=Exit   F12=Cancel   F11=Display Billing Info  F6=Print              Bottom 

For this example, we need to get the fields that I've highlighted in red: the order number from line 1, column 49; the screen ID from line 1, column 71; and the customer number from line 3, column 13.

The screen ID is used only to verify that the user presses the Esc key on the correct display. If the wrong screen ID is entered, the program simply ignores the request.

My program to extract the data starts by determining whether this is a 132x27 screen or an 80x24 screen by calling the Retrieve Screen Dimensions (QsnRtvScrDim) API.

     D height          s             10I 0
     D width           s             10I 0
       .     
       .                                   
       QsnRtvScrDim( height        
                   : width         
                   : *omit         
                   : *omit );      

This API passes back the screen height and width into the parameters I've provided, above. The last two parameters are omissible, and since I don't need them, I've provided the *OMIT special value.

The screen dimensions are important because they dictate the buffer size. Remember, we're going to load the whole screen into this buffer, so it has to be large enough for either 80x24 or 132x27, depending on the current screen dimensions. IBM provides the Create Input Buffer (QsnCrtInpBuf) API for creating the buffer.

     D inbuf           s             10I 0 
       .
       .
       inbuf = QsnCrtInpBuf( height * width
                           : *omit         
                           : *omit         
                           : *omit         
                           : *omit    );   

This API creates an input buffer in the computer's memory, and it returns a handle to that buffer. When I want to retrieve the screen contents, I pass that inbuf handle to the QsnReadScr API and that tells the system where to store the screen contents.

     D p_screen        s               *                          
     D screen80        s             80A   dim(24) based(p_screen)
     D screen132       s            132A   dim(27) based(p_screen)                                
       .
       .
       QsnReadScr( *omit    
                 : inbuf    
                 : *omit    
                 : *omit    
                 : *omit ); 

        p_screen = QsnRtvDta( inbuf   
                            : *omit   
                            : *omit );

The QsnReadScr API is the key piece of this program. This API causes the operating system to request the screen contents from the 5250 terminal (or emulator). The terminal sends back the screen, and the operating system stores it in the buffer that we created earlier. Now that it's in that buffer, I call the Retrieve Pointer to Data in Input Buffer (QsnRtvDta) API, and it tells me where the screen data is located in the computer's memory.

Because my screen80 and screen132 arrays are based on the p_screen pointer, they overlay the same spot in memory as my screen image. Now I can read those arrays to get the data from the screen.

Why do I have two arrays? Well, the layout of the buffer will be a little different if the screen is 80 column instead of 132 column. So one array is designed to interpret 80-column data, and the other interprets 132-column data.

     D ordno           s              5A  
     D cust            s              4a  
     D screen          s              9a  

        if (width = 132);                       
           screen = %subst(Screen132(1): 71: 9);
           ordno  = %subst(Screen132(1): 49: 5);
           cust   = %subst(Screen132(3): 13: 4);
        else;                                   
           screen = %subst(Screen80(1): 71: 9); 
           ordno  = %subst(Screen80(1): 49: 5); 
           cust   = %subst(Screen80(3): 13: 4); 
        endif;    

        QsnDltBuf( inbuf : *omit );

Each entry in my array represents one row of the screen. So if I read Screen80(1), it gives me the contents of the first row. If I read Screen80(3), it gives me the contents of the third row. To extract only part of the row, I use the %subst() BIF.

At this point, if all is well, the screen ID is in the variable named screen, the order number is in the variable named ordno, and the customer number is in the variable named cust. I no longer need to interrogate the screen further, so I call the Delete Buffer (QsnDltBuf) API to tell the system I no longer need the buffer, and it can use the memory for something else.

Finally, I can use that data with Start PC Command (STRPCCMD). For the sake of example, I tell the PC to open a browser window, and I tell it to display the particular order and customer number. Obviously, in your scenario, the PC command will be different.

     if (screen = 'OEICBDS2C');                                   
                                                                  
        callp(e) QCMDEXC('STRPCO': 6);                            
                                                                  
        cmd = 'STRPCCMD PCCMD('rundll32 url,FileProtocolHandler '
            + 'http://as400.example.com/cgi-bin/oeiinvhr4.pgm'   
            + '?order=' + %trim(Ordno)                            
            + '&cust=' + %trim(cust)                              
            + ') PAUSE(*NO)';                                   
                                                                  
        callp(e) QCMDEXC(cmd: %len(cmd));                         
                                                                  
     endif;

After I finish coding my program, I need to install it as the Attention key program in my job. To do that, I type the following:

SETATNPGM PGM(GRABDATA) SET(*ON)

Now that I've done that, whenever I press the 5250 Attention key (usually the Esc key on the PC), the operating system runs my GRABDATA program, which extracts the appropriate data and runs my PC command.

The Set Attention Program (SETATNPGM) command sets the Attention key only temporarily. If I want to make it permanent, I can run the following command instead:

CHGUSRPRF USRPRF(my-user-id) ATNPGM(GRABDATA)

You can download the complete GRABDATA program from the following link.
http://www.pentontech.com/IBMContent/Documents/article/57287_684_GrabData.zip

Part 2 of this article explains how to grab data from the screen from a CL program by using DDS instead of calling APIs.

ProVIP Sponsors

ProVIP Sponsors