Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 5 |
CRAP | |
0.00% |
0 / 38 |
Termios | |
0.00% |
0 / 1 |
|
0.00% |
0 / 5 |
240 | |
0.00% |
0 / 38 |
__construct | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 9 |
|||
enableRawMode | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 16 |
|||
disableRawMode | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 5 |
|||
getWindowSize | n/a |
0 / 0 |
5 | n/a |
0 / 0 |
|||||
getInstance | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 4 |
|||
ffi | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 4 |
1 | <?php declare(strict_types=1); |
2 | |
3 | namespace Aviat\Kilo; |
4 | |
5 | use FFI; |
6 | use FFI\CData; |
7 | |
8 | use Aviat\Kilo\Enum\C; |
9 | |
10 | /** |
11 | * An implicit singleton wrapper around terminal settings to simplify enabling/disabling raw mode |
12 | */ |
13 | class Termios { |
14 | private CData $originalTermios; |
15 | |
16 | private function __construct() |
17 | { |
18 | $ffi = self::ffi(); |
19 | $termios = $ffi->new('struct termios'); |
20 | if ($termios === NULL) |
21 | { |
22 | throw new TermiosException('Failed to create termios struct'); |
23 | } |
24 | |
25 | $termiosAddr = FFI::addr($termios); |
26 | $res = $ffi->tcgetattr(C::STDIN_FILENO, $termiosAddr); |
27 | |
28 | if ($res === -1) |
29 | { |
30 | throw new TermiosException('Failed to get existing terminal settings'); |
31 | } |
32 | |
33 | $this->originalTermios = $termios; |
34 | } |
35 | |
36 | /** |
37 | * Put the current terminal into raw input mode |
38 | * |
39 | * Returns TRUE if successful. Will return NULL if run more than once, as |
40 | * raw mode is pretty binary...there's no point in reapplying raw mode! |
41 | * |
42 | * @return bool|null |
43 | */ |
44 | public static function enableRawMode(): ?bool |
45 | { |
46 | static $run = FALSE; |
47 | |
48 | // Don't run this more than once! |
49 | if ($run === TRUE) |
50 | { |
51 | return NULL; |
52 | } |
53 | |
54 | $run = TRUE; |
55 | |
56 | $instance = self::getInstance(); |
57 | |
58 | // Make sure to restore normal mode on exit/die/crash |
59 | register_shutdown_function([static::class, 'disableRawMode']); |
60 | |
61 | $termios = clone $instance->originalTermios; |
62 | $termios->c_iflag &= ~(C::BRKINT | C::ICRNL | C::INPCK | C::ISTRIP | C::IXON); |
63 | $termios->c_oflag = 0; // &= ~(C::OPOST); |
64 | $termios->c_cflag |= (C::CS8); |
65 | $termios->c_lflag &= ~( C::ECHO | C::ICANON | C::IEXTEN | C::ISIG ); |
66 | $termios->c_cc[C::VMIN] = 0; |
67 | $termios->c_cc[C::VTIME] = 1; |
68 | |
69 | // Turn on raw mode |
70 | $res = self::ffi() |
71 | ->tcsetattr(C::STDIN_FILENO, C::TCSAFLUSH, FFI::addr($termios)); |
72 | |
73 | return $res !== -1; |
74 | } |
75 | |
76 | /** |
77 | * Restores terminal settings that were changed when going into raw mode. |
78 | * |
79 | * Returns TRUE if settings are applied successfully. If raw mode was not |
80 | * enabled, this will output a line of escape codes and a new line. |
81 | * |
82 | * @return bool |
83 | */ |
84 | public static function disableRawMode(): bool |
85 | { |
86 | $instance = self::getInstance(); |
87 | |
88 | // Cleanup |
89 | Terminal::clear(); |
90 | Terminal::write("\n"); // New line, please |
91 | |
92 | $res = self::ffi()->tcsetattr(C::STDIN_FILENO, C::TCSAFLUSH, FFI::addr($instance->originalTermios)); |
93 | |
94 | return $res !== -1; |
95 | } |
96 | |
97 | /** |
98 | * Get the size of the current terminal window |
99 | * |
100 | * @codeCoverageIgnore |
101 | * @return array|null |
102 | */ |
103 | public static function getWindowSize(): ?array |
104 | { |
105 | // First, try to get the answer from ioctl |
106 | $ffi = self::ffi(); |
107 | $ws = $ffi->new('struct winsize'); |
108 | if ($ws !== NULL) |
109 | { |
110 | $res = $ffi->ioctl(C::STDOUT_FILENO, C::TIOCGWINSZ, FFI::addr($ws)); |
111 | if ($res === 0 && $ws->ws_col !== 0 && $ws->ws_row !== 0) |
112 | { |
113 | return [$ws->ws_row, $ws->ws_col]; |
114 | } |
115 | } |
116 | |
117 | return null; |
118 | } |
119 | |
120 | private static function getInstance(): self |
121 | { |
122 | static $instance; |
123 | |
124 | if ($instance === NULL) |
125 | { |
126 | $instance = new self(); |
127 | } |
128 | |
129 | return $instance; |
130 | } |
131 | |
132 | /** |
133 | * A 'singleton' function to replace a global variable |
134 | * |
135 | * @return FFI |
136 | */ |
137 | private static function ffi(): FFI |
138 | { |
139 | static $ffi; |
140 | |
141 | if ($ffi === NULL) |
142 | { |
143 | $ffi = FFI::load(__DIR__ . '/ffi.h'); |
144 | } |
145 | |
146 | return $ffi; |
147 | } |
148 | } |