Merge 1.x into master #1
|
@ -80,6 +80,22 @@ final class Getch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* public function keyCode(string $device): array
|
||||||
|
{
|
||||||
|
|
||||||
|
$arrayType = \FFI::arrayType(self::$ffi->type('int'), [3]);
|
||||||
|
$res = \FFI::new($arrayType);
|
||||||
|
|
||||||
|
$arrayType = self::$ffi->keyCode($device);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'type' => $arrayType[0],
|
||||||
|
'code' => $arrayType[1],
|
||||||
|
'value' => $arrayType[2],
|
||||||
|
'keyCode' => $arrayType[3],
|
||||||
|
];
|
||||||
|
}*/
|
||||||
|
|
||||||
public function getch(): int
|
public function getch(): int
|
||||||
{
|
{
|
||||||
return self::$ffi->_getch();
|
return self::$ffi->_getch();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
CC = gcc
|
CC = gcc
|
||||||
CFLAGS = -shared -Wall -fPIC
|
CFLAGS = -shared -Wall -Wno-unknown-pragmas -fPIC
|
||||||
|
|
||||||
all:
|
all:
|
||||||
@${CC} ${CFLAGS} getch.c -o libgetch.so
|
@${CC} ${CFLAGS} getch.c -o libgetch.so
|
||||||
|
|
|
@ -3,14 +3,105 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <errno.h>
|
#include <fcntl.h>
|
||||||
|
#include <linux/input.h>
|
||||||
|
#include <glob.h>
|
||||||
|
|
||||||
#define CTRL_KEY(k) ((k) & 0x1f)
|
// #define FKEY(k) ((k) >= KEY_F1 && (k) <= KEY_F10) || (k) == KEY_F12 || (k) == KEY_F11
|
||||||
|
#define NOTNUMPAD(k) ((k) >= KEY_HOME && (k) <= KEY_DELETE)
|
||||||
|
|
||||||
static struct termios oldattr;
|
#define EVENT_DEVICE_GLOB "/dev/input/by-path/*-event-kbd"
|
||||||
|
|
||||||
/*
|
static struct termios oldTermAttributes;
|
||||||
static char *strrev(char *str)
|
|
||||||
|
static void setRawMode(void);
|
||||||
|
static void setNormalMode(void);
|
||||||
|
|
||||||
|
inline static int discardRead(unsigned int length)
|
||||||
|
{
|
||||||
|
char buffer[length];
|
||||||
|
ssize_t bytes_read;
|
||||||
|
|
||||||
|
int flags = fcntl(STDIN_FILENO, F_GETFL);
|
||||||
|
fcntl(STDIN_FILENO, F_SETFL, flags|O_NONBLOCK);
|
||||||
|
|
||||||
|
if( (bytes_read = fread(buffer, sizeof(char), length, stdin)) == -1) {
|
||||||
|
perror("discardRead");
|
||||||
|
}
|
||||||
|
|
||||||
|
fcntl(STDIN_FILENO, F_SETFL, flags);
|
||||||
|
|
||||||
|
return (int)bytes_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getEventDevice(const char * device)
|
||||||
|
{
|
||||||
|
glob_t search;
|
||||||
|
|
||||||
|
int globResult = glob(
|
||||||
|
EVENT_DEVICE_GLOB,
|
||||||
|
GLOB_NOSORT,
|
||||||
|
NULL,
|
||||||
|
&search
|
||||||
|
);
|
||||||
|
|
||||||
|
if(0 != globResult) {
|
||||||
|
globfree(&search);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(search.gl_pathc == 0) {
|
||||||
|
globfree(&search);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t pathLength;
|
||||||
|
|
||||||
|
for(int i = 0; i<search.gl_pathc;i++) {
|
||||||
|
if(access(search.gl_pathv[i], F_OK) == 0) {
|
||||||
|
pathLength = strlen(search.gl_pathv[i]) + 1;
|
||||||
|
strncpy((char *)device, search.gl_pathv[i], pathLength);
|
||||||
|
globfree(&search);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
globfree(&search);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned short getScanCode()
|
||||||
|
{
|
||||||
|
struct input_event inputEvent[5];
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
const char device[FILENAME_MAX];
|
||||||
|
if(-1 == getEventDevice(device)) {
|
||||||
|
return KEY_RESERVED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( (fd = open(device, O_RDONLY)) == -1 ) {
|
||||||
|
return KEY_RESERVED;
|
||||||
|
};
|
||||||
|
|
||||||
|
if( read(fd, &inputEvent, sizeof (inputEvent)) == -1 ) {
|
||||||
|
return KEY_RESERVED;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
for(int i = 0; i<=5; i++) {
|
||||||
|
if(inputEvent[i].type == EV_KEY) {
|
||||||
|
return inputEvent[i].code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return KEY_RESERVED;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma ide diagnostic ignored "cppcoreguidelines-narrowing-conversions"
|
||||||
|
inline static char *reverseString(char *str)
|
||||||
{
|
{
|
||||||
char *p1, *p2;
|
char *p1, *p2;
|
||||||
|
|
||||||
|
@ -24,129 +115,114 @@ static char *strrev(char *str)
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
*/
|
#pragma clang diagnostic pop
|
||||||
static void setNormalMode(void)
|
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma ide diagnostic ignored "bugprone-reserved-identifier"
|
||||||
|
inline static void pushStdin(int c) {
|
||||||
|
ungetc(c, stdin);
|
||||||
|
}
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
|
||||||
|
inline static void setNormalMode(void)
|
||||||
{
|
{
|
||||||
tcsetattr(STDIN_FILENO, TCSANOW, &oldattr);
|
tcsetattr(STDIN_FILENO, TCSANOW, &oldTermAttributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void setRawMode(void)
|
inline static void setRawMode(void)
|
||||||
{
|
{
|
||||||
tcgetattr(STDIN_FILENO, &oldattr);
|
tcgetattr(STDIN_FILENO, &oldTermAttributes);
|
||||||
|
|
||||||
struct termios raw = oldattr;
|
struct termios raw = oldTermAttributes;
|
||||||
|
|
||||||
cfmakeraw(&raw);
|
cfmakeraw(&raw);
|
||||||
tcsetattr(STDIN_FILENO, TCSANOW, &raw);
|
tcsetattr(STDIN_FILENO, TCSANOW, &raw);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// F1 - 59
|
unsigned short resolveScanCode(unsigned short key)
|
||||||
// F2 - 60,
|
{
|
||||||
// F3 - 61,
|
switch(key) {
|
||||||
// F2 - 62
|
case KEY_DOWN: return KEY_KP2;
|
||||||
// F1-12 = 59 - 68
|
case KEY_LEFT: return KEY_KP4;
|
||||||
enum special_keycodes {
|
case KEY_RIGHT: return KEY_KP6;
|
||||||
GETCH_F1 = 59,
|
case KEY_UP: return KEY_KP8;
|
||||||
GETCH_F2,
|
case KEY_HOME: return KEY_KP7;
|
||||||
GETCH_F3,
|
case KEY_END: return KEY_KP1;
|
||||||
GETCH_F4,
|
case KEY_INSERT: return KEY_KP0;
|
||||||
GETCH_F5,
|
case KEY_DELETE: return KEY_KPDOT;
|
||||||
GETCH_F6,
|
case KEY_PAGEUP: return KEY_KP9;
|
||||||
GETCH_F7,
|
case KEY_PAGEDOWN: return KEY_KP3;
|
||||||
GETCH_F8,
|
default: return key;
|
||||||
GETCH_F9,
|
};
|
||||||
GETCH_F10,
|
|
||||||
GETCH_UP_ARROW = 72,
|
|
||||||
GETCH_LEFT_ARROW = 75,
|
|
||||||
GETCH_RIGHT_ARROW = 77,
|
|
||||||
GETCH_DOWN_ARROW = 80,
|
|
||||||
GETCH_DELETE = 83,
|
|
||||||
GETCH_F11 = 87,
|
|
||||||
GETCH_F12,
|
|
||||||
GETCH_HOME = 102,
|
|
||||||
GETCH_PGUP = 104,
|
|
||||||
GETCH_END = 107,
|
|
||||||
GETCH_PGDOWN = 109,
|
|
||||||
GETCH_INSERT
|
|
||||||
};
|
|
||||||
|
|
||||||
void _ungetc(int c) {
|
|
||||||
ungetc(c, stdin);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int readKey(void) {
|
int readKey(void) {
|
||||||
|
|
||||||
|
unsigned short scanCode;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int key = getchar();
|
int key = getchar();
|
||||||
|
|
||||||
|
if (key == 27) {
|
||||||
|
|
||||||
if (key == '\x1b') {
|
scanCode = getScanCode();
|
||||||
char seq[4];
|
|
||||||
if ((seq[0] = getchar()) == EOF) return '\x1b'; // [
|
|
||||||
if ((seq[1] = getchar()) == EOF) return '\x1b';
|
|
||||||
|
|
||||||
if (seq[0] == '[') {
|
if (scanCode == KEY_ESC) {
|
||||||
if(seq[1] == 'H') { _ungetc(GETCH_HOME); return 0; }
|
return 27;
|
||||||
if(seq[1] == 'F') { _ungetc(GETCH_END); return 0; }
|
|
||||||
|
|
||||||
if (seq[1] >= '0' && seq[1] <= '9') {
|
|
||||||
if ((seq[2] = getchar()) == EOF) return '\x1b';
|
|
||||||
if (seq[2] == '~') {
|
|
||||||
switch (seq[1]) {
|
|
||||||
case '1': _ungetc(GETCH_HOME); return 0;
|
|
||||||
case '2': _ungetc(GETCH_INSERT); return 0;
|
|
||||||
case '3': _ungetc(GETCH_DELETE); return 0;
|
|
||||||
case '4': _ungetc(GETCH_END); return 0;
|
|
||||||
case '5': _ungetc(GETCH_PGUP); return 0;
|
|
||||||
case '6': _ungetc(GETCH_PGDOWN); return 0;
|
|
||||||
case '7': _ungetc(GETCH_HOME); return 0;
|
|
||||||
case '8': _ungetc(GETCH_END); return 0;
|
|
||||||
}
|
|
||||||
} else if(seq[2] >= '0' && seq[2] <= '9') {
|
|
||||||
if ((seq[3] = getchar()) == EOF) return '\x1b';
|
|
||||||
if(seq[3] != '~') return seq[3];
|
|
||||||
|
|
||||||
switch(seq[2]) {
|
|
||||||
case '5': _ungetc(GETCH_F5); return 0;
|
|
||||||
case '7': _ungetc(GETCH_F6); return 0;
|
|
||||||
case '8': _ungetc(GETCH_F7); return 0;
|
|
||||||
case '9': _ungetc(GETCH_F8); return 0;
|
|
||||||
case '0': _ungetc(GETCH_F9); return 0;
|
|
||||||
case '1': _ungetc(GETCH_F10); return 0;
|
|
||||||
case '3': _ungetc(GETCH_F11); return 0;
|
|
||||||
case '4': _ungetc(GETCH_F12); return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else { return seq[2]; }
|
|
||||||
} else {
|
|
||||||
switch (seq[1]) {
|
|
||||||
case 'A': _ungetc(GETCH_UP_ARROW); return 0;
|
|
||||||
case 'B': _ungetc(GETCH_DOWN_ARROW); return 0;
|
|
||||||
case 'C': _ungetc(GETCH_RIGHT_ARROW); return 0;
|
|
||||||
case 'D': _ungetc(GETCH_LEFT_ARROW); return 0;
|
|
||||||
case 'H': _ungetc(GETCH_HOME); return 0;
|
|
||||||
case 'F': _ungetc(GETCH_END); return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (seq[0] == 'O') {
|
|
||||||
switch (seq[1]) {
|
int returnResult = 0;
|
||||||
case 'H': _ungetc(GETCH_HOME); return 0;
|
|
||||||
case 'F': _ungetc(GETCH_END); return 0;
|
if (NOTNUMPAD(scanCode)) {
|
||||||
case 'P': _ungetc(GETCH_F1); return 0;
|
scanCode = resolveScanCode(scanCode);
|
||||||
case 'Q': _ungetc(GETCH_F2); return 0;
|
returnResult = 224;
|
||||||
case 'R': _ungetc(GETCH_F3); return 0;
|
|
||||||
case 'S': _ungetc(GETCH_F4); return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return '\x1b';
|
|
||||||
} else {
|
u_short discardBytes;
|
||||||
return key;
|
|
||||||
|
switch (scanCode) {
|
||||||
|
case KEY_F1:
|
||||||
|
case KEY_F2:
|
||||||
|
case KEY_F3:
|
||||||
|
case KEY_F4:
|
||||||
|
|
||||||
|
case KEY_KP1: // END
|
||||||
|
case KEY_KP2: // DOWN
|
||||||
|
case KEY_KP4: // LEFT
|
||||||
|
case KEY_KP6: // RIGHT
|
||||||
|
case KEY_KP7: // HOME
|
||||||
|
case KEY_KP8: // UP
|
||||||
|
discardBytes = 2;
|
||||||
|
break;
|
||||||
|
case KEY_KP0: // INSERT
|
||||||
|
case KEY_KPDOT: // DELETE
|
||||||
|
case KEY_KP9: // PAGEUP
|
||||||
|
case KEY_KP3: // PAGEDOWN
|
||||||
|
discardBytes = 3;
|
||||||
|
break;
|
||||||
|
case KEY_F5:
|
||||||
|
case KEY_F6:
|
||||||
|
case KEY_F7:
|
||||||
|
case KEY_F8:
|
||||||
|
case KEY_F9:
|
||||||
|
case KEY_F10:
|
||||||
|
case KEY_F11:
|
||||||
|
case KEY_F12:
|
||||||
|
discardBytes = 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
discardBytes = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discardBytes != 0) {
|
||||||
|
discardRead(discardBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
pushStdin(scanCode);
|
||||||
|
return returnResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
int _getch(void) {
|
int _getch(void) {
|
||||||
|
@ -161,129 +237,7 @@ int _getch(void) {
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* reads from keypress, doesn't echo */
|
int _ungetch(int ch)
|
||||||
int old_getch(void)
|
|
||||||
{
|
|
||||||
int ch;
|
|
||||||
|
|
||||||
setRawMode();
|
|
||||||
atexit(setNormalMode);
|
|
||||||
|
|
||||||
ch = getchar();
|
|
||||||
|
|
||||||
setNormalMode();
|
|
||||||
|
|
||||||
if(ch == 27) {
|
|
||||||
|
|
||||||
char sequence[4];
|
|
||||||
|
|
||||||
if (read(STDIN_FILENO, &sequence[0], 1) != 1) return 27; // [
|
|
||||||
if (read(STDIN_FILENO, &sequence[1], 1) != 1) return 27; // 0-9
|
|
||||||
|
|
||||||
switch(ch)
|
|
||||||
{
|
|
||||||
case 79: // F1-F4
|
|
||||||
ch = getchar();
|
|
||||||
switch(ch) {
|
|
||||||
case 80: // F1, [ESC]OP
|
|
||||||
_ungetc(GETCH_F1);
|
|
||||||
return 0;
|
|
||||||
break;
|
|
||||||
case 81: // F2, [ESC]OQ
|
|
||||||
_ungetc(GETCH_F2);
|
|
||||||
return 0;
|
|
||||||
break;
|
|
||||||
case 82: // F3,, [ESC]OR
|
|
||||||
_ungetc(61);
|
|
||||||
return 0;
|
|
||||||
case 83: // F4, , [ESC]OS
|
|
||||||
_ungetc(62);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
case 91: // [, Everything else
|
|
||||||
ch = getchar();
|
|
||||||
switch(ch) {
|
|
||||||
case 49: // 1, F5-F8, HOME
|
|
||||||
ch = getchar();
|
|
||||||
switch(ch) {
|
|
||||||
case 53: // 5, F5
|
|
||||||
getchar(); // get the ~
|
|
||||||
_ungetc(63);
|
|
||||||
return 0;
|
|
||||||
case 55: // 7, F6
|
|
||||||
getchar();
|
|
||||||
_ungetc(64);
|
|
||||||
return 0;
|
|
||||||
case 56: // 8, F7
|
|
||||||
getchar();
|
|
||||||
_ungetc(65);
|
|
||||||
return 0;
|
|
||||||
case 57: // 9, F8
|
|
||||||
getchar();
|
|
||||||
_ungetc(66);
|
|
||||||
return 0;
|
|
||||||
case 126: // ~, HOME
|
|
||||||
_ungetc(102);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
case 50: // 2, F9-F12, INSERT
|
|
||||||
ch = getchar();
|
|
||||||
switch(ch) {
|
|
||||||
case 48: // 0, F9, [ESC][20~
|
|
||||||
getchar();
|
|
||||||
_ungetc(67);
|
|
||||||
return 0;
|
|
||||||
case 49: // 1, F10, [ESC][21~
|
|
||||||
getchar();
|
|
||||||
_ungetc(68);
|
|
||||||
return 0;
|
|
||||||
case 51: // 3, F11, [ESC][23~
|
|
||||||
getchar();
|
|
||||||
_ungetc(87);
|
|
||||||
return 0;
|
|
||||||
case 52: // 4, F12, [ESC][24~
|
|
||||||
getchar();
|
|
||||||
_ungetc(88);
|
|
||||||
return 0;
|
|
||||||
case 126: // ~, INSERT, [ESC][2~
|
|
||||||
_ungetc(110);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
case 51: // 3, DELETE, [ESC][3~
|
|
||||||
getchar(); // ~
|
|
||||||
_ungetc(83);
|
|
||||||
return 0;
|
|
||||||
case 52: // 4, END, [ESC][4~
|
|
||||||
getchar(); // ~
|
|
||||||
_ungetc(107);
|
|
||||||
return 0;
|
|
||||||
case 53: // 5, PGUP, [ESC][5~
|
|
||||||
getchar(); // ~
|
|
||||||
_ungetc(104);
|
|
||||||
return 0;
|
|
||||||
case 54: // 6, PGDN, [ESC][6~
|
|
||||||
getchar(); // ~
|
|
||||||
_ungetc(109);
|
|
||||||
return 0;
|
|
||||||
case 65: // A, UP_ARROW, [ESC][A //72
|
|
||||||
_ungetc(72);
|
|
||||||
return 0;
|
|
||||||
case 66: // B, DOWN_ARROW, [ESC][B // 80
|
|
||||||
_ungetc(80);
|
|
||||||
return 0;
|
|
||||||
case 67: // C, RIGHT_ARROW, [ESC][C /77
|
|
||||||
_ungetc(77);
|
|
||||||
return 0;
|
|
||||||
case 68: // D, LEFT_ARROW, [ESC][D //75
|
|
||||||
_ungetc(75);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
int _ungetch(char ch)
|
|
||||||
{
|
{
|
||||||
return ungetc(ch, stdin);
|
return ungetc(ch, stdin);
|
||||||
}
|
}
|
Binary file not shown.
|
@ -59,6 +59,8 @@ class GetchTest extends TestCase
|
||||||
|
|
||||||
public function testHomeKey()
|
public function testHomeKey()
|
||||||
{
|
{
|
||||||
|
self::markTestSkipped('This seems impossible to test, since it relies on someone actually pressing keys on the keyboard.');
|
||||||
|
|
||||||
$stdin = $this->ffi->stdin;
|
$stdin = $this->ffi->stdin;
|
||||||
|
|
||||||
foreach (str_split(strrev(self::HOME_KEY)) as $character) {
|
foreach (str_split(strrev(self::HOME_KEY)) as $character) {
|
||||||
|
@ -66,6 +68,6 @@ class GetchTest extends TestCase
|
||||||
}
|
}
|
||||||
$g = new Getch();
|
$g = new Getch();
|
||||||
self::assertEquals(0, $g->getch());
|
self::assertEquals(0, $g->getch());
|
||||||
self::assertEquals(102, $g->getch());
|
self::assertEquals(71, $g->getch());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue