Skip to content

Commit

Permalink
Unify MS Windows console events into one buffer
Browse files Browse the repository at this point in the history
  • Loading branch information
Konstantin-Glukhov committed Jun 29, 2023
1 parent 60eb2a9 commit fd4da73
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 159 deletions.
15 changes: 4 additions & 11 deletions os.c
Expand Up @@ -244,18 +244,11 @@ public int iread(int fd, unsigned char *buf, unsigned int len)
}
#else
#if MSDOS_COMPILER==WIN32C
if (win32_kbhit())
if (win32_kbhit() && WIN32getch() == intr_char)
{
int c;

c = WIN32getch();
if (c == intr_char)
{
sigs |= S_INTERRUPT;
reading = 0;
return (READ_INTR);
}
WIN32ungetch(c);
sigs |= S_INTERRUPT;
reading = 0;
return (READ_INTR);
}
#endif
#endif
Expand Down
233 changes: 85 additions & 148 deletions screen.c
Expand Up @@ -140,20 +140,17 @@ extern int sc_height;
// UTF16 and UTF8 buffer sizes
#define UTF8_MAX_LENGTH 4
#define UTF16_MAX_LENGTH 2
struct keyRecord
#define KEY_BUFFER_MAX_LENGTH 6
struct consoleBuffer
{
int scan;
char ascii;
char utf8[UTF8_MAX_LENGTH];
int utf8_nbytes;
int utf8_current_byte;
} currentKey;
char buf[KEY_BUFFER_MAX_LENGTH];
int buf_nbytes;
int buf_current_byte;
} console;
// Scan codes must be returned as 2-byte sequence: with '\0' 1st byte, scancode 2nd byte
#define setScancode(scancode) (console.buf_nbytes = 2, console.buf[0] = 0, console.buf[1] = scancode)

static int keyCount = 0;
static WORD curr_attr;
static char x11mousebuf[] = "[M???"; /* Mouse report, after ESC */
static int x11mousePos, x11mouseCount;
static int win_unget_pending = FALSE;
static int win_unget_data;

static HANDLE con_out_save = INVALID_HANDLE_VALUE; /* previous console */
Expand Down Expand Up @@ -2807,53 +2804,39 @@ public int win32_kbhit(void)
char16_t utf16[UTF16_MAX_LENGTH];
int utf16_nwords;

if (keyCount > 0 || win_unget_pending)
return (TRUE);

currentKey.ascii = 0;
currentKey.scan = 0;

if (x11mouseCount > 0)
{
currentKey.ascii = x11mousebuf[x11mousePos++];
--x11mouseCount;
keyCount = 1;
return (TRUE);
}

// Read Console Input Loop
// Read Console Input Loop and fill console.buf with either mouse or key event info
console.buf_current_byte = 0;
while(1) {
PeekConsoleInputW(tty, &ip, 1, &read);
if (read == 0)
return (FALSE);
ReadConsoleInputW(tty, &ip, 1, &read);
/* generate an X11 mouse sequence from the mouse event */
if (mousecap && ip.EventType == MOUSE_EVENT &&
ip.Event.MouseEvent.dwEventFlags != MOUSE_MOVED)
{
x11mousebuf[3] = X11MOUSE_OFFSET + ip.Event.MouseEvent.dwMousePosition.X + 1;
x11mousebuf[4] = X11MOUSE_OFFSET + ip.Event.MouseEvent.dwMousePosition.Y + 1;
if (mousecap && ip.EventType == MOUSE_EVENT && ip.Event.MouseEvent.dwEventFlags != MOUSE_MOVED) {
console.buf_nbytes = KEY_BUFFER_MAX_LENGTH;
console.buf[0] = ESC;
console.buf[1] = '[';
console.buf[2] = 'M';
console.buf[3] = '?';
console.buf[4] = X11MOUSE_OFFSET + ip.Event.MouseEvent.dwMousePosition.X + 1;
console.buf[5] = X11MOUSE_OFFSET + ip.Event.MouseEvent.dwMousePosition.Y + 1;
switch (ip.Event.MouseEvent.dwEventFlags)
{
case 0: /* press or release */
if (ip.Event.MouseEvent.dwButtonState == 0)
x11mousebuf[2] = X11MOUSE_OFFSET + X11MOUSE_BUTTON_REL;
console.buf[3] = X11MOUSE_OFFSET + X11MOUSE_BUTTON_REL;
else if (ip.Event.MouseEvent.dwButtonState & (FROM_LEFT_3RD_BUTTON_PRESSED | FROM_LEFT_4TH_BUTTON_PRESSED))
continue;
continue; // if we get another mouse event everything will be overwritten, should return TRUE instead?
else
x11mousebuf[2] = X11MOUSE_OFFSET + X11MOUSE_BUTTON1 + ((int)ip.Event.MouseEvent.dwButtonState << 1);
console.buf[3] = X11MOUSE_OFFSET + X11MOUSE_BUTTON1 + ((int)ip.Event.MouseEvent.dwButtonState << 1);
break;
case MOUSE_WHEELED:
x11mousebuf[2] = X11MOUSE_OFFSET + (((int)ip.Event.MouseEvent.dwButtonState < 0) ? X11MOUSE_WHEEL_DOWN : X11MOUSE_WHEEL_UP);
console.buf[3] = X11MOUSE_OFFSET + (((int)ip.Event.MouseEvent.dwButtonState < 0) ? X11MOUSE_WHEEL_DOWN : X11MOUSE_WHEEL_UP);
break;
default:
continue;
continue; // if we get another mouse event everything will be overwritten, should return TRUE instead?
}
x11mousePos = 0;
x11mouseCount = 5;
currentKey.ascii = ESC;
keyCount = 1;
return (TRUE);
return TRUE;
}
/*
* Wait for a real key-down event, but ignore
Expand All @@ -2871,136 +2854,91 @@ public int win32_kbhit(void)
(ip.Event.KeyEvent.dwControlKeyState & (RIGHT_ALT_PRESSED|LEFT_CTRL_PRESSED)) == (RIGHT_ALT_PRESSED|LEFT_CTRL_PRESSED)
)
) ||
ip.Event.KeyEvent.wVirtualKeyCode == VK_NUMLOCK ||
ip.Event.KeyEvent.wVirtualKeyCode == VK_CAPITAL ||
ip.Event.KeyEvent.wVirtualKeyCode == VK_KANJI ||
ip.Event.KeyEvent.wVirtualKeyCode == VK_SHIFT ||
ip.Event.KeyEvent.wVirtualKeyCode == VK_CONTROL ||
ip.Event.KeyEvent.wVirtualKeyCode == VK_MENU
) continue;

// If we got here, we got a real charachter code
// If ASCII character, return it without coversion
if ( ip.Event.KeyEvent.uChar.UnicodeChar <= 0x7F ) {
currentKey.ascii = ip.Event.KeyEvent.uChar.AsciiChar;
// UTF-16 character
} else {
// If HS (high surrogate), save it to the first UTF-16 word and wait for LS
if (0xD800 <= ip.Event.KeyEvent.uChar.UnicodeChar && ip.Event.KeyEvent.uChar.UnicodeChar <= 0xDBFF) {
utf16[0] = ip.Event.KeyEvent.uChar.UnicodeChar;
continue;
// If LS (low surrogate), save it to the second UTF-16 word
} else if(0xDC00 <= ip.Event.KeyEvent.uChar.UnicodeChar && ip.Event.KeyEvent.uChar.UnicodeChar <= 0xDFFF) {
utf16[1] = ip.Event.KeyEvent.uChar.UnicodeChar; // found LS
utf16_nwords = 2;
// If BMP (Basic Multilingual Plane), save it to the first UTF-16 word
} else {
utf16[0] = ip.Event.KeyEvent.uChar.UnicodeChar;
utf16_nwords = 1;
if (ip.Event.KeyEvent.uChar.UnicodeChar) { // If character is available return it as UTF-8 multi-byte string
// If Alt-E is pressed return it's scancode
if (ip.Event.KeyEvent.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) {
switch (ip.Event.KeyEvent.wVirtualScanCode)
{
case PCK_ALT_E:
setScancode(PCK_ALT_E);
return TRUE;
}
}
// Convert UTF-16 to UTF-8
currentKey.utf8_nbytes = WideCharToMultiByte(CP_UTF8, 0, utf16, utf16_nwords, currentKey.utf8, UTF8_MAX_LENGTH, NULL, NULL);
if (currentKey.utf8_nbytes) {
currentKey.ascii = currentKey.utf8[0];
currentKey.utf8_current_byte = 1;
if ( ip.Event.KeyEvent.uChar.UnicodeChar <= 0x7F ) {
// If ASCII character, save it to the 1st byte of UTF-8 string and return
console.buf[0] = ip.Event.KeyEvent.uChar.AsciiChar;
console.buf_nbytes = 1;
} else {
currentKey.utf8_current_byte = 0;
return FALSE;
}
}

// TODO: Document what the following block does
currentKey.scan = ip.Event.KeyEvent.wVirtualScanCode;
keyCount = ip.Event.KeyEvent.wRepeatCount;

if (ip.Event.KeyEvent.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) {
switch (currentKey.scan)
{
case PCK_ALT_E: /* letter 'E' */
currentKey.ascii = 0;
break;
}
} else if (ip.Event.KeyEvent.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) {
switch (currentKey.scan)
{
case PCK_RIGHT: /* right arrow */
currentKey.scan = PCK_CTL_RIGHT;
break;
case PCK_LEFT: /* left arrow */
currentKey.scan = PCK_CTL_LEFT;
break;
case PCK_DELETE: /* delete */
currentKey.scan = PCK_CTL_DELETE;
break;
}
} else if (ip.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED)
{
switch (currentKey.scan)
{
case PCK_SHIFT_TAB: /* tab */
currentKey.ascii = 0;
break;
// If Unicode character, coververt to UTF-8 multi-byte string
if (0xD800 <= ip.Event.KeyEvent.uChar.UnicodeChar && ip.Event.KeyEvent.uChar.UnicodeChar <= 0xDBFF) {
// If HS (high surrogate), save it to the 1st word of UTF-16 buffer and wait for LS
utf16[0] = ip.Event.KeyEvent.uChar.UnicodeChar;
continue;
} else if(0xDC00 <= ip.Event.KeyEvent.uChar.UnicodeChar && ip.Event.KeyEvent.uChar.UnicodeChar <= 0xDFFF) {
// If LS (low surrogate), save it to the 2nd word of UTF-16 buffer
utf16[1] = ip.Event.KeyEvent.uChar.UnicodeChar; // found LS
utf16_nwords = 2;
} else {
// If BMP (Basic Multilingual Plane), save it to the 1st word of UTF-16 buffer
utf16[0] = ip.Event.KeyEvent.uChar.UnicodeChar;
utf16_nwords = 1;
}
// Convert UTF-16 to UTF-8
console.buf_nbytes = WideCharToMultiByte(CP_UTF8, 0, utf16, utf16_nwords, console.buf, UTF8_MAX_LENGTH, NULL, NULL);
if (!console.buf_nbytes)
return FALSE;
}
} else { // Otherwise return a key scancode
if (ip.Event.KeyEvent.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) {
// If CTRL is pressed
switch (ip.Event.KeyEvent.wVirtualScanCode)
{
case PCK_RIGHT: /* right arrow */
setScancode(PCK_CTL_RIGHT);
break;
case PCK_LEFT: /* left arrow */
setScancode(PCK_CTL_LEFT);
break;
case PCK_DELETE: /* delete */
setScancode(PCK_CTL_DELETE);
break;
}
} else
setScancode(ip.Event.KeyEvent.wVirtualScanCode & 0x00FF);
}
return TRUE;
}
}

