Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dart:io - stdin does not report bytes for many keys on windows #48329

Open
ellemenno opened this issue Feb 7, 2022 · 4 comments
Open

dart:io - stdin does not report bytes for many keys on windows #48329

ellemenno opened this issue Feb 7, 2022 · 4 comments
Labels
area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. library-io

Comments

@ellemenno
Copy link

ellemenno commented Feb 7, 2022

Experience

i'm working on a cross-platform commandline app, testing with a simple stdin echo program in dart 2.16 (see below), and am noticing missing key codes when running in windows (but not in linux).

it seems like the silent keys on windows are Esc (0x1b), and the multi-byte code sequences that start with 0x1b on linux.

Expectation

i expect to see some key code for every non-modifier key on my keyboard. i recognize some code values may differ across platforms.

Notes

so far, i'm testing windows cmd and wsl ubuntu on the same windows 10 machine, using the same Terminal app, and i see character codes from linux for all non-modifier keys (except PrtScr and the Windows key), but am getting no value for a number of important keys from windows (ESC, function keys, arrow keys, and Insert,Delete,Home,End,PgUp,PgDn).

update: added results from osx (practically identical to wsl)

Windows Terminal version 1.11.3471.0

win (cmd.exe)

> ver
Microsoft Windows [Version 10.0.19042.1466]
> dart --version
Dart SDK version: 2.16.0 (stable) (Mon Jan 31 15:28:59 2022 +0100) on "windows_x64"

wsl (ubuntu bash)

$ lsb_release --description
Description:    Ubuntu 18.04.5 LTS
$ dart --version
Dart SDK version: 2.16.0 (stable) (Mon Jan 31 15:28:59 2022 +0100) on "linux_x64"

OSX Terminal version 2.12 (443)

osx (darwin bash)

$ sw_vers
ProductName:    macOS
ProductVersion: 12.2
BuildVersion:   21D49
$ dart --version
Dart SDK version: 2.16.0 (stable) (Mon Jan 31 15:28:59 2022 +0100) on "macos_x64"

Test code

stdin.dart

import 'dart:io';

void main() {
  stderr.write('\n');
  stderr.write('type to echo the code sequence read by stdin.\n');
  stderr.write("type 'q' to exit.\n");

  stdin
    ..echoMode = false // for windows sake, echoMode must be disabled first
    ..lineMode = false; // see https://github.com/dart-lang/sdk/issues/28599#issuecomment-615940833

  var codeRead, codePrint;

  while (true) {
    codeRead = 0;
    codePrint = 0;
    while (codeRead <= 0) {
      codeRead = stdin.readByteSync();
    }
    if (codeRead == 0x7F) {
      codePrint = 0x2421; // unicode control picture for delete (U+2421)
    } else if (codeRead <= 0x20) {
      codePrint = 0x2400 + codeRead; // replace with matching control picture (U+2400 - U+2420)
    } else {
      codePrint = codeRead; // printable string, so use as is
    }
    stderr.write(
        "0x${codeRead.toRadixString(16).padLeft(2, '0')} '${String.fromCharCode(codePrint)}'\n");

    if (String.fromCharCode(codeRead) == 'q') {
      exit(0);
    }
  }
}

Results

table of characters reported by dart:io.stdin.readByteSync():

all same: 0x09 (␉), and printable chars 0x20 - 0x7e
Backspace 0x08 (␈) and Delete 0x7f (␡) are reported on all three OSes, but sometimes from different keys
Enter is reported as line feed 0x0a (␊) on linux and osx, and as carriage return 0x0d (␍) on win

F11 is skipped in the table, because the terminal apps consume it first to toggle full-screen or mission control, and it doesn't get passed along.

key wsl & osx win
ESC 0x1b '␛'
F1 0x1b '␛'
0x4f 'O'
0x50 'P'
F2 0x1b '␛'
0x4f 'O'
0x51 'Q'
F3 0x1b '␛'
0x4f 'O'
0x52 'R'
F4 0x1b '␛'
0x4f 'O'
0x53 'S'
F5 0x1b '␛'
0x5b '['
0x31 '1'
0x35 '5'
0x7e '~'
F6 0x1b '␛'
0x5b '['
0x31 '1'
0x37 '7'
0x7e '~'
F7 0x1b '␛'
0x5b '['
0x31 '1'
0x39 '8'
0x7e '~'
F8 0x1b '␛'
0x5b '['
0x31 '1'
0x39 '9'
0x7e '~'
F9 0x1b '␛'
0x5b '['
0x32 '2'
0x30 '0'
0x7e '~'
F10 0x1b '␛'
0x5b '['
0x32 '2'
0x31 '1'
0x7e '~'
F12 0x1b '␛'
0x5b '['
0x32 '2'
0x34 '4'
0x7e '~'
Insert 0x1b '␛'
0x5b '['
0x32 '2'
0x7e '~'
Delete 0x1b '␛'
0x5b '['
0x33 '3'
0x7e '~'
Arrow ↑ 0x1b '␛'
0x5b '['
0x41 'A'
Arrow ↓ 0x1b '␛'
0x5b '['
0x42 'B'
Arrow → 0x1b '␛'
0x5b '['
0x43 'C'
Arrow ← 0x1b '␛'
0x5b '['
0x44 'D'
PgUp 0x1b '␛'
0x5b '['
0x35 '5'
0x7e '~'
PgDn 0x1b '␛'
0x5b '['
0x36 '6'
0x7e '~'
End 0x1b '␛'
0x5b '['
0x46 'F'
Home 0x1b '␛'
0x5b '['
0x48 'H'
@kevmoo kevmoo added area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-io labels Feb 7, 2022
@ellemenno
Copy link
Author

hi, @kevmoo it's been a year, and this issue still exists on the latest stable dart (3.3.2):

> dart --version
Dart SDK version: 3.3.2 (stable) (Tue Mar 19 20:44:48 2024 +0000) on "windows_x64"

Esc (0x1b), and the multi-byte code sequences that start with 0x1b enumerated in the table above (like arrow keys) aren't read by dart:io.stdin.readByteSync() on windows.

any chance this can be addressed in a near-term release?

@kevmoo
Copy link
Member

kevmoo commented Mar 26, 2024

Maybe @brianquinlan has some ideas?

@ellemenno
Copy link
Author

hi @brianquinlan & @kevmoo, i'm out of my depth here, but i'd like to help move this forward if i can (please let me know if there is something specifically helpful that I could do..)

i've found that the ConEmu project has a handy demo utility called KeyEvents that shows what data is received on Windows when various keys are pressed. it demonstrates reacting to all the keys that Dart is missing in this issue.

comparing implementations, Dart's ReadByte for Windows function uses the ReadFile Windows API, while ConEmu's KeyEvents demo uses the ReadConsoleInput API.

looking at the Windows API docs, the limitations noted in this issue seem to be expected when using the ReadFile method (my emphasis):

To get keyboard input, a process can use ReadFile or ReadConsole with a handle to the console's input buffer, or it can use ReadFile to read input from a file or a pipe if STDIN has been redirected. These functions only return keyboard events that can be translated into ANSI or Unicode characters. The input that can be returned includes control key combinations. The functions do not return keyboard events involving the function keys or arrow keys. Input events generated by mouse, window, focus, or menu input are discarded.
High-Level Console Input and Output Functions

bool Stdin::ReadByte(intptr_t fd, int* byte) {
  HANDLE h = GetStdHandle(STD_INPUT_HANDLE);
  uint8_t buffer[1];
  DWORD read = 0;
  BOOL success = ReadFile(h, buffer, 1, &read, nullptr);
  if (!success && (GetLastError() != ERROR_BROKEN_PIPE)) {
    return false;
  }
  *byte = (read == 1) ? buffer[0] : -1;
  return true;
}

stdio_win.cc

..whereas the ReadConsoleInput API does return keyboard events for function and arrow keys, and escape, and the others that are missing keycodes in the chart above.

  // ..
  INPUT_RECORD r, rl = {};
  HANDLE h = GetStdHandle(STD_INPUT_HANDLE);
  HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
  SetConsoleCtrlHandler(HandlerRoutine, TRUE);
  BOOL lbEscPressed = FALSE;
  wchar_t szFormat[1024];
  // ..
  while (TRUE)
  {
    memset(&r, 0, sizeof(r)); dw = 0;
    if (ReadConsoleInput(h, &r, 1, &dw))
    {
      // ..
      __INPUT_RECORD_Dump(&r, szFormat);
      DWORD nLen = wcslen(szFormat);
      WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), szFormat, nLen, &nLen, NULL);
      // ..
    }
  }

KeyEvents.cpp

so, can the Windows ReadByte implementation be updated to use the ReadConsoleInput API and gain parity with the Mac and Linux implementations?

@lrhn lrhn added area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. and removed area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. labels Apr 21, 2024
@jtmcdole
Copy link
Contributor

jtmcdole commented Oct 9, 2024

Just to note; mason_logger uses package win32 to enable raw mode. I use this in my own code to read arrow keys, backspace, and more from stdin:

        ///----- Arrow keys -----\\\
        case 0x41:
          // \1b[A
          final modifiers = rolling.length == 1
              ? Modifiers.lut[rolling[0] - 1]!
              : Modifiers.lut[0]!;

          _events.add(KeycodeEvent('↑', Keycode.up, modifiers: modifiers));
          return;

        case 0x42:
          // \1b[B
          final modifiers = rolling.length == 1
              ? Modifiers.lut[rolling[0] - 1]!
              : Modifiers.lut[0]!;
          _events.add(KeycodeEvent('↓', Keycode.down, modifiers: modifiers));
          return;

        case 0x43:
          // \1b[C
          final modifiers = rolling.length == 1
              ? Modifiers.lut[rolling[0] - 1]!
              : Modifiers.lut[0]!;
          _events.add(KeycodeEvent('→', Keycode.right, modifiers: modifiers));
          return;

        case 0x44:
          // \1b[D
          final modifiers = rolling.length == 1
              ? Modifiers.lut[rolling[0] - 1]!
              : Modifiers.lut[0]!;
          _events.add(KeycodeEvent('←', Keycode.left, modifiers: modifiers));
          return;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. library-io
Projects
None yet
Development

No branches or pull requests

4 participants