Skip to content

PDW 4.0.4

Latest

Choose a tag to compare

@spiralshapeturtle spiralshapeturtle released this 23 Jul 14:37

PDW 4.0.4 - Release Notes

PDW 4.0.4 is a combined stability and hardening release: a full source-code audit (memory safety,
buffer handling, and rare corner-case defects across the decoders, the output feeds, the input
paths, and the screen/GUI code), the new high-resolution toolbar, a title-bar/corner display-glitch
fix, a window-position safety fix, and one small new option (a menu-bar toggle). Most fixes target
unusual conditions - corrupt or truncated over-the-air frames, feeds under stress, reconnect timing,
and misconfigured or corrupt pdw.ini / filters.ini files - so day-to-day behaviour is unchanged.
No decoder output or configuration format changes; existing pdw.ini and filters.ini files work
unchanged.

Health trend: 4- and 8-hour windows + worst-dip band (FIX [HealthSparkLong])

The Health panel's trend sparkline can now look back over a full night: the window menu adds
"4 hours" and "8 hours" alongside the existing 1/5/15/60-minute options (the trend history buffer
was grown from 1 hour to 8 hours to match). Over such a long window each pixel spans several minutes,
and a plain per-pixel average would smooth short reception drops away entirely - exactly the events
you want to catch when reviewing the past night. The sparkline now draws the average as the trend
line (as before) and adds a bold, colour-coded band that hangs down from the line to the worst
value in each pixel's slice, so a brief RX collapse shows up as a red spike stabbing downward even
while the averaged line stays high. The hover tooltip reports the window's lowest value and when it
happened, e.g. "lowest 40% at 03:15", so you can pinpoint an overnight problem without reading the
graph pixel by pixel. Purely a display/history change; no decoder, feed or configuration format is
affected, and short windows look the same as before (the band only appears where there is a real dip).

Antivirus false-positive: version-resource metadata (FIX [AvMetadata])

Some antivirus products (notably Windows Defender, as Trojan:Win32/Wacatac.C!ml) flagged the
unsigned binary on a heuristic/reputation basis - not on any actual behaviour. A source audit found
no networking, obfuscation, reconnaissance or persistence code behind the detection. Two things in
the executable's version resource were needlessly feeding the heuristic and have been corrected: the
CompanyName no longer contains a substring that reputation systems associate with piracy, and the
OriginalFilename now matches the shipped executable name (a mismatch there mimics self-renaming
malware). A LegalCopyright line and more descriptive product strings were added. No behaviour, no
decoder output and no configuration changed. The most effective remaining step is Authenticode code
signing of the released binaries (see the build workflow).

Telnet Penalty-system RX score over-strict on POCSAG (FIX [RxqSyncThreshold])

The Health panel's "Penalty system" score (and the telnet wire-format RXQ it shares) could degrade
faster on POCSAG than intended, even while messages decoded and displayed perfectly. PDW's POCSAG
sync/idle-word detector is deliberately more tolerant of noise than the reference algorithm this
score was ported from (a long-standing, unrelated PDW characteristic, unchanged here); a borderline
sync/idle match was still scored as a full error by the RX-quality tracker, something the reference
algorithm never does because it would have rejected that same match outright. The score now skips
that credit on a borderline match, exactly like the reference algorithm does, while decoding itself -
what you see on screen - is completely unaffected. Only the "Penalty system" health source is
affected; the classic RX-Q needle score never looked at this and needs no change. FLEX was audited
alongside this and found to already match its reference algorithm.

Unreadable POCSAG capcode now shown in red (FIX [PocsagCapcodeColor])

When a POCSAG address word can't be corrected, PDW already replaced the capcode with ???????
instead of showing a wrong-but-plausible number (FIX [PocsagCapcodeGuard]). That placeholder is now
colored red, the same color already used for corrupted characters in message text, making it
immediately clear at a glance that a capcode is unreliable rather than just unusual-looking. Purely
a screen color change - the underlying value, filter matching, logging and output feeds are all
unaffected.

Stray RX-Q "100%" under the combined-layout needle (FIX [HealthComboRxqLeak], [HealthFitSizeInvalidate])

With the Health panel's third layout - "Show RX needle alongside", which keeps the classic RX needle
in its old top-right slot next to the panel - the old green RX-Q "100%" box could flash under the
needle right after maximizing the window and then quickly disappear. The panel decides whether it
fits the available toolbar width and caches that verdict; HealthPanel_Active() returns the cached
value, and the cache was only refreshed when the panel itself repainted. Within one title-bar repaint
the classic pieces (the PANE1 header width and the RX-Q "100%"-box suppression) read that cache
before the panel's own paint refreshed it, so a "did not fit" -> "fits" transition on a resize
(e.g. maximizing from a narrower window where the combined layout did not fit) made them draw one
frame using the stale "did not fit" verdict - painting the classic RX-Q box under the needle until
the next full repaint erased it. The fit is now re-evaluated once up front on every title-bar repaint
(so all readers in that pass agree) and the cache is invalidated on any window-size change (so a
pre-resize verdict can never leak into the new layout). Only the combined "panel + needle" layout was
affected; the classic corner, the panel-only layout, decoding and the on-screen RX-quality all behave
exactly as before.

Default message font (FIX [DefaultFontConsolas])

The built-in default message-display font changed from Courier New (8pt) to Consolas 12pt. This
only affects a fresh install or a pdw.ini with no Font.* keys yet - anyone with an existing
pdw.ini keeps whatever font they already have configured. The shipped release pdw.ini
(release-template/pdw.ini) was updated to match (Font.FaceName=Consolas, Font.Height=-16).

