setup(); } } public function __destruct() { if (--self::$init == 0) { $this->shutdown(); } } private function setup(): void { $this->oldStty = exec("stty -g"); exec("stty raw -echo"); //ob_start(); echo "\e[s"; // save cursor pos echo "\e[?1047h"; // alt buffer echo "\e[H\e[0m\e[2J"; // clear screen echo "\e[?7l"; // autowrap //ob_flush(); $this->measureTerminal(); pcntl_signal(SIGWINCH, $this->measureTerminal(...)); $this->active = true; } private function measureTerminal(): void { $this->lines = intval(exec("tput lines")); $this->columns = intval(exec("tput cols")); } public function shutdown(): void { //ob_end_clean(); if (!$this->active) return; exec("stty {$this->oldStty}"); echo "\e[?1047l"; // main buffer echo "\e[u\e[?25h"; // restore cursor pos and visibility echo "\e[?7h"; // autowrap $this->active = false; } public function getSize(): array { return [ $this->columns, $this->lines ]; } public function setCursor(int $column, int $row, bool $visible = false): void { if ($row > 0 && $column > 0) printf("\e[%d;%dH", $row, $column); echo "\e[?25".($visible?"h":"l"); } private string $inputBuffer = ''; public function readKey(): ?string { $r = [ STDIN ]; $w = $e = []; if (stream_select($r, $w, $e, 0)) { $read = fread(STDIN, 64); $this->inputBuffer .= $read; } return $this->parseInputBuffer(); } private function parseInputBuffer(): ?string { if (str_starts_with($this->inputBuffer, "\e")) { if (strncmp($this->inputBuffer, "\e[A", 3) === 0) { $this->inputBuffer = mb_substr($this->inputBuffer, 3); return "k{UP}"; } elseif (strncmp($this->inputBuffer, "\e[B", 3) === 0) { $this->inputBuffer = mb_substr($this->inputBuffer, 3); return "k{DOWN}"; } elseif (strncmp($this->inputBuffer, "\e[C", 3) === 0) { $this->inputBuffer = mb_substr($this->inputBuffer, 3); return "k{RIGHT}"; } elseif (strncmp($this->inputBuffer, "\e[D", 3) === 0) { $this->inputBuffer = mb_substr($this->inputBuffer, 3); return "k{LEFT}"; } elseif (strncmp($this->inputBuffer, "\e[5~", 4) === 0) { $this->inputBuffer = mb_substr($this->inputBuffer, 4); return "k{PGUP}"; } elseif (strncmp($this->inputBuffer, "\e[6~", 4) === 0) { $this->inputBuffer = mb_substr($this->inputBuffer, 4); return "k{PGDN}"; } elseif (strncmp($this->inputBuffer, "\e\e", 2) === 0) { $this->inputBuffer = mb_substr($this->inputBuffer, 2); return chr(27); } $this->inputBuffer = mb_substr($this->inputBuffer, 1); } if (mb_strlen($this->inputBuffer) > 0) { $ch = mb_substr($this->inputBuffer, 0, 1); // if (ord($ch) == 3) { // return "k{^C}"; // } $this->inputBuffer = mb_substr($this->inputBuffer, 1); return $ch; } return null; } }