LinuxQuestions.org
Download your favorite Linux distribution at LQ ISO.
Go Back   LinuxQuestions.org > Blogs > rainbowsally
User Name
Password

Notices

Rate this Entry

Intermediate C - getch-term

Posted 03-31-2012 at 04:27 AM by rainbowsally
Updated 04-09-2012 at 01:23 PM by rainbowsally (bum list bullets fixed)

Intermediate. That doesn't mean you have to be an intermediate C programmer to use this. It just means that it will probably be more understandable to those who are at about this level of programming skill.

Features:
  • getch()
  • simple ansi sequence to keycode converter
  • expandable

There are several versions of getch()-like functionalities on the net but none of them handle ansi sequences well (or at all) and none of them are very clear as to what they do or why.

[By "ansi sequences" here, I mean the escape codes and strings you sometimes see when you thought you were doing a backspace or cursor up.]

Some otherwise portable apps balk simply because we do not have a good getch() clone. I'm currently looking at a decompiler (similar to IDA) but it is not ported to Linux (well, they started but never finished) due to this one missing element.

Non-features:
  • * does not read key states such as whether a key is up or down, etc., (that requires a separate thread and real-time buffer loading which might as well use X for all that trouble).

That said, here's the missing piece for some of those apps that require getch in a medium-small package. And it will likely be included in our libLQ soon.

Create the makefile using the old makefile-creator like this:
Code:
makefile-creator c main
And here's today's toy.

file src/main.c
purpose: test getch-term and amuse the mind.
Code:
// tester for getch-term which includes a place to plug in a much
// more complete ansi-sequence to key codes converter, but this is 
// the default.  The LONG key codes like alt-home are raw, untranslated
// codes.  Most of the other keys are internally converted to 
// uniformly incrementing values, though non-standard.  (Use the key
// names or rewrite/create a better ansi to key translator as needed.)
//
// (C) 2012, released under GPL, -rs.

#include <stdio.h>
#include "getch-term.h"

void dbg(){}

int main(int argc, char** argv)
{
  dbg();
  printf("Press a bunch of keys to see their hex values\n"
  "Certain ALT- and CTRL- keys are defined in the\n"
  "in the linux system, but the rest will show,\n"
  "including the Ctrl-C\n\n"
  "  *** PRESS Ctrl-C to quit *** \n\n");
  
  term_setNonCanonical();
  term_flagCBrk(0); // no ctrl-c break
  int c;
  while(1)
  {
    if( keyin())
    {
      c = getch();
      printf("0x%.3X: %c\n", c, c);
      if(c == 3) // ctrl-c
        break;
    }
  }
  // be nice... restore the terminal to normal operation.
  term_setCanonical();
  return 0;
}
file: src/getch-term.h
purpose: declare a few funcs that may be useful in a doxygen-compatible format.
Code:
// getch-term.h

/* Only two or three functions are needed to use getch(). But 
 *  half of this mess is required for getch(), so why not use the 
 *  whole thing.  Easier to test.  -rs
 *  
 *  (C) 2012, Rainbow Sally.  Released under GPL.
 */


#ifndef getch_term_h
#define getch_term_h

