From be74c6f097630e8c6c46cc922f2f32b9581acc0b Mon Sep 17 00:00:00 2001 From: "R. Eric Wheeler" Date: Mon, 15 Feb 2021 11:44:39 -0800 Subject: [PATCH] Implement returns of special character codes --- composer.lock | 144 ++++++++-------- src/Console/Getch.php | 30 ++++ src/Console/Resources/getch.c | 268 +++++++++++++++++++++++++++-- src/Console/Resources/keycodes.txt | 16 ++ src/Console/Resources/libgetch.so | Bin 16440 -> 16696 bytes tests/Getch/GetchTest.php | 23 ++- tests/Ungetch/UngetchTest.php | 4 +- tests/test.h | 2 + 8 files changed, 395 insertions(+), 92 deletions(-) create mode 100644 src/Console/Resources/keycodes.txt diff --git a/composer.lock b/composer.lock index 935e256..b0b2ea0 100644 --- a/composer.lock +++ b/composer.lock @@ -377,16 +377,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.18.1", + "version": "v2.18.2", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "c68ff6231adb276857761e43b7ed082f164dce0b" + "reference": "18f8c9d184ba777380794a389fabc179896ba913" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/c68ff6231adb276857761e43b7ed082f164dce0b", - "reference": "c68ff6231adb276857761e43b7ed082f164dce0b", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/18f8c9d184ba777380794a389fabc179896ba913", + "reference": "18f8c9d184ba777380794a389fabc179896ba913", "shasum": "" }, "require": { @@ -468,7 +468,7 @@ "description": "A tool to automatically fix PHP code style", "support": { "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", - "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v2.18.1" + "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v2.18.2" }, "funding": [ { @@ -476,7 +476,7 @@ "type": "github" } ], - "time": "2021-01-21T18:50:42+00:00" + "time": "2021-01-26T00:22:21+00:00" }, { "name": "jetbrains/phpstorm-stubs", @@ -484,12 +484,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "44640f75da5865d1c3b0e433cc72ee4cdc677926" + "reference": "b8cf707c050f775cdb7693afa6099bd227b383f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/44640f75da5865d1c3b0e433cc72ee4cdc677926", - "reference": "44640f75da5865d1c3b0e433cc72ee4cdc677926", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/b8cf707c050f775cdb7693afa6099bd227b383f4", + "reference": "b8cf707c050f775cdb7693afa6099bd227b383f4", "shasum": "" }, "require-dev": { @@ -524,7 +524,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2021-01-25T16:29:22+00:00" + "time": "2021-02-15T11:11:55+00:00" }, { "name": "myclabs/deep-copy", @@ -1351,16 +1351,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.1", + "version": "9.5.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "e7bdf4085de85a825f4424eae52c99a1cec2f360" + "reference": "f661659747f2f87f9e72095bb207bceb0f151cb4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e7bdf4085de85a825f4424eae52c99a1cec2f360", - "reference": "e7bdf4085de85a825f4424eae52c99a1cec2f360", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f661659747f2f87f9e72095bb207bceb0f151cb4", + "reference": "f661659747f2f87f9e72095bb207bceb0f151cb4", "shasum": "" }, "require": { @@ -1438,7 +1438,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.1" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.2" }, "funding": [ { @@ -1450,7 +1450,7 @@ "type": "github" } ], - "time": "2021-01-17T07:42:25+00:00" + "time": "2021-02-02T14:45:58+00:00" }, { "name": "psr/container", @@ -2571,16 +2571,16 @@ }, { "name": "symfony/console", - "version": "v5.2.1", + "version": "v5.2.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "47c02526c532fb381374dab26df05e7313978976" + "reference": "89d4b176d12a2946a1ae4e34906a025b7b6b135a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/47c02526c532fb381374dab26df05e7313978976", - "reference": "47c02526c532fb381374dab26df05e7313978976", + "url": "https://api.github.com/repos/symfony/console/zipball/89d4b176d12a2946a1ae4e34906a025b7b6b135a", + "reference": "89d4b176d12a2946a1ae4e34906a025b7b6b135a", "shasum": "" }, "require": { @@ -2639,7 +2639,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Console Component", + "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", "keywords": [ "cli", @@ -2648,7 +2648,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.2.1" + "source": "https://github.com/symfony/console/tree/v5.2.3" }, "funding": [ { @@ -2664,7 +2664,7 @@ "type": "tidelift" } ], - "time": "2020-12-18T08:03:05+00:00" + "time": "2021-01-28T22:06:19+00:00" }, { "name": "symfony/deprecation-contracts", @@ -2735,16 +2735,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v5.2.1", + "version": "v5.2.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "1c93f7a1dff592c252574c79a8635a8a80856042" + "reference": "4f9760f8074978ad82e2ce854dff79a71fe45367" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/1c93f7a1dff592c252574c79a8635a8a80856042", - "reference": "1c93f7a1dff592c252574c79a8635a8a80856042", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4f9760f8074978ad82e2ce854dff79a71fe45367", + "reference": "4f9760f8074978ad82e2ce854dff79a71fe45367", "shasum": "" }, "require": { @@ -2797,10 +2797,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony EventDispatcher Component", + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.2.1" + "source": "https://github.com/symfony/event-dispatcher/tree/v5.2.3" }, "funding": [ { @@ -2816,7 +2816,7 @@ "type": "tidelift" } ], - "time": "2020-12-18T08:03:05+00:00" + "time": "2021-01-27T10:36:42+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -2899,16 +2899,16 @@ }, { "name": "symfony/filesystem", - "version": "v5.2.1", + "version": "v5.2.3", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "fa8f8cab6b65e2d99a118e082935344c5ba8c60d" + "reference": "262d033b57c73e8b59cd6e68a45c528318b15038" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/fa8f8cab6b65e2d99a118e082935344c5ba8c60d", - "reference": "fa8f8cab6b65e2d99a118e082935344c5ba8c60d", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/262d033b57c73e8b59cd6e68a45c528318b15038", + "reference": "262d033b57c73e8b59cd6e68a45c528318b15038", "shasum": "" }, "require": { @@ -2938,10 +2938,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Filesystem Component", + "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.2.1" + "source": "https://github.com/symfony/filesystem/tree/v5.2.3" }, "funding": [ { @@ -2957,20 +2957,20 @@ "type": "tidelift" } ], - "time": "2020-11-30T17:05:38+00:00" + "time": "2021-01-27T10:01:46+00:00" }, { "name": "symfony/finder", - "version": "v5.2.1", + "version": "v5.2.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "0b9231a5922fd7287ba5b411893c0ecd2733e5ba" + "reference": "4adc8d172d602008c204c2e16956f99257248e03" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/0b9231a5922fd7287ba5b411893c0ecd2733e5ba", - "reference": "0b9231a5922fd7287ba5b411893c0ecd2733e5ba", + "url": "https://api.github.com/repos/symfony/finder/zipball/4adc8d172d602008c204c2e16956f99257248e03", + "reference": "4adc8d172d602008c204c2e16956f99257248e03", "shasum": "" }, "require": { @@ -2999,10 +2999,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Finder Component", + "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.2.1" + "source": "https://github.com/symfony/finder/tree/v5.2.3" }, "funding": [ { @@ -3018,20 +3018,20 @@ "type": "tidelift" } ], - "time": "2020-12-08T17:02:38+00:00" + "time": "2021-01-28T22:06:19+00:00" }, { "name": "symfony/options-resolver", - "version": "v5.2.1", + "version": "v5.2.3", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "87a2a4a766244e796dd9cb9d6f58c123358cd986" + "reference": "5d0f633f9bbfcf7ec642a2b5037268e61b0a62ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/87a2a4a766244e796dd9cb9d6f58c123358cd986", - "reference": "87a2a4a766244e796dd9cb9d6f58c123358cd986", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/5d0f633f9bbfcf7ec642a2b5037268e61b0a62ce", + "reference": "5d0f633f9bbfcf7ec642a2b5037268e61b0a62ce", "shasum": "" }, "require": { @@ -3063,7 +3063,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony OptionsResolver Component", + "description": "Provides an improved replacement for the array_replace PHP function", "homepage": "https://symfony.com", "keywords": [ "config", @@ -3071,7 +3071,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v5.2.1" + "source": "https://github.com/symfony/options-resolver/tree/v5.2.3" }, "funding": [ { @@ -3087,7 +3087,7 @@ "type": "tidelift" } ], - "time": "2020-10-24T12:08:07+00:00" + "time": "2021-01-27T12:56:27+00:00" }, { "name": "symfony/polyfill-ctype", @@ -3721,16 +3721,16 @@ }, { "name": "symfony/process", - "version": "v5.2.1", + "version": "v5.2.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "bd8815b8b6705298beaa384f04fabd459c10bedd" + "reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/bd8815b8b6705298beaa384f04fabd459c10bedd", - "reference": "bd8815b8b6705298beaa384f04fabd459c10bedd", + "url": "https://api.github.com/repos/symfony/process/zipball/313a38f09c77fbcdc1d223e57d368cea76a2fd2f", + "reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f", "shasum": "" }, "require": { @@ -3760,10 +3760,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Process Component", + "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.2.1" + "source": "https://github.com/symfony/process/tree/v5.2.3" }, "funding": [ { @@ -3779,7 +3779,7 @@ "type": "tidelift" } ], - "time": "2020-12-08T17:03:37+00:00" + "time": "2021-01-27T10:15:41+00:00" }, { "name": "symfony/service-contracts", @@ -3862,16 +3862,16 @@ }, { "name": "symfony/stopwatch", - "version": "v5.2.1", + "version": "v5.2.3", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "2b105c0354f39a63038a1d8bf776ee92852813af" + "reference": "b12274acfab9d9850c52583d136a24398cdf1a0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/2b105c0354f39a63038a1d8bf776ee92852813af", - "reference": "2b105c0354f39a63038a1d8bf776ee92852813af", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/b12274acfab9d9850c52583d136a24398cdf1a0c", + "reference": "b12274acfab9d9850c52583d136a24398cdf1a0c", "shasum": "" }, "require": { @@ -3901,10 +3901,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Stopwatch Component", + "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v5.2.1" + "source": "https://github.com/symfony/stopwatch/tree/v5.2.3" }, "funding": [ { @@ -3920,20 +3920,20 @@ "type": "tidelift" } ], - "time": "2020-11-01T16:14:45+00:00" + "time": "2021-01-27T10:15:41+00:00" }, { "name": "symfony/string", - "version": "v5.2.1", + "version": "v5.2.3", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "5bd67751d2e3f7d6f770c9154b8fbcb2aa05f7ed" + "reference": "c95468897f408dd0aca2ff582074423dd0455122" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/5bd67751d2e3f7d6f770c9154b8fbcb2aa05f7ed", - "reference": "5bd67751d2e3f7d6f770c9154b8fbcb2aa05f7ed", + "url": "https://api.github.com/repos/symfony/string/zipball/c95468897f408dd0aca2ff582074423dd0455122", + "reference": "c95468897f408dd0aca2ff582074423dd0455122", "shasum": "" }, "require": { @@ -3976,7 +3976,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony String component", + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", "homepage": "https://symfony.com", "keywords": [ "grapheme", @@ -3987,7 +3987,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.2.1" + "source": "https://github.com/symfony/string/tree/v5.2.3" }, "funding": [ { @@ -4003,7 +4003,7 @@ "type": "tidelift" } ], - "time": "2020-12-05T07:33:16+00:00" + "time": "2021-01-25T15:14:59+00:00" }, { "name": "theseer/tokenizer", diff --git a/src/Console/Getch.php b/src/Console/Getch.php index 301c2e1..4825458 100644 --- a/src/Console/Getch.php +++ b/src/Console/Getch.php @@ -19,6 +19,31 @@ use RuntimeException; final class Getch { + // Special key codes + public const GETCH_SPECIAL = 0; + public const GETCH_F1 = 59; + public const GETCH_F2 = 60; + public const GETCH_F3 = 61; + public const GETCH_F4 = 62; + public const GETCH_F5 = 63; + public const GETCH_F6 = 64; + public const GETCH_F7 = 65; + public const GETCH_F8 = 66; + public const GETCH_F9 = 67; + public const GETCH_F10 = 68; + public const GETCH_F11 = 87; + public const GETCH_F12 = 88; + public const GETCH_UP_ARROW = 72; + public const GETCH_LEFT_ARROW = 75; + public const GETCH_RIGHT_ARROW = 77; + public const GETCH_DOWN_ARROW = 80; + public const GETCH_DELETE = 83; + public const GETCH_HOME = 102; + public const GETCH_PGUP = 104; + public const GETCH_END = 107; + public const GETCH_PGDN = 109; + public const GETCH_INSERT = 110; + private const LINUX_LIBRARY = __DIR__.'/Resources/libgetch.so'; private const WINDOWS_LIBRARY = 'ucrtbase.dll'; private const DECLARATIONS = << #include #include -static struct termios oldattr; +#include +#include +#define CTRL_KEY(k) ((k) & 0x1f) + +static struct termios oldattr; +static int stdin_flags; + +/* static char *strrev(char *str) { char *p1, *p2; @@ -19,26 +26,152 @@ static char *strrev(char *str) } return str; } - - -static void setRawMode(void) -{ - struct termios newattr; - - tcgetattr(STDIN_FILENO, &oldattr); - newattr = oldattr; - cfmakeraw(&newattr); - tcsetattr(STDIN_FILENO, TCSANOW, &newattr); - -} +*/ static void setNormalMode(void) { tcsetattr(STDIN_FILENO, TCSANOW, &oldattr); + fcntl(STDIN_FILENO, F_SETFL, stdin_flags); } +static void setRawMode(void) +{ + tcgetattr(STDIN_FILENO, &oldattr); + atexit(setNormalMode); + + struct termios raw = oldattr; + + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag &= ~(OPOST); + raw.c_cflag |= (CS8); + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + raw.c_cc[VMIN] = 0; + raw.c_cc[VTIME] = 1; + + tcsetattr(STDIN_FILENO, TCSANOW, &raw); + +} + +// F1 - 59 +// F2 - 60, +// F3 - 61, +// F2 - 62 +// F1-12 = 59 - 68 +enum special_keycodes { + GETCH_F1 = 59, + GETCH_F2, + GETCH_F3, + GETCH_F4, + GETCH_F5, + GETCH_F6, + GETCH_F7, + GETCH_F8, + 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 bytesRead; + + char key; + + while ((key = fgetc(stdin)) == EOF) { + } + + if (key == '\x1b') { + char seq[4]; + if ((seq[0] = getchar()) == EOF) return '\x1b'; // [ + if ((seq[1] = getchar()) == EOF) return '\x1b'; + + if (seq[0] == '[') { + if(seq[1] == 'H') { _ungetc(GETCH_HOME); return 0; } + 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]) { + case 'H': _ungetc(GETCH_HOME); return 0; + case 'F': _ungetc(GETCH_END); return 0; + case 'P': _ungetc(GETCH_F1); return 0; + case 'Q': _ungetc(GETCH_F2); return 0; + case 'R': _ungetc(GETCH_F3); return 0; + case 'S': _ungetc(GETCH_F4); return 0; + } + } + return '\x1b'; + } else { + return key; + } +} + +int _getch(void) { + + setRawMode(); + + int key = readKey(); + + setNormalMode(); + + return key; +} /* reads from keypress, doesn't echo */ -int _getch(void) +int old_getch(void) { int ch; @@ -49,6 +182,113 @@ int _getch(void) 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; } diff --git a/src/Console/Resources/keycodes.txt b/src/Console/Resources/keycodes.txt new file mode 100644 index 0000000..58ce75a --- /dev/null +++ b/src/Console/Resources/keycodes.txt @@ -0,0 +1,16 @@ +F1-10 = 59..68 +UP_ARROW = 72 +LEFT_ARROW = 75 +RIGHT_ARROW = 77 +DOWN_ARROW = 80 +DELETE = 83 +F11-12 = 87..88 +HOME = 102 +PGUP = 104 +END = 107 +PGDN = 109 +INSERT = 110 + + + + diff --git a/src/Console/Resources/libgetch.so b/src/Console/Resources/libgetch.so index 8750c7b2d20a7bc1ce5539d80fcf26417a03bb0e..82dae79752af20315d789689f8a515987fd7fc90 100755 GIT binary patch literal 16696 zcmeHOYj7OJweHme3uC;(*x=Ys*ia}DR9IyE1{tp`uRMU?*o4G5qqVep1WBvAJ4$31 z4-sMPt&PD@1@TRViFN8L-g2q7-6FA{cQ9tp%G!DW(=QW0G2Vk#EF4>|GG<~!Ye zdS^$g6#0?-xV632bNYPeoPM0??&+EBejvP}p~UAC97@G4g1F&Ig@m-A_a>bH35kWG z0@s;hhSV*~iN>U=Cn$)i(D_msN>Pv>k$5V%`XOAxWj?|#L!>Zq>yH0E)%eU6*L~{ui&w4v>jyvn zpKp9gNyc8OK7p~Y*a3|5bBk>-Q`~?~$Hu{*90z}T9Q>(qaB6P=(HkBFP^do9aqtb} z;0MRSKL{SK7_dc(k=W;Y8!>SJrpLx}xhN zn>(UPR#;*;^+xQL_O3`r`_`y%ni5eb;y7{9($wX2fN#~jO$9aGV>h+kW4A=wJA~cS z1zD3wIL+-{A|8!2-yYo}S|mo7wn$vWI+|^nmU1>}L)EF&h!gE?cSJ|~=B8jG7Mw4Z zuefz-y?tYFu1Vb(oTr*wB1$k4=t|>VQI+yxWT>Cg^g;Rxl05FFwzpp@VEY@5^~`9$ zm~wbqsS7jDX#S`k1eHAhXuMD3-5~|=Ji`3(B?_l8N`05ZkPD|8b4a^z&WmKjE*up% zVZ?<`HYn!3kYZ>WcaPtI3)fvwDptF2-Ss41^gz)AMGq7`Q1n3214R$~fAN6T z|3QV7Du4S1A*^kKPD%Et)&H}K!=`!JdGA1!t$G(%|5YKRh;Ji#W+;nL^*iEeNt!t- z`Ck)HOVP}rz0|wDimDlKeBo(~>XKC;1-{Ps_JVx8%P^ zJS}N6jgtQ+@wDX2tdacV#M4qQ6O#NC@GZN*%V6zX{2%~pXZcnli$7|1={*j`g2g;5&o#}zK^ob30!CTw@rm_({m+$+W%bmbZxe0;sJWS&WvIh zq-RLU8A#6LB=15ZbvOFARjKruTB~R3Knv{BKfRF6%KMZ#$g&2=?jhN?jBF3fZty=c z7Xwh1u*m;tHPY$5P}>nc3+!yJ)GlRlw)TGiwl|R(S@>TV>`%|`ADQ6aUJr2+H-G9j z$Y{v?3U5lOvJz`II@e*OE#J}XkgDQTQ;ko+YM_g%CJa_i*d2s@@HcMChp3in!f}JW zO&Cos>7Nny_VrJz=nK683q*9yHj)gxy70r@`XD z$Q{(c7NmD&)+nmY3u!gYF^W1du_`piS-Z$7O zumiUfcFbVECrsYiLk4?^FxlCkHW+=kLiJDAuJ&)EZ+u9riZ}F+TVzLr1@x)oLn&WkJ9(Thw1+WmJDA=&j*7^Y22A8y$TALi6@iMUW~Bx zfV3P=UqIpg_YGq%S^^kT;dqDc@ z$`BR1-~SMGB$;}2pGqxq65ugm&}V0)7CN`l)&JW(Wktl_DV=|!!kLt151Zg`D53hQ>7{~ z%=JPdB-QfQlUOF5hu-Y7nRSrDfY#r6D{eHr7>XV!dZ6fme}e~>p3Y|PMEo-1{fLhv z?n8VY@pZ(Vn5Djpm_ejT_!Y#(nqLbVMcjgT0kIPE_%(9_eW`D#U*ic@HNj+0 zPg)CgsvtlIvZZ=gp^l3PtD9W;a7q287da>ZEXnUhyan?2K#jcQ9jQR_rx450Hu)bi z+c)Oy4?;c>a`~Sz%S&6zNQ3PEfVcto`6^J8{}*%i7XYt^T>ckr>>n>>`&sbAFK|CU z>$cyl?CJm1TF8eWpG@*%D0-mifuaYB9w>UC=z*dKiXJF>py+{rnFn}ZBJV@guQPJ5 zBNb})!s(5Vymzoq?;HJut{Lxl)G+pmt1v@T-uFoF6BJl}W;7d<06yDN;Jtv;%M^c+ zB4VMI)B78Rj3U_as={T#ihwSN@kU({uIHfE$6p9C zaC!JsRMx*n>*L>1$+I8vS|<4tEyt^@qw5_+0g&@+dSQ`U?1m{+XbBc%CI)3R@6;=p;OEN)VUbh=uCE z2RN0xCxiq-ts>$FpoIS+qyRl_if6%6xo_%r$!|x%4rzSy6s0e}86o)^Vxe-!^XD<> z`_O-$x?1Jqz81>oT_{j}y7dEszxAAy_}KpZA#h)T{xB->vHfj=yb)vj`3&HcPj3i@ zuJ9R&kL`!_Evkf`PM+~d|A-elUvV4ofGb~4(qHSIE+s`! ztH#l%9cP8=`8D8Ik1bY+rnr-Eda%2zN#u^T*iNV2L_|_qXo_`qcSN0Nb8z9j8y6H-qVq28b|fB;Y_aiti{sczJklApn|nGtx4^{3+0b5(Th_ogI4$rKL#XBP%D`U-3;?_mJDwFD{3#@FrVO`zIupM5t z3}<%q2^d))s%j!;w?(>|>BP>mFRZFtd22n;<*PQ>VN2Ut%hmzH;U1lXy?n*$rFARp z)eQ}IgxA~a>z1wv<17%>+MLPxM<;`1?L#3xc|+%Ij(H`r{V{FLc(T)fi=u~%yBZBfa29XT5 z#=zpd6t2{Hp%g>+i-=&fO?TI}W@N&gGFAOnS#TO9L^|7>P<+gxoKZQM&*nrz1TlCz zF(3+!#(x^4#|Qp~iD7uFzzIzkdSG6^vw9U0^v=L$yl!MVg!zpEt#t$4771D-usyG9 znGR}2mV4X3QQK2JDDZm|Q-04Pdpupo_%8~Ke8TT_<~ggMx84;UuV-K)LnE$k`+&A% zIw-Yr7k-R0rR`*H`ynl0%I)PGm}g48*lo}2Nv4nR3#!h5%jNvGdhB@}&y?RUS>N0K zK5ftUM+CH{ro)D`JoSFb&7h;rNW`-7n*bOrVsLwk^*wI9F# z^7_}DUuH#3*`Dct0r$J?`F*4^s0>(7o16Z}_yGv0?{WG3-ood+2X*~f&USns>E)eN zSkCV^$8P|LP_2l$vOUumA#vM_kXEfxv|F=G*pBI|5V-C6eW_d9v!2(U`J+gX4%fdg zq$IuCzEQE}8Zar9pzXp`zE(Fuwz| zY7|ROqgWOd%7-Sc_<)oW(R`_uN-0fSdLeC~m8wENM5t|4QZ?1NL9IhXQ(bbnDJR{^?@6i|eE?Z}nJm=>$X z2mQ3W&hV$1iXE|Jw}|Z)v7OKo_b0T`G^0A-b5ft?X9w#El^8w(I}RCZd^(Zvb#Z^S z#y7=&3T0N&_?mDKPyI%Raf;8-STm|W{K*SnfB2DqUO}~Lq5h*)BrgG*R|Ve#YI@8X!R=BVwTGs1gV zXN>VX8UGUF%C-;W^8I$vNfsR2R`&MYJMC1ukUo$pI_bjhoo(5Ce|mRve>QDo)g^Y% z;iTQ0=}%@eN7Ab36td}l<@6j#J4weWsGi)hI!*m^U> zI?Tm-Js+uN{uF2ge>6Yq)HtZj_XU~EtqS_*7#m!YnN8XkO=CB&Z{|<%WNeY^p7Bn` zyW<9s>sRSIN3;<^x$&^1fBBW7?4{Q#Z7e<2;T}d5cS}4 z&mcjo2iNyi0#R1QHBrOfosBL6>J}?*s-%C5iz?OW!RL7Juo^}n zj6fKHFalu&!U%*B2qW;>jDR(ArO_H|c;{ZFtZ$Z_y2=G>jWmc$b!&HtKsTEb3@YyK?pv~-;~q50Q|rzPve zG0ne9JT09khBf~R@wDum7|{F{@V&>t>tY?<^c?`!(T3-V+&|^qf#KZ1(>e`jUgdIc zZ>)LgGg_DYe>1?t{xNO8zKh6;8`kKg^~T>Hvfh}gw<2#@7jHPXqrqQtgDrsur?oe{ zX*SwXgBz@oO)tIY}4Psl{aAsWecahZ#7&75DEGd17GPQvpDr${B zRklW+Dl2D>HI^thzowok4bG(M`%lx^8cj?ZdanHFjY`Ej+F8+j5e2OBi2Z6DngA|4(Xq`bzK+gmHWTn=E^!?mj^K@eHP+I62Hfb| z@a*b!>JGzx7n0Qgd4JbNUsnC=CI4C_}pZuUwIR(1oYNc`>H1Z*yen9U8ZNwA*cFCa}G5QtB006@Wr)vFC#m$ z--=@qd}t(q3)#}Q+H1g161KUi<=MKnTW8jtKo6$vZvZ|5pMT_&zv{}51D}Vn+~JjX zYq9q2r@)&a*WX;F{ob4MKSI73@_W4Y`;DBwOJ2tjg}jO6VF@D;Mj(tp7=bVXVFbbm zgb@fM5Jn)3z-Kf9vOiMxL-OOC-g8MV&lSIU1Lm#F5A#0N#mvjT>f3~8x`e6hzq~yl zzjmXN*ARUws)2}h@W%?x3XPJJR>B~%I|LnH5wy#86x25}?d_H=AY;A1S%EFrHz`7NN33lt_ z>uJZW{BRx#?Gl!G;DNDEwqD76@#8a;#NCfKDEYno_)I0gyC0wB&JRD1*}>!XZZ_1L}NSru<~_eFcH>fyXxmwtSnsy09Bv2$Ja zMf>$}Gl+W;Q?JKc9hnbaX}wyg_EpvYyjM-v={lOBKEM}qf1&$Q9j^XTAfEINhVWn# z!d;BZeNI~2fJ(&U5A{c-9Y>@}d~(34eYtOvE|no15j8!|F9NUT{|T+XPz}dXLHVj7 z>J%uof0Fm7>vvwje!%z+PIA!ix`4gTc!clA`W+Xr^Ej&YTPvOyp&!Bgd4d8JDAn5e z6LiSW0q$4cef?GA)939K;E^ix;bVghnPOk7Xqhtg7fDtjZdG48-Ul*T|6*P zHUW>K?_hZhIEUUR$^x&JUou<+Kd$jPDwxmlQVspnz@xq%4RfxB{vT@KS2aF8zvPn| z`t$3mk84E@ybX9YKlcJ(R>gnSQ*esTAa-B%sGDgkwv)4aC~>8TG?7%^K9J4tPiF0u zlP?tQ|m)$v!&kCLWi>8cL@zX+3@{n8kBbx-O8=b5lZX1=5a8c8_1h zO&apUZeEa^UgVd%8Ag8Cn=TZPP|DMPkk;h4QnBJt&PnbEbqa>|N$o)YV5~1$>{GGS zP(RcRMIKa)Bu}Ob#Z126$Jme+(%B>#aBU##sF?nlVyMOr+_GW2F2xSid2}k z24LJdePuZ~T62q;tjC1X{g4W+Z>cKwvR+=vGO?F+n^0NTLroWMPFO`rW3|HZyD zYBYtmkkC_P+yc>hkk(4_{3p-NZvL()G8KEF-vi$4v6uCGOUxLE9-F)KNAPh7Xzob= zvd))0=Mwubaffi = \FFI::load(__DIR__.'/../test.h'); - $stdin = $this->ffi->stdin; + $file = $this->ffi->stdin; + $stdin = $this->ffi->fopen('/dev/stdin', 'a+'); foreach (range('D', 'A') as $character) { - $this->ffi->ungetc(ord($character), $stdin); + $this->ffi->ungetc(ord($character), $file); } - - parent::setUp(); // TODO: Change the autogenerated stub } /** * @preserveGlobalState disabled + * @backupStaticAttributes false + * @backupGlobals false */ public function testFailureOnInvalidLibrary() { + Getch::resetFFI(); $this->expectException(\RuntimeException::class); \getch(__DIR__.'/library.so'); } @@ -53,4 +56,16 @@ class GetchTest extends TestCase $this->expectException(\RuntimeException::class); \getch(); } + + public function testHomeKey() + { + $stdin = $this->ffi->stdin; + + foreach (str_split(strrev(self::HOME_KEY)) as $character) { + $this->ffi->ungetc(ord($character), $stdin); + } + $g = new Getch(); + self::assertEquals(0, $g->getch()); + self::assertEquals(102, $g->getch()); + } } diff --git a/tests/Ungetch/UngetchTest.php b/tests/Ungetch/UngetchTest.php index 1b5515a..e495257 100644 --- a/tests/Ungetch/UngetchTest.php +++ b/tests/Ungetch/UngetchTest.php @@ -34,14 +34,14 @@ class UngetchTest extends TestCase public function testForFun() { - foreach(\str_split(\strrev('Hello World!')) as $char) { + foreach (\str_split(\strrev('Hello World!')) as $char) { ungetch($char); } $result = ''; do { $ord = getch(); $result .= \chr($ord); - } while($ord !== ord('!')); + } while ($ord !== ord('!')); self::assertSame('Hello World!', $result); } } diff --git a/tests/test.h b/tests/test.h index 5f84fc9..9de0eca 100644 --- a/tests/test.h +++ b/tests/test.h @@ -13,5 +13,7 @@ typedef struct _iobuf FILE *stdin; +FILE *fopen(const char *filename, const char *mode); + int ungetc(int ch, FILE *stream);