Replace event-driven WM_COMMNOTIFY architecture with polling main loop

The ISR still fills the ring buffer (mandatory for 115200 baud), but the
app now polls ReadComm directly via a PeekMessage loop instead of waiting
for WM_COMMNOTIFY.  Blink uses GetTickCount instead of WM_TIMER.  This
eliminates all Windows message overhead from the data path while keeping
the message loop alive for keyboard, paint, and scrollbar.

Removed from KPCOMM.PAS: NotifyWndProc, hidden notification window,
RegisterClass/CreateWindow, EnableCommNotification, SetCommEventMask,
DoCommEvent, Process*Notify methods, OnComm/CommEvent/RThreshold/
SThreshold properties, modem shadow state (CTS/DSR/CD).

Removed from KPANSI.PAS: WM_TIMER handler, SetTimer/KillTimer, replaced
with public TickBlink method using GetTickCount at 500ms intervals.

Removed from drv/isr.c: checkNotify function and its call from
isrDispatch.  Removed from drv/commdrv.c: pfnPostMessage, all
rxNotifySent/txNotifySent edge-trigger bookkeeping, gutted
enableNotification to a no-op API-compat stub.  Removed from
drv/commdrv.h: rxNotifySent/txNotifySent fields (shifts struct layout),
PostMessageProcT typedef, pfnPostMessage extern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Duensing 2026-03-01 19:01:40 -06:00
parent acf1a6b691
commit ec0ec8f074
7 changed files with 86 additions and 462 deletions

View file

@ -71,9 +71,9 @@ type
FCellWidth: Integer; { Character cell width in pixels (typically 8) }
FCellHeight: Integer; { Character cell height in pixels (typ 12-16) }
{ Blink/timer state }
{ Blink state }
FBlinkOn: Boolean; { Cursor blink phase: True=visible }
FTimerActive: Boolean; { True if WM_TIMER is running (SetTimer) }
FLastBlinkTick: Longint; { GetTickCount value at last blink toggle }
{ Scrollback view }
FScrollPos: Integer; { Lines scrolled back (0=live, >0=viewing history) }
@ -97,9 +97,6 @@ type
FPaintFont: HFont; { GDI font handle for OEM_CHARSET rendering }
FStockFont: Boolean; { True if FPaintFont is a stock object (no delete) }
{ Rendering control }
FBlinkCount: Integer; { Timer ticks since last blink toggle }
{ 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 }
@ -158,7 +155,6 @@ type
procedure UpdateScrollbar;
procedure WMEraseBkgnd(var Msg: TWMEraseBkgnd); message wm_EraseBkgnd;
procedure WMGetDlgCode(var Msg: TMessage); message wm_GetDlgCode;
procedure WMTimer(var Msg: TWMTimer); message wm_Timer;
procedure WMVScroll(var Msg: TWMScroll); message wm_VScroll;
protected
procedure CreateParams(var Params: TCreateParams); override;
@ -170,6 +166,7 @@ type
destructor Destroy; override;
procedure Clear;
procedure Reset;
procedure TickBlink;
procedure Write(const S: string);
property CursorCol: Integer read GetCursorCol;
property CursorRow: Integer read GetCursorRow;
@ -210,10 +207,8 @@ const
$00FFFFFF { 15 White (bright) }
);
{ Blink timer interval. Win16 minimum is ~55 ms (18.2 Hz tick). }
BlinkTimerMs = 55;
{ Cursor and text blink toggle every 9 timer ticks (~500 ms). }
BlinkInterval = 9;
{ Blink toggle interval in milliseconds (cursor + text blink). }
BlinkMs = 500;
{ OUT_RASTER_PRECIS may not be defined in Delphi 1.0 WinTypes }
OutRasterPrecis = 6;
@ -484,12 +479,11 @@ begin
FCellWidth := 8;
FCellHeight := 16;
FBlinkOn := True;
FTimerActive := False;
FLastBlinkTick := GetTickCount;
FScrollPos := 0;
FWrapMode := True;
FPaintFont := 0;
FStockFont := False;
FBlinkCount := 0;
FAllDirty := True;
FScrollbarDirty := False;
FTextBlinkOn := True;
@ -648,11 +642,6 @@ end;
destructor TKPAnsi.Destroy;
begin
if FTimerActive and HandleAllocated then
begin
KillTimer(Handle, 1);
FTimerActive := False;
end;
DestroyRowBuffers;
if (FPaintFont <> 0) and not FStockFont then
begin
@ -1870,13 +1859,6 @@ begin
CreateRowBuffers;
FAllDirty := True;
{ Start render/blink timer }
if not FTimerActive then
begin
SetTimer(Handle, 1, BlinkTimerMs, nil);
FTimerActive := True;
end;
Invalidate;
end;
@ -2322,21 +2304,20 @@ begin
end;
procedure TKPAnsi.WMTimer(var Msg: TWMTimer);
procedure TKPAnsi.TickBlink;
var
Now: Longint;
begin
{ Blink counter: toggle cursor and text blink every BlinkInterval ticks }
Inc(FBlinkCount);
if FBlinkCount >= BlinkInterval then
Now := GetTickCount;
if Now - FLastBlinkTick >= BlinkMs then
begin
FBlinkCount := 0;
FLastBlinkTick := Now;
FBlinkOn := not FBlinkOn;
FTextBlinkOn := not FTextBlinkOn;
DirtyBlinkRows;
end;
{ Render blink changes and any stale dirty rows }
FlipToScreen;
end;
end;
procedure TKPAnsi.WMVScroll(var Msg: TWMScroll);

View file

@ -5,40 +5,16 @@ unit KPComm;
{ 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. }
{ }
{ Port lifecycle: OpenComm -> BuildCommDCB + SetCommState -> }
{ EnableCommNotification -> CloseComm. }
{ Port lifecycle: OpenComm -> BuildCommDCB + SetCommState -> CloseComm. }
{ }
{ WM_COMMNOTIFY messages are received through a hidden utility window }
{ with a registered class and dispatched to ProcessReceiveNotify, }
{ 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. }
{ Data is read by polling Input (ReadComm) from a PeekMessage main loop }
{ rather than through WM_COMMNOTIFY event dispatch. }
interface
uses
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
THandshaking = (hsNone, hsXonXoff, hsRtsCts, hsBoth);
TInputMode = (imText, imBinary);
@ -47,7 +23,6 @@ type
private
{ Port state }
FCommId: Integer; { Comm port handle from OpenComm (-1 = closed) }
FHWndNotify: HWnd; { Hidden window receiving WM_COMMNOTIFY }
{ Configuration (published properties) }
FCommPort: Integer; { Port number (1-based: 1=COM1, 2=COM2, ...) }
@ -55,8 +30,6 @@ type
FPortOpen: Boolean; { True while port is open and operational }
FInBufferSize: Integer; { Receive ring buffer size in bytes }
FOutBufferSize: Integer; { Transmit ring buffer size in bytes }
FRThreshold: Integer; { RX byte count triggering CN_RECEIVE (0=disabled) }
FSThreshold: Integer; { TX free space triggering CN_TRANSMIT (0=disabled) }
FHandshaking: THandshaking; { Flow control mode (none/XonXoff/RtsCts/both) }
FInputLen: Integer; { Max bytes per Input read (0=up to 255) }
FInputMode: TInputMode; { Text or binary read mode }
@ -72,26 +45,14 @@ type
{ Runtime state }
FBreakState: Boolean; { True while break signal is being sent }
FCommEvent: Integer; { Last event code passed to OnComm (comEv* const) }
{ Modem line shadow state (toggled on transition events from driver) }
FCTSState: Boolean; { CTS line level (toggled on ev_CTS) }
FDSRState: Boolean; { DSR line level (toggled on ev_DSR) }
FCDState: Boolean; { DCD/RLSD line level (toggled on ev_RLSD) }
{ Event handler }
FOnComm: TNotifyEvent; { Fired on comm events; check CommEvent for code }
procedure ApplyHandshaking;
procedure ApplyOptions;
procedure ClosePort;
procedure DoCommEvent(EventCode: Integer);
function GetInBufferCount: Integer;
function GetInput: string;
function GetOutBufferCount: Integer;
procedure OpenPort;
procedure ProcessEventNotify;
procedure ProcessReceiveNotify;
procedure ProcessTransmitNotify;
procedure SetBreak(Value: Boolean);
procedure SetCommPort(Value: Integer);
procedure SetDTREnable(Value: Boolean);
@ -111,19 +72,13 @@ type
property Output: string write SetOutput;
property InBufferCount: Integer read GetInBufferCount;
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 CommEvent: Integer read FCommEvent;
published
property CommPort: Integer read FCommPort write SetCommPort default 1;
property Settings: string read FSettings write SetSettings;
property PortOpen: Boolean read FPortOpen write SetPortOpen default False;
property InBufferSize: Integer read FInBufferSize write SetInBufferSize 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 InputLen: Integer read FInputLen write FInputLen default 0;
property InputMode: TInputMode read FInputMode write FInputMode default imText;
@ -132,7 +87,6 @@ type
property NullDiscard: Boolean read FNullDiscard write SetNullDiscard default False;
property EOFEnable: Boolean read FEOFEnable write FEOFEnable default False;
property ParityReplace: string read FParityReplace write SetParityReplace;
property OnComm: TNotifyEvent read FOnComm write FOnComm;
end;
procedure Register;
@ -140,11 +94,6 @@ procedure Register;
implementation
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 }
{ fields into a single UINT (Word) at offset 12 of the structure. }
{ Delphi 1.0 maps this UINT to TDCB.Flags. }
@ -162,46 +111,6 @@ const
dcbDtrflow = $0800;
dcbRtsflow = $1000;
{ Hidden notification window class name }
NotifyClassName = 'KPCommNotify';
var
NotifyClassRegistered: Boolean; { True after RegisterClass for notification window }
{ ----------------------------------------------------------------------- }
{ 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 }
@ -277,18 +186,11 @@ end;
procedure TKPComm.ClosePort;
var
Msg: TMsg;
begin
{ Set FPortOpen first to prevent stale WM_COMMNOTIFY processing }
FPortOpen := False;
if FCommId >= 0 then
begin
{ Disable notifications BEFORE dropping modem lines so the ISR }
{ stops posting WM_COMMNOTIFY while we tear down. }
EnableCommNotification(FCommId, 0, -1, -1);
if FBreakState then
begin
ClearCommBreak(FCommId);
@ -299,22 +201,6 @@ begin
CloseComm(FCommId);
FCommId := -1;
end;
if FHWndNotify <> 0 then
begin
{ Flush any stale WM_COMMNOTIFY that the ISR posted before we }
{ disabled notifications. Without this, DispatchMessage would }
{ dereference the freed window structure and lock up. }
while PeekMessage(Msg, FHWndNotify, wm_CommNotify,
wm_CommNotify, pm_Remove) do
{ discard };
DestroyWindow(FHWndNotify);
FHWndNotify := 0;
end;
FCTSState := False;
FDSRState := False;
FCDState := False;
end;
@ -322,14 +208,11 @@ constructor TKPComm.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FCommId := -1;
FHWndNotify := 0;
FCommPort := 1;
FSettings := '9600,N,8,1';
FPortOpen := False;
FInBufferSize := 4096;
FOutBufferSize := 4096;
FRThreshold := 0;
FSThreshold := 0;
FHandshaking := hsNone;
FInputLen := 0;
FInputMode := imText;
@ -339,10 +222,6 @@ begin
FEOFEnable := False;
FParityReplace := '?';
FBreakState := False;
FCommEvent := 0;
FCTSState := False;
FDSRState := False;
FCDState := False;
end;
@ -354,14 +233,6 @@ begin
end;
procedure TKPComm.DoCommEvent(EventCode: Integer);
begin
FCommEvent := EventCode;
if Assigned(FOnComm) then
FOnComm(Self);
end;
function TKPComm.GetInBufferCount: Integer;
var
Stat: TComStat;
@ -416,12 +287,9 @@ end;
procedure TKPComm.OpenPort;
var
WC: TWndClass;
DCB: TDCB;
Buf: array[0..255] of Char;
Setting: string;
RxTh: Integer;
TxTh: Integer;
begin
{ Open the comm port }
StrPCopy(Buf, 'COM' + IntToStr(FCommPort));
@ -472,137 +340,10 @@ begin
else
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. }
{ ev_RxChar is deliberately excluded: it fires per received byte, }
{ flooding WM_COMMNOTIFY with CN_EVENT on every ISR. Data arrival }
{ is already handled by CN_RECEIVE via EnableCommNotification. }
SetCommEventMask(FCommId,
ev_CTS or ev_DSR or ev_RLSD or ev_Ring or
ev_Err or ev_Break);
{ 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;
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);
begin
FBreakState := Value;
@ -685,7 +426,7 @@ begin
begin
Written := WriteComm(FCommId, @Value[1], Length(Value));
if Written < 0 then
DoCommEvent(comEvtTxFull);
raise Exception.Create('WriteComm failed');
end;
end;

View file

@ -8,5 +8,5 @@ uses
begin
Application.CreateForm(TMainForm, MainForm);
Application.Run;
MainForm.Run;
end.

View file

@ -5,8 +5,8 @@ unit TestMain;
{ }
{ Layout: toolbar row at top (port, settings, open/close, status), }
{ TKPAnsi terminal filling the rest of the form. Received serial data }
{ is fed to the terminal via TKPAnsi.Write; keystrokes from the terminal }
{ are sent out via TKPComm.Output. }
{ is polled from TKPComm.Input in a PeekMessage main loop; keystrokes }
{ from the terminal are sent out via TKPComm.Output. }
interface
@ -29,13 +29,15 @@ type
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 BtnCloseClick(Sender: TObject);
procedure BtnOpenClick(Sender: TObject);
procedure CommEvent(Sender: TObject);
procedure UpdateStatus;
public
constructor Create(AOwner: TComponent); override;
procedure Run;
end;
var
@ -70,7 +72,6 @@ begin
try
FComm.CommPort := StrToInt(FEditPort.Text);
FComm.Settings := FEditSettings.Text;
FComm.RThreshold := 1;
FComm.PortOpen := True;
except
on E: Exception do
@ -81,28 +82,6 @@ begin
end;
procedure TMainForm.CommEvent(Sender: TObject);
var
S: string;
begin
case FComm.CommEvent of
comEvReceive:
begin
{ Drain all available data. The driver uses edge-triggered }
{ CN_RECEIVE: it posts once when rxCount crosses the threshold }
{ and won't re-post until rxCount drops below and re-crosses. }
{ If we don't drain here, remaining data stalls permanently. }
{ Write renders each chunk immediately (no batching). }
repeat
S := FComm.Input;
if Length(S) > 0 then
FAnsi.Write(S);
until Length(S) = 0;
end;
end;
end;
constructor TMainForm.Create(AOwner: TComponent);
begin
inherited CreateNew(AOwner);
@ -114,7 +93,6 @@ begin
{ Serial component }
FComm := TKPComm.Create(Self);
FComm.OnComm := CommEvent;
{ Row 1: Port and Settings }
FLabelPort := TLabel.Create(Self);
@ -177,15 +155,54 @@ begin
{ Font diagnostic: write known CP437 box-drawing characters. }
{ 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 3: single-line box bottom └───┘ }
{ If you see accented letters (Ú Ä ¿ ° ± ² Û À Ù), the font is }
{ ANSI_CHARSET instead of OEM_CHARSET. }
{ Line 3: single-line box bottom +---+ }
{ If you see accented letters, the font is ANSI_CHARSET instead of }
{ OEM_CHARSET. }
FAnsi.Write(#$DA#$C4#$C4#$C4#$BF' '#$B0#$B1#$B2#$DB' '#$C0#$C4#$C4#$C4#$D9#13#10);
end;
procedure TMainForm.Run;
var
Msg: TMsg;
S: string;
begin
FDone := False;
while not FDone do
begin
{ Process all pending Windows messages (keyboard, paint, scrollbar) }
while PeekMessage(Msg, 0, 0, 0, pm_Remove) 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 directly -- no WM_COMMNOTIFY, no events }
if FComm.PortOpen then
begin
repeat
S := FComm.Input;
if Length(S) > 0 then
FAnsi.Write(S);
until Length(S) = 0;
end;
{ Tick blink state (uses GetTickCount, no WM_TIMER) }
FAnsi.TickBlink;
end;
end;
procedure TMainForm.UpdateStatus;
begin
if FComm.PortOpen then

View file

@ -215,16 +215,7 @@ PortStateT ports[MAX_PORTS];
// -----------------------------------------------------------------------
static HANDLE ghInstance = NULL;
// -----------------------------------------------------------------------
// 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
// ISR hit counter for diagnostics (incremented in isr4, wraps at 65535)
volatile uint16_t isrHitCount = 0;
@ -577,7 +568,6 @@ int16_t FAR PASCAL _export cflush(int16_t commId, int16_t queue)
port->rxHead = 0;
port->rxTail = 0;
port->rxCount = 0;
port->rxNotifySent = 0;
port->comDeb.qInHead = 0;
port->comDeb.qInTail = 0;
port->comDeb.qInCount = 0;
@ -589,7 +579,6 @@ int16_t FAR PASCAL _export cflush(int16_t commId, int16_t queue)
port->txHead = 0;
port->txTail = 0;
port->txCount = 0;
port->txNotifySent = 0;
port->comDeb.qOutHead = 0;
port->comDeb.qOutTail = 0;
port->comDeb.qOutCount = 0;
@ -726,42 +715,27 @@ void enableFifo(PortStateT *port)
// -----------------------------------------------------------------------
// 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)
{
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) {
return FALSE;
}
port = &ports[commId];
if (!port->isOpen) {
dbgStr("KPCOMM: enableNotif not open\r\n");
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->rxNotifyThresh = rxThresh;
port->txNotifyThresh = txThresh;
port->rxNotifySent = 0;
port->txNotifySent = 0;
dbgStr("KPCOMM: enableNotif OK\r\n");
return TRUE;
}
@ -1095,8 +1069,6 @@ static void initPortState(PortStateT *port, int16_t commId)
port->txImmediate = -1;
port->rxNotifyThresh = -1;
port->txNotifyThresh = -1;
port->rxNotifySent = 0;
port->txNotifySent = 0;
}
@ -1294,12 +1266,6 @@ int16_t FAR PASCAL _export reccom(int16_t commId, void FAR *buf, int16_t len)
port->rxCount--;
bytesRead++;
}
// Reset edge flag under CLI so the next ISR will re-post
// CN_RECEIVE when new data crosses the threshold again.
if (port->rxNotifyThresh >= 0 &&
port->rxCount < (uint16_t)port->rxNotifyThresh) {
port->rxNotifySent = 0;
}
_enable();
// If flow control was asserted and buffer has drained, deassert
@ -1459,14 +1425,12 @@ int16_t FAR PASCAL _export setque(int16_t commId, int16_t rxSz, int16_t txSz)
port->rxHead = 0;
port->rxTail = 0;
port->rxCount = 0;
port->rxNotifySent = 0;
port->txBufH = newTxH;
port->txBuf = newTxBuf;
port->txSize = (uint16_t)txSz;
port->txHead = 0;
port->txTail = 0;
port->txCount = 0;
port->txNotifySent = 0;
port->comDeb.qInSize = (uint16_t)rxSz;
port->comDeb.qInCount = 0;
port->comDeb.qInHead = 0;
@ -1534,14 +1498,6 @@ int16_t FAR PASCAL _export sndcom(int16_t commId, void FAR *buf, int16_t len)
port->txCount++;
bytesWritten++;
}
// Reset TX edge flag under CLI so the ISR will re-post
// CN_TRANSMIT when free space crosses the threshold again.
if (port->txNotifyThresh >= 0) {
uint16_t txFree = port->txSize - port->txCount;
if (txFree < (uint16_t)port->txNotifyThresh) {
port->txNotifySent = 0;
}
}
_enable();
// Sync ComDEB queue counts

View file

@ -324,7 +324,7 @@ typedef struct {
// Port state structure
//
// One per COM port. Accessed from both application context (reccom,
// sndcom, etc.) and ISR context (handleRx, handleTx, checkNotify).
// sndcom, etc.) and ISR context (handleRx, handleTx).
// Fields shared between contexts are protected by _disable()/_enable().
// -----------------------------------------------------------------------
typedef struct {
@ -380,12 +380,10 @@ typedef struct {
// Error accumulator (sticky CE_* bits, cleared by stacom/GetCommError)
uint16_t errorFlags; // Accumulated CE_RXOVER, CE_OVERRUN, CE_FRAME, etc.
// WM_COMMNOTIFY event notification
// WM_COMMNOTIFY event notification (stored for API compat, not used)
HWND hwndNotify; // Target window for PostMessage (0=disabled)
int16_t rxNotifyThresh; // CN_RECEIVE fires when rxCount >= this (-1=disabled)
int16_t txNotifyThresh; // CN_TRANSMIT fires when txFree >= this (-1=disabled)
uint8_t rxNotifySent; // Edge flag: 1 = CN_RECEIVE posted, suppresses repeats
uint8_t txNotifySent; // Edge flag: 1 = CN_TRANSMIT posted, suppresses repeats
int16_t rxNotifyThresh; // CN_RECEIVE threshold (-1=disabled)
int16_t txNotifyThresh; // CN_TRANSMIT threshold (-1=disabled)
// ISR chaining and PIC management
void (FAR *prevIsr)(void); // Previous ISR vector (restored on unhook)
@ -404,16 +402,6 @@ typedef struct {
// -----------------------------------------------------------------------
extern PortStateT ports[MAX_PORTS];
// -----------------------------------------------------------------------
// Dynamically resolved PostMessage
//
// COMM.DRV loads at boot from [boot] in SYSTEM.INI, before USER.EXE
// is available. PostMessage is resolved lazily on first use via
// GetModuleHandle("USER") + GetProcAddress. NULL until resolved.
// -----------------------------------------------------------------------
typedef BOOL (FAR PASCAL *PostMessageProcT)(HWND, UINT, WPARAM, LPARAM);
extern PostMessageProcT pfnPostMessage;
// ISR hit counter for diagnostics (incremented in isr4, wraps at 65535)
extern volatile uint16_t isrHitCount;

View file

@ -10,7 +10,6 @@
// Prototypes
// -----------------------------------------------------------------------
static void checkFlowRx(PortStateT *port);
static void checkNotify(PortStateT *port);
static void handleLsr(PortStateT *port, uint8_t lsr);
static void handleMsr(PortStateT *port);
static void handleRx(PortStateT *port, uint8_t lsr);
@ -82,62 +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: edge-triggered -- post once when rxCount reaches
// threshold, suppress until count drops below and re-crosses.
// Without edge detection, every ISR with rxCount >= 1 floods the
// message queue with stale WM_COMMNOTIFY, delaying keyboard input.
if (port->rxNotifyThresh >= 0) {
if (port->rxCount >= (uint16_t)port->rxNotifyThresh) {
if (!port->rxNotifySent) {
notifyBits |= CN_RECEIVE;
port->rxNotifySent = 1;
}
} else {
port->rxNotifySent = 0;
}
}
// CN_TRANSMIT: edge-triggered -- post once when free space reaches
// threshold, suppress until space drops below and re-crosses.
if (port->txNotifyThresh >= 0) {
uint16_t txFree = port->txSize - port->txCount;
if (txFree >= (uint16_t)port->txNotifyThresh) {
if (!port->txNotifySent) {
notifyBits |= CN_TRANSMIT;
port->txNotifySent = 1;
}
} else {
port->txNotifySent = 0;
}
}
// 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
// -----------------------------------------------------------------------
@ -453,8 +396,6 @@ void isrDispatch(PortStateT *port)
port->comDeb.qOutCount = port->txCount;
port->comDeb.qOutHead = port->txHead;
port->comDeb.qOutTail = port->txTail;
checkNotify(port);
}