#ifdef __cplusplus
extern "C"
{
  #endif
  
  /// Sets one or more flags on or off in termios struct.  Used by
  /// term_flagsEcho(on/off), etc.
  void term_setLocalFlags(unsigned flags, int on_off);
  
  /// Turns echo on or off.  Any non-zero value will be interpreted
  /// as 'on'.
  void term_flagEcho(int on_off);
  
  /// Turns ctrl-c break handling on or off.
  void term_flagCBrk(int on_off);
  
  /// Turns echo off and sets termios to non-canonical config needed
  /// by getch() and other funcs.
  void term_setNonCanonical();
  
  /// Restores original terminal configuration
  void term_setCanonical();
  
  /// returns non-zero if keyboard buffer is completely full.
  int keybuf_full();
  
  /// returns non-zero if the keyboard buffer is completely empty.
  int keybuf_empty();
  
  /// pops the next key from the keyboard buffer.  Returns 0 if none.
  int pop_key();
  
  /// extracts and returns the string at the keyboard buffer, 
  /// zero terminated, possibly longer than a real ansi code.  
  /// Does not remove chars from the keybuf, terminates 
  /// string at next ESC char or 8 chars, whichever comes first.
  /// Used internally, but may be useful externally if exact strings
  /// (ansi esc sequences) need to be decoded better than the simple
  /// built-in version.
  const char* keybuf_toString();
  
  /// place to plug in a background task.  This task should run quickly
  /// or in parts so the keyboard doesn't hang.  The enable for this 
  /// function should be checked to see if it's disabled for cases 
  /// when reading keys for cursor movement, or other things that require
  /// very rapid key reading.
  
  extern void (*keybuf_bgtask)();
  extern int keybuf_bgtask_enable;
  
  /// only checks if a key is ready, has nothing to do with the keybuf
  /// or term setup.
  int os_keyin();
  
  /// Used internally, returns number of bytes read.
  int keybuf_update();
  
  /// wipes out the keybuffer and also anything the OS may be waiting
  /// to send.
  void keybuf_clear();
  
  /// can be set to zero if this causes conflicts with other key names.
  #define USE_KEYBUF_KEYNAMES 1
  
  #if USE_KEYBUF_KEYNAMES
  enum
  {
    F1_KEY = 0x100,  F2_KEY,  F3_KEY,  F4_KEY,  F5_KEY,  F6_KEY,
    F7_KEY,  F8_KEY,  F9_KEY,  F10_KEY,  F11_KEY,  F12_KEY,
    
    INS_KEY,  HOME_KEY,  PGUP_KEY,  DEL_KEY,  END_KEY,  PGDN_KEY
  };
  
  // FKEY_MASK is for function keys as well as cursor keys
  #define FKEY_MASK 0x100
  #define CTRL_MASK 0x200
  #define ALT_MASK 0x400
  
  #endif // USE_KEYBUF_KEYNAMES
  
  /// updates keybuf and optionally calls user defined keybuf_keylookup()
  /// or returns ascii or encoded ansi seq as a single integer value if
  /// the key is a string starting with an esc char.  Funky but ok for 
  /// simple jobs.  And if fact, this routine is what the entire utility
  /// is about.
  int keybuf_getKey();
  
  /// Waits for a keyboard input and optionally calls a user defined 
  /// background task while waiting.
  void keybuf_keyWait();
  
  /// And here it is.  The moment you've been waiting for...
  int getch();
  
  /** \file getch-term.h
   *  
   *  To use getch() use
   *  - term_setNonCanonical();
   *  to allow turning off echo and do non-blocking keyboard input.
   */
  
  
  #ifdef __cplusplus
} // extern "C"
#endif

#endif // getch_term_h
file: getch-term.c
purpose: clone getch(), among other things.
Code:
// getch-term.c -- a terminal setup for getch-like functionality
// with simple key encoding and naming.

#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <string.h>


static struct termios ts_prev, ts_custom;
static int term_inited;
static int term_state; // true if canonical, false if custom

// only runs first time, harmless if called repeatedly. 
static void term_initCheck()
{
  if(term_inited) 
    return;
  tcgetattr( STDIN_FILENO, &ts_prev );
  ts_custom = ts_prev;
  ts_custom.c_lflag &= ~ICANON;
  term_inited++;
}

void term_setLocalFlags(unsigned flags, int on_off)
{
  term_initCheck();
  if(!on_off)    
    ts_custom.c_lflag &= ~flags;
  else
    ts_custom.c_lflag |= flags;
  tcsetattr( STDIN_FILENO, TCSANOW, &ts_custom );
}

void term_flagEcho(int on_off)
{
  if(term_state)
    return;
  term_setLocalFlags(ECHO, on_off);
}

// turn ctrl-c break handling on or off
void term_flagCBrk(int on_off)
{
  if(term_state)
    return;
  term_setLocalFlags(ISIG, on_off);
}

// Turns echo off and sets termios to non-canonical config
void term_setNonCanonical()
{
  term_initCheck();
  term_flagEcho(0);
  tcsetattr( STDIN_FILENO, TCSANOW, &ts_custom );
  term_state = 0;
}

