new function cinPeek(); named from c++ cin.peek()

This commit is contained in:
R. Eric Wheeler 2021-03-01 09:37:48 -08:00
parent 6372eaddca
commit 876c303e5e
6 changed files with 179 additions and 58 deletions

View File

@ -4,15 +4,39 @@ project(getch C)
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
set(CMAKE_COLOR_MAKEFILE ON) set(CMAKE_COLOR_MAKEFILE ON)
add_library(getch SHARED getch.c getch.h) add_library(libgetch SHARED getch.c getch.h)
add_library(libgetch_static STATIC getch.c getch.h)
add_executable(example example.c ) add_executable(example example.c )
add_executable(getchTest tests/test.c tests/getchTests.c) add_executable(getchTest tests/test.c tests/getchTests.c)
add_test(NAME getchTests
COMMAND getchTest add_test(NAME defaultTests
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) COMMAND "getchTest"
target_link_libraries(getchTest -L./build getch) WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
target_link_libraries(example -L./build getch) )
target_link_libraries(getchTest -L./build libgetch)
target_link_libraries(example -L./build libgetch)
if (ENABLE_TESTS EQUAL 1) if (ENABLE_TESTS EQUAL 1)
enable_testing() enable_testing()
endif () endif ()
add_custom_target(escapeTest getchTest escapeReturnsTest
COMMAND getchTest specialKeyTest
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
USES_TERMINAL
)
set_target_properties(libgetch PROPERTIES PUBLIC_HEADER getch.h)
set_target_properties(libgetch PROPERTIES OUTPUT_NAME getch)
set_target_properties(libgetch_static PROPERTIES OUTPUT_NAME getch_static)
include(GNUInstallDirs)
install(TARGETS libgetch libgetch_static
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
PUBLIC_HEADER
DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}
)

View File

@ -10,6 +10,8 @@ See https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/getch-getwc
Difference is that _getch will read CTRL+C, or any control characters. Difference is that _getch will read CTRL+C, or any control characters.
There is also an extra function `int cinPeek()` that returns the next character in stdin without removing it.
### Note ### Note
Make sure that the user calling the function is in the group "input" Make sure that the user calling the function is in the group "input"

View File

@ -11,4 +11,17 @@ int main() {
key = _getch(); key = _getch();
printf("Special Key : %d\n", key); printf("Special Key : %d\n", key);
} }
do {
key = _getch();
printf("Key : %d\n", key);
} while(key != 113);
_ungetch('A');
int res = cinPeek();
int getch = _getch();
printf("%c : %c\n\r", res, getch);
} }

97
getch.c
View File

