diff --git a/composer.json b/composer.json index edf1884..87f1db0 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,7 @@ { - "name": "olivebbs/getch", + "name": "sikofitt/getch", "description": "Implements _getch and _ungetch for windows and linux using ffi", "type": "library", - "keywords": ["getch", "windows", "conio", "linux", "console", "conio.h", "hotkey", "termios"], "require": { "php": "^8.2", "ext-ffi": "*" @@ -12,7 +11,7 @@ }, "autoload": { "psr-4": { - "Olive\\Console\\": "src/Console/" + "Sikofitt\\Console\\": "src/Console/" }, "files": [ "functions.php" @@ -20,7 +19,7 @@ }, "autoload-dev": { "psr-4": { - "Olive\\Tests\\Console\\": "tests/" + "Sikofitt\\Tests\\Console\\": "tests/" } }, "license": "MPL-2.0", @@ -29,8 +28,5 @@ "name": "R. Eric Wheeler", "email": "sikofitt@gmail.com" } - ], - "conflict": { - "sikofitt/getch": "*" - } + ] } diff --git a/src/Console/Getch.php b/src/Console/Getch.php new file mode 100644 index 0000000..e10947f --- /dev/null +++ b/src/Console/Getch.php @@ -0,0 +1,115 @@ +_kbhit()) { + $result = self::$ffi->_getch(); + self::$ffi->_ungetch($result); + + return $result; + } + + return -1; + } + + return self::$ffi->cinPeek(); + } + + public function getch(): int + { + return self::$ffi->_getch(); + } + + public function ungetch(string|int $char): int + { + if (is_string($char)) { + $char = ord($char[0]); + } + + return self::$ffi->_ungetch($char); + } +} diff --git a/tests/Getch/GetchTest.php b/tests/Getch/GetchTest.php new file mode 100644 index 0000000..36fdd65 --- /dev/null +++ b/tests/Getch/GetchTest.php @@ -0,0 +1,95 @@ +ffi = \FFI::load(__DIR__.'/../test.h'); + $file = $this->ffi->stdin; + $stdin = $this->ffi->fopen('/dev/stdin', 'a+'); + + foreach (range('D', 'A') as $character) { + $this->ffi->ungetc(ord($character), $file); + } + } + + /** + * @preserveGlobalState disabled + * + * @backupStaticAttributes false + * + * @backupGlobals false + */ + public function testFailureOnInvalidLibrary() + { + Getch::resetFFI(); + $this->expectException(\RuntimeException::class); + \getch(__DIR__.'/library.so'); + } + + public function testGetchClass() + { + $getch = new Getch(); + foreach (range('A', 'D') as $character) { + self::assertSame(\ord($character), $getch->getch()); + } + } + + public function testGetchFunction() + { + foreach (range('A', 'D') as $character) { + self::assertSame(\ord($character), getch()); + } + } + + public function testUnsupportedOS() + { + if (PHP_OS_FAMILY !== 'Linux' || PHP_OS_FAMILY !== 'Windows') { + self::markTestSkipped('This test only applies to non Linux or Windows systems.'); + } + $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 new file mode 100644 index 0000000..53a50e9 --- /dev/null +++ b/tests/Ungetch/UngetchTest.php @@ -0,0 +1,57 @@ +ungetch('A'); + self::assertSame(65, $res); + $res = $g->getch(); + self::assertSame(65, $res); + $res = ungetch(65); + self::assertSame(65, $res); + $res = $g->getch(); + self::assertSame(65, $res); + } + + public function testTypeError() + { + $g = new Getch(); + $this->expectException(\TypeError::class); + $g->ungetch(new \stdClass()); + } + + public function testForFun() + { + foreach (\str_split(\strrev('Hello World!')) as $char) { + ungetch($char); + } + $result = ''; + do { + $ord = getch(); + $result .= \chr($ord); + } while ($ord !== ord('!')); + self::assertSame('Hello World!', $result); + } +}