php-kilo/src/Termios.php

108 lines
2.4 KiB
PHP

<?php declare(strict_types=1);
namespace Aviat\Kilo;
use FFI;
use FFI\CData;
use Aviat\Kilo\Enum\C;
/**
* An implicit singleton wrapper around terminal settings to simplify enabling/disabling raw mode
*/
class Termios {
private CData $originalTermios;
private function __construct()
{
$ffi = get_ffi();
$termios = $ffi->new('struct termios');
if ($termios === NULL)
{
throw new TermiosException('Failed to create termios struct');
}
$termiosAddr = FFI::addr($termios);
$res = $ffi->tcgetattr(C::STDIN_FILENO, $termiosAddr);
if ($res === -1)
{
throw new TermiosException('Failed to get existing terminal settings');
}
$this->originalTermios = $termios;
}
/**
* Put the current terminal into raw input mode
*
* Returns TRUE if successful. Will return NULL if run more than once, as
* raw mode is pretty binary...there's no point in reapplying raw mode!
*
* @return bool|null
*/
public static function enableRawMode(): ?bool
{
static $run = FALSE;
// Don't run this more than once!
if ($run === TRUE)
{
return NULL;
}
$run = TRUE;
$instance = self::getInstance();
// Make sure to restore normal mode on exit/die/crash
register_shutdown_function([static::class, 'disableRawMode']);
$termios = clone $instance->originalTermios;
$termios->c_iflag &= ~(C::BRKINT | C::ICRNL | C::INPCK | C::ISTRIP | C::IXON);
$termios->c_oflag &= ~(C::OPOST);
$termios->c_cflag |= (C::CS8);
$termios->c_lflag &= ~( C::ECHO | C::ICANON | C::IEXTEN | C::ISIG );
$termios->c_cc[C::VMIN] = 0;
$termios->c_cc[C::VTIME] = 1;
// Turn on raw mode
$res = get_ffi()->tcsetattr(C::STDIN_FILENO, C::TCSAFLUSH, FFI::addr($termios));
return $res !== -1;
}
/**
* Restores terminal settings that were changed when going into raw mode.
*
* Returns TRUE if settings are applied successfully. If raw mode was not
* enabled, this will output a line of escape codes and a new line.
*
* @return bool
*/
public static function disableRawMode(): bool
{
$instance = self::getInstance();
// Cleanup
write_stdout(ANSI::CLEAR_SCREEN);
write_stdout(ANSI::RESET_CURSOR);
write_stdout("\n"); // New line, please
$res = get_ffi()->tcsetattr(C::STDIN_FILENO, C::TCSAFLUSH, FFI::addr($instance->originalTermios));
return $res !== -1;
}
private static function getInstance(): self
{
static $instance;
if ($instance === NULL)
{
$instance = new self();
}
return $instance;
}
}