@ -14,13 +14,34 @@
#include <glob.h> #include <glob.h>
#include "getch.h" #include "getch.h"
#define FKEY(k) ((k) >= KEY_F1 && (k) <= KEY_F10) || (k) == KEY_F12 || (k) == KEY_F11
#define NOTNUMPAD(k) ((k) >= KEY_HOME && (k) <= KEY_DELETE)
#define EVENT_DEVICE_GLOB "/dev/input/by-path/*-event-kbd" #define EVENT_DEVICE_GLOB "/dev/input/by-path/*-event-kbd"
#define FKEY(k) ((k) >= KEY_F1 && (k) <= KEY_F10) || (k) == KEY_F12 || (k) == KEY_F11
#define NOTNUMPAD(k) ((k) >= KEY_HOME && (k) <= KEY_DELETE)
#define XOR_SWAP(a,b) do\
{\
(a) ^= (b);\
(b) ^= (a);\
(a) ^= (b);\
} while (0)
static struct termios oldTermAttributes; static struct termios oldTermAttributes;
inline static void reverseString(char * str)
{
if (str)
{
char * end = str + strlen(str) - 1;
while (str < end)
{
XOR_SWAP(*str, *end);
str++;
end--;
}
}
}
static int discardRead(unsigned int length) static int discardRead(unsigned int length)
{ {
char buffer[length]; char buffer[length];
@ -38,7 +59,7 @@ static int discardRead(unsigned int length)
return (int)bytesRead; return (int)bytesRead;
} }
static int getEventDevice(const char * device) static int getEventDevice(char ** device)
{ {
glob_t search; glob_t search;
@ -64,7 +85,8 @@ static int getEventDevice(const char * device)
for(int i = 0; i<search.gl_pathc;i++) { for(int i = 0; i<search.gl_pathc;i++) {
if(access(search.gl_pathv[i], F_OK) == 0) { if(access(search.gl_pathv[i], F_OK) == 0) {
pathLength = strlen(search.gl_pathv[i]) + 1; pathLength = strlen(search.gl_pathv[i]) + 1;
strncpy((char *)device, search.gl_pathv[i], pathLength); *device = (char *)malloc(pathLength);
strcpy(*device, search.gl_pathv[i]);
globfree(&search); globfree(&search);
return 1; return 1;
} }
@ -76,30 +98,33 @@ static int getEventDevice(const char * device)
static unsigned short getScanCode() static unsigned short getScanCode()
{ {
struct input_event inputEvent[3]; struct input_event inputEvent[5];
int eventDevice; int eventDevice;
const char device[FILENAME_MAX]; char *device;
if(getEventDevice(device) == -1) { if(getEventDevice(&device) == -1) {
perror("getEventDevice"); perror("getEventDevice");
free(device);
return KEY_RESERVED; return KEY_RESERVED;
} }
if( ( eventDevice = open(device, O_RDONLY)) == -1 ) { if( ( eventDevice = open(device, O_RDONLY)) == -1 ) {
free(device);
perror("open"); perror("open");
return KEY_RESERVED; return KEY_RESERVED;
}; };
free(device);
if( read(eventDevice, &inputEvent, sizeof(inputEvent)) == -1) { if( read(eventDevice, &inputEvent, sizeof(inputEvent)) == -1) {
close(eventDevice); close(eventDevice);
perror("read");
return KEY_RESERVED; return KEY_RESERVED;
} }
close(eventDevice); close(eventDevice);
for(int i = 0;i<3;i++) { for(int i = 0;i<5;i++) {
if(inputEvent[i].type == EV_KEY && inputEvent[i].code != KEY_ENTER) { if(inputEvent[i].type == EV_KEY && inputEvent[i].code != KEY_ENTER) {
return inputEvent[i].code; return inputEvent[i].code;
} }
@ -153,7 +178,7 @@ static int readKey(void) {
unsigned short scanCode = getScanCode(); unsigned short scanCode = getScanCode();
if (scanCode == KEY_ESC || scanCode == KEY_RESERVED) { if (scanCode == KEY_ESC ) {
return 27; return 27;
} }
@ -171,7 +196,6 @@ static int readKey(void) {
case KEY_F2: case KEY_F2:
case KEY_F3: case KEY_F3:
case KEY_F4: case KEY_F4:
case KEY_KP1: // END case KEY_KP1: // END
case KEY_KP2: // DOWN case KEY_KP2: // DOWN
case KEY_KP4: // LEFT case KEY_KP4: // LEFT
@ -199,6 +223,7 @@ static int readKey(void) {
default: default:
discardBytes = 0; discardBytes = 0;
break;
} }
if (discardBytes != 0) { if (discardBytes != 0) {
@ -225,7 +250,55 @@ int _getch(void) {
return key; return key;
} }
int getch(void)
{
return _getch();
}
int _ungetch(int ch) int _ungetch(int ch)
{ {
return ungetc(ch, stdin); return ungetc(ch, stdin);
} }
int ungetch(int ch)
{
return _ungetch(ch);
}
int *cinPeekCount(ushort count)
{
char buffer[count];
int flags = fcntl(STDIN_FILENO, F_GETFL);
fcntl(STDIN_FILENO, F_SETFL, flags|O_NONBLOCK);
size_t byteCount = fread(&buffer, sizeof(char), count, stdin);
fcntl(STDIN_FILENO, F_SETFL, flags);
int *res = (int*)malloc(byteCount);
for(int i=0;i<byteCount;i++)
{
res[i] = (int)buffer[i];
}
reverseString(buffer);
for(int i=0;i<byteCount;i++) {
pushStdin(buffer[i]);
}
return res;
}
int cinPeek()
{
int flags = fcntl(STDIN_FILENO, F_GETFL);
fcntl(STDIN_FILENO, F_SETFL, flags|O_NONBLOCK);
int result = fgetc(stdin);
fcntl(STDIN_FILENO, F_SETFL, flags);
ungetc(result, stdin);
return result;
}

View File

@ -8,6 +8,9 @@
#define GETCH_H #define GETCH_H
int _getch(void); int _getch(void);
int getch(void);
int _ungetch(int ch); int _ungetch(int ch);
int ungetch(int ch);
int cinPeek();
#endif //GETCH_H #endif

View File

@ -5,15 +5,23 @@
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
#include <string.h> #include <string.h>
#include <libevdev-1.0/libevdev/libevdev.h>
#include <glob.h> #include <glob.h>
#include "../getch.h" #include "../getch.h"
#define XOR_SWAP(a,b) do\
{\
(a) ^= (b);\
(b) ^= (a);\
(a) ^= (b);\
} while (0)
#define EV_PRESSED 1 #define EV_PRESSED 1
#define EV_RELEASED 0 #define EV_RELEASED 0
inline static void writeToEventDevice(__u16 code) inline static void writeToEventDevice(__u16 code)
{ {
struct libevdev *dev = NULL;
glob_t globResult; glob_t globResult;
glob("/dev/input/by-path/*-event-kbd", GLOB_NOSORT, NULL, &globResult); glob("/dev/input/by-path/*-event-kbd", GLOB_NOSORT, NULL, &globResult);
@ -23,34 +31,45 @@ inline static void writeToEventDevice(__u16 code)
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
char device[strlen(globResult.gl_pathv[0])]; char device[strlen(globResult.gl_pathv[0])+1];
strncpy(device, globResult.gl_pathv[0], strlen(globResult.gl_pathv[0]) + 1); strcpy(device, globResult.gl_pathv[0]);
globfree(&globResult); globfree(&globResult);
struct input_event inputEvent[2]; struct input_event inputEvent[3];
int eventDevice = open(device, O_WRONLY); int eventDevice = open(device, O_WRONLY|O_NONBLOCK);
if(eventDevice == -1) { if(eventDevice == -1) {
perror("open"); perror("open");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
for(int i=0; i<2;i++) { inputEvent[0].time.tv_usec = 0;
gettimeofday(&inputEvent[i].time, NULL); inputEvent[0].time.tv_sec = 0;
inputEvent[i].type = EV_KEY; inputEvent[0].type = 4;
inputEvent[i].code = code; inputEvent[0].code = 4;
inputEvent[i].value = i ? EV_RELEASED : EV_PRESSED; inputEvent[0].value = code;
inputEvent[1].time.tv_usec = 0;
inputEvent[1].time.tv_sec = 0;
inputEvent[1].type = EV_KEY;
inputEvent[1].code = code;
inputEvent[1].value = EV_PRESSED;
inputEvent[2].time.tv_usec = 0;
inputEvent[2].time.tv_sec = 0;
inputEvent[2].type = EV_KEY;
inputEvent[2].code = code;
inputEvent[2].value = EV_RELEASED;
int res;
res = write(eventDevice, &inputEvent, sizeof(inputEvent));
if(res == -1) {
perror("Write");
} }
write(eventDevice, &inputEvent, sizeof(inputEvent)); close(eventDevice);
close(eventDevice);
sync();
fflush(NULL);
} }
inline static void reverseString(char * str) inline static void reverseString(char * str)
@ -59,24 +78,12 @@ inline static void reverseString(char * str)
{ {
char * end = str + strlen(str) - 1; char * end = str + strlen(str) - 1;
// swap the values in the two given variables
// XXX: fails when a and b refer to same memory location
# define XOR_SWAP(a,b) do\
{\
(a) ^= (b);\
(b) ^= (a);\
(a) ^= (b);\
} while (0)
// walk inwards from both ends of the string,
// swapping until we get to the middle
while (str < end) while (str < end)
{ {
XOR_SWAP(*str, *end); XOR_SWAP(*str, *end);
str++; str++;
end--; end--;
} }
# undef XOR_SWAP
} }
} }
@ -94,12 +101,12 @@ inline static void writeStringToStdin(char * string)
} }
static char *asciiCodes[3] = { static char *asciiCodes[3] = {
"\x1bOPP", "\x1bOP",
"\x1b[H", "\x1b[H",
"\x1bOJ" "\x1bOQ"
}; };
static int scanCodes[3] = { KEY_F1, KEY_HOME, KEY_F5 }; static int scanCodes[3] = { KEY_F1, KEY_HOME, KEY_F2 };
CTEST(getchTests, simpleGetchSetReturn_q) CTEST(getchTests, simpleGetchSetReturn_q)
{ {
@ -113,9 +120,8 @@ CTEST(getchTests, simpleGetchSetReturn_q)
CTEST(getchTests, SpecialKey) CTEST(getchTests, SpecialKey)
{ {
writeStringToStdin(asciiCodes[0]);
writeToEventDevice(scanCodes[0]); writeToEventDevice(scanCodes[0]);
writeStringToStdin(asciiCodes[0]);
int key = _getch(); int key = _getch();
int code = _getch(); int code = _getch();
@ -126,10 +132,8 @@ CTEST(getchTests, SpecialKey)
CTEST(specialKeyTests, Home) CTEST(specialKeyTests, Home)
{ {
writeToEventDevice(scanCodes[1]);
writeStringToStdin(asciiCodes[1]); writeStringToStdin(asciiCodes[1]);
writeToEventDevice(KEY_HOME);
int key = _getch(); int key = _getch();
int code = _getch(); int code = _getch();
@ -139,8 +143,10 @@ CTEST(specialKeyTests, Home)
CTEST(escapeReturnsTest, Esc) CTEST(escapeReturnsTest, Esc)
{ {
ungetc(27, stdin);
writeToEventDevice(KEY_ESC); writeToEventDevice(KEY_ESC);
ungetc(27, stdin);
int key = _getch(); int key = _getch();
ASSERT_EQUAL(27, key); ASSERT_EQUAL(27, key);