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 = 0; // &= ~(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 = self::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 Terminal::clear(); Terminal::write("\n"); // New line, please $res = self::ffi()->tcsetattr(C::STDIN_FILENO, C::TCSAFLUSH, FFI::addr($instance->originalTermios)); return $res !== -1; } /** * Get the size of the current terminal window * * @codeCoverageIgnore * @return array|null */ public static function getWindowSize(): ?array { // First, try to get the answer from ioctl $ffi = self::ffi(); $ws = $ffi->new('struct winsize'); if ($ws !== NULL) { $res = $ffi->ioctl(C::STDOUT_FILENO, C::TIOCGWINSZ, FFI::addr($ws)); if ($res === 0 && $ws->ws_col !== 0 && $ws->ws_row !== 0) { return [$ws->ws_row, $ws->ws_col]; } } return null; } private static function getInstance(): self { static $instance; if ($instance === NULL) { $instance = new self(); } return $instance; } /** * A 'singleton' function to replace a global variable * * @return FFI */ private static function ffi(): FFI { static $ffi; if ($ffi === NULL) { $ffi = FFI::load(__DIR__ . '/ffi.h'); } return $ffi; } }