Implement basic canonical mode for Windows

The `cat` command now works properly, when run by itself on the bash
command prompt. It's working beautifully so far, and is only missing
a few keystrokes for clearing words and lines. Definitely works more
well than the one that ships with WIN32 :-)
This commit is contained in:
Justine Tunney 2023-10-03 22:34:45 -07:00
parent 4825737509
commit f26a280cda
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
36 changed files with 320 additions and 231 deletions

View file

@ -27,7 +27,7 @@ void __printfds(void);
uint32_t sys_getuid_nt(void);
int __pause_thread(uint32_t);
int IsWindowsExecutable(int64_t);
int CountConsoleInputBytes(int64_t);
int CountConsoleInputBytes(struct Fd *);
int FlushConsoleInputBytes(int64_t);
forceinline int64_t __getfdhandleactual(int fd) {

View file

@ -114,7 +114,7 @@ static int ioctl_fionread(int fd, uint32_t *arg) {
return __winerr();
}
} else if (GetConsoleMode(handle, &cm)) {
int bytes = CountConsoleInputBytes(handle);
int bytes = CountConsoleInputBytes(g_fds.p + fd);
return MAX(0, bytes);
} else {
return eopnotsupp();

View file

@ -155,7 +155,7 @@ textwindows int sys_poll_nt(struct pollfd *fds, uint64_t nfds, uint32_t *ms,
pipefds[i].revents |= POLLERR;
}
} else if (GetConsoleMode(pipefds[i].handle, &cm)) {
if (CountConsoleInputBytes(pipefds[i].handle)) {
if (CountConsoleInputBytes(g_fds.p + fds[pipeindices[i]].fd)) {
pipefds[i].revents |= POLLIN;
}
} else {

View file

@ -26,12 +26,14 @@
#include "libc/fmt/itoa.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/dll.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/nomultics.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/macros.internal.h"
#include "libc/mem/mem.h"
#include "libc/nt/console.h"
#include "libc/nt/enum/consolemodeflags.h"
#include "libc/nt/enum/filetype.h"
#include "libc/nt/enum/vk.h"
#include "libc/nt/enum/wait.h"
@ -91,19 +93,20 @@ static const struct {
#define KEYSTROKE_CONTAINER(e) DLL_CONTAINER(struct Keystroke, elem, e)
struct Keystroke {
char buf[32];
unsigned buflen;
char buf[23];
unsigned char buflen;
struct Dll elem;
};
struct Keystrokes {
struct Dll *list;
struct Dll *line;
struct Dll *free;
bool end_of_file;
unsigned char pc;
uint16_t utf16hs;
unsigned allocated;
pthread_mutex_t lock;
struct Keystroke pool[32];
struct Keystroke pool[8];
};
static struct Keystrokes __keystroke;
@ -124,6 +127,11 @@ static textwindows void UnblockSignals(uint64_t mask) {
atomic_store_explicit(&__get_tls()->tib_sigmask, mask, memory_order_release);
}
static textwindows int RaiseSignal(int sig) {
__get_tls()->tib_sigpending |= 1ull << (sig - 1);
return 0;
}
static textwindows int GetVirtualKey(uint16_t vk, bool shift, bool ctrl) {
for (int i = 0; i < ARRAYLEN(kVirtualKey); ++i) {
if (kVirtualKey[i].vk == vk) {
@ -152,7 +160,7 @@ static textwindows int ProcessKeyEvent(const struct NtInputRecord *r, char *p) {
return 0;
}
// process virtual keys
// process arrow keys, function keys, etc.
int n = 0;
if (!c) {
int w;
@ -184,7 +192,7 @@ static textwindows int ProcessKeyEvent(const struct NtInputRecord *r, char *p) {
// enter sends \r in a raw terminals
// make it a multics newline instead
if (c == '\r' && !(__ttymagic & kFdTtyNoCr2Nl)) {
if (c == '\r' && !(__ttyconf.magic & kTtyNoCr2Nl)) {
c = '\n';
}
@ -200,25 +208,24 @@ static textwindows int ProcessKeyEvent(const struct NtInputRecord *r, char *p) {
}
// handle ctrl-c and ctrl-\, which tcsetattr() is able to remap
if (!(__ttymagic & kFdTtyNoIsigs)) {
if (c == __vintr && __vintr != _POSIX_VDISABLE) {
STRACE("encountered CTRL(%#c) c_cc[VINTR] will raise SIGINT", CTRL(c));
__get_tls()->tib_sigpending |= 1ull << (SIGINT - 1);
return 0;
} else if (c == __vquit && __vquit != _POSIX_VDISABLE) {
STRACE("encountered CTRL(%#c) c_cc[VQUITR] will raise SIGQUIT", CTRL(c));
__get_tls()->tib_sigpending |= 1ull << (SIGQUIT - 1);
return 0;
if (c && !(__ttyconf.magic & kTtyNoIsigs)) {
if (c == __ttyconf.vintr) {
return RaiseSignal(SIGINT);
} else if (c == __ttyconf.vquit) {
return RaiseSignal(SIGQUIT);
}
}
// handle ctrl-d the end of file keystroke
if (!(__ttymagic & kFdTtyUncanon)) {
if (c == __veof && __veof != _POSIX_VDISABLE) {
STRACE("encountered CTRL(%#c) c_cc[VEOF] closing console input", CTRL(c));
// handle ctrl-d which generates end-of-file, unless pending line data
// is present, in which case we flush that without the newline instead
if (c && c == __ttyconf.veof && !(__ttyconf.magic & kTtyUncanon)) {
if (dll_is_empty(__keystroke.line)) {
__keystroke.end_of_file = true;
return 0;
} else {
dll_make_last(&__keystroke.list, __keystroke.line);
__keystroke.line = 0;
}
return 0;
}
// insert esc prefix when alt is held
@ -241,9 +248,9 @@ static textwindows int ProcessKeyEvent(const struct NtInputRecord *r, char *p) {
// See o//examples/ttyinfo.com and o//tool/viz/life.com
static textwindows int ProcessMouseEvent(const struct NtInputRecord *r,
char *b) {
int e = 0;
char *p = b;
uint32_t currentbs = __mousebuttons;
unsigned char e = 0;
uint32_t currentbs = __ttyconf.mousebs;
uint32_t ev = r->Event.MouseEvent.dwEventFlags;
uint32_t bs = r->Event.MouseEvent.dwButtonState;
ev &= kNtMouseMoved | kNtMouseWheeled;
@ -254,7 +261,7 @@ static textwindows int ProcessMouseEvent(const struct NtInputRecord *r,
(kNtShiftPressed | kNtLeftCtrlPressed | kNtRightCtrlPressed |
kNtLeftAltPressed | kNtRightAltPressed))) {
bool isup = ((int)r->Event.MouseEvent.dwButtonState >> 16) > 0;
if (__ttymagic & kFdTtyXtMouse) {
if (__ttyconf.magic & kTtyXtMouse) {
e = isup ? 80 : 81;
goto OutputXtermMouseEvent;
} else {
@ -269,7 +276,7 @@ static textwindows int ProcessMouseEvent(const struct NtInputRecord *r,
}
}
}
} else if ((bs || currentbs) && (__ttymagic & kFdTtyXtMouse)) {
} else if ((bs || currentbs) && (__ttyconf.magic & kTtyXtMouse)) {
if (bs && (ev & kNtMouseMoved) && currentbs) {
e |= 32; // dragging
}
@ -282,30 +289,28 @@ static textwindows int ProcessMouseEvent(const struct NtInputRecord *r,
*p++ = '<';
p = FormatInt32(p, e);
*p++ = ';';
p = FormatInt32(p, r->Event.MouseEvent.dwMousePosition.X + 1);
p = FormatInt32(p, (r->Event.MouseEvent.dwMousePosition.X + 1) & 0x7fff);
*p++ = ';';
p = FormatInt32(p, r->Event.MouseEvent.dwMousePosition.Y + 1);
p = FormatInt32(p, (r->Event.MouseEvent.dwMousePosition.Y + 1) & 0x7fff);
if (!bs && currentbs) {
*p++ = 'm'; // up
} else {
*p++ = 'M'; // down
}
__mousebuttons = bs;
__ttyconf.mousebs = bs;
}
return p - b;
}
static textwindows int ConvertConsoleInputToAnsi(const struct NtInputRecord *r,
char p[hasatleast 32]) {
char p[hasatleast 23]) {
switch (r->EventType) {
case kNtKeyEvent:
return ProcessKeyEvent(r, p);
case kNtMouseEvent:
return ProcessMouseEvent(r, p);
case kNtWindowBufferSizeEvent:
STRACE("detected console resize will raise SIGWINCH");
__get_tls()->tib_sigpending |= 1ull << (SIGWINCH - 1);
return 0;
return RaiseSignal(SIGWINCH);
default:
return 0;
}
@ -315,8 +320,8 @@ static textwindows struct Keystroke *NewKeystroke(void) {
struct Dll *e;
struct Keystroke *k = 0;
int i, n = ARRAYLEN(__keystroke.pool);
if (atomic_load_explicit(&__keystroke.allocated, memory_order_acquire) < n &&
(i = atomic_fetch_add(&__keystroke.allocated, 1)) < n) {
if (atomic_load_explicit(&__keystroke.pc, memory_order_acquire) < n &&
(i = atomic_fetch_add(&__keystroke.pc, 1)) < n) {
k = __keystroke.pool + i;
} else {
if ((e = dll_first(__keystroke.free))) {
@ -339,41 +344,123 @@ static textwindows struct Keystroke *NewKeystroke(void) {
return k;
}
static textwindows void IngestConsoleInputRecord(struct NtInputRecord *r) {
int len;
struct Keystroke *k;
char buf[sizeof(k->buf)];
if ((len = ConvertConsoleInputToAnsi(r, buf))) {
if ((k = NewKeystroke())) {
memcpy(k->buf, buf, sizeof(k->buf));
k->buflen = len;
dll_make_last(&__keystroke.list, &k->elem);
static textwindows void WriteTty(struct Fd *f, const char *p, size_t n) {
int64_t hOutput;
uint32_t dwConsoleMode;
if (f->kind == kFdConsole) {
hOutput = f->extra;
} else if (g_fds.p[1].kind == kFdFile &&
GetConsoleMode(g_fds.p[1].handle, &dwConsoleMode)) {
hOutput = g_fds.p[1].handle;
} else if (g_fds.p[2].kind == kFdFile &&
GetConsoleMode(g_fds.p[2].handle, &dwConsoleMode)) {
hOutput = g_fds.p[2].handle;
} else {
hOutput = g_fds.p[1].handle;
}
WriteFile(hOutput, p, n, 0, 0);
}
static textwindows bool IsCtl(int c) {
return isascii(c) && iscntrl(c) && c != '\n' && c != '\t';
}
static textwindows void WriteTtyCtl(struct Fd *f, const char *p, size_t n) {
size_t i;
for (i = 0; i < n; ++i) {
if (IsCtl(p[i])) {
char ctl[2];
ctl[0] = '^';
ctl[1] = p[i] ^ 0100;
WriteTty(f, ctl, 2);
} else {
STRACE("ran out of memory to hold keystroke %#.*s", len, buf);
WriteTty(f, p + i, 1);
}
}
}
static textwindows void IngestConsoleInput(int64_t handle) {
static textwindows void EchoTty(struct Fd *f, const char *p, size_t n) {
if (__ttyconf.magic & kTtyEchoRaw) {
WriteTty(f, p, n);
} else {
WriteTtyCtl(f, p, n);
}
}
static textwindows void IngestConsoleInputRecord(struct Fd *f,
struct NtInputRecord *r) {
// convert win32 console event into ansi
int len;
char buf[23];
if (!(len = ConvertConsoleInputToAnsi(r, buf))) {
return;
}
// handle backspace in canonical mode
if (len == 1 && buf[0] && //
(buf[0] & 255) == __ttyconf.verase && //
!(__ttyconf.magic & kTtyUncanon)) {
struct Dll *e;
if ((e = dll_last(__keystroke.line))) {
struct Keystroke *k = KEYSTROKE_CONTAINER(e);
dll_remove(&__keystroke.line, e);
dll_make_first(&__keystroke.free, e);
for (int i = k->buflen; i--;) {
if ((k->buf[i] & 0300) == 0200) continue;
WriteTty(f, "\b \b", 3);
if (!(__ttyconf.magic & kTtyEchoRaw) && IsCtl(k->buf[i])) {
WriteTty(f, "\b \b", 3);
}
}
}
return;
}
// allocate an object to hold this keystroke
struct Keystroke *k;
if (!(k = NewKeystroke())) {
STRACE("ran out of memory to hold keystroke %#.*s", len, buf);
return;
}
memcpy(k->buf, buf, sizeof(k->buf));
k->buflen = len;
// echo input if it was successfully recorded
// assuming the win32 console isn't doing it already
if (!(__ttyconf.magic & kTtySilence)) {
EchoTty(f, buf, len);
}
// save keystroke to appropriate list
if (__ttyconf.magic & kTtyUncanon) {
dll_make_last(&__keystroke.list, &k->elem);
} else {
dll_make_last(&__keystroke.line, &k->elem);
// handle end-of-line in canonical mode
if (len == 1 && buf[0] &&
((buf[0] & 255) == '\n' || //
(buf[0] & 255) == __ttyconf.veol || //
(buf[0] & 255) == __ttyconf.veol2)) {
dll_make_last(&__keystroke.list, __keystroke.line);
__keystroke.line = 0;
return;
}
}
}
static textwindows void IngestConsoleInput(struct Fd *f) {
uint32_t i, n;
struct NtInputRecord records[16];
if (!__keystroke.end_of_file) {
do {
if (GetNumberOfConsoleInputEvents(handle, &n)) {
if (n) {
n = MIN(ARRAYLEN(records), n);
if (ReadConsoleInput(handle, records, n, &n)) {
for (i = 0; i < n && !__keystroke.end_of_file; ++i) {
IngestConsoleInputRecord(records + i);
}
} else {
STRACE("ReadConsoleInput failed w/ %d", GetLastError());
__keystroke.end_of_file = true;
break;
}
if (ReadConsoleInput(f->handle, records, ARRAYLEN(records), &n)) {
for (i = 0; i < n && !__keystroke.end_of_file; ++i) {
IngestConsoleInputRecord(f, records + i);
}
} else {
STRACE("GetNumberOfConsoleInputRecords failed w/ %d", GetLastError());
STRACE("ReadConsoleInput failed w/ %d", GetLastError());
__keystroke.end_of_file = true;
break;
}
@ -398,12 +485,12 @@ textwindows int FlushConsoleInputBytes(int64_t handle) {
return rc;
}
textwindows int CountConsoleInputBytes(int64_t handle) {
textwindows int CountConsoleInputBytes(struct Fd *f) {
int count = 0;
struct Dll *e;
uint64_t m = BlockSignals();
LockKeystrokes();
IngestConsoleInput(handle);
IngestConsoleInput(f);
for (e = dll_first(__keystroke.list); e; e = dll_next(__keystroke.list, e)) {
count += KEYSTROKE_CONTAINER(e)->buflen;
}
@ -415,52 +502,45 @@ textwindows int CountConsoleInputBytes(int64_t handle) {
return count;
}
static textwindows bool DigestConsoleInput(void *data, size_t size, int *rc) {
static textwindows bool DigestConsoleInput(char *data, size_t size, int *rc) {
// handle eof once available input is consumed
if (dll_is_empty(__keystroke.list) && __keystroke.end_of_file) {
*rc = 0;
return true;
}
// copy keystroke(s) into user buffer
int toto = 0;
struct Dll *e;
if ((e = dll_first(__keystroke.list))) {
while ((e = dll_first(__keystroke.list))) {
struct Keystroke *k = KEYSTROKE_CONTAINER(e);
uint32_t got = MIN(size, k->buflen);
uint32_t remain = k->buflen - got;
if (got) memcpy(data, k->buf, got);
if (remain) memmove(k->buf, k->buf + got, remain);
if (!remain) {
if (got) {
memcpy(data, k->buf, got);
data += got;
size -= got;
toto += got;
}
if (remain) {
memmove(k->buf, k->buf + got, remain);
} else {
dll_remove(&__keystroke.list, e);
dll_make_first(&__keystroke.free, e);
}
k->buflen = remain;
if (got) {
*rc = got;
return true;
if ((__ttyconf.magic & kTtyUncanon) && toto >= __ttyconf.vmin) {
break;
}
} else if (__keystroke.end_of_file) {
*rc = 0;
return true;
}
return false;
}
// Manual CMD.EXE echoing for when !ICANON && ECHO is the case.
static textwindows void EchoTerminalInput(struct Fd *f, char *p, size_t n) {
int64_t hOutput;
if (f->kind == kFdConsole) {
hOutput = f->extra;
// return result
if (toto) {
*rc = toto;
return true;
} else {
hOutput = g_fds.p[1].handle;
}
if (__ttymagic & kFdTtyEchoRaw) {
WriteFile(hOutput, p, n, 0, 0);
} else {
size_t i;
for (i = 0; i < n; ++i) {
if (isascii(p[i]) && iscntrl(p[i]) && p[i] != '\n' && p[i] != '\t') {
char ctl[2];
ctl[0] = '^';
ctl[1] = p[i] ^ 0100;
WriteFile(hOutput, ctl, 2, 0, 0);
} else {
WriteFile(hOutput, p + i, 1, 0, 0);
}
}
return false;
}
}
@ -472,26 +552,23 @@ static textwindows ssize_t ReadFromWindowsConsole(struct Fd *f, void *data,
uint64_t m;
m = BlockSignals();
LockKeystrokes();
IngestConsoleInput(f->handle);
IngestConsoleInput(f);
done = DigestConsoleInput(data, size, &rc);
UnlockKeystrokes();
UnblockSignals(m);
if (done) break;
if (f->flags & O_NONBLOCK) return eagain();
uint32_t ms = __SIG_POLL_INTERVAL_MS;
if (__ttymagic & kFdTtyNoBlock) {
if (!__vtime) {
if (!__ttyconf.vmin) {
if (!__ttyconf.vtime) {
return 0;
} else {
ms = __vtime * 100;
ms = __ttyconf.vtime * 100;
}
}
if (_check_interrupts(kSigOpRestartable)) return -1;
if (__pause_thread(ms)) return -1;
}
if (rc > 0 && (__ttymagic & kFdTtyEchoing)) {
EchoTerminalInput(f, data, size);
}
return rc;
}

View file

@ -19,14 +19,6 @@ COSMOPOLITAN_C_START_
#define kFdEpoll 7
#define kFdReserved 8
#define kFdTtyEchoing 1 /* read()→write() (ECHO && !ICANON) */
#define kFdTtyEchoRaw 2 /* don't ^X visualize control codes */
#define kFdTtyUncanon 4 /* enables non-canonical (raw) mode */
#define kFdTtyNoCr2Nl 8 /* don't map \r → \n (a.k.a !ICRNL) */
#define kFdTtyNoIsigs 16
#define kFdTtyNoBlock 32
#define kFdTtyXtMouse 64
struct Fd {
char kind;
bool eoftty;

View file

@ -55,22 +55,7 @@ textwindows int tcgetattr_nt(int fd, struct termios *tio) {
}
bzero(tio, sizeof(*tio));
tio->c_cc[VMIN] = !(__ttymagic & kFdTtyNoBlock);
tio->c_cc[VEOF] = __veof;
tio->c_cc[VTIME] = __vtime;
tio->c_cc[VINTR] = __vintr;
tio->c_cc[VQUIT] = __vquit;
tio->c_cc[VERASE] = CTRL('?');
tio->c_cc[VWERASE] = CTRL('W');
tio->c_cc[VKILL] = CTRL('U');
tio->c_cc[VMIN] = CTRL('A');
tio->c_cc[VSTART] = _POSIX_VDISABLE;
tio->c_cc[VSTOP] = _POSIX_VDISABLE;
tio->c_cc[VSUSP] = _POSIX_VDISABLE;
tio->c_cc[VREPRINT] = CTRL('R');
tio->c_cc[VDISCARD] = CTRL('O');
tio->c_cc[VLNEXT] = CTRL('V');
memcpy(tio->c_cc, __ttyconf.c_cc, NCCS);
tio->c_iflag = IUTF8;
tio->c_lflag = ECHOE;
@ -78,21 +63,24 @@ textwindows int tcgetattr_nt(int fd, struct termios *tio) {
tio->_c_ispeed = B38400;
tio->_c_ospeed = B38400;
if (inmode & kNtEnableLineInput) {
// kNtEnableLineInput and kNtEnableEchoInput only apply to programs
// that call ReadFile() or ReadConsole(). since we do not use them,
// the flags could serve the purpose of inter-process communication
if ((inmode & kNtEnableLineInput) || !(__ttyconf.magic & kTtyUncanon)) {
tio->c_lflag |= ICANON;
}
// kNtEnableEchoInput only works with kNtEnableLineInput enabled.
if ((inmode & kNtEnableEchoInput) || (__ttymagic & kFdTtyEchoing)) {
if ((inmode & kNtEnableEchoInput) || !(__ttyconf.magic & kTtySilence)) {
tio->c_lflag |= ECHO;
}
// The Windows console itself always echos control codes as ASCII.
if ((inmode & kNtEnableEchoInput) || !(__ttymagic & kFdTtyEchoRaw)) {
if ((inmode & kNtEnableEchoInput) || !(__ttyconf.magic & kTtyEchoRaw)) {
tio->c_lflag |= ECHOCTL;
}
if (!(__ttymagic & kFdTtyNoCr2Nl)) {
if (!(__ttyconf.magic & kTtyNoCr2Nl)) {
tio->c_iflag |= ICRNL;
}
if (!(__ttymagic & kFdTtyNoIsigs)) {
if (!(__ttyconf.magic & kTtyNoIsigs)) {
tio->c_lflag |= ISIG;
}
if (inmode & kNtEnableProcessedInput) {

View file

@ -72,45 +72,34 @@ textwindows int tcsetattr_nt(int fd, int opt, const struct termios *tio) {
inmode &= ~(kNtEnableLineInput | kNtEnableEchoInput |
kNtEnableProcessedInput | kNtEnableVirtualTerminalInput);
inmode |= kNtEnableWindowInput;
__ttymagic = 0;
__ttyconf.magic = 0;
if (tio->c_lflag & ICANON) {
inmode |=
kNtEnableLineInput | kNtEnableProcessedInput | kNtEnableQuickEditMode;
} else {
inmode &= ~kNtEnableQuickEditMode;
__ttymagic |= kFdTtyUncanon;
if (!tio->c_cc[VMIN]) {
__ttymagic |= kFdTtyNoBlock;
}
__vtime = tio->c_cc[VTIME];
__ttyconf.magic |= kTtyUncanon;
}
if (!(tio->c_iflag & ICRNL)) {
__ttymagic |= kFdTtyNoCr2Nl;
__ttyconf.magic |= kTtyNoCr2Nl;
}
if (!(tio->c_lflag & ECHOCTL)) {
__ttymagic |= kFdTtyEchoRaw;
__ttyconf.magic |= kTtyEchoRaw;
}
if (tio->c_lflag & ECHO) {
// "kNtEnableEchoInput can be used only if the
// kNtEnableLineInput mode is also enabled." -MSDN
if (tio->c_lflag & ICANON) {
inmode |= kNtEnableEchoInput;
} else {
// If ECHO is enabled in raw mode, then read(0) needs to
// magically write(1) to simulate echoing. This normally
// visualizes control codes, e.g. \r → ^M unless ECHOCTL
// hasn't been specified.
__ttymagic |= kFdTtyEchoing;
}
} else {
__ttyconf.magic |= kTtySilence;
}
if (!(tio->c_lflag & ISIG)) {
__ttymagic |= kFdTtyNoIsigs;
__ttyconf.magic |= kTtyNoIsigs;
}
__veof = tio->c_cc[VEOF];
__vintr = tio->c_cc[VINTR];
__vquit = tio->c_cc[VQUIT];
if ((tio->c_lflag & ISIG) && //
tio->c_cc[VINTR] == CTRL('C')) {
memcpy(__ttyconf.c_cc, tio->c_cc, NCCS);
if ((tio->c_lflag & ISIG) && __ttyconf.vintr == CTRL('C')) {
// allows ctrl-c to be delivered asynchronously via win32
inmode |= kNtEnableProcessedInput;
}

View file

@ -135,11 +135,11 @@ static textwindows ssize_t sys_write_nt_impl(int fd, void *data, size_t size,
} else {
m |= IsMouseModeCommand(x);
if (p[i] == 'h') {
__ttymagic |= kFdTtyXtMouse;
__ttyconf.magic |= kTtyXtMouse;
cm2 |= kNtEnableMouseInput;
cm2 &= kNtEnableQuickEditMode; // precludes mouse events
} else if (p[i] == 'l') {
__ttymagic &= ~kFdTtyXtMouse;
__ttyconf.magic &= ~kTtyXtMouse;
cm2 |= kNtEnableQuickEditMode; // disables mouse too
}
t = 0;