void term_setCanonical()
{
  term_initCheck();
  tcsetattr( STDIN_FILENO, TCSANOW, &ts_prev );
  term_state = 1;
}


/*
 *  keybuf is a cycling buffer of 256 chars
 *  when keybuf_head == keybuf_tail it is empty.
 *  When keybuf_head != keybuf_tail, at least one
 *  char is in the buffer.
 */  

#define MAX_KEYBUF 256
#define NEXT(ht) \
ht += 1; if(ht >= MAX_KEYBUF) ht = 0

// a cycling buffer
static char keybuf[MAX_KEYBUF+1];
static int _keybuf_head = 0;
static int keybuf_tail = 0;

// full if next char would make keybuf_head = keybuf_tail
int keybuf_full()
{
  if((_keybuf_head+1 % MAX_KEYBUF) == keybuf_tail)
    return 1;
  else
    return 0;
}

// empty if tail = head
int keybuf_empty()
{
  if(_keybuf_head == keybuf_tail)
    return 1;
  else
    return 0;
}

int pop_key()
{
  if(keybuf_empty())
    return 0;
  int k = keybuf[keybuf_tail];
  NEXT(keybuf_tail);
  return k;
}

// a place for unlooped string from keybuf
static char keybuf_string[MAX_KEYBUF+1];

/// extracts and returns the string at the keyboard buffer, 
/// zero terminated, possibly longer than a real ansi code.  
/// Does not remove chars from the keybuf, terminates 
/// string at next ESC char or 8 chars, whichever comes first.
const char* keybuf_toString()
{
  int i;
  int save_tail = keybuf_tail;
  for(i = 0; (i < 8) && (!keybuf_empty()) && (keybuf[keybuf_tail] != 0x1B); i++)
  {
    keybuf_string[i] = keybuf[keybuf_tail];
    NEXT(keybuf_tail);
  }
  keybuf_tail = save_tail;
  keybuf_string[i] = 0;
  return keybuf_string;
}

/// plug a key lookup function in here for more exact processing
/// Note that the string may be longer than a single character or 
/// even an ansi sequence, but the function should only return the 
/// number of bytes processed for one character output for the 
/// whole string and set the character 'ch' to the value derived.

int (*keybuf_keylookup)(int* ch, const char* s) = 0;

/// place to plug in a background task.  This task should run quickly
/// or in parts so the keyboard doesn't hang.  The enable for this 
/// function should be checked to see if it's disabled for cases 
/// when reading keys for cursor movement, or other things that require
/// very rapid key reading.

void (*keybuf_bgtask)() = 0;
int keybuf_bgtask_enable = 0;

int os_keyin()
{
  fd_set set;
  struct timeval timeout;
  
  /* Initialize the file descriptor set. */
  FD_ZERO (&set);
  FD_SET (STDIN_FILENO, &set);
  
  /* Initialize the timeout data structure. */
  timeout.tv_sec = 0;
  timeout.tv_usec = 1;
  
  /* select returns 0 if timeout, 1 if input available, -1 if error. */
  int res = select (FD_SETSIZE, &set, NULL, NULL, &timeout);
  if(res > 0)
    return 1;
  else
    return 0;
}

int keyin()
{
  if(!keybuf_empty())
    return 1;
  else
    return os_keyin();
}

// returns number read
int keybuf_update()
{
  int nread = 1;
  while((!keybuf_full()) && (nread))
  {
    if(os_keyin())
    {
      nread = keybuf_tail - _keybuf_head;
      if(nread > 0) // read en bloc, and adjust real nread
      {
        nread = read(STDIN_FILENO, keybuf + _keybuf_head, nread);
        keybuf_tail += nread;
      }
      else // wrap, one by one until the above works
      {
        nread = read(STDIN_FILENO, keybuf + _keybuf_head, 1);
        NEXT(_keybuf_head);
      }
    }
    else
      nread = 0;
  }
}

void keybuf_clear()
{
  _keybuf_head = keybuf_tail = 0;
  while(os_keyin())
  {
    keybuf_update();
    _keybuf_head = keybuf_tail = 0;
  }
}

