Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 29
Termios
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 4
56
0.00% covered (danger)
0.00%
0 / 29
 __construct
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 6
 enableRawMode
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 14
 disableRawMode
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 6
 getInstance
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 3
1<?php declare(strict_types=1);
2
3namespace Aviat\Kilo;
4
5use FFI;
6use FFI\CData;
7
8use Aviat\Kilo\Enum\C;
9
10/**
11 * An implicit singleton wrapper around terminal settings to simplify enabling/disabling raw mode
12 */
13class Termios {
14    private CData $originalTermios;
15
16    private function __construct()
17    {
18        $ffi = get_ffi();
19        $termios = $ffi->new('struct termios');
20        $res = $ffi->tcgetattr(C::STDIN_FILENO, FFI::addr($termios));
21
22        if ($res === -1)
23        {
24            throw new TermiosException('Failed to get existing terminal settings');
25        }
26
27        $this->originalTermios = $termios;
28    }
29
30    /**
31     * Put the current terminal into raw input mode
32     *
33     * Returns TRUE if successful. Will return NULL if run more than once, as
34     * raw mode is pretty binary...there's no point in reapplying raw mode!
35     *
36     * @return bool|null
37     */
38    public static function enableRawMode(): ?bool
39    {
40        static $run = FALSE;
41
42        // Don't run this more than once!
43        if ($run === TRUE)
44        {
45            return NULL;
46        }
47
48        $run = TRUE;
49
50        $instance = self::getInstance();
51
52        // Make sure to restore normal mode on exit/die/crash
53        register_shutdown_function([static::class, 'disableRawMode']);
54
55        $termios = clone $instance->originalTermios;
56        // $termios->c_iflag &= ~(C::BRKINT | C::ICRNL | C::INPCK | C::ISTRIP | C::IXON);
57        // $termios->c_oflag &= ~(C::OPOST);
58        $termios->c_iflag = 0;
59        $termios->c_oflag = 0;
60        $termios->c_cflag |= (C::CS8);
61        $termios->c_lflag &= ~( C::ECHO | C::ICANON | C::IEXTEN | C::ISIG );
62        $termios->c_cc[C::VMIN] = 0;
63        $termios->c_cc[C::VTIME] = 1;
64
65        // Turn on raw mode
66        $res = get_ffi()->tcsetattr(C::STDIN_FILENO, C::TCSAFLUSH, FFI::addr($termios));
67
68        return $res !== -1;
69    }
70
71    /**
72     * Restores terminal settings that were changed when going into raw mode.
73     *
74     * Returns TRUE if settings are applied successfully. If raw mode was not
75     * enabled, this will output a line of escape codes and a new line.
76     *
77     * @return bool
78     */
79    public static function disableRawMode(): bool
80    {
81        $instance = self::getInstance();
82
83        // Cleanup
84        write_stdout(ANSI::CLEAR_SCREEN);
85        write_stdout(ANSI::RESET_CURSOR);
86        write_stdout("\n"); // New line, please
87
88        $res = get_ffi()->tcsetattr(C::STDIN_FILENO, C::TCSAFLUSH, FFI::addr($instance->originalTermios));
89
90        return $res !== -1;
91    }
92
93    private static function getInstance(): self
94    {
95        static $instance;
96
97        if ($instance === NULL)
98        {
99            $instance = new self();
100        }
101
102        return $instance;
103    }
104}