Intermediate C - getch-term
Posted 03-31-2012 at 03:27 AM by rainbowsally
Updated 04-09-2012 at 12:23 PM by rainbowsally (bum list bullets fixed)
Updated 04-09-2012 at 12: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:
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:
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:
And here's today's toy.
file src/main.c
purpose: test getch-term and amuse the mind.
file: src/getch-term.h
purpose: declare a few funcs that may be useful in a doxygen-compatible format.
file: getch-term.c
purpose: clone getch(), among other things.
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/
:-)
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
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;
}
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
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();
}
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/
:-)
Total Comments 0