Toolbar Health panel (FIX [HealthPanel]/[HealthSource]/[FeedStatus]/[FeedStatusConn]/[HealthPanelCorner]/[HealthSparkColor]/[HealthDotGreenStart]/[HealthPanelTips2]/[FeedDotLatch]/[FeedLastError]/[FeedTransitionLog]/[HealthSparkThreshold]/[HealthDotShapes]/[HealthNeedleCombo])

A new, compact status strip on the right side of the toolbar band. It is a true switchover with the
classic corner: while the panel is shown it replaces the needle meter and the RX-Q percentage box
(so nothing is displayed twice); hide the panel (right-click menu) and the classic needle + RX-Q corner
come back exactly as before. On windows too narrow for the panel the classic corner is shown as well.

  • Health score with colour coding. Shows the active RX-health score (0-100%) in green/orange/red.
    Two selectable sources: the classic RX-Q score (default, backward compatible) or the stricter
    "Penalty system" score ("instant drop, slow recovery") that PDW already computed internally for the
    telnet wire format - it works regardless of the telnet server being enabled.
    Right-click the panel to switch; the choice is saved to pdw.ini ([HealthPanel] Source).
  • The low-quality mail alert follows the selected source. The existing RX-quality e-mail alert
    now reads the same score the panel shows, so switching the source also switches what the alert
    reacts to. Thresholds, hysteresis and cooldown behave exactly as before; the alert mail now also
    names the active source.
  • Trend sparkline. A small graph of the health score over a configurable window (1/5/15/60
    minutes, right-click menu; [HealthPanel] SparkMinutes). The line is drawn in status colours
    (FIX [HealthSparkColor]): a healthy period is a solid green line, degradation shows as orange, and
    below the mail-alert threshold the line turns red.
  • COM link dot. When serial input is enabled: green = port open and receiving, orange = open but
    no data (stall/warmup), red = port not open.
  • Output-feed dots. One dot + short tag per enabled output feed (SMTP SM, webhook WH,
    Telegram TG, Pushover PO, MQTT MQ, MySQL MY, SQLite SQ, telnet server TS): green = no
    known problem (FIX [HealthDotGreenStart]: this includes "enabled but nothing delivered yet" - the
    per-message feeds can legitimately go days before their first push), orange = retrying, red = last
    delivery/connection failed. Disabled feeds are hidden so the strip stays compact. For the
    connection-oriented feeds the dot also tracks the connection itself (FIX [FeedStatusConn]): MQTT
    turns green as soon as the warm broker connection is established (and red while the broker is
    unreachable), and MySQL turns green on a successful (re)connect instead of waiting for the next
    INSERT - so the panel shows a sensible state right after startup.
  • Hover tooltips (FIX [HealthPanelTips2]). Every panel entry explains itself on mouse-over: the
    score shows its active source, the sparkline its window and alert level, the COM dot the link
    state, and each feed dot its full status (see the "last problem" item below). An earlier attempt
    ([HealthPanelTipsDisabled]) attached a second, independent tooltip control to the toolbar with its
    own message-subclassing hook - a fragile combination that was pulled after a crash report. The new
    implementation registers the hover regions with the toolbar's own tooltip control instead (the
    one that already serves the button tooltips), so no extra window and no second subclassing hook
    are involved.
  • Tooltips no longer clip off the right edge (FIX [HealthPanelTipPos]/[HealthPanelTipPos2]). The
    panel hugs the right window edge, so the rightmost feed tooltips are drawn to the left of the
    cursor. They were still running off the screen because the bubble was measured before the tooltip
    control had laid out the new text (so an old, too-small width was used). The tooltip is now shown
    first, then measured, then positioned, with a hard clamp to the monitor work area - so every tip
    stays fully on-screen regardless of how wide it is or how close to the edge the dot sits.
  • Steady feed dots - no more green/orange/red strobing (FIX [FeedDotLatch]). The feed dot now
    shows the last outcome, not the instantaneous state of the current attempt. Previously a
    continuously failing feed cycled on every message: a new send briefly flashed the dot green
    ("sending"), then orange per retry, then red again. Two rules stop that: an in-flight send never
    changes the dot (activity is not an outcome), and a retry never downgrades a red dot back to
    orange - once red, only a real successful delivery/connection turns the dot green again.
  • Feed dots remember what went wrong (FIX [FeedLastError]). Each feed records a short "last
    problem" description (e.g. API rejected message (HTTP 401), broker unreachable, queue full - message dropped) plus the time of the last status change. The tooltip of an orange/red dot shows
    it: Telegram: FAILED since 14:02 - API rejected message (HTTP 401). Cleared automatically on the
    next successful delivery.
  • Health log: status transitions on disk (FIX [FeedTransitionLog]). Every feed status change
    (and every COM link state change) is written to a new daily log file {date}_health.log via the
    central log manager, e.g. TG (Telegram): OK -> FAILED - API rejected message (HTTP 401). A dot
    that was red during the night is now diagnosable the next morning even though the panel itself is
    long green again. Steady states write nothing, so the file stays tiny.
  • Health log: no more routine "idle -> OK" noise (FIX [HealthLogNoIdleOk]). A feed's first
    successful contact after startup (or after MQTT's proactive idle-reconnect) is not a failure and
    used to log anyway, cluttering the file with harmless entries. That specific transition is now
    skipped; every transition that involves an actual problem - going into RETRYING/FAILED, and the
    later recovery back to OK - still logs exactly as before.
  • Alert-threshold marker in the sparkline (FIX [HealthSparkThreshold]). A thin dotted line marks
    the mail-alert level inside the trend graph, so you can see how much headroom the score has before
    the alert would fire, instead of only the colour flip.
  • Feed/COM dot size tuned (FIX [HealthDotSize]). Went 7px -> 10px (too small on hi-DPI/RDP) ->
    8px, which reads as the sweet spot between the two. The layout recalculates itself around the new
    size, and the retry ring / error "minus" bar scale with it automatically.
  • Re-tuned status colours (FIX [HealthStatusTriad], supersedes [HealthGreenLighter]). The green,
    orange and red now form one coherent triad tuned for a status strip that sits permanently on the
    light-gray toolbar band. The green is darker and more saturated than the earlier light green, which
    read too faint on the "100%" score text; the red is a strong, deep red so a fault reads instantly;
    orange stays clearly distinct from both. The message-text colour palette (used for on-screen paging
    text) is untouched - these are panel-local colours only.
  • Colour-blind-friendly dot shapes (FIX [HealthDotShapes]/[HealthDotNoBar]). Status is not
    colour-only: a retrying feed draws as a ring (hollow), while healthy and failed are both solid
    discs (green and red). The COM dot uses the same coding (ring = open but no data, solid red = not
    open). The earlier white "minus" bar on the failed dot was dropped: a red disc with a horizontal
    white bar reads as the universal "no-entry / disabled / off" glyph, so an errored feed looked
    switched-off rather than faulty. Colour + shape (ring vs disc) + the hover tooltip carry the state.
  • Smooth, round status dots (FIX [HealthDotAA]). The small dots were drawn with the plain GDI
    circle primitive, which does no antialiasing - at ~8px the outline came out angular ("cog"-like).
    Each dot is now rendered at 4x and smoothly downscaled (the same technique the signal meter and
    toolbar icons already use), so the dots read as clean, round discs at any DPI.
  • Dot size and alignment polish (FIX [HealthDotSize]/[HealthDotVAlign]). The dots are one pixel
    larger and nudged down slightly so they sit optically centred against their tag text instead of
    riding high.
  • The panel adapts to the free space (labels drop first, then the sparkline) and hides itself on very
    narrow windows. Right-click the panel area to hide it; right-click the same spot to show it again
    ([HealthPanel] Visible). Fully DPI-aware, drawn with the same GDI style as the rest of the toolbar.
  • Optional "panel + needle" layout (FIX [HealthNeedleCombo]). A third right-click option, Show RX
    needle alongside
    , brings the classic RX signal-strength needle back into its old far-right corner
    slot while keeping the Health panel. The panel shrinks just enough to make room, so the RX-health
    percentage stays where it is and the needle sits to its right. The old RX-Q percentage box and
    warning square stay hidden (the panel's own score replaces them). Saved to pdw.ini
    ([HealthPanel] ShowNeedle, default off); turning it on while the panel is hidden also shows the
    panel.
  • Dead-receiver detection for the Penalty-system source (FIX [HealthRxqStale]). The penalty score
    is only recomputed when frames are actually decoded, so on dead air it froze at its last (typically
    healthy) value: a receiver that died after a healthy period kept showing a green ~100% and the
    low-quality mail alert could never fire for this source. The Health panel and the alert now treat a
    penalty score that has not been recomputed for 2 minutes as dead air and read 0%. The telnet wire
    score itself is untouched (it keeps exact p2kflex parity and still freezes, like the reference
    implementation). Note: on networks with legitimate multi-minute transmission gaps this source dips
    to 0% between transmissions - the classic needle source is the better pick for such channels.
  • COM dot no longer green on a dead-but-reopenable link (FIX [Rs232LinkRealData]). The serial
    link dot was derived from the reconnect watchdog's own clocks, and every successful automatic
    reopen reset the "just opened" grace period - so a link that reopens fine but delivers nothing
    (e.g. a Moxa NPort whose TCP tunnel is down: the virtual port opens, reads return 0 bytes) showed
    a green "receiving" dot indefinitely. The dot is now judged by actually received data: the warmup
    grace applies only after a user-initiated open, and the dot turns orange as soon as no data has
    arrived for more than 5 seconds.
  • Toolbar corner can no longer go blank under GDI pressure (FIX [HealthPanelFitsFail]). If the
    panel's off-screen back-buffer could not be created (GDI-handle exhaustion, display churn) the
    panel drew nothing while still claiming the corner, leaving neither the panel nor the classic
    needle/RX-Q box visible until resources recovered. The claim is now withdrawn on failure so the
    classic corner renders again on the next repaint.
  • Overall-status accent bar (FIX [HealthRollupBar]). A full-height colour strip on the panel's
    left edge is the "one light to glance at": it shows the worst of everything - the RX health, the
    COM input link and every enabled feed. Green when the whole chain is healthy, orange/red the moment
    anything degrades or fails, so you no longer have to scan all the individual dots. Being part of the
    panel edge rather than an inline dot next to the number, a red bar reads as "the strip has a problem"
    and is never mistaken for the RX score itself being red. Its tooltip summarises the active
    faults/warnings.
  • "RX" caption on the score (FIX [HealthScoreLabel]). A small RX label precedes the percentage
    so the number is unmistakably the RX-health score and not confused with the RX-Q box it replaces.
  • Click an entry to open its settings (FIX [HealthClickConfig]). Left-click a feed dot to open
    that feed's configuration dialog, the COM dot to open Interface setup, or the score to open the
    System Alerts dialog. The tooltips mention the click target. Right-click still opens the panel's
    own menu (source / trend window / hide).
  • Soft area fill under the trend line (FIX [HealthSparkFill]). The sparkline now has a light tint
    under the curve in the current status colour, making the trend direction easier to read at a glance;
    the bold status line and the dotted alert-level marker stay crisp on top.

COM link-lost e-mail alert + "System Alerts" dialog (FIX [ComLinkAlert])

A new, optional e-mail alert for the serial input, complementing the RX-quality alert. The RX-quality
alert watches decode quality; on a total loss of serial data (a COM/USB adapter unplugged, or a Moxa
NPort whose network tunnel drops) the classic RX-needle score simply freezes at its last value and the
quality alert never fires. This alert instead watches the physical link state directly and e-mails when
the serial input is enabled but no data has been received for a configurable number of minutes
(default 3). It recovers automatically when data resumes, and re-mails at most once per cooldown period
(default 120 min) while the link stays down.

  • The old "RX Quality Alert" menu/dialog is now "System Alerts" and holds both alerts (RX quality
    and COM link-lost) in two clearly separated groups that share one recipient list. Enable the COM
    alert with the Enable COM link-lost alert checkbox and set its "no serial data for" and cooldown
    minutes - no pdw.ini editing required.
  • Opt-in, off by default. Reuses the RX-quality alert's recipient list and SMTP host - no separate
    mail configuration. Settings persist in pdw.ini under [ComLinkAlert] (Enabled, Minutes,
    Cooldown). Only active when serial (COM) input is enabled; ignored for sound-card input.
  • Note: a Moxa NPort keeps the virtual COM port open when its network side drops, so PDW sees "open
    but no data" (the COM dot goes orange, not red) - this alert treats both "open but no data" and "port
    not open" as the link being down.

Output-feed & logging robustness audit

A focused concurrency/lifetime scrub of the multi-threaded output-feed and logging machinery
(producer/consumer ring buffers, worker-thread lifecycles, shutdown/reconfigure paths). Findings and
fixes:

  • Telnet wire-log no longer drops lines (FIX [WireLogMultiLine]). When a single locked telnet
    operation emitted more than one wire-line (e.g. a <TX_START> immediately followed by the message
    line, or <TX_STOP>/<RS232>/<AUDIO> markers together in one watchdog tick), only the last line
    reached telnet_traffic.log. The per-thread staging buffer now accumulates the whole batch and
    flushes it intact. The live telnet stream to clients was never affected - this was disk-log fidelity
    only.
  • Webhook URL is no longer written to the log (FIX [WebhookUrlSecret]). Webhook URLs commonly embed
    an auth token in the path/query; the log now records only the outcome (SENT/ERROR + HTTP status),
    never the URL.
  • Webhook group JSON stays valid on very large group calls (FIX [WebhookSubRollback]). A group with
    enough subscribers to exceed the subscriber-list buffer could POST truncated (invalid) JSON. Each
    subscriber entry is now written all-or-nothing, so the array always closes cleanly.
  • Mail worker start is failure-safe (FIX [SmtpThreadStart]). If the mail worker thread or its event
    could not be created, the feed now reverts to a clean stopped state instead of leaking the event and
    queuing mail to a worker that never runs.
  • SMTP test-dialog response handle cleared on close (FIX [SmtpRespWnd]). Cancelling the SMTP setup
    dialog while a test mail was still sending could leave the worker writing to the closed dialog's list
    box; the handle is now cleared on close.
  • Additional hardening: sqlite maintenance now aborts promptly on shutdown so exit/reconfigure
    can't stall (FIX [SqliteMaintCancel]); mqtt/webhook event-handle teardown and signalling are fully
    serialized under the feed lock (FIX [MqttEventRace]/[WebhookEventRace]); mysql/smtp snapshot the
    socket before shutdown() to avoid a descriptor-reuse race (FIX [MysqlShutdownFd]/[SmtpShutdownFd]);
    the mqtt/smtp run-flags use std::atomic like the other feeds (FIX [MqttAtomicRun]/[SmtpAtomicRun]);
    and a debug-only credential trace was removed from the base64 encoder (FIX [SmtpCredTrace]).

Pre-release scrub (second pass)

A second adversarial pass over the same feed/logging layer, with every finding verified against the
code before fixing. None of these change day-to-day behaviour; they matter when an endpoint is down,
a queue is full, or PDW is closing.

  • SMTP: no more crash on exit while a mail was in flight (FIX [SmtpShutdownNullCfg]). Closing PDW
    cleared the SMTP configuration pointers before the mail worker had stopped; a worker mid-send could
    then read a NULL server/username/password and crash during shutdown. The worker is now stopped and
    joined first, with defensive NULL guards as backup.
  • Exit/reconfigure no longer freezes on a dead endpoint (FIX [FlushBounded]). The MQTT, webhook,
    Telegram and Pushover workers flush their remaining queue on shutdown, and each queued message got
    a full network attempt (10-20 s of timeouts) even when the endpoint was unreachable - a full queue
    could freeze the GUI for 10+ minutes on exit or a settings change. The flush now stops at the first
    transport failure and logs how many messages were dropped (LOST).
  • SQLite: database maintenance can no longer stall exit for minutes (FIX [SqliteMaintInterrupt]).
    The hourly age purge ran as one unbounded DELETE (minutes on a multi-GB backlog) with no cancel
    point, and vacuum walked the whole freelist in one call. Maintenance is now batched and
    interruptible; a stop/reconfigure aborts it almost immediately, with the database left consistent.
  • SQLite: a failed COMMIT can no longer silently wedge the feed (FIX [SqliteCommitCheck]). If a
    COMMIT failed with the transaction still open (e.g. a concurrent reader holding the database busy),
    the feed kept "inserting" into an orphan transaction that was rolled back at close - total silent
    data loss while reporting OK. The result is now checked, rolled back on failure, and the
    transaction state is resynced from the engine.
  • MySQL: the shutdown flush now actually delivers (FIX [MysqlStopDrain]). Stop/reconfigure shut
    the socket down before the worker's final flush ran, so up to 63 queued rows were discarded
    unlogged on every stop. The worker now gets a 3 s grace window to flush over the live connection;
    anything still lost is logged.
  • MySQL: stop no longer waits out a connect in progress (FIX [MysqlConnAbort]). A stop landing
    while the worker was mid-connect against a slow or dead server waited for the full
    DNS/connect/handshake timeouts (15-65 s of frozen GUI). The in-progress connect is now interrupted
    immediately, and the connect sequence checks the stop flag between phases.
  • SMTP: full-queue corner could corrupt the send queue (FIX [SmtpRingPublish]). The lock-free
    mail queue published its indices in two steps; with the ring nearly full (hung mail server), a
    thread switch between the two steps could make the whole backlog unsendable or resend stale slots.
    Indices are now computed locally and published in a single store.
  • SMTP: recipient captured per queued mail (FIX [SmtpToSnapshot]). The worker read the To-address
    from the live settings at send time; changing it in the settings dialog while a mail was in flight
    could tear the recipient string. The recipient is now captured when the mail is queued.
  • Telnet: reconnect replay of a completely full backlog delivered zero lines (FIX
    [TelnetReplayCount]).
    A client reconnecting after missing more than a full backlog window got an
    empty <BUFFER_START><BUFFER_STOP> instead of the newest lines - exactly when it had missed the
    most. The replay now counts entries instead of comparing ring positions.
  • Telnet: event-log disk writes moved off the server lock (FIX [TsEventLogStaged]). The same
    treatment the wire-log got earlier ([TsWireLogLockFree]): connect/disconnect/state event lines were
    still written to disk while the telnet lock was held, briefly stalling the decoder on a slow disk.
    They are now staged per-thread and written right after the lock is released.
  • Telnet: listen-socket close race closed (FIX [TelnetListenSockLock]). Disable/shutdown closed
    the listening socket while the worker could still pick up the (recyclable) handle value; the handle
    is now detached under the server lock on both sides.
  • Telegram: supergroup migration no longer rewrites settings from the worker thread (FIX
    [TgProfileGuiSync]).
    A group upgrading to a supergroup made the worker rewrite the stored chat-id
    list concurrently with the GUI - a torn chat list could be saved to pdw.ini. The update is now
    applied on the GUI thread.
  • Telegram: a rate limit on the plain-text fallback is retried (FIX [TgFallback429]). A message
    whose HTML was rejected and whose plain-text resend hit HTTP 429 was dropped even though retry
    attempts remained; it now re-enters the retry loop.
  • Log settings: applying them no longer discards buffered log lines (FIX [LogReconfigureFlush]).
    With write-buffering enabled, pressing OK in the log settings threw away any lines still in the
    buffer; they are now flushed to disk first. Event signalling and buffer publication in the log
    manager are also fully serialized (FIX [LogEventRace]).
  • Dropped messages are now always visible (FIX [QueueDropVisible], [ShutdownLost], [MysqlDropLog],
    [SqliteDrainLog]).
    A queue-full drop now always turns the feed status red (previously invisible
    with per-feed logging off) and its log line is written outside the feed lock; every shutdown-time
    drop path - all feeds, including SMTP's new "N queued messages unsent at shutdown" line - now
    leaves a LOST/DROP log line.
  • Small polish: the SMTP monitor-window handle is read once per response line (FIX
    [SmtpRespSnapshot]) and a few remaining em-dashes in runtime strings were replaced with plain ASCII
    (FIX [AsciiRuntime]).

FLEX/POCSAG decode-path audit

A full adversarial pass over the primary decode path (FLEX and POCSAG vector/address parsing, BCH
error correction, and the shared display buffers) against corrupt/truncated over-the-air frames. The
path was already memory-safe; the fixes below correct one display defect and harden three
never-yet-triggered corner cases.

  • FLEX long-address capcode no longer wraps (FIX [FlexLongAddrOverflow]). With FLEX Group Mode
    turned off
    , a long-address FLEX message whose capcode fell outside the 9-digit range could
    overflow a 32-bit intermediate and display a wrong (but plausible) capcode. The value is now
    computed in 64-bit and any capcode outside the valid 9-digit range is shown as ????????? instead
    of a wrong number. (With Group Mode on - the default - long addresses are skipped, so this path was
    never reached in a default configuration.)
  • FLEX RX-quality reading corrected (FIX [FlexBiterrorDoubleCount]). Each FLEX message word fed
    its bit-error count into the on-screen RX-Quality meter twice, making the FLEX reading slightly more
    pessimistic than POCSAG. Now counted once, matching POCSAG. The telnet RX-quality feed is a separate
    path and was never affected.
  • Defensive decoder bounds (FIX [PocsagNumBound], [FlexNumWordClamp], [EccPosGuard]). Three array
    writes/reads that were safe only by an implicit invariant now carry an explicit bound (POCSAG numeric
    buffer, FLEX numeric word span, BCH error-position table index), so a future change to the
    surrounding logic cannot silently turn them into an out-of-bounds access.

Show/hide the menu bar (FIX [MenuBarToggle])

New Display > Show Menu Bar checkbox hides the whole menu bar (File, Edit, Interface, ...) while
keeping the toolbar visible; the message panes reflow to use the freed space immediately, with no
leftover gap. Shortcut Ctrl+Shift+M toggles it either way (it keeps working even while the menu
bar itself is hidden, so it always gets you back). With the menu bar hidden, right-clicking anywhere
in the main window (including the toolbar) also offers a Show Menu Bar entry in the context
menu. The state is saved to pdw.ini as ShowMenuBar (default 1, i.e. visible) and restored on
the next start.

New high-resolution toolbar + themed dialogs (FIX [ToolbarHiResIcons])

The toolbar now uses 72x72 32-bit icons with a real alpha channel, box-filtered down to the current
DPI, and the application manifest opts in to Common Controls v6 - so every dialog also gets the modern
themed look instead of the classic Windows 2000 style.

Toolbar icons squared up to one grid (FIX [ToolbarIconGrid])

Follow-up polish on the icon set so every button carries the same optical weight. All 13 glyphs are
now drawn to a single 54 px content box centred on the canvas: the folder and the two copy-pane
icons were enlarged to fill their box, the statistics bars were widened and raised to sit on-centre,
the pause bars were thickened so the two-bar shape no longer looks smaller than its neighbours, and
the help "?" was given a slightly larger ring with clear whitespace around the glyph (the round shape
gets a 1 px optical bump so it does not read small next to the square icons). Corner radii were
unified. The spacing between buttons is a single fixed 8 px grid gutter instead of a per-button
margin. Purely cosmetic - no button, tooltip or behaviour changed.

Window never opens off-screen (FIX [WindowPosMinimized])

If PDW was closed while minimized, an older build could save an off-screen window position
(-32000,-32000) and start up invisible (only the tray icon reachable). The saved position is now
sanity-checked against the connected monitors, and a corrupt or zero window size falls back to the
default. PDW always opens on a visible part of the desktop.

Title bar re-syncs after a Remote Desktop / theme change (FIX [ToolbarResync])

Long-standing display glitch: a full-width black band under the toolbar plus a broken divider line
by the signal meter, appearing after the machine had been left running - often seen over a Remote
Desktop session (e.g. a Retina client). Root cause: when Windows changes the theme or display mode
(which a Remote Desktop connect/disconnect does), the toolbar quietly resizes to a different height,
but PDW only recomputed its toolbar height on a window resize. The header, divider, RX-quality box
and signal meter are all positioned from that height, so they ended up several pixels below where
the toolbar now ended - a black gap that only a manual resize cleared. PDW now detects the
toolbar-height change on its own and re-aligns the whole layout automatically within about a second,
so the glitch corrects itself without any manual resize.

Faster title-bar re-sync after Remote Desktop / theme change (FIX [ToolbarResyncProactive])

The [ToolbarResync] heal above ran on a once-per-second check, so the corner could stay visibly wrong
for up to a second after a Remote Desktop connect/disconnect. PDW now also reacts directly to the
system events that cause the resize (theme, display-mode, DPI, system-setting and colour changes):
a short, self-cancelling timer re-aligns the layout about 150 ms after the toolbar settles to its new
height, instead of waiting for the next one-second check. The once-per-second check stays as a
fallback. The result is that the meter, divider and header snap back into place almost immediately
when reconnecting or disconnecting a session.

Title-bar header self-heals (FIX [TitleBarSelfHeal])

As an extra safety net, the whole header row under the toolbar (Time / Date / Address / Monitored
Messages and the divider line) is now refreshed once per second instead of only its RX-quality corner,
so a momentarily blanked header repairs itself within a second.

Signal-meter stays aligned in the toolbar (FIX [SigindBandAlign])

The small signal-strength meter in the top-right corner is now vertically centered in the actual
toolbar band instead of being pinned to a fixed offset. Previously, once the toolbar settled to its
themed steady-state height (slightly shorter than at startup, also re-triggered by the repaint after a
brief COM-port drop), the meter could drift down past the divider into the title-bar band. It now
shares the same anchor as the divider and RX-Q box, so it stays put.

Divider line stays continuous under the signal meter (FIX [SigindDividerClip])

The horizontal divider line directly under the toolbar could break off (leave a gap) beneath the
signal meter. Once the toolbar settled to its shorter themed steady-state, the meter's black
background box reached down onto the divider row and painted over that segment; because the meter is
repainted on toolbar hover without redrawing the divider, the gap stuck until the next full repaint.
The meter box is now kept strictly above the divider row, and the meter repaint also restores the
divider segment beneath it, so the line stays unbroken. Correct at startup as before.

Divider line stays continuous left of the signal meter (FIX [RxqSquareBandClamp], [RxqSquareDividerClip])

Companion fix to [SigindDividerClip] above, for a similar break just LEFT of the signal meter. The
small RX-quality indicator square (light gray when reception is good, a warning icon when poor) sits
at a fixed vertical offset tuned for the taller toolbar band. Once the toolbar settled to its shorter
themed steady-state (e.g. after a Remote Desktop disconnect at high DPI), the square's bottom reached
a few pixels below the divider and painted over both the divider row and the top edge of the header
box underneath - a short gap in the line just left of the meter that every once-per-second repaint
re-created, so neither a resize nor the re-sync fixes above could heal it. The square (and the warning
icon) is now clamped to stay strictly above the divider, and the divider segment under its span is
re-asserted on every repaint. At the taller startup height the rendering is unchanged.

Redesigned signal meter (FIX [SigindFlatGauge], [SigindGaugeGray], [SigindGaugeBigger], [SigindWiggleVisibility])

The signal meter is now a clean flat gauge - a rounded frame with a half-circle scale and a red needle

  • drawn from a high-resolution source and scaled crisply to any DPI, in the same slate-gray tone and
    stroke weight as the redesigned toolbar icons. The gauge fills more of its box so it reads better next
    to the zoom text. The needle itself is drawn live over a cached back-buffer, so the scale can never be
    nibbled by the moving needle and there is no flicker. The at-rest needle jitter - the small wiggle you
    see when noise is present but no message is decoding - was made as visible as on the classic meter: the
    needle was lengthened to fill the enlarged scale and its low end restored to the original hand-tuned
    spacing, so it visibly snaps out of rest again. Signal-strength behaviour is unchanged; this is a visual
    refresh only.

Signal-meter/RX-Q bitmaps reload after a display change (FIX [DisplayBitmapReload])

Hygiene fix found while diagnosing the above: the signal-meter and RX-quality warning-icon bitmaps are
now recreated for the new display whenever Windows reports a display-driver change (e.g. an RDP
connect/disconnect), instead of only at startup.

Window close no longer runs unrelated re-sync code (FIX [WmCloseFallthrough])

Internal correctness fix, no user-visible behaviour change: closing the main window shared a code path
with the toolbar re-sync handlers above (a C switch falls through case labels regardless of which
one matched), so every ordinary close briefly ran that handler's code before exiting. Harmless in
practice (its one-shot timer was already cancelled on window teardown) but fragile, so window close now
returns directly instead of falling through.

Copy with a full scrollback could crash (FIX [VscrollClamp])

Completes [ClipboardBounds] below. With a full message buffer (normal after some uptime) and the
view scrolled up (e.g. Home key), every newly arriving line pushed the pane's scroll position one
step below zero. Using Copy Upper/Lower/Selection afterwards converted that negative position to a
huge unsigned offset and read far outside the message buffer - on the x64 build a likely crash.
The scroll position is now clamped where the buffer wraps, and the copy loop clamps defensively.

Double-click word selection read outside the buffer (FIX [DblClickBounds])

Double-clicking a word had no bounds anywhere on the path: a window wider than the internal
180-character line, a click in the empty band below the last text line, or a word starting at
column 0 of the very first buffer line made the word-boundary scans walk outside the pane buffer
(garbage selection at best, crash at worst). Click coordinates, the target line and both scan
directions are now bounded; the Google Maps position lookup gets the same guards.

Filter delete could corrupt the filter list (FIX [FilterDelReentry])

The filter-window delete loop processes pending window messages while it works. Pressing Delete/F8
again while a large multi-delete was still running re-entered the delete handler; when the nested
pass returned, the outer loop continued with a stale index and could erase past the end of the
filter list (memory corruption / crash). A delete request is now ignored while one is in progress.

Mouse wheel direction and multi-monitor fixes (FIX [WheelDelta], [WheelCoordSign])

Only a wheel delta of exactly +120 counted as "scroll up", so precision touchpads (small deltas)
and fast wheel movements (coalesced +/-240, +/-360) always scrolled DOWN regardless of direction;
the sign of the delta is now used. Additionally, wheel coordinates were read as unsigned, so on a
monitor positioned left of or above the primary the inside-window test always failed and the wheel
did nothing; coordinates are now read signed.

ACARS Colors dialog corrupted main screen colors (FIX [AcarsColorsInit])

Opening the ACARS Colors dialog as the FIRST color dialog since startup and clicking OK silently
saved the main Time/Date, Message and Bit-errors colors as black (the dialog wrote values it had
never initialized). The DBI and Labels fields also shared a single color slot, so editing either
overwrote the other on every OK. All values the dialog saves are now seeded when it opens, and DBI
has its own color slot (including in the preview and the Default reset).

Logfile dialog no longer wipes the filename (FIX [LogDlgNameWipe])

With a fixed (non-date) logfile name configured, the filename field was always empty when the
dialog opened, and toggling ANY checkbox in the dialog (enable, date, ISO, log-rejected, buffer)
erased a filename the user had just typed - OK then failed with "You haven't entered a file
name!". A leftover unconditional field update overwrote the correct value; removed.

POCSAG function-number list duplicated (FIX [FnuComboReset])

Browsing filters with Next/Previous re-initializes the filter-edit dialog, and every jump onto a
POCSAG filter appended another "All, 1, 2, 3, 4" set to the function-number dropdown. Picking a
duplicated entry then saved an invalid function number (e.g. -8) into the capcode, a filter that
never matched again. The list is now reset before it is filled.

Config-file validation (FIX [ScreenColClamp], [FilterTypeClamp], [ReadFiltersBounds])

Hardening against hand-edited or corrupt configuration files:

  • ScreenColN (column order) values outside 0..7 were written straight through the column-layout
    code as an out-of-bounds array WRITE at startup; the Columns dialog could also launder a bad
    value into -1 via an empty dropdown selection. Clamped at load and at save.
  • FilterDefaultType outside 0..5 (or an empty dropdown selection in Filter Options) drove an
    out-of-bounds index into the capcode-length table when adding a new filter. Clamped at load and
    at save.
  • A truncated filters.ini line made three field scanners walk past the end of the line buffer,
    and overlong last-hit date/time tokens overflowed their 10-byte fields into adjacent filter
    data. Scans now stop at end-of-string and the copies are bounded.

Filtered-pane scrollbar accuracy (FIX [PaneFilterScrollbarSync])

The vertical scrollbar of the Filtered pane is now refreshed on every appended line, so it always
reflects whether older messages have scrolled off the top. The busy Monitored pane already did this
implicitly via its constant auto-scroll; the low-traffic Filtered pane could, in edge cases (after a
buffer wrap, or while scrolled up), be left with a scrollbar that lagged the true content extent.

Group-call logging fix (FIX [GroupcallLogFilename])

During a FLEX group-call conversion the message/filter log could, for the 2nd and later members of a
group, write log lines using a stale internal filename - occasionally routing monitor-log lines into
the filter file. The log filename is now re-derived for every member. Screen and feed output were not
affected.

Decoder robustness on corrupt frames

  • POCSAG uncorrectable capcodes (FIX [PocsagCapcodeGuard]) - a RIC address word with an
    uncorrectable bit error is again shown as ??????? instead of a plausible-but-wrong capcode. The
    guard had been rendered ineffective by an earlier change.
  • POCSAG long-message safety (FIX [PocsagAlpBound]) - a long run of message codewords without an
    intervening address word can no longer overrun the internal alpha buffer.
  • MOBITEX (FIX [MobitexMpakOverflow], [MobitexStaleMpak], [MobitexResCompare], [MobitexBaseIdParse]) -
    fixed a buffer overflow on certain rare MPAK service messages with a full ESN, a stale MPAK label
    that could carry over to the next frame, an RX/TX-MAN comparison bug, and hardened the optional
    base-ids.txt parser against malformed lines.
  • ACARS (FIX [AcarsBitrateTerm]) - the bitrate field is now terminated, so a previous protocol's
    value can no longer stick to it after a mode switch.

Output-feed reliability

  • SMTP monitor-window freeze (FIX [SmtpAddRespDeadlock]) - disabling SMTP (or exiting) while the
    SMTP monitor/test window was open could deadlock the whole application. The status-line update is
    now non-blocking.
  • SMTP TLS + recipients (FIX [SmtpImplicitTlsFail], [SmtpEmptyRcpt]) - a failed implicit-TLS
    (port 465) handshake now fails cleanly instead of hanging on timeouts or silently falling back to
    plaintext; an empty recipient in a ;-separated list is skipped instead of dropping the whole mail.
  • Pushover (FIX [PushoverFirstConnectRetry]) - a transient network hiccup on the first connection
    attempt no longer silently drops a single message; it now retries like the other feeds.
  • SQLite (FIX [SqliteSizeCap], [SqliteStopDrain]) - the optional maximum-size cap no longer risks
    emptying the whole table on a database without incremental auto-vacuum (it now measures real data
    size); queued rows are flushed on shutdown/reconfigure instead of being discarded.
  • MySQL (FIX [MysqlErrPktWedge]) - an oversized server error reply no longer wedges the feed in an
    endless reconnect loop behind one row.
  • Telnet server (FIX [TelnetPartialSend], [TelnetReplayDup]) - a slow client can no longer receive
    a torn, half-sent line, and backlog replay after a reconnect no longer duplicates the lines that
    arrive during the replay.
  • Telnet server wire-log latency (FIX [TsWireLogLockFree]) - the optional wire-log disk write ran
    while the telnet server's internal state lock was held (a synchronous file write by default, since
    write-buffering is off unless enabled in the Logfile dialog), briefly stalling every other telnet
    operation - client accept/send, RS232/AUDIO state updates - for the duration of that write. The line
    is now only formatted under the lock; the actual disk write happens right after the lock is released.
  • MQTT (FIX [MqttSubRollback]) - an oversized group-subscriber list degrades to a dropped entry
    instead of malformed JSON.
  • Telegram (FIX [TgMigrateToken]) - a chat migration now matches the exact chat id rather than any
    substring, so it can no longer corrupt a different id in the list.
  • Central log (FIX [ReconfigureLock]) - applying new log settings while a feed is writing can no
    longer produce a garbled log filename.

Input-path hardening

  • COM port lock-out (FIX [ComPortReopenLeak]) - if the serial reader thread had to be force-stopped
    in the middle of a reconnect, an exclusively-opened COM handle could leak and lock PDW out of its own
    port until a restart. The in-flight handle is now always released.
  • Sound card (FIX [WaveInErrReset], [StopCapBufferLeak], [BuffersReadyReset]) - audio buffers are
    correctly reset/released on device errors and restarts, preventing a rare buffer-tracking overrun and
    stale-audio processing after a stop/start cycle.

Low-severity hardening sweep (second fix round)

The remaining (low-severity) findings from the display-layer audit, all fixed:

  • Resource leaks - the font dialog leaked one font handle per "OK" (FIX [FontDlgLeak]); every
    print job leaked the printer-settings handles, a failed StartDoc still "printed", and Cancel
    kept printing into the dead job without ever aborting it (FIX [PrinterJobGuards]); two clipboard
    paths freed memory owned by the clipboard, risking a later use-after-free (FIX [ClipOwnedFree]).
  • Selection & scrolling edge cases - dragging a selection above/left of a pane fed negative
    coordinates into the selection as huge values (FIX [PaneSelCoordSign]); dragging the scrollbar
    thumb in a scrollback larger than 65535 lines jumped to the wrong line (FIX [ThumbTrack32]);
    Copy Selection after a click without drag left an inverted cell on screen and copied a stale
    one-cell rect (FIX [CopySelNoDrag]).
  • Scrollback resize is OOM-safe - changing the scrollback size now allocates the new buffers
    BEFORE freeing the old ones; a failed allocation keeps the current buffer and size instead of
    falling back to unchecked mallocs while a message box pumped paints against inconsistent pane
    state (FIX [ScrollDlgOomSafe]).
  • Stale dialog state - the filter window now restyles its list correctly when the color option
    was changed from the main window, and the menu variant of Filter Options goes through the same
    path as the button (FIX [FilterColorsStale]); the filter-edit dialog re-arms its init guard on
    every open, so init-time control echoes can no longer corrupt multi-edit sep-file fields
    (FIX [FilterEditInitGuard]); two uninitialized locals fixed (sort focus with a single selected
    row, backup-extension text when no filters.ini exists - FIX [SortFilterFocusInit],
    [ResetHitExtInit]).
  • Wide-window and highlight corner cases - on a window wider than the internal 180-character
    line the final character of a message could escape the line-wrap bound; the wrap point is now
    clamped and the hard bound is a >= test (FIX [LastcharWrapEscape]); a text-filter match at the
    very first character of a message no longer disables the highlight for the whole message
    (FIX [HighlightPosZero]); right-to-left (Hebrew) mode now reverses the per-character colors
    together with the text (FIX [ReverseColorSync]).
  • Signal meter self-heal - a failed bitmap/DC (re)load during display changes no longer leaves
    the meter blank until restart or presents an uninitialized meter face; the next repaint retries
    (FIX [GaugeSrcdcFail], [SigindReloadRetry], [ExclamLoadGuard]).
  • Bounded string assembly - the title-bar text, the ".txt" append for the clipboard-save
    filename, and a path-helper length check are now bounded (FIX [WindowTextBound],
    [EditFileExtBound], [PathBufferOffByOne]).

Miscellaneous safety fixes

  • Bounds and termination hardening across clipboard copy, several dialogs (logfile, filter, scrollback
    options), the error dialog, filter-label substitution, and path building - all guarding against
    overly long paths, malformed filters.ini fields, or unusual selections.
  • Printer output now paginates instead of clipping everything past the first page.
  • Language table loading is read-only (works from a read-only install folder) and handles high-bit
    characters correctly.
  • The About-dialog logo bitmap is freed on close (was leaking one handle per open).