static int _hashed_keys[] =
{
  // f1 - f12
  
  0x1097F,    0x10940,    0x10941,    0x10942,
  0x1463F,    0x14641,    0x146A2,    0x14683,
  0x1459B,    0x145FC,    0x141FE,    0x141DF,
  
  // ins, home, pgup, del, end, pgDn
  
  0x171CB,    0x10BC3,    0x1726E,    0x171AC,
  0x10BC1,    0x1724F,
  
  // lft dn, rt, up        
  
  0x10BFF,    0x10BFD,    0x10BFE,    0x10BFC, 
  
  // index 22 in table here ->
  
  // ctrl- f1 - f11 (no ctrl-f12)
  0x12534,    0x12535,    0x12536,    0x12537,
  0x1CC0F,    0x1B4D1,    0x12072,    0x14353,
  0x13FAB,    0x1A80C,    0x1A50E,
  0x141DF, // ctrl-f12 just a copy of f12
  
  // ctrl- ins, home, pgup, del end pgdn
  
  0x1569B,    0x1E204,    0x1D77E,    0x1A87C,
  0x1E202,    0x14C1F,
  
  // ctrl- lft, dn, rt, up
  
  0x1E200,    0x1E2FE,    0x1E2FF,    0x1E2FD,
  0 // terminator
};

// some names for the hashed keys
static int xlate_key_hash(int key_hash)
{
  int index = -1;
  int i;
  for(i = 0; _hashed_keys[i] != 0; i++)
  {
    if(_hashed_keys[i] == key_hash)
    {
      index = i;
      break;
    }
  }
  
  if(index < 0)
  {
    // handle a few special cases we know about
    
    // esc
    if(key_hash == 0x10000) 
      return 0x1B;
    
    // ctrl-esc
      else if(key_hash == 0x1001B) 
        return 0x21B;
      
      // alt keys
        else if ((0xFF00 & key_hash) == 0)
          return 0x400 + key_hash;
        
        // else return raw code
          return key_hash; 
  }
  
  // if we found the key, split into normal and ctrl-types
  // and add masks.
  if(index < 22) // where ctrl-keys start
    return 0x100 + index;
  else
    return 0x300 + index;
}

int keybuf_getKey()
{
  int ch = 0;
  keybuf_update();
  if(keybuf_keylookup)
  {
    // single char
    if((_keybuf_head - keybuf_tail) == 1)
      ch = pop_key();
    else
    {
      keybuf_tail += keybuf_keylookup(&ch, keybuf_toString());
      keybuf_tail--;
      NEXT(keybuf_tail);
    }
  }
  else 
  {
    ch = pop_key();
    if(ch == 27) // esc
    {
      ch = 00;
      int ext = 0;
      int fn = 0;
      
      // sloppy but in mostly unique values as long as
      // there's only one key in the buffer.  Ctrl cursor
      // keys duplicate the worst.
      while((ext = pop_key()))
      {
        ch = (ch << 5) ^ (ch + ext);
        //        ch += fn++;
      }
      ch = (ch & 0xFFFF) | 0x10000;
      return xlate_key_hash(ch);
    }
  }
  return ch;
}

void keybuf_keyWait()
{
  while(! keyin())
  {
    // do short background tasks, if any, here
    if(keybuf_bgtask)
      keybuf_bgtask();
    else // be nice
      usleep(25000);
  }
}


int getch( ) 
{
  int ch = 0;
  keybuf_keyWait();
  return keybuf_getKey();
}
Notes:

Before we do much more work on the lib, we need makefile-creator2 (aka mc2). If you liked makefile-creator, mc2 will blow the doors off. It's almost ready to upload.

In the meantime, give this a spin. I think you'll enjoy the process and maybe even like the demo.

:-)

Need the OLD makefile-creator?

It should be right around here someplace.
http://www.linuxquestions.org/questi...-part-2-34421/

:-)
Posted in Uncategorized
Views 456 Comments 0
« Prev     Main     Next »
Total Comments 0

Comments

 

  



All times are GMT -5. The time now is 04:20 PM.

Main Menu
Advertisement

My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
identi.ca: @linuxquestions
Facebook: linuxquestions Google+: linuxquestions
Open Source Consulting | Domain Registration