/*
* Read a character from the keyboard.
* Read a character from the console.
*
* Known issues:
* - WIN32getch API should be int like libc (with unsigned char values or -1).
* - The unicode code below can return 0 - incorrectly indicating scan code.
* - UTF16-LE surrogate pairs don't work (and return 0).
* - If win32_kbhit returns true then WIN32getch should never block, but it
* will block till the next keypress if it's numlock/capslock scan code.
*/
public char WIN32getch(void)
{
static int pending_scancode = 0;

if (win_unget_pending)
{
win_unget_pending = FALSE;
return (char) win_unget_data;
}
// Return the rest of the buffer
if (console.buf_current_byte < console.buf_nbytes)
return console.buf[console.buf_current_byte++];

// If extended key, return its scan code
if (pending_scancode) {
pending_scancode = 0;
return ((char)(currentKey.scan & 0x00FF));
// Wait for the mouse or keyboard event
while (!win32_kbhit()) {
Sleep(20);
if (ABORT_SIGS())
return ('\003');
}

// If UTF-8 byte string, return it one byte at a time
if (currentKey.utf8_current_byte < currentKey.utf8_nbytes)
return currentKey.utf8[currentKey.utf8_current_byte++];

do {
while (!win32_kbhit())
{
Sleep(20);
if (ABORT_SIGS())
return ('\003');
}
keyCount--;
/*
* On PC's, the extended keys return a 2 byte sequence beginning
* with '00', so if the ascii code is 00, the next byte will be
* the lsb of the scan code.
*/
pending_scancode = (currentKey.ascii == 0x00);
} while (pending_scancode &&
(currentKey.scan == PCK_CAPS_LOCK || currentKey.scan == PCK_NUM_LOCK));

return currentKey.ascii;
}

/*
* Make the next call to WIN32getch return ch without changing the queue state.
*/
public void WIN32ungetch(int ch)
{
win_unget_pending = TRUE;
win_unget_data = ch;
// Return first byte of the buffer
return console.buf[console.buf_current_byte++];
}
#endif

Expand Down Expand Up @@ -3038,4 +2976,3 @@ public void WIN32textout(char *text, int len)
#endif
}
#endif

2 comments on commit fd4da73

@gwsw
Copy link
Owner

@gwsw gwsw commented on fd4da73 Jun 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't removing WIN32ungetch reintroduce bug #378?

@Konstantin-Glukhov
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure. Didn't have time to look at it yet. It should be an easy fix.

Please sign in to comment.