From b8cb08c8a847b5009718f4b9e648debf1652cde2 Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Tue, 10 Oct 2023 14:20:19 -0400 Subject: [PATCH] Attempt to detect musl libc --- src/Editor.php | 3 +- src/Kilo.php | 4 +- src/Terminal/ANSI.php | 13 ++- src/Terminal/C.php | 125 +-------------------------- src/{ => Terminal}/Enum/Color.php | 2 +- src/{ => Terminal}/Enum/Color256.php | 2 +- src/Terminal/Enum/LibType.php | 11 +++ src/Terminal/Termios.php | 45 +++++++++- src/Terminal/ffi_musl.h | 70 +++++++++++++++ tests/ANSITest.php | 2 +- 10 files changed, 137 insertions(+), 140 deletions(-) rename src/{ => Terminal}/Enum/Color.php (96%) rename src/{ => Terminal}/Enum/Color256.php (93%) create mode 100644 src/Terminal/Enum/LibType.php create mode 100644 src/Terminal/ffi_musl.h diff --git a/src/Editor.php b/src/Editor.php index 84e1f7f..89fb750 100644 --- a/src/Editor.php +++ b/src/Editor.php @@ -2,8 +2,9 @@ namespace Aviat\Kilo; -use Aviat\Kilo\Enum\{Color, Highlight, KeyType, RawKeyCode, SearchDirection}; +use Aviat\Kilo\Enum\{Highlight, KeyType, RawKeyCode, SearchDirection}; use Aviat\Kilo\Terminal\ANSI; +use Aviat\Kilo\Terminal\Enum\Color; use Aviat\Kilo\Terminal\Terminal; use Aviat\Kilo\Type\{Point, StatusMessage}; use Aviat\Kilo\Type\TerminalSize; diff --git a/src/Kilo.php b/src/Kilo.php index c226471..9c93a65 100644 --- a/src/Kilo.php +++ b/src/Kilo.php @@ -2,10 +2,10 @@ namespace Aviat\Kilo; -use Aviat\Kilo\Enum\Color; -use Aviat\Kilo\Enum\Color256; use Aviat\Kilo\Enum\Highlight; use Aviat\Kilo\Enum\RawKeyCode; +use Aviat\Kilo\Terminal\Enum\Color; +use Aviat\Kilo\Terminal\Enum\Color256; // ----------------------------------------------------------------------------- // ! App Constants diff --git a/src/Terminal/ANSI.php b/src/Terminal/ANSI.php index 0163bf0..c26eccc 100644 --- a/src/Terminal/ANSI.php +++ b/src/Terminal/ANSI.php @@ -2,9 +2,8 @@ namespace Aviat\Kilo\Terminal; -use Aviat\Kilo\Enum; -use Aviat\Kilo\Enum\Color; -use Aviat\Kilo\Enum\Color256; +use Aviat\Kilo\Terminal\Enum\Color; +use Aviat\Kilo\Terminal\Enum\Color256; /** * ANSI @@ -66,9 +65,9 @@ class ANSI { * @param Color $ground * @return string */ - public static function color(Enum\Color | Enum\Color256 | int $color, Enum\Color $ground = Enum\Color::Fg): string + public static function color(Color|Color256| int $color, Color $ground = Color::Fg): string { - if ( ! $color instanceof Enum\Color) + if ( ! $color instanceof Color) { return self::color256($color, $ground); } @@ -83,9 +82,9 @@ class ANSI { * @param Color $ground * @return string */ - public static function color256(Enum\Color256 | int $color, Enum\Color $ground = Enum\Color::Fg): string + public static function color256(Color256| int $color, Color $ground = Color::Fg): string { - if ($color instanceof Enum\Color256) + if ($color instanceof Color256) { $color = $color->value; } diff --git a/src/Terminal/C.php b/src/Terminal/C.php index 8c37afa..9acd774 100644 --- a/src/Terminal/C.php +++ b/src/Terminal/C.php @@ -17,132 +17,9 @@ class C { public const STDIN_FILENO = 0; public const STDOUT_FILENO = 1; public const STDERR_FILENO = 2; + public const TCSANOW = 0; public const TCSAFLUSH = 2; - // ------------------------------------------------------------------------ - // ! Termios flags and constants - // ------------------------------------------------------------------------ - - /* Input modes */ - public const IGNBRK = (1 << 0); /* Ignore break condition. */ - public const BRKINT = (1 << 1); /* Signal interrupt on break. */ - public const IGNPAR = (1 << 2); /* Ignore characters with parity errors. */ - public const PARMRK = (1 << 3); /* Mark parity and framing errors. */ - public const INPCK = (1 << 4); /* Enable input parity check. */ - public const ISTRIP = (1 << 5); /* Strip 8th bit off characters. */ - public const INLCR = (1 << 6); /* Map NL to CR on input. */ - public const IGNCR = (1 << 7); /* Ignore CR. */ - public const ICRNL = (1 << 8); /* Map CR to NL on input. */ - public const IXON = (1 << 9); /* Enable start/stop output control. */ - public const IXOFF = (1 << 10); /* Enable start/stop input control. */ - public const IXANY = (1 << 11); /* Any character will restart after stop. */ - public const IMAXBEL = (1 << 13); /* Ring bell when input queue is full. */ - public const IUCLC = (1 << 14); /* Translate upper case input to lower case. */ - - /* Output modes */ - public const OPOST = (1 << 0); /* Perform output processing. */ - public const ONLCR = (1 << 1); /* Map NL to CR-NL on output. */ - public const OXTABS = (1 << 2); /* Expand tabs to spaces. */ - public const ONOEOT = (1 << 3); /* Discard EOT (^D) on output. */ - public const OCRNL = (1 << 4); /* Map CR to NL. */ - public const ONOCR = (1 << 5); /* Discard CR's when on column 0. */ - public const ONLRET = (1 << 6); /* Move to column 0 on NL. */ - public const NLDLY = (3 << 8); /* NL delay. */ - public const NL0 = (0 << 8); /* NL type 0. */ - public const NL1 = (1 << 8); /* NL type 1. */ - public const TABDLY = (3 << 10 | 1 << 2); /* TAB delay. */ - public const TAB0 = (0 << 10); /* TAB delay type 0. */ - public const TAB1 = (1 << 10); /* TAB delay type 1. */ - public const TAB2 = (2 << 10); /* TAB delay type 2. */ - public const TAB3 = (1 << 2); /* Expand tabs to spaces. */ - public const CRDLY = (3 << 12); /* CR delay. */ - public const CR0 = (0 << 12); /* CR delay type 0. */ - public const CR1 = (1 << 12); /* CR delay type 1. */ - public const CR2 = (2 << 12); /* CR delay type 2. */ - public const CR3 = (3 << 12); /* CR delay type 3. */ - public const FFDLY = (1 << 14); /* FF delay. */ - public const FF0 = (0 << 14); /* FF delay type 0. */ - public const FF1 = (1 << 14); /* FF delay type 1. */ - public const BSDLY = (1 << 15); /* BS delay. */ - public const BS0 = (0 << 15); /* BS delay type 0. */ - public const BS1 = (1 << 15); /* BS delay type 1. */ - public const VTDLY = (1 << 16); /* VT delay. */ - public const VT0 = (0 << 16); /* VT delay type 0. */ - public const VT1 = (1 << 16); /* VT delay type 1. */ - public const OLCUC = (1 << 17); /* Translate lower case output to upper case */ - public const OFILL = (1 << 18); /* Send fill characters for delays. */ - public const OFDEL = (1 << 19); /* Fill is DEL. */ - - /* Control modes */ - public const CIGNORE = (1 << 0); /* Ignore these control flags. */ - public const CS5 = 0; /* 5 bits per byte. */ - public const CS6 = (1 << 8); /* 6 bits per byte. */ - public const CS7 = (1 << 9); /* 7 bits per byte. */ - public const CS8 = (C::CS6|C::CS7); /* 8 bits per byte. */ - public const CSIZE = (C::CS5|C::CS6|C::CS7|C::CS8); /* Number of bits per byte (mask). */ - public const CSTOPB = (1 << 10); /* Two stop bits instead of one. */ - public const CREAD = (1 << 11); /* Enable receiver. */ - public const PARENB = (1 << 12); /* Parity enable. */ - public const PARODD = (1 << 13); /* Odd parity instead of even. */ - public const HUPCL = (1 << 14); /* Hang up on last close. */ - public const CLOCAL = (1 << 15); /* Ignore modem status lines. */ - public const CRTSCTS = (1 << 16); /* RTS/CTS flow control. */ - public const CRTS_IFLOW = C::CRTSCTS; /* Compatibility. */ - public const CCTS_OFLOW = C::CRTSCTS; /* Compatibility. */ - public const CDTRCTS = (1 << 17); /* DTR/CTS flow control. */ - public const MDMBUF = (1 << 20); /* DTR/DCD flow control. */ - public const CHWFLOW = (C::MDMBUF|C::CRTSCTS|C::CDTRCTS); /* All types of flow control. */ - - /* Local modes */ - public const ECHOKE = (1 << 0); /* Visual erase for KILL. */ - public const _ECHOE = (1 << 1); /* Visual erase for ERASE. */ - public const ECHOE = C::_ECHOE; - public const _ECHOK = (1 << 2); /* Echo NL after KILL. */ - public const ECHOK = C::_ECHOK; - public const _ECHO = (1 << 3); /* Enable echo. */ - public const ECHO = C::_ECHO; - public const _ECHONL = (1 << 4); /* Echo NL even if ECHO is off. */ - public const ECHONL = C::_ECHONL; - public const ECHOPRT = (1 << 5); /* Hardcopy visual erase. */ - public const ECHOCTL = (1 << 6); /* Echo control characters as ^X. */ - public const _ISIG = (1 << 7); /* Enable signals. */ - public const ISIG = C::_ISIG; - public const _ICANON = (1 << 8); /* Do erase and kill processing. */ - public const ICANON = C::_ICANON; - public const ALTWERASE = (1 << 9); /* Alternate WERASE algorithm. */ - public const _IEXTEN = (1 << 10); /* Enable DISCARD and LNEXT. */ - public const IEXTEN = C::_IEXTEN; - public const EXTPROC = (1 << 11); /* External processing. */ - public const _TOSTOP = (1 << 22); /* Send SIGTTOU for background output. */ - public const TOSTOP = C::_TOSTOP; - public const FLUSHO = (1 << 23); /* Output being flushed (state). */ - public const XCASE = (1 << 24); /* Canonical upper/lower case. */ - public const NOKERNINFO = (1 << 25); /* Disable VSTATUS. */ - public const PENDIN = (1 << 29); /* Retype pending input (state). */ - public const _NOFLSH = (1 << 31); /* Disable flush after interrupt. */ - public const NOFLSH = C::_NOFLSH; - - /* Control characters */ - public const VEOF = 0; /* End-of-file character [ICANON]. */ - public const VEOL = 1; /* End-of-line character [ICANON]. */ - public const VEOL2 = 2; /* Second EOL character [ICANON]. */ - public const VERASE = 3; /* Erase character [ICANON]. */ - public const VWERASE = 4; /* Word-erase character [ICANON]. */ - public const VKILL = 5; /* Kill-line character [ICANON]. */ - public const VREPRINT = 6; /* Reprint-line character [ICANON]. */ - public const VINTR = 8; /* Interrupt character [ISIG]. */ - public const VQUIT = 9; /* Quit character [ISIG]. */ - public const VSUSP = 10; /* Suspend character [ISIG]. */ - public const VDSUSP = 11; /* Delayed suspend character [ISIG]. */ - public const VSTART = 12; /* Start (X-ON) character [IXON, IXOFF]. */ - public const VSTOP = 13; /* Stop (X-OFF) character [IXON, IXOFF]. */ - public const VLNEXT = 14; /* Literal-next character [IEXTEN]. */ - public const VDISCARD = 15; /* Discard character [IEXTEN]. */ - public const VMIN = 16; /* Minimum number of bytes read at once [!ICANON]. */ - public const VTIME = 17; /* Time-out value (tenths of a second) [!ICANON]. */ - public const VSTATUS = 18; /* Status character [ICANON]. */ - public const NCCS = 20; /* Value duplicated in . */ - // ------------------------------------------------------------------------ // ! IOCTL constants // ------------------------------------------------------------------------ diff --git a/src/Enum/Color.php b/src/Terminal/Enum/Color.php similarity index 96% rename from src/Enum/Color.php rename to src/Terminal/Enum/Color.php index 922c617..8a4597e 100644 --- a/src/Enum/Color.php +++ b/src/Terminal/Enum/Color.php @@ -1,6 +1,6 @@ cfmakeraw(FFI::addr($termios)); $res = self::ffi() - ->tcsetattr(C::STDIN_FILENO, C::TCSAFLUSH, FFI::addr($termios)); + ->tcsetattr(C::STDIN_FILENO, C::TCSANOW, FFI::addr($termios)); return $res !== -1; } @@ -94,12 +95,22 @@ class Termios { */ public static function getWindowSize(): ?array { + $res = NULL; + // First, try to get the answer from ioctl $ffi = self::ffi(); $ws = $ffi->new('struct winsize'); if ($ws !== NULL) { - $res = $ffi->ioctl(C::STDOUT_FILENO, C::TIOCGWINSZ, FFI::addr($ws)); + if (self::getLibType() === LibType::MUSL) + { + $res = $ffi->tcgetwinsize(C::STDOUT_FILENO, FFI::addr($ws)); + } + else + { + $res = $ffi->ioctl(C::STDOUT_FILENO, C::TIOCGWINSZ, FFI::addr($ws)); + } + if ($res === 0 && $ws->ws_col !== 0 && $ws->ws_row !== 0) { return [$ws->ws_row, $ws->ws_col]; @@ -109,6 +120,27 @@ class Termios { return null; } + private static function getLibType(): LibType + { + static $type; + + if ($type === NULL) + { + if (file_exists("/usr/lib/libc.so")) + { + $rawLibInfo = (string)shell_exec("/usr/lib/libc.so"); + if (str_contains(strtolower($rawLibInfo), "musl")) + { + $type = LibType::MUSL; + } + } + + $type = LibType::GLIBC; + } + + return $type; + } + private static function getInstance(): self { static $instance; @@ -132,7 +164,14 @@ class Termios { if ($ffi === NULL) { - $ffi = FFI::load(__DIR__ . '/ffi.h'); + if (self::getLibType() === LibType::MUSL) + { + $ffi = FFI::load(__DIR__ . '/ffi_musl.h'); + } + else + { + $ffi = FFI::load(__DIR__ . '/ffi.h'); + } } return $ffi; diff --git a/src/Terminal/ffi_musl.h b/src/Terminal/ffi_musl.h new file mode 100644 index 0000000..92881ad --- /dev/null +++ b/src/Terminal/ffi_musl.h @@ -0,0 +1,70 @@ +/** + * Interfaces for PHP FFI + * + * Most of the structure code is cribbed from GLib + * + * Defines are not (generally) recognized by the FFI integration + */ + +// PHP 'constants' for FFI integration +// These seem to be the only define statements supported by the FFI integration +#define FFI_SCOPE "terminal" +#define FFI_LIB "libc.so" + +// Nonsense for a test with a single quote +// Ignored by PHP due to the octothorpe (#) +#if 0 +# char* x = "String with \" escape char"; +# char y = 'q'; +#endif + +// ----------------------------------------------------------------------------- +//! +// ----------------------------------------------------------------------------- + +/* Type of terminal control flag masks. */ +typedef unsigned long int tcflag_t; + +/* Type of control characters. */ +typedef unsigned char cc_t; + +/* Type of baud rate specifiers. */ +typedef long int speed_t; + +/* Terminal control structure. */ +struct termios +{ + /* Input modes. */ + tcflag_t c_iflag; + + /* Output modes. */ + tcflag_t c_oflag; + + /* Control modes. */ + tcflag_t c_cflag; + + /* Local modes. */ + tcflag_t c_lflag; + + /* Control characters. */ + cc_t c_cc[20]; + + /* Input and output baud rates. */ + speed_t __ispeed, __ospeed; +}; + +int tcgetattr (int fd, struct termios *termios_p); +int tcsetattr (int fd, int optional_actions, const struct termios *termios_p); +void cfmakeraw(struct termios *); +int tcgetwinsize(int fd, struct winsize *); + +// ----------------------------------------------------------------------------- +//! +// ----------------------------------------------------------------------------- +struct winsize { + unsigned short ws_row; + unsigned short ws_col; + unsigned short ws_xpixel; + unsigned short ws_ypixel; +}; +int ioctl (int, int, ...); diff --git a/tests/ANSITest.php b/tests/ANSITest.php index 73780ed..35d48e5 100644 --- a/tests/ANSITest.php +++ b/tests/ANSITest.php @@ -2,8 +2,8 @@ namespace Aviat\Kilo\Tests; -use Aviat\Kilo\Enum\Color; use Aviat\Kilo\Terminal\ANSI; +use Aviat\Kilo\Terminal\Enum\Color; use PHPUnit\Framework\TestCase; class ANSITest extends TestCase {