
            [42m                                          [40m    
            [42m            RAWKEYS in ARexx              [40m
            [42m            ----------------              [40m
            [42m  Getting the Function keys to function   [40m
            [42m                                          [40m  [3;33mby John Collett[0m
            [42m  and getting help from the 'Help' key    [40m
            [42m                                          [40m

                            
   [42m  Defining a Need  [40m
        
    · At any time, I can click on LeftAmiga/Help, get a run down of all my
    AutoCLI settings, and change any of them. 
     
    · When I am in HyperBook (which is, nowadays, all the time), a click on
    the Help button turns the pointer into a question mark, and I can then
    get a brief reminder of the function of any of the editing gadgets
    (though in fact I rarely need such help now). 
    
    · When I am using the QED text editor, I can use F keys, shifted F
    keys, Alt/F keys, Ctrl/F keys, and so on, all of them with functions
    which I have decided I want them to have, and which I have entered in
    the relevant startup file.  A menu item shows all F key settings.

    So I have got into the habit of expecting help to be easily available
    when I want it, and finally decided that it would be useful to set up
    some sort of help-displaying procedure which I might be able to include
    in any ARexx program.  

    Sounds easy, doesn't it?  You just want a program to know when you have
    pressed, say, the Help key, or an F key.  I seem to remember it was
    easy in Basic, where they had that INKEY$ thing which could quickly be
    trained to recognise any key press.  So I tried to do something similar
    in ARexx.  Instructions and functions like 'pull' and 'readch()' could
    manage the easy part of the job, recognising the legible and printable
    alphanumeric characters, but after that I ran into problems.  I could
    not get them to recognise some of the special keys, and had to look
    further afield for explanation and advice. 
    
   [42m  Explorations  [40m
    
    I knew it was something to do with RAW keys, and the QED docs provide
    an easy introduction to that topic.  When setting up QED key entries, I
    consult the 'RAWKEY CODE - KEY CAP TABLE' in the QED docs, where key
    codes 0 to 95 are listed. The underlying pattern is easy to see if you
    think in 16s. 
   
      0  [42m ` [40m                 --------->             [42m \ [40m  13
                                                     
           16  [42m Q [40m           --------->           [42m ] [40m  27
                                                     
             32  [42m A [40m         --------->         [42m ' [40m  42
                                                     
               49  [42m Z [40m       --------->       [42m / [40m  58
     
    
    The upper-left key of the main keyboard, the ~ and ` key, is 0.  Count
    along to the right as far as the | and \ key, and you get to 13.  For
    the following rows, start at 16 for the Q key, 32 for the A key, and 49
    for the Z key (48 is 'reserved').  On all three lines you just count
    along to the right, as far as 27 for } and ], 42 for " and ', and 58
    for ? and / respectively. The numeric pad, the F keys, and all other
    keys use the remaining numbers.  The 'Help' key, for example, happens
    to be the last in the list, number 95, and so to give it a particular
    job in QED, you make an entry in the QED startup file such as 'KEY 95
    TEXT Help Key pressed'.  In this utterly trivial example, when you
    press the 'Help' key the words 'Help key pressed' appear at the cursor
    in your current editing window. The point is, it is easy to set any key
    to anything, and QED knows when any key is pressed.  I wanted to
    emulate this in my own ARexx programs.

    Pages 11 to 13 of [4mThe AmigaDOS Manual[0m (Second Edition, Bantam
    Amiga Library, 1987) started to look promising when they described a
    window device called RAW: which 'does no character translation', and
    'accepts input and returns output in exactly the same form that it was
    originally typed'.  But then there was this box :
    
     -----------------------------------------------------------------
    | [1mWARNING[0m RAW: is intended for the advanced user. Do not use RAW: |
    | experimentally.                                                 |
     -----------------------------------------------------------------
    
    - which is a bit like telling someone not to get into the water until
    they have learned to swim.
    
    No further mention until page 224, where for the next 14 pages you are
    told everything there is to know about CON Keyboard Input, CON Screen
    Output, RAW Screen Output, RAW Keyboard Input (yes, in that order),
    Selection of RAW Input Events, and RAW Input Event Types.  There are
    heaps of clearly printed tables, with a reassuring degree of
    correspondence with those in the QED docs mentioned above, and here and
    there overlapping with what I already know about ANSI codes.  Pages
    234-237 have a list like the one in the QED docs of Raw Key Numbers,
    plus Unshifted Default Values, and Shifted Default Values. This version
    is in hex rather than decimal, the four main rows starting with 00, 10,
    20, 30 - an occasion on which the hex values appear to have the edge on
    the decimal set for clarity.  (Though it is a bit odd to note that the
    list calls 'A', for example, the 'Unshifted Default Value' and 'a' the
    'Shifted Default Value', with all the letters of the alphabet similarly
    reversed!  Am I missing something?)

    And then I tried, I really did, to understand all the accompanying
    text on those pages, and to get some sort of meaningful response about
    RAW values back from the machine.  After one or two early encouraging
    signs, no matter what I did, I soon came up against an impenetrable
    barrier of no response or an unexpected and apparently irrelevant
    response, and at last the AmigaDOS Manual went back on the shelf.

    Of course, I also scanned Megadisc indexes and Fish Disk lists (try
    saying that lot quickly when you've had a couple of beers), but
    couldn't find anything to help unravel the problem, and was honestly
    going to abandon the whole idea.  Then I remembered that one of the
    categories of 'idcmp' messages catered for in the OpenWindow() function
    in RexxArpLib was 'RAWKEY'.  I looked it up, checked it out, and got it
    to work - and this is where the story really starts (though I would
    still like to hear from anyone who knows a way of recognising the RAW
    keys in ARexx without using 'rexxarplib.library'). 

   [42m  Laying the Foundations  [40m
    
    The OpenWindow() function may specify any combination of up to 16
    different types of messages which it is prepared to expect, to receive,
    to recognise, and to act upon.  Crucial for the present discussion,
    they include VANILLAKEY and RAWKEY.
    
    If you want to use the OpenWindow() function you must previously use
    the CreateHost() function, and to do that you must have the
    'rexxarplib.library' available in LIBS:, and explicitly added to the
    'active' list by a call to addlib() early in a program. The key job of
    CreateHost() is to open a couple of message ports, one for the
    proposed window to 'listen to', and another which must be notified
    when any IDCMP event occurs (a mouse click, a keypress, a menu
    choice, and so on).  The author of 'rexxarplib' calls them
    'controlport' and 'notifyport'; in the program below I call them HOST
    and PORT.  I have already written about some of this in earlier
    contributions to MegaDisc, but it does get a bit complicated, and
    more than one run through it may be helpful.

   [42m  Passing Messages  [40m
    
    The default message which is sent by any IDCMP event is normally just
    the name of the class of event.  For example, if the Window has been
    told to expect and recognise mouse moves, then such an event will send
    the message 'MOUSEMOVE'.  Similarly the messages 'VANILLAKEY' and
    'RAWKEY' will be sent if, in the first case, a plain alphanumeric key
    has been pressed, and if, in the second case, some other key, usually
    with non-printable output, has been pressed (though the first
    category also contains some keys with non-printable output).
    
    But by means of the ModifyHost() function, the message which is to be
    sent can also be made to include various other pieces of information as
    required, such as an ASCII code, the window position and size, the
    mouse pointer position, any menu selection, even the number of
    seconds elapsed since Jan 1, 1980! 

    So for any key pressed, VANILLA or RAW, we can easily get enough
    information to be able to identify the key, and then to program
    subsequent behaviour accordingly.  I have written the following program
    just to explore these points.  In itself it does nothing very useful,
    but it does demonstrate how any key can be recognised in an ARexx
    program, and contains a range of examples of the types of tasks which
    can quite easily be attached to the 'Help' key and the F keys.

   [42m  Program Format  [40m
    
    In the script below, not only are small pointers and reminders
    included as comments, but also occasional longer explanations.  If
    you lift out the entire block as it stands, it could run, but you
    might prefer to remove the longer comment paragraphs.  If you run the
    program, I suggest you do so :

    Either : from a CLI window placed along the bottom quarter or so of
    your screen.  That CLI will display all key data, while the main
    window above, as opened by the program, will display any graphics or
    text which you generate by using the program.  

    Or : the program could be run from a 'Project' type icon with a
    Default Tool setting of RX.  Its output will be exactly what you
    need if, in 'Information', you also give it a Tool Type setting of,
    say, CONSOLE=CON:10/190/600/60/KeyPress Data.     

   [42m  Here we go  [40m

    /*  Check that required libraries are active      */    
     if ~show('l', "rexxarplib.library") then do
       check = addlib('rexxsupport.library',0,-30,0) 
       check = addlib('rexxarplib.library',0,-30,0)   
       end
     
    /* Open message port for Intuition functions      */
     address AREXX '"call CreateHost(HOST, PORT)"'  
    
    /* Wait until it is really open                   */  
     if ~show('Ports',HOST) then address command 'WaitForPort HOST' 
    
    /* Set size of window; specify gadgets required   */
     left = 10 ; top = 10 ; width = 600 ; height = 180
     flags = 'WINDOWCLOSE+WINDOWDRAG+WINDOWDEPTH+WINDOWSIZING'
     
    /* In 'idcmp', we tell the port what kind of messages to expect.
    In this application, only keypresses or Closewindow are involved, 
    but we wish to detect both types of keypress :
      (1)  Alphanumeric (and a few others) = 'VANILLAKEY'
      (2)  The rest = 'RAWKEY'    */
    
     idcmp = 'CLOSEWINDOW+VANILLAKEY+RAWKEY'
     call SetReqColor(HOST,BACKGROUNDPEN,0)  /* For F9 task below */ 
  
    /* At last we can open the window                 */
     call OpenWindow(HOST,left,top,width,height,idcmp,flags) 
     call openport(PORT) ; call ActivateWindow(HOST)
    
    /* We can determine exactly what messages will be sent back for each
    category. '%l' sends the name of the idcmp class so that we can
    differentiate between VANILLA and RAW key presses. '%a' identifies
    an ASCII character, '%x' and '%y' the current mouse pointer position.*/
     
     call ModifyHost(HOST,'RAWKEY','%l %a')
     call ModifyHost(HOST,'VANILLAKEY','%l %a %x %y')
    
    /* Assigning 'req2' for later use.                */
     req2 = '\Do it exactly like the sample shown.'
     req2 = req2 || '\Rewrite the sample to make your entry.'
    
     /* Display text */ 
     a.= ''
     a.1="Press 'Help' to see a list of F key specs, or press an F key"
     a.2="to have it do its defined task.  For example, you could press"
     a.3="F9 to clear this window when you have read these lines."
     a.5="If your mouse pointer is in this window, the character for any"
     a.6="alphanumeric key you press will appear at the pointer position."    
     a.8="Data on ANY key pressed will be shown in your CLI output." 
     do n = 1 to 8 ; call pat(1,20,20 + n*10,a.n,1) ; end

     /*  Start  */ 
     do forever 
    
       /* Wait for, receive, examine, and acknowledge messages */
       call waitpkt(PORT) ; p = getpkt(PORT)
       if p ~== NULL() then do                                  
         i = getarg(p) ;  t = reply(p, 0)
    
     /* We are only expecting CloseWindow or a keypress.
     If it isn't one, it must be the other. */
    
         if i ~= 'CLOSEWINDOW' then call keypressed()
         else do       
          call CloseWindow(HOST) 
          say '09'x '[31m' ;  exit 
          end   /* of 'else'       */
         end    /* of 'then do'    */
       end      /* of 'do forever' */ 
        
      exit
    /*         ----------------------------------        */
    
    /* A 'print-at' function :
     win 1 is the main window, win 2 the inset 'Help' window. */
    
    pat:
     parse arg win, row, col, text, rgb
     if win = 1 then thishost = HOST ; else thishost = HOST2    
     do   
       call SetAPen(thishost,rgb) ; call Move(thishost,row,col) 
       call Text(thishost,text)
       end
     return
 
    /* When the keypressed() function is called, the variable 'i'
    contains the whole message.  That message must be parsed to extract
    the name of the IDCMP class, and at the same time we get the actual
    character and the current pointer position, as set by ModifyHost()
    above. We remove any extraneous spaces by the strip() function, and
    derive ASCII and hex values by appropriate data conversion functions.
    We don't really need them, but the exercise is interesting and
    informative. The parsing routine will find it difficult to isolate
    the actual character if it is a space, and so that needs a special
    check. Then we build a template so that the data obtained can be
    displayed in tidy columns in an output window as the program runs.

    In the case of a VANILLAKEY, the 'show' template is of the form
   
    [32m               a   ASCII  97   Hex  61
    [32m               A   ASCII  65   Hex  41
    [32m      Delete Key   ASCII 127   Hex  7F
  
    And for a RAWKEY
   
          Help         Code   95   Hex  5F
          F5           Code   84   Hex  54
   
    The variable 'keytype' is not actually displayed (though it could
    easily be) and the variable 'char' is only displayed if the key
    pressed was alphanumeric.  You might like to try an arrangement which
    always prints the 'char', and you might get some surprises.  */
   
    keypressed:   
      do
       parse var i keytype char x y
       char = strip(char) ; asc = c2d(char) ; hex = c2x(char)
       if index(i,'VANILLAKEY   ') then do; asc = 32; hex = 20; end
       show = copies(' ',20) || 'ASCII       Hex   '
       show = overlay(asc,show,27)
       show = overlay(hex,show,37)
       if keytype = 'VANILLAKEY' then do
    
    /* A colour switch in the next line will be crunched by fullview.
    It should read '*E[32m' (and my *E looks like [ in reverse video).  
    It should be okay in your text editor, and as ARexx code. */
         switch = '[32m'
         select
    
          /* These are the invisible VANILLAs */
          when asc = 8  then show = overlay('Backspace key',show,1,19,' ') 
          when asc = 9  then show = overlay('Tab',show,1,19,' ') 
          when asc = 13 then show = overlay('Return key',show,1,19,' ') 
          when asc = 27 then show = overlay('Escape key',show,1,19,' ') 
          when asc = 32 then show = overlay('Space bar',show,1,19,' ') 
          when asc =127 then show = overlay('Delete key',show,1,19,' ') 
          otherwise          show = overlay(char,show,16)
    
    /* If the character is visible, it is displayed at the pointer position
    in the main window, just to show that we can actually do something with
    it.  You can get some interesting squirls and things if you slowly drag
    the mouse around while holding a letter key down - a whole new art form
    waiting to burst on an unsuspecting world.  */

           call Move(HOST,x,y) ; call Text(HOST,char) 
          end                       /* of 'select' */
         end                        /* of 'then do'*/
       else if keytype = 'RAWKEY' then do
         switch = '[31m'           /*  Another colour switch */
         select
          when hex = 60 then rk = 'Left Shift'
          when hex = 61 then rk = 'Right Shift'
          when hex = 62 then rk = 'Caps Lock On'
          when hex = 63 then rk = 'Ctrl'
          when hex = 64 then rk = 'Left Alt'
          when hex = 65 then rk = 'Right Alt'
          when hex = 66 then rk = 'Left Amiga'
          when hex = 67 then rk = 'Right Amiga'
          when hex = 4C then rk = 'Up Arrow'
          when hex = 4D then rk = 'Down Arrow'
          when hex = 4E then rk = 'Right Arrow'
          when hex = 4F then rk = 'Left Arrow'
          when hex = 5F then rk = 'Help'
          otherwise rk = 'F' || hex - 49
          end               /* of 'select'            */
        show = overlay('Code ',show,21)
        show = overlay(rk,show,1,19,' ') 
    
    /* Now this is what it's all about.  If we have pressed the 'Help'
    key or an F key, the program goes off to 'rawkeyjob:' and does
    whatever job we have set it up to do.  */

          if left(rk,1) = 'F' | rk = 'Help' then call rawkeyjob()
        end                 /* of 'else if   then do' */
      say '09'x switch show 
      end                   /* of initial 'do'        */
     return rk            
    
    /* And here follows the list of jobs, none of them earth-shattering,
    but collectively they suggest the range of things that F keys can be
    set to do, and show how the 'Help' key can be made to live up to
    its name.  */
    
    rawkeyjob:
      select
        when rk = 'Help' then do; call listFkeys(); rk = '';end
        when rk = 'F1' then do
          req1 = 'Type in x y colour and then the text to be displayed.'
          d = '20 40 1 This text will appear at 20 40 in colour 1'
          call GetReq()
          parse var res x y co text ; call pat(1,x,y,text,co)
          end
        when rk = 'F2' then do
          req1 = 'To draw a line, type in x1 y1 x2 y2 colour.'
          d = '100 50 400 80 4' ; call GetReq()
          parse var res x1 y1 x2 y2 co 
          call SetAPen(HOST,co) ; call Move(HOST,x1,y1) 
          call Draw(HOST,x2,y2)
          end
        when rk = 'F3' then do
          req1 = 'Enter opposite corners, x1 y1 x2 y2 colour.'
          d = '100 50 400 80 4' ; call GetReq()
          parse var res x1 y1 x2 y2 co 
          call SetAPen(HOST,co) ; call Move(HOST,x1,y1) 
          call Draw(HOST,x2,y1) ; call Draw(HOST,x2,y2)
          call Draw(HOST,x1,y2) ; call Draw(HOST,x1,y1)
          end
        when rk = 'F4' then do
          req1 = 'Enter x1 y1 colour.' ; d = '200 74 2'
          call GetReq() ; parse var res x y co 
          call SetAPen(HOST,co) ; call Flood(HOST,1,x,y) 
          end
    
    /* The F5 job tries to run Fullview, and will look for it in 'df1:c',
    which is where mine happens to be.  You can change it if necessary, or
    simply see what happens if it can't find it.  The point is, of course,
    that any other program [4mcan[32;40;0m be run in this way.  */
    
        when rk = 'F5' then do
          fv = 'df1:c/fullview'
          if exists(fv) then address command fv
          else res = Request(20,20,fv 'not found',,'Okay') 
          end
    
    /* F6 has the job of saying that it hasn't been allocated a job.  It's
    a handy slot for you to insert something of your own if you just want
    a quick-and-easy way of trying it out in this setting.  */
    
        when rk = 'F6' then do
          a = "You have pressed F6,\but F6 has not yet\been set a task."
          b = "\\Surely you can think\of something\for it to do?"
          res = Request(120,20,a || b,,'Okay')
          end
        when rk = 'F7' then say statef(GetFile(20,20))
        when rk = 'F8' then address command list
        when rk = 'F9' then call BackFill(Host)
        when rk = 'F10' then do 
         call CloseWindow(HOST) ; say '09'x '[31mBye' ; exit ; end
        otherwise
        end         
     return
  
    GetReq:
      res = Request(80,50,req1 || req2,d,'Okay','Cancel')
      return
       
    /* This 'Help' job is not without its complications, because it opens
    up a window of its own, requiring another use of the CreateHost() and
    OpenWindow() functions as described above.  Its ports must be
    uniquely named, and are here called HOST2 and PORT2.
    Just for interest, this window is given a contrasting colour
    background (black in WB2).     */

    listFkeys:
      address AREXX '"call CreateHost(HOST2, PORT2)"'    
      if ~show('Ports','HOST2') then address command 'WaitForPort HOST2' 
      /* Window Background colour */
      call SetReqColor(HOST2,BACKGROUNDPEN,1)
      flags = 'WINDOWCLOSE+WINDOWDRAG+BACKFILL'
      tit = 'Your F Key settings'
      call OpenWindow(HOST2,100,100,350,140,'CLOSEWINDOW', flags,tit) 
      call openport(PORT2) ; call ActivateWindow(HOST2)  
      call SetBPen(HOST2,1)      /* To fit background */
      a.1 = 'F1 : Display a line of text' ; a.2 = 'F2 : Draw a line'
      a.3 = 'F3 : Draw a rectangle' ; a.4 = 'F4 : Flood an area'
      a.5 = 'F5 : Run Fullview' ; a.6 = 'F6 : A message'
      a.7 = 'F7 : Obtain file data' ; a.8 = 'F8 : A DOS example'
      a.9 = 'F9 : Clean window'
      a.10 = 'F10: Close main window. Quit program'
      do i = 1 to 10 ;  call pat(2,20,10 + 10 * i,a.i,2) ; end
      call pat(2,20,130,' Close this Help window before going on',3)
    
    /* All of this, just to wait for the help window to be closed  */
    
      do forever  
       call waitpkt(PORT2) ; p = getpkt(PORT2)
       if p ~== NULL() then do                                  
         i = getarg(p) ;  t = reply(p, 0)
         call CloseWindow(HOST2) 
         leave
         end 
       end 
       call ClosePort(PORT2)
     return

           
    [33;3mHamilton, N.Z.   June, 1993[0m
    
                       ---------------------
    
    