Merge 2.x into master

This commit is contained in:
R. Eric Wheeler 2024-07-18 13:18:01 -07:00
parent 24e53cd2cc
commit a278fd6400
4 changed files with 271 additions and 8 deletions

View File

@ -1,8 +1,7 @@
{ {
"name": "olivebbs/getch", "name": "sikofitt/getch",
"description": "Implements _getch and _ungetch for windows and linux using ffi", "description": "Implements _getch and _ungetch for windows and linux using ffi",
"type": "library", "type": "library",
"keywords": ["getch", "windows", "conio", "linux", "console", "conio.h", "hotkey", "termios"],
"require": { "require": {
"php": "^8.2", "php": "^8.2",
"ext-ffi": "*" "ext-ffi": "*"
@ -12,7 +11,7 @@
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Olive\\Console\\": "src/Console/" "Sikofitt\\Console\\": "src/Console/"
}, },
"files": [ "files": [
"functions.php" "functions.php"
@ -20,7 +19,7 @@
}, },
"autoload-dev": { "autoload-dev": {
"psr-4": { "psr-4": {
"Olive\\Tests\\Console\\": "tests/" "Sikofitt\\Tests\\Console\\": "tests/"
} }
}, },
"license": "MPL-2.0", "license": "MPL-2.0",
@ -29,8 +28,5 @@
"name": "R. Eric Wheeler", "name": "R. Eric Wheeler",
"email": "sikofitt@gmail.com" "email": "sikofitt@gmail.com"
} }
], ]
"conflict": {
"sikofitt/getch": "*"
}
} }

115
src/Console/Getch.php Normal file
View File

@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
/*
* Copyright (c) 2020-2024 https://sikofitt.com sikofitt@gmail.com
*
* 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 https://mozilla.org/MPL/2.0/.
*/
namespace Sikofitt\Console;
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 codes.
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 = <<<DECLARATIONS
int _getch();
int _ungetch(int c);
DECLARATIONS;
private static ?\FFI $ffi = null;
public static function resetFFI(): void
{
self::$ffi = null;
}
public function __construct(?string $linuxLibrary = null)
{
if (null === $linuxLibrary) {
$linuxLibrary = self::LINUX_LIBRARY;
}
if (null === self::$ffi) {
$osFamily = PHP_OS_FAMILY;
if ('Windows' === $osFamily) {
$declarations = self::DECLARATIONS.' int _kbhit();';
self::$ffi = \FFI::cdef($declarations, self::WINDOWS_LIBRARY);
} elseif ('Linux' === $osFamily) {
if (!file_exists($linuxLibrary)) {
throw new \RuntimeException(sprintf('Could not find library file %s.', $linuxLibrary));
}
$declarations = self::DECLARATIONS.' int cinPeek();';
self::$ffi = \FFI::cdef($declarations, $linuxLibrary);
} else {
throw new \RuntimeException(sprintf('Sorry, %s is not supported yet.', $osFamily));
}
}
}
public function peek(): int
{
if (PHP_OS_FAMILY === 'Windows') {
if (self::$ffi->_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);
}
}

95
tests/Getch/GetchTest.php Normal file
View File

@ -0,0 +1,95 @@
<?php
/*
* Copyright (c) 2020-2024 https://sikofitt.com sikofitt@gmail.com
*
* 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 https://mozilla.org/MPL/2.0/.
*/
namespace Sikofitt\Tests\Console\Getch;
use PHPUnit\Framework\TestCase;
use Sikofitt\Console\Getch;
class GetchTest extends TestCase
{
private \FFI $ffi;
private const HOME_KEY = "\x1b[H";
public function setUp(): void
{
$this->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);
}
}

View File

@ -0,0 +1,57 @@
<?php
/*
* Copyright (c) 2020-2024 https://sikofitt.com sikofitt@gmail.com
*
* 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 https://mozilla.org/MPL/2.0/.
*/
namespace Sikofitt\Tests\Console\Ungetch;
use PHPUnit\Framework\TestCase;
use Sikofitt\Console\Getch;
class UngetchTest extends TestCase
{
public function setUp(): void
{
parent::setUp(); // TODO: Change the autogenerated stub
}
public function testUngetch()
{
$g = new Getch();
$res = $g->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);
}
}