From 8baf1bdf54d904b53eb9672d70ae6fb57989cbaa Mon Sep 17 00:00:00 2001 From: Dominique Fournier Date: Fri, 8 Jun 2018 14:44:13 +0000 Subject: [PATCH] Add console support git-svn-id: https://svn.fournier38.fr/svn/ProgSVN/trunk@4227 bf3deb0d-5f1a-0410-827f-c0cc1f45334c --- console.php | 340 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 console.php diff --git a/console.php b/console.php new file mode 100644 index 0000000..2e7f7d8 --- /dev/null +++ b/console.php @@ -0,0 +1,340 @@ + + */ + +/** Allow to manage a linux Console to have a minimal but working text interface + * When using this class, you must use the $console::echo method and not + * display directely on screen + */ +class console +{ + // PROPERTIES + // {{{ + /** Save the initial stty value + */ + private $initSttyState; + + /** Line Content + */ + private $lineContent = ""; + + /** If true, display each char the user has pressed (echo mode) + */ + private $echoMode = true; + + /** List of non printable chars in decimal. The non printable chars are not + * displayed but are correctely captured + */ + private $nonWriteableChar = array (1, 2, 4, 6, 8, 9, 16, 18, + 20, 21, 22, 23, 24, 25, + 127); + // }}} + + /** The constructor init the console. + * Check if we have the rights to execute, if wa have in cli... + */ + public function __construct () + // {{{ + { + if (! function_exists ("exec")) + throw $this->ConsoleException ("No exec support in PHP"); + $this->initSttyState = exec ("stty -g"); + // Set the terminal to return the value each time a key is pressed. + // Do not display anything, so we don't see the characters when the user is + // deleting. + exec ("stty -echo -icanon min 1 time 0"); + } + // }}} + + /** The destructor return the terminal to initial state + */ + public function __destruct () + // {{{ + { + exec ("stty $this->initSttyState"); + } + // }}} + + /** Each time a key is pressed by the user, display the value on screen (echo) + */ + public function setEcho () + // {{{ + { + $this->echoMode = true; + } + // }}} + + /** Each time a key is pressed by the user, DO NOT display the value on screen + * (echo disabled) + */ + public function unsetEcho () + // {{{ + { + $this->echoMode = false; + } + // }}} + + /** Display a text on screen + * @param string $message The message to display + */ + public function echo ($message) + // {{{ + { + echo $message; + $this->lineContent .= $message; + } + // }}} + + /** Wait one valid character from the user. + * The non printable chars are not displayed, nor returned + * The ESC Sequences are skipped + * @return the pressed char + */ + public function getc () + // {{{ + { + $char = $this->getKey (); + while (in_array (ord ($char), $this->nonWriteableChar)) + $char = $this->getKey (); + return $char; + } + // }}} + + /** Wait one key pressed by the user. If the key pressed is an ESC sequence, + * return this sequence + * The non printable chars are not displayed, but are correctely returned + * @return the pressed char + */ + public function getKey () + // {{{ + { + $char = fgetc (STDIN); + if ($char === chr (27)) + { + $sequence = $char; + // Start an ESC CSI sequence. Do not display it, just return it, complete. + // ESC [ is followed by any number (including none) of "parameter bytes" + // in the range 0x30–0x3F (ASCII 0–9:;<=>?), then by any number of + // "intermediate bytes" in the range 0x20–0x2F (ASCII space and + // !"#$%&'()*+,-./), then finally by a single "final byte" in the range + // 0x40–0x7E (ASCII @A–Z[\]^_`a–z{|}~).[14]:5.4 + $char = fgetc (STDIN); + if ($char !== chr (91)) + $this->consoleException ("Invalid ESC CSI sequence provided"); + $sequence .= $char; + $char = fgetc (STDIN); + while (ord ($char) < 64 || ord ($char) > 126) + { + $sequence .= $char; + $char = fgetc (STDIN); + } + $sequence .= $char; + return $sequence; + } + if ($this->echoMode && ! in_array (ord ($char), $this->nonWriteableChar)) + echo $char; + return $char; + } + // }}} + + /** Get the line of characters pressed by the user and return the result. + * Stop when the user valid by \n or one of the stoppers chars. If the + * stoppers chars are set, the last char of the returned string is the + * stopper char. If not set, the method do not return \n at the end. + * Manage correctely the backspace, the Ctrl+W to remove word... + * @param string $stopperChar The stopper chars. + * @return The typed string + */ + public function gets ($stopperChar = false) + // {{{ + { + // Gets can not delete chars before the call. Keep the prompt (if exists) + $prompt = $this->lineContent; + $minLength = strlen ($this->lineContent); + // The cursor position from first char of line. + $cursorPos = $minLength; + $string = ""; + while (1) + { + $char = $this->getKey (); + if ($stopperChar === false && $char === "\n") + { + // End of process without stopperChars + $this->lineContent = ""; + break; + } + if ($stopperChar !== false && in_array ($char, str_split ($stopperChar))) + { + // End of process with stopperChars + $this->lineContent = ""; + $string .= $char; + break; + } + elseif (ord($char) === 21) + { + // Empty line next to prompt (Ctrl+U) + // {{{ + $string = substr ($string, $cursorPos - $minLength); + echo "\r".str_repeat (" ", strlen ($this->lineContent))."\r"; + $this->lineContent = $prompt.$string; + echo $this->lineContent; + $cursorPos = $minLength; + // Move the cursor on the correct position + echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos); + // }}} + } + elseif (ord($char) === 23) + { + // Remove the last word (Ctrl+W) + // TODO : Cursor management !! + $tmp = substr ($string, 0, $cursorPos - $minLength); + $end = substr ($string, $cursorPos - $minLength); + $tmp = rtrim ($tmp); + $pos = strrpos ($tmp, " "); + $string = substr ($string, 0, $pos).$end; + $cursorPos = $minLength + $pos; + if ($this->echoMode) + { + echo "\r".str_repeat (" ", strlen ($this->lineContent))."\r"; + $this->lineContent = $prompt.$string; + echo $this->lineContent; + // Move the cursor on the correct position + echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos); + } + } + elseif (ord($char) === 127) + { + // Remove the previous char (Backspace) + // {{{ + if (strlen ($this->lineContent) < $minLength + 1) + continue; + if ($cursorPos < $minLength +1) + continue; + $cursorPos--; + $strArr = str_split ($string); + unset ($strArr[$cursorPos - $minLength]); + $string = implode ($strArr); + if ($this->echoMode) + { + echo "\r".str_repeat (" ", strlen ($this->lineContent))."\r"; + $this->lineContent = $prompt.$string; + echo $this->lineContent; + // Move the cursor on the correct position + echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos); + } + // }}} + } + elseif (ord ($char{0}) === 27) + { + // ESC SEQUENCE + /*file_put_contents ("/tmp/debug", "SEQ CHAR=", FILE_APPEND); + foreach (str_split ($char) as $key) + file_put_contents ("/tmp/debug", ord ($key)." ", FILE_APPEND); + file_put_contents ("/tmp/debug", "\n", FILE_APPEND);*/ + if ($char === chr (27).chr (91).chr (67)) + { + // Cursor right + // {{{ + if ($cursorPos < strlen ($this->lineContent)) + { + echo $char; + $cursorPos++; + } + // }}} + } + elseif ($char === chr (27).chr (91).chr (68)) + { + // Cursor left + // {{{ + if ($cursorPos > $minLength) + { + echo $char; + $cursorPos--; + } + // }}} + } + elseif ($char === chr (27).chr (91).chr (70)) + { + // End key + // {{{ + $cursorPos = $minLength + strlen ($string); + // Move the cursor on the correct position + echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos); + // }}} + } + elseif ($char === chr (27).chr (91).chr (72)) + { + // Home key + // {{{ + $cursorPos = $minLength; + // Move the cursor on the correct position + echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos); + // }}} + } + elseif ($char === chr (27).chr (91).chr (51).chr (126)) + { + // Remove the char under the cursor (Delete) + // {{{ + if ($cursorPos >= strlen ($this->lineContent)) + continue; + $cursorPos; + $strArr = str_split ($string); + unset ($strArr[$cursorPos - $minLength]); + $string = implode ($strArr); + if ($this->echoMode) + { + echo "\r".str_repeat (" ", strlen ($this->lineContent))."\r"; + $this->lineContent = $prompt.$string; + echo $this->lineContent; + // Move the cursor on the correct position + echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos); + } + // }}} + } + } + else + { + // Normal char : Add it to the string + // {{{ + $strArr = str_split ($string); + $firstArr = array_slice ($strArr, 0, $cursorPos - $minLength); + $lastArr = array_slice ($strArr, $cursorPos - $minLength); + $insertArr = array ($char); + $strArr = array_merge ($firstArr, $insertArr, $lastArr); + $string = implode ($strArr); + $cursorPos++; + echo "\r"; + $this->lineContent = $prompt.$string; + echo $this->lineContent; + // Move the cursor on the correct position + echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos); + // }}} + } + } + return $string; + } + // }}} + + /** Clear the existing line + */ + public function clearLine () + // {{{ + { + echo "\r".str_repeat (" ", strlen ($this->lineContent))."\r"; + $this->lineContent = ""; + } + // }}} + + /** Error management + * @param string $message The message to throw in the exception + */ + public function consoleException ($message) + // {{{ + { + throw new \Exception ($message, 500); + } + // }}} + +}