LinuxQuestions.org
Welcome to the most active Linux Forum on the web.
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 12-11-2022, 06:33 PM   #1
jr_bob_dobbs
Member
 
Registered: Mar 2009
Distribution: Bedrock, Devuan, Slackware, Linux From Scratch, Void
Posts: 621
Blog Entries: 121

Rep: Reputation: 186Reputation: 186
foolproof yet correct handling of keyboard input


Some time ago I started a project to write a text editor. The specification I came up with ahead of time was that it would use a reduced subset of the keys of nano and that it would use ncurses.

Window resize events, tab handling, word wrap and all originally specified functions are complete. The editor works and I use it every day. I'm not asking how to write a text editor. Already did it.

There is just one nagging problem that I've yet to fix. Keyboard handling is not always correct. Keys from the keypad are not always correct. Backspace is sometimes ignored, or is sometimes interpreted as if it were the delete key. Home and end keys are sometimes ignored. The delete key sometimes is ignored. In an attempt to find consistency, I've tried my editor in the console, in xterm and a few other terminals. I've run it in screen and not in screen. The only consistency I've noted is that my editor, if started in the Linux console or xterm will correctly handle input keys ... unless one is in screen in xterm, detaches, exits xorg and then reattaches in the console.

How I originally wrote keyboard handling, and everything else, was to read the man pages of ncurses and its functions.

I begin to realize that keyboard handling is a bit complex. The kernel has drivers to read the various keyboards (One could in a USB keyboard in addition to a laptop's built-in keyboard). Each terminal takes the key-presses, and then passes them to the editor. Wait, if one is in xorg (or other x-windowing system) then I guess xorg reads keys from the kernel, then passes them to the window manager which in tern passes them to the terminal and then eventually to my editor?

I looked at the source code for nano, to see how it handles keyboard input, and then had to walk away and have a lie-down.

Is there some sort of guide or tutorial to correctly, yet in a way that always works, handle key-presses?

Last edited by jr_bob_dobbs; 12-11-2022 at 06:34 PM.
 
Old 12-11-2022, 07:27 PM   #2
GazL
LQ Veteran
 
Registered: May 2008
Distribution: CRUX 3.7
Posts: 6,592

Rep: Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708
A lot of the ncurses info out there on the web is woefully out of date.

I can provide an example of a small demo program I wrote several years ago while trying to get to grips with the 'wide' ncurses library. Perhaps that will give you some ideas.

keypress.h:
Code:
#ifndef KEYPRESS_H
#define KEYPRESS_H

#include <wchar.h>

#define KEYDATASIZE 8  /* Max characters to allow in an input escape sequence */

typedef struct keypress {
    int type;
    wchar_t data[KEYDATASIZE];
} keypress_t;

int get_keypress(keypress_t *k);

/*
 *  If ncurses keypad() mode is enabled and a defined key is received
 *  then k.type will be set to KEY_CODE_YES and k.data[0] will 
 *  contain the keycode value. The function will return 1.
 *
 *  If ncurses keypad() mode is not active, or if an unrecognised
 *  escape sequence is received, k.type will be set to OK and 
 *  k.data[] will contain a null terminated string of wchars.
 *  The return value will be the number of wchars stored.
 *  
 *  If the escape sequence is longer than KEYDATASIZE - 1
 *  it will be truncated.
*/

#endif /* KEYPRESS_H */
keypress.c:
Code:
/* keypress.c */

#include <wchar.h>
#include <curses.h>

#include "keypress.h"

int get_keypress(keypress_t *k)
{
    wint_t wch = L'\0';
    size_t n = 0;
    int ret = 0;
    int keypad_on = 0;
    
    if ( !k )
        return 0;
    
    for (int i = 0 ; i < KEYDATASIZE ; i++)
        k->data[i] = L'\0';
    
    ret = get_wch(&wch);
    if ( KEY_CODE_YES == ret )
    {
        k->type = ret;
        k->data[n++] = wch;
    }
    else if ( OK != ret )
    {
        k->data[n] = L'\0';
        return n;
    }
    else if ( /* ESC */ L'\033' == wch )
    {
        k->type = ret;
        nodelay(stdscr,TRUE);
        keypad_on = is_keypad(stdscr); 
        if ( keypad_on )
            keypad(stdscr, FALSE);
        while ( ret == OK )
        {
            if ( n < KEYDATASIZE - 1 )
                k->data[n++] = wch;
            
            ret = get_wch(&wch);
        }
        nodelay(stdscr, FALSE);
        if ( keypad_on )
            keypad(stdscr,TRUE);
    }
    else
    {
        k->type = ret;
        k->data[n++] = wch;
    }
    k->data[n] = L'\0';
    return n;
}
keypress-example.c:
Code:
/* keypress-example.c */

#include <locale.h>  /* setlocale() */
#include <stdlib.h>  /* setenv(), exit() */
#include <stdio.h>   /* perror() */
#include <string.h>  /* strcmp() */
#include <wchar.h>
#include <wctype.h>  /* iswprint() */
#include <signal.h>  /* sigaction() */
#include <curses.h>

#include "keypress.h"  /* get_keypress() */

static int running = 1;
static int opt_keypad = 0;


void sigterm_handler(int sig)
{
    running = 0;
    return;
}


void update_screen(keypress_t *key)
{
    if ( !key )
        return;

    move(8, 1);    
    if ( KEY_CODE_YES == key->type )
    {
        printw("Keycode octal( %04o )", key->data[0]);
        switch ( key->data[0] )
        {
        case KEY_BACKSPACE:
            printw(": KEY_BACKSPACE ( Backspace )");
            break;
        case KEY_DC:
            printw(": KEY_DC ( Delete Character )");
            break;
        }
    }
    else if ( OK == key->type )
    {
        if ( iswprint(key->data[0]) )
            printw("Unicode( 0x%04x ): '%lc'", key->data[0], key->data[0]);
        else if ( key->data[0] == L'\033' && wcslen(key->data) > 1)
            printw("Escape Sequence: %ls", key->data);
        else
            printw("Non printable/control character( 0x%04x ): %s",
                   key->data[0], key_name(key->data[0]));
    }
    clrtoeol();
    refresh();
        
    return;
}


void draw_screen()
{
    clear();
    attr_on(A_BOLD, NULL);
    mvprintw(1, 1, "NCurses Wide-Input Handling Demonstration");
    attr_off(A_BOLD, NULL);
    if ( opt_keypad )
    {
        mvprintw(3, 1, "Keypad() is enabled.");
        mvprintw(4, 1, "See ");
        attr_on(A_UNDERLINE, NULL);
        printw("/usr/include/ncurses/curses.h");
        attr_off(A_UNDERLINE, NULL);
        printw(" for list of pre-defined keycode symbols.");
    }
    mvhline_set(6, 1, NULL, COLS - 2);
    mvhline_set(10, 1, NULL, COLS - 2);
    mvprintw(12, 1, "Press a key ('q' to exit, 'k' to toggle keypad() mode).");
    refresh();

    return;
}


void display_loop()
{
    keypress_t key;
     
    draw_screen();
    while ( running )
    {
        get_keypress(&key);
        if ( KEY_CODE_YES == key.type && KEY_RESIZE == key.data[0] )
            draw_screen();
        else if ( 0 == wcscmp(key.data, L"q") )
            running = 0;
        else if ( 0 == wcscmp(key.data, L"k") ) {
            opt_keypad = ~opt_keypad;
            keypad(stdscr, opt_keypad);
            draw_screen();
        } else
            update_screen(&key);
    }

    return;
}


int main(int argc, char *argv[])
{
    
    struct sigaction sa;    

    if ( !setlocale(LC_ALL, "") )
    {
        perror("setlocale");
        exit(EXIT_FAILURE);
    }

    
    /* PARSE ARGS */

    for ( int i = 1 ; i < argc ; i++ )
    {
        if ( strcmp("--keypad", argv[i]) == 0 )
            opt_keypad = 1;
    }

    
    /* Setup a signal handler for SIGINT/SIGTERM so that we can exit
       gracefully rather than leaving the screen in a mess. */
    sa.sa_handler = sigterm_handler;  
    if ( -1 == sigaction(SIGTERM, &sa, NULL) )
        perror("sigaction: ");
    if ( -1 == sigaction(SIGINT, &sa, NULL) )
        perror("sigaction: ");


    /* NCURSES INITIALISATION */
    
    setenv("ESCDELAY","200", FALSE);  /* improve keypad() responsiveness. */
    
    initscr();
    scrollok(stdscr, TRUE);
    if ( opt_keypad )
        keypad(stdscr, TRUE);
    cbreak();
    noecho();
    curs_set(0);
    

    /* MAIN LOOP */

    display_loop();
    

    /* NCURSES CLEANUP */

    clear();
    refresh();
    endwin();

    return EXIT_SUCCESS;
}
Makefile:
Code:
# Makefile for keypress: An ncurses input demonstration

CFLAGS = -Wall -O2
CPPFLAGS = 
NCFLAGS := $(shell pkg-config --cflags ncursesw)
LDLIBS := $(shell pkg-config --libs ncursesw)

PREFIX=/usr/local


keypress-example: keypress-example.o keypress.o
	$(CC) -o $@ $^ $(LDLIBS)

%.o: %.c
	$(CC) -c -o $@ $(CFLAGS) $(CPPFLAGS) $(NCFLAGS) $<

keypress-example.o: keypress-example.c keypress.h
keypress.o: keypress.c keypress.h

all: clean keypress-example

clean:
	rm -f  *.o 

distclean: clean
	rm -f keypress-example \#*\# *~ TAGS

install: keypress-example
	mkdir -p $(DESTDIR)$(PREFIX)/bin
	cp keypress-example $(DESTDIR)$(PREFIX)/bin

tags:
	etags *.c *.h

edit: tags
	emacs *.c *.h Makefile &

.PHONY: clean distclean all install tags edit

################################################################# End. #
Unfortunately and as you've already discovered, due to the nature of terminal input and the differences between terminals, it is a somewhat messy topic.

My advice would be to always use the ncursesw "wide" library and get_wch(), and to use keypad(, TRUE) mode to make life easier.

Last edited by GazL; 12-21-2022 at 05:51 AM. Reason: fix comment in keypress.h
 
Old 12-12-2022, 06:10 PM   #3
jr_bob_dobbs
Member
 
Registered: Mar 2009
Distribution: Bedrock, Devuan, Slackware, Linux From Scratch, Void
Posts: 621

Original Poster
Blog Entries: 121

Rep: Reputation: 186Reputation: 186
That was an interesting program. At first it refused to compile but adding
Code:
#ifndef keypad
  #include <curses.h>
#endif
near the top of the keypress.h file fixed it.

It's revealed what I already suspected: each term has different ideas as to what many keys are. Thank you. I will read up on the wide thing and see how that goes.

Last edited by jr_bob_dobbs; 12-12-2022 at 06:12 PM.
 
Old 12-12-2022, 06:26 PM   #4
EdGr
Member
 
Registered: Dec 2010
Location: California, USA
Distribution: I run my own OS
Posts: 912

Rep: Reputation: 436Reputation: 436Reputation: 436Reputation: 436Reputation: 436
I think that you asked for this problem by using ncurses when you didn't actually want a terminal emulator.

If you write a GUI program instead, you will receive input events exactly as X generated them. No terminals involved. I recommend GTK 3. It passes the keypress events as-is.
Ed
 
Old 12-12-2022, 06:35 PM   #5
GazL
LQ Veteran
 
Registered: May 2008
Distribution: CRUX 3.7
Posts: 6,592

Rep: Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708
That's weird. It compiles fine as is on both my CRUX and Slackware boxes.
There's nothing curses specific in keypress.h that would need the include, so I'm not sure what that is all about.

This is what the make output looks like on my system:
Code:
$ make
cc -c -o keypress-example.o -Wall -O2  -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=600  keypress-example.c
cc -c -o keypress.o -Wall -O2  -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=600  keypress.c
cc -o keypress-example keypress-example.o keypress.o -lncursesw

And, yes, most the terminals tend to do their own thing. That's where keypad mode helps a little, though it does rely on the $TERM and terminfo entries being correct, which isn't always the case.

To be honest, it's probably best to forget that the function keys exist and just stick to the normal keys and cursor movement.

P.S. I've not encountered an escape sequence longer than 7 yet, but there might be some out there, so perhaps giving KEYDATASIZE a little more headroom might not be a bad idea.

Last edited by GazL; 12-12-2022 at 07:07 PM.
 
Old 12-20-2022, 06:27 PM   #6
jr_bob_dobbs
Member
 
Registered: Mar 2009
Distribution: Bedrock, Devuan, Slackware, Linux From Scratch, Void
Posts: 621

Original Poster
Blog Entries: 121

Rep: Reputation: 186Reputation: 186
Quote:
Originally Posted by EdGr View Post
I think that you asked for this problem by using ncurses when you didn't actually want a terminal emulator.
Request for clarification: I wrote a text editor, not a terminal emulator.
I had to use ncurses, since my editor runs in the console as well, when x-windows is not running.

Quote:
If you write a GUI program instead, you will receive input events exactly as X generated them.
If that were true, Qemu wouldn't be eating colon keys But this gets off-topic.

An update. I've tested nano on more terminals and ... wow, things go wonky for nano as well. So for now I've been using Gazl's most excellent program to suss out the key codes for the terminals that I *do* use and, like nano apparently does, will ignore other terminals. I figure if my editor works in the console, xterm and (to be fixed still) the OpenBSD console I'll stop there.

Last edited by jr_bob_dobbs; 12-20-2022 at 06:31 PM. Reason: update
 
Old 12-20-2022, 06:50 PM   #7
EdGr
Member
 
Registered: Dec 2010
Location: California, USA
Distribution: I run my own OS
Posts: 912

Rep: Reputation: 436Reputation: 436Reputation: 436Reputation: 436Reputation: 436
Quote:
Originally Posted by jr_bob_dobbs View Post
Request for clarification: I wrote a text editor, not a terminal emulator.
I had to use ncurses, since my editor runs in the console as well, when x-windows is not running.
Yes, the downside of writing a GUI program is that X is required.

Your text editor lives higher in the software stack than the terminal emulation being done by ncurses, VTE, and the console. Your program is seeing the result. There may being a way to read the keyboard directly, but I have not tried to do that.
Ed
 
Old 12-21-2022, 05:24 AM   #8
GazL
LQ Veteran
 
Registered: May 2008
Distribution: CRUX 3.7
Posts: 6,592

Rep: Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708
Just being pedantic, but ncurses' goal is "terminal abstraction" not "terminal emulation".
 
Old 12-21-2022, 06:32 AM   #9
hazel
LQ Guru
 
Registered: Mar 2016
Location: Harrow, UK
Distribution: LFS, AntiX, Slackware
Posts: 6,661
Blog Entries: 17

Rep: Reputation: 3976Reputation: 3976Reputation: 3976Reputation: 3976Reputation: 3976Reputation: 3976Reputation: 3976Reputation: 3976Reputation: 3976Reputation: 3976Reputation: 3976
I wonder if this is related to the need to patch kbd when building LFS to get consistent handling of backspace and delete.
Quote:
After patching, the backspace key generates the character with code 127, and the delete key generates a well-known escape sequence.
 
Old 12-21-2022, 07:07 AM   #10
NevemTeve
Senior Member
 
Registered: Oct 2011
Location: Budapest
Distribution: Debian/GNU/Linux, AIX
Posts: 4,428
Blog Entries: 1

Rep: Reputation: 1679Reputation: 1679Reputation: 1679Reputation: 1679Reputation: 1679Reputation: 1679Reputation: 1679Reputation: 1679Reputation: 1679Reputation: 1679Reputation: 1679
This BackSpace/Delete problem is only 50+ years old, so there is no point in being impatient.
My terminfo database says:
Code:
infocmp linux | egrep 'kbs|kdch1'
        kb2=\E[G, kbs=^?, kcbt=\E[Z, kcub1=\E[D, kcud1=\E[B,
        kcuf1=\E[C, kcuu1=\E[A, kdch1=\E[3~, kend=\E[4~,
That's quite okay. Now the problem is with xterm: original terminfo database says kbs=\b (or kbs=^H), linux-patched terminfo-database says kbs=\177 (or kbs=^?)

Last edited by NevemTeve; 12-21-2022 at 07:16 AM.
 
Old 12-21-2022, 07:30 AM   #11
GazL
LQ Veteran
 
Registered: May 2008
Distribution: CRUX 3.7
Posts: 6,592

Rep: Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708Reputation: 4708
For backspace/delete to do the right thing, 4 things must agree.
  1. the sequence the terminal/emulator generates when the key is pressed.
  2. the erase value of the tty/pty. (check with: stty -a | grep --color '\berase =')
  3. the kbs= value of the terminfo. (check with: infocmp | grep --color '\bkbs='
  4. the program must either use ncurses keypad(, TRUE), or query the stty erase value before attempting to parse any keyboard input.
Traditionally xterm has used BS=^H. Some distro have been known to patch this, and that causes interoperability issues when sshing to hosts whose terminfo entry for xterm may still specify the traditional value: ^H.

Most other terminal emulators follow the linux console and use BS=DEL. I can't remember what the OpenBSD console uses.


P.S. we can blame emacs for all this BS=DEL nonsense!
 
Old 12-23-2022, 01:41 PM   #12
jr_bob_dobbs
Member
 
Registered: Mar 2009
Distribution: Bedrock, Devuan, Slackware, Linux From Scratch, Void
Posts: 621

Original Poster
Blog Entries: 121

Rep: Reputation: 186Reputation: 186
Thank you all for the replies. So much information!

p.s. I still remember back in the day, using CP/M and memorizing that ^h (8) was backspace and 127 was delete. The keyboard had a backspace key and that generated a ^h. I'd probably memorized half the ascii table back then. Right, let me stop rambling. Thanks again.
 
  


Reply

Tags
keyboard keys ncurses


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
Foolproof live CD installer reboy Linux - Laptop and Netbook 7 06-18-2019 05:17 AM
wxPython foolproof build from source script SittingInFrontOfPC Linux - Software 6 08-26-2006 11:45 PM
Foolproof guide to install Xine? DodoTux Linux - Software 3 03-30-2006 12:47 AM
Cleanest and foolproof way to append query string to URLs (PHP) vharishankar Programming 4 07-11-2005 11:38 AM
Foolproof hiding of folders? habala Linux - Newbie 2 01-01-2005 05:02 AM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 10:09 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
Open Source Consulting | Domain Registration