diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c842cc3..981c08c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,7 +21,7 @@ before_script: # If Xdebug was installed you can generate a coverage report and see code coverage metrics. test:7.4: only: - - master + - 1.x tags: - default image: php:7.4 @@ -29,7 +29,7 @@ test:7.4: - vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never test:8.0: only: - - master + - 1.x tags: - default image: php:8.0 diff --git a/README.md b/README.md index b4cb16d..3c4c622 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ This simply uses the FFI extension to enable _getch and _ungetch in Windows and linux. +[![pipeline status](https://repos.bgemi.net/sikofitt/getch/badges/1.x/pipeline.svg)](https://repos.bgemi.net/sikofitt/getch/-/commits/1.x) +[![coverage report](https://repos.bgemi.net/sikofitt/getch/badges/1.x/coverage.svg)](https://repos.bgemi.net/sikofitt/getch/-/commits/1.x) + ```shell script $ composer require sikofitt/getch:dev-master ``` 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/functions.php b/functions.php index bf6a4bd..64443ca 100644 --- a/functions.php +++ b/functions.php @@ -31,12 +31,3 @@ if (!function_exists('ungetch')) { return $g->ungetch($char); } } - -if (!function_exists('ungetchString')) { - function ungetchString(string $string, string $linuxLibrary = null): bool - { - $g = new Getch($linuxLibrary); - - return $g->ungetchString($string); - } -} diff --git a/src/Console/Getch.php b/src/Console/Getch.php index 301c2e1..5a8f6c3 100644 --- a/src/Console/Getch.php +++ b/src/Console/Getch.php @@ -19,6 +19,36 @@ use RuntimeException; final class Getch { + // Special key codes + // Extended scan code used to indicate special keyboard functions + public const KEY_RESERVED = 0; + public const KEY_E0 = 0; + public const KEY_E1 = 224; + + // Supported scan scodes. + public const KEY_F1 = 59; + public const KEY_F2 = 60; + public const KEY_F3 = 61; + public const KEY_F4 = 62; + public const KEY_F5 = 63; + public const KEY_F6 = 64; + public const KEY_F7 = 65; + public const KEY_F8 = 66; + public const KEY_F9 = 67; + public const KEY_F10 = 68; + public const KEY_F11 = 87; + public const KEY_F12 = 88; + public const KEY_UP = 72; + public const KEY_LEFT = 75; + public const KEY_RIGHT = 77; + public const KEY_DOWN = 80; + public const KEY_DELETE = 83; + public const KEY_HOME = 71; + public const KEY_PAGEUP = 73; + public const KEY_END = 79; + public const KEY_PAGEDOWN = 81; + public const KEY_INSERT = 82; + private const LINUX_LIBRARY = __DIR__.'/Resources/libgetch.so'; private const WINDOWS_LIBRARY = 'ucrtbase.dll'; private const DECLARATIONS = <<_kbhit()) { + $result = $ffi->_getch(); + $ffi->_ungetch($result); + return $result; + } + return -1; + } + return $ffi->cinPeek(); + } + public function getch(): int { return self::$ffi->_getch(); diff --git a/src/Console/Resources/Makefile b/src/Console/Resources/Makefile index fa3a278..1bdfab1 100644 --- a/src/Console/Resources/Makefile +++ b/src/Console/Resources/Makefile @@ -1,5 +1,5 @@ CC = gcc -CFLAGS = -shared -Wall -fPIC +CFLAGS = -shared -Wall -Wno-unknown-pragmas -fPIC all: @${CC} ${CFLAGS} getch.c -o libgetch.so diff --git a/src/Console/Resources/getch.c b/src/Console/Resources/getch.c index a0acfb0..398b063 100644 --- a/src/Console/Resources/getch.c +++ b/src/Console/Resources/getch.c @@ -1,58 +1,303 @@ +/*********************************************************************** + * This Source Code Form is subject to the terms of the Mozilla Public * + * License, v. 2.0. If a copy of the MPL was not distributed with this * + * file, You can obtain one at http://mozilla.org/MPL/2.0/. * + ***********************************************************************/ + #include #include #include #include #include -static struct termios oldattr; +#include +#include +#include -static char *strrev(char *str) +#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; + +inline static void reverseString(char * str) { - char *p1, *p2; + if (str) + { + char * end = str + strlen(str) - 1; - if (! str || ! *str) - return str; - for (p1 = str, p2 = str + strlen(str) - 1; p2 > p1; ++p1, --p2) - { - *p1 ^= *p2; - *p2 ^= *p1; - *p1 ^= *p2; - } - return str; + while (str < end) + { + XOR_SWAP(*str, *end); + str++; + end--; + } + } } +static int discardRead(unsigned int length) +{ + char buffer[length]; + ssize_t bytesRead; + + int flags = fcntl(STDIN_FILENO, F_GETFL); + fcntl(STDIN_FILENO, F_SETFL, flags|O_NONBLOCK); + + if( (bytesRead = fread(buffer, sizeof(char), length, stdin)) == -1) { + perror("discardRead"); + } + + fcntl(STDIN_FILENO, F_SETFL, flags); + + return (int)bytesRead; +} + +static int getEventDevice(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; iffi = \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,28 @@ class GetchTest extends TestCase $this->expectException(\RuntimeException::class); \getch(); } + + 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; + + 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(71, $g->getch()); + } + + public function setPeek() + { + $g = new Getch(); + $g->ungetch('q'); + $peek = $g->peek(); + $getch = $g->getch(); + self::assertEquals($peek, $getch); + self::assertEquals(113, $peek); + } } 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);