mirror of
https://github.com/phpseclib/phpseclib.git
synced 2024-11-16 10:15:14 +00:00
ANSI: improve vt100 terminal emulation
store each coordinate's attributes independently and add support for a few more escape codes
This commit is contained in:
parent
e3d251ac57
commit
cc0420b36b
@ -117,6 +117,22 @@ class File_ANSI
|
|||||||
*/
|
*/
|
||||||
var $old_y;
|
var $old_y;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An empty attribute cell
|
||||||
|
*
|
||||||
|
* @var Object
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
var $base_attr_cell;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current attribute cell
|
||||||
|
*
|
||||||
|
* @var Object
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
var $attr_cell;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An empty attribute row
|
* An empty attribute row
|
||||||
*
|
*
|
||||||
@ -141,62 +157,6 @@ class File_ANSI
|
|||||||
*/
|
*/
|
||||||
var $attrs;
|
var $attrs;
|
||||||
|
|
||||||
/**
|
|
||||||
* The current foreground color
|
|
||||||
*
|
|
||||||
* @var String
|
|
||||||
* @access private
|
|
||||||
*/
|
|
||||||
var $foreground;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current background color
|
|
||||||
*
|
|
||||||
* @var String
|
|
||||||
* @access private
|
|
||||||
*/
|
|
||||||
var $background;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bold flag
|
|
||||||
*
|
|
||||||
* @var Boolean
|
|
||||||
* @access private
|
|
||||||
*/
|
|
||||||
var $bold;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Underline flag
|
|
||||||
*
|
|
||||||
* @var Boolean
|
|
||||||
* @access private
|
|
||||||
*/
|
|
||||||
var $underline;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Blink flag
|
|
||||||
*
|
|
||||||
* @var Boolean
|
|
||||||
* @access private
|
|
||||||
*/
|
|
||||||
var $blink;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse flag
|
|
||||||
*
|
|
||||||
* @var Boolean
|
|
||||||
* @access private
|
|
||||||
*/
|
|
||||||
var $reverse;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Color flag
|
|
||||||
*
|
|
||||||
* @var Boolean
|
|
||||||
* @access private
|
|
||||||
*/
|
|
||||||
var $color;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current ANSI code
|
* Current ANSI code
|
||||||
*
|
*
|
||||||
@ -205,6 +165,14 @@ class File_ANSI
|
|||||||
*/
|
*/
|
||||||
var $ansi;
|
var $ansi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tokenization
|
||||||
|
*
|
||||||
|
* @var Array
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
var $tokenization;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default Constructor.
|
* Default Constructor.
|
||||||
*
|
*
|
||||||
@ -213,6 +181,16 @@ class File_ANSI
|
|||||||
*/
|
*/
|
||||||
function File_ANSI()
|
function File_ANSI()
|
||||||
{
|
{
|
||||||
|
$attr_cell = new stdClass();
|
||||||
|
$attr_cell->bold = false;
|
||||||
|
$attr_cell->underline = false;
|
||||||
|
$attr_cell->blink = false;
|
||||||
|
$attr_cell->background = 'black';
|
||||||
|
$attr_cell->foreground = 'white';
|
||||||
|
$attr_cell->reverse = false;
|
||||||
|
$this->base_attr_cell = clone($attr_cell);
|
||||||
|
$this->attr_cell = clone($attr_cell);
|
||||||
|
|
||||||
$this->setHistory(200);
|
$this->setHistory(200);
|
||||||
$this->setDimensions(80, 24);
|
$this->setDimensions(80, 24);
|
||||||
}
|
}
|
||||||
@ -232,17 +210,9 @@ class File_ANSI
|
|||||||
$this->max_y = $y - 1;
|
$this->max_y = $y - 1;
|
||||||
$this->x = $this->y = 0;
|
$this->x = $this->y = 0;
|
||||||
$this->history = $this->history_attrs = array();
|
$this->history = $this->history_attrs = array();
|
||||||
$this->attr_row = array_fill(0, $this->max_x + 1, '');
|
$this->attr_row = array_fill(0, $this->max_x + 2, $this->base_attr_cell);
|
||||||
$this->screen = array_fill(0, $this->max_y + 1, '');
|
$this->screen = array_fill(0, $this->max_y + 1, '');
|
||||||
$this->attrs = array_fill(0, $this->max_y + 1, $this->attr_row);
|
$this->attrs = array_fill(0, $this->max_y + 1, $this->attr_row);
|
||||||
$this->foreground = 'white';
|
|
||||||
$this->background = 'black';
|
|
||||||
$this->bold = false;
|
|
||||||
$this->underline = false;
|
|
||||||
$this->blink = false;
|
|
||||||
$this->reverse = false;
|
|
||||||
$this->color = false;
|
|
||||||
|
|
||||||
$this->ansi = '';
|
$this->ansi = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,6 +248,7 @@ class File_ANSI
|
|||||||
*/
|
*/
|
||||||
function appendString($source)
|
function appendString($source)
|
||||||
{
|
{
|
||||||
|
$this->tokenization = array('');
|
||||||
for ($i = 0; $i < strlen($source); $i++) {
|
for ($i = 0; $i < strlen($source); $i++) {
|
||||||
if (strlen($this->ansi)) {
|
if (strlen($this->ansi)) {
|
||||||
$this->ansi.= $source[$i];
|
$this->ansi.= $source[$i];
|
||||||
@ -294,6 +265,8 @@ class File_ANSI
|
|||||||
default:
|
default:
|
||||||
continue 2;
|
continue 2;
|
||||||
}
|
}
|
||||||
|
$this->tokenization[] = $this->ansi;
|
||||||
|
$this->tokenization[] = '';
|
||||||
// http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
// http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
||||||
switch ($this->ansi) {
|
switch ($this->ansi) {
|
||||||
case "\x1B[H": // Move cursor to upper left corner
|
case "\x1B[H": // Move cursor to upper left corner
|
||||||
@ -315,7 +288,7 @@ class File_ANSI
|
|||||||
case "\x1B[K": // Clear screen from cursor right
|
case "\x1B[K": // Clear screen from cursor right
|
||||||
$this->screen[$this->y] = substr($this->screen[$this->y], 0, $this->x);
|
$this->screen[$this->y] = substr($this->screen[$this->y], 0, $this->x);
|
||||||
|
|
||||||
array_splice($this->attrs[$this->y], $this->x + 1);
|
array_splice($this->attrs[$this->y], $this->x + 1, $this->max_x - $this->x, array_fill($this->x, $this->max_x - $this->x - 1, $this->base_attr_cell));
|
||||||
break;
|
break;
|
||||||
case "\x1B[2K": // Clear entire line
|
case "\x1B[2K": // Clear entire line
|
||||||
$this->screen[$this->y] = str_repeat(' ', $this->x);
|
$this->screen[$this->y] = str_repeat(' ', $this->x);
|
||||||
@ -323,6 +296,7 @@ class File_ANSI
|
|||||||
break;
|
break;
|
||||||
case "\x1B[?1h": // set cursor key to application
|
case "\x1B[?1h": // set cursor key to application
|
||||||
case "\x1B[?25h": // show the cursor
|
case "\x1B[?25h": // show the cursor
|
||||||
|
case "\x1B(B": // set united states g0 character set
|
||||||
break;
|
break;
|
||||||
case "\x1BE": // Move to next line
|
case "\x1BE": // Move to next line
|
||||||
$this->_newLine();
|
$this->_newLine();
|
||||||
@ -330,6 +304,10 @@ class File_ANSI
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
switch (true) {
|
switch (true) {
|
||||||
|
case preg_match('#\x1B\[(\d+)B#', $this->ansi, $match): // Move cursor down n lines
|
||||||
|
$this->old_y = $this->y;
|
||||||
|
$this->y+= $match[1];
|
||||||
|
break;
|
||||||
case preg_match('#\x1B\[(\d+);(\d+)H#', $this->ansi, $match): // Move cursor to screen location v,h
|
case preg_match('#\x1B\[(\d+);(\d+)H#', $this->ansi, $match): // Move cursor to screen location v,h
|
||||||
$this->old_x = $this->x;
|
$this->old_x = $this->x;
|
||||||
$this->old_y = $this->y;
|
$this->old_y = $this->y;
|
||||||
@ -338,64 +316,42 @@ class File_ANSI
|
|||||||
break;
|
break;
|
||||||
case preg_match('#\x1B\[(\d+)C#', $this->ansi, $match): // Move cursor right n lines
|
case preg_match('#\x1B\[(\d+)C#', $this->ansi, $match): // Move cursor right n lines
|
||||||
$this->old_x = $this->x;
|
$this->old_x = $this->x;
|
||||||
$x = $match[1] - 1;
|
$this->x+= $match[1];
|
||||||
|
break;
|
||||||
|
case preg_match('#\x1B\[(\d+)D#', $this->ansi, $match): // Move cursor left n lines
|
||||||
|
$this->old_x = $this->x;
|
||||||
|
$this->x-= $match[1];
|
||||||
break;
|
break;
|
||||||
case preg_match('#\x1B\[(\d+);(\d+)r#', $this->ansi, $match): // Set top and bottom lines of a window
|
case preg_match('#\x1B\[(\d+);(\d+)r#', $this->ansi, $match): // Set top and bottom lines of a window
|
||||||
break;
|
break;
|
||||||
case preg_match('#\x1B\[(\d*(?:;\d*)*)m#', $this->ansi, $match): // character attributes
|
case preg_match('#\x1B\[(\d*(?:;\d*)*)m#', $this->ansi, $match): // character attributes
|
||||||
|
$attr_cell = &$this->attr_cell;
|
||||||
$mods = explode(';', $match[1]);
|
$mods = explode(';', $match[1]);
|
||||||
foreach ($mods as $mod) {
|
foreach ($mods as $mod) {
|
||||||
switch ($mod) {
|
switch ($mod) {
|
||||||
case 0: // Turn off character attributes
|
case 0: // Turn off character attributes
|
||||||
$this->attrs[$this->y][$this->x] = '';
|
$attr_cell = clone($this->base_attr_cell);
|
||||||
|
|
||||||
if ($this->bold) $this->attrs[$this->y][$this->x].= '</b>';
|
|
||||||
if ($this->underline) $this->attrs[$this->y][$this->x].= '</u>';
|
|
||||||
if ($this->blink) $this->attrs[$this->y][$this->x].= '</blink>';
|
|
||||||
if ($this->color) $this->attrs[$this->y][$this->x].= '</span>';
|
|
||||||
|
|
||||||
if ($this->reverse) {
|
|
||||||
$temp = $this->background;
|
|
||||||
$this->background = $this->foreground;
|
|
||||||
$this->foreground = $temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->bold = $this->underline = $this->blink = $this->color = $this->reverse = false;
|
|
||||||
break;
|
break;
|
||||||
case 1: // Turn bold mode on
|
case 1: // Turn bold mode on
|
||||||
if (!$this->bold) {
|
$attr_cell->bold = true;
|
||||||
$this->attrs[$this->y][$this->x] = '<b>';
|
|
||||||
$this->bold = true;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 4: // Turn underline mode on
|
case 4: // Turn underline mode on
|
||||||
if (!$this->underline) {
|
$attr_cell->underline = true;
|
||||||
$this->attrs[$this->y][$this->x] = '<u>';
|
|
||||||
$this->underline = true;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 5: // Turn blinking mode on
|
case 5: // Turn blinking mode on
|
||||||
if (!$this->blink) {
|
$attr_cell->blink = true;
|
||||||
$this->attrs[$this->y][$this->x] = '<blink>';
|
|
||||||
$this->blink = true;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 7: // Turn reverse video on
|
case 7: // Turn reverse video on
|
||||||
$this->reverse = !$this->reverse;
|
$attr_cell->reverse = !$attr_cell->reverse;
|
||||||
$temp = $this->background;
|
$temp = $attr_cell->background;
|
||||||
$this->background = $this->foreground;
|
$attr_cell->background = $attr_cell->foreground;
|
||||||
$this->foreground = $temp;
|
$attr_cell->foreground = $temp;
|
||||||
$this->attrs[$this->y][$this->x] = '<span style="color: ' . $this->foreground . '; background: ' . $this->background . '">';
|
|
||||||
if ($this->color) {
|
|
||||||
$this->attrs[$this->y][$this->x] = '</span>' . $this->attrs[$this->y][$this->x];
|
|
||||||
}
|
|
||||||
$this->color = true;
|
|
||||||
break;
|
break;
|
||||||
default: // set colors
|
default: // set colors
|
||||||
//$front = $this->reverse ? &$this->background : &$this->foreground;
|
//$front = $attr_cell->reverse ? &$attr_cell->background : &$attr_cell->foreground;
|
||||||
$front = &$this->{ $this->reverse ? 'background' : 'foreground' };
|
$front = &$attr_cell->{ $attr_cell->reverse ? 'background' : 'foreground' };
|
||||||
//$back = $this->reverse ? &$this->foreground : &$this->background;
|
//$back = $attr_cell->reverse ? &$attr_cell->foreground : &$attr_cell->background;
|
||||||
$back = &$this->{ $this->reverse ? 'foreground' : 'background' };
|
$back = &$attr_cell->{ $attr_cell->reverse ? 'foreground' : 'background' };
|
||||||
switch ($mod) {
|
switch ($mod) {
|
||||||
case 30: $front = 'black'; break;
|
case 30: $front = 'black'; break;
|
||||||
case 31: $front = 'red'; break;
|
case 31: $front = 'red'; break;
|
||||||
@ -416,28 +372,22 @@ class File_ANSI
|
|||||||
case 47: $back = 'white'; break;
|
case 47: $back = 'white'; break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
user_error('Unsupported attribute: ' . $mod);
|
//user_error('Unsupported attribute: ' . $mod);
|
||||||
$this->ansi = '';
|
$this->ansi = '';
|
||||||
break 2;
|
break 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
unset($temp);
|
|
||||||
$this->attrs[$this->y][$this->x] = '<span style="color: ' . $this->foreground . '; background: ' . $this->background . '">';
|
|
||||||
if ($this->color) {
|
|
||||||
$this->attrs[$this->y][$this->x] = '</span>' . $this->attrs[$this->y][$this->x];
|
|
||||||
}
|
|
||||||
$this->color = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
user_error("{$this->ansi} unsupported\r\n");
|
//user_error("{$this->ansi} is unsupported\r\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->ansi = '';
|
$this->ansi = '';
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->tokenization[count($this->tokenization) - 1].= $source[$i];
|
||||||
switch ($source[$i]) {
|
switch ($source[$i]) {
|
||||||
case "\r":
|
case "\r":
|
||||||
$this->x = 0;
|
$this->x = 0;
|
||||||
@ -445,12 +395,32 @@ class File_ANSI
|
|||||||
case "\n":
|
case "\n":
|
||||||
$this->_newLine();
|
$this->_newLine();
|
||||||
break;
|
break;
|
||||||
|
case "\x08": // backspace
|
||||||
|
if ($this->x) {
|
||||||
|
$this->x--;
|
||||||
|
$this->attrs[$this->y][$this->x] = clone($this->base_attr_cell);
|
||||||
|
$this->screen[$this->y] = substr_replace(
|
||||||
|
$this->screen[$this->y],
|
||||||
|
$source[$i],
|
||||||
|
$this->x,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "\x0F": // shift
|
case "\x0F": // shift
|
||||||
break;
|
break;
|
||||||
case "\x1B": // start ANSI escape code
|
case "\x1B": // start ANSI escape code
|
||||||
|
$this->tokenization[count($this->tokenization) - 1] = substr($this->tokenization[count($this->tokenization) - 1], 0, -1);
|
||||||
|
//if (!strlen($this->tokenization[count($this->tokenization) - 1])) {
|
||||||
|
// array_pop($this->tokenization);
|
||||||
|
//}
|
||||||
$this->ansi.= "\x1B";
|
$this->ansi.= "\x1B";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
$this->attrs[$this->y][$this->x] = clone($this->attr_cell);
|
||||||
|
if ($this->x > strlen($this->screen[$this->y])) {
|
||||||
|
$this->screen[$this->y] = str_repeat(' ', $this->x);
|
||||||
|
}
|
||||||
$this->screen[$this->y] = substr_replace(
|
$this->screen[$this->y] = substr_replace(
|
||||||
$this->screen[$this->y],
|
$this->screen[$this->y],
|
||||||
$source[$i],
|
$source[$i],
|
||||||
@ -498,6 +468,63 @@ class File_ANSI
|
|||||||
$this->y++;
|
$this->y++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current coordinate without preformating
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
function _processCoordinate($last_attr, $cur_attr, $char)
|
||||||
|
{
|
||||||
|
$output = '';
|
||||||
|
|
||||||
|
if ($last_attr != $cur_attr) {
|
||||||
|
$close = $open = '';
|
||||||
|
if ($last_attr->foreground != $cur_attr->foreground) {
|
||||||
|
if ($cur_attr->foreground != 'white') {
|
||||||
|
$open.= '<span style="color: ' . $cur_attr->foreground . '">';
|
||||||
|
}
|
||||||
|
if ($last_attr->foreground != 'white') {
|
||||||
|
$close = '</span>' . $close;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($last_attr->background != $cur_attr->background) {
|
||||||
|
if ($cur_attr->background != 'black') {
|
||||||
|
$open.= '<span style="background: ' . $cur_attr->background . '">';
|
||||||
|
}
|
||||||
|
if ($last_attr->backtground != 'black') {
|
||||||
|
$close = '</span>' . $close;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($last_attr->bold != $cur_attr->bold) {
|
||||||
|
if ($cur_attr->bold) {
|
||||||
|
$open.= '<b>';
|
||||||
|
} else {
|
||||||
|
$close = '</b>' . $close;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($last_attr->underline != $cur_attr->underline) {
|
||||||
|
if ($cur_attr->underline) {
|
||||||
|
$open.= '<u>';
|
||||||
|
} else {
|
||||||
|
$close = '</u>' . $close;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($last_attr->blink != $cur_attr->blink) {
|
||||||
|
if ($cur_attr->blink) {
|
||||||
|
$open.= '<blink>';
|
||||||
|
} else {
|
||||||
|
$close = '</blink>' . $close;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$output.= $close . $open;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output.= htmlspecialchars($char);
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current screen without preformating
|
* Returns the current screen without preformating
|
||||||
*
|
*
|
||||||
@ -507,18 +534,16 @@ class File_ANSI
|
|||||||
function _getScreen()
|
function _getScreen()
|
||||||
{
|
{
|
||||||
$output = '';
|
$output = '';
|
||||||
|
$last_attr = $this->base_attr_cell;
|
||||||
for ($i = 0; $i <= $this->max_y; $i++) {
|
for ($i = 0; $i <= $this->max_y; $i++) {
|
||||||
for ($j = 0; $j <= $this->max_x + 1; $j++) {
|
for ($j = 0; $j <= $this->max_x; $j++) {
|
||||||
if (isset($this->attrs[$i][$j])) {
|
$cur_attr = $this->attrs[$i][$j];
|
||||||
$output.= $this->attrs[$i][$j];
|
$output.= $this->_processCoordinate($last_attr, $cur_attr, isset($this->screen[$i][$j]) ? $this->screen[$i][$j] : '');
|
||||||
}
|
$last_attr = $this->attrs[$i][$j];
|
||||||
if (isset($this->screen[$i][$j])) {
|
|
||||||
$output.= htmlspecialchars($this->screen[$i][$j]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$output.= "\r\n";
|
$output.= "\r\n";
|
||||||
}
|
}
|
||||||
return rtrim($output);
|
return $output; //rtrim($output);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -529,7 +554,7 @@ class File_ANSI
|
|||||||
*/
|
*/
|
||||||
function getScreen()
|
function getScreen()
|
||||||
{
|
{
|
||||||
return '<pre style="color: white; background: black" width="' . ($this->max_x + 1) . '">' . $this->_getScreen() . '</pre>';
|
return '<pre width="' . ($this->max_x + 1) . '" style="color: white; background: black">' . $this->_getScreen() . '</pre>';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -541,19 +566,17 @@ class File_ANSI
|
|||||||
function getHistory()
|
function getHistory()
|
||||||
{
|
{
|
||||||
$scrollback = '';
|
$scrollback = '';
|
||||||
|
$last_attr = $this->base_attr_cell;
|
||||||
for ($i = 0; $i < count($this->history); $i++) {
|
for ($i = 0; $i < count($this->history); $i++) {
|
||||||
for ($j = 0; $j <= $this->max_x + 1; $j++) {
|
for ($j = 0; $j <= $this->max_x + 1; $j++) {
|
||||||
if (isset($this->history_attrs[$i][$j])) {
|
$cur_attr = $this->history_attrs[$i][$j];
|
||||||
$scrollback.= $this->history_attrs[$i][$j];
|
$scrollback.= $this->_processCoordinate($last_attr, $cur_attr, isset($this->history[$i][$j]) ? $this->history[$i][$j] : '');
|
||||||
}
|
$last_attr = $this->history_attrs[$i][$j];
|
||||||
if (isset($this->history[$i][$j])) {
|
|
||||||
$scrollback.= htmlspecialchars($this->history[$i][$j]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$scrollback.= "\r\n";
|
$scrollback.= "\r\n";
|
||||||
}
|
}
|
||||||
$scrollback.= $this->_getScreen();
|
$scrollback.= $this->_getScreen();
|
||||||
|
|
||||||
return '<pre style="color: white; background: black" width="' . ($this->max_x + 1) . '">' . $scrollback . '</pre>';
|
return '<pre width="' . ($this->max_x + 1) . '" style="color: white; background: black">' . $scrollback . '</span></pre>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user