Compare commits
4 commits
5dd464eb18
...
c8ccedf569
| Author | SHA1 | Date | |
|---|---|---|---|
| c8ccedf569 | |||
| fbf4ed7c40 | |||
| ec0ec8f074 | |||
| acf1a6b691 |
7 changed files with 406 additions and 593 deletions
|
|
@ -45,48 +45,77 @@ type
|
||||||
|
|
||||||
TKPAnsi = class(TCustomControl)
|
TKPAnsi = class(TCustomControl)
|
||||||
private
|
private
|
||||||
FScreen: TList;
|
{ Terminal buffer state }
|
||||||
FScrollback: TList;
|
FScreen: TList; { Active screen lines (FRows PTermLine ptrs) }
|
||||||
FCursorRow: Integer;
|
FScrollback: TList; { Scrollback history (up to FScrollbackSize) }
|
||||||
FCursorCol: Integer;
|
|
||||||
FSaveCurRow: Integer;
|
{ Cursor position (0-based row/col within the active screen) }
|
||||||
FSaveCurCol: Integer;
|
FCursorRow: Integer; { Current row (0 = top) }
|
||||||
FAttrFG: Integer;
|
FCursorCol: Integer; { Current column (0 = left) }
|
||||||
FAttrBG: Integer;
|
FSaveCurRow: Integer; { Saved row for SCP/RCP (ESC[s / ESC[u) }
|
||||||
FAttrBold: Boolean;
|
FSaveCurCol: Integer; { Saved column for SCP/RCP }
|
||||||
FAttrBlink: Boolean;
|
|
||||||
FAttrReverse: Boolean;
|
{ Current text attributes (set by SGR escape sequences) }
|
||||||
FParseState: TParseState;
|
FAttrFG: Integer; { Foreground color index 0-7 }
|
||||||
FParamStr: string;
|
FAttrBG: Integer; { Background color index 0-7 }
|
||||||
FMusicStr: string;
|
FAttrBold: Boolean; { Bold: maps FG to bright (index + 8) }
|
||||||
FCellWidth: Integer;
|
FAttrBlink: Boolean; { Blink: cell toggles visibility on timer }
|
||||||
FCellHeight: Integer;
|
FAttrReverse: Boolean; { Reverse video: swap FG/BG at render time }
|
||||||
FBlinkOn: Boolean;
|
|
||||||
FTimerActive: Boolean;
|
{ ANSI escape sequence parser state }
|
||||||
FScrollPos: Integer;
|
FParseState: TParseState; { Current parser state machine position }
|
||||||
FWrapMode: Boolean;
|
FParamStr: string; { Accumulated CSI parameter digits/semicolons }
|
||||||
FCols: Integer;
|
FMusicStr: string; { Accumulated ANSI music string (ESC[M..^N) }
|
||||||
FRows: Integer;
|
|
||||||
FScrollbackSize: Integer;
|
{ Font metrics (measured from OEM charset paint font) }
|
||||||
FCursorVisible: Boolean;
|
FCellWidth: Integer; { Character cell width in pixels (typically 8) }
|
||||||
FLastCursorRow: Integer;
|
FCellHeight: Integer; { Character cell height in pixels (typ 12-16) }
|
||||||
FOnKeyData: TKeyDataEvent;
|
|
||||||
FPaintFont: HFont;
|
{ Blink state }
|
||||||
FStockFont: Boolean;
|
FBlinkOn: Boolean; { Cursor blink phase: True=visible }
|
||||||
FBlinkCount: Integer;
|
FLastBlinkTick: Longint; { GetTickCount value at last blink toggle }
|
||||||
FUpdateCount: Integer;
|
|
||||||
FPendingScroll: Integer;
|
{ Scrollback view }
|
||||||
FDirtyRow: array[0..255] of Boolean;
|
FScrollPos: Integer; { Lines scrolled back (0=live, >0=viewing history) }
|
||||||
FAllDirty: Boolean;
|
|
||||||
FTextBlinkOn: Boolean;
|
{ Terminal modes }
|
||||||
FGlyphBufH: THandle;
|
FWrapMode: Boolean; { Auto-wrap at right margin (DEC ?7h/l) }
|
||||||
FGlyphBuf: Pointer;
|
|
||||||
FRowBufH: THandle;
|
{ Terminal dimensions }
|
||||||
FRowBuf: Pointer;
|
FCols: Integer; { Number of columns (default 80) }
|
||||||
FDibInfo: TDibInfo;
|
FRows: Integer; { Number of rows (default 25) }
|
||||||
FRowBufSize: Integer;
|
FScrollbackSize: Integer; { Max scrollback lines to retain (default 500) }
|
||||||
FNibbleFG: Byte;
|
|
||||||
FNibbleBG: Byte;
|
{ Cursor visibility (DEC ?25h/l) }
|
||||||
|
FCursorVisible: Boolean; { True if cursor is shown }
|
||||||
|
FLastCursorRow: Integer; { Previous cursor row for ghost cleanup }
|
||||||
|
|
||||||
|
{ Events }
|
||||||
|
FOnKeyData: TKeyDataEvent; { Keyboard data callback (keys -> serial) }
|
||||||
|
|
||||||
|
{ Paint font }
|
||||||
|
FPaintFont: HFont; { GDI font handle for OEM_CHARSET rendering }
|
||||||
|
FStockFont: Boolean; { True if FPaintFont is a stock object (no delete) }
|
||||||
|
|
||||||
|
{ Dirty tracking: per-row flags for incremental rendering }
|
||||||
|
FDirtyRow: array[0..255] of Boolean; { True = row needs re-render }
|
||||||
|
FAllDirty: Boolean; { True = all rows need re-render }
|
||||||
|
FScrollbarDirty: Boolean; { True = scrollbar range/position needs update }
|
||||||
|
FTextBlinkOn: Boolean; { Text blink phase: True=visible, False=hidden }
|
||||||
|
|
||||||
|
{ Font atlas: glyph bitmaps + nibble lookup table (GlobalAlloc) }
|
||||||
|
FGlyphBufH: THandle; { GlobalAlloc handle for glyph block (8256 bytes) }
|
||||||
|
FGlyphBuf: Pointer; { Far ptr: offset 0..63 = nibble table, 64+ = glyphs }
|
||||||
|
|
||||||
|
{ Row pixel buffer: reusable 8bpp DIB for one terminal row }
|
||||||
|
FRowBufH: THandle; { GlobalAlloc handle for pixel buffer }
|
||||||
|
FRowBuf: Pointer; { Far ptr to pixel data (cols*cellW*cellH bytes) }
|
||||||
|
FDibInfo: TDibInfo; { BITMAPINFO with 16-color ANSI palette }
|
||||||
|
FRowBufSize: Integer; { Pixel buffer size in bytes }
|
||||||
|
|
||||||
|
{ Nibble table color cache: avoids rebuild when colors unchanged }
|
||||||
|
FNibbleFG: Byte; { FG index currently in nibble table (255=invalid) }
|
||||||
|
FNibbleBG: Byte; { BG index currently in nibble table (255=invalid) }
|
||||||
procedure AllocLine(Line: PTermLine);
|
procedure AllocLine(Line: PTermLine);
|
||||||
procedure BuildAtlas;
|
procedure BuildAtlas;
|
||||||
procedure ClearLine(Line: PTermLine);
|
procedure ClearLine(Line: PTermLine);
|
||||||
|
|
@ -105,7 +134,6 @@ type
|
||||||
procedure EraseLine(Mode: Integer);
|
procedure EraseLine(Mode: Integer);
|
||||||
procedure ExecuteCSI(FinalCh: Char);
|
procedure ExecuteCSI(FinalCh: Char);
|
||||||
procedure ExecuteMusic;
|
procedure ExecuteMusic;
|
||||||
procedure FlipToScreen;
|
|
||||||
procedure FreeLineList(List: TList);
|
procedure FreeLineList(List: TList);
|
||||||
function GetCursorCol: Integer;
|
function GetCursorCol: Integer;
|
||||||
function GetCursorRow: Integer;
|
function GetCursorRow: Integer;
|
||||||
|
|
@ -126,7 +154,6 @@ type
|
||||||
procedure UpdateScrollbar;
|
procedure UpdateScrollbar;
|
||||||
procedure WMEraseBkgnd(var Msg: TWMEraseBkgnd); message wm_EraseBkgnd;
|
procedure WMEraseBkgnd(var Msg: TWMEraseBkgnd); message wm_EraseBkgnd;
|
||||||
procedure WMGetDlgCode(var Msg: TMessage); message wm_GetDlgCode;
|
procedure WMGetDlgCode(var Msg: TMessage); message wm_GetDlgCode;
|
||||||
procedure WMTimer(var Msg: TWMTimer); message wm_Timer;
|
|
||||||
procedure WMVScroll(var Msg: TWMScroll); message wm_VScroll;
|
procedure WMVScroll(var Msg: TWMScroll); message wm_VScroll;
|
||||||
protected
|
protected
|
||||||
procedure CreateParams(var Params: TCreateParams); override;
|
procedure CreateParams(var Params: TCreateParams); override;
|
||||||
|
|
@ -136,11 +163,12 @@ type
|
||||||
public
|
public
|
||||||
constructor Create(AOwner: TComponent); override;
|
constructor Create(AOwner: TComponent); override;
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
procedure BeginUpdate;
|
|
||||||
procedure Clear;
|
procedure Clear;
|
||||||
procedure EndUpdate;
|
|
||||||
procedure Reset;
|
procedure Reset;
|
||||||
|
procedure FlipToScreen;
|
||||||
|
procedure TickBlink;
|
||||||
procedure Write(const S: string);
|
procedure Write(const S: string);
|
||||||
|
procedure WriteDeferred(const S: string);
|
||||||
property CursorCol: Integer read GetCursorCol;
|
property CursorCol: Integer read GetCursorCol;
|
||||||
property CursorRow: Integer read GetCursorRow;
|
property CursorRow: Integer read GetCursorRow;
|
||||||
published
|
published
|
||||||
|
|
@ -180,10 +208,8 @@ const
|
||||||
$00FFFFFF { 15 White (bright) }
|
$00FFFFFF { 15 White (bright) }
|
||||||
);
|
);
|
||||||
|
|
||||||
{ Blink timer interval. Win16 minimum is ~55 ms (18.2 Hz tick). }
|
{ Blink toggle interval in milliseconds (cursor + text blink). }
|
||||||
BlinkTimerMs = 55;
|
BlinkMs = 500;
|
||||||
{ Cursor and text blink toggle every 9 timer ticks (~500 ms). }
|
|
||||||
BlinkInterval = 9;
|
|
||||||
|
|
||||||
{ OUT_RASTER_PRECIS may not be defined in Delphi 1.0 WinTypes }
|
{ OUT_RASTER_PRECIS may not be defined in Delphi 1.0 WinTypes }
|
||||||
OutRasterPrecis = 6;
|
OutRasterPrecis = 6;
|
||||||
|
|
@ -263,12 +289,6 @@ begin
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
procedure TKPAnsi.BeginUpdate;
|
|
||||||
begin
|
|
||||||
Inc(FUpdateCount);
|
|
||||||
end;
|
|
||||||
|
|
||||||
|
|
||||||
procedure TKPAnsi.BuildAtlas;
|
procedure TKPAnsi.BuildAtlas;
|
||||||
{ Render all 256 CP437 characters into a monochrome bitmap, then extract }
|
{ Render all 256 CP437 characters into a monochrome bitmap, then extract }
|
||||||
{ per-glyph pixel masks into the glyph block at offset 64. Each glyph }
|
{ per-glyph pixel masks into the glyph block at offset 64. Each glyph }
|
||||||
|
|
@ -460,15 +480,13 @@ begin
|
||||||
FCellWidth := 8;
|
FCellWidth := 8;
|
||||||
FCellHeight := 16;
|
FCellHeight := 16;
|
||||||
FBlinkOn := True;
|
FBlinkOn := True;
|
||||||
FTimerActive := False;
|
FLastBlinkTick := GetTickCount;
|
||||||
FScrollPos := 0;
|
FScrollPos := 0;
|
||||||
FWrapMode := True;
|
FWrapMode := True;
|
||||||
FPaintFont := 0;
|
FPaintFont := 0;
|
||||||
FStockFont := False;
|
FStockFont := False;
|
||||||
FBlinkCount := 0;
|
|
||||||
FUpdateCount := 0;
|
|
||||||
FPendingScroll := 0;
|
|
||||||
FAllDirty := True;
|
FAllDirty := True;
|
||||||
|
FScrollbarDirty := False;
|
||||||
FTextBlinkOn := True;
|
FTextBlinkOn := True;
|
||||||
FRowBufSize := 0;
|
FRowBufSize := 0;
|
||||||
FGlyphBufH := 0;
|
FGlyphBufH := 0;
|
||||||
|
|
@ -625,11 +643,6 @@ end;
|
||||||
|
|
||||||
destructor TKPAnsi.Destroy;
|
destructor TKPAnsi.Destroy;
|
||||||
begin
|
begin
|
||||||
if FTimerActive and HandleAllocated then
|
|
||||||
begin
|
|
||||||
KillTimer(Handle, 1);
|
|
||||||
FTimerActive := False;
|
|
||||||
end;
|
|
||||||
DestroyRowBuffers;
|
DestroyRowBuffers;
|
||||||
if (FPaintFont <> 0) and not FStockFont then
|
if (FPaintFont <> 0) and not FStockFont then
|
||||||
begin
|
begin
|
||||||
|
|
@ -730,14 +743,12 @@ begin
|
||||||
FScreen.Insert(0, Line);
|
FScreen.Insert(0, Line);
|
||||||
{ Scroll down is rare; just repaint everything }
|
{ Scroll down is rare; just repaint everything }
|
||||||
FAllDirty := True;
|
FAllDirty := True;
|
||||||
FPendingScroll := 0;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
procedure TKPAnsi.DoScrollUp;
|
procedure TKPAnsi.DoScrollUp;
|
||||||
var
|
var
|
||||||
Line: PTermLine;
|
Line: PTermLine;
|
||||||
I: Integer;
|
|
||||||
begin
|
begin
|
||||||
if FScreen.Count < FRows then
|
if FScreen.Count < FRows then
|
||||||
Exit;
|
Exit;
|
||||||
|
|
@ -745,39 +756,19 @@ begin
|
||||||
Line := FScreen[0];
|
Line := FScreen[0];
|
||||||
FScrollback.Add(Line);
|
FScrollback.Add(Line);
|
||||||
FScreen.Delete(0);
|
FScreen.Delete(0);
|
||||||
TrimScrollback;
|
{ TrimScrollback deferred to ParseData for batching }
|
||||||
{ Add blank line at bottom }
|
{ Add blank line at bottom }
|
||||||
GetMem(Line, SizeOf(TTermLineRec));
|
GetMem(Line, SizeOf(TTermLineRec));
|
||||||
AllocLine(Line);
|
AllocLine(Line);
|
||||||
FScreen.Add(Line);
|
FScreen.Add(Line);
|
||||||
UpdateScrollbar;
|
FScrollbarDirty := True;
|
||||||
|
|
||||||
Inc(FPendingScroll);
|
{ Without ScrollDC, all rows must be re-rendered after a scroll }
|
||||||
if FPendingScroll >= FRows then
|
{ because the on-screen pixels haven't moved to match FScreen. }
|
||||||
begin
|
|
||||||
{ Scrolled more than one screen; just repaint everything }
|
|
||||||
FAllDirty := True;
|
FAllDirty := True;
|
||||||
FPendingScroll := 0;
|
|
||||||
end
|
|
||||||
else
|
|
||||||
begin
|
|
||||||
{ Shift dirty flags up to match the scrolled line positions }
|
|
||||||
for I := 1 to FRows - 1 do
|
|
||||||
FDirtyRow[I - 1] := FDirtyRow[I];
|
|
||||||
FDirtyRow[FRows - 1] := True;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
procedure TKPAnsi.EndUpdate;
|
|
||||||
begin
|
|
||||||
Dec(FUpdateCount);
|
|
||||||
if FUpdateCount <= 0 then
|
|
||||||
begin
|
|
||||||
FUpdateCount := 0;
|
|
||||||
FlipToScreen;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
|
|
||||||
procedure TKPAnsi.EraseDisplay(Mode: Integer);
|
procedure TKPAnsi.EraseDisplay(Mode: Integer);
|
||||||
|
|
@ -1232,8 +1223,7 @@ procedure TKPAnsi.FlipToScreen;
|
||||||
var
|
var
|
||||||
DC: HDC;
|
DC: HDC;
|
||||||
Row: Integer;
|
Row: Integer;
|
||||||
R: TRect;
|
HasDirty: Boolean;
|
||||||
ScrollY: Integer;
|
|
||||||
begin
|
begin
|
||||||
if not HandleAllocated then
|
if not HandleAllocated then
|
||||||
Exit;
|
Exit;
|
||||||
|
|
@ -1242,26 +1232,16 @@ begin
|
||||||
if FRowBuf = nil then
|
if FRowBuf = nil then
|
||||||
Exit;
|
Exit;
|
||||||
|
|
||||||
{ Scrollback view: force full redraw, ignore pending scroll }
|
{ Scrollback view: force full redraw (line mapping changes) }
|
||||||
if FScrollPos <> 0 then
|
if FScrollPos <> 0 then
|
||||||
begin
|
|
||||||
FAllDirty := True;
|
FAllDirty := True;
|
||||||
FPendingScroll := 0;
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ Deferred scroll: shift existing screen pixels up }
|
{ Deferred scrollbar update (batched from DoScrollUp) }
|
||||||
if (FPendingScroll > 0) and not FAllDirty then
|
if FScrollbarDirty then
|
||||||
begin
|
begin
|
||||||
R.Left := 0;
|
UpdateScrollbar;
|
||||||
R.Top := 0;
|
FScrollbarDirty := False;
|
||||||
R.Right := FCols * FCellWidth;
|
|
||||||
R.Bottom := FRows * FCellHeight;
|
|
||||||
ScrollY := FPendingScroll * FCellHeight;
|
|
||||||
DC := GetDC(Handle);
|
|
||||||
ScrollDC(DC, 0, -ScrollY, R, R, 0, nil);
|
|
||||||
ReleaseDC(Handle, DC);
|
|
||||||
end;
|
end;
|
||||||
FPendingScroll := 0;
|
|
||||||
|
|
||||||
{ Dirty old cursor row to erase ghost when cursor moved between rows }
|
{ Dirty old cursor row to erase ghost when cursor moved between rows }
|
||||||
if FCursorRow <> FLastCursorRow then
|
if FCursorRow <> FLastCursorRow then
|
||||||
|
|
@ -1273,6 +1253,22 @@ begin
|
||||||
FLastCursorRow := FCursorRow;
|
FLastCursorRow := FCursorRow;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
{ Early exit: skip GetDC/ReleaseDC when nothing needs rendering }
|
||||||
|
if not FAllDirty then
|
||||||
|
begin
|
||||||
|
HasDirty := False;
|
||||||
|
for Row := 0 to FRows - 1 do
|
||||||
|
begin
|
||||||
|
if FDirtyRow[Row] then
|
||||||
|
begin
|
||||||
|
HasDirty := True;
|
||||||
|
Break;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
if not HasDirty then
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
{ Interleaved render + blast: single buffer is reused per row }
|
{ Interleaved render + blast: single buffer is reused per row }
|
||||||
DC := GetDC(Handle);
|
DC := GetDC(Handle);
|
||||||
for Row := 0 to FRows - 1 do
|
for Row := 0 to FRows - 1 do
|
||||||
|
|
@ -1488,7 +1484,6 @@ begin
|
||||||
Exit;
|
Exit;
|
||||||
|
|
||||||
{ Full repaint: render each row into the shared buffer and blast it }
|
{ Full repaint: render each row into the shared buffer and blast it }
|
||||||
FPendingScroll := 0;
|
|
||||||
FAllDirty := True;
|
FAllDirty := True;
|
||||||
|
|
||||||
for Row := 0 to FRows - 1 do
|
for Row := 0 to FRows - 1 do
|
||||||
|
|
@ -1509,28 +1504,90 @@ end;
|
||||||
|
|
||||||
|
|
||||||
procedure TKPAnsi.ParseData(const S: string);
|
procedure TKPAnsi.ParseData(const S: string);
|
||||||
|
{ Process incoming data with an inlined fast path for printable characters. }
|
||||||
|
{ ~80% of BBS data is printable text in normal state. Inlining avoids the }
|
||||||
|
{ per-character method call to ProcessChar, and caching the Line pointer }
|
||||||
|
{ eliminates repeated TList lookups for consecutive chars on the same row. }
|
||||||
|
{ }
|
||||||
|
{ Does NOT call FlipToScreen -- the caller (Write) calls FlipToScreen }
|
||||||
|
{ after ParseData returns, ensuring immediate rendering. }
|
||||||
var
|
var
|
||||||
I: Integer;
|
I: Integer;
|
||||||
|
Ch: Char;
|
||||||
|
Line: PTermLine;
|
||||||
|
FGIdx: Byte;
|
||||||
|
BGIdx: Byte;
|
||||||
begin
|
begin
|
||||||
|
Line := nil;
|
||||||
|
|
||||||
for I := 1 to Length(S) do
|
for I := 1 to Length(S) do
|
||||||
ProcessChar(S[I]);
|
begin
|
||||||
|
Ch := S[I];
|
||||||
|
|
||||||
|
{ Fast path: printable character in normal state }
|
||||||
|
if (FParseState = psNormal) and (Ch >= ' ') then
|
||||||
|
begin
|
||||||
|
if FCursorCol >= FCols then
|
||||||
|
begin
|
||||||
|
if FWrapMode then
|
||||||
|
begin
|
||||||
|
FCursorCol := 0;
|
||||||
|
Inc(FCursorRow);
|
||||||
|
if FCursorRow >= FRows then
|
||||||
|
begin
|
||||||
|
FCursorRow := FRows - 1;
|
||||||
|
DoScrollUp;
|
||||||
|
end;
|
||||||
|
Line := nil;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
FCursorCol := FCols - 1;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if Line = nil then
|
||||||
|
Line := FScreen[FCursorRow];
|
||||||
|
|
||||||
|
if FAttrBold then
|
||||||
|
FGIdx := FAttrFG + 8
|
||||||
|
else
|
||||||
|
FGIdx := FAttrFG;
|
||||||
|
BGIdx := FAttrBG;
|
||||||
|
|
||||||
|
if FAttrReverse then
|
||||||
|
begin
|
||||||
|
Line^.Cells[FCursorCol].FG := BGIdx;
|
||||||
|
Line^.Cells[FCursorCol].BG := FGIdx;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
Line^.Cells[FCursorCol].FG := FGIdx;
|
||||||
|
Line^.Cells[FCursorCol].BG := BGIdx;
|
||||||
|
end;
|
||||||
|
Line^.Cells[FCursorCol].Ch := Ch;
|
||||||
|
Line^.Cells[FCursorCol].Bold := FAttrBold;
|
||||||
|
Line^.Cells[FCursorCol].Blink := FAttrBlink;
|
||||||
|
FDirtyRow[FCursorRow] := True;
|
||||||
|
Inc(FCursorCol);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
{ Slow path: control chars, escape sequences }
|
||||||
|
Line := nil;
|
||||||
|
ProcessChar(Ch);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ Deferred scrollback trim -- batched from DoScrollUp }
|
||||||
|
TrimScrollback;
|
||||||
|
|
||||||
{ Snap to bottom on new data }
|
{ Snap to bottom on new data }
|
||||||
if FScrollPos <> 0 then
|
if FScrollPos <> 0 then
|
||||||
begin
|
begin
|
||||||
FScrollPos := 0;
|
FScrollPos := 0;
|
||||||
UpdateScrollbar;
|
FScrollbarDirty := True;
|
||||||
FAllDirty := True;
|
FAllDirty := True;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ Reset cursor blink to visible on new data }
|
|
||||||
FBlinkOn := True;
|
|
||||||
|
|
||||||
{ Render immediately -- no throttle. Data hits the screen as soon }
|
|
||||||
{ as it arrives. BeginUpdate/EndUpdate suppresses intermediate }
|
|
||||||
{ renders when the caller is batching multiple Write calls. }
|
|
||||||
if FUpdateCount = 0 then
|
|
||||||
FlipToScreen;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1818,13 +1875,6 @@ begin
|
||||||
CreateRowBuffers;
|
CreateRowBuffers;
|
||||||
FAllDirty := True;
|
FAllDirty := True;
|
||||||
|
|
||||||
{ Start render/blink timer }
|
|
||||||
if not FTimerActive then
|
|
||||||
begin
|
|
||||||
SetTimer(Handle, 1, BlinkTimerMs, nil);
|
|
||||||
FTimerActive := True;
|
|
||||||
end;
|
|
||||||
|
|
||||||
Invalidate;
|
Invalidate;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
@ -2212,15 +2262,28 @@ end;
|
||||||
|
|
||||||
|
|
||||||
procedure TKPAnsi.TrimScrollback;
|
procedure TKPAnsi.TrimScrollback;
|
||||||
|
{ Batch-optimized: free excess items, shift remainder down in one pass, }
|
||||||
|
{ then shrink from the end. O(n) total vs O(k*n) for k front-deletions. }
|
||||||
var
|
var
|
||||||
|
Excess: Integer;
|
||||||
|
I: Integer;
|
||||||
Line: PTermLine;
|
Line: PTermLine;
|
||||||
begin
|
begin
|
||||||
while FScrollback.Count > FScrollbackSize do
|
Excess := FScrollback.Count - FScrollbackSize;
|
||||||
|
if Excess <= 0 then
|
||||||
|
Exit;
|
||||||
|
{ Free the oldest lines }
|
||||||
|
for I := 0 to Excess - 1 do
|
||||||
begin
|
begin
|
||||||
Line := FScrollback[0];
|
Line := FScrollback[I];
|
||||||
FreeMem(Line, SizeOf(TTermLineRec));
|
FreeMem(Line, SizeOf(TTermLineRec));
|
||||||
FScrollback.Delete(0);
|
|
||||||
end;
|
end;
|
||||||
|
{ Shift remaining items down in one pass }
|
||||||
|
for I := 0 to FScrollback.Count - Excess - 1 do
|
||||||
|
FScrollback[I] := FScrollback[I + Excess];
|
||||||
|
{ Remove excess slots from the end (O(1) per deletion) }
|
||||||
|
for I := 1 to Excess do
|
||||||
|
FScrollback.Delete(FScrollback.Count - 1);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2257,20 +2320,18 @@ begin
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
procedure TKPAnsi.WMTimer(var Msg: TWMTimer);
|
procedure TKPAnsi.TickBlink;
|
||||||
|
var
|
||||||
|
Now: Longint;
|
||||||
begin
|
begin
|
||||||
{ Blink counter: toggle cursor and text blink every BlinkInterval ticks }
|
Now := GetTickCount;
|
||||||
Inc(FBlinkCount);
|
if Now - FLastBlinkTick >= BlinkMs then
|
||||||
if FBlinkCount >= BlinkInterval then
|
|
||||||
begin
|
begin
|
||||||
FBlinkCount := 0;
|
FLastBlinkTick := Now;
|
||||||
FBlinkOn := not FBlinkOn;
|
FBlinkOn := not FBlinkOn;
|
||||||
FTextBlinkOn := not FTextBlinkOn;
|
FTextBlinkOn := not FTextBlinkOn;
|
||||||
DirtyBlinkRows;
|
DirtyBlinkRows;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ Render blink changes and any stale dirty rows }
|
|
||||||
FlipToScreen;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2316,6 +2377,16 @@ end;
|
||||||
|
|
||||||
|
|
||||||
procedure TKPAnsi.Write(const S: string);
|
procedure TKPAnsi.Write(const S: string);
|
||||||
|
begin
|
||||||
|
if Length(S) > 0 then
|
||||||
|
begin
|
||||||
|
ParseData(S);
|
||||||
|
FlipToScreen;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
procedure TKPAnsi.WriteDeferred(const S: string);
|
||||||
begin
|
begin
|
||||||
if Length(S) > 0 then
|
if Length(S) > 0 then
|
||||||
ParseData(S);
|
ParseData(S);
|
||||||
|
|
|
||||||
|
|
@ -5,80 +5,54 @@ unit KPComm;
|
||||||
{ TKPComm is a non-visual TComponent descendant providing RS-232 serial }
|
{ TKPComm is a non-visual TComponent descendant providing RS-232 serial }
|
||||||
{ I/O via the Windows 3.1 comm API. Installs to the "KP" palette tab. }
|
{ I/O via the Windows 3.1 comm API. Installs to the "KP" palette tab. }
|
||||||
{ }
|
{ }
|
||||||
{ Port lifecycle: OpenComm -> BuildCommDCB + SetCommState -> }
|
{ Port lifecycle: OpenComm -> BuildCommDCB + SetCommState -> CloseComm. }
|
||||||
{ EnableCommNotification -> CloseComm. }
|
|
||||||
{ }
|
{ }
|
||||||
{ WM_COMMNOTIFY messages are received through a hidden utility window }
|
{ Data is read by polling Input (ReadComm) from a PeekMessage main loop }
|
||||||
{ with a registered class and dispatched to ProcessReceiveNotify, }
|
{ rather than through WM_COMMNOTIFY event dispatch. }
|
||||||
{ ProcessTransmitNotify, and ProcessEventNotify. }
|
|
||||||
{ }
|
|
||||||
{ Modem line status (CTS/DSR/CD) is tracked via shadow booleans toggled }
|
|
||||||
{ on transition events from GetCommEventMask, since the 16-bit comm API }
|
|
||||||
{ only provides transition events, not absolute line levels. }
|
|
||||||
|
|
||||||
interface
|
interface
|
||||||
|
|
||||||
uses
|
uses
|
||||||
SysUtils, Classes, WinTypes, WinProcs, Messages;
|
SysUtils, Classes, WinTypes, WinProcs, Messages;
|
||||||
|
|
||||||
const
|
|
||||||
{ Communication events }
|
|
||||||
comEvReceive = 1;
|
|
||||||
comEvSend = 2;
|
|
||||||
comEvCTS = 3;
|
|
||||||
comEvDSR = 4;
|
|
||||||
comEvCD = 5;
|
|
||||||
comEvRing = 6;
|
|
||||||
comEvEOF = 7;
|
|
||||||
|
|
||||||
{ Error events }
|
|
||||||
comEvtBreak = 1001;
|
|
||||||
comEvtFrame = 1004;
|
|
||||||
comEvtOverrun = 1006;
|
|
||||||
comEvtRxOver = 1008;
|
|
||||||
comEvtParity = 1009;
|
|
||||||
comEvtTxFull = 1010;
|
|
||||||
|
|
||||||
type
|
type
|
||||||
THandshaking = (hsNone, hsXonXoff, hsRtsCts, hsBoth);
|
THandshaking = (hsNone, hsXonXoff, hsRtsCts, hsBoth);
|
||||||
TInputMode = (imText, imBinary);
|
TInputMode = (imText, imBinary);
|
||||||
|
|
||||||
TKPComm = class(TComponent)
|
TKPComm = class(TComponent)
|
||||||
private
|
private
|
||||||
FCommId: Integer;
|
{ Port state }
|
||||||
FHWndNotify: HWnd;
|
FCommId: Integer; { Comm port handle from OpenComm (-1 = closed) }
|
||||||
FCommPort: Integer;
|
|
||||||
FSettings: string;
|
{ Configuration (published properties) }
|
||||||
FPortOpen: Boolean;
|
FCommPort: Integer; { Port number (1-based: 1=COM1, 2=COM2, ...) }
|
||||||
FInBufferSize: Integer;
|
FSettings: string; { Baud/parity/data/stop string (e.g. '9600,N,8,1') }
|
||||||
FOutBufferSize: Integer;
|
FPortOpen: Boolean; { True while port is open and operational }
|
||||||
FRThreshold: Integer;
|
FInBufferSize: Integer; { Receive ring buffer size in bytes }
|
||||||
FSThreshold: Integer;
|
FOutBufferSize: Integer; { Transmit ring buffer size in bytes }
|
||||||
FHandshaking: THandshaking;
|
FHandshaking: THandshaking; { Flow control mode (none/XonXoff/RtsCts/both) }
|
||||||
FInputLen: Integer;
|
FInputLen: Integer; { Max bytes per Input read (0=up to 255) }
|
||||||
FInputMode: TInputMode;
|
FInputMode: TInputMode; { Text or binary read mode }
|
||||||
FDTREnable: Boolean;
|
|
||||||
FRTSEnable: Boolean;
|
{ Modem control lines }
|
||||||
FNullDiscard: Boolean;
|
FDTREnable: Boolean; { DTR line state (True=asserted) }
|
||||||
FEOFEnable: Boolean;
|
FRTSEnable: Boolean; { RTS line state (True=asserted) }
|
||||||
FParityReplace: string;
|
|
||||||
FBreakState: Boolean;
|
{ DCB options }
|
||||||
FCommEvent: Integer;
|
FNullDiscard: Boolean; { Strip null bytes from received data }
|
||||||
FCTSState: Boolean;
|
FEOFEnable: Boolean; { Detect EOF character in stream }
|
||||||
FDSRState: Boolean;
|
FParityReplace: string; { Replacement char for parity errors ('' = none) }
|
||||||
FCDState: Boolean;
|
|
||||||
FOnComm: TNotifyEvent;
|
{ Runtime state }
|
||||||
|
FBreakState: Boolean; { True while break signal is being sent }
|
||||||
|
|
||||||
procedure ApplyHandshaking;
|
procedure ApplyHandshaking;
|
||||||
procedure ApplyOptions;
|
procedure ApplyOptions;
|
||||||
procedure ClosePort;
|
procedure ClosePort;
|
||||||
procedure DoCommEvent(EventCode: Integer);
|
|
||||||
function GetInBufferCount: Integer;
|
function GetInBufferCount: Integer;
|
||||||
function GetInput: string;
|
function GetInput: string;
|
||||||
function GetOutBufferCount: Integer;
|
function GetOutBufferCount: Integer;
|
||||||
procedure OpenPort;
|
procedure OpenPort;
|
||||||
procedure ProcessEventNotify;
|
|
||||||
procedure ProcessReceiveNotify;
|
|
||||||
procedure ProcessTransmitNotify;
|
|
||||||
procedure SetBreak(Value: Boolean);
|
procedure SetBreak(Value: Boolean);
|
||||||
procedure SetCommPort(Value: Integer);
|
procedure SetCommPort(Value: Integer);
|
||||||
procedure SetDTREnable(Value: Boolean);
|
procedure SetDTREnable(Value: Boolean);
|
||||||
|
|
@ -98,19 +72,13 @@ type
|
||||||
property Output: string write SetOutput;
|
property Output: string write SetOutput;
|
||||||
property InBufferCount: Integer read GetInBufferCount;
|
property InBufferCount: Integer read GetInBufferCount;
|
||||||
property OutBufferCount: Integer read GetOutBufferCount;
|
property OutBufferCount: Integer read GetOutBufferCount;
|
||||||
property CDHolding: Boolean read FCDState;
|
|
||||||
property CTSHolding: Boolean read FCTSState;
|
|
||||||
property DSRHolding: Boolean read FDSRState;
|
|
||||||
property Break: Boolean read FBreakState write SetBreak;
|
property Break: Boolean read FBreakState write SetBreak;
|
||||||
property CommEvent: Integer read FCommEvent;
|
|
||||||
published
|
published
|
||||||
property CommPort: Integer read FCommPort write SetCommPort default 1;
|
property CommPort: Integer read FCommPort write SetCommPort default 1;
|
||||||
property Settings: string read FSettings write SetSettings;
|
property Settings: string read FSettings write SetSettings;
|
||||||
property PortOpen: Boolean read FPortOpen write SetPortOpen default False;
|
property PortOpen: Boolean read FPortOpen write SetPortOpen default False;
|
||||||
property InBufferSize: Integer read FInBufferSize write SetInBufferSize default 4096;
|
property InBufferSize: Integer read FInBufferSize write SetInBufferSize default 4096;
|
||||||
property OutBufferSize: Integer read FOutBufferSize write SetOutBufferSize default 4096;
|
property OutBufferSize: Integer read FOutBufferSize write SetOutBufferSize default 4096;
|
||||||
property RThreshold: Integer read FRThreshold write FRThreshold default 0;
|
|
||||||
property SThreshold: Integer read FSThreshold write FSThreshold default 0;
|
|
||||||
property Handshaking: THandshaking read FHandshaking write SetHandshaking default hsNone;
|
property Handshaking: THandshaking read FHandshaking write SetHandshaking default hsNone;
|
||||||
property InputLen: Integer read FInputLen write FInputLen default 0;
|
property InputLen: Integer read FInputLen write FInputLen default 0;
|
||||||
property InputMode: TInputMode read FInputMode write FInputMode default imText;
|
property InputMode: TInputMode read FInputMode write FInputMode default imText;
|
||||||
|
|
@ -119,7 +87,6 @@ type
|
||||||
property NullDiscard: Boolean read FNullDiscard write SetNullDiscard default False;
|
property NullDiscard: Boolean read FNullDiscard write SetNullDiscard default False;
|
||||||
property EOFEnable: Boolean read FEOFEnable write FEOFEnable default False;
|
property EOFEnable: Boolean read FEOFEnable write FEOFEnable default False;
|
||||||
property ParityReplace: string read FParityReplace write SetParityReplace;
|
property ParityReplace: string read FParityReplace write SetParityReplace;
|
||||||
property OnComm: TNotifyEvent read FOnComm write FOnComm;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure Register;
|
procedure Register;
|
||||||
|
|
@ -127,11 +94,6 @@ procedure Register;
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
const
|
const
|
||||||
{ WM_COMMNOTIFY notification codes }
|
|
||||||
CN_RECEIVE = $0001;
|
|
||||||
CN_TRANSMIT = $0002;
|
|
||||||
CN_EVENT = $0004;
|
|
||||||
|
|
||||||
{ DCB Flags field bit masks. The Windows 3.1 DCB packs thirteen 1-bit }
|
{ DCB Flags field bit masks. The Windows 3.1 DCB packs thirteen 1-bit }
|
||||||
{ fields into a single UINT (Word) at offset 12 of the structure. }
|
{ fields into a single UINT (Word) at offset 12 of the structure. }
|
||||||
{ Delphi 1.0 maps this UINT to TDCB.Flags. }
|
{ Delphi 1.0 maps this UINT to TDCB.Flags. }
|
||||||
|
|
@ -149,46 +111,6 @@ const
|
||||||
dcbDtrflow = $0800;
|
dcbDtrflow = $0800;
|
||||||
dcbRtsflow = $1000;
|
dcbRtsflow = $1000;
|
||||||
|
|
||||||
{ Hidden notification window class name }
|
|
||||||
NotifyClassName = 'KPCommNotify';
|
|
||||||
|
|
||||||
var
|
|
||||||
NotifyClassRegistered: Boolean;
|
|
||||||
|
|
||||||
|
|
||||||
{ ----------------------------------------------------------------------- }
|
|
||||||
{ Hidden notification window procedure }
|
|
||||||
{ }
|
|
||||||
{ Receives WM_COMMNOTIFY from the comm driver. Retrieves the TKPComm }
|
|
||||||
{ instance pointer stored in the window's extra bytes (offset 0) and }
|
|
||||||
{ dispatches to the appropriate Process* method. }
|
|
||||||
{ ----------------------------------------------------------------------- }
|
|
||||||
|
|
||||||
function NotifyWndProc(Wnd: HWnd; Msg: Word;
|
|
||||||
WParam: Word; LParam: Longint): Longint; export;
|
|
||||||
var
|
|
||||||
Comm: TKPComm;
|
|
||||||
NotifyCode: Word;
|
|
||||||
begin
|
|
||||||
if Msg = wm_CommNotify then
|
|
||||||
begin
|
|
||||||
Comm := TKPComm(GetWindowLong(Wnd, 0));
|
|
||||||
if (Comm <> nil) and Comm.FPortOpen and (Comm.FCommId >= 0) then
|
|
||||||
begin
|
|
||||||
NotifyCode := Word(LParam);
|
|
||||||
if (NotifyCode and CN_RECEIVE) <> 0 then
|
|
||||||
Comm.ProcessReceiveNotify;
|
|
||||||
if (NotifyCode and CN_TRANSMIT) <> 0 then
|
|
||||||
Comm.ProcessTransmitNotify;
|
|
||||||
if (NotifyCode and CN_EVENT) <> 0 then
|
|
||||||
Comm.ProcessEventNotify;
|
|
||||||
end;
|
|
||||||
Result := 0;
|
|
||||||
end
|
|
||||||
else
|
|
||||||
Result := DefWindowProc(Wnd, Msg, WParam, LParam);
|
|
||||||
end;
|
|
||||||
|
|
||||||
|
|
||||||
{ ----------------------------------------------------------------------- }
|
{ ----------------------------------------------------------------------- }
|
||||||
{ TKPComm }
|
{ TKPComm }
|
||||||
|
|
@ -265,7 +187,6 @@ end;
|
||||||
|
|
||||||
procedure TKPComm.ClosePort;
|
procedure TKPComm.ClosePort;
|
||||||
begin
|
begin
|
||||||
{ Set FPortOpen first to prevent stale WM_COMMNOTIFY processing }
|
|
||||||
FPortOpen := False;
|
FPortOpen := False;
|
||||||
|
|
||||||
if FCommId >= 0 then
|
if FCommId >= 0 then
|
||||||
|
|
@ -280,16 +201,6 @@ begin
|
||||||
CloseComm(FCommId);
|
CloseComm(FCommId);
|
||||||
FCommId := -1;
|
FCommId := -1;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if FHWndNotify <> 0 then
|
|
||||||
begin
|
|
||||||
DestroyWindow(FHWndNotify);
|
|
||||||
FHWndNotify := 0;
|
|
||||||
end;
|
|
||||||
|
|
||||||
FCTSState := False;
|
|
||||||
FDSRState := False;
|
|
||||||
FCDState := False;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -297,14 +208,11 @@ constructor TKPComm.Create(AOwner: TComponent);
|
||||||
begin
|
begin
|
||||||
inherited Create(AOwner);
|
inherited Create(AOwner);
|
||||||
FCommId := -1;
|
FCommId := -1;
|
||||||
FHWndNotify := 0;
|
|
||||||
FCommPort := 1;
|
FCommPort := 1;
|
||||||
FSettings := '9600,N,8,1';
|
FSettings := '9600,N,8,1';
|
||||||
FPortOpen := False;
|
FPortOpen := False;
|
||||||
FInBufferSize := 4096;
|
FInBufferSize := 4096;
|
||||||
FOutBufferSize := 4096;
|
FOutBufferSize := 4096;
|
||||||
FRThreshold := 0;
|
|
||||||
FSThreshold := 0;
|
|
||||||
FHandshaking := hsNone;
|
FHandshaking := hsNone;
|
||||||
FInputLen := 0;
|
FInputLen := 0;
|
||||||
FInputMode := imText;
|
FInputMode := imText;
|
||||||
|
|
@ -314,10 +222,6 @@ begin
|
||||||
FEOFEnable := False;
|
FEOFEnable := False;
|
||||||
FParityReplace := '?';
|
FParityReplace := '?';
|
||||||
FBreakState := False;
|
FBreakState := False;
|
||||||
FCommEvent := 0;
|
|
||||||
FCTSState := False;
|
|
||||||
FDSRState := False;
|
|
||||||
FCDState := False;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -329,14 +233,6 @@ begin
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
procedure TKPComm.DoCommEvent(EventCode: Integer);
|
|
||||||
begin
|
|
||||||
FCommEvent := EventCode;
|
|
||||||
if Assigned(FOnComm) then
|
|
||||||
FOnComm(Self);
|
|
||||||
end;
|
|
||||||
|
|
||||||
|
|
||||||
function TKPComm.GetInBufferCount: Integer;
|
function TKPComm.GetInBufferCount: Integer;
|
||||||
var
|
var
|
||||||
Stat: TComStat;
|
Stat: TComStat;
|
||||||
|
|
@ -391,12 +287,9 @@ end;
|
||||||
|
|
||||||
procedure TKPComm.OpenPort;
|
procedure TKPComm.OpenPort;
|
||||||
var
|
var
|
||||||
WC: TWndClass;
|
|
||||||
DCB: TDCB;
|
DCB: TDCB;
|
||||||
Buf: array[0..255] of Char;
|
Buf: array[0..255] of Char;
|
||||||
Setting: string;
|
Setting: string;
|
||||||
RxTh: Integer;
|
|
||||||
TxTh: Integer;
|
|
||||||
begin
|
begin
|
||||||
{ Open the comm port }
|
{ Open the comm port }
|
||||||
StrPCopy(Buf, 'COM' + IntToStr(FCommPort));
|
StrPCopy(Buf, 'COM' + IntToStr(FCommPort));
|
||||||
|
|
@ -447,134 +340,10 @@ begin
|
||||||
else
|
else
|
||||||
EscapeCommFunction(FCommId, CLRRTS);
|
EscapeCommFunction(FCommId, CLRRTS);
|
||||||
|
|
||||||
{ Register notification window class (once per process) }
|
|
||||||
if not NotifyClassRegistered then
|
|
||||||
begin
|
|
||||||
FillChar(WC, SizeOf(WC), 0);
|
|
||||||
WC.lpfnWndProc := @NotifyWndProc;
|
|
||||||
WC.cbWndExtra := SizeOf(Longint);
|
|
||||||
WC.hInstance := HInstance;
|
|
||||||
WC.lpszClassName := NotifyClassName;
|
|
||||||
if not RegisterClass(WC) then
|
|
||||||
begin
|
|
||||||
CloseComm(FCommId);
|
|
||||||
FCommId := -1;
|
|
||||||
raise Exception.Create('RegisterClass failed');
|
|
||||||
end;
|
|
||||||
NotifyClassRegistered := True;
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ Create hidden notification window and store Self for dispatch }
|
|
||||||
FHWndNotify := CreateWindow(NotifyClassName, '', ws_Popup,
|
|
||||||
0, 0, 0, 0, 0, 0, HInstance, nil);
|
|
||||||
if FHWndNotify = 0 then
|
|
||||||
begin
|
|
||||||
CloseComm(FCommId);
|
|
||||||
FCommId := -1;
|
|
||||||
raise Exception.Create('CreateWindow failed');
|
|
||||||
end;
|
|
||||||
SetWindowLong(FHWndNotify, 0, Longint(Self));
|
|
||||||
|
|
||||||
{ Enable event mask for modem status, errors, and breaks }
|
|
||||||
SetCommEventMask(FCommId,
|
|
||||||
ev_CTS or ev_DSR or ev_RLSD or ev_Ring or
|
|
||||||
ev_Err or ev_Break or ev_RxChar);
|
|
||||||
|
|
||||||
{ Enable comm notifications -- -1 disables the notification }
|
|
||||||
if FRThreshold > 0 then
|
|
||||||
RxTh := FRThreshold
|
|
||||||
else
|
|
||||||
RxTh := -1;
|
|
||||||
if FSThreshold > 0 then
|
|
||||||
TxTh := FSThreshold
|
|
||||||
else
|
|
||||||
TxTh := -1;
|
|
||||||
EnableCommNotification(FCommId, FHWndNotify, RxTh, TxTh);
|
|
||||||
|
|
||||||
{ Reset modem line shadow state }
|
|
||||||
FCTSState := False;
|
|
||||||
FDSRState := False;
|
|
||||||
FCDState := False;
|
|
||||||
|
|
||||||
FPortOpen := True;
|
FPortOpen := True;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
procedure TKPComm.ProcessEventNotify;
|
|
||||||
var
|
|
||||||
EvtMask: Word;
|
|
||||||
Stat: TComStat;
|
|
||||||
ErrFlags: Integer;
|
|
||||||
begin
|
|
||||||
EvtMask := GetCommEventMask(FCommId,
|
|
||||||
ev_CTS or ev_DSR or ev_RLSD or ev_Ring or ev_Err or ev_Break);
|
|
||||||
|
|
||||||
if (EvtMask and ev_Break) <> 0 then
|
|
||||||
DoCommEvent(comEvtBreak);
|
|
||||||
|
|
||||||
{ Modem line shadow state: toggle on each transition event. }
|
|
||||||
{ The 16-bit comm API only provides transition events, not }
|
|
||||||
{ absolute line levels. }
|
|
||||||
if (EvtMask and ev_CTS) <> 0 then
|
|
||||||
begin
|
|
||||||
FCTSState := not FCTSState;
|
|
||||||
DoCommEvent(comEvCTS);
|
|
||||||
end;
|
|
||||||
|
|
||||||
if (EvtMask and ev_DSR) <> 0 then
|
|
||||||
begin
|
|
||||||
FDSRState := not FDSRState;
|
|
||||||
DoCommEvent(comEvDSR);
|
|
||||||
end;
|
|
||||||
|
|
||||||
if (EvtMask and ev_RLSD) <> 0 then
|
|
||||||
begin
|
|
||||||
FCDState := not FCDState;
|
|
||||||
DoCommEvent(comEvCD);
|
|
||||||
end;
|
|
||||||
|
|
||||||
if (EvtMask and ev_Ring) <> 0 then
|
|
||||||
DoCommEvent(comEvRing);
|
|
||||||
|
|
||||||
if (EvtMask and ev_Err) <> 0 then
|
|
||||||
begin
|
|
||||||
ErrFlags := GetCommError(FCommId, Stat);
|
|
||||||
if (ErrFlags and ce_Frame) <> 0 then
|
|
||||||
DoCommEvent(comEvtFrame);
|
|
||||||
if (ErrFlags and ce_Overrun) <> 0 then
|
|
||||||
DoCommEvent(comEvtOverrun);
|
|
||||||
if (ErrFlags and ce_RxOver) <> 0 then
|
|
||||||
DoCommEvent(comEvtRxOver);
|
|
||||||
if (ErrFlags and ce_RxParity) <> 0 then
|
|
||||||
DoCommEvent(comEvtParity);
|
|
||||||
if (ErrFlags and ce_TxFull) <> 0 then
|
|
||||||
DoCommEvent(comEvtTxFull);
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
|
|
||||||
procedure TKPComm.ProcessReceiveNotify;
|
|
||||||
begin
|
|
||||||
if FRThreshold <= 0 then
|
|
||||||
Exit;
|
|
||||||
{ WM_COMMNOTIFY with CN_RECEIVE means data is available -- the driver }
|
|
||||||
{ already checked the threshold. No need to call GetCommError here. }
|
|
||||||
DoCommEvent(comEvReceive);
|
|
||||||
end;
|
|
||||||
|
|
||||||
|
|
||||||
procedure TKPComm.ProcessTransmitNotify;
|
|
||||||
var
|
|
||||||
Stat: TComStat;
|
|
||||||
begin
|
|
||||||
if FSThreshold <= 0 then
|
|
||||||
Exit;
|
|
||||||
GetCommError(FCommId, Stat);
|
|
||||||
if Integer(Stat.cbOutQue) <= FSThreshold then
|
|
||||||
DoCommEvent(comEvSend);
|
|
||||||
end;
|
|
||||||
|
|
||||||
|
|
||||||
procedure TKPComm.SetBreak(Value: Boolean);
|
procedure TKPComm.SetBreak(Value: Boolean);
|
||||||
begin
|
begin
|
||||||
FBreakState := Value;
|
FBreakState := Value;
|
||||||
|
|
@ -657,7 +426,7 @@ begin
|
||||||
begin
|
begin
|
||||||
Written := WriteComm(FCommId, @Value[1], Length(Value));
|
Written := WriteComm(FCommId, @Value[1], Length(Value));
|
||||||
if Written < 0 then
|
if Written < 0 then
|
||||||
DoCommEvent(comEvtTxFull);
|
raise Exception.Create('WriteComm failed');
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,5 @@ uses
|
||||||
|
|
||||||
begin
|
begin
|
||||||
Application.CreateForm(TMainForm, MainForm);
|
Application.CreateForm(TMainForm, MainForm);
|
||||||
Application.Run;
|
MainForm.Run;
|
||||||
end.
|
end.
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ unit TestMain;
|
||||||
{ }
|
{ }
|
||||||
{ Layout: toolbar row at top (port, settings, open/close, status), }
|
{ Layout: toolbar row at top (port, settings, open/close, status), }
|
||||||
{ TKPAnsi terminal filling the rest of the form. Received serial data }
|
{ TKPAnsi terminal filling the rest of the form. Received serial data }
|
||||||
{ is fed to the terminal via TKPAnsi.Write; keystrokes from the terminal }
|
{ is polled from TKPComm.Input in a PeekMessage main loop; keystrokes }
|
||||||
{ are sent out via TKPComm.Output. }
|
{ from the terminal are sent out via TKPComm.Output. }
|
||||||
|
|
||||||
interface
|
interface
|
||||||
|
|
||||||
|
|
@ -17,22 +17,27 @@ uses
|
||||||
type
|
type
|
||||||
TMainForm = class(TForm)
|
TMainForm = class(TForm)
|
||||||
private
|
private
|
||||||
FComm: TKPComm;
|
{ Components (owned by Self, freed automatically) }
|
||||||
FAnsi: TKPAnsi;
|
FComm: TKPComm; { Serial communications component }
|
||||||
FLabelPort: TLabel;
|
FAnsi: TKPAnsi; { ANSI terminal display }
|
||||||
FEditPort: TEdit;
|
|
||||||
FLabelSettings: TLabel;
|
{ Toolbar controls }
|
||||||
FEditSettings: TEdit;
|
FLabelPort: TLabel; { "Port:" label }
|
||||||
FBtnOpen: TButton;
|
FEditPort: TEdit; { COM port number entry }
|
||||||
FBtnClose: TButton;
|
FLabelSettings: TLabel; { "Settings:" label }
|
||||||
FLabelStatus: TLabel;
|
FEditSettings: TEdit; { Baud/parity/data/stop entry }
|
||||||
|
FBtnOpen: TButton; { Opens the serial port }
|
||||||
|
FBtnClose: TButton; { Closes the serial port }
|
||||||
|
FLabelStatus: TLabel; { Displays "Open" or "Closed" }
|
||||||
|
|
||||||
|
FDone: Boolean; { True when WM_QUIT received }
|
||||||
procedure AnsiKeyData(Sender: TObject; const Data: string);
|
procedure AnsiKeyData(Sender: TObject; const Data: string);
|
||||||
procedure BtnCloseClick(Sender: TObject);
|
procedure BtnCloseClick(Sender: TObject);
|
||||||
procedure BtnOpenClick(Sender: TObject);
|
procedure BtnOpenClick(Sender: TObject);
|
||||||
procedure CommEvent(Sender: TObject);
|
|
||||||
procedure UpdateStatus;
|
procedure UpdateStatus;
|
||||||
public
|
public
|
||||||
constructor Create(AOwner: TComponent); override;
|
constructor Create(AOwner: TComponent); override;
|
||||||
|
procedure Run;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
var
|
var
|
||||||
|
|
@ -67,7 +72,6 @@ begin
|
||||||
try
|
try
|
||||||
FComm.CommPort := StrToInt(FEditPort.Text);
|
FComm.CommPort := StrToInt(FEditPort.Text);
|
||||||
FComm.Settings := FEditSettings.Text;
|
FComm.Settings := FEditSettings.Text;
|
||||||
FComm.RThreshold := 1;
|
|
||||||
FComm.PortOpen := True;
|
FComm.PortOpen := True;
|
||||||
except
|
except
|
||||||
on E: Exception do
|
on E: Exception do
|
||||||
|
|
@ -78,31 +82,6 @@ begin
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
procedure TMainForm.CommEvent(Sender: TObject);
|
|
||||||
var
|
|
||||||
S: string;
|
|
||||||
begin
|
|
||||||
case FComm.CommEvent of
|
|
||||||
comEvReceive:
|
|
||||||
begin
|
|
||||||
{ Drain all available data in a single update batch. This }
|
|
||||||
{ suppresses per-Write rendering so we get one paint at the }
|
|
||||||
{ end instead of one per 255-byte chunk. }
|
|
||||||
FAnsi.BeginUpdate;
|
|
||||||
try
|
|
||||||
repeat
|
|
||||||
S := FComm.Input;
|
|
||||||
if Length(S) > 0 then
|
|
||||||
FAnsi.Write(S);
|
|
||||||
until Length(S) = 0;
|
|
||||||
finally
|
|
||||||
FAnsi.EndUpdate;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
|
|
||||||
constructor TMainForm.Create(AOwner: TComponent);
|
constructor TMainForm.Create(AOwner: TComponent);
|
||||||
begin
|
begin
|
||||||
inherited CreateNew(AOwner);
|
inherited CreateNew(AOwner);
|
||||||
|
|
@ -114,7 +93,6 @@ begin
|
||||||
|
|
||||||
{ Serial component }
|
{ Serial component }
|
||||||
FComm := TKPComm.Create(Self);
|
FComm := TKPComm.Create(Self);
|
||||||
FComm.OnComm := CommEvent;
|
|
||||||
|
|
||||||
{ Row 1: Port and Settings }
|
{ Row 1: Port and Settings }
|
||||||
FLabelPort := TLabel.Create(Self);
|
FLabelPort := TLabel.Create(Self);
|
||||||
|
|
@ -177,15 +155,66 @@ begin
|
||||||
|
|
||||||
{ Font diagnostic: write known CP437 box-drawing characters. }
|
{ Font diagnostic: write known CP437 box-drawing characters. }
|
||||||
{ If the OEM font is working, you should see: }
|
{ If the OEM font is working, you should see: }
|
||||||
{ Line 1: single-line box top ┌───┐ }
|
{ Line 1: single-line box top +---+ }
|
||||||
{ Line 2: shade + full block ░▒▓█ }
|
{ Line 2: shade + full block ░▒▓█ }
|
||||||
{ Line 3: single-line box bottom └───┘ }
|
{ Line 3: single-line box bottom +---+ }
|
||||||
{ If you see accented letters (Ú Ä ¿ ° ± ² Û À Ù), the font is }
|
{ If you see accented letters, the font is ANSI_CHARSET instead of }
|
||||||
{ ANSI_CHARSET instead of OEM_CHARSET. }
|
{ OEM_CHARSET. }
|
||||||
FAnsi.Write(#$DA#$C4#$C4#$C4#$BF' '#$B0#$B1#$B2#$DB' '#$C0#$C4#$C4#$C4#$D9#13#10);
|
FAnsi.Write(#$DA#$C4#$C4#$C4#$BF' '#$B0#$B1#$B2#$DB' '#$C0#$C4#$C4#$C4#$D9#13#10);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
procedure TMainForm.Run;
|
||||||
|
var
|
||||||
|
Msg: TMsg;
|
||||||
|
S: string;
|
||||||
|
HasData: Boolean;
|
||||||
|
begin
|
||||||
|
Show;
|
||||||
|
FDone := False;
|
||||||
|
while not FDone do
|
||||||
|
begin
|
||||||
|
{ Process all pending Windows messages (keyboard, paint, scrollbar) }
|
||||||
|
while PeekMessage(Msg, 0, 0, 0, pm_Remove or pm_NoYield) do
|
||||||
|
begin
|
||||||
|
if Msg.message = wm_Quit then
|
||||||
|
begin
|
||||||
|
FDone := True;
|
||||||
|
Break;
|
||||||
|
end;
|
||||||
|
TranslateMessage(Msg);
|
||||||
|
DispatchMessage(Msg);
|
||||||
|
end;
|
||||||
|
|
||||||
|
if FDone then
|
||||||
|
Break;
|
||||||
|
|
||||||
|
{ Poll serial data -- read one chunk per iteration }
|
||||||
|
HasData := False;
|
||||||
|
if FComm.PortOpen then
|
||||||
|
begin
|
||||||
|
S := FComm.Input;
|
||||||
|
if Length(S) > 0 then
|
||||||
|
begin
|
||||||
|
FAnsi.WriteDeferred(S);
|
||||||
|
HasData := True;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ Tick blink (dirties rows if interval elapsed), then render }
|
||||||
|
FAnsi.TickBlink;
|
||||||
|
FAnsi.FlipToScreen;
|
||||||
|
|
||||||
|
{ Yield CPU to other apps when no serial data is flowing. }
|
||||||
|
{ PM_NOYIELD keeps message draining fast; Yield here gives other }
|
||||||
|
{ apps a timeslice only when idle. During bulk data flow, HasData }
|
||||||
|
{ stays True and the loop runs at full speed. }
|
||||||
|
if not HasData then
|
||||||
|
Yield;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
procedure TMainForm.UpdateStatus;
|
procedure TMainForm.UpdateStatus;
|
||||||
begin
|
begin
|
||||||
if FComm.PortOpen then
|
if FComm.PortOpen then
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,7 @@ static void readPortConfig(int16_t commId, uint16_t FAR *baseAddr, uint8_t F
|
||||||
static uint16_t readSystemIni(const char FAR *section, const char FAR *key, uint16_t defVal);
|
static uint16_t readSystemIni(const char FAR *section, const char FAR *key, uint16_t defVal);
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// Port base addresses and IRQ table
|
// Port base addresses and IRQ defaults (overridden by SYSTEM.INI)
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
static const uint16_t portBase[MAX_PORTS] = {
|
static const uint16_t portBase[MAX_PORTS] = {
|
||||||
COM1_BASE, COM2_BASE, COM3_BASE, COM4_BASE
|
COM1_BASE, COM2_BASE, COM3_BASE, COM4_BASE
|
||||||
|
|
@ -204,25 +204,18 @@ static const uint8_t portIrq[MAX_PORTS] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// Global port state array
|
// Global port state array -- one PortStateT per COM port.
|
||||||
|
// Accessed from both application context and ISR context.
|
||||||
|
// Segment address loaded into DS by ISR entry points (isr3/isr4).
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
PortStateT ports[MAX_PORTS];
|
PortStateT ports[MAX_PORTS];
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// Global instance handle
|
// DLL instance handle (saved in LibMain for resource loading)
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
static HANDLE ghInstance = NULL;
|
static HANDLE ghInstance = NULL;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// ISR hit counter for diagnostics (incremented in isr4, wraps at 65535)
|
||||||
// Dynamically resolved PostMessage
|
|
||||||
//
|
|
||||||
// comm.drv is loaded from [boot] before USER.EXE, so we can't have a
|
|
||||||
// static import for PostMessage. Resolve it on first use via
|
|
||||||
// GetProcAddress(GetModuleHandle("USER"), "PostMessage").
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
PostMessageProcT pfnPostMessage = NULL;
|
|
||||||
|
|
||||||
// ISR hit counter for diagnostics
|
|
||||||
volatile uint16_t isrHitCount = 0;
|
volatile uint16_t isrHitCount = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -722,40 +715,27 @@ void enableFifo(PortStateT *port)
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// enableNotification - Register window for WM_COMMNOTIFY (ordinal 100)
|
// enableNotification - Register window for WM_COMMNOTIFY (ordinal 100)
|
||||||
|
//
|
||||||
|
// Retained as a no-op stub for API compatibility. Stores threshold
|
||||||
|
// values but nothing reads them -- polling replaces notifications.
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
int16_t FAR PASCAL _export enableNotification(int16_t commId, HWND hwnd, int16_t rxThresh, int16_t txThresh)
|
int16_t FAR PASCAL _export enableNotification(int16_t commId, HWND hwnd, int16_t rxThresh, int16_t txThresh)
|
||||||
{
|
{
|
||||||
PortStateT *port;
|
PortStateT *port;
|
||||||
|
|
||||||
dbgInt16("KPCOMM: enableNotif Id", commId);
|
|
||||||
dbgHex16("KPCOMM: enableNotif hwnd", (uint16_t)hwnd);
|
|
||||||
dbgInt16("KPCOMM: enableNotif rxTh", rxThresh);
|
|
||||||
dbgInt16("KPCOMM: enableNotif txTh", txThresh);
|
|
||||||
|
|
||||||
if (commId < 0 || commId >= MAX_PORTS) {
|
if (commId < 0 || commId >= MAX_PORTS) {
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
port = &ports[commId];
|
port = &ports[commId];
|
||||||
if (!port->isOpen) {
|
if (!port->isOpen) {
|
||||||
dbgStr("KPCOMM: enableNotif not open\r\n");
|
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lazily resolve PostMessage from USER.EXE
|
|
||||||
// (not available at boot when comm.drv is loaded from [boot])
|
|
||||||
if (!pfnPostMessage) {
|
|
||||||
HMODULE hUser = GetModuleHandle("USER");
|
|
||||||
if (hUser) {
|
|
||||||
pfnPostMessage = (PostMessageProcT)GetProcAddress(hUser, "PostMessage");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
port->hwndNotify = hwnd;
|
port->hwndNotify = hwnd;
|
||||||
port->rxNotifyThresh = rxThresh;
|
port->rxNotifyThresh = rxThresh;
|
||||||
port->txNotifyThresh = txThresh;
|
port->txNotifyThresh = txThresh;
|
||||||
|
|
||||||
dbgStr("KPCOMM: enableNotif OK\r\n");
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1063,21 +1043,21 @@ static void initPortState(PortStateT *port, int16_t commId)
|
||||||
trigKey[11] = 'E';
|
trigKey[11] = 'E';
|
||||||
trigKey[12] = 'R';
|
trigKey[12] = 'R';
|
||||||
trigKey[13] = '\0';
|
trigKey[13] = '\0';
|
||||||
rxTrigger = readSystemIni("386Enh", trigKey, 8);
|
rxTrigger = readSystemIni("386Enh", trigKey, 1);
|
||||||
}
|
}
|
||||||
switch (rxTrigger) {
|
switch (rxTrigger) {
|
||||||
case 1:
|
|
||||||
port->fifoTrigger = FCR_TRIG_1;
|
|
||||||
break;
|
|
||||||
case 4:
|
case 4:
|
||||||
port->fifoTrigger = FCR_TRIG_4;
|
port->fifoTrigger = FCR_TRIG_4;
|
||||||
break;
|
break;
|
||||||
|
case 8:
|
||||||
|
port->fifoTrigger = FCR_TRIG_8;
|
||||||
|
break;
|
||||||
case 14:
|
case 14:
|
||||||
port->fifoTrigger = FCR_TRIG_14;
|
port->fifoTrigger = FCR_TRIG_14;
|
||||||
break;
|
break;
|
||||||
case 8:
|
case 1:
|
||||||
default:
|
default:
|
||||||
port->fifoTrigger = FCR_TRIG_8;
|
port->fifoTrigger = FCR_TRIG_1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
117
drv/commdrv.h
117
drv/commdrv.h
|
|
@ -322,86 +322,87 @@ typedef struct {
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// Port state structure
|
// Port state structure
|
||||||
|
//
|
||||||
|
// One per COM port. Accessed from both application context (reccom,
|
||||||
|
// sndcom, etc.) and ISR context (handleRx, handleTx).
|
||||||
|
// Fields shared between contexts are protected by _disable()/_enable().
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
typedef struct {
|
typedef struct {
|
||||||
// Stock-compatible ComDEB -- must be kept synchronized.
|
// Stock-compatible ComDEB -- must be at offset 0.
|
||||||
// cevt returns a FAR pointer to this.
|
// cevt (SetCommEventMask) returns a FAR pointer to this.
|
||||||
|
// Third-party code reads msrShadow at offset 35 (KB Q101417).
|
||||||
ComDebT comDeb;
|
ComDebT comDeb;
|
||||||
|
|
||||||
uint16_t baseAddr; // UART base I/O address
|
// Hardware identification (from SYSTEM.INI or defaults)
|
||||||
uint8_t irq; // IRQ number (3 or 4)
|
uint16_t baseAddr; // UART base I/O address (e.g. 0x03F8 for COM1)
|
||||||
int16_t commId; // Port ID (0=COM1, 1=COM2, ...)
|
uint8_t irq; // IRQ number (3 for COM2/4, 4 for COM1/3)
|
||||||
uint8_t isOpen; // Port open flag
|
int16_t commId; // Port index (0=COM1, 1=COM2, 2=COM3, 3=COM4)
|
||||||
uint8_t is16550; // 16550 FIFO detected
|
uint8_t isOpen; // Nonzero while port is open (guards ISR dispatch)
|
||||||
uint8_t fifoEnabled; // FIFO enabled (COMnFIFO setting)
|
uint8_t is16550; // Nonzero if 16550 FIFO detected at init
|
||||||
uint8_t fifoTrigger; // RX FIFO trigger FCR bits (FCR_TRIG_*)
|
uint8_t fifoEnabled; // Nonzero if FIFO use enabled (COMnFIFO in SYSTEM.INI)
|
||||||
|
uint8_t fifoTrigger; // RX FIFO trigger level FCR bits (FCR_TRIG_*)
|
||||||
|
|
||||||
// Ring buffers (GlobalAlloc'd GMEM_FIXED)
|
// Receive ring buffer (GlobalAlloc'd GMEM_FIXED for ISR stability)
|
||||||
HGLOBAL rxBufH; // Receive buffer handle
|
HGLOBAL rxBufH; // GlobalAlloc handle (for GlobalFree on close)
|
||||||
uint8_t FAR *rxBuf; // Receive ring buffer
|
uint8_t FAR *rxBuf; // Far pointer to buffer data
|
||||||
uint16_t rxSize; // Buffer size
|
uint16_t rxSize; // Buffer capacity in bytes
|
||||||
uint16_t rxHead; // Write position (ISR writes)
|
uint16_t rxHead; // Write position -- ISR increments
|
||||||
uint16_t rxTail; // Read position (app reads)
|
uint16_t rxTail; // Read position -- reccom increments
|
||||||
uint16_t rxCount; // Bytes in buffer
|
uint16_t rxCount; // Bytes in buffer (ISR increments, reccom decrements)
|
||||||
|
|
||||||
HGLOBAL txBufH; // Transmit buffer handle
|
// Transmit ring buffer (GlobalAlloc'd GMEM_FIXED for ISR stability)
|
||||||
uint8_t FAR *txBuf; // Transmit ring buffer
|
HGLOBAL txBufH; // GlobalAlloc handle (for GlobalFree on close)
|
||||||
uint16_t txSize; // Buffer size
|
uint8_t FAR *txBuf; // Far pointer to buffer data
|
||||||
uint16_t txHead; // Write position (app writes)
|
uint16_t txSize; // Buffer capacity in bytes
|
||||||
uint16_t txTail; // Read position (ISR reads)
|
uint16_t txHead; // Write position -- sndcom increments
|
||||||
uint16_t txCount; // Bytes in buffer
|
uint16_t txTail; // Read position -- ISR (handleTx) increments
|
||||||
|
uint16_t txCount; // Bytes in buffer (sndcom increments, ISR decrements)
|
||||||
|
|
||||||
// DCB shadow
|
// Serial parameters (cached from DCB at inicom/setcom time)
|
||||||
uint16_t baudRate; // Current baud rate
|
uint16_t baudRate; // Baud rate (raw or CBR_* index for >32767)
|
||||||
uint8_t byteSize; // Data bits (5-8)
|
uint8_t byteSize; // Data bits per character (5-8)
|
||||||
uint8_t parity; // Parity mode
|
uint8_t parity; // Parity mode (NOPARITY, ODDPARITY, EVENPARITY, ...)
|
||||||
uint8_t stopBits; // Stop bits
|
uint8_t stopBits; // Stop bits (ONESTOPBIT, TWOSTOPBITS)
|
||||||
|
|
||||||
// Flow control state
|
// Flow control state (managed by ISR and application code)
|
||||||
uint8_t hsMode; // Handshaking mode
|
uint8_t hsMode; // Handshaking mode (HS_NONE/XONXOFF/RTSCTS/BOTH)
|
||||||
uint8_t txStopped; // TX halted by flow control
|
uint8_t txStopped; // Nonzero = TX halted (received XOFF or CTS dropped)
|
||||||
uint8_t rxStopped; // We sent XOFF / dropped RTS
|
uint8_t rxStopped; // Nonzero = we sent XOFF or dropped RTS
|
||||||
uint8_t xonChar; // XON character (default 0x11)
|
uint8_t xonChar; // XON character to send/recognize (default 0x11 DC1)
|
||||||
uint8_t xoffChar; // XOFF character (default 0x13)
|
uint8_t xoffChar; // XOFF character to send/recognize (default 0x13 DC3)
|
||||||
uint16_t xoffLim; // Send XOFF when rxCount > rxSize - xoffLim
|
uint16_t xoffLim; // Assert flow control when rxCount > rxSize - xoffLim
|
||||||
uint16_t xonLim; // Send XON when rxCount < xonLim
|
uint16_t xonLim; // Release flow control when rxCount < xonLim
|
||||||
|
|
||||||
// Modem control shadow
|
// Modem control line shadow (tracks EscapeCommFunction calls)
|
||||||
uint8_t dtrState; // DTR line state
|
uint8_t dtrState; // Nonzero = DTR is asserted
|
||||||
uint8_t rtsState; // RTS line state (when not flow-controlled)
|
uint8_t rtsState; // Nonzero = RTS is asserted (when not flow-controlled)
|
||||||
|
|
||||||
// Error accumulator
|
// Error accumulator (sticky CE_* bits, cleared by stacom/GetCommError)
|
||||||
uint16_t errorFlags; // CE_* error flags (sticky until read)
|
uint16_t errorFlags; // Accumulated CE_RXOVER, CE_OVERRUN, CE_FRAME, etc.
|
||||||
|
|
||||||
// Event notification
|
// WM_COMMNOTIFY event notification (stored for API compat, not used)
|
||||||
HWND hwndNotify; // Window for WM_COMMNOTIFY
|
HWND hwndNotify; // Target window for PostMessage (0=disabled)
|
||||||
int16_t rxNotifyThresh; // CN_RECEIVE threshold (-1=disabled)
|
int16_t rxNotifyThresh; // CN_RECEIVE threshold (-1=disabled)
|
||||||
int16_t txNotifyThresh; // CN_TRANSMIT threshold (-1=disabled)
|
int16_t txNotifyThresh; // CN_TRANSMIT threshold (-1=disabled)
|
||||||
|
|
||||||
// ISR state
|
// ISR chaining and PIC management
|
||||||
void (FAR *prevIsr)(void); // Previous ISR in chain
|
void (FAR *prevIsr)(void); // Previous ISR vector (restored on unhook)
|
||||||
uint8_t irqMask; // PIC mask bit for this IRQ
|
uint8_t irqMask; // PIC mask bit for this IRQ (1 << irq)
|
||||||
uint8_t breakState; // Break signal active
|
uint8_t breakState; // Nonzero while break signal is active on line
|
||||||
|
|
||||||
// Priority transmit
|
// Priority transmit (XON/XOFF flow control characters)
|
||||||
int16_t txImmediate; // -1=none, else char to send immediately
|
int16_t txImmediate; // -1=none, 0..255=char to send before buffered data
|
||||||
|
|
||||||
// DCB copy for GetCommState
|
// Full DCB copy (returned by getdcb, updated by setcom)
|
||||||
DCB dcb; // Full DCB for GETDCB/SETCOM
|
DCB dcb;
|
||||||
} PortStateT;
|
} PortStateT;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// Global port state array
|
// Global port state array (one entry per COM port, indexed by commId)
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
extern PortStateT ports[MAX_PORTS];
|
extern PortStateT ports[MAX_PORTS];
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// ISR hit counter for diagnostics (incremented in isr4, wraps at 65535)
|
||||||
// Dynamically resolved PostMessage (USER.EXE not loaded at boot)
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
typedef BOOL (FAR PASCAL *PostMessageProcT)(HWND, UINT, WPARAM, LPARAM);
|
|
||||||
extern PostMessageProcT pfnPostMessage;
|
|
||||||
|
|
||||||
// ISR hit counter for diagnostics
|
|
||||||
extern volatile uint16_t isrHitCount;
|
extern volatile uint16_t isrHitCount;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
|
|
|
||||||
55
drv/isr.c
55
drv/isr.c
|
|
@ -10,7 +10,6 @@
|
||||||
// Prototypes
|
// Prototypes
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
static void checkFlowRx(PortStateT *port);
|
static void checkFlowRx(PortStateT *port);
|
||||||
static void checkNotify(PortStateT *port);
|
|
||||||
static void handleLsr(PortStateT *port, uint8_t lsr);
|
static void handleLsr(PortStateT *port, uint8_t lsr);
|
||||||
static void handleMsr(PortStateT *port);
|
static void handleMsr(PortStateT *port);
|
||||||
static void handleRx(PortStateT *port, uint8_t lsr);
|
static void handleRx(PortStateT *port, uint8_t lsr);
|
||||||
|
|
@ -21,11 +20,17 @@ void _far _interrupt isr4(void);
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// Saved previous ISR vectors for IRQ3 and IRQ4
|
// Saved previous ISR vectors for IRQ3 and IRQ4
|
||||||
|
//
|
||||||
|
// When we hook an IRQ via DPMI, the original vector is saved here so
|
||||||
|
// unhookIsr can restore it. Only saved/restored on first-use/last-use
|
||||||
|
// of each IRQ (ref-counted for shared IRQs like COM1+COM3).
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
static void (_far _interrupt *prevIsr3)(void) = NULL;
|
static void (_far _interrupt *prevIsr3)(void) = NULL; // Original IRQ3 vector
|
||||||
static void (_far _interrupt *prevIsr4)(void) = NULL;
|
static void (_far _interrupt *prevIsr4)(void) = NULL; // Original IRQ4 vector
|
||||||
|
|
||||||
// Track how many ports are using each IRQ so we know when to unhook
|
// Reference counts: how many open ports share each IRQ.
|
||||||
|
// hookIsr increments; unhookIsr decrements. Vector is only
|
||||||
|
// installed/restored when the count transitions through 0.
|
||||||
static int16_t irq3RefCount = 0;
|
static int16_t irq3RefCount = 0;
|
||||||
static int16_t irq4RefCount = 0;
|
static int16_t irq4RefCount = 0;
|
||||||
|
|
||||||
|
|
@ -76,46 +81,6 @@ static void checkFlowRx(PortStateT *port)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// checkNotify - Post WM_COMMNOTIFY if threshold conditions met
|
|
||||||
//
|
|
||||||
// Called at end of ISR dispatch, outside the IIR loop.
|
|
||||||
// Uses PostMessage to avoid reentrancy issues.
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
static void checkNotify(PortStateT *port)
|
|
||||||
{
|
|
||||||
uint16_t notifyBits;
|
|
||||||
|
|
||||||
if (!port->hwndNotify) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyBits = 0;
|
|
||||||
|
|
||||||
// CN_RECEIVE: rxCount crossed threshold from below
|
|
||||||
if (port->rxNotifyThresh >= 0 && port->rxCount >= (uint16_t)port->rxNotifyThresh) {
|
|
||||||
notifyBits |= CN_RECEIVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// CN_TRANSMIT: space available crossed threshold
|
|
||||||
if (port->txNotifyThresh >= 0) {
|
|
||||||
uint16_t txFree = port->txSize - port->txCount;
|
|
||||||
if (txFree >= (uint16_t)port->txNotifyThresh) {
|
|
||||||
notifyBits |= CN_TRANSMIT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CN_EVENT: any event bits accumulated
|
|
||||||
if (port->comDeb.evtWord & port->comDeb.evtMask) {
|
|
||||||
notifyBits |= CN_EVENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notifyBits && pfnPostMessage) {
|
|
||||||
pfnPostMessage(port->hwndNotify, WM_COMMNOTIFY, (WPARAM)port->commId, (LPARAM)notifyBits);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// handleLsr - Process line status errors
|
// handleLsr - Process line status errors
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
|
|
@ -431,8 +396,6 @@ void isrDispatch(PortStateT *port)
|
||||||
port->comDeb.qOutCount = port->txCount;
|
port->comDeb.qOutCount = port->txCount;
|
||||||
port->comDeb.qOutHead = port->txHead;
|
port->comDeb.qOutHead = port->txHead;
|
||||||
port->comDeb.qOutTail = port->txTail;
|
port->comDeb.qOutTail = port->txTail;
|
||||||
|
|
||||||
checkNotify(port);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue