Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions source/dcell/common.d
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,11 @@ version (Posix)
}
else version (Windows)
{
import dcell.ttyscreen;

Screen newScreen()
{
import std.exception;

throw new Exception("windows not supported yet");
return null;
return newTtyScreen();
}
}
else
Expand Down
31 changes: 30 additions & 1 deletion source/dcell/parser.d
Original file line number Diff line number Diff line change
Expand Up @@ -1014,7 +1014,6 @@ private:
else if (chr < ' ' && p0 >= 0x41 && p0 <= 0x5a)
{
key = cast(Key) p0;
chr = 0;
}
else if (key == 0x11 || key == 0x13 || key == 0x14)
{
Expand Down Expand Up @@ -1110,6 +1109,36 @@ private:
mod |= Modifiers.alt;
escaped = false;
}
if (dch < ' ' && k < Key.rune)
{
switch (cast(int) k)
{
case 0xd, 0xa:
k = Key.enter;
break;
case 0x9:
k = Key.tab;
break;
case 0x8:
k = Key.backspace;
break;
case 0x1b:
k = Key.esc;
break;
case 0: // control-space
k = Key.rune;
mod |= Modifiers.ctrl;
dch = ' ';
break;
default:
// most likely entered with a CTRL keypress
k = Key.rune;
mod |= Modifiers.ctrl;
dch = dch + '\x60';
break;
}
}

Event ev = {
type: EventType.key, when: MonoTime.currTime(), key: {
key: k, ch: dch, mod: mod
Expand Down
209 changes: 190 additions & 19 deletions source/dcell/termio.d
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,6 @@ interface TtyImpl
*/
void raw();

/**
* Make input blocking or non-blocking. Blocking input
* will cause reads against the terminal to block forever
* until at least one character is returned. Otherwise it
* will return in at most
*/
void blocking(bool b);

/**
* Read input. May return an empty slice if no data
* is present and blocking is disabled.
Expand Down Expand Up @@ -178,17 +170,6 @@ version (Posix)
file.flush();
}

void blocking(bool b) @trusted
{
termios tio;
enforce(tcgetattr(fd, &tio) >= 0);
tio.c_cc[VMIN] = b ? 1 : 0;
tio.c_cc[VTIME] = 0;

enforce(tcsetattr(fd, TCSANOW, &tio) >= 0);
block = b;
}

void raw() @trusted
{
termios tio;
Expand Down Expand Up @@ -399,6 +380,13 @@ version (Posix)
}

}
else version (Windows)
{
TtyImpl newDevTty()
{
return new WinTty();
}
}
else
{
TtyImpl newDevTty(string _ = "/dev/tty")
Expand Down Expand Up @@ -502,3 +490,186 @@ version (Posix)
}
}
}

version (Windows)
{

import core.sys.windows.windows;

// Kernel32.dll functions
extern (Windows)
{
BOOL ReadConsoleInputW(HANDLE hConsoleInput, INPUT_RECORD* lpBuffer, DWORD nLength, DWORD* lpNumEventsRead);

BOOL GetNumberOfConsoleInputEvents(HANDLE hConsoleInput, DWORD* lpcNumberOfEvents);

BOOL FlushConsoleInputBuffer(HANDLE hConsoleInput);

DWORD WaitForMultipleObjects(DWORD nCount, const HANDLE* lpHandles, BOOL bWaitAll, DWORD dwMilliseconds);

BOOL SetConsoleMode(HANDLE hConsoleHandle, DWORD dwMode);

BOOL GetConsoleMode(HANDLE hConsoleHandle, DWORD* lpMode);

BOOL GetConsoleScreenBufferInfo(HANDLE hConsoleOutput, CONSOLE_SCREEN_BUFFER_INFO* lpConsoleScreenBufferInfo);

HANDLE CreateEventW(SECURITY_ATTRIBUTES* secAttr, BOOL bManualReset, BOOL bInitialState, LPCWSTR lpName);

BOOL SetEvent(HANDLE hEvent);

BOOL WriteConsoleW(HANDLE hFile, LPCVOID buf, DWORD nNumBytesToWrite, LPDWORD lpNumBytesWritten, LPVOID rsvd);

BOOL CloseHandle(HANDLE hObject);
}

// WindowsTty use ReadConsoleInput, as that is the only
// way to get window resize events.
package class WinTty : TtyImpl
{

this()
{
input = GetStdHandle(STD_INPUT_HANDLE);
output = GetStdHandle(STD_OUTPUT_HANDLE);
eventH = CreateEventW(null, true, false, null);
}

void save()
{

GetConsoleMode(output, &omode);
GetConsoleMode(input, &imode);
}

void restore()
{
SetConsoleMode(output, omode);
SetConsoleMode(input, imode);
}

void start()
{
save();
if (!started)
{
started = true;
FlushConsoleInputBuffer(input);
}
}

void stop()
{
SetEvent(eventH);
}

void close()
{
CloseHandle(input);
CloseHandle(output);
CloseHandle(eventH);
}

void raw()
{
SetConsoleMode(input, ENABLE_VIRTUAL_TERMINAL_INPUT | ENABLE_WINDOW_INPUT | ENABLE_EXTENDED_FLAGS);
SetConsoleMode(output,
ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN);

}

void flush()
{
}

/**
* Read input. May return an empty slice if no data
* is present and blocking is disabled.
*/
string read(Duration dur = Duration.zero)
{
HANDLE[2] handles;
handles[0] = input;
handles[1] = eventH;

DWORD dly;
if (dur.isNegative || dur == Duration.max)
{
dly = INFINITE;
}
else
{
dly = cast(DWORD)(dur.total!"msecs");
}

auto rv = WaitForMultipleObjects(2, handles.ptr, false, dly);
string result = null;

// WaitForMultipleObjects returns WAIT_OBJECT_0 + the index.
switch (rv)
{
case WAIT_OBJECT_0 + 1: // w.cancelFlag
return result;
case WAIT_OBJECT_0: // input
INPUT_RECORD[128] recs;
DWORD nrec;
ReadConsoleInput(input, recs.ptr, 128, &nrec);

foreach (ev; recs[0 .. nrec])
{
switch (ev.EventType)
{
case KEY_EVENT:
auto chr = ev.KeyEvent.AsciiChar;
result ~= chr;
break;
case WINDOW_BUFFER_SIZE_EVENT:
wasResized = true;
break;
default: // we could process focus, etc. here, but we already
// get them inline via VT sequences
break;
}
}

return result;
default:
return result;
}
}

/**
* Write output.
*/
void write(string s)
{
import std.utf;

wstring w = toUTF16(s);

WriteConsoleW(output, w.ptr, cast(uint) w.length, null, null);
}

Coord windowSize()
{
GetConsoleScreenBufferInfo(output, &oscreen);
return Coord(oscreen.dwSize.X, oscreen.dwSize.Y);
}

bool resized()
{
bool result = wasResized;
wasResized = false;
return result;
}

private:
HANDLE output;
HANDLE input;
HANDLE eventH;
DWORD omode;
DWORD imode;
CONSOLE_SCREEN_BUFFER_INFO oscreen;
bool started;
bool wasResized;
}
Comment on lines +494 to +674
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Windows KEY_EVENT_RECORD structure documentation - bKeyDown flag and character handling

💡 Result:

Short answer

  • bKeyDown is TRUE when the key is pressed and FALSE when it is released; ReadConsoleInput/PeekConsoleInput will deliver both press and release KEY_EVENT_RECORDs. [1]
  • The uChar union holds the translated character (UnicodeChar or AsciiChar). Printable characters are usually present in uChar on the key‑down event; non‑printable keys (Shift, Ctrl, arrows, etc.) typically have uChar == 0 and you can see character bytes only for events that produce characters. Use ReadConsoleInputW to get UTF‑16 characters. [1][2][3]

Notes / best practices

  • If you only want character input, filter for KEY_EVENT records where bKeyDown is TRUE and uChar != 0. [1][3]
  • ALT pressed/released by itself and Ctrl+C in processed input mode are handled specially by the system and may not be passed to the app. [1]

Sources
[1] Microsoft: KEY_EVENT_RECORD (Windows Console) — documentation of bKeyDown and uChar.
[2] Microsoft Q&A / ReadConsoleInput notes — uChar is nonzero only for printable characters; ReadConsoleInputA may return multi‑byte bytes for codepages.
[3] Stack Overflow — practical notes: ignore key‑up events and zero uChar, and prefer ReadConsoleInputW for Unicode.


🏁 Script executed:

# Check D core.sys.windows bindings for KEY_EVENT_RECORD and CONSOLE_SCREEN_BUFFER_INFO
fd -e d -x grep -l "KEY_EVENT_RECORD\|CONSOLE_SCREEN_BUFFER_INFO" {} \;

Repository: gdamore/dcell

Length of output: 80


🌐 Web query:

CONSOLE_SCREEN_BUFFER_INFO structure - dwSize is buffer size, srWindow is visible window coordinates

💡 Result:

  • dwSize: a COORD giving the console screen buffer size in character columns and rows (i.e., total buffer width/height). [1]

  • srWindow: a SMALL_RECT that gives the visible window’s coordinates inside that buffer — Left/Top are the upper‑left cell, Right/Bottom the lower‑right cell; coordinates are in character‑cell units with origin (0,0) at the buffer’s upper‑left. You can modify srWindow and pass it to SetConsoleWindowInfo to move/resize/scroll the visible window. [1][2][3]

Sources: Microsoft Docs — CONSOLE_SCREEN_BUFFER_INFO; SMALL_RECT; GetConsoleScreenBufferInfo. [1][2][3]


🏁 Script executed:

# Search the D language runtime for the windows bindings
find . -path "*/core/sys/windows*" -name "*.d" 2>/dev/null | head -20

Repository: gdamore/dcell

Length of output: 39


🏁 Script executed:

# Check if the repository has any Windows API structure definitions
rg "KEY_EVENT_RECORD|struct.*KEY_EVENT" -t d

Repository: gdamore/dcell

Length of output: 39


🏁 Script executed:

# Look for how this code is used elsewhere or if there are tests
rg "WinTty\|windowSize\|KEY_EVENT" -t d -B 2 -A 2

Repository: gdamore/dcell

Length of output: 39


Fix KEY_EVENT handling and window size calculation for correctness

The Windows TTY implementation has two bugs that affect functionality:

  1. KEY_EVENT handling in read
    ReadConsoleInput delivers both key-down and key-up events, and every event appends AsciiChar to result without filtering. According to Windows API documentation, bKeyDown is TRUE only on key press and FALSE on key release; additionally, uChar is typically zero for non-printable keys. The current code will emit spurious characters from key-up events and non-printable keys. Correct this by checking if (ev.KeyEvent.bKeyDown && ev.KeyEvent.AsciiChar != 0) before appending.

  2. Window size vs buffer size in windowSize()
    GetConsoleScreenBufferInfo populates dwSize with the total screen buffer dimensions, not the visible window. The visible window dimensions are in srWindow (a SMALL_RECT with Left, Top, Right, Bottom coordinates). Return Coord(oscreen.srWindow.Right - oscreen.srWindow.Left + 1, oscreen.srWindow.Bottom - oscreen.srWindow.Top + 1) to get the actual on-screen dimensions.

  3. Error handling (optional)
    Win32 calls ignore return values; adding minimal checks or asserts would catch failures during development.

🤖 Prompt for AI Agents
In source/dcell/termio.d around lines 494 to 674, the WinTty.read and windowSize
implementations are incorrect: modify KEY_EVENT handling to only append
characters when ev.KeyEvent.bKeyDown is true and ev.KeyEvent.AsciiChar != 0 to
avoid emitting key-up and non-printable events; change windowSize() to compute
visible dimensions from oscreen.srWindow as (Right - Left + 1, Bottom - Top + 1)
instead of using oscreen.dwSize; optionally add minimal checks of Win32 return
values (or asserts/logs) for ReadConsoleInput and GetConsoleScreenBufferInfo to
catch failures during development.

}